1use std::fmt;
4
5use memvid_core::error::LockedError;
6use memvid_core::MemvidError;
7
8use crate::utils::format_bytes;
9
10#[derive(Debug)]
12pub struct CapacityExceededMessage {
13 pub current: u64,
14 pub limit: u64,
15 pub required: u64,
16}
17
18impl fmt::Display for CapacityExceededMessage {
19 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20 write!(
21 f,
22 "Storage capacity exceeded\n\n\
23 Current usage: {}\n\
24 Capacity limit: {}\n\
25 Required: {}\n\n\
26 To continue, either:\n\
27 1. Upgrade your plan: https://app.memvid.com/plan\n\
28 2. Sync tickets: memvid tickets sync <file> --memory-id <UUID>",
29 format_bytes(self.current),
30 format_bytes(self.limit),
31 format_bytes(self.required)
32 )
33 }
34}
35
36impl std::error::Error for CapacityExceededMessage {}
37
38#[derive(Debug)]
40pub struct ApiKeyRequiredMessage {
41 pub file_size: u64,
42 pub limit: u64,
43}
44
45impl fmt::Display for ApiKeyRequiredMessage {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 write!(
48 f,
49 "API key required for files larger than {}\n\n\
50 File size: {}\n\
51 Free tier limit: {}\n\n\
52 To use this file:\n\
53 1. Get your API key from https://app.memvid.com/api\n\
54 2. Sync tickets: memvid tickets sync <file> --memory-id <UUID>",
55 format_bytes(self.limit),
56 format_bytes(self.file_size),
57 format_bytes(self.limit)
58 )
59 }
60}
61
62impl std::error::Error for ApiKeyRequiredMessage {}
63
64#[derive(Debug)]
66pub struct MemoryAlreadyBoundMessage {
67 pub existing_memory_id: String,
68 pub existing_memory_name: String,
69 pub bound_at: String,
70}
71
72impl fmt::Display for MemoryAlreadyBoundMessage {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 write!(
75 f,
76 "This file is already bound to memory '{}' ({})\n\
77 Bound at: {}\n\n\
78 To rebind to a different memory:\n\
79 memvid unbind <file>\n\
80 memvid tickets sync <file> --memory-id <NEW_UUID>",
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 let core = err
121 .chain()
122 .find_map(|cause| cause.downcast_ref::<MemvidError>());
123 if let Some(core_err) = core {
124 match core_err {
125 MemvidError::CapacityExceeded {
126 current,
127 limit,
128 required,
129 } => {
130 let msg = CapacityExceededMessage {
131 current: *current,
132 limit: *limit,
133 required: *required,
134 }
135 .to_string();
136 return (2, msg);
137 }
138 MemvidError::ApiKeyRequired { file_size, limit } => {
139 let msg = ApiKeyRequiredMessage {
140 file_size: *file_size,
141 limit: *limit,
142 }
143 .to_string();
144 return (2, msg);
145 }
146 MemvidError::Lock(reason) => {
147 return (3, format!("File lock error: {reason}\nHint: check the active writer with `memvid who <file>` or request release with `memvid nudge <file>`"));
148 }
149 MemvidError::Locked(LockedError { message, .. }) => {
150 return (3, format!("File lock error: {message}\nHint: check the active writer with `memvid who <file>` or request release with `memvid nudge <file>`"));
151 }
152 MemvidError::InvalidHeader { reason } => {
153 return (4, format!("{core_err}\nHint: run `memvid doctor <file>` to rebuild indexes and repair the footer.\nDetails: {reason}"));
154 }
155 MemvidError::InvalidToc { reason } => {
156 return (4, format!("{core_err}\nHint: run `memvid doctor <file>` to rebuild indexes and repair the footer.\nDetails: {reason}"));
157 }
158 MemvidError::WalCorruption { reason, .. } => {
159 return (4, format!("{core_err}\nHint: run `memvid doctor <file>` to rebuild indexes and repair the footer.\nDetails: {reason}"));
160 }
161 MemvidError::ManifestWalCorrupted { reason, .. } => {
162 return (4, format!("{core_err}\nHint: run `memvid doctor <file>` to rebuild indexes and repair the footer.\nDetails: {reason}"));
163 }
164 MemvidError::TicketRequired { tier } => {
165 return (2, format!("ticket required for tier {tier:?}. Apply a ticket before mutating this memory."));
166 }
167 _ => {
168 return (1, core_err.to_string());
169 }
170 }
171 }
172
173 (1, err.to_string())
175}