diff --git a/packages/plugins/openapi/src/sdk/definitions.ts b/packages/plugins/openapi/src/sdk/definitions.ts index 68a11ec2e..7f9a54cca 100644 --- a/packages/plugins/openapi/src/sdk/definitions.ts +++ b/packages/plugins/openapi/src/sdk/definitions.ts @@ -219,7 +219,7 @@ export const compileToolDefinitions = ( operations: readonly ExtractedOperation[], ): ToolDefinition[] => { const raw = operations.map((op, index) => { - const operationId = op.operationId as string; + const operationId = op.operationId; const group = normalizeGroupSegment(op.tags[0]) ?? derivePathGroup(op.pathTemplate); const leaf = deriveLeaf(operationId, op.method, op.pathTemplate, group); const versionSegment = deriveVersionSegment(op.pathTemplate); diff --git a/packages/plugins/openapi/src/sdk/non-json-body.test.ts b/packages/plugins/openapi/src/sdk/non-json-body.test.ts index f42067d23..7c0b70cd7 100644 --- a/packages/plugins/openapi/src/sdk/non-json-body.test.ts +++ b/packages/plugins/openapi/src/sdk/non-json-body.test.ts @@ -11,7 +11,7 @@ // --------------------------------------------------------------------------- import { describe, expect, it } from "@effect/vitest"; -import { Effect } from "effect"; +import { Effect, Schema } from "effect"; import { FetchHttpClient } from "effect/unstable/http"; import { createServer } from "node:http"; import type { AddressInfo } from "node:net"; @@ -28,6 +28,11 @@ import { openApiPlugin } from "./plugin"; const autoApprove: InvokeOptions = { onElicitation: "accept-all" }; const TEST_SCOPE = "test-scope"; +const JsonNameBody = Schema.fromJsonString( + Schema.Struct({ + name: Schema.String, + }), +); const memoryProvider: SecretProvider = (() => { const store = new Map(); @@ -223,7 +228,7 @@ describe("OpenAPI non-JSON request body dispatch", () => { expect(captured.contentType).toBe("text/xml"); const body = captured.body.toString("utf8"); expect(body).not.toBe("[object Object]"); - expect(JSON.parse(body)).toEqual({ name: "Acme" }); + expect(Schema.decodeUnknownSync(JsonNameBody)(body)).toEqual({ name: "Acme" }); }), ); @@ -383,7 +388,9 @@ describe("OpenAPI non-JSON request body dispatch", () => { ); expect(captured.contentType).toBe("application/json"); - expect(JSON.parse(captured.body.toString("utf8"))).toEqual({ name: "Acme" }); + expect(Schema.decodeUnknownSync(JsonNameBody)(captured.body.toString("utf8"))).toEqual({ + name: "Acme", + }); }), ); diff --git a/packages/plugins/openapi/src/sdk/preview.ts b/packages/plugins/openapi/src/sdk/preview.ts index bdbfd1030..8d69b13b7 100644 --- a/packages/plugins/openapi/src/sdk/preview.ts +++ b/packages/plugins/openapi/src/sdk/preview.ts @@ -12,6 +12,10 @@ import { HttpMethod, ServerInfo, type ExtractionResult } from "./types"; /** Scopes declared by a flow: `{ scopeName: description }` */ const OAuth2Scopes = Schema.Record(Schema.String, Schema.String); +const SecuritySchemeType = Schema.Literals(["http", "apiKey", "oauth2", "openIdConnect"]); +type SecuritySchemeType = typeof SecuritySchemeType.Type; + +const decodeSecuritySchemeType = Schema.decodeUnknownOption(SecuritySchemeType); export class OAuth2AuthorizationCodeFlow extends Schema.Class( "OAuth2AuthorizationCodeFlow", @@ -43,7 +47,7 @@ export class SecurityScheme extends Schema.Class("SecurityScheme /** Key name in components.securitySchemes (e.g. "api_token") */ name: Schema.String, /** OpenAPI security scheme type */ - type: Schema.Literals(["http", "apiKey", "oauth2", "openIdConnect"]), + type: SecuritySchemeType, /** For type: "http" — e.g. "bearer", "basic" */ scheme: Schema.OptionFromOptional(Schema.String), /** For type: "http" with scheme "bearer" — e.g. "JWT" */ @@ -215,19 +219,20 @@ const extractSecuritySchemes = ( if (!resolved || typeof resolved !== "object") return []; const scheme = resolved; - const type = scheme.type as string; - if (!["http", "apiKey", "oauth2", "openIdConnect"].includes(type)) return []; + const type = decodeSecuritySchemeType(scheme.type); + if (Option.isNone(type)) return []; + const schemeType = type.value; return [ new SecurityScheme({ name, - type: type as "http" | "apiKey" | "oauth2" | "openIdConnect", + type: schemeType, scheme: Option.fromNullishOr(scheme.scheme as string | undefined), bearerFormat: Option.fromNullishOr(scheme.bearerFormat as string | undefined), in: Option.fromNullishOr(scheme.in as "header" | "query" | "cookie" | undefined), headerName: Option.fromNullishOr(scheme.name as string | undefined), description: Option.fromNullishOr(scheme.description as string | undefined), - flows: type === "oauth2" ? extractFlows(scheme.flows) : Option.none(), + flows: schemeType === "oauth2" ? extractFlows(scheme.flows) : Option.none(), openIdConnectUrl: Option.fromNullishOr( scheme.openIdConnectUrl as string | undefined, ),