+16
lib/atex/did.ex
+16
lib/atex/did.ex
···
1
+
defmodule Atex.DID do
2
+
@re ~r/^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/
3
+
@blessed_re ~r/^did:(?:plc|web):[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/
4
+
5
+
@spec re() :: Regex.t()
6
+
def re, do: @re
7
+
8
+
@spec match?(String.t()) :: boolean()
9
+
def match?(value), do: Regex.match?(@re, value)
10
+
11
+
@spec blessed_re() :: Regex.t()
12
+
def blessed_re, do: @blessed_re
13
+
14
+
@spec match_blessed?(String.t()) :: boolean()
15
+
def match_blessed?(value), do: Regex.match?(@blessed_re, value)
16
+
end
+9
lib/atex/handle.ex
+9
lib/atex/handle.ex
···
1
+
defmodule Atex.Handle do
2
+
@re ~r/^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/
3
+
4
+
@spec re() :: Regex.t()
5
+
def re, do: @re
6
+
7
+
@spec match?(String.t()) :: boolean()
8
+
def match?(value), do: Regex.match?(@re, value)
9
+
end
+76
lib/atex/lexicon/validators.ex
+76
lib/atex/lexicon/validators.ex
···
1
+
defmodule Atex.Lexicon.Validators do
2
+
alias Atex.Lexicon.Validators
3
+
4
+
@type blob_option() :: {:accept, list(String.t())} | {:max_size, integer()}
5
+
6
+
@type blob_t() ::
7
+
%{
8
+
"$type": String.t(),
9
+
req: %{"$link": String.t()},
10
+
mimeType: String.t(),
11
+
size: integer()
12
+
}
13
+
| %{}
14
+
15
+
@spec string(list(Validators.String.option())) :: Peri.custom_def()
16
+
def string(options \\ []), do: {:custom, {Validators.String, :validate, [options]}}
17
+
18
+
@spec integer(list(Validators.Integer.option())) :: Peri.custom_def()
19
+
def integer(options \\ []), do: {:custom, {Validators.Integer, :validate, [options]}}
20
+
21
+
@spec array(Peri.schema_def(), list(Validators.Array.option())) :: Peri.custom_def()
22
+
def array(inner_type, options \\ []) do
23
+
{:ok, ^inner_type} = Peri.validate_schema(inner_type)
24
+
{:custom, {Validators.Array, :validate, [inner_type, options]}}
25
+
end
26
+
27
+
@spec blob(list(blob_option())) :: Peri.schema_def()
28
+
def blob(options \\ []) do
29
+
options = Keyword.validate!(options, accept: nil, max_size: nil)
30
+
accept = Keyword.get(options, :accept)
31
+
max_size = Keyword.get(options, :max_size)
32
+
33
+
mime_type =
34
+
{:required,
35
+
if(accept,
36
+
do: {:string, {:regex, strings_to_re(accept)}},
37
+
else: {:string, {:regex, ~r"^.+/.+$"}}
38
+
)}
39
+
40
+
{
41
+
:either,
42
+
{
43
+
# Newer blobs
44
+
%{
45
+
"$type": {:required, {:literal, "blob"}},
46
+
ref: {:required, %{"$link": {:required, :string}}},
47
+
mimeType: mime_type,
48
+
size: {:required, if(max_size != nil, do: {:integer, {:lte, max_size}}, else: :integer)}
49
+
},
50
+
# Old deprecated blobs
51
+
%{
52
+
cid: {:reqiured, :string},
53
+
mimeType: mime_type
54
+
}
55
+
}
56
+
}
57
+
end
58
+
59
+
@spec boolean_validate(boolean(), String.t(), keyword() | map()) ::
60
+
Peri.validation_result()
61
+
def boolean_validate(success?, error_message, context \\ []) do
62
+
if success? do
63
+
:ok
64
+
else
65
+
{:error, error_message, context}
66
+
end
67
+
end
68
+
69
+
@spec strings_to_re(list(String.t())) :: Regex.t()
70
+
defp strings_to_re(strings) do
71
+
strings
72
+
|> Enum.map(&String.replace(&1, "*", ".+"))
73
+
|> Enum.join("|")
74
+
|> then(&~r/^(#{&1})$/)
75
+
end
76
+
end
+52
lib/atex/lexicon/validators/array.ex
+52
lib/atex/lexicon/validators/array.ex
···
1
+
defmodule Atex.Lexicon.Validators.Array do
2
+
@type option() :: {:min_length, non_neg_integer()} | {:max_length, non_neg_integer()}
3
+
4
+
@option_keys [:min_length, :max_length]
5
+
6
+
# Needs type input
7
+
@spec validate(Peri.schema_def(), term(), list(option())) :: Peri.validation_result()
8
+
def validate(inner_type, value, options) when is_list(value) do
9
+
# TODO: validate inner_type with Peri to make sure it's correct?
10
+
11
+
options
12
+
|> Keyword.validate!(min_length: nil, max_length: nil)
13
+
|> Stream.map(&validate_option(value, &1))
14
+
|> Enum.find(:ok, fn x -> x != :ok end)
15
+
|> case do
16
+
:ok ->
17
+
value
18
+
|> Stream.map(&Peri.validate(inner_type, &1))
19
+
|> Enum.find({:ok, nil}, fn
20
+
{:ok, _} -> false
21
+
{:error, _} -> true
22
+
end)
23
+
|> case do
24
+
{:ok, _} -> :ok
25
+
e -> e
26
+
end
27
+
28
+
e ->
29
+
e
30
+
end
31
+
end
32
+
33
+
def validate(_inner_type, value, _options),
34
+
do: {:error, "expected type of `array`, received #{value}", [expected: :array, actual: value]}
35
+
36
+
@spec validate_option(list(), option()) :: Peri.validation_result()
37
+
defp validate_option(value, option)
38
+
39
+
defp validate_option(_value, {option, nil}) when option in @option_keys, do: :ok
40
+
41
+
defp validate_option(value, {:min_length, expected}) when length(value) >= expected,
42
+
do: :ok
43
+
44
+
defp validate_option(value, {:min_length, expected}) when length(value) < expected,
45
+
do: {:error, "should have a minimum length of #{expected}", [length: expected]}
46
+
47
+
defp validate_option(value, {:max_length, expected}) when length(value) <= expected,
48
+
do: :ok
49
+
50
+
defp validate_option(value, {:max_length, expected}) when length(value) > expected,
51
+
do: {:error, "should have a maximum length of #{expected}", [length: expected]}
52
+
end
+55
lib/atex/lexicon/validators/integer.ex
+55
lib/atex/lexicon/validators/integer.ex
···
1
+
defmodule Atex.Lexicon.Validators.Integer do
2
+
alias Atex.Lexicon.Validators
3
+
4
+
@type option() ::
5
+
{:minimum, integer()}
6
+
| {:maximum, integer()}
7
+
| {:enum, list(integer())}
8
+
| {:const, integer()}
9
+
10
+
@option_keys [:minimum, :maximum, :enum, :const]
11
+
12
+
@spec validate(term(), list(option())) :: Peri.validation_result()
13
+
def validate(value, options) when is_integer(value) do
14
+
options
15
+
|> Keyword.validate!(
16
+
minimum: nil,
17
+
maximum: nil,
18
+
enum: nil,
19
+
const: nil
20
+
)
21
+
|> Stream.map(&validate_option(value, &1))
22
+
|> Enum.find(:ok, fn x -> x != :ok end)
23
+
end
24
+
25
+
def validate(value, _options),
26
+
do:
27
+
{:error, "expected type of `integer`, received #{value}",
28
+
[expected: :integer, actual: value]}
29
+
30
+
@spec validate_option(integer(), option()) :: Peri.validation_result()
31
+
defp validate_option(value, option)
32
+
33
+
defp validate_option(_value, {option, nil}) when option in @option_keys, do: :ok
34
+
35
+
defp validate_option(value, {:minimum, expected}) when value >= expected, do: :ok
36
+
37
+
defp validate_option(value, {:minimum, expected}) when value < expected,
38
+
do: {:error, "", [value: expected]}
39
+
40
+
defp validate_option(value, {:maximum, expected}) when value <= expected, do: :ok
41
+
42
+
defp validate_option(value, {:maximum, expected}) when value > expected,
43
+
do: {:error, "", [value: expected]}
44
+
45
+
defp validate_option(value, {:enum, values}),
46
+
do:
47
+
Validators.boolean_validate(value in values, "should be one of the expected values",
48
+
enum: values
49
+
)
50
+
51
+
defp validate_option(value, {:const, expected}) when value == expected, do: :ok
52
+
53
+
defp validate_option(value, {:const, expected}),
54
+
do: {:error, "should match constant value", [actual: value, expected: expected]}
55
+
end
+182
lib/atex/lexicon/validators/string.ex
+182
lib/atex/lexicon/validators/string.ex
···
1
+
defmodule Atex.Lexicon.Validators.String do
2
+
alias Atex.Lexicon.Validators
3
+
4
+
@type format() ::
5
+
:at_identifier
6
+
| :at_uri
7
+
| :cid
8
+
| :datetime
9
+
| :did
10
+
| :handle
11
+
| :nsid
12
+
| :tid
13
+
| :record_key
14
+
| :uri
15
+
| :language
16
+
17
+
@type option() ::
18
+
{:format, format()}
19
+
| {:min_length, non_neg_integer()}
20
+
| {:max_length, non_neg_integer()}
21
+
| {:min_graphemes, non_neg_integer()}
22
+
| {:max_graphemes, non_neg_integer()}
23
+
| {:enum, list(String.t())}
24
+
| {:const, String.t()}
25
+
26
+
@option_keys [
27
+
:format,
28
+
:min_length,
29
+
:max_length,
30
+
:min_graphemes,
31
+
:max_graphemes,
32
+
:enum,
33
+
:const
34
+
]
35
+
36
+
@record_key_re ~r"^[a-zA-Z0-9.-_:~]$"
37
+
38
+
# TODO: probably should go into a different module, one with general lexicon -> validator gen conversions
39
+
@spec format_to_atom(String.t()) :: format()
40
+
def format_to_atom(format) do
41
+
case format do
42
+
"at-identifier" -> :at_identifier
43
+
"at-uri" -> :at_uri
44
+
"cid" -> :cid
45
+
"datetime" -> :datetime
46
+
"did" -> :did
47
+
"handle" -> :handle
48
+
"nsid" -> :nsid
49
+
"tid" -> :tid
50
+
"record-key" -> :record_key
51
+
"uri" -> :uri
52
+
"language" -> :language
53
+
_ -> raise "Unknown lexicon string format `#{format}`"
54
+
end
55
+
end
56
+
57
+
@spec validate(term(), list(option())) :: Peri.validation_result()
58
+
def validate(value, options) when is_binary(value) do
59
+
options
60
+
|> Keyword.validate!(
61
+
format: nil,
62
+
min_length: nil,
63
+
max_length: nil,
64
+
min_graphemes: nil,
65
+
max_graphemes: nil,
66
+
enum: nil,
67
+
const: nil
68
+
)
69
+
# Stream so we early exit at the first error.
70
+
|> Stream.map(&validate_option(value, &1))
71
+
|> Enum.find(:ok, fn x -> x != :ok end)
72
+
end
73
+
74
+
def validate(value, _options),
75
+
do:
76
+
{:error, "expected type of `string`, received #{value}", [expected: :string, actual: value]}
77
+
78
+
@spec validate_option(String.t(), option()) :: Peri.validation_result()
79
+
defp validate_option(value, option)
80
+
81
+
defp validate_option(_value, {option, nil}) when option in @option_keys, do: :ok
82
+
83
+
defp validate_option(value, {:format, :at_identifier}),
84
+
do:
85
+
Validators.boolean_validate(
86
+
Atex.DID.match?(value) or Atex.Handle.match?(value),
87
+
"should be a valid DID or handle"
88
+
)
89
+
90
+
defp validate_option(value, {:format, :at_uri}),
91
+
do: Validators.boolean_validate(Atex.AtURI.match?(value), "should be a valid at:// URI")
92
+
93
+
defp validate_option(value, {:format, :cid}) do
94
+
# TODO: is there a regex provided by the lexicon docs/somewhere?
95
+
try do
96
+
Multiformats.CID.decode(value)
97
+
rescue
98
+
_ -> {:error, "should be a valid CID", []}
99
+
end
100
+
end
101
+
102
+
defp validate_option(value, {:format, :datetime}) do
103
+
# NaiveDateTime is used over DateTime because the result isn't actually
104
+
# being used, so we don't need to include a calendar library just for this.
105
+
case NaiveDateTime.from_iso8601(value) do
106
+
{:ok, _} -> :ok
107
+
{:error, _} -> {:error, "should be a valid datetime", []}
108
+
end
109
+
end
110
+
111
+
defp validate_option(value, {:format, :did}),
112
+
do: Validators.boolean_validate(Atex.DID.match?(value), "should be a valid DID")
113
+
114
+
defp validate_option(value, {:format, :handle}),
115
+
do: Validators.boolean_validate(Atex.Handle.match?(value), "should be a valid handle")
116
+
117
+
defp validate_option(value, {:format, :nsid}),
118
+
do: Validators.boolean_validate(Atex.NSID.match?(value), "should be a valid NSID")
119
+
120
+
defp validate_option(value, {:format, :tid}),
121
+
do: Validators.boolean_validate(Atex.TID.match?(value), "should be a valid TID")
122
+
123
+
defp validate_option(value, {:format, :record_key}),
124
+
do:
125
+
Validators.boolean_validate(
126
+
Regex.match?(@record_key_re, value),
127
+
"should be a valid record key"
128
+
)
129
+
130
+
defp validate_option(value, {:format, :uri}) do
131
+
case URI.new(value) do
132
+
{:ok, _} -> :ok
133
+
{:error, _} -> {:error, "should be a valid URI", []}
134
+
end
135
+
end
136
+
137
+
defp validate_option(value, {:format, :language}) do
138
+
case Cldr.LanguageTag.parse(value) do
139
+
{:ok, _} -> :ok
140
+
{:error, _} -> {:error, "should be a valid BCP 47 language tag", []}
141
+
end
142
+
end
143
+
144
+
defp validate_option(value, {:min_length, expected}) when byte_size(value) >= expected,
145
+
do: :ok
146
+
147
+
defp validate_option(value, {:min_length, expected}) when byte_size(value) < expected,
148
+
do: {:error, "should have a minimum byte length of #{expected}", [length: expected]}
149
+
150
+
defp validate_option(value, {:max_length, expected}) when byte_size(value) <= expected,
151
+
do: :ok
152
+
153
+
defp validate_option(value, {:max_length, expected}) when byte_size(value) > expected,
154
+
do: {:error, "should have a maximum byte length of #{expected}", [length: expected]}
155
+
156
+
defp validate_option(value, {:min_graphemes, expected}),
157
+
do:
158
+
Validators.boolean_validate(
159
+
String.length(value) >= expected,
160
+
"should have a minimum length of #{expected}",
161
+
length: expected
162
+
)
163
+
164
+
defp validate_option(value, {:max_graphemes, expected}),
165
+
do:
166
+
Validators.boolean_validate(
167
+
String.length(value) <= expected,
168
+
"should have a maximum length of #{expected}",
169
+
length: expected
170
+
)
171
+
172
+
defp validate_option(value, {:enum, values}),
173
+
do:
174
+
Validators.boolean_validate(value in values, "should be one of the expected values",
175
+
enum: values
176
+
)
177
+
178
+
defp validate_option(value, {:const, expected}) when value == expected, do: :ok
179
+
180
+
defp validate_option(value, {:const, expected}),
181
+
do: {:error, "should match constant value", [actual: value, expected: expected]}
182
+
end
+12
lib/atex/nsid.ex
+12
lib/atex/nsid.ex
···
1
+
defmodule Atex.NSID do
2
+
@re ~r/^[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?:\\.[a-zA-Z](?:[a-zA-Z0-9]{0,62})?)$/
3
+
4
+
@spec re() :: Regex.t()
5
+
def re, do: @re
6
+
7
+
@spec match?(String.t()) :: boolean()
8
+
def match?(value), do: Regex.match?(@re, value)
9
+
10
+
# TODO: methods for fetching the authority and name from a nsid.
11
+
# maybe stuff for fetching the repo that belongs to an authority
12
+
end
+21
-1
lib/atex/tid.ex
+21
-1
lib/atex/tid.ex
···
122
122
"""
123
123
@spec decode(String.t()) :: {:ok, t()} | :error
124
124
def decode(<<timestamp::binary-size(11), clock_id::binary-size(2)>> = tid) do
125
-
if Regex.match?(@re, tid) do
125
+
if match?(tid) do
126
126
timestamp = Base32Sortable.decode(timestamp)
127
127
clock_id = Base32Sortable.decode(clock_id)
128
128
···
162
162
clock_id = (tid.clock_id &&& 1023) |> Base32Sortable.encode() |> String.pad_leading(2, "2")
163
163
timestamp <> clock_id
164
164
end
165
+
166
+
@doc """
167
+
Check if a given string matches the format for a TID.
168
+
169
+
## Examples
170
+
171
+
iex> Atex.TID.match?("3jzfcijpj2z2a")
172
+
true
173
+
174
+
iex> Atex.TID.match?("2222222222222")
175
+
true
176
+
177
+
iex> Atex.TID.match?("banana")
178
+
false
179
+
180
+
iex> Atex.TID.match?("kjzfcijpj2z2a")
181
+
false
182
+
"""
183
+
@spec match?(String.t()) :: boolean()
184
+
def match?(value), do: Regex.match?(@re, value)
165
185
end
166
186
167
187
defimpl String.Chars, for: Atex.TID do
+1
mix.exs
+1
mix.exs
+5
-2
mix.lock
+5
-2
mix.lock
···
1
1
%{
2
2
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
3
+
"cldr_utils": {:hex, :cldr_utils, "2.28.3", "d0ac5ed25913349dfaca8b7fe14722d588d8ccfa3e335b0510c7cc3f3c54d4e6", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "40083cd9a5d187f12d675cfeeb39285f0d43e7b7f2143765161b72205d57ffb5"},
3
4
"credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"},
5
+
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
4
6
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
7
+
"ex_cldr": {:hex, :ex_cldr, "2.42.0", "17ea930e88b8802b330e1c1e288cdbaba52cbfafcccf371ed34b299a47101ffb", [:mix], [{:cldr_utils, "~> 2.28", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "07264a7225810ecae6bdd6715d8800c037a1248dc0063923cddc4ca3c4888df6"},
5
8
"ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"},
6
9
"file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"},
7
10
"finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},
···
16
19
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
17
20
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
18
21
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
19
-
"peri": {:hex, :peri, "0.5.0", "c71e57d1c9abd26ae05f82cefb3a3f19ec2cf19602385a329843679af15a3082", [:mix], [{:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.1", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "526a93bfae9ba567f7cb0e87694de68b9e708e038a2cec7a3001851bcd4bfe71"},
22
+
"peri": {:hex, :peri, "0.5.1", "2140fd94095282aea1435c98307f25dde42005d319abb9927179301c310619c1", [:mix], [{:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.1", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "c214590d3bdf9d0e5f6d36df1cc87d956b7625c9ba32ca786983ba6df1936be3"},
20
23
"recase": {:hex, :recase, "0.8.1", "ab98cd35857a86fa5ca99036f575241d71d77d9c2ab0c39aacf1c9b61f6f7d1d", [:mix], [], "hexpm", "9fd8d63e7e43bd9ea385b12364e305778b2bbd92537e95c4b2e26fc507d5e4c2"},
21
-
"req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"},
24
+
"req": {:hex, :req, "0.5.12", "7ce85835867a114c28b6cfc2d8a412f86660290907315ceb173a00e587b853d2", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d65f3d0e7032eb245706554cb5240dbe7a07493154e2dd34e7bb65001aa6ef32"},
22
25
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
23
26
"typedstruct": {:hex, :typedstruct, "0.5.3", "d68ae424251a41b81a8d0c485328ab48edbd3858f3565bbdac21b43c056fc9b4", [:make, :mix], [], "hexpm", "b53b8186701417c0b2782bf02a2db5524f879b8488f91d1d83b97d84c2943432"},
24
27
"varint": {:hex, :varint, "1.5.1", "17160c70d0428c3f8a7585e182468cac10bbf165c2360cf2328aaa39d3fb1795", [:mix], [], "hexpm", "24f3deb61e91cb988056de79d06f01161dd01be5e0acae61d8d936a552f1be73"},