use crate::api::RequestFailure;
use crate::icons::Icon;
use crate::node;
use crate::properties::{Property, PropertyFormat};
use crate::types::{Type, TypeLayout};
use serde_json::{Map, Number, Value};
async fn response_to_object(response: reqwest::Response) -> Object {
let json_input = response.text().await.unwrap();
let json: serde_json::Value = serde_json::from_str(json_input.as_ref()).unwrap();
let o = json["object"].clone();
json_to_object(o)
}
pub(crate) async fn response_to_list(response: reqwest::Response) -> ListOfObjects {
let json_input = response.text().await.unwrap();
let json: serde_json::Value = serde_json::from_str(json_input.as_ref()).unwrap();
let received = json["data"].as_array().unwrap();
let mut data: Vec<Object> = Vec::new();
for o in 0..received.len() {
let obj = json_to_object(received[o].clone());
data.push(obj);
}
let has_more: bool = json["pagination"]["has_more"].as_bool().unwrap();
let total = json["pagination"]["total"].as_u64().unwrap() as usize;
let offset = json["pagination"]["offset"].as_u64().unwrap() as usize;
let limit = json["pagination"]["limit"].as_u64().unwrap() as usize;
let next_offset = (offset as usize) + received.len();
ListOfObjects {
objects: data,
has_more,
next_offset,
offset,
limit,
total,
}
}
fn json_to_object(json: Value) -> Object {
let id = json["id"].as_str().unwrap().to_string();
let archived = json["archived"].as_bool().unwrap();
let icon = Icon::from_json(json["icon"].clone());
let json_properties = json["properties"].as_array().unwrap();
let mut properties: Vec<Property> = Vec::new();
for p in 0..json_properties.len() {
let format = json_properties[p]["format"].as_str().unwrap().to_string();
let key = json_properties[p]["key"].as_str().unwrap().to_string();
let name = json_properties[p]["name"].as_str().unwrap().to_string();
let id = json_properties[p]["id"].as_str().unwrap().to_string();
let object = match json_properties[p]["object"].as_str() {
Some(s) => s.to_string(),
None => "".to_string(),
};
properties.push(Property {
format: PropertyFormat::from_str(&format),
key,
name,
id,
object,
});
}
let layout = json["layout"].as_str().unwrap().to_string();
let markdown: String = match json["markdown"].as_str() {
Some(m) => m.to_string(),
None => "".to_string(),
};
let name = json["name"].as_str().unwrap().to_string();
let object = json["object"].as_str().unwrap().to_string();
let snippet = json["snippet"].as_str().unwrap().to_string();
let space_id = json["space_id"].as_str().unwrap().to_string();
let type_obj = match json["type"]["id"].as_str() {
Some(_) => Some(Type::from_json(json["type"].clone())),
None => None,
};
Object {
archived,
icon,
id,
layout: TypeLayout::from_str(&layout),
markdown,
name,
object,
properties,
snippet,
space_id,
type_obj,
}
}
#[derive(Debug)]
pub struct Object {
pub archived: bool,
pub icon: Option<Icon>,
pub id: String,
pub layout: TypeLayout,
pub markdown: String,
pub name: String,
pub object: String,
pub properties: Vec<Property>,
pub snippet: String,
pub space_id: String,
pub type_obj: Option<Type>,
}
impl Object {
pub fn from_json(json: Value) -> Self {
let id = json["id"].as_str().unwrap().to_string();
let archived = json["archived"].as_bool().unwrap();
let icon = Icon::from_json(json["icon"].clone());
let json_properties = json["properties"].as_array().unwrap();
let mut properties: Vec<Property> = Vec::new();
for p in 0..json_properties.len() {
properties.push(Property::from_json(json_properties[p].clone()));
}
let layout = json["layout"].as_str().unwrap().to_string();
let markdown: String = match json["markdown"].as_str() {
Some(m) => m.to_string(),
None => "".to_string(),
};
let name = json["name"].as_str().unwrap().to_string();
let object = json["object"].as_str().unwrap().to_string();
let snippet = json["snippet"].as_str().unwrap().to_string();
let space_id = json["space_id"].as_str().unwrap().to_string();
let type_obj = match json["type"]["id"].as_str() {
Some(_) => Some(Type::from_json(json["type"].clone())),
None => None,
};
Object {
archived,
icon,
id,
layout: TypeLayout::from_str(&layout),
markdown,
name,
object,
properties,
snippet,
space_id,
type_obj,
}
}
}
#[derive(Debug)]
pub struct ListOfObjects {
pub objects: Vec<Object>,
pub has_more: bool,
pub next_offset: usize,
pub offset: usize,
pub limit: usize,
pub total: usize,
}
#[derive(Debug)]
pub struct ListObjectsRequest<'a> {
api_key: &'a str,
offset: u32,
limit: u32,
server: &'a str,
space_id: &'a str,
}
impl<'a> ListObjectsRequest<'a> {
pub fn new(api_key: &'a str, server: &'a str) -> Self {
ListObjectsRequest {
api_key,
offset: 0,
limit: 100,
server,
space_id: "",
}
}
pub fn space_id(mut self, space_id: &'a str) -> Self {
self.space_id = space_id;
self
}
pub fn offset(mut self, offset: u32) -> Self {
self.offset = offset;
self
}
pub fn limit(mut self, limit: u32) -> Self {
self.limit = limit;
self
}
pub async fn send(&self) -> Result<ListOfObjects, RequestFailure> {
let endpoint = format!("/v1/spaces/{}/objects", self.space_id);
match node::get(self.api_key, self.server, &endpoint).await {
Ok(r) => Ok(response_to_list(r).await),
Err(e) => Err(RequestFailure::reqwest_error(e)),
}
}
}
#[derive(Debug)]
pub struct CreateObjectRequest<'a> {
api_key: &'a str,
body: Value,
icon: Value,
name: Value,
properties: Vec<Value>,
server: &'a str,
space_id: &'a str,
template_id: Value,
type_key: Value,
}
impl<'a> CreateObjectRequest<'a> {
pub fn new(api_key: &'a str, server: &'a str) -> Self {
CreateObjectRequest {
api_key,
body: Value::String("".to_string()),
icon: Value::Null,
name: Value::String("".to_string()),
properties: Vec::new(),
server,
space_id: "",
template_id: Value::String("".to_string()),
type_key: Value::String("".to_string()),
}
}
pub fn space_id(mut self, space_id: &'a str) -> Self {
self.space_id = space_id;
self
}
pub fn body(mut self, body: &str) -> Self {
self.body = Value::String(body.to_string());
self
}
pub fn name(mut self, name: &str) -> Self {
self.name = Value::String(name.to_string());
self
}
pub fn icon(mut self, icon: &Icon) -> Self {
self.icon = icon.to_json();
self
}
pub fn template_id(mut self, template_id: &str) -> Self {
self.template_id = Value::String(template_id.to_string());
self
}
pub fn type_key(mut self, type_key: &str) -> Self {
self.type_key = Value::String(type_key.to_string());
self
}
pub fn property(mut self, ptype: PropertyFormat, pkey: &str, pvalue: &str) -> Self {
let mut values = Map::new();
values.insert("key".to_string(), Value::String(pkey.to_string()));
match ptype {
PropertyFormat::Checkbox => {
if pvalue.to_ascii_lowercase() == "true" {
values.insert("checkbox".to_string(), Value::Bool(true));
} else {
values.insert("checkbox".to_string(), Value::Bool(false));
}
}
PropertyFormat::Date => {
values.insert("date".to_string(), Value::String(pvalue.to_string()));
}
PropertyFormat::Email => {
values.insert("email".to_string(), Value::String(pvalue.to_string()));
}
PropertyFormat::Files => {
let mut files: Vec<Value> = Vec::new();
files.push(Value::String(pvalue.to_string()));
values.insert("files".to_string(), Value::Array(files));
}
PropertyFormat::Number => {
let num: Number = match pvalue.parse::<i128>() {
Ok(num) => Number::from_i128(num).unwrap(),
Err(_) => Number::from_i128(-1).unwrap(),
};
values.insert("number".to_string(), Value::Number(num));
}
PropertyFormat::MultiSelect => {
values.insert(
"multi_select".to_string(),
Value::String(pvalue.to_string()),
);
}
PropertyFormat::Objects => {
let mut objects: Vec<Value> = Vec::new();
objects.push(Value::String(pvalue.to_string()));
values.insert("objects".to_string(), Value::Array(objects));
}
PropertyFormat::Phone => {
values.insert("phone".to_string(), Value::String(pvalue.to_string()));
}
PropertyFormat::Select => {
values.insert("select".to_string(), Value::String(pvalue.to_string()));
}
PropertyFormat::Text => {
values.insert("text".to_string(), Value::String(pvalue.to_string()));
}
PropertyFormat::Url => {
values.insert("url".to_string(), Value::String(pvalue.to_string()));
}
}
self.properties.push(Value::Object(values));
self
}
fn to_string(&self) -> String {
let mut values = Map::new();
values.insert("icon".to_string(), self.icon.clone());
values.insert("body".to_string(), self.body.clone());
values.insert("name".to_string(), self.name.clone());
values.insert("template_id".to_string(), self.template_id.clone());
values.insert("type_key".to_string(), self.type_key.clone());
let mut properties: Vec<Value> = Vec::new();
for p in self.properties.iter() {
properties.push(p.clone());
}
values.insert("properties".to_string(), Value::Array(properties));
Value::Object(values).to_string()
}
pub async fn send(&self) -> Result<Object, RequestFailure> {
let endpoint = format!("/v1/spaces/{}/objects", self.space_id);
println!("{}", self.to_string());
let body = self.to_string();
match node::post(self.api_key, self.server, &endpoint, &body).await {
Ok(response) => {
if response.status() == http::StatusCode::CREATED {
return Ok(response_to_object(response).await);
} else {
let possible_status: Vec<http::StatusCode> = Vec::from([
http::StatusCode::UNAUTHORIZED,
http::StatusCode::BAD_REQUEST,
http::StatusCode::TOO_MANY_REQUESTS,
http::StatusCode::INTERNAL_SERVER_ERROR,
]);
return Err(RequestFailure::api_error(response, possible_status).await);
}
}
Err(e) => Err(RequestFailure::reqwest_error(e)),
}
}
}
#[derive(Debug)]
pub struct DeleteObjectRequest<'a> {
api_key: &'a str,
server: &'a str,
space_id: &'a str,
object_id: &'a str,
}
impl<'a> DeleteObjectRequest<'a> {
pub fn new(api_key: &'a str, server: &'a str) -> Self {
DeleteObjectRequest {
api_key,
server,
space_id: "",
object_id: "",
}
}
pub fn space_id(mut self, space_id: &'a str) -> Self {
self.space_id = space_id;
self
}
pub fn object_id(mut self, object_id: &'a str) -> Self {
self.object_id = object_id;
self
}
pub async fn send(&self) -> Result<Object, RequestFailure> {
let endpoint = format!("/v1/spaces/{}/objects/{}", self.space_id, self.object_id);
match node::delete(self.api_key, self.server, &endpoint).await {
Ok(response) => {
if response.status() == http::StatusCode::OK {
return Ok(response_to_object(response).await);
} else {
let possible_status: Vec<http::StatusCode> = Vec::from([
http::StatusCode::UNAUTHORIZED,
http::StatusCode::NOT_FOUND,
http::StatusCode::GONE,
http::StatusCode::INTERNAL_SERVER_ERROR,
]);
return Err(RequestFailure::api_error(response, possible_status).await);
}
}
Err(e) => Err(RequestFailure::reqwest_error(e)),
}
}
}
#[derive(Debug)]
pub struct GetObjectRequest<'a> {
api_key: &'a str,
server: &'a str,
space_id: &'a str,
object_id: &'a str,
}
impl<'a> GetObjectRequest<'a> {
pub fn new(api_key: &'a str, server: &'a str) -> Self {
GetObjectRequest {
api_key,
server,
space_id: "",
object_id: "",
}
}
pub fn space_id(mut self, space_id: &'a str) -> Self {
self.space_id = space_id;
self
}
pub fn object_id(mut self, object_id: &'a str) -> Self {
self.object_id = object_id;
self
}
pub async fn send(&self) -> Result<Object, RequestFailure> {
let endpoint = format!("/v1/spaces/{}/objects/{}", self.space_id, self.object_id);
match node::get(self.api_key, self.server, &endpoint).await {
Ok(response) => {
if response.status() == http::StatusCode::OK {
return Ok(response_to_object(response).await);
} else {
let possible_status: Vec<http::StatusCode> = Vec::from([
http::StatusCode::UNAUTHORIZED,
http::StatusCode::NOT_FOUND,
http::StatusCode::GONE,
http::StatusCode::INTERNAL_SERVER_ERROR,
]);
return Err(RequestFailure::api_error(response, possible_status).await);
}
}
Err(e) => Err(RequestFailure::reqwest_error(e)),
}
}
}
#[derive(Debug)]
pub struct UpdateObjectRequest<'a> {
api_key: &'a str,
icon: Value,
markdown: Value,
name: Value,
object_id: &'a str,
properties: Vec<Value>,
server: &'a str,
space_id: &'a str,
type_key: Value,
}
impl<'a> UpdateObjectRequest<'a> {
pub fn new(api_key: &'a str, server: &'a str) -> Self {
UpdateObjectRequest {
api_key,
icon: Value::Null,
markdown: Value::String("".to_string()),
name: Value::String("".to_string()),
object_id: "",
properties: Vec::new(),
server,
space_id: "",
type_key: Value::String("".to_string()),
}
}
pub fn space_id(mut self, space_id: &'a str) -> Self {
self.space_id = space_id;
self
}
pub fn object_id(mut self, object_id: &'a str) -> Self {
self.object_id = object_id;
self
}
pub fn markdown(mut self, markdown: &str) -> Self {
self.markdown = Value::String(markdown.to_string());
self
}
pub fn name(mut self, name: &str) -> Self {
self.name = Value::String(name.to_string());
self
}
pub fn icon(mut self, icon: &Icon) -> Self {
self.icon = icon.to_json();
self
}
pub fn type_key(mut self, type_key: &str) -> Self {
self.type_key = Value::String(type_key.to_string());
self
}
pub fn property(mut self, ptype: PropertyFormat, pkey: &str, pvalue: &str) -> Self {
let mut values = Map::new();
values.insert("key".to_string(), Value::String(pkey.to_string()));
match ptype {
PropertyFormat::Checkbox => {
if pvalue.to_ascii_lowercase() == "true" {
values.insert("checkbox".to_string(), Value::Bool(true));
} else {
values.insert("checkbox".to_string(), Value::Bool(false));
}
}
PropertyFormat::Date => {
values.insert("date".to_string(), Value::String(pvalue.to_string()));
}
PropertyFormat::Email => {
values.insert("email".to_string(), Value::String(pvalue.to_string()));
}
PropertyFormat::Files => {
let mut files: Vec<Value> = Vec::new();
files.push(Value::String(pvalue.to_string()));
values.insert("files".to_string(), Value::Array(files));
}
PropertyFormat::Number => {
let num: Number = match pvalue.parse::<i128>() {
Ok(num) => Number::from_i128(num).unwrap(),
Err(_) => Number::from_i128(-1).unwrap(),
};
values.insert("number".to_string(), Value::Number(num));
}
PropertyFormat::MultiSelect => {
values.insert(
"multi_select".to_string(),
Value::String(pvalue.to_string()),
);
}
PropertyFormat::Objects => {
let mut objects: Vec<Value> = Vec::new();
objects.push(Value::String(pvalue.to_string()));
values.insert("objects".to_string(), Value::Array(objects));
}
PropertyFormat::Phone => {
values.insert("phone".to_string(), Value::String(pvalue.to_string()));
}
PropertyFormat::Select => {
values.insert("select".to_string(), Value::String(pvalue.to_string()));
}
PropertyFormat::Text => {
values.insert("text".to_string(), Value::String(pvalue.to_string()));
}
PropertyFormat::Url => {
values.insert("url".to_string(), Value::String(pvalue.to_string()));
}
}
self.properties.push(Value::Object(values));
self
}
fn to_string(&self) -> String {
let mut values = Map::new();
values.insert("icon".to_string(), self.icon.clone());
values.insert("markdown".to_string(), self.markdown.clone());
values.insert("name".to_string(), self.name.clone());
values.insert("type_key".to_string(), self.type_key.clone());
let mut properties: Vec<Value> = Vec::new();
for p in self.properties.iter() {
properties.push(p.clone());
}
values.insert("properties".to_string(), Value::Array(properties));
Value::Object(values).to_string()
}
pub async fn send(&self) -> Result<Object, RequestFailure> {
let endpoint = format!("/v1/spaces/{}/objects/{}", self.space_id, self.object_id);
println!("{}", self.to_string());
let body = self.to_string();
match node::patch(self.api_key, self.server, &endpoint, &body).await {
Ok(response) => {
if response.status() == http::StatusCode::CREATED {
return Ok(response_to_object(response).await);
} else {
let possible_status: Vec<http::StatusCode> = Vec::from([
http::StatusCode::UNAUTHORIZED,
http::StatusCode::BAD_REQUEST,
http::StatusCode::TOO_MANY_REQUESTS,
http::StatusCode::INTERNAL_SERVER_ERROR,
]);
return Err(RequestFailure::api_error(response, possible_status).await);
}
}
Err(e) => Err(RequestFailure::reqwest_error(e)),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
pub fn test_create_object_request() {
let request = CreateObjectRequest::new("secret_api_key", "server_url")
.name("name_of_object")
.body("body_of_object")
.template_id("id_of_template")
.property(PropertyFormat::Checkbox, "completed", "true")
.property(PropertyFormat::Files, "attached", "bringyourownfiles")
.property(
PropertyFormat::Objects,
"things_to_have",
"bringyourownobjects",
)
.body("I changed my mind, here is the text!")
.type_key("test_object");
let internals = format!("{:#?}", request);
assert!(internals.contains("api_key: \"secret_api_key"));
assert!(internals.contains("server: \"server_url"));
println!("{}", internals);
}
#[test]
pub fn test_list_objects_request() {
let request = ListObjectsRequest::new("secret_api_key", "server_url")
.space_id("lost_in_space")
.offset(0)
.limit(42);
let internals = format!("{:#?}", request);
assert!(internals.contains("api_key: \"secret_api_key"));
assert!(internals.contains("offset: 0"));
assert!(internals.contains("limit: 42"));
assert!(internals.contains("space_id: \"lost_in_space"));
assert!(internals.contains("server: \"server_url"));
println!("{}", internals);
}
}