1use serde::{Deserialize, Serialize};
6use serde_json::{Value, json};
7use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13pub enum ErrorCategory {
14 Validation,
16 Auth,
18 Permission,
20 NotFound,
22 Conflict,
24 RateLimited,
26 Network,
28 Storage,
30 Database,
32 Cache,
34 Config,
36 Timeout,
38 Internal,
40}
41
42impl ErrorCategory {
43 pub fn http_status(&self) -> u16 {
45 match self {
46 ErrorCategory::Validation => 400,
47 ErrorCategory::Auth => 401,
48 ErrorCategory::Permission => 403,
49 ErrorCategory::NotFound => 404,
50 ErrorCategory::Conflict => 409,
51 ErrorCategory::RateLimited => 429,
52 ErrorCategory::Network => 502,
53 ErrorCategory::Storage => 500,
54 ErrorCategory::Database => 500,
55 ErrorCategory::Cache => 500,
56 ErrorCategory::Config => 500,
57 ErrorCategory::Timeout => 408,
58 ErrorCategory::Internal => 500,
59 }
60 }
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
67pub enum WaeErrorKind {
68 InvalidFormat {
71 field: String,
73 expected: String,
75 },
76 OutOfRange {
78 field: String,
80 min: Option<String>,
82 max: Option<String>,
84 },
85 Required {
87 field: String,
89 },
90 AlreadyExists {
92 field: String,
94 value: String,
96 },
97 NotAllowed {
99 field: String,
101 allowed: Vec<String>,
103 },
104 InvalidParams {
106 param: String,
108 reason: String,
110 },
111
112 InvalidCredentials,
115 AccountLocked,
117 UserNotFound {
119 identifier: String,
121 },
122 UserAlreadyExists {
124 username: String,
126 },
127 InvalidToken {
129 reason: String,
131 },
132 TokenExpired,
134 TokenNotValidYet,
136 InvalidSignature,
138 InvalidAlgorithm,
140 MissingClaim {
142 claim: String,
144 },
145 InvalidClaim {
147 claim: String,
149 },
150 InvalidIssuer {
152 expected: String,
154 actual: String,
156 },
157 InvalidAudience,
159 KeyError,
161 EncodingError,
163 DecodingError,
165
166 InvalidAuthorizationCode,
169 InvalidRedirectUri,
171 InvalidClientId,
173 InvalidClientSecret,
175 InvalidScope {
177 scope: String,
179 },
180 InvalidAccessToken,
182 InvalidRefreshToken,
184 AccessDenied {
186 reason: String,
188 },
189 UnsupportedGrantType {
191 grant_type: String,
193 },
194 UnsupportedResponseType {
196 response_type: String,
198 },
199 StateMismatch,
201 OAuth2ProviderError {
203 message: String,
205 },
206
207 InvalidSamlRequest {
210 reason: String,
212 },
213 InvalidSamlResponse {
215 reason: String,
217 },
218 InvalidAssertion {
220 reason: String,
222 },
223 SignatureVerificationFailed {
225 reason: String,
227 },
228 MissingSignature,
230 CertificateError {
232 reason: String,
234 },
235 XmlParsingError {
237 reason: String,
239 },
240 Base64DecodeError {
242 reason: String,
244 },
245 CompressionError {
247 reason: String,
249 },
250 AssertionExpired,
252 AssertionNotYetValid,
254 SamlAudienceValidationFailed {
256 expected: String,
258 actual: String,
260 },
261 SamlIssuerValidationFailed {
263 expected: String,
265 actual: String,
267 },
268 DestinationValidationFailed,
270 ReplayAttackDetected,
272 UnsupportedBinding {
274 binding: String,
276 },
277 UnsupportedNameIdFormat {
279 format: String,
281 },
282
283 InvalidSecret {
286 reason: String,
288 },
289 InvalidCode,
291 CodeExpired,
293 InvalidCodeFormat {
295 expected: usize,
297 actual: usize,
299 },
300 InvalidTimeStep,
302 Base32Error {
304 reason: String,
306 },
307 HmacError {
309 reason: String,
311 },
312 QrCodeError {
314 reason: String,
316 },
317
318 PermissionDenied {
321 action: String,
323 },
324 Forbidden {
326 resource: String,
328 },
329
330 ResourceNotFound {
333 resource_type: String,
335 identifier: String,
337 },
338
339 ResourceConflict {
342 resource: String,
344 reason: String,
346 },
347
348 ConnectionFailed {
351 target: String,
353 },
354 DnsResolutionFailed {
356 host: String,
358 },
359 TlsError {
361 reason: String,
363 },
364 ProtocolError {
366 protocol: String,
368 reason: String,
370 },
371 ServiceUnavailable {
373 service: String,
375 },
376 GatewayError {
378 gateway: String,
380 },
381
382 StorageReadFailed {
385 path: String,
387 },
388 StorageWriteFailed {
390 path: String,
392 },
393 StorageDeleteFailed {
395 path: String,
397 },
398 InsufficientCapacity {
400 required: u64,
402 available: u64,
404 },
405 DataCorruption {
407 location: String,
409 },
410 StorageFileNotFound {
412 path: String,
414 },
415
416 DatabaseConnectionFailed {
419 reason: String,
421 },
422 QueryFailed {
424 query: Option<String>,
426 reason: String,
428 },
429 ExecuteFailed {
431 statement: Option<String>,
433 reason: String,
435 },
436 TransactionFailed {
438 reason: String,
440 },
441 MigrationFailed {
443 version: Option<String>,
445 reason: String,
447 },
448
449 CacheConnectionFailed {
452 reason: String,
454 },
455 CacheKeyNotFound {
457 key: String,
459 },
460 SerializationFailed {
462 type_name: String,
464 },
465 DeserializationFailed {
467 type_name: String,
469 },
470
471 CircuitBreakerOpen {
474 service: String,
476 },
477 RateLimitExceeded {
479 limit: u64,
481 },
482 MaxRetriesExceeded {
484 attempts: u32,
486 },
487 BulkheadFull {
489 max_concurrent: usize,
491 },
492 BulkheadWaitTimeout,
494
495 TaskNotFound {
498 task_id: String,
500 },
501 TaskAlreadyExists {
503 task_id: String,
505 },
506 InvalidCronExpression {
508 expression: String,
510 },
511 TaskExecutionFailed {
513 task_id: String,
515 reason: String,
517 },
518 SchedulerShutdown,
520
521 MockError {
524 reason: String,
526 },
527 AssertionFailed {
529 message: String,
531 },
532 FixtureError {
534 reason: String,
536 },
537 EnvironmentError {
539 reason: String,
541 },
542
543 OperationTimeout {
546 operation: String,
548 timeout_ms: u64,
550 },
551
552 ConfigMissing {
555 key: String,
557 },
558 ConfigInvalid {
560 key: String,
562 reason: String,
564 },
565
566 InternalError {
569 reason: String,
571 },
572 NotImplemented {
574 feature: String,
576 },
577 IoError {
579 operation: String,
581 reason: String,
583 },
584 JsonError {
586 reason: String,
588 },
589 ParseError {
591 type_name: String,
593 reason: String,
595 },
596 RequestError {
598 url: String,
600 reason: String,
602 },
603}
604
605impl WaeErrorKind {
606 pub fn category(&self) -> ErrorCategory {
608 match self {
609 WaeErrorKind::InvalidFormat { .. }
610 | WaeErrorKind::OutOfRange { .. }
611 | WaeErrorKind::Required { .. }
612 | WaeErrorKind::AlreadyExists { .. }
613 | WaeErrorKind::NotAllowed { .. }
614 | WaeErrorKind::InvalidParams { .. } => ErrorCategory::Validation,
615
616 WaeErrorKind::InvalidCredentials
617 | WaeErrorKind::AccountLocked
618 | WaeErrorKind::UserNotFound { .. }
619 | WaeErrorKind::UserAlreadyExists { .. }
620 | WaeErrorKind::InvalidToken { .. }
621 | WaeErrorKind::TokenExpired
622 | WaeErrorKind::TokenNotValidYet
623 | WaeErrorKind::InvalidSignature
624 | WaeErrorKind::InvalidAlgorithm
625 | WaeErrorKind::MissingClaim { .. }
626 | WaeErrorKind::InvalidClaim { .. }
627 | WaeErrorKind::InvalidIssuer { .. }
628 | WaeErrorKind::InvalidAudience
629 | WaeErrorKind::KeyError
630 | WaeErrorKind::EncodingError
631 | WaeErrorKind::DecodingError
632 | WaeErrorKind::InvalidAuthorizationCode
633 | WaeErrorKind::InvalidRedirectUri
634 | WaeErrorKind::InvalidClientId
635 | WaeErrorKind::InvalidClientSecret
636 | WaeErrorKind::InvalidScope { .. }
637 | WaeErrorKind::InvalidAccessToken
638 | WaeErrorKind::InvalidRefreshToken
639 | WaeErrorKind::AccessDenied { .. }
640 | WaeErrorKind::UnsupportedGrantType { .. }
641 | WaeErrorKind::UnsupportedResponseType { .. }
642 | WaeErrorKind::StateMismatch
643 | WaeErrorKind::OAuth2ProviderError { .. }
644 | WaeErrorKind::InvalidSamlRequest { .. }
645 | WaeErrorKind::InvalidSamlResponse { .. }
646 | WaeErrorKind::InvalidAssertion { .. }
647 | WaeErrorKind::SignatureVerificationFailed { .. }
648 | WaeErrorKind::MissingSignature
649 | WaeErrorKind::CertificateError { .. }
650 | WaeErrorKind::XmlParsingError { .. }
651 | WaeErrorKind::Base64DecodeError { .. }
652 | WaeErrorKind::CompressionError { .. }
653 | WaeErrorKind::AssertionExpired
654 | WaeErrorKind::AssertionNotYetValid
655 | WaeErrorKind::SamlAudienceValidationFailed { .. }
656 | WaeErrorKind::SamlIssuerValidationFailed { .. }
657 | WaeErrorKind::DestinationValidationFailed
658 | WaeErrorKind::ReplayAttackDetected
659 | WaeErrorKind::UnsupportedBinding { .. }
660 | WaeErrorKind::UnsupportedNameIdFormat { .. }
661 | WaeErrorKind::InvalidSecret { .. }
662 | WaeErrorKind::InvalidCode
663 | WaeErrorKind::CodeExpired
664 | WaeErrorKind::InvalidCodeFormat { .. }
665 | WaeErrorKind::InvalidTimeStep
666 | WaeErrorKind::Base32Error { .. }
667 | WaeErrorKind::HmacError { .. }
668 | WaeErrorKind::QrCodeError { .. } => ErrorCategory::Auth,
669
670 WaeErrorKind::PermissionDenied { .. } | WaeErrorKind::Forbidden { .. } => ErrorCategory::Permission,
671
672 WaeErrorKind::ResourceNotFound { .. } => ErrorCategory::NotFound,
673
674 WaeErrorKind::ResourceConflict { .. } => ErrorCategory::Conflict,
675
676 WaeErrorKind::RateLimitExceeded { .. } => ErrorCategory::RateLimited,
677
678 WaeErrorKind::ConnectionFailed { .. }
679 | WaeErrorKind::DnsResolutionFailed { .. }
680 | WaeErrorKind::TlsError { .. }
681 | WaeErrorKind::ProtocolError { .. }
682 | WaeErrorKind::ServiceUnavailable { .. }
683 | WaeErrorKind::GatewayError { .. } => ErrorCategory::Network,
684
685 WaeErrorKind::StorageReadFailed { .. }
686 | WaeErrorKind::StorageWriteFailed { .. }
687 | WaeErrorKind::StorageDeleteFailed { .. }
688 | WaeErrorKind::InsufficientCapacity { .. }
689 | WaeErrorKind::DataCorruption { .. }
690 | WaeErrorKind::StorageFileNotFound { .. } => ErrorCategory::Storage,
691
692 WaeErrorKind::DatabaseConnectionFailed { .. }
693 | WaeErrorKind::QueryFailed { .. }
694 | WaeErrorKind::ExecuteFailed { .. }
695 | WaeErrorKind::TransactionFailed { .. }
696 | WaeErrorKind::MigrationFailed { .. } => ErrorCategory::Database,
697
698 WaeErrorKind::CacheConnectionFailed { .. }
699 | WaeErrorKind::CacheKeyNotFound { .. }
700 | WaeErrorKind::SerializationFailed { .. }
701 | WaeErrorKind::DeserializationFailed { .. } => ErrorCategory::Cache,
702
703 WaeErrorKind::CircuitBreakerOpen { .. } | WaeErrorKind::BulkheadFull { .. } | WaeErrorKind::BulkheadWaitTimeout => {
704 ErrorCategory::Network
705 }
706
707 WaeErrorKind::MaxRetriesExceeded { .. } => ErrorCategory::Network,
708
709 WaeErrorKind::TaskNotFound { .. }
710 | WaeErrorKind::TaskAlreadyExists { .. }
711 | WaeErrorKind::InvalidCronExpression { .. }
712 | WaeErrorKind::TaskExecutionFailed { .. }
713 | WaeErrorKind::SchedulerShutdown => ErrorCategory::Internal,
714
715 WaeErrorKind::MockError { .. }
716 | WaeErrorKind::AssertionFailed { .. }
717 | WaeErrorKind::FixtureError { .. }
718 | WaeErrorKind::EnvironmentError { .. } => ErrorCategory::Internal,
719
720 WaeErrorKind::OperationTimeout { .. } => ErrorCategory::Timeout,
721
722 WaeErrorKind::ConfigMissing { .. } | WaeErrorKind::ConfigInvalid { .. } => ErrorCategory::Config,
723
724 WaeErrorKind::InternalError { .. }
725 | WaeErrorKind::NotImplemented { .. }
726 | WaeErrorKind::IoError { .. }
727 | WaeErrorKind::JsonError { .. }
728 | WaeErrorKind::ParseError { .. }
729 | WaeErrorKind::RequestError { .. } => ErrorCategory::Internal,
730 }
731 }
732
733 pub fn i18n_key(&self) -> &'static str {
735 match self {
736 WaeErrorKind::InvalidFormat { .. } => "wae.error.validation.invalid_format",
737 WaeErrorKind::OutOfRange { .. } => "wae.error.validation.out_of_range",
738 WaeErrorKind::Required { .. } => "wae.error.validation.required",
739 WaeErrorKind::AlreadyExists { .. } => "wae.error.validation.already_exists",
740 WaeErrorKind::NotAllowed { .. } => "wae.error.validation.not_allowed",
741 WaeErrorKind::InvalidParams { .. } => "wae.error.validation.invalid_params",
742
743 WaeErrorKind::InvalidCredentials => "wae.error.auth.invalid_credentials",
744 WaeErrorKind::AccountLocked => "wae.error.auth.account_locked",
745 WaeErrorKind::UserNotFound { .. } => "wae.error.auth.user_not_found",
746 WaeErrorKind::UserAlreadyExists { .. } => "wae.error.auth.user_already_exists",
747 WaeErrorKind::InvalidToken { .. } => "wae.error.auth.invalid_token",
748 WaeErrorKind::TokenExpired => "wae.error.auth.token_expired",
749 WaeErrorKind::TokenNotValidYet => "wae.error.auth.token_not_valid_yet",
750 WaeErrorKind::InvalidSignature => "wae.error.auth.invalid_signature",
751 WaeErrorKind::InvalidAlgorithm => "wae.error.auth.invalid_algorithm",
752 WaeErrorKind::MissingClaim { .. } => "wae.error.auth.missing_claim",
753 WaeErrorKind::InvalidClaim { .. } => "wae.error.auth.invalid_claim",
754 WaeErrorKind::InvalidIssuer { .. } => "wae.error.auth.invalid_issuer",
755 WaeErrorKind::InvalidAudience => "wae.error.auth.invalid_audience",
756 WaeErrorKind::KeyError => "wae.error.auth.key_error",
757 WaeErrorKind::EncodingError => "wae.error.auth.encoding_error",
758 WaeErrorKind::DecodingError => "wae.error.auth.decoding_error",
759
760 WaeErrorKind::InvalidAuthorizationCode => "wae.error.oauth2.invalid_authorization_code",
761 WaeErrorKind::InvalidRedirectUri => "wae.error.oauth2.invalid_redirect_uri",
762 WaeErrorKind::InvalidClientId => "wae.error.oauth2.invalid_client_id",
763 WaeErrorKind::InvalidClientSecret => "wae.error.oauth2.invalid_client_secret",
764 WaeErrorKind::InvalidScope { .. } => "wae.error.oauth2.invalid_scope",
765 WaeErrorKind::InvalidAccessToken => "wae.error.oauth2.invalid_access_token",
766 WaeErrorKind::InvalidRefreshToken => "wae.error.oauth2.invalid_refresh_token",
767 WaeErrorKind::AccessDenied { .. } => "wae.error.oauth2.access_denied",
768 WaeErrorKind::UnsupportedGrantType { .. } => "wae.error.oauth2.unsupported_grant_type",
769 WaeErrorKind::UnsupportedResponseType { .. } => "wae.error.oauth2.unsupported_response_type",
770 WaeErrorKind::StateMismatch => "wae.error.oauth2.state_mismatch",
771 WaeErrorKind::OAuth2ProviderError { .. } => "wae.error.oauth2.provider_error",
772
773 WaeErrorKind::InvalidSamlRequest { .. } => "wae.error.saml.invalid_request",
774 WaeErrorKind::InvalidSamlResponse { .. } => "wae.error.saml.invalid_response",
775 WaeErrorKind::InvalidAssertion { .. } => "wae.error.saml.invalid_assertion",
776 WaeErrorKind::SignatureVerificationFailed { .. } => "wae.error.saml.signature_verification_failed",
777 WaeErrorKind::MissingSignature => "wae.error.saml.missing_signature",
778 WaeErrorKind::CertificateError { .. } => "wae.error.saml.certificate_error",
779 WaeErrorKind::XmlParsingError { .. } => "wae.error.saml.xml_parsing_error",
780 WaeErrorKind::Base64DecodeError { .. } => "wae.error.saml.base64_decode_error",
781 WaeErrorKind::CompressionError { .. } => "wae.error.saml.compression_error",
782 WaeErrorKind::AssertionExpired => "wae.error.saml.assertion_expired",
783 WaeErrorKind::AssertionNotYetValid => "wae.error.saml.assertion_not_yet_valid",
784 WaeErrorKind::SamlAudienceValidationFailed { .. } => "wae.error.saml.audience_validation_failed",
785 WaeErrorKind::SamlIssuerValidationFailed { .. } => "wae.error.saml.issuer_validation_failed",
786 WaeErrorKind::DestinationValidationFailed => "wae.error.saml.destination_validation_failed",
787 WaeErrorKind::ReplayAttackDetected => "wae.error.saml.replay_attack_detected",
788 WaeErrorKind::UnsupportedBinding { .. } => "wae.error.saml.unsupported_binding",
789 WaeErrorKind::UnsupportedNameIdFormat { .. } => "wae.error.saml.unsupported_name_id_format",
790
791 WaeErrorKind::InvalidSecret { .. } => "wae.error.totp.invalid_secret",
792 WaeErrorKind::InvalidCode => "wae.error.totp.invalid_code",
793 WaeErrorKind::CodeExpired => "wae.error.totp.code_expired",
794 WaeErrorKind::InvalidCodeFormat { .. } => "wae.error.totp.invalid_code_format",
795 WaeErrorKind::InvalidTimeStep => "wae.error.totp.invalid_time_step",
796 WaeErrorKind::Base32Error { .. } => "wae.error.totp.base32_error",
797 WaeErrorKind::HmacError { .. } => "wae.error.totp.hmac_error",
798 WaeErrorKind::QrCodeError { .. } => "wae.error.totp.qr_code_error",
799
800 WaeErrorKind::PermissionDenied { .. } => "wae.error.permission.denied",
801 WaeErrorKind::Forbidden { .. } => "wae.error.permission.forbidden",
802
803 WaeErrorKind::ResourceNotFound { .. } => "wae.error.not_found.resource",
804
805 WaeErrorKind::ResourceConflict { .. } => "wae.error.conflict.resource",
806
807 WaeErrorKind::RateLimitExceeded { .. } => "wae.error.rate_limited",
808
809 WaeErrorKind::ConnectionFailed { .. } => "wae.error.network.connection_failed",
810 WaeErrorKind::DnsResolutionFailed { .. } => "wae.error.network.dns_resolution_failed",
811 WaeErrorKind::TlsError { .. } => "wae.error.network.tls_error",
812 WaeErrorKind::ProtocolError { .. } => "wae.error.network.protocol_error",
813 WaeErrorKind::ServiceUnavailable { .. } => "wae.error.network.service_unavailable",
814 WaeErrorKind::GatewayError { .. } => "wae.error.network.gateway_error",
815
816 WaeErrorKind::StorageReadFailed { .. } => "wae.error.storage.read_failed",
817 WaeErrorKind::StorageWriteFailed { .. } => "wae.error.storage.write_failed",
818 WaeErrorKind::StorageDeleteFailed { .. } => "wae.error.storage.delete_failed",
819 WaeErrorKind::InsufficientCapacity { .. } => "wae.error.storage.insufficient_capacity",
820 WaeErrorKind::DataCorruption { .. } => "wae.error.storage.data_corruption",
821 WaeErrorKind::StorageFileNotFound { .. } => "wae.error.storage.file_not_found",
822
823 WaeErrorKind::DatabaseConnectionFailed { .. } => "wae.error.database.connection_failed",
824 WaeErrorKind::QueryFailed { .. } => "wae.error.database.query_failed",
825 WaeErrorKind::ExecuteFailed { .. } => "wae.error.database.execute_failed",
826 WaeErrorKind::TransactionFailed { .. } => "wae.error.database.transaction_failed",
827 WaeErrorKind::MigrationFailed { .. } => "wae.error.database.migration_failed",
828
829 WaeErrorKind::CacheConnectionFailed { .. } => "wae.error.cache.connection_failed",
830 WaeErrorKind::CacheKeyNotFound { .. } => "wae.error.cache.key_not_found",
831 WaeErrorKind::SerializationFailed { .. } => "wae.error.cache.serialization_failed",
832 WaeErrorKind::DeserializationFailed { .. } => "wae.error.cache.deserialization_failed",
833
834 WaeErrorKind::CircuitBreakerOpen { .. } => "wae.error.resilience.circuit_breaker_open",
835 WaeErrorKind::MaxRetriesExceeded { .. } => "wae.error.resilience.max_retries_exceeded",
836 WaeErrorKind::BulkheadFull { .. } => "wae.error.resilience.bulkhead_full",
837 WaeErrorKind::BulkheadWaitTimeout => "wae.error.resilience.bulkhead_wait_timeout",
838
839 WaeErrorKind::TaskNotFound { .. } => "wae.error.scheduler.task_not_found",
840 WaeErrorKind::TaskAlreadyExists { .. } => "wae.error.scheduler.task_already_exists",
841 WaeErrorKind::InvalidCronExpression { .. } => "wae.error.scheduler.invalid_cron_expression",
842 WaeErrorKind::TaskExecutionFailed { .. } => "wae.error.scheduler.task_execution_failed",
843 WaeErrorKind::SchedulerShutdown => "wae.error.scheduler.shutdown",
844
845 WaeErrorKind::MockError { .. } => "wae.error.testing.mock_error",
846 WaeErrorKind::AssertionFailed { .. } => "wae.error.testing.assertion_failed",
847 WaeErrorKind::FixtureError { .. } => "wae.error.testing.fixture_error",
848 WaeErrorKind::EnvironmentError { .. } => "wae.error.testing.environment_error",
849
850 WaeErrorKind::OperationTimeout { .. } => "wae.error.timeout.operation",
851
852 WaeErrorKind::ConfigMissing { .. } => "wae.error.config.missing",
853 WaeErrorKind::ConfigInvalid { .. } => "wae.error.config.invalid",
854
855 WaeErrorKind::InternalError { .. } => "wae.error.internal.error",
856 WaeErrorKind::NotImplemented { .. } => "wae.error.internal.not_implemented",
857 WaeErrorKind::IoError { .. } => "wae.error.internal.io_error",
858 WaeErrorKind::JsonError { .. } => "wae.error.internal.json_error",
859 WaeErrorKind::ParseError { .. } => "wae.error.internal.parse_error",
860 WaeErrorKind::RequestError { .. } => "wae.error.internal.request_error",
861 }
862 }
863
864 pub fn i18n_data(&self) -> Value {
866 match self {
867 WaeErrorKind::InvalidFormat { field, expected } => {
868 json!({ "field": field, "expected": expected })
869 }
870 WaeErrorKind::OutOfRange { field, min, max } => {
871 json!({ "field": field, "min": min, "max": max })
872 }
873 WaeErrorKind::Required { field } => json!({ "field": field }),
874 WaeErrorKind::AlreadyExists { field, value } => json!({ "field": field, "value": value }),
875 WaeErrorKind::NotAllowed { field, allowed } => json!({ "field": field, "allowed": allowed }),
876 WaeErrorKind::InvalidParams { param, reason } => json!({ "param": param, "reason": reason }),
877
878 WaeErrorKind::InvalidCredentials => json!({}),
879 WaeErrorKind::AccountLocked => json!({}),
880 WaeErrorKind::UserNotFound { identifier } => json!({ "identifier": identifier }),
881 WaeErrorKind::UserAlreadyExists { username } => json!({ "username": username }),
882 WaeErrorKind::InvalidToken { reason } => json!({ "reason": reason }),
883 WaeErrorKind::TokenExpired => json!({}),
884 WaeErrorKind::TokenNotValidYet => json!({}),
885 WaeErrorKind::InvalidSignature => json!({}),
886 WaeErrorKind::InvalidAlgorithm => json!({}),
887 WaeErrorKind::MissingClaim { claim } => json!({ "claim": claim }),
888 WaeErrorKind::InvalidClaim { claim } => json!({ "claim": claim }),
889 WaeErrorKind::InvalidIssuer { expected, actual } => json!({ "expected": expected, "actual": actual }),
890 WaeErrorKind::InvalidAudience => json!({}),
891 WaeErrorKind::KeyError => json!({}),
892 WaeErrorKind::EncodingError => json!({}),
893 WaeErrorKind::DecodingError => json!({}),
894
895 WaeErrorKind::InvalidAuthorizationCode => json!({}),
896 WaeErrorKind::InvalidRedirectUri => json!({}),
897 WaeErrorKind::InvalidClientId => json!({}),
898 WaeErrorKind::InvalidClientSecret => json!({}),
899 WaeErrorKind::InvalidScope { scope } => json!({ "scope": scope }),
900 WaeErrorKind::InvalidAccessToken => json!({}),
901 WaeErrorKind::InvalidRefreshToken => json!({}),
902 WaeErrorKind::AccessDenied { reason } => json!({ "reason": reason }),
903 WaeErrorKind::UnsupportedGrantType { grant_type } => json!({ "grant_type": grant_type }),
904 WaeErrorKind::UnsupportedResponseType { response_type } => json!({ "response_type": response_type }),
905 WaeErrorKind::StateMismatch => json!({}),
906 WaeErrorKind::OAuth2ProviderError { message } => json!({ "message": message }),
907
908 WaeErrorKind::InvalidSamlRequest { reason } => json!({ "reason": reason }),
909 WaeErrorKind::InvalidSamlResponse { reason } => json!({ "reason": reason }),
910 WaeErrorKind::InvalidAssertion { reason } => json!({ "reason": reason }),
911 WaeErrorKind::SignatureVerificationFailed { reason } => json!({ "reason": reason }),
912 WaeErrorKind::MissingSignature => json!({}),
913 WaeErrorKind::CertificateError { reason } => json!({ "reason": reason }),
914 WaeErrorKind::XmlParsingError { reason } => json!({ "reason": reason }),
915 WaeErrorKind::Base64DecodeError { reason } => json!({ "reason": reason }),
916 WaeErrorKind::CompressionError { reason } => json!({ "reason": reason }),
917 WaeErrorKind::AssertionExpired => json!({}),
918 WaeErrorKind::AssertionNotYetValid => json!({}),
919 WaeErrorKind::SamlAudienceValidationFailed { expected, actual } => {
920 json!({ "expected": expected, "actual": actual })
921 }
922 WaeErrorKind::SamlIssuerValidationFailed { expected, actual } => {
923 json!({ "expected": expected, "actual": actual })
924 }
925 WaeErrorKind::DestinationValidationFailed => json!({}),
926 WaeErrorKind::ReplayAttackDetected => json!({}),
927 WaeErrorKind::UnsupportedBinding { binding } => json!({ "binding": binding }),
928 WaeErrorKind::UnsupportedNameIdFormat { format } => json!({ "format": format }),
929
930 WaeErrorKind::InvalidSecret { reason } => json!({ "reason": reason }),
931 WaeErrorKind::InvalidCode => json!({}),
932 WaeErrorKind::CodeExpired => json!({}),
933 WaeErrorKind::InvalidCodeFormat { expected, actual } => json!({ "expected": expected, "actual": actual }),
934 WaeErrorKind::InvalidTimeStep => json!({}),
935 WaeErrorKind::Base32Error { reason } => json!({ "reason": reason }),
936 WaeErrorKind::HmacError { reason } => json!({ "reason": reason }),
937 WaeErrorKind::QrCodeError { reason } => json!({ "reason": reason }),
938
939 WaeErrorKind::PermissionDenied { action } => json!({ "action": action }),
940 WaeErrorKind::Forbidden { resource } => json!({ "resource": resource }),
941
942 WaeErrorKind::ResourceNotFound { resource_type, identifier } => {
943 json!({ "resource_type": resource_type, "identifier": identifier })
944 }
945
946 WaeErrorKind::ResourceConflict { resource, reason } => json!({ "resource": resource, "reason": reason }),
947
948 WaeErrorKind::RateLimitExceeded { limit } => json!({ "limit": limit }),
949
950 WaeErrorKind::ConnectionFailed { target } => json!({ "target": target }),
951 WaeErrorKind::DnsResolutionFailed { host } => json!({ "host": host }),
952 WaeErrorKind::TlsError { reason } => json!({ "reason": reason }),
953 WaeErrorKind::ProtocolError { protocol, reason } => json!({ "protocol": protocol, "reason": reason }),
954 WaeErrorKind::ServiceUnavailable { service } => json!({ "service": service }),
955 WaeErrorKind::GatewayError { gateway } => json!({ "gateway": gateway }),
956
957 WaeErrorKind::StorageReadFailed { path } => json!({ "path": path }),
958 WaeErrorKind::StorageWriteFailed { path } => json!({ "path": path }),
959 WaeErrorKind::StorageDeleteFailed { path } => json!({ "path": path }),
960 WaeErrorKind::InsufficientCapacity { required, available } => {
961 json!({ "required": required, "available": available })
962 }
963 WaeErrorKind::DataCorruption { location } => json!({ "location": location }),
964 WaeErrorKind::StorageFileNotFound { path } => json!({ "path": path }),
965
966 WaeErrorKind::DatabaseConnectionFailed { reason } => json!({ "reason": reason }),
967 WaeErrorKind::QueryFailed { query, reason } => json!({ "query": query, "reason": reason }),
968 WaeErrorKind::ExecuteFailed { statement, reason } => json!({ "statement": statement, "reason": reason }),
969 WaeErrorKind::TransactionFailed { reason } => json!({ "reason": reason }),
970 WaeErrorKind::MigrationFailed { version, reason } => json!({ "version": version, "reason": reason }),
971
972 WaeErrorKind::CacheConnectionFailed { reason } => json!({ "reason": reason }),
973 WaeErrorKind::CacheKeyNotFound { key } => json!({ "key": key }),
974 WaeErrorKind::SerializationFailed { type_name } => json!({ "type": type_name }),
975 WaeErrorKind::DeserializationFailed { type_name } => json!({ "type": type_name }),
976
977 WaeErrorKind::CircuitBreakerOpen { service } => json!({ "service": service }),
978 WaeErrorKind::MaxRetriesExceeded { attempts } => json!({ "attempts": attempts }),
979 WaeErrorKind::BulkheadFull { max_concurrent } => json!({ "max_concurrent": max_concurrent }),
980 WaeErrorKind::BulkheadWaitTimeout => json!({}),
981
982 WaeErrorKind::TaskNotFound { task_id } => json!({ "task_id": task_id }),
983 WaeErrorKind::TaskAlreadyExists { task_id } => json!({ "task_id": task_id }),
984 WaeErrorKind::InvalidCronExpression { expression } => json!({ "expression": expression }),
985 WaeErrorKind::TaskExecutionFailed { task_id, reason } => json!({ "task_id": task_id, "reason": reason }),
986 WaeErrorKind::SchedulerShutdown => json!({}),
987
988 WaeErrorKind::MockError { reason } => json!({ "reason": reason }),
989 WaeErrorKind::AssertionFailed { message } => json!({ "message": message }),
990 WaeErrorKind::FixtureError { reason } => json!({ "reason": reason }),
991 WaeErrorKind::EnvironmentError { reason } => json!({ "reason": reason }),
992
993 WaeErrorKind::OperationTimeout { operation, timeout_ms } => {
994 json!({ "operation": operation, "timeout_ms": timeout_ms })
995 }
996
997 WaeErrorKind::ConfigMissing { key } => json!({ "key": key }),
998 WaeErrorKind::ConfigInvalid { key, reason } => json!({ "key": key, "reason": reason }),
999
1000 WaeErrorKind::InternalError { reason } => json!({ "reason": reason }),
1001 WaeErrorKind::NotImplemented { feature } => json!({ "feature": feature }),
1002 WaeErrorKind::IoError { operation, reason } => json!({ "operation": operation, "reason": reason }),
1003 WaeErrorKind::JsonError { reason } => json!({ "reason": reason }),
1004 WaeErrorKind::ParseError { type_name, reason } => json!({ "type": type_name, "reason": reason }),
1005 WaeErrorKind::RequestError { url, reason } => json!({ "url": url, "reason": reason }),
1006 }
1007 }
1008}
1009
1010impl fmt::Display for WaeErrorKind {
1011 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1012 write!(f, "{}", self.i18n_key())
1013 }
1014}
1015
1016#[derive(Debug, Clone, Serialize, Deserialize)]
1020pub struct WaeError {
1021 pub kind: Box<WaeErrorKind>,
1023}
1024
1025impl WaeError {
1026 pub fn new(kind: WaeErrorKind) -> Self {
1028 Self { kind: Box::new(kind) }
1029 }
1030
1031 pub fn i18n_key(&self) -> &'static str {
1033 self.kind.i18n_key()
1034 }
1035
1036 pub fn i18n_data(&self) -> Value {
1038 self.kind.i18n_data()
1039 }
1040
1041 pub fn category(&self) -> ErrorCategory {
1043 self.kind.category()
1044 }
1045
1046 pub fn http_status(&self) -> u16 {
1048 self.category().http_status()
1049 }
1050
1051 pub fn invalid_format(field: impl Into<String>, expected: impl Into<String>) -> Self {
1055 Self::new(WaeErrorKind::InvalidFormat { field: field.into(), expected: expected.into() })
1056 }
1057
1058 pub fn out_of_range(field: impl Into<String>, min: Option<String>, max: Option<String>) -> Self {
1060 Self::new(WaeErrorKind::OutOfRange { field: field.into(), min, max })
1061 }
1062
1063 pub fn required(field: impl Into<String>) -> Self {
1065 Self::new(WaeErrorKind::Required { field: field.into() })
1066 }
1067
1068 pub fn already_exists(field: impl Into<String>, value: impl Into<String>) -> Self {
1070 Self::new(WaeErrorKind::AlreadyExists { field: field.into(), value: value.into() })
1071 }
1072
1073 pub fn not_allowed(field: impl Into<String>, allowed: Vec<String>) -> Self {
1075 Self::new(WaeErrorKind::NotAllowed { field: field.into(), allowed })
1076 }
1077
1078 pub fn invalid_params(param: impl Into<String>, reason: impl Into<String>) -> Self {
1080 Self::new(WaeErrorKind::InvalidParams { param: param.into(), reason: reason.into() })
1081 }
1082
1083 pub fn invalid_credentials() -> Self {
1087 Self::new(WaeErrorKind::InvalidCredentials)
1088 }
1089
1090 pub fn account_locked() -> Self {
1092 Self::new(WaeErrorKind::AccountLocked)
1093 }
1094
1095 pub fn user_not_found(identifier: impl Into<String>) -> Self {
1097 Self::new(WaeErrorKind::UserNotFound { identifier: identifier.into() })
1098 }
1099
1100 pub fn user_already_exists(username: impl Into<String>) -> Self {
1102 Self::new(WaeErrorKind::UserAlreadyExists { username: username.into() })
1103 }
1104
1105 pub fn invalid_token(reason: impl Into<String>) -> Self {
1107 Self::new(WaeErrorKind::InvalidToken { reason: reason.into() })
1108 }
1109
1110 pub fn token_expired() -> Self {
1112 Self::new(WaeErrorKind::TokenExpired)
1113 }
1114
1115 pub fn token_not_valid_yet() -> Self {
1117 Self::new(WaeErrorKind::TokenNotValidYet)
1118 }
1119
1120 pub fn invalid_signature() -> Self {
1122 Self::new(WaeErrorKind::InvalidSignature)
1123 }
1124
1125 pub fn missing_claim(claim: impl Into<String>) -> Self {
1127 Self::new(WaeErrorKind::MissingClaim { claim: claim.into() })
1128 }
1129
1130 pub fn invalid_claim(claim: impl Into<String>) -> Self {
1132 Self::new(WaeErrorKind::InvalidClaim { claim: claim.into() })
1133 }
1134
1135 pub fn invalid_issuer(expected: impl Into<String>, actual: impl Into<String>) -> Self {
1137 Self::new(WaeErrorKind::InvalidIssuer { expected: expected.into(), actual: actual.into() })
1138 }
1139
1140 pub fn invalid_audience() -> Self {
1142 Self::new(WaeErrorKind::InvalidAudience)
1143 }
1144
1145 pub fn permission_denied(action: impl Into<String>) -> Self {
1149 Self::new(WaeErrorKind::PermissionDenied { action: action.into() })
1150 }
1151
1152 pub fn forbidden(resource: impl Into<String>) -> Self {
1154 Self::new(WaeErrorKind::Forbidden { resource: resource.into() })
1155 }
1156
1157 pub fn not_found(resource_type: impl Into<String>, identifier: impl Into<String>) -> Self {
1161 Self::new(WaeErrorKind::ResourceNotFound { resource_type: resource_type.into(), identifier: identifier.into() })
1162 }
1163
1164 pub fn connection_failed(target: impl Into<String>) -> Self {
1168 Self::new(WaeErrorKind::ConnectionFailed { target: target.into() })
1169 }
1170
1171 pub fn service_unavailable(service: impl Into<String>) -> Self {
1173 Self::new(WaeErrorKind::ServiceUnavailable { service: service.into() })
1174 }
1175
1176 pub fn storage_read_failed(path: impl Into<String>) -> Self {
1180 Self::new(WaeErrorKind::StorageReadFailed { path: path.into() })
1181 }
1182
1183 pub fn storage_write_failed(path: impl Into<String>) -> Self {
1185 Self::new(WaeErrorKind::StorageWriteFailed { path: path.into() })
1186 }
1187
1188 pub fn storage_file_not_found(path: impl Into<String>) -> Self {
1190 Self::new(WaeErrorKind::StorageFileNotFound { path: path.into() })
1191 }
1192
1193 pub fn database(kind: WaeErrorKind) -> Self {
1197 Self::new(kind)
1198 }
1199
1200 pub fn database_connection_failed(reason: impl Into<String>) -> Self {
1202 Self::new(WaeErrorKind::DatabaseConnectionFailed { reason: reason.into() })
1203 }
1204
1205 pub fn query_failed(query: Option<String>, reason: impl Into<String>) -> Self {
1207 Self::new(WaeErrorKind::QueryFailed { query, reason: reason.into() })
1208 }
1209
1210 pub fn execute_failed(statement: Option<String>, reason: impl Into<String>) -> Self {
1212 Self::new(WaeErrorKind::ExecuteFailed { statement, reason: reason.into() })
1213 }
1214
1215 pub fn transaction_failed(reason: impl Into<String>) -> Self {
1217 Self::new(WaeErrorKind::TransactionFailed { reason: reason.into() })
1218 }
1219
1220 pub fn cache_key_not_found(key: impl Into<String>) -> Self {
1224 Self::new(WaeErrorKind::CacheKeyNotFound { key: key.into() })
1225 }
1226
1227 pub fn serialization_failed(type_name: impl Into<String>) -> Self {
1229 Self::new(WaeErrorKind::SerializationFailed { type_name: type_name.into() })
1230 }
1231
1232 pub fn deserialization_failed(type_name: impl Into<String>) -> Self {
1234 Self::new(WaeErrorKind::DeserializationFailed { type_name: type_name.into() })
1235 }
1236
1237 pub fn circuit_breaker_open(service: impl Into<String>) -> Self {
1241 Self::new(WaeErrorKind::CircuitBreakerOpen { service: service.into() })
1242 }
1243
1244 pub fn rate_limit_exceeded(limit: u64) -> Self {
1246 Self::new(WaeErrorKind::RateLimitExceeded { limit })
1247 }
1248
1249 pub fn max_retries_exceeded(attempts: u32) -> Self {
1251 Self::new(WaeErrorKind::MaxRetriesExceeded { attempts })
1252 }
1253
1254 pub fn bulkhead_full(max_concurrent: usize) -> Self {
1256 Self::new(WaeErrorKind::BulkheadFull { max_concurrent })
1257 }
1258
1259 pub fn task_not_found(task_id: impl Into<String>) -> Self {
1263 Self::new(WaeErrorKind::TaskNotFound { task_id: task_id.into() })
1264 }
1265
1266 pub fn invalid_cron_expression(expression: impl Into<String>) -> Self {
1268 Self::new(WaeErrorKind::InvalidCronExpression { expression: expression.into() })
1269 }
1270
1271 pub fn task_execution_failed(task_id: impl Into<String>, reason: impl Into<String>) -> Self {
1273 Self::new(WaeErrorKind::TaskExecutionFailed { task_id: task_id.into(), reason: reason.into() })
1274 }
1275
1276 pub fn scheduler_shutdown() -> Self {
1278 Self::new(WaeErrorKind::SchedulerShutdown)
1279 }
1280
1281 pub fn operation_timeout(operation: impl Into<String>, timeout_ms: u64) -> Self {
1285 Self::new(WaeErrorKind::OperationTimeout { operation: operation.into(), timeout_ms })
1286 }
1287
1288 pub fn config_missing(key: impl Into<String>) -> Self {
1292 Self::new(WaeErrorKind::ConfigMissing { key: key.into() })
1293 }
1294
1295 pub fn config_invalid(key: impl Into<String>, reason: impl Into<String>) -> Self {
1297 Self::new(WaeErrorKind::ConfigInvalid { key: key.into(), reason: reason.into() })
1298 }
1299
1300 pub fn internal(reason: impl Into<String>) -> Self {
1304 Self::new(WaeErrorKind::InternalError { reason: reason.into() })
1305 }
1306
1307 pub fn not_implemented(feature: impl Into<String>) -> Self {
1309 Self::new(WaeErrorKind::NotImplemented { feature: feature.into() })
1310 }
1311
1312 pub fn io_error(operation: impl Into<String>, reason: impl Into<String>) -> Self {
1314 Self::new(WaeErrorKind::IoError { operation: operation.into(), reason: reason.into() })
1315 }
1316
1317 pub fn json_error(reason: impl Into<String>) -> Self {
1319 Self::new(WaeErrorKind::JsonError { reason: reason.into() })
1320 }
1321
1322 pub fn parse_error(type_name: impl Into<String>, reason: impl Into<String>) -> Self {
1324 Self::new(WaeErrorKind::ParseError { type_name: type_name.into(), reason: reason.into() })
1325 }
1326
1327 pub fn request_error(url: impl Into<String>, reason: impl Into<String>) -> Self {
1329 Self::new(WaeErrorKind::RequestError { url: url.into(), reason: reason.into() })
1330 }
1331}
1332
1333impl fmt::Display for WaeError {
1334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1335 write!(f, "{:?}: {}", self.kind.category(), self.kind.i18n_key())
1336 }
1337}
1338
1339impl std::error::Error for WaeError {}
1340
1341impl From<std::io::Error> for WaeError {
1342 fn from(err: std::io::Error) -> Self {
1343 Self::io_error("unknown", err.to_string())
1344 }
1345}
1346
1347impl From<serde_json::Error> for WaeError {
1348 fn from(err: serde_json::Error) -> Self {
1349 Self::json_error(err.to_string())
1350 }
1351}
1352
1353pub type WaeResult<T> = Result<T, WaeError>;