1use std::fmt;
29
30#[derive(Debug, thiserror::Error)]
35pub enum TxGateError {
36 #[error("Parse error: {0}")]
38 Parse(#[from] ParseError),
39
40 #[error("Policy denied: {rule} - {reason}")]
42 PolicyDenied {
43 rule: String,
45 reason: String,
47 },
48
49 #[error("Signing error: {0}")]
51 Sign(#[from] SignError),
52
53 #[error("Storage error: {0}")]
55 Store(#[from] StoreError),
56
57 #[error("Configuration error: {0}")]
59 Config(#[from] ConfigError),
60
61 #[error("Policy error: {0}")]
63 Policy(#[from] PolicyError),
64}
65
66impl TxGateError {
67 #[must_use]
69 pub fn policy_denied(rule: impl Into<String>, reason: impl Into<String>) -> Self {
70 Self::PolicyDenied {
71 rule: rule.into(),
72 reason: reason.into(),
73 }
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
82#[repr(i32)]
83pub enum RpcErrorCode {
84 ParseFailed = -32700,
86 InvalidRequest = -32600,
88 MethodNotFound = -32601,
90 InvalidParams = -32602,
92 InternalError = -32603,
94
95 PolicyDenied = -32001,
98 ChainNotSupported = -32002,
100 KeyNotFound = -32003,
102 SignatureFailed = -32004,
104}
105
106impl RpcErrorCode {
107 #[must_use]
109 pub const fn code(self) -> i32 {
110 self as i32
111 }
112
113 #[must_use]
115 pub const fn message(self) -> &'static str {
116 match self {
117 Self::ParseFailed => "Parse error",
118 Self::InvalidRequest => "Invalid Request",
119 Self::MethodNotFound => "Method not found",
120 Self::InvalidParams => "Invalid params",
121 Self::InternalError => "Internal error",
122 Self::PolicyDenied => "Policy denied",
123 Self::ChainNotSupported => "Chain not supported",
124 Self::KeyNotFound => "Key not found",
125 Self::SignatureFailed => "Signature failed",
126 }
127 }
128}
129
130impl fmt::Display for RpcErrorCode {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 write!(f, "{} ({})", self.message(), self.code())
133 }
134}
135
136impl From<&TxGateError> for RpcErrorCode {
137 fn from(error: &TxGateError) -> Self {
138 match error {
139 TxGateError::Parse(ParseError::UnsupportedChain { .. }) => Self::ChainNotSupported,
140 TxGateError::Parse(_) => Self::ParseFailed,
141 TxGateError::PolicyDenied { .. } | TxGateError::Policy(_) => Self::PolicyDenied,
142 TxGateError::Sign(SignError::KeyNotFound { .. })
143 | TxGateError::Store(StoreError::KeyNotFound { .. }) => Self::KeyNotFound,
144 TxGateError::Sign(_) => Self::SignatureFailed,
145 TxGateError::Store(_) | TxGateError::Config(_) => Self::InternalError,
146 }
147 }
148}
149
150impl From<TxGateError> for RpcErrorCode {
151 fn from(error: TxGateError) -> Self {
152 Self::from(&error)
153 }
154}
155
156#[derive(Debug, thiserror::Error)]
165pub enum ParseError {
166 #[error("unknown transaction type")]
168 UnknownTxType,
169
170 #[error("RLP decoding failed: {context}")]
172 InvalidRlp {
173 context: String,
175 },
176
177 #[error("malformed transaction: {context}")]
179 MalformedTransaction {
180 context: String,
182 },
183
184 #[error("malformed calldata")]
186 MalformedCalldata,
187
188 #[error("unsupported chain: {chain}")]
190 UnsupportedChain {
191 chain: String,
193 },
194
195 #[error("invalid address: {address}")]
197 InvalidAddress {
198 address: String,
200 },
201
202 #[error("assembly failed: {context}")]
204 AssemblyFailed {
205 context: String,
207 },
208}
209
210impl ParseError {
211 #[must_use]
213 pub fn invalid_rlp(context: impl Into<String>) -> Self {
214 Self::InvalidRlp {
215 context: context.into(),
216 }
217 }
218
219 #[must_use]
221 pub fn malformed_transaction(context: impl Into<String>) -> Self {
222 Self::MalformedTransaction {
223 context: context.into(),
224 }
225 }
226
227 #[must_use]
229 pub fn unsupported_chain(chain: impl Into<String>) -> Self {
230 Self::UnsupportedChain {
231 chain: chain.into(),
232 }
233 }
234
235 #[must_use]
237 pub fn invalid_address(address: impl Into<String>) -> Self {
238 Self::InvalidAddress {
239 address: address.into(),
240 }
241 }
242
243 #[must_use]
245 pub fn assembly_failed(context: impl Into<String>) -> Self {
246 Self::AssemblyFailed {
247 context: context.into(),
248 }
249 }
250}
251
252#[derive(Debug, thiserror::Error)]
261pub enum SignError {
262 #[error("key not found: {name}")]
264 KeyNotFound {
265 name: String,
267 },
268
269 #[error("invalid key material")]
271 InvalidKey,
272
273 #[error("signature failed: {context}")]
275 SignatureFailed {
276 context: String,
278 },
279
280 #[error("wrong curve: expected {expected}, got {actual}")]
282 WrongCurve {
283 expected: String,
285 actual: String,
287 },
288}
289
290impl SignError {
291 #[must_use]
293 pub fn key_not_found(name: impl Into<String>) -> Self {
294 Self::KeyNotFound { name: name.into() }
295 }
296
297 #[must_use]
299 pub fn signature_failed(context: impl Into<String>) -> Self {
300 Self::SignatureFailed {
301 context: context.into(),
302 }
303 }
304
305 #[must_use]
307 pub fn wrong_curve(expected: impl Into<String>, actual: impl Into<String>) -> Self {
308 Self::WrongCurve {
309 expected: expected.into(),
310 actual: actual.into(),
311 }
312 }
313}
314
315#[derive(Debug, thiserror::Error)]
324pub enum StoreError {
325 #[error("I/O error: {0}")]
327 IoError(#[source] std::io::Error),
328
329 #[error("encryption failed")]
331 EncryptionFailed,
332
333 #[error("decryption failed (wrong password?)")]
335 DecryptionFailed,
336
337 #[error("key already exists: {name}")]
339 KeyExists {
340 name: String,
342 },
343
344 #[error("key not found: {name}")]
346 KeyNotFound {
347 name: String,
349 },
350
351 #[error("invalid key file format")]
353 InvalidFormat,
354
355 #[error("permission denied")]
357 PermissionDenied,
358}
359
360impl StoreError {
361 #[must_use]
363 pub const fn io_error(error: std::io::Error) -> Self {
364 Self::IoError(error)
365 }
366
367 #[must_use]
369 pub fn key_exists(name: impl Into<String>) -> Self {
370 Self::KeyExists { name: name.into() }
371 }
372
373 #[must_use]
375 pub fn key_not_found(name: impl Into<String>) -> Self {
376 Self::KeyNotFound { name: name.into() }
377 }
378}
379
380impl From<std::io::Error> for StoreError {
381 fn from(error: std::io::Error) -> Self {
382 match error.kind() {
384 std::io::ErrorKind::PermissionDenied => Self::PermissionDenied,
385 std::io::ErrorKind::NotFound => Self::InvalidFormat,
386 _ => Self::IoError(error),
387 }
388 }
389}
390
391#[derive(Debug, thiserror::Error)]
400pub enum PolicyError {
401 #[error("address is blacklisted: {address}")]
403 Blacklisted {
404 address: String,
406 },
407
408 #[error("address not in whitelist: {address}")]
410 NotWhitelisted {
411 address: String,
413 },
414
415 #[error("exceeds transaction limit: limit={limit}, amount={amount}")]
417 ExceedsTransactionLimit {
418 limit: String,
420 amount: String,
422 },
423
424 #[error("invalid configuration: {context}")]
426 InvalidConfiguration {
427 context: String,
429 },
430}
431
432impl PolicyError {
433 #[must_use]
435 pub fn blacklisted(address: impl Into<String>) -> Self {
436 Self::Blacklisted {
437 address: address.into(),
438 }
439 }
440
441 #[must_use]
443 pub fn not_whitelisted(address: impl Into<String>) -> Self {
444 Self::NotWhitelisted {
445 address: address.into(),
446 }
447 }
448
449 #[must_use]
451 pub fn exceeds_transaction_limit(limit: impl Into<String>, amount: impl Into<String>) -> Self {
452 Self::ExceedsTransactionLimit {
453 limit: limit.into(),
454 amount: amount.into(),
455 }
456 }
457
458 #[must_use]
460 pub fn invalid_configuration(context: impl Into<String>) -> Self {
461 Self::InvalidConfiguration {
462 context: context.into(),
463 }
464 }
465
466 #[must_use]
468 pub fn denial_reason(&self) -> String {
469 self.to_string()
470 }
471
472 #[must_use]
474 pub const fn rule_name(&self) -> &'static str {
475 match self {
476 Self::Blacklisted { .. } => "blacklist",
477 Self::NotWhitelisted { .. } => "whitelist",
478 Self::ExceedsTransactionLimit { .. } => "tx_limit",
479 Self::InvalidConfiguration { .. } => "configuration",
480 }
481 }
482}
483
484#[derive(Debug, thiserror::Error)]
490pub enum ConfigError {
491 #[error("configuration file not found: {path}")]
493 FileNotFound {
494 path: String,
496 },
497
498 #[error("failed to parse configuration: {context}")]
500 ParseFailed {
501 context: String,
503 },
504
505 #[error("invalid value for {field}: {value}")]
507 InvalidValue {
508 field: String,
510 value: String,
512 },
513
514 #[error("missing required field: {field}")]
516 MissingField {
517 field: String,
519 },
520
521 #[error("I/O error: {context}")]
523 Io {
524 context: String,
526 #[source]
528 source: std::io::Error,
529 },
530
531 #[error("could not determine home directory")]
533 NoHomeDirectory,
534}
535
536impl ConfigError {
537 #[must_use]
539 pub fn file_not_found(path: impl Into<String>) -> Self {
540 Self::FileNotFound { path: path.into() }
541 }
542
543 #[must_use]
545 pub fn parse_failed(context: impl Into<String>) -> Self {
546 Self::ParseFailed {
547 context: context.into(),
548 }
549 }
550
551 #[must_use]
553 pub fn invalid_value(field: impl Into<String>, value: impl Into<String>) -> Self {
554 Self::InvalidValue {
555 field: field.into(),
556 value: value.into(),
557 }
558 }
559
560 #[must_use]
562 pub fn missing_field(field: impl Into<String>) -> Self {
563 Self::MissingField {
564 field: field.into(),
565 }
566 }
567
568 #[must_use]
570 pub fn io(context: impl Into<String>, source: std::io::Error) -> Self {
571 Self::Io {
572 context: context.into(),
573 source,
574 }
575 }
576
577 #[must_use]
579 pub const fn no_home_directory() -> Self {
580 Self::NoHomeDirectory
581 }
582}
583
584pub type Result<T> = std::result::Result<T, TxGateError>;
590
591pub type ParseResult<T> = std::result::Result<T, ParseError>;
593
594pub type SignResult<T> = std::result::Result<T, SignError>;
596
597pub type StoreResult<T> = std::result::Result<T, StoreError>;
599
600pub type PolicyResult<T> = std::result::Result<T, PolicyError>;
602
603pub type ConfigResult<T> = std::result::Result<T, ConfigError>;
605
606#[cfg(test)]
611mod tests {
612 #![allow(clippy::items_after_statements)]
613
614 use super::*;
615
616 #[test]
621 fn test_txgate_error_from_parse_error() {
622 let parse_err = ParseError::UnknownTxType;
623 let txgate_err: TxGateError = parse_err.into();
624
625 assert!(matches!(
626 txgate_err,
627 TxGateError::Parse(ParseError::UnknownTxType)
628 ));
629 assert_eq!(
630 txgate_err.to_string(),
631 "Parse error: unknown transaction type"
632 );
633 }
634
635 #[test]
636 fn test_txgate_error_from_sign_error() {
637 let sign_err = SignError::key_not_found("test-key");
638 let txgate_err: TxGateError = sign_err.into();
639
640 assert!(matches!(
641 txgate_err,
642 TxGateError::Sign(SignError::KeyNotFound { .. })
643 ));
644 assert_eq!(
645 txgate_err.to_string(),
646 "Signing error: key not found: test-key"
647 );
648 }
649
650 #[test]
651 fn test_txgate_error_from_store_error() {
652 let store_err = StoreError::DecryptionFailed;
653 let txgate_err: TxGateError = store_err.into();
654
655 assert!(matches!(
656 txgate_err,
657 TxGateError::Store(StoreError::DecryptionFailed)
658 ));
659 assert_eq!(
660 txgate_err.to_string(),
661 "Storage error: decryption failed (wrong password?)"
662 );
663 }
664
665 #[test]
666 fn test_txgate_error_from_policy_error() {
667 let policy_err = PolicyError::blacklisted("0x1234");
668 let txgate_err: TxGateError = policy_err.into();
669
670 assert!(matches!(
671 txgate_err,
672 TxGateError::Policy(PolicyError::Blacklisted { .. })
673 ));
674 assert_eq!(
675 txgate_err.to_string(),
676 "Policy error: address is blacklisted: 0x1234"
677 );
678 }
679
680 #[test]
681 fn test_txgate_error_from_config_error() {
682 let config_err = ConfigError::missing_field("api_key");
683 let txgate_err: TxGateError = config_err.into();
684
685 assert!(matches!(
686 txgate_err,
687 TxGateError::Config(ConfigError::MissingField { .. })
688 ));
689 assert_eq!(
690 txgate_err.to_string(),
691 "Configuration error: missing required field: api_key"
692 );
693 }
694
695 #[test]
696 fn test_txgate_error_policy_denied() {
697 let err = TxGateError::policy_denied("whitelist", "address not in whitelist");
698
699 assert!(matches!(err, TxGateError::PolicyDenied { .. }));
700 assert_eq!(
701 err.to_string(),
702 "Policy denied: whitelist - address not in whitelist"
703 );
704 }
705
706 #[test]
711 fn test_rpc_error_code_values() {
712 assert_eq!(RpcErrorCode::ParseFailed.code(), -32700);
713 assert_eq!(RpcErrorCode::InvalidRequest.code(), -32600);
714 assert_eq!(RpcErrorCode::MethodNotFound.code(), -32601);
715 assert_eq!(RpcErrorCode::InvalidParams.code(), -32602);
716 assert_eq!(RpcErrorCode::InternalError.code(), -32603);
717 assert_eq!(RpcErrorCode::PolicyDenied.code(), -32001);
718 assert_eq!(RpcErrorCode::ChainNotSupported.code(), -32002);
719 assert_eq!(RpcErrorCode::KeyNotFound.code(), -32003);
720 assert_eq!(RpcErrorCode::SignatureFailed.code(), -32004);
721 }
722
723 #[test]
724 fn test_rpc_error_code_messages() {
725 assert_eq!(RpcErrorCode::ParseFailed.message(), "Parse error");
726 assert_eq!(RpcErrorCode::InvalidRequest.message(), "Invalid Request");
727 assert_eq!(RpcErrorCode::MethodNotFound.message(), "Method not found");
728 assert_eq!(RpcErrorCode::InvalidParams.message(), "Invalid params");
729 assert_eq!(RpcErrorCode::InternalError.message(), "Internal error");
730 assert_eq!(RpcErrorCode::PolicyDenied.message(), "Policy denied");
731 assert_eq!(
732 RpcErrorCode::ChainNotSupported.message(),
733 "Chain not supported"
734 );
735 assert_eq!(RpcErrorCode::KeyNotFound.message(), "Key not found");
736 assert_eq!(RpcErrorCode::SignatureFailed.message(), "Signature failed");
737 }
738
739 #[test]
740 fn test_rpc_error_code_display() {
741 assert_eq!(
742 RpcErrorCode::ParseFailed.to_string(),
743 "Parse error (-32700)"
744 );
745 assert_eq!(
746 RpcErrorCode::KeyNotFound.to_string(),
747 "Key not found (-32003)"
748 );
749 }
750
751 #[test]
752 fn test_rpc_error_code_from_txgate_error() {
753 let err = TxGateError::Parse(ParseError::UnknownTxType);
755 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::ParseFailed);
756
757 let err = TxGateError::Parse(ParseError::unsupported_chain("unknown"));
758 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::ChainNotSupported);
759
760 let err = TxGateError::Sign(SignError::key_not_found("test"));
762 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::KeyNotFound);
763
764 let err = TxGateError::Sign(SignError::InvalidKey);
765 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::SignatureFailed);
766
767 let err = TxGateError::policy_denied("whitelist", "not allowed");
769 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::PolicyDenied);
770
771 let err = TxGateError::Policy(PolicyError::blacklisted("0x1234"));
772 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::PolicyDenied);
773
774 let err = TxGateError::Store(StoreError::key_not_found("test"));
776 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::KeyNotFound);
777
778 let err = TxGateError::Store(StoreError::DecryptionFailed);
779 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::InternalError);
780
781 let err = TxGateError::Config(ConfigError::missing_field("test"));
783 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::InternalError);
784 }
785
786 #[test]
791 fn test_parse_error_display() {
792 assert_eq!(
793 ParseError::UnknownTxType.to_string(),
794 "unknown transaction type"
795 );
796
797 assert_eq!(
798 ParseError::invalid_rlp("failed to decode nonce").to_string(),
799 "RLP decoding failed: failed to decode nonce"
800 );
801
802 assert_eq!(
803 ParseError::malformed_transaction("missing to field").to_string(),
804 "malformed transaction: missing to field"
805 );
806
807 assert_eq!(
808 ParseError::MalformedCalldata.to_string(),
809 "malformed calldata"
810 );
811
812 assert_eq!(
813 ParseError::unsupported_chain("cosmos").to_string(),
814 "unsupported chain: cosmos"
815 );
816
817 assert_eq!(
818 ParseError::invalid_address("0xinvalid").to_string(),
819 "invalid address: 0xinvalid"
820 );
821
822 assert_eq!(
823 ParseError::assembly_failed("missing signature").to_string(),
824 "assembly failed: missing signature"
825 );
826 }
827
828 #[test]
829 fn test_parse_error_constructors() {
830 let err = ParseError::invalid_rlp("test context");
831 assert!(matches!(err, ParseError::InvalidRlp { context } if context == "test context"));
832
833 let err = ParseError::malformed_transaction("test context");
834 assert!(
835 matches!(err, ParseError::MalformedTransaction { context } if context == "test context")
836 );
837
838 let err = ParseError::unsupported_chain("test-chain");
839 assert!(matches!(err, ParseError::UnsupportedChain { chain } if chain == "test-chain"));
840
841 let err = ParseError::invalid_address("bad-addr");
842 assert!(matches!(err, ParseError::InvalidAddress { address } if address == "bad-addr"));
843
844 let err = ParseError::assembly_failed("test context");
845 assert!(matches!(err, ParseError::AssemblyFailed { context } if context == "test context"));
846 }
847
848 #[test]
853 fn test_sign_error_display() {
854 assert_eq!(
855 SignError::key_not_found("my-key").to_string(),
856 "key not found: my-key"
857 );
858
859 assert_eq!(SignError::InvalidKey.to_string(), "invalid key material");
860
861 assert_eq!(
862 SignError::signature_failed("hash mismatch").to_string(),
863 "signature failed: hash mismatch"
864 );
865
866 assert_eq!(
867 SignError::wrong_curve("secp256k1", "ed25519").to_string(),
868 "wrong curve: expected secp256k1, got ed25519"
869 );
870 }
871
872 #[test]
873 fn test_sign_error_constructors() {
874 let err = SignError::key_not_found("test-key");
875 assert!(matches!(err, SignError::KeyNotFound { name } if name == "test-key"));
876
877 let err = SignError::signature_failed("test reason");
878 assert!(matches!(err, SignError::SignatureFailed { context } if context == "test reason"));
879
880 let err = SignError::wrong_curve("secp256k1", "ed25519");
881 assert!(
882 matches!(err, SignError::WrongCurve { expected, actual } if expected == "secp256k1" && actual == "ed25519")
883 );
884 }
885
886 #[test]
891 fn test_store_error_display() {
892 assert_eq!(
893 StoreError::EncryptionFailed.to_string(),
894 "encryption failed"
895 );
896
897 assert_eq!(
898 StoreError::DecryptionFailed.to_string(),
899 "decryption failed (wrong password?)"
900 );
901
902 assert_eq!(
903 StoreError::key_exists("existing-key").to_string(),
904 "key already exists: existing-key"
905 );
906
907 assert_eq!(
908 StoreError::key_not_found("missing-key").to_string(),
909 "key not found: missing-key"
910 );
911
912 assert_eq!(
913 StoreError::InvalidFormat.to_string(),
914 "invalid key file format"
915 );
916
917 assert_eq!(
918 StoreError::PermissionDenied.to_string(),
919 "permission denied"
920 );
921 }
922
923 #[test]
924 fn test_store_error_from_io_error() {
925 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
926 let store_err: StoreError = io_err.into();
927 assert!(matches!(store_err, StoreError::PermissionDenied));
928
929 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
930 let store_err: StoreError = io_err.into();
931 assert!(matches!(store_err, StoreError::InvalidFormat));
932
933 let io_err = std::io::Error::other("something else");
934 let store_err: StoreError = io_err.into();
935 assert!(matches!(store_err, StoreError::IoError(_)));
936 }
937
938 #[test]
939 fn test_store_error_constructors() {
940 let err = StoreError::key_exists("test-key");
941 assert!(matches!(err, StoreError::KeyExists { name } if name == "test-key"));
942
943 let err = StoreError::key_not_found("test-key");
944 assert!(matches!(err, StoreError::KeyNotFound { name } if name == "test-key"));
945 }
946
947 #[test]
952 fn test_policy_error_display() {
953 assert_eq!(
954 PolicyError::blacklisted("0x1234").to_string(),
955 "address is blacklisted: 0x1234"
956 );
957
958 assert_eq!(
959 PolicyError::not_whitelisted("0x5678").to_string(),
960 "address not in whitelist: 0x5678"
961 );
962
963 assert_eq!(
964 PolicyError::exceeds_transaction_limit("5 ETH", "10 ETH").to_string(),
965 "exceeds transaction limit: limit=5 ETH, amount=10 ETH"
966 );
967
968 assert_eq!(
969 PolicyError::invalid_configuration("missing whitelist").to_string(),
970 "invalid configuration: missing whitelist"
971 );
972 }
973
974 #[test]
975 fn test_policy_error_rule_names() {
976 assert_eq!(PolicyError::blacklisted("x").rule_name(), "blacklist");
977 assert_eq!(PolicyError::not_whitelisted("x").rule_name(), "whitelist");
978 assert_eq!(
979 PolicyError::exceeds_transaction_limit("1", "2").rule_name(),
980 "tx_limit"
981 );
982 assert_eq!(
983 PolicyError::invalid_configuration("x").rule_name(),
984 "configuration"
985 );
986 }
987
988 #[test]
989 fn test_policy_error_denial_reason() {
990 let err = PolicyError::blacklisted("0x1234");
991 assert_eq!(err.denial_reason(), "address is blacklisted: 0x1234");
992 }
993
994 #[test]
999 fn test_config_error_display() {
1000 assert_eq!(
1001 ConfigError::file_not_found("/path/to/config.toml").to_string(),
1002 "configuration file not found: /path/to/config.toml"
1003 );
1004
1005 assert_eq!(
1006 ConfigError::parse_failed("invalid TOML syntax").to_string(),
1007 "failed to parse configuration: invalid TOML syntax"
1008 );
1009
1010 assert_eq!(
1011 ConfigError::invalid_value("port", "-1").to_string(),
1012 "invalid value for port: -1"
1013 );
1014
1015 assert_eq!(
1016 ConfigError::missing_field("api_key").to_string(),
1017 "missing required field: api_key"
1018 );
1019 }
1020
1021 #[test]
1022 fn test_config_error_constructors() {
1023 let err = ConfigError::file_not_found("/test/path");
1024 assert!(matches!(err, ConfigError::FileNotFound { path } if path == "/test/path"));
1025
1026 let err = ConfigError::parse_failed("test context");
1027 assert!(matches!(err, ConfigError::ParseFailed { context } if context == "test context"));
1028
1029 let err = ConfigError::invalid_value("field", "value");
1030 assert!(
1031 matches!(err, ConfigError::InvalidValue { field, value } if field == "field" && value == "value")
1032 );
1033
1034 let err = ConfigError::missing_field("test_field");
1035 assert!(matches!(err, ConfigError::MissingField { field } if field == "test_field"));
1036
1037 let io_err = std::io::Error::other("test io error");
1038 let err = ConfigError::io("reading config", io_err);
1039 assert!(matches!(err, ConfigError::Io { context, .. } if context == "reading config"));
1040
1041 let err = ConfigError::no_home_directory();
1042 assert!(matches!(err, ConfigError::NoHomeDirectory));
1043 }
1044
1045 #[test]
1046 fn test_config_error_io_display() {
1047 let io_err = std::io::Error::other("underlying error");
1048 let err = ConfigError::io("reading config file", io_err);
1049 assert_eq!(err.to_string(), "I/O error: reading config file");
1050 }
1051
1052 #[test]
1053 fn test_config_error_no_home_directory_display() {
1054 let err = ConfigError::no_home_directory();
1055 assert_eq!(err.to_string(), "could not determine home directory");
1056 }
1057
1058 #[test]
1059 fn test_config_error_io_source() {
1060 let io_err = std::io::Error::other("test error");
1061 let config_err = ConfigError::io("test context", io_err);
1062
1063 use std::error::Error;
1065 assert!(config_err.source().is_some());
1066 }
1067
1068 #[test]
1073 fn test_store_error_source() {
1074 let io_err = std::io::Error::other("test error");
1075 let store_err = StoreError::io_error(io_err);
1076
1077 use std::error::Error;
1079 assert!(store_err.source().is_some());
1080 }
1081
1082 #[test]
1083 fn test_txgate_error_is_send_sync() {
1084 fn assert_send_sync<T: Send + Sync>() {}
1085 assert_send_sync::<TxGateError>();
1086 assert_send_sync::<ParseError>();
1087 assert_send_sync::<SignError>();
1088 assert_send_sync::<StoreError>();
1089 assert_send_sync::<PolicyError>();
1090 assert_send_sync::<ConfigError>();
1091 }
1092}