Giter Site home page Giter Site logo

ajv-validator / ajv-merge-patch Goto Github PK

View Code? Open in Web Editor NEW
46.0 46.0 18.0 57 KB

$merge and $patch keywords for Ajv JSON-Schema validator to extend schemas

Home Page: https://ajv.js.org

License: MIT License

JavaScript 100.00%
ajv json-merge-patch json-patch json-schema keywords

ajv-merge-patch's People

Contributors

donaldjarmstrong avatar epoberezkin avatar filipesilva avatar fregante avatar rpl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

ajv-merge-patch's Issues

$patch remove keyword from required

I saw in the tests how to add an attribute to the required keyword { "op": "add", "path": "/required/-", "value": "q" }
but I want to ask how can i remove a keyword from being required I tried
{ "op": "remove", "path": "/required/-q"} but it did not work i seem to get it wrong so if anyone can help I would be grateful.
Thank you

npm run test fails (async.spec.js) after clean clone and npm install

To reproduce:

git clone [email protected]:ajv-validator/ajv-merge-patch.git
cd ajv-merge-patch
npm i
npm test

Expected: all tests pass
Actual:

  1) async schema loading
       $merge
         should load missing schemas:
     TypeError: resolver.resolve is not a function
      at resolveUrl (node_modules/ajv/dist/compile/resolve.js:90:21)
...
  2) async schema loading
       $patch
         should load missing schemas:
     TypeError: resolver.resolve is not a function
      at resolveUrl (node_modules/ajv/dist/compile/resolve.js:90:21)

This is due to a change introduced in ajv v8.10.0 which is pulled by the devDependency "ajv": "^8.2.0". Specifically,

ajv-validator/ajv@0e47ab4

This very short PR fixes this issue in the same technique as that commit in ajv.

Support Ajv v7

Or we not suppose to use merge / patch for JSON schemas?

Merge schemas with required keyword

Hi,
If I try to merge schemas with "required" keyword, as result incorrect merge of "required" values.

Test schema

{
  "$merge": {
    "source": {
      "required": ["foo", "bar"],
      "properties": {
        "foo": { "type": "string" },
        "bar": { "type": "string" }
      }
    },
    "with": {
      "required": ["baz"],
      "properties": {
        "baz": { "type": "number" }
      }
    }
  }
}

Schema after compile

{
    "$merge": {
        "source": {
            "required": [
                "baz"
            ],
            "properties": {
                "foo": {
                    "type": "string"
                },
                "bar": {
                    "type": "string"
                },
                "baz": {
                    "type": "number"
                }
            }
        },
        "with": {
            "required": [
                "baz"
            ],
            "properties": {
                "baz": {
                    "type": "number"
                }
            }
        }
    }
}

Test validation

const validate = ajv.compile(schema)

validate({"foo": "asd", "bar": "asd", "baz": 123})
true
validate({"foo": "asd", "baz": 123})
true
validate({"baz": 123})
true
validate({"foo": "asd"})
false

Default values over merged schemas

Hello,
I'm trying to build a mechanism to versioning default values for my app over ajv backed separated JSON files.

TL; DR:

I couldnt generate default values from a merged schema. Please elaborate on what i'm missing or doing wrong.

Details:

You can find the sandbox here

My concerns are:

  • Use default values and validate the data only related props according to the version
  • Use v0 as a base JSON and struct upcoming versions via $merge/$patch keywords (Dont repeat unchanged props and definitions)
  • Use a separeted common definitions JSON file to feed the base or merged/patched schemas.

A simple example goes like this:

/schemas/v0/App.json:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://some.domain.com/schemas/v0/App.json#",
  "title": "App Schema v0",
  "description": "App Properties",
  "type": "object",
  "additionalProperties": false,
  "definitions": {
    "yesNo": {
      "$ref": "https://some.domain.com/schemas/commonDefinitions.json#/definitions/yesNo"
    },
    "description": {
      "$ref": "https://some.domain.com/schemas/commonDefinitions.json#/definitions/description"
    },
    "color": {
      "$ref": "https://some.domain.com/schemas/commonDefinitions.json#/definitions/color"
    },
    "URL": {
      "$ref": "https://some.domain.com/schemas/commonDefinitions.json#/definitions/color"
    }
  },
  "properties": {
    "appVersion": {
      "type": "string",
      "const": "0",
      "default": "0"
    },
    "appBgColor": {
      "$ref": "#/definitions/color",
      "description": "App bg color",
      "default": "#ffffff"
    },
    "name": {
      "type": "string",
      "default": "New App"
    },
    "description": {
      "$ref": "#/definitions/description",
      "default": "my app"
    },
    "logoURL": {
      "$ref": "#/definitions/URL",
      "description": "Logo asset URL",
      "default": "https://source.unsplash.com/random/50x50"
    }
  },
  "required": ["appVersion"]
}

/schemas/v1/App.json:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://some.domain.com/schemas/v1/App.json#",
  "$merge": {
    "source": {
      "$ref": "https://some.domain.com/schemas/v0/App.json#"
    },
    "with": {
      "properties": {
        "appVersion": {
          "const": "1",
          "default": "1"
        },
        "showItemIcons": {
          "$ref": "#/definitions/yesNo",
          "default": "Nope"
        }
      }
    }
  }
}

/schemas/commonDefinitions.json:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://some.domain.com/schemas/commonDefinitions.json#",
  "title": "App Schema Common Definitions",
  "description": "Common Definitions",
  "type": "object",
  "additionalProperties": false,
  "definitions": {
    "yesNo": {
      "type": "string",
      "enum": ["Yes", "No"]
    },
    "alignment": {
      "type": "string",
      "enum": ["left", "center", "right"]
    },
    "description": {
      "type": "string",
      "maxLength": 5
    },
    "color": {
      "type": "string"
    },
    "URL": {
      "type": "string"
    },
    "empty": {
      "type": "string",
      "maxLength": 0
    }
  }
}

I couldn't make ajv to produce correct data with defaults on v1/App.json:

{}

However it's ok for v0/App.json:

{"appVersion":"0","appBgColor":"#ffffff","name":"New App","description":"my app","logoURL":"https://source.unsplash.com/random/50x50"}

Additional problems:

  • It seems validation ignore default values. See v0/App.json#properties.description shouldn't exceed 5 length value. But validator tells it's valid for 'my app'.
  • I couldn't make codesandbox work by referencing relatively within schemas. Is it ok to access other schemas as:
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://www.jotform.com/portal/schemas/v1/App.json#",
  "$merge": {
    "source": {
      "$ref": "../v0/App.json"
    },
    "with": {
      ......
    }
}
  • And finally please see that i need 'yesNo' definition starting from v1. However it seems it needs to be defined in also v0. I got MissingRefErrors if i dont include it to v0. What is the correct way to extend definitions and required?

DefaultValue not working

const Ajv = require('ajv');
const ajv = new Ajv({
  allErrors: true,
  format: 'full',
  useDefaults: true,
  coerceTypes: false,
  errorDataPath: 'property',
  sourceCode: false,
});
require('ajv-merge-patch')(ajv);
const base = {
  type: 'object',
  properties: {
    operation: {
      type: 'string',
      enum: ['create', 'update', 'delete'],
    },
    id: {
      type: 'string',
    },
  },
};

const schema = {
  $merge: {
    source: base,
    with: {
      type: 'object',
      properties: {
        test: {
          type: 'integer',
          default: 0,
        },
      },
    }
  }
};

const params = {
  operation: 'delete'
};
const validate = ajv.compile(schema);
const isValidParams = validate(params);
console.log(params);

Actual :
{ operation: 'delete' }
Expected :
{ operation: 'delete' , test: 0}

Error: can't resolve reference http://json-schema.org/draft-06/schema# from id #

I'm trying to use the library in the browser compiled by babel and webpack. When I run this code:

import Ajv from 'ajv';
import ajvMergePatch from 'ajv-merge-patch';

const ajvInstance = new Ajv({});
ajvMergePatch(ajvInstance);

I get the following error:

bootstrap 436b72c1f8f84619e6c0:802 Error: can't resolve reference http://json-schema.org/draft-06/schema# from id #
    at Object.generate_ref [as code] (http://localhost/1.js:8952:22)
    at Object.generate_validate [as validate] (http://localhost/1.js:9645:37)
    at Object.generate_anyOf [as code] (http://localhost/1.js:7185:27)
    at Object.generate_validate [as validate] (http://localhost/1.js:9718:37)
    at Object.generate_properties [as code] (http://localhost/1.js:8636:26)
    at generate_validate (http://localhost/1.js:9718:37)
    at localCompile (http://localhost/1.js:5573:22)
    at Ajv.compile (http://localhost/1.js:5541:13)
    at _compile (http://localhost/1.js:4840:29)
    at Ajv.compile (http://localhost/1.js:4626:34)
    at Ajv.addKeyword (http://localhost/1.js:9930:40)
    at webpackJsonp../lib/node_modules/ajv-merge-patch/keywords/add_keyword.js.module.exports (http://localhost/1.js:2230:7)
    at webpackJsonp../lib/node_modules/ajv-merge-patch/keywords/merge.js.module.exports (http://localhost/1.js:2288:3)
    at addKeywords (http://localhost/1.js:2214:3)

Implement allRequired

additionalProperties is implemented, but allRequired isn't. This would be helpful, as when the source spec for the object has allRequired, the merged one allows missing keys.

Crashes on AJV initialized with `meta: false`

Following will crash with not very descriptive error Error: can't resolve reference http://json-schema.org/draft-07/schema# from id #

const AJV = require('ajv');
const ajv = new AJV({ meta: false });
require('ajv-merge-patch')(ajv);

If it works as intended it'll be good to document that AJV cannot be initialized with meta: false, if we want to take advantage of this plugin

Merge/patch additionalProperties

Hi,

Trying to use ajv-merge-patch in a scenario where my definitions are split up over multiple JSON files - lets say I have the following 4 files;

/system.json (all objects have these properties - however not always all of them required)

{
	"id": "/system.json",
	"type": "object",
	"properties": {
		"id": {
			"type": "number"
		}
	}
}

/object/user.json (a specific object type - includes all from system.json)

{
	"id": "/object/user.json",
	"$merge": {
		"source": {
			"type": "object",
			"properties": {
				"firstname": {
					"type": ["string", "null"]
				}
			}
		},
		"with": {
			"$ref": "/system.json#"
		}
	}
}

/general/crud/create/request.json (template for incoming record create requests)

{
	"id": "/general/crud/create/request.json",
	"type": "object",
	"properties": {
		"userId": {
			"type": "number"
		},
		"values": {
			"type": "object"
		}
	},
	"required": ["userId", "values"],
	"additionalProperties": false
}

user/action/create/request.json (specific user create template - extends /general/crud/create/request.json above - making the values node specific with the user model above - adds one additional property)

{
	"id": "/user/action/create/request.json",
	"type": "object",
	"$patch": {
		"source": {
			"$ref": "/general/crud/create/request.json#"
		},
		"with": [{
			"op": "add",
			"path": "/properties/values",
			"value": {
				"$merge": {
					"source": {
						"$ref": "/object/user.json#"
					},
					"with": {
						"properties": {
							"unencrypted_password": {
								"type": "string"
							}
						},
						"required": ["unencrypted_password"]
					}
				}
			}
		}]
	}
}

Now what I would like to do is put an "additionalProperties: false" on the user model as other use cases besides create should not specify any additional properties - however if I do this currently my "unencrypted_password" will be rejected - injecting additionalProperties: true anywhere in the last schema does not seem to have any effect.

Am I just trying to do something stupid here, or is it just not possible to override additionalProperties when using patch/merge in its current state? Is this something on the roadmap to be done or something which would not even be considered if I wrote a PR for it?

Update for ajv v6

Gives out an error:

error: uncaughtException: can't resolve reference http://json-schema.org/draft-06/schema#

Using the library in TypeScript v2.5

the following code produce the error:

import * as Ajv from 'ajv';
import * as AjvAsync from 'ajv-async';

import * as AjvKeywords from 'ajv-keywords';
import * as AjvMergePatch from 'ajv-merge-patch';

export const ajv = AjvAsync(new Ajv({
  coerceTypes: 'array',
  jsonPointers: true,
  allErrors: true,
  $data: true,
  removeAdditional: true,
}));

AjvMergePatch(ajv);
AjvKeywords(ajv);

Update fast-json-patch deps

Please, update fast-json-patch to the v3.

Current used version v2 contains vulnerability.

npm audit
# npm audit report

fast-json-patch  <3.1.1
Severity: high
Starcounter-Jack JSON-Patch Prototype Pollution vulnerability - https://github.com/advisories/GHSA-8gh8-hqwg-xf34
No fix available
node_modules/fast-json-patch
  ajv-merge-patch  *
  Depends on vulnerable versions of fast-json-patch
  node_modules/ajv-merge-patch

2 high severity vulnerabilities

Some issues need review, and may require choosing
a different dependency.

Merge/Patch for schema with inner ref question

Hi,

I've started using JSON schema in a project and it's all working well until I've tried using merge patch. There doesn't seem to be too many examples out there regarding merge/patch for cases where the schema being modified has references inside it so I was hoping I could get some guidance. I have a test schema:

const Ajv = require("ajv")
const ajv = new Ajv({allErrors: true, v5: true})
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
require('ajv-merge-patch')(ajv);

console.log("Start with base schema ================================")

const a = {
  "$id": "a.json#",
  type: "object",
  properties: {
    size: {type: "number"}
  },
  required: ["size"],
  additionalProperties: false
}
ajv.addSchema(a, "a.json")

A simple merge works okay

console.log("Simple Merge Test =====================================")
const b = {
  "$id": "b.json#",
  $merge: {
    source: { "$ref": "a.json#" },
    with: {
      properties: {
        length: { type: "number" }
      },
      required: ["length"],
      additionalProperties: false
    }
  }
}
const bv = ajv.compile(b);
console.log(bv({ length: 3, size: 4}))

however, if I create a schema with a reference to the first one inside and try to extend the inner schema via the outer schema it fails:

const c = {
  "$id": "c.json#",
  type: "object",
  properties: {
    inner: { "$ref": "a.json#" }
  }
}
console.log("Nested Ref Merge test ==================================")
const d = {
  "$id": "d.json#",
  $merge: {
    source: { "$ref": "c.json#" },
    with: {
      properties: {
        inner: {
          properties: {
            length: { type: "number" }
          },
          required: ["length"]
        }
      }
    }
  }
}
const dv = ajv.compile(d)

I believe this is due to refs not being replaced in the schema. Firstly I was wondering if what I am doing is odd or not - the main reason I was doing it this way is because in my actual scenario I have multiple properties based off of the same schema but only want to merge additional properties into some of them.

If it's not odd then how would I get the above to work?

Many thanks

Feature Proposal: Support arrays of objects to be merged in the "with" property

Since the $merge operation does not concatenate or intersect arrays, but rather overwrites them wholesale, it is basically nonsensical for the top-level node under the with property of the $merge schema to be an array. For example, this would not produce a valid schema post merge:

//Valid baseSchema
{
  "$id": "baseSchema",
  "type": "object",
  "properties": {
    "foo": {
      "type": "number"
    }
  }
}

//Invalid merged schema
{
  "$id": "schemaExtended",
  "$merge": {
    "source": { "$ref": "baseSchema.json#" },
    "with": ["foo", "bar"]
    }
  }
}

Because this is the case, would it be possible to have the with property support an array, which would then be interpreted as a series of consecutive merges?

{
  "$id": "baseSchema",
  "type": "object",
  "properties": {
    "foo": {
      "type": "number"
    }
  }
}

//Cumbersome "nested" syntax for handling multiple merges, which is basically impossible to parse
{
  "$id": "schemaExtended",
  "$merge": {
    "source": {
      "$merge": {
        "source": { "$ref": "baseSchema.json#" },
        "with": {
          "properties": {
            "foo": {
              "minimum": 0
            },
            "bar": {
              "type": "string"
            }
          }
        }
      }
    }
    "with": {
      "properties": {
        "baz": {
          "type": "boolean"
        }
      },
      "additionalProperties": false
    }
  }
}

//Much cleaner syntax which mirrors the way _.extend and Object.assign work
{
  "$id": "schemaExtended",
  "$merge": {
    "source": { "$ref": "baseSchema.json#" },
    "with": [{
      "properties": {
        "foo": {
          "minimum": 0
        },
        "bar": {
          "type": "string"
        }
      }
    }, {
      "properties": {
        "baz": {
          "type": "boolean"
        }
      },
      "additionalProperties": false
    }]
  }
}

Is there interest in implementing this? I doubt it would ever break existing functionality, since top-level arrays under with already always produce invalid schemas, and I think it would be pretty easy to implement. If this feels like something worth doing, I can submit a quick PR.

Nested merge and patch

Hi,

I'm not sure if this is an issue with my understand of the spec or not. I understand merge a lot better than patch and so I'd like to use it where possible and then patch things I can't fix with merge. However, I'm having difficulty applying a patch to a schema who's source I apply a merge:

deliver.json

{
  "type": "object",
  "definitions": {
      "settings": {
          "type": "object",
          "properties": {
              "country": { 
                "type": "array", 
                "items": { "type": "string" } 
              },
              "client": { 
                "type": "array", 
                "items": { 
                  "type": "string", 
                  "enum": ["mobile", "desktop"] 
                }
              }
          },
          "additionalProperties": false
      }
  },
  "properties": {
    "include": { "$ref": "#/definitions/settings" },
    "exclude": { "$ref": "#/definitions/settings" },
    "templateParameters": { 
      "type": "object", 
      "required": [], 
      "patternProperties": { 
        ".*": { "type": "string" } 
      } 
    },
    "expire": { "type": "string", "format": "date" }
  },
  "required": [],
  "additionalProperties": false
}

and my new schema:

x.json

{
  "$patch": {
    "source" : {
      "$merge": {
        "source": { "$ref": "deliver.json" },
        "with": {
          "properties": {
            "templateParameters": {
              "properties": {
                "link": { "type": "string" }
              },
              "required": ["link"]
            }
          },
          "required": ["templateParameters"]
        }
      }
    },
    "with": [{
      "op": "replace",
      "path": "/properties/include/properties/client/items",
      "value": { "type": "string", "enum": [ "alpha", "bravo" ] }
    }]
  }
}

I get the error message:

{ [OPERATION_PATH_UNRESOLVABLE: Cannot perform the operation at a path that does not exist]
  message: 'Cannot perform the operation at a path that does not exist',
  name: 'OPERATION_PATH_UNRESOLVABLE',
  index: 0,
  operation:
   { op: 'replace',
     path: '/properties/include/properties/client/items',
     value: { type: 'string', enum: [Array] } },
  tree: { '$merge': { source: [Object], with: [Object] } } }

Is this expected? I'm not sure if this kind of thing is allowed under the specification but if it were then I think the order the transformations are applied would need to be altered.

Thanks

Merge schemas with required properties

Given schema1

{
  type: 'object',
  properties: {
     properties: {
        foo: { type: "string" }
      },
  },
  required: ["foo"]
}

And given schema2

{
  $merge: {
    source: schema1,
    with: {
      type: 'object',
      properties: {
        bar: { type: "string" }
      },
      required: ["baz"]
    }
  }
}

I expect foo and bar to be required in schema2.
Instead, only bar is required.

Is this a bug or a feature?

Vulnerability in ajv-merge-patch 4.1.0 version via json-merge-patch - 0.2.3 version

HI! I would very thankful if you upgrade json-merge-patch from 0.2.3 to 1.0.2 in ajv-merge-patch 4.1.0 version.

json-merge-patch has vulnerability and i haven’t ability upgrade ajv-merge-patch to 5.0.1 version.

Explanation
The json-merge-patch package is vulnerable to Prototype Pollution. The apply function in the apply.js file allows the addition or modification of object prototype properties via accessors such as proto or constructor.prototype. A remote attacker with the ability to apply patches or influence their content can exploit this vulnerability to modify the behavior of object prototypes which, depending on their use in the application, may result in a Denial of Service (DoS), Remote Code Execution (RCE), or other unexpected behavior.

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

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.

$ref errors with ajv 6

Updating ajv-merge-patch devDependency to [email protected] produces the following test failures:

  1) async schema loading "before each" hook for "should load missing schemas":
     can't resolve reference http://json-schema.org/draft-06/schema# from id #


  2) errors missing $ref "before each" hook for "should throw exception if cannot resolve $ref":
     can't resolve reference http://json-schema.org/draft-06/schema# from id #


  3) keyword $merge "before each" hook for "should extend schema defined in $merge":
     can't resolve reference http://json-schema.org/draft-06/schema# from id #


  4) keyword $patch "before each" hook for "should extend schema defined in $patch":
     can't resolve reference http://json-schema.org/draft-06/schema# from id #

I ran into these errors trying to use relative $ref as the source in my schema.

Using $merge with Fastify and shared schemas

I'm trying to put this together with fastify. I added ajv-merge-path as described in the docs into the validation (https://github.com/fastify/fastify/blob/master/lib/validation.js#L157)

example case:

schemas/defs.json

{
  "$id": "/defs.json",
  "type": "object",
  "definitions": {
    "test": {
      "type": "object",
      "properties": {
        "name": { "type": "string" }
      }
    }
  }
}

schemas/foo.json

{
  "$id": "/foo.json",
  "type": "object",
  "definitions": {
    "bar": {
      "type": "object",
      "$merge": {
        "source": {
          "$ref": "/defs.json#/definitions/test"
        },
        "with": {
          "properties": {
            "id": { "type": "string" }
          }
        }
      }
    }
  }
}
fastify.get('/defs.json', (request, reply) => {
  reply.send(require('./schemas/defs'))
})
fastify.get('/foo.json', (request, reply) => {
  reply.send(require('./schemas/foo'))
})
fastify.post(
  '/',
  {
    schema: {
      body: { $ref: '/foo.json#/definitions/bar' }
    }
  },
  function(request, reply) {
    reply.send(fastify.getSchemas())
  }
)
  • I'm not sure why I need to add the requests for the json files, but Swagger seems to need them
  • The body in Swagger is empty. If I add properties directly to foo, everything works as expected, so the $merge doesn't work.

Any ideas how I can make this work?

Error: can't resolve reference http://json-schema.org/draft-07/schema#

It seems as if there is an out-of-the-box compatibility issue with Ajv2020 instances of ajv.

When trying to apply the ajv-merge-patch keywords, I get...

Error: can't resolve reference http://json-schema.org/draft-07/schema# from id # at Object.code (/Users/[redacted]/node_modules/ajv/dist/vocabularies/core/ref.js:21:19) at ...

...which looks like is related to code in /keywords/add_keyword.js of this repo where this metaschema is referenced.

Code to reproduce...

import Ajv2020 from 'ajv/dist/2020.js';
import ajvMergePatch from 'ajv-merge-patch';

const ajv = new Ajv2020();
ajvMergePatch(ajv);

I was able to workaround with the following...

import { readFileSync } from 'fs';

import Ajv2020 from 'ajv/dist/2020.js';
import ajvMergePatch from 'ajv-merge-patch';

const ajv = new Ajv2020();

// required by ajv-merge-patch
const draft7MetaUrl = new URL('../node_modules/ajv/dist/refs/json-schema-draft-07.json', import.meta.url);
const draft7MetaSchema = JSON.parse(readFileSync(draft7MetaUrl, 'utf8'));
ajv.addMetaSchema(draft7MetaSchema);

ajvMergePatch(ajv);

...but figured I would open issue here to raise visibility.

I know that ajv itself indicates there is no cross-compatibility between draft-2020-12 and draft-07 schema within same ajv instance, however this does seems to work. At a minimum, I would find it unexpected that a developer using only draft-2020-12 (or draft-2019 for that matter) schemas within their application and wanting to leverage this extension would get such breakage, with reference to a schema they are not even using.

Cannot get it to work with subschemas

First of all: thank you for your work. I have a question that I can't figure out for the life of me. I wonder if you can figure out what I'm missing here?

I'm trying to patch a schema and then validate data against subschemas of the schema. It seems the patch is not applied to subschemas, but somehow applied to the root schema (without taking the source path defined in the patch into account).

// fhir.schema.test.json
{
	"$id": "http://hl7.org/fhir/json-schema/4.0#",
	"discriminator": {
		"propertyName": "resourceType",
		"mapping": {
			"Account": "#/definitions/Account",
			"Patient": "#/definitions/Patient"
		}
	},
	"oneOf": [
		{
			"$ref": "#/definitions/Account"
		},
		{
			"$ref": "#/definitions/Patient"
		}
	],
	"definitions": {
		"Account": {
			"properties": {
				"resourceType": {
					"const": "Account"
				}
			},
			"additionalProperties": false,
			"required": [
				"resourceType"
			]
		},
		"Patient": {
			"properties": {
				"resourceType": {
					"const": "Patient"
				}
			},
			"additionalProperties": false,
			"required": [
				"resourceType"
			]
		}
	}
}
const schemas = [
	require('./fhir.schema.test.json'),
	{
		$patch: {
			source: { $ref: '#/definitions/Patient' },
			with: [
				{ op: 'add', path: '/properties/q', value: { type: 'number' } },
				{ op: 'add', path: '/required/-', value: 'q' }
			]
		}
	}
];

const everything = Object.assign(...schemas);
ajv.addSchema(everything, 'key');

const rootSchema = ajv.getSchema(`key`);
const getSubSchema = definition => ajv.getSchema(`key#/definitions/${definition}`);
// TESTS AND EXPECTATIONS

const tests = [
	{ resourceType: 'Account' }, // Expect true (no q necessary in 'Account')
	{ resourceType: 'Patient', q: 1 }, // Expect true
	{ resourceType: 'Patient', q: 'abc' }, // Expect false (q is wrong type)
	{ resourceType: 'Patient' }, // Expect false (missing q)
];

console.log('rootSchema', inspect(rootSchema.schema, { depth: null }));

tests.forEach(test => {
	const subSchema = getSubSchema(test.resourceType);
	console.log('test:', test);
	console.log('rootSchema isValid:', rootSchema(test));
	console.log('rootSchema errors:', rootSchema.errors);
	console.log('subSchema:', inspect(subSchema.schema, { depth: null }));
	console.log('subSchema isValid:', subSchema(test));
	console.log('subSchema errors:', subSchema.errors);
});

output:

rootSchema {
  '$id': 'http://hl7.org/fhir/json-schema/4.0#',
  discriminator: {
    propertyName: 'resourceType',
    mapping: {
      Account: '#/definitions/Account',
      Patient: '#/definitions/Patient'
    }
  },
  oneOf: [
    { '$ref': '#/definitions/Account' },
    { '$ref': '#/definitions/Patient' }
  ],
  definitions: {
    Account: {
      properties: { resourceType: { const: 'Account' } },
      additionalProperties: false,
      required: [ 'resourceType' ]
    },
    Patient: {
      properties: { resourceType: { const: 'Patient' } },
      additionalProperties: false,
      required: [ 'resourceType' ]
    }
  },
  '$patch': {
    source: { '$ref': '#/definitions/Patient' },
    with: [
      { op: 'add', path: '/properties/q', value: { type: 'number' } },
      { op: 'add', path: '/required/-', value: 'q' }
    ]
  }
}
test: { resourceType: 'Account' }
rootSchema isValid: false
rootSchema errors: [
  {
    keyword: 'const',
    dataPath: '.resourceType',
    schemaPath: '#/properties/resourceType/const',
    params: { allowedValue: 'Patient' },  # <-- WHY? IT'S AN 'Account'?
    message: 'should be equal to constant'
  },
  {
    keyword: 'required',
    dataPath: '',
    schemaPath: '#/required',
    params: { missingProperty: 'q' },
    message: "should have required property 'q'"  # <-- NO IT SHOULD NOT
  },
  {
    keyword: '$patch',
    dataPath: '',
    schemaPath: '#/$patch',
    params: { keyword: '$patch' },
    message: 'should pass "$patch" keyword validation'
  }
]
subSchema: {
  properties: { resourceType: { const: 'Account' } },
  additionalProperties: false,
  required: [ 'resourceType' ]
}
subSchema isValid: true
subSchema errors: null
test: { resourceType: 'Patient', q: 1 }
rootSchema isValid: false
rootSchema errors: [
  {
    keyword: 'required',
    dataPath: '',
    schemaPath: '#/required',
    params: { missingProperty: 'q' },
    message: "should have required property 'q'" # <-- IT HAS PROPERTY q!
  },
  {
    keyword: '$patch',
    dataPath: '',
    schemaPath: '#/$patch',
    params: { keyword: '$patch' },
    message: 'should pass "$patch" keyword validation'
  }
]
subSchema: {
  properties: { resourceType: { const: 'Patient' } },
  additionalProperties: false,
  required: [ 'resourceType' ]
}
subSchema isValid: true
subSchema errors: null
test: { resourceType: 'Patient', q: 'abc' }
rootSchema isValid: false
rootSchema errors: [
  {
    keyword: 'required',
    dataPath: '',
    schemaPath: '#/required',
    params: { missingProperty: 'q' },
    message: "should have required property 'q'" # <-- IT HAS PROPERTY q!
  },
  {
    keyword: '$patch',
    dataPath: '',
    schemaPath: '#/$patch',
    params: { keyword: '$patch' },
    message: 'should pass "$patch" keyword validation'
  }
]
subSchema: {
  properties: { resourceType: { const: 'Patient' } },
  additionalProperties: false,
  required: [ 'resourceType' ]
}
subSchema isValid: true # <-- WHY? q HAS THE WRONG TYPE
subSchema errors: null
test: { resourceType: 'Patient' }
rootSchema isValid: false
rootSchema errors: [
  {
    keyword: 'required',
    dataPath: '',
    schemaPath: '#/required',
    params: { missingProperty: 'q' },
    message: "should have required property 'q'"
  },
  {
    keyword: '$patch',
    dataPath: '',
    schemaPath: '#/$patch',
    params: { keyword: '$patch' },
    message: 'should pass "$patch" keyword validation'
  }
]
subSchema: {
  properties: { resourceType: { const: 'Patient' } },
  additionalProperties: false,
  required: [ 'resourceType' ]
}
subSchema isValid: true # <-- WHY? q IS MISSING!
subSchema errors: null

As you can see, testing against the rootSchema doens't differentiate between the various definitions, but testing against the subSchemas doesn't apply the patch.

I have tried various combinations of ajv.compile and ajv.schema, but nothing seems to give the results I'm expecting.

Is there any way to get the schema that results from applying $merge or $patch definitions?

For example, if I have the following two schemas:

{
  "$id": "baseSchema",
  "type": "object",
  "properties": {
    "foo": {
      "type": "number"
    }
  }
}

{
  "$id": "schemaExtended",
  "$merge": {
    "source": { "$ref": "schemaExtended.json#" },
    "with": {
      "properties": {
        "foo": {
          "minimum": 0
        },
        "bar": {
          "type": "string"
        }
      }
    }
  }
}

Is there anyway to output the result of the merge operation? Something like ajv.getMergedSchema(schemaExtended);, which would produce:

{
  "$id": "schemaExtended",
  "type": "object",
  "properties": {
    "foo": {
      "type": "number",
      "minimum": 0
    }
    "bar": {
      "type": "string"
    }
  }
}

The reason I ask is that most other json-schema implementations do not support merge-patch, so it would be nice if there was some method by which JSON schemas that use these keywords could be converted to regular JSON schemas.

Type inference support

Let's admit a simple base schema like this one:

const SnakeSchema = {
  type: 'object',
  properties: {
    name: { type: 'string' },
    foo_bar: { type: 'string' },
  },
} as const;

I want the type inference to work within my application, so I use the JTDDataType utility.

import { JTDDataType } from 'ajv/dist/jtd';

const SnakeSchema = { /* ... */ } as const;

type Snake = JTDDataType<typeof SnakeSchema>;

Now, let's write a simple patch:

const KebabSchema = {
  $patch: {
    source: SnakeSchema,
    with: [
      { op: 'move', from: '/properties/foo_bar', to: '/properties/fooBar' },
    ],
  },
} as const;

The only way I found to get the infered type is by doing this:

type Kebab = Omit<Snake, 'foo_bar'> & {
  fooBar: SnakeSchema['foo_bar'];

As it is not really DRY, I was wondering if there is something I miss in the docs... If not, maybe is there something that can be done in the project?

Plugin does not work with strict mode

Describe the bug
With ajv option set to strict mode the ajv-merge-patch package does not work. I receive the error:
strict mode: required property "value" is not defined at "#/properties/with/items/anyOf/0" (strictRequired)

To Reproduce
Steps to reproduce the behavior:

  1. Set ajv schema option { strict: true }
  2. Register the plugin
  3. Run fastify
  4. See error

Expected behavior
Package should work even with strict mode on

Do you have a proposed solution?
In this line a property is required which is not defined, after removing it locally everything worked as expected.

"required": [ "value" ]

Additional context and screenshots
Add any other context about the problem, and screenshots if applicable.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.