XOR validation using Joi-browser - javascript

I am using joi-browser 13.4.0. In order to generate error message for each input field I am trying to validate fields using .required() like so:
config = {
input1: Joi.string()
.empty("")
.required(),
input2: Joi.string()
.empty("")
.required()
};
schema = Joi.object(this.config).xor("input1", "input2");
But this example is invalid because when input1 or input2 is set to .required(), .xor() function is being ignored. Is there any other way to implement XOR validation without using .xor() method?
Thanks.

You don't need required() if you're using xor:
config = {
input1: Joi.string().empty(""),
input2: Joi.string().empty("")
};
schema = Joi.object(config).xor("input1", "input2");
In fact, using required() like that would never validate. You'd get one of the following error messages:
ValidationError: child "input1" fails because ["input1" is required]
or
ValidationError: "value" contains a conflict between exclusive peers [input1, input2]

Use object.length()
Is there any other way to implement XOR validation without using .xor() method?
Yes, you could for example use the object().length() property to limit the keys in an object to 1.
const Joi = require('joi-browser')
const schema = Joi.object().keys({
input1: Joi.string().empty(''),
input2: Joi.string().empty('')
}).required().length(1);
const value = {
input1: "input1",
};
// this will fail
// const value = {};
// this will fail too
// const value = {
// input1: 'input1',
// input2: 'input2',
// };
const result = Joi.validate(value, schema);
console.log(JSON.stringify(result.error, null, 2));
Be careful
Don't forget to add required() to the parent object, otherwise it is possible to pass undefined to the validation function!
Without required() on the parent it is possible that a simple undefined will pass the validation:
const Joi = require('joi-browser')
const schema = Joi.object().keys({
input1: Joi.string().empty(''),
input2: Joi.string().empty('')
}).length(1); // no required()
const value = undefined; // this will pass validation
const result = Joi.validate(value, schema);
console.log(JSON.stringify(result.error, null, 2));

Related

Why yup is trigger my tests even though previous tests are failing?

I want to validate my object with the schema using yup.
But I notice that when I wrote my own test function it's trigger anyway.
I mean I validate the age property for number, null, positive, integer value. then I want to continue with my own logic test.
So I expect to NOT enter the function unless the previous tests are valid.
I'm not sure if this is how it meant to be, but in this way I must also check for valid input in my tests function, even though I add the number, null, positive, integer checks.
So am I using the yup wrong?
What I expect form yup is not invoke the test if the previous tests are invalid.
stackblitz
import { object, string, number, date, InferType } from 'yup';
let userSchema = object({
age: number()
.nullable()
.positive()
.integer()
.test({
message: 'test message',
test: (v) => {
console.log('in test!', v);
return !!v.toPrecision();
},
}),
});
userSchema
.validate({ age: null })
.then((res) => {
console.log({ res });
})
.catch((e) => {
console.log({ e });
});

Yup conditional validation based on a a non field value

I am trying to create a yup schema where based on a variables value the schema changes slightly. In my case depending on the value of prop myCondition I need to make a field as required. I would like to know if there is any better way I can achieve the same using Yup. Here's my current code structure that works:
// config.js
const COMMON_SCHEMA = {
str1: yup
.string()
.nullable()
.required('Please input str1'),
str3: yup
.string()
.nullable()
};
const VALIDATION_SCHEMA_1 = yup.object().shape({
...COMMON_SCHEMA,
str2: yup.string().nullable(),
});
const VALIDATION_SCHEMA_2 = yup.object().shape({
...COMMON_SCHEMA,
str2: yup
.string()
.nullable()
.required('Please input str2'),
});
const SCHEMAS = {
VALIDATION_SCHEMA_1,
VALIDATION_SCHEMA_2
}
export default SCHEMAS;
the following is how I conditionally choose different schemas:
// app.js
import SCHEMAS from './config';
...
<Formik
validationSchema={
this.props.myCondition === true
? SCHEMAS.VALIDATION_SCHEMA_1
: SCHEMAS.VALIDATION_SCHEMA_2
}
>
...
</Formik>
I feel like I can achieve whatever I am doing above in an easier way with yup. One of the approaches I tried was to pass in the prop myCondition into config file and work with it's value by using yup.when() but when only works if the value you want to use is also a part of the form so I couldn't achieve the same.
This can be achieved by the following way:
You can wrap your schema in a function and pass your conditonal prop:
const wrapperSchema = (condition) =>
Yup.object().shape({
...COMMON_SCHEMA,
str2: yup.string()
.when([], {
is: () => condition === true,
then: yup.string().nullable(),
otherwise: yup.string().nullable().required('Please input str2'),
}),
});
Please note that first param of when is a required param, you can also pass e.g. ["title", "name"] where title and name can be your field values, and you can retrieve and use them in is callback, in the same order, if you have to.
You can conditionally add validation directly in the validationSchema, like this: https://stackoverflow.com/a/56216753/8826314
Edited to add, you can include your value in the initial form, so that you can do the when check against that field. For example:
<Formik
initialValues={
...,
str2
}
validationSchema={
...,
str2: yup.object().when('str2', {
is: str2 => condition check that returns boolean,
then: Yup.string().nullable(),
otherwise: Yup.string().nullable().required('Please input str2')
})
}
>
...
</Formik>

How to run custom validation function in the joi schema

I want to make a field mandatory or optional based on something that is not in the schema. For example, something stored in the global scope. This is the Joi schema:
first_name: Joi.string().min(2).max(10).regex(Regex.alphabeta, 'alphabeta').error(JoiCustomErrors)
How to make first_name be required if some_global_scope_var is 1, else, make it optional?
Note: some_global_scope_var is not part of the schema.
Access Global scope with $ character
Normally you would access a relative property last_name without a prefix. For global access prefix your variable with $.
Complete Solution
const Joi = require('#hapi/joi');
global.app = {
someValue: 1,
};
const schema = Joi.object().keys({
first_name: Joi.string()
.min(2)
.max(10)
.when('$app.someValue', {
is: Joi.equal(1),
then: Joi.required(),
})
});
const value = {
first_name: 'a1300',
};
const result = schema.validate(value);
console.log(JSON.stringify(result.error, null, 2));
P.S. I omitted regex(Regex.alphabeta, 'alphabeta').error(JoiCustomErrors)

how to validate joi sub-schema in best way

Is it possible to validate joi schema without getting casting error? i.e. I have N fields but I want to validate 1 field only.
I have tried 2 ways, as below:
const Joi = require("joi");
const _ = require('lodash');
const testSchema = Joi.object().keys({
name: Joi.string().trim().min(5).max(25).required(),
allowed: Joi.number().integer().min(0).max(1).default(0)
});
// works smoothly; no error
// const {error, value} = Joi.validate({name :"abc", allowed: 1}, testSchema);
// (Way 1) --> Error: "value" must be a number
// const {error, value} = Joi.validate({name :"abc", allowed: 1}, Joi.reach(testSchema, 'allowed'));
// (Way 2) --> Error: "value" must be a number
const {error, value} = Joi.validate({name :"abc", allowed: 1}, _.find(testSchema._inner.children, {key: 'allowed'}).schema);
console.log(error);
P.S. I know the 3rd approach to compose final schema from smaller schema(s) but I don't want to go for that.
Instead of key value object just pass the value of the key, like:
Joi.validate(1, Joi.reach(testSchema, 'allowed'));

Stripping unknown keys when validating with Joi

I'm using Joi to validate a JavaScript object in the server. The schema is like the following:
var schema = Joi.object().keys({
displayName: Joi.string().required(),
email: Joi.string().email(),
enabled: Joi.boolean().default(false, "Default as disabled")
}).unknown(false);
The schema above will report an error if there is an unknown key in the object, which is expected, but what I want is to strip all the unknown silently, without an error. Is it possible to be done?
You need to use the stripUnknown option if you want to strip the unknown keys from the objects that you are validating.
cf options on https://github.com/hapijs/joi/blob/master/API.md#validatevalue-schema-options-callback
As in Version 14.3.4, there is a simple solution to this issue. Here is the code that solves the problem for you.
// Sample data for testing.
const user = {
fullname: "jayant malik",
email: "demo#mail.com",
password: "password111",
username: "hello",
name: "Hello"
};
// You define your schema here
const user_schema = joi
.object({
fullname: joi.string().min(4).max(30).trim(),
email: joi.string().email().required().min(10).max(50).trim(),
password: joi.string().min(6).max(20),
username: joi.string().min(5).max(20).alphanum().trim()
})
.options({ stripUnknown: true });
// You validate the object here.
const result = user_schema.validate(user);
// Here is your final result with unknown keys trimmed from object.
console.log("Object with trimmed keys: ", result.value);
const joi = require('joi');
joi.validate(object, schema, {stripUnknown:true}, callback);
Here is the current way to include the strip unknown option:
const validated = customSchema.validate(objForValidation, { stripUnknown: true });
If you pass in an objForValidation that has a key which isn't defined in your customSchema, it will remove that entry before validating.

Categories

Resources