connect_1password/models/
item.rs1use std::ascii::AsciiExt;
2
3use crate::error::{CustomError, Error};
4use chrono::{DateTime, Utc};
5use hyper::StatusCode;
6use log::debug;
7use regex::Regex;
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10
11#[derive(Debug, Deserialize, PartialEq)]
13pub struct ItemData {
14 pub id: String,
16 pub title: String,
18 pub vault: VaultID,
20 pub category: Option<String>,
22 pub urls: Option<Vec<UrlObject>>,
24 pub favorite: Option<bool>,
26 pub tags: Option<Vec<String>>,
28 pub state: Option<String>,
30 pub created_at: Option<DateTime<Utc>>,
32 pub updated_at: Option<DateTime<Utc>>,
34}
35
36#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
38pub struct VaultID {
39 pub id: String,
41}
42
43#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
45pub struct UrlObject {
46 pub url: String,
48 pub primary: bool,
50}
51
52#[derive(Debug, Deserialize, Serialize, Clone)]
54pub struct FieldObject {
55 pub section: Option<SectionID>,
57 pub purpose: Option<String>,
59 pub r#type: Option<String>,
61 pub value: Option<String>,
63 pub generate: Option<bool>,
65 pub label: Option<String>,
69}
70
71#[derive(Debug)]
73pub enum FieldType {
74 Concealed,
76}
77
78impl Into<String> for FieldType {
79 fn into(self) -> String {
80 let value = match self {
81 Self::Concealed => "CONCEALED",
82 };
83
84 value.to_string()
85 }
86}
87
88#[derive(Debug, Deserialize, Serialize, Clone)]
90pub struct SectionObject {
91 pub id: String,
93 pub label: Option<String>,
95}
96
97impl SectionObject {
98 pub fn new(id: &str, label: &str) -> Self {
100 Self {
101 id: id.to_string(),
102 label: Some(label.to_string()),
103 }
104 }
105}
106
107#[derive(Debug, Deserialize, Serialize, Clone)]
109pub struct SectionID {
110 pub id: String,
112}
113
114impl SectionID {
115 pub fn new() -> Self {
117 Self {
118 id: Uuid::new_v4().to_string(),
119 }
120 }
121}
122
123#[derive(Debug, Deserialize, Serialize)]
125pub struct FullItem {
126 pub title: String,
128 pub vault: VaultID,
130 pub category: Option<String>,
132 pub urls: Option<Vec<UrlObject>>,
134 pub favorite: Option<bool>,
136 pub tags: Option<Vec<String>>,
138 pub fields: Vec<FieldObject>,
140 pub sections: Vec<SectionObject>,
142}
143
144pub trait DefaultItem {
146 fn build(&self) -> Result<FullItem, Box<dyn std::error::Error + Send + Sync>>;
148}
149
150pub trait LoginItem {
152 fn title(self, username: &str) -> Self;
154 fn username(self, username: &str) -> Self;
156 fn password(self, password: &str) -> Self;
158 fn build(&self) -> Result<FullItem, Box<dyn std::error::Error + Send + Sync>>;
160}
161
162pub trait ApiCredentialItem {
164 fn api_key(self, key: &str, title: &str) -> Self;
166 fn build(&self) -> Result<FullItem, Box<dyn std::error::Error + Send + Sync>>;
168}
169
170#[derive(Debug)]
172pub struct ItemBuilder {
173 pub title: String,
175 pub vault: VaultID,
177 pub category: Option<String>,
179 pub urls: Option<Vec<UrlObject>>,
181 pub favorite: Option<bool>,
183 pub tags: Option<Vec<String>>,
185 pub fields: Vec<FieldObject>,
187 pub sections: Vec<SectionObject>,
189}
190
191#[derive(Debug)]
193pub enum ItemCategory {
194 ApiCredential,
196 Login,
198 Password,
200}
201
202impl ItemCategory {
203 fn default() -> Self {
204 Self::ApiCredential
205 }
206}
207
208impl Into<String> for ItemCategory {
209 fn into(self) -> String {
210 let value = match self {
211 Self::ApiCredential => "API_CREDENTIAL",
212 Self::Login => "LOGIN",
213 Self::Password => "PASSWORD",
214 };
215
216 value.to_string()
217 }
218}
219
220impl ItemBuilder {
221 pub fn new(vault_id: &str, category: ItemCategory) -> Self {
223 let vault = VaultID {
224 id: vault_id.to_string(),
225 };
226
227 Self {
228 vault,
229 title: String::default(),
230 category: Some(category.into()),
231 favorite: Some(false),
232 urls: None,
233 tags: None,
234 fields: vec![],
235 sections: vec![],
236 }
237 }
238
239 pub(crate) fn add_otp(mut self, secret: &str) -> Self {
241 let section = SectionID::new();
242 let section_obj = SectionObject::new(§ion.id, "OTP");
243
244 self.sections.push(section_obj);
245
246 let field_object = FieldObject {
247 section: Some(section),
248 label: None,
249 purpose: None,
250 r#type: Some("OTP".to_string()),
251 generate: Some(true),
252 value: Some(secret.to_string()),
253 };
254 self.fields.push(field_object);
255
256 self
257 }
258}
259
260impl DefaultItem for ItemBuilder {
261 fn build(&self) -> Result<FullItem, Box<dyn std::error::Error + Send + Sync>> {
262 Ok(FullItem {
263 title: self.title.clone(),
264 category: self.category.clone(),
265 favorite: self.favorite,
266 fields: self.fields.clone(),
267 sections: self.sections.clone(),
268 tags: self.tags.clone(),
269 urls: self.urls.clone(),
270 vault: self.vault.clone(),
271 })
272 }
273}
274
275impl LoginItem for ItemBuilder {
276 fn title(mut self, title: &str) -> Self {
277 self.title = title.to_string();
278 self
279 }
280
281 fn username(mut self, username: &str) -> Self {
282 let field: FieldObject = FieldObject {
283 value: Some(username.to_string()),
284 purpose: Some("USERNAME".to_string()),
285 generate: None,
286 label: None,
287 r#type: None,
288 section: None,
289 };
290
291 self.fields.push(field);
292 self
293 }
294
295 fn password(mut self, password: &str) -> Self {
296 let field: FieldObject = FieldObject {
297 value: password.is_empty().then(|| password.to_string()),
298 purpose: Some("PASSWORD".to_string()),
299 generate: password.is_empty().then(|| true),
300 label: None,
301 r#type: None,
302 section: None,
303 };
304
305 self.fields.push(field);
306 self
307 }
308
309 fn build(&self) -> Result<FullItem, Box<dyn std::error::Error + Send + Sync>> {
310 if self.title.is_empty() {
311 return Err(Box::new(CustomError::new("Title is required")));
312 }
313
314 Ok(FullItem {
315 title: self.title.clone(),
316 category: self.category.clone(),
317 favorite: self.favorite,
318 fields: self.fields.clone(),
319 sections: self.sections.clone(),
320 tags: self.tags.clone(),
321 urls: self.urls.clone(),
322 vault: self.vault.clone(),
323 })
324 }
325}
326
327impl ApiCredentialItem for ItemBuilder {
328 fn api_key(mut self, key: &str, title: &str) -> Self {
329 let section = SectionID::new();
330 let section_obj = SectionObject::new(§ion.id, "API Key");
331
332 self.sections.push(section_obj);
333
334 let field_object = FieldObject {
335 section: Some(section),
336 label: None,
337 purpose: None,
338 r#type: Some(FieldType::Concealed.into()),
339 generate: Some(key.is_empty()),
340 value: Some(key.to_string()),
341 };
342 self.fields.push(field_object);
343 self.title = title.to_string();
344
345 self
346 }
347
348 fn build(&self) -> Result<FullItem, Box<dyn std::error::Error + Send + Sync>> {
349 Ok(FullItem {
350 title: self.title.clone(),
351 category: self.category.clone(),
352 favorite: self.favorite,
353 fields: self.fields.clone(),
354 sections: self.sections.clone(),
355 tags: self.tags.clone(),
356 urls: self.urls.clone(),
357 vault: self.vault.clone(),
358 })
359 }
360}