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; } = "";
}
}