1

I'm trying to validate a classic JSON schema (with Ajv and json-server) with required fields, but for a HTTP PATCH request.

It's okay for a POST because the data arrive in full and nothing should be ommited.

However, the required fields of the schema make problem when attempting to PATCH an existing resource.

Here's what I'm currently doing for a POST :

const schema = require('./movieSchema.json');
const validate = new Ajv().compile(schema);

// ...

movieValidation.post('/:id', function (req, res, next) {
    const valid = validate(req.body);
    if (!valid) {
        const [err] = validate.errors;

        let field = (err.keyword === 'required') ? err.params.missingProperty : err.dataPath;

        return res.status(400).json({
            errorMessage: `Erreur de type '${err.keyword}' sur le champs '${field}' : '${err.message}'`
        });
    }
    next();
});

... but if i'm doing the same for a movieValidation.patch(...) and tries to send only this chunk of data :

{
    "release_date": "2020-07-15",
    "categories": [
        "Action",
        "Aventure",
        "Science-Fiction",
        "Thriller"
    ]
}

... it will fail the whole validation (whereas all the fields are okay and they validate the schema)

Here's my complete moviesSchema.json :

{
    "type": "object",
    "$schema": "http://json-schema.org/draft-07/schema#",
    "properties": {
        "title": {
            "title": "Titre",
            "type": "string",
            "description": "Titre complet du film"
        },
        "release_date": {
            "title": "Date de sortie",
            "description": "Date de sortie du film au cinéma",
            "type": "string",
            "format": "date",
            "example": "2019-06-28"
        },
        "categories": {
            "title": "Catégories",
            "description": "Catégories du film",
            "type": "array",
            "items": {
                "type": "string"
            }
        },
        "description": {
            "title": "Résumé",
            "description": "Résumé du film",
            "type": "string"
        },
        "poster": {
            "title": "Affiche",
            "description": "Affiche officielle du film",
            "type": "string",
            "pattern": "^https?:\/\/"
        },
        "backdrop": {
            "title": "Fond",
            "description": "Image de fond",
            "type": "string",
            "pattern": "^https?:\/\/"
        }
    },
    "required": [
        "title",
        "release_date",
        "categories",
        "description"
    ],
    "additionalProperties": false
}

For now, I did the trick using a different schema which is the same as the original one, but without the required clause at the end. But I don't like this solution as it's duplicating code unnecessarily (and it's not elegant at all).

Is there any clever solution/tool to achieve this properly? Thanks

Jason Desrosiers
  • 22,479
  • 5
  • 47
  • 53
jmpp
  • 346
  • 1
  • 4
  • 22

2 Answers2

4

If you're using HTTP PATCH correctly, there's another way to deal with this problem.

The body of a PATCH request is supposed be a diff media type of some kind, not plain JSON. The diff media type defines a set of operations (add, remove, replace) to perform to transform the JSON. The diff is then applied to the original resource resulting in the new state of the resource. A couple of JSON diff media types are JSON Patch (more powerful) and JSON Merge Patch (more natural).

If you validate the request body for a PATCH, you aren't really validating your resource, you are validating the diff format. However, if you apply the patch to your resource first, then you can validate the result with the full schema (then persist the changes or 400 depending on the result).

Remember, in REST it's resources and representations that matter, not requests and responses.

Community
  • 1
  • 1
Jason Desrosiers
  • 22,479
  • 5
  • 47
  • 53
  • Okay, I think I get it. So, what I've tried to do above is a kind of "JSON Merge Patch", but my error was trying to validate the diff and not the entire JSON after the merge. I could eventually use the `json-merge-patch` package to achieve this I guess. I'll give it a try. Thanks for sharing the RFCs, that was really instructive. – jmpp Aug 14 '20 at 07:09
1

It's not uncommon to have multiple schemas, one per payload you want to validate.

In your situation, it looks like you've done exactly the right thing.

You can de-duplicate your schemas using references ($ref), splitting your property subschemas into a separate file.

You end up with a schema which contains your model, and a schema for each representation of said model, but without duplication (where possible).

If you need more guidance on how exactly you go about this, please comment and I'll update the answer with more details.


Here is an example of how you could do what you want. You will need to create multiple schema files, and reference the right schema when you need to validate POST or PATCH requests. I've simplified the examples to only include "title".

In one schema, you have something like...

{
  "$id": "https://example.com/object/movie",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "movie": {
      "properties": {
        "title": {
          "$ref": "#/definitions/title"
        }
      },
      "additionalProperties": false
    },
    "title": {
      "title": "Titre",
      "type": "string",
      "description": "Titre complet du film"
    }
  }
}

Then you would have one for POST and PATCH...

{
  "$id": "https://example.com/movie/patch",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "allOf": [{
    "$ref": "/object/movie#/definitions/movie"
  }],
}
{
  "$id": "https://example.com/movie/post",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "allOf": [{
    "$ref": "/object/movie#/definitions/movie"
  }],
  "required": ["title"]
}

Change example.com to whatever domain you want to use for your ID. You will then need to load in to the implementation all of your schemas.

Once loaded in, the references will work, as they are based on URI resolution, using $id for each schema resource.

Notice the $ref values in the POST and PATCH schemas do not start with a #, meaning the target is not THIS schema resource.

Relequestual
  • 11,631
  • 6
  • 47
  • 83
  • I discovered the JSON schemas recently and I'm not yet aware of all that can be done with it. How would you do a subschema of `moviesSchema.json` using `$ref`? Exemple is welcome Thank you – jmpp Aug 14 '20 at 07:14
  • 1
    @jmpp, have a look at this link: https://json-schema.org/understanding-json-schema/structuring.html#using-id-with-ref – aeberhart Aug 14 '20 at 11:16
  • Links are great but not useful if the link goes away. – Relequestual Aug 14 '20 at 11:18
  • Also, the example in the link doesn't show cross file references – Relequestual Aug 14 '20 at 11:37