2020-06-19 08:45:38 +03:00

151 lines
4.0 KiB
Elixir

defmodule MicrosoftTranslator.Client do
@empty_state %{data: [], done: false}
@base_host "api.cognitive.microsofttranslator.com"
@base_path "/"
@api_methods %{
languages: %{
method: "GET",
path: "languages"
},
detect: %{
method: "POST",
path: "detect"
},
translate: %{
method: "POST",
path: "translate"
}
}
@api_default_params %{
"api-version" => "3.0"
}
@availaible_api_methods Map.keys(@api_methods)
@default_headers [{"Content-Type", "application/json"}]
alias MicrosoftTranslator.Auth
def call(api_method \\ :languages, args \\ %{})
when api_method in @availaible_api_methods and is_map(args) do
body = generate_body(api_method, args)
headers = generate_headers()
params = generate_params(api_method, args)
api_method
|> fetch(headers, body, params)
|> parse(api_method)
end
defp generate_headers(headers \\ [])
defp generate_headers(header) when is_tuple(header),
do: generate_headers([header])
defp generate_headers(headers) when is_list(headers),
do: @default_headers ++ Auth.authorization_headers() ++ headers
defp generate_params(:translate, params) do
params
|> Map.take([:to, :from])
|> params_to_string()
end
defp generate_params(api_method, _params) when api_method in [:detect, :languages],
do: params_to_string(%{})
defp params_to_string(params_map) do
params_map
|> Map.merge(@api_default_params)
|> Enum.reduce("", fn {key, value}, acc -> acc <> "&" <> "#{key}" <> "=" <> "#{value}" end)
|> String.replace_leading("&", "?")
end
defp generate_body(method, params), do: transform_body(method, params) |> Jason.encode!()
defp transform_body(:languages, _), do: %{}
defp transform_body(method, params) when method in [:detect, :translate],
do: [%{text: Map.fetch!(params, :text)}]
defp transform_body(_, params), do: params
defp parse({:ok, %{data: body}}, api_method) do
Jason.decode!(body, keys: :atoms)
|> transform_response(api_method)
end
defp transform_response(response, :detect) do
result =
response
|> List.first()
|> Map.fetch(:language)
case result do
{:ok, language} -> %{languageCode: language}
end
end
defp transform_response(response, :translate) do
result =
response
|> List.first()
|> Map.take([:translations])
end
defp transform_response(response, :languages) do
%{dictionary: dictionary} = response
dictionary
|> Enum.map(fn {key, %{name: name, nativeName: native, dir: dir}} ->
%{code: "#{key}", name: name, nativeName: native, dir: dir}
end)
end
def fetch(api_method, headers, body, params) do
method = @api_methods[api_method].method
path = @base_path <> @api_methods[api_method].path <> params
with {:ok, conn} <- Mint.HTTP.connect(:https, @base_host, 443),
{:ok, conn, _ref} <- Mint.HTTP.request(conn, method, path, headers, body) do
handle_response(conn, @empty_state)
end
end
defp handle_response(conn, state) do
receive do
message ->
case Mint.HTTP.stream(conn, message) do
{:ok, conn, responses} ->
case Enum.reduce(responses, state, &handle_res/2) do
# Loop ends here
%{done: true} = state -> {:ok, state}
%{done: false} = state -> handle_response(conn, state)
end
{:error, _, reason, _} ->
{:error, reason}
:unknown ->
exit({:unexpected, message})
end
end
end
defp handle_res({:status, _, status}, state),
do: Map.put(state, :status, status)
defp handle_res({:headers, _, headers}, state),
do: Map.put(state, :headers, headers)
defp handle_res({:data, _, data}, state),
do: Map.update!(state, :data, fn acc -> [data | acc] end)
defp handle_res({:done, _}, state) do
Map.update!(state, :data, fn acc ->
acc
|> Enum.reverse()
|> Enum.join("")
end)
|> Map.put(:done, true)
end
end