mirror of https://github.com/jkjoy/sunpeiwen.git
170 lines
5.4 KiB
TypeScript
170 lines
5.4 KiB
TypeScript
import type {AnySchema} from "../types"
|
|
import type {SchemaObjCxt, SchemaCxt} from "./index"
|
|
import {subschemaCode} from "./validate"
|
|
import {escapeFragment, escapeJsonPointer} from "./util"
|
|
import {_, str, Code, Name, getProperty} from "./codegen"
|
|
import {JSONType} from "./rules"
|
|
|
|
interface SubschemaContext {
|
|
// TODO use Optional? align with SchemCxt property types
|
|
schema: AnySchema
|
|
schemaPath: Code
|
|
errSchemaPath: string
|
|
topSchemaRef?: Code
|
|
errorPath?: Code
|
|
dataLevel?: number
|
|
dataTypes?: JSONType[]
|
|
data?: Name
|
|
parentData?: Name
|
|
parentDataProperty?: Code | number
|
|
dataNames?: Name[]
|
|
dataPathArr?: (Code | number)[]
|
|
propertyName?: Name
|
|
jtdDiscriminator?: string
|
|
jtdMetadata?: boolean
|
|
compositeRule?: true
|
|
createErrors?: boolean
|
|
allErrors?: boolean
|
|
}
|
|
|
|
export enum Type {
|
|
Num,
|
|
Str,
|
|
}
|
|
|
|
export type SubschemaArgs = Partial<{
|
|
keyword: string
|
|
schemaProp: string | number
|
|
schema: AnySchema
|
|
schemaPath: Code
|
|
errSchemaPath: string
|
|
topSchemaRef: Code
|
|
data: Name | Code
|
|
dataProp: Code | string | number
|
|
dataTypes: JSONType[]
|
|
definedProperties: Set<string>
|
|
propertyName: Name
|
|
dataPropType: Type
|
|
jtdDiscriminator: string
|
|
jtdMetadata: boolean
|
|
compositeRule: true
|
|
createErrors: boolean
|
|
allErrors: boolean
|
|
}>
|
|
|
|
export function applySubschema(it: SchemaObjCxt, appl: SubschemaArgs, valid: Name): SchemaCxt {
|
|
const subschema = getSubschema(it, appl)
|
|
extendSubschemaData(subschema, it, appl)
|
|
extendSubschemaMode(subschema, appl)
|
|
const nextContext = {...it, ...subschema, items: undefined, props: undefined}
|
|
subschemaCode(nextContext, valid)
|
|
return nextContext
|
|
}
|
|
|
|
function getSubschema(
|
|
it: SchemaObjCxt,
|
|
{keyword, schemaProp, schema, schemaPath, errSchemaPath, topSchemaRef}: SubschemaArgs
|
|
): SubschemaContext {
|
|
if (keyword !== undefined && schema !== undefined) {
|
|
throw new Error('both "keyword" and "schema" passed, only one allowed')
|
|
}
|
|
|
|
if (keyword !== undefined) {
|
|
const sch = it.schema[keyword]
|
|
return schemaProp === undefined
|
|
? {
|
|
schema: sch,
|
|
schemaPath: _`${it.schemaPath}${getProperty(keyword)}`,
|
|
errSchemaPath: `${it.errSchemaPath}/${keyword}`,
|
|
}
|
|
: {
|
|
schema: sch[schemaProp],
|
|
schemaPath: _`${it.schemaPath}${getProperty(keyword)}${getProperty(schemaProp)}`,
|
|
errSchemaPath: `${it.errSchemaPath}/${keyword}/${escapeFragment(schemaProp)}`,
|
|
}
|
|
}
|
|
|
|
if (schema !== undefined) {
|
|
if (schemaPath === undefined || errSchemaPath === undefined || topSchemaRef === undefined) {
|
|
throw new Error('"schemaPath", "errSchemaPath" and "topSchemaRef" are required with "schema"')
|
|
}
|
|
return {
|
|
schema,
|
|
schemaPath,
|
|
topSchemaRef,
|
|
errSchemaPath,
|
|
}
|
|
}
|
|
|
|
throw new Error('either "keyword" or "schema" must be passed')
|
|
}
|
|
|
|
function extendSubschemaData(
|
|
subschema: SubschemaContext,
|
|
it: SchemaObjCxt,
|
|
{dataProp, dataPropType: dpType, data, dataTypes, propertyName}: SubschemaArgs
|
|
): void {
|
|
if (data !== undefined && dataProp !== undefined) {
|
|
throw new Error('both "data" and "dataProp" passed, only one allowed')
|
|
}
|
|
|
|
const {gen} = it
|
|
|
|
if (dataProp !== undefined) {
|
|
const {errorPath, dataPathArr, opts} = it
|
|
const nextData = gen.let("data", _`${it.data}${getProperty(dataProp)}`, true)
|
|
dataContextProps(nextData)
|
|
subschema.errorPath = str`${errorPath}${getErrorPath(dataProp, dpType, opts.jsPropertySyntax)}`
|
|
subschema.parentDataProperty = _`${dataProp}`
|
|
subschema.dataPathArr = [...dataPathArr, subschema.parentDataProperty]
|
|
}
|
|
|
|
if (data !== undefined) {
|
|
const nextData = data instanceof Name ? data : gen.let("data", data, true) // replaceable if used once?
|
|
dataContextProps(nextData)
|
|
if (propertyName !== undefined) subschema.propertyName = propertyName
|
|
// TODO something is possibly wrong here with not changing parentDataProperty and not appending dataPathArr
|
|
}
|
|
|
|
if (dataTypes) subschema.dataTypes = dataTypes
|
|
|
|
function dataContextProps(_nextData: Name): void {
|
|
subschema.data = _nextData
|
|
subschema.dataLevel = it.dataLevel + 1
|
|
subschema.dataTypes = []
|
|
it.definedProperties = new Set<string>()
|
|
subschema.parentData = it.data
|
|
subschema.dataNames = [...it.dataNames, _nextData]
|
|
}
|
|
}
|
|
|
|
function extendSubschemaMode(
|
|
subschema: SubschemaContext,
|
|
{jtdDiscriminator, jtdMetadata, compositeRule, createErrors, allErrors}: SubschemaArgs
|
|
): void {
|
|
if (compositeRule !== undefined) subschema.compositeRule = compositeRule
|
|
if (createErrors !== undefined) subschema.createErrors = createErrors
|
|
if (allErrors !== undefined) subschema.allErrors = allErrors
|
|
subschema.jtdDiscriminator = jtdDiscriminator // not inherited
|
|
subschema.jtdMetadata = jtdMetadata // not inherited
|
|
}
|
|
|
|
function getErrorPath(
|
|
dataProp: Name | string | number,
|
|
dataPropType?: Type,
|
|
jsPropertySyntax?: boolean
|
|
): Code | string {
|
|
// let path
|
|
if (dataProp instanceof Name) {
|
|
const isNumber = dataPropType === Type.Num
|
|
return jsPropertySyntax
|
|
? isNumber
|
|
? _`"[" + ${dataProp} + "]"`
|
|
: _`"['" + ${dataProp} + "']"`
|
|
: isNumber
|
|
? _`"/" + ${dataProp}`
|
|
: _`"/" + ${dataProp}.replace(/~/g, "~0").replace(/\\//g, "~1")` // TODO maybe use global escapePointer
|
|
}
|
|
return jsPropertySyntax ? getProperty(dataProp).toString() : "/" + escapeJsonPointer(dataProp)
|
|
}
|