use core::fmt;
use crate::deeplapi::DeeplAPIMessage;
use super::DpTran;
use super::connection;
use connection::ConnectionError;
use super::DeeplAPIError;
use super::ApiKeyType;
use serde::{Deserialize, Serialize};
pub const DEEPL_API_GLOSSARIES: &str = "https://api-free.deepl.com/v3/glossaries";
pub const DEEPL_API_GLOSSARIES_PRO: &str = "https://api.deepl.com/v3/glossaries";
pub const DEEPL_API_GLOSSARIES_PAIRS: &str = "https://api-free.deepl.com/v2/glossary-language-pairs";
pub const DEEPL_API_GLOSSARIES_PRO_PAIRS: &str = "https://api.deepl.com/v2/glossary-language-pairs";
#[derive(Debug, PartialEq)]
pub enum GlossariesApiError {
ConnectionError(ConnectionError),
JsonError(String, String),
CouldNotCreateGlossary,
QuotaExceeded,
}
impl fmt::Display for GlossariesApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GlossariesApiError::ConnectionError(e) => write!(f, "Connection error: {}", e),
GlossariesApiError::JsonError(e, j) => write!(f, "JSON error: {}\nJSON: {}", e, j),
GlossariesApiError::CouldNotCreateGlossary => write!(f, "Could not create glossary on DeepL API by some reason"),
GlossariesApiError::QuotaExceeded => write!(f, "Glossary quota exceeded on DeepL API"),
}
}
}
#[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq)]
pub enum GlossariesApiFormat {
Tsv,
Csv,
}
impl fmt::Display for GlossariesApiFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GlossariesApiFormat::Tsv => write!(f, "tsv"),
GlossariesApiFormat::Csv => write!(f, "csv"),
}
}
}
#[derive(serde::Deserialize, serde::Serialize, Clone, Debug)]
pub struct GlossariesApiDictionaryPostData {
source_lang: String,
target_lang: String,
entries: String,
entries_format: String,
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct GlossariesApiDictionaryResponseData {
pub source_lang: String,
pub target_lang: String,
pub entry_count: u64,
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct GlossariesApiPostData {
name: String,
dictionaries: Vec<GlossariesApiDictionaryPostData>,
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct GlossariesApiResponseData {
pub glossary_id: String,
pub name: String,
pub dictionaries: Vec<GlossariesApiDictionaryResponseData>,
pub creation_time: String,
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct GlossariesApiList {
pub glossaries: Vec<GlossariesApiResponseData>,
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct GlossariesApiSupportedLanguageEntry {
pub source_lang: String,
pub target_lang: String,
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct GlossariesApiSupportedLanguages {
pub supported_languages: Vec<GlossariesApiSupportedLanguageEntry>,
}
impl GlossariesApiPostData {
pub fn new(glossary_name: String, dictionaries: Vec<GlossariesApiDictionaryPostData>) -> Self {
let post_data = GlossariesApiPostData {
name: glossary_name,
dictionaries,
};
post_data
}
pub fn send(&self, api: &DpTran) -> Result<GlossariesApiResponseData, GlossariesApiError> {
let url = if api.api_key_type == ApiKeyType::Free {
api.api_urls.glossaries_for_free.clone()
} else {
api.api_urls.glossaries_for_pro.clone()
};
let header_auth_key = format!("Authorization: DeepL-Auth-Key {}", api.api_key);
let header_content_type = "Content-Type: application/json";
let headers = vec![header_auth_key, header_content_type.to_string()];
let post_data_json = serde_json::to_string(self).unwrap();
let ret = connection::post_with_headers(url, post_data_json, &headers);
match ret {
Ok(res) => {
let ret: Result<DeeplAPIMessage, serde_json::Error> = serde_json::from_str(&res);
if let Ok(ret) = ret {
if ret.message == "Quota exceeded" {
return Err(GlossariesApiError::QuotaExceeded);
} else {
return Err(GlossariesApiError::CouldNotCreateGlossary);
}
}
let ret: GlossariesApiResponseData = serde_json::from_str(&res).map_err(|e| GlossariesApiError::JsonError(e.to_string(), res.clone()))?;
Ok(ret)
},
Err(e) => Err(GlossariesApiError::ConnectionError(e)),
}
}
pub fn get_dictionaries(&self) -> Vec<GlossariesApiDictionaryPostData> {
self.dictionaries.clone()
}
}
impl GlossariesApiResponseData {
pub fn get_glossary_details(api: &DpTran, glossary_id: &super::GlossaryID) -> Result<Self, DeeplAPIError> {
let url = if api.api_key_type == ApiKeyType::Free {
format!("{}/{}", api.api_urls.glossaries_for_free.clone(), glossary_id)
} else {
format!("{}/{}", api.api_urls.glossaries_for_pro.clone(), glossary_id)
};
let header_auth_key = format!("Authorization: DeepL-Auth-Key {}", api.api_key);
let headers = vec![header_auth_key];
let ret = connection::get_with_headers(url, &headers);
match ret {
Ok(res) => {
let ret: GlossariesApiResponseData = serde_json::from_str(&res).map_err(|e| DeeplAPIError::JsonError(e.to_string(), res.clone()))?;
Ok(ret)
},
Err(e) => Err(DeeplAPIError::ConnectionError(e)),
}
}
}
impl GlossariesApiDictionaryPostData {
pub fn new(source_lang: &String, target_lang: &String, entries: &String, entries_format: &String) -> Self {
GlossariesApiDictionaryPostData {
source_lang: source_lang.clone(),
target_lang: target_lang.clone(),
entries: entries.clone(),
entries_format: entries_format.clone(),
}
}
pub fn retrieve_entries(&mut self, api: &DpTran, glossary_id: &String) -> Result<Self, DeeplAPIError> {
let url = if api.api_key_type == ApiKeyType::Free {
format!("{}/{}/entries?source_lang={}&target_lang={}", api.api_urls.glossaries_for_free.clone(), glossary_id, self.source_lang, self.target_lang)
} else {
format!("{}/{}/entries?source_lang={}&target_lang={}", api.api_urls.glossaries_for_pro.clone(), glossary_id, self.source_lang, self.target_lang)
};
let header_auth_key = format!("Authorization: DeepL-Auth-Key {}", api.api_key);
let headers = vec![header_auth_key];
let ret = connection::get_with_headers(url, &headers);
match ret {
Ok(res) => {
#[derive(serde::Deserialize, Debug)]
struct EntriesResponse {
dictionaries: Vec<GlossariesApiDictionaryPostData>,
}
let ret: EntriesResponse = serde_json::from_str(&res).map_err(|e| DeeplAPIError::JsonError(e.to_string(), res.clone()))?;
let dictionary = &ret.dictionaries[0];
Ok(dictionary.clone())
},
Err(e) => Err(DeeplAPIError::ConnectionError(e)),
}
}
pub fn get_source_lang(&self) -> &String {
&self.source_lang
}
pub fn get_target_lang(&self) -> &String {
&self.target_lang
}
pub fn get_entries_iter(&self) -> impl Iterator<Item = (String, String)> + '_ {
let lines = self.entries.lines();
lines.filter_map(|line| {
let mut parts = if self.entries_format == "tsv" {
line.split('\t')
} else {
line.split(',')
};
if let (Some(source), Some(target)) = (parts.next(), parts.next()) {
Some((source.to_string(), target.to_string()))
} else {
None
}
})
}
}
impl GlossariesApiList {
pub fn get_registered_dictionaries(api: &DpTran) -> Result<Self, DeeplAPIError> {
let url = if api.api_key_type == ApiKeyType::Free {
api.api_urls.glossaries_for_free.clone()
} else {
api.api_urls.glossaries_for_pro.clone()
};
let header_auth_key = format!("Authorization: DeepL-Auth-Key {}", api.api_key);
let headers = vec![header_auth_key];
let ret = connection::get_with_headers(url, &headers);
match ret {
Ok(res) => {
let ret: GlossariesApiList = serde_json::from_str(&res).map_err(|e| DeeplAPIError::JsonError(e.to_string(), res.clone()))?;
Ok(ret)
},
Err(e) => Err(DeeplAPIError::ConnectionError(e)),
}
}
}
pub fn delete_glossary(api: &DpTran, glossary_id: &String) -> Result<(), GlossariesApiError> {
let url = if api.api_key_type == ApiKeyType::Free {
format!("{}/{}", api.api_urls.glossaries_for_free.clone(), glossary_id)
} else {
format!("{}/{}", api.api_urls.glossaries_for_pro.clone(), glossary_id)
};
let header_auth_key = format!("Authorization: DeepL-Auth-Key {}", api.api_key);
let headers = vec![header_auth_key];
let ret = connection::delete_with_headers(url, &headers);
match ret {
Ok(_) => Ok(()),
Err(e) => Err(GlossariesApiError::ConnectionError(e)),
}
}
pub fn patch_glossary(api: &DpTran, glossary_id: &String, patch_data: &GlossariesApiPostData) -> Result<(), GlossariesApiError> {
let url = if api.api_key_type == ApiKeyType::Free {
format!("{}/{}", api.api_urls.glossaries_for_free.clone(), glossary_id)
} else {
format!("{}/{}", api.api_urls.glossaries_for_pro.clone(), glossary_id)
};
let header_auth_key = format!("Authorization: DeepL-Auth-Key {}", api.api_key);
let header_content_type = "Content-Type: application/json";
let headers = vec![header_auth_key, header_content_type.to_string()];
let patch_data_json = serde_json::to_string(patch_data).unwrap();
let ret = connection::patch_with_headers(url, patch_data_json, &headers);
match ret {
Ok(_) => Ok(()),
Err(e) => Err(GlossariesApiError::ConnectionError(e)),
}
}
impl GlossariesApiSupportedLanguages {
pub fn get(api: &DpTran) -> Result<GlossariesApiSupportedLanguages, GlossariesApiError> {
let url = if api.api_key_type == ApiKeyType::Free {
api.api_urls.glossaries_language_pairs_for_free.clone()
} else {
api.api_urls.glossaries_language_pairs_for_pro.clone()
};
let header_auth_key = format!("Authorization: DeepL-Auth-Key {}", api.api_key);
let headers = vec![header_auth_key];
let ret = connection::get_with_headers(url, &headers);
match ret {
Ok(res) => {
let ret: GlossariesApiSupportedLanguages = serde_json::from_str(&res).map_err(|e| GlossariesApiError::JsonError(e.to_string(), res.clone()))?;
Ok(ret)
},
Err(e) => Err(GlossariesApiError::ConnectionError(e)),
}
}
pub fn is_lang_pair_supported(&self, source_lang: &String, target_lang: &String) -> bool {
for pair in &self.supported_languages {
if pair.source_lang.to_ascii_uppercase() == source_lang.to_ascii_uppercase()
&& pair.target_lang.to_ascii_uppercase() == target_lang.to_ascii_uppercase() {
return true;
}
}
false
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn impl_glossaries_dictionary() {
let dict = GlossariesApiDictionaryPostData::new(&"EN".to_string(), &"FR".to_string(), &"Hello\tBonjour\nGoodbye\tAu revoir".to_string(), &"tsv".to_string());
assert_eq!(dict.get_source_lang(), &"EN".to_string());
}
#[test]
fn impl_glossaries_post_data() {
let dict1 = GlossariesApiDictionaryPostData::new(&"EN".to_string(), &"FR".to_string(), &"Hello\tBonjour\nGoodbye\tAu revoir".to_string(), &"tsv".to_string());
let dict2 = GlossariesApiDictionaryPostData::new(&"DE".to_string(), &"EN".to_string(), &"Hallo\tHello\nTschüss\tGoodbye".to_string(), &"tsv".to_string());
let glossaries_post_data = GlossariesApiPostData::new("MyGlossary".to_string(), vec![dict1.clone(), dict2.clone()]);
let dictionaries = glossaries_post_data.get_dictionaries();
assert_eq!(dictionaries.len(), 2);
assert_eq!(dictionaries[0].get_source_lang(), &"EN".to_string());
}
#[test]
fn api_glossaries_post_send() {
let api_key = std::env::var("DPTRAN_DEEPL_API_KEY").unwrap();
let api = DpTran::with_endpoint(&api_key, &ApiKeyType::Free, super::super::super::tests::get_endpoint());
let dict = GlossariesApiDictionaryPostData::new(&"EN".to_string(), &"FR".to_string(), &"Hello\tBonjour\nGoodbye\tAu revoir".to_string(), &"tsv".to_string());
let glossaries = vec![dict];
let post_data = GlossariesApiPostData::new("MyGlossary".to_string(), glossaries);
let res = post_data.send(&api);
assert!(res.is_ok());
let glossary_response = GlossariesApiList::get_registered_dictionaries(&api);
assert!(glossary_response.is_ok());
let glossary_response = glossary_response.unwrap();
assert!(glossary_response.glossaries.len() > 0);
let created_glossary = &glossary_response.glossaries[0];
assert_eq!(created_glossary.name, "MyGlossary".to_string());
assert_eq!(created_glossary.dictionaries.len(), 1);
assert_eq!(created_glossary.dictionaries[0].source_lang.to_uppercase(), "EN".to_string());
assert_eq!(created_glossary.dictionaries[0].target_lang.to_uppercase(), "FR".to_string());
assert_eq!(created_glossary.dictionaries[0].entry_count, 2);
let delete_res = delete_glossary(&api, &created_glossary.glossary_id);
assert!(delete_res.is_ok());
}
#[test]
fn api_glossaries_get_registered_dictionaries() {
let api_key = std::env::var("DPTRAN_DEEPL_API_KEY").unwrap();
let api = DpTran::with_endpoint(&api_key, &ApiKeyType::Free, super::super::super::tests::get_endpoint());
let res = GlossariesApiList::get_registered_dictionaries(&api);
assert!(res.is_ok());
}
#[test]
fn api_glossaries_supported_languages() {
let api_key = std::env::var("DPTRAN_DEEPL_API_KEY").unwrap();
let api = DpTran::with_endpoint(&api_key, &ApiKeyType::Free, super::super::super::tests::get_endpoint());
let res = GlossariesApiSupportedLanguages::get(&api);
assert!(res.is_ok());
let supported_languages = res.unwrap();
assert!(supported_languages.is_lang_pair_supported(&"EN".to_string(), &"FR".to_string()));
assert!(!supported_languages.is_lang_pair_supported(&"EN".to_string(), &"XX".to_string()));
}
#[test]
fn api_glossaries_patch_glossary() {
let api_key = std::env::var("DPTRAN_DEEPL_API_KEY").unwrap();
let api = DpTran::with_endpoint(&api_key, &ApiKeyType::Free, super::super::super::tests::get_endpoint());
let dict = GlossariesApiDictionaryPostData::new(&"EN".to_string(), &"FR".to_string(), &"Hello\tBonjour\nGoodbye\tAu revoir".to_string(), &"tsv".to_string());
let glossaries = vec![dict];
let post_data = GlossariesApiPostData::new("MyGlossary".to_string(), glossaries);
let res = post_data.send(&api);
assert!(res.is_ok());
let glossary_response = GlossariesApiList::get_registered_dictionaries(&api);
assert!(glossary_response.is_ok());
let glossary_response = glossary_response.unwrap();
let created_glossary = &glossary_response.glossaries[0];
let new_dict = GlossariesApiDictionaryPostData::new(&"EN".to_string(), &"FR".to_string(), &"Hello\tBonjour!\nGoodbye\tAu revoir!".to_string(), &"tsv".to_string());
let patch_data = GlossariesApiPostData::new("MyGlossaryUpdated".to_string(), vec![new_dict]);
let patch_res = patch_glossary(&api, &created_glossary.glossary_id, &patch_data);
assert!(patch_res.is_ok());
let updated_glossary_response = GlossariesApiList::get_registered_dictionaries(&api);
assert!(updated_glossary_response.is_ok());
let updated_glossary_response = updated_glossary_response.unwrap();
let updated_glossary = &updated_glossary_response.glossaries[0];
assert_eq!(updated_glossary.name, "MyGlossaryUpdated".to_string());
assert_eq!(updated_glossary.dictionaries.len(), 1);
assert_eq!(updated_glossary.dictionaries[0].source_lang.to_uppercase(), "EN".to_string());
assert_eq!(updated_glossary.dictionaries[0].target_lang.to_uppercase(), "FR".to_string());
assert_eq!(updated_glossary.dictionaries[0].entry_count, 2);
let delete_res = delete_glossary(&api, &created_glossary.glossary_id);
assert!(delete_res.is_ok());
}
#[test]
fn impl_glossaries_api_format_debug() {
let format_tsv = GlossariesApiFormat::Tsv;
let format_csv = GlossariesApiFormat::Csv;
assert_eq!(format!("{:?}", format_tsv), "Tsv");
assert_eq!(format!("{:?}", format_csv), "Csv");
}
#[test]
fn impl_get_entries_iter() {
let tsv_data = "Hello\tこんにちは\nWorld\t世界";
let dict_post_data = GlossariesApiDictionaryPostData::new(&"EN".to_string(), &"JA".to_string(), &tsv_data.to_string(), &"tsv".to_string());
let mut iter = dict_post_data.get_entries_iter();
let first = iter.next().unwrap();
assert_eq!(first.0, "Hello".to_string());
assert_eq!(first.1, "こんにちは".to_string());
let second = iter.next().unwrap();
assert_eq!(second.0, "World".to_string());
assert_eq!(second.1, "世界".to_string());
let csv_data = "Hello,こんにちは\nWorld,世界";
let dict_post_data_csv = GlossariesApiDictionaryPostData::new(&"EN".to_string(), &"JA".to_string(), &csv_data.to_string(), &"csv".to_string());
let mut iter_csv = dict_post_data_csv.get_entries_iter();
let first_csv = iter_csv.next().unwrap();
assert_eq!(first_csv.0, "Hello".to_string());
assert_eq!(first_csv.1, "こんにちは".to_string());
let second_csv = iter_csv.next().unwrap();
assert_eq!(second_csv.0, "World".to_string());
assert_eq!(second_csv.1, "世界".to_string());
}
}