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