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            auth::Error::NotYetValid => BackendErrorKind::AuthExpired,
1268            _ => BackendErrorKind::Unauthenticated,
1269        };
1270        let msg = format!("{error:#}");
1271        Self {
1272            kind,
1273            msg,
1274            ..Default::default()
1275        }
1276    }
1277}
1278
1279impl GatewayApiError {
1280    pub fn fiat_rates_missing() -> Self {
1281        let kind = GatewayErrorKind::FiatRatesMissing;
1282        let msg = kind.to_string();
1283        Self {
1284            kind,
1285            msg,
1286            ..Default::default()
1287        }
1288    }
1289
1290    /// Wrap a backend error returned from a request made on the user's behalf.
1291    pub fn backend(error: impl fmt::Display) -> Self {
1292        let msg = format!("{error:#}");
1293        let kind = GatewayErrorKind::Server;
1294        Self {
1295            kind,
1296            msg,
1297            ..Default::default()
1298        }
1299    }
1300}
1301
1302impl LspApiError {
1303    pub fn provision(error: impl fmt::Display) -> Self {
1304        let msg = format!("{error:#}");
1305        let kind = LspErrorKind::Provision;
1306        Self {
1307            kind,
1308            msg,
1309            ..Default::default()
1310        }
1311    }
1312
1313    pub fn scid(error: impl fmt::Display) -> Self {
1314        let msg = format!("{error:#}");
1315        let kind = LspErrorKind::Scid;
1316        Self {
1317            kind,
1318            msg,
1319            ..Default::default()
1320        }
1321    }
1322
1323    pub fn command(error: impl fmt::Display) -> Self {
1324        let msg = format!("{error:#}");
1325        let kind = LspErrorKind::Command;
1326        Self {
1327            kind,
1328            msg,
1329            ..Default::default()
1330        }
1331    }
1332
1333    pub fn rejection(error: impl fmt::Display) -> Self {
1334        let msg = format!("{error:#}");
1335        let kind = LspErrorKind::Rejection;
1336        Self {
1337            kind,
1338            msg,
1339            ..Default::default()
1340        }
1341    }
1342
1343    pub fn not_found(error: impl fmt::Display) -> Self {
1344        let msg = format!("{error:#}");
1345        let kind = LspErrorKind::NotFound;
1346        Self {
1347            kind,
1348            msg,
1349            ..Default::default()
1350        }
1351    }
1352}
1353
1354impl MegaApiError {
1355    pub fn at_capacity(msg: impl Into<String>) -> Self {
1356        let kind = MegaErrorKind::AtCapacity;
1357        let msg = msg.into();
1358        Self {
1359            kind,
1360            msg,
1361            ..Default::default()
1362        }
1363    }
1364
1365    pub fn wrong_mega_id(
1366        req_mega_id: &MegaId,
1367        actual_mega_id: &MegaId,
1368    ) -> Self {
1369        let kind = MegaErrorKind::WrongMegaId;
1370        let msg = format!("Req: {req_mega_id}, Actual: {actual_mega_id}");
1371        Self {
1372            kind,
1373            msg,
1374            ..Default::default()
1375        }
1376    }
1377
1378    pub fn unknown_user(user_pk: &UserPk, msg: impl fmt::Display) -> Self {
1379        Self {
1380            kind: MegaErrorKind::UnknownUser,
1381            msg: format!("{user_pk}: {msg}"),
1382            ..Default::default()
1383        }
1384    }
1385}
1386
1387impl NodeApiError {
1388    pub fn wrong_user_pk(current_pk: UserPk, given_pk: UserPk) -> Self {
1389        // We don't name these 'expected' and 'actual' because the meaning of
1390        // those terms is swapped depending on if you're the server or client.
1391        let msg =
1392            format!("Node has UserPk '{current_pk}' but received '{given_pk}'");
1393        let kind = NodeErrorKind::WrongUserPk;
1394        Self {
1395            kind,
1396            msg,
1397            ..Default::default()
1398        }
1399    }
1400
1401    pub fn wrong_node_pk(derived_pk: NodePk, given_pk: NodePk) -> Self {
1402        // We don't name these 'expected' and 'actual' because the meaning of
1403        // those terms is swapped depending on if you're the server or client.
1404        let msg =
1405            format!("Derived NodePk '{derived_pk}' but received '{given_pk}'");
1406        let kind = NodeErrorKind::WrongNodePk;
1407        Self {
1408            kind,
1409            msg,
1410            ..Default::default()
1411        }
1412    }
1413
1414    pub fn wrong_measurement(
1415        req_measurement: &Measurement,
1416        actual_measurement: &Measurement,
1417    ) -> Self {
1418        let kind = NodeErrorKind::WrongMeasurement;
1419        let msg =
1420            format!("Req: {req_measurement}, Actual: {actual_measurement}");
1421        Self {
1422            kind,
1423            msg,
1424            ..Default::default()
1425        }
1426    }
1427
1428    pub fn proxy(error: impl fmt::Display) -> Self {
1429        let msg = format!("{error:#}");
1430        let kind = NodeErrorKind::Proxy;
1431        Self {
1432            kind,
1433            msg,
1434            ..Default::default()
1435        }
1436    }
1437
1438    pub fn provision(error: impl fmt::Display) -> Self {
1439        let msg = format!("{error:#}");
1440        let kind = NodeErrorKind::Provision;
1441        Self {
1442            kind,
1443            msg,
1444            ..Default::default()
1445        }
1446    }
1447
1448    pub fn command(error: impl fmt::Display) -> Self {
1449        let msg = format!("{error:#}");
1450        let kind = NodeErrorKind::Command;
1451        Self {
1452            kind,
1453            msg,
1454            ..Default::default()
1455        }
1456    }
1457
1458    pub fn bad_auth(error: impl fmt::Display) -> Self {
1459        let msg = format!("{error:#}");
1460        let kind = NodeErrorKind::BadAuth;
1461        Self {
1462            kind,
1463            msg,
1464            ..Default::default()
1465        }
1466    }
1467
1468    pub fn not_found(error: impl fmt::Display) -> Self {
1469        let msg = format!("{error:#}");
1470        let kind = NodeErrorKind::NotFound;
1471        Self {
1472            kind,
1473            msg,
1474            ..Default::default()
1475        }
1476    }
1477}
1478
1479impl RunnerApiError {
1480    pub fn at_capacity(error: impl fmt::Display) -> Self {
1481        let kind = RunnerErrorKind::AtCapacity;
1482        let msg = format!("{error:#}");
1483        Self {
1484            kind,
1485            msg,
1486            ..Default::default()
1487        }
1488    }
1489
1490    pub fn temporarily_unavailable(error: impl fmt::Display) -> Self {
1491        let kind = RunnerErrorKind::TemporarilyUnavailable;
1492        let msg = format!("{error:#}");
1493        Self {
1494            kind,
1495            msg,
1496            ..Default::default()
1497        }
1498    }
1499
1500    pub fn service_unavailable(error: impl fmt::Display) -> Self {
1501        let kind = RunnerErrorKind::ServiceUnavailable;
1502        let msg = format!("{error:#}");
1503        Self {
1504            kind,
1505            msg,
1506            ..Default::default()
1507        }
1508    }
1509
1510    pub fn unknown_measurement(measurement: enclave::Measurement) -> Self {
1511        let kind = RunnerErrorKind::UnknownMeasurement;
1512        let msg = format!("{measurement}");
1513        Self {
1514            kind,
1515            msg,
1516            ..Default::default()
1517        }
1518    }
1519
1520    pub fn unknown_user(user_pk: &UserPk, msg: impl fmt::Display) -> Self {
1521        Self {
1522            kind: RunnerErrorKind::UnknownUser,
1523            msg: format!("{user_pk}: {msg}"),
1524            ..Default::default()
1525        }
1526    }
1527}
1528
1529impl SdkApiError {
1530    pub fn command(error: impl fmt::Display) -> Self {
1531        let msg = format!("{error:#}");
1532        let kind = SdkErrorKind::Command;
1533        Self {
1534            kind,
1535            msg,
1536            ..Default::default()
1537        }
1538    }
1539
1540    pub fn bad_auth(error: impl fmt::Display) -> Self {
1541        let msg = format!("{error:#}");
1542        let kind = SdkErrorKind::BadAuth;
1543        Self {
1544            kind,
1545            msg,
1546            ..Default::default()
1547        }
1548    }
1549
1550    pub fn not_found(error: impl fmt::Display) -> Self {
1551        let msg = format!("{error:#}");
1552        let kind = SdkErrorKind::NotFound;
1553        Self {
1554            kind,
1555            msg,
1556            ..Default::default()
1557        }
1558    }
1559}
1560
1561// --- Build JSON response --- //
1562
1563pub mod error_response {}
1564
1565// --- Misc error utilities --- //
1566
1567/// Converts a [`Vec<anyhow::Result<()>>`] to an [`anyhow::Result<()>`],
1568/// with any error messages joined by a semicolon.
1569pub fn join_results(results: Vec<anyhow::Result<()>>) -> anyhow::Result<()> {
1570    let errors = results
1571        .into_iter()
1572        .filter_map(|res| match res {
1573            Ok(_) => None,
1574            Err(e) => Some(format!("{e:#}")),
1575        })
1576        .collect::<Vec<String>>();
1577    if errors.is_empty() {
1578        Ok(())
1579    } else {
1580        let joined_errs = errors.join("; ");
1581        Err(anyhow!("{joined_errs}"))
1582    }
1583}
1584
1585// --- Test utils for asserting error invariants --- //
1586
1587#[cfg(any(test, feature = "test-utils"))]
1588pub mod invariants {
1589    use proptest::{
1590        arbitrary::{Arbitrary, any},
1591        prop_assert, prop_assert_eq, proptest,
1592    };
1593
1594    use super::*;
1595
1596    pub fn assert_error_kind_invariants<K>()
1597    where
1598        K: ApiErrorKind + Arbitrary,
1599    {
1600        // error code 0 and default error code must be unknown
1601        assert!(K::from_code(0).is_unknown());
1602        assert!(K::default().is_unknown());
1603
1604        // CommonErrorKind is a strict subset of ApiErrorKind
1605        //
1606        // CommonErrorKind [ _, 1, 2, 3, 4, 5, 6 ]
1607        //    ApiErrorKind [ _, 1, 2, 3, 4, 5,   , 100, 101 ]
1608        //                                     ^
1609        //                                    BAD
1610        for common_kind in CommonErrorKind::KINDS {
1611            let common_code = common_kind.to_code();
1612            let common_status = common_kind.to_http_status();
1613            let api_kind = K::from_code(common_kind.to_code());
1614            let api_code = api_kind.to_code();
1615            let api_status = api_kind.to_http_status();
1616            assert_eq!(common_code, api_code, "Error codes must match");
1617            assert_eq!(common_status, api_status, "HTTP statuses must match");
1618
1619            if api_kind.is_unknown() {
1620                panic!(
1621                    "all CommonErrorKind's should be covered; \
1622                     missing common code: {common_code}, \
1623                     common kind: {common_kind:?}",
1624                );
1625            }
1626        }
1627
1628        // error kind enum isomorphic to error code representation
1629        // kind -> code -> kind2 -> code2
1630        for kind in K::KINDS {
1631            let code = kind.to_code();
1632            let kind2 = K::from_code(code);
1633            let code2 = kind2.to_code();
1634            assert_eq!(code, code2);
1635            assert_eq!(kind, &kind2);
1636        }
1637
1638        // try the first 200 error codes to ensure isomorphic
1639        // code -> kind -> code2 -> kind2
1640        for code in 0_u16..200 {
1641            let kind = K::from_code(code);
1642            let code2 = kind.to_code();
1643            let kind2 = K::from_code(code2);
1644            assert_eq!(code, code2);
1645            assert_eq!(kind, kind2);
1646        }
1647
1648        // ensure proptest generator is also well-behaved
1649        proptest!(|(kind in any::<K>())| {
1650            let code = kind.to_code();
1651            let kind2 = K::from_code(code);
1652            let code2 = kind2.to_code();
1653            prop_assert_eq!(code, code2);
1654            prop_assert_eq!(kind, kind2);
1655        });
1656
1657        // - Ensure the error kind message is non-empty, otherwise the error is
1658        //   displayed like ": Here's my extra info" (with leading ": ")
1659        // - Ensure the error kind message doesn't end with '.', otherwise the
1660        //   error is displayed like "Service is at capacity.: Extra info"
1661        proptest!(|(kind in any::<K>())| {
1662            prop_assert!(!kind.to_msg().is_empty());
1663            prop_assert!(!kind.to_msg().ends_with('.'));
1664        });
1665    }
1666
1667    pub fn assert_api_error_invariants<E, K>()
1668    where
1669        E: ApiError + Arbitrary + PartialEq,
1670        K: ApiErrorKind + Arbitrary,
1671    {
1672        // Double roundtrip proptest
1673        // - ApiError -> ErrorResponse -> ApiError
1674        // - ErrorResponse -> ApiError -> ErrorResponse
1675        // i.e. The errors should be equal in serialized & unserialized form.
1676        proptest!(|(e1 in any::<E>())| {
1677            let err_resp1 = Into::<ErrorResponse>::into(e1.clone());
1678            let e2 = E::from(err_resp1.clone());
1679            let err_resp2 = Into::<ErrorResponse>::into(e2.clone());
1680            prop_assert_eq!(e1, e2);
1681            prop_assert_eq!(err_resp1, err_resp2);
1682        });
1683
1684        // Check that the ApiError Display impl is of form
1685        // `<kind_msg>: <main_msg>`
1686        //
1687        // NOTE: We used to prefix with `[<code>=<kind_name>]` like
1688        // "[106=Command]", but this was not helpful, so we removed it.
1689        proptest!(|(
1690            kind in any::<K>(),
1691            main_msg in arbitrary::any_string()
1692        )| {
1693            let code = kind.to_code();
1694            let msg = main_msg.clone();
1695            // Insert structured data which should not appear in the output
1696            let data = serde_json::Value::String(String::from("dummy"));
1697            let sensitive = false;
1698            let err_resp = ErrorResponse { code, msg, data, sensitive };
1699            let api_error = E::from(err_resp);
1700            let kind_msg = kind.to_msg();
1701
1702            let actual_display = format!("{api_error}");
1703            let expected_display =
1704                format!("{kind_msg}: {main_msg}");
1705            prop_assert_eq!(actual_display, expected_display);
1706        });
1707    }
1708}
1709
1710// --- Tests --- //
1711
1712#[cfg(test)]
1713mod test {
1714    use lexe_common::test_utils::roundtrip;
1715    use proptest::{prelude::any, prop_assert_eq, proptest};
1716
1717    use super::*;
1718
1719    #[test]
1720    fn client_error_kinds_non_zero() {
1721        for kind in CommonErrorKind::KINDS {
1722            assert_ne!(kind.to_code(), 0);
1723        }
1724    }
1725
1726    #[test]
1727    fn error_kind_invariants() {
1728        invariants::assert_error_kind_invariants::<BackendErrorKind>();
1729        invariants::assert_error_kind_invariants::<GatewayErrorKind>();
1730        invariants::assert_error_kind_invariants::<LspErrorKind>();
1731        invariants::assert_error_kind_invariants::<MegaErrorKind>();
1732        invariants::assert_error_kind_invariants::<NodeErrorKind>();
1733        invariants::assert_error_kind_invariants::<RunnerErrorKind>();
1734        invariants::assert_error_kind_invariants::<SdkErrorKind>();
1735    }
1736
1737    #[test]
1738    fn api_error_invariants() {
1739        use invariants::assert_api_error_invariants;
1740        assert_api_error_invariants::<BackendApiError, BackendErrorKind>();
1741        assert_api_error_invariants::<GatewayApiError, GatewayErrorKind>();
1742        assert_api_error_invariants::<LspApiError, LspErrorKind>();
1743        assert_api_error_invariants::<MegaApiError, MegaErrorKind>();
1744        assert_api_error_invariants::<NodeApiError, NodeErrorKind>();
1745        assert_api_error_invariants::<RunnerApiError, RunnerErrorKind>();
1746        assert_api_error_invariants::<SdkApiError, SdkErrorKind>();
1747    }
1748
1749    #[test]
1750    fn node_lsp_command_error_is_concise() {
1751        let err1 = format!("{:#}", NodeApiError::command("Oops!"));
1752        let err2 = format!("{:#}", LspApiError::command("Oops!"));
1753
1754        assert_eq!(err1, "Error: Oops!");
1755        assert_eq!(err2, "Error: Oops!");
1756    }
1757
1758    #[test]
1759    fn error_response_serde_roundtrip() {
1760        roundtrip::json_value_roundtrip_proptest::<ErrorResponse>();
1761    }
1762
1763    /// Check that we can deserialize old [`ErrorResponse`]s.
1764    #[test]
1765    fn error_response_compat() {
1766        /// The old version of [`ErrorResponse`].
1767        #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1768        #[derive(Arbitrary)]
1769        pub struct OldErrorResponse {
1770            pub code: ErrorCode,
1771            #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
1772            pub msg: String,
1773        }
1774
1775        proptest!(|(old in any::<OldErrorResponse>())| {
1776            let json_str = serde_json::to_string(&old).unwrap();
1777            let new =
1778                serde_json::from_str::<ErrorResponse>(&json_str).unwrap();
1779            prop_assert_eq!(old.code, new.code);
1780            prop_assert_eq!(old.msg, new.msg);
1781            prop_assert_eq!(new.data, serde_json::Value::Null);
1782            prop_assert_eq!(new.sensitive, false);
1783        });
1784    }
1785}