Swagger Inheritance

Swagger spec is an awesome tool for describing APIs in a standardized way, allowing programs to understand and interpret them. It is a set of files (often just one) written in JSON or yaml that defines the routes, parameters, responses, and metadata of an API. After a spec has been written it can be used to generate clients, create human-friendly documentation, automate testing, or even scaffold code for the application code itself. I've been using Swagger for both work and personal projects over the last year and have really enjoyed both the utilities it offers and the strict adherence to interface that it promotes. One thing that has given me grief, though, is how loosely some utilities respect inheritance.

See, the Swagger spec offers a few different forms of inheritance. Most of these are to help developers avoid the dreaded repetitive code problem, though some are just to help keep things organized within a document. A few missteps could lead to circular issue, but I'm getting ahead of myself. The problem I've run into is with resolving the different inheritance methods. The application that I'm building shouldn't have to jump around the spec looking for different references - that is domain logic that should be encapsulated in a Swagger-aware utility.

So, what are the different forms of inheritance?

Path Creation

The first, and most basic one, is path creation. In the root document of the schema are two fields, both optional, which impact path routing: 'host' and 'basepath'. These two attributes help define the routes supported by the application. For example, this psuedo-schema has a route defined that is actually constructed by three separate areas.

  1. {

  2. "host": "http://domain.tld",

  3. "basePath": "/v1",

  4. "paths": {

  5. "/info": ...

  6. }

  7. }

  8. // resulting route

  9. // http://domain.tld/v1/info

Overriding Attributes

Some attributes defined are applied liberally to all nested objects unless they are overridden further down. This includes 'produces', 'consumes', 'schemes', and 'security' in the root document, and then 'parameters' in the path item level. All of these definitions are inherited by nested operations and can be either completely overridden or additive in nature. For example, if 'schemes' is defined in both the root and operation then the root will be ignored by the operation, but if 'parameters' is defined in both the path item and operation then the operation merges the definitions.

  1. {

  2. "schemes": ["http"],

  3. "paths": {

  4. "/resource/{id}": {

  5. "parameters": [

  6. {

  7. "name": "id",

  8. "in": "path"

  9. }

  10. ],

  11. "get": {

  12. "schemes": [],

  13. "parameters": [

  14. {

  15. "name": "view",

  16. "in": "query"

  17. }

  18. ]

  19. }

  20. }

  21. }

  22. }

  23. // for GET /resource/{id}

  24. // allowed schemes: any

  25. // parameters: 'id' and 'view'

Here is where things start to get fun. If I'm writing a validation piece I don't want to jump in between the path item and operation to get a list of the applicable parameters, nor do I want to want to compare the root with the operation to get allowed mime types. Ideally, there would be a Swagger parser that would bubble all this through so that my validation piece would just ask 'What parameters do I need to be aware of?' and the parser would hand over a list of resolved parameters.

Reference Objects

Oh, did I say that things were starting to get fun? Reference objects are fun. A spec can list out shared parameter, response, and schema definitions within the appropriate locations in the root and then use JSON Pointers to reference them. This helps a lot with encapsulating similar pieces into one location and avoiding repetitious definitions but does mean that either each utility or, preferably, a parser needs to be able to understand and resolve pointers.

  1. {

  2. "paths": {

  3. "/resource/{id}": {

  4. "parameters": [

  5. "$ref": "#/parameters/Identifier"

  6. ]

  7. }

  8. },

  9. "parameters": {

  10. "Identifier": {

  11. "name": "id",

  12. "in": "path"

  13. }

  14. }

  15. }

  16. // this acts as if the definition was injected above

Composition

There are several forms of model composition allowed by Swagger, the most popular of which is the 'allOf'. Instead of using a reference to define a parameter or response, it takes the reference and merges it with a schema. There is also the 'discriminator' field to infer hierarchy between composed models. I haven't played much with these attributes, though I've definitely wondered if the increased complexity here is worth the coolness factor.

  1. {

  2. "definitions": {

  3. "BasicModel": {

  4. "type": "object",

  5. "properties": {

  6. "name": {

  7. "type": "string"

  8. }

  9. }

  10. },

  11. "ComplexModel": {

  12. "allOf": [

  13. {

  14. "$ref": "#/definitions/BasicModel"

  15. },

  16. {

  17. "type": "object",

  18. "properties": {

  19. "value": {

  20. "type": "string"

  21. }

  22. }

  23. }

  24. ]

  25. }

  26. }

  27. }

One of the reasons why I've been wanting to divine out these cases is because I'm building a shiny new framework that runs off of a Swagger spec. This framework will need to magically do routing and validation, among other things, based off of the defined spec and I've had a terrible time finding a parser that covers all of these cases. After some Swagger-flavored logic leaked into my framework one time too many I decided to build my own parser that would take a spec and figure out all this inheritance stuff so that my code could just pass around a fully resolved object instead of trying to do tricky stuff. I've been making some good progress on these fronts, so hopefully I'll be sharing more on this front over the coming months.