1 | import type {
|
---|
2 | AddedFormat,
|
---|
3 | FormatValidator,
|
---|
4 | AsyncFormatValidator,
|
---|
5 | CodeKeywordDefinition,
|
---|
6 | KeywordErrorDefinition,
|
---|
7 | ErrorObject,
|
---|
8 | } from "../../types"
|
---|
9 | import type {KeywordCxt} from "../../compile/validate"
|
---|
10 | import {_, str, nil, or, Code, getProperty, regexpCode} from "../../compile/codegen"
|
---|
11 |
|
---|
12 | type FormatValidate =
|
---|
13 | | FormatValidator<string>
|
---|
14 | | FormatValidator<number>
|
---|
15 | | AsyncFormatValidator<string>
|
---|
16 | | AsyncFormatValidator<number>
|
---|
17 | | RegExp
|
---|
18 | | string
|
---|
19 | | true
|
---|
20 |
|
---|
21 | export type FormatError = ErrorObject<"format", {format: string}, string | {$data: string}>
|
---|
22 |
|
---|
23 | const error: KeywordErrorDefinition = {
|
---|
24 | message: ({schemaCode}) => str`must match format "${schemaCode}"`,
|
---|
25 | params: ({schemaCode}) => _`{format: ${schemaCode}}`,
|
---|
26 | }
|
---|
27 |
|
---|
28 | const def: CodeKeywordDefinition = {
|
---|
29 | keyword: "format",
|
---|
30 | type: ["number", "string"],
|
---|
31 | schemaType: "string",
|
---|
32 | $data: true,
|
---|
33 | error,
|
---|
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
|
---|
38 |
|
---|
39 | if ($data) validate$DataFormat()
|
---|
40 | else validateFormat()
|
---|
41 |
|
---|
42 | function validate$DataFormat(): void {
|
---|
43 | const fmts = gen.scopeValue("formats", {
|
---|
44 | ref: self.formats,
|
---|
45 | code: opts.code.formats,
|
---|
46 | })
|
---|
47 | const fDef = gen.const("fDef", _`${fmts}[${schemaCode}]`)
|
---|
48 | const fType = gen.let("fType")
|
---|
49 | const format = gen.let("format")
|
---|
50 | // TODO simplify
|
---|
51 | gen.if(
|
---|
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)
|
---|
55 | )
|
---|
56 | cxt.fail$data(or(unknownFmt(), invalidFmt()))
|
---|
57 |
|
---|
58 | function unknownFmt(): Code {
|
---|
59 | if (opts.strictSchema === false) return nil
|
---|
60 | return _`${schemaCode} && !${format}`
|
---|
61 | }
|
---|
62 |
|
---|
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}`
|
---|
69 | }
|
---|
70 | }
|
---|
71 |
|
---|
72 | function validateFormat(): void {
|
---|
73 | const formatDef: AddedFormat | undefined = self.formats[schema]
|
---|
74 | if (!formatDef) {
|
---|
75 | unknownFormat()
|
---|
76 | return
|
---|
77 | }
|
---|
78 | if (formatDef === true) return
|
---|
79 | const [fmtType, format, fmtRef] = getFormat(formatDef)
|
---|
80 | if (fmtType === ruleType) cxt.pass(validCondition())
|
---|
81 |
|
---|
82 | function unknownFormat(): void {
|
---|
83 | if (opts.strictSchema === false) {
|
---|
84 | self.logger.warn(unknownMsg())
|
---|
85 | return
|
---|
86 | }
|
---|
87 | throw new Error(unknownMsg())
|
---|
88 |
|
---|
89 | function unknownMsg(): string {
|
---|
90 | return `unknown format "${schema as string}" ignored in schema at path "${errSchemaPath}"`
|
---|
91 | }
|
---|
92 | }
|
---|
93 |
|
---|
94 | function getFormat(fmtDef: AddedFormat): [string, FormatValidate, Code] {
|
---|
95 | const code =
|
---|
96 | fmtDef instanceof RegExp
|
---|
97 | ? regexpCode(fmtDef)
|
---|
98 | : opts.code.formats
|
---|
99 | ? _`${opts.code.formats}${getProperty(schema)}`
|
---|
100 | : undefined
|
---|
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`]
|
---|
104 | }
|
---|
105 |
|
---|
106 | return ["string", fmtDef, fmt]
|
---|
107 | }
|
---|
108 |
|
---|
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})`
|
---|
113 | }
|
---|
114 | return typeof format == "function" ? _`${fmtRef}(${data})` : _`${fmtRef}.test(${data})`
|
---|
115 | }
|
---|
116 | }
|
---|
117 | },
|
---|
118 | }
|
---|
119 |
|
---|
120 | export default def
|
---|