Yup conditional object validation not working - javascript

I'm trying to define a Yup validation for an object - if a defined sibling is set to true, the field of type object should be required, otherwise not
Example:
const { object, string, number, date, boolean } = require('yup')
const contactSchema = object({
isBig: boolean(),
count: number()
.when('isBig', {
is: true, // alternatively: (val) => val == true
then: number().min(5),
otherwise: number().min(0),
}),
complexOne: object({
simpleOne: string(),
})
.when('isBig', {
is: true,
then: object().required(),
otherwise: object(),
})
})
The object passed into the validation:
{
isBig: true,
count: -1,
}
As you can see, I intentionally don't pass the complexOne since I want Yup to display the error. The validation for the count works correctly - if the value is less than 0 and the isBig is set to true, Yup will correctly display an error message ValidationError: count must be greater than or equal to 5
Unfortunately, it completely ignores the conditional validation set for the complexOne field. Either yup does not support the when for object types or I'm doing something incorrectly.
Thanks for any help

you must set strict option to true in order to only validate the object, and skip any coercion or transformation:
contactSchema.validate(contact, { strict: true })
.then(obj => {
console.log(obj)
}, err => {
console.log(err.message)
})
Demo:

Related

Changing default Boolean value for Joi is not working properly

I'm using joi Sandbox v17.7.0 at https://joi.dev/tester/
Schema:
Joi.object({
id: Joi.when(Joi.ref('query.byRef'), {
is: Joi.boolean().valid(true),
then: Joi.string().min(1),
otherwise: Joi.number().positive(),
}),
query: Joi.object(
{
byRef: Joi.boolean().default(false)
}
),
})
And my data is
{
id: "1",
}
Basically, I want id to be parsed as integer when byRef = false, and as string when byRef = true. When I pass by ref value in my data, as
{
id: "1",
query: {
byRef: false,
}
}
Then it works as expected. But when I omit it, the default value doesn't work as expected. Whether I change it to true or false.
I've tried it as
is: true
that also doesn't work. What can I do to make sure default value is set?

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

Mongoose automatically change the type of the value

In mongoose.model, I have chosen the type of name to be a string and the type of age to be a number, but when I enter a number as the value of name, I don't get an error and the same thing happens when I use something like '18' as the value of age.
Here is the code:
const User = mongoose.model('User', {
name: { type: String },
age: { type: Number }
});
const me = new User({
name: 12,
age: '18'
});
me.save().then(() => console.log(me)).catch(error => console.log(error));
Mongoose casts the values to the corresponding type, if it fails a CastError is thrown, from the doc:
Before running validators, Mongoose attempts to coerce values to the
correct type. This process is called casting the document. If casting
fails for a given path, the error.errors object will contain a CastError object.
You can try this by given age the value 'aa' for example.
If you want to override this behavior you can use one of the following options:
Disable casting globally: mongoose.Number.cast(false)
Disable casting just for a given path:
age: {
type: Number,
cast: false // Disable casting just for this path
},
Use a custom function:
age: {
type: Number,
cast: v => { return typeof v === 'number' && !isNaN(v) ? Number(v) : v; } // Override casting just for this path
}

Get the value of another field for validation in Yup Schema

I am using Formik with Yup for validation and TypeScript
I have a field that needs to validate based on the value of another field.
The first field is called price and the second field is called tips. The max tip value is 10% of the what ever the price entered is.
I tried to create validation for this using the following:
tips: yup.number()
.min(0, `Minimum tip is $0`)
.max( parseFloat(yup.ref('price'))* 0.1, "Maximum tip is 10% of the price.");
however this doesn't compile because yup.ref returns a Ref. How can I get the value of the price field in this validation?
number.max cannot reference other field and calculate with it at validation.
If you want to do this, you need to implement own schema with mixed.test.
Here is a example.
tips: number()
.min(0, `Minimum tip is $0`)
.test({
name: 'max',
exclusive: false,
params: { },
message: '${path} must be less than 10% of the price',
test: function (value) {
// You can access the price field with `this.parent`.
return value <= parseFloat(this.parent.price * 0.1)
},
}),
Here is doc.
You can check and try how it works here.
I hope this will help you.
if you don't want to use this.parent to access the other properties values you can use the context.
tips: number()
.min(0, `Minimum tip is $0`)
.test(
'max',
'${path} must be less than 10% of the price',
(value, context) => value <= parseFloat(context.parent.price * 0.1),
),
// OR
tips: number()
.min(0, `Minimum tip is $0`)
.test({
name: 'max',
exclusive: false,
params: { },
message: '${path} must be less than 10% of the price',
test: (value, context) => value <= parseFloat(context.parent.price * 0.1),
}),
For referencing other field value we can use this.parent or ctx.parent in case if our value is not nested.
object({
title: string(),
price: string().test('test-name', 'test-message', (value, ctx) => {
let title = ctx.parent.title;
}),
foo: string()
.test('test-name1', 'test-message1', function (value) {
let title = this.parent.title
})
})
but if we have nested value parent is going to give parent of nested
value. in this case parent is not going to work if we want to access
very parent value. we can access parent value with ctx.from. ctx.from contains parents from bottom to top. for example:
object({
title: string(),
ourObject: object({
param1: string(),
param2: string()
.test('test-name', 'test-msg', (value, ctx) => {
let title = ctx.from[ctx.from.length - 1].value.title
})
})
})
or we can easily access any data we want with providing context to schema when validating
object({
price: string(),
foo: string()
.test('test-name', 'test-message', (value, ctx) => {
let arr = ctx.context.arr;
})
})
.validate({ price: 5, foo: 'boo' }, { context: { arr: [1, 2] } })
.then(() => ...)
.catch((err) => ...)
doc
LIKE SO
.test((val,obj)=>{
console.log(val,obj.parent.otherField)
})
val=>current field value
obj=>object of the whole schema
EXAMPLE::::
date: yup.
string()
.required()
.test('type', 'Date should be future date', (val) => {
console.log(val);
}),
time: yup
.string()
.required()
.test('type', 'Time should be future time', (val,obj) => {
console.log(val,'in time val',obj.parent.date,'date')
}),

SimpleSchema match any type but null

I'm planning to make a collection to hold different app-wide settings, like, say, amount of logged in users today, Google analytics tracking ID, etc. So I made a schema like this:
options_schema = new SimpleSchema({
key: {
type: String,
unique: true
},
value: {
},
modified: {
type: Date
}
});
Now the main problem is that I want value to be of any type: Number, String, Date, or even custom Objects. Though it has to be present, can't be null.
But of course it gets angry about not specifying the type. Is there a workaround for this?
You can use Match patterns for your fields' type which allow you to do pretty much anything :
const notNullPattern = Match.Where(val => val !== null)
value : {
type : notNullPattern
}
(See Arrow functions)
Note that this will allow everything but null, including undefined.
Defining patterns this way allow you to use them everywhere in your application including in check :
check({
key : 'the key',
modified : Date.now(),
value : {} // or [], 42, false, 'hello ground', ...
}, optionsSchema)
Match.test(undefined, notNullPattern) //true
Match.test({}, notNullPattern) //true
Match.test(null, notNullPattern) //false
A more general solution to exclude one value would simply be:
const notValuePattern =
unwantedValue => Match.Where(val => val !== unwantedValue))
The use of which is similar to the above:
Match.test(42, notValuePattern(null)) // true
Note that due to the use of the identity operator === it will notably fail for NaN:
Match.test(NaN, notValuePattern(NaN)) // true :(
A solution could be:
const notValuePattern =
unwantedValue => Match.Where(val => Number.isNaN(unwantedValue)?
!Number.isNaN(val)
: val !== unwantedValue
)
Should you want a solution to exclude some specific values in a schema (kind of the contrary of Match.OneOf), you could use the following:
const notOneOfPattern = (...unwantedValues) =>
Match.Where(val => !unwantedValues.includes(val)
)
This uses Array.prototype.includes and the ... spread operator. Use as follow:
Match.test(42, notOneOfPattern('self-conscious whale', 43)) // true
Match.test('tuna', notOneOfPattern('tyranny', 'tuna')) // false
Match.test('evil', notOneOfPattern('Plop', 'kittens')) // true
const disallowedValues = ['coffee', 'unicorns', 'bug-free software']
Match.test('bad thing', notOneOfPattern(...disallowedValues)) // true

Categories

Resources