required.ts 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
  2. import type {KeywordCxt} from "../../compile/validate"
  3. import {
  4. checkReportMissingProp,
  5. checkMissingProp,
  6. reportMissingProp,
  7. propertyInData,
  8. noPropertyInData,
  9. } from "../code"
  10. import {_, str, nil, not, Name, Code} from "../../compile/codegen"
  11. import {checkStrictMode} from "../../compile/util"
  12. export type RequiredError = ErrorObject<
  13. "required",
  14. {missingProperty: string},
  15. string[] | {$data: string}
  16. >
  17. const error: KeywordErrorDefinition = {
  18. message: ({params: {missingProperty}}) => str`must have required property '${missingProperty}'`,
  19. params: ({params: {missingProperty}}) => _`{missingProperty: ${missingProperty}}`,
  20. }
  21. const def: CodeKeywordDefinition = {
  22. keyword: "required",
  23. type: "object",
  24. schemaType: "array",
  25. $data: true,
  26. error,
  27. code(cxt: KeywordCxt) {
  28. const {gen, schema, schemaCode, data, $data, it} = cxt
  29. const {opts} = it
  30. if (!$data && schema.length === 0) return
  31. const useLoop = schema.length >= opts.loopRequired
  32. if (it.allErrors) allErrorsMode()
  33. else exitOnErrorMode()
  34. if (opts.strictRequired) {
  35. const props = cxt.parentSchema.properties
  36. const {definedProperties} = cxt.it
  37. for (const requiredKey of schema) {
  38. if (props?.[requiredKey] === undefined && !definedProperties.has(requiredKey)) {
  39. const schemaPath = it.schemaEnv.baseId + it.errSchemaPath
  40. const msg = `required property "${requiredKey}" is not defined at "${schemaPath}" (strictRequired)`
  41. checkStrictMode(it, msg, it.opts.strictRequired)
  42. }
  43. }
  44. }
  45. function allErrorsMode(): void {
  46. if (useLoop || $data) {
  47. cxt.block$data(nil, loopAllRequired)
  48. } else {
  49. for (const prop of schema) {
  50. checkReportMissingProp(cxt, prop)
  51. }
  52. }
  53. }
  54. function exitOnErrorMode(): void {
  55. const missing = gen.let("missing")
  56. if (useLoop || $data) {
  57. const valid = gen.let("valid", true)
  58. cxt.block$data(valid, () => loopUntilMissing(missing, valid))
  59. cxt.ok(valid)
  60. } else {
  61. gen.if(checkMissingProp(cxt, schema, missing))
  62. reportMissingProp(cxt, missing)
  63. gen.else()
  64. }
  65. }
  66. function loopAllRequired(): void {
  67. gen.forOf("prop", schemaCode as Code, (prop) => {
  68. cxt.setParams({missingProperty: prop})
  69. gen.if(noPropertyInData(gen, data, prop, opts.ownProperties), () => cxt.error())
  70. })
  71. }
  72. function loopUntilMissing(missing: Name, valid: Name): void {
  73. cxt.setParams({missingProperty: missing})
  74. gen.forOf(
  75. missing,
  76. schemaCode as Code,
  77. () => {
  78. gen.assign(valid, propertyInData(gen, data, missing, opts.ownProperties))
  79. gen.if(not(valid), () => {
  80. cxt.error()
  81. gen.break()
  82. })
  83. },
  84. nil
  85. )
  86. }
  87. },
  88. }
  89. export default def