dependabot-hex 0.107.13 → 0.107.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -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