use serde_json::{json, Value};
use std::fmt;
const USER_AGENT_STR: &str = "Bizowie::API";
#[derive(Debug)]
pub enum Error {
Http(reqwest::Error),
MissingMethod,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Http(e) => write!(f, "HTTP error: {}", e),
Error::MissingMethod => {
write!(f, "[Bizowie::API] fatal error: no method given")
}
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Http(e) => Some(e),
Error::MissingMethod => None,
}
}
}
impl From<reqwest::Error> for Error {
fn from(e: reqwest::Error) -> Self {
Error::Http(e)
}
}
#[derive(Debug, Clone)]
pub struct BizowieAPIResponse {
pub data: Value,
pub success: i64,
}
pub struct BizowieAPI {
api_key: String,
secret_key: String,
site: String,
v2: bool,
api_version: Option<String>,
debug: bool,
client: reqwest::Client,
}
impl BizowieAPI {
pub fn new(
api_key: impl Into<String>,
secret_key: impl Into<String>,
site: impl Into<String>,
) -> Self {
Self {
api_key: api_key.into(),
secret_key: secret_key.into(),
site: site.into(),
v2: false,
api_version: None,
debug: false,
client: reqwest::Client::builder()
.user_agent(USER_AGENT_STR)
.build()
.expect("failed to build reqwest client"),
}
}
pub fn v2(mut self, v2: bool) -> Self {
self.v2 = v2;
self
}
pub fn api_version(mut self, version: impl Into<String>) -> Self {
self.api_version = Some(version.into());
self
}
pub fn debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
pub async fn call(
&self,
method: &str,
params: Option<&Value>,
) -> Result<BizowieAPIResponse, Error> {
if self.v2 {
self.call_v2(method, params).await
} else {
self.call_v1(method, params).await
}
}
async fn call_v1(
&self,
method: &str,
params: Option<&Value>,
) -> Result<BizowieAPIResponse, Error> {
if method.is_empty() {
return Err(Error::MissingMethod);
}
let empty = json!({});
let request_body = serde_json::to_string(params.unwrap_or(&empty))
.expect("serializing serde_json::Value to String cannot fail");
let form = reqwest::multipart::Form::new()
.text("api_key", self.api_key.clone())
.text("secret_key", self.secret_key.clone())
.text("site", self.site.clone())
.text("request", request_body);
let res = self
.client
.post(format!("https://{}/bz/api/{}", self.site, method))
.multipart(form)
.send()
.await?;
self.parse_response(res).await
}
async fn call_v2(
&self,
method: &str,
params: Option<&Value>,
) -> Result<BizowieAPIResponse, Error> {
if method.is_empty() {
return Err(Error::MissingMethod);
}
let mut payload = match params {
Some(Value::Object(map)) => map.clone(),
Some(other) => {
let mut m = serde_json::Map::new();
m.insert("_params".into(), other.clone());
m
}
None => serde_json::Map::new(),
};
payload.insert("api_key".into(), Value::String(self.api_key.clone()));
payload.insert("secret_key".into(), Value::String(self.secret_key.clone()));
payload
.entry("api_version".to_string())
.or_insert_with(|| {
Value::String(self.api_version.clone().unwrap_or_else(|| "1.00".to_string()))
});
let body = serde_json::to_string(&Value::Object(payload))
.expect("serializing serde_json::Value to String cannot fail");
let res = self
.client
.post(format!("https://{}/bz/apiv2/call/{}", self.site, method))
.header(reqwest::header::CONTENT_TYPE, "form-data")
.body(body)
.send()
.await?;
self.parse_response(res).await
}
async fn parse_response(
&self,
res: reqwest::Response,
) -> Result<BizowieAPIResponse, Error> {
let status = res.status();
let text = res.text().await?;
let mut data: Value = match serde_json::from_str(&text) {
Ok(v) => v,
Err(_) => {
if self.debug {
eprintln!("[Bizowie::API] {}\n{}", status, text);
}
json!({ "unprocessed": 1 })
}
};
let success = match data.as_object_mut().and_then(|m| m.remove("success")) {
Some(Value::Number(n)) => n.as_i64().unwrap_or(0),
Some(Value::Bool(b)) => {
if b {
1
} else {
0
}
}
_ => 0,
};
Ok(BizowieAPIResponse { data, success })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_chain() {
let bz = BizowieAPI::new("a", "b", "c")
.v2(true)
.api_version("1.00")
.debug(true);
assert!(bz.v2);
assert_eq!(bz.api_version.as_deref(), Some("1.00"));
assert!(bz.debug);
assert_eq!(bz.api_key, "a");
assert_eq!(bz.secret_key, "b");
assert_eq!(bz.site, "c");
}
}