lib/parser/parser_result_types.ex

defmodule JsonSchema.Parser.ParserError do
  @moduledoc """
  Represents an error generated while parsing a JSON schema object.
  """

  use TypedStruct
  alias JsonSchema.Types

  @type error_type ::
          :could_not_read_file
          | :invalid_json
          | :invalid_uri
          | :missing_property
          | :name_collision
          | :name_not_a_regex
          | :unexpected_type
          | :unexpected_value
          | :unknown_enum_type
          | :unknown_node_type
          | :unknown_primitive_type
          | :unknown_type
          | :unknown_union_type
          | :unresolved_reference

  typedstruct do
    field :identifier, Types.typeIdentifier(), enforce: true
    field :error_type, error_type, enforce: true
    field :message, String.t(), enforce: true
  end

  @doc """
  Constructs a `ParserError`.
  """
  @spec new(Types.typeIdentifier(), error_type, String.t()) :: t
  def new(identifier, error_type, message) do
    %__MODULE__{
      identifier: identifier,
      error_type: error_type,
      message: message
    }
  end
end

defmodule JsonSchema.Parser.ParserWarning do
  @moduledoc """
  Represents a warning generated while parsing a JSON schema object.
  """

  use TypedStruct
  alias JsonSchema.Types

  @type warning_type :: atom

  typedstruct do
    field :identifier, Types.typeIdentifier(), enforce: true
    field :warning_type, warning_type, enforce: true
    field :message, String.t(), enforce: true
  end

  @doc """
  Constructs a `ParserWarning`.
  """
  @spec new(Types.typeIdentifier(), atom, String.t()) :: t
  def new(identifier, warning_type, message) do
    %__MODULE__{
      identifier: identifier,
      warning_type: warning_type,
      message: message
    }
  end
end

defmodule JsonSchema.Parser.ParserResult do
  @moduledoc """
  Represents the result of parsing a subset of a JSON schema including
  parsed types, warnings, and errors.
  """

  use TypedStruct
  require Logger
  alias JsonSchema.{Parser, Types}
  alias Parser.{ErrorUtil, ParserError, ParserWarning}

  typedstruct do
    field :type_dict, Types.typeDictionary(), enforce: true
    field :warnings, [ParserWarning.t()], enforce: true
    field :errors, [ParserError.t()], enforce: true
  end

  @doc """
  Returns an empty `ParserResult`.
  """
  @spec new :: t
  def new, do: %__MODULE__{type_dict: %{}, warnings: [], errors: []}

  @doc """
  Creates a `ParserResult` from a type dictionary.

  A `ParserResult` consists of a type dictionary corresponding to the
  succesfully parsed part of a JSON schema object, and a list of warnings and
  errors encountered while parsing.
  """
  @spec new(Types.typeDictionary(), [ParserWarning.t()], [ParserError.t()]) :: t
  def new(type_dict, warnings \\ [], errors \\ []) do
    %__MODULE__{type_dict: type_dict, warnings: warnings, errors: errors}
  end

  @doc """
  Merges two `ParserResult`s and adds any collisions errors from merging their
  type dictionaries to the list of errors in the merged `ParserResult`.
  """
  @spec merge(t, t) :: t
  def merge(
        %__MODULE__{
          type_dict: type_dict1,
          warnings: warnings1,
          errors: errors1
        },
        %__MODULE__{
          type_dict: type_dict2,
          warnings: warnings2,
          errors: errors2
        }
      ) do
    keys1 = type_dict1 |> Map.keys() |> MapSet.new()
    keys2 = type_dict2 |> Map.keys() |> MapSet.new()

    collisions =
      keys1
      |> MapSet.intersection(keys2)
      |> Enum.map(fn schema_path ->
        {schema_path, [ErrorUtil.name_collision(schema_path)]}
      end)

    merged_type_dict = type_dict1 |> Map.merge(type_dict2)
    merged_warnings = warnings1 |> Enum.concat(warnings2)
    merged_errors = collisions |> Enum.concat(errors1) |> Enum.concat(errors2)

    %__MODULE__{
      type_dict: merged_type_dict,
      warnings: merged_warnings,
      errors: merged_errors
    }
  end
end

defmodule JsonSchema.Parser.SchemaResult do
  @moduledoc """
  Represents the result of parsing a whole JSON schema including the parsed
  schema, along with all warnings and errors generated while parsing the schema
  and its members.
  """

  use TypedStruct
  require Logger
  alias JsonSchema.Parser.{ErrorUtil, ParserError, ParserWarning}
  alias JsonSchema.Types

  typedstruct do
    field :schema_dict, Types.schemaDictionary(), enforce: true
    field :warnings, [{Path.t(), [ParserWarning.t()]}], enforce: true
    field :errors, [{Path.t(), [ParserError.t()]}], enforce: true
  end

  @doc """
  Returns an empty `SchemaResult`.
  """
  @spec new :: t
  def new, do: %__MODULE__{schema_dict: %{}, warnings: [], errors: []}

  @doc """
  Constructs a new `SchemaResult`. A `SchemaResult` consists of a schema
  dictionary corresponding to the succesfully parsed JSON schema files,
  and a list of warnings and errors encountered while parsing.
  """
  @spec new(Types.schemaDictionary(), [{Path.t(), [ParserWarning.t()]}], [
          {Path.t(), [ParserError.t()]}
        ]) :: t
  def new(schema_dict, warnings \\ [], errors \\ []) do
    %__MODULE__{schema_dict: schema_dict, warnings: warnings, errors: errors}
  end

  @doc """
  Merges two `SchemaResult`s and adds any collisions errors from merging their
  schema dictionaries to the list of errors in the merged `SchemaResult`.
  """
  @spec merge(t, t) :: t
  def merge(
        %__MODULE__{
          schema_dict: schema_dict1,
          warnings: warnings1,
          errors: errors1
        },
        %__MODULE__{
          schema_dict: schema_dict2,
          warnings: warnings2,
          errors: errors2
        }
      ) do
    keys1 = schema_dict1 |> Map.keys() |> MapSet.new()
    keys2 = schema_dict2 |> Map.keys() |> MapSet.new()

    collisions =
      keys1
      |> MapSet.intersection(keys2)
      |> Enum.map(fn schema_path ->
        {schema_path, [ErrorUtil.name_collision(schema_path)]}
      end)

    merged_schema_dict = Map.merge(schema_dict1, schema_dict2)
    merged_warnings = Enum.uniq(warnings1 ++ warnings2)
    merged_errors = Enum.uniq(collisions ++ errors1 ++ errors2)

    %__MODULE__{
      schema_dict: merged_schema_dict,
      warnings: merged_warnings,
      errors: merged_errors
    }
  end
end