use crate::{
handle_api_response, ApiError, ClientError, Rate, Voting, VotingResults, CONTENT_TYPE,
DEFAULT_BASE_URL, USER_AGENT,
};
use reqwest::{Method, Response};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[derive(Debug, Serialize, Deserialize)]
struct VotingRequest {
choices: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
struct SetChoiceRequest {
choice: String,
index: i32,
}
#[derive(Debug, Serialize, Deserialize)]
struct SetChoiceResponse {
choices: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
struct VoteResponse {
revoted: bool,
}
#[derive(Debug, Serialize, Deserialize)]
struct Ballot {
ballot: HashMap<String, i32>,
}
#[derive(Debug, Serialize, Deserialize)]
struct OkResponse {
code: i32,
message: String,
}
pub struct Client {
token: String,
client: reqwest::Client,
api_url: String,
rate: Arc<Mutex<Option<Rate>>>,
}
impl Client {
pub fn new(token: String) -> Self {
Self::builder(token).build()
}
pub fn builder(token: String) -> ClientBuilder {
ClientBuilder::new(token)
}
pub fn get_rate(&self) -> Option<Rate> {
let rate = self.rate.lock().unwrap();
rate.clone()
}
async fn request<T: serde::Serialize>(
&self,
method: Method,
path: &str,
body: Option<T>,
) -> Result<Response, ClientError> {
let url = format!("{}{}", self.api_url, path);
let mut request = self
.client
.request(method, url)
.header("Authorization", format!("Bearer {}", self.token))
.header("Accept", CONTENT_TYPE)
.header("User-Agent", USER_AGENT);
if let Some(b) = body {
request = request.header("Content-Type", CONTENT_TYPE);
request = request.json(&b);
}
let response = request
.send()
.await
.map_err(|err| ClientError::HttpRequestError(err.without_url()));
if let Ok(response) = &response {
let rate_update = Rate::from_headers(response.headers());
let mut rate = self.rate.lock().unwrap();
*rate = rate_update;
}
response
}
pub async fn create_voting(&self, choices: Vec<String>) -> Result<Voting, ApiError> {
let response = self
.request(Method::POST, "v1/votings", Some(VotingRequest { choices }))
.await?;
handle_api_response(response).await
}
pub async fn get_voting(&self, id: &str) -> Result<Voting, ApiError> {
let mut uri = "v1/votings/".to_string();
url_escape::encode_path_to_string(id, &mut uri);
let response = self.request::<Voting>(Method::GET, &uri, None).await?;
handle_api_response(response).await
}
pub async fn delete_voting(&self, id: &str) -> Result<(), ApiError> {
let mut uri = "v1/votings/".to_string();
url_escape::encode_path_to_string(id, &mut uri);
let response = self
.request::<OkResponse>(Method::DELETE, &uri, None)
.await?;
let _ = handle_api_response::<OkResponse>(response).await?;
Ok(())
}
pub async fn set_choice(
&self,
voting_id: &str,
choice: &str,
index: i32,
) -> Result<Vec<String>, ApiError> {
let mut uri = "v1/votings/".to_string();
url_escape::encode_path_to_string(voting_id, &mut uri);
uri.push_str("/choices");
let response = self
.request(
Method::POST,
&uri,
Some(SetChoiceRequest {
choice: choice.to_string(),
index,
}),
)
.await?;
let resp = handle_api_response::<SetChoiceResponse>(response).await?;
Ok(resp.choices)
}
pub async fn vote(
&self,
voting_id: &str,
voter_id: &str,
ballot: HashMap<String, i32>,
) -> Result<bool, ApiError> {
let mut uri = "v1/votings/".to_string();
url_escape::encode_path_to_string(voting_id, &mut uri);
uri.push_str("/ballots/");
url_escape::encode_path_to_string(voter_id, &mut uri);
let response = self
.request(Method::POST, &uri, Some(Ballot { ballot }))
.await?;
let response = handle_api_response::<VoteResponse>(response).await?;
Ok(response.revoted)
}
pub async fn unvote(&self, voting_id: &str, voter_id: &str) -> Result<(), ApiError> {
let mut uri = "v1/votings/".to_string();
url_escape::encode_path_to_string(voting_id, &mut uri);
uri.push_str("/ballots/");
url_escape::encode_path_to_string(voter_id, &mut uri);
let response = self
.request::<OkResponse>(Method::DELETE, &uri, None)
.await?;
let _ = handle_api_response::<OkResponse>(response).await?;
Ok(())
}
pub async fn get_ballot(
&self,
voting_id: &str,
voter_id: &str,
) -> Result<HashMap<String, i32>, ApiError> {
let mut uri = "v1/votings/".to_string();
url_escape::encode_path_to_string(voting_id, &mut uri);
uri.push_str("/ballots/");
url_escape::encode_path_to_string(voter_id, &mut uri);
let response = self.request::<Ballot>(Method::GET, &uri, None).await?;
let response = handle_api_response::<Ballot>(response).await?;
Ok(response.ballot)
}
pub async fn get_voting_results(&self, voting_id: &str) -> Result<VotingResults, ApiError> {
let mut uri = "v1/votings/".to_string();
url_escape::encode_path_to_string(voting_id, &mut uri);
uri.push_str("/results");
let response = self
.request::<VotingResults>(Method::GET, &uri, None)
.await?;
handle_api_response(response).await
}
pub async fn get_voting_results_duels(
&self,
voting_id: &str,
) -> Result<VotingResults, ApiError> {
let mut uri = "v1/votings/".to_string();
url_escape::encode_path_to_string(voting_id, &mut uri);
uri.push_str("/results/duels");
let response = self
.request::<VotingResults>(Method::GET, &uri, None)
.await?;
handle_api_response(response).await
}
}
pub struct ClientBuilder {
token: String,
api_url: Option<String>,
reqwest_client: Option<reqwest::Client>,
}
impl ClientBuilder {
fn new(token: String) -> Self {
ClientBuilder {
token,
api_url: None,
reqwest_client: None,
}
}
pub fn api_url(mut self, api_url: String) -> Self {
self.api_url = Some(api_url);
self
}
pub fn reqwest_client(mut self, client: reqwest::Client) -> Self {
self.reqwest_client = Some(client);
self
}
pub fn build(self) -> Client {
let mut api_url = match self.api_url {
Some(url) => {
let _ = reqwest::Url::parse(&url).expect("Invalid API URL");
url
}
None => DEFAULT_BASE_URL.to_string(),
};
if !api_url.ends_with('/') {
api_url.push('/');
}
let client = self.reqwest_client.unwrap_or_default();
Client {
token: self.token,
client,
api_url,
rate: Arc::new(Mutex::new(None)),
}
}
}