Making getErrors synchronous#205
Conversation
|
@srivastava-diya, please do the first review |
|
Hi @jdesrosiers, @srivastava-diya |
0ece108 to
d24ba98
Compare
|
I made resolver required in ErrorHandler and getErrors, then removed resolver checks from all handlers that use it. |
|
hey @jdesrosiers all my comments have been addressed, LGTM from my side. |
| /** | ||
| * @param {AST} ast | ||
| * @returns {API.ErrorResolver} | ||
| */ | ||
| const createErrorResolver = (ast) => ({ | ||
| getCompiledKeywordValue: (schemaLocation) => getCompiledKeywordValue(ast, schemaLocation), | ||
| getSiblingKeywordValue: (schemaLocation, siblingKeywordUri) => { | ||
| return getSiblingKeywordValue(ast, schemaLocation, siblingKeywordUri); | ||
| } | ||
| }); |
There was a problem hiding this comment.
I don't think this is necessary. Just pass the AST instead of the resolver and call the functions directly.
There was a problem hiding this comment.
It was solving the problem of recursion with anyOf, oneof and dependencies.
These handlers are calling getErrors recursively, so when I delete this method and pass the ast directly, it will be expected that the higher level has AST but the recursive calls expect another type which is ErrorResolver, but I am going to pass it and solve it by ternary condition that checks the type and selecting the value according to it.
If it suits, we will keep it in that way .
There was a problem hiding this comment.
No, I mean you don't need an ErrorResolver at all. It doesn't need to exist anywhere. In all cases, you can pass the AST and import the utility functions that use that AST where they are needed.
| const getSiblingKeywordValue = (ast, schemaLocation, siblingKeywordUri) => { | ||
| const parentLocation = schemaLocation.replace(/\/[^/]+$/, ""); | ||
| const parentNode = ast[parentLocation]; | ||
|
|
||
| if (!Array.isArray(parentNode)) { | ||
| return undefined; | ||
| } | ||
|
|
||
| for (const [keywordUri, keywordLocation, keywordValue] of parentNode) { | ||
| if (keywordUri === siblingKeywordUri) { | ||
| return { | ||
| keywordLocation, | ||
| keywordValue | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| return undefined; | ||
| }; |
There was a problem hiding this comment.
I don't think it's necessary this function to return the value and the location. The value will come from the AST in all cases. The only thing we need is the location of the sibling keyword.
There was a problem hiding this comment.
the value is returned with the location as the handlers like contains.js needs the value but has no access to the AST, getError only has it.
There was a problem hiding this comment.
The value that comes from the AST always includes the values you need from the sibling keyword. The min/max keywords use a tuple like [value, isExclusive] and the contains keyword uses an object like { constains, minContains, maxContains }. You don't need to get the value again. All you should ever need that you don't get from the keyword lookup is the schema location of the sibling keyword(s).
|
Hi @jdesrosiers |
| const compiledPattern = resolver.getCompiledKeywordValue(schemaLocation); | ||
| const pattern = /** @type RegExp */ (compiledPattern).source; |
There was a problem hiding this comment.
The cast should be on compiledPattern.
| /** | ||
| * @param {AST} ast | ||
| * @returns {API.ErrorResolver} | ||
| */ | ||
| const createErrorResolver = (ast) => ({ | ||
| getCompiledKeywordValue: (schemaLocation) => getCompiledKeywordValue(ast, schemaLocation), | ||
| getSiblingKeywordValue: (schemaLocation, siblingKeywordUri) => { | ||
| return getSiblingKeywordValue(ast, schemaLocation, siblingKeywordUri); | ||
| } | ||
| }); |
There was a problem hiding this comment.
No, I mean you don't need an ErrorResolver at all. It doesn't need to exist anywhere. In all cases, you can pass the AST and import the utility functions that use that AST where they are needed.
| const getSiblingKeywordValue = (ast, schemaLocation, siblingKeywordUri) => { | ||
| const parentLocation = schemaLocation.replace(/\/[^/]+$/, ""); | ||
| const parentNode = ast[parentLocation]; | ||
|
|
||
| if (!Array.isArray(parentNode)) { | ||
| return undefined; | ||
| } | ||
|
|
||
| for (const [keywordUri, keywordLocation, keywordValue] of parentNode) { | ||
| if (keywordUri === siblingKeywordUri) { | ||
| return { | ||
| keywordLocation, | ||
| keywordValue | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| return undefined; | ||
| }; |
There was a problem hiding this comment.
The value that comes from the AST always includes the values you need from the sibling keyword. The min/max keywords use a tuple like [value, isExclusive] and the contains keyword uses an object like { constains, minContains, maxContains }. You don't need to get the value again. All you should ever need that you don't get from the keyword lookup is the schema location of the sibling keyword(s).
|
Hi @jdesrosiers |
| const parentLocation = schemaLocation.replace(/\/[^/]+$/, ""); | ||
| const parentNode = ast[parentLocation]; | ||
| const containsNode = Array.isArray(parentNode) ? parentNode.find(([, keywordLocation]) => keywordLocation === schemaLocation) : undefined; | ||
| const containsRange = /** @type {ContainsRange} */ (containsNode?.[2] ?? {}); |
There was a problem hiding this comment.
Why aren't you using getCompiledKeywordValue here?
There was a problem hiding this comment.
Sorry I used the AST and totally forgot that I'm already doing this in getCompiledKewewordValue, my bad.
| const exclusiveKeywordLocation = getSiblingKeywordValue(ast, schemaLocation, "https://json-schema.org/keyword/draft-04/exclusiveMaximum"); | ||
| const exclusiveLocation = exclusive && exclusiveKeywordLocation ? exclusiveKeywordLocation : ""; |
There was a problem hiding this comment.
This only gets used if maximum < lowestMaximum && exclusive === true, so only build it in the conditional below if it's exclusive.
| }; | ||
|
|
||
| /** @type (ast: AST, schemaLocation: string, siblingKeywordUri: string) => string | undefined */ | ||
| export const getSiblingKeywordValue = (ast, schemaLocation, siblingKeywordUri) => { |
There was a problem hiding this comment.
This now returns a location, not a value. So, we need to update the function name.
7e68633 to
60dbebf
Compare
This PR removes async schema value lookups from the error formatting path and makes getErrors synchronous by resolving keyword values from the compiled AST to using it so it can be used in
@hyperjump/json-schemaas we need here (#204).I replaced async schema reads with two AST resolver functions: getCompiledKeywordValue() for normal keyword lookups by schemaLocation, and getSiblingKeywordValue() for sibling keywords in the same parent node (like draft-04 min/max exclusivity and contains min/max contains). Then getErrors passes this resolver to handlers, so the whole error handling path is synchronous and no longer depends on await getSchema().
getErrors now passes an AST resolver to handlers, including recursive ones, and sibling-dependent keywords use sibling lookup from the same parent AST node. I also adjusted handlers for compiled value shapes (like regex, draft-04 tuples, and const/enum JSON strings), then updated types. All tests are still passing leaves us with the same behavior.