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