using System.Collections.Generic; using System.Text.Json.Serialization; namespace D365MetadataBridge.Models { // ======================== // Table models // ======================== public class TableInfoModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("label")] public string? Label { get; set; } [JsonPropertyName("developerDocumentation")] public string? DeveloperDocumentation { get; set; } [JsonPropertyName("tableGroup")] public string? TableGroup { get; set; } [JsonPropertyName("tabletype")] public string? TableType { get; set; } [JsonPropertyName("cacheLookup")] public string? CacheLookup { get; set; } [JsonPropertyName("clusteredIndex")] public string? ClusteredIndex { get; set; } [JsonPropertyName("primaryIndex")] public string? PrimaryIndex { get; set; } [JsonPropertyName("saveDataPerCompany")] public string? SaveDataPerCompany { get; set; } /// "Read=Allow, Create=Deny" — the set permissions; null when none are. [JsonPropertyName("subscriberAccessLevel")] public string? SubscriberAccessLevel { get; set; } [JsonPropertyName("allowRowVersionChangeTracking")] public string? AllowRowVersionChangeTracking { get; set; } [JsonPropertyName("extends")] public string? Extends { get; set; } [JsonPropertyName("supportInheritance")] public string? SupportInheritance { get; set; } [JsonPropertyName("instanceRelationType")] public string? InstanceRelationType { get; set; } [JsonPropertyName("model")] public string? Model { get; set; } [JsonPropertyName("fields")] public List Fields { get; set; } = new List(); [JsonPropertyName("fieldGroups")] public List FieldGroups { get; set; } = new List(); [JsonPropertyName("indexes")] public List Indexes { get; set; } = new List(); [JsonPropertyName("relations")] public List Relations { get; set; } = new List(); [JsonPropertyName("methods")] public List Methods { get; set; } = new List(); } public class FieldInfoModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("fieldType")] public string FieldType { get; set; } = ""; [JsonPropertyName("extendedDataType")] public string? ExtendedDataType { get; set; } [JsonPropertyName("enumType")] public string? EnumType { get; set; } [JsonPropertyName("label")] public string? Label { get; set; } [JsonPropertyName("helpText")] public string? HelpText { get; set; } [JsonPropertyName("mandatory")] public bool Mandatory { get; set; } [JsonPropertyName("allowEdit")] public string? AllowEdit { get; set; } [JsonPropertyName("stringSize")] public int? StringSize { get; set; } } public class FieldGroupModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("label")] public string? Label { get; set; } [JsonPropertyName("fields")] public List Fields { get; set; } = new List(); } public class IndexInfoModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("allowDuplicates")] public bool AllowDuplicates { get; set; } [JsonPropertyName("alternateKey")] public bool AlternateKey { get; set; } [JsonPropertyName("fields")] public List Fields { get; set; } = new List(); } public class IndexFieldModel { [JsonPropertyName("dataField")] public string DataField { get; set; } = ""; [JsonPropertyName("includedColumn")] public bool IncludedColumn { get; set; } } public class RelationInfoModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("relatedTable")] public string RelatedTable { get; set; } = ""; [JsonPropertyName("cardinality")] public string? Cardinality { get; set; } [JsonPropertyName("relatedTableCardinality")] public string? RelatedTableCardinality { get; set; } [JsonPropertyName("constraints")] public List Constraints { get; set; } = new List(); } public class RelationConstraintModel { [JsonPropertyName("field")] public string? Field { get; set; } [JsonPropertyName("relatedField")] public string? RelatedField { get; set; } [JsonPropertyName("value")] public string? Value { get; set; } } // ======================== // Class models // ======================== public class ClassInfoModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("extends")] public string? Extends { get; set; } [JsonPropertyName("isAbstract")] public bool IsAbstract { get; set; } [JsonPropertyName("isFinal")] public bool IsFinal { get; set; } [JsonPropertyName("isStatic")] public bool IsStatic { get; set; } [JsonPropertyName("model")] public string? Model { get; set; } [JsonPropertyName("declaration")] public string? Declaration { get; set; } [JsonPropertyName("methods")] public List Methods { get; set; } = new List(); } public class MethodInfoModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("source")] public string? Source { get; set; } [JsonPropertyName("isStatic")] public bool IsStatic { get; set; } } // ======================== // Enum models // ======================== public class EnumInfoModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("label")] public string? Label { get; set; } [JsonPropertyName("helpText")] public string? HelpText { get; set; } [JsonPropertyName("isExtensible")] public bool IsExtensible { get; set; } [JsonPropertyName("model")] public string? Model { get; set; } [JsonPropertyName("values")] public List Values { get; set; } = new List(); } public class EnumValueModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("value")] public int Value { get; set; } [JsonPropertyName("label")] public string? Label { get; set; } } // ======================== // EDT models // ======================== public class EdtInfoModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("baseType")] public string BaseType { get; set; } = ""; [JsonPropertyName("extends")] public string? Extends { get; set; } /// /// Root of the Extends chain. Reported for orientation in a multi-level hierarchy; it is /// not necessarily the element that declares the StringSize -- see StringSizeInheritedFrom /// for that. /// /// Null for a root EDT, and also null when the chain could not be walked to its end -- a /// dangling Extends or a cycle -- because the last ancestor reached is not the root and /// naming it as one would be a wrong answer rather than a missing one. /// [JsonPropertyName("rootEdt")] public string? RootEdt { get; set; } [JsonPropertyName("label")] public string? Label { get; set; } [JsonPropertyName("helpText")] public string? HelpText { get; set; } [JsonPropertyName("stringSize")] public int? StringSize { get; set; } /// /// Ancestor the reported StringSize came from. Null when the number is what this EDT's /// own XML declares. Set both when the EDT declares nothing (the common case) and when /// an ancestor's value overrode one the EDT did declare. /// [JsonPropertyName("stringSizeInheritedFrom")] public string? StringSizeInheritedFrom { get; set; } [JsonPropertyName("referenceTable")] public string? ReferenceTable { get; set; } [JsonPropertyName("enumType")] public string? EnumType { get; set; } [JsonPropertyName("model")] public string? Model { get; set; } // --- New gap-fill properties --- [JsonPropertyName("formHelp")] public string? FormHelp { get; set; } [JsonPropertyName("configurationKey")] public string? ConfigurationKey { get; set; } [JsonPropertyName("alignment")] public string? Alignment { get; set; } [JsonPropertyName("displayLength")] public int? DisplayLength { get; set; } [JsonPropertyName("relationType")] public string? RelationType { get; set; } /// True when the EDT is marked IsExtensible = Yes — required for AxEdtExtension to apply. [JsonPropertyName("isExtensible")] public bool IsExtensible { get; set; } // AxEdtReal specific [JsonPropertyName("noOfDecimals")] public int? NoOfDecimals { get; set; } [JsonPropertyName("decimalSeparator")] public string? DecimalSeparator { get; set; } [JsonPropertyName("signDisplay")] public string? SignDisplay { get; set; } } // ======================== // Form models // ======================== public class FormInfoModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("formPattern")] public string? FormPattern { get; set; } [JsonPropertyName("model")] public string? Model { get; set; } [JsonPropertyName("dataSources")] public List DataSources { get; set; } = new List(); [JsonPropertyName("controls")] public List Controls { get; set; } = new List(); [JsonPropertyName("methods")] public List Methods { get; set; } = new List(); } public class FormDataSourceModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("table")] public string Table { get; set; } = ""; [JsonPropertyName("joinSource")] public string? JoinSource { get; set; } [JsonPropertyName("linkType")] public string? LinkType { get; set; } // --- New gap-fill properties --- [JsonPropertyName("allowEdit")] public string? AllowEdit { get; set; } [JsonPropertyName("allowCreate")] public string? AllowCreate { get; set; } [JsonPropertyName("allowDelete")] public string? AllowDelete { get; set; } } public class FormControlModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("controlType")] public string ControlType { get; set; } = ""; [JsonPropertyName("dataSource")] public string? DataSource { get; set; } [JsonPropertyName("dataField")] public string? DataField { get; set; } [JsonPropertyName("children")] public List? Children { get; set; } // --- New gap-fill properties --- [JsonPropertyName("caption")] public string? Caption { get; set; } [JsonPropertyName("label")] public string? Label { get; set; } [JsonPropertyName("helpText")] public string? HelpText { get; set; } [JsonPropertyName("visible")] public string? Visible { get; set; } [JsonPropertyName("enabled")] public string? Enabled { get; set; } [JsonPropertyName("dataMethod")] public string? DataMethod { get; set; } [JsonPropertyName("autoDeclaration")] public string? AutoDeclaration { get; set; } } // ======================== // Query / View / DataEntity models // ======================== public class QueryInfoModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("model")] public string? Model { get; set; } [JsonPropertyName("description")] public string? Description { get; set; } [JsonPropertyName("dataSources")] public List DataSources { get; set; } = new List(); } public class QueryDataSourceModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("table")] public string Table { get; set; } = ""; [JsonPropertyName("joinMode")] public string? JoinMode { get; set; } [JsonPropertyName("fetchMode")] public string? FetchMode { get; set; } [JsonPropertyName("childDataSources")] public List? ChildDataSources { get; set; } [JsonPropertyName("ranges")] public List? Ranges { get; set; } [JsonPropertyName("fields")] public List? Fields { get; set; } } public class QueryRangeModel { [JsonPropertyName("field")] public string Field { get; set; } = ""; [JsonPropertyName("value")] public string? Value { get; set; } [JsonPropertyName("status")] public string? Status { get; set; } } public class ViewInfoModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("label")] public string? Label { get; set; } [JsonPropertyName("model")] public string? Model { get; set; } [JsonPropertyName("query")] public string? Query { get; set; } [JsonPropertyName("isPublic")] public bool IsPublic { get; set; } [JsonPropertyName("isReadOnly")] public bool IsReadOnly { get; set; } [JsonPropertyName("primaryKey")] public string? PrimaryKey { get; set; } [JsonPropertyName("fields")] public List Fields { get; set; } = new List(); [JsonPropertyName("relations")] public List Relations { get; set; } = new List(); [JsonPropertyName("methods")] public List Methods { get; set; } = new List(); [JsonPropertyName("dataSources")] public List DataSources { get; set; } = new List(); } public class ViewFieldModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("fieldType")] public string FieldType { get; set; } = ""; [JsonPropertyName("dataSource")] public string? DataSource { get; set; } [JsonPropertyName("dataField")] public string? DataField { get; set; } [JsonPropertyName("dataMethod")] public string? DataMethod { get; set; } [JsonPropertyName("label")] public string? Label { get; set; } [JsonPropertyName("isComputed")] public bool IsComputed { get; set; } } public class DataEntityInfoModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("label")] public string? Label { get; set; } [JsonPropertyName("publicEntityName")] public string? PublicEntityName { get; set; } [JsonPropertyName("publicCollectionName")] public string? PublicCollectionName { get; set; } [JsonPropertyName("isPublic")] public bool IsPublic { get; set; } [JsonPropertyName("model")] public string? Model { get; set; } [JsonPropertyName("dataSources")] public List DataSources { get; set; } = new List(); [JsonPropertyName("fields")] public List Fields { get; set; } = new List(); // --- New gap-fill properties --- [JsonPropertyName("isReadOnly")] public bool IsReadOnly { get; set; } [JsonPropertyName("entityCategory")] public string? EntityCategory { get; set; } [JsonPropertyName("dataManagementEnabled")] public bool DataManagementEnabled { get; set; } [JsonPropertyName("stagingTable")] public string? StagingTable { get; set; } [JsonPropertyName("keys")] public List Keys { get; set; } = new List(); [JsonPropertyName("fieldMappings")] public List FieldMappings { get; set; } = new List(); [JsonPropertyName("computedColumns")] public List ComputedColumns { get; set; } = new List(); } public class DataEntityFieldMappingModel { [JsonPropertyName("fieldName")] public string FieldName { get; set; } = ""; [JsonPropertyName("dataSource")] public string? DataSource { get; set; } [JsonPropertyName("dataField")] public string? DataField { get; set; } } public class ReportInfoModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("model")] public string? Model { get; set; } [JsonPropertyName("dataSets")] public List DataSets { get; set; } = new List(); [JsonPropertyName("designs")] public List Designs { get; set; } = new List(); } public class ReportDataSetModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("dataSourceType")] public string? DataSourceType { get; set; } [JsonPropertyName("query")] public string? Query { get; set; } [JsonPropertyName("fields")] public List Fields { get; set; } = new List(); } public class ReportDataSetFieldModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("dataField")] public string? DataField { get; set; } [JsonPropertyName("dataType")] public string? DataType { get; set; } } public class ReportDesignModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("caption")] public string? Caption { get; set; } [JsonPropertyName("style")] public string? Style { get; set; } [JsonPropertyName("hasRdl")] public bool HasRdl { get; set; } } // ======================== // Cross-reference models // ======================== public class ReferenceInfoModel { [JsonPropertyName("sourcePath")] public string SourcePath { get; set; } = ""; [JsonPropertyName("sourceModule")] public string? SourceModule { get; set; } [JsonPropertyName("kind")] public string? Kind { get; set; } [JsonPropertyName("line")] public int Line { get; set; } [JsonPropertyName("column")] public int Column { get; set; } /// Categorized reference type: call, extends, implements, field-access, type-reference [JsonPropertyName("referenceType")] public string? ReferenceType { get; set; } /// Source class name parsed from SourcePath (e.g. "SalesFormLetter" from "/Classes/SalesFormLetter/Methods/run") [JsonPropertyName("callerClass")] public string? CallerClass { get; set; } /// Source method name parsed from SourcePath (e.g. "run" from "/Classes/SalesFormLetter/Methods/run") [JsonPropertyName("callerMethod")] public string? CallerMethod { get; set; } } /// /// Enriched extension class result with method-level CoC detail. /// public class ExtensionClassDetailModel { [JsonPropertyName("className")] public string ClassName { get; set; } = ""; [JsonPropertyName("module")] public string? Module { get; set; } /// /// The AOT path this class actually extends, read from its [ExtensionOf] declaration — /// e.g. "/Tables/SalesTable", "/Forms/SalesTable/DataSources/SalesLine", /// "/Forms/VendOpenTrans/DataSources/TaxWithholdTrans/DataFields/TaxReimbursement_IT". /// /// Without this the caller cannot tell a table CoC from a form CoC when both objects /// share a name: asking about "SalesTable" legitimately matches 14 extensions of the /// TABLE and 34 of the FORM and the elements below it, which must never be pooled into /// one count. /// [JsonPropertyName("extendedElement")] public string ExtendedElement { get; set; } = ""; /// Methods that the extension class wraps via CoC (next calls) [JsonPropertyName("wrappedMethods")] public List WrappedMethods { get; set; } = new List(); } /// /// Enriched event subscriber result with handler type categorization. /// public class EventSubscriberDetailModel { [JsonPropertyName("className")] public string ClassName { get; set; } = ""; [JsonPropertyName("module")] public string? Module { get; set; } [JsonPropertyName("methodName")] public string? MethodName { get; set; } /// Event name (e.g. "onInserted", "onValidatedWrite") [JsonPropertyName("eventName")] public string? EventName { get; set; } /// Handler type: "dataEvent", "delegate", "pre", "post" [JsonPropertyName("handlerType")] public string? HandlerType { get; set; } } /// /// API usage caller information from cross-reference database. /// public class ApiUsageCallerModel { [JsonPropertyName("callerClass")] public string CallerClass { get; set; } = ""; [JsonPropertyName("callerMethod")] public string? CallerMethod { get; set; } [JsonPropertyName("module")] public string? Module { get; set; } [JsonPropertyName("kind")] public string? Kind { get; set; } [JsonPropertyName("line")] public int Line { get; set; } } // ======================== // Search models // ======================== public class SearchResultModel { [JsonPropertyName("results")] public List Results { get; set; } = new List(); [JsonPropertyName("totalCount")] public int TotalCount { get; set; } } public class SearchItemModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("type")] public string Type { get; set; } = ""; [JsonPropertyName("model")] public string? Model { get; set; } } public class MethodSourceModel { [JsonPropertyName("className")] public string ClassName { get; set; } = ""; [JsonPropertyName("methodName")] public string MethodName { get; set; } = ""; [JsonPropertyName("source")] public string? Source { get; set; } [JsonPropertyName("found")] public bool Found { get; set; } } // ======================== // Batch operation models // ======================== public class BatchOperationRequest { [JsonPropertyName("operation")] public string Operation { get; set; } = ""; [JsonPropertyName("params")] public Dictionary? Params { get; set; } /// /// Deserializes a typed parameter from the Params dictionary. /// Values arrive as JsonElement from System.Text.Json — convert to the target type. /// /// The old `catch { return null; }` made a WRONGLY SHAPED parameter /// indistinguishable from an absent one, and the dispatcher's /// `?? throw new ArgumentException("Missing: fields")` then told the caller the key /// was missing while it sat right there in the request. That is the param-shape /// contract bug this project keeps re-living — e.g. constraints sent as /// [{fieldName, relatedFieldName}] when this side reads [{field, relatedField}], or /// index fields sent as [{fieldName}] when this side reads a flat string[]. A /// failure now names the parameter and shows what actually arrived. /// /// JsonOptions.Default is used so the batch path resolves property names exactly the /// way the single-op BridgeRequest.GetParam<T> does; deserializing without it was /// a second, quieter way for the two paths to disagree. /// public T? GetTypedParam(string key) where T : class { if (Params == null || !Params.TryGetValue(key, out var raw) || raw == null) return null; // Already the correct type (in-process caller, not a JSON round trip). if (raw is T typed) return typed; var json = raw is System.Text.Json.JsonElement je ? je.GetRawText() : System.Text.Json.JsonSerializer.Serialize(raw, Protocol.JsonOptions.Default); try { // A JSON `null` deserializes to null here — same as an absent key, which is // what the caller's "Missing: " message correctly describes. return System.Text.Json.JsonSerializer.Deserialize(json, Protocol.JsonOptions.Default); } catch (System.Text.Json.JsonException ex) { throw new System.ArgumentException( $"Parameter '{key}' has the wrong shape: {ex.Message} Received: {Preview(json)}"); } } /// Caps the echoed payload — a batch op can carry a large array. private static string Preview(string json) => json.Length <= 300 ? json : json.Substring(0, 300) + "…(truncated)"; } public class BatchOperationResult { [JsonPropertyName("objectType")] public string ObjectType { get; set; } = ""; [JsonPropertyName("objectName")] public string ObjectName { get; set; } = ""; [JsonPropertyName("totalOperations")] public int TotalOperations { get; set; } [JsonPropertyName("successCount")] public int SuccessCount { get; set; } [JsonPropertyName("failureCount")] public int FailureCount { get; set; } [JsonPropertyName("operations")] public List Operations { get; set; } = new List(); } public class BatchOperationItemResult { [JsonPropertyName("operation")] public string Operation { get; set; } = ""; [JsonPropertyName("success")] public bool Success { get; set; } [JsonPropertyName("error")] public string? Error { get; set; } [JsonPropertyName("elapsedMs")] public long ElapsedMs { get; set; } } // ======================== // Capabilities model // ======================== public class CapabilitiesModel { [JsonPropertyName("objectTypes")] public Dictionary> ObjectTypes { get; set; } = new Dictionary>(); [JsonPropertyName("version")] public string Version { get; set; } = "1.0.0"; } // ======================== // Form pattern discovery models // ======================== public class FormPatternModel { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("version")] public string? Version { get; set; } [JsonPropertyName("description")] public string? Description { get; set; } } public class FormPatternDiscoveryResult { [JsonPropertyName("patterns")] public List Patterns { get; set; } = new List(); [JsonPropertyName("count")] public int Count { get; set; } [JsonPropertyName("source")] public string Source { get; set; } = ""; } }