1#![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
28pub 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
42pub type ErrorCode = u16;
44
45#[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 #[cfg_attr(
64 any(test, feature = "test-utils"),
65 proptest(strategy = "arbitrary::any_json_value()")
66 )]
67 #[serde(default)] pub data: serde_json::Value,
69
70 #[serde(default)] pub sensitive: bool,
75}
76
77pub 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
99pub 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 const KINDS: &'static [Self];
119
120 fn is_unknown(&self) -> bool;
123
124 fn to_name(self) -> &'static str;
128
129 fn to_msg(self) -> &'static str;
132
133 fn to_code(self) -> ErrorCode;
135
136 fn from_code(code: ErrorCode) -> Self;
141}
142
143pub trait ToHttpStatus {
145 fn to_http_status(&self) -> StatusCode;
146}
147
148#[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 pub data: D,
175 pub sensitive: bool,
179 }
180
181 impl $api_error {
182 #[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 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 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#[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 $( #[doc = $item_msg:literal] )*
356 $item_name:ident = $item_code:literal
357 ),*
358
359 $(,)?
360 }
361 } => { $(#[$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 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 #[deny(unreachable_patterns)]
411 match code {
412 0 => Self::Unknown(0),
415 $( $item_code => Self::$item_name, )*
416 _ => Self::Unknown(code),
417 }
418 }
419 }
420
421 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 write!(f, "{msg}")
438 }
439 }
440
441 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> for $error_kind_name {
460 #[inline]
461 fn from(common: CommonErrorKind) -> Self {
462 Self::from_code(common.to_code())
465 }
466 }
467
468 #[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 prop_oneof![
485 9 => sample::select(Self::KINDS),
486 1 => any::<ErrorCode>().prop_map(Self::from_code),
487 ].boxed()
488 }
489 }
490 }
491}
492
493pub struct CommonApiError {
503 pub kind: CommonErrorKind,
504 pub msg: String,
505 }
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#[derive(Copy, Clone, Debug)]
520#[repr(u16)]
521pub enum CommonErrorKind {
522 UnknownReqwest = 1,
524 Building = 2,
526 Connect = 3,
528 Timeout = 4,
530 Decode = 5,
532 Server = 6,
534 Rejection = 7,
536 AtCapacity = 8,
538 }
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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
560 pub enum BackendErrorKind {
561 Unknown(ErrorCode),
563
564 UnknownReqwest = 1,
568 Building = 2,
570 Connect = 3,
572 Timeout = 4,
574 Decode = 5,
576 Server = 6,
578 Rejection = 7,
580 AtCapacity = 8,
582
583 Database = 100,
587 NotFound = 101,
589 Duplicate = 102,
591 Conversion = 103,
593 Unauthenticated = 104,
595 Unauthorized = 105,
597 AuthExpired = 106,
599 InvalidParsedRequest = 107,
601 BatchSizeOverLimit = 108,
603 NotUpdatable = 109,
605 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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
643 pub enum GatewayErrorKind {
644 Unknown(ErrorCode),
646
647 UnknownReqwest = 1,
651 Building = 2,
653 Connect = 3,
655 Timeout = 4,
657 Decode = 5,
659 Server = 6,
661 Rejection = 7,
663 AtCapacity = 8,
665
666 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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
696 pub enum LspErrorKind {
697 Unknown(ErrorCode),
699
700 UnknownReqwest = 1,
704 Building = 2,
706 Connect = 3,
708 Timeout = 4,
710 Decode = 5,
712 Server = 6,
714 Rejection = 7,
716 AtCapacity = 8,
718
719 Provision = 100,
723 Scid = 101,
725 Command = 102,
729 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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
760 pub enum MegaErrorKind {
761 Unknown(ErrorCode),
763
764 UnknownReqwest = 1,
768 Building = 2,
770 Connect = 3,
772 Timeout = 4,
774 Decode = 5,
776 Server = 6,
778 Rejection = 7,
780 AtCapacity = 8,
782
783 WrongMegaId = 100,
787 RunnerUnreachable = 101,
789 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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
819 pub enum NodeErrorKind {
820 Unknown(ErrorCode),
822
823 UnknownReqwest = 1,
827 Building = 2,
829 Connect = 3,
831 Timeout = 4,
833 Decode = 5,
835 Server = 6,
837 Rejection = 7,
839 AtCapacity = 8,
841
842 WrongUserPk = 100,
846 WrongNodePk = 101,
848 WrongMeasurement = 102,
850 Provision = 103,
852 BadAuth = 104,
854 Proxy = 105,
856 Command = 106,
860 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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
895 pub enum RunnerErrorKind {
896 Unknown(ErrorCode),
898
899 UnknownReqwest = 1,
903 Building = 2,
905 Connect = 3,
907 Timeout = 4,
909 Decode = 5,
911 Server = 6,
913 Rejection = 7,
915 AtCapacity = 8,
917
918 Runner = 100,
922 UnknownMeasurement = 101,
925 OldVersion = 102,
927 TemporarilyUnavailable = 103,
930 ServiceUnavailable = 104,
932 Boot = 106,
934 EvictionFailure = 107,
936 UnknownUser = 108,
938 LeaseExpired = 109,
940 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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
977 pub enum SdkErrorKind {
978 Unknown(ErrorCode),
980
981 UnknownReqwest = 1,
985 Building = 2,
987 Connect = 3,
989 Timeout = 4,
991 Decode = 5,
993 Server = 6,
995 Rejection = 7,
997 AtCapacity = 8,
999
1000 Command = 100,
1006 BadAuth = 101,
1008 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
1035impl 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 #[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 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 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 let msg = format!("{err:?}");
1109 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 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 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
1148impl 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 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 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 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
1561pub mod error_response {}
1564
1565pub 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#[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 assert!(K::from_code(0).is_unknown());
1602 assert!(K::default().is_unknown());
1603
1604 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 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 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 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 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 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 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 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#[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 #[test]
1765 fn error_response_compat() {
1766 #[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}