Skip to main content

otherone_memory/
types.rs

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}