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
203impl ParseError {
204 #[must_use]
206 pub fn invalid_rlp(context: impl Into<String>) -> Self {
207 Self::InvalidRlp {
208 context: context.into(),
209 }
210 }
211
212 #[must_use]
214 pub fn malformed_transaction(context: impl Into<String>) -> Self {
215 Self::MalformedTransaction {
216 context: context.into(),
217 }
218 }
219
220 #[must_use]
222 pub fn unsupported_chain(chain: impl Into<String>) -> Self {
223 Self::UnsupportedChain {
224 chain: chain.into(),
225 }
226 }
227
228 #[must_use]
230 pub fn invalid_address(address: impl Into<String>) -> Self {
231 Self::InvalidAddress {
232 address: address.into(),
233 }
234 }
235}
236
237#[derive(Debug, thiserror::Error)]
246pub enum SignError {
247 #[error("key not found: {name}")]
249 KeyNotFound {
250 name: String,
252 },
253
254 #[error("invalid key material")]
256 InvalidKey,
257
258 #[error("signature failed: {context}")]
260 SignatureFailed {
261 context: String,
263 },
264
265 #[error("wrong curve: expected {expected}, got {actual}")]
267 WrongCurve {
268 expected: String,
270 actual: String,
272 },
273}
274
275impl SignError {
276 #[must_use]
278 pub fn key_not_found(name: impl Into<String>) -> Self {
279 Self::KeyNotFound { name: name.into() }
280 }
281
282 #[must_use]
284 pub fn signature_failed(context: impl Into<String>) -> Self {
285 Self::SignatureFailed {
286 context: context.into(),
287 }
288 }
289
290 #[must_use]
292 pub fn wrong_curve(expected: impl Into<String>, actual: impl Into<String>) -> Self {
293 Self::WrongCurve {
294 expected: expected.into(),
295 actual: actual.into(),
296 }
297 }
298}
299
300#[derive(Debug, thiserror::Error)]
309pub enum StoreError {
310 #[error("I/O error: {0}")]
312 IoError(#[source] std::io::Error),
313
314 #[error("encryption failed")]
316 EncryptionFailed,
317
318 #[error("decryption failed (wrong password?)")]
320 DecryptionFailed,
321
322 #[error("key already exists: {name}")]
324 KeyExists {
325 name: String,
327 },
328
329 #[error("key not found: {name}")]
331 KeyNotFound {
332 name: String,
334 },
335
336 #[error("invalid key file format")]
338 InvalidFormat,
339
340 #[error("permission denied")]
342 PermissionDenied,
343}
344
345impl StoreError {
346 #[must_use]
348 pub const fn io_error(error: std::io::Error) -> Self {
349 Self::IoError(error)
350 }
351
352 #[must_use]
354 pub fn key_exists(name: impl Into<String>) -> Self {
355 Self::KeyExists { name: name.into() }
356 }
357
358 #[must_use]
360 pub fn key_not_found(name: impl Into<String>) -> Self {
361 Self::KeyNotFound { name: name.into() }
362 }
363}
364
365impl From<std::io::Error> for StoreError {
366 fn from(error: std::io::Error) -> Self {
367 match error.kind() {
369 std::io::ErrorKind::PermissionDenied => Self::PermissionDenied,
370 std::io::ErrorKind::NotFound => Self::InvalidFormat,
371 _ => Self::IoError(error),
372 }
373 }
374}
375
376#[derive(Debug, thiserror::Error)]
385pub enum PolicyError {
386 #[error("address is blacklisted: {address}")]
388 Blacklisted {
389 address: String,
391 },
392
393 #[error("address not in whitelist: {address}")]
395 NotWhitelisted {
396 address: String,
398 },
399
400 #[error("exceeds transaction limit: limit={limit}, amount={amount}")]
402 ExceedsTransactionLimit {
403 limit: String,
405 amount: String,
407 },
408
409 #[error("invalid configuration: {context}")]
411 InvalidConfiguration {
412 context: String,
414 },
415}
416
417impl PolicyError {
418 #[must_use]
420 pub fn blacklisted(address: impl Into<String>) -> Self {
421 Self::Blacklisted {
422 address: address.into(),
423 }
424 }
425
426 #[must_use]
428 pub fn not_whitelisted(address: impl Into<String>) -> Self {
429 Self::NotWhitelisted {
430 address: address.into(),
431 }
432 }
433
434 #[must_use]
436 pub fn exceeds_transaction_limit(limit: impl Into<String>, amount: impl Into<String>) -> Self {
437 Self::ExceedsTransactionLimit {
438 limit: limit.into(),
439 amount: amount.into(),
440 }
441 }
442
443 #[must_use]
445 pub fn invalid_configuration(context: impl Into<String>) -> Self {
446 Self::InvalidConfiguration {
447 context: context.into(),
448 }
449 }
450
451 #[must_use]
453 pub fn denial_reason(&self) -> String {
454 self.to_string()
455 }
456
457 #[must_use]
459 pub const fn rule_name(&self) -> &'static str {
460 match self {
461 Self::Blacklisted { .. } => "blacklist",
462 Self::NotWhitelisted { .. } => "whitelist",
463 Self::ExceedsTransactionLimit { .. } => "tx_limit",
464 Self::InvalidConfiguration { .. } => "configuration",
465 }
466 }
467}
468
469#[derive(Debug, thiserror::Error)]
475pub enum ConfigError {
476 #[error("configuration file not found: {path}")]
478 FileNotFound {
479 path: String,
481 },
482
483 #[error("failed to parse configuration: {context}")]
485 ParseFailed {
486 context: String,
488 },
489
490 #[error("invalid value for {field}: {value}")]
492 InvalidValue {
493 field: String,
495 value: String,
497 },
498
499 #[error("missing required field: {field}")]
501 MissingField {
502 field: String,
504 },
505
506 #[error("I/O error: {context}")]
508 Io {
509 context: String,
511 #[source]
513 source: std::io::Error,
514 },
515
516 #[error("could not determine home directory")]
518 NoHomeDirectory,
519}
520
521impl ConfigError {
522 #[must_use]
524 pub fn file_not_found(path: impl Into<String>) -> Self {
525 Self::FileNotFound { path: path.into() }
526 }
527
528 #[must_use]
530 pub fn parse_failed(context: impl Into<String>) -> Self {
531 Self::ParseFailed {
532 context: context.into(),
533 }
534 }
535
536 #[must_use]
538 pub fn invalid_value(field: impl Into<String>, value: impl Into<String>) -> Self {
539 Self::InvalidValue {
540 field: field.into(),
541 value: value.into(),
542 }
543 }
544
545 #[must_use]
547 pub fn missing_field(field: impl Into<String>) -> Self {
548 Self::MissingField {
549 field: field.into(),
550 }
551 }
552
553 #[must_use]
555 pub fn io(context: impl Into<String>, source: std::io::Error) -> Self {
556 Self::Io {
557 context: context.into(),
558 source,
559 }
560 }
561
562 #[must_use]
564 pub const fn no_home_directory() -> Self {
565 Self::NoHomeDirectory
566 }
567}
568
569pub type Result<T> = std::result::Result<T, TxGateError>;
575
576pub type ParseResult<T> = std::result::Result<T, ParseError>;
578
579pub type SignResult<T> = std::result::Result<T, SignError>;
581
582pub type StoreResult<T> = std::result::Result<T, StoreError>;
584
585pub type PolicyResult<T> = std::result::Result<T, PolicyError>;
587
588pub type ConfigResult<T> = std::result::Result<T, ConfigError>;
590
591#[cfg(test)]
596mod tests {
597 #![allow(clippy::items_after_statements)]
598
599 use super::*;
600
601 #[test]
606 fn test_txgate_error_from_parse_error() {
607 let parse_err = ParseError::UnknownTxType;
608 let txgate_err: TxGateError = parse_err.into();
609
610 assert!(matches!(
611 txgate_err,
612 TxGateError::Parse(ParseError::UnknownTxType)
613 ));
614 assert_eq!(
615 txgate_err.to_string(),
616 "Parse error: unknown transaction type"
617 );
618 }
619
620 #[test]
621 fn test_txgate_error_from_sign_error() {
622 let sign_err = SignError::key_not_found("test-key");
623 let txgate_err: TxGateError = sign_err.into();
624
625 assert!(matches!(
626 txgate_err,
627 TxGateError::Sign(SignError::KeyNotFound { .. })
628 ));
629 assert_eq!(
630 txgate_err.to_string(),
631 "Signing error: key not found: test-key"
632 );
633 }
634
635 #[test]
636 fn test_txgate_error_from_store_error() {
637 let store_err = StoreError::DecryptionFailed;
638 let txgate_err: TxGateError = store_err.into();
639
640 assert!(matches!(
641 txgate_err,
642 TxGateError::Store(StoreError::DecryptionFailed)
643 ));
644 assert_eq!(
645 txgate_err.to_string(),
646 "Storage error: decryption failed (wrong password?)"
647 );
648 }
649
650 #[test]
651 fn test_txgate_error_from_policy_error() {
652 let policy_err = PolicyError::blacklisted("0x1234");
653 let txgate_err: TxGateError = policy_err.into();
654
655 assert!(matches!(
656 txgate_err,
657 TxGateError::Policy(PolicyError::Blacklisted { .. })
658 ));
659 assert_eq!(
660 txgate_err.to_string(),
661 "Policy error: address is blacklisted: 0x1234"
662 );
663 }
664
665 #[test]
666 fn test_txgate_error_from_config_error() {
667 let config_err = ConfigError::missing_field("api_key");
668 let txgate_err: TxGateError = config_err.into();
669
670 assert!(matches!(
671 txgate_err,
672 TxGateError::Config(ConfigError::MissingField { .. })
673 ));
674 assert_eq!(
675 txgate_err.to_string(),
676 "Configuration error: missing required field: api_key"
677 );
678 }
679
680 #[test]
681 fn test_txgate_error_policy_denied() {
682 let err = TxGateError::policy_denied("whitelist", "address not in whitelist");
683
684 assert!(matches!(err, TxGateError::PolicyDenied { .. }));
685 assert_eq!(
686 err.to_string(),
687 "Policy denied: whitelist - address not in whitelist"
688 );
689 }
690
691 #[test]
696 fn test_rpc_error_code_values() {
697 assert_eq!(RpcErrorCode::ParseFailed.code(), -32700);
698 assert_eq!(RpcErrorCode::InvalidRequest.code(), -32600);
699 assert_eq!(RpcErrorCode::MethodNotFound.code(), -32601);
700 assert_eq!(RpcErrorCode::InvalidParams.code(), -32602);
701 assert_eq!(RpcErrorCode::InternalError.code(), -32603);
702 assert_eq!(RpcErrorCode::PolicyDenied.code(), -32001);
703 assert_eq!(RpcErrorCode::ChainNotSupported.code(), -32002);
704 assert_eq!(RpcErrorCode::KeyNotFound.code(), -32003);
705 assert_eq!(RpcErrorCode::SignatureFailed.code(), -32004);
706 }
707
708 #[test]
709 fn test_rpc_error_code_messages() {
710 assert_eq!(RpcErrorCode::ParseFailed.message(), "Parse error");
711 assert_eq!(RpcErrorCode::InvalidRequest.message(), "Invalid Request");
712 assert_eq!(RpcErrorCode::MethodNotFound.message(), "Method not found");
713 assert_eq!(RpcErrorCode::InvalidParams.message(), "Invalid params");
714 assert_eq!(RpcErrorCode::InternalError.message(), "Internal error");
715 assert_eq!(RpcErrorCode::PolicyDenied.message(), "Policy denied");
716 assert_eq!(
717 RpcErrorCode::ChainNotSupported.message(),
718 "Chain not supported"
719 );
720 assert_eq!(RpcErrorCode::KeyNotFound.message(), "Key not found");
721 assert_eq!(RpcErrorCode::SignatureFailed.message(), "Signature failed");
722 }
723
724 #[test]
725 fn test_rpc_error_code_display() {
726 assert_eq!(
727 RpcErrorCode::ParseFailed.to_string(),
728 "Parse error (-32700)"
729 );
730 assert_eq!(
731 RpcErrorCode::KeyNotFound.to_string(),
732 "Key not found (-32003)"
733 );
734 }
735
736 #[test]
737 fn test_rpc_error_code_from_txgate_error() {
738 let err = TxGateError::Parse(ParseError::UnknownTxType);
740 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::ParseFailed);
741
742 let err = TxGateError::Parse(ParseError::unsupported_chain("unknown"));
743 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::ChainNotSupported);
744
745 let err = TxGateError::Sign(SignError::key_not_found("test"));
747 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::KeyNotFound);
748
749 let err = TxGateError::Sign(SignError::InvalidKey);
750 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::SignatureFailed);
751
752 let err = TxGateError::policy_denied("whitelist", "not allowed");
754 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::PolicyDenied);
755
756 let err = TxGateError::Policy(PolicyError::blacklisted("0x1234"));
757 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::PolicyDenied);
758
759 let err = TxGateError::Store(StoreError::key_not_found("test"));
761 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::KeyNotFound);
762
763 let err = TxGateError::Store(StoreError::DecryptionFailed);
764 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::InternalError);
765
766 let err = TxGateError::Config(ConfigError::missing_field("test"));
768 assert_eq!(RpcErrorCode::from(&err), RpcErrorCode::InternalError);
769 }
770
771 #[test]
776 fn test_parse_error_display() {
777 assert_eq!(
778 ParseError::UnknownTxType.to_string(),
779 "unknown transaction type"
780 );
781
782 assert_eq!(
783 ParseError::invalid_rlp("failed to decode nonce").to_string(),
784 "RLP decoding failed: failed to decode nonce"
785 );
786
787 assert_eq!(
788 ParseError::malformed_transaction("missing to field").to_string(),
789 "malformed transaction: missing to field"
790 );
791
792 assert_eq!(
793 ParseError::MalformedCalldata.to_string(),
794 "malformed calldata"
795 );
796
797 assert_eq!(
798 ParseError::unsupported_chain("cosmos").to_string(),
799 "unsupported chain: cosmos"
800 );
801
802 assert_eq!(
803 ParseError::invalid_address("0xinvalid").to_string(),
804 "invalid address: 0xinvalid"
805 );
806 }
807
808 #[test]
809 fn test_parse_error_constructors() {
810 let err = ParseError::invalid_rlp("test context");
811 assert!(matches!(err, ParseError::InvalidRlp { context } if context == "test context"));
812
813 let err = ParseError::malformed_transaction("test context");
814 assert!(
815 matches!(err, ParseError::MalformedTransaction { context } if context == "test context")
816 );
817
818 let err = ParseError::unsupported_chain("test-chain");
819 assert!(matches!(err, ParseError::UnsupportedChain { chain } if chain == "test-chain"));
820
821 let err = ParseError::invalid_address("bad-addr");
822 assert!(matches!(err, ParseError::InvalidAddress { address } if address == "bad-addr"));
823 }
824
825 #[test]
830 fn test_sign_error_display() {
831 assert_eq!(
832 SignError::key_not_found("my-key").to_string(),
833 "key not found: my-key"
834 );
835
836 assert_eq!(SignError::InvalidKey.to_string(), "invalid key material");
837
838 assert_eq!(
839 SignError::signature_failed("hash mismatch").to_string(),
840 "signature failed: hash mismatch"
841 );
842
843 assert_eq!(
844 SignError::wrong_curve("secp256k1", "ed25519").to_string(),
845 "wrong curve: expected secp256k1, got ed25519"
846 );
847 }
848
849 #[test]
850 fn test_sign_error_constructors() {
851 let err = SignError::key_not_found("test-key");
852 assert!(matches!(err, SignError::KeyNotFound { name } if name == "test-key"));
853
854 let err = SignError::signature_failed("test reason");
855 assert!(matches!(err, SignError::SignatureFailed { context } if context == "test reason"));
856
857 let err = SignError::wrong_curve("secp256k1", "ed25519");
858 assert!(
859 matches!(err, SignError::WrongCurve { expected, actual } if expected == "secp256k1" && actual == "ed25519")
860 );
861 }
862
863 #[test]
868 fn test_store_error_display() {
869 assert_eq!(
870 StoreError::EncryptionFailed.to_string(),
871 "encryption failed"
872 );
873
874 assert_eq!(
875 StoreError::DecryptionFailed.to_string(),
876 "decryption failed (wrong password?)"
877 );
878
879 assert_eq!(
880 StoreError::key_exists("existing-key").to_string(),
881 "key already exists: existing-key"
882 );
883
884 assert_eq!(
885 StoreError::key_not_found("missing-key").to_string(),
886 "key not found: missing-key"
887 );
888
889 assert_eq!(
890 StoreError::InvalidFormat.to_string(),
891 "invalid key file format"
892 );
893
894 assert_eq!(
895 StoreError::PermissionDenied.to_string(),
896 "permission denied"
897 );
898 }
899
900 #[test]
901 fn test_store_error_from_io_error() {
902 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
903 let store_err: StoreError = io_err.into();
904 assert!(matches!(store_err, StoreError::PermissionDenied));
905
906 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
907 let store_err: StoreError = io_err.into();
908 assert!(matches!(store_err, StoreError::InvalidFormat));
909
910 let io_err = std::io::Error::other("something else");
911 let store_err: StoreError = io_err.into();
912 assert!(matches!(store_err, StoreError::IoError(_)));
913 }
914
915 #[test]
916 fn test_store_error_constructors() {
917 let err = StoreError::key_exists("test-key");
918 assert!(matches!(err, StoreError::KeyExists { name } if name == "test-key"));
919
920 let err = StoreError::key_not_found("test-key");
921 assert!(matches!(err, StoreError::KeyNotFound { name } if name == "test-key"));
922 }
923
924 #[test]
929 fn test_policy_error_display() {
930 assert_eq!(
931 PolicyError::blacklisted("0x1234").to_string(),
932 "address is blacklisted: 0x1234"
933 );
934
935 assert_eq!(
936 PolicyError::not_whitelisted("0x5678").to_string(),
937 "address not in whitelist: 0x5678"
938 );
939
940 assert_eq!(
941 PolicyError::exceeds_transaction_limit("5 ETH", "10 ETH").to_string(),
942 "exceeds transaction limit: limit=5 ETH, amount=10 ETH"
943 );
944
945 assert_eq!(
946 PolicyError::invalid_configuration("missing whitelist").to_string(),
947 "invalid configuration: missing whitelist"
948 );
949 }
950
951 #[test]
952 fn test_policy_error_rule_names() {
953 assert_eq!(PolicyError::blacklisted("x").rule_name(), "blacklist");
954 assert_eq!(PolicyError::not_whitelisted("x").rule_name(), "whitelist");
955 assert_eq!(
956 PolicyError::exceeds_transaction_limit("1", "2").rule_name(),
957 "tx_limit"
958 );
959 assert_eq!(
960 PolicyError::invalid_configuration("x").rule_name(),
961 "configuration"
962 );
963 }
964
965 #[test]
966 fn test_policy_error_denial_reason() {
967 let err = PolicyError::blacklisted("0x1234");
968 assert_eq!(err.denial_reason(), "address is blacklisted: 0x1234");
969 }
970
971 #[test]
976 fn test_config_error_display() {
977 assert_eq!(
978 ConfigError::file_not_found("/path/to/config.toml").to_string(),
979 "configuration file not found: /path/to/config.toml"
980 );
981
982 assert_eq!(
983 ConfigError::parse_failed("invalid TOML syntax").to_string(),
984 "failed to parse configuration: invalid TOML syntax"
985 );
986
987 assert_eq!(
988 ConfigError::invalid_value("port", "-1").to_string(),
989 "invalid value for port: -1"
990 );
991
992 assert_eq!(
993 ConfigError::missing_field("api_key").to_string(),
994 "missing required field: api_key"
995 );
996 }
997
998 #[test]
999 fn test_config_error_constructors() {
1000 let err = ConfigError::file_not_found("/test/path");
1001 assert!(matches!(err, ConfigError::FileNotFound { path } if path == "/test/path"));
1002
1003 let err = ConfigError::parse_failed("test context");
1004 assert!(matches!(err, ConfigError::ParseFailed { context } if context == "test context"));
1005
1006 let err = ConfigError::invalid_value("field", "value");
1007 assert!(
1008 matches!(err, ConfigError::InvalidValue { field, value } if field == "field" && value == "value")
1009 );
1010
1011 let err = ConfigError::missing_field("test_field");
1012 assert!(matches!(err, ConfigError::MissingField { field } if field == "test_field"));
1013
1014 let io_err = std::io::Error::other("test io error");
1015 let err = ConfigError::io("reading config", io_err);
1016 assert!(matches!(err, ConfigError::Io { context, .. } if context == "reading config"));
1017
1018 let err = ConfigError::no_home_directory();
1019 assert!(matches!(err, ConfigError::NoHomeDirectory));
1020 }
1021
1022 #[test]
1023 fn test_config_error_io_display() {
1024 let io_err = std::io::Error::other("underlying error");
1025 let err = ConfigError::io("reading config file", io_err);
1026 assert_eq!(err.to_string(), "I/O error: reading config file");
1027 }
1028
1029 #[test]
1030 fn test_config_error_no_home_directory_display() {
1031 let err = ConfigError::no_home_directory();
1032 assert_eq!(err.to_string(), "could not determine home directory");
1033 }
1034
1035 #[test]
1036 fn test_config_error_io_source() {
1037 let io_err = std::io::Error::other("test error");
1038 let config_err = ConfigError::io("test context", io_err);
1039
1040 use std::error::Error;
1042 assert!(config_err.source().is_some());
1043 }
1044
1045 #[test]
1050 fn test_store_error_source() {
1051 let io_err = std::io::Error::other("test error");
1052 let store_err = StoreError::io_error(io_err);
1053
1054 use std::error::Error;
1056 assert!(store_err.source().is_some());
1057 }
1058
1059 #[test]
1060 fn test_txgate_error_is_send_sync() {
1061 fn assert_send_sync<T: Send + Sync>() {}
1062 assert_send_sync::<TxGateError>();
1063 assert_send_sync::<ParseError>();
1064 assert_send_sync::<SignError>();
1065 assert_send_sync::<StoreError>();
1066 assert_send_sync::<PolicyError>();
1067 assert_send_sync::<ConfigError>();
1068 }
1069}