Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register custom validator without modifying the input schema #2462

Closed
mikestead opened this issue Jul 2, 2024 · 1 comment
Closed

Register custom validator without modifying the input schema #2462

mikestead opened this issue Jul 2, 2024 · 1 comment

Comments

@mikestead
Copy link

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
Copy link
Author

mikestead commented Jul 2, 2024

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;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

1 participant