homeassistant 0.3.0

This is a library for connecting to the Home Assistant API
Documentation
use crate::types::*;
use std::convert::TryFrom;
use std::sync::{Arc, RwLock, Weak};
use std::time;

pub mod errors;
pub mod native_app;
pub mod rest;
pub mod types;

#[derive(Debug)]
pub struct HomeAssistantAPI {
    instance_url: String,
    token: Token,
    client_id: String,
    self_reference: Weak<RwLock<Self>>,
}

#[derive(Debug, Clone)]
pub enum Token {
    Oauth(OAuthToken),
    LongLived(LongLivedToken),
    None,
}

impl Token {
    pub fn as_string(&self) -> Result<String, errors::Error> {
        match self {
            Token::Oauth(token) => Ok(token.token.clone()),
            Token::LongLived(token) => Ok(token.token.clone()),
            Token::None => Err(errors::Error::NoAuth()),
        }
    }

    pub fn need_refresh(&self) -> bool {
        match self {
            Token::Oauth(token) => {
                match time::SystemTime::now().duration_since(token.token_expiration) {
                    Ok(sec_left) => sec_left > time::Duration::from_secs(10),
                    Err(_) => false,
                }
            }
            Token::LongLived(_) => false,
            Token::None => false,
        }
    }
}

#[derive(Debug, Clone)]
pub struct OAuthToken {
    token: String,
    token_expiration: std::time::SystemTime,
    refresh_token: String,
}

#[derive(Debug, Clone)]
pub struct LongLivedToken {
    token: String,
}

impl HomeAssistantAPI {
    pub fn new(instance_url: String, client_id: String) -> Arc<RwLock<Self>> {
        let token = Token::None;
        let ret = Arc::new(RwLock::new(Self {
            instance_url,
            token,
            client_id,
            self_reference: Weak::new(),
        }));

        ret.write().unwrap().self_reference = Arc::downgrade(&ret);

        ret
    }

    pub fn set_oauth_token(
        &mut self,
        access_token: String,
        expires_in: u32,
        refresh_token: String,
    ) {
        let oauth = OAuthToken {
            token: access_token,
            token_expiration: time::SystemTime::now()
                + time::Duration::from_secs(expires_in as u64),
            refresh_token,
        };
        self.token = Token::Oauth(oauth);
    }

    pub fn set_long_lived_token(&mut self, token: String) {
        self.token = Token::LongLived(LongLivedToken { token });
    }

    pub async fn refresh_oauth_token(&mut self) -> Result<(), errors::Error> {
        let refresh_token = String::from("test");

        let response = reqwest::Client::new()
            .post(format!("{}/auth/token", self.instance_url).as_str())
            .query(&[
                ("grant_type", "refresh_token"),
                ("client_id", &self.client_id),
                ("refresh_token", refresh_token.as_str()),
            ])
            .send()
            .await?;

        let refresh_token_resp: RefreshAccessTokenResponse = response.json().await?;
        self.set_oauth_token(
            refresh_token_resp.access_token,
            refresh_token_resp.expires_in,
            refresh_token,
        );
        Ok(())
    }

    pub async fn access_token(
        &mut self,
        code: String,
        client_id: String,
    ) -> Result<GetAccessTokenResponse, errors::Error> {
        let request = GetAccessTokenRequest {
            grant_type: "authorization_code".to_string(),
            code,
            client_id,
        };
        let resp = reqwest::Client::new()
            .post(format!("{}/auth/token", self.instance_url).as_str())
            .form(&request)
            .send()
            .await?;

        match resp.status().as_str() {
            "200" => {
                let access_token_resp = resp.json::<GetAccessTokenResponse>().await?;
                self.set_oauth_token(
                    access_token_resp.access_token.clone(),
                    access_token_resp.expires_in,
                    access_token_resp.refresh_token.clone(),
                );
                Ok(access_token_resp)
            }
            _ => {
                let error = resp.json::<GetAccessTokenError>().await?;
                Err(errors::Error::HaApi(format!(
                    "Error getting access token from HA Error: {} Details: {}",
                    error.error, error.error_description
                )))
            }
        }
    }

    pub async fn get_rest_client(&self) -> rest::Rest {
        match rest::Rest::try_from(self.self_reference.clone()) {
            Ok(rest) => rest,
            Err(_) => unreachable!(),
        }
    }

    pub async fn get_native_client_from_config(
        &self,
        config: native_app::NativeAppConfig,
    ) -> native_app::NativeApp {
        match native_app::NativeApp::from_config(config, self.self_reference.clone()) {
            Ok(native_app) => native_app,
            Err(_) => unreachable!(),
        }
    }

    pub async fn get_native_client(&self) -> native_app::NativeApp {
        match native_app::NativeApp::new(self.self_reference.clone()) {
            Ok(native_app) => native_app,
            Err(_) => unreachable!(),
        }
    }
}