1defmodule Maru.Builder.DSLs do
2  @moduledoc """
3  General DSLs for parsing router.
4  """
5
6  alias Maru.Struct.Resource
7  alias Maru.Struct.Plug, as: MaruPlug
8  alias Maru.Builder.Path, as: MaruPath
9
10  @doc """
11  Define path prefix of current router.
12  """
13  defmacro prefix(path) do
14    path = MaruPath.split path
15    quote do
16      Resource.push_path(unquote(path))
17    end
18  end
19
20
21  @doc """
22  Define params block of current endpoint.
23  """
24  defmacro params([do: block]) do
25    quote do
26      import Maru.Builder.Namespaces, only: []
27      import Kernel, except: [use: 1]
28      import Maru.Builder.Params
29      @group []
30      unquote(block)
31      import Maru.Builder.Params, only: []
32      import Kernel
33      import Maru.Builder.Namespaces
34    end
35  end
36
37
38  @doc """
39  Save shared param to module attribute.
40  """
41  defmacro params(name, [do: block]) do
42    quote do
43      @shared_params unquote({name, block |> Macro.escape})
44    end
45  end
46
47
48  @doc """
49  Define version of current router.
50  """
51  defmacro version(v) do
52    quote do
53      Resource.set_version(unquote(v))
54    end
55  end
56
57  @doc """
58  version: "v1", do ... end:
59    Version of routes within block.
60
61  version: "v2", extend: "v1", at: V1
62    Define version and extended router of current router.
63  """
64  defmacro version(v, [do: block]) do
65    quote do
66      s = Resource.snapshot
67      Resource.set_version(unquote(v))
68      unquote(block)
69      Resource.restore(s)
70    end
71  end
72
73  defmacro version(v, opts) do
74    quote do
75      Resource.set_version(unquote(v))
76      @extend {unquote(v), unquote(opts)}
77    end
78  end
79
80  defmacro helpers({_, _, module}) do
81    module = Module.concat(module)
82    quote do
83      unquote(module).__shared_params__ |> Enum.each(&(@shared_params &1))
84      import unquote(module)
85    end
86  end
87
88  defmacro helpers([do: block]) do
89    quote do
90      import Kernel, only: []
91      import Maru.Builder.DSLs, only: [params: 2]
92      unquote(block)
93      import Maru.Builder.DSLs
94      import Maru.Builder.DSLs, except: [params: 2]
95      import Kernel
96    end
97  end
98
99  @doc """
100  Define description for current endpoint.
101  """
102  defmacro desc(desc) do
103    quote do
104      @desc %{summary: unquote(desc)}
105    end
106  end
107
108  @doc """
109  Define description with a block for current endpoint.
110  """
111  defmacro desc(desc, [do: block]) do
112    quote do
113      @desc %{summary: unquote(desc)}
114      import Maru.Builder.Description
115      unquote(block)
116      import Maru.Builder.Description, only: []
117    end
118  end
119
120  @doc """
121  Mount another router to current router.
122  """
123  defmacro mount({_, _, [h | t]}=mod) do
124    h = Module.concat([h])
125    module =
126      __CALLER__.aliases
127      |> Keyword.get(h, h)
128      |> Module.split
129      |> Enum.concat(t)
130      |> Module.concat
131    try do
132      true = {:__routes__, 0} in module.__info__(:functions)
133    rescue
134      [UndefinedFunctionError, MatchError] ->
135        raise """
136          #{inspect module} is not an available Maru.Router.
137          If you mount it to another module written at the same file,
138          make sure this module at the front of the file.
139        """
140    end
141    quote do
142      for route <- unquote(mod).__routes__ do
143        @mounted Maru.Struct.Route.merge(
144          @resource, @plugs, __MODULE__, route
145        )
146      end
147    end
148  end
149
150  @doc """
151  Push a `Plug` struct to current scope.
152  """
153  defmacro plug(plug)
154
155  defmacro plug({:when, _, [plug, guards]}) do
156    do_plug(nil, plug, [], guards)
157  end
158
159  defmacro plug(plug) do
160    do_plug(nil, plug, [], true)
161  end
162
163  @doc """
164  Push a `Plug` struct with options and guards to current scope.
165  """
166  defmacro plug(plug, opts)
167
168  defmacro plug(plug, {:when, _, [opts, guards]}) do
169    do_plug(nil, plug, opts, guards)
170  end
171
172  defmacro plug(plug, opts) do
173    do_plug(nil, plug, opts, true)
174  end
175
176  @doc """
177  Push a overridable `Plug` struct to current scope.
178  """
179  defmacro plug_overridable(name, plug)
180
181  defmacro plug_overridable(name, {:when, _, [plug, guards]}) do
182    do_plug(name, plug, [], guards)
183  end
184
185  defmacro plug_overridable(name, plug) do
186    do_plug(name, plug, [], true)
187  end
188
189  @doc """
190  Push a overridable `Plug` struct with options and guards to current scope.
191  """
192  defmacro plug_overridable(name, plug, opts)
193
194  defmacro plug_overridable(name, plug, {:when, _, [opts, guards]}) do
195    do_plug(name, plug, opts, guards)
196  end
197
198  defmacro plug_overridable(name, plug, opts) do
199    do_plug(name, plug, opts, true)
200  end
201
202  defp do_plug(name, plug, opts, guards) do
203    quote do
204      Resource.push_plug(%MaruPlug{
205        name:    unquote(name),
206        plug:    unquote(plug),
207        options: unquote(opts),
208        guards:  unquote(Macro.escape(guards)),
209     })
210    end
211  end
212
213  @doc """
214  Define pipeline block of current endpoint.
215  """
216  defmacro pipeline(block) do
217    quote do
218      import Kernel, only: []
219      import Maru.Builder.DSLs, only: []
220      import Maru.Builder.Pipeline, only: [
221        plug: 1, plug: 2, plug_overridable: 2, plug_overridable: 3
222      ]
223      unquote(block)
224      import Maru.Builder.Pipeline, only: []
225      import Maru.Builder.DSLs
226      import Kernel
227    end
228  end
229
230
231  @doc """
232  Define plugs which execute before routes match.
233  """
234  defmacro before([do: block]) do
235    quote do
236      if @make_plug do
237        import Maru.Builder.DSLs, except: [
238          plug: 1, plug: 2, plug_overridable: 2, plug_overridable: 3
239        ]
240        import Maru.Builder.Before
241        unquote(block)
242        import Maru.Builder.Before, only: []
243        import Maru.Builder.DSLs
244      else
245        Maru.Utils.warn "#{inspect __MODULE__}: `before` only works for plug router, Ignore.\n"
246      end
247    end
248  end
249
250end
251