Skip to content

Commit

Permalink
[1/x] generalize linker to annotator
Browse files Browse the repository at this point in the history
  • Loading branch information
bcherny committed Jun 25, 2024
1 parent 6fc74b3 commit 44793a4
Show file tree
Hide file tree
Showing 14 changed files with 150 additions and 145 deletions.
4 changes: 2 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ TODO use an external validation library

Resolves referenced schemas (in the file, on the local filesystem, or over the network).

#### 3. Linker
#### 3. Annotator

Adds links back from each node in a schema to its parent (available via the `Parent` symbol on each node), for convenience.
Annotates the JSON schema with metadata that will be used later on. For example, this step adds links back from each node in a schema to its parent (available via the `Metadata` symbol on each node), for convenience.

#### 4. Normalizer

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ See [server demo](example) and [browser demo](https://github.com/bcherny/json-sc
|-|-|-|-|
| additionalProperties | boolean | `true` | Default value for `additionalProperties`, when it is not explicitly set |
| bannerComment | string | `"/* eslint-disable */\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source JSON Schema file,\n* and run json-schema-to-typescript to regenerate this file.\n*/"` | Disclaimer comment prepended to the top of each generated file |
| customName | `(LinkedJSONSchema, string \| undefined) => string \| undefined` | `undefined` | Custom function to provide a type name for a given schema
| customName | `(JSONSchema, string \| undefined) => string \| undefined` | `undefined` | Custom function to provide a type name for a given schema
| cwd | string | `process.cwd()` | Root directory for resolving [`$ref`](https://tools.ietf.org/id/draft-pbryan-zyp-json-ref-03.html)s |
| declareExternallyReferenced | boolean | `true` | Declare external schemas referenced via `$ref`? |
| enableConstEnums | boolean | `true` | Prepend enums with [`const`](https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)? |
Expand Down
41 changes: 41 additions & 0 deletions src/annotator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {isPlainObject} from 'lodash'
import {DereferencedPaths} from './resolver'
import {AnnotatedJSONSchema, JSONSchema, Parent, isAnnotated} from './types/JSONSchema'

/**
* Traverses over the schema, assigning to each
* node metadata that will be used downstream.
*/
export function annotate(
schema: JSONSchema,
dereferencedPaths: DereferencedPaths,
parent: JSONSchema | null = null,
): AnnotatedJSONSchema {
if (!Array.isArray(schema) && !isPlainObject(schema)) {
return schema as AnnotatedJSONSchema
}

// Handle cycles
if (isAnnotated(schema)) {
return schema
}

// Add a reference to this schema's parent
Object.defineProperty(schema, Parent, {
enumerable: false,
value: parent,
writable: false,
})

// Arrays
if (Array.isArray(schema)) {
schema.forEach(child => annotate(child, dereferencedPaths, schema))
}

// Objects
for (const key in schema) {
annotate(schema[key], dereferencedPaths, schema)
}

return schema as AnnotatedJSONSchema
}
14 changes: 7 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import {dereference} from './resolver'
import {error, stripExtension, Try, log, parseFileAsJSONSchema} from './utils'
import {validate} from './validator'
import {isDeepStrictEqual} from 'util'
import {link} from './linker'
import {annotate} from './annotator'
import {validateOptions} from './optionValidator'
import {JSONSchema as LinkedJSONSchema} from './types/JSONSchema'
import {AnnotatedJSONSchema} from './types/JSONSchema'

export {EnumJSONSchema, JSONSchema, NamedEnumJSONSchema, CustomTypeJSONSchema} from './types/JSONSchema'

Expand All @@ -35,7 +35,7 @@ export interface Options {
/**
* Custom function to provide a type name for a given schema
*/
customName?: (schema: LinkedJSONSchema, keyNameFromDefinition: string | undefined) => string | undefined
customName?: (schema: AnnotatedJSONSchema, keyNameFromDefinition: string | undefined) => string | undefined
/**
* Root directory for resolving [`$ref`](https://tools.ietf.org/id/draft-pbryan-zyp-json-ref-03.html)s.
*/
Expand Down Expand Up @@ -159,12 +159,12 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia
}
}

const linked = link(dereferencedSchema)
const annotated = annotate(dereferencedSchema, dereferencedPaths)
if (process.env.VERBOSE) {
log('green', 'linker', time(), '✅ No change')
log('green', 'annotater', time(), '✅ No change')
}

const errors = validate(linked, name)
const errors = validate(annotated, name)
if (errors.length) {
errors.forEach(_ => error(_))
throw new ValidationError()
Expand All @@ -173,7 +173,7 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia
log('green', 'validator', time(), '✅ No change')
}

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

const parsed = parse(normalized, _options)
Expand Down
37 changes: 0 additions & 37 deletions src/linker.ts

This file was deleted.

14 changes: 7 additions & 7 deletions src/normalizer.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import {JSONSchemaTypeName, LinkedJSONSchema, NormalizedJSONSchema, Parent} from './types/JSONSchema'
import {JSONSchemaTypeName, AnnotatedJSONSchema, NormalizedJSONSchema, JSONSchema, Parent} from './types/JSONSchema'
import {appendToDescription, escapeBlockComment, isSchemaLike, justName, toSafeString, traverse} from './utils'
import {Options} from './'
import {DereferencedPaths} from './resolver'
import {isDeepStrictEqual} from 'util'

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

function hasType(schema: LinkedJSONSchema, type: JSONSchemaTypeName) {
function hasType(schema: JSONSchema, type: JSONSchemaTypeName) {
return schema.type === type || (Array.isArray(schema.type) && schema.type.includes(type))
}
function isObjectType(schema: LinkedJSONSchema) {
function isObjectType(schema: JSONSchema) {
return schema.properties !== undefined || hasType(schema, 'object') || hasType(schema, 'any')
}
function isArrayType(schema: LinkedJSONSchema) {
function isArrayType(schema: JSONSchema) {
return schema.items !== undefined || hasType(schema, 'array') || hasType(schema, 'any')
}
function isEnumTypeWithoutTsEnumNames(schema: LinkedJSONSchema) {
function isEnumTypeWithoutTsEnumNames(schema: JSONSchema) {
return schema.type === 'string' && schema.enum !== undefined && schema.tsEnumNames === undefined
}

Expand Down Expand Up @@ -232,7 +232,7 @@ rules.set('Add tsEnumNames to enum types', (schema, _, options) => {
})

export function normalize(
rootSchema: LinkedJSONSchema,
rootSchema: AnnotatedJSONSchema,
dereferencedPaths: DereferencedPaths,
filename: string,
options: Options,
Expand Down
42 changes: 21 additions & 21 deletions src/types/JSONSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,41 +42,37 @@ export interface JSONSchema extends JSONSchema4 {

export const Parent = Symbol('Parent')

export interface LinkedJSONSchema extends JSONSchema {
/**
* A reference to this schema's parent node, for convenience.
* `null` when this is the root schema.
*/
[Parent]: LinkedJSONSchema | null
export interface AnnotatedJSONSchema extends JSONSchema {
[Parent]: AnnotatedJSONSchema

additionalItems?: boolean | LinkedJSONSchema
additionalProperties?: boolean | LinkedJSONSchema
items?: LinkedJSONSchema | LinkedJSONSchema[]
additionalItems?: boolean | AnnotatedJSONSchema
additionalProperties?: boolean | AnnotatedJSONSchema
items?: AnnotatedJSONSchema | AnnotatedJSONSchema[]
definitions?: {
[k: string]: LinkedJSONSchema
[k: string]: AnnotatedJSONSchema
}
properties?: {
[k: string]: LinkedJSONSchema
[k: string]: AnnotatedJSONSchema
}
patternProperties?: {
[k: string]: LinkedJSONSchema
[k: string]: AnnotatedJSONSchema
}
dependencies?: {
[k: string]: LinkedJSONSchema | string[]
[k: string]: AnnotatedJSONSchema | string[]
}
allOf?: LinkedJSONSchema[]
anyOf?: LinkedJSONSchema[]
oneOf?: LinkedJSONSchema[]
not?: LinkedJSONSchema
allOf?: AnnotatedJSONSchema[]
anyOf?: AnnotatedJSONSchema[]
oneOf?: AnnotatedJSONSchema[]
not?: AnnotatedJSONSchema
}

/**
* Normalized JSON schema.
*
* Note: `definitions` and `id` are removed by the normalizer. Use `$defs` and `$id` instead.
*/
export interface NormalizedJSONSchema extends Omit<LinkedJSONSchema, 'definitions' | 'id'> {
[Parent]: NormalizedJSONSchema | null
export interface NormalizedJSONSchema extends Omit<AnnotatedJSONSchema, 'definitions' | 'id'> {
[Parent]: NormalizedJSONSchema

additionalItems?: boolean | NormalizedJSONSchema
additionalProperties: boolean | NormalizedJSONSchema
Expand Down Expand Up @@ -134,14 +130,18 @@ export const getRootSchema = memoize((schema: NormalizedJSONSchema): NormalizedJ
return getRootSchema(parent)
})

export function isBoolean(schema: LinkedJSONSchema | JSONSchemaType): schema is boolean {
export function isBoolean(schema: AnnotatedJSONSchema | JSONSchemaType): schema is boolean {
return schema === true || schema === false
}

export function isPrimitive(schema: LinkedJSONSchema | JSONSchemaType): schema is JSONSchemaType {
export function isPrimitive(schema: AnnotatedJSONSchema | JSONSchemaType): schema is JSONSchemaType {
return !isPlainObject(schema)
}

export function isCompound(schema: JSONSchema): boolean {
return Array.isArray(schema.type) || 'anyOf' in schema || 'oneOf' in schema
}

export function isAnnotated(schema: JSONSchema): schema is AnnotatedJSONSchema {
return schema.hasOwnProperty(Parent)
}
Loading

0 comments on commit 44793a4

Please sign in to comment.