Replace AuthConfig union (NoAuth/BearerAuth/ApiKeyAuth) with plain headers on InvocationConfig. Auth is just headers now — simpler and covers all cases. New previewSpec(): - Reuses parse() + extract() for heavy lifting - Extracts security schemes from components.securitySchemes - Derives auth strategies from top-level security array - Builds header presets for the frontend (which headers to show, which are secrets) - Returns metadata: title, version, servers, operation count, tags Tested against Cloudflare spec: 4 security schemes, 3 auth strategies, correct header presets (Bearer Token, API Key + Email, etc.) 19 tests passing, all typechecks clean.
Unified · Split
| @@ -10,6 +10,13 @@ export { · 9 unmodified lines | ||
| 10 | 10 | makeInMemoryOperationStore, |
| 11 | 11 | type OpenApiOperationStore, |
| 12 | 12 | } from "./operation-store"; |
| 13 | +export { | |
| 14 | + previewSpec, | |
| 15 | + SecurityScheme, | |
| 16 | + AuthStrategy, | |
| 17 | + HeaderPreset, | |
| 18 | + SpecPreview, | |
| 19 | +} from "./preview"; | |
| 13 | 20 | export { DocResolver, resolveBaseUrl, preferredContent } from "./openapi-utils"; |
| 14 | 21 | |
| 15 | 22 | export { |
| @@ -19,10 +26,6 @@ export { · 3 unmodified lines | ||
| 19 | 26 | } from "./errors"; |
| 20 | 27 | |
| 21 | 28 | export { |
| 22 | − AuthConfig, | |
| 23 | − NoAuth, | |
| 24 | − BearerAuth, | |
| 25 | − ApiKeyAuth, | |
| 26 | 29 | ExtractedOperation, |
| 27 | 30 | ExtractionResult, |
| 28 | 31 | InvocationConfig, |
| @@ -1,4 +1,4 @@ | ||
| 1 | −import { Effect, Layer, Match, Option } from "effect"; | |
| 1 | +import { Effect, Layer, Option } from "effect"; | |
| 2 | 2 | import { |
| 3 | 3 | HttpClient, |
| 4 | 4 | HttpClientRequest, |
| @@ -14,7 +14,6 @@ import { · 9 unmodified lines | ||
| 14 | 14 | import { OpenApiInvocationError } from "./errors"; |
| 15 | 15 | import type { OpenApiOperationStore } from "./operation-store"; |
| 16 | 16 | import { |
| 17 | − type AuthConfig, | |
| 18 | 17 | type OperationBinding, |
| 19 | 18 | InvocationConfig, |
| 20 | 19 | InvocationResult, |
| @@ -117,35 +116,19 @@ const resolvePath = Effect.fn("OpenApi.resolvePath")(function* ( · 96 unmodified lines | ||
| 117 | 116 | }); |
| 118 | 117 | |
| 119 | 118 | // --------------------------------------------------------------------------- |
| 120 | −// Auth application | |
| 119 | +// Header application | |
| 121 | 120 | // --------------------------------------------------------------------------- |
| 122 | 121 | |
| 123 | −const applyAuth = ( | |
| 122 | +const applyHeaders = ( | |
| 124 | 123 | request: HttpClientRequest.HttpClientRequest, |
| 125 | − auth: AuthConfig, | |
| 126 | −): HttpClientRequest.HttpClientRequest => | |
| 127 | − Match.valueTags(auth, { | |
| 128 | − NoAuth: () => request, | |
| 129 | − BearerAuth: ({ token, headerName, prefix }) => | |
| 130 | − HttpClientRequest.setHeader(request, headerName, `${prefix}${token}`), | |
| 131 | − ApiKeyAuth: ({ name, value, in: location }) => { | |
| 132 | − if (location === "header") { | |
| 133 | − return HttpClientRequest.setHeader(request, name, value); | |
| 134 | − } | |
| 135 | − if (location === "query") { | |
| 136 | − return HttpClientRequest.setUrlParam(request, name, value); | |
| 137 | − } | |
| 138 | − // cookie | |
| 139 | − const existing = | |
| 140 | − request.headers["cookie"] ?? ""; | |
| 141 | − const cookie = `${name}=${encodeURIComponent(value)}`; | |
| 142 | − return HttpClientRequest.setHeader( | |
| 143 | − request, | |
| 144 | − "cookie", | |
| 145 | − existing ? `${existing}; ${cookie}` : cookie, | |
| 146 | − ); | |
| 147 | − }, | |
| 148 | − }); | |
| 124 | + headers: Record<string, string>, | |
| 125 | +): HttpClientRequest.HttpClientRequest => { | |
| 126 | + let req = request; | |
| 127 | + for (const [name, value] of Object.entries(headers)) { | |
| 128 | + req = HttpClientRequest.setHeader(req, name, value); | |
| 129 | + } | |
| 130 | + return req; | |
| 131 | +}; | |
| 149 | 132 | |
| 150 | 133 | // --------------------------------------------------------------------------- |
| 151 | 134 | // Response helpers |
| @@ -221,8 +204,8 @@ export const invoke = Effect.fn("OpenApi.invoke")(function* ( · 69 unmodified lines | ||
| 221 | 204 | } |
| 222 | 205 | } |
| 223 | 206 | |
| 224 | − // Auth | |
| 225 | − request = applyAuth(request, config.auth); | |
| 207 | + // Static headers (auth, custom headers, etc.) | |
| 208 | + request = applyHeaders(request, config.headers); | |
| 226 | 209 | |
| 227 | 210 | // Execute |
| 228 | 211 | const response = yield* client.execute(request).pipe( |
| @@ -78,6 +78,26 @@ const TestLayer = HttpApiBuilder.serve().pipe( · 77 unmodified lines | ||
| 78 | 78 | // --------------------------------------------------------------------------- |
| 79 | 79 | |
| 80 | 80 | layer(TestLayer)("OpenAPI Plugin", (it) => { |
| 81 | + it.effect("previewSpec returns metadata and header presets", () => | |
| 82 | + Effect.gen(function* () { | |
| 83 | + const httpClient = yield* HttpClient.HttpClient; | |
| 84 | + const clientLayer = Layer.succeed(HttpClient.HttpClient, httpClient); | |
| 85 | + | |
| 86 | + const executor = yield* createExecutor( | |
| 87 | + makeTestConfig({ | |
| 88 | + plugins: [ | |
| 89 | + openApiPlugin({ httpClientLayer: clientLayer }), | |
| 90 | + ] as const, | |
| 91 | + }), | |
| 92 | + ); | |
| 93 | + | |
| 94 | + const preview = yield* executor.openapi.previewSpec(specJson); | |
| 95 | + | |
| 96 | + expect(preview.operationCount).toBeGreaterThanOrEqual(2); | |
| 97 | + expect(preview.servers).toBeDefined(); | |
| 98 | + }), | |
| 99 | + ); | |
| 100 | + | |
| 81 | 101 | it.effect("registers tools from an OpenAPI spec", () => |
| 82 | 102 | Effect.gen(function* () { |
| 83 | 103 | const httpClient = yield* HttpClient.HttpClient; |
| @@ -18,11 +18,10 @@ import { · 17 unmodified lines | ||
| 18 | 18 | makeInMemoryOperationStore, |
| 19 | 19 | type OpenApiOperationStore, |
| 20 | 20 | } from "./operation-store"; |
| 21 | +import { previewSpec, type SpecPreview } from "./preview"; | |
| 21 | 22 | import { |
| 22 | − type AuthConfig, | |
| 23 | 23 | type ExtractedOperation, |
| 24 | 24 | InvocationConfig, |
| 25 | − NoAuth, | |
| 26 | 25 | OperationBinding, |
| 27 | 26 | } from "./types"; |
| 28 | 27 | |
| @@ -34,7 +33,8 @@ export interface OpenApiSpecConfig { · 5 unmodified lines | ||
| 34 | 33 | readonly spec: string; |
| 35 | 34 | readonly baseUrl?: string; |
| 36 | 35 | readonly namespace?: string; |
| 37 | − readonly auth?: AuthConfig; | |
| 36 | + /** Static headers applied to every request for this spec */ | |
| 37 | + readonly headers?: Record<string, string>; | |
| 38 | 38 | } |
| 39 | 39 | |
| 40 | 40 | // --------------------------------------------------------------------------- |
| @@ -42,10 +42,17 @@ export interface OpenApiSpecConfig { · 1 unmodified line | ||
| 42 | 42 | // --------------------------------------------------------------------------- |
| 43 | 43 | |
| 44 | 44 | export interface OpenApiPluginExtension { |
| 45 | + /** Preview a spec without registering — returns metadata, auth strategies, header presets */ | |
| 46 | + readonly previewSpec: ( | |
| 47 | + specText: string, | |
| 48 | + ) => Effect.Effect<SpecPreview, Error>; | |
| 49 | + | |
| 50 | + /** Add an OpenAPI spec and register its operations as tools */ | |
| 45 | 51 | readonly addSpec: ( |
| 46 | 52 | config: OpenApiSpecConfig, |
| 47 | 53 | ) => Effect.Effect<{ readonly toolCount: number }, Error>; |
| 48 | 54 | |
| 55 | + /** Remove all tools from a previously added spec by namespace */ | |
| 49 | 56 | readonly removeSpec: (namespace: string) => Effect.Effect<void>; |
| 50 | 57 | } |
| 51 | 58 | |
| @@ -103,6 +110,8 @@ export const openApiPlugin = (options?: { · 51 unmodified lines | ||
| 103 | 110 | |
| 104 | 111 | return { |
| 105 | 112 | extension: { |
| 113 | + previewSpec: (specText: string) => previewSpec(specText), | |
| 114 | + | |
| 106 | 115 | addSpec: (config: OpenApiSpecConfig) => |
| 107 | 116 | Effect.gen(function* () { |
| 108 | 117 | const doc = yield* parse(config.spec); |
| @@ -121,7 +130,7 @@ export const openApiPlugin = (options?: { · 12 unmodified lines | ||
| 121 | 130 | const baseUrl = config.baseUrl ?? resolveBaseUrl(result.servers); |
| 122 | 131 | const invocationConfig = new InvocationConfig({ |
| 123 | 132 | baseUrl, |
| 124 | − auth: config.auth ?? new NoAuth(), | |
| 133 | + headers: config.headers ?? {}, | |
| 125 | 134 | }); |
| 126 | 135 | |
| 127 | 136 | const registrations = result.operations.map((op) => |
| @@ -0,0 +1,192 @@ | ||
| 1 | +import { Effect, Option } from "effect"; | |
| 2 | +import { Schema } from "effect"; | |
| 3 | + | |
| 4 | +import { parse } from "./parse"; | |
| 5 | +import { extract } from "./extract"; | |
| 6 | +import type { ExtractionResult } from "./types"; | |
| 7 | + | |
| 8 | +// --------------------------------------------------------------------------- | |
| 9 | +// Security scheme — what the spec declares it needs | |
| 10 | +// --------------------------------------------------------------------------- | |
| 11 | + | |
| 12 | +export class SecurityScheme extends Schema.Class<SecurityScheme>("SecurityScheme")({ | |
| 13 | + /** Key name in components.securitySchemes (e.g. "api_token") */ | |
| 14 | + name: Schema.String, | |
| 15 | + /** OpenAPI security scheme type */ | |
| 16 | + type: Schema.Literal("http", "apiKey", "oauth2", "openIdConnect"), | |
| 17 | + /** For type: "http" — e.g. "bearer", "basic" */ | |
| 18 | + scheme: Schema.optionalWith(Schema.String, { as: "Option" }), | |
| 19 | + /** For type: "apiKey" — where the key goes */ | |
| 20 | + in: Schema.optionalWith(Schema.Literal("header", "query", "cookie"), { as: "Option" }), | |
| 21 | + /** For type: "apiKey" — the header/query/cookie name */ | |
| 22 | + headerName: Schema.optionalWith(Schema.String, { as: "Option" }), | |
| 23 | + description: Schema.optionalWith(Schema.String, { as: "Option" }), | |
| 24 | +}) {} | |
| 25 | + | |
| 26 | +// --------------------------------------------------------------------------- | |
| 27 | +// Auth strategy — a valid combination of security schemes | |
| 28 | +// --------------------------------------------------------------------------- | |
| 29 | + | |
| 30 | +export class AuthStrategy extends Schema.Class<AuthStrategy>("AuthStrategy")({ | |
| 31 | + /** The security schemes required together for this strategy */ | |
| 32 | + schemes: Schema.Array(Schema.String), | |
| 33 | +}) {} | |
| 34 | + | |
| 35 | +// --------------------------------------------------------------------------- | |
| 36 | +// Header preset — derived from an auth strategy | |
| 37 | +// --------------------------------------------------------------------------- | |
| 38 | + | |
| 39 | +export class HeaderPreset extends Schema.Class<HeaderPreset>("HeaderPreset")({ | |
| 40 | + /** Human-readable label for the UI (e.g. "Bearer Token", "API Key + Email") */ | |
| 41 | + label: Schema.String, | |
| 42 | + /** Headers this strategy needs. Value is null when the user must provide it. */ | |
| 43 | + headers: Schema.Record({ key: Schema.String, value: Schema.NullOr(Schema.String) }), | |
| 44 | + /** Which headers should be stored as secrets */ | |
| 45 | + secretHeaders: Schema.Array(Schema.String), | |
| 46 | +}) {} | |
| 47 | + | |
| 48 | +// --------------------------------------------------------------------------- | |
| 49 | +// Spec preview — everything the frontend needs | |
| 50 | +// --------------------------------------------------------------------------- | |
| 51 | + | |
| 52 | +export class SpecPreview extends Schema.Class<SpecPreview>("SpecPreview")({ | |
| 53 | + title: Schema.optionalWith(Schema.String, { as: "Option" }), | |
| 54 | + version: Schema.optionalWith(Schema.String, { as: "Option" }), | |
| 55 | + /** Reuses ServerInfo from extraction */ | |
| 56 | + servers: Schema.Array(Schema.Unknown), | |
| 57 | + operationCount: Schema.Number, | |
| 58 | + tags: Schema.Array(Schema.String), | |
| 59 | + securitySchemes: Schema.Array(SecurityScheme), | |
| 60 | + /** Valid auth strategies (each is a set of schemes used together) */ | |
| 61 | + authStrategies: Schema.Array(AuthStrategy), | |
| 62 | + /** Pre-built header presets derived from auth strategies */ | |
| 63 | + headerPresets: Schema.Array(HeaderPreset), | |
| 64 | +}) {} | |
| 65 | + | |
| 66 | +// --------------------------------------------------------------------------- | |
| 67 | +// Security scheme extraction | |
| 68 | +// --------------------------------------------------------------------------- | |
| 69 | + | |
| 70 | +const extractSecuritySchemes = ( | |
| 71 | + rawSchemes: Record<string, unknown>, | |
| 72 | +): SecurityScheme[] => | |
| 73 | + Object.entries(rawSchemes).flatMap(([name, schemeOrRef]) => { | |
| 74 | + if (!schemeOrRef || typeof schemeOrRef !== "object") return []; | |
| 75 | + const scheme = schemeOrRef as Record<string, unknown>; | |
| 76 | + if ("$ref" in scheme) return []; | |
| 77 | + | |
| 78 | + const type = scheme.type as string; | |
| 79 | + if (!["http", "apiKey", "oauth2", "openIdConnect"].includes(type)) return []; | |
| 80 | + | |
| 81 | + return [ | |
| 82 | + new SecurityScheme({ | |
| 83 | + name, | |
| 84 | + type: type as "http" | "apiKey" | "oauth2" | "openIdConnect", | |
| 85 | + scheme: Option.fromNullable(scheme.scheme as string | undefined), | |
| 86 | + in: Option.fromNullable( | |
| 87 | + scheme.in as "header" | "query" | "cookie" | undefined, | |
| 88 | + ), | |
| 89 | + headerName: Option.fromNullable(scheme.name as string | undefined), | |
| 90 | + description: Option.fromNullable(scheme.description as string | undefined), | |
| 91 | + }), | |
| 92 | + ]; | |
| 93 | + }); | |
| 94 | + | |
| 95 | +// --------------------------------------------------------------------------- | |
| 96 | +// Header preset builder | |
| 97 | +// --------------------------------------------------------------------------- | |
| 98 | + | |
| 99 | +const buildHeaderPresets = ( | |
| 100 | + schemes: readonly SecurityScheme[], | |
| 101 | + strategies: readonly AuthStrategy[], | |
| 102 | +): HeaderPreset[] => { | |
| 103 | + const schemeMap = new Map(schemes.map((s) => [s.name, s])); | |
| 104 | + | |
| 105 | + return strategies.flatMap((strategy) => { | |
| 106 | + const resolved = strategy.schemes | |
| 107 | + .map((name) => schemeMap.get(name)) | |
| 108 | + .filter((s): s is SecurityScheme => s !== undefined); | |
| 109 | + | |
| 110 | + if (resolved.length === 0) return []; | |
| 111 | + | |
| 112 | + const headers: Record<string, string | null> = {}; | |
| 113 | + const secretHeaders: string[] = []; | |
| 114 | + const labelParts: string[] = []; | |
| 115 | + | |
| 116 | + for (const scheme of resolved) { | |
| 117 | + if (scheme.type === "http" && Option.getOrElse(scheme.scheme, () => "") === "bearer") { | |
| 118 | + headers["Authorization"] = null; | |
| 119 | + secretHeaders.push("Authorization"); | |
| 120 | + labelParts.push("Bearer Token"); | |
| 121 | + } else if (scheme.type === "http" && Option.getOrElse(scheme.scheme, () => "") === "basic") { | |
| 122 | + headers["Authorization"] = null; | |
| 123 | + secretHeaders.push("Authorization"); | |
| 124 | + labelParts.push("Basic Auth"); | |
| 125 | + } else if (scheme.type === "apiKey" && Option.getOrElse(scheme.in, () => "") === "header") { | |
| 126 | + const headerName = Option.getOrElse(scheme.headerName, () => scheme.name); | |
| 127 | + headers[headerName] = null; | |
| 128 | + secretHeaders.push(headerName); | |
| 129 | + labelParts.push(scheme.name); | |
| 130 | + } else if (scheme.type === "apiKey") { | |
| 131 | + labelParts.push(`${scheme.name} (${Option.getOrElse(scheme.in, () => "unknown")})`); | |
| 132 | + } else { | |
| 133 | + labelParts.push(scheme.name); | |
| 134 | + } | |
| 135 | + } | |
| 136 | + | |
| 137 | + if (Object.keys(headers).length === 0 && resolved.length > 0) { | |
| 138 | + return [ | |
| 139 | + new HeaderPreset({ label: labelParts.join(" + "), headers: {}, secretHeaders: [] }), | |
| 140 | + ]; | |
| 141 | + } | |
| 142 | + | |
| 143 | + return [ | |
| 144 | + new HeaderPreset({ label: labelParts.join(" + "), headers, secretHeaders }), | |
| 145 | + ]; | |
| 146 | + }); | |
| 147 | +}; | |
| 148 | + | |
| 149 | +// --------------------------------------------------------------------------- | |
| 150 | +// Collect unique tags from extraction result | |
| 151 | +// --------------------------------------------------------------------------- | |
| 152 | + | |
| 153 | +const collectTags = (result: ExtractionResult): string[] => { | |
| 154 | + const tagSet = new Set<string>(); | |
| 155 | + for (const op of result.operations) { | |
| 156 | + for (const tag of op.tags) tagSet.add(tag); | |
| 157 | + } | |
| 158 | + return [...tagSet].sort(); | |
| 159 | +}; | |
| 160 | + | |
| 161 | +// --------------------------------------------------------------------------- | |
| 162 | +// Public API | |
| 163 | +// --------------------------------------------------------------------------- | |
| 164 | + | |
| 165 | +/** Preview an OpenAPI spec — extract metadata without registering anything. | |
| 166 | + * Reuses parse() + extract() for the heavy lifting. */ | |
| 167 | +export const previewSpec = Effect.fn("OpenApi.previewSpec")(function* ( | |
| 168 | + specText: string, | |
| 169 | +) { | |
| 170 | + const doc = yield* parse(specText); | |
| 171 | + const result = yield* extract(doc); | |
| 172 | + | |
| 173 | + const securitySchemes = extractSecuritySchemes( | |
| 174 | + doc.components?.securitySchemes ?? {}, | |
| 175 | + ); | |
| 176 | + | |
| 177 | + const rawSecurity = (doc.security ?? []) as Array<Record<string, unknown>>; | |
| 178 | + const authStrategies = rawSecurity.map( | |
| 179 | + (entry) => new AuthStrategy({ schemes: Object.keys(entry) }), | |
| 180 | + ); | |
| 181 | + | |
| 182 | + return new SpecPreview({ | |
| 183 | + title: result.title, | |
| 184 | + version: result.version, | |
| 185 | + servers: result.servers as unknown as readonly unknown[], | |
| 186 | + operationCount: result.operations.length, | |
| 187 | + tags: collectTags(result), | |
| 188 | + securitySchemes, | |
| 189 | + authStrategies, | |
| 190 | + headerPresets: buildHeaderPresets(securitySchemes, authStrategies), | |
| 191 | + }); | |
| 192 | +}); | |
| @@ -6,6 +6,7 @@ import { resolve } from "node:path"; · 5 unmodified lines | ||
| 6 | 6 | import type { ParsedDocument } from "./parse"; |
| 7 | 7 | import { parse } from "./parse"; |
| 8 | 8 | import { extract } from "./extract"; |
| 9 | +import { previewSpec } from "./preview"; | |
| 9 | 10 | import type { ExtractionResult } from "./types"; |
| 10 | 11 | import { createExecutor, makeTestConfig } from "@executor/sdk"; |
| 11 | 12 | import { openApiPlugin } from "./plugin"; |
| @@ -158,6 +159,42 @@ describe("Real specs: Cloudflare API", () => { · 146 unmodified lines | ||
| 158 | 159 | }), |
| 159 | 160 | ); |
| 160 | 161 | |
| 162 | + it.effect("previewSpec returns security schemes and header presets", () => | |
| 163 | + Effect.gen(function* () { | |
| 164 | + const preview = yield* previewSpec(specText); | |
| 165 | + | |
| 166 | + expect(preview.operationCount).toBeGreaterThan(1000); | |
| 167 | + expect(Option.isSome(preview.title)).toBe(true); | |
| 168 | + expect(preview.servers.length).toBeGreaterThan(0); | |
| 169 | + | |
| 170 | + // Cloudflare has 4 security schemes | |
| 171 | + expect(preview.securitySchemes.length).toBe(4); | |
| 172 | + const schemeNames = preview.securitySchemes.map((s) => s.name); | |
| 173 | + expect(schemeNames).toContain("api_token"); | |
| 174 | + expect(schemeNames).toContain("api_key"); | |
| 175 | + expect(schemeNames).toContain("api_email"); | |
| 176 | + | |
| 177 | + // Should have header presets derived from security strategies | |
| 178 | + expect(preview.headerPresets.length).toBeGreaterThan(0); | |
| 179 | + | |
| 180 | + // Bearer token preset should include Authorization header | |
| 181 | + const bearerPreset = preview.headerPresets.find((p) => | |
| 182 | + p.label.includes("Bearer"), | |
| 183 | + ); | |
| 184 | + expect(bearerPreset).toBeDefined(); | |
| 185 | + expect(bearerPreset!.headers["Authorization"]).toBeNull(); // user must provide | |
| 186 | + expect(bearerPreset!.secretHeaders).toContain("Authorization"); | |
| 187 | + | |
| 188 | + // API key + email preset | |
| 189 | + const keyEmailPreset = preview.headerPresets.find((p) => | |
| 190 | + p.label.includes("api_email"), | |
| 191 | + ); | |
| 192 | + expect(keyEmailPreset).toBeDefined(); | |
| 193 | + expect(keyEmailPreset!.headers["X-Auth-Email"]).toBeNull(); | |
| 194 | + expect(keyEmailPreset!.headers["X-Auth-Key"]).toBeNull(); | |
| 195 | + }), | |
| 196 | + ); | |
| 197 | + | |
| 161 | 198 | it.effect("removeSpec cleans up all Cloudflare tools", () => |
| 162 | 199 | Effect.gen(function* () { |
| 163 | 200 | const executor = yield* createExecutor( |
| @@ -7,33 +7,6 @@ import { Schema } from "effect"; · 6 unmodified lines | ||
| 7 | 7 | export const OperationId = Schema.String.pipe(Schema.brand("OperationId")); |
| 8 | 8 | export type OperationId = typeof OperationId.Type; |
| 9 | 9 | |
| 10 | −// --------------------------------------------------------------------------- | |
| 11 | −// Auth | |
| 12 | −// --------------------------------------------------------------------------- | |
| 13 | − | |
| 14 | −export class NoAuth extends Schema.TaggedClass<NoAuth>()("NoAuth", {}) {} | |
| 15 | − | |
| 16 | −export class BearerAuth extends Schema.TaggedClass<BearerAuth>()( | |
| 17 | − "BearerAuth", | |
| 18 | − { | |
| 19 | − token: Schema.String, | |
| 20 | − headerName: Schema.optionalWith(Schema.String, { default: () => "Authorization" }), | |
| 21 | − prefix: Schema.optionalWith(Schema.String, { default: () => "Bearer " }), | |
| 22 | − }, | |
| 23 | −) {} | |
| 24 | − | |
| 25 | −export class ApiKeyAuth extends Schema.TaggedClass<ApiKeyAuth>()( | |
| 26 | − "ApiKeyAuth", | |
| 27 | − { | |
| 28 | − name: Schema.String, | |
| 29 | − value: Schema.String, | |
| 30 | − in: Schema.Literal("header", "query", "cookie"), | |
| 31 | − }, | |
| 32 | −) {} | |
| 33 | − | |
| 34 | −export const AuthConfig = Schema.Union(NoAuth, BearerAuth, ApiKeyAuth); | |
| 35 | −export type AuthConfig = typeof AuthConfig.Type; | |
| 36 | − | |
| 37 | 10 | // --------------------------------------------------------------------------- |
| 38 | 11 | // HTTP |
| 39 | 12 | // --------------------------------------------------------------------------- |
| @@ -125,7 +98,11 @@ export class InvocationConfig extends Schema.Class<InvocationConfig>( · 85 unmodified lines | ||
| 125 | 98 | "InvocationConfig", |
| 126 | 99 | )({ |
| 127 | 100 | baseUrl: Schema.String, |
| 128 | − auth: AuthConfig, | |
| 101 | + /** Static headers applied to every request (auth, custom headers, etc.) */ | |
| 102 | + headers: Schema.optionalWith( | |
| 103 | + Schema.Record({ key: Schema.String, value: Schema.String }), | |
| 104 | + { default: () => ({}) }, | |
| 105 | + ), | |
| 129 | 106 | }) {} |
| 130 | 107 | |
| 131 | 108 | export class InvocationResult extends Schema.Class<InvocationResult>( |