Best Match for Validation error with oneof or anyof - jsonschema

I am trying to get proper validation error from oneof or anyof pattern. I have json schema with two or more oneof/anyof condition as mentioned below:
json_schema = {
"type": "object",
"properties": {
"comment": {
"description": "Server Pool Policy Qualification Comments",
"type": "string",
"default": ""
},
"name": {
"description": "Server Pool Policy Qualification Name",
"type": "string",
"default": "",
"pattern": "^[\\-\\.:_a-zA-Z0-9]{1,16}$"
},
"qualifications": {
"description": "Qualifications of Server Pool Policy Qualification",
"type": "array",
"items": {
"description": "Qualification of Server Pool Policy Qualification",
"type": "object",
"oneOf": [
{
"properties": {
"type": {
"description": "Qualification Type",
"type": "string",
"enum": [
"adapter"
]
},
"adapter_qualification":{
"description": "Adapter Qualifications - Adapter Type",
"type": "array",
"properties": {
"adapter_type": {
"description": "Adapter Qualifications - Adapter Type",
"type": "string",
"enum": [
"virtualized-scsi-if"
]
},
"adapter_pid": {
"description": "Adapter Qualifications - Adapter PID (RegEx)",
"type": "string",
"default": "",
"pattern": "[ !#$%\\(\\)\\*\\+,\\-\\./:;\\?#\\[\\\\\\]\\^_\\{\\|\\}~a-zA-Z0-9]{0,256}"
},
"adapter_maximum_capacity": {
"description": "Adapter Qualifications - Maximum Capacity",
"type": "string",
"default": "unspecified",
"pattern": "^unspecified$|^[0-9]$|^[0-9][0-9]$|^[0-9][0-9][0-9]$|^[0-9][0-9][0-9][0-9]$|^[0-5][0-9][0-9][0-9][0-9]$|^6[0-4][0-9][0-9][0-9]$|^65[0-4][0-9][0-9]$|^655[0-2][0-9]$|^6553[0-5]$"
}
},
"additionalProperties": False,
"required": [
"type",
"adapter_type"
]
}
}
},
{
"properties": {
"type": {
"description": "Qualification Type",
"type": "string",
"enum": [
"server_pid"
]
},
"server_pid": {
"description": "Server PID Qualifications - Server PID",
"type": "string",
"default": "",
"pattern": "^123$"
}
},
"additionalProperties": False,
"required": [
"type",
"server_pid"
]
}
]
}
}
},
"additionalProperties": False,
"required": [
"name"
]
}
I have data which has additional element first_rack_id but best matches 2nd element from oneof.
data = {
"descr": "description",
"name": "domainGroup",
"qualifications": [
{
"server_pid": "B200M5",
"type": "server_pid",
"first_rack_id": "10"
}
]
}
validator = Draft7Validator(json_schema)
best = best_match(validator.iter_errors(data))
My expectation is that the error message thrown by validation will find 2nd element from oneof and throw error saying additional property is not allowed. but i get match for 1st element as mentioned below:
'server_pid' is not one of ['adapter']
Failed validating 'enum' in schema[0]['properties']['type']:
{'description': 'Qualification Type',
'enum': ['adapter'],
'type': 'string'}
On instance['type']:
'server_pid'
how do i specify validator to best match with property "type" which will match with enum "server_pid" instead of enum "adapter"

You can specify which schema to validate against with the if/then keywords. It's a bit verbose and can be error prone, but it's the best way to express this sort of thing. Although popular, oneOf is almost never the right choice.
"allOf": [
{
"if": {
"type": "object",
"properties": {
"type": { "const": "adapter" }
},
"required": ["type"]
},
"then": { "$ref": "#/definitions/adapter" }
},
{
"if": {
"type": "object",
"properties": {
"type": { "const": "server_pid" }
},
"required": ["type"]
},
"then": { "$ref": "#/definitions/server-pid" }
}
],

Since best_match need a sort key to help match errors and the default key is to use most depth errors key, see:
best_match
relevance
So maybe you can use a less depth key to match the errors.(below function is just a draft test, you can use it as reference)
def match_less_path(error):
return len(error.path)
best = best_match(validator.iter_errors(data), match_less_path)
And I test the output like this:
Additional properties are not allowed ('first_rack_id' was unexpected)
Failed validating 'additionalProperties' in schema[1]:
{'additionalProperties': False,
'properties': {'server_pid': {'default': '',
'description': 'Server PID '
'Qualifications - Server '
'PID',
'pattern': '^123$',
'type': 'string'},
'type': {'description': 'Qualification Type',
'enum': ['server_pid'],
'type': 'string'}},
'required': ['type', 'server_pid']}
On instance:
{'first_rack_id': '10', 'server_pid': 'B200M5', 'type': 'server_pid'}

Related

Angular6 JSON schema form for Array of Items

I am trying out Angular6 JSON form for my application and stuck in the issue of having array schema
The basic layout looks like
{
"schema": {
"type": "array",
"properties": {
"type": { "type": "string" },
"number": { "type": "string" },
}
},
"layout": [
{
"type": "array",
"items": [ {
"type": "div",
"displayFlex": true,
"flex-direction": "row",
"items": [
{ "key": "type", "flex": "1 1 50px",
"notitle": true, "placeholder": "Type"
},
{ "key": "number", "flex": "4 4 200px",
"notitle": true, "placeholder": "Phone Number"
}
]
} ]
}
],
"data": [
{ "type": "cell", "number": "702-123-4567" },
{ "type": "work", "number": "702-987-6543" }
]
}
But I am not getting the expected outcome, that is Form is prefilled with the data
[
{ "type": "cell", "number": "702-123-4567" },
{ "type": "work", "number": "702-987-6543" }
]
Refer: https://hamidihamza.com/Angular6-json-schema-form/
Based on your code there are some parts you may need to revisit.
The schema type should be either an object or boolean based on the documentation http://json-schema.org/latest/json-schema-core.html
Within the schema section, it seems that you want the type and number to be your properties of a JSON instance. Having this you can only pass one instance of data to the framework to fill in your properties because the framework cannot decide on which value to use for your property of type string.
In case of looking for having an array of type and number, you can have a property like "phone number" with the type array. below is an example from angular6-json-schema flex layout example which I think you had as your reference.
"schema": {
...
"phone_numbers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": { "type": "string", "enum": [ "cell", "home", "work" ] },
"number": { "type": "string" }
},
"required": [ "type", "number" ]
}
And pass your data having something as follows:
"data": {
...
"phone_numbers": [
{ "type": "cell", "number": "702-123-4567" },
{ "type": "work", "number": "702-987-6543" }
],
}

How to prevent certain fields based on another fields value with JSON Schema validator

Depending on the salaryRange the user selects I need to validate differently by requiring some fields and rejecting others. I feel like its a combination of allOf and not but I can't seem to quite get it.
Scenario #1
User selects salaryRange(Hourly)
Require hourlyRate
Prevent the submission of fields feeOne and feeTwo
Scenario #2
User selects salaryRange(0-50k OR 50-100k)
Require feeOne and feeTwo
Prevent the submission of field hourlyRate
Here is my schema
{
"schema": "http://json-schema.org/draft-04/schema#",
"$id": "http://mysite/schemas/job.json#",
"title": "Job",
"description": "Create job",
"type": "object",
"properties": {
"title": { "type": "string" },
"description": { "type": "string" },
"salaryRange": { "enum": ["0-50k", "50-100k", "100-150k", "150-200k", "200-300k", "300k+", "nonExempt", "Hourly"] },
"hourlyRate": {
"type": "number",
"minimum": 0,
"maximum": 300
},
"feeOne": {
"type": "number",
"minimum": 0
},
"feeTwo": {
"type": "number",
"minimum": 0
}
} ,
"additionalProperties": false,
"required": [
"title",
"description",
"salaryRange"
]
}
You can use oneOf and not required to model all possible combinations.
Here is an example in js:
https://runkit.com/embed/cf8cra1mwvx3/
{
"schema": "http://json-schema.org/draft-04/schema#",
"$id": "http://mysite/schemas/job.json#",
"title": "Job",
"description": "Create job",
"type": "object",
"properties": {
"title": { "type": "string" },
"description": { "type": "string" },
"salaryRange": { "enum": ["0-50k", "50-100k", "100-150k", "150-200k", "200-300k", "300k+", "nonExempt", "Hourly"] },
"hourlyRate": {
"type": "number",
"minimum": 0,
"maximum": 300
},
"feeOne": {
"type": "number",
"minimum": 0
},
"feeTwo": {
"type": "number",
"minimum": 0
}
},
"oneOf": [
{
"description": "Disallow fees for hourly salary",
"properties": {
"salaryRange": { "enum": ["Hourly"] }
},
"required": ["hourlyRate"],
"allOf": [
{"not":{"required":["feeOne"]}},
{"not":{"required":["feeTwo"]}}
]
},
{
"description": "Disallow hourly rate for 0-50k, 50-100k salaries",
"properties": {
"salaryRange": { "enum": ["0-50k", "50-100k"] }
},
"required": ["feeOne", "feeTwo"],
"not":{"required":["hourlyRate"]}
},
{
"description": "Allow other cases",
"properties": {
"salaryRange": { "not" : {"enum": ["Hourly", "0-50k", "50-100k"] } }
}
}
],
"additionalProperties": false,
"required": [
"title",
"description",
"salaryRange"
]
}

Validate a property value against another property value

Take the following schema:
{
"properties": {
"StageHEP": {
"description": "The stage of hepatitis",
"type": "string",
"enum": ["ACUTE", "CHRONIC", "UNK"]
},
"complications": {
"description": "Disease complications",
"type": "string",
"enum: ["CIRR", "CANCER", "NONE", "UNK"]
}
}
}
I want to create a validation rule (within the schema) that states that:
if StageHEP = ACUTE, complications property cannot be CIRR
Is it possible with json-schema draft v4?
You can do it using "oneOf":
{
"oneOf": [
{
"properties": {
"StageHEP": {
"description": "The stage of hepatitis",
"type": "string",
"enum": [
"CHRONIC",
"UNK"
]
},
"complications": {
"description": "Disease complications",
"type": "string",
"enum": [
"CIRR",
"CANCER",
"NONE",
"UNK"
]
},
"additionalProperties": false
}
},
{
"properties": {
"StageHEP": {
"description": "The stage of hepatitis",
"type": "string",
"enum": [
"ACUTE"
]
},
"complications": {
"description": "Disease complications",
"type": "string",
"enum": [
"CANCER",
"NONE",
"UNK"
]
},
"additionalProperties": false
}
}
]
}

What is the fragment that represent an item in a json schema links array

Given the following two JSON Schema definitions
Schema A
{
"$schema": "http://json-schema.org/draft-04/hyper-schema",
"title": "Member Resource",
"description": "A Member at a group",
"id": "schemata/member",
"definitions": {
"first_name": {
"description": "the first name",
"example": "Severus",
"minLength": 1,
"maxLength": 255,
"type": "string"
},
"last_name": {
"description": "the last name",
"example": "Snape",
"minLength": 1,
"maxLength": 255,
"type": "string"
},
"member_response": {
"description": "Successful response to a show or search request",
"type": "object",
"additionalProperties": false,
"properties": {
"member": { "$ref": "/schemata/member" }
},
"required": ["member"]
},
"error_response": {
"description": "Error response to a show or search request",
"type": "object",
"additionalProperties": false,
"properties": {
"reference_id": {"$ref": "#/definitions/reference_id"},
"errors": {"$ref": "#/definitions/errors"}
},
"required": [errors"]
}
},
"links": [
{
"description": "Retrieve a member",
"href": "/members/{(%23%2Fschemata%member%2Fdefinitions%2Fidentity)}",
"method": "GET",
"rel": "instance",
"title": "Show",
"targetSchema": {
"description": "Result of a get request. Can be either a success or a failure.",
"type": ["object"],
"oneOf": [
{ "$ref": "#/definitions/member_response" },
{ "$ref": "#/definitions/error_response" }
]
}
},
{
"description": "Search for a member",
"href": "/members/search",
"method": "GET",
"rel": "instance",
"schema": {
"description": "The expected payload for a search request",
"type": "object",
"additionalProperties": false,
"properties": {
"first_name": {
"$ref": "#/definitions/first_name"
},
"last_name": {
"$ref": "#/definitions/last_name"
}
},
"required": ["first_name", "last_name"]
},
"targetSchema": {
"description": "Result of a get request. Can be either a success or a failure.",
"type": ["object"],
"oneOf": [
{ "$ref": "#/definitions/member_response" },
{ "$ref": "#/definitions/error_response" }
]
}
"title": "Search"
}
],
}
Schema B
{
"$schema": "http://json-schema.org/draft-04/hyper-schema",
"title": "Member Resource",
"description": "A Member at a group",
"id": "schemata/member",
"definitions": {
"first_name": {
"description": "the first name",
"example": "Severus",
"minLength": 1,
"maxLength": 255,
"type": "string"
},
"last_name": {
"description": "the last name",
"example": "Snape",
"minLength": 1,
"maxLength": 255,
"type": "string"
},
"search_payload": {
"description": "The expected payload for a search request",
"type": "object",
"additionalProperties": false,
"properties": {
"first_name": {
"$ref": "#/definitions/first_name"
},
"last_name": {
"$ref": "#/definitions/last_name"
}
},
"required": ["first_name", "last_name"]
},
"member_response": {
"description": "Successful response to a show or search request",
"type": "object",
"additionalProperties": false,
"properties": {
"member": { "$ref": "/schemata/member" }
},
"required": ["member"]
},
"error_response": {
"description": "Error response to a show or search request",
"type": "object",
"additionalProperties": false,
"properties": {
"reference_id": {"$ref": "#/definitions/reference_id"},
"errors": {"$ref": "#/definitions/errors"}
},
"required": [errors"]
},
"get_response": {
"description": "Result of a get request. Can be either a success or a failure.",
"type": ["object"],
"oneOf": [
{ "$ref": "#/definitions/member_response" },
{ "$ref": "#/definitions/error_response" }
]
}
},
"links": [
{
"description": "Retrieve a member",
"href": "/members/{(%23%2Fschemata%member%2Fdefinitions%2Fidentity)}",
"method": "GET",
"rel": "instance",
"title": "Show",
"targetSchema": {
"$ref": "#/definitions/get_response"
}
},
{
"description": "Search for a member",
"href": "/members/search",
"method": "GET",
"rel": "instance",
"schema": {
"$ref": "#/definitions/search_payload"
},
"targetSchema": {
"$ref": "#/definitions/get_response"
},
"title": "Search"
}
],
}
Both schemas are functionally the same. The difference is that targetSchema is defined inline in schema A but as a ref in schema B.
I use a library to validate the input and output to and API endpoint. For example when testing my APIs I want to validate that the response to each request returns a JSON object that conforms with targetSchema for that API.
JSON::Validator.fully_validate(
schema,
object_to_test,
:fragment => "/path/to/fragment"
)
In order to validate against the targetSchema for the /members/search API defined above I need to be able to reference its targetSchema.
In schema B I can do
JSON::Validator.fully_validate(
schema,
object_to_test,
:fragment => "#/definitions/get_response"
)
Is it possible to do the above for schema A too? i.e. can I reference the actual targetSchema of the search link directly. Perhaps it might look like the following
JSON::Validator.fully_validate(
schema,
object_to_test,
:fragment => "#/links[1]/targetSchema"
)
or
JSON::Validator.fully_validate(
schema,
object_to_test,
:fragment => "#/links/[SOME_WAY_OF_SPECIFYING_THAT_TITLA_EQL_SEARCH"]/targetSchema"
)
Given your schema, you can reference the search link's targetSchema with the following JSON Pointer(1).
#/links/1/targetSchema
Here 1 is the index of the desired item in the links array. This is the only way to reference an item in an array. To precisely answer the question -- there is no way of specifying the item in the array where title equals "search".
Obviously, referencing the targetSchema using an index is fragile. If you add a link to the schema in the wrong place, your code will break. You would be better off if you looped through the links in code and chose the one you need.
You might ask, "Why is it so difficult to reference a link's targetSchema for validation?" The answer is that targetSchema is not intended for validation. targetSchema is intended to be informational only. It's documentation. The only schema that the response should be responsible for conforming to is the one it declares in the response(2). This is one of the core ideas of REST. The client and server are decoupled. The client doesn't make any assumptions about the response it will get. The response itself should have all the information needed to interpret the response and what you can do next.
https://www.rfc-editor.org/rfc/rfc6901
http://json-schema.org/latest/json-schema-core.html#anchor33

Json schema dynamic key validation

Facing an issue with schema validation.
schema :
{
"type": "object",
"$schema": "http://json-schema.org/draft-03/schema",
"id": "#",
"required": true,
"patternProperties": {
"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,6}$": {
"type": "object",
"required": true,
"properties": {
"_from": {
"id": "_from",
"type": "string",
"required": true
},
"message": {
"type": "object",
"id": "message",
"properties": {
"detail": {
"type": "string",
"id": "detail",
"required": true
},
"from": {
"type": "string",
"id": "from",
"required": true
}
}
}
}
}
}
}
json :
{
"tom#example.com": {
"_from": "giles#gmail.com",
"message": {
"from": "Giles#gmail.com",
"detail": "AnyonewanttomeetmeinParis"
}
},
"harry#example.com": {
"_from": "giles#gmail.com",
"message": {
"from": "Giles#gmail.com",
"detail": "AnyonewanttomeetmeinParis"
}
}
}
Here the key email address is dynamic, somehow it doesn't validate regex for email validation.
Can you please advise me to correct the schema.
I am validating using : http://json-schema-validator.herokuapp.com/index.jsp
I see in your pattern that you seem to have forgotten to escape some characters or didn't do it correctly:
"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,6}$"
and it causes the error that you can see when you hover the mouse over the link at the top of the validator:
it should be:
"^[A-Z0-9\\._%\\+-]+#[A-Z0-9\\.-]+\\.[A-Z]{2,6}$"
or without escaping the inner/class characters but I'd use the first pattern because I think its intention is clearer:
"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,6}$"
You need to have two \ because the first \ is an escape for the second \. With a single one it wouldn't work because there is no escape sequence like \. or \+ in javascript. You want to have a \in the pattern itself.
However json schema patternProperties are case sensitive by default so you need to extend your email pattern by adding a-z to it:
"^[A-Za-z0-9\\._%\\+-]+#[A-Za-z0-9\\.-]+\\.[A-Za-z]{2,6}$"
(I didn't find any other way to make it case insensitive)
You also need to exclude any other property names by adding "additionalProperties": false next to the patternProperties or otherwise it catches everything else that does not match the pattern.
The working schema should then look like this:
{
"type": "object",
"$schema": "http://json-schema.org/draft-03/schema",
"id": "#",
"required": true,
"patternProperties": {
"^[A-Za-z0-9\\._%\\+-]+#[A-Za-z0-9\\.-]+\\.[A-Za-z]{2,6}$": {
"type": "object",
"required": true,
"properties": {
"_from": {
"id": "_from",
"type": "string",
"required": true
},
"message": {
"type": "object",
"id": "message",
"properties": {
"detail": {
"type": "string",
"id": "detail",
"required": true
},
"from": {
"type": "string",
"id": "from",
"required": true
}
}
}
}
}
},
"additionalProperties": false
}
I've tested it on: http://jsonschemalint.com/
Changed the schema as per draft 04 :
{
"type": "object",
"$schema": "http://json-schema.org/draft-04/schema",
"patternProperties": {
"^[A-Za-z0-9\\._%\\+-]+#[A-Za-z0-9\\.-]+\\.[A-Za-z]{2,6}$": {
"type": "object",
"properties": {
"__from": {
"type": "string"
},
"message": {
"type": "object",
"properties": {
"from": {
"type": "string"
},
"detail": {
"type": "string"
}
},
"required": [ "from","detail"]
}
},
"required": [ "__from","message"]
}
},
"additionalProperties": false
}