Packages
phoenix_swagger_ro
0.8.3
PhoenixSwagger is the library that provides swagger integration to the phoenix web framework adding the readonly property.
Current section
Files
Jump to
Current section
Files
lib/phoenix_swagger/conn_validator.ex
defmodule PhoenixSwagger.ConnValidator do
alias PhoenixSwagger.Validator
@table :validator_table
@doc """
Validate a request.
Feel free to use it in your own Plugs. Returns:
* `{:ok, conn}` on success
* `{:error, :no_matching_path}` if the request path could not be mapped to a schema
* `{:error, message, path}` if the request was mapped but failed validation
* `{:error, [{message, path}], path}` if more than one validation error has been detected
"""
def validate(conn) do
with {:ok, path} <- find_matching_path(conn),
:ok <- validate_body_params(path, conn),
:ok <- validate_query_params(path, conn),
do: {:ok, conn}
end
defp find_matching_path(conn) do
found =
:ets.tab2list(@table)
|> Enum.sort()
|> Enum.find(fn {path, base_path, _} ->
base_path_segments = String.split(base_path || "", "/") |> tl
path_segments = String.split(path, "/") |> tl
path_info_without_base = remove_base_path(conn.path_info, base_path_segments)
req_path_segments = [String.downcase(conn.method) | path_info_without_base]
equal_paths?(path_segments, req_path_segments)
end)
case found do
nil -> {:error, :no_matching_path}
{path, _, _} -> {:ok, path}
end
end
defp validate_array(name, values, items = %{"type" => type}, parameters) do
with :ok <-
String.split(values, ",")
|> Enum.map(&{type, name, &1, false, Map.get(items, "enum"), nil})
|> validate_query_params() do
validate_query_params(parameters)
else
error -> error
end
end
defp validate_enum(name, value, enum, parameters) do
cond do
value in enum -> validate_query_params(parameters)
true -> {:error, "Value #{inspect(value)} is not allowed in enum.", "#/#{name}"}
end
end
defp validate_boolean(_name, value, parameters) when value in ["true", "false"] do
validate_query_params(parameters)
end
defp validate_boolean(name, _value, _parameters) do
{:error, "Type mismatch. Expected Boolean but got String.", "#/#{name}"}
end
defp validate_integer(name, value, parameters) do
_ = String.to_integer(value)
validate_query_params(parameters)
rescue
ArgumentError ->
{:error, "Type mismatch. Expected Integer but got String.", "#/#{name}"}
end
defp validate_number(name, value, parameters) do
{_, ""} = Float.parse(value)
validate_query_params(parameters)
rescue
MatchError ->
{:error, "Type mismatch. Expected Number but got String.", "#/#{name}"}
end
defp validate_query_params([]), do: :ok
defp validate_query_params([{_type, _name, nil, false, _, _} | parameters]) do
validate_query_params(parameters)
end
defp validate_query_params([{_type, name, nil, true, _, _} | _]) do
{:error, "Required property #{name} was not present.", "#"}
end
defp validate_query_params([{_type, name, val, _, enum, _} | parameters])
when not is_nil(enum) do
validate_enum(name, val, enum, parameters)
end
defp validate_query_params([{"string", _name, _val, _, _, _} | parameters]) do
validate_query_params(parameters)
end
defp validate_query_params([{"integer", name, val, _, _, _} | parameters]) do
validate_integer(name, val, parameters)
end
defp validate_query_params([{"number", name, val, _, _, _} | parameters]) do
validate_number(name, val, parameters)
end
defp validate_query_params([{"boolean", name, val, _, _, _} | parameters]) do
validate_boolean(name, val, parameters)
end
defp validate_query_params([{"array", name, vals, _, _, items} | parameters]) do
validate_array(name, vals, items, parameters)
end
defp validate_query_params(path, conn) do
[{_path, _basePath, schema}] = :ets.lookup(@table, path)
parameters =
for parameter <- schema.schema["parameters"],
parameter["type"] != nil,
parameter["in"] in ["query", "path"] do
{parameter["type"], parameter["name"], get_param_value(conn.params, parameter["name"]),
parameter["required"], parameter["enum"], parameter["items"]}
end
validate_query_params(parameters)
end
defp get_in_nested(params = nil, _), do: params
defp get_in_nested(params, nil), do: params
defp get_in_nested(params, ""), do: params
defp get_in_nested(params, nested_map) when map_size(nested_map) == 1 do
[{key, child_nested_map}] = Map.to_list(nested_map)
get_in_nested(params[key], child_nested_map)
end
defp get_param_value(params, nested_name) when is_binary(nested_name) do
nested_map = Plug.Conn.Query.decode(nested_name)
get_in_nested(params, nested_map)
end
defp validate_body_params(path, conn) do
Validator.validate(path, conn.body_params)
end
defp equal_paths?([], []), do: true
defp equal_paths?([head | orig_path_rest], [head | req_path_rest]),
do: equal_paths?(orig_path_rest, req_path_rest)
defp equal_paths?(["{" <> _ | orig_path_rest], [_ | req_path_rest]),
do: equal_paths?(orig_path_rest, req_path_rest)
defp equal_paths?(_, _), do: false
# It is pretty safe to strip request path by base path. They can't be
# non-equal. In this way, the router even will not execute this plug.
defp remove_base_path(path, []), do: path
defp remove_base_path([_path | rest], [_base_path | base_path_rest]) do
remove_base_path(rest, base_path_rest)
end
end