index.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. import type {
  2. AddedKeywordDefinition,
  3. AnySchema,
  4. AnySchemaObject,
  5. KeywordErrorCxt,
  6. KeywordCxtParams,
  7. } from "../../types"
  8. import type {SchemaCxt, SchemaObjCxt} from ".."
  9. import type {InstanceOptions} from "../../core"
  10. import {boolOrEmptySchema, topBoolOrEmptySchema} from "./boolSchema"
  11. import {coerceAndCheckDataType, getSchemaTypes} from "./dataType"
  12. import {shouldUseGroup, shouldUseRule} from "./applicability"
  13. import {checkDataType, checkDataTypes, reportTypeError, DataType} from "./dataType"
  14. import {assignDefaults} from "./defaults"
  15. import {funcKeywordCode, macroKeywordCode, validateKeywordUsage, validSchemaType} from "./keyword"
  16. import {getSubschema, extendSubschemaData, SubschemaArgs, extendSubschemaMode} from "./subschema"
  17. import {_, nil, str, or, not, getProperty, Block, Code, Name, CodeGen} from "../codegen"
  18. import N from "../names"
  19. import {resolveUrl} from "../resolve"
  20. import {
  21. schemaRefOrVal,
  22. schemaHasRulesButRef,
  23. checkUnknownRules,
  24. checkStrictMode,
  25. unescapeJsonPointer,
  26. mergeEvaluated,
  27. } from "../util"
  28. import type {JSONType, Rule, RuleGroup} from "../rules"
  29. import {
  30. ErrorPaths,
  31. reportError,
  32. reportExtraError,
  33. resetErrorsCount,
  34. keyword$DataError,
  35. } from "../errors"
  36. // schema compilation - generates validation function, subschemaCode (below) is used for subschemas
  37. export function validateFunctionCode(it: SchemaCxt): void {
  38. if (isSchemaObj(it)) {
  39. checkKeywords(it)
  40. if (schemaCxtHasRules(it)) {
  41. topSchemaObjCode(it)
  42. return
  43. }
  44. }
  45. validateFunction(it, () => topBoolOrEmptySchema(it))
  46. }
  47. function validateFunction(
  48. {gen, validateName, schema, schemaEnv, opts}: SchemaCxt,
  49. body: Block
  50. ): void {
  51. if (opts.code.es5) {
  52. gen.func(validateName, _`${N.data}, ${N.valCxt}`, schemaEnv.$async, () => {
  53. gen.code(_`"use strict"; ${funcSourceUrl(schema, opts)}`)
  54. destructureValCxtES5(gen, opts)
  55. gen.code(body)
  56. })
  57. } else {
  58. gen.func(validateName, _`${N.data}, ${destructureValCxt(opts)}`, schemaEnv.$async, () =>
  59. gen.code(funcSourceUrl(schema, opts)).code(body)
  60. )
  61. }
  62. }
  63. function destructureValCxt(opts: InstanceOptions): Code {
  64. return _`{${N.instancePath}="", ${N.parentData}, ${N.parentDataProperty}, ${N.rootData}=${
  65. N.data
  66. }${opts.dynamicRef ? _`, ${N.dynamicAnchors}={}` : nil}}={}`
  67. }
  68. function destructureValCxtES5(gen: CodeGen, opts: InstanceOptions): void {
  69. gen.if(
  70. N.valCxt,
  71. () => {
  72. gen.var(N.instancePath, _`${N.valCxt}.${N.instancePath}`)
  73. gen.var(N.parentData, _`${N.valCxt}.${N.parentData}`)
  74. gen.var(N.parentDataProperty, _`${N.valCxt}.${N.parentDataProperty}`)
  75. gen.var(N.rootData, _`${N.valCxt}.${N.rootData}`)
  76. if (opts.dynamicRef) gen.var(N.dynamicAnchors, _`${N.valCxt}.${N.dynamicAnchors}`)
  77. },
  78. () => {
  79. gen.var(N.instancePath, _`""`)
  80. gen.var(N.parentData, _`undefined`)
  81. gen.var(N.parentDataProperty, _`undefined`)
  82. gen.var(N.rootData, N.data)
  83. if (opts.dynamicRef) gen.var(N.dynamicAnchors, _`{}`)
  84. }
  85. )
  86. }
  87. function topSchemaObjCode(it: SchemaObjCxt): void {
  88. const {schema, opts, gen} = it
  89. validateFunction(it, () => {
  90. if (opts.$comment && schema.$comment) commentKeyword(it)
  91. checkNoDefault(it)
  92. gen.let(N.vErrors, null)
  93. gen.let(N.errors, 0)
  94. if (opts.unevaluated) resetEvaluated(it)
  95. typeAndKeywords(it)
  96. returnResults(it)
  97. })
  98. return
  99. }
  100. function resetEvaluated(it: SchemaObjCxt): void {
  101. // TODO maybe some hook to execute it in the end to check whether props/items are Name, as in assignEvaluated
  102. const {gen, validateName} = it
  103. it.evaluated = gen.const("evaluated", _`${validateName}.evaluated`)
  104. gen.if(_`${it.evaluated}.dynamicProps`, () => gen.assign(_`${it.evaluated}.props`, _`undefined`))
  105. gen.if(_`${it.evaluated}.dynamicItems`, () => gen.assign(_`${it.evaluated}.items`, _`undefined`))
  106. }
  107. function funcSourceUrl(schema: AnySchema, opts: InstanceOptions): Code {
  108. const schId = typeof schema == "object" && schema[opts.schemaId]
  109. return schId && (opts.code.source || opts.code.process) ? _`/*# sourceURL=${schId} */` : nil
  110. }
  111. // schema compilation - this function is used recursively to generate code for sub-schemas
  112. function subschemaCode(it: SchemaCxt, valid: Name): void {
  113. if (isSchemaObj(it)) {
  114. checkKeywords(it)
  115. if (schemaCxtHasRules(it)) {
  116. subSchemaObjCode(it, valid)
  117. return
  118. }
  119. }
  120. boolOrEmptySchema(it, valid)
  121. }
  122. function schemaCxtHasRules({schema, self}: SchemaCxt): boolean {
  123. if (typeof schema == "boolean") return !schema
  124. for (const key in schema) if (self.RULES.all[key]) return true
  125. return false
  126. }
  127. function isSchemaObj(it: SchemaCxt): it is SchemaObjCxt {
  128. return typeof it.schema != "boolean"
  129. }
  130. function subSchemaObjCode(it: SchemaObjCxt, valid: Name): void {
  131. const {schema, gen, opts} = it
  132. if (opts.$comment && schema.$comment) commentKeyword(it)
  133. updateContext(it)
  134. checkAsyncSchema(it)
  135. const errsCount = gen.const("_errs", N.errors)
  136. typeAndKeywords(it, errsCount)
  137. // TODO var
  138. gen.var(valid, _`${errsCount} === ${N.errors}`)
  139. }
  140. function checkKeywords(it: SchemaObjCxt): void {
  141. checkUnknownRules(it)
  142. checkRefsAndKeywords(it)
  143. }
  144. function typeAndKeywords(it: SchemaObjCxt, errsCount?: Name): void {
  145. if (it.opts.jtd) return schemaKeywords(it, [], false, errsCount)
  146. const types = getSchemaTypes(it.schema)
  147. const checkedTypes = coerceAndCheckDataType(it, types)
  148. schemaKeywords(it, types, !checkedTypes, errsCount)
  149. }
  150. function checkRefsAndKeywords(it: SchemaObjCxt): void {
  151. const {schema, errSchemaPath, opts, self} = it
  152. if (schema.$ref && opts.ignoreKeywordsWithRef && schemaHasRulesButRef(schema, self.RULES)) {
  153. self.logger.warn(`$ref: keywords ignored in schema at path "${errSchemaPath}"`)
  154. }
  155. }
  156. function checkNoDefault(it: SchemaObjCxt): void {
  157. const {schema, opts} = it
  158. if (schema.default !== undefined && opts.useDefaults && opts.strictSchema) {
  159. checkStrictMode(it, "default is ignored in the schema root")
  160. }
  161. }
  162. function updateContext(it: SchemaObjCxt): void {
  163. const schId = it.schema[it.opts.schemaId]
  164. if (schId) it.baseId = resolveUrl(it.opts.uriResolver, it.baseId, schId)
  165. }
  166. function checkAsyncSchema(it: SchemaObjCxt): void {
  167. if (it.schema.$async && !it.schemaEnv.$async) throw new Error("async schema in sync schema")
  168. }
  169. function commentKeyword({gen, schemaEnv, schema, errSchemaPath, opts}: SchemaObjCxt): void {
  170. const msg = schema.$comment
  171. if (opts.$comment === true) {
  172. gen.code(_`${N.self}.logger.log(${msg})`)
  173. } else if (typeof opts.$comment == "function") {
  174. const schemaPath = str`${errSchemaPath}/$comment`
  175. const rootName = gen.scopeValue("root", {ref: schemaEnv.root})
  176. gen.code(_`${N.self}.opts.$comment(${msg}, ${schemaPath}, ${rootName}.schema)`)
  177. }
  178. }
  179. function returnResults(it: SchemaCxt): void {
  180. const {gen, schemaEnv, validateName, ValidationError, opts} = it
  181. if (schemaEnv.$async) {
  182. // TODO assign unevaluated
  183. gen.if(
  184. _`${N.errors} === 0`,
  185. () => gen.return(N.data),
  186. () => gen.throw(_`new ${ValidationError as Name}(${N.vErrors})`)
  187. )
  188. } else {
  189. gen.assign(_`${validateName}.errors`, N.vErrors)
  190. if (opts.unevaluated) assignEvaluated(it)
  191. gen.return(_`${N.errors} === 0`)
  192. }
  193. }
  194. function assignEvaluated({gen, evaluated, props, items}: SchemaCxt): void {
  195. if (props instanceof Name) gen.assign(_`${evaluated}.props`, props)
  196. if (items instanceof Name) gen.assign(_`${evaluated}.items`, items)
  197. }
  198. function schemaKeywords(
  199. it: SchemaObjCxt,
  200. types: JSONType[],
  201. typeErrors: boolean,
  202. errsCount?: Name
  203. ): void {
  204. const {gen, schema, data, allErrors, opts, self} = it
  205. const {RULES} = self
  206. if (schema.$ref && (opts.ignoreKeywordsWithRef || !schemaHasRulesButRef(schema, RULES))) {
  207. gen.block(() => keywordCode(it, "$ref", (RULES.all.$ref as Rule).definition)) // TODO typecast
  208. return
  209. }
  210. if (!opts.jtd) checkStrictTypes(it, types)
  211. gen.block(() => {
  212. for (const group of RULES.rules) groupKeywords(group)
  213. groupKeywords(RULES.post)
  214. })
  215. function groupKeywords(group: RuleGroup): void {
  216. if (!shouldUseGroup(schema, group)) return
  217. if (group.type) {
  218. gen.if(checkDataType(group.type, data, opts.strictNumbers))
  219. iterateKeywords(it, group)
  220. if (types.length === 1 && types[0] === group.type && typeErrors) {
  221. gen.else()
  222. reportTypeError(it)
  223. }
  224. gen.endIf()
  225. } else {
  226. iterateKeywords(it, group)
  227. }
  228. // TODO make it "ok" call?
  229. if (!allErrors) gen.if(_`${N.errors} === ${errsCount || 0}`)
  230. }
  231. }
  232. function iterateKeywords(it: SchemaObjCxt, group: RuleGroup): void {
  233. const {
  234. gen,
  235. schema,
  236. opts: {useDefaults},
  237. } = it
  238. if (useDefaults) assignDefaults(it, group.type)
  239. gen.block(() => {
  240. for (const rule of group.rules) {
  241. if (shouldUseRule(schema, rule)) {
  242. keywordCode(it, rule.keyword, rule.definition, group.type)
  243. }
  244. }
  245. })
  246. }
  247. function checkStrictTypes(it: SchemaObjCxt, types: JSONType[]): void {
  248. if (it.schemaEnv.meta || !it.opts.strictTypes) return
  249. checkContextTypes(it, types)
  250. if (!it.opts.allowUnionTypes) checkMultipleTypes(it, types)
  251. checkKeywordTypes(it, it.dataTypes)
  252. }
  253. function checkContextTypes(it: SchemaObjCxt, types: JSONType[]): void {
  254. if (!types.length) return
  255. if (!it.dataTypes.length) {
  256. it.dataTypes = types
  257. return
  258. }
  259. types.forEach((t) => {
  260. if (!includesType(it.dataTypes, t)) {
  261. strictTypesError(it, `type "${t}" not allowed by context "${it.dataTypes.join(",")}"`)
  262. }
  263. })
  264. narrowSchemaTypes(it, types)
  265. }
  266. function checkMultipleTypes(it: SchemaObjCxt, ts: JSONType[]): void {
  267. if (ts.length > 1 && !(ts.length === 2 && ts.includes("null"))) {
  268. strictTypesError(it, "use allowUnionTypes to allow union type keyword")
  269. }
  270. }
  271. function checkKeywordTypes(it: SchemaObjCxt, ts: JSONType[]): void {
  272. const rules = it.self.RULES.all
  273. for (const keyword in rules) {
  274. const rule = rules[keyword]
  275. if (typeof rule == "object" && shouldUseRule(it.schema, rule)) {
  276. const {type} = rule.definition
  277. if (type.length && !type.some((t) => hasApplicableType(ts, t))) {
  278. strictTypesError(it, `missing type "${type.join(",")}" for keyword "${keyword}"`)
  279. }
  280. }
  281. }
  282. }
  283. function hasApplicableType(schTs: JSONType[], kwdT: JSONType): boolean {
  284. return schTs.includes(kwdT) || (kwdT === "number" && schTs.includes("integer"))
  285. }
  286. function includesType(ts: JSONType[], t: JSONType): boolean {
  287. return ts.includes(t) || (t === "integer" && ts.includes("number"))
  288. }
  289. function narrowSchemaTypes(it: SchemaObjCxt, withTypes: JSONType[]): void {
  290. const ts: JSONType[] = []
  291. for (const t of it.dataTypes) {
  292. if (includesType(withTypes, t)) ts.push(t)
  293. else if (withTypes.includes("integer") && t === "number") ts.push("integer")
  294. }
  295. it.dataTypes = ts
  296. }
  297. function strictTypesError(it: SchemaObjCxt, msg: string): void {
  298. const schemaPath = it.schemaEnv.baseId + it.errSchemaPath
  299. msg += ` at "${schemaPath}" (strictTypes)`
  300. checkStrictMode(it, msg, it.opts.strictTypes)
  301. }
  302. export class KeywordCxt implements KeywordErrorCxt {
  303. readonly gen: CodeGen
  304. readonly allErrors?: boolean
  305. readonly keyword: string
  306. readonly data: Name // Name referencing the current level of the data instance
  307. readonly $data?: string | false
  308. schema: any // keyword value in the schema
  309. readonly schemaValue: Code | number | boolean // Code reference to keyword schema value or primitive value
  310. readonly schemaCode: Code | number | boolean // Code reference to resolved schema value (different if schema is $data)
  311. readonly schemaType: JSONType[] // allowed type(s) of keyword value in the schema
  312. readonly parentSchema: AnySchemaObject
  313. readonly errsCount?: Name // Name reference to the number of validation errors collected before this keyword,
  314. // requires option trackErrors in keyword definition
  315. params: KeywordCxtParams // object to pass parameters to error messages from keyword code
  316. readonly it: SchemaObjCxt // schema compilation context (schema is guaranteed to be an object, not boolean)
  317. readonly def: AddedKeywordDefinition
  318. constructor(it: SchemaObjCxt, def: AddedKeywordDefinition, keyword: string) {
  319. validateKeywordUsage(it, def, keyword)
  320. this.gen = it.gen
  321. this.allErrors = it.allErrors
  322. this.keyword = keyword
  323. this.data = it.data
  324. this.schema = it.schema[keyword]
  325. this.$data = def.$data && it.opts.$data && this.schema && this.schema.$data
  326. this.schemaValue = schemaRefOrVal(it, this.schema, keyword, this.$data)
  327. this.schemaType = def.schemaType
  328. this.parentSchema = it.schema
  329. this.params = {}
  330. this.it = it
  331. this.def = def
  332. if (this.$data) {
  333. this.schemaCode = it.gen.const("vSchema", getData(this.$data, it))
  334. } else {
  335. this.schemaCode = this.schemaValue
  336. if (!validSchemaType(this.schema, def.schemaType, def.allowUndefined)) {
  337. throw new Error(`${keyword} value must be ${JSON.stringify(def.schemaType)}`)
  338. }
  339. }
  340. if ("code" in def ? def.trackErrors : def.errors !== false) {
  341. this.errsCount = it.gen.const("_errs", N.errors)
  342. }
  343. }
  344. result(condition: Code, successAction?: () => void, failAction?: () => void): void {
  345. this.failResult(not(condition), successAction, failAction)
  346. }
  347. failResult(condition: Code, successAction?: () => void, failAction?: () => void): void {
  348. this.gen.if(condition)
  349. if (failAction) failAction()
  350. else this.error()
  351. if (successAction) {
  352. this.gen.else()
  353. successAction()
  354. if (this.allErrors) this.gen.endIf()
  355. } else {
  356. if (this.allErrors) this.gen.endIf()
  357. else this.gen.else()
  358. }
  359. }
  360. pass(condition: Code, failAction?: () => void): void {
  361. this.failResult(not(condition), undefined, failAction)
  362. }
  363. fail(condition?: Code): void {
  364. if (condition === undefined) {
  365. this.error()
  366. if (!this.allErrors) this.gen.if(false) // this branch will be removed by gen.optimize
  367. return
  368. }
  369. this.gen.if(condition)
  370. this.error()
  371. if (this.allErrors) this.gen.endIf()
  372. else this.gen.else()
  373. }
  374. fail$data(condition: Code): void {
  375. if (!this.$data) return this.fail(condition)
  376. const {schemaCode} = this
  377. this.fail(_`${schemaCode} !== undefined && (${or(this.invalid$data(), condition)})`)
  378. }
  379. error(append?: boolean, errorParams?: KeywordCxtParams, errorPaths?: ErrorPaths): void {
  380. if (errorParams) {
  381. this.setParams(errorParams)
  382. this._error(append, errorPaths)
  383. this.setParams({})
  384. return
  385. }
  386. this._error(append, errorPaths)
  387. }
  388. private _error(append?: boolean, errorPaths?: ErrorPaths): void {
  389. ;(append ? reportExtraError : reportError)(this, this.def.error, errorPaths)
  390. }
  391. $dataError(): void {
  392. reportError(this, this.def.$dataError || keyword$DataError)
  393. }
  394. reset(): void {
  395. if (this.errsCount === undefined) throw new Error('add "trackErrors" to keyword definition')
  396. resetErrorsCount(this.gen, this.errsCount)
  397. }
  398. ok(cond: Code | boolean): void {
  399. if (!this.allErrors) this.gen.if(cond)
  400. }
  401. setParams(obj: KeywordCxtParams, assign?: true): void {
  402. if (assign) Object.assign(this.params, obj)
  403. else this.params = obj
  404. }
  405. block$data(valid: Name, codeBlock: () => void, $dataValid: Code = nil): void {
  406. this.gen.block(() => {
  407. this.check$data(valid, $dataValid)
  408. codeBlock()
  409. })
  410. }
  411. check$data(valid: Name = nil, $dataValid: Code = nil): void {
  412. if (!this.$data) return
  413. const {gen, schemaCode, schemaType, def} = this
  414. gen.if(or(_`${schemaCode} === undefined`, $dataValid))
  415. if (valid !== nil) gen.assign(valid, true)
  416. if (schemaType.length || def.validateSchema) {
  417. gen.elseIf(this.invalid$data())
  418. this.$dataError()
  419. if (valid !== nil) gen.assign(valid, false)
  420. }
  421. gen.else()
  422. }
  423. invalid$data(): Code {
  424. const {gen, schemaCode, schemaType, def, it} = this
  425. return or(wrong$DataType(), invalid$DataSchema())
  426. function wrong$DataType(): Code {
  427. if (schemaType.length) {
  428. /* istanbul ignore if */
  429. if (!(schemaCode instanceof Name)) throw new Error("ajv implementation error")
  430. const st = Array.isArray(schemaType) ? schemaType : [schemaType]
  431. return _`${checkDataTypes(st, schemaCode, it.opts.strictNumbers, DataType.Wrong)}`
  432. }
  433. return nil
  434. }
  435. function invalid$DataSchema(): Code {
  436. if (def.validateSchema) {
  437. const validateSchemaRef = gen.scopeValue("validate$data", {ref: def.validateSchema}) // TODO value.code for standalone
  438. return _`!${validateSchemaRef}(${schemaCode})`
  439. }
  440. return nil
  441. }
  442. }
  443. subschema(appl: SubschemaArgs, valid: Name): SchemaCxt {
  444. const subschema = getSubschema(this.it, appl)
  445. extendSubschemaData(subschema, this.it, appl)
  446. extendSubschemaMode(subschema, appl)
  447. const nextContext = {...this.it, ...subschema, items: undefined, props: undefined}
  448. subschemaCode(nextContext, valid)
  449. return nextContext
  450. }
  451. mergeEvaluated(schemaCxt: SchemaCxt, toName?: typeof Name): void {
  452. const {it, gen} = this
  453. if (!it.opts.unevaluated) return
  454. if (it.props !== true && schemaCxt.props !== undefined) {
  455. it.props = mergeEvaluated.props(gen, schemaCxt.props, it.props, toName)
  456. }
  457. if (it.items !== true && schemaCxt.items !== undefined) {
  458. it.items = mergeEvaluated.items(gen, schemaCxt.items, it.items, toName)
  459. }
  460. }
  461. mergeValidEvaluated(schemaCxt: SchemaCxt, valid: Name): boolean | void {
  462. const {it, gen} = this
  463. if (it.opts.unevaluated && (it.props !== true || it.items !== true)) {
  464. gen.if(valid, () => this.mergeEvaluated(schemaCxt, Name))
  465. return true
  466. }
  467. }
  468. }
  469. function keywordCode(
  470. it: SchemaObjCxt,
  471. keyword: string,
  472. def: AddedKeywordDefinition,
  473. ruleType?: JSONType
  474. ): void {
  475. const cxt = new KeywordCxt(it, def, keyword)
  476. if ("code" in def) {
  477. def.code(cxt, ruleType)
  478. } else if (cxt.$data && def.validate) {
  479. funcKeywordCode(cxt, def)
  480. } else if ("macro" in def) {
  481. macroKeywordCode(cxt, def)
  482. } else if (def.compile || def.validate) {
  483. funcKeywordCode(cxt, def)
  484. }
  485. }
  486. const JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/
  487. const RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/
  488. export function getData(
  489. $data: string,
  490. {dataLevel, dataNames, dataPathArr}: SchemaCxt
  491. ): Code | number {
  492. let jsonPointer
  493. let data: Code
  494. if ($data === "") return N.rootData
  495. if ($data[0] === "/") {
  496. if (!JSON_POINTER.test($data)) throw new Error(`Invalid JSON-pointer: ${$data}`)
  497. jsonPointer = $data
  498. data = N.rootData
  499. } else {
  500. const matches = RELATIVE_JSON_POINTER.exec($data)
  501. if (!matches) throw new Error(`Invalid JSON-pointer: ${$data}`)
  502. const up: number = +matches[1]
  503. jsonPointer = matches[2]
  504. if (jsonPointer === "#") {
  505. if (up >= dataLevel) throw new Error(errorMsg("property/index", up))
  506. return dataPathArr[dataLevel - up]
  507. }
  508. if (up > dataLevel) throw new Error(errorMsg("data", up))
  509. data = dataNames[dataLevel - up]
  510. if (!jsonPointer) return data
  511. }
  512. let expr = data
  513. const segments = jsonPointer.split("/")
  514. for (const segment of segments) {
  515. if (segment) {
  516. data = _`${data}${getProperty(unescapeJsonPointer(segment))}`
  517. expr = _`${expr} && ${data}`
  518. }
  519. }
  520. return expr
  521. function errorMsg(pointerType: string, up: number): string {
  522. return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}`
  523. }
  524. }