Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Parameter Styles and Non-primitive Types #62

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
21 changes: 20 additions & 1 deletion lib/open_api/processor/operation/param.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,42 @@ defmodule OpenAPI.Processor.Operation.Param do
@typedoc "Location of the param"
@type location :: :cookie | :header | :path | :query

@typedoc "Style of the param"
@type style ::
:matrix | :label | :form | :simple | :space_delimited | :pipe_delimited | :deep_object

@typedoc "Processed param data used by the renderer"
@type t :: %__MODULE__{
description: String.t() | nil,
explode: boolean,
location: location,
name: String.t(),
style: style,
value_type: Type.t()
}

defstruct [
:description,
:explode,
:name,
:location,
:style,
:value_type
]

@doc false
@spec from_spec(State.t(), Parameter.t()) :: {State.t(), t}
def from_spec(state, %Parameter{} = param) do
%Parameter{description: description, name: name} = param
%Parameter{description: description, explode: explode, name: name} = param
{state, value_type} = value_type(state, param)

param =
%__MODULE__{
description: description,
explode: explode,
name: name,
location: location(param),
style: style(param),
value_type: value_type
}

Expand All @@ -47,6 +57,15 @@ defmodule OpenAPI.Processor.Operation.Param do
defp location(%Parameter{in: "path"}), do: :path
defp location(%Parameter{in: "query"}), do: :query

@spec style(Parameter.t()) :: style
defp style(%Parameter{style: "matrix"}), do: :matrix
defp style(%Parameter{style: "label"}), do: :label
defp style(%Parameter{style: "form"}), do: :form
defp style(%Parameter{style: "simple"}), do: :simple
defp style(%Parameter{style: "spaceDelimited"}), do: :space_delimited
defp style(%Parameter{style: "pipeDelimited"}), do: :pipe_delimited
defp style(%Parameter{style: "deepObject"}), do: :deep_object

@spec value_type(State.t(), Parameter.t()) :: {State.t(), Type.t()}
defp value_type(_state, %Parameter{schema: nil}), do: {:string, :generic}

Expand Down
50 changes: 41 additions & 9 deletions lib/open_api/renderer/operation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ defmodule OpenAPI.Renderer.Operation do
]

"""
require Logger
alias OpenAPI.Processor.Operation
alias OpenAPI.Processor.Operation.Param
alias OpenAPI.Processor.Schema
Expand Down Expand Up @@ -249,7 +250,6 @@ defmodule OpenAPI.Renderer.Operation do
module_name: module_name,
request_body: request_body,
request_method: request_method,
request_path: request_path,
request_path_parameters: path_params,
request_query_parameters: query_params,
responses: responses
Expand Down Expand Up @@ -281,14 +281,9 @@ defmodule OpenAPI.Renderer.Operation do
end

url =
String.replace(request_path, ~r/\{([[:word:]]+)\}/, "#\{\\1\}")
|> then(&"\"#{&1}\"")
|> Code.string_to_quoted!()
|> then(fn url ->
quote do
{:url, unquote(url)}
end
end)
quote do
{:url, unquote(render_url(state, operation))}
end

method =
quote do
Expand Down Expand Up @@ -346,6 +341,43 @@ defmodule OpenAPI.Renderer.Operation do
end
end

defp render_url(_state, operation) do
%Operation{request_path: path, request_path_parameters: path_params} = operation
param_map = Map.new(path_params, fn %Param{name: name} = param -> {"{#{name}}", param} end)

String.split(path, "/")
|> Enum.map(fn
path_segment ->
if param = Map.get(param_map, path_segment) do
%Param{name: name} = param

case param do
%Param{style: :simple, value_type: primitive}
when primitive in [:boolean, :integer, :number] ->
quote do: "#{unquote(Util.identifier(name))}"

%Param{style: :simple, value_type: {:string, _}} ->
quote do: "#{unquote(Util.identifier(name))}"

%Param{style: :simple, value_type: {:array, primitive}}
when primitive in [:boolean, :integer, :number] ->
quote do: "#{Enum.join(unquote(Util.identifier(name)), ",")}"

%Param{style: :simple, value_type: {:array, {:string, _}}} ->
quote do: "#{Enum.join(unquote(Util.identifier(name)), ",")}"

_else ->
Logger.warning("Path param style not implemented for type: #{inspect(param)}")
quote do: "#{unquote(Util.identifier(name))}"
end
else
path_segment
end
end)
|> Enum.intersperse("/")
|> Util.collapse_binary()
end

@doc """
Render the spec of an operation function

Expand Down
32 changes: 32 additions & 0 deletions lib/open_api/renderer/util.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,30 @@ defmodule OpenAPI.Renderer.Util do
|> Enum.reject(&is_nil/1)
end

@doc """
Collapse a list of binary expressions into a single binary

This utility is useful when several individual double-quoted strings are concatenated. Instead
of creating a large `<<...>>` expression, this function will produce an AST that is formatted
as a single double-quoted string (when possible).
"""
@spec collapse_binary(Macro.t()) :: Macro.t()
def collapse_binary(nodes) do
nodes =
nodes
|> Enum.map(fn
{:<<>>, _meta, args} ->
{:<<>>, _meta, args} = collapse_binary(args)
args

other_node ->
other_node
end)
|> List.flatten()

{:<<>>, [], nodes}
end

@doc """
Convert an AST into formatted code as a string

Expand Down Expand Up @@ -92,6 +116,14 @@ defmodule OpenAPI.Renderer.Util do
ast_node
end

@doc """
Unquote the given string as an identifier (rather than a string literal or atom)
"""
@spec identifier(String.t()) :: Macro.t()
def identifier(value) do
{String.to_atom(value), [], nil}
end

@doc """
Enforce the existence of whitespace after an expression

Expand Down