301 lines
13 KiB
C#
301 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace AyCode.Core.Serializers.Toons;
|
|
|
|
/// <summary>
|
|
/// Defines the type of relationship between entities.
|
|
/// </summary>
|
|
public enum ToonRelationType
|
|
{
|
|
/// <summary>
|
|
/// Many-to-one relationship (e.g., Person.Company -> Company).
|
|
/// </summary>
|
|
ManyToOne,
|
|
|
|
/// <summary>
|
|
/// One-to-many relationship (e.g., Company.Employees -> Person[]).
|
|
/// </summary>
|
|
OneToMany,
|
|
|
|
/// <summary>
|
|
/// One-to-one relationship.
|
|
/// </summary>
|
|
OneToOne,
|
|
|
|
/// <summary>
|
|
/// Many-to-many relationship.
|
|
/// </summary>
|
|
ManyToMany
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides custom description metadata for Toon serialization with flexible fallback and placeholder support.
|
|
/// This attribute can be applied to classes and properties to provide rich contextual information
|
|
/// that will be included in the @types section of serialized output.
|
|
///
|
|
/// <para><b>KEY FEATURES:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item><b>Partial Support:</b> You can specify only some properties (e.g., just Constraints), others will use fallbacks</item>
|
|
/// <item><b>Placeholder System:</b> Use [#AttributeName] to reference Microsoft DataAnnotations or smart inference</item>
|
|
/// <item><b>Fallback Chain:</b> ToonDescription → Microsoft Attributes → Smart Inference (automatic)</item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>FALLBACK PRIORITIES:</b></para>
|
|
/// <list type="number">
|
|
/// <item><b>Description:</b> ToonDescription.Description → [Description] → Smart Inference</item>
|
|
/// <item><b>Purpose:</b> ToonDescription.Purpose → Smart Inference</item>
|
|
/// <item><b>Constraints:</b> ToonDescription.Constraints → [Range]/[Required]/etc → Type Constraints → Smart Inference</item>
|
|
/// <item><b>Examples:</b> ToonDescription.Examples (no automatic fallback)</item>
|
|
/// </list>
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// <para><b>SUPPORTED PLACEHOLDERS:</b></para>
|
|
/// <list type="table">
|
|
/// <listheader>
|
|
/// <term>Placeholder</term>
|
|
/// <description>Resolves To (Where: C=Class, D=Description, P=Purpose, C=Constraints, E=Examples)</description>
|
|
/// </listheader>
|
|
/// <item><term>[#Description]</term><description>Microsoft [Description] attribute (Class.D, Property.D)</description></item>
|
|
/// <item><term>[#DisplayName]</term><description>Microsoft [DisplayName] attribute (Class.D, Property.D)</description></item>
|
|
/// <item><term>[#SmartDescription]</term><description>Auto-inferred description (Class.D, Property.D)</description></item>
|
|
/// <item><term>[#SmartPurpose]</term><description>Auto-inferred purpose (Property.P only, empty for classes)</description></item>
|
|
/// <item><term>[#Range]</term><description>Microsoft [Range] attribute → "range: min-max" (Property.C)</description></item>
|
|
/// <item><term>[#Required]</term><description>Microsoft [Required] attribute → "required" (Property.C)</description></item>
|
|
/// <item><term>[#MaxLength]</term><description>Microsoft [MaxLength] attribute → "max-length: N" (Property.C)</description></item>
|
|
/// <item><term>[#MinLength]</term><description>Microsoft [MinLength] attribute → "min-length: N" (Property.C)</description></item>
|
|
/// <item><term>[#StringLength]</term><description>Microsoft [StringLength] attribute → "length: min-max" (Property.C)</description></item>
|
|
/// <item><term>[#EmailAddress]</term><description>Microsoft [EmailAddress] attribute → "email-format" (Property.C)</description></item>
|
|
/// <item><term>[#Phone]</term><description>Microsoft [Phone] attribute → "phone-format" (Property.C)</description></item>
|
|
/// <item><term>[#Url]</term><description>Microsoft [Url] attribute → "url-format" (Property.C)</description></item>
|
|
/// <item><term>[#CreditCard]</term><description>Microsoft [CreditCard] attribute → "credit-card-format" (Property.C)</description></item>
|
|
/// <item><term>[#RegularExpression]</term><description>Microsoft [RegularExpression] → "pattern: ..." (Property.C)</description></item>
|
|
/// <item><term>[#SmartTypeConstraints]</term><description>Type-derived constraints (nullable, numeric, etc.) (Property.C)</description></item>
|
|
/// <item><term>[#SmartInferenceConstraints]</term><description>Auto-inferred constraints (email-format, range, etc.) (Property.C)</description></item>
|
|
/// <item><term>[#SmartGeneratedExample]</term><description>Auto-generated example value (Property.E)</description></item>
|
|
/// </list>
|
|
///
|
|
/// <para><b>USAGE MODES:</b></para>
|
|
///
|
|
/// <para><b>1. FULL CUSTOM (all properties specified):</b></para>
|
|
/// <code>
|
|
/// [ToonDescription("User email address",
|
|
/// Purpose = "Authentication and notifications",
|
|
/// Constraints = "required, email-format, unique",
|
|
/// Examples = "user@example.com, admin@company.com")]
|
|
/// public string Email { get; set; }
|
|
/// </code>
|
|
///
|
|
/// <para><b>2. PARTIAL (only some properties, rest auto-filled):</b></para>
|
|
/// <code>
|
|
/// [Description("Contact email")] // Microsoft attribute
|
|
/// [Required]
|
|
/// [EmailAddress]
|
|
/// [ToonDescription(Constraints = "[#Required], [#EmailAddress], unique")] // Only constraints specified
|
|
/// public string Email { get; set; }
|
|
/// // Result:
|
|
/// // Description: "Contact email" (from Microsoft [Description])
|
|
/// // Constraints: "required, email-format, unique" (merged with placeholders)
|
|
/// </code>
|
|
///
|
|
/// <para><b>3. PLACEHOLDER APPEND MODE (merge with Microsoft attributes):</b></para>
|
|
/// <code>
|
|
/// [Range(0, 150)]
|
|
/// [Required]
|
|
/// [ToonDescription(Constraints = "[#Required], [#Range], verified-by-admin")]
|
|
/// public int Age { get; set; }
|
|
/// // Result: "required, range: 0-150, verified-by-admin" (merged)
|
|
/// </code>
|
|
///
|
|
/// <para><b>4. REPLACE MODE (no placeholders = full override):</b></para>
|
|
/// <code>
|
|
/// [Range(0, 150)] // This is IGNORED
|
|
/// [ToonDescription(Constraints = "custom-validation-only")]
|
|
/// public int Score { get; set; }
|
|
/// // Result: "custom-validation-only" (Microsoft attributes ignored)
|
|
/// </code>
|
|
///
|
|
/// <para><b>5. FULL AUTOMATIC (no ToonDescription at all):</b></para>
|
|
/// <code>
|
|
/// [Required]
|
|
/// [EmailAddress]
|
|
/// [MaxLength(100)]
|
|
/// public string Email { get; set; }
|
|
/// // Result:
|
|
/// // Description: "Email address" (smart inference)
|
|
/// // Constraints: "required, email-format, max-length: 100" (from Microsoft attributes)
|
|
/// </code>
|
|
///
|
|
/// <para><b>6. COMBINING PLACEHOLDERS IN DESCRIPTION:</b></para>
|
|
/// <code>
|
|
/// [DisplayName("User Email")]
|
|
/// [Description("Contact email address")]
|
|
/// [ToonDescription(Description = "[#DisplayName]: [#Description] (primary contact)")]
|
|
/// public string Email { get; set; }
|
|
/// // Result: "User Email: Contact email address (primary contact)"
|
|
/// </code>
|
|
///
|
|
/// <para><b>7. CLASS-LEVEL WITH PLACEHOLDERS AND FALLBACK:</b></para>
|
|
/// <code>
|
|
/// // With Microsoft attribute
|
|
/// [Description("Base user entity")]
|
|
/// [ToonDescription("[#Description] with extended functionality",
|
|
/// Purpose = "Manages user authentication and authorization")]
|
|
/// public class User { }
|
|
/// // Result:
|
|
/// // Description: "Base user entity with extended functionality"
|
|
/// // Purpose: "Manages user authentication and authorization"
|
|
///
|
|
/// // With inheritance (Inherited = true)
|
|
/// [ToonDescription("Admin user account",
|
|
/// Purpose = "Administrative users with elevated privileges")]
|
|
/// public class AdminUser : User { }
|
|
/// // Result: "Admin user account" (own description)
|
|
///
|
|
/// public class SuperUser : AdminUser { }
|
|
/// // Result: "Admin user account" (inherited from AdminUser)
|
|
///
|
|
/// // Without any ToonDescription
|
|
/// public class GuestUser : User { }
|
|
/// // Result: "Object of type GuestUser" (smart inference)
|
|
/// </code>
|
|
///
|
|
/// <para><b>BEST PRACTICES:</b></para>
|
|
/// <list type="bullet">
|
|
/// <item>Use placeholders ([#...]) when you want to MERGE with existing Microsoft attributes</item>
|
|
/// <item>Omit placeholders when you want to REPLACE (full custom control)</item>
|
|
/// <item>Leave properties empty to use automatic fallbacks</item>
|
|
/// <item>Combine placeholders with custom text for rich, DRY documentation</item>
|
|
/// </list>
|
|
/// </remarks>
|
|
///
|
|
/// <example>
|
|
/// <para><b>COMPLETE EXAMPLE:</b></para>
|
|
/// <code>
|
|
/// [ToonDescription("Represents a user account in the system",
|
|
/// Purpose = "User authentication and profile management")]
|
|
/// public class Person
|
|
/// {
|
|
/// // Full custom
|
|
/// [ToonDescription("Unique identifier",
|
|
/// Purpose = "Primary key / database identity",
|
|
/// Constraints = "required, auto-increment, positive")]
|
|
/// public int Id { get; set; }
|
|
///
|
|
/// // Merge with Microsoft attributes
|
|
/// [Range(18, 120)]
|
|
/// [Required]
|
|
/// [ToonDescription(Constraints = "[#Required], [#Range], verified")]
|
|
/// public int Age { get; set; }
|
|
/// // Result: "required, range: 18-120, verified"
|
|
///
|
|
/// // Partial - only constraints, rest auto-filled
|
|
/// [Description("Contact email")]
|
|
/// [EmailAddress]
|
|
/// [ToonDescription(Constraints = "[#EmailAddress], unique")]
|
|
/// public string Email { get; set; }
|
|
/// // Description: "Contact email" (from Microsoft)
|
|
/// // Constraints: "email-format, unique" (merged)
|
|
///
|
|
/// // Fully automatic - no ToonDescription
|
|
/// [Required]
|
|
/// [MaxLength(100)]
|
|
/// public string Name { get; set; }
|
|
/// // Description: "Name of the Person" (smart inference)
|
|
/// // Constraints: "required, max-length: 100" (from Microsoft attributes)
|
|
///
|
|
/// // Placeholder in description
|
|
/// [DisplayName("Account Balance")]
|
|
/// [ToonDescription(Description = "[#DisplayName] in USD",
|
|
/// Constraints = "range: 0-999999, decimal(18,2)",
|
|
/// Examples = "0.00, 1234.56, 999999.99")]
|
|
/// public decimal Balance { get; set; }
|
|
/// // Description: "Account Balance in USD"
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
|
public sealed class ToonDescriptionAttribute : Attribute
|
|
{
|
|
/// <summary>
|
|
/// Gets the human-readable description of the property or type.
|
|
/// This appears in the @types section to help LLMs understand the data structure.
|
|
/// </summary>
|
|
public string Description { get; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the purpose of this property (what it's used for).
|
|
/// Examples: "Primary key", "User authentication", "Audit trail".
|
|
/// </summary>
|
|
public string? Purpose { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the constraints or validation rules for this property.
|
|
/// Examples: "required, email-format", "range: 0-150", "max-length: 100".
|
|
/// </summary>
|
|
public string? Constraints { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets example values for this property.
|
|
/// Helps LLMs understand the expected format and content.
|
|
/// </summary>
|
|
public string? Examples { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether this property is a primary key.
|
|
/// If not explicitly set, convention-based detection will be used (e.g., property named "Id").
|
|
/// </summary>
|
|
public bool? IsPrimaryKey { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the foreign key navigation property name.
|
|
/// If not explicitly set, convention-based detection will be used (e.g., "CompanyId" -> "Company").
|
|
/// Example: For a property "CompanyId", set ForeignKey = "Company" to indicate it references the Company navigation property.
|
|
/// </summary>
|
|
public string? ForeignKey { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the relationship type for navigation properties.
|
|
/// If not explicitly set, convention-based detection will be used based on property type.
|
|
/// </summary>
|
|
public ToonRelationType? Navigation { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the inverse navigation property name for bidirectional relationships.
|
|
/// Example: For Company.Employees, set InverseProperty = "Company" to indicate the inverse property on Person.
|
|
/// </summary>
|
|
public string? InverseProperty { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the database table name for this entity (class-level only).
|
|
/// If not explicitly set, will fallback to EF Core [Table] or Linq2Db [Table] attributes, then to class name.
|
|
/// Example: TableName = "tbl_Persons" for custom table naming.
|
|
/// </summary>
|
|
public string? TableName { get; set; }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the ToonDescriptionAttribute with the specified description.
|
|
/// </summary>
|
|
/// <param name="description">Human-readable description of the property or type.</param>
|
|
public ToonDescriptionAttribute(string description)
|
|
{
|
|
Description = description ?? throw new ArgumentNullException(nameof(description));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a string representation of this attribute for debugging.
|
|
/// </summary>
|
|
public override string ToString()
|
|
{
|
|
var parts = new List<string> { $"Description: {Description}" };
|
|
if (!string.IsNullOrEmpty(Purpose))
|
|
parts.Add($"Purpose: {Purpose}");
|
|
if (!string.IsNullOrEmpty(Constraints))
|
|
parts.Add($"Constraints: {Constraints}");
|
|
if (!string.IsNullOrEmpty(Examples))
|
|
parts.Add($"Examples: {Examples}");
|
|
return string.Join(", ", parts);
|
|
}
|
|
}
|