pub(crate) mod helpers;
use serde::Deserialize;
use std::{
collections::{HashMap, HashSet},
env,
};
use helpers::{format_flag_query, get_api_url};
use request::{get_bearer_token_header, HttpVerb, SmartcarRequestBuilder};
use response::{Access, Compatibility, Meta, User, Vehicles};
pub mod auth_client;
pub mod error;
pub mod request;
pub mod response;
pub mod vehicle;
pub mod webhooks;
pub async fn get_user(acc: &Access) -> Result<(User, Meta), error::Error> {
let url = format!("{api_url}/v2.0/user", api_url = get_api_url());
let (res, meta) = SmartcarRequestBuilder::new(&url, HttpVerb::Get)
.add_header("Authorization", &get_bearer_token_header(&acc.access_token))
.send()
.await?;
let data = res.json::<User>().await?;
Ok((data, meta))
}
pub async fn get_vehicles(
acc: &Access,
limit: Option<i32>,
offset: Option<i32>,
) -> Result<(Vehicles, Meta), error::Error> {
let url = format!("{api_url}/v2.0/vehicles", api_url = get_api_url());
let mut req = SmartcarRequestBuilder::new(&url, HttpVerb::Get)
.add_header("Authorization", &get_bearer_token_header(&acc.access_token));
if let Some(l) = limit {
req = req.add_query("limit", &l.to_string())
}
if let Some(o) = offset {
req = req.add_query("offset", &o.to_string());
}
let (res, meta) = req.send().await?;
let data = res.json::<Vehicles>().await?;
Ok((data, meta))
}
pub struct CompatibilityOptions {
pub client_id: Option<String>,
pub client_secret: Option<String>,
pub flags: Option<HashMap<String, String>>,
}
pub async fn get_compatibility(
vin: &str,
scope: &ScopeBuilder,
country: &str,
options: Option<CompatibilityOptions>,
) -> Result<(Compatibility, Meta), error::Error> {
let mut client_id = env::var("SMARTCAR_CLIENT_ID");
let mut client_secret = env::var("SMARTCAR_CLIENT_SECRET");
let url = format!("{}/v2.0/compatibility", get_api_url());
let mut req = SmartcarRequestBuilder::new(&url, HttpVerb::Get)
.add_query("vin", vin)
.add_query("scope", &scope.query_value)
.add_query("country", country);
if let Some(opts) = options {
if let Some(flags) = opts.flags {
req = req.add_query("flags", &format_flag_query(&flags));
};
if let Some(id) = opts.client_id {
client_id = Ok(id);
};
if let Some(secret) = opts.client_secret {
client_secret = Ok(secret);
};
};
let id = match client_id {
Err(_) => {
let msg = "compatibility::client id must be passed as an env variable (SMARTCAR_CLIENT_ID) OR via CompatibilityOptionsBuilder";
return Err(error::Error::MissingParameters(msg.to_string()));
}
Ok(v) => v,
};
let secret = match client_secret {
Err(_) => {
let msg = "compatibility::client secret must be passed as an env variable (SMARTCAR_CLIENT_SECRET) OR via CompatibilityOptionsBuilder";
return Err(error::Error::MissingParameters(msg.to_string()));
}
Ok(v) => v,
};
let (res, meta) = req
.add_header(
"Authorization",
&request::get_basic_b64_auth_header(&id, &secret),
)
.send()
.await?;
let data = res.json::<Compatibility>().await?;
Ok((data, meta))
}
#[derive(Deserialize, Debug, Eq, PartialEq, Hash, Clone, Copy)]
pub enum Permission {
ReadCompass, ReadEngineOil, ReadBattery, ReadCharge, ControlCharge, ReadThermometer, ReadFuel, ReadLocation, ControlSecurity, ReadOdometer, ReadSpeedeomter, ReadTires, ReadVehicleInfo, ReadVin, }
impl Permission {
fn as_str(&self) -> &str {
match self {
Permission::ReadCompass => "read_compass",
Permission::ReadEngineOil => "read_engine_oil",
Permission::ReadBattery => "read_battery",
Permission::ReadCharge => "read_charge",
Permission::ControlCharge => "control_charge",
Permission::ReadThermometer => "read_thermometer",
Permission::ReadFuel => "read_fuel",
Permission::ReadLocation => "read_location",
Permission::ControlSecurity => "control_security",
Permission::ReadOdometer => "read_odometer",
Permission::ReadSpeedeomter => "read_speedometer",
Permission::ReadTires => "read_tires",
Permission::ReadVehicleInfo => "read_vehicle_info",
Permission::ReadVin => "read_vin",
}
}
}
#[derive(Deserialize, Debug)]
pub struct ScopeBuilder {
pub permissions: HashSet<Permission>,
query_value: String,
}
impl Default for ScopeBuilder {
fn default() -> Self {
Self::new()
}
}
impl ScopeBuilder {
pub fn new() -> ScopeBuilder {
ScopeBuilder {
permissions: HashSet::new(),
query_value: String::from(""),
}
}
pub fn add_permission(mut self, p: Permission) -> Self {
if !self.permissions.contains(&p) {
if !self.query_value.is_empty() {
self.query_value.push(' ');
};
self.query_value.push_str(p.as_str());
self.permissions.insert(p);
}
self
}
pub fn add_permissions<T>(mut self, permissions: T) -> Self
where
T: AsRef<[Permission]>,
{
let permissions_slice = permissions.as_ref();
for p in permissions_slice {
if !self.permissions.contains(p) {
if !self.query_value.is_empty() {
self.query_value.push(' ');
}
self.query_value.push_str(p.as_str());
self.permissions.insert(*p);
}
}
self
}
pub fn with_all_permissions() -> ScopeBuilder {
ScopeBuilder {
permissions: HashSet::new(),
query_value: String::from(""),
}
.add_permissions(vec![
Permission::ReadCompass,
Permission::ReadEngineOil,
Permission::ReadBattery,
Permission::ReadCharge,
Permission::ControlCharge,
Permission::ReadThermometer,
Permission::ReadFuel,
Permission::ReadLocation,
Permission::ControlSecurity,
Permission::ReadOdometer,
Permission::ReadSpeedeomter,
Permission::ReadTires,
Permission::ReadVehicleInfo,
Permission::ReadVin,
])
}
}
#[test]
fn test_getting_scope_url_params_string() {
let permissions = ScopeBuilder::new().add_permissions([
Permission::ReadEngineOil,
Permission::ReadFuel,
Permission::ReadVin,
]);
let expecting = "read_engine_oil read_fuel read_vin";
assert_eq!(&permissions.query_value, expecting);
}