#![warn(missing_docs)]
use core::fmt;
use serde::Deserialize;
use std::io;
use reqwest::header;
use reqwest::StatusCode;
mod doc;
mod glos;
mod lang;
mod text;
pub use {
doc::{DocState, Document, DocumentOptions, DocumentStatus},
glos::{
GlossariesResult, Glossary, GlossaryEntriesFormat, GlossaryLanguagePair,
GlossaryLanguagePairsResult,
},
lang::{Language, LanguageInfo, LanguageType, ParseLanguageError},
text::{Formality, SplitSentences, TagHandling, TextOptions, TranslateTextResult, Translation},
};
static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
pub struct DeepL {
client: reqwest::blocking::Client,
url: reqwest::Url,
user_agent: Option<String>,
auth: String,
}
#[derive(Debug)]
pub enum Error {
Api(String),
Io(io::Error),
InvalidLanguage(ParseLanguageError),
InvalidResponse,
Reqwest(reqwest::Error),
Response(StatusCode, String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Api(err) => write!(f, "{err}"),
Self::Io(err) => write!(f, "{err}"),
Self::InvalidLanguage(err) => write!(f, "{err}"),
Self::InvalidResponse => write!(f, "invalid response"),
Self::Reqwest(err) => write!(f, "{err}"),
Self::Response(status, err) => write!(f, "{status} {err}"),
}
}
}
impl std::error::Error for Error {}
impl From<ParseLanguageError> for Error {
fn from(err: ParseLanguageError) -> Self {
Self::InvalidLanguage(err)
}
}
impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Self {
Self::Reqwest(err)
}
}
#[derive(Debug, Deserialize)]
pub struct Usage {
pub character_count: u64,
pub character_limit: u64,
}
#[macro_export]
macro_rules! builder {
(
$name:ident {
@must{
$($must_field:ident: $must_type:ty,)+
};
@optional{
$($opt_field:ident: $opt_type:ty,)+
};
}
) => {
use paste::paste;
paste! {
#[doc = "Options for `" [<$name>] "` translation"]
#[derive(Debug, Clone, serde::Serialize)]
pub struct [<$name Options>] {
$($must_field: $must_type,)+
$($opt_field: Option<$opt_type>,)+
}
impl [<$name Options>] {
#[must_use]
#[doc = "Construct a new `" [<$name Options>] "`"]
pub fn new($($must_field: $must_type,)+) -> Self {
Self {
$($must_field,)+
$($opt_field: None,)+
}
}
$(
#[doc = "Setter for `" $opt_field "`"]
pub fn $opt_field(mut self, $opt_field: $opt_type) -> Self {
self.$opt_field = Some($opt_field);
self
}
)+
}
}
};
}
impl DeepL {
pub fn new(key: &str) -> Self {
let base = if key.ends_with(":fx") {
"https://api-free.deepl.com/v2"
} else {
"https://api.deepl.com/v2"
};
DeepL {
client: reqwest::blocking::Client::new(),
url: reqwest::Url::parse(base).unwrap(),
user_agent: None,
auth: format!("DeepL-Auth-Key {}", &key),
}
}
pub fn client(&mut self, client: reqwest::blocking::Client) -> &mut Self {
self.client = client;
self
}
pub fn set_app_info(&mut self, app: String) -> &mut Self {
self.user_agent = Some(app);
self
}
fn post<U>(&self, url: U) -> reqwest::blocking::RequestBuilder
where
U: reqwest::IntoUrl,
{
self.client.post(url).headers(self.default_headers())
}
fn get<U>(&self, url: U) -> reqwest::blocking::RequestBuilder
where
U: reqwest::IntoUrl,
{
self.client.get(url).headers(self.default_headers())
}
fn delete<U>(&self, url: U) -> reqwest::blocking::RequestBuilder
where
U: reqwest::IntoUrl,
{
self.client.delete(url).headers(self.default_headers())
}
fn default_headers(&self) -> header::HeaderMap {
let app = if let Some(s) = &self.user_agent {
s.clone()
} else {
APP_USER_AGENT.to_string()
};
let mut map = reqwest::header::HeaderMap::new();
map.insert(
header::USER_AGENT,
header::HeaderValue::from_str(&app).unwrap(),
);
map.insert(
header::AUTHORIZATION,
header::HeaderValue::from_str(&self.auth).unwrap(),
);
map
}
pub fn usage(&self) -> Result<Usage, Error> {
let url = format!("{}/usage", self.url);
let resp = self.get(url).send().map_err(Error::Reqwest)?;
let usage: Usage = resp.json()?;
Ok(usage)
}
}
#[cfg(test)]
mod test;