Skip to main content

lexe_api_core/
error.rs

1//! Serializable api error types and error kinds returned by various lexe
2//! services.
3
4// Deny suspicious match names that are probably non-existent variants.
5#![deny(non_snake_case)]
6
7use std::{error::Error, fmt};
8
9use anyhow::anyhow;
10use http::status::StatusCode;
11use lexe_common::api::{
12    MegaId, auth,
13    user::{NodePk, UserPk},
14};
15#[cfg(any(test, feature = "test-utils"))]
16use lexe_common::test_utils::arbitrary;
17use lexe_enclave::enclave::{self, Measurement};
18#[cfg(any(test, feature = "test-utils"))]
19use proptest_derive::Arbitrary;
20use serde::{Deserialize, Serialize};
21use thiserror::Error;
22#[cfg(feature = "axum")]
23use tracing::{error, warn};
24
25#[cfg(feature = "axum")]
26use crate::axum_helpers;
27
28// Associated constants can't be imported.
29pub const CLIENT_400_BAD_REQUEST: StatusCode = StatusCode::BAD_REQUEST;
30pub const CLIENT_401_UNAUTHORIZED: StatusCode = StatusCode::UNAUTHORIZED;
31pub const CLIENT_404_NOT_FOUND: StatusCode = StatusCode::NOT_FOUND;
32pub const CLIENT_409_CONFLICT: StatusCode = StatusCode::CONFLICT;
33pub const CLIENT_426_UPGRADE_REQUIRED: StatusCode =
34    StatusCode::UPGRADE_REQUIRED;
35pub const SERVER_500_INTERNAL_SERVER_ERROR: StatusCode =
36    StatusCode::INTERNAL_SERVER_ERROR;
37pub const SERVER_502_BAD_GATEWAY: StatusCode = StatusCode::BAD_GATEWAY;
38pub const SERVER_503_SERVICE_UNAVAILABLE: StatusCode =
39    StatusCode::SERVICE_UNAVAILABLE;
40pub const SERVER_504_GATEWAY_TIMEOUT: StatusCode = StatusCode::GATEWAY_TIMEOUT;
41
42/// `ErrorCode` is the common serialized representation for all `ErrorKind`s.
43pub type ErrorCode = u16;
44
45/// `ErrorResponse` is the common JSON-serialized representation for all
46/// `ApiError`s. It is the only error struct actually sent across the wire.
47/// Everything else is converted to / from it.
48///
49/// For displaying the full human-readable message to the user, convert
50/// `ErrorResponse` to the corresponding API error type first.
51#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
52#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
53pub struct ErrorResponse {
54    pub code: ErrorCode,
55
56    #[cfg_attr(
57        any(test, feature = "test-utils"),
58        proptest(strategy = "arbitrary::any_string()")
59    )]
60    pub msg: String,
61
62    /// Structured data associated with this error.
63    #[cfg_attr(
64        any(test, feature = "test-utils"),
65        proptest(strategy = "arbitrary::any_json_value()")
66    )]
67    #[serde(default)] // For backwards compat
68    pub data: serde_json::Value,
69
70    /// Whether `data` contains sensitive information that Lexe shouldn't see
71    /// (e.g. a route). Such data may still be logged by the app or in SDKs but
72    /// shouldn't be logged inside of Lexe infra.
73    #[serde(default)] // For backwards compat
74    pub sensitive: bool,
75}
76
77/// A 'trait alias' defining all the supertraits an API error type must impl
78/// to be accepted for use in the `RestClient` and across all Lexe APIs.
79pub trait ApiError:
80    ToHttpStatus
81    + From<CommonApiError>
82    + From<ErrorResponse>
83    + Into<ErrorResponse>
84    + Error
85    + Clone
86{
87}
88
89impl<E> ApiError for E where
90    E: ToHttpStatus
91        + From<CommonApiError>
92        + From<ErrorResponse>
93        + Into<ErrorResponse>
94        + Error
95        + Clone
96{
97}
98
99/// `ApiErrorKind` defines the methods required of all API error kinds.
100/// Implementations of this trait are derived by `api_error_kind!`.
101///
102/// Try to keep this light, since debugging macros is a pain : )
103pub trait ApiErrorKind:
104    Copy
105    + Clone
106    + Default
107    + Eq
108    + PartialEq
109    + fmt::Debug
110    + fmt::Display
111    + ToHttpStatus
112    + From<CommonErrorKind>
113    + From<ErrorCode>
114    + Sized
115    + 'static
116{
117    /// An array of all known error kind variants, excluding `Unknown(_)`.
118    const KINDS: &'static [Self];
119
120    /// Returns `true` if the error kind is unrecognized (at least by this
121    /// version of the software).
122    fn is_unknown(&self) -> bool;
123
124    /// Returns the variant name of this error kind.
125    ///
126    /// Ex: `MyErrorKind::Foo.to_name() == "Foo"`
127    fn to_name(self) -> &'static str;
128
129    /// Returns the human-readable message for this error kind. For a generated
130    /// error kind, this is the same as the variant's doc string.
131    fn to_msg(self) -> &'static str;
132
133    /// Returns the serializable [`ErrorCode`] for this error kind.
134    fn to_code(self) -> ErrorCode;
135
136    /// Returns the error kind for this raw [`ErrorCode`].
137    ///
138    /// This method is infallible as every error kind must always have an
139    /// `Unknown(_)` variant for backwards compatibility.
140    fn from_code(code: ErrorCode) -> Self;
141}
142
143/// A trait to get the HTTP status code for a given Error.
144pub trait ToHttpStatus {
145    fn to_http_status(&self) -> StatusCode;
146}
147
148// --- api_error! and api_error_kind! macros --- //
149
150// Easily debug/view the macro expansions with `cargo expand`:
151//
152// ```bash
153// $ cargo install cargo-expand
154// $ cd public/lexe-common/
155// $ cargo expand api::error
156// ```
157
158/// This macro takes the name of an [`ApiError`] and its error kind type to
159/// generate the various impls required by the [`ApiError`] trait alias.
160///
161/// This macro should be used in combination with `api_error_kind!` below.
162///
163/// ```ignore
164/// api_error!(FooApiError, FooErrorKind);
165/// ```
166#[macro_export]
167macro_rules! api_error {
168    ($api_error:ident, $api_error_kind:ident) => {
169        #[derive(Clone, Debug, Default, Eq, PartialEq, Error)]
170        pub struct $api_error<D = serde_json::Value> {
171            pub kind: $api_error_kind,
172            pub msg: String,
173            /// Structured data associated with this error.
174            pub data: D,
175            /// Whether `data` contains sensitive information that Lexe
176            /// shouldn't see (e.g. a route). Such data may still be logged by
177            /// the app or in SDKs but shouldn't be logged inside Lexe infra.
178            pub sensitive: bool,
179        }
180
181        impl $api_error {
182            /// Log this error and get its HTTP [`StatusCode`].
183            #[cfg(feature = "axum")]
184            fn log_and_status(&self) -> StatusCode {
185                let status = self.to_http_status();
186
187                if status.is_server_error() {
188                    tracing::error!("{self}");
189                } else if status.is_client_error() {
190                    tracing::warn!("{self}");
191                } else {
192                    // All other statuses are unexpected. Log these at error.
193                    tracing::error!(
194                        "Unexpected status code {status} for error: {self}"
195                    );
196                }
197
198                status
199            }
200        }
201
202        impl fmt::Display for $api_error {
203            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204                let kind_msg = self.kind.to_msg();
205                let msg = &self.msg;
206                write!(f, "{kind_msg}: {msg}")
207            }
208        }
209
210        impl From<ErrorResponse> for $api_error {
211            fn from(err_resp: ErrorResponse) -> Self {
212                let ErrorResponse {
213                    code,
214                    msg,
215                    data,
216                    sensitive,
217                } = err_resp;
218
219                let kind = $api_error_kind::from_code(code);
220
221                Self {
222                    kind,
223                    msg,
224                    data,
225                    sensitive,
226                }
227            }
228        }
229
230        impl From<$api_error> for ErrorResponse {
231            fn from(api_error: $api_error) -> Self {
232                let $api_error {
233                    kind,
234                    msg,
235                    data,
236                    sensitive,
237                } = api_error;
238
239                let code = kind.to_code();
240
241                Self {
242                    code,
243                    msg,
244                    data,
245                    sensitive,
246                }
247            }
248        }
249
250        impl From<CommonApiError> for $api_error {
251            fn from(common_error: CommonApiError) -> Self {
252                let CommonApiError { kind, msg } = common_error;
253                let kind = $api_error_kind::from(kind);
254                Self {
255                    kind,
256                    msg,
257                    ..Default::default()
258                }
259            }
260        }
261
262        impl ToHttpStatus for $api_error {
263            fn to_http_status(&self) -> StatusCode {
264                self.kind.to_http_status()
265            }
266        }
267
268        #[cfg(feature = "axum")]
269        impl axum::response::IntoResponse for $api_error {
270            fn into_response(self) -> http::Response<axum::body::Body> {
271                // Server-side errors need to be logged here, since the error
272                // will have been converted to an `http::Response` by the time
273                // `axum`'s layers can access it.
274                let status = self.log_and_status();
275                let error_response = ErrorResponse::from(self);
276                axum_helpers::build_json_response(status, &error_response)
277            }
278        }
279
280        #[cfg(any(test, feature = "test-utils"))]
281        impl proptest::arbitrary::Arbitrary for $api_error {
282            type Parameters = ();
283            type Strategy = proptest::strategy::BoxedStrategy<Self>;
284            fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
285                use proptest::{arbitrary::any, strategy::Strategy};
286
287                (
288                    any::<$api_error_kind>(),
289                    arbitrary::any_string(),
290                    arbitrary::any_json_value(),
291                    any::<bool>(),
292                )
293                    .prop_map(|(kind, msg, data, sensitive)| Self {
294                        kind,
295                        msg,
296                        data,
297                        sensitive,
298                    })
299                    .boxed()
300            }
301        }
302    };
303}
304
305/// This macro takes an error kind enum declaration and generates impls for the
306/// trait [`ApiErrorKind`] (and its dependent traits).
307///
308/// Each invocation should be paired with a `ToHttpStatus` impl.
309///
310/// ### Example
311///
312/// ```ignore
313/// api_error_kind! {
314///     #[derive(Copy, Clone, Debug, Eq, PartialEq)]
315///     pub enum FooErrorKind {
316///         /// Unknown error
317///         Unknown(ErrorCode),
318///
319///         /// A Foo error occured
320///         Foo = 1,
321///         /// Bar failed to complete
322///         Bar = 2,
323///     }
324/// }
325///
326/// impl ToHttpStatus for FooErrorKind {
327///     fn to_http_status(&self) -> StatusCode {
328///         use FooErrorKind::*;
329///         match self {
330///             Unknown(_) => SERVER_500_INTERNAL_SERVER_ERROR,
331///
332///             Foo => CLIENT_400_BAD_REQUEST,
333///             Bar => SERVER_500_INTERNAL_SERVER_ERROR,
334///         }
335///     }
336/// }
337/// ```
338///
339/// * All error kind types _must_ have an `Unknown(ErrorCode)` variant and it
340///   _must_ be first. This handles any unrecognized errors seen from remote
341///   services and preserves the error code for debugging / propagating.
342///
343/// * Doc strings on the error variants are used for [`ApiErrorKind::to_msg`]
344///   and the [`fmt::Display`] impl.
345#[macro_export]
346macro_rules! api_error_kind {
347    {
348        $(#[$enum_meta:meta])*
349        pub enum $error_kind_name:ident {
350            $( #[doc = $unknown_msg:literal] )*
351            Unknown(ErrorCode),
352
353            $(
354                // use the doc string for the error message
355                $( #[doc = $item_msg:literal] )*
356                $item_name:ident = $item_code:literal
357            ),*
358
359            $(,)?
360        }
361    } => { // generate the error kind enum + impls
362
363        $(#[$enum_meta])*
364        pub enum $error_kind_name {
365            $( #[doc = $unknown_msg] )*
366            Unknown(ErrorCode),
367
368            $(
369                $( #[doc = $item_msg] )*
370                $item_name
371            ),*
372        }
373
374        // --- macro-generated impls --- //
375
376        impl ApiErrorKind for $error_kind_name {
377            const KINDS: &'static [Self] = &[
378                $( Self::$item_name, )*
379            ];
380
381            #[inline]
382            fn is_unknown(&self) -> bool {
383                matches!(self, Self::Unknown(_))
384            }
385
386            fn to_name(self) -> &'static str {
387                match self {
388                    $( Self::$item_name => stringify!($item_name), )*
389                    Self::Unknown(_) => "Unknown",
390                }
391            }
392
393            fn to_msg(self) -> &'static str {
394                let kind_msg = match self {
395                    $( Self::$item_name => concat!($( $item_msg, )*), )*
396                    Self::Unknown(_) => concat!($( $unknown_msg, )*),
397                };
398                kind_msg.trim_start()
399            }
400
401            fn to_code(self) -> ErrorCode {
402                match self {
403                    $( Self::$item_name => $item_code, )*
404                    Self::Unknown(code) => code,
405                }
406            }
407
408            fn from_code(code: ErrorCode) -> Self {
409                // this deny attr makes duplicate codes a compile error : )
410                #[deny(unreachable_patterns)]
411                match code {
412                    // make 0 the first entry so any variants with 0 code will
413                    // raise a compile error.
414                    0 => Self::Unknown(0),
415                    $( $item_code => Self::$item_name, )*
416                    _ => Self::Unknown(code),
417                }
418            }
419        }
420
421        // --- standard trait impls --- //
422
423        impl Default for $error_kind_name {
424            fn default() -> Self {
425                Self::Unknown(0)
426            }
427        }
428
429        impl fmt::Display for $error_kind_name {
430            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431                let msg = (*self).to_msg();
432
433                // No ':' because the ApiError's Display impl adds it.
434                //
435                // NOTE: We used to prefix with `[<code>=<kind_name>]` like
436                // "[106=Command]", but this was not helpful, so we removed it.
437                write!(f, "{msg}")
438            }
439        }
440
441        // --- impl Into/From ErrorCode --- //
442
443        impl From<ErrorCode> for $error_kind_name {
444            #[inline]
445            fn from(code: ErrorCode) -> Self {
446                Self::from_code(code)
447            }
448        }
449
450        impl From<$error_kind_name> for ErrorCode {
451            #[inline]
452            fn from(val: $error_kind_name) -> ErrorCode {
453                val.to_code()
454            }
455        }
456
457        // --- impl From CommonErrorKind --- //
458
459        impl From<CommonErrorKind> for $error_kind_name {
460            #[inline]
461            fn from(common: CommonErrorKind) -> Self {
462                // We can use `Self::from_code` here bc `error_kind_invariants`
463                // checks that the recovered `ApiError` kind != Unknown
464                Self::from_code(common.to_code())
465            }
466        }
467
468        // --- impl Arbitrary --- //
469
470        // Unfortunately, we can't just derive Arbitrary since proptest will
471        // generate `Unknown(code)` with code that actually is a valid variant.
472        #[cfg(any(test, feature = "test-utils"))]
473        impl proptest::arbitrary::Arbitrary for $error_kind_name {
474            type Parameters = ();
475            type Strategy = proptest::strategy::BoxedStrategy<Self>;
476
477            fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
478                use proptest::{prop_oneof, sample};
479                use proptest::arbitrary::any;
480                use proptest::strategy::Strategy;
481
482                // 9/10 sample a valid error code, o/w sample a random error
483                // code (likely unknown).
484                prop_oneof![
485                    9 => sample::select(Self::KINDS),
486                    1 => any::<ErrorCode>().prop_map(Self::from_code),
487                ].boxed()
488            }
489        }
490    }
491}
492
493// --- Error structs --- //
494
495/// Errors common to all [`ApiError`]s.
496///
497/// - This is an intermediate error type which should only be used in API
498///   library code (e.g. `RestClient`, `lexe_api::server`) which cannot assume a
499///   specific API error type.
500/// - [`ApiError`]s and [`ApiErrorKind`]s must impl `From<CommonApiError>` and
501///   `From<CommonErrorKind>` respectively to ensure all cases are covered.
502pub struct CommonApiError {
503    pub kind: CommonErrorKind,
504    pub msg: String,
505    // `data` and `sensitive` can be added here if necessary.
506}
507
508api_error!(BackendApiError, BackendErrorKind);
509api_error!(GatewayApiError, GatewayErrorKind);
510api_error!(LspApiError, LspErrorKind);
511api_error!(MegaApiError, MegaErrorKind);
512api_error!(NodeApiError, NodeErrorKind);
513api_error!(RunnerApiError, RunnerErrorKind);
514api_error!(SdkApiError, SdkErrorKind);
515
516// --- Error variants --- //
517
518/// Error variants common to all `ApiError`s.
519#[derive(Copy, Clone, Debug)]
520#[repr(u16)]
521pub enum CommonErrorKind {
522    /// Unknown Reqwest client error
523    UnknownReqwest = 1,
524    /// Error building the HTTP request
525    Building = 2,
526    /// Error connecting to a remote HTTP service
527    Connect = 3,
528    /// Request timed out
529    Timeout = 4,
530    /// Error decoding/deserializing the HTTP response body
531    Decode = 5,
532    /// General server error
533    Server = 6,
534    /// Client provided a bad request that the server rejected
535    Rejection = 7,
536    /// Server is currently at capacity; retry later
537    AtCapacity = 8,
538    // NOTE: If adding a variant, be sure to also update Self::KINDS!
539}
540
541impl ToHttpStatus for CommonErrorKind {
542    fn to_http_status(&self) -> StatusCode {
543        use CommonErrorKind::*;
544        match self {
545            UnknownReqwest => CLIENT_400_BAD_REQUEST,
546            Building => CLIENT_400_BAD_REQUEST,
547            Connect => SERVER_503_SERVICE_UNAVAILABLE,
548            Timeout => SERVER_504_GATEWAY_TIMEOUT,
549            Decode => SERVER_502_BAD_GATEWAY,
550            Server => SERVER_500_INTERNAL_SERVER_ERROR,
551            Rejection => CLIENT_400_BAD_REQUEST,
552            AtCapacity => SERVER_503_SERVICE_UNAVAILABLE,
553        }
554    }
555}
556
557api_error_kind! {
558    /// All variants of errors that the backend can return.
559    #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
560    pub enum BackendErrorKind {
561        /// Unknown error
562        Unknown(ErrorCode),
563
564        // --- Common --- //
565
566        /// Unknown Reqwest client error
567        UnknownReqwest = 1,
568        /// Error building the HTTP request
569        Building = 2,
570        /// Error connecting to a remote HTTP service
571        Connect = 3,
572        /// Request timed out
573        Timeout = 4,
574        /// Error decoding/deserializing the HTTP response body
575        Decode = 5,
576        /// General server error
577        Server = 6,
578        /// Client provided a bad request that the server rejected
579        Rejection = 7,
580        /// Server is at capacity
581        AtCapacity = 8,
582
583        // --- Backend --- //
584
585        /// Database error
586        Database = 100,
587        /// Resource not found
588        NotFound = 101,
589        /// Resource was duplicate
590        Duplicate = 102,
591        /// Could not convert field or model to type
592        Conversion = 103,
593        /// User failed authentication
594        Unauthenticated = 104,
595        /// User not authorized
596        Unauthorized = 105,
597        /// Auth token or auth request is expired
598        AuthExpired = 106,
599        /// Parsed request is invalid
600        InvalidParsedRequest = 107,
601        /// Request batch size is over the limit
602        BatchSizeOverLimit = 108,
603        /// Resource is not updatable
604        NotUpdatable = 109,
605        /// Client is too old to handle the response; upgrade required
606        ClientUpgradeRequired = 110,
607    }
608}
609
610impl ToHttpStatus for BackendErrorKind {
611    fn to_http_status(&self) -> StatusCode {
612        use BackendErrorKind::*;
613        match self {
614            Unknown(_) => SERVER_500_INTERNAL_SERVER_ERROR,
615
616            UnknownReqwest => CLIENT_400_BAD_REQUEST,
617            Building => CLIENT_400_BAD_REQUEST,
618            Connect => SERVER_503_SERVICE_UNAVAILABLE,
619            Timeout => SERVER_504_GATEWAY_TIMEOUT,
620            Decode => SERVER_502_BAD_GATEWAY,
621            Server => SERVER_500_INTERNAL_SERVER_ERROR,
622            Rejection => CLIENT_400_BAD_REQUEST,
623            AtCapacity => SERVER_503_SERVICE_UNAVAILABLE,
624
625            Database => SERVER_500_INTERNAL_SERVER_ERROR,
626            NotFound => CLIENT_404_NOT_FOUND,
627            Duplicate => CLIENT_409_CONFLICT,
628            Conversion => SERVER_500_INTERNAL_SERVER_ERROR,
629            Unauthenticated => CLIENT_401_UNAUTHORIZED,
630            Unauthorized => CLIENT_401_UNAUTHORIZED,
631            AuthExpired => CLIENT_401_UNAUTHORIZED,
632            InvalidParsedRequest => CLIENT_400_BAD_REQUEST,
633            BatchSizeOverLimit => CLIENT_400_BAD_REQUEST,
634            NotUpdatable => CLIENT_400_BAD_REQUEST,
635            ClientUpgradeRequired => CLIENT_426_UPGRADE_REQUIRED,
636        }
637    }
638}
639
640api_error_kind! {
641    /// All variants of errors that the gateway can return.
642    #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
643    pub enum GatewayErrorKind {
644        /// Unknown error
645        Unknown(ErrorCode),
646
647        // --- Common --- //
648
649        /// Unknown Reqwest client error
650        UnknownReqwest = 1,
651        /// Error building the HTTP request
652        Building = 2,
653        /// Error connecting to a remote HTTP service
654        Connect = 3,
655        /// Request timed out
656        Timeout = 4,
657        /// Error decoding/deserializing the HTTP response body
658        Decode = 5,
659        /// General server error
660        Server = 6,
661        /// Client provided a bad request that the server rejected
662        Rejection = 7,
663        /// Server is at capacity
664        AtCapacity = 8,
665
666        // --- Gateway --- //
667
668        /// Missing fiat exchange rates; issue with upstream data source
669        FiatRatesMissing = 100,
670    }
671}
672
673impl ToHttpStatus for GatewayErrorKind {
674    fn to_http_status(&self) -> StatusCode {
675        use GatewayErrorKind::*;
676        match self {
677            Unknown(_) => SERVER_500_INTERNAL_SERVER_ERROR,
678
679            UnknownReqwest => CLIENT_400_BAD_REQUEST,
680            Building => CLIENT_400_BAD_REQUEST,
681            Connect => SERVER_503_SERVICE_UNAVAILABLE,
682            Timeout => SERVER_504_GATEWAY_TIMEOUT,
683            Decode => SERVER_502_BAD_GATEWAY,
684            Server => SERVER_500_INTERNAL_SERVER_ERROR,
685            Rejection => CLIENT_400_BAD_REQUEST,
686            AtCapacity => SERVER_503_SERVICE_UNAVAILABLE,
687
688            FiatRatesMissing => SERVER_500_INTERNAL_SERVER_ERROR,
689        }
690    }
691}
692
693api_error_kind! {
694    /// All variants of errors that the LSP can return.
695    #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
696    pub enum LspErrorKind {
697        /// Unknown error
698        Unknown(ErrorCode),
699
700        // --- Common --- //
701
702        /// Unknown Reqwest client error
703        UnknownReqwest = 1,
704        /// Error building the HTTP request
705        Building = 2,
706        /// Error connecting to a remote HTTP service
707        Connect = 3,
708        /// Request timed out
709        Timeout = 4,
710        /// Error decoding/deserializing the HTTP response body
711        Decode = 5,
712        /// General server error
713        Server = 6,
714        /// Client provided a bad request that the server rejected
715        Rejection = 7,
716        /// Server is at capacity
717        AtCapacity = 8,
718
719        // --- LSP --- //
720
721        /// Error occurred during provisioning
722        Provision = 100,
723        /// Error occurred while fetching new scid
724        Scid = 101,
725        /// Error
726        // NOTE: Intentionally NOT descriptive.
727        // These get displayed on the app UI frequently and should be concise.
728        Command = 102,
729        /// Resource not found
730        NotFound = 103,
731    }
732}
733
734impl ToHttpStatus for LspErrorKind {
735    fn to_http_status(&self) -> StatusCode {
736        use LspErrorKind::*;
737        match self {
738            Unknown(_) => SERVER_500_INTERNAL_SERVER_ERROR,
739
740            UnknownReqwest => CLIENT_400_BAD_REQUEST,
741            Building => CLIENT_400_BAD_REQUEST,
742            Connect => SERVER_503_SERVICE_UNAVAILABLE,
743            Timeout => SERVER_504_GATEWAY_TIMEOUT,
744            Decode => SERVER_502_BAD_GATEWAY,
745            Server => SERVER_500_INTERNAL_SERVER_ERROR,
746            Rejection => CLIENT_400_BAD_REQUEST,
747            AtCapacity => SERVER_503_SERVICE_UNAVAILABLE,
748
749            Provision => SERVER_500_INTERNAL_SERVER_ERROR,
750            Scid => SERVER_500_INTERNAL_SERVER_ERROR,
751            Command => SERVER_500_INTERNAL_SERVER_ERROR,
752            NotFound => CLIENT_404_NOT_FOUND,
753        }
754    }
755}
756
757api_error_kind! {
758    /// All variants of errors that the LSP can return.
759    #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
760    pub enum MegaErrorKind {
761        /// Unknown error
762        Unknown(ErrorCode),
763
764        // --- Common --- //
765
766        /// Unknown Reqwest client error
767        UnknownReqwest = 1,
768        /// Error building the HTTP request
769        Building = 2,
770        /// Error connecting to a remote HTTP service
771        Connect = 3,
772        /// Request timed out
773        Timeout = 4,
774        /// Error decoding/deserializing the HTTP response body
775        Decode = 5,
776        /// General server error
777        Server = 6,
778        /// Client provided a bad request that the server rejected
779        Rejection = 7,
780        /// Server is at capacity
781        AtCapacity = 8,
782
783        // --- Mega --- //
784
785        /// Request mega_id doesn't match current mega_id
786        WrongMegaId = 100,
787        /// Usernode runner is currently unreachable; try again later
788        RunnerUnreachable = 101,
789        /// The requested user is not known to this meganode
790        UnknownUser = 102,
791    }
792}
793
794impl ToHttpStatus for MegaErrorKind {
795    fn to_http_status(&self) -> StatusCode {
796        use MegaErrorKind::*;
797        match self {
798            Unknown(_) => SERVER_500_INTERNAL_SERVER_ERROR,
799
800            UnknownReqwest => CLIENT_400_BAD_REQUEST,
801            Building => CLIENT_400_BAD_REQUEST,
802            Connect => SERVER_503_SERVICE_UNAVAILABLE,
803            Timeout => SERVER_504_GATEWAY_TIMEOUT,
804            Decode => SERVER_502_BAD_GATEWAY,
805            Server => SERVER_500_INTERNAL_SERVER_ERROR,
806            Rejection => CLIENT_400_BAD_REQUEST,
807            AtCapacity => SERVER_503_SERVICE_UNAVAILABLE,
808
809            WrongMegaId => CLIENT_400_BAD_REQUEST,
810            RunnerUnreachable => SERVER_503_SERVICE_UNAVAILABLE,
811            UnknownUser => CLIENT_404_NOT_FOUND,
812        }
813    }
814}
815
816api_error_kind! {
817    /// All variants of errors that the node can return.
818    #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
819    pub enum NodeErrorKind {
820        /// Unknown error
821        Unknown(ErrorCode),
822
823        // --- Common --- //
824
825        /// Unknown Reqwest client error
826        UnknownReqwest = 1,
827        /// Error building the HTTP request
828        Building = 2,
829        /// Error connecting to a remote HTTP service
830        Connect = 3,
831        /// Request timed out
832        Timeout = 4,
833        /// Error decoding/deserializing the HTTP response body
834        Decode = 5,
835        /// General server error
836        Server = 6,
837        /// Client provided a bad request that the server rejected
838        Rejection = 7,
839        /// Server is at capacity
840        AtCapacity = 8,
841
842        // --- Node --- //
843
844        /// Wrong user pk
845        WrongUserPk = 100,
846        /// Given node pk doesn't match node pk derived from seed
847        WrongNodePk = 101,
848        /// Request measurement doesn't match current enclave measurement
849        WrongMeasurement = 102,
850        /// Error occurred during provisioning
851        Provision = 103,
852        /// Authentication error
853        BadAuth = 104,
854        /// Could not proxy request to node
855        Proxy = 105,
856        /// Error
857        // NOTE: Intentionally NOT descriptive.
858        // These get displayed on the app UI frequently and should be concise.
859        Command = 106,
860        /// Resource not found
861        NotFound = 107,
862    }
863}
864
865impl ToHttpStatus for NodeErrorKind {
866    fn to_http_status(&self) -> StatusCode {
867        use NodeErrorKind::*;
868        match self {
869            Unknown(_) => SERVER_500_INTERNAL_SERVER_ERROR,
870
871            UnknownReqwest => CLIENT_400_BAD_REQUEST,
872            Building => CLIENT_400_BAD_REQUEST,
873            Connect => SERVER_503_SERVICE_UNAVAILABLE,
874            Timeout => SERVER_504_GATEWAY_TIMEOUT,
875            Decode => SERVER_502_BAD_GATEWAY,
876            Server => SERVER_500_INTERNAL_SERVER_ERROR,
877            Rejection => CLIENT_400_BAD_REQUEST,
878            AtCapacity => SERVER_503_SERVICE_UNAVAILABLE,
879
880            WrongUserPk => CLIENT_400_BAD_REQUEST,
881            WrongNodePk => CLIENT_400_BAD_REQUEST,
882            WrongMeasurement => CLIENT_400_BAD_REQUEST,
883            Provision => SERVER_500_INTERNAL_SERVER_ERROR,
884            BadAuth => CLIENT_401_UNAUTHORIZED,
885            Proxy => SERVER_502_BAD_GATEWAY,
886            Command => SERVER_500_INTERNAL_SERVER_ERROR,
887            NotFound => CLIENT_404_NOT_FOUND,
888        }
889    }
890}
891
892api_error_kind! {
893    /// All variants of errors that the runner can return.
894    #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
895    pub enum RunnerErrorKind {
896        /// Unknown error
897        Unknown(ErrorCode),
898
899        // --- Common --- //
900
901        /// Unknown Reqwest client error
902        UnknownReqwest = 1,
903        /// Error building the HTTP request
904        Building = 2,
905        /// Error connecting to a remote HTTP service
906        Connect = 3,
907        /// Request timed out
908        Timeout = 4,
909        /// Error decoding/deserializing the HTTP response body
910        Decode = 5,
911        /// General server error
912        Server = 6,
913        /// Client provided a bad request that the server rejected
914        Rejection = 7,
915        /// Server is at capacity
916        AtCapacity = 8,
917
918        // --- Runner --- //
919
920        /// General Runner error
921        Runner = 100,
922        /// Unknown or unserviceable measurement
923        // The measurement is provided by the caller
924        UnknownMeasurement = 101,
925        /// Caller requested a version which is too old
926        OldVersion = 102,
927        /// Requested node temporarily unavailable, most likely due to a common
928        /// race condition; retry the request (temporary error)
929        TemporarilyUnavailable = 103,
930        /// Runner service is unavailable (semi-permanent error)
931        ServiceUnavailable = 104,
932        /// Requested node failed to boot
933        Boot = 106,
934        /// Failed to evict a usernode
935        EvictionFailure = 107,
936        /// The requested user is not known to the runner
937        UnknownUser = 108,
938        /// Tried to renew a lease that has already expired
939        LeaseExpired = 109,
940        /// Tried to renew a lease belonging to a different user
941        WrongLease = 110,
942    }
943}
944
945impl ToHttpStatus for RunnerErrorKind {
946    fn to_http_status(&self) -> StatusCode {
947        use RunnerErrorKind::*;
948        match self {
949            Unknown(_) => SERVER_500_INTERNAL_SERVER_ERROR,
950
951            UnknownReqwest => CLIENT_400_BAD_REQUEST,
952            Building => CLIENT_400_BAD_REQUEST,
953            Connect => SERVER_503_SERVICE_UNAVAILABLE,
954            Timeout => SERVER_504_GATEWAY_TIMEOUT,
955            Decode => SERVER_502_BAD_GATEWAY,
956            Server => SERVER_500_INTERNAL_SERVER_ERROR,
957            Rejection => CLIENT_400_BAD_REQUEST,
958            AtCapacity => SERVER_503_SERVICE_UNAVAILABLE,
959
960            Runner => SERVER_500_INTERNAL_SERVER_ERROR,
961            UnknownMeasurement => CLIENT_404_NOT_FOUND,
962            OldVersion => CLIENT_400_BAD_REQUEST,
963            TemporarilyUnavailable => CLIENT_409_CONFLICT,
964            ServiceUnavailable => SERVER_503_SERVICE_UNAVAILABLE,
965            Boot => SERVER_500_INTERNAL_SERVER_ERROR,
966            EvictionFailure => SERVER_500_INTERNAL_SERVER_ERROR,
967            UnknownUser => CLIENT_404_NOT_FOUND,
968            LeaseExpired => CLIENT_400_BAD_REQUEST,
969            WrongLease => CLIENT_400_BAD_REQUEST,
970        }
971    }
972}
973
974api_error_kind! {
975    /// All variants of errors that the SDK can return.
976    #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
977    pub enum SdkErrorKind {
978        /// Unknown error
979        Unknown(ErrorCode),
980
981        // --- Common --- //
982
983        /// Unknown Reqwest client error
984        UnknownReqwest = 1,
985        /// Error building the HTTP request
986        Building = 2,
987        /// Error connecting to a remote HTTP service
988        Connect = 3,
989        /// Request timed out
990        Timeout = 4,
991        /// Error decoding/deserializing the HTTP response body
992        Decode = 5,
993        /// General server error
994        Server = 6,
995        /// Client provided a bad request that the server rejected
996        Rejection = 7,
997        /// Server is at capacity
998        AtCapacity = 8,
999
1000        // --- SDK --- //
1001
1002        /// Error
1003        // NOTE: Intentionally NOT descriptive.
1004        // These get displayed to users frequently and should be concise.
1005        Command = 100,
1006        /// Authentication error
1007        BadAuth = 101,
1008        /// Resource not found
1009        NotFound = 102,
1010    }
1011}
1012
1013impl ToHttpStatus for SdkErrorKind {
1014    fn to_http_status(&self) -> StatusCode {
1015        use SdkErrorKind::*;
1016        match self {
1017            Unknown(_) => SERVER_500_INTERNAL_SERVER_ERROR,
1018
1019            UnknownReqwest => CLIENT_400_BAD_REQUEST,
1020            Building => CLIENT_400_BAD_REQUEST,
1021            Connect => SERVER_503_SERVICE_UNAVAILABLE,
1022            Timeout => SERVER_504_GATEWAY_TIMEOUT,
1023            Decode => SERVER_502_BAD_GATEWAY,
1024            Server => SERVER_500_INTERNAL_SERVER_ERROR,
1025            Rejection => CLIENT_400_BAD_REQUEST,
1026            AtCapacity => SERVER_503_SERVICE_UNAVAILABLE,
1027
1028            Command => SERVER_500_INTERNAL_SERVER_ERROR,
1029            BadAuth => CLIENT_401_UNAUTHORIZED,
1030            NotFound => CLIENT_404_NOT_FOUND,
1031        }
1032    }
1033}
1034
1035// --- CommonApiError / CommonErrorKind impls --- //
1036
1037impl CommonApiError {
1038    pub fn new(kind: CommonErrorKind, msg: String) -> Self {
1039        Self { kind, msg }
1040    }
1041
1042    #[inline]
1043    pub fn to_code(&self) -> ErrorCode {
1044        self.kind.to_code()
1045    }
1046
1047    /// Log this error and get its HTTP [`StatusCode`].
1048    #[cfg(feature = "axum")]
1049    fn log_and_status(&self) -> StatusCode {
1050        let status = self.kind.to_http_status();
1051
1052        if status.is_server_error() {
1053            error!("{self}");
1054        } else if status.is_client_error() {
1055            warn!("{self}");
1056        } else {
1057            // All other statuses are unexpected. Log these at error.
1058            error!("Unexpected status code {status} for error: {self}");
1059        }
1060
1061        status
1062    }
1063}
1064
1065impl fmt::Display for CommonApiError {
1066    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1067        let kind = &self.kind;
1068        let msg = &self.msg;
1069        // This just uses the `Debug` impl for the kind, since we don't have a
1070        // `kind_msg` provided by the `api_error_kind!` macro.
1071        write!(f, "{kind:?}: {msg}")
1072    }
1073}
1074
1075impl CommonErrorKind {
1076    #[cfg(any(test, feature = "test-utils"))]
1077    const KINDS: &'static [Self] = &[
1078        Self::UnknownReqwest,
1079        Self::Building,
1080        Self::Connect,
1081        Self::Timeout,
1082        Self::Decode,
1083        Self::Server,
1084        Self::Rejection,
1085        Self::AtCapacity,
1086    ];
1087
1088    #[inline]
1089    pub fn to_code(self) -> ErrorCode {
1090        self as ErrorCode
1091    }
1092}
1093
1094impl From<serde_json::Error> for CommonApiError {
1095    fn from(err: serde_json::Error) -> Self {
1096        let kind = CommonErrorKind::Decode;
1097        let msg = format!("Failed to deserialize response as json: {err:#}");
1098        Self { kind, msg }
1099    }
1100}
1101
1102#[cfg(feature = "reqwest")]
1103impl From<reqwest::Error> for CommonApiError {
1104    fn from(err: reqwest::Error) -> Self {
1105        // NOTE: The `reqwest::Error` `Display` impl is totally useless!!
1106        // We've had tons of problems with it swallowing TLS errors.
1107        // You have to use the `Debug` impl to get any info about the source.
1108        let msg = format!("{err:?}");
1109        // Be more granular than just returning a general reqwest::Error
1110        let kind = if err.is_builder() {
1111            CommonErrorKind::Building
1112        } else if err.is_connect() {
1113            CommonErrorKind::Connect
1114        } else if err.is_timeout() {
1115            CommonErrorKind::Timeout
1116        } else if err.is_decode() {
1117            CommonErrorKind::Decode
1118        } else {
1119            CommonErrorKind::UnknownReqwest
1120        };
1121        Self { kind, msg }
1122    }
1123}
1124
1125impl From<CommonApiError> for ErrorResponse {
1126    fn from(CommonApiError { kind, msg }: CommonApiError) -> Self {
1127        let code = kind.to_code();
1128        // TODO(max): Maybe use new fields from common error
1129        Self {
1130            code,
1131            msg,
1132            ..Default::default()
1133        }
1134    }
1135}
1136
1137#[cfg(feature = "axum")]
1138impl axum::response::IntoResponse for CommonApiError {
1139    fn into_response(self) -> http::Response<axum::body::Body> {
1140        // Server-side errors need to be logged here, since the error is
1141        // converted to an `http::Response` by the time `axum` can access it.
1142        let status = self.log_and_status();
1143        let error_response = ErrorResponse::from(self);
1144        axum_helpers::build_json_response(status, &error_response)
1145    }
1146}
1147
1148// --- ApiError impls --- //
1149
1150impl BackendApiError {
1151    pub fn database(error: impl fmt::Display) -> Self {
1152        let kind = BackendErrorKind::Database;
1153        let msg = format!("{error:#}");
1154        Self {
1155            kind,
1156            msg,
1157            ..Default::default()
1158        }
1159    }
1160
1161    pub fn not_found(error: impl fmt::Display) -> Self {
1162        let kind = BackendErrorKind::NotFound;
1163        let msg = format!("{error:#}");
1164        Self {
1165            kind,
1166            msg,
1167            ..Default::default()
1168        }
1169    }
1170
1171    pub fn duplicate(error: impl fmt::Display) -> Self {
1172        let kind = BackendErrorKind::Duplicate;
1173        let msg = format!("{error:#}");
1174        Self {
1175            kind,
1176            msg,
1177            ..Default::default()
1178        }
1179    }
1180
1181    pub fn unauthorized(error: impl fmt::Display) -> Self {
1182        let kind = BackendErrorKind::Unauthorized;
1183        let msg = format!("{error:#}");
1184        Self {
1185            kind,
1186            msg,
1187            ..Default::default()
1188        }
1189    }
1190
1191    pub fn unauthenticated(error: impl fmt::Display) -> Self {
1192        let kind = BackendErrorKind::Unauthenticated;
1193        let msg = format!("{error:#}");
1194        Self {
1195            kind,
1196            msg,
1197            ..Default::default()
1198        }
1199    }
1200
1201    pub fn invalid_parsed_req(error: impl fmt::Display) -> Self {
1202        let kind = BackendErrorKind::InvalidParsedRequest;
1203        let msg = format!("{error:#}");
1204        Self {
1205            kind,
1206            msg,
1207            ..Default::default()
1208        }
1209    }
1210
1211    pub fn not_updatable(error: impl fmt::Display) -> Self {
1212        let kind = BackendErrorKind::NotUpdatable;
1213        let msg = format!("{error:#})");
1214        Self {
1215            kind,
1216            msg,
1217            ..Default::default()
1218        }
1219    }
1220
1221    pub fn bcs_serialize(err: bcs::Error) -> Self {
1222        let kind = BackendErrorKind::Building;
1223        let msg = format!("Failed to serialize bcs request: {err:#}");
1224        Self {
1225            kind,
1226            msg,
1227            ..Default::default()
1228        }
1229    }
1230
1231    pub fn batch_size_too_large() -> Self {
1232        let kind = BackendErrorKind::BatchSizeOverLimit;
1233        let msg = kind.to_msg().to_owned();
1234        Self {
1235            kind,
1236            msg,
1237            ..Default::default()
1238        }
1239    }
1240
1241    pub fn conversion(error: impl fmt::Display) -> Self {
1242        let kind = BackendErrorKind::Conversion;
1243        let msg = format!("{error:#}");
1244        Self {
1245            kind,
1246            msg,
1247            ..Default::default()
1248        }
1249    }
1250
1251    pub fn client_upgrade_required(error: impl fmt::Display) -> Self {
1252        let kind = BackendErrorKind::ClientUpgradeRequired;
1253        let msg = format!("{error:#}");
1254        Self {
1255            kind,
1256            msg,
1257            ..Default::default()
1258        }
1259    }
1260}
1261
1262impl From<auth::Error> for BackendApiError {
1263    fn from(error: auth::Error) -> Self {
1264        let kind = match error {
1265            auth::Error::ClockDrift => BackendErrorKind::AuthExpired,
1266            auth::Error::Expired => BackendErrorKind::AuthExpired,
1267            _ => BackendErrorKind::Unauthenticated,
1268        };
1269        let msg = format!("{error:#}");
1270        Self {
1271            kind,
1272            msg,
1273            ..Default::default()
1274        }
1275    }
1276}
1277
1278impl GatewayApiError {
1279    pub fn fiat_rates_missing() -> Self {
1280        let kind = GatewayErrorKind::FiatRatesMissing;
1281        let msg = kind.to_string();
1282        Self {
1283            kind,
1284            msg,
1285            ..Default::default()
1286        }
1287    }
1288}
1289
1290impl LspApiError {
1291    pub fn provision(error: impl fmt::Display) -> Self {
1292        let msg = format!("{error:#}");
1293        let kind = LspErrorKind::Provision;
1294        Self {
1295            kind,
1296            msg,
1297            ..Default::default()
1298        }
1299    }
1300
1301    pub fn scid(error: impl fmt::Display) -> Self {
1302        let msg = format!("{error:#}");
1303        let kind = LspErrorKind::Scid;
1304        Self {
1305            kind,
1306            msg,
1307            ..Default::default()
1308        }
1309    }
1310
1311    pub fn command(error: impl fmt::Display) -> Self {
1312        let msg = format!("{error:#}");
1313        let kind = LspErrorKind::Command;
1314        Self {
1315            kind,
1316            msg,
1317            ..Default::default()
1318        }
1319    }
1320
1321    pub fn rejection(error: impl fmt::Display) -> Self {
1322        let msg = format!("{error:#}");
1323        let kind = LspErrorKind::Rejection;
1324        Self {
1325            kind,
1326            msg,
1327            ..Default::default()
1328        }
1329    }
1330
1331    pub fn not_found(error: impl fmt::Display) -> Self {
1332        let msg = format!("{error:#}");
1333        let kind = LspErrorKind::NotFound;
1334        Self {
1335            kind,
1336            msg,
1337            ..Default::default()
1338        }
1339    }
1340}
1341
1342impl MegaApiError {
1343    pub fn at_capacity(msg: impl Into<String>) -> Self {
1344        let kind = MegaErrorKind::AtCapacity;
1345        let msg = msg.into();
1346        Self {
1347            kind,
1348            msg,
1349            ..Default::default()
1350        }
1351    }
1352
1353    pub fn wrong_mega_id(
1354        req_mega_id: &MegaId,
1355        actual_mega_id: &MegaId,
1356    ) -> Self {
1357        let kind = MegaErrorKind::WrongMegaId;
1358        let msg = format!("Req: {req_mega_id}, Actual: {actual_mega_id}");
1359        Self {
1360            kind,
1361            msg,
1362            ..Default::default()
1363        }
1364    }
1365
1366    pub fn unknown_user(user_pk: &UserPk, msg: impl fmt::Display) -> Self {
1367        Self {
1368            kind: MegaErrorKind::UnknownUser,
1369            msg: format!("{user_pk}: {msg}"),
1370            ..Default::default()
1371        }
1372    }
1373}
1374
1375impl NodeApiError {
1376    pub fn wrong_user_pk(current_pk: UserPk, given_pk: UserPk) -> Self {
1377        // We don't name these 'expected' and 'actual' because the meaning of
1378        // those terms is swapped depending on if you're the server or client.
1379        let msg =
1380            format!("Node has UserPk '{current_pk}' but received '{given_pk}'");
1381        let kind = NodeErrorKind::WrongUserPk;
1382        Self {
1383            kind,
1384            msg,
1385            ..Default::default()
1386        }
1387    }
1388
1389    pub fn wrong_node_pk(derived_pk: NodePk, given_pk: NodePk) -> Self {
1390        // We don't name these 'expected' and 'actual' because the meaning of
1391        // those terms is swapped depending on if you're the server or client.
1392        let msg =
1393            format!("Derived NodePk '{derived_pk}' but received '{given_pk}'");
1394        let kind = NodeErrorKind::WrongNodePk;
1395        Self {
1396            kind,
1397            msg,
1398            ..Default::default()
1399        }
1400    }
1401
1402    pub fn wrong_measurement(
1403        req_measurement: &Measurement,
1404        actual_measurement: &Measurement,
1405    ) -> Self {
1406        let kind = NodeErrorKind::WrongMeasurement;
1407        let msg =
1408            format!("Req: {req_measurement}, Actual: {actual_measurement}");
1409        Self {
1410            kind,
1411            msg,
1412            ..Default::default()
1413        }
1414    }
1415
1416    pub fn proxy(error: impl fmt::Display) -> Self {
1417        let msg = format!("{error:#}");
1418        let kind = NodeErrorKind::Proxy;
1419        Self {
1420            kind,
1421            msg,
1422            ..Default::default()
1423        }
1424    }
1425
1426    pub fn provision(error: impl fmt::Display) -> Self {
1427        let msg = format!("{error:#}");
1428        let kind = NodeErrorKind::Provision;
1429        Self {
1430            kind,
1431            msg,
1432            ..Default::default()
1433        }
1434    }
1435
1436    pub fn command(error: impl fmt::Display) -> Self {
1437        let msg = format!("{error:#}");
1438        let kind = NodeErrorKind::Command;
1439        Self {
1440            kind,
1441            msg,
1442            ..Default::default()
1443        }
1444    }
1445
1446    pub fn bad_auth(error: impl fmt::Display) -> Self {
1447        let msg = format!("{error:#}");
1448        let kind = NodeErrorKind::BadAuth;
1449        Self {
1450            kind,
1451            msg,
1452            ..Default::default()
1453        }
1454    }
1455
1456    pub fn not_found(error: impl fmt::Display) -> Self {
1457        let msg = format!("{error:#}");
1458        let kind = NodeErrorKind::NotFound;
1459        Self {
1460            kind,
1461            msg,
1462            ..Default::default()
1463        }
1464    }
1465}
1466
1467impl RunnerApiError {
1468    pub fn at_capacity(error: impl fmt::Display) -> Self {
1469        let kind = RunnerErrorKind::AtCapacity;
1470        let msg = format!("{error:#}");
1471        Self {
1472            kind,
1473            msg,
1474            ..Default::default()
1475        }
1476    }
1477
1478    pub fn temporarily_unavailable(error: impl fmt::Display) -> Self {
1479        let kind = RunnerErrorKind::TemporarilyUnavailable;
1480        let msg = format!("{error:#}");
1481        Self {
1482            kind,
1483            msg,
1484            ..Default::default()
1485        }
1486    }
1487
1488    pub fn service_unavailable(error: impl fmt::Display) -> Self {
1489        let kind = RunnerErrorKind::ServiceUnavailable;
1490        let msg = format!("{error:#}");
1491        Self {
1492            kind,
1493            msg,
1494            ..Default::default()
1495        }
1496    }
1497
1498    pub fn unknown_measurement(measurement: enclave::Measurement) -> Self {
1499        let kind = RunnerErrorKind::UnknownMeasurement;
1500        let msg = format!("{measurement}");
1501        Self {
1502            kind,
1503            msg,
1504            ..Default::default()
1505        }
1506    }
1507
1508    pub fn unknown_user(user_pk: &UserPk, msg: impl fmt::Display) -> Self {
1509        Self {
1510            kind: RunnerErrorKind::UnknownUser,
1511            msg: format!("{user_pk}: {msg}"),
1512            ..Default::default()
1513        }
1514    }
1515}
1516
1517impl SdkApiError {
1518    pub fn command(error: impl fmt::Display) -> Self {
1519        let msg = format!("{error:#}");
1520        let kind = SdkErrorKind::Command;
1521        Self {
1522            kind,
1523            msg,
1524            ..Default::default()
1525        }
1526    }
1527
1528    pub fn bad_auth(error: impl fmt::Display) -> Self {
1529        let msg = format!("{error:#}");
1530        let kind = SdkErrorKind::BadAuth;
1531        Self {
1532            kind,
1533            msg,
1534            ..Default::default()
1535        }
1536    }
1537
1538    pub fn not_found(error: impl fmt::Display) -> Self {
1539        let msg = format!("{error:#}");
1540        let kind = SdkErrorKind::NotFound;
1541        Self {
1542            kind,
1543            msg,
1544            ..Default::default()
1545        }
1546    }
1547}
1548
1549// --- Build JSON response --- //
1550
1551pub mod error_response {}
1552
1553// --- Misc error utilities --- //
1554
1555/// Converts a [`Vec<anyhow::Result<()>>`] to an [`anyhow::Result<()>`],
1556/// with any error messages joined by a semicolon.
1557pub fn join_results(results: Vec<anyhow::Result<()>>) -> anyhow::Result<()> {
1558    let errors = results
1559        .into_iter()
1560        .filter_map(|res| match res {
1561            Ok(_) => None,
1562            Err(e) => Some(format!("{e:#}")),
1563        })
1564        .collect::<Vec<String>>();
1565    if errors.is_empty() {
1566        Ok(())
1567    } else {
1568        let joined_errs = errors.join("; ");
1569        Err(anyhow!("{joined_errs}"))
1570    }
1571}
1572
1573// --- Test utils for asserting error invariants --- //
1574
1575#[cfg(any(test, feature = "test-utils"))]
1576pub mod invariants {
1577    use proptest::{
1578        arbitrary::{Arbitrary, any},
1579        prop_assert, prop_assert_eq, proptest,
1580    };
1581
1582    use super::*;
1583
1584    pub fn assert_error_kind_invariants<K>()
1585    where
1586        K: ApiErrorKind + Arbitrary,
1587    {
1588        // error code 0 and default error code must be unknown
1589        assert!(K::from_code(0).is_unknown());
1590        assert!(K::default().is_unknown());
1591
1592        // CommonErrorKind is a strict subset of ApiErrorKind
1593        //
1594        // CommonErrorKind [ _, 1, 2, 3, 4, 5, 6 ]
1595        //    ApiErrorKind [ _, 1, 2, 3, 4, 5,   , 100, 101 ]
1596        //                                     ^
1597        //                                    BAD
1598        for common_kind in CommonErrorKind::KINDS {
1599            let common_code = common_kind.to_code();
1600            let common_status = common_kind.to_http_status();
1601            let api_kind = K::from_code(common_kind.to_code());
1602            let api_code = api_kind.to_code();
1603            let api_status = api_kind.to_http_status();
1604            assert_eq!(common_code, api_code, "Error codes must match");
1605            assert_eq!(common_status, api_status, "HTTP statuses must match");
1606
1607            if api_kind.is_unknown() {
1608                panic!(
1609                    "all CommonErrorKind's should be covered; \
1610                     missing common code: {common_code}, \
1611                     common kind: {common_kind:?}",
1612                );
1613            }
1614        }
1615
1616        // error kind enum isomorphic to error code representation
1617        // kind -> code -> kind2 -> code2
1618        for kind in K::KINDS {
1619            let code = kind.to_code();
1620            let kind2 = K::from_code(code);
1621            let code2 = kind2.to_code();
1622            assert_eq!(code, code2);
1623            assert_eq!(kind, &kind2);
1624        }
1625
1626        // try the first 200 error codes to ensure isomorphic
1627        // code -> kind -> code2 -> kind2
1628        for code in 0_u16..200 {
1629            let kind = K::from_code(code);
1630            let code2 = kind.to_code();
1631            let kind2 = K::from_code(code2);
1632            assert_eq!(code, code2);
1633            assert_eq!(kind, kind2);
1634        }
1635
1636        // ensure proptest generator is also well-behaved
1637        proptest!(|(kind in any::<K>())| {
1638            let code = kind.to_code();
1639            let kind2 = K::from_code(code);
1640            let code2 = kind2.to_code();
1641            prop_assert_eq!(code, code2);
1642            prop_assert_eq!(kind, kind2);
1643        });
1644
1645        // - Ensure the error kind message is non-empty, otherwise the error is
1646        //   displayed like ": Here's my extra info" (with leading ": ")
1647        // - Ensure the error kind message doesn't end with '.', otherwise the
1648        //   error is displayed like "Service is at capacity.: Extra info"
1649        proptest!(|(kind in any::<K>())| {
1650            prop_assert!(!kind.to_msg().is_empty());
1651            prop_assert!(!kind.to_msg().ends_with('.'));
1652        });
1653    }
1654
1655    pub fn assert_api_error_invariants<E, K>()
1656    where
1657        E: ApiError + Arbitrary + PartialEq,
1658        K: ApiErrorKind + Arbitrary,
1659    {
1660        // Double roundtrip proptest
1661        // - ApiError -> ErrorResponse -> ApiError
1662        // - ErrorResponse -> ApiError -> ErrorResponse
1663        // i.e. The errors should be equal in serialized & unserialized form.
1664        proptest!(|(e1 in any::<E>())| {
1665            let err_resp1 = Into::<ErrorResponse>::into(e1.clone());
1666            let e2 = E::from(err_resp1.clone());
1667            let err_resp2 = Into::<ErrorResponse>::into(e2.clone());
1668            prop_assert_eq!(e1, e2);
1669            prop_assert_eq!(err_resp1, err_resp2);
1670        });
1671
1672        // Check that the ApiError Display impl is of form
1673        // `<kind_msg>: <main_msg>`
1674        //
1675        // NOTE: We used to prefix with `[<code>=<kind_name>]` like
1676        // "[106=Command]", but this was not helpful, so we removed it.
1677        proptest!(|(
1678            kind in any::<K>(),
1679            main_msg in arbitrary::any_string()
1680        )| {
1681            let code = kind.to_code();
1682            let msg = main_msg.clone();
1683            // Insert structured data which should not appear in the output
1684            let data = serde_json::Value::String(String::from("dummy"));
1685            let sensitive = false;
1686            let err_resp = ErrorResponse { code, msg, data, sensitive };
1687            let api_error = E::from(err_resp);
1688            let kind_msg = kind.to_msg();
1689
1690            let actual_display = format!("{api_error}");
1691            let expected_display =
1692                format!("{kind_msg}: {main_msg}");
1693            prop_assert_eq!(actual_display, expected_display);
1694        });
1695    }
1696}
1697
1698// --- Tests --- //
1699
1700#[cfg(test)]
1701mod test {
1702    use lexe_common::test_utils::roundtrip;
1703    use proptest::{prelude::any, prop_assert_eq, proptest};
1704
1705    use super::*;
1706
1707    #[test]
1708    fn client_error_kinds_non_zero() {
1709        for kind in CommonErrorKind::KINDS {
1710            assert_ne!(kind.to_code(), 0);
1711        }
1712    }
1713
1714    #[test]
1715    fn error_kind_invariants() {
1716        invariants::assert_error_kind_invariants::<BackendErrorKind>();
1717        invariants::assert_error_kind_invariants::<GatewayErrorKind>();
1718        invariants::assert_error_kind_invariants::<LspErrorKind>();
1719        invariants::assert_error_kind_invariants::<MegaErrorKind>();
1720        invariants::assert_error_kind_invariants::<NodeErrorKind>();
1721        invariants::assert_error_kind_invariants::<RunnerErrorKind>();
1722        invariants::assert_error_kind_invariants::<SdkErrorKind>();
1723    }
1724
1725    #[test]
1726    fn api_error_invariants() {
1727        use invariants::assert_api_error_invariants;
1728        assert_api_error_invariants::<BackendApiError, BackendErrorKind>();
1729        assert_api_error_invariants::<GatewayApiError, GatewayErrorKind>();
1730        assert_api_error_invariants::<LspApiError, LspErrorKind>();
1731        assert_api_error_invariants::<MegaApiError, MegaErrorKind>();
1732        assert_api_error_invariants::<NodeApiError, NodeErrorKind>();
1733        assert_api_error_invariants::<RunnerApiError, RunnerErrorKind>();
1734        assert_api_error_invariants::<SdkApiError, SdkErrorKind>();
1735    }
1736
1737    #[test]
1738    fn node_lsp_command_error_is_concise() {
1739        let err1 = format!("{:#}", NodeApiError::command("Oops!"));
1740        let err2 = format!("{:#}", LspApiError::command("Oops!"));
1741
1742        assert_eq!(err1, "Error: Oops!");
1743        assert_eq!(err2, "Error: Oops!");
1744    }
1745
1746    #[test]
1747    fn error_response_serde_roundtrip() {
1748        roundtrip::json_value_roundtrip_proptest::<ErrorResponse>();
1749    }
1750
1751    /// Check that we can deserialize old [`ErrorResponse`]s.
1752    #[test]
1753    fn error_response_compat() {
1754        /// The old version of [`ErrorResponse`].
1755        #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1756        #[derive(Arbitrary)]
1757        pub struct OldErrorResponse {
1758            pub code: ErrorCode,
1759            #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
1760            pub msg: String,
1761        }
1762
1763        proptest!(|(old in any::<OldErrorResponse>())| {
1764            let json_str = serde_json::to_string(&old).unwrap();
1765            let new =
1766                serde_json::from_str::<ErrorResponse>(&json_str).unwrap();
1767            prop_assert_eq!(old.code, new.code);
1768            prop_assert_eq!(old.msg, new.msg);
1769            prop_assert_eq!(new.data, serde_json::Value::Null);
1770            prop_assert_eq!(new.sensitive, false);
1771        });
1772    }
1773}