use crate::client_credentials::GresiqCredentials;
use crate::error::GresiqError;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use smbcloud_network::environment::Environment;
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct DocumentQuery {
pub filter: Option<serde_json::Value>,
pub order: Option<String>,
pub dir: Option<String>,
pub limit: Option<u32>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct GresiqDocument<T> {
pub id: String,
pub key: String,
pub collection: String,
pub doc: T,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Deserialize)]
struct DocumentsEnvelope<T> {
documents: Vec<GresiqDocument<T>>,
}
#[derive(Debug, Clone)]
pub struct GresiqClient {
base_url: String,
api_key: String,
api_secret: String,
extra_headers: HashMap<String, String>,
http: reqwest::Client,
}
impl GresiqClient {
pub fn from_credentials(environment: Environment, credentials: GresiqCredentials<'_>) -> Self {
let base_url = crate::client_credentials::base_url(&environment);
GresiqClient {
base_url,
api_key: credentials.api_key.to_string(),
api_secret: credentials.api_secret.to_string(),
extra_headers: HashMap::new(),
http: reqwest::Client::new(),
}
}
pub fn with_extra_headers(mut self, headers: HashMap<String, String>) -> Self {
self.extra_headers = headers;
self
}
pub async fn insert<T: Serialize>(&self, table: &str, record: &T) -> Result<(), GresiqError> {
let url = format!("{}/gresiq/v1/{}", self.base_url, table);
let body = serde_json::json!({ "record": record });
let mut builder = self
.http
.post(&url)
.header("X-Gresiq-Api-Key", &self.api_key)
.header("X-Gresiq-Api-Secret", &self.api_secret)
.json(&body);
for (key, value) in &self.extra_headers {
builder = builder.header(key.as_str(), value.as_str());
}
let response = builder.send().await?;
if response.status().is_success() {
log::debug!("gresiq: {} inserted ok", table);
return Ok(());
}
let status = response.status().as_u16();
let message = response
.text()
.await
.unwrap_or_else(|_| "unreadable response body".to_string());
Err(GresiqError::Api { status, message })
}
pub async fn upsert_document<T: Serialize>(
&self,
collection: &str,
key: Option<&str>,
doc: &T,
) -> Result<(), GresiqError> {
let url = format!("{}/gresiq/v1/collections/{}", self.base_url, collection);
let body = match key {
Some(key) => serde_json::json!({ "key": key, "doc": doc }),
None => serde_json::json!({ "doc": doc }),
};
let response = self.authed_post(&url).json(&body).send().await?;
if response.status().is_success() {
log::debug!("gresiq: upserted document into {}", collection);
return Ok(());
}
Err(self.api_error(response).await)
}
pub async fn get_collection<T: DeserializeOwned>(
&self,
collection: &str,
query: &DocumentQuery,
) -> Result<Vec<GresiqDocument<T>>, GresiqError> {
let url = format!("{}/gresiq/v1/collections/{}", self.base_url, collection);
let mut params: Vec<(&str, String)> = Vec::new();
if let Some(filter) = &query.filter {
params.push(("filter", filter.to_string()));
}
if let Some(order) = &query.order {
params.push(("order", order.clone()));
}
if let Some(dir) = &query.dir {
params.push(("dir", dir.clone()));
}
if let Some(limit) = query.limit {
params.push(("limit", limit.to_string()));
}
let response = self.authed_get(&url).query(¶ms).send().await?;
if !response.status().is_success() {
return Err(self.api_error(response).await);
}
let envelope: DocumentsEnvelope<T> = response.json().await?;
Ok(envelope.documents)
}
fn authed_post(&self, url: &str) -> reqwest::RequestBuilder {
self.with_auth(self.http.post(url))
}
fn authed_get(&self, url: &str) -> reqwest::RequestBuilder {
self.with_auth(self.http.get(url))
}
fn with_auth(&self, builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
let mut builder = builder
.header("X-Gresiq-Api-Key", &self.api_key)
.header("X-Gresiq-Api-Secret", &self.api_secret);
for (key, value) in &self.extra_headers {
builder = builder.header(key.as_str(), value.as_str());
}
builder
}
async fn api_error(&self, response: reqwest::Response) -> GresiqError {
let status = response.status().as_u16();
let message = response
.text()
.await
.unwrap_or_else(|_| "unreadable response body".to_string());
GresiqError::Api { status, message }
}
}