Page MenuHomePhorge

No OneTemporary

Size
8 KB
Referenced Files
None
Subscribers
None
diff --git a/README.md b/README.md
index 322b3dd..0102857 100644
--- a/README.md
+++ b/README.md
@@ -1,191 +1,199 @@
# Open API Spex
Add Open API Specification 3 (formerly swagger) to Plug applications.
## Installation
The package can be installed by adding `open_api_spex` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:open_api_spex, github: "mbuhot/open_api_spex"}
]
end
```
## Generating an API spec
Start by adding an `ApiSpec` module to your application.
```elixir
defmodule MyApp.ApiSpec do
alias OpenApiSpex.{OpenApi, Server, Info, Paths}
def spec do
%OpenApi{
servers: [
# Populate the Server info from a phoenix endpoint
Server.from_endpoint(MyAppWeb.Endpoint, otp_app: :my_app)
],
info: %Info{
title: "My App",
version: "1.0"
},
# populate the paths from a phoenix router
paths: Paths.from_router(MyAppWeb.Router)
}
|> OpenApiSpex.resolve_schema_modules() # discover request/response schemas from path specs
end
end
```
For each plug (controller) that will handle api requests, add an `open_api_operation` callback.
It will be passed the plug opts that were declared in the router, this will be the action for a phoenix controller.
```elixir
defmodule MyApp.UserController do
alias OpenApiSpex.Operation
@spec open_api_operation(any) :: Operation.t
def open_api_operation(action), do: apply(__MODULE__, :"#{action}_operation", [])
@spec show_operation() :: Operation.t
def show_operation() do
%Operation{
tags: ["users"],
summary: "Show user",
description: "Show a user by ID",
operationId: "UserController.show",
parameters: [
Operation.parameter(:id, :path, :integer, "User ID", example: 123)
],
responses: %{
200 => Operation.response("User", "application/json", Schemas.UserResponse)
}
}
end
def show(conn, %{"id" => id}) do
{:ok, user} = MyApp.Users.find_by_id(id)
json(conn, 200, user)
end
end
```
Declare the JSON schemas for request/response bodies in a `Schemas` module:
```elixir
defmodule MyApp.Schemas do
alias OpenApiSpex.Schema
defmodule User do
def schema do
%Schema{
title: "User",
description: "A user of the app",
type: :object,
properties: %{
id: %Schema{type: :integer, description: "User ID"},
name: %Schema{type: :string, description: "User name"},
email: %Schema{type: :string, description: "Email address", format: :email},
inserted_at: %Schema{type: :string, description: "Creation timestamp", format: :datetime},
updated_at: %Schema{type: :string, description: "Update timestamp", format: :datetime}
}
}
end
end
defmodule UserResponse do
def schema do
%Schema{
title: "UserResponse",
description: "Response schema for single user",
type: :object,
properties: %{
data: User
}
}
end
end
end
```
Now you can create a mix task to write the swagger file to disk:
```elixir
defmodule Mix.Tasks.MyApp.OpenApiSpec do
def run([output_file]) do
json =
MyApp.ApiSpec.spec()
|> Poison.encode!(pretty: true)
:ok = File.write!(output_file, json)
end
end
```
Generate the file with: `mix myapp.openapispec spec.json`
## Serving the API Spec from a Controller
+To serve the API spec from your application, first add the `OpenApiSpex.Plug.PutApiSpec` plug somewhere in the pipeline.
+
```elixir
-defmodule MyApp.OpenApiSpecController do
- def show(conn, _params) do
- spec =
- MyApp.ApiSpec.spec()
+ pipeline :api do
+ plug OpenApiSpex.Plug.PutApiSpec, module: MyApp.ApiSpec
+ end
+```
- json(conn, spec)
+Now the spec will be available for use in downstream plugs.
+The `OpenApiSpex.Plug.RenderSpec` plug will render the spec as JSON:
+
+```elixir
+ scope "/api" do
+ pipe_through :api
+ resources "/users", MyApp.UserController, only: [:create, :index, :show]
+ get "/openapi", OpenApiSpex.Plug.RenderSpec, []
end
-end
```
## Use the API Spec to cast params
Add the `OpenApiSpex.Plug.Cast` plug to a controller to cast the request parameters to elixir types defined by the operation schema.
```elixir
plug OpenApiSpex.Plug.Cast, operation_id: "UserController.show"
```
The `operation_id` can be inferred when used from a Phoenix controller from the contents of `conn.private`.
```elixir
defmodule MyApp.UserController do
use MyAppWeb, :controller
alias OpenApiSpex.Operation
plug OpenApiSpex.Plug.Cast
def open_api_operation(action) do
apply(__MODULE__, "#{action}_operation", [])
end
def create_operation do
import Operation
%Operation{
tags: ["users"],
summary: "Create user",
description: "Create a user",
operationId: "UserController.create",
parameters: [],
requestBody: request_body("The user attributes", "application/json", Schemas.UserRequest),
responses: %{
201 => response("User", "application/json", Schemas.UserResponse)
}
}
end
def create(conn, %{user: %{name: name, email: email, birthday: birthday = %Date{}}}) do
# params will have atom keys with values cast to standard elixir types
end
end
```
TODO: SwaggerUI 3.0
TODO: Request Validation
TODO: Validating examples in the spec
TODO: Validating responses in tests
diff --git a/lib/open_api_spex/plug/render_spec.ex b/lib/open_api_spex/plug/render_spec.ex
new file mode 100644
index 0000000..df5fb8f
--- /dev/null
+++ b/lib/open_api_spex/plug/render_spec.ex
@@ -0,0 +1,12 @@
+defmodule OpenApiSpex.Plug.RenderSpec do
+ @moduledoc """
+ Renders the API spec stored earlier in the Conn by `OpenApiSpex.Plug.PutApiSpec`
+ """
+
+ def init(opts), do: opts
+ def call(conn, _opts) do
+ conn
+ |> Plug.Conn.put_resp_content_type("application/json")
+ |> Plug.Conn.send_resp(200, Poison.encode!(conn.private.open_api_spex.spec))
+ end
+end
\ No newline at end of file
diff --git a/mix.exs b/mix.exs
index 7ccb5b5..3d98667 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,32 +1,33 @@
defmodule OpenApiSpex.Mixfile do
use Mix.Project
def project do
[
app: :open_api_spex,
version: "0.1.0",
elixir: "~> 1.5",
elixirc_paths: elixirc_paths(Mix.env),
start_permanent: Mix.env == :prod,
deps: deps()
]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:poison, ">= 0.0.0"},
+ {:plug, ">= 0.0.0"},
{:phoenix, "~> 1.3", only: :test}
]
end
end
diff --git a/test/support/open_api_spec_controller.ex b/test/support/open_api_spec_controller.ex
deleted file mode 100644
index 6827c88..0000000
--- a/test/support/open_api_spec_controller.ex
+++ /dev/null
@@ -1,8 +0,0 @@
-defmodule OpenApiSpexTest.OpenApiSpecController do
- alias OpenApiSpexTest.ApiSpec
-
- def init(:show), do: :show
- def call(conn, :show) do
- Phoenix.Controller.json(conn, ApiSpec.spec())
- end
-end
\ No newline at end of file
diff --git a/test/support/router.ex b/test/support/router.ex
index 7ffe161..1ae80fa 100644
--- a/test/support/router.ex
+++ b/test/support/router.ex
@@ -1,14 +1,14 @@
defmodule OpenApiSpexTest.Router do
use Phoenix.Router
pipeline :api do
- plug Plug.Parsers, parsers: [:json], pass: ["text/*"], json_decoder: Poison
plug OpenApiSpex.Plug.PutApiSpec, module: OpenApiSpexTest.ApiSpec
+ plug Plug.Parsers, parsers: [:json], pass: ["text/*"], json_decoder: Poison
end
- scope "/api", OpenApiSpexTest do
+ scope "/api" do
pipe_through :api
- resources "/users", UserController, only: [:create, :index, :show]
- get "/openapi", OpenApiSpecController, :show
+ resources "/users", OpenApiSpexTest.UserController, only: [:create, :index, :show]
+ get "/openapi", OpenApiSpex.Plug.RenderSpec, []
end
end
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Fri, Oct 9, 9:50 PM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1784894
Default Alt Text
(8 KB)

Event Timeline