1extern crate alloc;
4use alloc::collections::BTreeMap;
5use alloc::string::String;
6use alloc::vec::Vec;
7use core::fmt;
8
9use crate::entity::PropertyValue;
10use crate::{Header, Timestamp};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
16pub enum NoteStatus {
17 #[default]
18 Active,
19 Archived,
20 Deleted,
21}
22
23impl NoteStatus {
24 pub const fn name(self) -> &'static str {
26 match self {
27 Self::Active => "active",
28 Self::Archived => "archived",
29 Self::Deleted => "deleted",
30 }
31 }
32}
33
34impl fmt::Display for NoteStatus {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 f.write_str(self.name())
37 }
38}
39
40impl core::str::FromStr for NoteStatus {
41 type Err = crate::error::UnknownVariant;
42 fn from_str(s: &str) -> Result<Self, Self::Err> {
43 match s.trim().to_ascii_lowercase().as_str() {
44 "active" => Ok(Self::Active),
45 "archived" => Ok(Self::Archived),
46 "deleted" => Ok(Self::Deleted),
47 other => Err(crate::error::UnknownVariant::new(
48 "note_status",
49 other,
50 &["active", "archived", "deleted"],
51 )),
52 }
53 }
54}
55
56#[derive(Clone, Debug)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize))]
63pub struct Note {
64 #[cfg_attr(feature = "serde", serde(flatten))]
66 pub header: Header,
67 pub kind: String,
69 pub status: NoteStatus,
71 pub content: String,
73 pub properties: BTreeMap<String, PropertyValue>,
75 pub tags: Vec<String>,
77 pub salience: Option<f64>,
79 pub decay_factor: Option<f64>,
81 pub expires_at: Option<Timestamp>,
83 pub deleted_at: Option<Timestamp>,
85}
86
87impl Note {
88 pub fn is_valid(&self) -> bool {
93 let salience_ok = self
94 .salience
95 .map(|s| s.is_finite() && (0.0..=1.0).contains(&s))
96 .unwrap_or(true);
97 let decay_ok = self
98 .decay_factor
99 .map(|d| d.is_finite() && d >= 0.0)
100 .unwrap_or(true);
101 salience_ok && decay_ok
102 }
103}
104
105#[cfg(feature = "serde")]
106impl<'de> serde::Deserialize<'de> for Note {
107 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
108 where
109 D: serde::Deserializer<'de>,
110 {
111 #[derive(serde::Deserialize)]
112 struct NoteRaw {
113 #[serde(flatten)]
114 header: Header,
115 kind: String,
116 status: NoteStatus,
117 content: String,
118 properties: BTreeMap<String, PropertyValue>,
119 tags: Vec<String>,
120 salience: Option<f64>,
121 decay_factor: Option<f64>,
122 expires_at: Option<Timestamp>,
123 deleted_at: Option<Timestamp>,
124 }
125
126 let raw = NoteRaw::deserialize(deserializer)?;
127
128 if let Some(s) = raw.salience {
129 if !s.is_finite() {
130 return Err(serde::de::Error::custom(alloc::format!(
131 "Note salience must be finite, got {s}"
132 )));
133 }
134 if !(0.0..=1.0).contains(&s) {
135 return Err(serde::de::Error::custom(alloc::format!(
136 "Note salience must be in [0.0, 1.0], got {s}"
137 )));
138 }
139 }
140 if let Some(d) = raw.decay_factor {
141 if !d.is_finite() {
142 return Err(serde::de::Error::custom(alloc::format!(
143 "Note decay_factor must be finite, got {d}"
144 )));
145 }
146 if d < 0.0 {
147 return Err(serde::de::Error::custom(alloc::format!(
148 "Note decay_factor must be non-negative, got {d}"
149 )));
150 }
151 }
152
153 Ok(Note {
154 header: raw.header,
155 kind: raw.kind,
156 status: raw.status,
157 content: raw.content,
158 properties: raw.properties,
159 tags: raw.tags,
160 salience: raw.salience,
161 decay_factor: raw.decay_factor,
162 expires_at: raw.expires_at,
163 deleted_at: raw.deleted_at,
164 })
165 }
166}
167
168#[cfg(test)]
169#[path = "note_tests.rs"]
170mod tests;