From 1a835ce83700207eeb73d2b29deaf9d0170d8d5d Mon Sep 17 00:00:00 2001 From: Gil Aviv <26069404+ggiill@users.noreply.github.com> Date: Mon, 13 Mar 2023 23:10:43 -0400 Subject: [PATCH 1/4] add default config + ability to config tesla middleware + tests + docs --- lib/ex_force.ex | 13 ++++++ lib/ex_force/client/tesla/tesla.ex | 58 ++++++++++++++++----------- test/config_test.exs | 63 ++++++++++++++++++++++++++++++ test/ex_force/oauth_test.exs | 2 +- test/ex_force_test.exs | 4 +- 5 files changed, 115 insertions(+), 25 deletions(-) create mode 100644 test/config_test.exs diff --git a/lib/ex_force.ex b/lib/ex_force.ex index e5752c1..6a814eb 100644 --- a/lib/ex_force.ex +++ b/lib/ex_force.ex @@ -14,6 +14,19 @@ defmodule ExForce do end ``` + Configure default settings in config/config.exs (optional). + + ```elixir + # config/config.exs + + config :ex_force, :api_version, "43.0" + config :ex_force, :adapter, {Tesla.Adapter.Hackney, [recv_timeout: 1_000]} + config :ex_force, :middleware, [ + Tesla.Middleware.Telemetry, + {Tesla.Middleware.Timeout, timeout: :timer.seconds(1)} + ] + ``` + Check out [Choosing a Tesla Adapter](https://github.com/chulkilee/ex_force/wiki/Choosing-a-Tesla-Adapter). ## Usage diff --git a/lib/ex_force/client/tesla/tesla.ex b/lib/ex_force/client/tesla/tesla.ex index 3fb960c..a5c3e20 100644 --- a/lib/ex_force/client/tesla/tesla.ex +++ b/lib/ex_force/client/tesla/tesla.ex @@ -41,20 +41,24 @@ defmodule ExForce.Client.Tesla do end def build_client(instance_url, opts) when is_binary(instance_url) do - Tesla.client( - [ - {ExForce.Client.Tesla.Middleware, - {instance_url, Keyword.get(opts, :api_version, @default_api_version)}}, - {Tesla.Middleware.Compression, format: "gzip"}, - {Tesla.Middleware.JSON, engine: Jason}, - {Tesla.Middleware.Headers, get_headers(opts)} - ], - Keyword.get(opts, :adapter) - ) + custom_middleware = get_from_opts_or_config(opts, :middleware, []) + + adapter = get_from_opts_or_config(opts, :adapter) + + headers = get_headers(opts) + + [ + {ExForce.Client.Tesla.Middleware, + {instance_url, get_from_opts_or_config(opts, :api_version, @default_api_version)}}, + {Tesla.Middleware.Compression, format: "gzip"}, + {Tesla.Middleware.JSON, engine: Jason}, + {Tesla.Middleware.Headers, headers}, + custom_middleware + ] + |> List.flatten() + |> Tesla.client(adapter) end - defp get_headers(opts), do: Keyword.get(opts, :headers, [{"user-agent", @default_user_agent}]) - @doc """ Returns a `Tesla` client for `ExForce.OAuth` functions @@ -65,16 +69,20 @@ defmodule ExForce.Client.Tesla do """ @impl ExForce.Client def build_oauth_client(instance_url, opts \\ []) do - Tesla.client( - [ - {Tesla.Middleware.BaseUrl, instance_url}, - {Tesla.Middleware.Compression, format: "gzip"}, - Tesla.Middleware.FormUrlencoded, - {Tesla.Middleware.DecodeJson, engine: Jason}, - {Tesla.Middleware.Headers, get_headers(opts)} - ], - Keyword.get(opts, :adapter) - ) + custom_middleware = get_from_opts_or_config(opts, :middleware, []) + + adapter = get_from_opts_or_config(opts, :adapter) + + [ + {Tesla.Middleware.DecodeJson, engine: Jason}, + {Tesla.Middleware.BaseUrl, instance_url}, + {Tesla.Middleware.Compression, format: "gzip"}, + Tesla.Middleware.FormUrlencoded, + {Tesla.Middleware.Headers, get_headers(opts)}, + custom_middleware + ] + |> List.flatten() + |> Tesla.client(adapter) end @doc """ @@ -87,6 +95,12 @@ defmodule ExForce.Client.Tesla do |> cast_response() end + defp get_from_opts_or_config(opts, key, default \\ nil), + do: Keyword.get(opts, key) || Application.get_env(:ex_force, key) || default + + defp get_headers(opts), + do: get_from_opts_or_config(opts, :headers, [{"user-agent", @default_user_agent}]) + defp cast_tesla_request(%Request{} = request) do request |> convert_struct(Tesla.Env) diff --git a/test/config_test.exs b/test/config_test.exs new file mode 100644 index 0000000..2cdbcdd --- /dev/null +++ b/test/config_test.exs @@ -0,0 +1,63 @@ +defmodule ExForce.ConfigTest do + # Must be set to async: false since this is manipulating global application config + use ExUnit.Case, async: false + + alias ExForce.{ + Client, + Request + } + + alias Plug.Conn + + test "build_client/2 - custom config" do + bypass = Bypass.open() + bypass_url = "http://127.0.0.1:#{bypass.port}" + + api_version = "456.0" + + custom_middleware = [ + Tesla.Middleware.Telemetry, + {Tesla.Middleware.Timeout, timeout: :timer.seconds(1)} + ] + + custom_middleware_representation = [ + {Tesla.Middleware.Telemetry, :call, [[]]}, + {Tesla.Middleware.Timeout, :call, [[timeout: 1000]]} + ] + + custom_opts = [ + api_version: api_version, + middleware: custom_middleware + ] + + Application.put_all_env(ex_force: custom_opts) + + on_exit(fn -> + Enum.each(custom_opts, fn {key, _} -> Application.delete_env(:ex_force, key) end) + end) + + Bypass.expect_once(bypass, "GET", "/foo", fn conn -> + conn + |> Conn.put_resp_content_type("application/json") + |> Conn.resp(200, ~w({"hello": "world"})) + end) + + client = ExForce.build_client(bypass_url) + + assert %Tesla.Client{ + adapter: nil, + fun: nil, + post: [], + pre: + [ + {ExForce.Client.Tesla.Middleware, :call, [{^bypass_url, ^api_version}]}, + {Tesla.Middleware.Compression, :call, [[format: "gzip"]]}, + {Tesla.Middleware.JSON, :call, [[engine: Jason]]}, + {Tesla.Middleware.Headers, :call, [[{"user-agent", "ex_force"}]]} + ] ++ ^custom_middleware_representation + } = client + + assert {:ok, %{status: 200, body: %{"hello" => "world"}}} = + Client.request(client, %Request{url: "/foo", method: :get}) + end +end diff --git a/test/ex_force/oauth_test.exs b/test/ex_force/oauth_test.exs index 2e9d763..e9fe5df 100644 --- a/test/ex_force/oauth_test.exs +++ b/test/ex_force/oauth_test.exs @@ -335,10 +335,10 @@ defmodule ExForce.OAuthTest do fun: nil, post: [], pre: [ + {Tesla.Middleware.DecodeJson, :call, [[engine: Jason]]}, {Tesla.Middleware.BaseUrl, :call, ["http://257.0.0.0:0"]}, {Tesla.Middleware.Compression, :call, [[format: "gzip"]]}, {Tesla.Middleware.FormUrlencoded, :call, [[]]}, - {Tesla.Middleware.DecodeJson, :call, [[engine: Jason]]}, {Tesla.Middleware.Headers, :call, [[{"user-agent", "agent"}]]} ] } diff --git a/test/ex_force_test.exs b/test/ex_force_test.exs index ab8cd95..c4c61b4 100644 --- a/test/ex_force_test.exs +++ b/test/ex_force_test.exs @@ -48,6 +48,8 @@ defmodule ExForceTest do defp map_sobject_id(enum), do: Enum.map(enum, fn %SObject{id: id} -> id end) + defp get(client, url), do: Client.request(client, %Request{url: url, method: :get}) + test "build_client/2 - map", %{bypass: bypass} do Bypass.expect_once(bypass, "GET", "/", fn conn -> conn @@ -1201,6 +1203,4 @@ defmodule ExForceTest do "account6" ] end - - defp get(client, url), do: Client.request(client, %Request{url: url, method: :get}) end From 37412c841b231d578fa89b3f744861aefc37de38 Mon Sep 17 00:00:00 2001 From: Gil Aviv <26069404+ggiill@users.noreply.github.com> Date: Tue, 14 Mar 2023 22:27:43 -0400 Subject: [PATCH 2/4] review feedback --- lib/ex_force.ex | 16 ++++---- lib/ex_force/client/tesla/tesla.ex | 61 ++++++++++++++++-------------- test/config_test.exs | 12 ++---- 3 files changed, 45 insertions(+), 44 deletions(-) diff --git a/lib/ex_force.ex b/lib/ex_force.ex index 6a814eb..df51d61 100644 --- a/lib/ex_force.ex +++ b/lib/ex_force.ex @@ -14,17 +14,19 @@ defmodule ExForce do end ``` - Configure default settings in config/config.exs (optional). + Configure default settings for the Tesla client (currently the only + client supported) in config/config.exs (optional). ```elixir # config/config.exs - config :ex_force, :api_version, "43.0" - config :ex_force, :adapter, {Tesla.Adapter.Hackney, [recv_timeout: 1_000]} - config :ex_force, :middleware, [ - Tesla.Middleware.Telemetry, - {Tesla.Middleware.Timeout, timeout: :timer.seconds(1)} - ] + config :ex_force, ExForce.Client.Tesla, + api_version: "43.0", + adapter: {Tesla.Adapter.Hackney, [recv_timeout: 1_000]}, + append_middleware: [ + Tesla.Middleware.Telemetry, + {Tesla.Middleware.Timeout, timeout: :timer.seconds(1)} + ] ``` Check out [Choosing a Tesla Adapter](https://github.com/chulkilee/ex_force/wiki/Choosing-a-Tesla-Adapter). diff --git a/lib/ex_force/client/tesla/tesla.ex b/lib/ex_force/client/tesla/tesla.ex index a5c3e20..880b2e0 100644 --- a/lib/ex_force/client/tesla/tesla.ex +++ b/lib/ex_force/client/tesla/tesla.ex @@ -41,24 +41,25 @@ defmodule ExForce.Client.Tesla do end def build_client(instance_url, opts) when is_binary(instance_url) do - custom_middleware = get_from_opts_or_config(opts, :middleware, []) - + append_middleware = get_from_opts_or_config(opts, :append_middleware, []) adapter = get_from_opts_or_config(opts, :adapter) - headers = get_headers(opts) - [ - {ExForce.Client.Tesla.Middleware, - {instance_url, get_from_opts_or_config(opts, :api_version, @default_api_version)}}, - {Tesla.Middleware.Compression, format: "gzip"}, - {Tesla.Middleware.JSON, engine: Jason}, - {Tesla.Middleware.Headers, headers}, - custom_middleware - ] - |> List.flatten() - |> Tesla.client(adapter) + middleware = + [ + {ExForce.Client.Tesla.Middleware, + {instance_url, get_from_opts_or_config(opts, :api_version, @default_api_version)}}, + {Tesla.Middleware.Compression, format: "gzip"}, + {Tesla.Middleware.JSON, engine: Jason}, + {Tesla.Middleware.Headers, headers} + ] ++ append_middleware + + Tesla.client(middleware, adapter) end + defp get_headers(opts), + do: get_from_opts_or_config(opts, :headers, [{"user-agent", @default_user_agent}]) + @doc """ Returns a `Tesla` client for `ExForce.OAuth` functions @@ -69,20 +70,19 @@ defmodule ExForce.Client.Tesla do """ @impl ExForce.Client def build_oauth_client(instance_url, opts \\ []) do - custom_middleware = get_from_opts_or_config(opts, :middleware, []) - + append_middleware = get_from_opts_or_config(opts, :append_middleware, []) adapter = get_from_opts_or_config(opts, :adapter) - [ - {Tesla.Middleware.DecodeJson, engine: Jason}, - {Tesla.Middleware.BaseUrl, instance_url}, - {Tesla.Middleware.Compression, format: "gzip"}, - Tesla.Middleware.FormUrlencoded, - {Tesla.Middleware.Headers, get_headers(opts)}, - custom_middleware - ] - |> List.flatten() - |> Tesla.client(adapter) + middleware = + [ + {Tesla.Middleware.DecodeJson, engine: Jason}, + {Tesla.Middleware.BaseUrl, instance_url}, + {Tesla.Middleware.Compression, format: "gzip"}, + Tesla.Middleware.FormUrlencoded, + {Tesla.Middleware.Headers, get_headers(opts)} + ] ++ append_middleware + + Tesla.client(middleware, adapter) end @doc """ @@ -95,11 +95,14 @@ defmodule ExForce.Client.Tesla do |> cast_response() end - defp get_from_opts_or_config(opts, key, default \\ nil), - do: Keyword.get(opts, key) || Application.get_env(:ex_force, key) || default + defp get_from_opts_or_config(opts, key, default \\ nil) do + from_opts = Keyword.get(opts, key) - defp get_headers(opts), - do: get_from_opts_or_config(opts, :headers, [{"user-agent", @default_user_agent}]) + app_config = Application.get_env(:ex_force, __MODULE__, []) + from_app_config = Keyword.get(app_config, key) + + from_opts || from_app_config || default + end defp cast_tesla_request(%Request{} = request) do request diff --git a/test/config_test.exs b/test/config_test.exs index 2cdbcdd..d6fe71e 100644 --- a/test/config_test.exs +++ b/test/config_test.exs @@ -25,16 +25,12 @@ defmodule ExForce.ConfigTest do {Tesla.Middleware.Timeout, :call, [[timeout: 1000]]} ] - custom_opts = [ + Application.put_env(:ex_force, ExForce.Client.Tesla, api_version: api_version, - middleware: custom_middleware - ] - - Application.put_all_env(ex_force: custom_opts) + append_middleware: custom_middleware + ) - on_exit(fn -> - Enum.each(custom_opts, fn {key, _} -> Application.delete_env(:ex_force, key) end) - end) + on_exit(fn -> Application.delete_env(:ex_force, ExForce.Client.Tesla) end) Bypass.expect_once(bypass, "GET", "/foo", fn conn -> conn From b45df90b7a3bb7f904fe2f629b7a66336bd96862 Mon Sep 17 00:00:00 2001 From: Gil Aviv <26069404+ggiill@users.noreply.github.com> Date: Tue, 14 Mar 2023 22:43:14 -0400 Subject: [PATCH 3/4] move function back in `ExForceTest` --- test/ex_force_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ex_force_test.exs b/test/ex_force_test.exs index c4c61b4..ab8cd95 100644 --- a/test/ex_force_test.exs +++ b/test/ex_force_test.exs @@ -48,8 +48,6 @@ defmodule ExForceTest do defp map_sobject_id(enum), do: Enum.map(enum, fn %SObject{id: id} -> id end) - defp get(client, url), do: Client.request(client, %Request{url: url, method: :get}) - test "build_client/2 - map", %{bypass: bypass} do Bypass.expect_once(bypass, "GET", "/", fn conn -> conn @@ -1203,4 +1201,6 @@ defmodule ExForceTest do "account6" ] end + + defp get(client, url), do: Client.request(client, %Request{url: url, method: :get}) end From fbca933dc01dc7c7f454d36e1f62cd40fa97a592 Mon Sep 17 00:00:00 2001 From: Gil Aviv <26069404+ggiill@users.noreply.github.com> Date: Tue, 14 Mar 2023 22:45:36 -0400 Subject: [PATCH 4/4] also update how to append `:append_middleware` --- lib/ex_force/client/tesla/tesla.ex | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/ex_force/client/tesla/tesla.ex b/lib/ex_force/client/tesla/tesla.ex index 880b2e0..e4b0e23 100644 --- a/lib/ex_force/client/tesla/tesla.ex +++ b/lib/ex_force/client/tesla/tesla.ex @@ -41,7 +41,6 @@ defmodule ExForce.Client.Tesla do end def build_client(instance_url, opts) when is_binary(instance_url) do - append_middleware = get_from_opts_or_config(opts, :append_middleware, []) adapter = get_from_opts_or_config(opts, :adapter) headers = get_headers(opts) @@ -52,7 +51,7 @@ defmodule ExForce.Client.Tesla do {Tesla.Middleware.Compression, format: "gzip"}, {Tesla.Middleware.JSON, engine: Jason}, {Tesla.Middleware.Headers, headers} - ] ++ append_middleware + ] ++ get_from_opts_or_config(opts, :append_middleware, []) Tesla.client(middleware, adapter) end @@ -70,7 +69,6 @@ defmodule ExForce.Client.Tesla do """ @impl ExForce.Client def build_oauth_client(instance_url, opts \\ []) do - append_middleware = get_from_opts_or_config(opts, :append_middleware, []) adapter = get_from_opts_or_config(opts, :adapter) middleware = @@ -80,7 +78,7 @@ defmodule ExForce.Client.Tesla do {Tesla.Middleware.Compression, format: "gzip"}, Tesla.Middleware.FormUrlencoded, {Tesla.Middleware.Headers, get_headers(opts)} - ] ++ append_middleware + ] ++ get_from_opts_or_config(opts, :append_middleware, []) Tesla.client(middleware, adapter) end