zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

utils.ex (8564B)


      1 defmodule Plug.Router.InvalidSpecError do
      2   defexception message: "invalid route specification"
      3 end
      4 
      5 defmodule Plug.Router.MalformedURIError do
      6   defexception message: "malformed URI", plug_status: 400
      7 end
      8 
      9 defmodule Plug.Router.Utils do
     10   @moduledoc false
     11 
     12   @doc """
     13   Decodes path information for dispatching.
     14   """
     15   def decode_path_info!(conn) do
     16     # TODO: Remove rescue as this can't fail from Elixir v1.13
     17     try do
     18       Enum.map(conn.path_info, &URI.decode/1)
     19     rescue
     20       e in ArgumentError ->
     21         reason = %Plug.Router.MalformedURIError{message: e.message}
     22         Plug.Conn.WrapperError.reraise(conn, :error, reason, __STACKTRACE__)
     23     end
     24   end
     25 
     26   @doc """
     27   Converts a given method to its connection representation.
     28 
     29   The request method is stored in the `Plug.Conn` struct as an uppercase string
     30   (like `"GET"` or `"POST"`). This function converts `method` to that
     31   representation.
     32 
     33   ## Examples
     34 
     35       iex> Plug.Router.Utils.normalize_method(:get)
     36       "GET"
     37 
     38   """
     39   def normalize_method(method) do
     40     method |> to_string |> String.upcase()
     41   end
     42 
     43   @doc ~S"""
     44   Builds the pattern that will be used to match against the request's host
     45   (provided via the `:host`) option.
     46 
     47   If `host` is `nil`, a wildcard match (`_`) will be returned. If `host` ends
     48   with a dot, a match like `"host." <> _` will be returned.
     49 
     50   ## Examples
     51 
     52       iex> Plug.Router.Utils.build_host_match(nil)
     53       {:_, [], Plug.Router.Utils}
     54 
     55       iex> Plug.Router.Utils.build_host_match("foo.com")
     56       "foo.com"
     57 
     58       iex> "api." |> Plug.Router.Utils.build_host_match() |> Macro.to_string()
     59       "\"api.\" <> _"
     60 
     61   """
     62   def build_host_match(host) do
     63     cond do
     64       is_nil(host) -> quote do: _
     65       String.last(host) == "." -> quote do: unquote(host) <> _
     66       is_binary(host) -> host
     67     end
     68   end
     69 
     70   @doc """
     71   Generates a representation that will only match routes
     72   according to the given `spec`.
     73 
     74   If a non-binary spec is given, it is assumed to be
     75   custom match arguments and they are simply returned.
     76 
     77   ## Examples
     78 
     79       iex> Plug.Router.Utils.build_path_match("/foo/:id")
     80       {[:id], ["foo", {:id, [], nil}]}
     81 
     82   """
     83   def build_path_match(path, context \\ nil) when is_binary(path) do
     84     case build_path_clause(path, true, context) do
     85       {params, match, true, _post_match} ->
     86         {Enum.map(params, &String.to_atom(&1)), match}
     87 
     88       {_, _, _, _} ->
     89         raise Plug.Router.InvalidSpecError,
     90               "invalid dynamic path. Only letters, numbers, and underscore are allowed after : in " <>
     91                 inspect(path)
     92     end
     93   end
     94 
     95   @doc """
     96   Builds a list of path param names and var match pairs.
     97 
     98   This is used to build parameter maps from existing variables.
     99   Excludes variables with underscore.
    100 
    101   ## Examples
    102 
    103       iex> Plug.Router.Utils.build_path_params_match(["id"])
    104       [{"id", {:id, [], nil}}]
    105       iex> Plug.Router.Utils.build_path_params_match(["_id"])
    106       []
    107 
    108       iex> Plug.Router.Utils.build_path_params_match([:id])
    109       [{"id", {:id, [], nil}}]
    110       iex> Plug.Router.Utils.build_path_params_match([:_id])
    111       []
    112 
    113   """
    114   # TODO: Make me private in Plug v2.0
    115   def build_path_params_match(params, context \\ nil)
    116 
    117   def build_path_params_match([param | _] = params, context) when is_binary(param) do
    118     params
    119     |> Enum.reject(&match?("_" <> _, &1))
    120     |> Enum.map(&{&1, Macro.var(String.to_atom(&1), context)})
    121   end
    122 
    123   def build_path_params_match([param | _] = params, context) when is_atom(param) do
    124     params
    125     |> Enum.map(&{Atom.to_string(&1), Macro.var(&1, context)})
    126     |> Enum.reject(&match?({"_" <> _var, _macro}, &1))
    127   end
    128 
    129   def build_path_params_match([], _context) do
    130     []
    131   end
    132 
    133   @doc """
    134   Builds a clause with match, guards, and post matches,
    135   including the known parameters.
    136   """
    137   def build_path_clause(path, guard, context \\ nil) when is_binary(path) do
    138     compiled = :binary.compile_pattern([":", "*"])
    139 
    140     {params, match, guards, post_match} =
    141       path
    142       |> split()
    143       |> build_path_clause([], [], [], [], context, compiled)
    144 
    145     if guard != true and guards != [] do
    146       raise ArgumentError, "cannot use \"when\" guards in route when using suffix matches"
    147     end
    148 
    149     params = params |> Enum.uniq() |> Enum.reverse()
    150     guards = Enum.reduce(guards, guard, &quote(do: unquote(&1) and unquote(&2)))
    151     {params, match, guards, post_match}
    152   end
    153 
    154   defp build_path_clause([segment | rest], params, match, guards, post_match, context, compiled) do
    155     case :binary.matches(segment, compiled) do
    156       [] ->
    157         build_path_clause(rest, params, [segment | match], guards, post_match, context, compiled)
    158 
    159       [{prefix_size, _}] ->
    160         suffix_size = byte_size(segment) - prefix_size - 1
    161         <<prefix::binary-size(prefix_size), char, suffix::binary-size(suffix_size)>> = segment
    162         {param, suffix} = parse_suffix(suffix)
    163         params = [param | params]
    164         var = Macro.var(String.to_atom(param), context)
    165 
    166         case char do
    167           ?* when suffix != "" ->
    168             raise Plug.Router.InvalidSpecError,
    169                   "globs (*var) cannot be followed by suffixes, got: #{inspect(segment)}"
    170 
    171           ?* when rest != [] ->
    172             raise Plug.Router.InvalidSpecError,
    173                   "globs (*var) must always be in the last path, got glob in: #{inspect(segment)}"
    174 
    175           ?* ->
    176             submatch =
    177               if prefix != "" do
    178                 IO.warn("""
    179                 doing a prefix match with globs is deprecated, invalid segment #{inspect(segment)}.
    180 
    181                 You can either replace by a single segment match:
    182 
    183                     /foo/bar-:var
    184 
    185                 Or by mixing single segment match with globs:
    186 
    187                     /foo/bar-:var/*rest
    188                 """)
    189 
    190                 quote do: [unquote(prefix) <> _ | _] = unquote(var)
    191               else
    192                 var
    193               end
    194 
    195             match =
    196               case match do
    197                 [] ->
    198                   submatch
    199 
    200                 [last | match] ->
    201                   Enum.reverse([quote(do: unquote(last) | unquote(submatch)) | match])
    202               end
    203 
    204             {params, match, guards, post_match}
    205 
    206           ?: ->
    207             match =
    208               if prefix == "",
    209                 do: [var | match],
    210                 else: [quote(do: unquote(prefix) <> unquote(var)) | match]
    211 
    212             {post_match, guards} =
    213               if suffix == "" do
    214                 {post_match, guards}
    215               else
    216                 guard =
    217                   quote do
    218                     binary_part(
    219                       unquote(var),
    220                       byte_size(unquote(var)) - unquote(byte_size(suffix)),
    221                       unquote(byte_size(suffix))
    222                     ) == unquote(suffix)
    223                   end
    224 
    225                 trim =
    226                   quote do
    227                     unquote(var) = String.trim_trailing(unquote(var), unquote(suffix))
    228                   end
    229 
    230                 {[trim | post_match], [guard | guards]}
    231               end
    232 
    233             build_path_clause(rest, params, match, guards, post_match, context, compiled)
    234         end
    235 
    236       [_ | _] ->
    237         raise Plug.Router.InvalidSpecError,
    238               "only one dynamic entry (:var or *glob) per path segment is allowed, got: " <>
    239                 inspect(segment)
    240     end
    241   end
    242 
    243   defp build_path_clause([], params, match, guards, post_match, _context, _compiled) do
    244     {params, Enum.reverse(match), guards, post_match}
    245   end
    246 
    247   defp parse_suffix(<<h, t::binary>>) when h in ?a..?z or h == ?_,
    248     do: parse_suffix(t, <<h>>)
    249 
    250   defp parse_suffix(suffix) do
    251     raise Plug.Router.InvalidSpecError,
    252           "invalid dynamic path. The characters : and * must be immediately followed by " <>
    253             "lowercase letters or underscore, got: :#{suffix}"
    254   end
    255 
    256   defp parse_suffix(<<h, t::binary>>, acc)
    257        when h in ?a..?z or h in ?A..?Z or h in ?0..?9 or h == ?_,
    258        do: parse_suffix(t, <<acc::binary, h>>)
    259 
    260   defp parse_suffix(rest, acc),
    261     do: {acc, rest}
    262 
    263   @doc """
    264   Splits the given path into several segments.
    265   It ignores both leading and trailing slashes in the path.
    266 
    267   ## Examples
    268 
    269       iex> Plug.Router.Utils.split("/foo/bar")
    270       ["foo", "bar"]
    271 
    272       iex> Plug.Router.Utils.split("/:id/*")
    273       [":id", "*"]
    274 
    275       iex> Plug.Router.Utils.split("/foo//*_bar")
    276       ["foo", "*_bar"]
    277 
    278   """
    279   def split(bin) do
    280     for segment <- String.split(bin, "/"), segment != "", do: segment
    281   end
    282 
    283   @deprecated "Use Plug.forward/4 instead"
    284   defdelegate forward(conn, new_path, target, opts), to: Plug
    285 end