1use crate::errors::AppError;
2use serde::Serialize;
3
4#[derive(Debug, Clone, Copy, clap::ValueEnum, Default)]
5pub enum OutputFormat {
6 #[default]
7 Json,
8 Text,
9 Markdown,
10}
11
12#[derive(Debug, Clone, Copy, clap::ValueEnum, Default)]
13pub enum JsonOutputFormat {
14 #[default]
15 Json,
16}
17
18pub fn emit_json<T: Serialize>(value: &T) -> Result<(), AppError> {
19 let json = serde_json::to_string_pretty(value)?;
20 println!("{json}");
21 Ok(())
22}
23
24pub fn emit_json_compact<T: Serialize>(value: &T) -> Result<(), AppError> {
25 let json = serde_json::to_string(value)?;
26 println!("{json}");
27 Ok(())
28}
29
30pub fn emit_text(msg: &str) {
31 println!("{msg}");
32}
33
34pub fn emit_progress(msg: &str) {
35 tracing::info!(message = msg);
36}
37
38pub fn emit_progress_i18n(en: &str, pt: &str) {
41 use crate::i18n::{current, Language};
42 match current() {
43 Language::English => tracing::info!(message = en),
44 Language::Portugues => tracing::info!(message = pt),
45 }
46}
47
48#[derive(Serialize)]
81pub struct RememberResponse {
82 pub memory_id: i64,
83 pub name: String,
84 pub namespace: String,
85 pub action: String,
86 pub operation: String,
88 pub version: i64,
89 pub entities_persisted: usize,
90 pub relationships_persisted: usize,
91 pub chunks_created: usize,
92 pub merged_into_memory_id: Option<i64>,
93 pub warnings: Vec<String>,
94 pub created_at: i64,
96 pub created_at_iso: String,
98 pub elapsed_ms: u64,
100}
101
102#[derive(Serialize, Clone)]
131pub struct RecallItem {
132 pub memory_id: i64,
133 pub name: String,
134 pub namespace: String,
135 #[serde(rename = "type")]
136 pub memory_type: String,
137 pub description: String,
138 pub snippet: String,
139 pub distance: f32,
140 pub source: String,
141}
142
143#[derive(Serialize)]
144pub struct RecallResponse {
145 pub query: String,
146 pub k: usize,
147 pub direct_matches: Vec<RecallItem>,
148 pub graph_matches: Vec<RecallItem>,
149 pub results: Vec<RecallItem>,
151 pub elapsed_ms: u64,
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use serde::Serialize;
159
160 #[derive(Serialize)]
161 struct Dummy {
162 val: u32,
163 }
164
165 struct NotSerializable;
167 impl Serialize for NotSerializable {
168 fn serialize<S: serde::Serializer>(&self, _: S) -> Result<S::Ok, S::Error> {
169 Err(serde::ser::Error::custom(
170 "falha intencional de serialização",
171 ))
172 }
173 }
174
175 #[test]
176 fn emit_json_retorna_ok_para_valor_valido() {
177 let v = Dummy { val: 42 };
178 assert!(emit_json(&v).is_ok());
179 }
180
181 #[test]
182 fn emit_json_retorna_erro_para_valor_nao_serializavel() {
183 let v = NotSerializable;
184 assert!(emit_json(&v).is_err());
185 }
186
187 #[test]
188 fn emit_json_compact_retorna_ok_para_valor_valido() {
189 let v = Dummy { val: 7 };
190 assert!(emit_json_compact(&v).is_ok());
191 }
192
193 #[test]
194 fn emit_json_compact_retorna_erro_para_valor_nao_serializavel() {
195 let v = NotSerializable;
196 assert!(emit_json_compact(&v).is_err());
197 }
198
199 #[test]
200 fn emit_text_nao_entra_em_panico() {
201 emit_text("mensagem de teste");
202 }
203
204 #[test]
205 fn emit_progress_nao_entra_em_panico() {
206 emit_progress("progresso de teste");
207 }
208
209 #[test]
210 fn remember_response_serializa_corretamente() {
211 let r = RememberResponse {
212 memory_id: 1,
213 name: "teste".to_string(),
214 namespace: "ns".to_string(),
215 action: "created".to_string(),
216 operation: "created".to_string(),
217 version: 1,
218 entities_persisted: 2,
219 relationships_persisted: 3,
220 chunks_created: 4,
221 merged_into_memory_id: None,
222 warnings: vec!["aviso".to_string()],
223 created_at: 1776569715,
224 created_at_iso: "2026-04-19T03:34:15Z".to_string(),
225 elapsed_ms: 123,
226 };
227 let json = serde_json::to_string(&r).unwrap();
228 assert!(json.contains("memory_id"));
229 assert!(json.contains("aviso"));
230 assert!(json.contains("\"namespace\""));
231 assert!(json.contains("\"merged_into_memory_id\""));
232 assert!(json.contains("\"operation\""));
233 assert!(json.contains("\"created_at\""));
234 assert!(json.contains("\"created_at_iso\""));
235 assert!(json.contains("\"elapsed_ms\""));
236 }
237
238 #[test]
239 fn recall_item_serializa_campo_type_renomeado() {
240 let item = RecallItem {
241 memory_id: 10,
242 name: "entidade".to_string(),
243 namespace: "ns".to_string(),
244 memory_type: "entity".to_string(),
245 description: "desc".to_string(),
246 snippet: "trecho".to_string(),
247 distance: 0.5,
248 source: "db".to_string(),
249 };
250 let json = serde_json::to_string(&item).unwrap();
251 assert!(json.contains("\"type\""));
252 assert!(!json.contains("memory_type"));
253 }
254
255 #[test]
256 fn recall_response_serializa_com_listas() {
257 let resp = RecallResponse {
258 query: "busca".to_string(),
259 k: 10,
260 direct_matches: vec![],
261 graph_matches: vec![],
262 results: vec![],
263 elapsed_ms: 42,
264 };
265 let json = serde_json::to_string(&resp).unwrap();
266 assert!(json.contains("direct_matches"));
267 assert!(json.contains("graph_matches"));
268 assert!(json.contains("\"k\":"));
269 assert!(json.contains("\"results\""));
270 assert!(json.contains("\"elapsed_ms\""));
271 }
272
273 #[test]
274 fn output_format_default_eh_json() {
275 let fmt = OutputFormat::default();
276 assert!(matches!(fmt, OutputFormat::Json));
277 }
278
279 #[test]
280 fn output_format_variantes_existem() {
281 let _text = OutputFormat::Text;
282 let _md = OutputFormat::Markdown;
283 let _json = OutputFormat::Json;
284 }
285
286 #[test]
287 fn recall_item_clone_produz_valor_igual() {
288 let item = RecallItem {
289 memory_id: 99,
290 name: "clone".to_string(),
291 namespace: "ns".to_string(),
292 memory_type: "relation".to_string(),
293 description: "d".to_string(),
294 snippet: "s".to_string(),
295 distance: 0.1,
296 source: "src".to_string(),
297 };
298 let cloned = item.clone();
299 assert_eq!(cloned.memory_id, item.memory_id);
300 assert_eq!(cloned.name, item.name);
301 }
302}