Rhys Sullivanopenapi works not bad782a4497 months agoHistory
568 lines · 14.1 KB · TypeScript
RawDownload
type OpenApiSerializableLocation = "path" | "query" | "header" | "cookie"; export type OpenApiSerializableContent = {  mediaType: string;}; export type OpenApiSerializableParameter = {  name: string;  location: OpenApiSerializableLocation;  style?: string;  explode?: boolean;  allowReserved?: boolean;  content?: ReadonlyArray<OpenApiSerializableContent>;}; export type OpenApiSerializableRequestBody = {  contentTypes?: ReadonlyArray<string>;  contents?: ReadonlyArray<OpenApiSerializableContent>;}; export type HttpBodyDecodingMode = "json" | "text" | "bytes"; export type SerializedOpenApiRequestBody = {  contentType: string;  body: string | Uint8Array;}; export type SerializedOpenApiQueryEntry = {  name: string;  value: string;  allowReserved?: boolean;}; export type SerializedOpenApiParameterValue =  | {      kind: "path" | "header";      value: string;    }  | {      kind: "query";      entries: ReadonlyArray<SerializedOpenApiQueryEntry>;    }  | {      kind: "cookie";      pairs: ReadonlyArray<{        name: string;        value: string;      }>;    }; const isRecord = (value: unknown): value is Record<string, unknown> =>  value !== null && typeof value === "object" && !Array.isArray(value); const textEncoder = new TextEncoder(); const normalizeMediaType = (value: string | undefined | null): string =>  (value ?? "").split(";", 1)[0]?.trim().toLowerCase() ?? ""; const primitiveString = (value: unknown): string => {  if (typeof value === "string") {    return value;  }   if (    typeof value === "number" ||    typeof value === "boolean" ||    typeof value === "bigint"  ) {    return String(value);  }   if (value === null || value === undefined) {    return "";  }   try {    return JSON.stringify(value);  } catch {    return String(value);  }}; export const isJsonMediaType = (mediaType: string | undefined | null): boolean => {  const normalized = normalizeMediaType(mediaType);  return (    normalized === "application/json" ||    normalized.endsWith("+json") ||    normalized.includes("json")  );}; const isTextMediaType = (mediaType: string | undefined | null): boolean => {  const normalized = normalizeMediaType(mediaType);  if (normalized.length === 0) {    return false;  }   return (    normalized.startsWith("text/") ||    normalized === "application/xml" ||    normalized.endsWith("+xml") ||    normalized.endsWith("/xml") ||    normalized === "application/x-www-form-urlencoded" ||    normalized === "application/javascript" ||    normalized === "application/ecmascript" ||    normalized === "application/graphql" ||    normalized === "application/sql" ||    normalized === "application/x-yaml" ||    normalized === "application/yaml" ||    normalized === "application/toml" ||    normalized === "application/csv" ||    normalized === "image/svg+xml" ||    normalized.endsWith("+yaml") ||    normalized.endsWith("+toml")  );}; export const httpBodyModeFromContentType = (  mediaType: string | undefined | null,): HttpBodyDecodingMode => {  if (isJsonMediaType(mediaType)) {    return "json";  }   if (isTextMediaType(mediaType)) {    return "text";  }   return normalizeMediaType(mediaType).length === 0 ? "text" : "bytes";}; const objectEntries = (value: unknown): Array<[string, unknown]> =>  Object.entries(isRecord(value) ? value : {}).sort(([left], [right]) =>    left.localeCompare(right),  ); const defaultStyle = (location: OpenApiSerializableLocation): string => {  switch (location) {    case "path":    case "header":      return "simple";    case "cookie":    case "query":      return "form";  }}; const defaultExplode = (  location: OpenApiSerializableLocation,  style: string,): boolean =>  location === "query" || location === "cookie" ? style === "form" : false; const selectedStyle = (parameter: OpenApiSerializableParameter): string =>  parameter.style ?? defaultStyle(parameter.location); const selectedExplode = (parameter: OpenApiSerializableParameter): boolean =>  parameter.explode ?? defaultExplode(parameter.location, selectedStyle(parameter)); const encodePathPart = (value: unknown): string =>  encodeURIComponent(primitiveString(value)); const encodeQueryValue = (value: string, allowReserved: boolean): string => {  const encoded = encodeURIComponent(value);  if (!allowReserved) {    return encoded;  }   return encoded    .replace(/%3A/gi, ":")    .replace(/%2F/gi, "/")    .replace(/%3F/gi, "?")    .replace(/%23/gi, "#")    .replace(/%5B/gi, "[")    .replace(/%5D/gi, "]")    .replace(/%40/gi, "@")    .replace(/%21/gi, "!")    .replace(/%24/gi, "$")    .replace(/%26/gi, "&")    .replace(/%27/gi, "'")    .replace(/%28/gi, "(")    .replace(/%29/gi, ")")    .replace(/%2A/gi, "*")    .replace(/%2B/gi, "+")    .replace(/%2C/gi, ",")    .replace(/%3B/gi, ";")    .replace(/%3D/gi, "=");}; const preferredContentType = (input: {  contents?: ReadonlyArray<OpenApiSerializableContent>;  contentTypes?: ReadonlyArray<string>;}): string | undefined => {  const candidates = [    ...(input.contents ?? []).map((content) => content.mediaType),    ...(input.contentTypes ?? []),  ];  if (candidates.length === 0) {    return undefined;  }   return (    candidates.find((mediaType) => mediaType === "application/json") ??    candidates.find((mediaType) => mediaType.toLowerCase().includes("+json")) ??    candidates.find((mediaType) => mediaType.toLowerCase().includes("json")) ??    candidates[0]  );}; const serializeBinaryContentValue = (value: unknown): Uint8Array => {  if (value instanceof Uint8Array) {    return value;  }   if (value instanceof ArrayBuffer) {    return new Uint8Array(value);  }   if (ArrayBuffer.isView(value)) {    return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);  }   if (typeof value === "string") {    return textEncoder.encode(value);  }   if (    Array.isArray(value) &&    value.every(      (entry) =>        typeof entry === "number" &&        Number.isInteger(entry) &&        entry >= 0 &&        entry <= 255,    )  ) {    return Uint8Array.from(value);  }   throw new Error(    "Binary OpenAPI request bodies must be bytes, a string, or an array of byte values",  );}; const serializeContentValue = (value: unknown, mediaType: string): string => {  const normalized = mediaType.toLowerCase();  if (    normalized === "application/json" ||    normalized.includes("+json") ||    normalized.includes("json")  ) {    return JSON.stringify(value);  }   if (normalized === "application/x-www-form-urlencoded") {    const params = new URLSearchParams();    for (const [key, entryValue] of objectEntries(value)) {      if (Array.isArray(entryValue)) {        for (const item of entryValue) {          params.append(key, primitiveString(item));        }        continue;      }       params.append(key, primitiveString(entryValue));    }    return params.toString();  }   return primitiveString(value);}; const serializeQueryParameter = (  parameter: OpenApiSerializableParameter,  value: unknown,): SerializedOpenApiParameterValue => {  const style = selectedStyle(parameter);  const explode = selectedExplode(parameter);  const allowReserved = parameter.allowReserved === true;  const contentType = preferredContentType({ contents: parameter.content });   if (contentType) {    return {      kind: "query",      entries: [        {          name: parameter.name,          value: serializeContentValue(value, contentType),          allowReserved,        },      ],    };  }   if (Array.isArray(value)) {    if (style === "spaceDelimited") {      return {        kind: "query",        entries: [          {            name: parameter.name,            value: value.map((entry) => primitiveString(entry)).join(" "),            allowReserved,          },        ],      };    }     if (style === "pipeDelimited") {      return {        kind: "query",        entries: [          {            name: parameter.name,            value: value.map((entry) => primitiveString(entry)).join("|"),            allowReserved,          },        ],      };    }     return {      kind: "query",      entries: explode        ? value.map((entry) => ({            name: parameter.name,            value: primitiveString(entry),            allowReserved,          }))        : [            {              name: parameter.name,              value: value.map((entry) => primitiveString(entry)).join(","),              allowReserved,            },          ],    };  }   if (isRecord(value)) {    const entries = objectEntries(value);     if (style === "deepObject") {      return {        kind: "query",        entries: entries.map(([key, entryValue]) => ({          name: `${parameter.name}[${key}]`,          value: primitiveString(entryValue),          allowReserved,        })),      };    }     return {      kind: "query",      entries: explode        ? entries.map(([key, entryValue]) => ({            name: key,            value: primitiveString(entryValue),            allowReserved,          }))        : [            {              name: parameter.name,              value: entries                .flatMap(([key, entryValue]) => [key, primitiveString(entryValue)])                .join(","),              allowReserved,            },          ],    };  }   return {    kind: "query",    entries: [      {        name: parameter.name,        value: primitiveString(value),        allowReserved,      },    ],  };}; const serializeHeaderParameter = (  parameter: OpenApiSerializableParameter,  value: unknown,): SerializedOpenApiParameterValue => {  if (Array.isArray(value)) {    return {      kind: "header",      value: value.map((entry) => primitiveString(entry)).join(","),    };  }   if (isRecord(value)) {    const explode = selectedExplode(parameter);    return {      kind: "header",      value: explode        ? objectEntries(value)            .map(([key, entryValue]) => `${key}=${primitiveString(entryValue)}`)            .join(",")        : objectEntries(value)            .flatMap(([key, entryValue]) => [key, primitiveString(entryValue)])            .join(","),    };  }   return {    kind: "header",    value: primitiveString(value),  };}; const serializeCookieParameter = (  parameter: OpenApiSerializableParameter,  value: unknown,): SerializedOpenApiParameterValue => {  if (Array.isArray(value)) {    return {      kind: "cookie",      pairs: value.map((entry, index) => ({        name:          selectedExplode(parameter) || value.length === 1            ? parameter.name            : `${parameter.name}${index + 1}`,        value: primitiveString(entry),      })),    };  }   if (isRecord(value)) {    const explode = selectedExplode(parameter);    return {      kind: "cookie",      pairs: explode        ? objectEntries(value).map(([key, entryValue]) => ({            name: key,            value: primitiveString(entryValue),          }))        : [            {              name: parameter.name,              value: objectEntries(value)                .flatMap(([key, entryValue]) => [key, primitiveString(entryValue)])                .join(","),            },          ],    };  }   return {    kind: "cookie",    pairs: [      {        name: parameter.name,        value: primitiveString(value),      },    ],  };}; const serializePathParameter = (  parameter: OpenApiSerializableParameter,  value: unknown,): SerializedOpenApiParameterValue => {  const style = selectedStyle(parameter);  const explode = selectedExplode(parameter);   if (Array.isArray(value)) {    const separator =      style === "label" ? "." : style === "matrix" ? "," : ",";    return {      kind: "path",      value:        style === "label"          ? `.${value.map((entry) => encodePathPart(entry)).join(separator)}`          : style === "matrix"            ? `;${parameter.name}=${value                .map((entry) => encodePathPart(entry))                .join(separator)}`            : value.map((entry) => encodePathPart(entry)).join(separator),    };  }   if (isRecord(value)) {    const entries = objectEntries(value).flatMap(([key, entryValue]) => [      encodePathPart(key),      encodePathPart(entryValue),    ]);    const joined = explode ? entries.join("=") : entries.join(",");    return {      kind: "path",      value:        style === "label"          ? `.${joined}`          : style === "matrix"            ? `;${parameter.name}=${joined}`

Lines 1–500 of 568.