Skip to content

Commit 0032e4b

Browse files
committed
refactor: extract pagination helpers and add input validation
CodeRabbit feedback on #113: - Add parseNonNegativeInt() Commander parser to helpers.ts — rejects negative numbers and non-integer input via InvalidArgumentError before the request reaches the server - Add buildPaginationParams() helper to centralize limit/offset → query param conversion across all 12 list commands - Replace inline parseInt + Record<string,string> construction with the new helpers in every command touched by this PR - Fix CHANGELOG PR suffix #112#113
1 parent 2ca3645 commit 0032e4b

26 files changed

Lines changed: 162 additions & 95 deletions

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
## [Unreleased]
99

1010
### 2026-04-27
11-
- **Feat**: ページング未対応だった `list` 系コマンドに `--limit` / `--offset` フラグを追加 — `types list`, `rules list`, `catalog datasets list`, `custom-data-models list`, `me policies list`, `me api-keys list`, `me oauth-clients list`, `admin tenants list`, `admin users list`, `admin api-keys list`, `admin oauth-clients list`, `admin policies list` で全件取得可能に (#112)
11+
- **Feat**: ページング未対応だった `list` 系コマンドに `--limit` / `--offset` フラグを追加 — `types list`, `rules list`, `catalog datasets list`, `custom-data-models list`, `me policies list`, `me api-keys list`, `me oauth-clients list`, `admin tenants list`, `admin users list`, `admin api-keys list`, `admin oauth-clients list`, `admin policies list` で全件取得可能に (#113)
12+
- **Refactor**: ページング処理を `helpers.ts``parseNonNegativeInt` / `buildPaginationParams` に共通化。負数や非整数を CLI 側で早期に弾くようバリデーションを追加 (#113)
1213

1314
## [0.13.0] - 2026-04-24
1415

src/commands/admin/api-keys.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Command } from "commander";
2-
import { withErrorHandler, createClient, resolveOptions, getFormat, outputResponse } from "../../helpers.js";
2+
import { withErrorHandler, createClient, resolveOptions, getFormat, outputResponse, parseNonNegativeInt, buildPaginationParams } from "../../helpers.js";
33
import { loadConfig, saveConfig } from "../../config.js";
44
import { parseJsonInput } from "../../input.js";
55
import { printApiKeyBox, printError } from "../../output.js";
@@ -109,17 +109,15 @@ export function registerApiKeysCommand(parent: Command): void {
109109
.command("list")
110110
.description("List all API keys, showing name, tenant, policy, and status (key values are masked)")
111111
.option("--tenant-id <id>", "Filter by tenant ID")
112-
.option("--limit <n>", "Maximum number of results", parseInt)
113-
.option("--offset <n>", "Skip N results", parseInt)
112+
.option("--limit <n>", "Maximum number of results", parseNonNegativeInt)
113+
.option("--offset <n>", "Skip N results", parseNonNegativeInt)
114114
.action(
115115
withErrorHandler(async (_opts: unknown, cmd: Command) => {
116116
const opts = cmd.opts() as { tenantId?: string; limit?: number; offset?: number };
117117
const client = createClient(cmd);
118118
const format = getFormat(cmd);
119-
const params: Record<string, string> = {};
119+
const params: Record<string, string> = buildPaginationParams(opts);
120120
if (opts.tenantId) params.tenantId = opts.tenantId;
121-
if (opts.limit !== undefined) params.limit = String(opts.limit);
122-
if (opts.offset !== undefined) params.offset = String(opts.offset);
123121
const response = await client.rawRequest("GET", "/admin/api-keys", {
124122
params,
125123
});

src/commands/admin/oauth-clients.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Command } from "commander";
2-
import { withErrorHandler, createClient, getFormat, outputResponse } from "../../helpers.js";
2+
import { withErrorHandler, createClient, getFormat, outputResponse, parseNonNegativeInt, buildPaginationParams } from "../../helpers.js";
33
import { parseJsonInput } from "../../input.js";
44
import { printSuccess } from "../../output.js";
55
import { addExamples } from "../help.js";
@@ -13,17 +13,13 @@ export function registerOAuthClientsCommand(parent: Command): void {
1313
const list = oauthClients
1414
.command("list")
1515
.description("List all registered OAuth clients and their configurations")
16-
.option("--limit <n>", "Maximum number of results", parseInt)
17-
.option("--offset <n>", "Skip N results", parseInt)
16+
.option("--limit <n>", "Maximum number of results", parseNonNegativeInt)
17+
.option("--offset <n>", "Skip N results", parseNonNegativeInt)
1818
.action(
1919
withErrorHandler(async (_opts: unknown, cmd: Command) => {
2020
const client = createClient(cmd);
2121
const format = getFormat(cmd);
22-
const cmdOpts = cmd.opts();
23-
24-
const params: Record<string, string> = {};
25-
if (cmdOpts.limit !== undefined) params["limit"] = String(cmdOpts.limit);
26-
if (cmdOpts.offset !== undefined) params["offset"] = String(cmdOpts.offset);
22+
const params = buildPaginationParams(cmd.opts());
2723

2824
const response = await client.rawRequest("GET", "/admin/oauth-clients", { params });
2925
outputResponse(response, format);

src/commands/admin/policies.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Command } from "commander";
2-
import { withErrorHandler, createClient, getFormat, outputResponse } from "../../helpers.js";
2+
import { withErrorHandler, createClient, getFormat, outputResponse, parseNonNegativeInt, buildPaginationParams } from "../../helpers.js";
33
import { parseJsonInput } from "../../input.js";
44
import { printSuccess } from "../../output.js";
55
import { addExamples } from "../help.js";
@@ -13,17 +13,13 @@ export function registerPoliciesCommand(parent: Command): void {
1313
const list = policies
1414
.command("list")
1515
.description("List all access control policies, showing their status and priority")
16-
.option("--limit <n>", "Maximum number of results", parseInt)
17-
.option("--offset <n>", "Skip N results", parseInt)
16+
.option("--limit <n>", "Maximum number of results", parseNonNegativeInt)
17+
.option("--offset <n>", "Skip N results", parseNonNegativeInt)
1818
.action(
1919
withErrorHandler(async (_opts: unknown, cmd: Command) => {
2020
const client = createClient(cmd);
2121
const format = getFormat(cmd);
22-
const cmdOpts = cmd.opts();
23-
24-
const params: Record<string, string> = {};
25-
if (cmdOpts.limit !== undefined) params["limit"] = String(cmdOpts.limit);
26-
if (cmdOpts.offset !== undefined) params["offset"] = String(cmdOpts.offset);
22+
const params = buildPaginationParams(cmd.opts());
2723

2824
const response = await client.rawRequest("GET", "/admin/policies", { params });
2925
outputResponse(response, format);

src/commands/admin/tenants.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Command } from "commander";
2-
import { withErrorHandler, createClient, getFormat, outputResponse } from "../../helpers.js";
2+
import { withErrorHandler, createClient, getFormat, outputResponse, parseNonNegativeInt, buildPaginationParams } from "../../helpers.js";
33
import { parseJsonInput } from "../../input.js";
44
import { printSuccess } from "../../output.js";
55
import { addExamples } from "../help.js";
@@ -13,17 +13,13 @@ export function registerTenantsCommand(parent: Command): void {
1313
const list = tenants
1414
.command("list")
1515
.description("List all tenants in the system, including their status and configuration")
16-
.option("--limit <n>", "Maximum number of results", parseInt)
17-
.option("--offset <n>", "Skip N results", parseInt)
16+
.option("--limit <n>", "Maximum number of results", parseNonNegativeInt)
17+
.option("--offset <n>", "Skip N results", parseNonNegativeInt)
1818
.action(
1919
withErrorHandler(async (_opts: unknown, cmd: Command) => {
2020
const client = createClient(cmd);
2121
const format = getFormat(cmd);
22-
const cmdOpts = cmd.opts();
23-
24-
const params: Record<string, string> = {};
25-
if (cmdOpts.limit !== undefined) params["limit"] = String(cmdOpts.limit);
26-
if (cmdOpts.offset !== undefined) params["offset"] = String(cmdOpts.offset);
22+
const params = buildPaginationParams(cmd.opts());
2723

2824
const response = await client.rawRequest("GET", "/admin/tenants", { params });
2925
outputResponse(response, format);

src/commands/admin/users.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Command } from "commander";
2-
import { withErrorHandler, createClient, getFormat, outputResponse } from "../../helpers.js";
2+
import { withErrorHandler, createClient, getFormat, outputResponse, parseNonNegativeInt, buildPaginationParams } from "../../helpers.js";
33
import { parseJsonInput } from "../../input.js";
44
import { printSuccess } from "../../output.js";
55
import { addExamples } from "../help.js";
@@ -13,17 +13,13 @@ export function registerUsersCommand(parent: Command): void {
1313
const list = users
1414
.command("list")
1515
.description("List all users across tenants, showing email, role, and status")
16-
.option("--limit <n>", "Maximum number of results", parseInt)
17-
.option("--offset <n>", "Skip N results", parseInt)
16+
.option("--limit <n>", "Maximum number of results", parseNonNegativeInt)
17+
.option("--offset <n>", "Skip N results", parseNonNegativeInt)
1818
.action(
1919
withErrorHandler(async (_opts: unknown, cmd: Command) => {
2020
const client = createClient(cmd);
2121
const format = getFormat(cmd);
22-
const cmdOpts = cmd.opts();
23-
24-
const params: Record<string, string> = {};
25-
if (cmdOpts.limit !== undefined) params["limit"] = String(cmdOpts.limit);
26-
if (cmdOpts.offset !== undefined) params["offset"] = String(cmdOpts.offset);
22+
const params = buildPaginationParams(cmd.opts());
2723

2824
const response = await client.rawRequest("GET", "/admin/users", { params });
2925
outputResponse(response, format);

src/commands/catalog.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Command } from "commander";
2-
import { withErrorHandler, createClient, getFormat, outputResponse } from "../helpers.js";
2+
import { withErrorHandler, createClient, getFormat, outputResponse, parseNonNegativeInt, buildPaginationParams } from "../helpers.js";
33
import { addExamples } from "./help.js";
44

55
export function registerCatalogCommand(program: Command): void {
@@ -40,17 +40,13 @@ export function registerCatalogCommand(program: Command): void {
4040
const datasetsList = datasets
4141
.command("list")
4242
.description("List all datasets published in the catalog")
43-
.option("--limit <n>", "Maximum number of results", parseInt)
44-
.option("--offset <n>", "Skip N results", parseInt)
43+
.option("--limit <n>", "Maximum number of results", parseNonNegativeInt)
44+
.option("--offset <n>", "Skip N results", parseNonNegativeInt)
4545
.action(
4646
withErrorHandler(async (_opts: unknown, cmd: Command) => {
4747
const client = createClient(cmd);
4848
const format = getFormat(cmd);
49-
const cmdOpts = cmd.opts();
50-
51-
const params: Record<string, string> = {};
52-
if (cmdOpts.limit !== undefined) params["limit"] = String(cmdOpts.limit);
53-
if (cmdOpts.offset !== undefined) params["offset"] = String(cmdOpts.offset);
49+
const params = buildPaginationParams(cmd.opts());
5450

5551
const response = await client.rawRequest("GET", "/catalog/datasets", { params });
5652
outputResponse(response, format);

src/commands/me-api-keys.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Command } from "commander";
2-
import { withErrorHandler, createClient, resolveOptions, getFormat, outputResponse } from "../helpers.js";
2+
import { withErrorHandler, createClient, resolveOptions, getFormat, outputResponse, parseNonNegativeInt, buildPaginationParams } from "../helpers.js";
33
import { loadConfig, saveConfig } from "../config.js";
44
import { parseJsonInput } from "../input.js";
55
import { printApiKeyBox, printError } from "../output.js";
@@ -66,17 +66,13 @@ export function addMeApiKeysSubcommand(me: Command): void {
6666
const list = apiKeys
6767
.command("list")
6868
.description("List your API keys")
69-
.option("--limit <n>", "Maximum number of results", parseInt)
70-
.option("--offset <n>", "Skip N results", parseInt)
69+
.option("--limit <n>", "Maximum number of results", parseNonNegativeInt)
70+
.option("--offset <n>", "Skip N results", parseNonNegativeInt)
7171
.action(
7272
withErrorHandler(async (_opts: unknown, cmd: Command) => {
7373
const client = createClient(cmd);
7474
const format = getFormat(cmd);
75-
const cmdOpts = cmd.opts();
76-
77-
const params: Record<string, string> = {};
78-
if (cmdOpts.limit !== undefined) params["limit"] = String(cmdOpts.limit);
79-
if (cmdOpts.offset !== undefined) params["offset"] = String(cmdOpts.offset);
75+
const params = buildPaginationParams(cmd.opts());
8076

8177
const response = await client.rawRequest("GET", "/me/api-keys", { params });
8278
response.data = cleanApiKeyData(response.data);

src/commands/me-oauth-clients.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Command } from "commander";
2-
import { withErrorHandler, createClient, resolveOptions, getFormat, outputResponse } from "../helpers.js";
2+
import { withErrorHandler, createClient, resolveOptions, getFormat, outputResponse, parseNonNegativeInt, buildPaginationParams } from "../helpers.js";
33
import { loadConfig, saveConfig, validateUrl } from "../config.js";
44
import { parseJsonInput } from "../input.js";
55
import { printSuccess, printError, printInfo, printWarning } from "../output.js";
@@ -15,17 +15,13 @@ export function addMeOAuthClientsSubcommand(me: Command): void {
1515
const list = oauthClients
1616
.command("list")
1717
.description("List your OAuth clients")
18-
.option("--limit <n>", "Maximum number of results", parseInt)
19-
.option("--offset <n>", "Skip N results", parseInt)
18+
.option("--limit <n>", "Maximum number of results", parseNonNegativeInt)
19+
.option("--offset <n>", "Skip N results", parseNonNegativeInt)
2020
.action(
2121
withErrorHandler(async (_opts: unknown, cmd: Command) => {
2222
const client = createClient(cmd);
2323
const format = getFormat(cmd);
24-
const cmdOpts = cmd.opts();
25-
26-
const params: Record<string, string> = {};
27-
if (cmdOpts.limit !== undefined) params["limit"] = String(cmdOpts.limit);
28-
if (cmdOpts.offset !== undefined) params["offset"] = String(cmdOpts.offset);
24+
const params = buildPaginationParams(cmd.opts());
2925

3026
const response = await client.rawRequest("GET", "/me/oauth-clients", { params });
3127
outputResponse(response, format);

src/commands/me-policies.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Command } from "commander";
2-
import { withErrorHandler, createClient, getFormat, outputResponse } from "../helpers.js";
2+
import { withErrorHandler, createClient, getFormat, outputResponse, parseNonNegativeInt, buildPaginationParams } from "../helpers.js";
33
import { parseJsonInput } from "../input.js";
44
import { printSuccess } from "../output.js";
55
import { addExamples, addNotes } from "./help.js";
@@ -13,17 +13,13 @@ export function addMePoliciesSubcommand(me: Command): void {
1313
const list = policies
1414
.command("list")
1515
.description("List your personal policies")
16-
.option("--limit <n>", "Maximum number of results", parseInt)
17-
.option("--offset <n>", "Skip N results", parseInt)
16+
.option("--limit <n>", "Maximum number of results", parseNonNegativeInt)
17+
.option("--offset <n>", "Skip N results", parseNonNegativeInt)
1818
.action(
1919
withErrorHandler(async (_opts: unknown, cmd: Command) => {
2020
const client = createClient(cmd);
2121
const format = getFormat(cmd);
22-
const cmdOpts = cmd.opts();
23-
24-
const params: Record<string, string> = {};
25-
if (cmdOpts.limit !== undefined) params["limit"] = String(cmdOpts.limit);
26-
if (cmdOpts.offset !== undefined) params["offset"] = String(cmdOpts.offset);
22+
const params = buildPaginationParams(cmd.opts());
2723

2824
const response = await client.rawRequest("GET", "/me/policies", { params });
2925
outputResponse(response, format);

0 commit comments

Comments
 (0)