Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85804103
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
8 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
R22 open_api_spex
Attached
Detach File
Event Timeline
Log In to Comment