contains.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import type {
  2. CodeKeywordDefinition,
  3. KeywordErrorDefinition,
  4. ErrorObject,
  5. AnySchema,
  6. } from "../../types"
  7. import type KeywordCxt from "../../compile/context"
  8. import {_, str, Name} from "../../compile/codegen"
  9. import {Type} from "../../compile/subschema"
  10. import {alwaysValidSchema} from "../../compile/util"
  11. import {checkStrictMode} from "../../compile/validate"
  12. export type ContainsError = ErrorObject<
  13. "contains",
  14. {minContains: number; maxContains?: number},
  15. AnySchema
  16. >
  17. const error: KeywordErrorDefinition = {
  18. message: ({params: {min, max}}) =>
  19. max === undefined
  20. ? str`should contain at least ${min} valid item(s)`
  21. : str`should contain at least ${min} and no more than ${max} valid item(s)`,
  22. params: ({params: {min, max}}) =>
  23. max === undefined ? _`{minContains: ${min}}` : _`{minContains: ${min}, maxContains: ${max}}`,
  24. }
  25. const def: CodeKeywordDefinition = {
  26. keyword: "contains",
  27. type: "array",
  28. schemaType: ["object", "boolean"],
  29. before: "uniqueItems",
  30. trackErrors: true,
  31. error,
  32. code(cxt: KeywordCxt) {
  33. const {gen, schema, parentSchema, data, it} = cxt
  34. let min: number
  35. let max: number | undefined
  36. const {minContains, maxContains} = parentSchema
  37. if (it.opts.next) {
  38. min = minContains === undefined ? 1 : minContains
  39. max = maxContains
  40. } else {
  41. min = 1
  42. }
  43. const len = gen.const("len", _`${data}.length`)
  44. cxt.setParams({min, max})
  45. if (max === undefined && min === 0) {
  46. checkStrictMode(it, `"minContains" == 0 without "maxContains": "contains" keyword ignored`)
  47. return
  48. }
  49. if (max !== undefined && min > max) {
  50. checkStrictMode(it, `"minContains" > "maxContains" is always invalid`)
  51. cxt.fail()
  52. return
  53. }
  54. if (alwaysValidSchema(it, schema)) {
  55. let cond = _`${len} >= ${min}`
  56. if (max !== undefined) cond = _`${cond} && ${len} <= ${max}`
  57. cxt.pass(cond)
  58. return
  59. }
  60. it.items = true
  61. const valid = gen.name("valid")
  62. if (max === undefined && min === 1) {
  63. validateItems(valid, () => gen.if(valid, () => gen.break()))
  64. } else {
  65. gen.let(valid, false)
  66. const schValid = gen.name("_valid")
  67. const count = gen.let("count", 0)
  68. validateItems(schValid, () => gen.if(schValid, () => checkLimits(count)))
  69. }
  70. cxt.result(valid, () => cxt.reset())
  71. function validateItems(_valid: Name, block: () => void): void {
  72. gen.forRange("i", 0, len, (i) => {
  73. cxt.subschema(
  74. {
  75. keyword: "contains",
  76. dataProp: i,
  77. dataPropType: Type.Num,
  78. compositeRule: true,
  79. },
  80. _valid
  81. )
  82. block()
  83. })
  84. }
  85. function checkLimits(count: Name): void {
  86. gen.code(_`${count}++`)
  87. if (max === undefined) {
  88. gen.if(_`${count} >= ${min}`, () => gen.assign(valid, true).break())
  89. } else {
  90. gen.if(_`${count} > ${max}`, () => gen.assign(valid, false).break())
  91. if (min === 1) gen.assign(valid, true)
  92. else gen.if(_`${count} >= ${min}`, () => gen.assign(valid, true))
  93. }
  94. }
  95. },
  96. }
  97. export default def