Current section

Files

Jump to
google_ads lib google_ads client.ex
Raw

lib/google_ads/client.ex

defmodule GoogleAds.Client do
@moduledoc """
This module is responsible for issuing requests to the Google Ads API.
Google advises to trace the request-id header
https://developers.google.com/google-ads/api/docs/concepts/call-structure#request-id
With the request id, we can create a correlation between the request and the response
returned by the Google Ads API. This is useful when working with Google Ads support.
"""
require Logger
alias GoogleAds
@behaviour GoogleAds.Adapter
@impl GoogleAds.Adapter
def search(developer_token, access_token, customer_id, request_body, pagination_options \\ []) do
request_body = maybe_add_page_token(request_body, pagination_options)
client = client(developer_token, access_token, customer_id)
client
|> Tesla.post("/customers/:customer_id/googleAds:search", request_body,
opts: [path_params: [customer_id: customer_id]]
)
|> handle_response()
end
@impl GoogleAds.Adapter
def append_lead_conversation(developer_token, access_token, customer_id, request_body) do
client = client(developer_token, access_token, customer_id)
client
|> Tesla.post("/customers/:customer_id/localServices:appendLeadConversation", request_body,
opts: [path_params: [customer_id: customer_id]]
)
|> handle_response()
end
defp maybe_add_page_token(request_body, pagination_options) do
page_token = Keyword.get(pagination_options, :pageToken)
if page_token do
Map.put(request_body, "pageToken", page_token)
else
request_body
end
end
defp handle_response({:ok, %Tesla.Env{status: 200, body: body}}) do
{:ok, body}
end
defp handle_response({:ok, %Tesla.Env{status: 401} = tesla_env}) do
Logger.info("[Google Ads] Failed to issue request with the error: #{inspect(tesla_env)}")
{:error, :invalid_credentials}
end
defp handle_response({:ok, %Tesla.Env{status: 403} = tesla_env}) do
Logger.info("[Google Ads] Failed to issue request with the error: #{inspect(tesla_env)}")
{:error, :invalid_permissions}
end
defp handle_response({:ok, %Tesla.Env{} = tesla_env}) do
Logger.info("[Google Ads] Failed to issue request with the error: #{inspect(tesla_env)}")
{:error, tesla_env}
end
defp handle_response({:error, error}) do
Logger.error("[Google Ads] Failed to issue request with the error: #{inspect(error)}")
{:error, error}
end
defp client(developer_token, token, customer_id) do
middleware = [
{Tesla.Middleware.Retry,
Keyword.merge(
[
should_retry: fn
{:ok, %{status: status}} when status in [429, 500, 503] -> true
{:ok, _tesla_env} -> false
{:error, _tesla_env} -> true
end
],
retry_config()
)},
{Tesla.Middleware.BaseUrl, base_url()},
{Tesla.Middleware.BearerAuth, token: token},
{Tesla.Middleware.Headers, [{"developer-token", developer_token}]},
Tesla.Middleware.OpenTelemetry,
{Tesla.Middleware.Logger, format: &format_log/3},
Tesla.Middleware.KeepRequest,
Tesla.Middleware.PathParams,
{Tesla.Middleware.Telemetry,
%{metadata: %{provider: :google_ads, customer_id: customer_id}}},
Tesla.Middleware.JSON
]
Tesla.client(middleware, adapter())
end
defp format_log(%Tesla.Env{} = request_env, {_any, %Tesla.Env{} = response_env}, time) do
response_request_id =
Enum.find_value(response_env.headers, "NOT SET", fn {key, value} ->
if key == "request-id", do: value
end)
"#{String.upcase(to_string(request_env.method))} #{request_env.url} -> #{response_env.status} (#{time} ms) request-id=#{response_request_id}"
end
defp format_log(%Tesla.Env{} = request_env, response, time) do
"#{String.upcase(to_string(request_env.method))} #{request_env.url} -> #{inspect(response)} (#{time} ms)"
end
defp base_url do
"https://googleads.googleapis.com/v#{Application.get_env(:google_ads, :api_version, "18")}"
end
defp retry_config do
config = Application.get_env(:google_ads, :retry_config, [])
[
delay: String.to_integer(config[:delay] || "1000"),
max_retries: String.to_integer(config[:max_retries] || "10"),
max_delay: String.to_integer(config[:max_delay] || "2000")
]
end
defp adapter do
Application.get_env(
:google_ads,
:client_adapter,
{Tesla.Adapter.Finch, name: GoogleAds.Finch}
)
end
end