limit.ts 3.0 KB

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