use async_trait;
use reqwest;
use std;
use urlencoding;
pub const STRING_NONE: Option<&str> = None;
pub struct Config {
url: String,
}
#[derive(Debug, Clone)]
pub enum ErrorKind {
HttpError,
NoItemFoundError,
DecodeError,
}
#[derive(Debug, Clone)]
pub struct Error {
pub kind: ErrorKind,
pub message: String,
}
pub struct Database {
config: Config,
}
pub trait Synchronous {
fn set(&self, key: impl AsRef<str>, value: impl AsRef<str>) -> Result<(), Error>;
fn get(&self, key: impl AsRef<str>) -> Result<String, Error>;
fn delete(&self, key: impl AsRef<str>) -> Result<(), Error>;
fn list(&self, prefix: Option<impl AsRef<str>>) -> Result<std::vec::Vec<String>, Error>;
}
#[async_trait::async_trait]
pub trait Asynchronous {
async fn set<T>(&self, key: T, value: T) -> Result<(), Error>
where
T: AsRef<str> + Send;
async fn get<T>(&self, key: T) -> Result<String, Error>
where
T: AsRef<str> + Send;
async fn delete<T>(&self, key: T) -> Result<(), Error>
where
T: AsRef<str> + Send;
async fn list<T>(&self, prefix: Option<T>) -> Result<std::vec::Vec<String>, Error>
where
T: AsRef<str> + Send;
}
impl Config {
pub fn new() -> Result<Config, std::env::VarError> {
let res = std::env::var("REPLIT_DB_URL");
match res {
Ok(r) => return Ok(Config { url: r }),
Err(e) => return Err(e)
}
}
pub fn new_custom_url(url: &str) -> Config {
return Config {
url: url.to_owned(),
};
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
return f.write_str(format!("{:#?}: {}", self.kind, self.message).as_str());
}
}
impl std::error::Error for Error {}
impl Database {
pub fn new(config: Config) -> Self {
return Self { config: config };
}
}
impl Synchronous for Database {
fn set(&self, key: impl AsRef<str>, value: impl AsRef<str>) -> Result<(), Error> {
let client = reqwest::blocking::Client::new();
let payload = format!(
"{}={}",
urlencoding::encode(key.as_ref()),
urlencoding::encode(value.as_ref())
);
let response = client
.post(self.config.url.as_str().to_string())
.body(payload)
.header("Content-Type", "application/x-www-form-urlencoded")
.send();
if response.is_err() {
return Err(Error {
kind: ErrorKind::HttpError,
message: response.unwrap_err().to_string(),
});
}
return Ok(());
}
fn get(&self, key: impl AsRef<str>) -> Result<String, Error> {
let client = reqwest::blocking::Client::new();
let response = client
.get(
self.config.url.as_str().to_string()
+ format!("/{}", urlencoding::encode(key.as_ref())).as_str(),
)
.send();
if response.is_err() {
return Err(Error {
kind: ErrorKind::HttpError,
message: response.unwrap_err().to_string(),
});
}
let response = response.unwrap();
if !response.status().is_success() {
return Err(Error {
kind: ErrorKind::NoItemFoundError,
message: "No items were found on the database.".to_string(),
});
}
let content = response.text().unwrap();
return Ok(content);
}
fn delete(&self, key: impl AsRef<str>) -> Result<(), Error> {
let client = reqwest::blocking::Client::new();
let response = client
.delete(
self.config.url.as_str().to_string()
+ format!("/{}", urlencoding::encode(key.as_ref())).as_str(),
)
.send();
if response.is_err() {
return Err(Error {
kind: ErrorKind::HttpError,
message: response.unwrap_err().to_string(),
});
}
if !response.unwrap().status().is_success() {
return Err(Error {
kind: ErrorKind::NoItemFoundError,
message: "No item with that name were found.".to_string(),
});
}
return Ok(());
}
fn list(&self, prefix: Option<impl AsRef<str>>) -> Result<Vec<String>, Error> {
let prefix2 = match &prefix {
Some(p) => p.as_ref(),
None => "",
};
let client = reqwest::blocking::Client::new();
let response = client
.get(
self.config.url.as_str().to_string()
+ format!("?prefix={}", urlencoding::encode(prefix2)).as_str(),
)
.send();
if response.is_err() {
return Err(Error {
kind: ErrorKind::HttpError,
message: response.unwrap_err().to_string(),
});
}
let content = response.unwrap().text();
if content.is_err() {
return Err(Error {
kind: ErrorKind::DecodeError,
message: content.unwrap_err().to_string(),
});
}
let mut variables: std::vec::Vec<String> = std::vec::Vec::new();
for v in content.unwrap().lines() {
variables.push(v.to_string());
}
return Ok(variables);
}
}
#[async_trait::async_trait]
impl Asynchronous for Database {
async fn set<T>(&self, key: T, value: T) -> Result<(), Error>
where
T: AsRef<str> + Send,
{
let client = reqwest::Client::new();
let payload = format!(
"{}={}",
urlencoding::encode(key.as_ref()),
urlencoding::encode(value.as_ref())
);
let response = client
.post(self.config.url.as_str().to_string())
.header("Content-Type", "application/x-www-form-urlencoded")
.body(payload)
.send()
.await;
if response.is_err() {
return Err(Error {
kind: ErrorKind::HttpError,
message: response.unwrap_err().to_string(),
});
}
return Ok(());
}
async fn get<T>(&self, key: T) -> Result<String, Error>
where
T: AsRef<str> + Send,
{
let client = reqwest::Client::new();
let response = client
.get(
self.config.url.as_str().to_string()
+ format!("/{}", urlencoding::encode(key.as_ref())).as_str(),
)
.send()
.await;
if response.is_err() {
return Err(Error {
kind: ErrorKind::HttpError,
message: response.unwrap_err().to_string(),
});
}
let response = response.unwrap();
if !response.status().is_success() {
return Err(Error {
kind: ErrorKind::NoItemFoundError,
message: "No items were found on the database.".to_string(),
});
}
let content = response.text().await.unwrap();
return Ok(content);
}
async fn delete<T>(&self, key: T) -> Result<(), Error>
where
T: AsRef<str> + Send,
{
let client = reqwest::Client::new();
let response = client
.delete(
self.config.url.as_str().to_string()
+ format!("/{}", urlencoding::encode(key.as_ref())).as_str(),
)
.send()
.await;
if response.is_err() {
return Err(Error {
kind: ErrorKind::HttpError,
message: response.unwrap_err().to_string(),
});
}
if !response.unwrap().status().is_success() {
return Err(Error {
kind: ErrorKind::NoItemFoundError,
message: "No item with that name were found.".to_string(),
});
}
return Ok(());
}
async fn list<T>(&self, prefix: Option<T>) -> Result<Vec<String>, Error>
where
T: AsRef<str> + Send,
{
let prefix2 = match &prefix {
Some(p) => p.as_ref(),
None => "",
};
let client = reqwest::Client::new();
let response = client
.get(
self.config.url.as_str().to_string()
+ format!("?prefix={}", urlencoding::encode(prefix2)).as_str(),
)
.send()
.await;
if response.is_err() {
return Err(Error {
kind: ErrorKind::HttpError,
message: response.unwrap_err().to_string(),
});
}
let content = response.unwrap().text().await;
if content.is_err() {
return Err(Error {
kind: ErrorKind::DecodeError,
message: content.unwrap_err().to_string(),
});
}
let mut variables: std::vec::Vec<String> = std::vec::Vec::new();
for v in content.unwrap().lines() {
variables.push(v.to_string());
}
return Ok(variables);
}
}