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