Current section
Files
Jump to
Current section
Files
lib/exonerate/id.ex
defmodule Exonerate.Id do
@moduledoc false
alias Exonerate.Cache
alias Exonerate.Degeneracy
alias Exonerate.Tools
alias Exonerate.Type
@doc """
walks through the schema and identifies places where ids are set.
It then clones the subschema where the ids are, and then caches this
as a separate entry under its own resource.
"""
def prescan(schema, module, resource, opts) do
root_resource =
resource
|> to_string
|> URI.parse()
resource_map =
Tools.scan(
schema,
%{JsonPtr.from_path("/") => root_resource},
&id_walk(module, &1, &2, &3, opts)
)
{schema, resource_map}
end
@type resource_map :: %{optional(JsonPtr.t()) => URI.t()}
@spec id_walk(module, Type.json(), JsonPtr.t(), resource_map, keyword) :: resource_map
defp id_walk(module, schema = %{"$id" => id}, pointer, resource_map, opts) do
register(module, schema, id, pointer, resource_map, opts)
end
defp id_walk(module, schema = %{"id" => id}, pointer, resource_map, opts) do
if match?({_, "properties"}, JsonPtr.pop(pointer)) do
# don't register an "id" which is underneath the "properties" key
resource_map
else
register(module, schema, id, pointer, resource_map, opts)
end
end
defp id_walk(_, _, _, acc, _), do: acc
defp register(module, schema, id, pointer, resource_map, opts) do
new_uri =
case URI.parse(id) do
# if we don't have any resource information, splice it into the existing resource.
%{fragment: fragment} when not is_nil(fragment) ->
raise ArgumentError, "id cannot contain a fragment (contained #{id})"
non_fragment_uri ->
resource_map
|> find_resource_uri(pointer)
|> elem(0)
|> Tools.uri_merge(non_fragment_uri)
end
# we have to canonicalize this, because it's possible this content
# is not reached from the main canonicalization effort.
schema = Degeneracy.canonicalize(schema, opts)
resource = to_string(new_uri)
Cache.put_schema(module, resource, schema)
Map.put(resource_map, pointer, new_uri)
end
@spec find_resource_uri(resource_map, JsonPtr.t()) :: {URI.t(), JsonPtr.t()}
@doc """
given a resource map and pointer, find the resource that matches the jsonpointer.
Note that resource map is a map of JsonPointer -> URI.
This function proceeds by reveres induction over JsonPtr. The execution time
might be very bad, but this function should be called rarely.
"""
def find_resource_uri(map, pointer) when is_map_key(map, pointer),
do: {map[pointer], JsonPtr.from_path("/")}
def find_resource_uri(map, pointer) do
{prev, leaf} = JsonPtr.pop(pointer)
{resource_uri, root} = find_resource_uri(map, prev)
{resource_uri, JsonPtr.join(root, leaf)}
end
end