defmodule JsonSchema.Parser.ObjectParser do @behaviour JsonSchema.Parser.ParserBehaviour @moduledoc """ Parses a JSON schema object type: { "type": "object", "properties": { "color": { "$ref": "#/color" }, "title": { "type": "string" }, "radius": { "type": "number" } }, "required": [ "color", "radius" ] } Into an `JsonSchema.Types.ObjectType` """ require Logger alias JsonSchema.{Parser, Types} alias Parser.{ParserResult, Util} alias Types.ObjectType @doc """ Returns true if the json subschema represents an allOf type. ## Examples iex> type?(%{}) false iex> an_object = %{"properties" => %{"name" => %{"type" => "string"}}} iex> type?(an_object) true """ @impl JsonSchema.Parser.ParserBehaviour @spec type?(map) :: boolean def type?(schema_node) do properties = schema_node["properties"] is_map(properties) end @doc """ Parses a JSON schema object type into an `JsonSchema.Types.ObjectType`. """ @impl JsonSchema.Parser.ParserBehaviour @spec parse(Types.schemaNode(), URI.t(), URI.t(), URI.t(), String.t()) :: ParserResult.t() def parse(schema_node, parent_id, id, path, name) do required = Map.get(schema_node, "required", []) properties_path = Util.add_fragment_child(path, "properties") properties_result = schema_node |> Map.get("properties") |> parse_child_types(parent_id, properties_path) properties_type_dict = create_property_dict(properties_result.type_dict, properties_path) pattern_properties_path = Util.add_fragment_child(path, "patternProperties") pattern_properties_result = if schema_node["patternProperties"] != nil do schema_node |> Map.get("patternProperties") |> parse_child_types(parent_id, pattern_properties_path, true) else ParserResult.new() end pattern_properties_type_dict = create_property_dict(pattern_properties_result.type_dict, pattern_properties_path) {additional_properties_path, additional_properties_result} = if schema_node["additionalProperties"] != nil do parser_result = schema_node |> Map.get("additionalProperties") |> Util.parse_type(parent_id, path, "additionalProperties") if parser_result != nil do {Util.add_fragment_child(path, "additionalProperties"), parser_result} else {nil, ParserResult.new()} end else {nil, ParserResult.new()} end object_type = ObjectType.new( name, path, properties_type_dict, pattern_properties_type_dict, additional_properties_path, required ) object_type |> Util.create_type_dict(path, id) |> ParserResult.new() |> ParserResult.merge(properties_result) |> ParserResult.merge(pattern_properties_result) |> ParserResult.merge(additional_properties_result) end @spec parse_child_types(map, URI.t(), URI.t(), boolean) :: ParserResult.t() defp parse_child_types(node_properties, parent_id, child_path, name_is_regex \\ false) do init_result = ParserResult.new() node_properties |> Enum.reduce(init_result, fn {child_name, child_node}, acc_result -> child_types = Util.parse_type(child_node, parent_id, child_path, child_name, name_is_regex) ParserResult.merge(acc_result, child_types) end) end @doc """ Creates a property dictionary based on a type dictionary and a type path. ## Examples iex> type_dict = %{} ...> path = URI.parse("#") ...> JsonSchema.Parser.ObjectParser.create_property_dict(type_dict, path) %{} """ @spec create_property_dict(Types.typeDictionary(), URI.t()) :: Types.propertyDictionary() def create_property_dict(type_dict, path) do type_dict |> Enum.reduce(%{}, fn {_child_path, child_type}, acc_property_dict -> child_type_path = Util.add_fragment_child(path, child_type.name) child_property_dict = %{child_type.name => child_type_path} Map.merge(acc_property_dict, child_property_dict) end) end end