use std::ascii::AsciiExt;
use crate::error::{CustomError, Error};
use chrono::{DateTime, Utc};
use hyper::StatusCode;
use log::debug;
use regex::Regex;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Deserialize, PartialEq)]
pub struct ItemData {
pub id: String,
pub title: String,
pub vault: VaultID,
pub category: Option<String>,
pub urls: Option<Vec<UrlObject>>,
pub favorite: Option<bool>,
pub tags: Option<Vec<String>>,
pub state: Option<String>,
pub created_at: Option<DateTime<Utc>>,
pub updated_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
pub struct VaultID {
pub id: String,
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
pub struct UrlObject {
pub url: String,
pub primary: bool,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct FieldObject {
pub section: Option<SectionID>,
pub purpose: Option<String>,
pub r#type: Option<String>,
pub value: Option<String>,
pub generate: Option<bool>,
pub label: Option<String>,
}
#[derive(Debug)]
pub enum FieldType {
Concealed,
}
impl Into<String> for FieldType {
fn into(self) -> String {
let value = match self {
Self::Concealed => "CONCEALED",
};
value.to_string()
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct SectionObject {
pub id: String,
pub label: Option<String>,
}
impl SectionObject {
pub fn new(id: &str, label: &str) -> Self {
Self {
id: id.to_string(),
label: Some(label.to_string()),
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct SectionID {
pub id: String,
}
impl SectionID {
pub fn new() -> Self {
Self {
id: Uuid::new_v4().to_string(),
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct FullItem {
pub title: String,
pub vault: VaultID,
pub category: Option<String>,
pub urls: Option<Vec<UrlObject>>,
pub favorite: Option<bool>,
pub tags: Option<Vec<String>>,
pub fields: Vec<FieldObject>,
pub sections: Vec<SectionObject>,
}
pub trait DefaultItem {
fn build(&self) -> Result<FullItem, Box<dyn std::error::Error + Send + Sync>>;
}
pub trait LoginItem {
fn title(self, username: &str) -> Self;
fn username(self, username: &str) -> Self;
fn password(self, password: &str) -> Self;
fn build(&self) -> Result<FullItem, Box<dyn std::error::Error + Send + Sync>>;
}
pub trait ApiCredentialItem {
fn api_key(self, key: &str, title: &str) -> Self;
fn build(&self) -> Result<FullItem, Box<dyn std::error::Error + Send + Sync>>;
}
#[derive(Debug)]
pub struct ItemBuilder {
pub title: String,
pub vault: VaultID,
pub category: Option<String>,
pub urls: Option<Vec<UrlObject>>,
pub favorite: Option<bool>,
pub tags: Option<Vec<String>>,
pub fields: Vec<FieldObject>,
pub sections: Vec<SectionObject>,
}
#[derive(Debug)]
pub enum ItemCategory {
ApiCredential,
Login,
Password,
}
impl ItemCategory {
fn default() -> Self {
Self::ApiCredential
}
}
impl Into<String> for ItemCategory {
fn into(self) -> String {
let value = match self {
Self::ApiCredential => "API_CREDENTIAL",
Self::Login => "LOGIN",
Self::Password => "PASSWORD",
};
value.to_string()
}
}
impl ItemBuilder {
pub fn new(vault_id: &str, category: ItemCategory) -> Self {
let vault = VaultID {
id: vault_id.to_string(),
};
Self {
vault,
title: String::default(),
category: Some(category.into()),
favorite: Some(false),
urls: None,
tags: None,
fields: vec![],
sections: vec![],
}
}
pub(crate) fn add_otp(mut self, secret: &str) -> Self {
let section = SectionID::new();
let section_obj = SectionObject::new(§ion.id, "OTP");
self.sections.push(section_obj);
let field_object = FieldObject {
section: Some(section),
label: None,
purpose: None,
r#type: Some("OTP".to_string()),
generate: Some(true),
value: Some(secret.to_string()),
};
self.fields.push(field_object);
self
}
}
impl DefaultItem for ItemBuilder {
fn build(&self) -> Result<FullItem, Box<dyn std::error::Error + Send + Sync>> {
Ok(FullItem {
title: self.title.clone(),
category: self.category.clone(),
favorite: self.favorite,
fields: self.fields.clone(),
sections: self.sections.clone(),
tags: self.tags.clone(),
urls: self.urls.clone(),
vault: self.vault.clone(),
})
}
}
impl LoginItem for ItemBuilder {
fn title(mut self, title: &str) -> Self {
self.title = title.to_string();
self
}
fn username(mut self, username: &str) -> Self {
let field: FieldObject = FieldObject {
value: Some(username.to_string()),
purpose: Some("USERNAME".to_string()),
generate: None,
label: None,
r#type: None,
section: None,
};
self.fields.push(field);
self
}
fn password(mut self, password: &str) -> Self {
let field: FieldObject = FieldObject {
value: password.is_empty().then(|| password.to_string()),
purpose: Some("PASSWORD".to_string()),
generate: password.is_empty().then(|| true),
label: None,
r#type: None,
section: None,
};
self.fields.push(field);
self
}
fn build(&self) -> Result<FullItem, Box<dyn std::error::Error + Send + Sync>> {
if self.title.is_empty() {
return Err(Box::new(CustomError::new("Title is required")));
}
Ok(FullItem {
title: self.title.clone(),
category: self.category.clone(),
favorite: self.favorite,
fields: self.fields.clone(),
sections: self.sections.clone(),
tags: self.tags.clone(),
urls: self.urls.clone(),
vault: self.vault.clone(),
})
}
}
impl ApiCredentialItem for ItemBuilder {
fn api_key(mut self, key: &str, title: &str) -> Self {
let section = SectionID::new();
let section_obj = SectionObject::new(§ion.id, "API Key");
self.sections.push(section_obj);
let field_object = FieldObject {
section: Some(section),
label: None,
purpose: None,
r#type: Some(FieldType::Concealed.into()),
generate: Some(key.is_empty()),
value: Some(key.to_string()),
};
self.fields.push(field_object);
self.title = title.to_string();
self
}
fn build(&self) -> Result<FullItem, Box<dyn std::error::Error + Send + Sync>> {
Ok(FullItem {
title: self.title.clone(),
category: self.category.clone(),
favorite: self.favorite,
fields: self.fields.clone(),
sections: self.sections.clone(),
tags: self.tags.clone(),
urls: self.urls.clone(),
vault: self.vault.clone(),
})
}
}