ref.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import type {CodeKeywordDefinition, AnySchema} from "../../types"
  2. import type {KeywordCxt} from "../../compile/validate"
  3. import MissingRefError from "../../compile/ref_error"
  4. import {callValidateCode} from "../code"
  5. import {_, nil, stringify, Code, Name} from "../../compile/codegen"
  6. import N from "../../compile/names"
  7. import {SchemaEnv, resolveRef} from "../../compile"
  8. import {mergeEvaluated} from "../../compile/util"
  9. const def: CodeKeywordDefinition = {
  10. keyword: "$ref",
  11. schemaType: "string",
  12. code(cxt: KeywordCxt): void {
  13. const {gen, schema: $ref, it} = cxt
  14. const {baseId, schemaEnv: env, validateName, opts, self} = it
  15. const {root} = env
  16. if (($ref === "#" || $ref === "#/") && baseId === root.baseId) return callRootRef()
  17. const schOrEnv = resolveRef.call(self, root, baseId, $ref)
  18. if (schOrEnv === undefined) throw new MissingRefError(it.opts.uriResolver, baseId, $ref)
  19. if (schOrEnv instanceof SchemaEnv) return callValidate(schOrEnv)
  20. return inlineRefSchema(schOrEnv)
  21. function callRootRef(): void {
  22. if (env === root) return callRef(cxt, validateName, env, env.$async)
  23. const rootName = gen.scopeValue("root", {ref: root})
  24. return callRef(cxt, _`${rootName}.validate`, root, root.$async)
  25. }
  26. function callValidate(sch: SchemaEnv): void {
  27. const v = getValidate(cxt, sch)
  28. callRef(cxt, v, sch, sch.$async)
  29. }
  30. function inlineRefSchema(sch: AnySchema): void {
  31. const schName = gen.scopeValue(
  32. "schema",
  33. opts.code.source === true ? {ref: sch, code: stringify(sch)} : {ref: sch}
  34. )
  35. const valid = gen.name("valid")
  36. const schCxt = cxt.subschema(
  37. {
  38. schema: sch,
  39. dataTypes: [],
  40. schemaPath: nil,
  41. topSchemaRef: schName,
  42. errSchemaPath: $ref,
  43. },
  44. valid
  45. )
  46. cxt.mergeEvaluated(schCxt)
  47. cxt.ok(valid)
  48. }
  49. },
  50. }
  51. export function getValidate(cxt: KeywordCxt, sch: SchemaEnv): Code {
  52. const {gen} = cxt
  53. return sch.validate
  54. ? gen.scopeValue("validate", {ref: sch.validate})
  55. : _`${gen.scopeValue("wrapper", {ref: sch})}.validate`
  56. }
  57. export function callRef(cxt: KeywordCxt, v: Code, sch?: SchemaEnv, $async?: boolean): void {
  58. const {gen, it} = cxt
  59. const {allErrors, schemaEnv: env, opts} = it
  60. const passCxt = opts.passContext ? N.this : nil
  61. if ($async) callAsyncRef()
  62. else callSyncRef()
  63. function callAsyncRef(): void {
  64. if (!env.$async) throw new Error("async schema referenced by sync schema")
  65. const valid = gen.let("valid")
  66. gen.try(
  67. () => {
  68. gen.code(_`await ${callValidateCode(cxt, v, passCxt)}`)
  69. addEvaluatedFrom(v) // TODO will not work with async, it has to be returned with the result
  70. if (!allErrors) gen.assign(valid, true)
  71. },
  72. (e) => {
  73. gen.if(_`!(${e} instanceof ${it.ValidationError as Name})`, () => gen.throw(e))
  74. addErrorsFrom(e)
  75. if (!allErrors) gen.assign(valid, false)
  76. }
  77. )
  78. cxt.ok(valid)
  79. }
  80. function callSyncRef(): void {
  81. cxt.result(
  82. callValidateCode(cxt, v, passCxt),
  83. () => addEvaluatedFrom(v),
  84. () => addErrorsFrom(v)
  85. )
  86. }
  87. function addErrorsFrom(source: Code): void {
  88. const errs = _`${source}.errors`
  89. gen.assign(N.vErrors, _`${N.vErrors} === null ? ${errs} : ${N.vErrors}.concat(${errs})`) // TODO tagged
  90. gen.assign(N.errors, _`${N.vErrors}.length`)
  91. }
  92. function addEvaluatedFrom(source: Code): void {
  93. if (!it.opts.unevaluated) return
  94. const schEvaluated = sch?.validate?.evaluated
  95. // TODO refactor
  96. if (it.props !== true) {
  97. if (schEvaluated && !schEvaluated.dynamicProps) {
  98. if (schEvaluated.props !== undefined) {
  99. it.props = mergeEvaluated.props(gen, schEvaluated.props, it.props)
  100. }
  101. } else {
  102. const props = gen.var("props", _`${source}.evaluated.props`)
  103. it.props = mergeEvaluated.props(gen, props, it.props, Name)
  104. }
  105. }
  106. if (it.items !== true) {
  107. if (schEvaluated && !schEvaluated.dynamicItems) {
  108. if (schEvaluated.items !== undefined) {
  109. it.items = mergeEvaluated.items(gen, schEvaluated.items, it.items)
  110. }
  111. } else {
  112. const items = gen.var("items", _`${source}.evaluated.items`)
  113. it.items = mergeEvaluated.items(gen, items, it.items, Name)
  114. }
  115. }
  116. }
  117. }
  118. export default def