geeny-api 0.3.0

Crate for consuming the Geeny APIs as a client
Documentation
// Copyright 2017 Telefónica Germany Next GmbH. See the COPYRIGHT file at
// the top-level directory of this distribution
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use reqwest::{header, StatusCode};

use endpoints::{DefaultClient, GeenyApi};
use errors::*;
use models::*;

/// Interface for Geeny Connect APIs
///
/// `ConnectApi` is an interface for interacting with the Geeny Connect API.
/// The full specification of these APIs may be found in the
/// [API Specification](https://connect.geeny.io/).
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ConnectApi(GeenyApi);

impl Default for ConnectApi {
    /// Create a ConnectApi handle for the Production Connect Service
    ///
    /// # Example
    ///
    /// ```rust
    /// use geeny_api::ConnectApi;
    ///
    /// let api = ConnectApi::default();
    /// ```
    fn default() -> Self {
        Self::new("https://connect.geeny.io".into(), 443)
    }
}

// Auth APIs
impl ConnectApi {
    /// Create a new Connect API handle
    ///
    /// # Example
    ///
    /// ```rust
    /// use geeny_api::ConnectApi;
    ///
    /// let api = ConnectApi::new("https://connect.geeny.io".into(), 443);
    /// ```
    pub fn new(host: String, port: u16) -> Self {
        ConnectApi(GeenyApi {
            host: host,
            port: port,
            client: DefaultClient::default(),
        })
    }

    /// Register a new account and obtain an API token
    ///
    /// If the response was successful, `Ok(AuthLoginResponse)` is returned.
    /// If a network or server error occurred, or if the parameters were incorrect,
    /// an `Err` is returned.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// extern crate geeny_api;
    /// use geeny_api::ConnectApi;
    /// use geeny_api::models::*;
    ///
    /// fn main() {
    ///     let api = ConnectApi::default();
    ///
    ///     let new_user = RegistrationRequest {
    ///         email: "demo@email.com".into(),
    ///         first_name: "Demo".into(),
    ///         last_name: "User".into(),
    ///         password: "S3cureP@ssword!".into(),
    ///         is_developer: true,
    ///     };
    ///
    ///     let response = api.registration(&new_user).unwrap();
    ///     println!("token: {}", response.token);
    /// }
    /// ```
    pub fn registration(&self, req: &RegistrationRequest) -> Result<AuthLoginResponse> {
        let mut rreq = self.0.client.0.post(&self.0.endpoint("/auth/registration"));
        rreq.json(req);

        info!("Requesting: {:?}", rreq);

        let mut resp = rreq.send()?;
        info!("Response: {:?}", resp);

        Ok(resp.json().chain_err(|| "Failed to login")?)
    }

    pub fn delete_user(&self, token: &str) -> Result<()> {
        let mut rreq = self.0.client.0.delete(&self.0.endpoint("/auth/me"));
        rreq.header(header::Authorization(format!("JWT {}", token)));
        info!("Requesting: {:?}", rreq);

        let resp = rreq.send()?;
        info!("Response: {:?}", resp);

        match resp.status() {
            StatusCode::NoContent => Ok(()),
            _ => Err(Error::from("Failed to delete user")),
        }
    }

    /// Perform a Basic Auth login and obtain an API token
    ///
    /// If the response was successful, `Ok(AuthLoginResponse)` is returned.
    /// If a network or server error occurred, or if the credentials were incorrect,
    /// an `Err` is returned.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// extern crate geeny_api;
    /// use geeny_api::ConnectApi;
    /// use geeny_api::models::*;
    ///
    /// fn main() {
    ///     let api = ConnectApi::default();
    ///
    ///     let log_req = AuthLoginRequest {
    ///         email: "demo@email.com".into(),
    ///         password: "S3cureP@ssword!".into(),
    ///     };
    ///
    ///     let response = api.login(&log_req).unwrap();
    ///     println!("token: {}", response.token);
    /// }
    /// ```
    pub fn login(&self, req: &AuthLoginRequest) -> Result<AuthLoginResponse> {
        let mut rreq = self.0.client.0.post(&self.0.endpoint("/auth/login"));

        rreq.json(req);

        info!("Requesting: {:?}", rreq);
        let mut resp = rreq.send()?;
        info!("Response: {:?}", resp);

        Ok(resp.json().chain_err(|| "Failed to login")?)
    }

    /// Check the validity of an API token
    ///
    /// If the response was successful, `Ok(())` is returned. If a network or
    /// server error occurred, or if the token is invalid, an `Err` is returned.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// extern crate geeny_api;
    /// use geeny_api::ConnectApi;
    /// use geeny_api::models::*;
    ///
    /// fn main() {
    ///     let api = ConnectApi::default();
    ///
    ///     // first, obtain a token
    ///     let log_req = AuthLoginRequest {
    ///         email: "demo@email.com".into(),
    ///         password: "S3cureP@ssword!".into(),
    ///     };
    ///
    ///     let response = api.login(&log_req).unwrap();
    ///
    ///     // then, verify the token
    ///     api.check_token(&response).unwrap();
    /// }
    /// ```
    pub fn check_token(&self, req: &AuthLoginResponse) -> Result<()> {
        let mut rreq = self.0.client.0.post(&self.0.endpoint("/auth/jwt/verify"));
        rreq.json(req);

        info!("Requesting: {:?}", rreq);
        let mut resp = rreq.send()?;
        info!("Response: {:?}", resp);

        // Verify the response was correct
        let _: AuthLoginResponse = resp.json()
            .chain_err(|| "Failed to deserialize token response")?;

        Ok(())
    }

    /// Refresh an API token
    ///
    /// If the response was successful, `Ok(AuthLoginResponse)` is returned.
    /// If a network or server error occurred, or if the token is not refreshable,
    /// an `Err` is returned.
    ///
    /// The token returned should be used for all further API requests, and the
    /// original token should be discarded on successful refresh.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// extern crate geeny_api;
    /// use geeny_api::ConnectApi;
    /// use geeny_api::models::*;
    ///
    /// fn main() {
    ///     let api = ConnectApi::default();
    ///
    ///     // first, obtain a token
    ///     let log_req = AuthLoginRequest {
    ///         email: "demo@email.com".into(),
    ///         password: "S3cureP@ssword!".into(),
    ///     };
    ///     let response = api.login(&log_req).unwrap();
    ///     println!("1st Token: {}", response.token);
    ///
    ///     // then, refresh the token
    ///     let new_response = api.refresh_token(&response).unwrap();
    ///     println!("2nd Token: {}", new_response.token);
    /// }
    /// ```
    pub fn refresh_token(&self, req: &AuthLoginResponse) -> Result<AuthLoginResponse> {
        let mut resp = self.0
            .client
            .0
            .post(&self.0.endpoint("/auth/jwt/refresh"))
            .json(req)
            .send()?;

        Ok(resp.json().chain_err(|| "Failed to refresh token")?)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use serde_json;

    #[test]
    fn serialize_test() {
        let api = ConnectApi::default();
        let s = serde_json::to_string(&api).unwrap();

        assert_eq!(s, "{\"host\":\"https://connect.geeny.io\",\"port\":443}");
    }

    #[test]
    fn deserialize_test() {
        let api = ConnectApi::default();
        let de_api: ConnectApi = serde_json::from_str(
            r#"
            {
                "host": "https://connect.geeny.io",
                "port": 443
            }
        "#,
        ).unwrap();

        assert_eq!(api.0.host, de_api.0.host);
        assert_eq!(api.0.port, de_api.0.port);
    }

    #[ignore]
    #[test]
    fn login_test() {
        use std::env;

        let user = env::var("TEST_USER").unwrap();
        let pass = env::var("TEST_PASSWORD").unwrap();

        let api = ConnectApi::default();
        let req = AuthLoginRequest {
            email: user,
            password: pass,
        };

        assert!(api.login(&req).is_ok());
    }

    #[ignore]
    #[test]
    fn token_check_test() {
        let api = ConnectApi::default();
        let tok_req = get_token();

        assert!(api.check_token(&tok_req).is_ok());
    }

    #[ignore]
    #[test]
    fn token_refresh_test() {
        let api = ConnectApi::default();
        let tok_req = get_token();

        let tok_refresh = api.refresh_token(&tok_req);

        assert!(tok_refresh.is_ok());
    }

    fn get_token() -> AuthLoginResponse {
        use std::env;
        let user = env::var("TEST_USER").unwrap();
        let pass = env::var("TEST_PASSWORD").unwrap();

        let api = ConnectApi::default();
        let req = AuthLoginRequest {
            email: user,
            password: pass,
        };

        api.login(&req).unwrap()
    }
}