1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use serde::{Deserialize, Serialize};
use std::{future::Future, pin::Pin};
use thiserror::Error;

pub mod document;
pub mod glossary;
pub mod languages;
pub mod translate;
pub mod usage;

/// Representing error during interaction with DeepL
#[derive(Debug, Error)]
pub enum Error {
    #[error("invalid response: {0}")]
    InvalidResponse(String),

    #[error("request fail: {0}")]
    RequestFail(String),

    #[error("fail to read file {0}: {1}")]
    ReadFileError(String, tokio::io::Error),

    #[error(
        "trying to download a document using a non-existing document ID or the wrong document key"
    )]
    NonExistDocument,

    #[error("tries to download a translated document that is currently being processed and is not yet ready for download")]
    TranslationNotDone,

    #[error("fail to write file: {0}")]
    WriteFileError(String),
}

const REPO_URL: &'static str = "https://github.com/Avimitin/deepl-rs";

/// Alias Result<T, E> to Result<T, [`Error`]>
type Result<T, E = Error> = std::result::Result<T, E>;

/// Pollable alias to a Pin<Box<dyn Future<...>>>. A convenient type for impl
/// [`IntoFuture`](std::future::IntoFuture) trait
type Pollable<'poll, T> = Pin<Box<dyn Future<Output = T> + Send + Sync + 'poll>>;

/// A self implemented Type Builder
#[macro_export]
macro_rules! impl_requester {
    (
        $name:ident {
            @required{
                $($must_field:ident: $must_type:ty,)+
            };
            @optional{
                $($opt_field:ident: $opt_type:ty,)*
            };
        } -> $fut_ret:ty;
    ) => {
        use paste::paste;
        use $crate::{DeepLApi, Error};

        paste! {
            #[doc = "Builder type for `" $name "`"]
            pub struct $name<'a> {
                client: &'a DeepLApi,

                $($must_field: $must_type,)+
                $($opt_field: Option<$opt_type>,)*
            }

            impl<'a> $name<'a> {
                pub fn new(client: &'a DeepLApi, $($must_field: $must_type,)+) -> Self {
                    Self {
                        client,
                        $($must_field,)+
                        $($opt_field: None,)*
                    }
                }

                $(
                    #[doc = "Setter for `" $opt_field "`"]
                    pub fn $opt_field(&mut self, $opt_field: $opt_type) -> &mut Self {
                        self.$opt_field = Some($opt_field);
                        self
                    }
                )*
            }
        }
    };
}

/// Formality preference for translation
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Formality {
    Default,
    More,
    Less,
    PreferMore,
    PreferLess,
}

impl AsRef<str> for Formality {
    fn as_ref(&self) -> &str {
        match self {
            Self::Default => "default",
            Self::More => "more",
            Self::Less => "less",
            Self::PreferMore => "prefer_more",
            Self::PreferLess => "prefer_less",
        }
    }
}

impl std::fmt::Display for Formality {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_ref())
    }
}

// detail message of the API error
#[derive(Deserialize)]
struct DeepLErrorResp {
    message: String,
}

/// Turn DeepL API error message into [`Error`]
async fn extract_deepl_error<T>(res: reqwest::Response) -> Result<T> {
    let resp = res
        .json::<DeepLErrorResp>()
        .await
        .map_err(|err| Error::InvalidResponse(format!("invalid error response: {err}")))?;
    Err(Error::RequestFail(resp.message))
}