Skip to content

Commit

Permalink
feat: add helpers function to deal and merge headers and query
Browse files Browse the repository at this point in the history
  • Loading branch information
zoedsoupe committed Jan 5, 2025
1 parent ec5984c commit b443e23
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 4 deletions.
68 changes: 65 additions & 3 deletions lib/supabase/fetcher.ex
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ defmodule Supabase.Fetcher do
url: Finch.Request.url(),
options: Finch.request_opts(),
service: Supabase.service(),
query: map,
query: list({String.t(), String.t()}),
body_decoder: module,
error_parser: module
}
Expand All @@ -119,9 +119,9 @@ defmodule Supabase.Fetcher do
:client,
:body,
method: :get,
query: %{},
query: [],
options: [],
headers: %{},
headers: [],
body_decoder: Supabase.Fetcher.JSONDecoder,
error_parser: Supabase.ErrorParser
]
Expand Down Expand Up @@ -505,6 +505,68 @@ defmodule Supabase.Fetcher do
get_header(resp, header) || default
end

@doc """
Tries to find and return the value for a query param, given it name, if it doesn't
existis, it returns the default value informed or `nil`.
"""
@spec get_query_param(t, param :: String.t(), default :: String.t() | nil) :: String.t() | nil
def get_query_param(%__MODULE__{} = builder, key, default \\ nil)
when is_binary(key) and (is_binary(default) or is_nil(default)) do
case List.keyfind(builder.query, key, 0) do
nil -> default
{^key, value} -> value
end
end

@doc """
Tries to find and return the value for a request headers, given it name, if it doesn't
existis, it returns the default value informed or `nil`.
Do not confuse with `get_header/2`.
"""
@spec get_req_header(t, name :: String.t(), default :: String.t() | nil) :: String.t() | nil
def get_req_header(%__MODULE__{} = builder, key, default \\ nil)
when is_binary(key) and (is_binary(default) or is_nil(default)) do
case List.keyfind(builder.headers, key, 0) do
nil -> default
{^key, value} -> value
end
end

@doc """
Merges an existing query param value with a new one, prepending the new value
with the existing one. If no current value exists for the param, this function
will behave the same as `with_query/2`.
"""
@spec merge_query_param(t, param :: String.t(), value :: String.t(),
with: joinner :: String.t()
) :: t
def merge_query_param(%__MODULE__{} = builder, key, value, [with: w] \\ [with: ","])
when is_binary(key) and is_binary(value) and is_binary(w) do
if curr = get_query_param(builder, key) do
with_query(builder, %{key => Enum.join([curr, value], w)})
else
with_query(builder, %{key => value})
end
end

@doc """
Merges an existing request header value with a new one, prepending the new value
with the existing one. If no current value exists for the header, this function
will behave the same as `with_headers/2`.
"""
@spec merge_req_header(t, header :: String.t(), value :: String.t(),
with: joinner :: String.t()
) :: t
def merge_req_header(%__MODULE__{} = builder, key, value, [with: w] \\ [with: ","])
when is_binary(key) and is_binary(value) and is_binary(w) do
if curr = get_req_header(builder, key) do
with_headers(builder, %{key => Enum.join([curr, value], w)})
else
with_headers(builder, %{key => value})
end
end

defimpl Inspect, for: Supabase.Fetcher do
import Inspect.Algebra

Expand Down
111 changes: 110 additions & 1 deletion test/supabase/fetcher_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,116 @@ defmodule Supabase.FetcherTest do
end
end

# VCR and "integration" tests
describe "get_query_param/3" do
setup ctx do
fetcher = ctx.client |> Fetcher.new() |> Fetcher.with_query(%{"key1" => "value1", "key2" => "value2"})
{:ok, Map.put(ctx, :fetcher, fetcher)}
end

test "retrieves an existing query parameter", %{fetcher: fetcher} do
assert Fetcher.get_query_param(fetcher, "key1") == "value1"
end

test "returns nil for a missing query parameter", %{fetcher: fetcher} do
assert Fetcher.get_query_param(fetcher, "key3") == nil
end

test "returns the default value for a missing query parameter", %{fetcher: fetcher} do
assert Fetcher.get_query_param(fetcher, "key3", "default_value") == "default_value"
end

test "handles empty query parameters gracefully", %{client: client} do
fetcher = Fetcher.new(client)
assert Fetcher.get_query_param(fetcher, "key") == nil
end
end

describe "get_req_header/3" do
setup ctx do
fetcher =
Fetcher.new(ctx.client)
|> Fetcher.with_headers(%{
"Authorization" => "Bearer token",
"Content-Type" => "application/json"
})

{:ok, fetcher: fetcher}
end

test "retrieves an existing header", %{fetcher: fetcher} do
assert Fetcher.get_req_header(fetcher, "Authorization") == "Bearer token"
end

test "returns nil for a missing header", %{fetcher: fetcher} do
assert Fetcher.get_req_header(fetcher, "Accept") == nil
end

test "returns the default value for a missing header", %{fetcher: fetcher} do
assert Fetcher.get_req_header(fetcher, "Accept", "application/xml") == "application/xml"
end

test "handles empty headers gracefully", %{client: client} do
fetcher = Fetcher.new(client)
assert Fetcher.get_req_header(fetcher, "Authorization") == nil
end
end

describe "merge_query_param/4" do
setup ctx do
fetcher = Fetcher.new(ctx.client) |> Fetcher.with_query(%{"key1" => "value1"})
{:ok, fetcher: fetcher}
end

test "merges a new query parameter when key does not exist", %{fetcher: fetcher} do
updated_fetcher = Fetcher.merge_query_param(fetcher, "key2", "value2")
assert Fetcher.get_query_param(updated_fetcher, "key2") == "value2"
end

test "merges with default separator when key exists", %{fetcher: fetcher} do
updated_fetcher = Fetcher.merge_query_param(fetcher, "key1", "value2")
assert Fetcher.get_query_param(updated_fetcher, "key1") == "value1,value2"
end

test "merges with custom separator when specified", %{fetcher: fetcher} do
updated_fetcher = Fetcher.merge_query_param(fetcher, "key1", "value2", [with: "|"])
assert Fetcher.get_query_param(updated_fetcher, "key1") == "value1|value2"
end

test "handles merging into an empty query", %{client: client} do
fetcher = Fetcher.new(client)
updated_fetcher = Fetcher.merge_query_param(fetcher, "key1", "value1")
assert Fetcher.get_query_param(updated_fetcher, "key1") == "value1"
end
end

describe "merge_req_header/4" do
setup ctx do
fetcher = Fetcher.new(ctx.client) |> Fetcher.with_headers(%{"key1" => "value1"})
{:ok, fetcher: fetcher}
end

test "merges a new header value when key does not exist", %{fetcher: fetcher} do
updated_fetcher = Fetcher.merge_req_header(fetcher, "key2", "value2")
assert Fetcher.get_req_header(updated_fetcher, "key2") == "value2"
end

test "merges with default separator when key exists", %{fetcher: fetcher} do
updated_fetcher = Fetcher.merge_req_header(fetcher, "key1", "value2")
assert Fetcher.get_req_header(updated_fetcher, "key1") == "value1,value2"
end

test "merges with custom separator when specified", %{fetcher: fetcher} do
updated_fetcher = Fetcher.merge_req_header(fetcher, "key1", "value2", [with: "|"])
assert Fetcher.get_req_header(updated_fetcher, "key1") == "value1|value2"
end

test "handles merging into an empty header", %{client: client} do
fetcher = Fetcher.new(client)
updated_fetcher = Fetcher.merge_req_header(fetcher, "key1", "value1")
assert Fetcher.get_req_header(updated_fetcher, "key1") == "value1"
end
end

defp have_header?(headers, name) do
Enum.any?(headers, fn {k, _} ->
String.downcase(k) == String.downcase(name)
Expand Down

0 comments on commit b443e23

Please sign in to comment.