errors.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import type {KeywordErrorCxt, KeywordErrorDefinition} from "../types"
  2. import type {SchemaCxt} from "./index"
  3. import {CodeGen, _, str, strConcat, Code, Name} from "./codegen"
  4. import {SafeExpr} from "./codegen/code"
  5. import {getErrorPath, Type} from "./util"
  6. import N from "./names"
  7. export const keywordError: KeywordErrorDefinition = {
  8. message: ({keyword}) => str`must pass "${keyword}" keyword validation`,
  9. }
  10. export const keyword$DataError: KeywordErrorDefinition = {
  11. message: ({keyword, schemaType}) =>
  12. schemaType
  13. ? str`"${keyword}" keyword must be ${schemaType} ($data)`
  14. : str`"${keyword}" keyword is invalid ($data)`,
  15. }
  16. export interface ErrorPaths {
  17. instancePath?: Code
  18. schemaPath?: string
  19. parentSchema?: boolean
  20. }
  21. export function reportError(
  22. cxt: KeywordErrorCxt,
  23. error: KeywordErrorDefinition = keywordError,
  24. errorPaths?: ErrorPaths,
  25. overrideAllErrors?: boolean
  26. ): void {
  27. const {it} = cxt
  28. const {gen, compositeRule, allErrors} = it
  29. const errObj = errorObjectCode(cxt, error, errorPaths)
  30. if (overrideAllErrors ?? (compositeRule || allErrors)) {
  31. addError(gen, errObj)
  32. } else {
  33. returnErrors(it, _`[${errObj}]`)
  34. }
  35. }
  36. export function reportExtraError(
  37. cxt: KeywordErrorCxt,
  38. error: KeywordErrorDefinition = keywordError,
  39. errorPaths?: ErrorPaths
  40. ): void {
  41. const {it} = cxt
  42. const {gen, compositeRule, allErrors} = it
  43. const errObj = errorObjectCode(cxt, error, errorPaths)
  44. addError(gen, errObj)
  45. if (!(compositeRule || allErrors)) {
  46. returnErrors(it, N.vErrors)
  47. }
  48. }
  49. export function resetErrorsCount(gen: CodeGen, errsCount: Name): void {
  50. gen.assign(N.errors, errsCount)
  51. gen.if(_`${N.vErrors} !== null`, () =>
  52. gen.if(
  53. errsCount,
  54. () => gen.assign(_`${N.vErrors}.length`, errsCount),
  55. () => gen.assign(N.vErrors, null)
  56. )
  57. )
  58. }
  59. export function extendErrors({
  60. gen,
  61. keyword,
  62. schemaValue,
  63. data,
  64. errsCount,
  65. it,
  66. }: KeywordErrorCxt): void {
  67. /* istanbul ignore if */
  68. if (errsCount === undefined) throw new Error("ajv implementation error")
  69. const err = gen.name("err")
  70. gen.forRange("i", errsCount, N.errors, (i) => {
  71. gen.const(err, _`${N.vErrors}[${i}]`)
  72. gen.if(_`${err}.instancePath === undefined`, () =>
  73. gen.assign(_`${err}.instancePath`, strConcat(N.instancePath, it.errorPath))
  74. )
  75. gen.assign(_`${err}.schemaPath`, str`${it.errSchemaPath}/${keyword}`)
  76. if (it.opts.verbose) {
  77. gen.assign(_`${err}.schema`, schemaValue)
  78. gen.assign(_`${err}.data`, data)
  79. }
  80. })
  81. }
  82. function addError(gen: CodeGen, errObj: Code): void {
  83. const err = gen.const("err", errObj)
  84. gen.if(
  85. _`${N.vErrors} === null`,
  86. () => gen.assign(N.vErrors, _`[${err}]`),
  87. _`${N.vErrors}.push(${err})`
  88. )
  89. gen.code(_`${N.errors}++`)
  90. }
  91. function returnErrors(it: SchemaCxt, errs: Code): void {
  92. const {gen, validateName, schemaEnv} = it
  93. if (schemaEnv.$async) {
  94. gen.throw(_`new ${it.ValidationError as Name}(${errs})`)
  95. } else {
  96. gen.assign(_`${validateName}.errors`, errs)
  97. gen.return(false)
  98. }
  99. }
  100. const E = {
  101. keyword: new Name("keyword"),
  102. schemaPath: new Name("schemaPath"), // also used in JTD errors
  103. params: new Name("params"),
  104. propertyName: new Name("propertyName"),
  105. message: new Name("message"),
  106. schema: new Name("schema"),
  107. parentSchema: new Name("parentSchema"),
  108. }
  109. function errorObjectCode(
  110. cxt: KeywordErrorCxt,
  111. error: KeywordErrorDefinition,
  112. errorPaths?: ErrorPaths
  113. ): Code {
  114. const {createErrors} = cxt.it
  115. if (createErrors === false) return _`{}`
  116. return errorObject(cxt, error, errorPaths)
  117. }
  118. function errorObject(
  119. cxt: KeywordErrorCxt,
  120. error: KeywordErrorDefinition,
  121. errorPaths: ErrorPaths = {}
  122. ): Code {
  123. const {gen, it} = cxt
  124. const keyValues: [Name, SafeExpr | string][] = [
  125. errorInstancePath(it, errorPaths),
  126. errorSchemaPath(cxt, errorPaths),
  127. ]
  128. extraErrorProps(cxt, error, keyValues)
  129. return gen.object(...keyValues)
  130. }
  131. function errorInstancePath({errorPath}: SchemaCxt, {instancePath}: ErrorPaths): [Name, Code] {
  132. const instPath = instancePath
  133. ? str`${errorPath}${getErrorPath(instancePath, Type.Str)}`
  134. : errorPath
  135. return [N.instancePath, strConcat(N.instancePath, instPath)]
  136. }
  137. function errorSchemaPath(
  138. {keyword, it: {errSchemaPath}}: KeywordErrorCxt,
  139. {schemaPath, parentSchema}: ErrorPaths
  140. ): [Name, string | Code] {
  141. let schPath = parentSchema ? errSchemaPath : str`${errSchemaPath}/${keyword}`
  142. if (schemaPath) {
  143. schPath = str`${schPath}${getErrorPath(schemaPath, Type.Str)}`
  144. }
  145. return [E.schemaPath, schPath]
  146. }
  147. function extraErrorProps(
  148. cxt: KeywordErrorCxt,
  149. {params, message}: KeywordErrorDefinition,
  150. keyValues: [Name, SafeExpr | string][]
  151. ): void {
  152. const {keyword, data, schemaValue, it} = cxt
  153. const {opts, propertyName, topSchemaRef, schemaPath} = it
  154. keyValues.push(
  155. [E.keyword, keyword],
  156. [E.params, typeof params == "function" ? params(cxt) : params || _`{}`]
  157. )
  158. if (opts.messages) {
  159. keyValues.push([E.message, typeof message == "function" ? message(cxt) : message])
  160. }
  161. if (opts.verbose) {
  162. keyValues.push(
  163. [E.schema, schemaValue],
  164. [E.parentSchema, _`${topSchemaRef}${schemaPath}`],
  165. [N.data, data]
  166. )
  167. }
  168. if (propertyName) keyValues.push([E.propertyName, propertyName])
  169. }