Skip to content

Commit 8a4d913

Browse files
committed
replace legacy mechanism for looking up key name from definitions
1 parent e241c01 commit 8a4d913

File tree

5 files changed

+57637
-84404
lines changed

5 files changed

+57637
-84404
lines changed

src/annotator.ts

+96-24
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,113 @@
11
import {isPlainObject} from 'lodash'
22
import {DereferencedPaths} from './resolver'
3-
import {AnnotatedJSONSchema, JSONSchema, Parent, isAnnotated} from './types/JSONSchema'
3+
import {AnnotatedJSONSchema, JSONSchema, Parent, KeyNameFromDefinition, isAnnotated} from './types/JSONSchema'
44

5-
/**
6-
* Traverses over the schema, assigning to each
7-
* node metadata that will be used downstream.
8-
*/
9-
export function annotate(
10-
schema: JSONSchema,
11-
dereferencedPaths: DereferencedPaths,
12-
parent: JSONSchema | null = null,
13-
): AnnotatedJSONSchema {
14-
if (!Array.isArray(schema) && !isPlainObject(schema)) {
15-
return schema as AnnotatedJSONSchema
5+
const annotators = new Set<
6+
(schema: JSONSchema, parent: JSONSchema | null, dereferencedPaths: DereferencedPaths) => void
7+
>()
8+
9+
// note: this must run first, since it is used by the next annotator
10+
annotators.add(function annotateParent(schema, parent) {
11+
Object.defineProperty(schema, Parent, {
12+
enumerable: false,
13+
value: parent,
14+
writable: false,
15+
})
16+
})
17+
18+
annotators.add(function annotateKeyNameFromDefinition(schema) {
19+
const keyNameFromSchema = getDefinitionKeyNameFromParent(schema as AnnotatedJSONSchema) // todo
20+
if (!keyNameFromSchema) {
21+
return
1622
}
1723

18-
// Handle cycles
19-
if (isAnnotated(schema)) {
20-
return schema
24+
Object.defineProperty(schema, KeyNameFromDefinition, {
25+
enumerable: false,
26+
value: keyNameFromSchema,
27+
writable: false,
28+
})
29+
})
30+
31+
annotators.add(function annotateKeyNameFrom$Ref(schema, _, dereferencedPaths) {
32+
if (schema.hasOwnProperty(KeyNameFromDefinition)) {
33+
return
2134
}
2235

23-
// Add a reference to this schema's parent
24-
Object.defineProperty(schema, Parent, {
36+
const ref = dereferencedPaths.get(schema)
37+
if (!ref) {
38+
return
39+
}
40+
41+
const keyNameFromRef = getDefinitionKeyNameFromRef(ref)
42+
if (!keyNameFromRef) {
43+
return
44+
}
45+
46+
Object.defineProperty(schema, KeyNameFromDefinition, {
2547
enumerable: false,
26-
value: parent,
48+
value: keyNameFromRef,
2749
writable: false,
2850
})
51+
})
2952

30-
// Arrays
31-
if (Array.isArray(schema)) {
32-
schema.forEach(child => annotate(child, dereferencedPaths, schema))
53+
/**
54+
* If this schema came from a `definitions` block, returns the key name for the definition.
55+
*/
56+
function getDefinitionKeyNameFromParent(schema: AnnotatedJSONSchema): string | undefined {
57+
const parent = schema[Parent]
58+
if (!parent) {
59+
return
60+
}
61+
const grandparent = parent[Parent]
62+
if (!grandparent) {
63+
return
3364
}
65+
if (Object.keys(grandparent).find(_ => grandparent[_] === parent) !== 'definitions') {
66+
return
67+
}
68+
return Object.keys(parent).find(_ => parent[_] === schema)
69+
}
3470

35-
// Objects
36-
for (const key in schema) {
37-
annotate(schema[key], dereferencedPaths, schema)
71+
function getDefinitionKeyNameFromRef(ref: string): string | undefined {
72+
const parts = ref.split('/')
73+
if (parts[parts.length - 2] !== 'definitions') {
74+
return
3875
}
76+
return parts[parts.length - 1]
77+
}
78+
79+
/**
80+
* Traverses over the schema, assigning to each
81+
* node metadata that will be used downstream.
82+
*/
83+
export function annotate(schema: JSONSchema, dereferencedPaths: DereferencedPaths): AnnotatedJSONSchema {
84+
function go(s: JSONSchema, parent: JSONSchema | null): void {
85+
if (!Array.isArray(s) && !isPlainObject(s)) {
86+
return
87+
}
88+
89+
// Handle cycles
90+
if (isAnnotated(s)) {
91+
return
92+
}
93+
94+
// Run annotators
95+
annotators.forEach(f => {
96+
f(s, parent, dereferencedPaths)
97+
})
98+
99+
// Handle arrays
100+
if (Array.isArray(s)) {
101+
s.forEach(_ => go(_, s))
102+
}
103+
104+
// Handle objects
105+
for (const key in s) {
106+
go(s[key], s)
107+
}
108+
}
109+
110+
go(schema, null)
39111

40112
return schema as AnnotatedJSONSchema
41113
}

src/parser.ts

+3-49
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {JSONSchema4Type, JSONSchema4TypeName} from 'json-schema'
2-
import {findKey, includes, isPlainObject, map, memoize, omit} from 'lodash'
2+
import {includes, map, omit} from 'lodash'
33
import {format} from 'util'
44
import {Options} from './'
55
import {typesOfSchema} from './typesOfSchema'
@@ -17,10 +17,9 @@ import {
1717
} from './types/AST'
1818
import {
1919
EnumJSONSchema,
20-
getRootSchema,
2120
isBoolean,
2221
isPrimitive,
23-
JSONSchemaWithDefinitions,
22+
KeyNameFromDefinition,
2423
NormalizedJSONSchema,
2524
Parent,
2625
SchemaSchema,
@@ -143,8 +142,7 @@ function parseNonLiteral(
143142
processed: Processed,
144143
usedNames: UsedNames,
145144
): AST {
146-
const definitions = getDefinitionsMemoized(getRootSchema(schema as any)) // TODO
147-
const keyNameFromDefinition = findKey(definitions, _ => _ === schema)
145+
const keyNameFromDefinition = schema[KeyNameFromDefinition]
148146

149147
switch (type) {
150148
case 'ALL_OF':
@@ -487,47 +485,3 @@ via the \`definition\` "${key}".`
487485
})
488486
}
489487
}
490-
491-
type Definitions = {[k: string]: NormalizedJSONSchema}
492-
493-
function getDefinitions(
494-
schema: NormalizedJSONSchema,
495-
isSchema = true,
496-
processed = new Set<NormalizedJSONSchema>(),
497-
): Definitions {
498-
if (processed.has(schema)) {
499-
return {}
500-
}
501-
processed.add(schema)
502-
if (Array.isArray(schema)) {
503-
return schema.reduce(
504-
(prev, cur) => ({
505-
...prev,
506-
...getDefinitions(cur, false, processed),
507-
}),
508-
{},
509-
)
510-
}
511-
if (isPlainObject(schema)) {
512-
return {
513-
...(isSchema && hasDefinitions(schema) ? schema.$defs : {}),
514-
...Object.keys(schema).reduce<Definitions>(
515-
(prev, cur) => ({
516-
...prev,
517-
...getDefinitions(schema[cur], false, processed),
518-
}),
519-
{},
520-
),
521-
}
522-
}
523-
return {}
524-
}
525-
526-
const getDefinitionsMemoized = memoize(getDefinitions)
527-
528-
/**
529-
* TODO: Reduce rate of false positives
530-
*/
531-
function hasDefinitions(schema: NormalizedJSONSchema): schema is JSONSchemaWithDefinitions {
532-
return '$defs' in schema
533-
}

src/types/JSONSchema.ts

+6-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {JSONSchema4, JSONSchema4Type, JSONSchema4TypeName} from 'json-schema'
2-
import {isPlainObject, memoize} from 'lodash'
2+
import {isPlainObject} from 'lodash'
33

44
export type SchemaType =
55
| 'ALL_OF'
@@ -40,10 +40,15 @@ export interface JSONSchema extends JSONSchema4 {
4040
deprecated?: boolean
4141
}
4242

43+
export const KeyNameFromDefinition = Symbol('KeyNameFromDefinition')
4344
export const Parent = Symbol('Parent')
4445

4546
export interface AnnotatedJSONSchema extends JSONSchema {
4647
[Parent]: AnnotatedJSONSchema
48+
/**
49+
* The name of the definition from the original $ref that was dereferenced, if there was one.
50+
*/
51+
[KeyNameFromDefinition]?: string
4752

4853
additionalItems?: boolean | AnnotatedJSONSchema
4954
additionalProperties?: boolean | AnnotatedJSONSchema
@@ -112,24 +117,10 @@ export interface SchemaSchema extends NormalizedJSONSchema {
112117
required: string[]
113118
}
114119

115-
export interface JSONSchemaWithDefinitions extends NormalizedJSONSchema {
116-
$defs: {
117-
[k: string]: NormalizedJSONSchema
118-
}
119-
}
120-
121120
export interface CustomTypeJSONSchema extends NormalizedJSONSchema {
122121
tsType: string
123122
}
124123

125-
export const getRootSchema = memoize((schema: NormalizedJSONSchema): NormalizedJSONSchema => {
126-
const parent = schema[Parent]
127-
if (!parent) {
128-
return schema
129-
}
130-
return getRootSchema(parent)
131-
})
132-
133124
export function isBoolean(schema: AnnotatedJSONSchema | JSONSchemaType): schema is boolean {
134125
return schema === true || schema === false
135126
}

test/__snapshots__/test/test.ts.md

+57,532-84,316
Large diffs are not rendered by default.

test/__snapshots__/test/test.ts.snap

-73.2 KB
Binary file not shown.

0 commit comments

Comments
 (0)