mandolin 0.4.7

Input openapi.json/yaml, output server source code in rust.
Documentation
# 2段階生成による `resolve.rs` 完全削除

## GCC との対比

GCC は `.c` を `.o` にコンパイルしてから `.o` 同士をリンクする。
各 `.o` は他のシンボルの**中身を知らなくても**名前だけで参照できる。

mandolin も同じ構造が取れる:

| GCC | mandolin |
|-----|---------|
| Phase 1: `.c``.o` (シンボル定義) | Phase 1: `components/schemas` → Rust 型定義 |
| Phase 2: リンク (シンボル参照を解決) | Phase 2: `paths` → オペレーション (型名を参照) |
| `extern void printf(...)` | `$ref: #/components/schemas/Point` |
| リンカが `printf` の実体を見つける | テンプレートが `"Point"` という名前を使う |

現在の `resolve.rs` は「`printf` の実装コードを呼び出し元に全部コピーペーストする」
ことに相当しており、そもそも不要な処理だった。

---

## 現在のフロー vs 提案

```
【現在】
openapi.json
  → resolve.rs ($refを全部インライン展開)
  → テンプレート (インライン済みJSONを走査)
  → struct PathsFooGetSchema, struct PathsBarPostSchema ...  ← 同内容が増殖

【提案】
openapi.json
  → lib.rs (serde_json::to_value そのまま、$ref展開なし)
  → テンプレート Phase 1: components/schemas を走査 → struct Point, struct Line ...
  → テンプレート Phase 2: paths を走査 → 型名として "Point" を参照するだけ
  → struct Point が1個、参照箇所は x: Point
```

---

## 変更 1: `resolve.rs` を削除

`resolve.rs` の役割は 2 つだった:

1. `$ref` のインライン展開 → **Phase 1 で不要になる**
2. デフォルトサーバの補完 → **`lib.rs` に移動できる(数行)**

`lib.rs` の変更:

```rust
pub fn environment(mut spec: OpenAPI) -> Result<minijinja::Environment<'static>, minijinja::Error> {
    // デフォルトサーバ補完 (resolve.rs から移動)
    if spec.servers.is_empty() {
        spec.servers.push(openapiv3::Server {
            url: "/api".to_string(),
            ..Default::default()
        });
    }

    // $ref展開なし、そのままシリアライズ
    let value = serde_json::to_value(&spec).unwrap();

    let mut env = minijinja::Environment::new();
    // ... フィルタ・キャッシュ登録 (変更なし)
    env.add_global("spec", minijinja::Value::from_serialize(&value));
    Ok(env)
}
```

`resolve` モジュールの `use`、`mod resolve`、`ResolvedOpenAPI` がすべて消える。

---

## 変更 2: テンプレートを 2 段階に分割

### Phase 1: スキーマのコンパイル

`spec.components.schemas` を明示的に走査し、すべての名前付き型を先に出力する。

```jinja
{# ===== Phase 1: スキーマ定義 ===== #}
{%- if spec.components and spec.components.schemas %}
{%- for schema_name, schema in spec.components.schemas|items %}
{{IDENTIFIED_SCHEMA("#/components/schemas/"+schema_name, schema)}}
{%- endfor %}
{%- endif %}
{{ IDENTIFIED_SCHEMA_DRAIN() }}  {# Phase 1 で発見されたインラインスキーマ #}

{# ===== Phase 2: オペレーション定義 ===== #}
{%- for path_key, path_item in spec.paths|items %}
{%- for method in methods %}
...
{%- endfor %}
{%- endfor %}
{{ IDENTIFIED_SCHEMA_DRAIN() }}  {# Phase 2 で発見されたインラインスキーマ #}
```

Rust は前方宣言不要なので出力順序は問わない。
ただし Phase 1 を先に出すことで可読性が上がる。

### `SCHEMA` マクロの簡略化

`$ref` を受け取ったら中身を見ずに型名だけ取り出す。
`include_pointer` も `IDENTIFIED_SCHEMA` も呼ばない。

```jinja
{%- macro SCHEMA(pointer, schema) -%}
{%- if schema["$ref"] %}{{schema["$ref"]|ref_name}}
{%- elif schema.type == "array" %}Vec<{{SCHEMA(pointer+"/items", schema.items)}}>
{%- elif schema.type == "object" %}
  {%- if schema.additionalProperties %}HashMap<String,{{SCHEMA(pointer+"/additionalProperties",schema.additionalProperties)}}>
  {%- else %}{{SCHEMA_NAME(pointer)}}
  {%- endif %}
{%- elif schema.type == "integer" %}i32
{%- elif schema.type == "string" %}String
{%- elif schema.type == "number" %}f64
{%- elif schema.type == "boolean" %}bool
{%- else %}u8
{%- endif %}
{%- endmacro -%}
```

`schema["$ref"]` が `"#/components/schemas/Point"` なら `ref_name` フィルタが `"Point"` を返す。
Phase 1 でその struct はすでに生成済みなので追加処理不要。

### `ref_name` フィルタ(Rust 側で追加)

```rust
// filter.rs に追加(数行)
pub fn ref_name(ref_path: &str) -> String {
    ref_path
        .rsplit('/')
        .next()
        .unwrap_or(ref_path)
        .to_pascal_case()
}
```

`"#/components/schemas/my_point"` → `"MyPoint"`

---

## 変更 3: パラメータ等の `$ref` 対応

`openapiv3` の `operation.parameters` は `Vec<ReferenceOr<Parameter>>` であり、
`$ref` で書かれたパラメータは `{"$ref":"#/components/parameters/PetId"}` のまま届く。
テンプレートが `parameter.name` を参照すると未定義になる。

解決策: `deref` フィルタを Rust 側に追加する。

```rust
// lib.rs 内、include_pointer と同様にクロージャでキャプチャ
let spec_for_deref = value.clone();
env.add_filter(
    "deref",
    move |v: minijinja::Value| -> Result<minijinja::Value, minijinja::Error> {
        if let Ok(ref_path) = v.get_attr("$ref") {
            if let Some(s) = ref_path.as_str() {
                let path = s.strip_prefix("#/").unwrap_or(s);
                let mut cur = &spec_for_deref;
                for seg in path.split('/') {
                    let decoded = seg.replace("~1", "/").replace("~0", "~");
                    cur = cur.get(&decoded).ok_or_else(|| {
                        minijinja::Error::new(minijinja::ErrorKind::InvalidOperation,
                            format!("deref: not found: {s}"))
                    })?;
                }
                return Ok(minijinja::Value::from_serialize(cur));
            }
        }
        Ok(v)
    },
);
```

テンプレートでは:

```jinja
{%- for param_or_ref in operation.parameters %}
{%- set parameter = param_or_ref|deref %}
    pub r#{{parameter.name}}: ...
{%- endfor %}
```

`$ref` でも直接記述でも同じ書き方で処理できる。
これは旧アーキテクチャの `include_ref` フィルタと同等だが、
`include_pointer` と統合して 1 フィルタで済む。

---

## `SCHEMA_NAME` / `schema_push` の扱い

Phase 1 で生成した名前付きスキーマは `$ref` 経由では `ref_name` フィルタで処理される。
`SCHEMA_NAME` → `schema_push` → `IDENTIFIED_SCHEMA` の経路は
**インライン匿名スキーマ専用**になる(例: パスに直接書かれた object)。

キャッシュの役割が明確化される:

| 対象 | 生成タイミング | 使うしくみ |
|------|--------------|-----------|
| `components/schemas/*` | Phase 1 の明示ループ | 直接 `IDENTIFIED_SCHEMA` を呼ぶ |
| パス内インライン匿名スキーマ | Phase 2 の `SCHEMA_NAME` 呼び出し時 | `schema_push` / `schema_drain` |

`schema_push` は匿名スキーマの重複排除のみに使われ、
名前付きスキーマに対して呼ばれることはなくなる。

---

## 削除・追加されるもの

| | 変更 |
|-|------|
| `resolve.rs` | **削除** |
| `lib.rs` | デフォルトサーバ補完を吸収、`resolve::` 参照を除去 |
| `filter.rs` | `ref_name` フィルタを追加(数行)、`deref` フィルタを追加 |
| `rust_axum.template` | Phase 1 ループを先頭に追加、`SCHEMA``$ref` 分岐を `ref_name` に変更 |
| `schema_cache.rs` | 変更なし(匿名スキーマの重複排除に引き続き必要) |

---

## `20260219-schema-ref.md` との比較

前回の提案(スキーマ `$ref` だけスキップ)と今回の比較:

| | schema-ref.md | two-phase.md(本提案) |
|-|--------------|----------------------|
| `resolve.rs` の変更 | 1行追加(スキップ条件) | **完全削除** |
| `$ref` の処理 | `SCHEMA_NAME` 経由(`include_pointer` が必要) | `ref_name` フィルタのみ(参照解決不要) |
| `schema_push` の用途 | 名前付き + 匿名の両方 | **匿名のみ**(責務が明確) |
| パラメータ `$ref` | 引き続き `resolve.rs` が解決 | `deref` フィルタで対応 |
| 複雑度 | `resolve.rs` の部分的な変更にとどまる | 設計が根本的にシンプルになる |

本提案の方が変更量は多いが、「`$ref` はコンパイル済みシンボルへの参照」という
一貫した概念モデルを持ち、特殊ケース(循環参照検出、スキップ条件)が消える。