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

Schema Validation: Multiple optional fields, but require at least one. #1090

Open
tyzoid opened this issue Sep 21, 2021 · 1 comment
Open

Comments

@tyzoid
Copy link

tyzoid commented Sep 21, 2021

Is your feature request related to a problem? Please describe.

Currently, there is no easy way to enforce a schema validation requirement across multiple fields.

Consider, for example, an event scheduling API, where users can book locations and/or staff.

  • A user may wish to schedule an event onsite with support staff, and so would schedule both a location and staff member.
  • A user may wish to schedule just the location, and provide their own support.
  • A user may wish to schedule staff support, but for an offsite event (hence, no location attribute)

But it would be invalid for a user to schedule neither support staff, nor a location, since this would not be a meaningful event for the system to track.

Currently, setting optional within the schema validation will bypass any other checks if the field is missing, which makes implementing rules across optional fields difficult.

I have identified two workarounds:

  1. Inserting a custom check in a separate required field, which validates the presence of at least one of the two optional fields, and throws a custom error message if not.
    • This is non-ideal, however, as the error field that comes back is not one of the two optional fields.
  2. Calling a separate function to validate the optional components outside the schema.
    • This is non-ideal, because the schema should describe the entirety of the validation requirements.

Describe the solution you'd like

I would like a mechanism added to the schema to define optional rules across multiple fields. For example, a require attribute in the optional options, which accepts oneOf or allOf. The require attribute would take effect if the current field is omitted.

{
    "location": {
        "in": ["body"],
        "optional": { "options": { "nullable": true, "require": { "oneOf": ["staff"] }}},
        "errorMessage": "Location ID invalid",
        "isInt": { "options": {"min": 0} },
    },
    "staff": {
        "in": ["body"],
        "optional": { "options": { "nullable": true, "require": { "oneOf": ["location"] }}},
        "errorMessage": "Staff ID invalid",
        "isInt": { "options": {"min": 0} },
    }
}

An alternate mechanism is to add a custom function to the optional component, which may be easier for maintenance of express-validator. The function would return true if the field optional, false if not.

{
    "location": {
        "in": ["body"],
        "optional": { "options": { "nullable": true, "custom": (value, { req }) => !!req.body.staff }},
        "errorMessage": "Location ID invalid",
        "isInt": { "options": {"min": 0} },
    },
    "staff": {
        "in": ["body"],
        "optional": { "options": { "nullable": true, "custom": (value, { req }) => !!req.body.location }},
        "errorMessage": "Staff ID invalid",
        "isInt": { "options": {"min": 0} },
    }
}
@tyzoid
Copy link
Author

tyzoid commented Sep 30, 2021

As an aside, this is our current workaround:

const rcMiddleware = [
    express.json(),
    body('location').custom((value, { req }) => {
        if (!value && !req.body.staff) {
            throw new Error('At least one of location or staff must be provided');
        }

        return true;
    }),
    v.validate(rcschema),
];

router.post("/reservation", rcMiddleware, function(req, res) {
    schedule.createReservation(req.auth, req.body).then(d => res.send(d));
});

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

No branches or pull requests

1 participant