diff --git a/src/applySchemaTyping.ts b/src/applySchemaTyping.ts index 491d3569..4e9b7607 100644 --- a/src/applySchemaTyping.ts +++ b/src/applySchemaTyping.ts @@ -1,9 +1,10 @@ import type {LinkedJSONSchema} from './types/JSONSchema' import {Intersection, Parent, Types} from './types/JSONSchema' import {typesOfSchema} from './typesOfSchema' +import {Options} from './' -export function applySchemaTyping(schema: LinkedJSONSchema) { - const types = typesOfSchema(schema) +export function applySchemaTyping(schema: LinkedJSONSchema, options?: Options) { + const types = typesOfSchema(schema, options) Object.defineProperty(schema, Types, { enumerable: false, diff --git a/src/index.ts b/src/index.ts index 1aa67be0..32b71451 100644 --- a/src/index.ts +++ b/src/index.ts @@ -85,6 +85,13 @@ export interface Options { * Generate unknown type instead of any */ unknownAny: boolean + /** + * Custom parser extensions for unsupported schema types + */ + parserExtensions?: Record< + string, + (schema: Record, compileSchema: (schema: Record) => string) => string + > } export const DEFAULT_OPTIONS: Options = { diff --git a/src/normalizer.ts b/src/normalizer.ts index 2d0d534d..e0171797 100644 --- a/src/normalizer.ts +++ b/src/normalizer.ts @@ -242,9 +242,9 @@ rules.set('Add tsEnumNames to enum types', (schema, _, options) => { // the intersection schema needs to participate in the schema cache during // the parsing step, so it cannot be re-calculated every time the schema // is encountered. -rules.set('Pre-calculate schema types and intersections', schema => { +rules.set('Pre-calculate schema types and intersections', (schema, _fileName, options) => { if (schema !== null && typeof schema === 'object') { - applySchemaTyping(schema) + applySchemaTyping(schema, options) } }) diff --git a/src/parser.ts b/src/parser.ts index 92acdeb3..e47a53b5 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -15,6 +15,7 @@ import type { } from './types/JSONSchema' import {Intersection, Types, getRootSchema, isBoolean, isPrimitive} from './types/JSONSchema' import {generateName, log, maybeStripDefault} from './utils' +import {generateType} from './generator' export type Processed = Map> @@ -157,15 +158,42 @@ function parseNonLiteral( standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'BOOLEAN', } - case 'CUSTOM_TYPE': + case 'CUSTOM_TYPE': { + let customType: string + + if (schema.tsType) { + customType = schema.tsType + } else if (options.parserExtensions && schema.type && typeof schema.type === 'string') { + const extension = options.parserExtensions[schema.type] + if (extension) { + // Create a compilation callback for nested schemas + const compileSchema = (nestedSchema: any): string => { + // Clone the schema to avoid modifying the original + const clonedSchema = {...nestedSchema} + if (!clonedSchema[Types]) { + applySchemaTyping(clonedSchema, options) + } + const ast = parse(clonedSchema, options, undefined, processed, usedNames) + return generateType(ast, options) + } + + customType = extension(schema, compileSchema) + } else { + customType = 'any' + } + } else { + customType = 'any' + } + return { comment: schema.description, deprecated: schema.deprecated, keyName, - params: schema.tsType!, + params: customType, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options), type: 'CUSTOM_TYPE', } + } case 'NAMED_ENUM': return { comment: schema.description, @@ -271,7 +299,7 @@ function parseNonLiteral( params: (schema.type as JSONSchema4TypeName[]).map(type => { const member: LinkedJSONSchema = {...omit(schema, '$id', 'description', 'title'), type} maybeStripDefault(member) - applySchemaTyping(member) + applySchemaTyping(member, options) return parse(member, options, undefined, processed, usedNames) }), type: 'UNION', diff --git a/src/typesOfSchema.ts b/src/typesOfSchema.ts index 2d8b31be..0ac7f395 100644 --- a/src/typesOfSchema.ts +++ b/src/typesOfSchema.ts @@ -1,5 +1,6 @@ import {isPlainObject} from 'lodash' import {isCompound, JSONSchema, SchemaType} from './types/JSONSchema' +import {Options} from './' /** * Duck types a JSONSchema schema or property to determine which kind of AST node to parse it into. @@ -9,12 +10,18 @@ import {isCompound, JSONSchema, SchemaType} from './types/JSONSchema' * types). The spec leaves it up to implementations to decide what to do with this * loosely-defined behavior. */ -export function typesOfSchema(schema: JSONSchema): Set { +export function typesOfSchema(schema: JSONSchema, options?: Options): Set { // tsType is an escape hatch that supercedes all other directives if (schema.tsType) { return new Set(['CUSTOM_TYPE']) } + if (options?.parserExtensions && schema.type && typeof schema.type === 'string') { + if (schema.type in options.parserExtensions) { + return new Set(['CUSTOM_TYPE']) + } + } + // Collect matched types const matchedTypes = new Set() for (const [schemaType, f] of Object.entries(matchers)) {