Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ jobs:
name: Test haskell codes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: haskell/actions/setup@v1
- uses: actions/checkout@v4
- uses: haskell-actions/setup@v2
id: haskell-setup
with:
ghc-version: "8.8.3"
enable-stack: true
stack-version: "2.5.1"
- name: Restore caches
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: |
${{steps.haskell-setup.outputs.stack-path}}
Expand All @@ -28,13 +28,13 @@ jobs:
name: Test e2e
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Setup Node.js environment
uses: actions/setup-node@v2.1.4
uses: actions/setup-node@v4
with:
node-version: 12.x
- name: Restore caches
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: |
~/.npm
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
/bazel-*
/*.prof
/schema
node_modules/
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lockfile-version=1
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ generated/ts/%: build
debug: build
stack exec --trace -- onix-exe +RTS -xc --RTS --schemaVersion v3 --language go

# The fixtures under fixtures/ are self-contained, so the tests deliberately do
# not depend on the `schema` target: `make schema` downloads the EDItEUR
# archives over the network, and requiring it here made the test suite
# unrunnable whenever editeur.org was unreachable. Keep fixtures from including
# anything outside fixtures/, or this dependency comes back.
# See docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md
.PHONY: test
test: schema
test:
stack test --trace --fast

.stack-work: $(HS_FILES) package.yaml stack.yaml
Expand Down
26 changes: 26 additions & 0 deletions docs/adr/0000-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# ADR-0000: タイトル

- **ステータス**: Proposed | Accepted | Superseded by ADR-XXXX
- **日付**: YYYY-MM-DD

## 背景

どういう状況で、何が問題になっているか。観測された事実 (エラーメッセージ、計測値、
外部サービスの挙動) を具体的に書く。

## 決定

何を決めたか。

## 理由

なぜその選択肢を選んだか。

## 検討した他の選択肢

- **案 A**: 内容と、採らなかった理由
- **案 B**: 内容と、採らなかった理由

## 結果

この決定によって何が変わるか。新しく発生する制約や、将来見直すべき条件も書く。
52 changes: 52 additions & 0 deletions docs/adr/0001-record-architecture-decisions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# ADR-0001: 設計判断を ADR として記録する

- **ステータス**: Accepted
- **日付**: 2026-09-13

## 背景

onix-codegen には、コードを読むだけでは意図が復元できない判断がいくつもある。例えば次のようなもの。

- スキーマの版を `v2` / `v3` というディレクトリで分け、既存の出力を置き換えない
- 言語固有の知識をテンプレートに閉じ込め、`src/` を言語非依存に保つ
- 対応していない XSD 構造は暗黙に無視せず `unimplemented` / `unreachable` で落とす

これらはいずれも「後方互換性を保ちながら ONIX 仕様に追従する」「サポート言語を増やしやすくする」という
このリポジトリの価値 (AGENTS.md) から導かれているが、根拠がコミットログに散っているため、
後から来た人が「なぜこうなっているのか」を再構成しづらい。結果として、良かれと思った変更が
その判断を静かに壊すことがある。

外部依存の扱いも同じ問題を抱えている。EDItEUR の配布物、Bazel、Stackage のスナップショットは
どれも外部の都合で壊れるが、「なぜこのバージョンに固定しているのか」が残っていないと、
更新のたびに同じ調査をやり直すことになる。

## 決定

設計判断を `docs/adr/` 配下の ADR として記録する。フォーマットは MADR を簡略化したもので、
`0000-template.md` を雛形とする。連番は一度振ったら変えず、判断が変わったときは既存の ADR を
書き換えるのではなく、新しい ADR を書いて古いものを `Superseded` にする。

AGENTS.md には、どういう判断を ADR にすべきかの基準を書き、両者を相互に参照させる。

## 理由

ADR は「決定そのもの」ではなく「決定に至った制約」を残せる点が、コメントやコミットメッセージより優れている。
このリポジトリで重要なのは、まさにその制約 (EDItEUR の配布形態、生成物の互換性、テンプレートの責務分担) が
時間とともに変わることなので、当時の前提ごと記録に残せる形式が要る。

Markdown をリポジトリ内に置くのは、スキーマや生成物と同じコミットで変更履歴が追えるため。
外部の Wiki やイシューに書くと、コードとの対応が切れる。

## 検討した他の選択肢

- **AGENTS.md / README.md に全部書く**: 判断の履歴が積み上がると読みものとして破綻する。
AGENTS.md は「今どうすべきか」を書く場所として保ち、「なぜそうなったか」は ADR に分ける。
- **GitHub Issues / Discussions に残す**: 検索性はあるが、リポジトリを clone しただけでは読めず、
コードとの対応も切れる。オフラインで完結しない。
- **記録しない (現状維持)**: 調査のやり直しと、意図しない互換性破壊が続く。

## 結果

- 新しい設計判断、および後から言語化された既知の判断は ADR に書く。
- ADR を追加したら `docs/adr/README.md` の一覧も更新する。
- ADR はレビュー対象になる。判断に異論があれば、実装ではなく ADR に対してコメントできる。
106 changes: 106 additions & 0 deletions docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# ADR-0002: ユニットテストを取得済みスキーマから切り離す

- **ステータス**: Accepted
- **日付**: 2026-09-13

## 背景

Makefile のテストターゲットは `schema` に依存していた。

```make
.PHONY: test
test: schema
stack test --trace --fast
```

`schema` ターゲットは Bazel の `http_archive` 経由で EDItEUR の zip をダウンロードし、
`schema/v2` / `schema/v3` に展開する。つまり `make test` は毎回 editeur.org への
ネットワークアクセスを要求していた。

2026-09-13 時点で、この取得が機能しなくなった。

```
WARNING: Download from https://www.editeur.org/files/ONIX%202.1/ONIX_for_Books_Release2-1_rev03_schema+codes_Issue_36.zip
failed: UnrecoverableHttpException GET returned 202 Accepted
ERROR: An error occurred during the fetch of repository 'org_editeur_v2'
make: *** [Makefile:35: schema/v2] Error 1
```

その結果、`Test haskell codes` ジョブはテストを 1 件も実行しないまま失敗する。
取得できない理由そのものは ADR-0003 で扱う。

### この依存は本物だった

当初、この依存は単なる過剰指定だと考えた。テストコードが直接読むのは
`fixtures/test_*.xsd` だけで、`./schema` という文字列は `src/Lib.hs` の `schemaRoot`
にしか現れないからである。しかしこれは誤りだった。

`fixtures/test_mixed_html.xsd` が、EDItEUR の配布物を直接 include していた。

```xml
<xs:include schemaLocation="../schema/v2/ONIX_XHTML_Subset.xsd" />
```

`Xsd.getSchema` は include を再帰的にたどり (`src/Xsd.hs` の `go` / `goInclude`)、
相対パスを `combineURIs` でローカルパスに解決して `Text.XML.readFile` で読む。
ファイルが無ければ IOException で落ちる。このフィクスチャは
`test/TestMixed.hs` と `test/TestModel.hs` の両方から読まれており、`schema/` は
`.gitignore` されているので、clean checkout では必ず存在しない。

つまり `test: schema` は正しい依存だった。git の履歴もそれを裏づけている。
`test: schema` を追加したコミット (9352123) は、リポジトリに置かれていた
`2_1_rev03_schema/` を削除し、このフィクスチャの include を `../schema/v2/` に
向け直した、まさにそのコミットである。

## 決定

依存を消すのではなく、**依存の対象をリポジトリ内に移す**。

1. `fixtures/test_mixed_html_xhtml_subset.xsd` を追加する。`test_mixed_html.xsd` が
`ref` している 40 個の要素名だけを宣言した、ONIX_XHTML_Subset.xsd の代替物である。
2. `test_mixed_html.xsd` の include をそちらに向ける。
3. そのうえで `test` ターゲットから `schema` 依存を外す。

`build` ターゲットの `schema` 依存は残す。実際にコードを生成するにはスキーマの実体が
要るため、こちらは今も本物の依存である。

## 理由

代替物は、テストが実際に検証している性質を保つように書いた。
`test/TestModel.hs` は、このフィクスチャを読んだうえで `Model.collectElements` が
空になることを表明している。`collectElements` は `complexMixed = False` の要素だけを
拾うフィルタなので、この表明の意味は「XHTML の要素がモデルに漏れてこない」ことである。
XHTML の内容要素はいずれも mixed content なので、代替物でも全要素を
`<xs:complexType mixed="true" />` として宣言した。要素名の集合が実物と一致していること、
フィクスチャ側の `ref` 40 個すべてに宣言が対応することは機械的に確認した。

代替物で足りるのは、テストがこのファイルから必要としているのが**要素の宣言の存在と
mixed であること**だけだからである。ONIX_XHTML_Subset.xsd の完全な内容 (属性、
コンテンツモデルの詳細) は、ここで検証されている性質に寄与していない。

そのうえで、テストを外部サービスの可用性から切り離す価値は大きい。パーサのバグを
直したいときに editeur.org の状態に左右されるのは、依存の向きとして誤っている。

なお、この変更は 202 の問題そのものを解決しない。コード生成と、生成物を最新スキーマへ
追従させる作業は依然としてダウンロードを必要とする (ADR-0003)。

## 検討した他の選択肢

- **`test: schema` を残す**: 事実としては正しい依存なので、これは筋が通っている。
ただし、たった 1 ファイルの XSD のためにテスト全体を外部サービスに縛り続けることになる。
- **ONIX_XHTML_Subset.xsd をそのまま `fixtures/` に取り込む**: 代替物より忠実だが、
EDItEUR の配布物の再配布にあたる。ライセンスの判断が要るため、テストを通すためだけに
踏み込むべきではない (ADR-0003 と同じ理由)。代替物は自前で書いたものなのでこの問題がない。
- **include を単に削除する**: `Annotation` 以外に要素が無くなるので `collectElements` は
空になり、表明は通ってしまう。だが「XHTML 要素が漏れてこない」ことを何も検証しなくなり、
テストが意味を失う。**採らない。**

## 結果

- `make test` はネットワークなしで実行できる。CI の Haskell ジョブは editeur.org に依存しない。
- `fixtures/test_mixed_html_xhtml_subset.xsd` は実物の代替物である。実物側の構造が変わって
テストの前提が動くことはあり得るので、スキーマの版を上げるときはこのファイルも見直す。
- コード生成 (`make build`、`make generated/...`) は引き続きダウンロードを必要とする。
- 新しいテストを書くときは `fixtures/` に最小の XSD を足す、という既存の慣習が
そのまま「テストを外部依存から切り離す」ことにもなる。この性質は維持する。
フィクスチャから `fixtures/` の外を include しないこと。
26 changes: 26 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Architecture Decision Records (ADR)

このディレクトリには、onix-codegen の設計判断を記録する ADR を置く。

## 一覧

| # | タイトル | ステータス |
| ------------------------------------------------------------- | ---------------------------------------------- | ---------- |
| [0001](0001-record-architecture-decisions.md) | 設計判断を ADR として記録する | Accepted |
| [0002](0002-decouple-unit-tests-from-the-vendored-schema.md) | ユニットテストを取得済みスキーマから切り離す | Accepted |

## 書き方

`0000-template.md` をコピーして、次の連番を付ける。番号は一度振ったら変えない。

ADR は「その時点で、どういう制約のもとに、なぜそう決めたか」を残すもの。
後から判断が変わったら、既存の ADR を書き換えるのではなく、新しい ADR を書いて
古いものの Status を `Superseded by ADR-XXXX` に更新する。決定の履歴が消えないことが重要。

## どういう判断を ADR にするか

このリポジトリの価値 (AGENTS.md 参照) に照らして、次のいずれかに影響するもの。

- **後方互換性**: 生成コードの公開 API に影響する判断、スキーマの版の扱い方
- **サポート言語の増やしやすさ**: 中間表現とテンプレートの責務分担、言語追加の手順に影響する判断
- **ビルドと CI の前提**: 外部依存 (EDItEUR の配布物、ツールチェーンのバージョン) の扱い方
2 changes: 1 addition & 1 deletion fixtures/test_mixed_html.xsd
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns="http://www.editeur.org/onix/2.1/reference" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.editeur.org/onix/2.1/reference" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:include schemaLocation="../schema/v2/ONIX_XHTML_Subset.xsd" />
<xs:include schemaLocation="test_mixed_html_xhtml_subset.xsd" />
<xs:element name="Annotation">
<xs:complexType mixed="true">
<xs:choice minOccurs="0" maxOccurs="unbounded">
Expand Down
53 changes: 53 additions & 0 deletions fixtures/test_mixed_html_xhtml_subset.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Stand-in for the ONIX_XHTML_Subset.xsd shipped in the EDItEUR distribution.

test_mixed_html.xsd used to include ../schema/v2/ONIX_XHTML_Subset.xsd, which
made the test suite depend on `make schema` having downloaded the EDItEUR
archive. This file declares only the element names that fixture refers to, in
the shape that matters to the code under test: every XHTML content element is
a mixed complex type, so Model.collectElements must filter all of them out.
See docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md
-->
<xs:schema xmlns="http://www.editeur.org/onix/2.1/reference" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.editeur.org/onix/2.1/reference" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name="h1"><xs:complexType mixed="true" /></xs:element>
<xs:element name="h2"><xs:complexType mixed="true" /></xs:element>
<xs:element name="h3"><xs:complexType mixed="true" /></xs:element>
<xs:element name="h4"><xs:complexType mixed="true" /></xs:element>
<xs:element name="h5"><xs:complexType mixed="true" /></xs:element>
<xs:element name="h6"><xs:complexType mixed="true" /></xs:element>
<xs:element name="div"><xs:complexType mixed="true" /></xs:element>
<xs:element name="ul"><xs:complexType mixed="true" /></xs:element>
<xs:element name="ol"><xs:complexType mixed="true" /></xs:element>
<xs:element name="dl"><xs:complexType mixed="true" /></xs:element>
<xs:element name="pre"><xs:complexType mixed="true" /></xs:element>
<xs:element name="hr"><xs:complexType mixed="true" /></xs:element>
<xs:element name="blockquote"><xs:complexType mixed="true" /></xs:element>
<xs:element name="address"><xs:complexType mixed="true" /></xs:element>
<xs:element name="table"><xs:complexType mixed="true" /></xs:element>
<xs:element name="a"><xs:complexType mixed="true" /></xs:element>
<xs:element name="br"><xs:complexType mixed="true" /></xs:element>
<xs:element name="span"><xs:complexType mixed="true" /></xs:element>
<xs:element name="bdo"><xs:complexType mixed="true" /></xs:element>
<xs:element name="object"><xs:complexType mixed="true" /></xs:element>
<xs:element name="img"><xs:complexType mixed="true" /></xs:element>
<xs:element name="map"><xs:complexType mixed="true" /></xs:element>
<xs:element name="tt"><xs:complexType mixed="true" /></xs:element>
<xs:element name="i"><xs:complexType mixed="true" /></xs:element>
<xs:element name="b"><xs:complexType mixed="true" /></xs:element>
<xs:element name="big"><xs:complexType mixed="true" /></xs:element>
<xs:element name="small"><xs:complexType mixed="true" /></xs:element>
<xs:element name="em"><xs:complexType mixed="true" /></xs:element>
<xs:element name="strong"><xs:complexType mixed="true" /></xs:element>
<xs:element name="dfn"><xs:complexType mixed="true" /></xs:element>
<xs:element name="code"><xs:complexType mixed="true" /></xs:element>
<xs:element name="q"><xs:complexType mixed="true" /></xs:element>
<xs:element name="sub"><xs:complexType mixed="true" /></xs:element>
<xs:element name="sup"><xs:complexType mixed="true" /></xs:element>
<xs:element name="samp"><xs:complexType mixed="true" /></xs:element>
<xs:element name="kbd"><xs:complexType mixed="true" /></xs:element>
<xs:element name="var"><xs:complexType mixed="true" /></xs:element>
<xs:element name="cite"><xs:complexType mixed="true" /></xs:element>
<xs:element name="abbr"><xs:complexType mixed="true" /></xs:element>
<xs:element name="acronym"><xs:complexType mixed="true" /></xs:element>
</xs:schema>
23 changes: 16 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
},
"dependencies": {},
"devDependencies": {
"@bazel/bazelisk": "1.7.3",
"@types/node": "14.14.25",
"@bazel/bazelisk": "1.28.1",
"@types/node": "22.20.2",
"fast-xml-parser": "3.17.6"
},
"peerDependencies": {
Expand Down
Loading