ajv-validator/ajv-merge-patch

Getting error "should pass \"$merge\" keyword validation"

Closed this issue · 10 comments

While trying to compile and validate some object against an schema using the $merge keyword, I'm always getting this as an error result:

{
 "errors": [
    {
      "dataPath": "", // <-- Some proper validation error
      "schemaPath":"#/required",
      "params": {
        "missingProperty": "bar"
      },
      "message": "should have required property 'bar'"
    },
    {
     "dataPath": "",  // <--- This always appears
     "schemaPath":"#/$merge",
      "params": {
        "keyword": "$merge"
      },
      "message": "should pass \"$merge\" keyword validation"
    }
  ]
}

The simplest use case can reproduce this:

const Ajv = require('ajv');

let ajv = new Ajv({ v5: true });
require('ajv-merge-patch/keywords/merge')(ajv);

let validate = ajv.compile({
  $merge: {
    source: {
      required: ['bar'],
      properties: {
        bar: {
          type: 'string'
        }
      },
      additionalProperties: true
    },
    'with': {
      properties: {
        foo: {
          type: 'string'
        }
      }
    }
  }
});

// Validate any object
validate({ foo: 42 });
console.log(JSON.stringify(validate.errors));

What am I doing wrong here?

Using ajv@^4.10.4 and ajv-merge-patch@^2.1.0.

For the record, if I just deepmerge both schemas and pass the result into ajv.compile it works as expected.

That's what I ended up doing as a workaround.

@nfantone if you mean that the error from $merge keyword is unexpected then this is simply how Ajv works with custom macro keywords (which is what $merge is), because otherwise the absence of such error is very confusing in most cases (you don't see which keyword failed in YOUR schema, you only see the keywords that failed in the generated schema). In case of $merge it may be easier, but in general case it is not. Or do you mean something else?

And if you just deepmerge then there is no $merge keyword in the schema you pass to Ajv.

@epoberezkin Apologies: there must be something I'm not following here.

My common sense led me to believe $merging two schemas would produce the same final output or behave in the same manner as having one complete/full schema, so it is transparent for the client/user. But, apparently, is not?

if you mean that the error from $merge keyword is unexpected then this is simply how Ajv works with custom macro keywords

That sounds like an implementation detail explanation. The fact that the feature was implemented using some useful inner mechanism of the framework should not dictate its final result. What I mean is: why is there a new error being pushed to the errors array if there is none? I'm using the validate.errors array to report back to the client what was wrong with what he sent. Yesterday, I change my JSON schema to include the $merge keyword and all my error responses started including a new, confusing one.

the absence of such error is very confusing in most cases

Really sorry again, but I'm not following you here. How is always including an error stating "should pass \"$merge\" keyword validation" not confusing? It really seems as if it's not giving out any real or useful information. I don't even know what the merge$ keyword validation is. And neither should the client.

On the other hand, if I add my own custom keyword with a validation function, then the error would make perfect sense.

And if you just deepmerge then there is no $merge keyword in the schema you pass to Ajv.

Sure. I only mentioned that because I thought I could produce the same result using both approaches: merge$ and deepmerge.

As I said, I may be off here since I'm not familiar with the inner working of Ajv (or JSON schema, in general). Please, correct me if I seem wrong somewhere.

so it is transparent for the client/user.

The errors that Ajv returns are designed for schema authors/app developers, rather than for the end users. For the schema author it is easier to understand where the error happened with this additional error. The main use case of Ajv is server-side validation where a validation error indicates some application error that should be reported to support/developers.

You can obviously use validation for the purpose of providing some feedback to the end users when they enter some erroneous data in the form. If that is your use case then you have to do post-processing of errors in your app, it's not in the scope of Ajv or this package. $merge keyword is not an isolated case where the error message doesn't make any sense to the end users, and it's quite common among other validators as well.

There is a demand, some plans and ideas (ajv-validator/ajv#100) for the generalised processing of Ajv errors to make them suitable for the end users. But it's out of scope for Ajv, it will be a standalone package. For now you just have to write this code in your app, which is much simpler than generalised processing (in your case, just filter out errors for $merge keyword).

My common sense led me to believe $merging two schemas would produce the same final output or behave in the same manner as having one complete/full schema

Different schemas will indeed have different errors generated even if they are equivalent. $merge is not some pre-processing feature, it is a schema keyword, so it has its own error.

@epoberezkin So, what you saying is that, in a general sense, two merged schemas are not equivalent to its "complete" counterpart. Because, as I said, apart from common sense and your last comment, this part of the README.md led me to believe otherwise. Or maybe we are using a different interpretation of "equivalent". Their validation processes (which are the sole purpose of using a schema in the first place) in both cases do not behave the same, therefore they are not equivalent.

Anyway, thank you very much for your patience and time on answering my concerns. Really appreciate it!

You're welcome. By "equivalent schemas" I mean "schemas with the same validation result in all cases", i.e. pass or fail validation. Errors are not part of JSON-schema spec, they are determined by the validation process (and different equivalent schemas define different process), so they can be different for "equivalent schemas".

The above meaning of "equivalent" can probably be added to readme to make it clearer.

The above meaning of "equivalent" can probably be added to readme to make it clearer.

I'll try to send you a PR soon.

Cheers.

@epoberezkin why don't you add the option keepAllMsgs in ajv.addKeyword({ macro: (s) => s, keepAllMsgs: true, }). If false, behave like it does now, if true behave like @nfantone suggested (like preprocessing the schema).
I know you said:

Different schemas will indeed have different errors generated even if they are equivalent. $merge is not some pre-processing feature, it is a schema keyword, so it has its own error.

but I think it would be very useful in the usecases where $merge and $patch are used and actually when macro keywords are defined in general.

@suricactus what you essentially want is to remove error message for a particular keyword.

It can be done with one line of code:

const errors = validate.errors.filter(e => e.keyword != '$merge');

I don't think there should be an option for something that can be easily achieved outside.

If anywhere, this feature should in ajv-errors package, e.g. null can be used as an error message to silently remove the error.