APIDevTools / json-schema-ref-parser

Parse, Resolve, and Dereference JSON Schema $ref pointers in Node and browsers
https://apitools.dev/json-schema-ref-parser
MIT License
953 stars 227 forks source link

Addition of dereferencedCache has broken deeply nested external dependencies #226

Closed mummybot closed 6 months ago

mummybot commented 3 years ago

The addition of the dereferencedCache in PR https://github.com/APIDevTools/json-schema-ref-parser/pull/195 has broken deeply nested external dependencies if a local dependency exists in the root schema.

Given the following schema:

// schema.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "definitions": {
    "country": {
      "type": "string",
      "enum": {
        "$ref": "./country-codes.json"
      }
    },
    "foreignCountry": {
      "type": "string",
      "enum": {
        "$ref": "#/definitions/countries"  // This being a local dependency prevents ./external-fail.json from resolving in nested schema.
      }
    },
    "externalFail": {
      "$ref": "./external-fail.json"
    }
  },
  "properties": {
    "pageOne": {
      "$ref": "./pageOne.json"
    }
  }
}
// pageOne.json
{
  "type": "object",
  "properties": {
    "nestedProperty": {
      "$ref": "./schema.json#/definitions/country",
      "#bespokeKey": {
        "append": {
          "component": "Callout",
          "props": {
            "type": "warning"
          },
          "on": [
            "GB",
            [
              "PROHIBITED",
              {
                "$ref": "./external-fail.json"
              }
           ]
          ]
        }
      },
      "not": {
        "enum": {
          "$ref": "./external-fail.json"
        }
      }
    }
  },
  "allOf": [
    {
      "if": {
        "properties": {
          "nestedProperty": {
            "type": "string",
            "enum": {
              "$ref": "./external-success.json"
            }
          }
        }
      },
      "then": {
        "required": ["otherBooleanProperty"],
        "properties": {
          "otherBooleanProperty": {
            "type": "boolean"
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "nestedProperty": {
            "type": "string",
            "enum": {
              "$ref": "./external-success.json"
            }
          },
          "otherBooleanProperty": {
            "const": false
          }
        }
      },
      "then": {
        "properties": {
          "nestedOtherProperty": {
            "$ref": "./schema.json#/definitions/country",
            "#bespokeKey": {
              "append": {
                "component": "Callout",
                "props": {
                  "type": "warning"
                },
                "on": [
                  [
                    "PROHIBITED",
                    {
                      "$ref": "./external-fail.json"
                    }
                  ]
                ]
              }
            },
            "not": {
              "enum": {
                "$ref": "./external-fail.json"
              }
            }
          }
        }
      }
    }
  ]
}
// external-fail.json
["AU", "US", "GB"]

Expected

//...
    "nestedProperty": {
      "type": "string",
      "enum": ["JP", "US", "GB", "AU"],
      "#bespokeKey": {
        "append": {
          "component": "Callout",
          "props": {
            "type": "warning"
          },
          "on": [
            "GB",
            [
              "PROHIBITED",
              ["AU", "US", "GB"]
            ]
          ]
        }
      },
      "not": {
        "enum": ["AU", "US", "GB"]
      }
    }
  },
//...

Actual

//...
    "nestedProperty": {
      "type": "string",
      "enum": ["JP", "US", "GB", "AU"],
      "#bespokeKey": {
        "append": {
          "component": "Callout",
          "props": {
            "type": "warning"
          },
          "on": [
            "GB",
            [
              "PROHIBITED",
              {
                "$ref": "./external-fail.json"
              }
            ]
          ]
        }
      },
      "not": {
        "enum": {
          "$ref": "./external-fail.json"
        }
      }
    }
//...

If I comment out https://github.com/APIDevTools/json-schema-ref-parser/blob/master/lib/dereference.js#L177 then it all works (by not adding to cache and therefore not skipping).

mummybot commented 3 years ago

In the example above I think this is due to it matching the nestedProperty which has a ref, and then assuming that there are no other refs within that property. not is valid schema, and can be an external ref. Any key within a parent property with an external ref should still be checked for other external refs.

mummybot commented 3 years ago

FYI @paztis