Skip to content

Commit

Permalink
Merge branch 'urielaero-master'
Browse files Browse the repository at this point in the history
  • Loading branch information
msawka committed Dec 15, 2015
2 parents 011a2a9 + e242b7b commit fdf1c4b
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 35 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ The following task-specific options are available:
* Description: Specifies the file name (within the output_path) of the Swagger JSON
* Type: string
* Default Value: "api.json"
* :pipe_through,
* Description: if pipe_through is defined only this is used.
* Type: list
* Default Value: nil
* Example: pipe_through: [:api]

### Swagger Config
The following Swagger-specific options are available:
Expand Down Expand Up @@ -224,6 +229,27 @@ The JSON output will look like:
}
```

If changeset is defined in models, like this:
```elixir
schema "users" do
field :name, :string
field :email, :string
field :bio, :string
field :number_of_pets, :integer

timestamps
end

@required_fields ~w(name email)
@optional_fields ~w(bio number_of_pets)

def changeset(model, params \\ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
end
```
Changeset is used for 'required' fields in schema.

### Converting Phoenix Routes into Swagger Paths
The [Phoenix Routes](https://github.com/phoenixframework/phoenix/blob/v1.0.0/lib/phoenix/router/route.ex) that is found via the [Phoenix Router](https://github.com/phoenixframework/phoenix/blob/v1.0.0/lib/phoenix/router.ex) are converted into a [Swagger Paths Object](http://swagger.io/specification/#pathsObject), each route becoming a [Path Item](http://swagger.io/specification/#pathItemObject). The Phoenix template paths are converted into [Swagger path templates](http://swagger.io/specification/#pathTemplating) and each templated variable is converted into a [Path paramter](http://swagger.io/specification/#parametersDefinitionsObject). All path parameters are assumed to be required and are of type string (except for parameters named `id`, which are assumed to be integers).

Expand Down
77 changes: 48 additions & 29 deletions lib/mix/tasks/swagger.ex
Original file line number Diff line number Diff line change
Expand Up @@ -123,43 +123,48 @@ defmodule Mix.Tasks.Swagger do
def add_routes(nil, swagger), do: swagger
def add_routes([], swagger), do: swagger
def add_routes([route | remaining_routes], swagger) do
swagger_path = path_from_route(String.split(route.path, "/"), nil)
pipe_through = Application.get_env(:swaggerdoc, :pipe_through, nil)
if pipe_through && route.pipe_through != pipe_through do
add_routes(remaining_routes, swagger)
else
swagger_path = path_from_route(String.split(route.path, "/"), nil)

path = swagger[:paths][swagger_path]
if path == nil do
path = %{}
end
path = swagger[:paths][swagger_path]
if path == nil do
path = %{}
end

func_name = "swaggerdoc_#{route.opts}"
verb = if route.plug != nil && Keyword.has_key?(route.plug.__info__(:functions), String.to_atom(func_name)) do
apply(route.plug, String.to_atom(func_name), [])
else
parse_default_verb(route.path)
end
func_name = "swaggerdoc_#{route.opts}"
verb = if route.plug != nil && Keyword.has_key?(route.plug.__info__(:functions), String.to_atom(func_name)) do
apply(route.plug, String.to_atom(func_name), [])
else
parse_default_verb(route.path)
end

response_schema = verb[:response_schema]
verb = Map.delete(verb, :response_schema)
response_schema = verb[:response_schema]
verb = Map.delete(verb, :response_schema)

verb_string = String.downcase("#{route.verb}")
if verb[:responses] == nil do
verb = Map.put(verb, :responses, default_responses(verb_string, response_schema))
end
verb_string = String.downcase("#{route.verb}")
if verb[:responses] == nil do
verb = Map.put(verb, :responses, default_responses(verb_string, response_schema))
end

if verb[:produces] == nil do
verb = Map.put(verb, :produces, Application.get_env(:swaggerdoc, :produces, []))
end
if verb[:produces] == nil do
verb = Map.put(verb, :produces, Application.get_env(:swaggerdoc, :produces, []))
end

if verb[:operationId] == nil do
verb = Map.put(verb, :operationId, "#{route.opts}")
end
if verb[:operationId] == nil do
verb = Map.put(verb, :operationId, "#{route.opts}")
end

if verb[:description] == nil do
verb = Map.put(verb, :description, "")
end
if verb[:description] == nil do
verb = Map.put(verb, :description, "")
end

path = Map.put(path, verb_string, verb)
paths = Map.put(swagger[:paths], swagger_path, path)
add_routes(remaining_routes, Map.put(swagger, :paths, paths))
path = Map.put(path, verb_string, verb)
paths = Map.put(swagger[:paths], swagger_path, path)
add_routes(remaining_routes, Map.put(swagger, :paths, paths))
end
end

@doc """
Expand Down Expand Up @@ -247,6 +252,13 @@ defmodule Mix.Tasks.Swagger do
end

module_json = %{"properties" => properties_json}

if :erlang.function_exported(module, :changeset, 2) do
module_struct = module.changeset(module.__struct__, %{})
required = required_fields module_struct.errors
module_json = Map.put(module_json, "required", required)
end

def_json = Map.put(def_json, "#{inspect module}", module_json)
end

Expand Down Expand Up @@ -274,4 +286,11 @@ defmodule Mix.Tasks.Swagger do
_ -> %{"type" => "string"}
end
end

def required_fields([]), do: []

def required_fields([head|tail]) do
{key, _msg} = head
[to_string(key)|required_fields(tail)]
end
end
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
%{"decimal": {:hex, :decimal, "1.1.0"},
"earmark": {:hex, :earmark, "0.1.17"},
"ecto": {:hex, :ecto, "1.0.1"},
"ecto": {:hex, :ecto, "1.0.7"},
"ex_doc": {:hex, :ex_doc, "0.8.4"},
"meck": {:hex, :meck, "0.8.3"},
"phoenix": {:hex, :phoenix, "1.0.1"},
Expand Down
90 changes: 85 additions & 5 deletions test/mix/tasks/swagger_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,28 @@ defmodule Mocks.UserModel do
end
end

defmodule Mocks.UserRequiredModel do
use Ecto.Model

schema "users" do
field :name, :string
field :email, :string
field :bio, :string
field :number_of_pets, :integer

timestamps
end

@required_fields ~w(name email)
@optional_fields ~w(bio number_of_pets)

def changeset(model, params \\ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
end

end

defmodule Mocks.SimpleRouter do
def __routes__, do: []
end
Expand Down Expand Up @@ -46,6 +68,7 @@ defmodule Mix.Tasks.Swagger.Tests do
Application.delete_env(:swaggerdoc, :schemes)
Application.delete_env(:swaggerdoc, :consumes)
Application.delete_env(:swaggerdoc, :produces)
Application.delete_env(:swaggerdoc, :pipe_through)
end

setup do
Expand Down Expand Up @@ -189,6 +212,35 @@ defmodule Mix.Tasks.Swagger.Tests do
}}
end

test "add_routes - route with select pipe_through" do
Application.put_env(:swaggerdoc, :pipe_through, [:api])
route = [%PhoenixRoute{
path: "/testing/:id",
opts: :index,
verb: "GET"
},%PhoenixRoute{
path: "/api/v1/testing/:id",
opts: :index,
verb: "GET",
pipe_through: [:api],
}]
assert Swagger.add_routes(route, %{paths: %{}}) == %{
paths: %{"/api/v1/testing/{id}" =>
%{"get" => %{
description: "",
operationId: "index",
parameters: [%{"description" => "", "in" => "path", "name" => "id", "required" => true, "type" => "integer"}],
produces: [],
responses: %{
"200" => %{"description" => "Resource Content"},
"401" => %{"description" => "Request is not authorized"}, "404" => %{"description" => "Resource not found"},
"500" => %{"description" => "Internal Server Error"}
}
}}
}}
Application.delete_env(:swaggerdoc, :pipe_through)
end

test "add_routes - route from custom plug" do
route = %PhoenixRoute{
path: "/test",
Expand Down Expand Up @@ -304,7 +356,18 @@ defmodule Mix.Tasks.Swagger.Tests do

test "build_definitions - modules but no models" do
assert Swagger.build_definitions([{Mocks.DefaultPlug, ""}], %{}) == %{}
end
end

#==============================
# required_fields tests

test "required_fields - parse errors from struct, if errors is empty" do
assert Swagger.required_fields([]) == []
end

test "required_fields - parse errors from struct" do
assert Swagger.required_fields([name: "can't be blank", email: "can't be blank"]) == ["name", "email"]
end

test "build_definitions - model" do
assert Swagger.build_definitions([{Mocks.UserModel, ""}], %{}) == %{
Expand All @@ -319,6 +382,23 @@ defmodule Mix.Tasks.Swagger.Tests do
"updated_at" => %{"format" => "date-time", "type" => "string"}}
}
}
end

test "build_definitions - model support changeset (required_fields)" do
assert Swagger.build_definitions([{Mocks.UserRequiredModel, ""}], %{}) == %{
"Mocks.UserRequiredModel" => %{
"properties" => %{
"bio" => %{"type" => "string"},
"email" => %{"type" => "string"},
"id" => %{"format" => "int64", "type" => "integer"},
"inserted_at" => %{"format" => "date-time", "type" => "string"},
"name" => %{"type" => "string"},
"number_of_pets" => %{"format" => "int64", "type" => "integer"},
"updated_at" => %{"format" => "date-time", "type" => "string"}
},
"required" => ["name", "email"]
}
}
end

#==============================
Expand Down Expand Up @@ -349,15 +429,15 @@ defmodule Mix.Tasks.Swagger.Tests do
end

test "convert_property_type - :Ecto.DateTime " do
assert Swagger.convert_property_type(:Ecto.DateTime ) == %{"type" => "string", "format" => "date-time"}
assert Swagger.convert_property_type(Ecto.DateTime ) == %{"type" => "string", "format" => "date-time"}
end

test "convert_property_type - :Ecto.Date" do
assert Swagger.convert_property_type(:Ecto.Date) ==%{"type" => "string", "format" => "date"}
assert Swagger.convert_property_type(Ecto.Date) ==%{"type" => "string", "format" => "date"}
end

test "convert_property_type - :Ecto.Time" do
assert Swagger.convert_property_type(:Ecto.Time) == %{"type" => "string", "format" => "date-time"}
assert Swagger.convert_property_type(Ecto.Time) == %{"type" => "string", "format" => "date-time"}
end

test "convert_property_type - :uuid" do
Expand Down Expand Up @@ -390,4 +470,4 @@ defmodule Mix.Tasks.Swagger.Tests do
after
:meck.unload
end
end
end

0 comments on commit fdf1c4b

Please sign in to comment.