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/validator.ex
defmodule PhoenixSwagger.Validator do
@moduledoc """
The PhoenixSwagger.Validator module provides converter of
swagger schema to ex_json_schema structure for further validation.
There are two main functions:
* parse_swagger_schema/1
* validate/2
Before `validate/2` will be called, a swagger schema should be parsed
for futher validation with the `parse_swagger_schema/1`. This function
takes path to a swagger schema and returns it in ex_json_schema format.
During execution of the `parse_swagger_schema/1` function, it creates
the `validator_table` ets table and stores associative key/value there.
Where `key` is an API path of a resource and `value` is input parameters
of a resource.
To validate of a parsed swagger schema, the `validate/1` should be used.
For more information, see more in ./phoenix_swagger/tests/ directory.
"""
@table :validator_table
@doc """
The `parse_swagger_schema/1` takes path or list of paths to a swagger schema(s),
parses it/them into ex_json_schema format and store to the `validator_table` ets
table.
## Examples
iex(1)> parse_swagger_schema("my_json_spec.json")
[{"/person", %{'__struct__' => 'Elixir.ExJsonSchema.Schema.Root',
location => root,
refs => %{},
schema => %{
"properties" => %{
"name" => %{"type" => "string"},
"age" => %{"type" => "integer"}
}
}
}
}]
"""
def parse_swagger_schema(specs) when is_list(specs) do
schemas =
Enum.map(specs, fn spec ->
read_swagger_schema(spec)
end)
schema =
Enum.reduce(schemas, %{}, fn schema, acc ->
acc =
if acc["paths"] == nil do
Map.merge(acc, schema)
else
acc =
Map.update!(acc, "paths", fn paths_map -> Map.merge(paths_map, schema["paths"]) end)
Map.update!(acc, "definitions", fn definitions_map ->
Map.merge(definitions_map, schema["definitions"])
end)
end
acc
end)
collect_schema_attrs(schema)
end
def parse_swagger_schema(spec) do
schema = read_swagger_schema(spec)
collect_schema_attrs(schema)
end
@doc """
The `validate/2` takes a resource path and input parameters
of this resource.
Returns `:ok` in a case when parameters are valid for the
given resource or:
* `{:error, :resource_not_exists}` in a case when path is not
exists in the validator table
* `{:error, error_message, path}` in a case when at least
one parameter is not valid for the given resource
"""
def validate(path, params) do
case :ets.lookup(@table, path) do
[] ->
{:error, :resource_not_exists}
[{_, _, schema}] ->
case ExJsonSchema.Validator.validate(schema, params) do
:ok ->
:ok
{:error, [{error, path}]} ->
{:error, error, path}
{:error, error} ->
{:error, error, path}
end
end
end
@doc false
defp collect_schema_attrs(schema) do
Enum.map(schema["paths"], fn {path, data} ->
Enum.map(Map.keys(data), fn method ->
parameters = data[method]["parameters"]
# we may have a request without parameters, so nothing to validate
# in this case
if parameters == nil do
[]
else
# Let's go through requests parameters from swagger schema
# and collect it into json schema properties.
properties =
Enum.reduce(parameters, %{}, fn parameter, acc ->
acc =
if parameter["type"] == nil do
ref = String.split(parameter["schema"]["$ref"], "/") |> List.last()
Map.merge(acc, schema["definitions"][ref])
else
acc
end
acc
end)
# collect request primitive parameters which do not refer to `definitions`
# these are mostly parameters from query string
properties =
Enum.reduce(parameters, properties, fn parameter, acc ->
if parameter["type"] != nil do
collect_properties(acc, parameter)
else
acc
end
end)
# actually all requests which have parameters are objects
properties =
if properties["type"] == nil do
Map.put_new(properties, "type", "object")
else
properties
end
# store path concatenated with method. This allows us
# to identify the same resources with different http methods.
path = "/" <> method <> path
schema_object =
Map.merge(
%{
"parameters" => parameters,
"type" => "object",
"definitions" => schema["definitions"]
},
properties
)
schema_object = schema_object
|> Map.update("definitions", %{}, &swagger_nullable_to_json_schema/1)
resolved_schema = ExJsonSchema.Schema.resolve(schema_object)
:ets.insert(@table, {path, schema["basePath"], resolved_schema})
{path, resolved_schema}
end
end)
end)
|> List.flatten()
end
# Swagger 2.0 and JSON-Schema differ in the treatment of nulls.
# When the "x-nullable" vendor extension is present in the swagger,
# convert the type to an array including "null"
defp swagger_nullable_to_json_schema(schema = %{"type" => type, "x-nullable" => true})
when is_binary(type) do
schema = %{schema | "type" => [type, "null"]}
swagger_nullable_to_json_schema(schema)
end
defp swagger_nullable_to_json_schema(schema = %{"$ref" => ref, "x-nullable" => true})
when is_binary(ref) do
schema = schema
|> Map.drop(["$ref", "x-nullable"])
|> Map.put("oneOf", [%{"type" => "null"}, %{"$ref" => ref}])
swagger_nullable_to_json_schema(schema)
end
defp swagger_nullable_to_json_schema(schema) when is_map(schema) do
for {k, v} <- schema,
into: %{},
do: {k, swagger_nullable_to_json_schema(v)}
end
defp swagger_nullable_to_json_schema(schema) when is_list(schema) do
for v <- schema, do: swagger_nullable_to_json_schema(v)
end
defp swagger_nullable_to_json_schema(other), do: other
@doc false
defp collect_properties(properties, parameter) when properties == %{} do
Map.put(
%{},
"properties",
Map.put_new(%{}, parameter["name"], %{"type" => parameter["type"]})
)
end
defp collect_properties(properties, parameter) do
props = Map.put(properties["properties"], parameter["name"], %{"type" => parameter["type"]})
Map.put(properties, "properties", props)
end
@doc false
defp read_swagger_schema(file) do
schema = File.read(file) |> elem(1) |> PhoenixSwagger.json_library().decode() |> elem(1)
# get rid from all keys besides 'paths' and 'definitions' as we
# need only in these fields for validation
Enum.reduce(schema, %{}, fn map, acc ->
{key, val} = map
if key in ["basePath", "paths", "definitions"] do
Map.put_new(acc, key, val)
else
acc
end
end)
end
end