papycli 0.5.1__tar.gz → 0.5.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. papycli-0.5.3/.github/workflows/release.yml +151 -0
  2. {papycli-0.5.1 → papycli-0.5.3}/CLAUDE.md +9 -1
  3. {papycli-0.5.1 → papycli-0.5.3}/PKG-INFO +13 -9
  4. {papycli-0.5.1 → papycli-0.5.3}/README.ja.md +12 -6
  5. {papycli-0.5.1 → papycli-0.5.3}/README.md +12 -8
  6. {papycli-0.5.1 → papycli-0.5.3}/design_doc.md +14 -3
  7. {papycli-0.5.1 → papycli-0.5.3}/pyproject.toml +1 -1
  8. papycli-0.5.3/src/papycli/checker.py +126 -0
  9. {papycli-0.5.1 → papycli-0.5.3}/src/papycli/completion.py +23 -2
  10. {papycli-0.5.1 → papycli-0.5.3}/src/papycli/main.py +70 -2
  11. papycli-0.5.3/tests/test_checker.py +219 -0
  12. {papycli-0.5.1 → papycli-0.5.3}/tests/test_completion.py +63 -3
  13. {papycli-0.5.1 → papycli-0.5.3}/tests/test_main.py +183 -0
  14. {papycli-0.5.1 → papycli-0.5.3}/uv.lock +1 -1
  15. papycli-0.5.1/.github/workflows/release.yml +0 -77
  16. {papycli-0.5.1 → papycli-0.5.3}/.gitignore +0 -0
  17. {papycli-0.5.1 → papycli-0.5.3}/.python-version +0 -0
  18. {papycli-0.5.1 → papycli-0.5.3}/LICENSE +0 -0
  19. {papycli-0.5.1 → papycli-0.5.3}/examples/docker-compose.yml +0 -0
  20. {papycli-0.5.1 → papycli-0.5.3}/examples/petstore-oas3.json +0 -0
  21. {papycli-0.5.1 → papycli-0.5.3}/src/papycli/__init__.py +0 -0
  22. {papycli-0.5.1 → papycli-0.5.3}/src/papycli/api_call.py +0 -0
  23. {papycli-0.5.1 → papycli-0.5.3}/src/papycli/config.py +0 -0
  24. {papycli-0.5.1 → papycli-0.5.3}/src/papycli/i18n.py +0 -0
  25. {papycli-0.5.1 → papycli-0.5.3}/src/papycli/init_cmd.py +0 -0
  26. {papycli-0.5.1 → papycli-0.5.3}/src/papycli/spec_loader.py +0 -0
  27. {papycli-0.5.1 → papycli-0.5.3}/src/papycli/summary.py +0 -0
  28. {papycli-0.5.1 → papycli-0.5.3}/tests/conftest.py +0 -0
  29. {papycli-0.5.1 → papycli-0.5.3}/tests/test_api_call.py +0 -0
  30. {papycli-0.5.1 → papycli-0.5.3}/tests/test_config.py +0 -0
  31. {papycli-0.5.1 → papycli-0.5.3}/tests/test_i18n.py +0 -0
  32. {papycli-0.5.1 → papycli-0.5.3}/tests/test_init_cmd.py +0 -0
  33. {papycli-0.5.1 → papycli-0.5.3}/tests/test_spec_loader.py +0 -0
  34. {papycli-0.5.1 → papycli-0.5.3}/tests/test_summary.py +0 -0
@@ -0,0 +1,151 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ build:
10
+ name: Build distribution
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Verify tag version matches pyproject.toml
16
+ run: |
17
+ TAG_VERSION="${GITHUB_REF_NAME#v}"
18
+ PKG_VERSION=$(python3 -c "
19
+ import tomllib
20
+ with open('pyproject.toml', 'rb') as f:
21
+ print(tomllib.load(f)['project']['version'])
22
+ ")
23
+ if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
24
+ echo "::error::Version mismatch: tag=$TAG_VERSION, pyproject.toml=$PKG_VERSION"
25
+ exit 1
26
+ fi
27
+ echo "Version OK: $PKG_VERSION"
28
+
29
+ - uses: astral-sh/setup-uv@v5
30
+
31
+ - name: Build wheel and sdist
32
+ run: uv build
33
+
34
+ - uses: actions/upload-artifact@v4
35
+ with:
36
+ name: dist
37
+ path: dist/
38
+
39
+ github-release:
40
+ name: Create GitHub Release (draft)
41
+ needs: build
42
+ runs-on: ubuntu-latest
43
+ permissions:
44
+ contents: write
45
+ models: read
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+ with:
49
+ fetch-depth: 0 # full history needed for git log
50
+
51
+ - uses: actions/download-artifact@v4
52
+ with:
53
+ name: dist
54
+ path: dist/
55
+
56
+ - name: Get previous release tag
57
+ id: prev-tag
58
+ env:
59
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60
+ REPO: ${{ github.repository }}
61
+ TAG: ${{ github.ref_name }}
62
+ run: |
63
+ # Exclude drafts, pre-releases, and the current tag to get the true previous release
64
+ PREV_TAG=$(gh release list --repo "$REPO" --limit 20 \
65
+ --json tagName,isDraft,isPrerelease \
66
+ --jq 'map(select((.isDraft | not) and (.isPrerelease | not) and (.tagName != env.TAG))) | .[0].tagName // ""')
67
+ echo "prev_tag=$PREV_TAG" >> "$GITHUB_OUTPUT"
68
+
69
+ - name: Collect commits since previous release
70
+ id: commits
71
+ env:
72
+ TAG: ${{ github.ref_name }}
73
+ PREV_TAG: ${{ steps.prev-tag.outputs.prev_tag }}
74
+ run: |
75
+ set -euo pipefail
76
+ if [ -n "${PREV_TAG}" ]; then
77
+ RANGE="${PREV_TAG}..${TAG}"
78
+ else
79
+ RANGE="${TAG}"
80
+ fi
81
+ # Validate that the range resolves before proceeding
82
+ if ! git rev-parse "${RANGE}" > /dev/null 2>&1; then
83
+ echo "Error: git range '${RANGE}' does not resolve." >&2
84
+ exit 1
85
+ fi
86
+ # Collect up to 100 subject lines; exclude merge commits
87
+ # Use --max-count instead of | head to avoid SIGPIPE under set -euo pipefail
88
+ COMMITS=$(git log "${RANGE}" --no-merges --max-count=100 --pretty=format:"%s")
89
+ # Use a unique delimiter to avoid collision with commit message content
90
+ DELIM=$(uuidgen)
91
+ {
92
+ echo "commits<<${DELIM}"
93
+ echo "${COMMITS}"
94
+ echo "${DELIM}"
95
+ } >> "$GITHUB_OUTPUT"
96
+
97
+ - name: Generate release notes with GitHub Models
98
+ id: ai
99
+ uses: actions/ai-inference@v1
100
+ with:
101
+ model: openai/gpt-4o-mini
102
+ system-prompt: |
103
+ You are a technical writer generating GitHub release notes from git commit messages.
104
+
105
+ Rules:
106
+ - Classify each commit subject into one of three sections:
107
+ ## Features — commits starting with "feat:"
108
+ ## Bug Fixes — commits starting with "fix:"
109
+ ## Maintenance — all other commits (refactor:, ci:, test:, perf:, etc.)
110
+ - IGNORE commits starting with "docs:" or "chore:" — do not include them at all.
111
+ - Rewrite each item as a concise, user-friendly bullet point (do not copy the raw commit subject verbatim).
112
+ - Omit any section that has no entries.
113
+ - Output valid Markdown only. No preamble, no explanation outside the Markdown.
114
+ prompt: |
115
+ Generate release notes for ${{ github.ref_name }} from the following commit messages:
116
+
117
+ ${{ steps.commits.outputs.commits }}
118
+
119
+ - name: Write release notes to file
120
+ env:
121
+ NOTES: ${{ steps.ai.outputs.response }}
122
+ run: printf '%s' "$NOTES" > release_notes.md
123
+
124
+ - name: Create draft release
125
+ env:
126
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
127
+ REPO: ${{ github.repository }}
128
+ TAG: ${{ github.ref_name }}
129
+ run: |
130
+ gh release create "$TAG" \
131
+ --repo "$REPO" \
132
+ --title "$TAG" \
133
+ --notes-file release_notes.md \
134
+ --draft \
135
+ dist/*
136
+
137
+ pypi-publish:
138
+ name: Publish to PyPI
139
+ needs: github-release
140
+ runs-on: ubuntu-latest
141
+ environment: pypi
142
+ permissions:
143
+ id-token: write
144
+ steps:
145
+ - uses: actions/download-artifact@v4
146
+ with:
147
+ name: dist
148
+ path: dist/
149
+
150
+ - name: Publish to PyPI
151
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -39,6 +39,7 @@ papycli/
39
39
  │ ├── main.py # エントリポイント・引数パース
40
40
  │ ├── init_cmd.py # config add コマンド(spec の変換・保存)
41
41
  │ ├── api_call.py # HTTP リクエスト実行
42
+ │ ├── checker.py # --check / --check-strict のパラメータ検証
42
43
  │ ├── completion.py # シェル補完スクリプト生成
43
44
  │ ├── config.py # 設定ファイルの読み書き
44
45
  │ ├── i18n.py # 日英ヘルプテキストの切り替えユーティリティ
@@ -46,6 +47,7 @@ papycli/
46
47
  │ └── summary.py # summary コマンド・CSV 出力
47
48
  ├── tests/
48
49
  │ ├── test_api_call.py
50
+ │ ├── test_checker.py
49
51
  │ ├── test_completion.py
50
52
  │ ├── test_config.py
51
53
  │ ├── test_i18n.py
@@ -66,7 +68,7 @@ papycli/
66
68
  ### 主要モジュール
67
69
 
68
70
  **`main.py`** — CLI エントリポイント
69
- 引数をパースし、各コマンド(`config add`/`use`/`remove`/`list`/`completion-script`、`summary`、メソッド呼び出し)に処理を委譲する。シェル補完用の `_complete` 内部コマンドもここで定義する。
71
+ 引数をパースし、各コマンド(`config add`/`use`/`remove`/`list`/`completion-script`、`spec`、`summary`、メソッド呼び出し)に処理を委譲する。シェル補完用の `_complete` 内部コマンドもここで定義する。
70
72
 
71
73
  **`init_cmd.py`** — API 初期化(`config add` コマンドの実処理)
72
74
  OpenAPI spec ファイルを受け取り、`$ref` を解決した上で papycli 内部の API 定義フォーマットに変換し、`$PAPYCLI_CONF_DIR/apis/<name>.json` に保存する。設定ファイル (`papycli.conf`) も更新する。
@@ -77,6 +79,9 @@ OpenAPI spec の JSON/YAML を読み込み、`$ref` を再帰的に解決して
77
79
  **`api_call.py`** — HTTP リクエスト実行
78
80
  メソッド・リソースパス・オプションを受け取り、`requests` ライブラリで HTTP リクエストを実行する。パステンプレート(`/pet/{petId}`)のマッチングと値の埋め込みも担当する。
79
81
 
82
+ **`checker.py`** — パラメータ検証
83
+ `--check` / `--check-strict` オプション用のリクエスト前バリデーションロジック。必須パラメータの存在確認、型チェック(integer / boolean)、enum 値の検証を行い、警告メッセージのリストを返す。
84
+
80
85
  **`completion.py`** — シェル補完
81
86
  bash / zsh 向けの補完スクリプトを生成する。補完の候補はメソッド、リソースパス、パラメータ名、enum 値の順にコンテキストに応じて提供する。
82
87
 
@@ -155,6 +160,7 @@ papycli config use <api-name>
155
160
  papycli config remove <api-name>
156
161
  papycli config list
157
162
  papycli config completion-script <bash|zsh>
163
+ papycli spec [resource]
158
164
  papycli summary [resource] [--csv]
159
165
  papycli --version
160
166
  papycli --help / -h
@@ -171,6 +177,8 @@ papycli --help / -h
171
177
  - `-p <name.subname> <value>` — ドット記法で 1 レベルのネストオブジェクトを構築する
172
178
  - `-d <json>` — 生の JSON 文字列。`-p` オプションを上書きする
173
179
  - `-H <header: value>` — カスタム HTTP ヘッダー
180
+ - `--check` — 送信前にパラメータを検証する(警告を stderr に出力、リクエストは送信)
181
+ - `--check-strict` — 送信前にパラメータを検証する(警告を stderr に出力、問題があればリクエスト中止・exit 1)
174
182
 
175
183
  ### パステンプレートのマッチング
176
184
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: papycli
3
- Version: 0.5.1
3
+ Version: 0.5.3
4
4
  Summary: A CLI tool to call REST APIs defined in OpenAPI 3.0 specs
5
5
  Project-URL: Homepage, https://github.com/tmonj1/papycli
6
6
  Project-URL: Repository, https://github.com/tmonj1/papycli
@@ -34,6 +34,8 @@ Description-Content-Type: text/markdown
34
34
  - Auto-generates a CLI from any OpenAPI 3.0 spec
35
35
  - Shell completion for bash and zsh
36
36
  - Register and switch between multiple APIs
37
+ - Inspect API specs with `papycli spec`
38
+ - Validate request parameters before sending with `--check` / `--check-strict`
37
39
 
38
40
  ## Requirements
39
41
 
@@ -41,8 +43,6 @@ Description-Content-Type: text/markdown
41
43
  |------|-------|
42
44
  | Python | 3.12 or later |
43
45
 
44
- No external tools (e.g. `jq`) required. Works with Python and pip alone.
45
-
46
46
  ---
47
47
 
48
48
  ## Installation
@@ -126,13 +126,13 @@ Once shell completion is enabled, tab completion is available:
126
126
 
127
127
  ```
128
128
  $ papycli <TAB>
129
- get post put patch delete config summary
129
+ get post put patch delete config spec summary
130
130
 
131
131
  $ papycli get <TAB>
132
132
  /pet/findByStatus /pet/{petId} /store/inventory ...
133
133
 
134
134
  $ papycli get /pet/findByStatus <TAB>
135
- -q -p -H -d --summary --verbose
135
+ -q -p -H -d --summary --verbose --check --check-strict
136
136
 
137
137
  $ papycli get /pet/findByStatus -q <TAB>
138
138
  status
@@ -210,10 +210,13 @@ papycli config use <api-name> Switch the active API
210
210
  papycli config list List registered APIs and current configuration
211
211
  papycli config completion-script <bash|zsh> Print a shell completion script
212
212
 
213
- # API call commands
213
+ # Inspection commands
214
+ papycli spec [resource] Show the raw internal API spec (filter by resource path)
214
215
  papycli summary [resource] List available endpoints (filter by resource prefix)
215
216
  Required params marked with *, array params with []
216
217
  papycli summary --csv Output endpoints in CSV format
218
+
219
+ # API call commands
217
220
  papycli <method> <resource> [options]
218
221
 
219
222
  Methods:
@@ -230,6 +233,9 @@ Options:
230
233
  → {"category":{"id":"1","name":"Dogs"}}
231
234
  -d <json> Raw JSON body (overrides -p)
232
235
  --summary Show endpoint info without sending a request
236
+ --check Validate params before sending (warn on stderr, request is still sent)
237
+ --check-strict Validate params before sending (warn on stderr, abort with exit 1 on failure)
238
+ --verbose / -v Show HTTP status line
233
239
  --version Show version
234
240
  --help / -h Show help
235
241
 
@@ -257,6 +263,4 @@ Environment variables:
257
263
  git clone https://github.com/tmonj1/papycli.git
258
264
  cd papycli
259
265
  pip install -e ".[dev]"
260
- ```
261
-
262
- See [CLAUDE.md](CLAUDE.md) for details.
266
+ ```
@@ -7,6 +7,8 @@
7
7
  - OpenAPI 3.0 仕様から CLI を自動生成
8
8
  - シェル補完(bash / zsh)対応
9
9
  - 複数 API の登録・切り替え
10
+ - `papycli spec` による API スペックの確認
11
+ - `--check` / `--check-strict` によるリクエスト前のパラメータ検証
10
12
 
11
13
  ## 必要環境
12
14
 
@@ -97,13 +99,13 @@ papycli delete /pet/1
97
99
 
98
100
  ```
99
101
  $ papycli <TAB>
100
- get post put patch delete config summary
102
+ get post put patch delete config spec summary
101
103
 
102
104
  $ papycli get <TAB>
103
105
  /pet/findByStatus /pet/{petId} /store/inventory ...
104
106
 
105
107
  $ papycli get /pet/findByStatus <TAB>
106
- -q -p -H -d --summary --verbose
108
+ -q -p -H -d --summary --verbose --check --check-strict
107
109
 
108
110
  $ papycli get /pet/findByStatus -q <TAB>
109
111
  status
@@ -181,10 +183,13 @@ papycli config use <api-name> アクティブな API を切り替
181
183
  papycli config list 登録済み API と現在の設定を一覧表示する
182
184
  papycli config completion-script <bash|zsh> シェル補完スクリプトを出力する
183
185
 
184
- # API 呼び出しコマンド
186
+ # 確認コマンド
187
+ papycli spec [resource] 内部 API スペックを表示する(リソースパスでフィルタ可能)
185
188
  papycli summary [resource] 利用可能なエンドポイントを表示する(リソースでフィルタ可能)
186
189
  必須パラメータは * 付き、配列パラメータは [] 付きで表示
187
190
  papycli summary --csv CSV フォーマットでエンドポイントを表示する
191
+
192
+ # API 呼び出しコマンド
188
193
  papycli <method> <resource> [options]
189
194
 
190
195
  メソッド:
@@ -201,6 +206,9 @@ papycli <method> <resource> [options]
201
206
  → {"category":{"id":"1","name":"Dogs"}}
202
207
  -d <json> 生の JSON ボディ(-p を上書きする)
203
208
  --summary リクエストを送らずにエンドポイント情報を表示する
209
+ --check 送信前にパラメータを検証する(警告を stderr に出力、リクエストは送信)
210
+ --check-strict 送信前にパラメータを検証する(警告を stderr に出力、問題があればリクエスト中止・exit 1)
211
+ --verbose / -v HTTP ステータス行を表示する
204
212
  --version バージョンを表示する
205
213
  --help / -h 使い方を表示する
206
214
 
@@ -228,6 +236,4 @@ papycli <method> <resource> [options]
228
236
  git clone https://github.com/<your-org>/papycli.git
229
237
  cd papycli
230
238
  pip install -e ".[dev]"
231
- ```
232
-
233
- 詳細は [CLAUDE.md](CLAUDE.md) を参照してください。
239
+ ```
@@ -7,6 +7,8 @@
7
7
  - Auto-generates a CLI from any OpenAPI 3.0 spec
8
8
  - Shell completion for bash and zsh
9
9
  - Register and switch between multiple APIs
10
+ - Inspect API specs with `papycli spec`
11
+ - Validate request parameters before sending with `--check` / `--check-strict`
10
12
 
11
13
  ## Requirements
12
14
 
@@ -14,8 +16,6 @@
14
16
  |------|-------|
15
17
  | Python | 3.12 or later |
16
18
 
17
- No external tools (e.g. `jq`) required. Works with Python and pip alone.
18
-
19
19
  ---
20
20
 
21
21
  ## Installation
@@ -99,13 +99,13 @@ Once shell completion is enabled, tab completion is available:
99
99
 
100
100
  ```
101
101
  $ papycli <TAB>
102
- get post put patch delete config summary
102
+ get post put patch delete config spec summary
103
103
 
104
104
  $ papycli get <TAB>
105
105
  /pet/findByStatus /pet/{petId} /store/inventory ...
106
106
 
107
107
  $ papycli get /pet/findByStatus <TAB>
108
- -q -p -H -d --summary --verbose
108
+ -q -p -H -d --summary --verbose --check --check-strict
109
109
 
110
110
  $ papycli get /pet/findByStatus -q <TAB>
111
111
  status
@@ -183,10 +183,13 @@ papycli config use <api-name> Switch the active API
183
183
  papycli config list List registered APIs and current configuration
184
184
  papycli config completion-script <bash|zsh> Print a shell completion script
185
185
 
186
- # API call commands
186
+ # Inspection commands
187
+ papycli spec [resource] Show the raw internal API spec (filter by resource path)
187
188
  papycli summary [resource] List available endpoints (filter by resource prefix)
188
189
  Required params marked with *, array params with []
189
190
  papycli summary --csv Output endpoints in CSV format
191
+
192
+ # API call commands
190
193
  papycli <method> <resource> [options]
191
194
 
192
195
  Methods:
@@ -203,6 +206,9 @@ Options:
203
206
  → {"category":{"id":"1","name":"Dogs"}}
204
207
  -d <json> Raw JSON body (overrides -p)
205
208
  --summary Show endpoint info without sending a request
209
+ --check Validate params before sending (warn on stderr, request is still sent)
210
+ --check-strict Validate params before sending (warn on stderr, abort with exit 1 on failure)
211
+ --verbose / -v Show HTTP status line
206
212
  --version Show version
207
213
  --help / -h Show help
208
214
 
@@ -230,6 +236,4 @@ Environment variables:
230
236
  git clone https://github.com/tmonj1/papycli.git
231
237
  cd papycli
232
238
  pip install -e ".[dev]"
233
- ```
234
-
235
- See [CLAUDE.md](CLAUDE.md) for details.
239
+ ```
@@ -37,11 +37,13 @@ papycli/
37
37
  │ ├── spec_loader.py # OpenAPI spec 読み込み・$ref 解決・内部形式変換
38
38
  │ ├── init_cmd.py # config add コマンドの処理
39
39
  │ ├── api_call.py # HTTP リクエスト実行・パステンプレートマッチング
40
+ │ ├── checker.py # --check / --check-strict のパラメータ検証
40
41
  │ ├── summary.py # summary コマンドの出力
41
42
  │ ├── completion.py # bash / zsh 補完スクリプト生成
42
43
  │ └── i18n.py # 日英ヘルプテキストの切り替えユーティリティ
43
44
  ├── tests/
44
45
  │ ├── test_api_call.py
46
+ │ ├── test_checker.py
45
47
  │ ├── test_completion.py
46
48
  │ ├── test_config.py
47
49
  │ ├── test_i18n.py
@@ -174,12 +176,19 @@ papycli/
174
176
  - `requests` による HTTP 実行
175
177
  - レスポンスの出力(JSON は整形表示、それ以外はそのまま)
176
178
  - `main.py` に `papycli <method> <resource>` コマンド追加
177
- - テスト: `test_api_call.py`
179
+ - `main.py` に `papycli spec [resource]` コマンド追加(内部 API 定義の JSON 表示)
180
+ - `checker.py`
181
+ - `--check` / `--check-strict` オプション用のパラメータ検証ロジック
182
+ - 必須パラメータの存在確認、型チェック(integer / boolean)、enum 値の検証
183
+ - `--check`: 問題があっても警告を出力してリクエストを送信する
184
+ - `--check-strict`: 問題があれば警告を出力してリクエストを中止し exit 1
185
+ - テスト: `test_api_call.py`、`test_checker.py`
178
186
  - パステンプレートマッチングの各ケース
179
187
  - `-p` による JSON 構築(配列、ネスト)
180
188
  - `responses` ライブラリで HTTP をモックしたエンドツーエンドテスト
189
+ - `check_request()` の各検証ケース(必須・型・enum・raw body)
181
190
 
182
- **完了条件**: `papycli get /store/inventory`、`papycli post /pet -p name "My Dog" -p status available` 等が Petstore サーバーに対して動作する。テストがパスする。
191
+ **完了条件**: `papycli get /store/inventory`、`papycli post /pet -p name "My Dog" -p status available` 等が Petstore サーバーに対して動作する。`papycli spec` で内部 API 定義が表示される。`--check` / `--check-strict` でパラメータ検証が動作する。テストがパスする。
183
192
 
184
193
  ---
185
194
 
@@ -217,9 +226,11 @@ papycli/
217
226
  4. パラメータ名補完: `-q <TAB>` → クエリパラメータ名の候補
218
227
  5. enum 値補完: `-q status <TAB>` → `available pending sold` 等
219
228
  - `main.py` に `--completion-script <bash|zsh>` コマンド追加
229
+ - 補完候補の強化(`spec` コマンド、`summary --csv`、`--check`/`--check-strict` オプションを追加)
230
+ - Windows 対応: `_complete` の出力を binary stream(LF のみ)に変更
220
231
  - 手動テスト手順をドキュメント化
221
232
 
222
- **完了条件**: `eval "$(papycli config completion-script zsh)"` 後にタブ補完が動作する。メソッド・リソース・クエリパラメータ名・ボディパラメータ名・enum 値が補完候補として表示される。
233
+ **完了条件**: `eval "$(papycli config completion-script zsh)"` 後にタブ補完が動作する。メソッド・リソース・クエリパラメータ名・ボディパラメータ名・enum 値が補完候補として表示される。`spec`・`summary`・`--check`・`--check-strict` も補完候補に含まれる。
223
234
 
224
235
  ---
225
236
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "papycli"
3
- version = "0.5.1"
3
+ version = "0.5.3"
4
4
  description = "A CLI tool to call REST APIs defined in OpenAPI 3.0 specs"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -0,0 +1,126 @@
1
+ """リクエスト前のパラメータ検証ロジック。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Sequence
7
+ from typing import Any
8
+
9
+
10
+ def _check_value(param: dict[str, Any], raw_value: Any) -> list[str]:
11
+ """単一パラメータ値に対して型・enum チェックを行い、警告メッセージのリストを返す。
12
+
13
+ raw_value は文字列(-p/-q 経由)または JSON から解析された任意の値。
14
+ """
15
+ warnings: list[str] = []
16
+ name = param["name"]
17
+ ptype = param.get("type", "")
18
+ enum = param.get("enum")
19
+
20
+ if ptype == "integer":
21
+ # Note: int(str(x)) rejects float strings like "1.5" (even if mathematically integer-like).
22
+ # For -d JSON where raw_value may already be a float (e.g. 1.5), this is intentional:
23
+ # the spec declares the type as integer, so floating-point values trigger a warning.
24
+ try:
25
+ int(str(raw_value))
26
+ except ValueError:
27
+ warnings.append(
28
+ f"Warning: parameter '{name}' expects integer, got '{raw_value}'"
29
+ )
30
+ elif ptype == "boolean":
31
+ if str(raw_value).lower() not in {"true", "false", "1", "0"}:
32
+ warnings.append(
33
+ f"Warning: parameter '{name}' expects boolean (true/false), got '{raw_value}'"
34
+ )
35
+
36
+ if enum is not None and str(raw_value) not in [str(e) for e in enum]:
37
+ warnings.append(
38
+ f"Warning: parameter '{name}' value '{raw_value}' is not in enum {enum}"
39
+ )
40
+
41
+ return warnings
42
+
43
+
44
+ def check_request(
45
+ apidef: dict[str, Any],
46
+ method: str,
47
+ resource: str,
48
+ query_params: Sequence[tuple[str, str]],
49
+ body_params: Sequence[tuple[str, str]],
50
+ raw_body: str | None,
51
+ ) -> list[str]:
52
+ """リクエスト前に必須パラメータ・型・enum を検証し、警告メッセージのリストを返す。
53
+
54
+ 問題がなければ空リストを返す。
55
+ """
56
+ from papycli.api_call import match_path_template # lazy import to avoid circular dependency
57
+
58
+ warnings: list[str] = []
59
+
60
+ match = match_path_template(resource, list(apidef.keys()))
61
+ if match is None:
62
+ return warnings # パス不明は api_call 側でエラーになるのでここでは無視
63
+
64
+ template, _ = match
65
+ ops: list[dict[str, Any]] = apidef[template]
66
+ op = next((o for o in ops if o["method"] == method), None)
67
+ if op is None:
68
+ return warnings
69
+
70
+ # ---- クエリパラメータのチェック ----
71
+ q_params: list[dict[str, Any]] = op.get("query_parameters", [])
72
+ provided_query = {name for name, _ in query_params}
73
+
74
+ for p in q_params:
75
+ if p.get("required") and p["name"] not in provided_query:
76
+ warnings.append(f"Warning: required query parameter '{p['name']}' is missing")
77
+
78
+ for name, value in query_params:
79
+ param_def = next((p for p in q_params if p["name"] == name), None)
80
+ if param_def:
81
+ warnings.extend(_check_value(param_def, value))
82
+
83
+ # ---- ボディパラメータのチェック ----
84
+ b_params: list[dict[str, Any]] = op.get("post_parameters", [])
85
+
86
+ if raw_body is not None:
87
+ # -d JSON の場合: JSON をパースして各フィールドをチェック
88
+ can_check_body = True
89
+ try:
90
+ parsed = json.loads(raw_body)
91
+ except json.JSONDecodeError:
92
+ warnings.append("Warning: failed to parse raw body as JSON")
93
+ can_check_body = False
94
+
95
+ if can_check_body and not isinstance(parsed, dict):
96
+ warnings.append(
97
+ "Warning: raw body is not a JSON object; parameter checks skipped"
98
+ )
99
+ can_check_body = False
100
+
101
+ if not can_check_body:
102
+ return warnings
103
+
104
+ body_dict: dict[str, Any] = parsed
105
+ provided_body = set(body_dict.keys())
106
+ for p in b_params:
107
+ if p.get("required") and p["name"] not in provided_body:
108
+ warnings.append(f"Warning: required body parameter '{p['name']}' is missing")
109
+
110
+ for p in b_params:
111
+ if p["name"] in body_dict:
112
+ warnings.extend(_check_value(p, body_dict[p["name"]]))
113
+ else:
114
+ # -p ペアの場合
115
+ provided_body = {name for name, _ in body_params}
116
+
117
+ for p in b_params:
118
+ if p.get("required") and p["name"] not in provided_body:
119
+ warnings.append(f"Warning: required body parameter '{p['name']}' is missing")
120
+
121
+ for name, value in body_params:
122
+ param_def = next((p for p in b_params if p["name"] == name), None)
123
+ if param_def:
124
+ warnings.extend(_check_value(param_def, value))
125
+
126
+ return warnings
@@ -12,7 +12,7 @@ from typing import Any
12
12
 
13
13
  METHODS = ["get", "post", "put", "patch", "delete"]
14
14
  CONFIG_SUBCOMMANDS = ["add", "completion-script", "list", "remove", "use"]
15
- TOP_LEVEL_COMMANDS = METHODS + ["config", "summary"]
15
+ TOP_LEVEL_COMMANDS = METHODS + ["config", "spec", "summary"]
16
16
 
17
17
  # ---------------------------------------------------------------------------
18
18
  # シェルスクリプトテンプレート
@@ -140,6 +140,25 @@ def completions_for_context(
140
140
  return [c for c in CONFIG_SUBCOMMANDS if c.startswith(incomplete)]
141
141
  return []
142
142
 
143
+ # summary コマンドの補完
144
+ if words[1] == "summary":
145
+ if current == 2:
146
+ candidates: list[str] = []
147
+ if apidef is not None:
148
+ candidates = [p for p in sorted(apidef.keys()) if p.startswith(incomplete)]
149
+ if "--csv".startswith(incomplete):
150
+ candidates.append("--csv")
151
+ return candidates
152
+ if current == 3 and (len(words) <= 2 or words[2] != "--csv"):
153
+ return ["--csv"] if "--csv".startswith(incomplete) else []
154
+ return []
155
+
156
+ # spec コマンドの補完
157
+ if words[1] == "spec":
158
+ if current == 2 and apidef is not None:
159
+ return [p for p in sorted(apidef.keys()) if p.startswith(incomplete)]
160
+ return []
161
+
143
162
  # words[1] が HTTP メソッドでない場合は補完なし
144
163
  if words[1] not in METHODS:
145
164
  return []
@@ -177,7 +196,9 @@ def completions_for_context(
177
196
 
178
197
  # オプション名(エンドポイントのパラメータ有無に応じてフィルタリング)
179
198
  op = _find_op(apidef, method, resource)
180
- opts: list[str] = ["-q", "-p", "-d", "-H", "--summary", "-v", "--verbose"]
199
+ opts: list[str] = [
200
+ "-q", "-p", "-d", "-H", "--summary", "-v", "--verbose", "--check", "--check-strict",
201
+ ]
181
202
  if op is not None:
182
203
  if not op.get("query_parameters"):
183
204
  opts = [o for o in opts if o != "-q"]