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