Skip to content

Commit 63d0e77

Browse files
Merge pull request #1 from fishtreesugar/init
init
2 parents 4b74f4f + ee30e66 commit 63d0e77

10 files changed

+513
-0
lines changed

.formatter.exs

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[
2+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
3+
]

.github/workflows/ci.yml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: CI
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
name: OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}
9+
strategy:
10+
matrix:
11+
otp: ["24.3", "25.1"]
12+
elixir: ["1.13", "1.14"]
13+
steps:
14+
- uses: actions/checkout@v3
15+
- uses: erlef/setup-beam@v1
16+
with:
17+
otp-version: ${{matrix.otp}}
18+
elixir-version: ${{matrix.elixir}}
19+
- name: Install Dependencies
20+
run: |
21+
mix local.rebar --force
22+
mix local.hex --force
23+
mix deps.get
24+
- name: Run Formatter
25+
run: mix format --check-formatted
26+
- name: Run Tests
27+
run: mix test --include httpbin

.gitignore

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# The directory Mix will write compiled artifacts to.
2+
/_build/
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover/
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps/
9+
10+
# Where third-party dependencies like ExDoc output generated docs.
11+
/doc/
12+
13+
# Ignore .fetch files in case you like to edit your project deps locally.
14+
/.fetch
15+
16+
# If the VM crashes, it generates a dump, let's ignore it too.
17+
erl_crash.dump
18+
19+
# Also ignore archive artifacts (built via "mix archive.build").
20+
*.ez
21+
22+
# Ignore package tarball (built via "mix hex.build").
23+
ex-http-client-builder-*.tar
24+
25+
# Temporary files, for example, from tests.
26+
/tmp/
27+
28+
.elixir_ls
29+
.DS_Store

lib/http_client_builder.ex

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
defmodule HttpClientBuilder do
2+
@moduledoc """
3+
A set of helpers for building HTTP client with finch easily.
4+
"""
5+
6+
defmacro __using__(client_opts \\ []) do
7+
case client_opts[:base_url_getter] do
8+
# absent
9+
nil ->
10+
:ok
11+
12+
# anonymous function
13+
{:fn, _, _} ->
14+
:ok
15+
16+
# function capture
17+
{:&, _, _} ->
18+
:ok
19+
20+
_ ->
21+
raise ":base_url_getter option is not a nullary anonymous function or function capture "
22+
end
23+
24+
case client_opts[:runtime_headers_getter] do
25+
# absent
26+
nil ->
27+
:ok
28+
29+
# anonymous function
30+
{:fn, _, _} ->
31+
:ok
32+
33+
# function capture
34+
{:&, _, _} ->
35+
:ok
36+
37+
_ ->
38+
raise ":runtime_headers_getter option is not a nullary anonymous function or function capture "
39+
end
40+
41+
quote do
42+
def child_spec(opts) do
43+
default_pools = %{:default => [size: 50]}
44+
pools = Keyword.get(unquote(client_opts), :pools, default_pools)
45+
46+
%{
47+
id: __MODULE__,
48+
start: {Finch, :start_link, [[name: __MODULE__, pools: pools]]},
49+
type: :supervisor
50+
}
51+
end
52+
53+
defp split_opts(opts) do
54+
{params, rest_opts} = Keyword.pop(opts, :params)
55+
56+
default_headers = Keyword.get(unquote(client_opts), :headers, [])
57+
{headers, rest_opts} = Keyword.pop(rest_opts, :headers, default_headers)
58+
59+
default_request_opts = Keyword.get(unquote(client_opts), :request_opts, [])
60+
{body, request_opts} = Keyword.pop(rest_opts, :body, nil)
61+
62+
final_request_opts =
63+
if Enum.empty?(request_opts), do: default_request_opts, else: request_opts
64+
65+
{params, headers, body, final_request_opts}
66+
end
67+
68+
def get(url, opts \\ []) do
69+
do_request(:get, url, opts)
70+
end
71+
72+
def post(url, opts \\ []) do
73+
do_request(:post, url, opts)
74+
end
75+
76+
def put(url, opts \\ []) do
77+
do_request(:put, url, opts)
78+
end
79+
80+
def delete(url, opts \\ []) do
81+
do_request(:delete, url, opts)
82+
end
83+
84+
def patch(url, opts \\ []) do
85+
do_request(:patch, url, opts)
86+
end
87+
88+
@doc """
89+
If `base_url_getter` option passed, it accept url's path,
90+
otherwise it accept full url. be careful when overriding this function for specific endpoint
91+
"""
92+
def do_request(method, url_or_path, opts) do
93+
{params, compile_time_headers, body, request_opts} = split_opts(opts)
94+
95+
url = build_url(url_or_path, params)
96+
97+
headers =
98+
if unquote(client_opts)[:runtime_headers_getter] do
99+
unquote(client_opts)[:runtime_headers_getter].() ++ compile_time_headers
100+
else
101+
compile_time_headers
102+
end
103+
104+
method
105+
|> Finch.build(url, headers, body)
106+
|> Finch.request(__MODULE__, request_opts)
107+
end
108+
109+
defp build_url(url_or_path, params) do
110+
base_url_getter = unquote(client_opts)[:base_url_getter]
111+
base_url = if base_url_getter, do: base_url_getter.(), else: ""
112+
query = if is_nil(params), do: "", else: "?" <> Plug.Conn.Query.encode(params)
113+
114+
base_url <> url_or_path <> query
115+
end
116+
117+
defoverridable get: 2, post: 2, put: 2, delete: 2, patch: 2, do_request: 3, build_url: 2
118+
end
119+
end
120+
end

mix.exs

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
defmodule HttpClientBuilder.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :http_client_builder,
7+
version: "1.0.0",
8+
elixir: "~> 1.11",
9+
elixirc_paths: elixirc_paths(Mix.env()),
10+
start_permanent: Mix.env() == :prod,
11+
deps: deps()
12+
]
13+
end
14+
15+
# Run "mix help compile.app" to learn about applications.
16+
def application do
17+
[]
18+
end
19+
20+
# Specifies which paths to compile per environment.
21+
defp elixirc_paths(:test), do: ["lib", "test/support"]
22+
defp elixirc_paths(_), do: ["lib"]
23+
24+
# Run "mix help deps" to learn about dependencies.
25+
defp deps do
26+
[
27+
{:finch, "~> 0.9"},
28+
# for encode plug compat query string
29+
{:plug, "~> 1.12"},
30+
{:jason, "~> 1.2", only: [:test]}
31+
]
32+
end
33+
end

mix.lock

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
%{
2+
"castore": {:hex, :castore, "0.1.18", "deb5b9ab02400561b6f5708f3e7660fc35ca2d51bfc6a940d2f513f89c2975fc", [:mix], [], "hexpm", "61bbaf6452b782ef80b33cdb45701afbcf0a918a45ebe7e73f1130d661e66a06"},
3+
"finch": {:hex, :finch, "0.13.0", "c881e5460ec563bf02d4f4584079e62201db676ed4c0ef3e59189331c4eddf7b", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "49957dcde10dcdc042a123a507a9c5ec5a803f53646d451db2f7dea696fba6cc"},
4+
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
5+
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
6+
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
7+
"mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"},
8+
"nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"},
9+
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
10+
"plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"},
11+
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
12+
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
13+
}

0 commit comments

Comments
 (0)