@@ -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 }
83179end
0 commit comments