mandolin 0.4.7

Input openapi.json/yaml, output server source code in rust.
Documentation
# discriminator を持つ親スキーマのインライン enum 生成

## 現状と問題点

lambda360 の `ShapeNode` は OpenAPI の `discriminator` を持つ多態型だが、
現在のテンプレートはこれを平凡な struct として生成している。

```rust
// 現在の出力(誤)
pub struct ShapeNode {
    pub r#op: String,
}
```

期待する出力は、`op` を内部タグとした Rust の enum(TypeScript はユニオン型)。

## 目標とする出力

### Rust

```rust
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "op")]
pub enum ShapeNode {
    #[serde(rename = "step")]
    Step {
        path: String,
        content_hash: Option<String>,
    },
    #[serde(rename = "union")]
    Union {
        a: Box<ShapeNode>,
        b: Box<ShapeNode>,
    },
    #[serde(rename = "rotate")]
    Rotate {
        shape: Box<ShapeNode>,
        axis: Vec<NumberOrExpr>,
        deg: NumberOrExpr,
    },
    // ...
}
```

- `#[serde(tag = "op")]` で JSON の `op` フィールドをタグとする
- 各バリアントのフィールドはタグフィールド (`op`) を**除いた**子スキーマのプロパティ
- 自己参照フィールド (`$ref` → ShapeNode) は `Box<ShapeNode>` に包む

### TypeScript

```typescript
type ShapeNode =
    | StepNode
    | UnionShapeNode
    | IntersectNode
    | SubtractNode
    | ScaleNode
    | TranslateNode
    | RotateNode
    | StretchNode;
```

TypeScript は再帰型を直接扱えるので Box 不要。

## 実装方針

### 1. IDENTIFIED_SCHEMA マクロに discriminator ブランチを追加

`schema.type == "object" and schema.discriminator` の場合は struct ではなく enum を生成する。

```jinja
{%- if schema.type == "object" and schema.discriminator %}
{# 内部タグ付き enum #}
#[derive(Clone,Debug,serde::Serialize,serde::Deserialize)]
#[serde(tag="{{schema.discriminator.propertyName}}")]
pub enum {{SCHEMA_NAME(pointer)}}{
{%- for disc_key, disc_ref in schema.discriminator.mapping|items %}
  {%- set child = disc_ref|include_pointer %}
    #[serde(rename="{{disc_key}}")]
    {{disc_key|to_pascal_case}}{
  {%- for prop_key, prop in child.properties|default({})|items %}
    {%- if prop_key != schema.discriminator.propertyName %}  {# タグフィールドをスキップ #}
        pub r#{{prop_key}}: ...,
    {%- endif %}
  {%- endfor %}
    },
{%- endfor %}
}
```

### 2. タグフィールドのスキップ

`discriminator.propertyName` (= `"op"`) と一致するプロパティを各バリアントのフィールド列挙からスキップする。
こうすることで `#[serde(tag = "op")]` との衝突(シリアライズ時の重複キー)を避けられる。

### 3. 再帰フィールドの Box 包み

SCHEMA マクロにて、`$ref` が指す先のスキーマに `discriminator` がある場合は `Box<T>` を出力する。

```jinja
{%- if schema["$ref"] %}
{%- if (schema["$ref"]|include_pointer).discriminator %}Box<{{schema["$ref"]|ref_name}}>
{%- else %}{{schema["$ref"]|ref_name}}
{%- endif %}
```

### 4. 子スキーマ (StepNode 等) の扱い

`StepNode`, `RotateNode` 等の子スキーマは引き続き独立した struct として生成する。
lambda360 の文脈ではこれらは enum バリアント内に展開されるため未使用になるが、
削除ロジックは不要(将来の独立利用に備え残す)。

### 5. TypeScript の IDENTIFIED_SCHEMA

discriminator があれば mapping の値を `ref_name` でユニオンとして並べる。

```jinja
{%- if schema.type == "object" and schema.discriminator %}
type {{SCHEMA_NAME(pointer)}} =
{%- for disc_key, disc_ref in schema.discriminator.mapping|items %}
    | {{disc_ref|ref_name}}
{%- endfor %};
```

## 懸念事項

### child schema の必須フィールド判定

子スキーマのプロパティをインライン展開するとき、`required` 配列も子スキーマのものを参照する必要がある。
現在の `IDENTIFIED_SCHEMA` は `schema.required` を使っているため、
discriminator バリアント生成時は `child.required` を参照するよう分岐が必要。

### allOf による継承フィールドは無視

`StepNode` 等は `allOf: [ShapeNode]` を持つが、これは「ShapeNode の一種」という宣言であり
テンプレート側では追加フィールドは不要(ShapeNode enum がタグで吸収するため)。
`allOf` で追加されるフィールドは現状通り無視する。

### Default 実装

`Default` が必要なケース(req.body の Default など)に備え、
mapping の最初のバリアントを `Default` で返す実装を追加する。

```rust
impl Default for ShapeNode {
    fn default() -> Self {
        Self::Step { path: Default::default(), content_hash: Default::default() }
    }
}
```

ただしバリアントのフィールドが確定してから生成する必要がある。

## 影響範囲

- `templates/rust_axum.template`: SCHEMA マクロ(Box 包み)、IDENTIFIED_SCHEMA マクロ(enum 生成)
- `templates/typescript_hono.template`: IDENTIFIED_SCHEMA マクロ(union 型)
- `src/` の変更は不要