Packages

A library for parsing, inspecting and manipulating JSON Schema documents.

Current section

Files

Jump to
json_schema lib parser object_parser.ex
Raw

lib/parser/object_parser.ex

defmodule JsonSchema.Parser.ObjectParser do
  @behaviour JsonSchema.Parser.ParserBehaviour
  @moduledoc ~S"""
  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, TypePath, Types}
  alias Parser.{ParserResult, Util}
  alias Types.ObjectType

  @doc ~S"""
  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 ~S"""
  Parses a JSON schema object type into an `JsonSchema.Types.ObjectType`.
  """
  @impl JsonSchema.Parser.ParserBehaviour
  @spec parse(Types.schemaNode(), URI.t(), URI.t(), TypePath.t(), String.t()) ::
          ParserResult.t()
  def parse(schema_node, parent_id, id, path, name) do
    required = Map.get(schema_node, "required", [])

    child_path = TypePath.add_child(path, "properties")

    child_types_result =
      schema_node
      |> Map.get("properties")
      |> parse_child_types(parent_id, child_path)

    type_dict = create_property_dict(child_types_result.type_dict, child_path)

    object_type = ObjectType.new(name, path, type_dict, required)

    object_type
    |> Util.create_type_dict(path, id)
    |> ParserResult.new()
    |> ParserResult.merge(child_types_result)
  end

  @spec parse_child_types(map, URI.t(), TypePath.t()) :: ParserResult.t()
  defp parse_child_types(node_properties, parent_id, child_path) 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)

      ParserResult.merge(acc_result, child_types)
    end)
  end

  @doc ~S"""
  Creates a property dictionary based on a type dictionary and a type path.

  ## Examples

      iex> type_dict = %{}
      ...> path = JsonSchema.TypePath.from_string("#")
      ...> JsonSchema.Parser.ObjectParser.create_property_dict(type_dict, path)
      %{}

  """
  @spec create_property_dict(Types.typeDictionary(), TypePath.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 = TypePath.add_child(path, child_type.name)

      if child_type_path == TypePath.from_string(child_path) do
        child_property_dict = %{child_type.name => child_type_path}
        Map.merge(acc_property_dict, child_property_dict)
      else
        acc_property_dict
      end
    end)
  end
end