Spaces:
Runtime error
Runtime error
| import type {AnySchema, EvaluatedProperties, EvaluatedItems} from "../types" | |
| import type {SchemaCxt, SchemaObjCxt} from "." | |
| import {_, getProperty, Code, Name, CodeGen} from "./codegen" | |
| import {_Code} from "./codegen/code" | |
| import type {Rule, ValidationRules} from "./rules" | |
| // TODO refactor to use Set | |
| export function toHash<T extends string = string>(arr: T[]): {[K in T]?: true} { | |
| const hash: {[K in T]?: true} = {} | |
| for (const item of arr) hash[item] = true | |
| return hash | |
| } | |
| export function alwaysValidSchema(it: SchemaCxt, schema: AnySchema): boolean | void { | |
| if (typeof schema == "boolean") return schema | |
| if (Object.keys(schema).length === 0) return true | |
| checkUnknownRules(it, schema) | |
| return !schemaHasRules(schema, it.self.RULES.all) | |
| } | |
| export function checkUnknownRules(it: SchemaCxt, schema: AnySchema = it.schema): void { | |
| const {opts, self} = it | |
| if (!opts.strictSchema) return | |
| if (typeof schema === "boolean") return | |
| const rules = self.RULES.keywords | |
| for (const key in schema) { | |
| if (!rules[key]) checkStrictMode(it, `unknown keyword: "${key}"`) | |
| } | |
| } | |
| export function schemaHasRules( | |
| schema: AnySchema, | |
| rules: {[Key in string]?: boolean | Rule} | |
| ): boolean { | |
| if (typeof schema == "boolean") return !schema | |
| for (const key in schema) if (rules[key]) return true | |
| return false | |
| } | |
| export function schemaHasRulesButRef(schema: AnySchema, RULES: ValidationRules): boolean { | |
| if (typeof schema == "boolean") return !schema | |
| for (const key in schema) if (key !== "$ref" && RULES.all[key]) return true | |
| return false | |
| } | |
| export function schemaRefOrVal( | |
| {topSchemaRef, schemaPath}: SchemaObjCxt, | |
| schema: unknown, | |
| keyword: string, | |
| $data?: string | false | |
| ): Code | number | boolean { | |
| if (!$data) { | |
| if (typeof schema == "number" || typeof schema == "boolean") return schema | |
| if (typeof schema == "string") return _`${schema}` | |
| } | |
| return _`${topSchemaRef}${schemaPath}${getProperty(keyword)}` | |
| } | |
| export function unescapeFragment(str: string): string { | |
| return unescapeJsonPointer(decodeURIComponent(str)) | |
| } | |
| export function escapeFragment(str: string | number): string { | |
| return encodeURIComponent(escapeJsonPointer(str)) | |
| } | |
| export function escapeJsonPointer(str: string | number): string { | |
| if (typeof str == "number") return `${str}` | |
| return str.replace(/~/g, "~0").replace(/\//g, "~1") | |
| } | |
| export function unescapeJsonPointer(str: string): string { | |
| return str.replace(/~1/g, "/").replace(/~0/g, "~") | |
| } | |
| export function eachItem<T>(xs: T | T[], f: (x: T) => void): void { | |
| if (Array.isArray(xs)) { | |
| for (const x of xs) f(x) | |
| } else { | |
| f(xs) | |
| } | |
| } | |
| type SomeEvaluated = EvaluatedProperties | EvaluatedItems | |
| type MergeEvaluatedFunc<T extends SomeEvaluated> = ( | |
| gen: CodeGen, | |
| from: Name | T, | |
| to: Name | Exclude<T, true> | undefined, | |
| toName?: typeof Name | |
| ) => Name | T | |
| interface MakeMergeFuncArgs<T extends SomeEvaluated> { | |
| mergeNames: (gen: CodeGen, from: Name, to: Name) => void | |
| mergeToName: (gen: CodeGen, from: T, to: Name) => void | |
| mergeValues: (from: T, to: Exclude<T, true>) => T | |
| resultToName: (gen: CodeGen, res?: T) => Name | |
| } | |
| function makeMergeEvaluated<T extends SomeEvaluated>({ | |
| mergeNames, | |
| mergeToName, | |
| mergeValues, | |
| resultToName, | |
| }: MakeMergeFuncArgs<T>): MergeEvaluatedFunc<T> { | |
| return (gen, from, to, toName) => { | |
| const res = | |
| to === undefined | |
| ? from | |
| : to instanceof Name | |
| ? (from instanceof Name ? mergeNames(gen, from, to) : mergeToName(gen, from, to), to) | |
| : from instanceof Name | |
| ? (mergeToName(gen, to, from), from) | |
| : mergeValues(from, to) | |
| return toName === Name && !(res instanceof Name) ? resultToName(gen, res) : res | |
| } | |
| } | |
| interface MergeEvaluated { | |
| props: MergeEvaluatedFunc<EvaluatedProperties> | |
| items: MergeEvaluatedFunc<EvaluatedItems> | |
| } | |
| export const mergeEvaluated: MergeEvaluated = { | |
| props: makeMergeEvaluated({ | |
| mergeNames: (gen, from, to) => | |
| gen.if(_`${to} !== true && ${from} !== undefined`, () => { | |
| gen.if( | |
| _`${from} === true`, | |
| () => gen.assign(to, true), | |
| () => gen.assign(to, _`${to} || {}`).code(_`Object.assign(${to}, ${from})`) | |
| ) | |
| }), | |
| mergeToName: (gen, from, to) => | |
| gen.if(_`${to} !== true`, () => { | |
| if (from === true) { | |
| gen.assign(to, true) | |
| } else { | |
| gen.assign(to, _`${to} || {}`) | |
| setEvaluated(gen, to, from) | |
| } | |
| }), | |
| mergeValues: (from, to) => (from === true ? true : {...from, ...to}), | |
| resultToName: evaluatedPropsToName, | |
| }), | |
| items: makeMergeEvaluated({ | |
| mergeNames: (gen, from, to) => | |
| gen.if(_`${to} !== true && ${from} !== undefined`, () => | |
| gen.assign(to, _`${from} === true ? true : ${to} > ${from} ? ${to} : ${from}`) | |
| ), | |
| mergeToName: (gen, from, to) => | |
| gen.if(_`${to} !== true`, () => | |
| gen.assign(to, from === true ? true : _`${to} > ${from} ? ${to} : ${from}`) | |
| ), | |
| mergeValues: (from, to) => (from === true ? true : Math.max(from, to)), | |
| resultToName: (gen, items) => gen.var("items", items), | |
| }), | |
| } | |
| export function evaluatedPropsToName(gen: CodeGen, ps?: EvaluatedProperties): Name { | |
| if (ps === true) return gen.var("props", true) | |
| const props = gen.var("props", _`{}`) | |
| if (ps !== undefined) setEvaluated(gen, props, ps) | |
| return props | |
| } | |
| export function setEvaluated(gen: CodeGen, props: Name, ps: {[K in string]?: true}): void { | |
| Object.keys(ps).forEach((p) => gen.assign(_`${props}${getProperty(p)}`, true)) | |
| } | |
| const snippets: {[S in string]?: _Code} = {} | |
| export function useFunc(gen: CodeGen, f: {code: string}): Name { | |
| return gen.scopeValue("func", { | |
| ref: f, | |
| code: snippets[f.code] || (snippets[f.code] = new _Code(f.code)), | |
| }) | |
| } | |
| export enum Type { | |
| Num, | |
| Str, | |
| } | |
| export 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) | |
| } | |
| export function checkStrictMode( | |
| it: SchemaCxt, | |
| msg: string, | |
| mode: boolean | "log" = it.opts.strictSchema | |
| ): void { | |
| if (!mode) return | |
| msg = `strict mode: ${msg}` | |
| if (mode === true) throw new Error(msg) | |
| it.self.logger.warn(msg) | |
| } | |