use error_chain::*;
use reqwest;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct UsageInformation {
pub character_limit: u64,
pub character_count: u64,
}
pub type LanguageList = Vec<LanguageInformation>;
#[derive(Debug, Deserialize)]
pub struct LanguageInformation {
pub language: String,
pub name: String,
}
pub enum SplitSentences {
None,
Punctuation,
PunctuationAndNewlines,
}
pub enum Formality {
Default,
More,
Less,
}
pub struct TranslationOptions {
pub split_sentences: Option<SplitSentences>,
pub preserve_formatting: Option<bool>,
pub formality: Option<Formality>,
}
#[derive(Debug, Deserialize)]
pub struct TranslatableTextList {
pub source_language: Option<String>,
pub target_language: String,
pub texts: Vec<String>,
}
#[derive(Debug, Deserialize, PartialEq)]
pub struct TranslatedText {
pub detected_source_language: String,
pub text: String,
}
#[derive(Debug, Deserialize)]
struct TranslatedTextList {
translations: Vec<TranslatedText>,
}
#[derive(Debug, Deserialize)]
struct ServerErrorMessage {
message: String,
}
pub struct DeepL {
api_key: String,
}
impl DeepL {
pub fn new(api_key: String) -> DeepL {
DeepL { api_key }
}
fn http_request(
&self,
url: &str,
query: &Vec<(&str, std::string::String)>,
) -> Result<reqwest::blocking::Response> {
let url = format!("https://api.deepl.com/v2{}", url);
let mut payload = query.clone();
payload.push(("auth_key", self.api_key.clone()));
let client = reqwest::blocking::Client::new();
let res = match client.post(&url).query(&payload).send() {
Ok(response) if response.status().is_success() => response,
Ok(response) if response.status() == reqwest::StatusCode::UNAUTHORIZED => {
bail!(ErrorKind::AuthorizationError)
}
Ok(response) if response.status() == reqwest::StatusCode::FORBIDDEN => {
bail!(ErrorKind::AuthorizationError)
}
Ok(response) => {
let status = response.status();
match response.json::<ServerErrorMessage>() {
Ok(server_error) => bail!(ErrorKind::ServerError(server_error.message)),
_ => bail!(ErrorKind::ServerError(status.to_string())),
}
}
Err(e) => {
bail!(e)
}
};
Ok(res)
}
pub fn usage_information(&self) -> Result<UsageInformation> {
let res = self.http_request("/usage", &vec![])?;
match res.json::<UsageInformation>() {
Ok(content) => return Ok(content),
_ => {
bail!(ErrorKind::DeserializationError);
}
};
}
pub fn source_languages(&self) -> Result<LanguageList> {
return self.languages("source");
}
pub fn target_languages(&self) -> Result<LanguageList> {
return self.languages("target");
}
fn languages(&self, language_type: &str) -> Result<LanguageList> {
let res = self.http_request("/languages", &vec![("type", language_type.to_string())])?;
match res.json::<LanguageList>() {
Ok(content) => return Ok(content),
_ => bail!(ErrorKind::DeserializationError),
}
}
pub fn translate(
&self,
options: Option<TranslationOptions>,
text_list: TranslatableTextList,
) -> Result<Vec<TranslatedText>> {
let mut query = vec![
("target_lang", text_list.target_language),
];
if let Some(source_language_content) = text_list.source_language {
query.push(("source_lang", source_language_content));
}
for text in text_list.texts {
query.push(("text", text));
}
if let Some(opt) = options {
if let Some(split_sentences) = opt.split_sentences {
query.push((
"split_sentences",
match split_sentences {
SplitSentences::None => "0".to_string(),
SplitSentences::PunctuationAndNewlines => "1".to_string(),
SplitSentences::Punctuation => "nonewlines".to_string(),
},
));
}
if let Some(preserve_formatting) = opt.preserve_formatting {
query.push((
"preserve_formatting",
match preserve_formatting {
false => "0".to_string(),
true => "1".to_string(),
},
));
}
if let Some(formality) = opt.formality {
query.push((
"formality",
match formality {
Formality::Default => "default".to_string(),
Formality::More => "more".to_string(),
Formality::Less => "less".to_string(),
},
));
}
}
let res = self.http_request("/translate", &query)?;
match res.json::<TranslatedTextList>() {
Ok(content) => Ok(content.translations),
_ => bail!(ErrorKind::DeserializationError),
}
}
}
mod errors {
use error_chain::*;
error_chain! {}
}
pub use errors::*;
error_chain! {
foreign_links {
IO(std::io::Error);
Transport(reqwest::Error);
}
errors {
AuthorizationError {
description("Authorization failed, is your API key correct?")
display("Authorization failed, is your API key correct?")
}
ServerError(message: String) {
description("An error occurred while communicating with the DeepL server.")
display("An error occurred while communicating with the DeepL server: '{}'.", message)
}
DeserializationError {
description("An error occurred while deserializing the response data.")
display("An error occurred while deserializing the response data.")
}
}
skip_msg_variant
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn usage_information() {
let key = std::env::var("DEEPL_API_KEY").unwrap();
let usage_information = DeepL::new(key).usage_information().unwrap();
assert!(usage_information.character_limit > 0);
}
#[test]
fn source_languages() {
let key = std::env::var("DEEPL_API_KEY").unwrap();
let source_languages = DeepL::new(key).source_languages().unwrap();
assert_eq!(source_languages.last().unwrap().name, "Chinese");
}
#[test]
fn target_languages() {
let key = std::env::var("DEEPL_API_KEY").unwrap();
let target_languages = DeepL::new(key).target_languages().unwrap();
assert_eq!(target_languages.last().unwrap().name, "Chinese");
}
#[test]
fn translate() {
let key = std::env::var("DEEPL_API_KEY").unwrap();
let deepl = DeepL::new(key);
let tests = vec![
(
None,
TranslatableTextList {
source_language: Some("DE".to_string()),
target_language: "EN-US".to_string(),
texts: vec!["ja".to_string()],
},
vec![TranslatedText {
detected_source_language: "DE".to_string(),
text: "yes".to_string(),
}],
),
(
Some(TranslationOptions {
split_sentences: None,
preserve_formatting: Some(true),
formality: None,
}),
TranslatableTextList {
source_language: Some("DE".to_string()),
target_language: "EN-US".to_string(),
texts: vec!["ja\n nein".to_string()],
},
vec![TranslatedText {
detected_source_language: "DE".to_string(),
text: "yes\n no".to_string(),
}],
),
(
Some(TranslationOptions {
split_sentences: Some(SplitSentences::None),
preserve_formatting: None,
formality: None,
}),
TranslatableTextList {
source_language: Some("DE".to_string()),
target_language: "EN-US".to_string(),
texts: vec!["Ja. Nein.".to_string()],
},
vec![TranslatedText {
detected_source_language: "DE".to_string(),
text: "Yes. No.".to_string(),
}],
),
(
Some(TranslationOptions {
split_sentences: None,
preserve_formatting: None,
formality: Some(Formality::More),
}),
TranslatableTextList {
source_language: Some("EN".to_string()),
target_language: "DE".to_string(),
texts: vec!["Please go home.".to_string()],
},
vec![TranslatedText {
detected_source_language: "EN".to_string(),
text: "Bitte gehen Sie nach Hause.".to_string(),
}],
),
(
Some(TranslationOptions {
split_sentences: None,
preserve_formatting: None,
formality: Some(Formality::Less),
}),
TranslatableTextList {
source_language: Some("EN".to_string()),
target_language: "DE".to_string(),
texts: vec!["Please go home.".to_string()],
},
vec![TranslatedText {
detected_source_language: "EN".to_string(),
text: "Bitte geh nach Hause.".to_string(),
}],
),
];
for test in tests {
assert_eq!(deepl.translate(test.0, test.1).unwrap(), test.2);
}
}
#[test]
#[should_panic(expected = "Error(ServerError(\"Parameter \\'text\\' not specified.")]
fn translate_empty() {
let key = std::env::var("DEEPL_API_KEY").unwrap();
let texts = TranslatableTextList {
source_language: Some("DE".to_string()),
target_language: "EN-US".to_string(),
texts: vec![],
};
DeepL::new(key).translate(None, texts).unwrap();
}
#[test]
#[should_panic(expected = "Error(ServerError(\"Value for \\'target_lang\\' not supported.")]
fn translate_wrong_language() {
let key = std::env::var("DEEPL_API_KEY").unwrap();
let texts = TranslatableTextList {
source_language: None,
target_language: "NONEXISTING".to_string(),
texts: vec!["ja".to_string()],
};
DeepL::new(key).translate(None, texts).unwrap();
}
#[test]
#[should_panic(expected = "Error(AuthorizationError")]
fn translate_unauthorized() {
let key = "wrong_key".to_string();
let texts = TranslatableTextList {
source_language: Some("DE".to_string()),
target_language: "EN-US".to_string(),
texts: vec!["ja".to_string()],
};
DeepL::new(key).translate(None, texts).unwrap();
}
}