Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/applySchemaTyping.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>, compileSchema: (schema: Record<string, unknown>) => string) => string
>
}

export const DEFAULT_OPTIONS: Options = {
Expand Down
4 changes: 2 additions & 2 deletions src/normalizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})

Expand Down
34 changes: 31 additions & 3 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<NormalizedJSONSchema, Map<SchemaType, AST>>

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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',
Expand Down
9 changes: 8 additions & 1 deletion src/typesOfSchema.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<SchemaType> {
export function typesOfSchema(schema: JSONSchema, options?: Options): Set<SchemaType> {
// 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<SchemaType>()
for (const [schemaType, f] of Object.entries(matchers)) {
Expand Down