Packages

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

Current section

Files

Jump to
json_schema lib resolver.ex
Raw

lib/resolver.ex

defmodule JsonSchema.Resolver do
  @moduledoc """
  Module containing functions for resolving types. Main function being
  the `resolve_type` function.
  """

  alias JsonSchema.{Parser, Types}
  alias Parser.{ErrorUtil, ParserError}
  alias Types.{PrimitiveType, SchemaDefinition, TypeReference}

  @doc """
  Resolves a type given its identifier, parent identifier, its enclosing `SchemaDefinition`
  and the schema dictionary of the whole set of parsed JSON schema files.
  """
  @spec resolve_type(
          Types.typeIdentifier(),
          Types.typeIdentifier(),
          SchemaDefinition.t(),
          Types.schemaDictionary()
        ) ::
          {:ok, {Types.typeDefinition(), SchemaDefinition.t()}}
          | {:error, ParserError.t()}
  def resolve_type(identifier, parent, schema_def, schema_dict) do
    resolved_result =
      cond do
        identifier in ["string", "number", "integer", "boolean"] ->
          resolve_primitive_identifier(identifier, schema_def)

        URI.parse(identifier).scheme == nil ->
          resolve_uri_fragment_identifier(URI.parse(identifier), parent, schema_def)

        URI.parse(identifier).scheme != nil ->
          resolve_fully_qualified_uri_identifier(URI.parse(identifier), parent, schema_dict)

        true ->
          {:error, ErrorUtil.unresolved_reference(identifier, parent)}
      end

    case resolved_result do
      {:ok, {resolved_type, resolved_schema_def}} ->
        case resolved_type do
          %TypeReference{} ->
            resolve_type(
              resolved_type.path,
              parent,
              resolved_schema_def,
              schema_dict
            )

          _ ->
            {:ok, {resolved_type, resolved_schema_def}}
        end

      {:error, error} ->
        {:error, error}
    end
  end

  @spec resolve_primitive_identifier(String.t(), SchemaDefinition.t()) ::
          {:ok, {Types.typeDefinition(), SchemaDefinition.t()}}
  defp resolve_primitive_identifier(identifier, schema_def) do
    primitive_type = PrimitiveType.new(identifier, identifier, identifier)
    {:ok, {primitive_type, schema_def}}
  end

  @spec resolve_uri_fragment_identifier(
          URI.t(),
          URI.t(),
          SchemaDefinition.t()
        ) ::
          {:ok, {Types.typeDefinition(), SchemaDefinition.t()}}
          | {:error, ParserError.t()}
  defp resolve_uri_fragment_identifier(identifier, parent, schema_def) do
    type_dict = schema_def.types
    resolved_type = type_dict[to_string(identifier)]

    if resolved_type != nil do
      {:ok, {resolved_type, schema_def}}
    else
      {:error, ErrorUtil.unresolved_reference(identifier, parent)}
    end
  end

  @spec resolve_fully_qualified_uri_identifier(
          URI.t(),
          Types.typeIdentifier(),
          Types.schemaDictionary()
        ) ::
          {:ok, {Types.typeDefinition(), SchemaDefinition.t()}}
          | {:error, ParserError.t()}
  defp resolve_fully_qualified_uri_identifier(identifier, parent, schema_dict) do
    schema_id = determine_schema_id(identifier)
    schema_def = schema_dict[schema_id]

    if schema_def != nil do
      type_dict = schema_def.types

      resolved_type =
        cond do
          to_string(identifier) == schema_id ->
            type_dict["#"]

          type_dict[to_string(identifier)] != nil ->
            type_dict[to_string(identifier)]

          true ->
            type_dict["##{identifier.fragment}"]
        end

      if resolved_type != nil do
        {:ok, {resolved_type, schema_def}}
      else
        {:error, ErrorUtil.unresolved_reference(identifier, parent)}
      end
    else
      {:error, ErrorUtil.unresolved_reference(identifier, parent)}
    end
  end

  @spec determine_schema_id(URI.t()) :: String.t()
  defp determine_schema_id(identifier) do
    identifier
    |> Map.put(:fragment, nil)
    |> to_string
  end
end