defmodule ExAws.Operation.CloudSearch do
@moduledoc """
A datastructure representing an operation on AWS CloudSearch.
This is fundamentally the same as `ExAws.Operation.JSON`, but it requires a
separate `ExAws.Operation` implementation so that separate configurations do
not have to be used for document API calls and search API calls. The
`ExAws.Operation.CloudSearch` must be configured with the `domain`,
and depending on the action being performed, one of the `config`, `doc`, or
`search` domains will be filled into the `config` during execution.
The `before_request` callback will be called before the host configuration is
finalized or the CloudSearch API version is prepended to the path.
Note: ExAws.CloudSearch only supports version 2013-01-01.
"""
defstruct stream_builder: nil,
service: :cloudsearch,
http_method: :post,
parser: nil,
path: "/",
data: %{},
params: %{},
headers: [],
before_request: nil,
api_version: "2013-01-01",
request_type: :search
@type t :: %__MODULE__{
stream_builder: nil | (ExAws.Config.t() -> ExAws.Config.t()),
service: :cloudsearch,
http_method: :get | :post | :put | :patch | :head | :delete,
parser: nil | fun(),
path: String.t(),
data: map,
params: map,
headers: list({String.t(), String.t()}),
before_request: nil | (t, ExAws.Config.t() -> t),
api_version: String.t(),
request_type: :config | :doc | :search
}
def new(opts) do
struct(%__MODULE__{parser: & &1}, opts)
end
end
defimpl ExAws.Operation, for: ExAws.Operation.CloudSearch do
@type response_t :: %{} | ExAws.Request.error_t() | no_return
def perform(operation, config) do
operation = handle_before(operation, config)
{operation, config} = configure_host(operation, config)
{operation, data, headers} = prepare_request(operation, config)
url = ExAws.Request.Url.build(operation, config)
headers = [{"accept", "application/json"}, {"x-amz-content-sha256", ""} | headers]
operation.http_method
|> ExAws.Request.request(url, data, headers, config, :cloudsearch)
|> parse_response(config)
end
def stream!(_, _) do
raise(ArgumentError, "This operation does not support streaming!")
end
defp handle_before(%{before_request: nil} = op, _) do
op
end
defp handle_before(%{before_request: callback} = op, config) do
callback.(op, config)
end
defp configure_host(
%{api_version: version, request_type: :config} = operation,
%{region: region} = config
) do
verify_version(version)
{operation, %{config | host: "cloudsearch.#{region}.amazonaws.com"}}
end
defp configure_host(
%{api_version: version, path: path, request_type: type} = operation,
%{region: region, search_domain: domain} = config
) do
verify_version(version)
{
%{operation | path: "/#{version}/#{path}", service: :cloudsearch},
%{config | host: "#{type}-#{domain}.#{region}.cloudsearch.amazonaws.com"}
}
end
defp prepare_request(%{request_type: :search, http_method: :post} = op, config) do
data =
op
|> parse_search_query(config)
|> URI.encode_query()
headers = [{"content-type", "application/x-www-form-urlencoded"} | op.headers]
{Map.put(op, :params, %{}), data, headers}
end
defp prepare_request(%{request_type: :search} = op, config) do
{Map.put(op, :params, parse_search_query(op, config)), %{}, op.headers}
end
defp prepare_request(%{data: data, headers: headers, request_type: :doc} = op, _config) do
{op, data, [{"content-type", "application/json"} | headers]}
end
defp prepare_request(%{request_type: :config, http_method: :post} = op, _config) do
data = URI.encode_query(op.params)
headers = [{"content-type", "application/x-www-form-urlencoded"} | op.headers]
{Map.put(op, :params, %{}), data, headers}
end
defp prepare_request(%{data: data, headers: headers} = op, _config) do
{op, data, headers}
end
defp parse_response({:error, _} = result, _) do
result
end
defp parse_response({:ok, %{body: ""}}, _) do
{:ok, %{}}
end
defp parse_response({:ok, %{body: body}}, config) do
{:ok, config[:json_codec].decode!(body)}
end
defp parse_search_query(%{params: %{} = params}, config) do
{query, parser} =
params
|> Map.get_lazy("q", fn -> Map.fetch!(params, :q) end)
|> ExAws.CloudSearch.QueryParser.parse()
{fq, _} =
params
|> Map.get("fq", Map.get(params, :fq))
|> ExAws.CloudSearch.QueryParser.parse()
params
|> put_param(:"q.parser", parser)
|> put_param(:fq, fq)
|> put_param(:q, query)
|> Enum.reduce(%{}, &convert_json_params(config, &1, &2))
end
defp convert_json_params(config, {key, {:json, value}}, params) do
Map.put(params, key, config[:json_codec].encode!(value))
end
defp convert_json_params(_config, {key, value}, params) do
Map.put(params, key, value)
end
defp put_param(params, _, nil) do
params
end
defp put_param(params, name, value) when is_atom(name) do
params
|> Map.put(to_string(name), value)
|> Map.delete(name)
end
defp verify_version("2013-01-01") do
nil
end
defp verify_version(version) do
raise(ExAws.Error, "Unsupported CloudSearch API version #{version}")
end
end