deepl_pro/
api.rs

1use crate::{Error, Lang, Result};
2use reqwest::{Client, RequestBuilder};
3use serde::{de::DeserializeOwned, Deserialize, Serialize};
4use std::{fmt, str::FromStr};
5use url::Url;
6
7const ENDPOINT_FREE: &str = "https://api-free.deepl.com";
8const ENDPOINT_PRO: &str = "https://api.deepl.com";
9
10/// Enumeration of split sentence options.
11#[derive(Debug, Serialize, Deserialize)]
12pub enum SplitSentences {
13    /// Do not split sentences.
14    #[serde(rename = "0")]
15    None,
16    /// Split on punctuation and newlines.
17    ///
18    /// Default for XML tag handling.
19    #[serde(rename = "1")]
20    One,
21    /// Split on punctuation only.
22    ///
23    /// Default for HTML tag handling.
24    #[serde(rename = "nonewlines")]
25    NoNewlines,
26}
27
28/// Variants for formality.
29#[derive(Debug, Default, Serialize, Deserialize)]
30pub enum Formality {
31    /// Default formality.
32    #[default]
33    Default,
34    /// For a more formal language.
35    More,
36    /// For a more informal language.
37    Less,
38    /// For a more formal language if available,
39    /// otherwise fallback to default formality.
40    PreferMore,
41    /// For a more informal language if available,
42    /// otherwise fallback to default formality.
43    PreferLess,
44}
45
46/// Supported language information.
47#[derive(Debug, Serialize, Deserialize)]
48pub struct Language {
49    /// Language code.
50    pub language: Lang,
51    /// Language name.
52    pub name: String,
53    /// Whether the language supports formality.
54    pub supports_formality: Option<bool>,
55}
56
57/// Enumeration of language types.
58#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone)]
59#[serde(rename_all = "lowercase")]
60pub enum LanguageType {
61    /// Source language.
62    #[default]
63    Source,
64    /// Target language.
65    Target,
66}
67
68impl AsRef<str> for LanguageType {
69    fn as_ref(&self) -> &str {
70        match self {
71            Self::Source => "source",
72            Self::Target => "target",
73        }
74    }
75}
76
77impl fmt::Display for LanguageType {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        write!(f, "{}", self.as_ref(),)
80    }
81}
82
83impl FromStr for LanguageType {
84    type Err = Error;
85
86    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
87        Ok(match s {
88            "source" => Self::Source,
89            "target" => Self::Target,
90            _ => return Err(Error::InvalidLanguageType(s.to_string())),
91        })
92    }
93}
94
95/// Account usage information.
96#[derive(Debug, Serialize, Deserialize)]
97pub struct Usage {
98    /// Character count.
99    pub character_count: u64,
100    /// Character limit.
101    pub character_limit: u64,
102}
103
104/// Variants for tag handling.
105#[derive(Debug, Serialize, Deserialize)]
106#[serde(rename_all = "lowercase")]
107pub enum TagHandling {
108    /// XML tag handling.
109    Xml,
110    /// HTML tag handling.
111    Html,
112}
113
114/// Single text translation.
115#[derive(Debug, Serialize, Deserialize)]
116pub struct TextTranslation {
117    /// Translated text.
118    pub text: String,
119    /// Detected source language.
120    pub detected_source_language: Lang,
121}
122
123/// Request to translate text.
124#[derive(Debug, Serialize, Deserialize)]
125pub struct TranslateTextRequest {
126    /// Text to translate.
127    pub text: Vec<String>,
128    /// Target language.
129    pub target_lang: Lang,
130    /// Tag handling.
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub tag_handling: Option<TagHandling>,
133    /// Source language.
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub source_lang: Option<Lang>,
136    /// Context string.
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub context: Option<String>,
139    /// Preserve formatting.
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub preserve_formatting: Option<bool>,
142    /// Glossary identifier.
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub glossary_id: Option<String>,
145    /// Outline detection.
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub outline_detection: Option<bool>,
148    /// Non splitting tags.
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub non_splitting_tags: Option<Vec<String>>,
151    /// Splitting tags.
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub splitting_tags: Option<Vec<String>>,
154    /// Ignore tags.
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub ignore_tags: Option<Vec<String>>,
157    /// Formality.
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub formality: Option<Formality>,
160    /// Split sentences.
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub split_sentences: Option<SplitSentences>,
163}
164
165impl TranslateTextRequest {
166    /// Create new translate text request.
167    pub fn new(text: Vec<String>, target_lang: Lang) -> Self {
168        Self {
169            text,
170            target_lang,
171            source_lang: None,
172            context: None,
173            preserve_formatting: None,
174            glossary_id: None,
175            outline_detection: None,
176            non_splitting_tags: None,
177            splitting_tags: None,
178            ignore_tags: None,
179            tag_handling: None,
180            formality: None,
181            split_sentences: None,
182        }
183    }
184}
185
186/// Response to a translate text request.
187#[derive(Debug, Serialize, Deserialize)]
188pub struct TranslateTextResponse {
189    /// Collection of translations.
190    pub translations: Vec<TextTranslation>,
191}
192
193/// Options when creating an API endpoint.
194pub struct ApiOptions {
195    /// API key.
196    api_key: String,
197    /// Endpoint URL.
198    endpoint: Url,
199    /// Custom HTTP client.
200    client: Option<Client>,
201}
202
203impl ApiOptions {
204    /// Create API options.
205    pub fn new(api_key: impl AsRef<str>) -> Self {
206        if api_key.as_ref().ends_with(":fx") {
207            Self::new_free(api_key)
208        } else {
209            Self::new_pro(api_key)
210        }
211    }
212
213    /// Create API options with a client.
214    pub fn new_with_client(api_key: impl AsRef<str>, client: Client) -> Self {
215        let mut options = Self::new(api_key);
216        options.client = Some(client);
217        options
218    }
219
220    /// API for the free endpoint.
221    fn new_free(api_key: impl AsRef<str>) -> Self {
222        Self {
223            api_key: api_key.as_ref().to_owned(),
224            endpoint: Url::parse(ENDPOINT_FREE).unwrap(),
225            client: None,
226        }
227    }
228
229    /// API for the pro endpoint.
230    fn new_pro(api_key: impl AsRef<str>) -> Self {
231        Self {
232            api_key: api_key.as_ref().to_owned(),
233            endpoint: Url::parse(ENDPOINT_PRO).unwrap(),
234            client: None,
235        }
236    }
237}
238
239/// Interface to the DeepL API.
240pub struct DeeplApi {
241    client: Client,
242    options: ApiOptions,
243}
244
245impl DeeplApi {
246    /// Create a new DeepL API client.
247    pub fn new(mut options: ApiOptions) -> Self {
248        Self {
249            client: options.client.take().unwrap_or_else(|| Client::new()),
250            options,
251        }
252    }
253
254    /// Get account usage.
255    pub async fn usage(&self) -> Result<Usage> {
256        let url = self.options.endpoint.join("v2/usage")?;
257        let req = self.client.get(url);
258        self.make_typed_request::<Usage>(req).await
259    }
260
261    /// Fetch supported languages.
262    pub async fn languages(&self, lang_type: LanguageType) -> Result<Vec<Language>> {
263        let mut url = self.options.endpoint.join("v2/languages")?;
264        url.query_pairs_mut()
265            .append_pair("type", lang_type.as_ref());
266        let req = self.client.get(url);
267        self.make_typed_request::<Vec<Language>>(req).await
268    }
269
270    /// Translate text.
271    pub async fn translate_text(
272        &self,
273        request: &TranslateTextRequest,
274    ) -> Result<TranslateTextResponse> {
275        let url = self.options.endpoint.join("v2/translate")?;
276        let req = self.client.post(url).json(request);
277        self.make_typed_request::<TranslateTextResponse>(req).await
278    }
279
280    async fn make_typed_request<T: DeserializeOwned>(&self, req: RequestBuilder) -> Result<T> {
281        let res = req
282            .header(
283                "Authorization",
284                format!("DeepL-Auth-Key {}", self.options.api_key),
285            )
286            .send()
287            .await?;
288        res.error_for_status_ref()?;
289        Ok(res.json::<T>().await?)
290    }
291}