UNPKG

hey-api-builders

Version:

A custom plugin for @hey-api/openapi-ts that generates mock data builders with a lightweight custom runtime, Zod integration, or static mock generation.

285 lines (229 loc) 7.41 kB
import type { Schema } from '../types'; export interface MockOptions { useDefault?: boolean; useExamples?: boolean; alwaysIncludeOptionals?: boolean; optionalsProbability?: number | false; omitNulls?: boolean; requiredOnly?: boolean; } /** * Custom mock data generator for JSON schemas * This is a lightweight alternative to json-schema-faker */ export class MockGenerator { private options: Required<MockOptions>; private seenRefs = new Set<string>(); constructor(options: MockOptions = {}) { this.options = { useDefault: options.useDefault ?? false, useExamples: options.useExamples ?? false, alwaysIncludeOptionals: options.alwaysIncludeOptionals ?? false, optionalsProbability: options.optionalsProbability ?? 1, omitNulls: options.omitNulls ?? false, requiredOnly: options.requiredOnly ?? false, }; } /** * Generate mock data from schema */ generate(schema: Schema): unknown { this.seenRefs.clear(); return this.generateValue(schema); } private generateValue(schema: Schema): unknown { if (schema.$ref) { if (this.seenRefs.has(schema.$ref)) { return null; } this.seenRefs.add(schema.$ref); } if (this.options.useDefault && schema.default !== undefined) { return schema.default; } if (this.options.useExamples && schema.examples && schema.examples.length > 0) { return schema.examples[0]; } if (schema.const !== undefined) { return schema.const; } if (schema.enum && schema.enum.length > 0) { return schema.enum[0]; } if (schema.nullable && this.shouldIncludeNull()) { return null; } if (schema.allOf && schema.allOf.length > 0) { return this.generateAllOf(schema.allOf); } if (schema.anyOf && schema.anyOf.length > 0) { return this.generateValue(schema.anyOf[0]); } if (schema.oneOf && schema.oneOf.length > 0) { return this.generateValue(schema.oneOf[0]); } if (Array.isArray(schema.type)) { const nonNullTypes = schema.type.filter((t) => t !== 'null'); const type = nonNullTypes.length > 0 ? nonNullTypes[0] : 'null'; return this.generateByType({ ...schema, type }); } return this.generateByType(schema); } private generateByType(schema: Schema): unknown { const type = schema.type || 'object'; switch (type) { case 'null': return null; case 'boolean': return false; case 'integer': case 'number': return this.generateNumber(schema); case 'string': return this.generateString(schema); case 'array': return this.generateArray(schema); case 'object': return this.generateObject(schema); default: return null; } } private generateNumber(schema: Schema): number { const min = schema.minimum ?? (schema.exclusiveMinimum ? typeof schema.exclusiveMinimum === 'number' ? schema.exclusiveMinimum + 1 : 1 : 0); const max = schema.maximum ?? (schema.exclusiveMaximum ? typeof schema.exclusiveMaximum === 'number' ? schema.exclusiveMaximum - 1 : 99 : 100); let value = min; if (schema.multipleOf) { value = Math.ceil(min / schema.multipleOf) * schema.multipleOf; if (value > max) { value = schema.multipleOf; } } return schema.type === 'integer' ? Math.floor(value) : value; } private generateString(schema: Schema): string { const minLength = schema.minLength ?? 0; const maxLength = schema.maxLength ?? 10; if (schema.format) { return this.generateStringByFormat(schema.format, minLength, maxLength); } if (schema.pattern) { return this.generateStringForPattern(schema.pattern, minLength, maxLength); } const length = Math.max(minLength, Math.min(maxLength, 10)); return 'a'.repeat(length); } private generateStringByFormat(format: string, minLength: number, maxLength: number): string { switch (format) { case 'email': return 'test@example.com'; case 'uri': case 'url': return 'https://example.com'; case 'uuid': return '00000000-0000-0000-0000-000000000000'; case 'date': return '2024-01-01'; case 'date-time': return '2024-01-01T00:00:00Z'; case 'time': return '00:00:00'; case 'ipv4': return '127.0.0.1'; case 'ipv6': return '::1'; case 'hostname': return 'localhost'; case 'phone': return '+1234567890'; default: { const length = Math.max(minLength, Math.min(maxLength, 10)); return 'string'.substring(0, Math.max(length, 1)); } } } private generateStringForPattern(pattern: string, minLength: number, maxLength: number): string { const length = Math.max(minLength, Math.min(maxLength, 10)); return 'x'.repeat(length); } private generateArray(schema: Schema): unknown[] { const minItems = schema.minItems ?? 0; const maxItems = schema.maxItems ?? 3; const length = Math.max(minItems, Math.min(maxItems, 1)); if (!schema.items) { return Array(length).fill(null); } if (Array.isArray(schema.items)) { return schema.items.map((itemSchema) => this.generateValue(itemSchema)); } return Array.from({ length }, () => this.generateValue(schema.items as Schema)); } private generateObject(schema: Schema): Record<string, unknown> { const obj: Record<string, unknown> = {}; if (!schema.properties) { return obj; } const required = new Set(schema.required || []); for (const [propName, propSchema] of Object.entries(schema.properties)) { const isRequired = required.has(propName); if (this.options.requiredOnly && !isRequired) { continue; } if (!isRequired && !this.shouldIncludeOptional()) { continue; } const value = this.generateValue(propSchema); if (this.options.omitNulls && value === null) { continue; } obj[propName] = value; } return obj; } private generateAllOf(schemas: Schema[]): unknown { const merged: Record<string, unknown> = {}; for (const schema of schemas) { const value = this.generateValue(schema); if (typeof value === 'object' && value !== null && !Array.isArray(value)) { Object.assign(merged, value); } } return merged; } private shouldIncludeOptional(): boolean { if (this.options.alwaysIncludeOptionals) { return true; } if (this.options.optionalsProbability === false) { return false; } return Math.random() < this.options.optionalsProbability; } private shouldIncludeNull(): boolean { return !this.options.omitNulls && Math.random() < 0.1; } } /** * Generates mock data using custom mock generator * @param schema - JSON Schema * @param options - Generation options * @returns Generated mock data */ export function generateMock<T = unknown>(schema: unknown, options?: MockOptions): T { const generator = new MockGenerator(options); return generator.generate(schema as Schema) as T; } export type { BuilderOptions } from '../types'; export type { Schema as BuilderSchema } from '../types';