/
pipeline.ex
172 lines (145 loc) · 4.8 KB
/
pipeline.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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
defmodule Phoenix.Controller.Pipeline do
@moduledoc false
@doc false
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
@behaviour Plug
require Phoenix.Endpoint
import Phoenix.Controller.Pipeline
Module.register_attribute(__MODULE__, :plugs, accumulate: true)
@before_compile Phoenix.Controller.Pipeline
@phoenix_log_level Keyword.get(opts, :log, :debug)
@phoenix_fallback :unregistered
@doc false
def init(opts), do: opts
@doc false
def call(conn, action) when is_atom(action) do
conn = update_in conn.private,
&(&1 |> Map.put(:phoenix_controller, __MODULE__)
|> Map.put(:phoenix_action, action))
Phoenix.Endpoint.instrument conn, :phoenix_controller_call,
%{conn: conn, log_level: @phoenix_log_level}, fn ->
phoenix_controller_pipeline(conn, action)
end
end
@doc false
def action(%Plug.Conn{private: %{phoenix_action: action}} = conn, _options) do
apply(__MODULE__, action, [conn, conn.params])
end
defoverridable [init: 1, call: 2, action: 2]
end
end
@doc false
def __action_fallback__(plug) do
quote bind_quoted: [plug: plug] do
@phoenix_fallback Phoenix.Controller.Pipeline.validate_fallback(
plug,
__MODULE__,
Module.get_attribute(__MODULE__, :phoenix_fallback))
end
end
@doc false
def validate_fallback(plug, module, fallback) do
cond do
fallback == nil ->
raise """
action_fallback can only be called when using Phoenix.Controller.
Add `use Phoenix.Controller` to #{inspect module}
"""
fallback != :unregistered ->
raise "action_fallback can only be called a single time per controller."
not is_atom(plug) ->
raise ArgumentError, "expected action_fallback to be a module or function plug, got #{inspect plug}"
fallback == :unregistered ->
case Atom.to_charlist(plug) do
~c"Elixir." ++ _ -> {:module, plug}
_ -> {:function, plug}
end
end
end
@doc false
defmacro __before_compile__(env) do
action = {:action, [], true}
plugs = [action|Module.get_attribute(env.module, :plugs)]
{conn, body} = Plug.Builder.compile(env, plugs,
log_on_halt: :debug,
init_mode: Phoenix.plug_init_mode())
fallback_ast =
env.module
|> Module.get_attribute(:phoenix_fallback)
|> build_fallback()
quote do
defoverridable [action: 2]
def action(var!(conn_before), opts) do
try do
var!(conn_after) = super(var!(conn_before), opts)
unquote(fallback_ast)
catch
:error, reason ->
Phoenix.Controller.Pipeline.__catch__(
var!(conn_before), reason, __MODULE__,
var!(conn_before).private.phoenix_action, System.stacktrace()
)
end
end
defp phoenix_controller_pipeline(unquote(conn), var!(action)) do
var!(conn) = unquote(conn)
var!(controller) = __MODULE__
_ = var!(conn)
_ = var!(controller)
_ = var!(action)
unquote(body)
end
end
end
defp build_fallback(:unregistered) do
quote do: var!(conn_after)
end
defp build_fallback({:module, plug}) do
quote bind_quoted: binding() do
case var!(conn_after) do
%Plug.Conn{} = conn_after -> conn_after
val -> plug.call(var!(conn_before), plug.init(val))
end
end
end
defp build_fallback({:function, plug}) do
quote do
case var!(conn_after) do
%Plug.Conn{} = conn_after -> conn_after
val -> unquote(plug)(var!(conn_before), val)
end
end
end
@doc false
def __catch__(%Plug.Conn{}, :function_clause, controller, action,
[{controller, action, [%Plug.Conn{} | _] = action_args, _loc} | _] = stack) do
args = [module: controller, function: action, arity: length(action_args), args: action_args]
reraise Phoenix.ActionClauseError, args, stack
end
def __catch__(%Plug.Conn{} = conn, reason, _controller, _action, stack) do
Plug.Conn.WrapperError.reraise(conn, :error, reason, stack)
end
@doc """
Stores a plug to be executed as part of the plug pipeline.
"""
defmacro plug(plug)
defmacro plug({:when, _, [plug, guards]}), do:
plug(plug, [], guards)
defmacro plug(plug), do:
plug(plug, [], true)
@doc """
Stores a plug with the given options to be executed as part of
the plug pipeline.
"""
defmacro plug(plug, opts)
defmacro plug(plug, {:when, _, [opts, guards]}), do:
plug(plug, opts, guards)
defmacro plug(plug, opts), do:
plug(plug, opts, true)
defp plug(plug, opts, guards) do
quote do
@plugs {unquote(plug), unquote(opts), unquote(Macro.escape(guards))}
end
end
end