Yup conditionally extend Object Schema with other Schema - yup

For a Project I use yup and I have some trouble extending a Schema when a property is set.
I build a Codesandbox to show my Problem but the basics are the following.
// Schema I want to extend by, I use it in other places which is why I want to spread it into the baseSchema
const baseSchema = yup.object({
firstName: yup.string().required()
}).required();
const extendedSchema = yup
.object({
hasOtherFields: yup
.mixed()
.required()
.oneOf([...TRUE_FLASE])
})
.when("hasOtherFields", {
is: (value?: typeof TRUE_FLASE[number]) =>
value != null && value === "true",
then: (schema) =>
schema
.shape({
...baseSchema.fields
})
.required()
})
.required();
So the property hasOtherFields needs to be set and needs to be either "true" or "false". If it is "true", I extend the extendedSchema with shape and pass in the fields from another the baseSchema which has a required string field.
Sadly this doesn't work ... If I create a nested object property inside the object declaration like so
const extendedSchema = yup
.object({
hasOtherFields: yup
.mixed()
.required()
.oneOf([...TRUE_FLASE]),
extendedFields: yup.object({}).when("hasOtherFields", {
is: (value?: typeof TRUE_FLASE[number]) =>
value != null && value === "true",
then: (schema) =>
schema
.shape({
...baseSchema.fields
})
.required()
})
})
it works, but that doesn't really work for my use case.
So my question is, is there a way ? I basically want the type to look like this
type Schema = {hasCoApplicant: "false"} | {hasCoApplicant: "true"; firstName: string};

Related

omit empty strings fields mongoose

I have the following schema:
const mySchema = new mongoose.Schema({
x: String,
y: String
})
when a user from the front-end requests in his body:
req.body = {
'x' : '',
'y': ''
}
this results in creating a field in MongoDB, but with an empty string.
I need a way to prevent this behavior by setting the empty strings to be undefined somehow.
Is there such an option in Mongoose? or do I have to predict my own middlewares for that?
You could use the set method for Mongoose Schemas:
const mySchema = new mongoose.Schema(
{
myAttribute: {
type: String,
set: (attribute: string) => attribute === '' ? undefined : attribute,
},
},
{ strict: 'throw' },
);
This will unset the field if the string equals ''.
Use this to trim the strings:
set: (a: string) => a?.trim() === '' ? undefined : a
You don't need mongoose, or a middleware to handle this. You can just write a quick few lines to check for empty values and exclude them from the MongoDB write operation.
Ex:
const newEntry = Object.entries(req.body).reduce((obj, [key, value]) => {
if (value) obj[key] = value
return obj
}, {})
In this example, I convert the req.body into an array using Object.entries and iterate over it with the Array.reduce method, wherein I add key:value pairs to a new object if there is a value to add. Since an empty string value is falsey I can do a simple if check on the value. I then assign the return of the reduce method to the variable newEntry. Then I would then take the new entry and create the MongoDB document with it.
This could be extracted into a helper method and reused in any of your routes that need to check remove empty values from an object.
Docs on Array.reduce
Docs on Object.entries

MongoDB field only accepts 3 special values

slider_value: {
type: Number,
required: false,
},
This is the Mongoose schema for one of the fields in my MongoDB model.
It may only accept the integer values of 1, 4, and 10.
How can this validator be specified in the schema?
If you only need to store either one of these three values, storing them as a string, and validating using the enum key would be reasonable. For example that could look like this:
{
slider_value: {
type: String,
enum: ["1", "4", "10"],
},
}
Alternatively, if it is a requirement to store them in form of an int, you could use a custom validator to check a value before it's saved. That would look like this:
{
slider_value: {
type: Number,
validate: {
validator: value => value === 1 || value === 4 || value === 10,
message: props => `${props.value} is invalid for slider_value`,
},
},
}
For more details on custom validators and validation in mongoose in generell, here are the mongoose validation docs.

Yup Child Schema accessing parent value (or passing the value to the child)

I have the following schema, and I am trying to determine how to allow the nested Yup.object().shape({..}) to have access to the RuleSchema validationTypeCode (or pass it to the child schema)
export const RuleSchema = Yup.object().shape({
description: Yup.string().required('Required').min(2).max(25),
fieldId: Yup.number().required('Required'),
validationTypeCode: Yup.string().required('Required'),
failureAction: Yup.string().required('Required'),
failureActionValue: Yup.string().required('Required'),
dataEntryWorkFlowRuleValidationList: Yup.array().of(
Yup.object().shape({
//I need to be able to access the validationTypeCode from the parent schema
fieldValue: Yup.string().min(5)
})
)
})
I've managed to find an approach that works for my scenario, but I am not sure it is the best way to do it.
The schema is now:
const RuleSchema = Yup.object().shape({
description: Yup.string().required('Description Required').min(2).max(25),
fieldId: Yup.number().required('dRequired'),
validationTypeCode: Yup.string().required('Required'),
failureAction: Yup.string().required('Required'),
failureActionValue: Yup.string().required('Required'),
dataEntryWorkFlowRuleValidationList: Yup.array().of(
Yup.object().shape({
fieldValue: Yup.string().when("$rule", (rule, schema) => {
return rule.fieldLength ? schema.max(rule.fieldLength, 'Field Value is too long') : schema
}
)
})
)
})
I call the validation including the entire record being validated into the validation as context.
RuleSchema.validateSync(_rule, {context: {rule: _rule }})

Mark all object properties, including nested objects as required

Is there a way to do it by default? And not by hand for every property?
If it's possible, i hope it can be done in Joi.default
const joi = Joi.defaults((schema) => {
return schema.strict().options({ stripUnknown: true })
})
Found a way, you can use presence('required'):
import Joi from '#hapi/joi'
const joi = Joi.defaults((schema) => {
return schema
.strict()
.options({ stripUnknown: true })
.presence('required')
})
export { joi }

Angular2 validator which relies on another form field

I am trying to create a validator in angular2 where I need to check the value of another form field. I have two scenarios I have tried where I have attempted to do this.
Scenario 1, form field looks like this:
Form = this._fb.group({
ansat: ['', [<any>Validators.required]],
helbred: ['', this.someValidator('ansat')],
});
I have the two fields above, and I would like to be able to check the value of "ansat" in the validator function "someValidator". someValidator looks like this:
someValidator(key: string) {
return (group: FormGroup) => {
console.log(group);
console.log(group.controls[key].value);
}
}
In my function, group includes all the correct information for my formgroup, but "controls" is undefined, which means I cannot get to the value of "ansat.
Scenario 2, form field looks like this:
this.myForm = this._fb.group({
ansat: ['', [<any>Validators.required]],
helbred: ['', c => Validators.compose([
this.someValidator(c,
group => group.controls['ansat'].value === 0
),
])]
});
And this is my someValidator function:
conditional(c, conditional) {
console.log(conditional);
console.log(c);
if(c.parent != undefined || c._parent != undefined){
if(c._parent.controls['ansat'].value === 0){
console.log(c._parent.controls['ansat'].value);
}
}
return null;
}
In this case, the control "c", has the correct information and includes a parent which is the group it is allocated in, but I cannot access it to try and get it's brother in the same group.
And in the case of conditional parameter, I tried sending the group through a function which I cannot make to get working either.
QUESTION: I would like to be able to access the value of "ansat" inside of a validator that I call on "helbred". How do I do this?
Any help is greatly appreciated!
Have a look at this plunker.
You're on the right track, you must pass the actual ansat control in the helbred control's validator, instead of only passing ansat control's value.
ansat: AbstractControl = new FormControl('', Validators.required);
helbred: AbstractControl = new FormControl('', Validators.required, this.someValidator(this.ansat));
this.myForm = this.formBuilder.group({
ansat: this.ansat,
helbred: this.helbred
});