1use serde::{Deserialize, Serialize};
6use serde_json::{Value, json};
7use std::fmt;
8
9pub type WaeResult<T> = Result<T, WaeError>;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct WaeError {
17 pub kind: Box<WaeErrorKind>,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25pub enum ErrorCategory {
26 Validation,
28 Auth,
30 Permission,
32 NotFound,
34 Conflict,
36 RateLimited,
38 Network,
40 Storage,
42 Database,
44 Cache,
46 Config,
48 Timeout,
50 Internal,
52}
53
54impl ErrorCategory {
55 pub fn http_status(&self) -> u16 {
57 match self {
58 ErrorCategory::Validation => 400,
59 ErrorCategory::Auth => 401,
60 ErrorCategory::Permission => 403,
61 ErrorCategory::NotFound => 404,
62 ErrorCategory::Conflict => 409,
63 ErrorCategory::RateLimited => 429,
64 ErrorCategory::Network => 502,
65 ErrorCategory::Storage => 500,
66 ErrorCategory::Database => 500,
67 ErrorCategory::Cache => 500,
68 ErrorCategory::Config => 500,
69 ErrorCategory::Timeout => 408,
70 ErrorCategory::Internal => 500,
71 }
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
79pub enum WaeErrorKind {
80 InvalidFormat {
83 field: String,
85 expected: String,
87 },
88 OutOfRange {
90 field: String,
92 min: Option<String>,
94 max: Option<String>,
96 },
97 Required {
99 field: String,
101 },
102 AlreadyExists {
104 field: String,
106 value: String,
108 },
109 NotAllowed {
111 field: String,
113 allowed: Vec<String>,
115 },
116 InvalidParams {
118 param: String,
120 reason: String,
122 },
123
124 InvalidCredentials,
127 AccountLocked,
129 UserNotFound {
131 identifier: String,
133 },
134 UserAlreadyExists {
136 username: String,
138 },
139 InvalidToken {
141 reason: String,
143 },
144 TokenExpired,
146 TokenNotValidYet,
148 InvalidSignature,
150 InvalidAlgorithm,
152 MissingClaim {
154 claim: String,
156 },
157 InvalidClaim {
159 claim: String,
161 },
162 InvalidIssuer {
164 expected: String,
166 actual: String,
168 },
169 InvalidAudience,
171 KeyError,
173 EncodingError,
175 DecodingError,
177
178 InvalidAuthorizationCode,
181 InvalidRedirectUri,
183 InvalidClientId,
185 InvalidClientSecret,
187 InvalidScope {
189 scope: String,
191 },
192 InvalidAccessToken,
194 InvalidRefreshToken,
196 AccessDenied {
198 reason: String,
200 },
201 UnsupportedGrantType {
203 grant_type: String,
205 },
206 UnsupportedResponseType {
208 response_type: String,
210 },
211 StateMismatch,
213 OAuth2ProviderError {
215 message: String,
217 },
218
219 InvalidSamlRequest {
222 reason: String,
224 },
225 InvalidSamlResponse {
227 reason: String,
229 },
230 InvalidAssertion {
232 reason: String,
234 },
235 SignatureVerificationFailed {
237 reason: String,
239 },
240 MissingSignature,
242 CertificateError {
244 reason: String,
246 },
247 XmlParsingError {
249 reason: String,
251 },
252 Base64DecodeError {
254 reason: String,
256 },
257 CompressionError {
259 reason: String,
261 },
262 AssertionExpired,
264 AssertionNotYetValid,
266 SamlAudienceValidationFailed {
268 expected: String,
270 actual: String,
272 },
273 SamlIssuerValidationFailed {
275 expected: String,
277 actual: String,
279 },
280 DestinationValidationFailed,
282 ReplayAttackDetected,
284 UnsupportedBinding {
286 binding: String,
288 },
289 UnsupportedNameIdFormat {
291 format: String,
293 },
294
295 InvalidSecret {
298 reason: String,
300 },
301 InvalidCode,
303 CodeExpired,
305 InvalidCodeFormat {
307 expected: usize,
309 actual: usize,
311 },
312 InvalidTimeStep,
314 Base32Error {
316 reason: String,
318 },
319 HmacError {
321 reason: String,
323 },
324 QrCodeError {
326 reason: String,
328 },
329
330 PermissionDenied {
333 action: String,
335 },
336 Forbidden {
338 resource: String,
340 },
341
342 ResourceNotFound {
345 resource_type: String,
347 identifier: String,
349 },
350
351 ResourceConflict {
354 resource: String,
356 reason: String,
358 },
359
360 ConnectionFailed {
363 target: String,
365 },
366 DnsResolutionFailed {
368 host: String,
370 },
371 TlsError {
373 reason: String,
375 },
376 ProtocolError {
378 protocol: String,
380 reason: String,
382 },
383 ServiceUnavailable {
385 service: String,
387 },
388 GatewayError {
390 gateway: String,
392 },
393
394 StorageReadFailed {
397 path: String,
399 },
400 StorageWriteFailed {
402 path: String,
404 },
405 StorageDeleteFailed {
407 path: String,
409 },
410 InsufficientCapacity {
412 required: u64,
414 available: u64,
416 },
417 DataCorruption {
419 location: String,
421 },
422 StorageFileNotFound {
424 path: String,
426 },
427
428 DatabaseConnectionFailed {
431 reason: String,
433 },
434 QueryFailed {
436 query: Option<String>,
438 reason: String,
440 },
441 ExecuteFailed {
443 statement: Option<String>,
445 reason: String,
447 },
448 TransactionFailed {
450 reason: String,
452 },
453 MigrationFailed {
455 version: Option<String>,
457 reason: String,
459 },
460
461 CacheConnectionFailed {
464 reason: String,
466 },
467 CacheKeyNotFound {
469 key: String,
471 },
472 SerializationFailed {
474 type_name: String,
476 },
477 DeserializationFailed {
479 type_name: String,
481 },
482
483 CircuitBreakerOpen {
486 service: String,
488 },
489 RateLimitExceeded {
491 limit: u64,
493 },
494 MaxRetriesExceeded {
496 attempts: u32,
498 },
499 BulkheadFull {
501 max_concurrent: usize,
503 },
504 BulkheadWaitTimeout,
506
507 TaskNotFound {
510 task_id: String,
512 },
513 TaskAlreadyExists {
515 task_id: String,
517 },
518 InvalidCronExpression {
520 expression: String,
522 },
523 TaskExecutionFailed {
525 task_id: String,
527 reason: String,
529 },
530 SchedulerShutdown,
532
533 MockError {
536 reason: String,
538 },
539 AssertionFailed {
541 message: String,
543 },
544 FixtureError {
546 reason: String,
548 },
549 EnvironmentError {
551 reason: String,
553 },
554
555 OperationTimeout {
558 operation: String,
560 timeout_ms: u64,
562 },
563
564 ConfigMissing {
567 key: String,
569 },
570 ConfigInvalid {
572 key: String,
574 reason: String,
576 },
577
578 InternalError {
581 reason: String,
583 },
584 NotImplemented {
586 feature: String,
588 },
589 IoError {
591 operation: String,
593 reason: String,
595 },
596 JsonError {
598 reason: String,
600 },
601 ParseError {
603 type_name: String,
605 reason: String,
607 },
608 RequestError {
610 url: String,
612 reason: String,
614 },
615}
616
617impl WaeErrorKind {
618 pub fn category(&self) -> ErrorCategory {
620 match self {
621 WaeErrorKind::InvalidFormat { .. }
622 | WaeErrorKind::OutOfRange { .. }
623 | WaeErrorKind::Required { .. }
624 | WaeErrorKind::AlreadyExists { .. }
625 | WaeErrorKind::NotAllowed { .. }
626 | WaeErrorKind::InvalidParams { .. } => ErrorCategory::Validation,
627
628 WaeErrorKind::InvalidCredentials
629 | WaeErrorKind::AccountLocked
630 | WaeErrorKind::UserNotFound { .. }
631 | WaeErrorKind::UserAlreadyExists { .. }
632 | WaeErrorKind::InvalidToken { .. }
633 | WaeErrorKind::TokenExpired
634 | WaeErrorKind::TokenNotValidYet
635 | WaeErrorKind::InvalidSignature
636 | WaeErrorKind::InvalidAlgorithm
637 | WaeErrorKind::MissingClaim { .. }
638 | WaeErrorKind::InvalidClaim { .. }
639 | WaeErrorKind::InvalidIssuer { .. }
640 | WaeErrorKind::InvalidAudience
641 | WaeErrorKind::KeyError
642 | WaeErrorKind::EncodingError
643 | WaeErrorKind::DecodingError
644 | WaeErrorKind::InvalidAuthorizationCode
645 | WaeErrorKind::InvalidRedirectUri
646 | WaeErrorKind::InvalidClientId
647 | WaeErrorKind::InvalidClientSecret
648 | WaeErrorKind::InvalidScope { .. }
649 | WaeErrorKind::InvalidAccessToken
650 | WaeErrorKind::InvalidRefreshToken
651 | WaeErrorKind::AccessDenied { .. }
652 | WaeErrorKind::UnsupportedGrantType { .. }
653 | WaeErrorKind::UnsupportedResponseType { .. }
654 | WaeErrorKind::StateMismatch
655 | WaeErrorKind::OAuth2ProviderError { .. }
656 | WaeErrorKind::InvalidSamlRequest { .. }
657 | WaeErrorKind::InvalidSamlResponse { .. }
658 | WaeErrorKind::InvalidAssertion { .. }
659 | WaeErrorKind::SignatureVerificationFailed { .. }
660 | WaeErrorKind::MissingSignature
661 | WaeErrorKind::CertificateError { .. }
662 | WaeErrorKind::XmlParsingError { .. }
663 | WaeErrorKind::Base64DecodeError { .. }
664 | WaeErrorKind::CompressionError { .. }
665 | WaeErrorKind::AssertionExpired
666 | WaeErrorKind::AssertionNotYetValid
667 | WaeErrorKind::SamlAudienceValidationFailed { .. }
668 | WaeErrorKind::SamlIssuerValidationFailed { .. }
669 | WaeErrorKind::DestinationValidationFailed
670 | WaeErrorKind::ReplayAttackDetected
671 | WaeErrorKind::UnsupportedBinding { .. }
672 | WaeErrorKind::UnsupportedNameIdFormat { .. }
673 | WaeErrorKind::InvalidSecret { .. }
674 | WaeErrorKind::InvalidCode
675 | WaeErrorKind::CodeExpired
676 | WaeErrorKind::InvalidCodeFormat { .. }
677 | WaeErrorKind::InvalidTimeStep
678 | WaeErrorKind::Base32Error { .. }
679 | WaeErrorKind::HmacError { .. }
680 | WaeErrorKind::QrCodeError { .. } => ErrorCategory::Auth,
681
682 WaeErrorKind::PermissionDenied { .. } | WaeErrorKind::Forbidden { .. } => ErrorCategory::Permission,
683
684 WaeErrorKind::ResourceNotFound { .. } => ErrorCategory::NotFound,
685
686 WaeErrorKind::ResourceConflict { .. } => ErrorCategory::Conflict,
687
688 WaeErrorKind::RateLimitExceeded { .. } => ErrorCategory::RateLimited,
689
690 WaeErrorKind::ConnectionFailed { .. }
691 | WaeErrorKind::DnsResolutionFailed { .. }
692 | WaeErrorKind::TlsError { .. }
693 | WaeErrorKind::ProtocolError { .. }
694 | WaeErrorKind::ServiceUnavailable { .. }
695 | WaeErrorKind::GatewayError { .. } => ErrorCategory::Network,
696
697 WaeErrorKind::StorageReadFailed { .. }
698 | WaeErrorKind::StorageWriteFailed { .. }
699 | WaeErrorKind::StorageDeleteFailed { .. }
700 | WaeErrorKind::InsufficientCapacity { .. }
701 | WaeErrorKind::DataCorruption { .. }
702 | WaeErrorKind::StorageFileNotFound { .. } => ErrorCategory::Storage,
703
704 WaeErrorKind::DatabaseConnectionFailed { .. }
705 | WaeErrorKind::QueryFailed { .. }
706 | WaeErrorKind::ExecuteFailed { .. }
707 | WaeErrorKind::TransactionFailed { .. }
708 | WaeErrorKind::MigrationFailed { .. } => ErrorCategory::Database,
709
710 WaeErrorKind::CacheConnectionFailed { .. }
711 | WaeErrorKind::CacheKeyNotFound { .. }
712 | WaeErrorKind::SerializationFailed { .. }
713 | WaeErrorKind::DeserializationFailed { .. } => ErrorCategory::Cache,
714
715 WaeErrorKind::CircuitBreakerOpen { .. } | WaeErrorKind::BulkheadFull { .. } | WaeErrorKind::BulkheadWaitTimeout => {
716 ErrorCategory::Network
717 }
718
719 WaeErrorKind::MaxRetriesExceeded { .. } => ErrorCategory::Network,
720
721 WaeErrorKind::TaskNotFound { .. }
722 | WaeErrorKind::TaskAlreadyExists { .. }
723 | WaeErrorKind::InvalidCronExpression { .. }
724 | WaeErrorKind::TaskExecutionFailed { .. }
725 | WaeErrorKind::SchedulerShutdown => ErrorCategory::Internal,
726
727 WaeErrorKind::MockError { .. }
728 | WaeErrorKind::AssertionFailed { .. }
729 | WaeErrorKind::FixtureError { .. }
730 | WaeErrorKind::EnvironmentError { .. } => ErrorCategory::Internal,
731
732 WaeErrorKind::OperationTimeout { .. } => ErrorCategory::Timeout,
733
734 WaeErrorKind::ConfigMissing { .. } | WaeErrorKind::ConfigInvalid { .. } => ErrorCategory::Config,
735
736 WaeErrorKind::InternalError { .. }
737 | WaeErrorKind::NotImplemented { .. }
738 | WaeErrorKind::IoError { .. }
739 | WaeErrorKind::JsonError { .. }
740 | WaeErrorKind::ParseError { .. }
741 | WaeErrorKind::RequestError { .. } => ErrorCategory::Internal,
742 }
743 }
744
745 pub fn i18n_key(&self) -> &'static str {
747 match self {
748 WaeErrorKind::InvalidFormat { .. } => "wae.error.validation.invalid_format",
749 WaeErrorKind::OutOfRange { .. } => "wae.error.validation.out_of_range",
750 WaeErrorKind::Required { .. } => "wae.error.validation.required",
751 WaeErrorKind::AlreadyExists { .. } => "wae.error.validation.already_exists",
752 WaeErrorKind::NotAllowed { .. } => "wae.error.validation.not_allowed",
753 WaeErrorKind::InvalidParams { .. } => "wae.error.validation.invalid_params",
754
755 WaeErrorKind::InvalidCredentials => "wae.error.auth.invalid_credentials",
756 WaeErrorKind::AccountLocked => "wae.error.auth.account_locked",
757 WaeErrorKind::UserNotFound { .. } => "wae.error.auth.user_not_found",
758 WaeErrorKind::UserAlreadyExists { .. } => "wae.error.auth.user_already_exists",
759 WaeErrorKind::InvalidToken { .. } => "wae.error.auth.invalid_token",
760 WaeErrorKind::TokenExpired => "wae.error.auth.token_expired",
761 WaeErrorKind::TokenNotValidYet => "wae.error.auth.token_not_valid_yet",
762 WaeErrorKind::InvalidSignature => "wae.error.auth.invalid_signature",
763 WaeErrorKind::InvalidAlgorithm => "wae.error.auth.invalid_algorithm",
764 WaeErrorKind::MissingClaim { .. } => "wae.error.auth.missing_claim",
765 WaeErrorKind::InvalidClaim { .. } => "wae.error.auth.invalid_claim",
766 WaeErrorKind::InvalidIssuer { .. } => "wae.error.auth.invalid_issuer",
767 WaeErrorKind::InvalidAudience => "wae.error.auth.invalid_audience",
768 WaeErrorKind::KeyError => "wae.error.auth.key_error",
769 WaeErrorKind::EncodingError => "wae.error.auth.encoding_error",
770 WaeErrorKind::DecodingError => "wae.error.auth.decoding_error",
771
772 WaeErrorKind::InvalidAuthorizationCode => "wae.error.oauth2.invalid_authorization_code",
773 WaeErrorKind::InvalidRedirectUri => "wae.error.oauth2.invalid_redirect_uri",
774 WaeErrorKind::InvalidClientId => "wae.error.oauth2.invalid_client_id",
775 WaeErrorKind::InvalidClientSecret => "wae.error.oauth2.invalid_client_secret",
776 WaeErrorKind::InvalidScope { .. } => "wae.error.oauth2.invalid_scope",
777 WaeErrorKind::InvalidAccessToken => "wae.error.oauth2.invalid_access_token",
778 WaeErrorKind::InvalidRefreshToken => "wae.error.oauth2.invalid_refresh_token",
779 WaeErrorKind::AccessDenied { .. } => "wae.error.oauth2.access_denied",
780 WaeErrorKind::UnsupportedGrantType { .. } => "wae.error.oauth2.unsupported_grant_type",
781 WaeErrorKind::UnsupportedResponseType { .. } => "wae.error.oauth2.unsupported_response_type",
782 WaeErrorKind::StateMismatch => "wae.error.oauth2.state_mismatch",
783 WaeErrorKind::OAuth2ProviderError { .. } => "wae.error.oauth2.provider_error",
784
785 WaeErrorKind::InvalidSamlRequest { .. } => "wae.error.saml.invalid_request",
786 WaeErrorKind::InvalidSamlResponse { .. } => "wae.error.saml.invalid_response",
787 WaeErrorKind::InvalidAssertion { .. } => "wae.error.saml.invalid_assertion",
788 WaeErrorKind::SignatureVerificationFailed { .. } => "wae.error.saml.signature_verification_failed",
789 WaeErrorKind::MissingSignature => "wae.error.saml.missing_signature",
790 WaeErrorKind::CertificateError { .. } => "wae.error.saml.certificate_error",
791 WaeErrorKind::XmlParsingError { .. } => "wae.error.saml.xml_parsing_error",
792 WaeErrorKind::Base64DecodeError { .. } => "wae.error.saml.base64_decode_error",
793 WaeErrorKind::CompressionError { .. } => "wae.error.saml.compression_error",
794 WaeErrorKind::AssertionExpired => "wae.error.saml.assertion_expired",
795 WaeErrorKind::AssertionNotYetValid => "wae.error.saml.assertion_not_yet_valid",
796 WaeErrorKind::SamlAudienceValidationFailed { .. } => "wae.error.saml.audience_validation_failed",
797 WaeErrorKind::SamlIssuerValidationFailed { .. } => "wae.error.saml.issuer_validation_failed",
798 WaeErrorKind::DestinationValidationFailed => "wae.error.saml.destination_validation_failed",
799 WaeErrorKind::ReplayAttackDetected => "wae.error.saml.replay_attack_detected",
800 WaeErrorKind::UnsupportedBinding { .. } => "wae.error.saml.unsupported_binding",
801 WaeErrorKind::UnsupportedNameIdFormat { .. } => "wae.error.saml.unsupported_name_id_format",
802
803 WaeErrorKind::InvalidSecret { .. } => "wae.error.totp.invalid_secret",
804 WaeErrorKind::InvalidCode => "wae.error.totp.invalid_code",
805 WaeErrorKind::CodeExpired => "wae.error.totp.code_expired",
806 WaeErrorKind::InvalidCodeFormat { .. } => "wae.error.totp.invalid_code_format",
807 WaeErrorKind::InvalidTimeStep => "wae.error.totp.invalid_time_step",
808 WaeErrorKind::Base32Error { .. } => "wae.error.totp.base32_error",
809 WaeErrorKind::HmacError { .. } => "wae.error.totp.hmac_error",
810 WaeErrorKind::QrCodeError { .. } => "wae.error.totp.qr_code_error",
811
812 WaeErrorKind::PermissionDenied { .. } => "wae.error.permission.denied",
813 WaeErrorKind::Forbidden { .. } => "wae.error.permission.forbidden",
814
815 WaeErrorKind::ResourceNotFound { .. } => "wae.error.not_found.resource",
816
817 WaeErrorKind::ResourceConflict { .. } => "wae.error.conflict.resource",
818
819 WaeErrorKind::RateLimitExceeded { .. } => "wae.error.rate_limited",
820
821 WaeErrorKind::ConnectionFailed { .. } => "wae.error.network.connection_failed",
822 WaeErrorKind::DnsResolutionFailed { .. } => "wae.error.network.dns_resolution_failed",
823 WaeErrorKind::TlsError { .. } => "wae.error.network.tls_error",
824 WaeErrorKind::ProtocolError { .. } => "wae.error.network.protocol_error",
825 WaeErrorKind::ServiceUnavailable { .. } => "wae.error.network.service_unavailable",
826 WaeErrorKind::GatewayError { .. } => "wae.error.network.gateway_error",
827
828 WaeErrorKind::StorageReadFailed { .. } => "wae.error.storage.read_failed",
829 WaeErrorKind::StorageWriteFailed { .. } => "wae.error.storage.write_failed",
830 WaeErrorKind::StorageDeleteFailed { .. } => "wae.error.storage.delete_failed",
831 WaeErrorKind::InsufficientCapacity { .. } => "wae.error.storage.insufficient_capacity",
832 WaeErrorKind::DataCorruption { .. } => "wae.error.storage.data_corruption",
833 WaeErrorKind::StorageFileNotFound { .. } => "wae.error.storage.file_not_found",
834
835 WaeErrorKind::DatabaseConnectionFailed { .. } => "wae.error.database.connection_failed",
836 WaeErrorKind::QueryFailed { .. } => "wae.error.database.query_failed",
837 WaeErrorKind::ExecuteFailed { .. } => "wae.error.database.execute_failed",
838 WaeErrorKind::TransactionFailed { .. } => "wae.error.database.transaction_failed",
839 WaeErrorKind::MigrationFailed { .. } => "wae.error.database.migration_failed",
840
841 WaeErrorKind::CacheConnectionFailed { .. } => "wae.error.cache.connection_failed",
842 WaeErrorKind::CacheKeyNotFound { .. } => "wae.error.cache.key_not_found",
843 WaeErrorKind::SerializationFailed { .. } => "wae.error.cache.serialization_failed",
844 WaeErrorKind::DeserializationFailed { .. } => "wae.error.cache.deserialization_failed",
845
846 WaeErrorKind::CircuitBreakerOpen { .. } => "wae.error.resilience.circuit_breaker_open",
847 WaeErrorKind::MaxRetriesExceeded { .. } => "wae.error.resilience.max_retries_exceeded",
848 WaeErrorKind::BulkheadFull { .. } => "wae.error.resilience.bulkhead_full",
849 WaeErrorKind::BulkheadWaitTimeout => "wae.error.resilience.bulkhead_wait_timeout",
850
851 WaeErrorKind::TaskNotFound { .. } => "wae.error.scheduler.task_not_found",
852 WaeErrorKind::TaskAlreadyExists { .. } => "wae.error.scheduler.task_already_exists",
853 WaeErrorKind::InvalidCronExpression { .. } => "wae.error.scheduler.invalid_cron_expression",
854 WaeErrorKind::TaskExecutionFailed { .. } => "wae.error.scheduler.task_execution_failed",
855 WaeErrorKind::SchedulerShutdown => "wae.error.scheduler.shutdown",
856
857 WaeErrorKind::MockError { .. } => "wae.error.testing.mock_error",
858 WaeErrorKind::AssertionFailed { .. } => "wae.error.testing.assertion_failed",
859 WaeErrorKind::FixtureError { .. } => "wae.error.testing.fixture_error",
860 WaeErrorKind::EnvironmentError { .. } => "wae.error.testing.environment_error",
861
862 WaeErrorKind::OperationTimeout { .. } => "wae.error.timeout.operation",
863
864 WaeErrorKind::ConfigMissing { .. } => "wae.error.config.missing",
865 WaeErrorKind::ConfigInvalid { .. } => "wae.error.config.invalid",
866
867 WaeErrorKind::InternalError { .. } => "wae.error.internal.error",
868 WaeErrorKind::NotImplemented { .. } => "wae.error.internal.not_implemented",
869 WaeErrorKind::IoError { .. } => "wae.error.internal.io_error",
870 WaeErrorKind::JsonError { .. } => "wae.error.internal.json_error",
871 WaeErrorKind::ParseError { .. } => "wae.error.internal.parse_error",
872 WaeErrorKind::RequestError { .. } => "wae.error.internal.request_error",
873 }
874 }
875
876 pub fn i18n_data(&self) -> Value {
878 match self {
879 WaeErrorKind::InvalidFormat { field, expected } => {
880 json!({ "field": field, "expected": expected })
881 }
882 WaeErrorKind::OutOfRange { field, min, max } => {
883 json!({ "field": field, "min": min, "max": max })
884 }
885 WaeErrorKind::Required { field } => json!({ "field": field }),
886 WaeErrorKind::AlreadyExists { field, value } => json!({ "field": field, "value": value }),
887 WaeErrorKind::NotAllowed { field, allowed } => json!({ "field": field, "allowed": allowed }),
888 WaeErrorKind::InvalidParams { param, reason } => json!({ "param": param, "reason": reason }),
889
890 WaeErrorKind::InvalidCredentials => json!({}),
891 WaeErrorKind::AccountLocked => json!({}),
892 WaeErrorKind::UserNotFound { identifier } => json!({ "identifier": identifier }),
893 WaeErrorKind::UserAlreadyExists { username } => json!({ "username": username }),
894 WaeErrorKind::InvalidToken { reason } => json!({ "reason": reason }),
895 WaeErrorKind::TokenExpired => json!({}),
896 WaeErrorKind::TokenNotValidYet => json!({}),
897 WaeErrorKind::InvalidSignature => json!({}),
898 WaeErrorKind::InvalidAlgorithm => json!({}),
899 WaeErrorKind::MissingClaim { claim } => json!({ "claim": claim }),
900 WaeErrorKind::InvalidClaim { claim } => json!({ "claim": claim }),
901 WaeErrorKind::InvalidIssuer { expected, actual } => json!({ "expected": expected, "actual": actual }),
902 WaeErrorKind::InvalidAudience => json!({}),
903 WaeErrorKind::KeyError => json!({}),
904 WaeErrorKind::EncodingError => json!({}),
905 WaeErrorKind::DecodingError => json!({}),
906
907 WaeErrorKind::InvalidAuthorizationCode => json!({}),
908 WaeErrorKind::InvalidRedirectUri => json!({}),
909 WaeErrorKind::InvalidClientId => json!({}),
910 WaeErrorKind::InvalidClientSecret => json!({}),
911 WaeErrorKind::InvalidScope { scope } => json!({ "scope": scope }),
912 WaeErrorKind::InvalidAccessToken => json!({}),
913 WaeErrorKind::InvalidRefreshToken => json!({}),
914 WaeErrorKind::AccessDenied { reason } => json!({ "reason": reason }),
915 WaeErrorKind::UnsupportedGrantType { grant_type } => json!({ "grant_type": grant_type }),
916 WaeErrorKind::UnsupportedResponseType { response_type } => json!({ "response_type": response_type }),
917 WaeErrorKind::StateMismatch => json!({}),
918 WaeErrorKind::OAuth2ProviderError { message } => json!({ "message": message }),
919
920 WaeErrorKind::InvalidSamlRequest { reason } => json!({ "reason": reason }),
921 WaeErrorKind::InvalidSamlResponse { reason } => json!({ "reason": reason }),
922 WaeErrorKind::InvalidAssertion { reason } => json!({ "reason": reason }),
923 WaeErrorKind::SignatureVerificationFailed { reason } => json!({ "reason": reason }),
924 WaeErrorKind::MissingSignature => json!({}),
925 WaeErrorKind::CertificateError { reason } => json!({ "reason": reason }),
926 WaeErrorKind::XmlParsingError { reason } => json!({ "reason": reason }),
927 WaeErrorKind::Base64DecodeError { reason } => json!({ "reason": reason }),
928 WaeErrorKind::CompressionError { reason } => json!({ "reason": reason }),
929 WaeErrorKind::AssertionExpired => json!({}),
930 WaeErrorKind::AssertionNotYetValid => json!({}),
931 WaeErrorKind::SamlAudienceValidationFailed { expected, actual } => {
932 json!({ "expected": expected, "actual": actual })
933 }
934 WaeErrorKind::SamlIssuerValidationFailed { expected, actual } => {
935 json!({ "expected": expected, "actual": actual })
936 }
937 WaeErrorKind::DestinationValidationFailed => json!({}),
938 WaeErrorKind::ReplayAttackDetected => json!({}),
939 WaeErrorKind::UnsupportedBinding { binding } => json!({ "binding": binding }),
940 WaeErrorKind::UnsupportedNameIdFormat { format } => json!({ "format": format }),
941
942 WaeErrorKind::InvalidSecret { reason } => json!({ "reason": reason }),
943 WaeErrorKind::InvalidCode => json!({}),
944 WaeErrorKind::CodeExpired => json!({}),
945 WaeErrorKind::InvalidCodeFormat { expected, actual } => json!({ "expected": expected, "actual": actual }),
946 WaeErrorKind::InvalidTimeStep => json!({}),
947 WaeErrorKind::Base32Error { reason } => json!({ "reason": reason }),
948 WaeErrorKind::HmacError { reason } => json!({ "reason": reason }),
949 WaeErrorKind::QrCodeError { reason } => json!({ "reason": reason }),
950
951 WaeErrorKind::PermissionDenied { action } => json!({ "action": action }),
952 WaeErrorKind::Forbidden { resource } => json!({ "resource": resource }),
953
954 WaeErrorKind::ResourceNotFound { resource_type, identifier } => {
955 json!({ "resource_type": resource_type, "identifier": identifier })
956 }
957
958 WaeErrorKind::ResourceConflict { resource, reason } => json!({ "resource": resource, "reason": reason }),
959
960 WaeErrorKind::RateLimitExceeded { limit } => json!({ "limit": limit }),
961
962 WaeErrorKind::ConnectionFailed { target } => json!({ "target": target }),
963 WaeErrorKind::DnsResolutionFailed { host } => json!({ "host": host }),
964 WaeErrorKind::TlsError { reason } => json!({ "reason": reason }),
965 WaeErrorKind::ProtocolError { protocol, reason } => json!({ "protocol": protocol, "reason": reason }),
966 WaeErrorKind::ServiceUnavailable { service } => json!({ "service": service }),
967 WaeErrorKind::GatewayError { gateway } => json!({ "gateway": gateway }),
968
969 WaeErrorKind::StorageReadFailed { path } => json!({ "path": path }),
970 WaeErrorKind::StorageWriteFailed { path } => json!({ "path": path }),
971 WaeErrorKind::StorageDeleteFailed { path } => json!({ "path": path }),
972 WaeErrorKind::InsufficientCapacity { required, available } => {
973 json!({ "required": required, "available": available })
974 }
975 WaeErrorKind::DataCorruption { location } => json!({ "location": location }),
976 WaeErrorKind::StorageFileNotFound { path } => json!({ "path": path }),
977
978 WaeErrorKind::DatabaseConnectionFailed { reason } => json!({ "reason": reason }),
979 WaeErrorKind::QueryFailed { query, reason } => json!({ "query": query, "reason": reason }),
980 WaeErrorKind::ExecuteFailed { statement, reason } => json!({ "statement": statement, "reason": reason }),
981 WaeErrorKind::TransactionFailed { reason } => json!({ "reason": reason }),
982 WaeErrorKind::MigrationFailed { version, reason } => json!({ "version": version, "reason": reason }),
983
984 WaeErrorKind::CacheConnectionFailed { reason } => json!({ "reason": reason }),
985 WaeErrorKind::CacheKeyNotFound { key } => json!({ "key": key }),
986 WaeErrorKind::SerializationFailed { type_name } => json!({ "type": type_name }),
987 WaeErrorKind::DeserializationFailed { type_name } => json!({ "type": type_name }),
988
989 WaeErrorKind::CircuitBreakerOpen { service } => json!({ "service": service }),
990 WaeErrorKind::MaxRetriesExceeded { attempts } => json!({ "attempts": attempts }),
991 WaeErrorKind::BulkheadFull { max_concurrent } => json!({ "max_concurrent": max_concurrent }),
992 WaeErrorKind::BulkheadWaitTimeout => json!({}),
993
994 WaeErrorKind::TaskNotFound { task_id } => json!({ "task_id": task_id }),
995 WaeErrorKind::TaskAlreadyExists { task_id } => json!({ "task_id": task_id }),
996 WaeErrorKind::InvalidCronExpression { expression } => json!({ "expression": expression }),
997 WaeErrorKind::TaskExecutionFailed { task_id, reason } => json!({ "task_id": task_id, "reason": reason }),
998 WaeErrorKind::SchedulerShutdown => json!({}),
999
1000 WaeErrorKind::MockError { reason } => json!({ "reason": reason }),
1001 WaeErrorKind::AssertionFailed { message } => json!({ "message": message }),
1002 WaeErrorKind::FixtureError { reason } => json!({ "reason": reason }),
1003 WaeErrorKind::EnvironmentError { reason } => json!({ "reason": reason }),
1004
1005 WaeErrorKind::OperationTimeout { operation, timeout_ms } => {
1006 json!({ "operation": operation, "timeout_ms": timeout_ms })
1007 }
1008
1009 WaeErrorKind::ConfigMissing { key } => json!({ "key": key }),
1010 WaeErrorKind::ConfigInvalid { key, reason } => json!({ "key": key, "reason": reason }),
1011
1012 WaeErrorKind::InternalError { reason } => json!({ "reason": reason }),
1013 WaeErrorKind::NotImplemented { feature } => json!({ "feature": feature }),
1014 WaeErrorKind::IoError { operation, reason } => json!({ "operation": operation, "reason": reason }),
1015 WaeErrorKind::JsonError { reason } => json!({ "reason": reason }),
1016 WaeErrorKind::ParseError { type_name, reason } => json!({ "type": type_name, "reason": reason }),
1017 WaeErrorKind::RequestError { url, reason } => json!({ "url": url, "reason": reason }),
1018 }
1019 }
1020}
1021
1022impl fmt::Display for WaeErrorKind {
1023 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1024 write!(f, "{}", self.i18n_key())
1025 }
1026}
1027
1028impl WaeError {
1029 pub fn new(kind: WaeErrorKind) -> Self {
1031 Self { kind: Box::new(kind) }
1032 }
1033
1034 pub fn i18n_key(&self) -> &'static str {
1036 self.kind.i18n_key()
1037 }
1038
1039 pub fn i18n_data(&self) -> Value {
1041 self.kind.i18n_data()
1042 }
1043
1044 pub fn category(&self) -> ErrorCategory {
1046 self.kind.category()
1047 }
1048
1049 pub fn http_status(&self) -> u16 {
1051 self.category().http_status()
1052 }
1053
1054 pub fn invalid_format(field: impl Into<String>, expected: impl Into<String>) -> Self {
1058 Self::new(WaeErrorKind::InvalidFormat { field: field.into(), expected: expected.into() })
1059 }
1060
1061 pub fn out_of_range(field: impl Into<String>, min: Option<String>, max: Option<String>) -> Self {
1063 Self::new(WaeErrorKind::OutOfRange { field: field.into(), min, max })
1064 }
1065
1066 pub fn required(field: impl Into<String>) -> Self {
1068 Self::new(WaeErrorKind::Required { field: field.into() })
1069 }
1070
1071 pub fn already_exists(field: impl Into<String>, value: impl Into<String>) -> Self {
1073 Self::new(WaeErrorKind::AlreadyExists { field: field.into(), value: value.into() })
1074 }
1075
1076 pub fn not_allowed(field: impl Into<String>, allowed: Vec<String>) -> Self {
1078 Self::new(WaeErrorKind::NotAllowed { field: field.into(), allowed })
1079 }
1080
1081 pub fn invalid_params(param: impl Into<String>, reason: impl Into<String>) -> Self {
1083 Self::new(WaeErrorKind::InvalidParams { param: param.into(), reason: reason.into() })
1084 }
1085
1086 pub fn invalid_credentials() -> Self {
1090 Self::new(WaeErrorKind::InvalidCredentials)
1091 }
1092
1093 pub fn account_locked() -> Self {
1095 Self::new(WaeErrorKind::AccountLocked)
1096 }
1097
1098 pub fn user_not_found(identifier: impl Into<String>) -> Self {
1100 Self::new(WaeErrorKind::UserNotFound { identifier: identifier.into() })
1101 }
1102
1103 pub fn user_already_exists(username: impl Into<String>) -> Self {
1105 Self::new(WaeErrorKind::UserAlreadyExists { username: username.into() })
1106 }
1107
1108 pub fn invalid_token(reason: impl Into<String>) -> Self {
1110 Self::new(WaeErrorKind::InvalidToken { reason: reason.into() })
1111 }
1112
1113 pub fn token_expired() -> Self {
1115 Self::new(WaeErrorKind::TokenExpired)
1116 }
1117
1118 pub fn token_not_valid_yet() -> Self {
1120 Self::new(WaeErrorKind::TokenNotValidYet)
1121 }
1122
1123 pub fn invalid_signature() -> Self {
1125 Self::new(WaeErrorKind::InvalidSignature)
1126 }
1127
1128 pub fn missing_claim(claim: impl Into<String>) -> Self {
1130 Self::new(WaeErrorKind::MissingClaim { claim: claim.into() })
1131 }
1132
1133 pub fn invalid_claim(claim: impl Into<String>) -> Self {
1135 Self::new(WaeErrorKind::InvalidClaim { claim: claim.into() })
1136 }
1137
1138 pub fn invalid_issuer(expected: impl Into<String>, actual: impl Into<String>) -> Self {
1140 Self::new(WaeErrorKind::InvalidIssuer { expected: expected.into(), actual: actual.into() })
1141 }
1142
1143 pub fn invalid_audience() -> Self {
1145 Self::new(WaeErrorKind::InvalidAudience)
1146 }
1147
1148 pub fn permission_denied(action: impl Into<String>) -> Self {
1152 Self::new(WaeErrorKind::PermissionDenied { action: action.into() })
1153 }
1154
1155 pub fn forbidden(resource: impl Into<String>) -> Self {
1157 Self::new(WaeErrorKind::Forbidden { resource: resource.into() })
1158 }
1159
1160 pub fn not_found(resource_type: impl Into<String>, identifier: impl Into<String>) -> Self {
1164 Self::new(WaeErrorKind::ResourceNotFound { resource_type: resource_type.into(), identifier: identifier.into() })
1165 }
1166
1167 pub fn connection_failed(target: impl Into<String>) -> Self {
1171 Self::new(WaeErrorKind::ConnectionFailed { target: target.into() })
1172 }
1173
1174 pub fn service_unavailable(service: impl Into<String>) -> Self {
1176 Self::new(WaeErrorKind::ServiceUnavailable { service: service.into() })
1177 }
1178
1179 pub fn storage_read_failed(path: impl Into<String>) -> Self {
1183 Self::new(WaeErrorKind::StorageReadFailed { path: path.into() })
1184 }
1185
1186 pub fn storage_write_failed(path: impl Into<String>) -> Self {
1188 Self::new(WaeErrorKind::StorageWriteFailed { path: path.into() })
1189 }
1190
1191 pub fn storage_file_not_found(path: impl Into<String>) -> Self {
1193 Self::new(WaeErrorKind::StorageFileNotFound { path: path.into() })
1194 }
1195
1196 pub fn database(kind: WaeErrorKind) -> Self {
1200 Self::new(kind)
1201 }
1202
1203 pub fn database_connection_failed(reason: impl Into<String>) -> Self {
1205 Self::new(WaeErrorKind::DatabaseConnectionFailed { reason: reason.into() })
1206 }
1207
1208 pub fn query_failed(query: Option<String>, reason: impl Into<String>) -> Self {
1210 Self::new(WaeErrorKind::QueryFailed { query, reason: reason.into() })
1211 }
1212
1213 pub fn execute_failed(statement: Option<String>, reason: impl Into<String>) -> Self {
1215 Self::new(WaeErrorKind::ExecuteFailed { statement, reason: reason.into() })
1216 }
1217
1218 pub fn transaction_failed(reason: impl Into<String>) -> Self {
1220 Self::new(WaeErrorKind::TransactionFailed { reason: reason.into() })
1221 }
1222
1223 pub fn cache_key_not_found(key: impl Into<String>) -> Self {
1227 Self::new(WaeErrorKind::CacheKeyNotFound { key: key.into() })
1228 }
1229
1230 pub fn serialization_failed(type_name: impl Into<String>) -> Self {
1232 Self::new(WaeErrorKind::SerializationFailed { type_name: type_name.into() })
1233 }
1234
1235 pub fn deserialization_failed(type_name: impl Into<String>) -> Self {
1237 Self::new(WaeErrorKind::DeserializationFailed { type_name: type_name.into() })
1238 }
1239
1240 pub fn circuit_breaker_open(service: impl Into<String>) -> Self {
1244 Self::new(WaeErrorKind::CircuitBreakerOpen { service: service.into() })
1245 }
1246
1247 pub fn rate_limit_exceeded(limit: u64) -> Self {
1249 Self::new(WaeErrorKind::RateLimitExceeded { limit })
1250 }
1251
1252 pub fn max_retries_exceeded(attempts: u32) -> Self {
1254 Self::new(WaeErrorKind::MaxRetriesExceeded { attempts })
1255 }
1256
1257 pub fn bulkhead_full(max_concurrent: usize) -> Self {
1259 Self::new(WaeErrorKind::BulkheadFull { max_concurrent })
1260 }
1261
1262 pub fn task_not_found(task_id: impl Into<String>) -> Self {
1266 Self::new(WaeErrorKind::TaskNotFound { task_id: task_id.into() })
1267 }
1268
1269 pub fn invalid_cron_expression(expression: impl Into<String>) -> Self {
1271 Self::new(WaeErrorKind::InvalidCronExpression { expression: expression.into() })
1272 }
1273
1274 pub fn task_execution_failed(task_id: impl Into<String>, reason: impl Into<String>) -> Self {
1276 Self::new(WaeErrorKind::TaskExecutionFailed { task_id: task_id.into(), reason: reason.into() })
1277 }
1278
1279 pub fn scheduler_shutdown() -> Self {
1281 Self::new(WaeErrorKind::SchedulerShutdown)
1282 }
1283
1284 pub fn operation_timeout(operation: impl Into<String>, timeout_ms: u64) -> Self {
1288 Self::new(WaeErrorKind::OperationTimeout { operation: operation.into(), timeout_ms })
1289 }
1290
1291 pub fn config_missing(key: impl Into<String>) -> Self {
1295 Self::new(WaeErrorKind::ConfigMissing { key: key.into() })
1296 }
1297
1298 pub fn config_invalid(key: impl Into<String>, reason: impl Into<String>) -> Self {
1300 Self::new(WaeErrorKind::ConfigInvalid { key: key.into(), reason: reason.into() })
1301 }
1302
1303 pub fn internal(reason: impl Into<String>) -> Self {
1307 Self::new(WaeErrorKind::InternalError { reason: reason.into() })
1308 }
1309
1310 pub fn not_implemented(feature: impl Into<String>) -> Self {
1312 Self::new(WaeErrorKind::NotImplemented { feature: feature.into() })
1313 }
1314
1315 pub fn io_error(operation: impl Into<String>, reason: impl Into<String>) -> Self {
1317 Self::new(WaeErrorKind::IoError { operation: operation.into(), reason: reason.into() })
1318 }
1319
1320 pub fn json_error(reason: impl Into<String>) -> Self {
1322 Self::new(WaeErrorKind::JsonError { reason: reason.into() })
1323 }
1324
1325 pub fn parse_error(type_name: impl Into<String>, reason: impl Into<String>) -> Self {
1327 Self::new(WaeErrorKind::ParseError { type_name: type_name.into(), reason: reason.into() })
1328 }
1329
1330 pub fn request_error(url: impl Into<String>, reason: impl Into<String>) -> Self {
1332 Self::new(WaeErrorKind::RequestError { url: url.into(), reason: reason.into() })
1333 }
1334}
1335
1336impl fmt::Display for WaeError {
1337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1338 write!(f, "{:?}: {}", self.kind.category(), self.kind.i18n_key())
1339 }
1340}
1341
1342impl std::error::Error for WaeError {}
1343
1344impl From<std::io::Error> for WaeError {
1345 fn from(err: std::io::Error) -> Self {
1346 Self::io_error("unknown", err.to_string())
1347 }
1348}
1349
1350impl From<serde_json::Error> for WaeError {
1351 fn from(err: serde_json::Error) -> Self {
1352 Self::json_error(err.to_string())
1353 }
1354}