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
pub mod auth;
pub mod collections;
pub mod delete;
pub mod download;
pub mod search;
pub mod upload;
pub mod users;

pub use auth::{Code, CodeProvider, Token};

use crate::{
    client::{
        collections::{CollectionsQuery, CollectionsResult},
        download::Download,
        search::{Search, SearchResult},
        upload::Upload,
        users::{UsersQuery, UsersResult},
    },
    errors::{Error, ErrorKind, Result},
    CenterDevice,
    ClientCredentials,
    WithProgress,
};

use failure::Fail;
use reqwest::{self, blocking::Response, IntoUrl, StatusCode};

pub struct UnauthorizedClient<'a> {
    pub(crate) base_url:           &'a str,
    pub(crate) client_credentials: ClientCredentials<'a>,
    pub(crate) http_client:        reqwest::blocking::Client,
}

impl<'a, 'b: 'a> UnauthorizedClient<'b> {
    pub fn authorize_with_code_flow<T: IntoUrl + ToString + Clone, S: CodeProvider>(
        self,
        redirect_uri: T,
        code_provider: &S,
    ) -> Result<AuthorizedClient<'a>> {
        let redirect_url = redirect_uri
            .clone()
            .into_url()
            .map_err(|e| e.context(ErrorKind::FailedToPrepareHttpRequest(redirect_uri.to_string())))?;

        let token = auth::authorization_code_flow(
            &self.client_credentials,
            self.base_url,
            &redirect_url,
            code_provider,
            &self.http_client,
        )?;

        let authorized_client = AuthorizedClient {
            base_url: self.base_url,
            client_credentials: self.client_credentials,
            token,
            http_client: self.http_client,
        };

        Ok(authorized_client)
    }
}

pub struct AuthorizedClient<'a> {
    pub(crate) base_url:           &'a str,
    pub(crate) client_credentials: ClientCredentials<'a>,
    pub(crate) token:              Token,
    pub(crate) http_client:        reqwest::blocking::Client,
}

impl<'a> AuthorizedClient<'a> {
    pub fn token(&self) -> &Token { &self.token }
}

impl<'a> CenterDevice for AuthorizedClient<'a> {
    fn refresh_access_token(&self) -> Result<Token> { auth::refresh_access_token(self) }

    fn search_documents(&self, search: Search) -> Result<SearchResult> { search::search_documents(self, search) }

    fn upload_file(&self, upload: Upload) -> Result<ID> { upload::upload_file(&self, upload) }

    fn download_file(&self, download: Download) -> Result<u64> { download::download_file(self, download) }

    fn download_file_with_progress<T: WithProgress>(&self, download: Download, progress: &mut T) -> Result<u64> {
        download::download_file_with_progress(self, download, progress)
    }

    fn delete_documents(&self, document_ids: &[&str]) -> Result<()> { delete::delete_documents(self, document_ids) }

    fn search_users(&self, users_query: UsersQuery) -> Result<UsersResult> { users::search_users(self, users_query) }

    fn search_collections(&self, collections_query: CollectionsQuery) -> Result<CollectionsResult> {
        collections::search_collections(self, collections_query)
    }
}

pub type ID = String;

pub(crate) trait GeneralErrHandler {
    type T: std::marker::Sized;

    fn general_err_handler(self, expected_states: &[StatusCode]) -> Result<Self::T>;
}

impl GeneralErrHandler for Response {
    type T = Response;

    fn general_err_handler(self, expected_states: &[StatusCode]) -> Result<Self> {
        match self.status() {
            code if expected_states.contains(&code) => Ok(self),
            code @ StatusCode::UNAUTHORIZED => Err(Error::from(ErrorKind::ApiCallFailedInvalidToken(code))),
            code @ StatusCode::TOO_MANY_REQUESTS => Err(Error::from(ErrorKind::ApiCallFailedTooManyRequests(code))),
            _ => Err(handle_error(self)),
        }
    }
}

fn handle_error(response: Response) -> Error {
    let status_code = response.status();

    match response.text() {
        Ok(body) => Error::from(ErrorKind::ApiCallFailed(status_code, body)),
        Err(e) => {
            e.context(ErrorKind::FailedToProcessHttpResponse(
                status_code,
                "reading body".to_string(),
            ))
            .into()
        }
    }
}