1use cognee_database::IngestDb;
9use cognee_delete::{DeleteMode, DeleteRequest, DeleteResult, DeleteScope, DeleteService};
10use uuid::Uuid;
11
12use super::error::ApiError;
13
14#[derive(Debug, Clone)]
19pub enum DatasetRef {
20 Name(String),
23 Id(Uuid),
26}
27
28impl DatasetRef {
29 pub async fn to_name(
38 &self,
39 owner_id: Uuid,
40 db: Option<&dyn IngestDb>,
41 ) -> Result<String, ApiError> {
42 match self {
43 DatasetRef::Name(name) => Ok(name.clone()),
44 DatasetRef::Id(id) => {
45 let db = db.ok_or_else(|| {
46 ApiError::InvalidArgument(
47 "db connection required to resolve dataset UUID".to_string(),
48 )
49 })?;
50 let dataset = db.get_dataset(*id).await.map_err(|e| {
51 ApiError::InvalidArgument(format!("Dataset {id} lookup failed: {e}"))
52 })?;
53 let dataset = dataset
54 .ok_or_else(|| ApiError::InvalidArgument(format!("Dataset {id} not found")))?;
55 if dataset.owner_id != owner_id {
56 return Err(ApiError::InvalidArgument(format!(
57 "Dataset {id} not owned by the requesting user"
58 )));
59 }
60 Ok(dataset.name)
61 }
62 }
63 }
64}
65
66#[derive(Debug, Clone)]
68pub enum ForgetTarget {
69 Item { data_id: Uuid, dataset: DatasetRef },
71 Dataset { dataset: DatasetRef },
73 All,
75 DatasetMemoryOnly { dataset: DatasetRef },
79 DataItemMemoryOnly { data_id: Uuid, dataset: DatasetRef },
81}
82
83#[derive(Debug, Clone)]
85pub struct ForgetResult {
86 pub target: String,
87 pub delete_result: DeleteResult,
88}
89
90pub async fn forget(
104 target: ForgetTarget,
105 owner_id: Uuid,
106 delete_service: &DeleteService,
107 db: Option<&dyn IngestDb>,
108) -> Result<ForgetResult, ApiError> {
109 #[cfg(feature = "telemetry")]
112 {
113 let (target_label, dataset_dbg, data_id_dbg) = match &target {
114 ForgetTarget::Item { data_id, dataset } => {
115 ("data_item", format!("{dataset:?}"), data_id.to_string())
116 }
117 ForgetTarget::Dataset { dataset } => ("dataset", format!("{dataset:?}"), String::new()),
118 ForgetTarget::All => ("everything", String::new(), String::new()),
119 ForgetTarget::DatasetMemoryOnly { dataset } => {
120 ("dataset_memory_only", format!("{dataset:?}"), String::new())
121 }
122 ForgetTarget::DataItemMemoryOnly { data_id, dataset } => (
123 "data_item_memory_only",
124 format!("{dataset:?}"),
125 data_id.to_string(),
126 ),
127 };
128 cognee_telemetry::send_telemetry(
129 "cognee.forget",
130 owner_id,
131 Some(serde_json::json!({
132 "target": target_label,
133 "dataset": dataset_dbg,
134 "data_id": data_id_dbg,
135 "cognee_version": env!("CARGO_PKG_VERSION"),
136 })),
137 );
138 }
139
140 let (scope, memory_only, label) = match target {
141 ForgetTarget::Item { data_id, dataset } => {
142 let dataset_name = dataset.to_name(owner_id, db).await?;
143 let scope = DeleteScope::Data {
144 owner_id,
145 data_id,
146 dataset_name: Some(dataset_name),
147 delete_dataset_if_empty: false,
148 };
149 (scope, false, format!("item:{data_id}"))
150 }
151 ForgetTarget::Dataset { dataset } => {
152 let dataset_name = dataset.to_name(owner_id, db).await?;
153 if let Some(db) = db {
156 let _dataset = db
157 .get_dataset_by_name(&dataset_name, owner_id, None)
158 .await
159 .map_err(|e| {
160 ApiError::InvalidArgument(format!(
161 "Dataset '{dataset_name}' not found: {e}"
162 ))
163 })?;
164 }
165 let scope = DeleteScope::Dataset {
166 owner_id,
167 dataset_name: dataset_name.clone(),
168 };
169 (scope, false, format!("dataset:{dataset_name}"))
170 }
171 ForgetTarget::All => {
172 let scope = DeleteScope::User { owner_id };
173 (scope, false, "all".to_string())
174 }
175 ForgetTarget::DatasetMemoryOnly { dataset } => {
176 let dataset_name = dataset.to_name(owner_id, db).await?;
177 let scope = DeleteScope::Dataset {
178 owner_id,
179 dataset_name: dataset_name.clone(),
180 };
181 (scope, true, format!("dataset_memory_only:{dataset_name}"))
182 }
183 ForgetTarget::DataItemMemoryOnly { data_id, dataset } => {
184 let dataset_name = dataset.to_name(owner_id, db).await?;
185 let scope = DeleteScope::Data {
186 owner_id,
187 data_id,
188 dataset_name: Some(dataset_name),
189 delete_dataset_if_empty: false,
190 };
191 (scope, true, format!("data_item_memory_only:{data_id}"))
192 }
193 };
194
195 let request = build_delete_request(scope, memory_only);
196
197 let delete_result = delete_service.execute(&request).await?;
198
199 Ok(ForgetResult {
200 target: label,
201 delete_result,
202 })
203}
204
205fn build_delete_request(scope: DeleteScope, memory_only: bool) -> DeleteRequest {
210 DeleteRequest {
211 scope,
212 mode: DeleteMode::Soft,
215 memory_only,
216 }
217}
218
219#[cfg(test)]
220#[allow(
221 clippy::unwrap_used,
222 clippy::expect_used,
223 reason = "test code — panics are acceptable failures"
224)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn forget_target_debug_format() {
230 let target = ForgetTarget::All;
231 let debug_str = format!("{target:?}");
232 assert!(debug_str.contains("All"));
233 }
234
235 #[test]
236 fn forget_target_item_holds_fields() {
237 let id = Uuid::new_v4();
238 let target = ForgetTarget::Item {
239 data_id: id,
240 dataset: DatasetRef::Name("test_ds".to_string()),
241 };
242 match target {
243 ForgetTarget::Item { data_id, dataset } => {
244 assert_eq!(data_id, id);
245 match dataset {
246 DatasetRef::Name(name) => assert_eq!(name, "test_ds"),
247 _ => panic!("expected Name variant"),
248 }
249 }
250 _ => panic!("expected Item variant"),
251 }
252 }
253
254 #[tokio::test]
257 async fn dataset_ref_name_passthrough() {
258 let owner_id = Uuid::new_v4();
261 let dref = DatasetRef::Name("my_ds".to_string());
262 let resolved = dref.to_name(owner_id, None).await.expect("passthrough ok");
263 assert_eq!(resolved, "my_ds");
264 }
265
266 #[tokio::test]
267 async fn dataset_ref_id_requires_db() {
268 let owner_id = Uuid::new_v4();
270 let dref = DatasetRef::Id(Uuid::new_v4());
271 let result = dref.to_name(owner_id, None).await;
272 match result {
273 Err(ApiError::InvalidArgument(msg)) => {
274 assert!(
275 msg.contains("db connection required"),
276 "unexpected msg: {msg}"
277 );
278 }
279 other => panic!("expected InvalidArgument, got {other:?}"),
280 }
281 }
282
283 #[test]
284 fn forget_target_dataset_uuid_variant_debug() {
285 let id = Uuid::new_v4();
287 let target = ForgetTarget::Dataset {
288 dataset: DatasetRef::Id(id),
289 };
290 let dbg = format!("{target:?}");
291 assert!(dbg.contains("Dataset"), "debug: {dbg}");
292 assert!(dbg.contains("Id"), "debug: {dbg}");
293 }
294}