Skip to content

Commit

Permalink
Fix cycles during generating (fix #376, fix #323)
Browse files Browse the repository at this point in the history
  • Loading branch information
Boris Cherny committed Jun 29, 2022
1 parent a92d7c9 commit 2ca6e50
Show file tree
Hide file tree
Showing 11 changed files with 1,482 additions and 1,125 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
},
"homepage": "https://github.com/bcherny/json-schema-to-typescript#readme",
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^9.0.9",
"@apidevtools/json-schema-ref-parser": "https://github.com/bcherny/json-schema-ref-parser.git#984282d3",
"@types/json-schema": "^7.0.11",
"@types/lodash": "^4.14.182",
"@types/prettier": "^2.6.1",
Expand Down
10 changes: 5 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,16 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia
// Initial clone to avoid mutating the input
const _schema = cloneDeep(schema)

const dereferenced = await dereference(_schema, _options)
const {dereferencedPaths, dereferencedSchema} = await dereference(_schema, _options)
if (process.env.VERBOSE) {
if (isDeepStrictEqual(_schema, dereferenced)) {
if (isDeepStrictEqual(_schema, dereferencedSchema)) {
log('green', 'dereferencer', time(), '✅ No change')
} else {
log('green', 'dereferencer', time(), '✅ Result:', dereferenced)
log('green', 'dereferencer', time(), '✅ Result:', dereferencedSchema)
}
}

const linked = link(dereferenced)
const linked = link(dereferencedSchema)
if (process.env.VERBOSE) {
log('green', 'linker', time(), '✅ No change')
}
Expand All @@ -164,7 +164,7 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia
log('green', 'validator', time(), '✅ No change')
}

const normalized = normalize(linked, name, _options)
const normalized = normalize(linked, dereferencedPaths, name, _options)
log('yellow', 'normalizer', time(), '✅ Result:', normalized)

const parsed = parse(normalized, _options)
Expand Down
47 changes: 40 additions & 7 deletions src/normalizer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import {JSONSchemaTypeName, LinkedJSONSchema, NormalizedJSONSchema, Parent} from './types/JSONSchema'
import {appendToDescription, escapeBlockComment, isSchemaLike, justName, toSafeString, traverse} from './utils'
import {Options} from './'

type Rule = (schema: LinkedJSONSchema, fileName: string, options: Options) => void
import {DereferencedPaths} from './resolver'

type Rule = (
schema: LinkedJSONSchema,
fileName: string,
options: Options,
key: string | null,
dereferencedPaths: DereferencedPaths
) => void
const rules = new Map<string, Rule>()

function hasType(schema: LinkedJSONSchema, type: JSONSchemaTypeName) {
Expand Down Expand Up @@ -65,10 +72,31 @@ rules.set('Transform id to $id', (schema, fileName) => {
}
})

rules.set('Default top level $id', (schema, fileName) => {
const isRoot = schema[Parent] === null
if (isRoot && !schema.$id) {
rules.set('Add an $id to anything that needs it', (schema, fileName, _options, _key, dereferencedPaths) => {
if (!isSchemaLike(schema)) {
return
}

// Top-level schema
if (!schema.$id && !schema[Parent]) {
schema.$id = toSafeString(justName(fileName))
return
}

// Sub-schemas with references
if (!isArrayType(schema) && !isObjectType(schema)) {
return
}

// We'll infer from $id and title downstream
// TODO: Normalize upstream
const dereferencedName = dereferencedPaths.get(schema)
if (!schema.$id && !schema.title && dereferencedName) {
schema.$id = toSafeString(justName(dereferencedName))
}

if (dereferencedName) {
dereferencedPaths.delete(schema)
}
})

Expand Down Expand Up @@ -188,7 +216,12 @@ rules.set('Transform const to singleton enum', schema => {
}
})

export function normalize(rootSchema: LinkedJSONSchema, filename: string, options: Options): NormalizedJSONSchema {
rules.forEach(rule => traverse(rootSchema, schema => rule(schema, filename, options)))
export function normalize(
rootSchema: LinkedJSONSchema,
dereferencedPaths: DereferencedPaths,
filename: string,
options: Options
): NormalizedJSONSchema {
rules.forEach(rule => traverse(rootSchema, (schema, key) => rule(schema, filename, options, key, dereferencedPaths)))
return rootSchema as NormalizedJSONSchema
}
16 changes: 14 additions & 2 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,23 @@ import $RefParser = require('@apidevtools/json-schema-ref-parser')
import {JSONSchema} from './types/JSONSchema'
import {log} from './utils'

export type DereferencedPaths = WeakMap<$RefParser.JSONSchemaObject, string>

export async function dereference(
schema: JSONSchema,
{cwd, $refOptions}: {cwd: string; $refOptions: $RefParser.Options}
): Promise<JSONSchema> {
): Promise<{dereferencedPaths: DereferencedPaths; dereferencedSchema: JSONSchema}> {
log('green', 'dereferencer', 'Dereferencing input schema:', cwd, schema)
const parser = new $RefParser()
return parser.dereference(cwd, schema as any, $refOptions) as any // TODO: fix types
const dereferencedPaths: DereferencedPaths = new WeakMap()
const dereferencedSchema = await parser.dereference(cwd, schema as any, {
...$refOptions,
dereference: {
...$refOptions.dereference,
onDereference($ref, schema) {
dereferencedPaths.set(schema, $ref)
}
}
}) as any // TODO: fix types
return {dereferencedPaths, dereferencedSchema}
}
1 change: 1 addition & 0 deletions src/typesOfSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const matchers: Record<SchemaType, (schema: JSONSchema) => boolean> = {
return 'enum' in schema && 'tsEnumNames' in schema
},
NAMED_SCHEMA(schema) {
// 8.2.1. The presence of "$id" in a subschema indicates that the subschema constitutes a distinct schema resource within a single schema document.
return '$id' in schema && ('patternProperties' in schema || 'properties' in schema)
},
NULL(schema) {
Expand Down
Loading

0 comments on commit 2ca6e50

Please sign in to comment.