joi
joi copied to clipboard
How to use the value of a sibling field as the label of another field?
Support plan
- is this issue currently blocking your project? (yes/no): no
- is this issue affecting a production system? (yes/no): no
Context
- node version: 14.17.3
- module version: 17.4.2
- environment (e.g. node, browser, native): node
- used with (e.g. hapi application, another framework, standalone, ...): express, typescript
- any other relevant information:
How can we help?
I have a process on my API that accepts arbitrary inputs from an Angular application to set values for a report which is generated. I'm trying to validate the input based on other parameters being passed in (such as the field data type) which works great, however when a value fails the validation the error messages are too vague / technical such as "params[2].field_value" must be a valid date
.
Here is an example input that I'm passing in, along with the validation code I'm using: (note the invalid date on the 3rd entry in the array on "End Date", in the "field_value" property)
{
"params": [
{
"field_seq": 1,
"field_label": "Cust ID",
"field_type": "varchar",
"field_required": "required",
"field_value": "1CALJAM"
},
{
"field_seq": 2,
"field_label": "Start Date",
"field_type": "date",
"field_required": "required",
"field_value": "08/01/2021"
},
{
"field_seq": 3,
"field_label": "End Date",
"field_type": "date",
"field_required": "required",
"field_value": "08/3120212"
},
{
"field_seq": 4,
"field_label": "BOL",
"field_type": "varchar",
"field_required": "required",
"field_value": "Y"
}
]
}
function validate(req: Request) {
const paramSchema = Joi.object({
field_seq: Joi.number().positive().precision(0).max(100).required(),
field_label: Joi.string()
.trim()
.max(255)
.required(),
field_type: Joi.string().trim().max(12).required(),
field_required: Joi.string().trim().max(12).required(),
field_value: Joi.when('field_required', { is: Joi.string().regex(/^required$/), then: Joi.required() })
.when('field_type', { is: Joi.string().regex(/^varchar$/), then: Joi.string().trim().max(4000) })
.when('field_type', { is: Joi.string().regex(/^number$/), then: Joi.number() })
.when('field_type', {
is: Joi.string().regex(/^date$/),
then: Joi.date(),
}),
});
const schema = Joi.object({
sessionId: Joi.string().trim().max(255).required(),
reportName: Joi.string().trim().max(255).required(),
params: Joi.array().required().min(0).items(paramSchema),
});
return schema.validate(req.body, { debug: true }).error;
}
I've tried using the label() setting and this does allow me to change the label for field_value
to something else, but so far only a static string has worked successfully.
I made an attempt to use function calls on custom()
to set a field value and later get it, but the order that the functions are called results in this not working. Here is an example of that validation code:
function validate(req: Request) {
let fieldLabel: string;
function setFieldLabel(label: string) {
console.log('setFieldLabel:', label);
fieldLabel = label;
}
function getFieldLabel(): string {
console.log('getFieldLabel:', fieldLabel);
return fieldLabel ?? 'empty';
}
const paramSchema = Joi.object({
field_seq: Joi.number().positive().precision(0).max(100).required(),
field_label: Joi.string()
.trim()
.max(255)
.required()
.custom((value) => {
setFieldLabel(value);
}),
field_type: Joi.string().trim().max(12).required(),
field_required: Joi.string().trim().max(12).required(),
field_value: Joi.when('field_required', { is: Joi.string().regex(/^required$/), then: Joi.required() })
.when('field_type', { is: Joi.string().regex(/^varchar$/), then: Joi.string().label(getFieldLabel()).trim().max(4000) })
.when('field_type', { is: Joi.string().regex(/^number$/), then: Joi.number().label(getFieldLabel()) })
.when('field_type', {
is: Joi.string().regex(/^date$/),
then: Joi.date().label(getFieldLabel()),
}),
});
const schema = Joi.object({
sessionId: Joi.string().trim().max(255).required(),
reportName: Joi.string().trim().max(255).required(),
params: Joi.array().required().min(0).items(paramSchema),
});
return schema.validate(req.body, { debug: true }).error;
}
Ideally, I would like to just replace "params[2].field_value" must be a valid date
with (in this specific example) "End Date" must be a valid date
, with the label value being pulled from the value of field_label
in the same object.
Is something like this even possible?
I ended up getting this working by modifying the output error message. This feels somewhat hacky to me, but will work well enough unless there is a better solution.
function validate(req: Request) {
const paramSchema = Joi.object({
field_seq: Joi.number().positive().precision(0).max(100).required(),
field_label: Joi.string().trim().max(255).required(),
field_type: Joi.string().trim().max(12).required(),
field_required: Joi.string().trim().max(12).required(),
field_value: Joi.when('field_required', { is: Joi.string().regex(/^required$/), then: Joi.required() })
.when('field_type', { is: Joi.string().regex(/^varchar$/), then: Joi.string().trim().max(4000) })
.when('field_type', { is: Joi.string().regex(/^number$/), then: Joi.number() })
.when('field_type', {
is: Joi.string().regex(/^date$/),
then: Joi.date(),
}),
});
const schema = Joi.object({
sessionId: Joi.string().trim().max(255).required(),
reportName: Joi.string().trim().max(255).required(),
params: Joi.array().required().min(0).items(paramSchema),
});
const error = schema.validate(req.body, { debug: true }).error;
if (error?.details.length > 0 && error?.details[0].path.includes('params')) {
error.details[0].message = error.details[0].message.replace(
error.details[0].context.label,
error._original.params[error.details[0].path[1]].field_label
);
}
return error;
}