davishmcclurg / json_schemer

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

Unable to reference schema in another folder #163

Closed phil-6 closed 12 months ago

phil-6 commented 12 months ago

Hey!

I'm not sure if I'm missing something here or if this is a genuine issue, apologies if this is a me-issue!

I'm in the process of switching from JSON-Schema and I have a json spec which reuses multiple schemas to try and keep things dry.

I'm getting Errno::ENOENT: No such file or directory @ rb_sysopen - /messages/message.schema.json when validating against a schema which references remote, local files.

When using schema.bundle the message.schema.json error is resolved but the issue referencing pagy remains. I'm getting this error whenever I reference a schema in a different folder.

The references are correctly fetched when using Redocly's CLI to validate and build the spec into our API Docs

Is there something I should be doing differently when using #ref?

Test

  test "schema should be valid"
    schema_path = Pathname.new('api_spec/chats/messages/index.schema.json')
    schema = JSONSchemer.schema(schema_path)

    assert schema.valid_schema? # true
    assert JSONSchemer.valid_schema?(schema) # false
    # assert schema.valid?(@response.parsed_body) # original test against API response
  end
Second Test Example ``` ruby test "schema should also be be valid" # post "/chats/#{chats(:one).id}/messages", params: params.to_json, headers: @auth_headers schema_path = Pathname.new('api_spec/chats/messages/message.schema.json') schema = JSONSchemer.schema(schema_path) assert schema.valid_schema? # true assert JSONSchemer.valid_schema?(schema) # false # assert schema.valid?(@response.parsed_body) # original test against API response end ```

api_spec/chats/messages/index.schema.json

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "/messages/index",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "messages_count": {
      "type": "integer"
    },
    "messages": {
      "type": "array",
      "items": {
        "$ref": "message.schema.json"
      }
    },
    "pagy_messages": {
      "$ref": "../../pagy.schema.json"
    }
  },
  "required": ["messages_count", "messages"]
}
api_spec/chats/messages/imessage.schema.json ``` json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "/message.schema.json", "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "chat_id": { "type": "string", "format": "uuid" }, "content": { "type": "string", "example": "Hello world! This is a chat message!" }, "system_message": { "type": "boolean" }, "created_at": { "type": "string", "format": "date-time", "example": "2023-08-08T16:42:54.077+01:00" }, "updated_at": { "type": "string", "format": "date-time", "example": "2023-08-08T16:42:54.077+01:00" }, "gif_url": { "type": ["string", "null"] }, "user": { "$ref": "../../users/user_summary.schema.json" }, "reply_to": { "type": ["object", "null"], "additionalProperties": false, "properties": { "message_id": { "type": "string", "format": "uuid" }, "content": { "type": "string" }, "created_at": { "type": "string", "format": "date-time", "example": "2023-08-08T16:42:54.077+01:00" }, "updated_at": { "type": "string", "format": "date-time", "example": "2023-08-08T16:42:54.077+01:00" }, "gif_url": { "type": ["string", "null"] }, "user": { "$ref": "../../users/user_summary.schema.json" } } }, "viewed_by": { "type": "array", "items": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "additionalProperties": false, "properties": { "user": { "$ref": "../../users/user_summary.schema.json" }, "viewed_at": { "type": "string" } }, "required": ["user", "viewed_at"] } }, "reactions_tally": { "type": "object", "properties": { "😁": { "type": "integer" }, "😲": { "type": "integer" } }, "additionalProperties": true }, "reactions": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "reaction": { "type": "string", "format": "emoji", "example": "\uD83D\uDE01" }, "count": { "type": "integer" }, "users": { "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "user": { "$ref": "../../users/user_summary.schema.json" }, "reacted_at": { "type": "string" } }, "required": ["user", "reacted_at"] } } }, "required": ["reaction", "count", "users"] } } }, "required": [ "id", "chat_id", "content", "system_message", "created_at", "gif_url", "user", "reply_to", "viewed_by", "reactions_tally", "reactions" ] } ```
api_spec/users/user_summary.schema.json ``` json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "/schemas/user_summary", "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "example": "Phil Reynolds" }, "picture_url": { "type": ["string", "null"], "format": "binary" }, "chat_message_colour": { "type": "string", "format": "hex-color", "example": "#ff0000" } }, "required": ["id", "name", "picture_url", "chat_message_colour"] } ```
api_spec/pagy.schema.json ``` json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "/pagy", "type": "object", "additionalProperties": false, "properties": { "vars": { "type": "object", "properties": { "page": { "type": "integer" }, "items": { "type": "integer" }, "outset": { "type": "integer" }, "size": { "type": "array", "items": { "type": "integer" } }, "page_param": { "type": "string"}, "params": { "type": "object" }, "fragment": { "type": "string" }, "link_extra": { "type": "string" }, "i18n_key": { "type": "string" }, "cycle": { "type": "boolean" }, "request_path": { "type": "string" }, "count": { "type": "integer" } } }, "count": { "type": "integer" }, "page": { "type": "integer" }, "outset": { "type": "integer" }, "items": { "type": "integer" }, "last": { "type": "integer" }, "pages": { "type": "integer" }, "offset": { "type": "integer" }, "params": { "type": "object" }, "from": { "type": "integer" }, "to": { "type": "integer" }, "in": { "type": "integer" }, "prev": { "type": ["integer", "null"] }, "next": { "type": ["integer", "null"] } } } ```
phil-6 commented 12 months ago

Some further experimentation offered a solution. The issue appears to be the was I was setting my IDs in the schema

Removing the IDs (turning them into anonymous schemas) resolves the issue completely and lets the relative references resolve.

Backtracking from here, it looks as though the ID wants to be a purely relative reference, and not contain any file path. (As indicated in most of the examples on json-schema.org!

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "chats/messages/index.schema.json",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "messages": {
      "type": "array",
      "items": {
        "$ref": "message.schema.json"
      }
    }
  }
}

gives the error: Errno::ENOENT: No such file or directory @ rb_sysopen - /home/phil/dev/root-path/api_spec/chats/messages/chats/messages/message.schema.json

where as

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "index",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "messages": {
      "type": "array",
      "items": {
        "$ref": "message.schema.json"
      }
    }
  }
}

Works completely!

davishmcclurg commented 12 months ago

Glad you got it figured out, @phil-6 :+1:

For absolute $id values to work, I believe you'd need to include the entire file path (eg, /home/phil/dev/root-path/api_spec/chats/messages/message.schema.json). Since you're using the file system, I recommend not including $id so that everything is resolved relative to the original schema file path.