dependabot-hex 0.107.13 → 0.107.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,216 @@
1
+ defprotocol Jason.Encoder do
2
+ @moduledoc """
3
+ Protocol controlling how a value is encoded to JSON.
4
+
5
+ ## Deriving
6
+
7
+ The protocol allows leveraging the Elixir's `@derive` feature
8
+ to simplify protocol implementation in trivial cases. Accepted
9
+ options are:
10
+
11
+ * `:only` - encodes only values of specified keys.
12
+ * `:except` - encodes all struct fields except specified keys.
13
+
14
+ By default all keys except the `:__struct__` key are encoded.
15
+
16
+ ## Example
17
+
18
+ Let's assume a presence of the following struct:
19
+
20
+ defmodule Test do
21
+ defstruct [:foo, :bar, :baz]
22
+ end
23
+
24
+ If we were to call `@derive Jason.Encoder` just before `defstruct`,
25
+ an implementaion similar to the follwing implementation would be generated:
26
+
27
+ defimpl Jason.Encoder, for: Test do
28
+ def encode(value, opts) do
29
+ Jason.Encode.map(Map.take(value, [:foo, :bar, :baz]), opts)
30
+ end
31
+ end
32
+
33
+ If we called `@derive {Jason.Encoder, only: [:foo]}`, an implementation
34
+ similar to the following implementation would be genrated:
35
+
36
+ defimpl Jason.Encoder, for: Test do
37
+ def encode(value, opts) do
38
+ Jason.Encode.map(Map.take(value, [:foo]), opts)
39
+ end
40
+ end
41
+
42
+ If we called `@derive {Jason.Encoder, except: [:foo]}`, an implementation
43
+ similar to the following implementation would be generated:
44
+
45
+ defimpl Jason.Encoder, for: Test do
46
+ def encode(value, opts) do
47
+ Jason.Encode.map(Map.take(value, [:bar, :baz]), opts)
48
+ end
49
+ end
50
+
51
+ The actually generated implementations are more efficient computing some data
52
+ during compilation similar to the macros from the `Jason.Helpers` module.
53
+
54
+ ## Explicit implementation
55
+
56
+ If you wish to implement the protocol fully yourself, it is advised to
57
+ use functions from the `Jason.Encode` module to do the actual iodata
58
+ generation - they are highly optimized and verified to always produce
59
+ valid JSON.
60
+ """
61
+
62
+ @type t :: term
63
+ @type opts :: Jason.Encode.opts()
64
+
65
+ @fallback_to_any true
66
+
67
+ @doc """
68
+ Encodes `value` to JSON.
69
+
70
+ The argument `opts` is opaque - it can be passed to various functions in
71
+ `Jason.Encode` (or to the protocol function itself) for encoding values to JSON.
72
+ """
73
+ @spec encode(t, opts) :: iodata
74
+ def encode(value, opts)
75
+ end
76
+
77
+ defimpl Jason.Encoder, for: Any do
78
+ defmacro __deriving__(module, struct, opts) do
79
+ fields = fields_to_encode(struct, opts)
80
+ kv = Enum.map(fields, &{&1, generated_var(&1, __MODULE__)})
81
+ escape = quote(do: escape)
82
+ encode_map = quote(do: encode_map)
83
+ encode_args = [escape, encode_map]
84
+ kv_iodata = Jason.Codegen.build_kv_iodata(kv, encode_args)
85
+
86
+ quote do
87
+ defimpl Jason.Encoder, for: unquote(module) do
88
+ require Jason.Helpers
89
+
90
+ def encode(%{unquote_splicing(kv)}, {unquote(escape), unquote(encode_map)}) do
91
+ unquote(kv_iodata)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ # The same as Macro.var/2 except it sets generated: true
98
+ defp generated_var(name, context) do
99
+ {name, [generated: true], context}
100
+ end
101
+
102
+ def encode(%_{} = struct, _opts) do
103
+ raise Protocol.UndefinedError,
104
+ protocol: @protocol,
105
+ value: struct,
106
+ description: """
107
+ Jason.Encoder protocol must always be explicitly implemented.
108
+
109
+ If you own the struct, you can derive the implementation specifying \
110
+ which fields should be encoded to JSON:
111
+
112
+ @derive {Jason.Encoder, only: [....]}
113
+ defstruct ...
114
+
115
+ It is also possible to encode all fields, although this should be \
116
+ used carefully to avoid accidentally leaking private information \
117
+ when new fields are added:
118
+
119
+ @derive Jason.Encoder
120
+ defstruct ...
121
+
122
+ Finally, if you don't own the struct you want to encode to JSON, \
123
+ you may use Protocol.derive/3 placed outside of any module:
124
+
125
+ Protocol.derive(Jason.Encoder, NameOfTheStruct, only: [...])
126
+ Protocol.derive(Jason.Encoder, NameOfTheStruct)
127
+ """
128
+ end
129
+
130
+ def encode(value, _opts) do
131
+ raise Protocol.UndefinedError,
132
+ protocol: @protocol,
133
+ value: value,
134
+ description: "Jason.Encoder protocol must always be explicitly implemented"
135
+ end
136
+
137
+ defp fields_to_encode(struct, opts) do
138
+ cond do
139
+ only = Keyword.get(opts, :only) ->
140
+ only
141
+
142
+ except = Keyword.get(opts, :except) ->
143
+ Map.keys(struct) -- [:__struct__ | except]
144
+
145
+ true ->
146
+ Map.keys(struct) -- [:__struct__]
147
+ end
148
+ end
149
+ end
150
+
151
+ # The following implementations are formality - they are already covered
152
+ # by the main encoding mechanism in Jason.Encode, but exist mostly for
153
+ # documentation purposes and if anybody had the idea to call the protocol directly.
154
+
155
+ defimpl Jason.Encoder, for: Atom do
156
+ def encode(atom, opts) do
157
+ Jason.Encode.atom(atom, opts)
158
+ end
159
+ end
160
+
161
+ defimpl Jason.Encoder, for: Integer do
162
+ def encode(integer, _opts) do
163
+ Jason.Encode.integer(integer)
164
+ end
165
+ end
166
+
167
+ defimpl Jason.Encoder, for: Float do
168
+ def encode(float, _opts) do
169
+ Jason.Encode.float(float)
170
+ end
171
+ end
172
+
173
+ defimpl Jason.Encoder, for: List do
174
+ def encode(list, opts) do
175
+ Jason.Encode.list(list, opts)
176
+ end
177
+ end
178
+
179
+ defimpl Jason.Encoder, for: Map do
180
+ def encode(map, opts) do
181
+ Jason.Encode.map(map, opts)
182
+ end
183
+ end
184
+
185
+ defimpl Jason.Encoder, for: BitString do
186
+ def encode(binary, opts) when is_binary(binary) do
187
+ Jason.Encode.string(binary, opts)
188
+ end
189
+
190
+ def encode(bitstring, _opts) do
191
+ raise Protocol.UndefinedError,
192
+ protocol: @protocol,
193
+ value: bitstring,
194
+ description: "cannot encode a bitstring to JSON"
195
+ end
196
+ end
197
+
198
+ defimpl Jason.Encoder, for: [Date, Time, NaiveDateTime, DateTime] do
199
+ def encode(value, _opts) do
200
+ [?\", @for.to_iso8601(value), ?\"]
201
+ end
202
+ end
203
+
204
+ defimpl Jason.Encoder, for: Decimal do
205
+ def encode(value, _opts) do
206
+ # silence the xref warning
207
+ decimal = Decimal
208
+ [?\", decimal.to_string(value), ?\"]
209
+ end
210
+ end
211
+
212
+ defimpl Jason.Encoder, for: Jason.Fragment do
213
+ def encode(%{encode: encode}, opts) do
214
+ encode.(opts)
215
+ end
216
+ end
@@ -0,0 +1,253 @@
1
+ defmodule Jason.Formatter do
2
+ @moduledoc ~S"""
3
+ Pretty-printing and minimizing functions for JSON-encoded data.
4
+
5
+ Input is required to be in an 8-bit-wide encoding such as UTF-8 or Latin-1
6
+ in `t:iodata/0` format. Input must ve valid JSON, invalid JSON may produce
7
+ unexpected results or errors.
8
+ """
9
+
10
+ @type opts :: [
11
+ {:indent, iodata}
12
+ | {:line_separator, iodata}
13
+ | {:record_separator, iodata}
14
+ | {:after_colon, iodata}
15
+ ]
16
+
17
+ import Record
18
+ defrecordp :opts, [:indent, :line, :record, :colon]
19
+
20
+ @doc ~S"""
21
+ Pretty-prints JSON-encoded `input`.
22
+
23
+ `input` may contain multiple JSON objects or arrays, optionally separated
24
+ by whitespace (e.g., one object per line). Objects in output will be
25
+ separated by newlines. No trailing newline is emitted.
26
+
27
+ ## Options
28
+
29
+ * `:indent` - used for nested objects and arrays (default: two spaces - `" "`);
30
+ * `:line_separator` - used in nested objects (default: `"\n"`);
31
+ * `:record_separator` - separates root-level objects and arrays
32
+ (default is the value for `:line_separator` option);
33
+ * `:after_colon` - printed after a colon inside objects (default: one space - `" "`).
34
+
35
+ ## Examples
36
+
37
+ iex> Jason.Formatter.pretty_print(~s|{"a":{"b": [1, 2]}}|)
38
+ ~s|{
39
+ "a": {
40
+ "b": [
41
+ 1,
42
+ 2
43
+ ]
44
+ }
45
+ }|
46
+
47
+ """
48
+ @spec pretty_print(iodata, opts) :: binary
49
+ def pretty_print(input, opts \\ []) do
50
+ input
51
+ |> pretty_print_to_iodata(opts)
52
+ |> IO.iodata_to_binary()
53
+ end
54
+
55
+ @doc ~S"""
56
+ Pretty-prints JSON-encoded `input` and returns iodata.
57
+
58
+ This function should be preferred to `pretty_print/2`, if the pretty-printed
59
+ JSON will be handed over to one of the IO functions or sent
60
+ over the socket. The Erlang runtime is able to leverage vectorised
61
+ writes and avoid allocating a continuous buffer for the whole
62
+ resulting string, lowering memory use and increasing performance.
63
+ """
64
+ @spec pretty_print_to_iodata(iodata, opts) :: iodata
65
+ def pretty_print_to_iodata(input, opts \\ []) do
66
+ opts = parse_opts(opts, " ", "\n", nil, " ")
67
+
68
+ depth = :first
69
+ empty = false
70
+
71
+ {output, _state} = pp_iodata(input, [], depth, empty, opts)
72
+
73
+ output
74
+ end
75
+
76
+ @doc ~S"""
77
+ Minimizes JSON-encoded `input`.
78
+
79
+ `input` may contain multiple JSON objects or arrays, optionally
80
+ separated by whitespace (e.g., one object per line). Minimized
81
+ output will contain one object per line. No trailing newline is emitted.
82
+
83
+ ## Options
84
+
85
+ * `:record_separator` - controls the string used as newline (default: `"\n"`).
86
+
87
+ ## Examples
88
+
89
+ iex> Jason.Formatter.minimize(~s|{ "a" : "b" , "c": \n\n 2}|)
90
+ ~s|{"a":"b","c":2}|
91
+
92
+ """
93
+ @spec minimize(iodata, opts) :: binary
94
+ def minimize(input, opts \\ []) do
95
+ input
96
+ |> minimize_to_iodata(opts)
97
+ |> IO.iodata_to_binary()
98
+ end
99
+
100
+ @doc ~S"""
101
+ Minimizes JSON-encoded `input` and returns iodata.
102
+
103
+ This function should be preferred to `minimize/2`, if the minimized
104
+ JSON will be handed over to one of the IO functions or sent
105
+ over the socket. The Erlang runtime is able to leverage vectorised
106
+ writes and avoid allocating a continuous buffer for the whole
107
+ resulting string, lowering memory use and increasing performance.
108
+ """
109
+ @spec minimize_to_iodata(iodata, opts) :: iodata
110
+ def minimize_to_iodata(input, opts) do
111
+ record = Keyword.get(opts, :record_separator, "\n")
112
+ opts = opts(indent: "", line: "", record: record, colon: "")
113
+
114
+ depth = :first
115
+ empty = false
116
+
117
+ {output, _state} = pp_iodata(input, [], depth, empty, opts)
118
+
119
+ output
120
+ end
121
+
122
+ defp parse_opts([{option, value} | opts], indent, line, record, colon) do
123
+ value = IO.iodata_to_binary(value)
124
+ case option do
125
+ :indent -> parse_opts(opts, value, line, record, colon)
126
+ :record_separator -> parse_opts(opts, indent, line, value, colon)
127
+ :after_colon -> parse_opts(opts, indent, line, record, value)
128
+ :line_separator -> parse_opts(opts, indent, value, record || value, colon)
129
+ end
130
+ end
131
+
132
+ defp parse_opts([], indent, line, record, colon) do
133
+ opts(indent: indent, line: line, record: record || line, colon: colon)
134
+ end
135
+
136
+ for depth <- 1..16 do
137
+ defp tab(" ", unquote(depth)), do: unquote(String.duplicate(" ", depth))
138
+ end
139
+
140
+ defp tab("", _), do: ""
141
+ defp tab(indent, depth), do: List.duplicate(indent, depth)
142
+
143
+ defp pp_iodata(<<>>, output_acc, depth, empty, opts) do
144
+ {output_acc, &pp_iodata(&1, &2, depth, empty, opts)}
145
+ end
146
+
147
+ defp pp_iodata(<<byte, rest::binary>>, output_acc, depth, empty, opts) do
148
+ pp_byte(byte, rest, output_acc, depth, empty, opts)
149
+ end
150
+
151
+ defp pp_iodata([], output_acc, depth, empty, opts) do
152
+ {output_acc, &pp_iodata(&1, &2, depth, empty, opts)}
153
+ end
154
+
155
+ defp pp_iodata([byte | rest], output_acc, depth, empty, opts) when is_integer(byte) do
156
+ pp_byte(byte, rest, output_acc, depth, empty, opts)
157
+ end
158
+
159
+ defp pp_iodata([head | tail], output_acc, depth, empty, opts) do
160
+ {output_acc, cont} = pp_iodata(head, output_acc, depth, empty, opts)
161
+ cont.(tail, output_acc)
162
+ end
163
+
164
+ defp pp_byte(byte, rest, output, depth, empty, opts) when byte in ' \n\r\t' do
165
+ pp_iodata(rest, output, depth, empty, opts)
166
+ end
167
+
168
+ defp pp_byte(byte, rest, output, depth, empty, opts) when byte in '{[' do
169
+ {out, depth} =
170
+ cond do
171
+ depth == :first -> {byte, 1}
172
+ depth == 0 -> {[opts(opts, :record), byte], 1}
173
+ empty -> {[opts(opts, :line), tab(opts(opts, :indent), depth), byte], depth + 1}
174
+ true -> {byte, depth + 1}
175
+ end
176
+
177
+ empty = true
178
+ pp_iodata(rest, [output, out], depth, empty, opts)
179
+ end
180
+
181
+ defp pp_byte(byte, rest, output, depth, true = _empty, opts) when byte in '}]' do
182
+ empty = false
183
+ depth = depth - 1
184
+ pp_iodata(rest, [output, byte], depth, empty, opts)
185
+ end
186
+
187
+ defp pp_byte(byte, rest, output, depth, false = empty, opts) when byte in '}]' do
188
+ depth = depth - 1
189
+ out = [opts(opts, :line), tab(opts(opts, :indent), depth), byte]
190
+ pp_iodata(rest, [output, out], depth, empty, opts)
191
+ end
192
+
193
+ defp pp_byte(byte, rest, output, depth, _empty, opts) when byte in ',' do
194
+ empty = false
195
+ out = [byte, opts(opts, :line), tab(opts(opts, :indent), depth)]
196
+ pp_iodata(rest, [output, out], depth, empty, opts)
197
+ end
198
+
199
+ defp pp_byte(byte, rest, output, depth, empty, opts) when byte in ':' do
200
+ out = [byte, opts(opts, :colon)]
201
+ pp_iodata(rest, [output, out], depth, empty, opts)
202
+ end
203
+
204
+ defp pp_byte(byte, rest, output, depth, empty, opts) do
205
+ out = if empty, do: [opts(opts, :line), tab(opts(opts, :indent), depth), byte], else: byte
206
+ empty = false
207
+
208
+ if byte == ?" do
209
+ pp_string(rest, [output, out], _in_bs = false, &pp_iodata(&1, &2, depth, empty, opts))
210
+ else
211
+ pp_iodata(rest, [output, out], depth, empty, opts)
212
+ end
213
+ end
214
+
215
+ defp pp_string(<<>>, output_acc, in_bs, cont) do
216
+ {output_acc, &pp_string(&1, &2, in_bs, cont)}
217
+ end
218
+
219
+ defp pp_string(binary, output_acc, true = _in_bs, cont) when is_binary(binary) do
220
+ <<byte, rest::binary>> = binary
221
+ pp_string(rest, [output_acc, byte], false, cont)
222
+ end
223
+
224
+ defp pp_string(binary, output_acc, false = _in_bs, cont) when is_binary(binary) do
225
+ case :binary.match(binary, ["\"", "\\"]) do
226
+ :nomatch ->
227
+ {[output_acc | binary], &pp_string(&1, &2, false, cont)}
228
+ {pos, 1} ->
229
+ {head, tail} = :erlang.split_binary(binary, pos + 1)
230
+ case :binary.at(binary, pos) do
231
+ ?\\ -> pp_string(tail, [output_acc | head], true, cont)
232
+ ?" -> cont.(tail, [output_acc | head])
233
+ end
234
+ end
235
+ end
236
+
237
+ defp pp_string([], output_acc, in_bs, cont) do
238
+ {output_acc, &pp_string(&1, &2, in_bs, cont)}
239
+ end
240
+
241
+ defp pp_string([byte | rest], output_acc, in_bs, cont) when is_integer(byte) do
242
+ cond do
243
+ in_bs -> pp_string(rest, [output_acc, byte], false, cont)
244
+ byte == ?" -> cont.(rest, [output_acc, byte])
245
+ true -> pp_string(rest, [output_acc, byte], byte == ?\\, cont)
246
+ end
247
+ end
248
+
249
+ defp pp_string([head | tail], output_acc, in_bs, cont) do
250
+ {output_acc, cont} = pp_string(head, output_acc, in_bs, cont)
251
+ cont.(tail, output_acc)
252
+ end
253
+ end