-
Notifications
You must be signed in to change notification settings - Fork 9
/
ref.ex
124 lines (96 loc) · 3.16 KB
/
ref.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
defmodule Xema.Ref do
@moduledoc """
This module contains a struct and functions to represent and handle
references.
"""
alias Xema.{Ref, Schema, Utils, Validator}
require Logger
@typedoc """
A reference contains a `pointer` and an optional `uri`.
"""
@type t :: %__MODULE__{
pointer: String.t(),
uri: URI.t() | nil
}
@derive {Inspect, optional: [:pointer, :uri]}
defstruct pointer: nil,
uri: nil
@compile {:inline, fetch_from_opts!: 2, fetch_by_key!: 3}
@doc """
Creates a new reference from the given `pointer`.
"""
@spec new(String.t()) :: Ref.t()
def new(pointer), do: %Ref{pointer: pointer}
@doc """
Creates a new reference from the given `pointer` and `uri`.
"""
@spec new(String.t(), URI.t() | nil) :: Ref.t()
def new("#" <> _ = pointer, _uri), do: new(pointer)
def new(pointer, uri) when is_binary(pointer),
do: %Ref{
pointer: pointer,
uri: Utils.update_uri(uri, pointer)
}
@doc """
Validates the given value with the referenced schema.
"""
@spec validate(Ref.t(), any, keyword) ::
:ok | {:error, map}
def validate(ref, value, opts) do
{schema, opts} = fetch_from_opts!(ref, opts)
Validator.validate(schema, value, opts)
end
@doc """
Returns the schema and the root for the given `ref` and `xema`.
"""
@spec fetch!(Ref.t(), struct, struct | nil) :: {struct | atom, struct}
def fetch!(ref, master, root) do
case fetch_by_key!(key(ref), master, root) do
{%Schema{}, _root} = schema ->
schema
{xema, root} ->
case fragment(ref) do
nil ->
{xema, root}
fragment ->
{Map.fetch!(xema.refs, fragment), xema}
end
end
end
@doc """
Returns the reference key for a `Ref` or an `URI`.
"""
@spec key(ref :: Ref.t() | URI.t()) :: String.t()
def key(%Ref{pointer: pointer, uri: nil}), do: pointer
def key(%Ref{uri: uri}), do: key(uri)
def key(%URI{} = uri), do: uri |> Map.put(:fragment, nil) |> URI.to_string()
def fragment(%Ref{uri: nil}), do: nil
def fragment(%Ref{uri: %URI{fragment: nil}}), do: nil
def fragment(%Ref{uri: %URI{fragment: ""}}), do: nil
def fragment(%Ref{uri: %URI{fragment: fragment}}), do: "##{fragment}"
defp fetch_from_opts!(%Ref{pointer: "#", uri: nil}, opts),
do: {opts[:root], opts}
defp fetch_from_opts!(%Ref{pointer: pointer, uri: nil}, opts),
do: {Map.fetch!(opts[:root].refs, pointer), opts}
defp fetch_from_opts!(%Ref{} = ref, opts) do
case fetch!(ref, opts[:master], opts[:root]) do
{:root, root} ->
{root, Keyword.put(opts, :root, root)}
{%Schema{} = schema, root} ->
{schema, Keyword.put(opts, :root, root)}
{xema, _} ->
{xema, Keyword.put(opts, :root, xema)}
end
end
defp fetch_by_key!("#", master, nil), do: {master, master}
defp fetch_by_key!("#", _master, root), do: {root, root}
defp fetch_by_key!(key, master, nil) do
{Map.fetch!(master.refs, key), master}
end
defp fetch_by_key!(key, master, root) do
case Map.get(root.refs, key) do
nil -> {Map.fetch!(master.refs, key), root}
schema -> {schema, root}
end
end
end