subschema.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import type {AnySchema} from "../types"
  2. import type {SchemaObjCxt, SchemaCxt} from "./index"
  3. import {subschemaCode} from "./validate"
  4. import {escapeFragment, escapeJsonPointer} from "./util"
  5. import {_, str, Code, Name, getProperty} from "./codegen"
  6. import {JSONType} from "./rules"
  7. interface SubschemaContext {
  8. // TODO use Optional? align with SchemCxt property types
  9. schema: AnySchema
  10. strictSchema?: boolean
  11. schemaPath: Code
  12. errSchemaPath: string
  13. topSchemaRef?: Code
  14. errorPath?: Code
  15. dataLevel?: number
  16. dataTypes?: JSONType[]
  17. data?: Name
  18. parentData?: Name
  19. parentDataProperty?: Code | number
  20. dataNames?: Name[]
  21. dataPathArr?: (Code | number)[]
  22. propertyName?: Name
  23. compositeRule?: true
  24. createErrors?: boolean
  25. allErrors?: boolean
  26. }
  27. export enum Type {
  28. Num,
  29. Str,
  30. }
  31. export type SubschemaArgs = Partial<{
  32. keyword: string
  33. schemaProp: string | number
  34. schema: AnySchema
  35. strictSchema: boolean
  36. schemaPath: Code
  37. errSchemaPath: string
  38. topSchemaRef: Code
  39. data: Name | Code
  40. dataProp: Code | string | number
  41. dataTypes: JSONType[]
  42. propertyName: Name
  43. dataPropType: Type
  44. compositeRule: true
  45. createErrors: boolean
  46. allErrors: boolean
  47. }>
  48. export function applySubschema(it: SchemaObjCxt, appl: SubschemaArgs, valid: Name): SchemaCxt {
  49. const subschema = getSubschema(it, appl)
  50. extendSubschemaData(subschema, it, appl)
  51. extendSubschemaMode(subschema, appl)
  52. const nextContext = {...it, ...subschema, items: undefined, props: undefined}
  53. subschemaCode(nextContext, valid)
  54. return nextContext
  55. }
  56. function getSubschema(
  57. it: SchemaObjCxt,
  58. {
  59. keyword,
  60. schemaProp,
  61. schema,
  62. strictSchema,
  63. schemaPath,
  64. errSchemaPath,
  65. topSchemaRef,
  66. }: SubschemaArgs
  67. ): SubschemaContext {
  68. if (keyword !== undefined && schema !== undefined) {
  69. throw new Error('both "keyword" and "schema" passed, only one allowed')
  70. }
  71. if (keyword !== undefined) {
  72. const sch = it.schema[keyword]
  73. return schemaProp === undefined
  74. ? {
  75. schema: sch,
  76. schemaPath: _`${it.schemaPath}${getProperty(keyword)}`,
  77. errSchemaPath: `${it.errSchemaPath}/${keyword}`,
  78. }
  79. : {
  80. schema: sch[schemaProp],
  81. schemaPath: _`${it.schemaPath}${getProperty(keyword)}${getProperty(schemaProp)}`,
  82. errSchemaPath: `${it.errSchemaPath}/${keyword}/${escapeFragment(schemaProp)}`,
  83. }
  84. }
  85. if (schema !== undefined) {
  86. if (schemaPath === undefined || errSchemaPath === undefined || topSchemaRef === undefined) {
  87. throw new Error('"schemaPath", "errSchemaPath" and "topSchemaRef" are required with "schema"')
  88. }
  89. return {
  90. schema,
  91. strictSchema,
  92. schemaPath,
  93. topSchemaRef,
  94. errSchemaPath,
  95. }
  96. }
  97. throw new Error('either "keyword" or "schema" must be passed')
  98. }
  99. function extendSubschemaData(
  100. subschema: SubschemaContext,
  101. it: SchemaObjCxt,
  102. {dataProp, dataPropType: dpType, data, dataTypes, propertyName}: SubschemaArgs
  103. ): void {
  104. if (data !== undefined && dataProp !== undefined) {
  105. throw new Error('both "data" and "dataProp" passed, only one allowed')
  106. }
  107. const {gen} = it
  108. if (dataProp !== undefined) {
  109. const {errorPath, dataPathArr, opts} = it
  110. const nextData = gen.let("data", _`${it.data}${getProperty(dataProp)}`, true)
  111. dataContextProps(nextData)
  112. subschema.errorPath = str`${errorPath}${getErrorPath(dataProp, dpType, opts.jsPropertySyntax)}`
  113. subschema.parentDataProperty = _`${dataProp}`
  114. subschema.dataPathArr = [...dataPathArr, subschema.parentDataProperty]
  115. }
  116. if (data !== undefined) {
  117. const nextData = data instanceof Name ? data : gen.let("data", data, true) // replaceable if used once?
  118. dataContextProps(nextData)
  119. if (propertyName !== undefined) subschema.propertyName = propertyName
  120. // TODO something is possibly wrong here with not changing parentDataProperty and not appending dataPathArr
  121. }
  122. if (dataTypes) subschema.dataTypes = dataTypes
  123. function dataContextProps(_nextData: Name): void {
  124. subschema.data = _nextData
  125. subschema.dataLevel = it.dataLevel + 1
  126. subschema.dataTypes = []
  127. subschema.parentData = it.data
  128. subschema.dataNames = [...it.dataNames, _nextData]
  129. }
  130. }
  131. function extendSubschemaMode(
  132. subschema: SubschemaContext,
  133. {compositeRule, createErrors, allErrors, strictSchema}: SubschemaArgs
  134. ): void {
  135. if (compositeRule !== undefined) subschema.compositeRule = compositeRule
  136. if (createErrors !== undefined) subschema.createErrors = createErrors
  137. if (allErrors !== undefined) subschema.allErrors = allErrors
  138. subschema.strictSchema = strictSchema // not inherited
  139. }
  140. function getErrorPath(
  141. dataProp: Name | string | number,
  142. dataPropType?: Type,
  143. jsPropertySyntax?: boolean
  144. ): Code | string {
  145. // let path
  146. if (dataProp instanceof Name) {
  147. const isNumber = dataPropType === Type.Num
  148. return jsPropertySyntax
  149. ? isNumber
  150. ? _`"[" + ${dataProp} + "]"`
  151. : _`"['" + ${dataProp} + "']"`
  152. : isNumber
  153. ? _`"/" + ${dataProp}`
  154. : _`"/" + ${dataProp}.replace(/~/g, "~0").replace(/\\//g, "~1")` // TODO maybe use global escapePointer
  155. }
  156. return jsPropertySyntax ? getProperty(dataProp).toString() : "/" + escapeJsonPointer(dataProp)
  157. }