1use chrono::Utc;
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5use crate::error::MemoryError;
6
7pub const HEADLESS_POINT_ID: &str = "headless";
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum MemoryPointKind {
12 Headless,
13 Root,
14 Point,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "snake_case")]
19pub enum MemoryPointStatus {
20 Active,
21 Deactivated,
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct MemoryPoint {
26 pub point_id: String,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub parent_id: Option<String>,
29 pub kind: MemoryPointKind,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub storage: Option<String>,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub types: Option<String>,
34 pub status: MemoryPointStatus,
35 pub created_at: String,
36 pub updated_at: String,
37 #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
38 pub attributes: serde_json::Map<String, serde_json::Value>,
39}
40
41impl MemoryPoint {
42 pub fn headless() -> Self {
43 let now = current_time();
44 Self {
45 point_id: HEADLESS_POINT_ID.to_string(),
46 parent_id: None,
47 kind: MemoryPointKind::Headless,
48 storage: None,
49 types: None,
50 status: MemoryPointStatus::Active,
51 created_at: now.clone(),
52 updated_at: now,
53 attributes: serde_json::Map::new(),
54 }
55 }
56
57 pub fn new_root(
58 storage: impl Into<String>,
59 types: impl Into<String>,
60 ) -> Result<Self, MemoryError> {
61 Self::new_with_parent(
62 Some(HEADLESS_POINT_ID.to_string()),
63 MemoryPointKind::Root,
64 storage,
65 types,
66 )
67 }
68
69 pub fn new_child(
70 parent_id: impl Into<String>,
71 storage: impl Into<String>,
72 types: impl Into<String>,
73 ) -> Result<Self, MemoryError> {
74 Self::new_with_parent(
75 Some(parent_id.into()),
76 MemoryPointKind::Point,
77 storage,
78 types,
79 )
80 }
81
82 pub fn update_types(&mut self, new_types: impl Into<String>) -> Result<(), MemoryError> {
83 self.ensure_not_headless("update_types")?;
84 let new_types = normalize_required(new_types.into(), "types")?;
85 self.types = Some(new_types);
86 self.touch();
87 Ok(())
88 }
89
90 pub fn update_storage(&mut self, new_storage: impl Into<String>) -> Result<(), MemoryError> {
91 self.ensure_not_headless("update_storage")?;
92 let new_storage = normalize_required(new_storage.into(), "storage")?;
93 self.storage = Some(new_storage);
94 self.touch();
95 Ok(())
96 }
97
98 pub fn set_parent(&mut self, parent_id: impl Into<String>) -> Result<(), MemoryError> {
99 self.ensure_not_headless("set_parent")?;
100 let parent_id = normalize_required(parent_id.into(), "parent_id")?;
101 self.parent_id = Some(parent_id);
102 self.kind = if self.parent_id.as_deref() == Some(HEADLESS_POINT_ID) {
103 MemoryPointKind::Root
104 } else {
105 MemoryPointKind::Point
106 };
107 self.touch();
108 Ok(())
109 }
110
111 pub fn deactivate(&mut self) -> Result<(), MemoryError> {
112 self.ensure_not_headless("deactivate")?;
113 self.status = MemoryPointStatus::Deactivated;
114 self.touch();
115 Ok(())
116 }
117
118 pub fn reactivate(&mut self) -> Result<(), MemoryError> {
119 self.ensure_not_headless("reactivate")?;
120 self.status = MemoryPointStatus::Active;
121 self.touch();
122 Ok(())
123 }
124
125 pub fn set_attribute(
126 &mut self,
127 key: impl Into<String>,
128 value: serde_json::Value,
129 ) -> Result<(), MemoryError> {
130 let key = normalize_required(key.into(), "attribute_key")?;
131 self.attributes.insert(key, value);
132 self.touch();
133 Ok(())
134 }
135
136 pub fn remove_attribute(&mut self, key: &str) -> Option<serde_json::Value> {
137 let removed = self.attributes.remove(key);
138 if removed.is_some() {
139 self.touch();
140 }
141 removed
142 }
143
144 pub fn is_headless(&self) -> bool {
145 self.kind == MemoryPointKind::Headless
146 }
147
148 pub fn is_active(&self) -> bool {
149 self.status == MemoryPointStatus::Active
150 }
151
152 pub fn validate(&self) -> Result<(), MemoryError> {
153 if self.is_headless() {
154 if self.point_id != HEADLESS_POINT_ID
155 || self.parent_id.is_some()
156 || self.storage.is_some()
157 || self.types.is_some()
158 {
159 return Err(MemoryError::InvalidHeadlessPoint);
160 }
161 return Ok(());
162 }
163
164 if self.parent_id.as_deref().unwrap_or("").trim().is_empty() {
165 return Err(MemoryError::MissingParent);
166 }
167 if self.storage.as_deref().unwrap_or("").trim().is_empty() {
168 return Err(MemoryError::EmptyField { field: "storage" });
169 }
170 if self.types.as_deref().unwrap_or("").trim().is_empty() {
171 return Err(MemoryError::EmptyField { field: "types" });
172 }
173
174 Ok(())
175 }
176
177 fn new_with_parent(
178 parent_id: Option<String>,
179 kind: MemoryPointKind,
180 storage: impl Into<String>,
181 types: impl Into<String>,
182 ) -> Result<Self, MemoryError> {
183 let storage = normalize_required(storage.into(), "storage")?;
184 let types = normalize_required(types.into(), "types")?;
185 let parent_id = parent_id
186 .map(|id| normalize_required(id, "parent_id"))
187 .transpose()?
188 .ok_or(MemoryError::MissingParent)?;
189 let now = current_time();
190
191 let point = Self {
192 point_id: Uuid::new_v4().to_string(),
193 parent_id: Some(parent_id),
194 kind,
195 storage: Some(storage),
196 types: Some(types),
197 status: MemoryPointStatus::Active,
198 created_at: now.clone(),
199 updated_at: now,
200 attributes: serde_json::Map::new(),
201 };
202 point.validate()?;
203 Ok(point)
204 }
205
206 fn ensure_not_headless(&self, operation: &'static str) -> Result<(), MemoryError> {
207 if self.is_headless() {
208 return Err(MemoryError::HeadlessModification { operation });
209 }
210 Ok(())
211 }
212
213 fn touch(&mut self) {
214 self.updated_at = current_time();
215 }
216}
217
218fn normalize_required(value: String, field: &'static str) -> Result<String, MemoryError> {
219 let value = value.trim().to_string();
220 if value.is_empty() {
221 return Err(MemoryError::EmptyField { field });
222 }
223 Ok(value)
224}
225
226fn current_time() -> String {
227 Utc::now().to_rfc3339()
228}