Register custom validator without modifying the input schema
mikestead opened this issue · 1 comments
mikestead commented
I've done a little searching but couldn't spot this raised or covered anywhere.
What version of Ajv you are you using?
8.16.0
What problem do you want to solve?
I'd like to add a custom validator to an existing schema without modifying the definition which I don't control. This would augment any validation already present on that schema.
What do you think is the correct solution to problem?
Something similar to avj.addKeyword(...)
but instead add a validator targeting a specific schema path that's been registered with the avj
instance.
avj.compile(jsonSchemaDefinitions);
avj.addValidator({
// firstName having an inline schema
schemaPath: '#/definitions/Account/properties/firstName',
validate: (schema, data) => {
// custom validation here
return true;
},
errors: false,
})
const validate = avj.getSchema('#/definitions/Account');
validate({ firstName: "james" });
Will you be able to implement it?
No bandwidth currently
mikestead commented
I've created a utility to work around this via keywords so I'll close the issue.
For anyone interested...
const avj = new AVJ({ allErrors: true });
addSchemaValidator(avj, {
schemaDefs: jsonSchemaDefinitions,
schemaPath: '#/definitions/Account/properties/firstName,
keyword: 'x-accountNameVerifier',
validate: ({ data: firstName, keyword, errors }) => {
if (!isValidName(firstName)) {
errors.push({
keyword,
message: 'must pass a valid firstName',
});
return false;
}
return true;
},
errors: true,
});
avj.compile(jsonSchemaDefinitions);
const validateAccount = avj.getSchema('#/definitions/Account');
if (!validateAccount({ firstName: "james" })) {
console.log(validateAccount.errors)
}
import AVJ, { AnySchemaObject, FuncKeywordDefinition, SchemaValidateFunction, ErrorObject } from 'ajv';
import { DataValidationCxt } from 'ajv/dist/types';
export type ValidateParams = {
schema: any;
data: any;
parentSchema?: AnySchemaObject;
dataCxt?: DataValidationCxt;
errors: Partial<ErrorObject>[];
};
export type SchemaValidator = (params: ValidateParams) => boolean;
export type SchemaValidatorDefinition = {
schemaDefs: any;
schemaPath: string;
keyword: string;
validate: SchemaValidator;
} & Omit<FuncKeywordDefinition, 'keyword' | 'validate'>;
export function addSchemaValidator(avj: AVJ, validatorDef: SchemaValidatorDefinition): AVJ {
const { schemaDefs, schemaPath, validate, ...keywordDef } = validatorDef;
let path = schemaPath;
if (path.startsWith('#')) path = path.slice(1);
if (path.startsWith('/')) path = path.slice(1);
let defs = schemaDefs;
for (const segment of path.split('/')) {
defs = defs[segment];
}
const keyword = keywordDef.keyword;
defs[keyword] = true;
const validateWrapperRef: SchemaValidateFunction = validateWrapper;
function validateWrapper(schema: any, data: any, parentSchema?: AnySchemaObject, dataCxt?: DataValidationCxt) {
const errors: Partial<ErrorObject>[] = [];
const isValid = validate({
schema,
data,
parentSchema,
dataCxt,
keyword,
errors,
});
if (!isValid && errors.length) {
validateWrapperRef.errors = errors;
}
return isValid;
}
avj.addKeyword({ ...keywordDef, validate: validateWrapper });
return avj;
}