Skip to content

Commit a1603c0

Browse files
committed
Additional improvements for v0.4.0 - Promise API and HTTP.Headers enhancements
## Changed - **Simplified Promise API** - HTTP.Promise.await/1 now returns %HTTP.Response{} directly instead of {:ok, response} - **Updated test assertions** - All tests updated to match new Promise API - **Enhanced HTTP.Headers** - Added get_all/2 and add/3 methods for multi-value headers - **Improved error handling** - Better response handling throughout the codebase ## Added - **Multi-value header support** - HTTP.Headers.get_all/2 and add/3 for handling headers with multiple values - **Case-insensitive header operations** - Consistent behavior across all header methods ## Technical Details - **API consistency** - Promise.await/1 now returns the response directly, matching Elixir conventions - **Backward compatibility** - Updated tests to ensure all functionality works as expected - **Enhanced header utilities** - Support for headers with multiple values of the same name
1 parent cf6db61 commit a1603c0

File tree

4 files changed

+130
-32
lines changed

4 files changed

+130
-32
lines changed

lib/http/promise.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ defmodule HTTP.Promise do
1414
Awaits the completion of the HTTP promise.
1515
1616
Returns:
17-
- `{:ok, %HTTP.Response{}}` on successful completion.
17+
- `%HTTP.Response{}` on successful completion.
1818
- `{:error, reason}` if the request fails or is aborted.
1919
"""
20-
@spec await(t()) :: {:ok, HTTP.Response.t()} | {:error, term()}
20+
@spec await(t()) :: HTTP.Response.t() | {:error, term()}
2121
def await(%__MODULE__{task: task}) do
2222
Task.await(task)
2323
end
@@ -43,7 +43,7 @@ defmodule HTTP.Promise do
4343
new_task =
4444
Task.Supervisor.async_nolink(:http_fetch_task_supervisor, fn ->
4545
case Task.await(current_task) do
46-
{:ok, response} ->
46+
%HTTP.Response{} = response ->
4747
# Call the success function and handle its result
4848
apply_callback(success_fun, [response]) |> handle_chained_result()
4949

test/http/promise_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ defmodule HTTP.PromiseTest do
1010
end
1111

1212
test "await promise" do
13-
task = Task.async(fn -> {:ok, %HTTP.Response{status: 200}} end)
13+
task = Task.async(fn -> %HTTP.Response{status: 200} end)
1414
promise = %HTTP.Promise{task: task}
15-
assert {:ok, %HTTP.Response{status: 200}} = HTTP.Promise.await(promise)
15+
assert %HTTP.Response{status: 200} = HTTP.Promise.await(promise)
1616
end
1717
end
1818
end

test/http_headers_test.exs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,108 @@ defmodule HTTP.HeadersTest do
208208
assert HTTP.Headers.to_list(headers) == [{"Content-Type", "application/json"}]
209209
end
210210
end
211+
212+
describe "get_all/2" do
213+
test "returns all values for a header name case-insensitive" do
214+
headers =
215+
HTTP.Headers.new([
216+
{"Accept", "text/html"},
217+
{"Accept", "application/json"},
218+
{"Accept", "*/*"}
219+
])
220+
221+
assert HTTP.Headers.get_all(headers, "accept") == ["text/html", "application/json", "*/*"]
222+
assert HTTP.Headers.get_all(headers, "ACCEPT") == ["text/html", "application/json", "*/*"]
223+
end
224+
225+
test "returns single value when only one exists" do
226+
headers =
227+
HTTP.Headers.new([
228+
{"Content-Type", "application/json"},
229+
{"Authorization", "Bearer token"}
230+
])
231+
232+
assert HTTP.Headers.get_all(headers, "content-type") == ["application/json"]
233+
end
234+
235+
test "returns empty list for missing header" do
236+
headers =
237+
HTTP.Headers.new([
238+
{"Content-Type", "application/json"}
239+
])
240+
241+
assert HTTP.Headers.get_all(headers, "missing") == []
242+
end
243+
244+
test "handles empty headers" do
245+
assert HTTP.Headers.get_all(HTTP.Headers.new(), "content-type") == []
246+
end
247+
248+
test "returns empty list when no headers match" do
249+
headers =
250+
HTTP.Headers.new([
251+
{"Content-Type", "application/json"},
252+
{"Authorization", "Bearer token"}
253+
])
254+
255+
assert HTTP.Headers.get_all(headers, "x-custom-header") == []
256+
end
257+
end
258+
259+
describe "add/3" do
260+
test "adds new header without replacing existing ones" do
261+
headers =
262+
HTTP.Headers.new([
263+
{"Accept", "text/html"}
264+
])
265+
266+
updated = HTTP.Headers.add(headers, "Accept", "application/json")
267+
assert HTTP.Headers.get_all(updated, "Accept") == ["text/html", "application/json"]
268+
end
269+
270+
test "adds new header to existing structure" do
271+
headers =
272+
HTTP.Headers.new([
273+
{"Content-Type", "text/plain"}
274+
])
275+
276+
updated = HTTP.Headers.add(headers, "Authorization", "Bearer token")
277+
assert HTTP.Headers.get(updated, "Authorization") == "Bearer token"
278+
assert HTTP.Headers.get(updated, "Content-Type") == "text/plain"
279+
end
280+
281+
test "adds header to empty structure" do
282+
headers = HTTP.Headers.new()
283+
updated = HTTP.Headers.add(headers, "Authorization", "Bearer token")
284+
assert HTTP.Headers.get(updated, "Authorization") == "Bearer token"
285+
end
286+
287+
test "normalizes header name when adding" do
288+
headers = HTTP.Headers.new()
289+
updated = HTTP.Headers.add(headers, "content-type", "application/json")
290+
assert HTTP.Headers.get(updated, "Content-Type") == "application/json"
291+
end
292+
293+
test "adds multiple headers with same name" do
294+
headers = HTTP.Headers.new()
295+
296+
headers = HTTP.Headers.add(headers, "Accept", "text/html")
297+
headers = HTTP.Headers.add(headers, "Accept", "application/json")
298+
headers = HTTP.Headers.add(headers, "Accept", "*/*")
299+
300+
assert HTTP.Headers.get_all(headers, "Accept") == ["text/html", "application/json", "*/*"]
301+
end
302+
303+
test "adds headers case-insensitive" do
304+
headers =
305+
HTTP.Headers.new([
306+
{"Accept", "text/html"}
307+
])
308+
309+
updated = HTTP.Headers.add(headers, "accept", "application/json")
310+
updated = HTTP.Headers.add(updated, "ACCEPT", "*/*")
311+
312+
assert HTTP.Headers.get_all(updated, "Accept") == ["text/html", "application/json", "*/*"]
313+
end
314+
end
211315
end

test/http_test.exs

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ defmodule HTTPTest do
1010
HTTP.fetch("#{@base_url}/status/200")
1111
|> HTTP.Promise.await()
1212

13-
assert {:ok, %HTTP.Response{}} = resp
13+
assert %HTTP.Response{} = resp
1414
end
1515

1616
test "fetch handles HTTP error status" do
1717
resp =
1818
HTTP.fetch("#{@base_url}/status/404")
1919
|> HTTP.Promise.await()
2020

21-
assert {:ok, %HTTP.Response{status: 404}} = resp
21+
assert %HTTP.Response{status: 404} = resp
2222
end
2323

2424
test "fetch handles different status codes" do
@@ -29,7 +29,7 @@ defmodule HTTPTest do
2929
HTTP.fetch("#{@base_url}/status/#{status}")
3030
|> HTTP.Promise.await()
3131

32-
assert {:ok, %HTTP.Response{status: ^status}} = resp
32+
assert %HTTP.Response{status: ^status} = resp
3333
end
3434
end
3535
end
@@ -40,7 +40,7 @@ defmodule HTTPTest do
4040
HTTP.fetch("#{@base_url}/get", method: "GET")
4141
|> HTTP.Promise.await()
4242

43-
assert {:ok, %HTTP.Response{status: 200}} = resp
43+
assert %HTTP.Response{status: 200} = resp
4444
end
4545

4646
test "POST request" do
@@ -51,7 +51,7 @@ defmodule HTTPTest do
5151
)
5252
|> HTTP.Promise.await()
5353

54-
assert {:ok, %HTTP.Response{status: 200}} = resp
54+
assert %HTTP.Response{status: 200} = resp
5555
end
5656

5757
test "PUT request" do
@@ -62,7 +62,7 @@ defmodule HTTPTest do
6262
)
6363
|> HTTP.Promise.await()
6464

65-
assert {:ok, %HTTP.Response{status: 200}} = resp
65+
assert %HTTP.Response{status: 200} = resp
6666
end
6767

6868
test "DELETE request" do
@@ -71,7 +71,7 @@ defmodule HTTPTest do
7171
|> HTTP.Promise.await()
7272

7373
# Allow 200 or 502 status codes (httpbin.org can be flaky)
74-
assert {:ok, %HTTP.Response{status: status}} = resp
74+
assert %HTTP.Response{status: status} = resp
7575
assert status in [200, 502]
7676
end
7777

@@ -84,7 +84,7 @@ defmodule HTTPTest do
8484
|> HTTP.Promise.await()
8585

8686
# Allow 200 or 502 status codes (httpbin.org can be flaky)
87-
assert {:ok, %HTTP.Response{status: status}} = resp
87+
assert %HTTP.Response{status: status} = resp
8888
assert status in [200, 502]
8989
end
9090
end
@@ -97,7 +97,7 @@ defmodule HTTPTest do
9797
HTTP.fetch("#{@base_url}/headers", headers: headers)
9898
|> HTTP.Promise.await()
9999

100-
assert {:ok, %HTTP.Response{status: 200}} = resp
100+
assert %HTTP.Response{status: 200} = resp
101101
end
102102

103103
test "content-type header" do
@@ -109,7 +109,7 @@ defmodule HTTPTest do
109109
)
110110
|> HTTP.Promise.await()
111111

112-
assert {:ok, %HTTP.Response{status: 200}} = resp
112+
assert %HTTP.Response{status: 200} = resp
113113
end
114114
end
115115

@@ -124,7 +124,7 @@ defmodule HTTPTest do
124124
)
125125
|> HTTP.Promise.await()
126126

127-
assert {:ok, %HTTP.Response{status: 200}} = resp
127+
assert %HTTP.Response{status: 200} = resp
128128
end
129129

130130
test "empty body" do
@@ -134,7 +134,7 @@ defmodule HTTPTest do
134134
)
135135
|> HTTP.Promise.await()
136136

137-
assert {:ok, %HTTP.Response{status: 200}} = resp
137+
assert %HTTP.Response{status: 200} = resp
138138
end
139139
end
140140

@@ -144,14 +144,11 @@ defmodule HTTPTest do
144144
HTTP.fetch("#{@base_url}/json")
145145
|> HTTP.Promise.await()
146146

147-
assert {:ok, %HTTP.Response{status: 200}} = resp
147+
assert %HTTP.Response{status: 200} = resp
148148

149-
case resp do
150-
{:ok, response} ->
151-
case HTTP.Response.json(response) do
152-
{:ok, json} -> assert is_map(json)
153-
{:error, reason} -> flunk("JSON parsing failed: #{inspect(reason)}")
154-
end
149+
case HTTP.Response.json(resp) do
150+
{:ok, json} -> assert is_map(json)
151+
{:error, reason} -> flunk("JSON parsing failed: #{inspect(reason)}")
155152
end
156153
end
157154

@@ -160,13 +157,10 @@ defmodule HTTPTest do
160157
HTTP.fetch("#{@base_url}/robots.txt")
161158
|> HTTP.Promise.await()
162159

163-
assert {:ok, %HTTP.Response{status: 200}} = resp
160+
assert %HTTP.Response{status: 200} = resp
164161

165-
case resp do
166-
{:ok, response} ->
167-
text = HTTP.Response.text(response)
168-
assert is_binary(text)
169-
end
162+
text = HTTP.Response.text(resp)
163+
assert is_binary(text)
170164
end
171165
end
172166

@@ -202,15 +196,15 @@ defmodule HTTPTest do
202196
HTTP.fetch("#{@base_url}/delay/1", options: [timeout: 5000])
203197
|> HTTP.Promise.await()
204198

205-
assert {:ok, %HTTP.Response{status: 200}} = resp
199+
assert %HTTP.Response{status: 200} = resp
206200
end
207201

208202
test "connect timeout" do
209203
resp =
210204
HTTP.fetch("#{@base_url}/get", options: [connect_timeout: 5000])
211205
|> HTTP.Promise.await()
212206

213-
assert {:ok, %HTTP.Response{status: 200}} = resp
207+
assert %HTTP.Response{status: 200} = resp
214208
end
215209
end
216210
end

0 commit comments

Comments
 (0)