1use std::fmt;
4
5#[cfg(feature = "encryption")]
6use memvid_core::encryption::EncryptionError;
7use memvid_core::error::LockedError;
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 Each memory can only be bound to one file.\n\
81 To use more memories, upgrade your plan at https://memvid.com/dashboard/plan",
82 self.existing_memory_name, self.existing_memory_id, self.bound_at
83 )
84 }
85}
86
87impl std::error::Error for MemoryAlreadyBoundMessage {}
88
89#[derive(Debug)]
91pub struct DuplicateUriError {
92 uri: String,
93}
94
95impl DuplicateUriError {
96 pub fn new<S: Into<String>>(uri: S) -> Self {
97 Self { uri: uri.into() }
98 }
99}
100
101impl fmt::Display for DuplicateUriError {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 write!(
104 f,
105 "frame with URI '{}' already exists. Use --update-existing to replace it or --allow-duplicate to keep both entries.",
106 self.uri
107 )
108 }
109}
110
111impl std::error::Error for DuplicateUriError {}
112
113pub fn render_error(err: &anyhow::Error) -> (i32, String) {
115 if let Some(cap) = err.downcast_ref::<CapacityExceededMessage>() {
117 return (2, cap.to_string());
118 }
119
120 #[cfg(feature = "encryption")]
121 {
122 let enc = err
123 .chain()
124 .find_map(|cause| cause.downcast_ref::<EncryptionError>());
125 if let Some(enc_err) = enc {
126 let message = match enc_err {
127 EncryptionError::InvalidMagic { .. } => format!(
128 "{enc_err}\nHint: is this an encrypted .mv2e capsule and not a plain .mv2 file?"
129 ),
130 _ => enc_err.to_string(),
131 };
132 return (5, message);
133 }
134 }
135
136 let core = err
138 .chain()
139 .find_map(|cause| cause.downcast_ref::<MemvidError>());
140 if let Some(core_err) = core {
141 match core_err {
142 MemvidError::CapacityExceeded {
143 current,
144 limit,
145 required,
146 } => {
147 let msg = CapacityExceededMessage {
148 current: *current,
149 limit: *limit,
150 required: *required,
151 }
152 .to_string();
153 return (2, msg);
154 }
155 MemvidError::ApiKeyRequired { file_size, limit } => {
156 let msg = ApiKeyRequiredMessage {
157 file_size: *file_size,
158 limit: *limit,
159 }
160 .to_string();
161 return (2, msg);
162 }
163 MemvidError::Lock(reason) => {
164 return (3, format!("File lock error: {reason}\nHint: check the active writer with `memvid who <file>` or request release with `memvid nudge <file>`"));
165 }
166 MemvidError::Locked(LockedError { message, .. }) => {
167 return (3, format!("File lock error: {message}\nHint: check the active writer with `memvid who <file>` or request release with `memvid nudge <file>`"));
168 }
169 MemvidError::InvalidHeader { reason } => {
170 return (4, format!("{core_err}\nHint: run `memvid doctor <file>` to rebuild indexes and repair the footer.\nDetails: {reason}"));
171 }
172 MemvidError::EncryptedFile { .. } => {
173 return (5, core_err.to_string());
174 }
175 MemvidError::InvalidToc { reason } => {
176 return (4, format!("{core_err}\nHint: run `memvid doctor <file>` to rebuild indexes and repair the footer.\nDetails: {reason}"));
177 }
178 MemvidError::WalCorruption { reason, .. } => {
179 return (4, format!("{core_err}\nHint: run `memvid doctor <file>` to rebuild indexes and repair the footer.\nDetails: {reason}"));
180 }
181 MemvidError::ManifestWalCorrupted { reason, .. } => {
182 return (4, format!("{core_err}\nHint: run `memvid doctor <file>` to rebuild indexes and repair the footer.\nDetails: {reason}"));
183 }
184 MemvidError::TicketRequired { tier } => {
185 return (2, format!("ticket required for tier {tier:?}. Apply a ticket before mutating this memory."));
186 }
187 _ => {
188 return (1, core_err.to_string());
189 }
190 }
191 }
192
193 (1, err.to_string())
195}