hey-api-builders
Version:
A custom plugin for @hey-api/openapi-ts that wraps JSON Schema Faker to generate builders.
149 lines (148 loc) • 6.37 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.handler = void 0;
function resolveRefs(schema, allSchemas) {
if (!schema)
return schema;
if (schema.$ref) {
const refPath = schema.$ref.replace('#/components/schemas/', '');
const resolved = allSchemas[refPath] || allSchemas[`${refPath}Schema`];
if (!resolved)
return schema;
return resolveRefs(resolved, allSchemas);
}
if (schema.properties) {
for (const key in schema.properties) {
schema.properties[key] = resolveRefs(schema.properties[key], allSchemas);
}
}
if (schema.items) {
schema.items = resolveRefs(schema.items, allSchemas);
}
if (schema.allOf) {
schema.allOf = schema.allOf.map((item) => resolveRefs(item, allSchemas));
}
return schema;
}
function sanitizeMethodName(prop) {
return prop
.replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase())
.replace(/[^a-zA-Z0-9]/g, '')
.replace(/^(.)/, (m) => m.toUpperCase());
}
const handler = ({ context, plugin }) => {
const schemas = {};
context.subscribe('schema', ({ name, schema }) => {
if (schema && typeof schema === 'object') {
schemas[name] = schema;
}
});
context.subscribe('after', () => {
var _a;
const file = context.createFile({
id: plugin.name,
path: plugin.output,
});
let outputContent = 'import { generateMock } from "hey-api-builders";\n';
outputContent += 'import type * as types from "./types.gen";\n\n';
for (const [schemaName, schema] of Object.entries(schemas)) {
if (!schema || typeof schema !== 'object')
continue;
const resolvedSchema = resolveRefs(schema, schemas);
const typeName = schemaName.replace(/Schema$/, '').trim();
const builderClassName = `${typeName}Builder`;
const isEnum = resolvedSchema.type === 'enum';
const builderOptionsType = `_${builderClassName}Options`;
outputContent += `
type ${builderOptionsType} = {
useDefault?: boolean;
useExamples?: boolean;
alwaysIncludeOptionals?: boolean;
optionalsProbability?: number | false;
omitNulls?: boolean;
};
`;
if (isEnum) {
const enumValues = ((_a = resolvedSchema.items) === null || _a === void 0 ? void 0 : _a.map((item) => item.const)) || [];
const enumSchema = Object.assign(Object.assign({}, resolvedSchema), { type: 'string', enum: enumValues });
outputContent += `
export class ${builderClassName} {
private options: ${builderOptionsType} = {};
setOptions(options: ${builderOptionsType}) { this.options = options || {}; return this; }
build(): types.${typeName} {
return generateMock(${JSON.stringify(enumSchema, null, 2)}, {
useDefaultValue: this.options.useDefault,
useExamplesValue: this.options.useExamples,
alwaysFakeOptionals: this.options.alwaysIncludeOptionals,
optionalsProbability: this.options.optionalsProbability,
omitNulls: this.options.omitNulls,
}) as types.${typeName};
}
}
export function create${builderClassName}() {
return new ${builderClassName}();
}
`;
continue;
}
if (resolvedSchema.properties) {
for (const [prop, propSchemaRaw] of Object.entries(resolvedSchema.properties)) {
const propSchema = propSchemaRaw;
if (propSchema && propSchema["type"] === "enum") {
const enumValues = Array.isArray(propSchema["items"])
? propSchema["items"].map((item) => item.const)
: [];
resolvedSchema.properties[prop] = Object.assign(Object.assign({}, propSchema), { type: "string", enum: enumValues });
}
else if (propSchema &&
Array.isArray(propSchema.items) &&
propSchema.logicalOperator === "or") {
const types = propSchema.items.map((item) => item.type).filter(Boolean);
if (types.length > 0) {
resolvedSchema.properties[prop] = Object.assign(Object.assign({}, propSchema), { type: types.length === 1 ? types[0] : types });
delete resolvedSchema.properties[prop].items;
delete resolvedSchema.properties[prop].logicalOperator;
}
else {
resolvedSchema.properties[prop] = {
anyOf: propSchema.items,
};
delete resolvedSchema.properties[prop].items;
delete resolvedSchema.properties[prop].logicalOperator;
}
}
}
}
let withMethods = '';
if (resolvedSchema.properties) {
for (const prop of Object.keys(resolvedSchema.properties)) {
const methodName = `with${sanitizeMethodName(prop)}`;
withMethods += `\n ${methodName}(value: types.${typeName}[\"${prop}\"]): this {\n this.overrides[\"${prop}\"] = value;\n return this;\n }`;
}
}
outputContent += `
export class ${builderClassName} {
private overrides: Partial<types.${typeName}> = {};
private options: ${builderOptionsType} = {};
setOptions(options: ${builderOptionsType}) { this.options = options || {}; return this; }
${withMethods}
build(): types.${typeName} {
const mock = generateMock(${JSON.stringify(resolvedSchema, null, 2)}, {
useDefaultValue: this.options.useDefault,
useExamplesValue: this.options.useExamples,
alwaysFakeOptionals: this.options.alwaysIncludeOptionals,
optionalsProbability: this.options.optionalsProbability,
omitNulls: this.options.omitNulls,
}) as types.${typeName};
return { ...mock, ...this.overrides };
}
}
export function create${builderClassName}() {
return new ${builderClassName}();
}
`;
}
file.add(outputContent);
});
};
exports.handler = handler;