History

Use schema in OpenAPI preview boundaries

Rhys Sullivan committed 6 months ago

Commit e9fd11a1 parent b039b6e3 files · +21 −9Browse files

Unified · Split

3 files
  1. packages/plugins/openapi/src/sdk/definitions.ts +1 −1
  2. packages/plugins/openapi/src/sdk/non-json-body.test.ts +10 −3
  3. packages/plugins/openapi/src/sdk/preview.ts +10 −5
packages/plugins/openapi/src/sdk/definitions.ts+1−1
@@ -219,7 +219,7 @@ export const compileToolDefinitions = ( · 218 unmodified lines
219219 operations: readonly ExtractedOperation[],
220220 ): ToolDefinition[] => {
221221 const raw = operations.map((op, index) => {
222− const operationId = op.operationId as string;
222+ const operationId = op.operationId;
223223 const group = normalizeGroupSegment(op.tags[0]) ?? derivePathGroup(op.pathTemplate);
224224 const leaf = deriveLeaf(operationId, op.method, op.pathTemplate, group);
225225 const versionSegment = deriveVersionSegment(op.pathTemplate);
packages/plugins/openapi/src/sdk/non-json-body.test.ts+10−3
@@ -11,7 +11,7 @@ · 10 unmodified lines
1111 // ---------------------------------------------------------------------------
1212
1313 import { describe, expect, it } from "@effect/vitest";
14−import { Effect } from "effect";
14+import { Effect, Schema } from "effect";
1515 import { FetchHttpClient } from "effect/unstable/http";
1616 import { createServer } from "node:http";
1717 import type { AddressInfo } from "node:net";
@@ -28,6 +28,11 @@ import { openApiPlugin } from "./plugin"; · 10 unmodified lines
2828
2929 const autoApprove: InvokeOptions = { onElicitation: "accept-all" };
3030 const TEST_SCOPE = "test-scope";
31+const JsonNameBody = Schema.fromJsonString(
32+ Schema.Struct({
33+ name: Schema.String,
34+ }),
35+);
3136
3237 const memoryProvider: SecretProvider = (() => {
3338 const store = new Map<string, string>();
@@ -223,7 +228,7 @@ describe("OpenAPI non-JSON request body dispatch", () => { · 189 unmodified lines
223228 expect(captured.contentType).toBe("text/xml");
224229 const body = captured.body.toString("utf8");
225230 expect(body).not.toBe("[object Object]");
226− expect(JSON.parse(body)).toEqual({ name: "Acme" });
231+ expect(Schema.decodeUnknownSync(JsonNameBody)(body)).toEqual({ name: "Acme" });
227232 }),
228233 );
229234
@@ -383,7 +388,9 @@ describe("OpenAPI non-JSON request body dispatch", () => { · 153 unmodified lines
383388 );
384389
385390 expect(captured.contentType).toBe("application/json");
386− expect(JSON.parse(captured.body.toString("utf8"))).toEqual({ name: "Acme" });
391+ expect(Schema.decodeUnknownSync(JsonNameBody)(captured.body.toString("utf8"))).toEqual({
392+ name: "Acme",
393+ });
387394 }),
388395 );
389396
packages/plugins/openapi/src/sdk/preview.ts+10−5
@@ -12,6 +12,10 @@ import { HttpMethod, ServerInfo, type ExtractionResult } from "./types"; · 11 unmodified lines
1212
1313 /** Scopes declared by a flow: `{ scopeName: description }` */
1414 const OAuth2Scopes = Schema.Record(Schema.String, Schema.String);
15+const SecuritySchemeType = Schema.Literals(["http", "apiKey", "oauth2", "openIdConnect"]);
16+type SecuritySchemeType = typeof SecuritySchemeType.Type;
17+
18+const decodeSecuritySchemeType = Schema.decodeUnknownOption(SecuritySchemeType);
1519
1620 export class OAuth2AuthorizationCodeFlow extends Schema.Class<OAuth2AuthorizationCodeFlow>(
1721 "OAuth2AuthorizationCodeFlow",
@@ -43,7 +47,7 @@ export class SecurityScheme extends Schema.Class<SecurityScheme>("SecurityScheme · 25 unmodified lines
4347 /** Key name in components.securitySchemes (e.g. "api_token") */
4448 name: Schema.String,
4549 /** OpenAPI security scheme type */
46− type: Schema.Literals(["http", "apiKey", "oauth2", "openIdConnect"]),
50+ type: SecuritySchemeType,
4751 /** For type: "http" — e.g. "bearer", "basic" */
4852 scheme: Schema.OptionFromOptional(Schema.String),
4953 /** For type: "http" with scheme "bearer" — e.g. "JWT" */
@@ -215,19 +219,20 @@ const extractSecuritySchemes = ( · 165 unmodified lines
215219 if (!resolved || typeof resolved !== "object") return [];
216220 const scheme = resolved;
217221
218− const type = scheme.type as string;
219− if (!["http", "apiKey", "oauth2", "openIdConnect"].includes(type)) return [];
222+ const type = decodeSecuritySchemeType(scheme.type);
223+ if (Option.isNone(type)) return [];
224+ const schemeType = type.value;
220225
221226 return [
222227 new SecurityScheme({
223228 name,
224− type: type as "http" | "apiKey" | "oauth2" | "openIdConnect",
229+ type: schemeType,
225230 scheme: Option.fromNullishOr(scheme.scheme as string | undefined),
226231 bearerFormat: Option.fromNullishOr(scheme.bearerFormat as string | undefined),
227232 in: Option.fromNullishOr(scheme.in as "header" | "query" | "cookie" | undefined),
228233 headerName: Option.fromNullishOr(scheme.name as string | undefined),
229234 description: Option.fromNullishOr(scheme.description as string | undefined),
230− flows: type === "oauth2" ? extractFlows(scheme.flows) : Option.none(),
235+ flows: schemeType === "oauth2" ? extractFlows(scheme.flows) : Option.none(),
231236 openIdConnectUrl: Option.fromNullishOr(
232237 scheme.openIdConnectUrl as string | undefined,
233238 ),