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
2 changes: 2 additions & 0 deletions docs/dev/case-v1p1-conformance-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ compeito の現在のゴールは **OpenCASE / OpenSALT との実用的な相互
| C11 | **エラー封筒の `imsx_codeMinorFieldName`** | 常に既定の `"sourcedId"`。invalid_sort_field / invalid_selection_field 系では `sort` / `fields` / `limit` 等の実フィールド名が意味的に正しい | P3 | `imsx_error_response` に fieldName 引数を追加し、各呼び出し箇所で該当フィールド名を渡す |
| C12 | **`ext:` associationType の文字種** | import 受理が `startswith("ext:")` のみで、公式パターン `(ext:)[a-zA-Z0-9.\-_]+` の文字種を検証しない(`ext:日本語` 等も通る) | P3 | 正規表現で検証し、不一致は invalid associationType として skip + warning |
| C13 | **スキーマ層の出力時検証なし** | Pydantic スキーマで identifier の UUID パターン・associationType / targetType の enum を検証していない(import 側で防いでいるため実害は低い) | P3 | strict 出力モード導入時に field_validator で同梱 |
| C14 | **未定義サブパスの 404 が imsx 形式でない** | `/{tenant}/ims/case/v1p1/...` 配下の未定義サブパスは FastAPI/Starlette 既定の 404(`{"detail":"Not Found"}`)を返し、imsx_StatusInfo 形式になっていない(既知リソース種別で ID 不在の 404 `unknownobject` は実装済み) | P2 | CASE API パス配下の catch-all ルートまたは `StarletteHTTPException` ハンドラを `main.py` に追加し、imsx 404 に変換 |
| C15 | **500 が imsx 形式でない** | 未捕捉例外は Starlette 既定のプレーン 500 を返し、`internal_server_error` の imsx_StatusInfo 形式になっていない | P2 | グローバル `Exception` ハンドラを `main.py` に追加し、CASE API パスの 500 を imsx 形式に変換 |

## デプロイ上の制約(参考)

Expand Down
19 changes: 16 additions & 3 deletions docs/spec/api-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ GET /550e8400-e29b-41d4-a716-446655440000/ims/case/v1p1/CFRubrics?doc=d86774f2-1
"identifier": "itm11111-1111-1111-1111-111111111111",
"uri": "https://case.example.com/550e8400-.../uri/itm11111-1111-1111-1111-111111111111"
},
"weight": 1.0,
"weight": 1,
"position": 1,
"rubricId": "rub11111-1111-1111-1111-111111111111",
"lastChangeDateTime": "2025-04-01T00:00:00+09:00",
Expand All @@ -545,7 +545,7 @@ GET /550e8400-e29b-41d4-a716-446655440000/ims/case/v1p1/CFRubrics?doc=d86774f2-1
"uri": "https://case.example.com/550e8400-.../uri/lvl11111-1111-1111-1111-111111111111",
"description": "十分に理解している",
"quality": "A",
"score": 5.0,
"score": 5,
"feedback": "優れた理解を示しています",
"position": 1,
"rubricCriterionId": "cri11111-1111-1111-1111-111111111111",
Expand All @@ -559,6 +559,11 @@ GET /550e8400-e29b-41d4-a716-446655440000/ims/case/v1p1/CFRubrics?doc=d86774f2-1
}
```

> **Note on `weight` / `score`:** whole-number values are emitted as integers
> (`1`, not `1.0`; `5`, not `5.0`). The `CASEBaseSchema.serialize_int_or_float`
> serializer renders integer-valued floats as `int` for round-trip parity with
> OpenCASE (see [round_trip_status.md](../dev/round_trip_status.md) cat C).

**Response (200) — no rubrics:**
```json
{
Expand All @@ -578,13 +583,17 @@ GET /550e8400-.../ims/case/v1p1/CFRubrics
"imsx_codeMinor": {
"imsx_codeMinorField": [
{
"imsx_codeMinorFieldName": "ims.case.v1p1",
"imsx_codeMinorFieldName": "sourcedId",
"imsx_codeMinorFieldValue": "invalid_selection_field"
}
]
}
}
```
> `imsx_codeMinorFieldName` is always `"sourcedId"` (per the imsx convention; see
> [api-spec.md](api-spec.md) error format and [conformance backlog](../dev/case-v1p1-conformance-backlog.md) C11).
> `imsx_description` carries the validation detail string from the framework, so the
> exact text varies; `"Validation error"` above is illustrative.

**Error — `doc` is not a valid UUID (400):**
```
Expand Down Expand Up @@ -738,6 +747,10 @@ The response includes an `Allow: GET` header.

### 500 Internal Server Error

> ⚠️ **Target shape, not yet implemented.** Uncaught errors currently return
> Starlette's default plain 500, not the imsx shape below. Tracked as
> [conformance backlog](../dev/case-v1p1-conformance-backlog.md) C15.

```json
{
"imsx_codeMajor": "failure",
Expand Down
10 changes: 8 additions & 2 deletions docs/spec/api-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,10 @@ The CASE v1.1 `imsx_StatusInfo` shape. Fields are at the root (no wrapper):
- HTTP status mapping: 400 → `failure/error`; 404 → `failure/error` + `unknownobject`; 405 → `failure/error` + `invalid_selection_field`; 429 → `failure/error` + `server_busy`; 500 → `failure/error` + `internal_server_error`.
- **429 (Server Busy):** defined for every endpoint in the CASE v1.1 OpenAPI. We do not implement rate limiting in Phase 1 explicitly, but API Gateway / Lambda throttling may yield 429. In that case we return the `server_busy` imsx_StatusInfo shape.
- We do not use FastAPI's default 422 Validation Error; a custom exception handler converts `RequestValidationError` into a **400** `invalid_selection_field` imsx_StatusInfo response.
- Requests to undefined sub-paths under `/{tenant}/ims/case/v1p1/...` return **404** (`unknownobject`) in the imsx_StatusInfo shape. FastAPI/Starlette's default 404 isn't in imsx form, so a catch-all route or a custom handler for the CASE API path translates it.
- ⚠️ **Not yet implemented** (see [conformance backlog](../dev/case-v1p1-conformance-backlog.md) C14 / C15) — two cases in the status mapping above are not yet wired:
- **404 for undefined sub-paths:** requests to undefined sub-paths under `/{tenant}/ims/case/v1p1/...` currently fall through to FastAPI/Starlette's default 404 (`{"detail": "Not Found"}`), **not** the imsx_StatusInfo shape. A catch-all route or a `StarletteHTTPException` handler for the CASE API path is needed to translate it (C14).
- **500 (internal_server_error):** uncaught server errors currently return Starlette's default plain 500, **not** the `internal_server_error` imsx shape. A global exception handler is needed (C15).
- Error shapes that **are** implemented: 400 (`invalid_uuid` / `invalid_selection_field`), 404 `unknownobject` for a known resource type whose ID does not exist, and 405.

## Unsupported HTTP methods

Expand Down Expand Up @@ -592,7 +595,10 @@ CASE v1.1 の imsx_StatusInfo 形式。ルートレベルに直接フィール
- HTTPステータスコード対応: 400→`failure/error`, 404→`failure/error`+`unknownobject`, 405→`failure/error`+`invalid_selection_field`, 429→`failure/error`+`server_busy`, 500→`failure/error`+`internal_server_error`
- **429 (Server Busy):** CASE v1.1 OpenAPI で全エンドポイントに定義されている。Phase 1 では明示的なレート制限を実装しないが、API Gateway / Lambda のスロットリングにより 429 が返される可能性がある。その場合は imsx_StatusInfo 形式で `server_busy` を返す
- FastAPI のデフォルト 422 Validation Error レスポンスは使用せず、imsx_StatusInfo 形式の **400**(`invalid_selection_field`)に変換する。カスタム例外ハンドラで RequestValidationError をキャッチし、imsx_StatusInfo 形式で返す
- CASE API パス配下(`/{tenant}/ims/case/v1p1/...`)の未定義サブパスへのアクセスには **404**(`unknownobject`)を imsx_StatusInfo 形式で返す。FastAPI/Starlette のデフォルト 404 レスポンスは imsx_StatusInfo 形式ではないため、CASE API パス配下の catch-all ルートまたはカスタム例外ハンドラで変換する
- ⚠️ **未実装**([conformance backlog](../dev/case-v1p1-conformance-backlog.md) C14 / C15 参照)— 上記マッピングのうち以下2件は未配線:
- **未定義サブパスの 404:** `/{tenant}/ims/case/v1p1/...` 配下の未定義サブパスは現状 FastAPI/Starlette のデフォルト 404(`{"detail": "Not Found"}`)にフォールスルーし、imsx_StatusInfo 形式**ではない**。CASE API パス配下の catch-all ルートまたは `StarletteHTTPException` ハンドラで変換する必要がある(C14)。
- **500(internal_server_error):** 未捕捉のサーバーエラーは現状 Starlette のデフォルトのプレーン 500 を返し、`internal_server_error` の imsx 形式**ではない**。グローバル例外ハンドラが必要(C15)。
- **実装済み**のエラー形式: 400(`invalid_uuid` / `invalid_selection_field`)、既知リソース種別で ID 不在時の 404 `unknownobject`、405。

## 非対応HTTPメソッド

Expand Down
8 changes: 4 additions & 4 deletions docs/spec/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ uv run python cli.py export csv --tenant {uuid} --doc {doc-uuid} --file output.c
# are included. isChildOf is NOT repeated in the CF Association sheet.
uv run python cli.py export xlsx --tenant {uuid} --doc {doc-uuid} --file output.xlsx

# CASE CFPackage JSON export (output is byte-for-byte identical to GET /CFPackages/{id})
# CASE CFPackage JSON export (same payload as GET /CFPackages/{id}; the CLI pretty-prints with indent, the API serves compact JSON)
# Re-importable via `import case --file`, or feed-able to any CASE-conformant editor.
uv run python cli.py export case --tenant {uuid} --doc {doc-uuid} --file output.json

Expand Down Expand Up @@ -162,7 +162,7 @@ uv run python cli.py doc delete --tenant {uuid} --doc {doc-uuid}
- `--file` is not readable (permissions, etc.) → exit ("Cannot read file: '{filepath}'", code 1)
- `--file` output path is not writable (directory missing, permissions) → exit ("Cannot write file: '{filepath}'", code 1)
- CSV import: file is not valid UTF-8 → exit ("CSV file is not valid UTF-8", code 1)
- `tenant update` with none of `--name` / `--private` / `--public` / `--slug` / `--clear-slug` → exit ("At least one of --name, --private, --public, --slug, or --clear-slug is required", code 1)
- `tenant update` with none of `--name` / `--private` / `--public` / `--slug` / `--clear-slug` / `--display-order` / `--clear-order` → exit ("At least one of --name, --private, --public, --slug, --clear-slug, --display-order, or --clear-order is required", code 1)

## CSV import defaults

Expand Down Expand Up @@ -294,7 +294,7 @@ uv run python cli.py export csv --tenant {uuid} --doc {doc-uuid} --file output.c
# isChildOf は CF Association シートには重複出力しない。
uv run python cli.py export xlsx --tenant {uuid} --doc {doc-uuid} --file output.xlsx

# CASE CFPackage JSON エクスポート(出力は GET /CFPackages/{id} と同一のバイト列
# CASE CFPackage JSON エクスポート(内容は GET /CFPackages/{id} と同一。CLI は可読性のため indent 付き整形、API は compact JSON
# import case --file で再取り込みするか、任意の CASE 準拠エディタへ受け渡せる
uv run python cli.py export case --tenant {uuid} --doc {doc-uuid} --file output.json

Expand Down Expand Up @@ -361,7 +361,7 @@ uv run python cli.py doc delete --tenant {uuid} --doc {doc-uuid}
- `--file` で指定したファイルが読み取れない(パーミッションエラー等) → エラー終了(「Cannot read file: '{filepath}'」、終了コード 1)
- `--file` で指定した出力先に書き込めない(ディレクトリが存在しない、パーミッションエラー等) → エラー終了(「Cannot write file: '{filepath}'」、終了コード 1)
- CSVインポート時、ファイルが UTF-8 としてデコードできない → エラー終了(「CSV file is not valid UTF-8」、終了コード 1)
- `tenant update` に `--name` / `--private` / `--public` / `--slug` / `--clear-slug` のいずれも指定されていない → エラー終了(「--name、--private、--public、--slug、--clear-slugのいずれかを指定してください」、終了コード 1)
- `tenant update` に `--name` / `--private` / `--public` / `--slug` / `--clear-slug` / `--display-order` / `--clear-order` のいずれも指定されていない → エラー終了(「--name、--private、--public、--slug、--clear-slug、--display-order、--clear-orderのいずれかを指定してください」、終了コード 1)

## CSVインポートのデフォルト動作

Expand Down
Loading
Loading