123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- 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)
- }
|