Skip to content

Commit

Permalink
path and schema injection
Browse files Browse the repository at this point in the history
  • Loading branch information
adropofilm committed May 29, 2019
1 parent 02c2302 commit 9d6e432
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 16 deletions.
Binary file added .DS_Store
Binary file not shown.
30 changes: 30 additions & 0 deletions examples/simple/lib/simple_web/channels/user_socket.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
defmodule SimpleWeb.UserSocket do
use Phoenix.Socket
use PhoenixSwagger

## Channels
# channel "room:*", Simple.RoomChannel
Expand Down Expand Up @@ -34,4 +35,33 @@ defmodule SimpleWeb.UserSocket do
#
# Returning `nil` makes this socket anonymous.
def id(_socket), do: nil

swagger_path(:test) do
get("/api/users/test")
summary("Test function")
description("List tests in db")
produces("application/json")
deprecated(false)

response(200, "OK", Schema.ref(:UsersResponse),
example: %{
data: [
%{
id: 1,
name: "Joe",
email: "[email protected]",
inserted_at: "2017-02-08T12:34:55Z",
updated_at: "2017-02-12T13:45:23Z"
},
%{
id: 2,
name: "Jack",
email: "[email protected]",
inserted_at: "2017-02-04T11:24:45Z",
updated_at: "2017-02-15T23:15:43Z"
}
]
}
)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule SimpleWeb.Helpers.CommonSchemas do
use PhoenixSwagger

def swagger_definitions do
%{
Error:
swagger_schema do
title("Error")
description("An error response")

properties do
success(:boolean, "Success bool")
msg(:string, "Error response", required: true)
end

example(%{
success: false,
msg: "User ID missing"
})
end
}
end

end
4 changes: 4 additions & 0 deletions examples/simple/lib/simple_web/router.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
defmodule SimpleWeb.Router do
use SimpleWeb, :router
import PhoenixSwagger
alias PhoenixSwagger.Plug.Validate

pipeline :api do
Expand All @@ -23,5 +24,8 @@ defmodule SimpleWeb.Router do
title: "Simple App"
}
}
|> add_module(SimpleWeb.Helpers.CommonSchemas)
|> add_module(SimpleWeb.UserSocket)

end
end
65 changes: 65 additions & 0 deletions examples/simple/priv/static/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,49 @@
"description": "Delete a user by ID"
}
},
"/api/users/test": {
"get": {
"tags": [
"UserSocket"
],
"summary": "Test function",
"responses": {
"200": {
"schema": {
"$ref": "#/definitions/UsersResponse"
},
"examples": {
"application/json": {
"data": [
{
"updated_at": "2017-02-12T13:45:23Z",
"name": "Joe",
"inserted_at": "2017-02-08T12:34:55Z",
"id": 1,
"email": "[email protected]"
},
{
"updated_at": "2017-02-15T23:15:43Z",
"name": "Jack",
"inserted_at": "2017-02-04T11:24:45Z",
"id": 2,
"email": "[email protected]"
}
]
}
},
"description": "OK"
}
},
"produces": [
"application/json"
],
"parameters": [],
"operationId": "SimpleWeb.UserSocket.test",
"description": "List tests in db",
"deprecated": false
}
},
"/api/users": {
"post": {
"tags": [
Expand Down Expand Up @@ -297,6 +340,28 @@
"email": "[email protected]"
},
"description": "A user of the app"
},
"Error": {
"type": "object",
"title": "Error",
"required": [
"msg"
],
"properties": {
"success": {
"type": "boolean",
"description": "Success bool"
},
"msg": {
"type": "string",
"description": "Error response"
}
},
"example": {
"success": false,
"msg": "User ID missing"
},
"description": "An error response"
}
}
}
16 changes: 16 additions & 0 deletions lib/helpers/misc_helpers.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
defmodule Helpers.MiscHelpers do

def merge_definitions(definitions, swagger_map = %{definitions: existing}) do
%{swagger_map | definitions: Map.merge(existing, definitions)}
end

def merge_paths(path, swagger_map) do
paths = Map.merge(swagger_map.paths, path, &merge_conflicts/3)
%{swagger_map | paths: paths}
end

defp merge_conflicts(_key, value1, value2) do
Map.merge(value1, value2)
end

end
20 changes: 4 additions & 16 deletions lib/mix/tasks/swagger.generate.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule Mix.Tasks.Phx.Swagger.Generate do
use Mix.Task
require Logger
alias Helpers.MiscHelpers, as: Helpers

@recursive true

Expand Down Expand Up @@ -118,7 +119,7 @@ defmodule Mix.Tasks.Phx.Swagger.Generate do
|> Enum.filter(&!is_nil(&1))
|> Enum.filter(&controller_function_exported?/1)
|> Enum.map(&get_swagger_path/1)
|> Enum.reduce(swagger_map, &merge_paths/2)
|> Enum.reduce(swagger_map, &Helpers.merge_paths/2)
end

defp find_swagger_path_function(route = %{opts: action, path: path, verb: verb}) when is_atom(action) do
Expand All @@ -129,8 +130,8 @@ defmodule Mix.Tasks.Phx.Swagger.Generate do
Code.ensure_compiled?(controller) ->
%{
controller: controller,
path: path |> format_path,
swagger_fun: swagger_fun,
path: format_path(path),
verb: verb
}
true ->
Expand All @@ -143,7 +144,6 @@ defmodule Mix.Tasks.Phx.Swagger.Generate do
nil
end


defp format_path(path) do
Regex.replace(~r/:([^\/]+)/, path, "{\\1}")
end
Expand All @@ -156,15 +156,6 @@ defmodule Mix.Tasks.Phx.Swagger.Generate do
apply(controller, fun, [route])
end

defp merge_paths(path, swagger_map) do
paths = Map.merge(swagger_map.paths, path, &merge_conflicts/3)
%{swagger_map | paths: paths}
end

defp merge_conflicts(_key, value1, value2) do
Map.merge(value1, value2)
end

defp collect_host(swagger_map, nil), do: swagger_map
defp collect_host(swagger_map, endpoint) do
endpoint_config = Application.get_env(app_name(), endpoint)
Expand Down Expand Up @@ -202,14 +193,11 @@ defmodule Mix.Tasks.Phx.Swagger.Generate do
|> Enum.uniq()
|> Enum.filter(&function_exported?(&1, :swagger_definitions, 0))
|> Enum.map(&apply(&1, :swagger_definitions, []))
|> Enum.reduce(swagger_map, &merge_definitions/2)
|> Enum.reduce(swagger_map, &Helpers.merge_definitions/2)
end

defp find_controller(route_map) do
Module.concat([:Elixir | Module.split(route_map.plug)])
end

defp merge_definitions(definitions, swagger_map = %{definitions: existing}) do
%{swagger_map | definitions: Map.merge(existing, definitions)}
end
end
75 changes: 75 additions & 0 deletions lib/phoenix_swagger.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule PhoenixSwagger do
use Application
alias PhoenixSwagger.Path
alias PhoenixSwagger.Path.PathObject
alias Helpers.MiscHelpers, as: Helpers

@moduledoc """
The PhoenixSwagger module provides macros for defining swagger operations and schemas.
Expand Down Expand Up @@ -201,6 +202,80 @@ defmodule PhoenixSwagger do
end
end

@doc """
Sometimes swagger paths and schema definitions defined in non-controller modules need
to be generated in the `swagger.json` output file. The `add_module/2` function
may be used to ensure their successful addition.
## Example
%{
info: %{
version: "1.0",
title: "Simple App"
}
}
|> add_module(SimpleWeb.UserSocket)
"""
def add_module(struct, module) do
functions = module.__info__(:functions)

paths = get_paths(functions, struct, module)

Enum.map(functions, fn {action, _arg} -> build_schemas(action, module) end)
|> Enum.filter(&!is_nil(&1))
|> Enum.reduce(swagger_map(paths), &Helpers.merge_definitions/2)
end

def build_schemas(function, module) do
if is_schema?(function), do: apply(module, function, [])
end

defp is_schema?(function) do
function
|> Atom.to_string
|> String.contains?("swagger_definitions")
end

defp get_paths(functions, struct, module) do
Enum.map(functions, fn {action, _arg} -> build_path(action, module) end)
|> Enum.filter(&!is_nil(&1))
|> Enum.reduce(swagger_map(struct), &Helpers.merge_paths/2)
end

def swagger_map(swagger_map) do
Map.update(swagger_map, :definitions, %{}, &(&1))
|> Map.update(:paths, %{}, &(&1))
end

defp build_path(function, module) do #room for improvement here for sure, comments?
case is_path?(function) do
{true, action} ->
apply(module, function, extract_args(action))
{false, _action} ->
nil
end
end

defp is_path?(function) do #room for improvement here for sure, comments?
funct_string =
function
|> Atom.to_string

contains =
funct_string
|> String.contains?("swagger_path")

{contains, String.replace(funct_string, "swagger_path_", "")}
end

defp extract_args(action) do
[
%{verb: action |> String.to_atom, path: ""}
]
end

@doc false
# Add a default operationId based on model name and action if required
def ensure_operation_id(path = %PathObject{operation: %{operationId: ""}}, module, action) do
Expand Down

0 comments on commit 9d6e432

Please sign in to comment.