dependencies.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import type {
  2. CodeKeywordDefinition,
  3. ErrorObject,
  4. KeywordErrorDefinition,
  5. SchemaMap,
  6. AnySchema,
  7. } from "../../types"
  8. import type {KeywordCxt} from "../../compile/validate"
  9. import {_, str} from "../../compile/codegen"
  10. import {alwaysValidSchema} from "../../compile/util"
  11. import {checkReportMissingProp, checkMissingProp, reportMissingProp, propertyInData} from "../code"
  12. export type PropertyDependencies = {[K in string]?: string[]}
  13. export interface DependenciesErrorParams {
  14. property: string
  15. missingProperty: string
  16. depsCount: number
  17. deps: string // TODO change to string[]
  18. }
  19. type SchemaDependencies = SchemaMap
  20. export type DependenciesError = ErrorObject<
  21. "dependencies",
  22. DependenciesErrorParams,
  23. {[K in string]?: string[] | AnySchema}
  24. >
  25. export const error: KeywordErrorDefinition = {
  26. message: ({params: {property, depsCount, deps}}) => {
  27. const property_ies = depsCount === 1 ? "property" : "properties"
  28. return str`must have ${property_ies} ${deps} when property ${property} is present`
  29. },
  30. params: ({params: {property, depsCount, deps, missingProperty}}) =>
  31. _`{property: ${property},
  32. missingProperty: ${missingProperty},
  33. depsCount: ${depsCount},
  34. deps: ${deps}}`, // TODO change to reference
  35. }
  36. const def: CodeKeywordDefinition = {
  37. keyword: "dependencies",
  38. type: "object",
  39. schemaType: "object",
  40. error,
  41. code(cxt: KeywordCxt) {
  42. const [propDeps, schDeps] = splitDependencies(cxt)
  43. validatePropertyDeps(cxt, propDeps)
  44. validateSchemaDeps(cxt, schDeps)
  45. },
  46. }
  47. function splitDependencies({schema}: KeywordCxt): [PropertyDependencies, SchemaDependencies] {
  48. const propertyDeps: PropertyDependencies = {}
  49. const schemaDeps: SchemaDependencies = {}
  50. for (const key in schema) {
  51. if (key === "__proto__") continue
  52. const deps = Array.isArray(schema[key]) ? propertyDeps : schemaDeps
  53. deps[key] = schema[key]
  54. }
  55. return [propertyDeps, schemaDeps]
  56. }
  57. export function validatePropertyDeps(
  58. cxt: KeywordCxt,
  59. propertyDeps: {[K in string]?: string[]} = cxt.schema
  60. ): void {
  61. const {gen, data, it} = cxt
  62. if (Object.keys(propertyDeps).length === 0) return
  63. const missing = gen.let("missing")
  64. for (const prop in propertyDeps) {
  65. const deps = propertyDeps[prop] as string[]
  66. if (deps.length === 0) continue
  67. const hasProperty = propertyInData(gen, data, prop, it.opts.ownProperties)
  68. cxt.setParams({
  69. property: prop,
  70. depsCount: deps.length,
  71. deps: deps.join(", "),
  72. })
  73. if (it.allErrors) {
  74. gen.if(hasProperty, () => {
  75. for (const depProp of deps) {
  76. checkReportMissingProp(cxt, depProp)
  77. }
  78. })
  79. } else {
  80. gen.if(_`${hasProperty} && (${checkMissingProp(cxt, deps, missing)})`)
  81. reportMissingProp(cxt, missing)
  82. gen.else()
  83. }
  84. }
  85. }
  86. export function validateSchemaDeps(cxt: KeywordCxt, schemaDeps: SchemaMap = cxt.schema): void {
  87. const {gen, data, keyword, it} = cxt
  88. const valid = gen.name("valid")
  89. for (const prop in schemaDeps) {
  90. if (alwaysValidSchema(it, schemaDeps[prop] as AnySchema)) continue
  91. gen.if(
  92. propertyInData(gen, data, prop, it.opts.ownProperties),
  93. () => {
  94. const schCxt = cxt.subschema({keyword, schemaProp: prop}, valid)
  95. cxt.mergeValidEvaluated(schCxt, valid)
  96. },
  97. () => gen.var(valid, true) // TODO var
  98. )
  99. cxt.ok(valid)
  100. }
  101. }
  102. export default def