forked from ajv-validator/ajv
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* discriminator keyword, ajv-validator#1119 (WIP) * OpenAPI discriminator, tests, ajv-validator#1119 * docs: discriminator
- Loading branch information
1 parent
4053312
commit 164a991
Showing
13 changed files
with
444 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import type {CodeKeywordDefinition, AnySchemaObject, KeywordErrorDefinition} from "../../types" | ||
import type {KeywordCxt} from "../../compile/validate" | ||
import {_, getProperty, Name} from "../../compile/codegen" | ||
import {DiscrError, DiscrErrorObj} from "../discriminator/types" | ||
|
||
export type DiscriminatorError = DiscrErrorObj<DiscrError.Tag> | DiscrErrorObj<DiscrError.Mapping> | ||
|
||
const error: KeywordErrorDefinition = { | ||
message: ({params: {discrError, tagName}}) => | ||
discrError === DiscrError.Tag | ||
? `tag "${tagName}" must be string` | ||
: `value of tag "${tagName}" must be in oneOf`, | ||
params: ({params: {discrError, tag, tagName}}) => | ||
_`{error: ${discrError}, tag: ${tagName}, tagValue: ${tag}}`, | ||
} | ||
|
||
const def: CodeKeywordDefinition = { | ||
keyword: "discriminator", | ||
type: "object", | ||
schemaType: "object", | ||
error, | ||
code(cxt: KeywordCxt) { | ||
const {gen, data, schema, parentSchema, it} = cxt | ||
const {oneOf} = parentSchema | ||
if (!it.opts.discriminator) { | ||
throw new Error("discriminator: requires discriminator option") | ||
} | ||
const tagName = schema.propertyName | ||
if (typeof tagName != "string") throw new Error("discriminator: requires propertyName") | ||
if (schema.mapping) throw new Error("discriminator: mapping is not supported") | ||
if (!oneOf) throw new Error("discriminator: requires oneOf keyword") | ||
const valid = gen.let("valid", false) | ||
const tag = gen.const("tag", _`${data}${getProperty(tagName)}`) | ||
gen.if( | ||
_`typeof ${tag} == "string"`, | ||
() => validateMapping(), | ||
() => cxt.error(false, {discrError: DiscrError.Tag, tag, tagName}) | ||
) | ||
cxt.ok(valid) | ||
|
||
function validateMapping(): void { | ||
const mapping = getMapping() | ||
gen.if(false) | ||
for (const tagValue in mapping) { | ||
gen.elseIf(_`${tag} === ${tagValue}`) | ||
gen.assign(valid, applyTagSchema(mapping[tagValue])) | ||
} | ||
gen.else() | ||
cxt.error(false, {discrError: DiscrError.Mapping, tag, tagName}) | ||
gen.endIf() | ||
} | ||
|
||
function applyTagSchema(schemaProp?: number): Name { | ||
const _valid = gen.name("valid") | ||
const schCxt = cxt.subschema({keyword: "oneOf", schemaProp}, _valid) | ||
cxt.mergeEvaluated(schCxt, Name) | ||
return _valid | ||
} | ||
|
||
function getMapping(): {[T in string]?: number} { | ||
const oneOfMapping: {[T in string]?: number} = {} | ||
const topRequired = hasRequired(parentSchema) | ||
let tagRequired = true | ||
for (let i = 0; i < oneOf.length; i++) { | ||
const sch = oneOf[i] | ||
const propSch = sch.properties?.[tagName] | ||
if (typeof propSch != "object") { | ||
throw new Error(`discriminator: oneOf schemas must have "properties/${tagName}"`) | ||
} | ||
tagRequired = tagRequired && (topRequired || hasRequired(sch)) | ||
addMappings(propSch, i) | ||
} | ||
if (!tagRequired) throw new Error(`discriminator: "${tagName}" must be required`) | ||
return oneOfMapping | ||
|
||
function hasRequired({required}: AnySchemaObject): boolean { | ||
return Array.isArray(required) && required.includes(tagName) | ||
} | ||
|
||
function addMappings(sch: AnySchemaObject, i: number): void { | ||
if (sch.const) { | ||
addMapping(sch.const, i) | ||
} else if (sch.enum) { | ||
for (const tagValue of sch.enum) { | ||
addMapping(tagValue, i) | ||
} | ||
} else { | ||
throw new Error(`discriminator: "properties/${tagName}" must have "const" or "enum"`) | ||
} | ||
} | ||
|
||
function addMapping(tagValue: unknown, i: number): void { | ||
if (typeof tagValue != "string" || tagValue in oneOfMapping) { | ||
throw new Error(`discriminator: "${tagName}" values must be unique strings`) | ||
} | ||
oneOfMapping[tagValue] = i | ||
} | ||
} | ||
}, | ||
} | ||
|
||
export default def |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import type {ErrorObject} from "../../types" | ||
|
||
export enum DiscrError { | ||
Tag = "tag", | ||
Mapping = "mapping", | ||
} | ||
|
||
export type DiscrErrorObj<E extends DiscrError> = ErrorObject< | ||
"discriminator", | ||
{error: E; tag: string; tagValue: unknown}, | ||
string | ||
> |
Oops, something went wrong.