1 | import type Ajv from "ajv"
|
---|
2 | import type {
|
---|
3 | Plugin,
|
---|
4 | CodeKeywordDefinition,
|
---|
5 | KeywordErrorDefinition,
|
---|
6 | Code,
|
---|
7 | Name,
|
---|
8 | ErrorObject,
|
---|
9 | } from "ajv"
|
---|
10 | import type {AddedFormat} from "ajv/dist/types"
|
---|
11 | import type {Rule} from "ajv/dist/compile/rules"
|
---|
12 | import {KeywordCxt} from "ajv"
|
---|
13 | import {_, str, or, getProperty, operators} from "ajv/dist/compile/codegen"
|
---|
14 |
|
---|
15 | type Kwd = "formatMaximum" | "formatMinimum" | "formatExclusiveMaximum" | "formatExclusiveMinimum"
|
---|
16 |
|
---|
17 | type Comparison = "<=" | ">=" | "<" | ">"
|
---|
18 |
|
---|
19 | const ops = operators
|
---|
20 |
|
---|
21 | const KWDs: {[K in Kwd]: {okStr: Comparison; ok: Code; fail: Code}} = {
|
---|
22 | formatMaximum: {okStr: "<=", ok: ops.LTE, fail: ops.GT},
|
---|
23 | formatMinimum: {okStr: ">=", ok: ops.GTE, fail: ops.LT},
|
---|
24 | formatExclusiveMaximum: {okStr: "<", ok: ops.LT, fail: ops.GTE},
|
---|
25 | formatExclusiveMinimum: {okStr: ">", ok: ops.GT, fail: ops.LTE},
|
---|
26 | }
|
---|
27 |
|
---|
28 | export type LimitFormatError = ErrorObject<Kwd, {limit: string; comparison: Comparison}>
|
---|
29 |
|
---|
30 | const error: KeywordErrorDefinition = {
|
---|
31 | message: ({keyword, schemaCode}) => str`should be ${KWDs[keyword as Kwd].okStr} ${schemaCode}`,
|
---|
32 | params: ({keyword, schemaCode}) =>
|
---|
33 | _`{comparison: ${KWDs[keyword as Kwd].okStr}, limit: ${schemaCode}}`,
|
---|
34 | }
|
---|
35 |
|
---|
36 | export const formatLimitDefinition: CodeKeywordDefinition = {
|
---|
37 | keyword: Object.keys(KWDs),
|
---|
38 | type: "string",
|
---|
39 | schemaType: "string",
|
---|
40 | $data: true,
|
---|
41 | error,
|
---|
42 | code(cxt) {
|
---|
43 | const {gen, data, schemaCode, keyword, it} = cxt
|
---|
44 | const {opts, self} = it
|
---|
45 | if (!opts.validateFormats) return
|
---|
46 |
|
---|
47 | const fCxt = new KeywordCxt(it, (self.RULES.all.format as Rule).definition, "format")
|
---|
48 | if (fCxt.$data) validate$DataFormat()
|
---|
49 | else validateFormat()
|
---|
50 |
|
---|
51 | function validate$DataFormat(): void {
|
---|
52 | const fmts = gen.scopeValue("formats", {
|
---|
53 | ref: self.formats,
|
---|
54 | code: opts.code.formats,
|
---|
55 | })
|
---|
56 | const fmt = gen.const("fmt", _`${fmts}[${fCxt.schemaCode}]`)
|
---|
57 | cxt.fail$data(
|
---|
58 | or(
|
---|
59 | _`typeof ${fmt} != "object"`,
|
---|
60 | _`${fmt} instanceof RegExp`,
|
---|
61 | _`typeof ${fmt}.compare != "function"`,
|
---|
62 | compareCode(fmt)
|
---|
63 | )
|
---|
64 | )
|
---|
65 | }
|
---|
66 |
|
---|
67 | function validateFormat(): void {
|
---|
68 | const format = fCxt.schema as string
|
---|
69 | const fmtDef: AddedFormat | undefined = self.formats[format]
|
---|
70 | if (!fmtDef || fmtDef === true) return
|
---|
71 | if (
|
---|
72 | typeof fmtDef != "object" ||
|
---|
73 | fmtDef instanceof RegExp ||
|
---|
74 | typeof fmtDef.compare != "function"
|
---|
75 | ) {
|
---|
76 | throw new Error(`"${keyword}": format "${format}" does not define "compare" function`)
|
---|
77 | }
|
---|
78 | const fmt = gen.scopeValue("formats", {
|
---|
79 | key: format,
|
---|
80 | ref: fmtDef,
|
---|
81 | code: opts.code.formats ? _`${opts.code.formats}${getProperty(format)}` : undefined,
|
---|
82 | })
|
---|
83 |
|
---|
84 | cxt.fail$data(compareCode(fmt))
|
---|
85 | }
|
---|
86 |
|
---|
87 | function compareCode(fmt: Name): Code {
|
---|
88 | return _`${fmt}.compare(${data}, ${schemaCode}) ${KWDs[keyword as Kwd].fail} 0`
|
---|
89 | }
|
---|
90 | },
|
---|
91 | dependencies: ["format"],
|
---|
92 | }
|
---|
93 |
|
---|
94 | const formatLimitPlugin: Plugin<undefined> = (ajv: Ajv): Ajv => {
|
---|
95 | ajv.addKeyword(formatLimitDefinition)
|
---|
96 | return ajv
|
---|
97 | }
|
---|
98 |
|
---|
99 | export default formatLimitPlugin
|
---|