use crate::errors::{EdbError, EdbResult, EdbResultExt};
use reqwest::{
header::{CONTENT_LENGTH, CONTENT_TYPE},
Client, Url,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
pub use serde_json::Value as Json;
use std::{collections::HashMap, fs::read_to_string, io::Write, str::FromStr};
#[derive(Debug, Deserialize, Serialize)]
pub struct EasyDB {
#[serde(rename = "UUID")]
uuid: String,
#[serde(rename = "Token")]
token: String,
#[serde(skip, default = "Client::new")]
client: Client,
#[serde(rename = "URL", default = "default_url")]
url: String,
}
fn default_url() -> String {
"https://app.easydb.io/database/".to_string()
}
impl EasyDB {
pub fn new() -> EdbResult<Self> {
let edb: Self = read_to_string("./easydb.toml")?.parse()?;
edb.validate_uuid()?;
Ok(edb)
}
pub fn from_uuid_token(uuid: String, token: String, url: Option<String>) -> EdbResult<Self> {
let edb = Self {
uuid,
token,
client: Client::new(),
url: url.unwrap_or_else(default_url),
};
edb.url.parse::<Url>()?;
edb.validate_uuid()?;
Ok(edb)
}
fn validate_uuid(&self) -> EdbResult<()> {
self.url.parse::<Url>().unwrap().join(&self.uuid)?;
Ok(())
}
fn create_key_url(&self, key: &str) -> EdbResult<Url> {
self.url
.parse::<Url>()
.unwrap()
.join(&format!("{}/", self.uuid))
.unwrap()
.join(key)
.chain_err(|| format!("Invalid key: {}", key))
}
pub fn uuid(&self) -> &str {
&self.uuid
}
pub fn token(&self) -> &str {
&self.token
}
pub fn url(&self) -> &str {
&self.url
}
pub fn get(&self, key: &str) -> EdbResult<String> {
self.get_json(key)?
.as_str()
.map(|s| s.to_string())
.ok_or_else(|| "Value was not a string".into())
}
pub fn get_json(&self, key: &str) -> EdbResult<Json> {
let mut s = Vec::new();
self.get_writer(key, &mut s)?;
Ok(serde_json::from_slice(&s)?)
}
pub fn put(&self, key: &str, value: &str) -> EdbResult<u16> {
let new_value = json!(value);
self.put_json(key, new_value)
}
pub fn put_json(&self, key: &str, value: Json) -> EdbResult<u16> {
let body = json!({ "value": value }).to_string();
Ok(self
.client
.post(self.create_key_url(key)?)
.header(CONTENT_TYPE, "application/json")
.header(CONTENT_LENGTH, body.len())
.header("token", &self.token)
.body(body)
.send()?
.status()
.as_u16())
}
pub fn delete(&self, key: &str) -> EdbResult<u16> {
Ok(self
.client
.delete(self.create_key_url(key)?)
.header(CONTENT_TYPE, "application/json")
.header("token", &self.token)
.send()?
.status()
.as_u16())
}
pub fn list(&self) -> EdbResult<HashMap<String, String>> {
self.list_json()?
.drain()
.map(|(s, v)| match v.as_str() {
Some(v_str) => Ok((s, v_str.to_string())),
None => Err(format!("A value was not a string: key: {}, value: {}", s, v).into()),
})
.collect()
}
pub fn list_json(&self) -> EdbResult<HashMap<String, Json>> {
let mut s = Vec::new();
self.list_writer(&mut s)?;
Ok(serde_json::from_slice(&s)?)
}
pub fn clear(&self) -> EdbResult<()> {
let map = self.list_json()?;
self.clear_keys(map.keys().map(|k| &k[..]))?;
Ok(())
}
fn clear_keys<'a, I>(&self, keys: I) -> EdbResult<()>
where
I: Iterator<Item = &'a str>,
{
for key in keys {
self.delete(key)?;
}
Ok(())
}
pub fn get_writer<W>(&self, key: &str, value: &mut W) -> EdbResult<u16>
where
W: Write,
{
let mut resp = self
.client
.get(self.create_key_url(key)?)
.header("token", &self.token)
.send()?;
resp.copy_to(value)?;
Ok(resp.status().as_u16())
}
pub fn list_writer<W>(&self, list: &mut W) -> EdbResult<u16>
where
W: Write,
{
let mut resp = self
.client
.get(self.url.parse::<Url>().unwrap().join(&self.uuid).unwrap())
.header("token", &self.token)
.send()?;
resp.copy_to(list)?;
Ok(resp.status().as_u16())
}
}
impl FromStr for EasyDB {
type Err = EdbError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(toml::from_str(s)?)
}
}