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
219 operations: readonly ExtractedOperation[],219 operations: readonly ExtractedOperation[],
220): ToolDefinition[] => {220): ToolDefinition[] => {
221 const raw = operations.map((op, index) => {221 const raw = operations.map((op, index) => {
222 const operationId = op.operationId as string;222 const operationId = op.operationId;
223 const group = normalizeGroupSegment(op.tags[0]) ?? derivePathGroup(op.pathTemplate);223 const group = normalizeGroupSegment(op.tags[0]) ?? derivePathGroup(op.pathTemplate);
224 const leaf = deriveLeaf(operationId, op.method, op.pathTemplate, group);224 const leaf = deriveLeaf(operationId, op.method, op.pathTemplate, group);
225 const versionSegment = deriveVersionSegment(op.pathTemplate);225 const versionSegment = deriveVersionSegment(op.pathTemplate);
packages/plugins/openapi/src/sdk/non-json-body.test.ts+10−3
@@ -11,7 +11,7 @@ · 10 unmodified lines
11// ---------------------------------------------------------------------------11// ---------------------------------------------------------------------------
12 12
13import { describe, expect, it } from "@effect/vitest";13import { describe, expect, it } from "@effect/vitest";
14import { Effect } from "effect";14import { Effect, Schema } from "effect";
15import { FetchHttpClient } from "effect/unstable/http";15import { FetchHttpClient } from "effect/unstable/http";
16import { createServer } from "node:http";16import { createServer } from "node:http";
17import type { AddressInfo } from "node:net";17import type { AddressInfo } from "node:net";
@@ -28,6 +28,11 @@ import { openApiPlugin } from "./plugin"; · 10 unmodified lines
28 28
29const autoApprove: InvokeOptions = { onElicitation: "accept-all" };29const autoApprove: InvokeOptions = { onElicitation: "accept-all" };
30const TEST_SCOPE = "test-scope";30const TEST_SCOPE = "test-scope";
31const JsonNameBody = Schema.fromJsonString(
32 Schema.Struct({
33 name: Schema.String,
34 }),
35);
31 36
32const memoryProvider: SecretProvider = (() => {37const memoryProvider: SecretProvider = (() => {
33 const store = new Map<string, string>();38 const store = new Map<string, string>();
@@ -223,7 +228,7 @@ describe("OpenAPI non-JSON request body dispatch", () => { · 189 unmodified lines
223 expect(captured.contentType).toBe("text/xml");228 expect(captured.contentType).toBe("text/xml");
224 const body = captured.body.toString("utf8");229 const body = captured.body.toString("utf8");
225 expect(body).not.toBe("[object Object]");230 expect(body).not.toBe("[object Object]");
226 expect(JSON.parse(body)).toEqual({ name: "Acme" });231 expect(Schema.decodeUnknownSync(JsonNameBody)(body)).toEqual({ name: "Acme" });
227 }),232 }),
228 );233 );
229 234
@@ -383,7 +388,9 @@ describe("OpenAPI non-JSON request body dispatch", () => { · 153 unmodified lines
383 );388 );
384 389
385 expect(captured.contentType).toBe("application/json");390 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 });
387 }),394 }),
388 );395 );
389 396
packages/plugins/openapi/src/sdk/preview.ts+10−5
@@ -12,6 +12,10 @@ import { HttpMethod, ServerInfo, type ExtractionResult } from "./types"; · 11 unmodified lines
12 12
13/** Scopes declared by a flow: `{ scopeName: description }` */13/** Scopes declared by a flow: `{ scopeName: description }` */
14const OAuth2Scopes = Schema.Record(Schema.String, Schema.String);14const OAuth2Scopes = Schema.Record(Schema.String, Schema.String);
15const SecuritySchemeType = Schema.Literals(["http", "apiKey", "oauth2", "openIdConnect"]);
16type SecuritySchemeType = typeof SecuritySchemeType.Type;
17
18const decodeSecuritySchemeType = Schema.decodeUnknownOption(SecuritySchemeType);
15 19
16export class OAuth2AuthorizationCodeFlow extends Schema.Class<OAuth2AuthorizationCodeFlow>(20export class OAuth2AuthorizationCodeFlow extends Schema.Class<OAuth2AuthorizationCodeFlow>(
17 "OAuth2AuthorizationCodeFlow",21 "OAuth2AuthorizationCodeFlow",
@@ -43,7 +47,7 @@ export class SecurityScheme extends Schema.Class<SecurityScheme>("SecurityScheme · 25 unmodified lines
43 /** Key name in components.securitySchemes (e.g. "api_token") */47 /** Key name in components.securitySchemes (e.g. "api_token") */
44 name: Schema.String,48 name: Schema.String,
45 /** OpenAPI security scheme type */49 /** OpenAPI security scheme type */
46 type: Schema.Literals(["http", "apiKey", "oauth2", "openIdConnect"]),50 type: SecuritySchemeType,
47 /** For type: "http" — e.g. "bearer", "basic" */51 /** For type: "http" — e.g. "bearer", "basic" */
48 scheme: Schema.OptionFromOptional(Schema.String),52 scheme: Schema.OptionFromOptional(Schema.String),
49 /** For type: "http" with scheme "bearer" — e.g. "JWT" */53 /** For type: "http" with scheme "bearer" — e.g. "JWT" */
@@ -215,19 +219,20 @@ const extractSecuritySchemes = ( · 165 unmodified lines
215 if (!resolved || typeof resolved !== "object") return [];219 if (!resolved || typeof resolved !== "object") return [];
216 const scheme = resolved;220 const scheme = resolved;
217 221
218 const type = scheme.type as string;222 const type = decodeSecuritySchemeType(scheme.type);
219 if (!["http", "apiKey", "oauth2", "openIdConnect"].includes(type)) return [];223 if (Option.isNone(type)) return [];
224 const schemeType = type.value;
220 225
221 return [226 return [
222 new SecurityScheme({227 new SecurityScheme({
223 name,228 name,
224 type: type as "http" | "apiKey" | "oauth2" | "openIdConnect",229 type: schemeType,
225 scheme: Option.fromNullishOr(scheme.scheme as string | undefined),230 scheme: Option.fromNullishOr(scheme.scheme as string | undefined),
226 bearerFormat: Option.fromNullishOr(scheme.bearerFormat as string | undefined),231 bearerFormat: Option.fromNullishOr(scheme.bearerFormat as string | undefined),
227 in: Option.fromNullishOr(scheme.in as "header" | "query" | "cookie" | undefined),232 in: Option.fromNullishOr(scheme.in as "header" | "query" | "cookie" | undefined),
228 headerName: Option.fromNullishOr(scheme.name as string | undefined),233 headerName: Option.fromNullishOr(scheme.name as string | undefined),
229 description: Option.fromNullishOr(scheme.description as string | undefined),234 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(),
231 openIdConnectUrl: Option.fromNullishOr(236 openIdConnectUrl: Option.fromNullishOr(
232 scheme.openIdConnectUrl as string | undefined,237 scheme.openIdConnectUrl as string | undefined,
233 ),238 ),