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 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
40pub type ErrorCode = u16;
42
43#[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 #[cfg_attr(
62 any(test, feature = "test-utils"),
63 proptest(strategy = "arbitrary::any_json_value()")
64 )]
65 #[serde(default)] pub data: serde_json::Value,
67
68 #[serde(default)] pub sensitive: bool,
73}
74
75pub 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
97pub 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 const KINDS: &'static [Self];
117
118 fn is_unknown(&self) -> bool;
121
122 fn to_name(self) -> &'static str;
126
127 fn to_msg(self) -> &'static str;
130
131 fn to_code(self) -> ErrorCode;
133
134 fn from_code(code: ErrorCode) -> Self;
139}
140
141pub trait ToHttpStatus {
143 fn to_http_status(&self) -> StatusCode;
144}
145
146#[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 pub data: D,
173 pub sensitive: bool,
177 }
178
179 impl $api_error {
180 #[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 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 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#[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 $( #[doc = $item_msg:literal] )*
354 $item_name:ident = $item_code:literal
355 ),*
356
357 $(,)?
358 }
359 } => { $(#[$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 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 #[deny(unreachable_patterns)]
409 match code {
410 0 => Self::Unknown(0),
413 $( $item_code => Self::$item_name, )*
414 _ => Self::Unknown(code),
415 }
416 }
417 }
418
419 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 write!(f, "{msg}")
436 }
437 }
438
439 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> for $error_kind_name {
458 #[inline]
459 fn from(common: CommonErrorKind) -> Self {
460 Self::from_code(common.to_code())
463 }
464 }
465
466 #[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 prop_oneof![
483 9 => sample::select(Self::KINDS),
484 1 => any::<ErrorCode>().prop_map(Self::from_code),
485 ].boxed()
486 }
487 }
488 }
489}
490
491pub struct CommonApiError {
501 pub kind: CommonErrorKind,
502 pub msg: String,
503 }
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#[derive(Copy, Clone, Debug)]
518#[repr(u16)]
519pub enum CommonErrorKind {
520 UnknownReqwest = 1,
522 Building = 2,
524 Connect = 3,
526 Timeout = 4,
528 Decode = 5,
530 Server = 6,
532 Rejection = 7,
534 AtCapacity = 8,
536 }
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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
558 pub enum BackendErrorKind {
559 Unknown(ErrorCode),
561
562 UnknownReqwest = 1,
566 Building = 2,
568 Connect = 3,
570 Timeout = 4,
572 Decode = 5,
574 Server = 6,
576 Rejection = 7,
578 AtCapacity = 8,
580
581 Database = 100,
585 NotFound = 101,
587 Duplicate = 102,
589 Conversion = 103,
591 Unauthenticated = 104,
593 Unauthorized = 105,
595 AuthExpired = 106,
597 InvalidParsedRequest = 107,
599 BatchSizeOverLimit = 108,
601 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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
638 pub enum GatewayErrorKind {
639 Unknown(ErrorCode),
641
642 UnknownReqwest = 1,
646 Building = 2,
648 Connect = 3,
650 Timeout = 4,
652 Decode = 5,
654 Server = 6,
656 Rejection = 7,
658 AtCapacity = 8,
660
661 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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
691 pub enum LspErrorKind {
692 Unknown(ErrorCode),
694
695 UnknownReqwest = 1,
699 Building = 2,
701 Connect = 3,
703 Timeout = 4,
705 Decode = 5,
707 Server = 6,
709 Rejection = 7,
711 AtCapacity = 8,
713
714 Provision = 100,
718 Scid = 101,
720 Command = 102,
724 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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
755 pub enum MegaErrorKind {
756 Unknown(ErrorCode),
758
759 UnknownReqwest = 1,
763 Building = 2,
765 Connect = 3,
767 Timeout = 4,
769 Decode = 5,
771 Server = 6,
773 Rejection = 7,
775 AtCapacity = 8,
777
778 WrongMegaId = 100,
782 RunnerUnreachable = 101,
784 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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
814 pub enum NodeErrorKind {
815 Unknown(ErrorCode),
817
818 UnknownReqwest = 1,
822 Building = 2,
824 Connect = 3,
826 Timeout = 4,
828 Decode = 5,
830 Server = 6,
832 Rejection = 7,
834 AtCapacity = 8,
836
837 WrongUserPk = 100,
841 WrongNodePk = 101,
843 WrongMeasurement = 102,
845 Provision = 103,
847 BadAuth = 104,
849 Proxy = 105,
851 Command = 106,
855 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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
890 pub enum RunnerErrorKind {
891 Unknown(ErrorCode),
893
894 UnknownReqwest = 1,
898 Building = 2,
900 Connect = 3,
902 Timeout = 4,
904 Decode = 5,
906 Server = 6,
908 Rejection = 7,
910 AtCapacity = 8,
912
913 Runner = 100,
917 UnknownMeasurement = 101,
920 OldVersion = 102,
922 TemporarilyUnavailable = 103,
925 ServiceUnavailable = 104,
927 Boot = 106,
929 EvictionFailure = 107,
931 UnknownUser = 108,
933 LeaseExpired = 109,
935 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 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
972 pub enum SdkErrorKind {
973 Unknown(ErrorCode),
975
976 UnknownReqwest = 1,
980 Building = 2,
982 Connect = 3,
984 Timeout = 4,
986 Decode = 5,
988 Server = 6,
990 Rejection = 7,
992 AtCapacity = 8,
994
995 Command = 100,
1001 BadAuth = 101,
1003 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
1030impl 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 #[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 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 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 let msg = format!("{err:?}");
1104 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 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 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
1143impl 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 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 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
1534pub mod error_response {}
1537
1538pub 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#[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 assert!(K::from_code(0).is_unknown());
1575 assert!(K::default().is_unknown());
1576
1577 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 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 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 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 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 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 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 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#[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 #[test]
1738 fn error_response_compat() {
1739 #[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}