Skip to content

Commit ad82697

Browse files
authored
Merge pull request #175 from pow-auth/refactor
Refactor
2 parents 7948f23 + 95c65d4 commit ad82697

34 files changed

+851
-676
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414
* `Assent.Strategy.OAuth2` now supports PKCE
1515
* `Assent.Strategy.OAuth2.Base.authorize_url/2` incomplete typespec fixed
1616
* `Assent.Strategy.decode_response/2` deprecated accepting result tuples and now accepts `Assent.HTTPAdapter.HTTPResponse` structs
17+
* `Assent.Strategy.request/5` deprecated in favor of `Assent.Strategy.http_request/5`
18+
* `Assent.Strategy.decode_response/2` deprecated in favor of `Assent.HTTPAdapter.decode_response/2`
19+
* `Assent.Config.get/3` deprecated in favor of `Keyword.get/3`
20+
* `Assent.Config.put/3` deprecated in favor of `Keyword.put/3`
21+
* `Assent.Config.merge/2` deprecated in favor of `Keyword.merge/2`
22+
* `Assent.Config.t()` type deprecated in favor of `Keyword.t()` type
23+
* `Assent.Config.fetch/2` deprecated in favor of `Assent.fetch_config/2`
1724

1825
## v0.2.10 (2024-04-11)
1926

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ defmodule ProviderAuth do
137137

138138
@config
139139
# Session params should be added to the config so the strategy can use them
140-
|> Config.put(:session_params, session_params)
140+
|> Keyword.put(:session_params, session_params)
141141
|> Github.callback(params)
142142
|> case do
143143
{:ok, %{user: user, token: token}} ->
@@ -166,8 +166,6 @@ config :my_app, :strategies,
166166

167167
```elixir
168168
defmodule MultiProviderAuth do
169-
alias Assent.Config
170-
171169
@spec request(atom()) :: {:ok, map()} | {:error, term()}
172170
def request(provider) do
173171
config = config!(provider)
@@ -180,7 +178,7 @@ defmodule MultiProviderAuth do
180178
config = config!(provider)
181179

182180
config
183-
|> Assent.Config.put(:session_params, session_params)
181+
|> Keyword.put(:session_params, session_params)
184182
|> config[:strategy].callback(params)
185183
end
186184

@@ -189,7 +187,7 @@ defmodule MultiProviderAuth do
189187
Application.get_env(:my_app, :strategies)[provider] ||
190188
raise "No provider configuration for #{provider}"
191189

192-
Config.put(config, :redirect_uri, "http://localhost:4000/oauth/#{provider}/callback")
190+
Keyword.put(config, :redirect_uri, "http://localhost:4000/oauth/#{provider}/callback")
193191
end
194192
end
195193
```

integration/lib/router.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ defmodule IntegrationServer.Router do
7575

7676
unquote(path)
7777
|> config!()
78-
|> Assent.Config.put(:session_params, get_session(conn, :session_params))
78+
|> Keyword.put(:session_params, get_session(conn, :session_params))
7979
|> unquote(module).callback(conn.params)
8080
|> case do
8181
{:ok, %{user: user, token: token}} ->

lib/assent.ex

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
defmodule Assent do
22
@moduledoc false
33

4+
defmodule MissingConfigError do
5+
defexception [:key, :config]
6+
7+
@type t :: %__MODULE__{
8+
key: atom(),
9+
config: Keyword.t()
10+
}
11+
12+
def message(exception) do
13+
key = inspect(exception.key)
14+
config_keys = inspect(Keyword.keys(exception.config))
15+
16+
"Expected #{key} in config, got: #{config_keys}"
17+
end
18+
end
19+
420
defmodule CallbackError do
521
defexception [:message, :error, :error_uri]
622
end
@@ -18,18 +34,33 @@ defmodule Assent do
1834
end
1935

2036
defmodule MissingParamError do
21-
defexception [:expected_key, :params]
37+
defexception [:key, :params]
2238

2339
@type t :: %__MODULE__{
24-
expected_key: binary(),
40+
key: binary(),
2541
params: map()
2642
}
2743

44+
# TODO: Deprecated, remove in 0.3
45+
def exception(opts) do
46+
opts =
47+
case Keyword.fetch(opts, :expected_key) do
48+
{:ok, key} ->
49+
IO.warn("The `expected_key` option is deprecated. Please use `key` instead.")
50+
[key: key, params: opts[:params]]
51+
52+
:error ->
53+
opts
54+
end
55+
56+
struct!(__MODULE__, opts)
57+
end
58+
2859
def message(exception) do
29-
expected_key = inspect(exception.expected_key)
30-
params = inspect(Map.keys(exception.params))
60+
key = inspect(exception.key)
61+
param_keys = exception.params |> Map.keys() |> Enum.sort() |> inspect()
3162

32-
"Expected #{expected_key} in params, got: #{params}"
63+
"Expected #{key} in params, got: #{param_keys}"
3364
end
3465
end
3566

@@ -112,6 +143,49 @@ defmodule Assent do
112143
end
113144
end
114145

146+
@doc """
147+
Fetches the key value from the configuration.
148+
149+
Returns a `Assent.MissingConfigError` if the key is not found.
150+
"""
151+
@spec fetch_config(Keyword.t(), atom()) :: {:ok, any()} | {:error, MissingConfigError.t()}
152+
def fetch_config(config, key) when is_list(config) and is_atom(key) do
153+
case Keyword.fetch(config, key) do
154+
{:ok, value} -> {:ok, value}
155+
:error -> {:error, MissingConfigError.exception(key: key, config: config)}
156+
end
157+
end
158+
159+
@doc """
160+
Fetches the key value from the params.
161+
162+
Returns a `Assent.MissingParamError` if the key is not found.
163+
"""
164+
@spec fetch_param(map(), binary()) :: {:ok, any()} | {:error, MissingParamError.t()}
165+
def fetch_param(params, key) when is_map(params) and is_binary(key) do
166+
case Map.fetch(params, key) do
167+
{:ok, value} -> {:ok, value}
168+
:error -> {:error, MissingParamError.exception(key: key, params: params)}
169+
end
170+
end
171+
172+
@default_json_library (Code.ensure_loaded?(JSON) && JSON) || Jason
173+
174+
@doc """
175+
Fetches the JSON library in config.
176+
177+
If not found in provided config, this will attempt to load the JSON library
178+
from global application environment for `:assent`. Defaults to
179+
`#{inspect(@default_json_library)}`.
180+
"""
181+
@spec json_library(Keyword.t()) :: module()
182+
def json_library(config) do
183+
case Keyword.fetch(config, :json_library) do
184+
:error -> Application.get_env(:assent, :json_library, @default_json_library)
185+
{:ok, json_library} -> json_library
186+
end
187+
end
188+
115189
import Bitwise
116190

117191
@doc false

lib/assent/config.ex

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
# TODO: Deprecated, remove in 0.3
12
defmodule Assent.Config do
2-
@moduledoc """
3-
Methods to handle configurations.
4-
"""
3+
@moduledoc false
54

6-
defmodule MissingKeyError do
5+
defmodule MissingConfigError do
76
@type t :: %__MODULE__{}
87

98
defexception [:key]
@@ -15,51 +14,29 @@ defmodule Assent.Config do
1514

1615
@type t :: Keyword.t()
1716

18-
@doc """
19-
Fetches the key value from the configuration.
20-
"""
21-
@spec fetch(t(), atom()) :: {:ok, any()} | {:error, MissingKeyError.t()}
22-
def fetch(config, key) do
23-
case Keyword.fetch(config, key) do
24-
{:ok, value} -> {:ok, value}
25-
:error -> {:error, MissingKeyError.exception(key: key)}
26-
end
27-
end
17+
@doc false
18+
@deprecated "Use Assent.fetch_config/2 instead"
19+
def fetch(config, key), do: Assent.fetch_config(config, key)
2820

21+
@deprecated "Use Keyword.get/3 instead"
2922
defdelegate get(config, key, default), to: Keyword
3023

24+
@deprecated "Use Keyword.put/3 instead"
3125
defdelegate put(config, key, value), to: Keyword
3226

27+
@deprecated "Use Keyword.merge/2 instead"
3328
defdelegate merge(config_a, config_b), to: Keyword
3429

35-
@default_json_library (Code.ensure_loaded?(JSON) && JSON) || Jason
36-
37-
@doc """
38-
Fetches the JSON library in config.
39-
40-
If not found in provided config, this will attempt to load the JSON library
41-
from global application environment for `:assent`. Defaults to
42-
`#{@default_json_library}`.
43-
"""
44-
@spec json_library(t()) :: module()
45-
def json_library(config) do
46-
case get(config, :json_library, nil) do
47-
nil ->
48-
Application.get_env(:assent, :json_library, @default_json_library)
49-
50-
json_library ->
51-
json_library
52-
end
53-
end
30+
@deprecated "Use Assent.json_library/1 instead"
31+
def json_library(config), do: Assent.json_library(config)
5432

55-
# TODO: Remove in next major version
5633
def __base_url__(config) do
57-
case fetch(config, :base_url) do
34+
case Assent.fetch_config(config, :base_url) do
5835
{:ok, base_url} ->
5936
{:ok, base_url}
6037

6138
{:error, error} ->
62-
case fetch(config, :site) do
39+
case Assent.fetch_config(config, :site) do
6340
{:ok, base_url} ->
6441
IO.warn("The `:site` configuration key is deprecated, use `:base_url` instead")
6542
{:ok, base_url}

lib/assent/http_adapter.ex

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ defmodule Assent.HTTPAdapter do
2626
end
2727
end
2828
"""
29+
alias Assent.{InvalidResponseError, ServerUnreachableError}
2930

3031
defmodule HTTPResponse do
3132
@moduledoc """
@@ -69,7 +70,7 @@ defmodule Assent.HTTPAdapter do
6970
{:ok, map()} | {:error, any()}
7071

7172
@doc """
72-
Sets a user agent header
73+
Sets a user agent header.
7374
7475
The header value will be `Assent-VERSION` with VERSION being the `:vsn` of
7576
`:assent` app.
@@ -80,4 +81,99 @@ defmodule Assent.HTTPAdapter do
8081

8182
{"User-Agent", "Assent-#{version}"}
8283
end
84+
85+
@default_http_client Enum.find_value(
86+
[
87+
{Req, Assent.HTTPAdapter.Req},
88+
{:httpc, Assent.HTTPAdapter.Httpc}
89+
],
90+
fn {dep, module} ->
91+
Code.ensure_loaded?(dep) && {module, []}
92+
end
93+
)
94+
95+
@doc """
96+
Makes a HTTP request.
97+
98+
## Options
99+
100+
- `:http_adapter` - The HTTP adapter to use, defaults to
101+
`#{inspect(elem(@default_http_client, 0))}`.
102+
- `:json_library` - The JSON library to use, see
103+
`Assent.json_library/1`.
104+
"""
105+
@spec request(atom(), binary(), binary() | nil, list(), Keyword.t()) ::
106+
{:ok, HTTPResponse.t()} | {:error, HTTPResponse.t()} | {:error, term()}
107+
def request(method, url, body, headers, opts) do
108+
{http_adapter, http_adapter_opts} = get_adapter(opts)
109+
110+
method
111+
|> http_adapter.request(url, body, headers, http_adapter_opts)
112+
|> case do
113+
{:ok, response} ->
114+
decode_response(response, opts)
115+
116+
{:error, error} ->
117+
{:error,
118+
ServerUnreachableError.exception(
119+
reason: error,
120+
http_adapter: http_adapter,
121+
request_url: url
122+
)}
123+
end
124+
|> case do
125+
{:ok, %{status: status} = resp} when status in 200..399 ->
126+
{:ok, %{resp | http_adapter: http_adapter, request_url: url}}
127+
128+
{:ok, %{status: status} = resp} when status in 400..599 ->
129+
{:error, %{resp | http_adapter: http_adapter, request_url: url}}
130+
131+
{:error, error} ->
132+
{:error, error}
133+
end
134+
end
135+
136+
defp get_adapter(opts) do
137+
default_http_adapter = Application.get_env(:assent, :http_adapter, @default_http_client)
138+
139+
case Keyword.get(opts, :http_adapter, default_http_adapter) do
140+
{http_adapter, opts} -> {http_adapter, opts}
141+
http_adapter when is_atom(http_adapter) -> {http_adapter, nil}
142+
end
143+
end
144+
145+
@doc """
146+
Decodes request response body.
147+
148+
## Options
149+
150+
- `:json_library` - The JSON library to use, see
151+
`Assent.json_library/1`
152+
"""
153+
@spec decode_response(HTTPResponse.t(), Keyword.t()) ::
154+
{:ok, HTTPResponse.t()} | {:error, InvalidResponseError.t()}
155+
def decode_response(%HTTPResponse{} = response, opts) do
156+
case decode(response.headers, response.body, opts) do
157+
{:ok, body} -> {:ok, %{response | body: body}}
158+
{:error, _error} -> {:error, InvalidResponseError.exception(response: response)}
159+
end
160+
end
161+
162+
defp decode(headers, body, opts) when is_binary(body) do
163+
case List.keyfind(headers, "content-type", 0) do
164+
{"content-type", "application/json" <> _rest} ->
165+
Assent.json_library(opts).decode(body)
166+
167+
{"content-type", "text/javascript" <> _rest} ->
168+
Assent.json_library(opts).decode(body)
169+
170+
{"content-type", "application/x-www-form-urlencoded" <> _reset} ->
171+
{:ok, URI.decode_query(body)}
172+
173+
_any ->
174+
{:ok, body}
175+
end
176+
end
177+
178+
defp decode(_headers, body, _opts), do: {:ok, body}
83179
end

0 commit comments

Comments
 (0)