6 KeywordErrorDefinition,
9 import type {KeywordCxt} from "../../compile/validate"
10 import {_, str, nil, or, Code, getProperty, regexpCode} from "../../compile/codegen"
13 | FormatValidator<string>
14 | FormatValidator<number>
15 | AsyncFormatValidator<string>
16 | AsyncFormatValidator<number>
21 export type FormatError = ErrorObject<"format", {format: string}, string | {$data: string}>
23 const error: KeywordErrorDefinition = {
24 message: ({schemaCode}) => str`must match format "${schemaCode}"`,
25 params: ({schemaCode}) => _`{format: ${schemaCode}}`,
28 const def: CodeKeywordDefinition = {
30 type: ["number", "string"],
34 code(cxt: KeywordCxt, ruleType?: string) {
35 const {gen, data, $data, schema, schemaCode, it} = cxt
36 const {opts, errSchemaPath, schemaEnv, self} = it
37 if (!opts.validateFormats) return
39 if ($data) validate$DataFormat()
42 function validate$DataFormat(): void {
43 const fmts = gen.scopeValue("formats", {
45 code: opts.code.formats,
47 const fDef = gen.const("fDef", _`${fmts}[${schemaCode}]`)
48 const fType = gen.let("fType")
49 const format = gen.let("format")
52 _`typeof ${fDef} == "object" && !(${fDef} instanceof RegExp)`,
53 () => gen.assign(fType, _`${fDef}.type || "string"`).assign(format, _`${fDef}.validate`),
54 () => gen.assign(fType, _`"string"`).assign(format, fDef)
56 cxt.fail$data(or(unknownFmt(), invalidFmt()))
58 function unknownFmt(): Code {
59 if (opts.strictSchema === false) return nil
60 return _`${schemaCode} && !${format}`
63 function invalidFmt(): Code {
64 const callFormat = schemaEnv.$async
65 ? _`(${fDef}.async ? await ${format}(${data}) : ${format}(${data}))`
66 : _`${format}(${data})`
67 const validData = _`(typeof ${format} == "function" ? ${callFormat} : ${format}.test(${data}))`
68 return _`${format} && ${format} !== true && ${fType} === ${ruleType} && !${validData}`
72 function validateFormat(): void {
73 const formatDef: AddedFormat | undefined = self.formats[schema]
78 if (formatDef === true) return
79 const [fmtType, format, fmtRef] = getFormat(formatDef)
80 if (fmtType === ruleType) cxt.pass(validCondition())
82 function unknownFormat(): void {
83 if (opts.strictSchema === false) {
84 self.logger.warn(unknownMsg())
87 throw new Error(unknownMsg())
89 function unknownMsg(): string {
90 return `unknown format "${schema as string}" ignored in schema at path "${errSchemaPath}"`
94 function getFormat(fmtDef: AddedFormat): [string, FormatValidate, Code] {
96 fmtDef instanceof RegExp
99 ? _`${opts.code.formats}${getProperty(schema)}`
101 const fmt = gen.scopeValue("formats", {key: schema, ref: fmtDef, code})
102 if (typeof fmtDef == "object" && !(fmtDef instanceof RegExp)) {
103 return [fmtDef.type || "string", fmtDef.validate, _`${fmt}.validate`]
106 return ["string", fmtDef, fmt]
109 function validCondition(): Code {
110 if (typeof formatDef == "object" && !(formatDef instanceof RegExp) && formatDef.async) {
111 if (!schemaEnv.$async) throw new Error("async format in sync schema")
112 return _`await ${fmtRef}(${data})`
114 return typeof format == "function" ? _`${fmtRef}(${data})` : _`${fmtRef}.test(${data})`