Current section

Files

Jump to
oasis lib oasis parser.ex
Raw

lib/oasis/parser.ex

defmodule Oasis.Parser do
@moduledoc false
def parse(type, value) when is_bitstring(value) do
do_parse(type, value)
end
def parse(%{"type" => "null"}, nil), do: nil
def parse(%{"type" => "array"} = type, value) when is_list(value) do
# post request body as :urlencoded or :multipart
do_parse_array(type, value)
end
def parse(%{"type" => "object"} = type, value) when is_map(value) do
# post request body as :urlencoded or :multipart
do_parse_object(type, value)
end
def parse(_type, value), do: value
defp do_parse(%{"type" => type}, value)
when is_bitstring(value) and type in ["array", "object"] do
case Jason.decode(value) do
{:ok, value} ->
value
{:error, _error} ->
raise_argument_error()
end
end
defp do_parse(%{"type" => "boolean"}, "true"), do: true
defp do_parse(%{"type" => "boolean"}, "false"), do: false
defp do_parse(%{"type" => "boolean"}, value), do: value
defp do_parse(%{"type" => "number"}, value) do
case Float.parse(value) do
{value, ""} ->
value
_ ->
raise_argument_error()
end
end
defp do_parse(%{"type" => "integer"}, value) do
String.to_integer(value)
end
defp do_parse(%{"type" => "string"}, value) do
value
end
defp do_parse(_type, value) do
value
end
defp do_parse_array(%{"items" => %{"type" => _type} = schema}, list) do
Enum.map(list, fn value ->
parse(schema, value)
end)
end
defp do_parse_array(%{"items" => items}, list)
when is_list(items) and length(items) == length(list) do
items
|> Enum.zip(list)
|> Enum.map(fn {type, value} ->
parse(type, value)
end)
end
defp do_parse_array(%{"items" => items}, list)
when is_list(items) and length(items) != length(list) do
raise_argument_error()
end
defp do_parse_array(_, list), do: list
defp do_parse_object(
%{"properties" => properties, "additionalProperties" => additional_properties} = type,
map
)
when is_map(properties) and is_map(additional_properties) do
properties_of_dependencies = properties_from_schema_dependencies(type)
properties = Map.merge(properties, properties_of_dependencies)
pattern_properties = pattern_properties(type)
Enum.reduce(map, map, fn {name, value}, acc ->
type = type_to_parse_property(name, properties, pattern_properties, additional_properties)
Map.put(acc, name, parse(type, value))
end)
end
defp do_parse_object(%{"properties" => properties} = type, map) when is_map(properties) do
properties_of_dependencies = properties_from_schema_dependencies(type)
properties = Map.merge(properties, properties_of_dependencies)
pattern_properties = pattern_properties(type)
Enum.reduce(map, map, fn {name, value}, acc ->
type = type_to_parse_property(name, properties, pattern_properties)
if type == nil do
acc
else
Map.put(acc, name, parse(type, value))
end
end)
end
defp do_parse_object(_, map), do: map
defp properties_from_schema_dependencies(type) do
type
|> Map.get("dependencies", %{})
|> Enum.reduce(%{}, fn
{_name, definition}, acc when is_map(definition) ->
# schema dependencies
properties = Map.get(definition, "properties", %{})
Map.merge(acc, properties)
_, acc ->
acc
end)
end
defp pattern_properties(type) do
type
|> Map.get("patternProperties", %{})
|> Enum.reduce([], fn {pattern, definition}, acc ->
regex = Regex.compile!(pattern)
[{regex, definition} | acc]
end)
end
defp type_to_parse_property(name, properties, pattern_properties, additional_properties \\ nil) do
type = Map.get(properties, name)
if type == nil do
# may find a matched property from "patternProperties" by name
pattern = type_from_pattern_properties(name, pattern_properties)
if pattern == nil do
# `additional_properties` may be as nil or a schema (with only a type for any additional properties)
additional_properties
else
{_, type} = pattern
type
end
else
type
end
end
defp type_from_pattern_properties(_name, []), do: nil
defp type_from_pattern_properties(name, pattern_properties) do
Enum.find(pattern_properties, fn {regex, _definition} ->
Regex.match?(regex, name)
end)
end
defp raise_argument_error() do
raise ArgumentError, "argument error"
end
end