Current section
Files
Jump to
Current section
Files
src/deeperl.erl
-module(deeperl).
-behaviour(gen_server).
%% API
-export([start_link/0, auth_key/1, usage/0, languages/0, translate/2, translate/3]).
-export_type([translation_options/0, usage_result/0, languages_result/0, translate_result/0]).
-type language() :: de | en | es | fr | it | nl | pl | pt | ru.
-type tag_list() :: [nonempty_string()].
-type translation_options() :: #{
source_lang => language(),
split_sentences => boolean() | nonewlines,
preserve_formatting => boolean(),
tag_handling => xml,
non_splitting_tags => tag_list(),
outline_detection => boolean(),
splitting_tags => tag_list(),
ignore_tags => tag_list()
}.
-type usage_result() :: {CharacterCount :: pos_integer(), CharacterLimit :: pos_integer()} | {error, invalid_response} | {error, term()}.
-type languages_result() :: [{Language :: deeperl:language(), Name :: binary()}] | {error, invalid_response} | {error, term()}.
-type translate_result() :: [{DetectedSourceLanguage :: deeperl:language(), Text :: iodata()}] | {error, invalid_response} | {error, {bad_option, Option :: any()}} | {error, term()}.
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {
conn :: pid(),
conn_mref :: erlang:monitor_process_identifier()
}).
%%%===================================================================
%%% API
%%%===================================================================
%% @private
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%% @private
init(_) ->
GunOpts = case application:get_env(gun_opts) of
undefined -> #{};
{ok, Opts} -> Opts
end,
{ok, ConnPid} = gun:open("api.deepl.com", 443, GunOpts),
{ok, #state{
conn = ConnPid,
conn_mref = monitor(process, ConnPid)
}}.
%% @private
handle_call(usage, _From, State) ->
{reply, call(deeperl_usage, [], State), State};
handle_call(languages, _From, State) ->
{reply, call(deeperl_languages, [], State), State};
handle_call({translate, TargetLang, Texts, Options}, _From, State) ->
{reply, call(deeperl_translate, {TargetLang, Texts, Options}, State), State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
%% @private
handle_cast(_Request, State) ->
{noreply, State}.
%% @private
handle_info({'DOWN', Mref, process, _ConnPid, Reason}, #state{conn_mref = Mref}) ->
exit(Reason);
handle_info(_Info, State) ->
{noreply, State}.
%% @private
terminate(_Reason, _State) ->
ok.
%% @private
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
-spec usage() -> usage_result().
usage() ->
gen_server:call(?SERVER, usage).
-spec languages() -> languages_result().
languages() ->
gen_server:call(?SERVER, languages).
-spec translate(
TargetLang :: language(),
Texts :: [iodata()]
) -> translate_result().
translate(TargetLang, Texts) ->
translate(TargetLang, Texts, #{}).
-spec translate(
TargetLang :: language(),
Text :: [iodata()],
Options :: translation_options()
) -> translate_result().
translate(TargetLang, Texts, Options) ->
gen_server:call(?SERVER, {translate, TargetLang, Texts, Options}).
-spec auth_key(AuthKey :: nonempty_string()) -> ok.
auth_key(AuthKey) ->
application:set_env(deeperl, auth_key, AuthKey).
%%%===================================================================
%%% Internal functions
%%%===================================================================
call(Module, Args, State) ->
Request = case application:get_env(auth_key) of
undefined -> {error, no_authkey};
{ok, AuthKey} -> gen_deeperl_procedure:request(Module, AuthKey, Args)
end,
Result = case Request of
{error, _Reason} = RequestError -> RequestError;
{Uri, Header, Body} -> request(Uri, Header, Body, State)
end,
case Result of
{error, Reason} -> {error, Reason};
{ok, R} -> gen_deeperl_procedure:response(Module, R)
end.
request(Uri, Header, Body, #state{conn = ConnPid, conn_mref = MRef}) ->
StreamRef = gun:post(ConnPid, Uri, Header, Body),
case gun:await(ConnPid, StreamRef, 3000, MRef) of
{response, nofin, 200, _} -> gun:await_body(ConnPid, StreamRef, 1000, MRef);
{response, fin, 200, _} -> {error, emptyresponse};
{response, IsFin, 400, _} -> error_message(IsFin, bad_request, ConnPid, StreamRef, MRef);
{response, IsFin, 403, _} -> error_message(IsFin, bad_auth, ConnPid, StreamRef, MRef);
{response, IsFin, 404, _} -> error_message(IsFin, not_found, ConnPid, StreamRef, MRef);
{response, IsFin, 413, _} -> error_message(IsFin, size_limit, ConnPid, StreamRef, MRef);
{response, IsFin, 429, _} -> error_message(IsFin, too_many_requests, ConnPid, StreamRef, MRef);
{response, IsFin, 456, _} -> error_message(IsFin, quota_exceeded, ConnPid, StreamRef, MRef);
{response, IsFin, 503, _} -> error_message(IsFin, temporarily_unavailable, ConnPid, StreamRef, MRef);
{response, IsFin, _, _} -> error_message(IsFin, unknown_error, ConnPid, StreamRef, MRef);
{error, _} = Error -> Error
end.
error_message(fin, Error, _ConnPid, _StreamRef, _MRef) ->
{error, Error};
error_message(nofin, Error, ConnPid, StreamRef, MRef) ->
{ok, Body} = gun:await_body(ConnPid, StreamRef, 1000, MRef),
{error, {Error, Body}}.