Rhys Sullivanfix ui and stuff7be8c287 months agoHistory
276 lines · 7.7 KB · TypeScript
RawDownload
import * as Effect from "effect/Effect";import * as Schema from "effect/Schema";import YAML from "yaml"; import {  SecretRefSchema,  StringMapSchema,} from "@executor/platform-sdk/schema"; export const OpenApiConnectionAuthSchema = Schema.Union(  Schema.Struct({    kind: Schema.Literal("none"),  }),  Schema.Struct({    kind: Schema.Literal("bearer"),    tokenSecretRef: SecretRefSchema,    headerName: Schema.NullOr(Schema.String),    prefix: Schema.NullOr(Schema.String),  }),); export const OpenApiConnectInputSchema = Schema.Struct({  name: Schema.String,  specUrl: Schema.String,  baseUrl: Schema.NullOr(Schema.String),  auth: OpenApiConnectionAuthSchema,}); export const OpenApiSourceConfigPayloadSchema = OpenApiConnectInputSchema; export const OpenApiUpdateSourceInputSchema = Schema.Struct({  sourceId: Schema.String,  config: OpenApiSourceConfigPayloadSchema,}); export const OpenApiSourceConfigSchema = Schema.Struct({  specUrl: Schema.String,  baseUrl: Schema.NullOr(Schema.String),  authStrategy: Schema.Literal("none", "bearer"),  documentHash: Schema.String,}); export const OpenApiStoredSourceDataSchema = Schema.Struct({  specUrl: Schema.String,  baseUrl: Schema.NullOr(Schema.String),  auth: OpenApiConnectionAuthSchema,  defaultHeaders: Schema.NullOr(StringMapSchema),  etag: Schema.NullOr(Schema.String),  lastSyncAt: Schema.NullOr(Schema.Number),}); export const OpenApiPreviewRequestSchema = Schema.Struct({  specUrl: Schema.String,}); export const OpenApiPreviewSecuritySchemeSchema = Schema.Struct({  name: Schema.String,  kind: Schema.Literal("apiKey", "http", "oauth2", "openIdConnect", "unknown"),  placement: Schema.NullOr(Schema.String),  scheme: Schema.NullOr(Schema.String),}); export const OpenApiPreviewResponseSchema = Schema.Struct({  title: Schema.NullOr(Schema.String),  version: Schema.NullOr(Schema.String),  baseUrl: Schema.NullOr(Schema.String),  namespace: Schema.NullOr(Schema.String),  operationCount: Schema.Number,  securitySchemes: Schema.Array(OpenApiPreviewSecuritySchemeSchema),  warnings: Schema.Array(Schema.String),}); export type OpenApiConnectionAuth = typeof OpenApiConnectionAuthSchema.Type;export type OpenApiConnectInput = typeof OpenApiConnectInputSchema.Type;export type OpenApiSourceConfigPayload =  typeof OpenApiSourceConfigPayloadSchema.Type;export type OpenApiSourceConfig = typeof OpenApiSourceConfigSchema.Type;export type OpenApiStoredSourceData = typeof OpenApiStoredSourceDataSchema.Type;export type OpenApiPreviewRequest = typeof OpenApiPreviewRequestSchema.Type;export type OpenApiPreviewSecurityScheme =  typeof OpenApiPreviewSecuritySchemeSchema.Type;export type OpenApiPreviewResponse = typeof OpenApiPreviewResponseSchema.Type;export type OpenApiUpdateSourceInput =  typeof OpenApiUpdateSourceInputSchema.Type; type JsonRecord = Record<string, unknown>; const asRecord = (value: unknown): JsonRecord | null =>  value !== null && typeof value === "object" && !Array.isArray(value)    ? value as JsonRecord    : null; const asString = (value: unknown): string | null =>  typeof value === "string" && value.trim().length > 0 ? value.trim() : null; const namespaceFromUrl = (value: string): string | null => {  try {    const url = new URL(value);    const hostname = url.hostname.trim().toLowerCase();    if (!hostname) {      return null;    }     const parts = hostname.split(".").filter(Boolean);    if (parts.length === 0) {      return null;    }     const candidate = (parts.length >= 2 ? parts[parts.length - 2] : parts[0])?.trim();    return candidate && candidate.length > 0 ? candidate : null;  } catch {    return null;  }}; const slugifyName = (value: string | null): string | null => {  if (!value) {    return null;  }   const slug = value    .trim()    .toLowerCase()    .replace(/[^a-z0-9]+/g, "-")    .replace(/^-+|-+$/g, "");   return slug.length > 0 ? slug : null;}; export const deriveOpenApiNamespace = (input: {  specUrl: string;  title?: string | null;}): string | null => slugifyName(input.title ?? null) ?? namespaceFromUrl(input.specUrl); const parseDocument = (text: string): unknown => {  const trimmed = text.trim();  if (!trimmed) {    throw new Error("The OpenAPI document is empty.");  }   if (trimmed.startsWith("{") || trimmed.startsWith("[")) {    return JSON.parse(trimmed);  }   return YAML.parse(trimmed);}; const deriveBaseUrl = (document: JsonRecord, specUrl: string): string | null => {  const servers = Array.isArray(document.servers) ? document.servers : [];  const firstServer = asRecord(servers[0]);  const rawUrl = asString(firstServer?.url);  if (rawUrl === null) {    return null;  }   try {    return new URL(rawUrl, specUrl).toString();  } catch {    return rawUrl;  }}; const previewSecuritySchemes = (document: JsonRecord): Array<OpenApiPreviewSecurityScheme> => {  const components = asRecord(document.components);  const securitySchemes = asRecord(components?.securitySchemes);  if (!securitySchemes) {    return [];  }   return Object.entries(securitySchemes)    .sort(([left], [right]) => left.localeCompare(right))    .map(([name, rawScheme]) => {      const scheme = asRecord(rawScheme);      const rawKind = asString(scheme?.type);      const kind =        rawKind === "apiKey" ||        rawKind === "http" ||        rawKind === "oauth2" ||        rawKind === "openIdConnect"          ? rawKind          : "unknown";       return {        name,        kind,        placement: asString(scheme?.in),        scheme: asString(scheme?.scheme),      } satisfies OpenApiPreviewSecurityScheme;    });}; const countOperations = (document: JsonRecord): number => {  const paths = asRecord(document.paths);  if (paths === null) {    return 0;  }   const methodNames = new Set([    "get",    "post",    "put",    "patch",    "delete",    "head",    "options",    "trace",  ]);   let count = 0;  for (const pathItem of Object.values(paths)) {    const pathRecord = asRecord(pathItem);    if (pathRecord === null) {      continue;    }     for (const key of Object.keys(pathRecord)) {      if (methodNames.has(key.toLowerCase())) {        count += 1;      }    }  }   return count;}; export const previewOpenApiDocument = async (  input: OpenApiPreviewRequest,): Promise<OpenApiPreviewResponse> => {  const response = await fetch(input.specUrl);  if (!response.ok) {    throw new Error(`Failed fetching spec: ${response.status} ${response.statusText}`);  }   const text = await response.text();  const parsed = parseDocument(text);  const document = asRecord(parsed);  if (document === null) {    throw new Error("The fetched document is not a valid OpenAPI object.");  }   const info = asRecord(document.info);  const warnings: string[] = [];  const title = asString(info?.title);   const openapiVersion = asString(document.openapi) ?? asString(document.swagger);  if (openapiVersion === null) {    warnings.push("The document does not declare an OpenAPI/Swagger version.");  }   const baseUrl = deriveBaseUrl(document, input.specUrl);  if (baseUrl === null) {    warnings.push("No server URL was found in the document.");  }   return {    title,    version: asString(info?.version),    baseUrl,    namespace: deriveOpenApiNamespace({      specUrl: input.specUrl,      title,    }),    operationCount: countOperations(document),    securitySchemes: previewSecuritySchemes(document),    warnings,  };}; export const decodeOpenApiStoredSourceData = (  value: unknown,): Effect.Effect<OpenApiStoredSourceData, Error, never> =>  Effect.try({    try: () => Schema.decodeUnknownSync(OpenApiStoredSourceDataSchema)(value),    catch: (cause) =>      cause instanceof Error ? cause : new Error(String(cause)),  });