Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 40 additions & 24 deletions lib/parameters.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Parameters do
alias Parameters.{
ParamsNode,
FieldNode
Params,
Field
}

defmacro __using__(_) do
Expand All @@ -17,18 +17,15 @@ defmodule Parameters do

def __on_definition__(%{module: module}, _kind, name, _args, _guards, _body) do
if ast = Module.get_attribute(module, :parameters_block) do
Module.put_attribute(module, :parameters, ParamsNode.parse(name, ast))
Module.put_attribute(module, :parameters, Params.parse(name, ast))
Module.delete_attribute(module, :parameters_block)
end
end

defmacro __before_compile__(env) do
for schema <- Module.get_attribute(env.module, :parameters) do
parent = Module.concat(Parameters, env.module)

quote do
unquote(define_schema(parent, schema))
end
quote do
unquote(define_schemas(env.module))
unquote(define_reflections())
end
end

Expand Down Expand Up @@ -69,8 +66,27 @@ defmodule Parameters do
changeset_for(module, fun, params)
end

defp define_schema(parent, node) do
module = Module.concat(parent, Macro.camelize("#{node.id}"))
defp define_reflections() do
quote do
def __parameters__ do
@parameters
end
end
end

defp define_schemas(module) do
parameters = Module.get_attribute(module, :parameters)
parent = Module.concat(Parameters, module)

for schema <- parameters do
quote do
unquote(define_schema(parent, schema))
end
end
end

defp define_schema(parent, params) do
module = Module.concat(parent, Macro.camelize("#{params.id}"))

quote do
defmodule unquote(module) do
Expand All @@ -79,12 +95,12 @@ defmodule Parameters do
@primary_key false

embedded_schema do
unquote(define_fields(node.fields))
unquote(define_fields(params.fields))
end

unquote(define_changeset(node.fields))
unquote(define_changeset(params.fields))

unquote(define_embeds(module, node.fields))
unquote(define_embeds(module, params.fields))
end
end
end
Expand All @@ -97,26 +113,26 @@ defmodule Parameters do

defp define_fields(fields) do
for field <- fields do
options = Keyword.take(field.options, [:default])
opts = Keyword.take(field.opts, [:default])

case field do
%FieldNode{type: :map, fields: fields} when is_list(fields) ->
%Field{type: :map, fields: fields} when is_list(fields) ->
quote do
embeds_one unquote(field.id),
Module.concat(__MODULE__, Macro.camelize("#{unquote(field.id)}")),
unquote(options)
unquote(opts)
end

%FieldNode{type: :array, fields: fields} when is_list(fields) ->
%Field{type: :array, fields: fields} when is_list(fields) ->
quote do
embeds_many unquote(field.id),
Module.concat(__MODULE__, Macro.camelize("#{unquote(field.id)}")),
unquote(options)
unquote(opts)
end

%FieldNode{fields: nil} ->
%Field{fields: nil} ->
quote do
field unquote(field.id), unquote(field.type), unquote(options)
field unquote(field.id), unquote(field.type), unquote(opts)
end
end
end
Expand All @@ -131,21 +147,21 @@ defmodule Parameters do
required_fields =
fields
|> Enum.filter(fn field ->
is_nil(field.fields) and Keyword.fetch!(field.options, :required)
is_nil(field.fields) and Keyword.fetch!(field.opts, :required)
end)
|> Enum.map(fn field -> field.id end)

optional_embeds =
fields
|> Enum.filter(fn field ->
not is_nil(field.fields) and not Keyword.fetch!(field.options, :required)
not is_nil(field.fields) and not Keyword.fetch!(field.opts, :required)
end)
|> Enum.map(fn field -> field.id end)

required_embeds =
fields
|> Enum.filter(fn field ->
not is_nil(field.fields) and Keyword.fetch!(field.options, :required)
not is_nil(field.fields) and Keyword.fetch!(field.opts, :required)
end)
|> Enum.map(fn field -> field.id end)

Expand Down
10 changes: 5 additions & 5 deletions lib/parameters/field_node.ex → lib/parameters/field.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
defmodule Parameters.FieldNode do
defmodule Parameters.Field do
defstruct [
:id,
:type,
:fields,
options: []
opts: []
]

def parse({:__block__, _metadata, ast}), do: parse(ast)
Expand All @@ -15,15 +15,15 @@ defmodule Parameters.FieldNode do
opts = [required: name == :requires]
fields = parse(ast)
fields = if is_list(fields), do: fields, else: [fields]
struct(__MODULE__, id: field, type: type, options: opts, fields: fields)
struct(__MODULE__, id: field, type: type, opts: opts, fields: fields)

[field, type, opts] ->
opts = Keyword.put(opts, :required, name == :requires)
struct(__MODULE__, id: field, type: type, options: opts)
struct(__MODULE__, id: field, type: type, opts: opts)

[field, type] ->
opts = [required: name == :requires]
struct(__MODULE__, id: field, type: type, options: opts)
struct(__MODULE__, id: field, type: type, opts: opts)
end
end
end
131 changes: 131 additions & 0 deletions lib/parameters/oas3.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
defmodule Parameters.OAS3 do
def type_mapper(:map), do: :object
def type_mapper(:integer), do: :number
def type_mapper(:float), do: :number
def type_mapper(any), do: any

def render_paths(routes, content_types) do
routes
|> Enum.group_by(& &1.path, &render_operation(&1, content_types))
|> Enum.map(fn {key, val} -> {key, Map.new(val)} end)
|> Map.new()
end

def render_operation(route, content_types) do
params = Enum.find(route.plug.__parameters__(), &(&1.id == route.plug_opts))
fields = params && params.fields

operation =
Map.new()
|> put_parameters(route, fields || [], content_types)
|> put_default_response(route, content_types)

{route.verb, operation}
end

def put_parameters(operation, %{verb: :get}, fields, _content_types) do
parameters =
for field <- fields do
Map.new(
in: "query",
name: field.id,
required: Keyword.get(field.opts, :required, false),
schema: Map.new(type: type_mapper(field.type)),
description: Keyword.get(field.opts, :description, "")
)
end

Map.put(operation, :parameters, parameters)
end

def put_parameters(operation, route, fields, content_types) do
req_body =
Map.new(
required: true,
description: ""
)

content =
for pipeline <- route.pipe_through,
Map.has_key?(content_types, pipeline),
into: Map.new() do
content_type = Map.get(content_types, pipeline)

required_fields =
for field <- fields, Keyword.get(field.opts, :required, false) do
field.id
end

properties =
for field <- fields, into: Map.new() do
{field.id, Map.new(type: type_mapper(field.type))}
end

example =
for field <- fields, into: Map.new() do
{field.id, type_mapper(field.type)}
end

content =
Map.new(
schema:
Map.new(
required: required_fields,
properties: properties,
example: example
)
)

{content_type, content}
end

req_body = Map.put(req_body, :content, content)

Map.put(operation, :requestBody, req_body)
end

def put_default_response(operation, _route, _content_types) do
responses = Map.new(default: Map.new(description: "OK"))

Map.put(operation, :responses, responses)
end

defmacro __using__(opts) do
content_types = Keyword.fetch!(opts, :content_types)
accepts = Keyword.fetch!(opts, :accepts)
otp_app = Keyword.fetch!(opts, :otp_app)
info = Keyword.fetch!(opts, :info)

quote do
@content_types Map.new(unquote(content_types))
@accepts Map.new(unquote(accepts))
@info Map.new(unquote(info))
@otp_app unquote(otp_app)

def render do
routes = __parameters__(:routes)

Map.new(
openapi: "3.0.0",
info: __parameters__(:info),
paths: Parameters.OAS3.render_paths(routes, __parameters__(:content_types))
)
end

def __parameters__(:info), do: @info
def __parameters__(:accepts), do: @accepts
def __parameters__(:content_types), do: @content_types

def __parameters__(:routes) do
config = Application.get_env(@otp_app, __MODULE__)

config
|> Keyword.fetch!(:router)
|> apply(:__routes__, [])
|> Enum.filter(fn route ->
function_exported?(route.plug, :__parameters__, 0)
end)
end
end
end
end
4 changes: 2 additions & 2 deletions lib/parameters/params_node.ex → lib/parameters/params.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Parameters.ParamsNode do
defmodule Parameters.Params do
defstruct [
:id,
fields: []
Expand All @@ -10,7 +10,7 @@ defmodule Parameters.ParamsNode do
def parse(name, ast) when is_list(ast) do
Enum.reduce(ast, struct(__MODULE__), fn field, schema ->
Map.update!(schema, :fields, fn fields ->
[Parameters.FieldNode.parse(field) | fields]
[Parameters.Field.parse(field) | fields]
end)
end)
|> Map.put(:id, name)
Expand Down