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 ~S""" Resolves a type given its `identifier`, `parent` identifier of the resolving subschema, the subschema's enclosing `SchemaDefinition` and the schema dictionary of the whole set of parsed JSON schema files. { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://example.com/circle.json", "title": "Circle", "description": "Schema for a circle shape", "type": "object", "properties": { "radius": { "type": "number" }, "center": { "$ref": "http://example.com/definitions.json#point" }, "color": { "$ref": "http://example.com/definitions.json#color" } }, "required": ["center", "radius"] } """ @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{ name: identifier, path: identifier, type: 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