1use std::fmt;
4
5use memvid_core::error::LockedError;
6#[cfg(feature = "encryption")]
7use memvid_core::encryption::EncryptionError;
8use memvid_core::MemvidError;
9
10use crate::utils::format_bytes;
11
12#[derive(Debug)]
14pub struct CapacityExceededMessage {
15 pub current: u64,
16 pub limit: u64,
17 pub required: u64,
18}
19
20impl fmt::Display for CapacityExceededMessage {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 write!(
23 f,
24 "Storage capacity exceeded\n\n\
25 Current usage: {}\n\
26 Capacity limit: {}\n\
27 Required: {}\n\n\
28 To continue, either:\n\
29 1. Upgrade your plan: https://app.memvid.com/plan\n\
30 2. Sync tickets: memvid tickets sync <file> --memory-id <UUID>",
31 format_bytes(self.current),
32 format_bytes(self.limit),
33 format_bytes(self.required)
34 )
35 }
36}
37
38impl std::error::Error for CapacityExceededMessage {}
39
40#[derive(Debug)]
42pub struct ApiKeyRequiredMessage {
43 pub file_size: u64,
44 pub limit: u64,
45}
46
47impl fmt::Display for ApiKeyRequiredMessage {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 write!(
50 f,
51 "API key required for files larger than {}\n\n\
52 File size: {}\n\
53 Free tier limit: {}\n\n\
54 To use this file:\n\
55 1. Get your API key from https://app.memvid.com/api\n\
56 2. Sync tickets: memvid tickets sync <file> --memory-id <UUID>",
57 format_bytes(self.limit),
58 format_bytes(self.file_size),
59 format_bytes(self.limit)
60 )
61 }
62}
63
64impl std::error::Error for ApiKeyRequiredMessage {}
65
66#[derive(Debug)]
68pub struct MemoryAlreadyBoundMessage {
69 pub existing_memory_id: String,
70 pub existing_memory_name: String,
71 pub bound_at: String,
72}
73
74impl fmt::Display for MemoryAlreadyBoundMessage {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write!(
77 f,
78 "This file is already bound to memory '{}' ({})\n\
79 Bound at: {}\n\n\
80 To rebind to a different memory:\n\
81 memvid unbind <file>\n\
82 memvid tickets sync <file> --memory-id <NEW_UUID>",
83 self.existing_memory_name, self.existing_memory_id, self.bound_at
84 )
85 }
86}
87
88impl std::error::Error for MemoryAlreadyBoundMessage {}
89
90#[derive(Debug)]
92pub struct DuplicateUriError {
93 uri: String,
94}
95
96impl DuplicateUriError {
97 pub fn new<S: Into<String>>(uri: S) -> Self {
98 Self { uri: uri.into() }
99 }
100}
101
102impl fmt::Display for DuplicateUriError {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 write!(
105 f,
106 "frame with URI '{}' already exists. Use --update-existing to replace it or --allow-duplicate to keep both entries.",
107 self.uri
108 )
109 }
110}
111
112impl std::error::Error for DuplicateUriError {}
113
114pub fn render_error(err: &anyhow::Error) -> (i32, String) {
116 if let Some(cap) = err.downcast_ref::<CapacityExceededMessage>() {
118 return (2, cap.to_string());
119 }
120
121 #[cfg(feature = "encryption")]
122 {
123 let enc = err
124 .chain()
125 .find_map(|cause| cause.downcast_ref::<EncryptionError>());
126 if let Some(enc_err) = enc {
127 let message = match enc_err {
128 EncryptionError::InvalidMagic { .. } => format!(
129 "{enc_err}\nHint: is this an encrypted .mv2e capsule and not a plain .mv2 file?"
130 ),
131 _ => enc_err.to_string(),
132 };
133 return (5, message);
134 }
135 }
136
137 let core = err
139 .chain()
140 .find_map(|cause| cause.downcast_ref::<MemvidError>());
141 if let Some(core_err) = core {
142 match core_err {
143 MemvidError::CapacityExceeded {
144 current,
145 limit,
146 required,
147 } => {
148 let msg = CapacityExceededMessage {
149 current: *current,
150 limit: *limit,
151 required: *required,
152 }
153 .to_string();
154 return (2, msg);
155 }
156 MemvidError::ApiKeyRequired { file_size, limit } => {
157 let msg = ApiKeyRequiredMessage {
158 file_size: *file_size,
159 limit: *limit,
160 }
161 .to_string();
162 return (2, msg);
163 }
164 MemvidError::Lock(reason) => {
165 return (3, format!("File lock error: {reason}\nHint: check the active writer with `memvid who <file>` or request release with `memvid nudge <file>`"));
166 }
167 MemvidError::Locked(LockedError { message, .. }) => {
168 return (3, format!("File lock error: {message}\nHint: check the active writer with `memvid who <file>` or request release with `memvid nudge <file>`"));
169 }
170 MemvidError::InvalidHeader { reason } => {
171 return (4, format!("{core_err}\nHint: run `memvid doctor <file>` to rebuild indexes and repair the footer.\nDetails: {reason}"));
172 }
173 MemvidError::EncryptedFile { .. } => {
174 return (5, core_err.to_string());
175 }
176 MemvidError::InvalidToc { reason } => {
177 return (4, format!("{core_err}\nHint: run `memvid doctor <file>` to rebuild indexes and repair the footer.\nDetails: {reason}"));
178 }
179 MemvidError::WalCorruption { reason, .. } => {
180 return (4, format!("{core_err}\nHint: run `memvid doctor <file>` to rebuild indexes and repair the footer.\nDetails: {reason}"));
181 }
182 MemvidError::ManifestWalCorrupted { reason, .. } => {
183 return (4, format!("{core_err}\nHint: run `memvid doctor <file>` to rebuild indexes and repair the footer.\nDetails: {reason}"));
184 }
185 MemvidError::TicketRequired { tier } => {
186 return (2, format!("ticket required for tier {tier:?}. Apply a ticket before mutating this memory."));
187 }
188 _ => {
189 return (1, core_err.to_string());
190 }
191 }
192 }
193
194 (1, err.to_string())
196}