Search code examples
ruby-on-railsopenapijsonschema

Using $ref to a different Open API 3.0.2 Specification File is throwing error


While I am well versed in the JSON Schema specification I am working to transition to the Open API 3.0.2 specification and I am running into an odd issue trying to DRY up and organize my definitions. I am trying to use $ref to other JSON structures from in nested folders and running into an error. I am sure there is a rule or pathing character I am missing. I should add the $ref under the info block appears to work correctly, it is only the model $ref.

I have been following these guides:

I am using the committee gem to test these schemas and the error is:

enter image description here

Attempted "$ref": "models/user.json" DRY Extraction to subfolder

{
  "type": "object",
  "description": "A user account.",
  "required": [
    "id",
    "email"
  ],
  "additionalProperties": false,
  "properties": {
    "id": {
      "type": "integer",
      "description": "Unique identifier for the given record."
    },
    "email": {
      "type": "string",
      "description": "The email associated to the given user."
    },
    "first_name": {
      "type": "string",
      "description": "User's first name."
    },
    "last_name": {
      "type": "string",
      "description": "User's last name."
    },
    "full_name": {
      "type": "string",
      "description": "Full user name"
    }
  }
}

How I have my folder structure setup:

common/
models/
requests/
api.json

The api.json file

{
  "openapi": "3.0.2",
  "components": {
    "schemas": {
      "error": {
        "properties": {
          "code": {
            "type": "string",
            "description": "Machine readable code for this specific error message."
          },
          "message": {
            "type": "string",
            "description": "Description of the error."
          }
        },
        "required": [
          "code",
          "message"
        ],
        "type": "object"
      },
      "user": {

        "type": "object",
        "description": "A user account.",
        "required": [
          "id",
          "email"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier for the given record."
          },
          "email": {
            "type": "string",
            "description": "The email associated to the given user."
          },
          "first_name": {
            "type": "string",
            "description": "User's first name."
          },
          "last_name": {
            "type": "string",
            "description": "User's last name."
          },
          "full_name": {
            "type": "string",
            "description": "Full user name"
          }
        }

      },
      "user_input": {
        "properties": {
          "email": {
            "type": "string"
          },
          "first_name": {
            "type": "string",
            "description": "User's first name."
          },
          "last_name": {
            "type": "string",
            "description": "User's last name."
          }
        },
        "required": [
          "email"
        ],
        "type": "object"
      }
    }
  },
  "info": { "$ref": "common/info.json" },
  "paths": {
    "/users": {
      "get": {
        "description": "Returns all the users in the system.",
        "operationId": "get--users",
        "parameters": [],
        "responses": {
          "200": {
            "description": "[com.test.test]",
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "models/user.json"
                  },
                  "type": "array"
                }
              }
            },
            "headers": {}
          }
        },
        "tags": [
          "user"
        ]
      },
      "post": {
        "description": "Creates a new user.",
        "operationId": "post--users",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/user_input"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "com.test.test",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "models/user.json"
                }
              }
            },
            "headers": {}
          },
          "409": {
            "description": "[com.test.test]",
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/error"
                  },
                  "type": "array"
                }
              }
            },
            "headers": {}
          }
        },
        "tags": [
          "user"
        ]
      }
    },
    "/users/{id}": {
      "get": {
        "description": "Returns the given user.",
        "operationId": "get--users-id",
        "parameters": [
          {
            "deprecated": false,
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "name": "id"
          }
        ],
        "responses": {
          "200": {
            "description": "com.test.test",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "models/user.json"
                }
              }
            },
            "headers": {}
          },
          "404": {
            "description": "unit",
            "content": {
              "application/json": {
                "schema": {
                  "type": "integer",
                  "nullable": true
                }
              }
            },
            "headers": {}
          }
        },
        "tags": [
          "user"
        ]
      }
    }
  },
  "servers": [
    {
      "url": "http://bobs.ngrok.io"
    }
  ]
}

Solution

  • From https://swagger.io/docs/specification/using-ref/

    A common misconception is that $ref is allowed anywhere in an OpenAPI specification file. Actually $ref is only allowed in places where the OpenAPI 3.0 Specification explicitly states that the value may be a reference. For example, $ref cannot be used in the info section and directly under paths:

    I adjusted my json to..

    {
      "openapi": "3.0.2",
      "components": {
        "schemas": {
          "error": {
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine readable code for this specific error message."
              },
              "message": {
                "type": "string",
                "description": "Description of the error."
              }
            },
            "required": [
              "code",
              "message"
            ],
            "type": "object"
          },
          "user": { "$ref": "models/user.json#/components/schemas/user" },
          "user_input": {
            "properties": {
              "email": {
                "type": "string"
              },
              "first_name": {
                "type": "string",
                "description": "User's first name."
              },
              "last_name": {
                "type": "string",
                "description": "User's last name."
              }
            },
            "required": [
              "email"
            ],
            "type": "object"
          }
        }
      },
      "info": {
        "contact": {
          "name": "Bobs Burgers",
          "email": "bob@bobsburgers.com"
        },
        "description": "Marketplace API.",
        "license": {
          "name": "Apache 2.0",
          "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
        },
        "termsOfService": "",
        "title": "explore",
        "version": "0.0.2-dev"
      },
      "paths": {
        "/users": {
          "get": {
            "description": "Returns all the users in the system.",
            "operationId": "get--users",
            "parameters": [],
            "responses": {
              "200": {
                "description": "[com.test.test]",
                "content": {
                  "application/json": {
                    "schema": {
                      "items": {
                        "$ref": "#/components/schemas/user"
                      },
                      "type": "array"
                    }
                  }
                },
                "headers": {}
              }
            },
            "tags": [
              "user"
            ]
          },
          "post": {
            "description": "Creates a new user.",
            "operationId": "post--users",
            "parameters": [],
            "requestBody": {
              "content": {
                "application/json": {
                  "schema": {
                    "$ref": "#/components/schemas/user_input"
                  }
                }
              }
            },
            "responses": {
              "200": {
                "description": "com.test.test",
                "content": {
                  "application/json": {
                    "schema": {
                      "$ref": "#/components/schemas/user"
                    }
                  }
                },
                "headers": {}
              },
              "409": {
                "description": "[com.test.test]",
                "content": {
                  "application/json": {
                    "schema": {
                      "items": {
                        "$ref": "#/components/schemas/error"
                      },
                      "type": "array"
                    }
                  }
                },
                "headers": {}
              }
            },
            "tags": [
              "user"
            ]
          }
        },
        "/users/{id}": {
          "get": {
            "description": "Returns the given user.",
            "operationId": "get--users-id",
            "parameters": [
              {
                "deprecated": false,
                "in": "path",
                "required": true,
                "schema": {
                  "type": "integer"
                },
                "name": "id"
              }
            ],
            "responses": {
              "200": {
                "description": "com.test.test",
                "content": {
                  "application/json": {
                    "schema": {
                      "$ref": "#/components/schemas/user"
                    }
                  }
                },
                "headers": {}
              },
              "404": {
                "description": "unit",
                "content": {
                  "application/json": {
                    "schema": {
                      "type": "integer",
                      "nullable": true
                    }
                  }
                },
                "headers": {}
              }
            },
            "tags": [
              "user"
            ]
          }
        }
      },
      "servers": [
        {
          "url": "http://bobs.ngrok.io"
        }
      ]
    }
    
    

    and in the models/user.json file...

    {
      "components": {
        "schemas": {
          "user": {
            "type": "object",
            "description": "A user account.",
            "required": [
              "id",
              "email"
            ],
            "additionalProperties": false,
            "properties": {
              "id": {
                "type": "integer",
                "description": "Unique identifier for the given record."
              },
              "email": {
                "type": "string",
                "description": "The email associated to the given user."
              },
              "first_name": {
                "type": "string",
                "description": "User's first name."
              },
              "last_name": {
                "type": "string",
                "description": "User's last name."
              },
              "full_name": {
                "type": "string",
                "description": "Full user name"
              }
            }
          }
        }
      }
    }
    
    

    The model $ref is supported but apparently has to be nested to work...

    "user": { "$ref": "models/user.json#/components/schemas/user" },