davishmcclurg / json_schemer

JSON Schema validator. Supports drafts 4, 6, 7, 2019-09, 2020-12, OpenAPI 3.0, and OpenAPI 3.1.
MIT License
399 stars 64 forks source link

For missing parent object the required fields inside this parent object are not part of the error response. #169

Closed sanjjaymahalingam closed 7 months ago

sanjjaymahalingam commented 8 months ago

While trying to validate a JSON payload against a particular JSON schema which is based on JSON draft 2019-09 the following errors are returned from the gem:-

[
  {
    "data_pointer": "",
    "type": "required",
    "error": "object at root is missing required properties: contains_liquid_contents, generic_keyword, is_heat_sensitive, lifestyle, list_price, manufacturer, safety_warning, target_audience_keyword",
    "details": {
      "missing_keys": [
        "contains_liquid_contents",
        "generic_keyword",
        "is_heat_sensitive",
        "lifestyle",
        "list_price",
        "manufacturer",
        "safety_warning",
        "target_audience_keyword"
      ]
    }
  },
  {
    "data_pointer": "",
    "type": "required",
    "error": "object at root is missing required properties: fulfillment_availability",
    "details": { "missing_keys": ["fulfillment_availability"] }
  }
]

In the response above the missing parent fields have been listed. For example as the fulfillment_availability parent object is missing from the JSON payload the gem gives the following error:-

{
    "data_pointer": "",
    "type": "required",
    "error": "object at root is missing required properties: fulfillment_availability",
    "details": { "missing_keys": ["fulfillment_availability"] }
}

According to the JSON schema file the schema for fulfilment_availabilty is as follows:-

"fulfillment_availability": {
      "title": "Fulfillment Availability",
      "description": "For those merchants using Amazon fulfillment services, please provide associated logistical information.",
      "type": "array",
      "minItems": 1,
      "minUniqueItems": 1,
      "maxUniqueItems": 1,
      "selectors": ["fulfillment_channel_code"],
      "items": {
        "type": "object",
        "required": ["fulfillment_channel_code"],
        "properties": {
          "fulfillment_channel_code": {
            "title": "Fulfillment Channel Code",
            "description": "For those merchants using Amazon fulfillment services, this designates which fulfillment network will be used. Specifying a value other than DEFAULT will cancel the Merchant-fulfilled offering.",
            "editable": true,
            "hidden": false,
            "examples": ["AMAZON_NA"],
            "type": "string",
            "enum": ["AMAZON_NA", "DEFAULT"],
            "enumNames": ["AMAZON_NA", "DEFAULT"]
          },
          "quantity": {
            "title": "Quantity",
            "description": "Enter the quantity of the item you are making available for sale. This is your current inventory commitment (as a whole number)",
            "editable": true,
            "hidden": false,
            "examples": ["152"],
            "type": "integer",
            "minimum": 0
          },
          "lead_time_to_ship_max_days": {
            "title": "Handling Time",
            "description": "Provide the time, in days, between when you receive an order for an item and when you can ship the item",
            "editable": true,
            "hidden": false,
            "examples": ["5"],
            "type": "integer",
            "minimum": 0,
            "maximum": 30
          },
          "restock_date": {
            "title": "Restock Date",
            "description": "Date that product will be restocked",
            "editable": true,
            "hidden": false,
            "examples": ["2020-05-05"],
            "type": "string",
            "oneOf": [
              { "type": "string", "format": "date" },
              { "type": "string", "format": "date-time" }
            ]
          },
          "is_inventory_available": {
            "title": "Inventory Always Available",
            "description": "Always available inventory is an alternative to quantity that allows inventory to never deplete. Enabling or 
             disabling will toggle this feature on or off. Note that a quantity cannot be specified when provided.",
            "editable": true,
            "hidden": false,
            "examples": ["Enabled"],
            "type": "boolean",
            "enum": [false, true],
            "enumNames": ["Disabled", "Enabled"]
          }
        }
        "additionalProperties": false
    }
}

As per the schema, only fulfillment_channel_code in the parent object fulfillment_availability is a required field. Hence as a suggestion if the parent object fulfillment_availability is missing the gem should give an error message as follows:-

{
  "data_pointer": "/fulfillment_availability/0",
  "type": "required",
  "error": "object at `/fulfillment_availability/0` is missing required properties: fulfillment_channel_code",
  "details": { "missing_keys": ["fulfillment_channel_code"] }
}
shubham-n-khanna commented 8 months ago

Can we make the changes in gem to get error message with inner required missing fields as shown above?

ahx commented 8 months ago

Could you provide a smaller example schema to reproduce the issue?

rohit-lingayat-sd commented 8 months ago

can we add extra keys called custom_schema, the same as schema, and schema_pointer in the response which we get after validate? custom_schema will have the responsibility of extracting only the node object and not the entire schema cc: @shubham-n-khanna @sanjjaymahalingam

sanjjaymahalingam commented 8 months ago

Hey @ahx, here is the JSON schema file we are using. Would it be of help:- schema.json

davishmcclurg commented 8 months ago

here is the JSON schema file we are using. Would it be of help:- schema.json

That schema is quite complex, but I see at least one condition where fulfillment_availability would be required:

{
  "$schema": "https://schemas.amazon.com/selling-partners/definitions/product-types/meta-schema/v1",
  "$id": "https://schemas.amazon.com/selling-partners/definitions/product-types/schema/v1/BEAUTY",
  ...
  "allOf": [
    ...
    {
      "if": {
        "allOf": [
          {
            "not": {
              "required": [
                "parentage_level"
              ],
              "properties": {
                "parentage_level": {
                  "contains": {
                    "required": [
                      "value"
                    ],
                    "properties": {
                      "value": {
                        "enum": [
                          "parent"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          {
            "not": {
              "required": [
                "skip_offer"
              ],
              "properties": {
                "skip_offer": {
                  "contains": {
                    "required": [
                      "value"
                    ],
                    "properties": {
                      "value": {
                        "enum": [
                          true
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "then": {
        "required": [
          "fulfillment_availability"
        ]
      }
    },
    ...
  ]
}

In order to debug this further, I would need a much simpler example schema and example data.


can we add extra keys called custom_schema, the same as schema, and schema_pointer in the response which we get after validate? custom_schema will have the responsibility of extracting only the node object and not the entire schema cc: @shubham-n-khanna @sanjjaymahalingam

I'm not sure what you mean by "only the node object"—can you provide an example?

sanjjaymahalingam commented 8 months ago

An example schema and example data for debugging:-

{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "$id": "https://schemas.amazon.com/selling-partners/definitions/product-types/schema/v1/BEAUTY",
  "$comment": "Amazon product type definition for BEAUTY product type",
  "$defs": {
    "marketplace_id": {
      "default": "ATVPDKIKX0DER",
      "editable": false,
      "hidden": true,
      "examples": ["Amazon.com"],
      "type": "string",
      "anyOf": [
        { "type": "string" },
        {
          "type": "string",
          "enum": ["ATVPDKIKX0DER"],
          "enumNames": ["Amazon.com"]
        }
      ]
    },
    "language_tag": {
      "default": "en_US",
      "editable": false,
      "hidden": true,
      "examples": ["English (United States)"],
      "type": "string",
      "anyOf": [
        { "type": "string" },
        {
          "type": "string",
          "enum": ["en_US"],
          "enumNames": ["English (United States)"]
        }
      ]
    }
  },
  "type": "object",
  "required": [
    "brand",
    "fulfillment_availability"
  ],
  "properties": {
    "brand": {
      "title": "Brand Name",
      "description": "Max. 50 characters",
      "examples": ["Sonny Brook Hams"],
      "type": "array",
      "minItems": 1,
      "minUniqueItems": 1,
      "maxUniqueItems": 1,
      "selectors": ["marketplace_id", "language_tag"],
      "items": {
        "type": "object",
        "required": ["language_tag", "marketplace_id", "value"],
        "properties": {
          "value": {
            "title": "Brand Name",
            "description": "Provide the brand name of the product",
            "editable": false,
            "hidden": false,
            "examples": ["Sony"],
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "language_tag": { "$ref": "#/$defs/language_tag" },
          "marketplace_id": { "$ref": "#/$defs/marketplace_id" }
        },
        "additionalProperties": false
      }
    },
    "fulfillment_availability": {
      "title": "Fulfillment Availability",
      "description": "For those merchants using Amazon fulfillment services, please provide associated logistical information.",
      "type": "array",
      "minItems": 1,
      "minUniqueItems": 1,
      "maxUniqueItems": 1,
      "selectors": ["fulfillment_channel_code"],
      "items": {
        "type": "object",
        "required": ["fulfillment_channel_code"],
        "properties": {
          "fulfillment_channel_code": {
            "title": "Fulfillment Channel Code",
            "description": "For those merchants using Amazon fulfillment services, this designates which fulfillment network will be used. Specifying a value other than DEFAULT will cancel the Merchant-fulfilled offering.",
            "editable": true,
            "hidden": false,
            "examples": ["AMAZON_NA"],
            "type": "string",
            "enum": ["AMAZON_NA", "DEFAULT"],
            "enumNames": ["AMAZON_NA", "DEFAULT"]
          },
          "quantity": {
            "title": "Quantity",
            "description": "Enter the quantity of the item you are making available for sale. This is your current inventory commitment (as a whole number)",
            "editable": true,
            "hidden": false,
            "examples": ["152"],
            "type": "integer",
            "minimum": 0
          }
        },
        "additionalProperties": false
      }
    }
  }    
}

data:-

{
  "brand": [
    {
      "value": "some value here",
      "language_tag": "en_US",
      "marketplace_id": "ATVPDKIKX0DER"
    }
  ]
}
rohit-lingayat-sd commented 8 months ago

@davishmcclurg let me share one example for better understanding. when we validate the schema it gives a response in the format below

# => [{"data"=>10,
#      "data_pointer"=>"/abc",
#      "schema"=>{"type"=>"integer", "minimum"=>11},
#      "schema_pointer"=>"/properties/abc",
#      "root_schema"=>{"type"=>"object", "properties"=>{"abc"=>{"type"=>"integer", "minimum"=>11}}},
#      "type"=>"minimum",
#      "error"=>"number at `/abc` is less than: 11"}]

so can we add one more field called custom_schema which will contain the schema of that field instead of the entire schema. e.g

# => [{"data"=>10,
#      "data_pointer"=>"/abc",
#      "schema"=>{"type"=>"integer", "minimum"=>11},
#      "schema_pointer"=>"/properties/abc",
#      "root_schema"=>{"type"=>"object", "properties"=>{"abc"=>{"type"=>"integer", "minimum"=>11}}},
#      "type"=>"minimum",
#       "custom_schema"=> "{
#            "title": "Fulfillment Availability",
#            "description": "For those merchants using Amazon fulfillment services, please provide associated logistical information.",
#            "type": "array",
#            "minItems": 1,
#            "minUniqueItems": 1,
#            "maxUniqueItems": 1,
#            "selectors": ["fulfillment_channel_code"],
#            "items": {
#              "type": "object",
#              "required": ["fulfillment_channel_code"],
#              "properties": {
#                "fulfillment_channel_code": {
#                  "title": "Fulfillment Channel Code",
#                  "description": "For those merchants using Amazon fulfillment services, this designates which fulfillment network will be used. Specifying a value other than DEFAULT will cancel the Merchant-fulfilled offering.",
#                  "editable": true,
#                  "hidden": false,
#                  "examples": ["AMAZON_NA"],
#                  "type": "string",
#                  "enum": ["AMAZON_NA", "DEFAULT"],
#                  "enumNames": ["AMAZON_NA", "DEFAULT"]
#                },
#                "quantity": {
#                  "title": "Quantity",
#                  "description": "Enter the quantity of the item you are making available for sale. This is your current inventory commitment (as a whole number)",
#                  "editable": true,
#                  "hidden": false,
#                  "examples": ["152"],
#                  "type": "integer",
#                  "minimum": 0
#                }
#              },
#              "additionalProperties": false
#            }
#          }
#        }"
#      "error"=>"number at `/abc` is less than: 11"}]

by doing this we can use the custom_schema field for our purpose.

davishmcclurg commented 8 months ago

An example schema and example data for debugging:-

@sanjjaymahalingam in that example, fulfillment_availability is explicitly required. Here is the output I get:

irb(main):015> JSONSchemer.schema(schema).validate(instance).map { |r| r.except('root_schema', 'schema') }
=>
[{"data"=>{"brand"=>[{"value"=>"some value here", "language_tag"=>"en_US", "marketplace_id"=>"ATVPDKIKX0DER"}]},
  "data_pointer"=>"",
  "schema_pointer"=>"",
  "type"=>"required",
  "error"=>"object at root is missing required properties: fulfillment_availability",
  "details"=>{"missing_keys"=>["fulfillment_availability"]}}]

That seems correct to me. What output are you expecting?

so can we add one more field called custom_schema which will contain the schema of that field instead of the entire schema.

@rohit-lingayat-sd would custom_schema always return the top-level properties schema for a certain property? In the output, schema is already the current property schema ("schema"=>{"type"=>"integer", "minimum"=>11} in your example). If you want access to intermediate schemas, I would use schema_pointer to walk through your schema to whatever level you need.

sanjjaymahalingam commented 8 months ago
{ 
  "data": { "brand": [{ "value": "some value here", "language_tag": "en_US", "marketplace_id": "ATVPDKIKX0DER" }]},
  "data_pointer": "/fulfillment_availability/0",
  "schema_pointer": "",
  "type": "required",
  "error": "object at `/fulfillment_availability/0` is missing required properties: fulfillment_channel_code",
  "details": { "missing_keys": [ "fulfillment_channel_code"]}
}

We were thinking the gem should provide a more precise response like this where it states that the fulfillment_channel_code in the fulfillment_availability parent object is missing even if the whole fulfillment_availability object is not provided in the input. When a parent object is missing it should check all the required child fields in the parent object (in this case only fulfillment_channel_code) and then provide an error as above.

davishmcclurg commented 8 months ago

When a parent object is missing it should check all the required child fields in the parent object (in this case only fulfillment_channel_code) and then provide an error as above.

If the parent is missing, aren't you able to assume any required child fields are missing as well? I don't think it makes sense to do speculative validation like that, since we can't know what the parent object would look like if it were provided. Child requirements could also be conditional on the value of the parent object.