Skip to content

Commit

Permalink
Document reusing schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
char0n committed Jan 3, 2020
1 parent 119b997 commit 4266983
Showing 1 changed file with 204 additions and 0 deletions.
204 changes: 204 additions & 0 deletions docs/reusing-schemas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# Reusing swagger schemas

When building an API it can be common to have schemas that are common to multiple actions.

## Scenario

For example, let's say we've got two endpoints:
- `GET /projects`
- `GET /books`

Both of these endpoints can have an erroneous responses like `401 Unauthorized`.
We don't want to define our new `Error` schema for both of these endpoints. This will create
duplicate code and we just want to keep our code [DRY](https://cs.wikipedia.org/wiki/Don%27t_repeat_yourself).

Here's what the swagger spec in our `ProjectsController` and `BookController` would look like:

```elixir
defmodule ProjectsController do
use HelloWeb, :controller
use PhoenixSwagger

swagger_path :index do
get "/projects"
produces "application/json"
parameter("Authorization", :header, :string, "OAuth2 access token", required: true)
parameters do
sort_by :query, :string, "The property to sort by"
sort_direction :query, :string, "The sort direction", enum: [:asc, :desc], default: :asc
company_id :string, :query, "The company id"
end
response(200, "OK", Schema.ref(:ListOfProjects))
response(401, "Unauthorized", Schema.ref(:Error))
end



@doc false
def swagger_definitions do
%{
ListOfProjects: ...schema definition...,
Error:
swagger_schema do
properties do
code(:string, "Error code", required: true)
message(:string, "Error message", required: true)
end
end
}
end
end
```

```elixir
defmodule BooksController do
use HelloWeb, :controller
use PhoenixSwagger

swagger_path :index do
get "/books"
produces "application/json"
parameter("Authorization", :header, :string, "OAuth2 access token", required: true)
parameters do
sort_by :query, :string, "The property to sort by"
sort_direction :query, :string, "The sort direction", enum: [:asc, :desc], default: :asc
company_id :string, :query, "The company id"
end
response(200, "OK", Schema.ref(:ListOfBooks))
response(401, "Unauthorized", Schema.ref(:Error))
end



@doc false
def swagger_definitions do
%{
ListOfBooks: ...schema definition...,
Error:
swagger_schema do
properties do
code(:string, "Error code", required: true)
message(:string, "Error message", required: true)
end
end
}
end
end
```

Our ProjectsController and BooksControllers have now identical `Error` schema defined in their modules.


## Extracting schemas into a module for reuse

We can easily extract common schemas into an ordinary elixir module. This module has to implement `PhoenixSwagger`
behaviour which means implementing the `swagger_definitions/0` function.

```elixir
defmodule CommonSchemas do
@moduledoc "Common schema declarations for phoenix swagger"

use PhoenixSwagger

@doc false
def swagger_definitions do
%{
Error:
swagger_schema do
properties do
code(:string, "Error code", required: true)
message(:string, "Error message", required: true)
end
end,
Errors:
swagger_schema do
properties do
errors(
Schema.new do
title("Errors")
description("A collection of Errors")
type(:array)
items(Schema.ref(:Error))
end
)
end
end
}
end
end
```

As you can see, it is also possible to reference other common schemas defined inside the `swagger_definitions/0`.

## Reusing the common schemas

Now, instead of defining the `Error` schema in every controller, we can just `alias` CommonSchema and
reference the `Error` schema by atom. It is also possible to call `CommonSchemas.swagger_definitions()[:Error]` to
get the reference to the Error schema, but it's verbose and unnecessary.

```elixir
defmodule ProjectsController do
use HelloWeb, :controller
use PhoenixSwagger
alias CommonSchemas

swagger_path :index do
get "/projects"
produces "application/json"
parameter("Authorization", :header, :string, "OAuth2 access token", required: true)
parameters do
sort_by :query, :string, "The property to sort by"
sort_direction :query, :string, "The sort direction", enum: [:asc, :desc], default: :asc
company_id :string, :query, "The company id"
end
response(200, "OK", Schema.ref(:ListOfProjects))
response(401, "Unauthorized", Schema.ref(:Error))
end



@doc false
def swagger_definitions do
%{
ListOfProjects: ...schema definition...
}
end
end
```

```elixir
defmodule BooksController do
use HelloWeb, :controller
use PhoenixSwagger

alias CommonSchemas

swagger_path :index do
get "/books"
produces "application/json"
parameter("Authorization", :header, :string, "OAuth2 access token", required: true)
parameters do
sort_by :query, :string, "The property to sort by"
sort_direction :query, :string, "The sort direction", enum: [:asc, :desc], default: :asc
company_id :string, :query, "The company id"
end
response(200, "OK", Schema.ref(:ListOfBooks))
response(401, "Unauthorized", Schema.ref(:Error))
end



@doc false
def swagger_definitions do
%{
ListOfBooks: ...schema definition...,
}
end
end
```

To avoid aliasing the `CommonSchemas` in every one of your controllers, find a `HelloWeb` module and add the alias
inside the `controller/0` function. This module will be called differently in your project depending on the the name of your
Phoenix project name: `<ProjectName>Web`. This file is the entrypoint for defining your web interface and is part
of every Phoenix installation.

0 comments on commit 4266983

Please sign in to comment.