# Toon — `ToonDescriptionAttribute` + Placeholder System

Manual metadata override for types and properties. Definition: `Serializers/Toons/ToonDescriptionAttribute.cs`.

Use when smart inference (`TOON_INFERENCE.md`) is insufficient — domain-specific constraints, business rules, or navigation metadata.

## Attribute signature

```csharp
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class,
                AllowMultiple = false, Inherited = true)]
public sealed class ToonDescriptionAttribute : Attribute
```

## Text properties

| Property | Emitted in `@types` as | Fallback if empty |
|---|---|---|
| `Description` | `description: "..."` | MS `[Description]` → smart inference (property) / smart inference (class) |
| `Purpose` | `purpose: "..."` | Smart inference (property only; classes get empty) |
| `Constraints` | `constraints: "..."` | MS DataAnnotations → type-derived → smart inference |
| `Examples` | `examples: "..."` | empty (no automatic fallback) |
| `BusinessRule` | `business-logic: "..."` | empty |

## Structural properties — class level

| Property | Emitted as | Notes |
|---|---|---|
| `TableName` | `table-name: "..."` | Fallback: EF `[Table]` / Linq2Db `[Table]` → class name. |
| `TypeRelation` | `related-type: "{relation} {types}"` | Use the `ToonTypeRelation` string constants (below). |
| `RelatedTypes` | (combined with `TypeRelation`) | `Type[]` — related type names joined by `, `. |

## Structural properties — property level (navigation)

| Property | Emitted as | Notes |
|---|---|---|
| `IsPrimaryKey` | `primary-key: true` | Convention: a property named `Id` is auto-detected without this. |
| `ForeignKey` | `foreign-key: "..."` | Convention: `CompanyId` ↔ `Company` pair auto-detected. |
| `Navigation` | `navigation: "many-to-one"` | `ToonRelationType` enum; emitted kebab-case. |
| `InverseProperty` | `inverse-property: "..."` | For bidirectional relationships. |

## Constructors

```csharp
[ToonDescription]                                // Named-args only
[ToonDescription("human-readable description")]  // Positional Description
```

## `ToonTypeRelation` string constants

Use with the `TypeRelation` property. Values serialize literally:

| Constant | String value | Example |
|---|---|---|
| `None` | `""` | — |
| `BaseOf` | `"base-of"` | `Order` is base-of `OrderDto` |
| `Entity` | `"entity"` | — |
| `DerivedFrom` | `"derived-from"` | `OrderDto` is derived-from `Order` |
| `DtoOf` | `"dto-of"` | `OrderDto` is dto-of `Order` |
| `ModelOf` | `"model-of"` | `OrderModel` is model-of `Order` |
| `ViewModelOf` | `"view-model-of"` | `OrderViewModel` is view-model-of `Order` |
| `ProjectionOf` | `"projection-of"` | `OrderSummary` is projection-of `Order` |

## `ToonRelationType` enum

For the `Navigation` property:

```csharp
public enum ToonRelationType { ManyToOne, OneToMany, OneToOne, ManyToMany }
```

Emitted kebab-case: `many-to-one`, `one-to-many`, `one-to-one`, `many-to-many`.

## Placeholder system

Placeholders of the form `[#Name]` inside `Description` / `Purpose` / `Constraints` / `Examples` resolve at serialization time against Microsoft DataAnnotations or smart inference. Implementation: `Serializers/Toons/AcToonSerializer.Placeholders.cs`.

### Available placeholders

**For `Description`, `Purpose`:**

| Placeholder | Resolves to |
|---|---|
| `[#Description]` | MS `[System.ComponentModel.Description]` value. |
| `[#DisplayName]` | MS `[DisplayName]` value. |
| `[#SmartDescription]` | Auto-inferred description (see `TOON_INFERENCE.md`). |
| `[#SmartPurpose]` | Auto-inferred purpose (properties only; empty for classes). |

**For `Constraints`:**

| Placeholder | Resolves to |
|---|---|
| `[#Required]` | `required` (when `[Required]` is present). |
| `[#Range]` | `range: min-max` (from `[Range(min, max)]`). |
| `[#MaxLength]` | `max-length: N`. |
| `[#MinLength]` | `min-length: N`. |
| `[#StringLength]` | `length: min-max` (from `[StringLength]`). |
| `[#EmailAddress]` | `email-format`. |
| `[#Phone]` | `phone-format`. |
| `[#Url]` | `url-format`. |
| `[#CreditCard]` | `credit-card-format`. |
| `[#RegularExpression]` | `pattern: ...`. |
| `[#SmartTypeConstraints]` | Type-derived constraints (nullable, numeric). |
| `[#SmartInferenceConstraints]` | Name-based inferred constraints. |

**For `Examples`:**

| Placeholder | Resolves to |
|---|---|
| `[#SmartGeneratedExample]` | Auto-generated example value. |

### Merge vs replace modes

The presence of a placeholder in `Constraints` flips the merge behavior:

- **Replace mode** — `Constraints` contains **no** `[#...]` → MS attributes ignored, smart inference ignored, the literal string is used as-is.
- **Merge mode** — `Constraints` contains **any** `[#...]` → placeholder resolved, then merged with type-derived + smart-inferred constraints.

```csharp
[Range(0, 150)]
[ToonDescription(Constraints = "custom-validation-only")]  // REPLACE — Range ignored
public int Score { get; set; }

[Range(0, 150)]
[ToonDescription(Constraints = "[#Range], verified")]      // MERGE — "range: 0-150, verified"
public int Age { get; set; }
```

`Description`, `Purpose`, `Examples` use straight substitution (no merge mode).

## Usage modes

### Full custom

```csharp
[ToonDescription("User email address",
                 Purpose = "Authentication and notifications",
                 Constraints = "required, email-format, unique",
                 Examples = "user@example.com")]
public string Email { get; set; }
```

### Merge with DataAnnotations

```csharp
[Description("Contact email")]
[Required]
[EmailAddress]
[ToonDescription(Constraints = "[#Required], [#EmailAddress], unique")]
public string Email { get; set; }
// description: "Contact email"
// constraints: "required, email-format, unique"
```

### Partial (some properties only, rest auto)

```csharp
[Description("Contact email")]
[EmailAddress]
[ToonDescription(Constraints = "[#EmailAddress], unique")]
public string Email { get; set; }
// description: "Contact email" (from [Description])
// purpose: (inferred, empty)
// constraints: "required, email-format, unique"
// examples: (empty)
```

### Full automatic (no `[ToonDescription]`)

Relies on MS DataAnnotations + smart inference. See `TOON_INFERENCE.md`.

### Class-level with inheritance

```csharp
[ToonDescription("Base user entity")]
public class User { }

[ToonDescription("Admin user account")]   // own description
public class AdminUser : User { }

public class SuperUser : AdminUser { }    // inherits AdminUser's description (Inherited = true)
```

## When to use which approach

| Scenario | Approach |
|---|---|
| Generic DTO with conventional property names | Nothing — smart inference covers it. |
| Existing MS DataAnnotations | Smart inference — auto-pulls from annotations. |
| Domain-specific constraint not covered by conventions | `[ToonDescription(Constraints = "...")]` replace mode. |
| Domain rule plus standard validation | `[ToonDescription(Constraints = "[#Required], [#Range], domain-rule")]` merge mode. |
| Navigation property relationship | `[ToonDescription(Navigation = ..., InverseProperty = ..., ForeignKey = ...)]`. |
| Entity metadata (tables, DTO-of mappings) | Class-level `[ToonDescription(TableName = ..., TypeRelation = ..., RelatedTypes = ...)]`. |
| Cross-property business logic | `[ToonDescription(BusinessRule = "this >= NetWeight")]` — descriptive only, not executed. |
