use std::sync::Mutex; use reqwest::{Client, Error as ReqwestError, Response}; use url::{Url}; use crate::robocraft2::{ITokenProvider, ErrorPayload}; use crate::robocraft2::{SearchPayload, SearchResponse, CreateRobotPayload, CreateRobotResponse, FactoryInfoResponse, PublishRobotPayload, PublishRobotResponse, MyRobotsResponse, GetRobotResponse}; /// Community Factory Robot 2 root URL pub const FACTORY_DOMAIN: &str = "https://factory.production.robocraft2.com"; #[derive(Debug)] pub enum FactoryError { Protocol(ReqwestError), Response(ErrorPayload), ResponseCode(ReqwestError, u16) } impl std::fmt::Display for FactoryError { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { match self { Self::Protocol(p) => if let Some(status) = p.status() { write!(f, "HTTP Error {}: {}", status, p) } else { write!(f, "HTTP Error: {}", p) } Self::Response(r) => write!(f, "Factory Error #{}: {}", r.error, r.error_message), Self::ResponseCode(p, s) => write!(f, "HTTP Error {}: {}", s, p) } } } impl std::error::Error for FactoryError {} async fn handle_json_response serde::Deserialize<'a>>(response: Response) -> Result { let status_code: u16 = response.status().into(); if status_code > 199 && status_code < 300 { Ok(response.json::().await.map_err(FactoryError::Protocol)?) } else { match response.json::().await { Ok(err) => Err(FactoryError::Response(err)), Err(e) => Err(FactoryError::ResponseCode(e, status_code)) } } } /// CRF API implementation pub struct FactoryAPI { client: Client, token: Mutex>, } impl FactoryAPI { /*/// Create a new instance, using `DefaultTokenProvider`. pub fn new() -> FactoryAPI { FactoryAPI { client: Client::new(), token: Box::new(DefaultTokenProvider{}), } }*/ /// Create a new instance using the provided token provider. pub fn with_auth(token_provider: Box) -> FactoryAPI { FactoryAPI { client: Client::new(), token: Mutex::new(token_provider), } } /// Retrieve CRF robots on the main page. pub async fn list(&self) -> Result { self.search(SearchPayload::default()).await } /// Search for robots on the CRF which meet the provided parameters pub async fn search(&self, params: SearchPayload) -> Result { let mut url = Url::parse(FACTORY_DOMAIN) .unwrap() .join("/v1/foundry/search") .unwrap(); if let Some(text) = ¶ms.text { url.query_pairs_mut().append_pair("text", text); } if let Some(base_minimum_cpu) = params.base_minimum_cpu { url.query_pairs_mut().append_pair("baseCpuMinimum", &base_minimum_cpu.to_string()); } if let Some(base_maximum_cpu) = ¶ms.base_maximum_cpu { url.query_pairs_mut().append_pair("baseCpuMaximum", &base_maximum_cpu.to_string()); } if let Some(x) = ¶ms.weapon_minimum_cpu { url.query_pairs_mut().append_pair("weaponCpuMinimum", &x.to_string()); } if let Some(x) = ¶ms.weapon_maximum_cpu { url.query_pairs_mut().append_pair("weaponCpuMaximum", &x.to_string()); } if let Some(x) = ¶ms.cosmetic_minimum_cpu { url.query_pairs_mut().append_pair("cosmeticCpuMinimum", &x.to_string()); } if let Some(x) = ¶ms.cosmetic_maximum_cpu { url.query_pairs_mut().append_pair("cosmeticCpuMaximum", &x.to_string()); } if let Some(x) = ¶ms.cluster_minimum { url.query_pairs_mut().append_pair("clusterMinimum", &x.to_string()); } if let Some(x) = ¶ms.cluster_maximum { url.query_pairs_mut().append_pair("clusterMaximum", &x.to_string()); } if let Some(x) = ¶ms.date_minimum { url.query_pairs_mut().append_pair("dateMinimum", x); } if let Some(x) = ¶ms.date_maximum { url.query_pairs_mut().append_pair("dateMaximum", x); } if let Some(x) = ¶ms.creator_id { url.query_pairs_mut().append_pair("creatorId", x); } if let Some(x) = ¶ms.page { url.query_pairs_mut().append_pair("page", &x.to_string()); } if let Some(x) = ¶ms.count { url.query_pairs_mut().append_pair("count", &x.to_string()); } url.query_pairs_mut().append_pair("sortBy", ¶ms.sort_by); url.query_pairs_mut().append_pair("orderBy", ¶ms.order_by); let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?; let result = self.client.get(url) .header("Authorization", "Bearer ".to_owned() + &token) .send().await .map_err(FactoryError::Protocol)?; handle_json_response::(result).await } pub async fn create_robot(&self, robot: CreateRobotPayload) -> Result { let url = Url::parse(FACTORY_DOMAIN) .unwrap() .join("/v1/foundry/garage") .unwrap(); let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?; let result = self.client.post(url) .header("Authorization", "Bearer ".to_owned() + &token) .header("Content-Type", "application/json") .json(&robot) .send().await .map_err(FactoryError::Protocol)?; handle_json_response::(result).await } pub async fn publish_robot(&self, robot: PublishRobotPayload, id: String) -> Result { let url = Url::parse(FACTORY_DOMAIN) .unwrap() .join(&format!("/v1/foundry/vehicles/{}/publish", id)) .unwrap(); let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?; let result = self.client.post(url) .header("Authorization", "Bearer ".to_owned() + &token) .header("Content-Type", "application/json") .json(&robot) .send().await .map_err(FactoryError::Protocol)?; handle_json_response(result).await } pub async fn unpublish_bot(&self, id: String) -> Result<(), FactoryError> { let url = Url::parse(FACTORY_DOMAIN) .unwrap() .join(&format!("/v1/foundry/vehicles/{}/unpublish", id)) .unwrap(); let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?; let result = self.client.post(url) .header("Authorization", "Bearer ".to_owned() + &token) .send().await .map_err(FactoryError::Protocol)?; let status_code = result.status().as_u16(); if status_code > 199 && status_code < 300 { Ok(()) } else { match result.json::().await { Ok(err) => Err(FactoryError::Response(err)), Err(e) => Err(FactoryError::ResponseCode(e, status_code)) } } } pub async fn delete_robot(&self, id: String) -> Result<(), FactoryError> { let url = Url::parse(FACTORY_DOMAIN) .unwrap() .join(&format!("/v1/foundry/vehicles/{}", id)) .unwrap(); let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?; let result = self.client.delete(url) .header("Authorization", "Bearer ".to_owned() + &token) .header("Content-Type", "application/json") .send().await .map_err(FactoryError::Protocol)?; let status_code = result.status().as_u16(); if status_code > 199 && status_code < 300 { Ok(()) } else { match result.json::().await { Ok(err) => Err(FactoryError::Response(err)), Err(e) => Err(FactoryError::ResponseCode(e, status_code)) } } } pub async fn factory_info(&self) -> Result { let url = Url::parse(FACTORY_DOMAIN) .unwrap() .join("/v1/foundry/info") .unwrap(); let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?; let result = self.client.get(url) .header("Authorization", "Bearer ".to_owned() + &token) .send().await .map_err(FactoryError::Protocol)?; handle_json_response::(result).await } pub async fn my_robots(&self) -> Result { let url = Url::parse(FACTORY_DOMAIN) .unwrap() .join("/v1/foundry/garage") .unwrap(); let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?; let result = self.client.get(url) .header("Authorization", "Bearer ".to_owned() + &token) .send().await .map_err(FactoryError::Protocol)?; handle_json_response::(result).await } pub async fn my_published_robots(&self) -> Result { let url = Url::parse(FACTORY_DOMAIN) .unwrap() .join("/v1/foundry/published") .unwrap(); let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?; let result = self.client.get(url) .header("Authorization", "Bearer ".to_owned() + &token) .send().await .map_err(FactoryError::Protocol)?; handle_json_response::(result).await } pub async fn get(&self, id: String) -> Result { let url = Url::parse(FACTORY_DOMAIN) .unwrap() .join(&format!("/v1/foundry/vehicles/{}", id)) .unwrap(); let token = self.token.lock().unwrap().token().await.map_err(FactoryError::Protocol)?; let result = self.client.get(url) .header("Authorization", "Bearer ".to_owned() + &token) .send().await .map_err(FactoryError::Protocol)?; handle_json_response::(result).await } }