Skip to content

Commit 22fcbcd

Browse files
committed
Open Api Specifications
0 parents  commit 22fcbcd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1810
-0
lines changed

.gitignore

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# The directory Mix will write compiled artifacts to.
2+
/_build/
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover/
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps/
9+
10+
# Where 3rd-party dependencies like ExDoc output generated docs.
11+
/doc/
12+
13+
# Ignore .fetch files in case you like to edit your project deps locally.
14+
/.fetch
15+
16+
# If the VM crashes, it generates a dump, let's ignore it too.
17+
erl_crash.dump
18+
19+
# Also ignore archive artifacts (built via "mix archive.build").
20+
*.ez

LICENSE

+362
Large diffs are not rendered by default.

README.md

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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

config/config.exs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This file is responsible for configuring your application
2+
# and its dependencies with the aid of the Mix.Config module.
3+
use Mix.Config
4+
5+
# This configuration is loaded before any dependency and is restricted
6+
# to this project. If another project depends on this project, this
7+
# file won't be loaded nor affect the parent project. For this reason,
8+
# if you want to provide default values for your application for
9+
# 3rd-party users, it should be done in your "mix.exs" file.
10+
11+
# You can configure your application as:
12+
#
13+
# config :open_api_spex, key: :value
14+
#
15+
# and access this configuration in your application as:
16+
#
17+
# Application.get_env(:open_api_spex, :key)
18+
#
19+
# You can also configure a 3rd-party app:
20+
#
21+
# config :logger, level: :info
22+
#
23+
24+
# It is also possible to import configuration files, relative to this
25+
# directory. For example, you can emulate configuration per environment
26+
# by uncommenting the line below and defining dev.exs, test.exs and such.
27+
# Configuration from the imported file will override the ones defined
28+
# here (which is why it is important to import them last).
29+
#
30+
# import_config "#{Mix.env}.exs"

lib/open_api_spex.ex

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
defmodule OpenApiSpex do
2+
alias OpenApiSpex.{OpenApi, SchemaResolver}
3+
4+
@moduledoc """
5+
"""
6+
def resolve_schema_modules(spec = %OpenApi{}) do
7+
SchemaResolver.resolve_schema_modules(spec)
8+
end
9+
10+
11+
end

lib/open_api_spex/callback.ex

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
defmodule OpenApiSpex.Callback do
2+
alias OpenApiSpex.PathItem
3+
@type t :: %{
4+
String.t => PathItem.t
5+
}
6+
end

lib/open_api_spex/components.ex

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
defmodule OpenApiSpex.Components do
2+
alias OpenApiSpex.{
3+
Schema, Reference, Response, Parameter, Example,
4+
RequestBody, Header, SecurityScheme, Link, Callback
5+
}
6+
defstruct [
7+
:schemas,
8+
:responses,
9+
:parameters,
10+
:examples,
11+
:requestBodies,
12+
:headers,
13+
:securitySchemes,
14+
:links,
15+
:callbacks,
16+
]
17+
@type t :: %{
18+
schemas: %{String.t => Schema.t | Reference.t},
19+
responses: %{String.t => Response.t | Reference.t},
20+
parameters: %{String.t => Parameter.t | Reference.t},
21+
examples: %{String.t => Example.t | Reference.t},
22+
requestBodies: %{String.t => RequestBody.t | Reference.t},
23+
headers: %{String.t => Header.t | Reference.t},
24+
securitySchemes: %{String.t => SecurityScheme.t | Reference.t},
25+
links: %{String.t => Link.t | Reference.t},
26+
callbacks: %{String.t => Callback.t | Reference.t}
27+
}
28+
end

lib/open_api_spex/contact.ex

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
defmodule OpenApiSpex.Contact do
2+
defstruct [
3+
:name,
4+
:url,
5+
:email
6+
]
7+
@type t :: %__MODULE__{
8+
name: String.t,
9+
url: String.t,
10+
email: String.t
11+
}
12+
end

lib/open_api_spex/discriminator.ex

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
defmodule OpenApiSpex.Discriminator do
2+
defstruct [
3+
:propertyName,
4+
:mapping
5+
]
6+
@type t :: %__MODULE__{
7+
propertyName: String.t,
8+
mapping: %{String.t => String.t}
9+
}
10+
end

lib/open_api_spex/encoding.ex

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
defmodule OpenApiSpex.Encoding do
2+
alias OpenApiSpex.{Header, Reference, Parameter}
3+
defstruct [
4+
:contentType,
5+
:headers,
6+
:style,
7+
:explode,
8+
:allowReserved
9+
]
10+
@type t :: %__MODULE__{
11+
contentType: String.t,
12+
headers: %{String.t => Header.t | Reference.t},
13+
style: Parameter.style,
14+
explode: boolean,
15+
allowReserved: boolean
16+
}
17+
end

lib/open_api_spex/example.ex

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
defmodule OpenApiSpex.Example do
2+
defstruct [
3+
:summary,
4+
:description,
5+
:value,
6+
:externalValue
7+
]
8+
@type t :: %{
9+
summary: String.t,
10+
description: String.t,
11+
value: any,
12+
externalValue: String.t
13+
}
14+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
defmodule OpenApiSpex.ExternalDocumentation do
2+
defstruct [
3+
:description,
4+
:url
5+
]
6+
@type t :: %__MODULE__{
7+
description: String.t,
8+
url: String.t
9+
}
10+
end

lib/open_api_spex/header.ex

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule OpenApiSpex.Header do
2+
alias OpenApiSpex.{Schema, Reference, Example}
3+
defstruct [
4+
:description,
5+
:required,
6+
:deprecated,
7+
:allowEmptyValue,
8+
:explode,
9+
:schema,
10+
:example,
11+
:examples,
12+
style: :simple
13+
]
14+
@type t :: %__MODULE__{
15+
description: String.t,
16+
required: boolean,
17+
deprecated: boolean,
18+
allowEmptyValue: boolean,
19+
style: :simple,
20+
explode: boolean,
21+
schema: Schema.t | Reference.t,
22+
example: any,
23+
examples: %{String.t => Example.t | Reference.t}
24+
}
25+
end

lib/open_api_spex/info.ex

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
defmodule OpenApiSpex.Info do
2+
alias OpenApiSpex.{Contact, License}
3+
@enforce_keys [:title, :version]
4+
defstruct [
5+
:title,
6+
:description,
7+
:termsOfService,
8+
:contact,
9+
:license,
10+
:version
11+
]
12+
@type t :: %__MODULE__{
13+
title: String.t,
14+
description: String.t,
15+
termsOfService: String.t,
16+
contact: Contact.t,
17+
license: License.t,
18+
version: String.t
19+
}
20+
end

lib/open_api_spex/license.ex

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
defmodule OpenApiSpex.License do
2+
defstruct [
3+
:name,
4+
:url
5+
]
6+
@type t :: %__MODULE__{
7+
name: String.t,
8+
url: String.t
9+
}
10+
end

0 commit comments

Comments
 (0)