format.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import type {
  2. AddedFormat,
  3. FormatValidator,
  4. AsyncFormatValidator,
  5. CodeKeywordDefinition,
  6. KeywordErrorDefinition,
  7. ErrorObject,
  8. } from "../../types"
  9. import type {KeywordCxt} from "../../compile/validate"
  10. import {_, str, nil, or, Code, getProperty, regexpCode} from "../../compile/codegen"
  11. type FormatValidate =
  12. | FormatValidator<string>
  13. | FormatValidator<number>
  14. | AsyncFormatValidator<string>
  15. | AsyncFormatValidator<number>
  16. | RegExp
  17. | string
  18. | true
  19. export type FormatError = ErrorObject<"format", {format: string}, string | {$data: string}>
  20. const error: KeywordErrorDefinition = {
  21. message: ({schemaCode}) => str`must match format "${schemaCode}"`,
  22. params: ({schemaCode}) => _`{format: ${schemaCode}}`,
  23. }
  24. const def: CodeKeywordDefinition = {
  25. keyword: "format",
  26. type: ["number", "string"],
  27. schemaType: "string",
  28. $data: true,
  29. error,
  30. code(cxt: KeywordCxt, ruleType?: string) {
  31. const {gen, data, $data, schema, schemaCode, it} = cxt
  32. const {opts, errSchemaPath, schemaEnv, self} = it
  33. if (!opts.validateFormats) return
  34. if ($data) validate$DataFormat()
  35. else validateFormat()
  36. function validate$DataFormat(): void {
  37. const fmts = gen.scopeValue("formats", {
  38. ref: self.formats,
  39. code: opts.code.formats,
  40. })
  41. const fDef = gen.const("fDef", _`${fmts}[${schemaCode}]`)
  42. const fType = gen.let("fType")
  43. const format = gen.let("format")
  44. // TODO simplify
  45. gen.if(
  46. _`typeof ${fDef} == "object" && !(${fDef} instanceof RegExp)`,
  47. () => gen.assign(fType, _`${fDef}.type || "string"`).assign(format, _`${fDef}.validate`),
  48. () => gen.assign(fType, _`"string"`).assign(format, fDef)
  49. )
  50. cxt.fail$data(or(unknownFmt(), invalidFmt()))
  51. function unknownFmt(): Code {
  52. if (opts.strictSchema === false) return nil
  53. return _`${schemaCode} && !${format}`
  54. }
  55. function invalidFmt(): Code {
  56. const callFormat = schemaEnv.$async
  57. ? _`(${fDef}.async ? await ${format}(${data}) : ${format}(${data}))`
  58. : _`${format}(${data})`
  59. const validData = _`(typeof ${format} == "function" ? ${callFormat} : ${format}.test(${data}))`
  60. return _`${format} && ${format} !== true && ${fType} === ${ruleType} && !${validData}`
  61. }
  62. }
  63. function validateFormat(): void {
  64. const formatDef: AddedFormat | undefined = self.formats[schema]
  65. if (!formatDef) {
  66. unknownFormat()
  67. return
  68. }
  69. if (formatDef === true) return
  70. const [fmtType, format, fmtRef] = getFormat(formatDef)
  71. if (fmtType === ruleType) cxt.pass(validCondition())
  72. function unknownFormat(): void {
  73. if (opts.strictSchema === false) {
  74. self.logger.warn(unknownMsg())
  75. return
  76. }
  77. throw new Error(unknownMsg())
  78. function unknownMsg(): string {
  79. return `unknown format "${schema as string}" ignored in schema at path "${errSchemaPath}"`
  80. }
  81. }
  82. function getFormat(fmtDef: AddedFormat): [string, FormatValidate, Code] {
  83. const code =
  84. fmtDef instanceof RegExp
  85. ? regexpCode(fmtDef)
  86. : opts.code.formats
  87. ? _`${opts.code.formats}${getProperty(schema)}`
  88. : undefined
  89. const fmt = gen.scopeValue("formats", {key: schema, ref: fmtDef, code})
  90. if (typeof fmtDef == "object" && !(fmtDef instanceof RegExp)) {
  91. return [fmtDef.type || "string", fmtDef.validate, _`${fmt}.validate`]
  92. }
  93. return ["string", fmtDef, fmt]
  94. }
  95. function validCondition(): Code {
  96. if (typeof formatDef == "object" && !(formatDef instanceof RegExp) && formatDef.async) {
  97. if (!schemaEnv.$async) throw new Error("async format in sync schema")
  98. return _`await ${fmtRef}(${data})`
  99. }
  100. return typeof format == "function" ? _`${fmtRef}(${data})` : _`${fmtRef}.test(${data})`
  101. }
  102. }
  103. },
  104. }
  105. export default def