Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F116175
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
15 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/open_api_spex.ex b/lib/open_api_spex.ex
index 3fa9c2e..941acc5 100644
--- a/lib/open_api_spex.ex
+++ b/lib/open_api_spex.ex
@@ -1,126 +1,127 @@
defmodule OpenApiSpex do
@moduledoc """
Provides the entry-points for defining schemas, validating and casting.
"""
alias OpenApiSpex.{OpenApi, Operation, Reference, Schema, SchemaResolver}
@doc """
Adds schemas to the api spec from the modules specified in the Operations.
Eg, if the response schema for an operation is defined with:
responses: %{
200 => Operation.response("User", "application/json", UserResponse)
}
Then the `UserResponse.schema()` function will be called to load the schema, and
a `Reference` to the loaded schema will be used in the operation response.
See `OpenApiSpex.schema` macro for a convenient syntax for defining schema modules.
"""
@spec resolve_schema_modules(OpenApi.t) :: OpenApi.t
def resolve_schema_modules(spec = %OpenApi{}) do
SchemaResolver.resolve_schema_modules(spec)
end
@doc """
Cast params to conform to a `OpenApiSpex.Schema`.
See `OpenApiSpex.Schema.cast/3` for additional examples and details.
"""
@spec cast(OpenApi.t, Schema.t | Reference.t, any) :: {:ok, any} | {:error, String.t}
def cast(spec = %OpenApi{}, schema = %Schema{}, params) do
Schema.cast(schema, params, spec.components.schemas)
end
def cast(spec = %OpenApi{}, schema = %Reference{}, params) do
Schema.cast(schema, params, spec.components.schemas)
end
@doc """
Cast all params in `Plug.Conn` to conform to the schemas for `OpenApiSpex.Operation`.
Returns `{:ok, Plug.Conn.t}` with `params` and `body_params` fields updated if successful,
or `{:error, reason}` if casting fails.
`content_type` may optionally be supplied to select the `requestBody` schema.
"""
@spec cast(OpenApi.t, Operation.t, Plug.Conn.t, content_type | nil) :: {:ok, Plug.Conn.t} | {:error, String.t}
when content_type: String.t
def cast(spec = %OpenApi{}, operation = %Operation{}, conn = %Plug.Conn{}, content_type \\ nil) do
Operation.cast(operation, conn, content_type, spec.components.schemas)
end
@doc """
Validate params against `OpenApiSpex.Schema`.
See `OpenApiSpex.Schema.validate/3` for examples of error messages.
"""
@spec validate(OpenApi.t, Schema.t | Reference.t, any) :: :ok | {:error, String.t}
def validate(spec = %OpenApi{}, schema = %Schema{}, params) do
Schema.validate(schema, params, spec.components.schemas)
end
def validate(spec = %OpenApi{}, schema = %Reference{}, params) do
Schema.validate(schema, params, spec.components.schemas)
end
@doc """
Validate all params in `Plug.Conn` against `OpenApiSpex.Operation` `parameter` and `requestBody` schemas.
`content_type` may be optionally supplied to select the `requestBody` schema.
"""
@spec validate(OpenApi.t, Operation.t, Plug.Conn.t, content_type | nil) :: :ok | {:error, String.t}
when content_type: String.t
def validate(spec = %OpenApi{}, operation = %Operation{}, conn = %Plug.Conn{}, content_type \\ nil) do
Operation.validate(operation, conn, content_type, spec.components.schemas)
end
@doc """
Declares a struct based `OpenApiSpex.Schema`
- defines the schema/0 callback
- ensures the schema is linked to the module by "x-struct" extension property
- defines a struct with keys matching the schema properties
- defines a @type `t` for the struct
- derives a `Poison.Encoder` for the struct
See `OpenApiSpex.Schema` for additional examples and details.
## Example
require OpenApiSpex
defmodule User do
OpenApiSpex.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", pattern: ~r/[a-zA-Z][a-zA-Z0-9_]+/},
email: %Schema{type: :string, description: "Email address", format: :email},
inserted_at: %Schema{type: :string, description: "Creation timestamp", format: :'date-time'},
updated_at: %Schema{type: :string, description: "Update timestamp", format: :'date-time'}
},
required: [:name, :email],
example: %{
"id" => 123,
"name" => "Joe User",
"email" => "joe@gmail.com",
"inserted_at" => "2017-09-12T12:34:55Z",
"updated_at" => "2017-09-13T10:11:12Z"
}
}
end
"""
defmacro schema(body) do
quote do
@behaviour OpenApiSpex.Schema
@schema struct(OpenApiSpex.Schema, Map.put(unquote(body), :"x-struct", __MODULE__))
def schema, do: @schema
- @derive [Poison.Encoder]
+
+ @derive Enum.filter([Poison.Encoder, Jason.Encoder], &Code.ensure_loaded?/1)
defstruct Schema.properties(@schema)
@type t :: %__MODULE__{}
end
end
end
diff --git a/lib/open_api_spex/open_api.ex b/lib/open_api_spex/open_api.ex
index d0fc0bc..8bf48b7 100644
--- a/lib/open_api_spex/open_api.ex
+++ b/lib/open_api_spex/open_api.ex
@@ -1,66 +1,70 @@
defmodule OpenApiSpex.OpenApi do
@moduledoc """
Defines the `OpenApiSpex.OpenApi.t` type.
"""
alias OpenApiSpex.{
Info, Server, Paths, Components,
SecurityRequirement, Tag, ExternalDocumentation,
OpenApi
}
@enforce_keys [:info, :paths]
defstruct [
openapi: "3.0.0",
info: nil,
servers: [],
paths: nil,
components: nil,
security: [],
tags: [],
externalDocs: nil
]
@typedoc """
[OpenAPI Object](https://swagger.io/specification/#oasObject)
This is the root document object of the OpenAPI document.
"""
@type t :: %OpenApi{
openapi: String.t,
info: Info.t,
servers: [Server.t] | nil,
paths: Paths.t,
components: Components.t | nil,
security: [SecurityRequirement.t] | nil,
tags: [Tag.t] | nil,
externalDocs: ExternalDocumentation.t | nil
}
- defimpl Poison.Encoder do
- def encode(api_spec = %OpenApi{}, options) do
- api_spec
- |> to_json()
- |> Poison.Encoder.encode(options)
- end
+ for encoder <- [Poison.Encoder, Jason.Encoder] do
+ if Code.ensure_loaded?(encoder) do
+ defimpl encoder do
+ def encode(api_spec = %OpenApi{}, options) do
+ api_spec
+ |> to_json()
+ |> unquote(encoder).encode(options)
+ end
- defp to_json(%Regex{source: source}), do: source
- defp to_json(value = %{__struct__: _}) do
- value
- |> Map.from_struct()
- |> to_json()
- end
- defp to_json(value) when is_map(value) do
- value
- |> Stream.map(fn {k,v} -> {to_string(k), to_json(v)} end)
- |> Stream.filter(fn {_, nil} -> false; _ -> true end)
- |> Enum.into(%{})
- end
- defp to_json(value) when is_list(value) do
- Enum.map(value, &to_json/1)
+ defp to_json(%Regex{source: source}), do: source
+ defp to_json(value = %{__struct__: _}) do
+ value
+ |> Map.from_struct()
+ |> to_json()
+ end
+ defp to_json(value) when is_map(value) do
+ value
+ |> Stream.map(fn {k,v} -> {to_string(k), to_json(v)} end)
+ |> Stream.filter(fn {_, nil} -> false; _ -> true end)
+ |> Enum.into(%{})
+ end
+ defp to_json(value) when is_list(value) do
+ Enum.map(value, &to_json/1)
+ end
+ defp to_json(nil), do: nil
+ defp to_json(true), do: true
+ defp to_json(false), do: false
+ defp to_json(value) when is_atom(value), do: to_string(value)
+ defp to_json(value), do: value
+ end
end
- defp to_json(nil), do: nil
- defp to_json(true), do: true
- defp to_json(false), do: false
- defp to_json(value) when is_atom(value), do: to_string(value)
- defp to_json(value), do: value
end
end
diff --git a/lib/open_api_spex/plug/render_spec.ex b/lib/open_api_spex/plug/render_spec.ex
index b1e75dd..355499e 100644
--- a/lib/open_api_spex/plug/render_spec.ex
+++ b/lib/open_api_spex/plug/render_spec.ex
@@ -1,36 +1,38 @@
defmodule OpenApiSpex.Plug.RenderSpec do
@moduledoc """
Renders the API spec as a JSON response.
The API spec must be stored in the conn by `OpenApiSpex.Plug.PutApiSpec` earlier in the plug pipeline.
## Example
defmodule MyAppWeb.Router do
use Phoenix.Router
alias MyAppWeb.UserController
pipeline :api do
plug :accepts, ["json"]
plug OpenApiSpex.Plug.PutApiSpec, module: MyAppWeb.ApiSpec
end
scope "/api" do
pipe_through :api
resources "/users", UserController, only: [:create, :index, :show]
get "/openapi", OpenApiSpex.Plug.RenderSpec, []
end
end
"""
@behaviour Plug
+ @json_encoder Enum.find([Jason, Poison], &Code.ensure_loaded?/1)
+
@impl Plug
def init(opts), do: opts
@impl Plug
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))
+ |> Plug.Conn.send_resp(200, @json_encoder.encode!(conn.private.open_api_spex.spec))
end
-end
\ No newline at end of file
+end
diff --git a/mix.exs b/mix.exs
index 7aee4f1..dbdecb5 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,58 +1,59 @@
defmodule OpenApiSpex.Mixfile do
use Mix.Project
@version "3.1.0"
def project do
[
app: :open_api_spex,
version: @version,
elixir: "~> 1.6",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
description: description(),
package: package(),
deps: deps(),
source_url: "https://github.com/open-api-spex/open_api_spex",
homepage_url: "https://github.com/open-api-spex/open_api_spex",
docs: [extras: ["README.md"], main: "readme", source_ref: "v#{@version}"],
dialyzer: [
- plt_add_apps: [:mix],
+ plt_add_apps: [:mix, :jason, :poison],
plt_add_deps: :apps_direct,
flags: ["-Werror_handling", "-Wno_unused", "-Wunmatched_returns", "-Wunderspecs"],
remove_defaults: [:unknown]
]
]
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: []]
defp description() do
"Leverage Open Api Specification 3 (swagger) to document, test, validate and explore your Plug and Phoenix APIs."
end
defp package() do
[
name: "open_api_spex",
files: ["lib", "mix.exs", "README.md", "LICENSE", "CHANGELOG.md"],
maintainers: ["Mike Buhot (m.buhot@gmail.com)"],
licenses: ["Mozilla Public License, version 2.0"],
links: %{"GitHub" => "https://github.com/open-api-spex/open_api_spex"}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
- {:poison, "~> 3.1"},
+ {:poison, "~> 3.1", optional: true},
+ {:jason, "~> 1.0", optional: true},
{:plug, "~> 1.7"},
{:phoenix, "~> 1.3", only: :test},
{:ex_doc, "~> 0.19", only: :dev, runtime: false},
{:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false}
]
end
end
diff --git a/mix.lock b/mix.lock
index 6dfbfe5..8d36519 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,14 +1,15 @@
%{
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"},
- "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"},
- "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
- "makeup": {:hex, :makeup, "0.5.1", "966c5c2296da272d42f1de178c1d135e432662eca795d6dc12e5e8787514edf7", [:mix], [{:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
- "makeup_elixir": {:hex, :makeup_elixir, "0.8.0", "1204a2f5b4f181775a0e456154830524cf2207cf4f9112215c05e0b76e4eca8b", [:mix], [{:makeup, "~> 0.5.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
- "mime": {:hex, :mime, "1.2.0", "78adaa84832b3680de06f88f0997e3ead3b451a440d183d688085be2d709b534", [:mix], [], "hexpm"},
- "nimble_parsec": {:hex, :nimble_parsec, "0.2.2", "d526b23bdceb04c7ad15b33c57c4526bf5f50aaa70c7c141b4b4624555c68259", [:mix], [], "hexpm"},
- "phoenix": {:hex, :phoenix, "1.3.4", "aaa1b55e5523083a877bcbe9886d9ee180bf2c8754905323493c2ac325903dc5", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
- "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"},
+ "earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm"},
+ "ex_doc": {:hex, :ex_doc, "0.19.2", "6f4081ccd9ed081b6dc0bd5af97a41e87f5554de469e7d76025fba535180565f", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
+ "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
+ "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
+ "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
+ "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
+ "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
+ "phoenix": {:hex, :phoenix, "1.4.0", "56fe9a809e0e735f3e3b9b31c1b749d4b436e466d8da627b8d82f90eaae714d2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
+ "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"},
"plug": {:hex, :plug, "1.7.1", "8516d565fb84a6a8b2ca722e74e2cd25ca0fc9d64f364ec9dbec09d33eb78ccd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 30, 2:49 PM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41476
Default Alt Text
(15 KB)
Attached To
Mode
R22 open_api_spex
Attached
Detach File
Event Timeline
Log In to Comment