Skip to content

Commit

Permalink
feat: Middleware.PathParams: add OpenAPI params
Browse files Browse the repository at this point in the history
Tesla.Middleware.PathParams has been extended to support OpenAPI style
parameters in addition to the existing Phoenix style parameters.

- Phoenix parameters begin with `:` and a letter (`a-zA-Z`) and continue
  to the next non-word character (not `a-zA-Z0-9_`). Examples include
  `:id`, `:post_id`, `:idPost`.

- OpenAPI parameters are contained in braces (`{}`), must begin with
  a letter (`a-zA-Z`) and may contain word characters and hyphens
  (`-a-zA-Z0-9_`). Examples inlucde `{id}`, `{post_id}`, `{IdPost}`.

Parameter value objects may be provided as maps with atom or string
keys, keyword lists, or structs. During path resolution, to avoid
potential atom exhaustion, the parameter value object is transformed to
a string-keyed map.

Parameter values that are `nil` or that are not present in the value
object are not replaced; all other values are converted to string (with
`to_string`) and encoded with `application/x-www-form-urlencoded`
formatting. If the value does not implement the `String.Chars` protocol,
it is the caller's responsibility to convert it to a string prior to
passing it to the Tesla client using this middleware.

Execution time is determined by the number of parameter patterns in the
path.

Closes: #566
  • Loading branch information
halostatue committed Dec 20, 2023
1 parent 189ffa5 commit ddb9ec3
Show file tree
Hide file tree
Showing 2 changed files with 320 additions and 60 deletions.
61 changes: 52 additions & 9 deletions lib/tesla/middleware/path_params.ex
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
defmodule Tesla.Middleware.PathParams do
@moduledoc """
Use templated URLs with separate params.
Use templated URLs with provided parameters in either Phoenix style (`:id`)
or OpenAPI style (`{id}`).
Useful when logging or reporting metric per URL.
Useful when logging or reporting metrics per URL.
## Parameter Values
Parameter values may be `t:struct/0` or must implement the `Enumerable`
protocol and produce `{key, value}` tuples when enumerated.
## Parameter Name Restrictions
Phoenix style parameters may contain letters, numbers, or underscores,
matching this regular expression:
:[a-zA-Z][_a-zA-Z0-9]*\b
OpenAPI style parameters may contain letters, numbers, underscores, or
hyphens (`-`), matching this regular expression:
\{[a-zA-Z][-_a-zA-Z0-9]*\}
In either case, parameters that begin with underscores (`_`), hyphens (`-`),
or numbers (`0-9`) are ignored and left as-is.
## Examples
```
```elixir
defmodule MyClient do
use Tesla
Expand All @@ -16,27 +37,49 @@ defmodule Tesla.Middleware.PathParams do
def user(id) do
params = [id: id]
get("/users/:id", opts: [path_params: params])
get("/users/{id}", opts: [path_params: params])
end
def posts(id, post_id) do
params = [id: id, post_id: post_id]
get("/users/:id/posts/:post_id", opts: [path_params: params])
end
end
```
"""

@behaviour Tesla.Middleware

@rx ~r/:([a-zA-Z]{1}[\w_]*)/

@impl Tesla.Middleware
def call(env, next, _) do
url = build_url(env.url, env.opts[:path_params])
Tesla.run(%{env | url: url}, next)
end

@rx ~r/:([a-zA-Z][a-zA-Z0-9_]*)|[{]([a-zA-Z][-a-zA-Z0-9_]*)[}]/

defp build_url(url, nil), do: url

defp build_url(url, params) do
Regex.replace(@rx, url, fn match, key ->
to_string(params[String.to_existing_atom(key)] || match)
defp build_url(url, params) when is_struct(params), do: build_url(url, Map.from_struct(params))

defp build_url(url, params) when is_map(params) or is_list(params) do
safe_params = Map.new(params, fn {name, value} -> {to_string(name), value} end)

Regex.replace(@rx, url, fn
# OpenAPI matches
match, "", name -> replace_param(safe_params, name, match)
# Phoenix matches
match, name, _ -> replace_param(safe_params, name, match)
end)
end

defp build_url(url, _params), do: url

defp replace_param(params, name, match) do
case Map.fetch(params, name) do
{:ok, nil} -> match
:error -> match
{:ok, value} -> URI.encode_www_form(to_string(value))
end
end
end
Loading

0 comments on commit ddb9ec3

Please sign in to comment.