UNPKG

counterfact

Version:

Generate a TypeScript-based mock server from an OpenAPI spec in seconds — with stateful routes, hot reload, and REPL support.

122 lines (121 loc) 4.8 kB
import { buildJsDoc } from "./jsdoc.js"; import { TypeCoder } from "./type-coder.js"; import { Requirement } from "./requirement.js"; import { pathJoin } from "../util/forward-slash-path.js"; export class SchemaTypeCoder extends TypeCoder { names() { return super.names(this.requirement.refUrl?.split("/").at(-1)); } jsdoc() { return buildJsDoc(this.requirement.data); } additionalPropertiesType(script) { const { additionalProperties, properties } = this.requirement.data; if (!additionalProperties.type) { return "unknown"; } if (Object.values(properties ?? {}).some((property) => property.type !== additionalProperties.type)) { return "unknown"; } const requirement = this.requirement.get("additionalProperties"); return new SchemaTypeCoder(requirement, this.version).write(script); } objectSchema(script) { const { data } = this.requirement; const typedData = data; const properties = Object.keys(typedData.properties ?? {}).map((name) => { const property = this.requirement.get("properties").get(name); const propertyData = property.data; const isRequired = typedData.required?.includes(name) || propertyData.required === true; const optionalFlag = isRequired ? "" : "?"; const comment = buildJsDoc(property.data); const commentPrefix = comment ? `\n${comment}` : ""; const propertyType = new SchemaTypeCoder(property, this.version).write(script); return `${commentPrefix}"${name}"${optionalFlag}: ${propertyType}`; }); if (typedData.additionalProperties) { properties.push(`[key: string]: ${this.additionalPropertiesType(script)}`); } return `{${properties.join(",")}}`; } arraySchema(script) { return `Array<${new SchemaTypeCoder(this.requirement.get("items"), this.version).write(script)}>`; } writePrimitive(value) { if (typeof value === "string") { return `"${value}"`; } if (value === null) { return "null"; } return String(value); } writeType(script, type) { if (Array.isArray(type)) { return type .map((item) => this.writeType(script, item)) .join(" | "); } if (typeof type !== "string") { return "unknown"; } if (type === "object") { return this.objectSchema(script); } if (type === "array") { return this.arraySchema(script); } if (type === "integer") { return "number"; } return type ?? "unknown"; } writeGroup(script, { allOf, anyOf, oneOf, }) { function matchingKey() { if (allOf) { return "allOf"; } if (anyOf) { return "anyOf"; } return "oneOf"; } const key = matchingKey(); const items = (allOf ?? anyOf ?? oneOf); const types = items.map((_item, index) => new SchemaTypeCoder(this.requirement.get(key).get(index), this.version).write(script)); // Include the default schema from discriminator.defaultMapping (OpenAPI 3.2) if (!allOf) { const { discriminator } = this.requirement.data; if (discriminator?.defaultMapping) { const defaultRequirement = new Requirement({ $ref: discriminator.defaultMapping }, "", this.requirement.specification); types.push(new SchemaTypeCoder(defaultRequirement, this.version).write(script)); } } return types.join(allOf ? " & " : " | "); } writeEnum(_script, requirement) { return requirement.data .map((item) => this.writePrimitive(item)) .join(" | "); } modulePath() { return pathJoin("types", this.version, this.requirement.refUrl.replace(/^#\//u, "") + ".ts"); } writeCode(script) { const { allOf, anyOf, oneOf, type, format, itemSchema } = this.requirement .data; if (itemSchema) { return `AsyncIterable<${new SchemaTypeCoder(this.requirement.get("itemSchema"), this.version).write(script)}>`; } if (allOf ?? anyOf ?? oneOf) { return this.writeGroup(script, { allOf, anyOf, oneOf }); } if (this.requirement.has("enum")) { return this.writeEnum(script, this.requirement.get("enum")); } if ((type === "string" && format === "binary") || type === "file") { return "Uint8Array | string"; } return this.writeType(script, type); } }