Skip to content

Commit

Permalink
Merge pull request #1 from fishtreesugar/init
Browse files Browse the repository at this point in the history
init
  • Loading branch information
fishtreesugar authored Oct 12, 2022
2 parents 4b74f4f + ee30e66 commit 63d0e77
Show file tree
Hide file tree
Showing 10 changed files with 513 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: CI

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
name: OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}
strategy:
matrix:
otp: ["24.3", "25.1"]
elixir: ["1.13", "1.14"]
steps:
- uses: actions/checkout@v3
- uses: erlef/setup-beam@v1
with:
otp-version: ${{matrix.otp}}
elixir-version: ${{matrix.elixir}}
- name: Install Dependencies
run: |
mix local.rebar --force
mix local.hex --force
mix deps.get
- name: Run Formatter
run: mix format --check-formatted
- name: Run Tests
run: mix test --include httpbin
29 changes: 29 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where third-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
ex-http-client-builder-*.tar

# Temporary files, for example, from tests.
/tmp/

.elixir_ls
.DS_Store
120 changes: 120 additions & 0 deletions lib/http_client_builder.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
defmodule HttpClientBuilder do
@moduledoc """
A set of helpers for building HTTP client with finch easily.
"""

defmacro __using__(client_opts \\ []) do
case client_opts[:base_url_getter] do
# absent
nil ->
:ok

# anonymous function
{:fn, _, _} ->
:ok

# function capture
{:&, _, _} ->
:ok

_ ->
raise ":base_url_getter option is not a nullary anonymous function or function capture "
end

case client_opts[:runtime_headers_getter] do
# absent
nil ->
:ok

# anonymous function
{:fn, _, _} ->
:ok

# function capture
{:&, _, _} ->
:ok

_ ->
raise ":runtime_headers_getter option is not a nullary anonymous function or function capture "
end

quote do
def child_spec(opts) do
default_pools = %{:default => [size: 50]}
pools = Keyword.get(unquote(client_opts), :pools, default_pools)

%{
id: __MODULE__,
start: {Finch, :start_link, [[name: __MODULE__, pools: pools]]},
type: :supervisor
}
end

defp split_opts(opts) do
{params, rest_opts} = Keyword.pop(opts, :params)

default_headers = Keyword.get(unquote(client_opts), :headers, [])
{headers, rest_opts} = Keyword.pop(rest_opts, :headers, default_headers)

default_request_opts = Keyword.get(unquote(client_opts), :request_opts, [])
{body, request_opts} = Keyword.pop(rest_opts, :body, nil)

final_request_opts =
if Enum.empty?(request_opts), do: default_request_opts, else: request_opts

{params, headers, body, final_request_opts}
end

def get(url, opts \\ []) do
do_request(:get, url, opts)
end

def post(url, opts \\ []) do
do_request(:post, url, opts)
end

def put(url, opts \\ []) do
do_request(:put, url, opts)
end

def delete(url, opts \\ []) do
do_request(:delete, url, opts)
end

def patch(url, opts \\ []) do
do_request(:patch, url, opts)
end

@doc """
If `base_url_getter` option passed, it accept url's path,
otherwise it accept full url. be careful when overriding this function for specific endpoint
"""
def do_request(method, url_or_path, opts) do
{params, compile_time_headers, body, request_opts} = split_opts(opts)

url = build_url(url_or_path, params)

headers =
if unquote(client_opts)[:runtime_headers_getter] do
unquote(client_opts)[:runtime_headers_getter].() ++ compile_time_headers
else
compile_time_headers
end

method
|> Finch.build(url, headers, body)
|> Finch.request(__MODULE__, request_opts)
end

defp build_url(url_or_path, params) do
base_url_getter = unquote(client_opts)[:base_url_getter]
base_url = if base_url_getter, do: base_url_getter.(), else: ""
query = if is_nil(params), do: "", else: "?" <> Plug.Conn.Query.encode(params)

base_url <> url_or_path <> query
end

defoverridable get: 2, post: 2, put: 2, delete: 2, patch: 2, do_request: 3, build_url: 2
end
end
end
33 changes: 33 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule HttpClientBuilder.MixProject do
use Mix.Project

def project do
[
app: :http_client_builder,
version: "1.0.0",
elixir: "~> 1.11",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
deps: deps()
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[]
end

# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]

# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:finch, "~> 0.9"},
# for encode plug compat query string
{:plug, "~> 1.12"},
{:jason, "~> 1.2", only: [:test]}
]
end
end
13 changes: 13 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
%{
"castore": {:hex, :castore, "0.1.18", "deb5b9ab02400561b6f5708f3e7660fc35ca2d51bfc6a940d2f513f89c2975fc", [:mix], [], "hexpm", "61bbaf6452b782ef80b33cdb45701afbcf0a918a45ebe7e73f1130d661e66a06"},
"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"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
"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"},
"nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"},
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
"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"},
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
}
Loading

0 comments on commit 63d0e77

Please sign in to comment.