|
| 1 | +# Open API Spex |
| 2 | + |
| 3 | +Add Open API Specification 3 (formerly swagger) to Plug applications. |
| 4 | + |
| 5 | +## Installation |
| 6 | + |
| 7 | +The package can be installed by adding `open_api_spex` to your list of dependencies in `mix.exs`: |
| 8 | + |
| 9 | +```elixir |
| 10 | +def deps do |
| 11 | + [ |
| 12 | + {:open_api_spex, github: "mbuhot/open_api_spex"} |
| 13 | + ] |
| 14 | +end |
| 15 | +``` |
| 16 | + |
| 17 | +## Usage |
| 18 | + |
| 19 | +Start by adding an `ApiSpec` module to your application. |
| 20 | + |
| 21 | +```elixir |
| 22 | +defmodule MyApp.ApiSpec do |
| 23 | + alias OpenApiSpex.{OpenApi, Server, Info, Paths} |
| 24 | + |
| 25 | + def spec do |
| 26 | + %OpenApi{ |
| 27 | + servers: [ |
| 28 | + # Populate the Server info from a phoenix endpoint |
| 29 | + Server.from_endpoint(MyAppWeb.Endpoint, otp_app: :my_app) |
| 30 | + ], |
| 31 | + info: %Info{ |
| 32 | + title: "My App", |
| 33 | + version: "1.0" |
| 34 | + }, |
| 35 | + # populate the paths from a phoenix router |
| 36 | + paths: Paths.from_router(MyAppWeb.Router) |
| 37 | + } |
| 38 | + |> OpenApiSpex.resolve_schema_modules() # discover request/response schemas from path specs |
| 39 | + end |
| 40 | +end |
| 41 | +``` |
| 42 | + |
| 43 | +For each plug (controller) that will handle api requests, add an `open_api_operation` callback. |
| 44 | +It will be passed the plug opts that were declared in the router, this will be the action for a phoenix controller. |
| 45 | + |
| 46 | +```elixir |
| 47 | +defmodule MyApp.UserController do |
| 48 | + alias OpenApiSpex.Operation |
| 49 | + |
| 50 | + @spec open_api_operation(any) :: Operation.t |
| 51 | + def open_api_operation(action), do: apply(__MODULE__, :"#{action}_operation", []) |
| 52 | + |
| 53 | + @spec show_operation() :: Operation.t |
| 54 | + def show_operation() do |
| 55 | + |
| 56 | + %Operation{ |
| 57 | + tags: ["users"], |
| 58 | + summary: "Show user", |
| 59 | + description: "Show a user by ID", |
| 60 | + operationId: "UserController.show", |
| 61 | + parameters: [ |
| 62 | + Operation.parameter(:id, :path, :integer, "User ID", example: 123) |
| 63 | + ], |
| 64 | + responses: %{ |
| 65 | + 200 => Operation.response("User", "application/json", Schemas.UserResponse) |
| 66 | + } |
| 67 | + } |
| 68 | + end |
| 69 | + def show(conn, %{"id" => id}) do |
| 70 | + {:ok, user} = MyApp.Users.find_by_id(id) |
| 71 | + json(conn, 200, user) |
| 72 | + end |
| 73 | +end |
| 74 | +``` |
| 75 | + |
| 76 | +Declare the JSON schemas for request/response bodies in a `Schemas` module: |
| 77 | + |
| 78 | +```elixir |
| 79 | +defmodule MyApp.Schemas do |
| 80 | + alias OpenApiSpex.Schema |
| 81 | + |
| 82 | + defmodule User do |
| 83 | + def schema do |
| 84 | + %Schema{ |
| 85 | + title: "User", |
| 86 | + description: "A user of the app", |
| 87 | + type: :object, |
| 88 | + properties: %{ |
| 89 | + id: %Schema{type: :integer, description: "User ID"}, |
| 90 | + name: %Schema{type: :string, description: "User name"}, |
| 91 | + email: %Schema{type: :string, description: "Email address", format: :email}, |
| 92 | + inserted_at: %Schema{type: :string, description: "Creation timestamp", format: :datetime}, |
| 93 | + updated_at: %Schema{type: :string, description: "Update timestamp", format: :datetime} |
| 94 | + } |
| 95 | + } |
| 96 | + end |
| 97 | + end |
| 98 | + |
| 99 | + defmodule UserResponse do |
| 100 | + def schema do |
| 101 | + %Schema{ |
| 102 | + title: "UserResponse", |
| 103 | + description: "Response schema for single user", |
| 104 | + type: :object, |
| 105 | + properties: %{ |
| 106 | + data: User |
| 107 | + } |
| 108 | + } |
| 109 | + end |
| 110 | + end |
| 111 | +end |
| 112 | +``` |
| 113 | + |
| 114 | +Now you can create a mix task to write the swagger file to disk: |
| 115 | + |
| 116 | +```elixir |
| 117 | +defmodule Mix.Tasks.MyApp.OpenApiSpec do |
| 118 | + def run([output_file]) do |
| 119 | + json = |
| 120 | + MyApp.ApiSpec.spec() |
| 121 | + |> Poison.encode!(pretty: true) |
| 122 | + |
| 123 | + :ok = File.write!(output_file, json) |
| 124 | + end |
| 125 | +end |
| 126 | +``` |
| 127 | + |
| 128 | +Generate the file with: `mix myapp.openapispec spec.json` |
| 129 | + |
| 130 | +You can also serve the swagger through a controller: |
| 131 | + |
| 132 | +```elixir |
| 133 | +defmodule MyApp.OpenApiSpecController do |
| 134 | + def show(conn, _params) do |
| 135 | + spec = |
| 136 | + MyApp.ApiSpec.spec() |
| 137 | + |
| 138 | + json(conn, spec) |
| 139 | + end |
| 140 | +end |
| 141 | +``` |
| 142 | + |
| 143 | +TODO: SwaggerUI 3.0 |
| 144 | + |
| 145 | +TODO: Request Validation |
| 146 | + |
| 147 | +TODO: Validating examples in the spec |
| 148 | + |
| 149 | +TODO: Validating responses in tests |
0 commit comments