Skip to main content

couchbase_core/mgmtx/
error.rs

1/*
2 *
3 *  * Copyright (c) 2025 Couchbase, Inc.
4 *  *
5 *  * Licensed under the Apache License, Version 2.0 (the "License");
6 *  * you may not use this file except in compliance with the License.
7 *  * You may obtain a copy of the License at
8 *  *
9 *  *    http://www.apache.org/licenses/LICENSE-2.0
10 *  *
11 *  * Unless required by applicable law or agreed to in writing, software
12 *  * distributed under the License is distributed on an "AS IS" BASIS,
13 *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  * See the License for the specific language governing permissions and
15 *  * limitations under the License.
16 *
17 */
18
19use crate::httpx;
20use crate::tracingcomponent::MetricsName;
21use http::{Method, StatusCode};
22use std::error::Error as StdError;
23use std::fmt::{Display, Formatter};
24
25pub type Result<T> = std::result::Result<T, Error>;
26
27#[derive(Debug, PartialEq)]
28pub struct Error {
29    inner: Box<ErrorImpl>,
30}
31
32impl Display for Error {
33    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34        write!(f, "{}", self.inner.kind)
35    }
36}
37
38impl StdError for Error {}
39
40impl Error {
41    pub(crate) fn new_message_error(msg: impl Into<String>) -> Self {
42        Self {
43            inner: Box::new(ErrorImpl {
44                kind: ErrorKind::Message(msg.into()),
45            }),
46        }
47    }
48
49    pub(crate) fn new_invalid_argument_error(
50        msg: impl Into<String>,
51        arg: impl Into<Option<String>>,
52    ) -> Self {
53        Self {
54            inner: Box::new(ErrorImpl {
55                kind: ErrorKind::InvalidArgument {
56                    msg: msg.into(),
57                    arg: arg.into(),
58                },
59            }),
60        }
61    }
62
63    pub fn kind(&self) -> &ErrorKind {
64        &self.inner.kind
65    }
66}
67
68#[derive(Debug)]
69pub struct ErrorImpl {
70    pub kind: ErrorKind,
71}
72
73impl PartialEq for ErrorImpl {
74    fn eq(&self, other: &Self) -> bool {
75        self.kind == other.kind
76    }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
80#[non_exhaustive]
81pub enum ErrorKind {
82    Server(ServerError),
83    Resource(ResourceError),
84    #[non_exhaustive]
85    InvalidArgument {
86        msg: String,
87        arg: Option<String>,
88    },
89    Message(String),
90    Http(httpx::error::Error),
91}
92
93impl Display for ErrorKind {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        match self {
96            ErrorKind::Server(e) => write!(f, "server error: {e}"),
97            ErrorKind::Resource(e) => write!(f, "resource error: {e}"),
98            ErrorKind::InvalidArgument { msg, arg } => {
99                if let Some(arg) = arg {
100                    write!(f, "invalid argument: {msg}: {arg}")
101                } else {
102                    write!(f, "invalid argument: {msg}")
103                }
104            }
105            ErrorKind::Message(msg) => write!(f, "{msg}"),
106            ErrorKind::Http(e) => write!(f, "http error: {e}"),
107        }
108    }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct ServerError {
113    status_code: StatusCode,
114    url: String,
115    body: String,
116    method: Method,
117    path: String,
118    kind: ServerErrorKind,
119}
120
121impl StdError for ServerError {}
122
123impl Display for ServerError {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        write!(
126            f,
127            "server error: method: {}, path: {} status code: {}, body: {}, kind: {}",
128            self.method, self.path, self.status_code, self.body, self.kind
129        )
130    }
131}
132
133impl ServerError {
134    pub(crate) fn new(
135        status_code: StatusCode,
136        url: String,
137        method: Method,
138        path: String,
139        body: String,
140        kind: ServerErrorKind,
141    ) -> Self {
142        Self {
143            status_code,
144            url,
145            method,
146            path,
147            body,
148            kind,
149        }
150    }
151
152    pub fn kind(&self) -> &ServerErrorKind {
153        &self.kind
154    }
155
156    pub fn status_code(&self) -> StatusCode {
157        self.status_code
158    }
159
160    pub fn body(&self) -> &str {
161        &self.body
162    }
163
164    pub fn path(&self) -> &str {
165        &self.path
166    }
167
168    pub fn method(&self) -> &Method {
169        &self.method
170    }
171
172    pub fn url(&self) -> &str {
173        &self.url
174    }
175}
176
177#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
178#[non_exhaustive]
179pub enum ServerErrorKind {
180    AccessDenied,
181    UnsupportedFeature { feature: String },
182    ScopeExists,
183    ScopeNotFound,
184    CollectionExists,
185    CollectionNotFound,
186    BucketExists,
187    BucketNotFound,
188    FlushDisabled,
189    ServerInvalidArg { arg: String, reason: String },
190    SampleAlreadyLoaded,
191    InvalidSampleBucket,
192    BucketUuidMismatch,
193    UserNotFound,
194    GroupNotFound,
195    OperationDelayed,
196    Unknown,
197}
198
199impl Display for ServerErrorKind {
200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201        match self {
202            ServerErrorKind::AccessDenied => write!(f, "access denied"),
203            ServerErrorKind::UnsupportedFeature { feature } => {
204                write!(f, "unsupported feature {feature}")
205            }
206            ServerErrorKind::ScopeExists => write!(f, "scope exists"),
207            ServerErrorKind::ScopeNotFound => write!(f, "scope not found"),
208            ServerErrorKind::CollectionExists => write!(f, "collection exists"),
209            ServerErrorKind::CollectionNotFound => write!(f, "collection not found"),
210            ServerErrorKind::BucketExists => write!(f, "bucket exists"),
211            ServerErrorKind::BucketNotFound => write!(f, "bucket not found"),
212            ServerErrorKind::FlushDisabled => write!(f, "flush disabled"),
213            ServerErrorKind::ServerInvalidArg { arg, reason } => {
214                write!(f, "server invalid argument: {arg} - {reason}")
215            }
216            ServerErrorKind::BucketUuidMismatch => write!(f, "bucket uuid mismatch"),
217            ServerErrorKind::UserNotFound => write!(f, "user not found"),
218            ServerErrorKind::GroupNotFound => write!(f, "group not found"),
219            ServerErrorKind::OperationDelayed => {
220                write!(f, "operation was delayed, but will continue")
221            }
222            ServerErrorKind::SampleAlreadyLoaded => write!(f, "sample already loaded"),
223            ServerErrorKind::InvalidSampleBucket => write!(f, "invalid sample bucket"),
224            ServerErrorKind::Unknown => write!(f, "unknown error"),
225        }
226    }
227}
228
229#[derive(Debug, Clone, PartialEq, Eq)]
230pub struct ResourceError {
231    cause: ServerError,
232    scope_name: String,
233    collection_name: String,
234    bucket_name: String,
235}
236
237impl StdError for ResourceError {}
238
239impl Display for ResourceError {
240    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241        write!(
242            f,
243            "resource error: scope: {}, collection: {}, bucket: {}, cause: {}",
244            self.scope_name, self.collection_name, self.bucket_name, self.cause
245        )
246    }
247}
248
249impl ResourceError {
250    pub(crate) fn new(
251        cause: ServerError,
252        bucket_name: impl Into<String>,
253        scope_name: impl Into<String>,
254        collection_name: impl Into<String>,
255    ) -> Self {
256        Self {
257            cause,
258            bucket_name: bucket_name.into(),
259            scope_name: scope_name.into(),
260            collection_name: collection_name.into(),
261        }
262    }
263
264    pub fn cause(&self) -> &ServerError {
265        &self.cause
266    }
267
268    pub fn bucket_name(&self) -> &str {
269        &self.bucket_name
270    }
271
272    pub fn scope_name(&self) -> &str {
273        &self.scope_name
274    }
275
276    pub fn collection_name(&self) -> &str {
277        &self.collection_name
278    }
279}
280
281impl<E> From<E> for Error
282where
283    ErrorKind: From<E>,
284{
285    fn from(err: E) -> Self {
286        Self {
287            inner: Box::new(ErrorImpl {
288                kind: ErrorKind::from(err),
289            }),
290        }
291    }
292}
293
294impl From<ServerError> for Error {
295    fn from(value: ServerError) -> Self {
296        Self {
297            inner: Box::new(ErrorImpl {
298                kind: ErrorKind::Server(value),
299            }),
300        }
301    }
302}
303
304impl From<ResourceError> for Error {
305    fn from(value: ResourceError) -> Self {
306        Self {
307            inner: Box::new(ErrorImpl {
308                kind: ErrorKind::Resource(value),
309            }),
310        }
311    }
312}
313
314impl From<httpx::error::Error> for Error {
315    fn from(value: httpx::error::Error) -> Self {
316        Self {
317            inner: Box::new(ErrorImpl {
318                kind: ErrorKind::Http(value),
319            }),
320        }
321    }
322}
323
324impl MetricsName for Error {
325    fn metrics_name(&self) -> &'static str {
326        match self.kind() {
327            ErrorKind::Server(e) => e.kind().metrics_name(),
328            ErrorKind::Resource(e) => e.cause().kind().metrics_name(),
329            ErrorKind::InvalidArgument { .. } => "mgmtx.InvalidArgument",
330            ErrorKind::Message(_) => "mgmtx._OTHER",
331            ErrorKind::Http(e) => e.metrics_name(),
332        }
333    }
334}
335
336impl MetricsName for ServerErrorKind {
337    fn metrics_name(&self) -> &'static str {
338        match self {
339            ServerErrorKind::AccessDenied => "mgmtx.AccessDenied",
340            ServerErrorKind::UnsupportedFeature { .. } => "mgmtx.UnsupportedFeature",
341            ServerErrorKind::ScopeExists => "mgmtx.ScopeExists",
342            ServerErrorKind::ScopeNotFound => "mgmtx.ScopeNotFound",
343            ServerErrorKind::CollectionExists => "mgmtx.CollectionExists",
344            ServerErrorKind::CollectionNotFound => "mgmtx.CollectionNotFound",
345            ServerErrorKind::BucketExists => "mgmtx.BucketExists",
346            ServerErrorKind::BucketNotFound => "mgmtx.BucketNotFound",
347            ServerErrorKind::FlushDisabled => "mgmtx.FlushDisabled",
348            ServerErrorKind::ServerInvalidArg { .. } => "mgmtx.ServerInvalidArg",
349            ServerErrorKind::SampleAlreadyLoaded => "mgmtx.SampleAlreadyLoaded",
350            ServerErrorKind::InvalidSampleBucket => "mgmtx.InvalidSampleBucket",
351            ServerErrorKind::BucketUuidMismatch => "mgmtx.BucketUuidMismatch",
352            ServerErrorKind::UserNotFound => "mgmtx.UserNotFound",
353            ServerErrorKind::GroupNotFound => "mgmtx.GroupNotFound",
354            ServerErrorKind::OperationDelayed => "mgmtx.OperationDelayed",
355            ServerErrorKind::Unknown => "mgmtx._OTHER",
356        }
357    }
358}