contains.ts 3.2 KB

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