-
Notifications
You must be signed in to change notification settings - Fork 574
/
json.ex
109 lines (86 loc) · 3.12 KB
/
json.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
defmodule Plug.Parsers.JSON do
@moduledoc """
Parses JSON request body.
JSON documents that aren't maps (arrays, strings, numbers, etc) are parsed
into a `"_json"` key to allow proper param merging.
An empty request body is parsed as an empty map.
## Options
All options supported by `Plug.Conn.read_body/2` are also supported here.
They are repeated here for convenience:
* `:length` - sets the maximum number of bytes to read from the request,
defaults to 8_000_000 bytes
* `:read_length` - sets the amount of bytes to read at one time from the
underlying socket to fill the chunk, defaults to 1_000_000 bytes
* `:read_timeout` - sets the timeout for each socket read, defaults to
15_000ms
So by default, `Plug.Parsers` will read 1_000_000 bytes at a time from the
socket with an overall limit of 8_000_000 bytes.
"""
@behaviour Plug.Parsers
def init(opts) do
{decoder, opts} = Keyword.pop(opts, :json_decoder)
{body_reader, opts} = Keyword.pop(opts, :body_reader, {Plug.Conn, :read_body, []})
decoder = validate_decoder!(decoder)
{body_reader, decoder, opts}
end
defp validate_decoder!(nil) do
raise ArgumentError, "JSON parser expects a :json_decoder option"
end
defp validate_decoder!({module, fun, args} = mfa)
when is_atom(module) and is_atom(fun) and is_list(args) do
arity = length(args) + 1
if Code.ensure_compiled(module) != {:module, module} do
raise ArgumentError,
"invalid :json_decoder option. The module #{inspect(module)} is not " <>
"loaded and could not be found"
end
if not function_exported?(module, fun, arity) do
raise ArgumentError,
"invalid :json_decoder option. The module #{inspect(module)} must " <>
"implement #{fun}/#{arity}"
end
mfa
end
defp validate_decoder!(decoder) when is_atom(decoder) do
validate_decoder!({decoder, :decode!, []})
end
defp validate_decoder!(decoder) do
raise ArgumentError,
"the :json_decoder option expects a module, or a three-element " <>
"tuple in the form of {module, function, extra_args}, got: #{inspect(decoder)}"
end
def parse(conn, "application", subtype, _headers, {{mod, fun, args}, decoder, opts}) do
if subtype == "json" or String.ends_with?(subtype, "+json") do
apply(mod, fun, [conn, opts | args]) |> decode(decoder)
else
{:next, conn}
end
end
def parse(conn, _type, _subtype, _headers, _opts) do
{:next, conn}
end
defp decode({:ok, "", conn}, _decoder) do
{:ok, %{}, conn}
end
defp decode({:ok, body, conn}, {module, fun, args}) do
try do
apply(module, fun, [body | args])
rescue
e -> raise Plug.Parsers.ParseError, exception: e
else
terms when is_map(terms) ->
{:ok, terms, conn}
terms ->
{:ok, %{"_json" => terms}, conn}
end
end
defp decode({:more, _, conn}, _decoder) do
{:error, :too_large, conn}
end
defp decode({:error, :timeout}, _decoder) do
raise Plug.TimeoutError
end
defp decode({:error, _}, _decoder) do
raise Plug.BadRequestError
end
end