qcells 0.1.1

A Rust Q.HOME+ESS - QCELLS API client.
Documentation
use crate::models::{InvertersResponse, LoginResponse, SiteResponse};
use crate::{BASE_URL, Result, Error, Site};

pub struct User {
    pub id: u64,
    pub username: String,
    pub email: String,
    pub country: String,
    pub token: String,
}

impl User {
    pub(crate) fn from_login(resp: &LoginResponse) -> User {
        return User {
            id: resp.user.id,
            username: resp.user.username.to_owned(),
            email: resp.user.email.to_owned(),
            country: resp.user.country.to_owned(),
            token: resp.token.to_owned(),
        };
    }

    pub fn get_site(&self, id: &u64) -> Result<Site> {
        let client = reqwest::blocking::Client::new();
        let request = client.post(format!("{}/userIndex/getSiteInfo", BASE_URL))
            .header("Accept", "application/json")
            .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
            .header("token", &self.token)
            .form(&[("siteId", id)]).build();

        let request = match request {
            Ok(req) => req,
            Err(err) => return Err(Error { message: err.to_string() })
        };
        let response = match client.execute(request) {
            Ok(resp) => resp,
            Err(err) => return Err(Error { message: err.to_string() })
        };
        if !response.status().is_success() {
            return Err(Error { message: format!("Received invalid status code {}", response.status().as_u16()) });
        }

        let site_info: SiteResponse = match response.json() {
            Ok(info) => info,
            Err(err) => return Err(Error { message: err.to_string() }),
        };

        let serial_numbers = match self.get_site_inverter_sn(&site_info.site_name) {
            Ok(sn) => sn,
            Err(err) => return Err(err)
        };

        return Ok(Site::from_response(
            id,
            self.token.to_string(),
            serial_numbers.0.as_str(),
            serial_numbers.1.as_str(),
            site_info,
        ));
    }

    pub fn get_site_inverter_sn(&self, site_name: &str) -> Result<(String, String)> {
        let client = reqwest::blocking::Client::new();
        let request = client.post(format!("{}/device/getPage", BASE_URL))
            .header("Accept", "application/json")
            .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
            .header("token", &self.token)
            .form(&[
                ("siteName", site_name),
                ("isSub", "1"),
                ("size", "10"),
                ("current", "1")
            ]).build();
        let request = match request {
            Ok(req) => req,
            Err(err) => return Err(Error { message: err.to_string() })
        };
        let response = match client.execute(request) {
            Ok(resp) => resp,
            Err(err) => return Err(Error { message: err.to_string() })
        };
        if !response.status().is_success() {
            return Err(Error { message: format!("Received invalid status code {}", response.status().as_u16()) });
        }

        let inverters: InvertersResponse = match response.json() {
            Ok(resp) => resp,
            Err(err) => return Err(Error { message: err.to_string() })
        };

        if inverters.rows.is_empty() {
            return Err(Error { message: format!("No inverters for site {} found", site_name) });
        }

        let inverter = match inverters.rows.get(0) {
            Some(inv) => inv,
            None => return Err(Error { message: format!("No inverters for site {} found", site_name) })
        };

        return Ok((inverter.sn.to_string(), inverter.wifi_sn.to_string()));
    }

    pub fn get_sites(&self) -> Result<Vec<Site>> {
        let client = reqwest::blocking::Client::new();
        let request = client.post(format!("{}/userIndex/getUserAllSiteId", BASE_URL))
            .header("Accept", "application/json")
            .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
            .header("token", &self.token)
            .form(&[("userId", self.id)]).build();

        let request = match request {
            Ok(req) => req,
            Err(err) => return Err(Error { message: err.to_string() })
        };
        let response = match client.execute(request) {
            Ok(resp) => resp,
            Err(err) => return Err(Error { message: err.to_string() })
        };
        if !response.status().is_success() {
            return Err(Error { message: format!("Received invalid status code {}", response.status().as_u16()) });
        }
        let site_ids: reqwest::Result<Vec<String>> = response.json();
        let site_ids = match site_ids {
            Ok(ids) => ids.iter().map(|x| x.parse::<u64>().expect("Int required")).collect::<Vec<_>>(),
            Err(err) => return Err(Error { message: err.to_string() })
        };
        let mut sites: Vec<Site> = Vec::new();
        for site_id in site_ids {
            match self.get_site(&site_id) {
                Ok(site) => sites.push(site),
                Err(err) => return Err(err)
            };
        }
        return Ok(sites);
    }
}

#[cfg(test)]
mod tests {
    use crate::*;

    #[test]
    fn test_get_sites() {
        let client = Client::new(
            "guest",
            "solax123456",
        );
        let client = match client {
            Ok(client) => client,
            Err(err) => panic!("{}", err.message)
        };
        let sites = client.user.get_sites();
        let sites = match sites {
            Ok(sites) => sites,
            Err(err) => panic!("{}", err.message)
        };
        assert!(!sites.is_empty());
        for site in sites {
            assert_ne!(site.id, 0);
        }
    }
}