ref.ts 4.3 KB

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