splitting swagger definition across many files
Asked Answered
P

4

5

Question: how can I split swagger definition across files? What are the possibilities in that area? The question details are described below:

example of what I want - in RAML

I do have experience in RAML and what I do is, for example:

/settings:
  description: |
    This resource defines application & components configuration
  get:
    is: [ includingCustomHeaders ]
    description: |
      Fetch entire configuration
    responses:
      200:
        body:
          example: !include samples/settings.json
          schema: !include schemas/settings.json

The last two lines are important here - theones with !include <filepath> - in RAML I can split my entire contract into many files that just get included dynamically by the RAML parser (and RAML parser is used by all tools that base on RAML).

My benefit from this is that:

  • I get my contract more clear and easier to maintain, because schemas are not inline
  • but that's really important: I can reuse the schema files within other tools to do validation, mock generation, stubs, generate tests, etc. In other words, this way I can reuse schema files within both contract (RAML, this case) and other tools (non-RAML, non-swagger, just JSONschema-based ones).

back to Swagger

As far as I read, swagger supports $ref keyword which allows to load external files. But is that files fetched through HTTP/AJAX or can they just be local files?

And is that supported by the whole specification or is it just some tools that support it and some that don't?

What I found here is that the input for swagger has to be one file. And this is extremely inconvenient for big projects:

  • because of size
  • and because I can't reuse the schema if I want to use something non-swagger

Or, in other words, can I achieve the same with swagger, that I can with RAML - in terms of splitting files?

Packthread answered 22/2, 2016 at 11:24 Comment(0)
A
6

The specification allows for references in multiple locations but not everywhere. These references are resolved depending on where the specification is being hosted--and what you're trying to do.

For something like rendering a dynamic user interface, then yes you do need to eventually load the entire definition into "a single object" which may be composed from many files. If performing a code generation, the definitions may be loaded directly from the file system. But ultimately there are swagger parsers doing the resolution, which is much more fine grained and controllable in Swagger than other definition formats.

In your case, you would use a JSON pointer to the schema reference:

responses:
  200:
    description: the response
    schema:

via local reference

      $ref: '#/definitions/myModel'

via absolute reference:

      $ref: 'http://path/to/your/resource'

via relative reference, which would be 'relative to where this doc is loaded'

      $ref: 'resource.json#/myModel

via inline definition

      type: object
      properties:
        id:
          type: string
Accommodate answered 22/2, 2016 at 15:58 Comment(2)
I like your answer, but I'm missing one thing - I don't specifically know which exact tools for certain job will I use, e.g. for docs generating. What can I do if I split files and the tool I want will need a single file? Is there any recommended/standard way to combine (build) a swagger mutli-file definition into one, so that I keep separate files in my repository + I can dynamically build the single file one?Packthread
That is an excellent question. There are a lot of tools out there and they have various levels of support of the swagger specification, as one would guess. If you go with official swagger-api OSS tools, then lack of support of the spec is a defect and will be fixed. The mechanism for splitting up specs really depends on the business needs so currently not very standard. With tooling including (swaggerhub) there will be standard practices being formed fairly quickly, because these tools will facilitate the whole process.Accommodate
E
2

When I split OpenAPI V3 files using references, I try to avoid the sock drawer anti-pattern and instead use functional groupings for the YAML files.

I also make it so that each YAML file itself is a valid OpenAPI V3 spec.

I start out with the openapi.yaml file.

openapi: 3.0.3
info:
  title: MyAPI
  description: |
    This is the public API for my stuff.
  version: "3"
tags:
  # NOTE: the name is needed as the info block uses `title` rather than name
  - name: Authentication
    $ref: 'authn.yaml#/info'
paths:
  # NOTE: here are the references to the other OpenAPI files 
  #  from the path.  Note because OpenAPI requires paths to
  #  start with `/` and that is already used as a separator
  #  replace the `/` with `%2F` for the path reference.
  '/authn/start':
    $ref: 'authn.yaml#/paths/%2Fstart'

Then in the functional group:

openapi: 3.0.3
info:
  title: Authentication
  description: |
    This is the authentication module.
  version: "3"
paths:
  # NOTE: don't include the `/authn` prefix here that top level grouping is
  #  in the `openapi.yaml` file.
  '/start':
    get:
      responses:
        "200":
          description: OK

By doing this separation you can independently test each file or the whole API as a group.

There may be points where you repeat yourself, but by doing this you limit the chance of breaking changes to other API endpoints when using a "common" library.

However, you should still have a common definition library for some things such as:

  • errors
  • security

There is a limitation on this approach and that's the "Discriminators" (it may be a ReDoc issue though, but if you had types that have discriminators outside of the openapi.yaml ReDoc fails to render correctly.

Ephor answered 11/4, 2020 at 17:57 Comment(0)
U
1

See this answer for details on how to split your Swagger documentation across many files. This is done using JSON, but the same concept can apply to RAML.

EDIT: Adding content of link here

The basic structure of your Swagger JSON should look something like this:

{
"swagger": "2.0",
"info": {
    "title": "",
    "version": "version number here"
},
"basePath": "/",
"host": "host goes here",
"schemes": [
    "http"
],
"produces": [
    "application/json"
],
"paths": {},
"definitions": {}
}

The paths and definitions are where you need to insert the paths that your API supports and the model definitions describing your response objects. You can populate these objects dynamically. One way of doing this could be to have a separate file for each entity's paths and models.

Let's say one of the objects in your API is a "car".

Path:

{
"paths": {
    "/cars": {
        "get": {
            "tags": [
                "Car"
            ],
            "summary": "Get all cars",
            "description": "Returns all of the cars.",             
            "responses": {
                "200": {
                    "description": "An array of cars",
                    "schema": {
                        "type": "array",
                        "items": {
                            "$ref": "#/definitions/car"
                        }
                    }
                },
                "404": {
                    "description": "error fetching cars",
                    "schema": {
                        "$ref": "#/definitions/error"
                    }
                }
            }
        }
    }
}

Model:

{
"car": {
    "properties": {
        "_id": {
            "type": "string",
            "description": "car unique identifier"
        },
        "make": {
            "type": "string",
            "description": "Make of the car"
        },
        "model":{
            "type": "string",
            "description": "Model of the car."
        }
    }
}
}

You could then put each of these in their own files. When you start your server, you could grab these two JSON objects, and append them to the appropriate object in your base swagger object (either paths or definitions) and serve that base object as your Swagger JSON object.

You could also further optimize this by only doing the appending once when the server is started (since the API documentation will not change while the server is running). Then, when when the "serve Swagger docs" endpoint is hit, you can just return the cached Swagger JSON object that you created when the server was started.

The "serve Swagger docs" endpoint can be intercepted by catching a request to /api-docs like below:

app.get('/api-docs', function(req, res) {
  // return the created Swagger JSON object here
});
Underpants answered 22/2, 2016 at 16:14 Comment(2)
I didn't downvote. However, What I'm missing in your reply in the link is how do you load the external reference (local file), e.g. "$ref": "#/definitions/car"? Is it supported by swagger or do you have to do it manually? And how do you include the file into final swagger - manually as well? Honestly, I know I can manually do everything, but I'm looking for a standard solution.Packthread
$ref": "#/definitions/car is not a file, it is a model object. So you would need to have the car model defined in the "models" in your swagger objects. You could include the definition for the "car" model in another file, but in the end it must be loaded into the swagger file that is served from the /api-docs endpoint.Underpants
D
0

You can use $ref but not have good flexibility, I suggest you process YAML with an external tool like 'Yamlinc' that mix multiple files into one using '$include' tag.

read more: https://github.com/javanile/yamlinc

Dispose answered 13/2, 2018 at 23:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.