Skip to content

Commit c910de6

Browse files
committed
wip: custom schema extension
1 parent 43ba08b commit c910de6

File tree

5 files changed

+51
-8
lines changed

5 files changed

+51
-8
lines changed

src/applySchemaTyping.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type {LinkedJSONSchema} from './types/JSONSchema'
22
import {Intersection, Parent, Types} from './types/JSONSchema'
33
import {typesOfSchema} from './typesOfSchema'
4+
import {Options} from './'
45

5-
export function applySchemaTyping(schema: LinkedJSONSchema) {
6-
const types = typesOfSchema(schema)
6+
export function applySchemaTyping(schema: LinkedJSONSchema, options?: Options) {
7+
const types = typesOfSchema(schema, options)
78

89
Object.defineProperty(schema, Types, {
910
enumerable: false,

src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ export interface Options {
8585
* Generate unknown type instead of any
8686
*/
8787
unknownAny: boolean
88+
/**
89+
* Custom parser extensions for unsupported schema types
90+
*/
91+
parserExtensions?: Record<
92+
string,
93+
(schema: Record<string, unknown>, compileSchema: (schema: Record<string, unknown>) => string) => string
94+
>
8895
}
8996

9097
export const DEFAULT_OPTIONS: Options = {

src/normalizer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,9 @@ rules.set('Add tsEnumNames to enum types', (schema, _, options) => {
242242
// the intersection schema needs to participate in the schema cache during
243243
// the parsing step, so it cannot be re-calculated every time the schema
244244
// is encountered.
245-
rules.set('Pre-calculate schema types and intersections', schema => {
245+
rules.set('Pre-calculate schema types and intersections', (schema, _fileName, options) => {
246246
if (schema !== null && typeof schema === 'object') {
247-
applySchemaTyping(schema)
247+
applySchemaTyping(schema, options)
248248
}
249249
})
250250

src/parser.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
} from './types/JSONSchema'
1616
import {Intersection, Types, getRootSchema, isBoolean, isPrimitive} from './types/JSONSchema'
1717
import {generateName, log, maybeStripDefault} from './utils'
18+
import {generateType} from './generator'
1819

1920
export type Processed = Map<NormalizedJSONSchema, Map<SchemaType, AST>>
2021

@@ -157,15 +158,42 @@ function parseNonLiteral(
157158
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
158159
type: 'BOOLEAN',
159160
}
160-
case 'CUSTOM_TYPE':
161+
case 'CUSTOM_TYPE': {
162+
let customType: string
163+
164+
if (schema.tsType) {
165+
customType = schema.tsType
166+
} else if (options.parserExtensions && schema.type && typeof schema.type === 'string') {
167+
const extension = options.parserExtensions[schema.type]
168+
if (extension) {
169+
// Create a compilation callback for nested schemas
170+
const compileSchema = (nestedSchema: any): string => {
171+
// Clone the schema to avoid modifying the original
172+
const clonedSchema = {...nestedSchema}
173+
if (!clonedSchema[Types]) {
174+
applySchemaTyping(clonedSchema, options)
175+
}
176+
const ast = parse(clonedSchema, options, undefined, processed, usedNames)
177+
return generateType(ast, options)
178+
}
179+
180+
customType = extension(schema, compileSchema)
181+
} else {
182+
customType = 'any'
183+
}
184+
} else {
185+
customType = 'any'
186+
}
187+
161188
return {
162189
comment: schema.description,
163190
deprecated: schema.deprecated,
164191
keyName,
165-
params: schema.tsType!,
192+
params: customType,
166193
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
167194
type: 'CUSTOM_TYPE',
168195
}
196+
}
169197
case 'NAMED_ENUM':
170198
return {
171199
comment: schema.description,
@@ -271,7 +299,7 @@ function parseNonLiteral(
271299
params: (schema.type as JSONSchema4TypeName[]).map(type => {
272300
const member: LinkedJSONSchema = {...omit(schema, '$id', 'description', 'title'), type}
273301
maybeStripDefault(member)
274-
applySchemaTyping(member)
302+
applySchemaTyping(member, options)
275303
return parse(member, options, undefined, processed, usedNames)
276304
}),
277305
type: 'UNION',

src/typesOfSchema.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {isPlainObject} from 'lodash'
22
import {isCompound, JSONSchema, SchemaType} from './types/JSONSchema'
3+
import {Options} from './'
34

45
/**
56
* 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'
910
* types). The spec leaves it up to implementations to decide what to do with this
1011
* loosely-defined behavior.
1112
*/
12-
export function typesOfSchema(schema: JSONSchema): Set<SchemaType> {
13+
export function typesOfSchema(schema: JSONSchema, options?: Options): Set<SchemaType> {
1314
// tsType is an escape hatch that supercedes all other directives
1415
if (schema.tsType) {
1516
return new Set(['CUSTOM_TYPE'])
1617
}
1718

19+
if (options?.parserExtensions && schema.type && typeof schema.type === 'string') {
20+
if (schema.type in options.parserExtensions) {
21+
return new Set(['CUSTOM_TYPE'])
22+
}
23+
}
24+
1825
// Collect matched types
1926
const matchedTypes = new Set<SchemaType>()
2027
for (const [schemaType, f] of Object.entries(matchers)) {

0 commit comments

Comments
 (0)