Refactor Toon serializer: robust navigation/type metadata
- Introduce AcNavigationPropertyInfo for unified, cached relationship metadata per property (primary key, navigation type, FK, other key, inverse, target type) - Refactor relationship detection to use AcNavigationPropertyInfo, replacing ad-hoc logic and old RelationshipMetadata - Add AcSerializerCommon.GetCSharpTypeName for consistent, C#-style type name formatting (handles primitives, generics, nullables, enums, collections) - Use topological sort for @types output to ensure dependency order - Improve enum handling: avoid redundant constraints, use new type name formatter for underlying types - Output navigation, foreign-key, other-key, and inverse-property metadata consistently in meta section - Enhance convention-based detection for inverse properties and "other key", including unidirectional/polymorphic support - Add comprehensive test for navigation metadata completeness and demo test entities - Add "source-code-language: C#" to meta section - Misc: code cleanup, remove unused cache, improve property filtering
This commit is contained in:
parent
18b119c7a8
commit
0bb0b06af4
|
|
@ -0,0 +1,33 @@
|
|||
namespace FruitBankHybrid.Shared.Tests.TestData;
|
||||
|
||||
// Demo entity-k a teszteléshez
|
||||
public class TestOrder
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int CustomerId { get; set; }
|
||||
public TestCustomer? Customer { get; set; }
|
||||
public List<TestOrderItem> OrderItems { get; set; } = new();
|
||||
}
|
||||
|
||||
public class TestCustomer
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public List<TestOrder> Orders { get; set; } = new();
|
||||
}
|
||||
|
||||
public class TestOrderItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int OrderId { get; set; }
|
||||
public TestOrder? Order { get; set; }
|
||||
public int ProductId { get; set; }
|
||||
public TestProduct? Product { get; set; }
|
||||
}
|
||||
|
||||
public class TestProduct
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public List<TestOrderItem> OrderItems { get; set; } = new();
|
||||
}
|
||||
|
|
@ -39,6 +39,7 @@ public sealed class ToonTests
|
|||
[TestMethod]
|
||||
public void OrderDtoToToon()
|
||||
{
|
||||
|
||||
var toon = AcToonSerializer.SerializeTypeMetadata<OrderDto>();
|
||||
|
||||
Console.WriteLine(toon);
|
||||
|
|
@ -101,4 +102,208 @@ public sealed class ToonTests
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ToonTypes_NavigationMetadata_ShouldBeComplete()
|
||||
{
|
||||
var toon = AcToonSerializer.SerializeTypeMetadata<Shipping>();
|
||||
Console.WriteLine(toon);
|
||||
Console.WriteLine("\n=== NAVIGATION METADATA ELLENŐRZÉS ===\n");
|
||||
|
||||
var lines = toon.Split('\n').Select(x => x.TrimEnd()).ToList();
|
||||
|
||||
// Ismerten egyirányú kapcsolatok - ezeknél nincs inverse property a másik oldalon
|
||||
// Customer: NopCommerce domain entity, nincs benne Orders kollekció
|
||||
// OrderNotes: OrderNote osztályban nincs Order navigation property
|
||||
// ProductDto: nincs benne OrderItems kollekció
|
||||
// GenericAttributes: polimorf kapcsolat ExpressionPredicate-tel, nincs inverse ÉS nincs egyértelmű FK
|
||||
// FONTOS: Csak az inverse-property hiányát engedjük! Az other-key-nek léteznie kell!
|
||||
var knownUnidirectionalNavigations = new HashSet<string>
|
||||
{
|
||||
"Customer",
|
||||
"OrderNotes",
|
||||
"ProductDto",
|
||||
"GenericAttributes",
|
||||
"ShippingDocumentFile",
|
||||
"Pallet"
|
||||
};
|
||||
|
||||
// GenericAttributes speciális eset - polimorf, nincs other-key sem
|
||||
var knownPolymorphicNavigations = new HashSet<string>
|
||||
{
|
||||
"GenericAttributes"
|
||||
};
|
||||
|
||||
// FK property-k NEM tartalmazhatnak foreign-key attribútumot
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
var line = lines[i].Trim();
|
||||
if (line.EndsWith("Id: int") || line.EndsWith("Id: int?"))
|
||||
{
|
||||
int j = i + 1;
|
||||
while (j < lines.Count)
|
||||
{
|
||||
if (lines[j].StartsWith(" ") && !lines[j].StartsWith(" "))
|
||||
break;
|
||||
|
||||
if (lines[j].StartsWith(" "))
|
||||
{
|
||||
var metaLine = lines[j].Trim();
|
||||
if (metaLine.StartsWith("foreign-key:"))
|
||||
{
|
||||
Assert.Fail($"FK property nem tartalmazhat foreign-key attribútumot: {line} -> {metaLine}");
|
||||
}
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("✓ FK property-k nem tartalmaznak foreign-key attribútumot\n");
|
||||
|
||||
// Számoljuk meg a hiányzó navigation metadatokat
|
||||
var missingMetadata = new List<string>();
|
||||
var skippedUnidirectional = new List<string>();
|
||||
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
var line = lines[i].Trim();
|
||||
|
||||
// Navigation property-k keresése
|
||||
if (line.Contains(": ") &&
|
||||
!line.Contains(": int") && !line.Contains(": string") &&
|
||||
!line.Contains(": DateTime") && !line.Contains(": decimal") &&
|
||||
!line.Contains(": bool") && !line.Contains(": Guid") &&
|
||||
!line.Contains(": double") && !line.Contains(": float") &&
|
||||
!line.Contains("description:") && !line.Contains("purpose:") &&
|
||||
!line.Contains("navigation:") && !line.Contains("foreign-key:") &&
|
||||
!line.Contains("table-name:") && !line.Contains("constraints:") &&
|
||||
!line.Contains("inverse-property:") && !line.Contains("other-key:") &&
|
||||
!line.Contains("primary-key:") && !line.Contains("examples:") &&
|
||||
!line.Contains("@meta") && !line.Contains("@types") &&
|
||||
!line.Contains("version") && !line.Contains("format") && !line.Contains("source-code-language") &&
|
||||
!line.Contains("underlying-type:") && !line.Contains("default-value:") && !line.Contains("values:"))
|
||||
{
|
||||
var propName = line.Split(':')[0].Trim();
|
||||
if (string.IsNullOrEmpty(propName) || propName == "types") continue;
|
||||
|
||||
// Következő sorok metadatainak összegyűjtése
|
||||
var metadata = new HashSet<string>();
|
||||
int j = i + 1;
|
||||
while (j < lines.Count && lines[j].StartsWith(" ") && lines[j].Trim().Contains(':'))
|
||||
{
|
||||
var metaLine = lines[j].Trim();
|
||||
if (metaLine.StartsWith("navigation:")) metadata.Add("navigation");
|
||||
if (metaLine.StartsWith("foreign-key:")) metadata.Add("foreign-key");
|
||||
if (metaLine.StartsWith("inverse-property:")) metadata.Add("inverse-property");
|
||||
if (metaLine.StartsWith("other-key:")) metadata.Add("other-key");
|
||||
j++;
|
||||
}
|
||||
|
||||
// Ha van navigation attribútum, ellenőrizzük a szükséges metadatokat
|
||||
if (metadata.Contains("navigation"))
|
||||
{
|
||||
var navLine = lines.Skip(i + 1).FirstOrDefault(x => x.Trim().StartsWith("navigation:"));
|
||||
if (navLine != null)
|
||||
{
|
||||
var isUnidirectional = knownUnidirectionalNavigations.Contains(propName);
|
||||
var isPolymorphic = knownPolymorphicNavigations.Contains(propName);
|
||||
|
||||
if (navLine.Contains("many-to-one"))
|
||||
{
|
||||
if (!metadata.Contains("foreign-key"))
|
||||
missingMetadata.Add($"{propName} (ManyToOne): hiányzik foreign-key");
|
||||
|
||||
if (!metadata.Contains("inverse-property"))
|
||||
{
|
||||
if (isUnidirectional)
|
||||
skippedUnidirectional.Add($"{propName} (ManyToOne): egyirányú kapcsolat, nincs inverse");
|
||||
else
|
||||
missingMetadata.Add($"{propName} (ManyToOne): hiányzik inverse-property");
|
||||
}
|
||||
}
|
||||
else if (navLine.Contains("one-to-many"))
|
||||
{
|
||||
// other-key: polimorf kapcsolatoknál nem kötelező
|
||||
if (!metadata.Contains("other-key"))
|
||||
{
|
||||
if (isPolymorphic)
|
||||
{
|
||||
skippedUnidirectional.Add($"{propName} (OneToMany): polimorf kapcsolat, nincs other-key");
|
||||
}
|
||||
else
|
||||
{
|
||||
// DEBUG: részletes info
|
||||
Console.WriteLine($"\n[DEBUG] {propName} (OneToMany) - other-key hiányzik!");
|
||||
|
||||
// Keressük meg a property típusát a Toon outputban
|
||||
var propTypePart = line.Split(':').LastOrDefault()?.Trim() ?? "";
|
||||
Console.WriteLine($" Property type: {propTypePart}");
|
||||
|
||||
// Ha List<X> formátum, keressük meg X-et
|
||||
if (propTypePart.StartsWith("List<") && propTypePart.EndsWith(">"))
|
||||
{
|
||||
var elementTypeName = propTypePart.Substring(5, propTypePart.Length - 6);
|
||||
Console.WriteLine($" Element type: {elementTypeName}");
|
||||
|
||||
// Keressük meg az element type definícióját
|
||||
var elementTypeDefIndex = lines.FindIndex(l => l.Trim().StartsWith($"{elementTypeName}:"));
|
||||
if (elementTypeDefIndex >= 0)
|
||||
{
|
||||
Console.WriteLine($" Element type definition found at line {elementTypeDefIndex}");
|
||||
// Listázzuk ki az element type property-jeit amik "Id"-re végződnek
|
||||
for (int k = elementTypeDefIndex + 1; k < lines.Count; k++)
|
||||
{
|
||||
var propLine = lines[k];
|
||||
if (!propLine.StartsWith(" ")) break; // Új típus definíció
|
||||
if (propLine.StartsWith(" ")) continue; // Metaadat, skip
|
||||
|
||||
var trimmed = propLine.Trim();
|
||||
if (trimmed.EndsWith(": int") && trimmed.Contains("Id"))
|
||||
{
|
||||
Console.WriteLine($" FK candidate: {trimmed}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
missingMetadata.Add($"{propName} (OneToMany): hiányzik other-key");
|
||||
}
|
||||
}
|
||||
|
||||
if (!metadata.Contains("inverse-property"))
|
||||
{
|
||||
if (isUnidirectional)
|
||||
skippedUnidirectional.Add($"{propName} (OneToMany): egyirányú kapcsolat, nincs inverse");
|
||||
else
|
||||
missingMetadata.Add($"{propName} (OneToMany): hiányzik inverse-property");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (skippedUnidirectional.Count > 0)
|
||||
{
|
||||
Console.WriteLine("EGYIRÁNYÚ/POLIMORF KAPCSOLATOK (nem hiba):");
|
||||
foreach (var skipped in skippedUnidirectional)
|
||||
{
|
||||
Console.WriteLine($" ℹ {skipped}");
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
if (missingMetadata.Count > 0)
|
||||
{
|
||||
Console.WriteLine("HIÁNYZÓ METAADATOK:");
|
||||
foreach (var missing in missingMetadata)
|
||||
{
|
||||
Console.WriteLine($" - {missing}");
|
||||
}
|
||||
Assert.Fail($"Hiányzó navigation metaadatok: {missingMetadata.Count} db");
|
||||
}
|
||||
|
||||
Console.WriteLine("✓ Minden navigation property tartalmazza a szükséges metadatokat");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue