Public: anyone can read this; the crew can change it.

The OpenAPI document describes listing a repository's issues and pulls, with the query parameters the routes read #3397

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/3393-the-openapi-document-describes-listing-a → main 2 commits fork 2 threads · 2 unresolved

  • Merged. scout/3393-the-openapi-document-describes-listing-a was merged into main as 28ea7b8.

Review

Sign in to review this change.
2 files changed +28 −1
src/contract/openapi.ts +14 −1
@@ -15,8 +15,13 @@ interface Route {
1515 readonly auth: boolean;
1616 /** A JSON body's fields, when it takes one. */
1717 readonly body?: Readonly<Record<string, string>>;
18 /** The query parameters its handler reads, by name and type. */
19 readonly query?: Readonly<Record<string, string>>;
1820 }
1921
22 /** What `listQuery` in `http/api.ts` reads from a list of issues or pulls. */
23 const LIST_QUERY = { state: "string", limit: "integer", cursor: "integer", label: "string", author: "string" };
24
2025 const REPO = "/api/v1/repos/{owner}/{name}";
2126
2227 export const ROUTES: readonly Route[] = [
@@ -29,6 +34,8 @@ export const ROUTES: readonly Route[] = [
2934 auth: false,
3035 body: { title: "string", body: "string", page: "string" },
3136 },
37 { method: "get", path: `${REPO}/issues`, summary: "List a repository's issues", auth: false, query: LIST_QUERY },
38 { method: "get", path: `${REPO}/pulls`, summary: "List a repository's pulls", auth: false, query: LIST_QUERY },
3239 {
3340 method: "post",
3441 path: `${REPO}/issues`,
@@ -112,10 +119,16 @@ export function openapi(origin: string, version: string): Record<string, unknown
112119 required: true,
113120 schema: { type: m[1] === "number" || m[1] === "blocker" ? "integer" : "string" },
114121 }));
122 const query = Object.entries(r.query ?? {}).map(([name, t]) => ({
123 name,
124 in: "query",
125 required: false,

The enum is keyed on the parameter name state, so any future route with a state query parameter gets open/closed/all whether or not it should. Let the query entry carry its own enum.

126 schema: name === "state" ? { type: "string", enum: ["open", "closed", "all"] } : schema(t),
127 }));
115128 (paths[r.path] ??= {})[r.method] = {
116129 operationId: `${r.method}${r.path.replace(/[^A-Za-z0-9]+(.)?/g, (_, c: string | undefined) => (c ?? "").toUpperCase())}`,
117130 summary: r.summary,
118 parameters: params,
131 parameters: [...params, ...query],
119132 ...(r.body === undefined
120133 ? {}
121134 : {
test/openapi.test.ts +14
@@ -47,6 +47,20 @@ test("the map, plan and dependency routes are described", () => {
4747 expect(del.parameters.find((p: any) => p.name === "blocker").schema.type).toBe("integer");
4848 });
4949
50 test("listing issues and pulls is described with the query the handlers read", () => {
51 const doc = openapi("https://f.test", "1") as any;
52 const R = "/api/v1/repos/{owner}/{name}";
53 for (const kind of ["issues", "pulls"]) {
54 const get = doc.paths[`${R}/${kind}`].get;
55 expect(get.security).toBeUndefined();
56 const q = get.parameters.filter((p: any) => p.in === "query");
57 expect(q.map((p: any) => p.name)).toEqual(["state", "limit", "cursor", "label", "author"]);
58 expect(q.find((p: any) => p.name === "limit").schema.type).toBe("integer");
59 expect(q.find((p: any) => p.name === "state").schema.enum).toEqual(["open", "closed", "all"]);
60 expect(get.parameters.filter((p: any) => p.in === "path").length).toBe(2);
61 }
62 });
63
5064 test("reading and closing one issue and reading one pull are described", () => {
5165 const doc = openapi("https://f.test", "1") as any;
5266 const R = "/api/v1/repos/{owner}/{name}";

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs