1use thiserror::Error;
55
56use crate::types::{AccountId, DelegateDecodeError, InvalidTxError, PublicKey};
57
58pub type ParseAccountIdError = near_account_id::ParseAccountError;
62
63#[derive(Debug, Clone, Error, PartialEq, Eq)]
65pub enum ParseAmountError {
66 #[error("Ambiguous amount '{0}'. Use explicit units like '5 NEAR' or '1000 yocto'")]
67 AmbiguousAmount(String),
68
69 #[error("Invalid amount format: '{0}'")]
70 InvalidFormat(String),
71
72 #[error("Invalid number in amount: '{0}'")]
73 InvalidNumber(String),
74
75 #[error("Amount overflow: value too large")]
76 Overflow,
77}
78
79#[derive(Debug, Clone, Error, PartialEq, Eq)]
81pub enum ParseGasError {
82 #[error("Invalid gas format: '{0}'. Use '30 Tgas', '5 Ggas', or '1000000 gas'")]
83 InvalidFormat(String),
84
85 #[error("Invalid number in gas: '{0}'")]
86 InvalidNumber(String),
87
88 #[error("Gas overflow: value too large")]
89 Overflow,
90}
91
92#[derive(Debug, Clone, Error, PartialEq, Eq)]
94pub enum ParseKeyError {
95 #[error("Invalid key format: expected 'ed25519:...' or 'secp256k1:...'")]
96 InvalidFormat,
97
98 #[error("Unknown key type: '{0}'")]
99 UnknownKeyType(String),
100
101 #[error("Invalid base58 encoding: {0}")]
102 InvalidBase58(String),
103
104 #[error("Invalid key length: expected {expected} bytes, got {actual}")]
105 InvalidLength { expected: usize, actual: usize },
106
107 #[error("Invalid curve point: key bytes do not represent a valid point on the curve")]
108 InvalidCurvePoint,
109
110 #[error("Invalid scalar: secret key bytes are not a valid scalar for this curve")]
111 InvalidScalar,
112}
113
114#[derive(Debug, Clone, Error, PartialEq, Eq)]
116pub enum ParseHashError {
117 #[error("Invalid base58 encoding: {0}")]
118 InvalidBase58(String),
119
120 #[error("Invalid hash length: expected 32 bytes, got {0}")]
121 InvalidLength(usize),
122}
123
124#[derive(Debug, Clone, Error, PartialEq, Eq)]
126pub enum SignerError {
127 #[error("Invalid seed phrase")]
128 InvalidSeedPhrase,
129
130 #[error("Signing failed: {0}")]
131 SigningFailed(String),
132
133 #[error("Key derivation failed: {0}")]
134 KeyDerivationFailed(String),
135}
136
137#[derive(Debug, Error)]
139pub enum KeyStoreError {
140 #[error("Key not found for account: {0}")]
141 KeyNotFound(AccountId),
142
143 #[error("IO error: {0}")]
144 Io(#[from] std::io::Error),
145
146 #[error("JSON error: {0}")]
147 Json(#[from] serde_json::Error),
148
149 #[error("Invalid credential format: {0}")]
150 InvalidFormat(String),
151
152 #[error("Invalid key: {0}")]
153 InvalidKey(#[from] ParseKeyError),
154
155 #[error("Path error: {0}")]
156 PathError(String),
157
158 #[error("Platform keyring error: {0}")]
159 Platform(String),
160}
161
162#[derive(Debug, Error)]
168pub enum RpcError {
169 #[error("HTTP error: {0}")]
171 Http(#[from] reqwest::Error),
172
173 #[error("Network error: {message}")]
174 Network {
175 message: String,
176 status_code: Option<u16>,
177 retryable: bool,
178 },
179
180 #[error("Timeout after {0} retries")]
181 Timeout(u32),
182
183 #[error("JSON parse error: {0}")]
184 Json(#[from] serde_json::Error),
185
186 #[error("Invalid response: {0}")]
187 InvalidResponse(String),
188
189 #[error("RPC error: {message} (code: {code})")]
191 Rpc {
192 code: i64,
193 message: String,
194 data: Option<serde_json::Value>,
195 },
196
197 #[error("Account not found: {0}")]
199 AccountNotFound(AccountId),
200
201 #[error("Invalid account ID: {0}")]
202 InvalidAccount(String),
203
204 #[error("Access key not found: {account_id} / {public_key}")]
205 AccessKeyNotFound {
206 account_id: AccountId,
207 public_key: PublicKey,
208 },
209
210 #[error("Contract not deployed on account: {0}")]
212 ContractNotDeployed(AccountId),
213
214 #[error("Contract state too large for account: {0}")]
215 ContractStateTooLarge(AccountId),
216
217 #[error("Contract execution failed on {contract_id}: {message}")]
218 ContractExecution {
219 contract_id: AccountId,
220 method_name: Option<String>,
221 message: String,
222 },
223
224 #[error("Contract panic: {message}")]
225 ContractPanic { message: String },
226
227 #[error("Function call error on {contract_id}.{method_name}: {}", panic.as_deref().unwrap_or("unknown error"))]
228 FunctionCall {
229 contract_id: AccountId,
230 method_name: String,
231 panic: Option<String>,
232 logs: Vec<String>,
233 },
234
235 #[error(
237 "Block not found: {0}. It may have been garbage-collected. Try an archival node for blocks older than 5 epochs."
238 )]
239 UnknownBlock(String),
240
241 #[error("Chunk not found: {0}. It may have been garbage-collected. Try an archival node.")]
242 UnknownChunk(String),
243
244 #[error(
245 "Epoch not found for block: {0}. The block may be invalid or too old. Try an archival node."
246 )]
247 UnknownEpoch(String),
248
249 #[error("Invalid shard ID: {0}")]
250 InvalidShardId(String),
251
252 #[error("Receipt not found: {0}")]
254 UnknownReceipt(String),
255
256 #[error("Invalid transaction: {0}")]
262 InvalidTx(crate::types::InvalidTxError),
263
264 #[error("Invalid transaction: {message}")]
267 InvalidTransaction {
268 message: String,
269 details: Option<serde_json::Value>,
270 },
271
272 #[error("Shard unavailable: {0}")]
274 ShardUnavailable(String),
275
276 #[error("Node not synced: {0}")]
277 NodeNotSynced(String),
278
279 #[error("Internal server error: {0}")]
280 InternalError(String),
281
282 #[error("Parse error: {0}")]
284 ParseError(String),
285
286 #[error("Request timeout: {message}")]
287 RequestTimeout {
288 message: String,
289 transaction_hash: Option<String>,
290 },
291}
292
293impl RpcError {
294 pub fn is_retryable(&self) -> bool {
296 match self {
297 RpcError::Http(e) => e.is_timeout() || e.is_connect(),
298 RpcError::Timeout(_) => true,
299 RpcError::Network { retryable, .. } => *retryable,
300 RpcError::ShardUnavailable(_) => true,
301 RpcError::NodeNotSynced(_) => true,
302 RpcError::InternalError(_) => true,
303 RpcError::RequestTimeout { .. } => true,
304 RpcError::InvalidTx(e) => e.is_retryable(),
305 RpcError::Rpc { code, .. } => {
306 *code == -32000 || *code == -32603
308 }
309 _ => false,
310 }
311 }
312
313 pub fn network(message: impl Into<String>, status_code: Option<u16>, retryable: bool) -> Self {
315 RpcError::Network {
316 message: message.into(),
317 status_code,
318 retryable,
319 }
320 }
321
322 pub fn invalid_transaction(
324 message: impl Into<String>,
325 details: Option<serde_json::Value>,
326 ) -> Self {
327 if let Some(ref data) = details {
329 if let Some(invalid_tx) = Self::try_parse_invalid_tx(data) {
330 return RpcError::InvalidTx(invalid_tx);
331 }
332 }
333
334 RpcError::InvalidTransaction {
335 message: message.into(),
336 details,
337 }
338 }
339
340 fn try_parse_invalid_tx(data: &serde_json::Value) -> Option<crate::types::InvalidTxError> {
345 if let Some(tx_err) = data.get("TxExecutionError") {
347 if let Some(invalid_tx_value) = tx_err.get("InvalidTxError") {
348 if let Ok(parsed) = serde_json::from_value(invalid_tx_value.clone()) {
349 return Some(parsed);
350 }
351 }
352 }
353
354 if let Some(invalid_tx_value) = data.get("InvalidTxError") {
356 if let Ok(parsed) = serde_json::from_value(invalid_tx_value.clone()) {
357 return Some(parsed);
358 }
359 }
360
361 None
362 }
363
364 pub fn function_call(
366 contract_id: AccountId,
367 method_name: impl Into<String>,
368 panic: Option<String>,
369 logs: Vec<String>,
370 ) -> Self {
371 RpcError::FunctionCall {
372 contract_id,
373 method_name: method_name.into(),
374 panic,
375 logs,
376 }
377 }
378}
379
380impl RpcError {
386 pub fn is_account_not_found(&self) -> bool {
388 matches!(self, RpcError::AccountNotFound(_))
389 }
390
391 pub fn is_contract_not_deployed(&self) -> bool {
393 matches!(self, RpcError::ContractNotDeployed(_))
394 }
395}
396
397#[derive(Debug, Error)]
398pub enum Error {
399 #[error(
401 "No signer configured. Use .credentials()/.signer() on NearBuilder, .with_signer() on the client, or .sign_with() on the transaction."
402 )]
403 NoSigner,
404
405 #[error(
406 "No signer account ID. Call .default_account() on NearBuilder or use a signer with an account ID."
407 )]
408 NoSignerAccount,
409
410 #[error("Invalid configuration: {0}")]
411 Config(String),
412
413 #[error(transparent)]
415 ParseAccountId(#[from] ParseAccountIdError),
416
417 #[error(transparent)]
418 ParseAmount(#[from] ParseAmountError),
419
420 #[error(transparent)]
421 ParseGas(#[from] ParseGasError),
422
423 #[error(transparent)]
424 ParseKey(#[from] ParseKeyError),
425
426 #[error(transparent)]
428 Rpc(Box<RpcError>),
429
430 #[error("Invalid transaction: {0}")]
437 InvalidTx(Box<InvalidTxError>),
438
439 #[error("Invalid transaction: {0}")]
442 InvalidTransaction(String),
443
444 #[error("Signing failed: {0}")]
446 Signing(#[from] SignerError),
447
448 #[error(transparent)]
450 KeyStore(#[from] KeyStoreError),
451
452 #[error("JSON error: {0}")]
454 Json(#[from] serde_json::Error),
455
456 #[error("Borsh error: {0}")]
457 Borsh(String),
458
459 #[error("Delegate action decode error: {0}")]
460 DelegateDecode(#[from] DelegateDecodeError),
461
462 #[error("Token {token} is not available on chain {chain_id}")]
464 TokenNotAvailable { token: String, chain_id: String },
465}
466
467impl From<RpcError> for Error {
468 fn from(err: RpcError) -> Self {
469 match err {
470 RpcError::InvalidTx(e) => Error::InvalidTx(Box::new(e)),
472 other => Error::Rpc(Box::new(other)),
473 }
474 }
475}
476
477impl Error {
478 pub fn is_invalid_tx(&self) -> bool {
480 matches!(self, Error::InvalidTx(_))
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487
488 #[test]
493 fn test_parse_account_id_error_display() {
494 let err = "".parse::<AccountId>().unwrap_err();
497 assert!(err.to_string().contains("too short"));
498
499 let err = "a".parse::<AccountId>().unwrap_err();
500 assert!(err.to_string().contains("too short"));
501
502 let err = "A.near".parse::<AccountId>().unwrap_err();
503 assert!(err.to_string().contains("invalid character"));
504
505 let err = "bad..account".parse::<AccountId>().unwrap_err();
506 assert!(err.to_string().contains("redundant separator"));
507 }
508
509 #[test]
514 fn test_parse_amount_error_display() {
515 assert_eq!(
516 ParseAmountError::AmbiguousAmount("123".to_string()).to_string(),
517 "Ambiguous amount '123'. Use explicit units like '5 NEAR' or '1000 yocto'"
518 );
519 assert_eq!(
520 ParseAmountError::InvalidFormat("xyz".to_string()).to_string(),
521 "Invalid amount format: 'xyz'"
522 );
523 assert_eq!(
524 ParseAmountError::InvalidNumber("abc".to_string()).to_string(),
525 "Invalid number in amount: 'abc'"
526 );
527 assert_eq!(
528 ParseAmountError::Overflow.to_string(),
529 "Amount overflow: value too large"
530 );
531 }
532
533 #[test]
538 fn test_parse_gas_error_display() {
539 assert_eq!(
540 ParseGasError::InvalidFormat("xyz".to_string()).to_string(),
541 "Invalid gas format: 'xyz'. Use '30 Tgas', '5 Ggas', or '1000000 gas'"
542 );
543 assert_eq!(
544 ParseGasError::InvalidNumber("abc".to_string()).to_string(),
545 "Invalid number in gas: 'abc'"
546 );
547 assert_eq!(
548 ParseGasError::Overflow.to_string(),
549 "Gas overflow: value too large"
550 );
551 }
552
553 #[test]
558 fn test_parse_key_error_display() {
559 assert_eq!(
560 ParseKeyError::InvalidFormat.to_string(),
561 "Invalid key format: expected 'ed25519:...' or 'secp256k1:...'"
562 );
563 assert_eq!(
564 ParseKeyError::UnknownKeyType("rsa".to_string()).to_string(),
565 "Unknown key type: 'rsa'"
566 );
567 assert_eq!(
568 ParseKeyError::InvalidBase58("invalid chars".to_string()).to_string(),
569 "Invalid base58 encoding: invalid chars"
570 );
571 assert_eq!(
572 ParseKeyError::InvalidLength {
573 expected: 32,
574 actual: 16
575 }
576 .to_string(),
577 "Invalid key length: expected 32 bytes, got 16"
578 );
579 assert_eq!(
580 ParseKeyError::InvalidCurvePoint.to_string(),
581 "Invalid curve point: key bytes do not represent a valid point on the curve"
582 );
583 }
584
585 #[test]
590 fn test_parse_hash_error_display() {
591 assert_eq!(
592 ParseHashError::InvalidBase58("bad input".to_string()).to_string(),
593 "Invalid base58 encoding: bad input"
594 );
595 assert_eq!(
596 ParseHashError::InvalidLength(16).to_string(),
597 "Invalid hash length: expected 32 bytes, got 16"
598 );
599 }
600
601 #[test]
606 fn test_signer_error_display() {
607 assert_eq!(
608 SignerError::InvalidSeedPhrase.to_string(),
609 "Invalid seed phrase"
610 );
611 assert_eq!(
612 SignerError::SigningFailed("hardware failure".to_string()).to_string(),
613 "Signing failed: hardware failure"
614 );
615 assert_eq!(
616 SignerError::KeyDerivationFailed("path error".to_string()).to_string(),
617 "Key derivation failed: path error"
618 );
619 }
620
621 #[test]
626 fn test_keystore_error_display() {
627 let account_id: AccountId = "alice.near".parse().unwrap();
628 assert_eq!(
629 KeyStoreError::KeyNotFound(account_id).to_string(),
630 "Key not found for account: alice.near"
631 );
632 assert_eq!(
633 KeyStoreError::InvalidFormat("missing field".to_string()).to_string(),
634 "Invalid credential format: missing field"
635 );
636 assert_eq!(
637 KeyStoreError::PathError("bad path".to_string()).to_string(),
638 "Path error: bad path"
639 );
640 assert_eq!(
641 KeyStoreError::Platform("keyring locked".to_string()).to_string(),
642 "Platform keyring error: keyring locked"
643 );
644 }
645
646 #[test]
651 fn test_rpc_error_display() {
652 let account_id: AccountId = "alice.near".parse().unwrap();
653
654 assert_eq!(RpcError::Timeout(3).to_string(), "Timeout after 3 retries");
655 assert_eq!(
656 RpcError::InvalidResponse("missing result".to_string()).to_string(),
657 "Invalid response: missing result"
658 );
659 assert_eq!(
660 RpcError::AccountNotFound(account_id.clone()).to_string(),
661 "Account not found: alice.near"
662 );
663 assert_eq!(
664 RpcError::InvalidAccount("bad-account".to_string()).to_string(),
665 "Invalid account ID: bad-account"
666 );
667 assert_eq!(
668 RpcError::ContractNotDeployed(account_id.clone()).to_string(),
669 "Contract not deployed on account: alice.near"
670 );
671 assert_eq!(
672 RpcError::ContractStateTooLarge(account_id.clone()).to_string(),
673 "Contract state too large for account: alice.near"
674 );
675 assert_eq!(
676 RpcError::UnknownBlock("12345".to_string()).to_string(),
677 "Block not found: 12345. It may have been garbage-collected. Try an archival node for blocks older than 5 epochs."
678 );
679 assert_eq!(
680 RpcError::UnknownChunk("abc123".to_string()).to_string(),
681 "Chunk not found: abc123. It may have been garbage-collected. Try an archival node."
682 );
683 assert_eq!(
684 RpcError::UnknownEpoch("epoch1".to_string()).to_string(),
685 "Epoch not found for block: epoch1. The block may be invalid or too old. Try an archival node."
686 );
687 assert_eq!(
688 RpcError::UnknownReceipt("receipt123".to_string()).to_string(),
689 "Receipt not found: receipt123"
690 );
691 assert_eq!(
692 RpcError::InvalidShardId("99".to_string()).to_string(),
693 "Invalid shard ID: 99"
694 );
695 assert_eq!(
696 RpcError::ShardUnavailable("shard 0".to_string()).to_string(),
697 "Shard unavailable: shard 0"
698 );
699 assert_eq!(
700 RpcError::NodeNotSynced("syncing...".to_string()).to_string(),
701 "Node not synced: syncing..."
702 );
703 assert_eq!(
704 RpcError::InternalError("database error".to_string()).to_string(),
705 "Internal server error: database error"
706 );
707 assert_eq!(
708 RpcError::ParseError("invalid json".to_string()).to_string(),
709 "Parse error: invalid json"
710 );
711 }
712
713 #[test]
714 fn test_rpc_error_is_retryable() {
715 use crate::types::InvalidTxError;
716
717 assert!(RpcError::Timeout(3).is_retryable());
719 assert!(RpcError::ShardUnavailable("shard 0".to_string()).is_retryable());
720 assert!(RpcError::NodeNotSynced("syncing".to_string()).is_retryable());
721 assert!(RpcError::InternalError("db error".to_string()).is_retryable());
722 assert!(
723 RpcError::RequestTimeout {
724 message: "timeout".to_string(),
725 transaction_hash: None,
726 }
727 .is_retryable()
728 );
729 assert!(
730 RpcError::InvalidTx(InvalidTxError::InvalidNonce {
731 tx_nonce: 5,
732 ak_nonce: 10
733 })
734 .is_retryable()
735 );
736 assert!(
737 RpcError::InvalidTx(InvalidTxError::ShardCongested {
738 congestion_level: 1.0,
739 shard_id: 0,
740 })
741 .is_retryable()
742 );
743 assert!(
744 RpcError::Network {
745 message: "connection reset".to_string(),
746 status_code: Some(503),
747 retryable: true,
748 }
749 .is_retryable()
750 );
751 assert!(
752 RpcError::Rpc {
753 code: -32000,
754 message: "server error".to_string(),
755 data: None,
756 }
757 .is_retryable()
758 );
759
760 let account_id: AccountId = "alice.near".parse().unwrap();
762 assert!(!RpcError::AccountNotFound(account_id.clone()).is_retryable());
763 assert!(!RpcError::ContractNotDeployed(account_id.clone()).is_retryable());
764 assert!(!RpcError::InvalidAccount("bad".to_string()).is_retryable());
765 assert!(!RpcError::UnknownBlock("12345".to_string()).is_retryable());
766 assert!(!RpcError::ParseError("bad json".to_string()).is_retryable());
767 assert!(
768 !RpcError::InvalidTx(InvalidTxError::NotEnoughBalance {
769 signer_id: account_id.clone(),
770 balance: crate::types::NearToken::from_near(1),
771 cost: crate::types::NearToken::from_near(100),
772 })
773 .is_retryable()
774 );
775 assert!(
776 !RpcError::InvalidTransaction {
777 message: "invalid".to_string(),
778 details: None,
779 }
780 .is_retryable()
781 );
782 }
783
784 #[test]
785 fn test_rpc_error_network_constructor() {
786 let err = RpcError::network("connection refused", Some(503), true);
787 match err {
788 RpcError::Network {
789 message,
790 status_code,
791 retryable,
792 } => {
793 assert_eq!(message, "connection refused");
794 assert_eq!(status_code, Some(503));
795 assert!(retryable);
796 }
797 _ => panic!("Expected Network error"),
798 }
799 }
800
801 #[test]
802 fn test_rpc_error_invalid_transaction_constructor_unstructured() {
803 let err = RpcError::invalid_transaction("invalid nonce", None);
804 match err {
805 RpcError::InvalidTransaction { message, details } => {
806 assert_eq!(message, "invalid nonce");
807 assert!(details.is_none());
808 }
809 _ => panic!("Expected InvalidTransaction error"),
810 }
811 }
812
813 #[test]
814 fn test_rpc_error_invalid_transaction_constructor_structured() {
815 let data = serde_json::json!({
817 "TxExecutionError": {
818 "InvalidTxError": {
819 "InvalidNonce": {
820 "tx_nonce": 5,
821 "ak_nonce": 10
822 }
823 }
824 }
825 });
826 let err = RpcError::invalid_transaction("invalid nonce", Some(data));
827 match err {
828 RpcError::InvalidTx(crate::types::InvalidTxError::InvalidNonce {
829 tx_nonce,
830 ak_nonce,
831 }) => {
832 assert_eq!(tx_nonce, 5);
833 assert_eq!(ak_nonce, 10);
834 }
835 other => panic!("Expected InvalidTx(InvalidNonce), got: {other:?}"),
836 }
837 }
838
839 #[test]
840 fn test_rpc_error_function_call_constructor() {
841 let account_id: AccountId = "contract.near".parse().unwrap();
842 let err = RpcError::function_call(
843 account_id.clone(),
844 "my_method",
845 Some("assertion failed".to_string()),
846 vec!["log1".to_string(), "log2".to_string()],
847 );
848 match err {
849 RpcError::FunctionCall {
850 contract_id,
851 method_name,
852 panic,
853 logs,
854 } => {
855 assert_eq!(contract_id, account_id);
856 assert_eq!(method_name, "my_method");
857 assert_eq!(panic, Some("assertion failed".to_string()));
858 assert_eq!(logs, vec!["log1", "log2"]);
859 }
860 _ => panic!("Expected FunctionCall error"),
861 }
862 }
863
864 #[test]
865 fn test_rpc_error_is_account_not_found() {
866 let account_id: AccountId = "alice.near".parse().unwrap();
867 assert!(RpcError::AccountNotFound(account_id).is_account_not_found());
868 assert!(!RpcError::Timeout(3).is_account_not_found());
869 }
870
871 #[test]
872 fn test_rpc_error_is_contract_not_deployed() {
873 let account_id: AccountId = "alice.near".parse().unwrap();
874 assert!(RpcError::ContractNotDeployed(account_id).is_contract_not_deployed());
875 assert!(!RpcError::Timeout(3).is_contract_not_deployed());
876 }
877
878 #[test]
879 fn test_rpc_error_contract_execution_display() {
880 let account_id: AccountId = "contract.near".parse().unwrap();
881 let err = RpcError::ContractExecution {
882 contract_id: account_id,
883 method_name: Some("my_method".to_string()),
884 message: "execution failed".to_string(),
885 };
886 assert_eq!(
887 err.to_string(),
888 "Contract execution failed on contract.near: execution failed"
889 );
890 }
891
892 #[test]
893 fn test_rpc_error_function_call_display() {
894 let account_id: AccountId = "contract.near".parse().unwrap();
895 let err = RpcError::FunctionCall {
896 contract_id: account_id.clone(),
897 method_name: "my_method".to_string(),
898 panic: Some("assertion failed".to_string()),
899 logs: vec![],
900 };
901 assert_eq!(
902 err.to_string(),
903 "Function call error on contract.near.my_method: assertion failed"
904 );
905
906 let err_no_panic = RpcError::FunctionCall {
907 contract_id: account_id,
908 method_name: "other_method".to_string(),
909 panic: None,
910 logs: vec![],
911 };
912 assert_eq!(
913 err_no_panic.to_string(),
914 "Function call error on contract.near.other_method: unknown error"
915 );
916 }
917
918 #[test]
919 fn test_rpc_error_invalid_tx_display() {
920 use crate::types::InvalidTxError;
921 let err = RpcError::InvalidTx(InvalidTxError::InvalidNonce {
922 tx_nonce: 5,
923 ak_nonce: 10,
924 });
925 assert!(err.to_string().contains("invalid nonce"));
926 }
927
928 #[test]
929 fn test_rpc_error_access_key_not_found_display() {
930 let account_id: AccountId = "alice.near".parse().unwrap();
931 let public_key: PublicKey = "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp"
932 .parse()
933 .unwrap();
934 let err = RpcError::AccessKeyNotFound {
935 account_id,
936 public_key: public_key.clone(),
937 };
938 assert!(err.to_string().contains("alice.near"));
939 assert!(err.to_string().contains(&public_key.to_string()));
940 }
941
942 #[test]
943 fn test_rpc_error_request_timeout_display() {
944 let err = RpcError::RequestTimeout {
945 message: "request timed out".to_string(),
946 transaction_hash: Some("abc123".to_string()),
947 };
948 assert_eq!(err.to_string(), "Request timeout: request timed out");
949 }
950
951 #[test]
956 fn test_error_no_signer_display() {
957 assert_eq!(
958 Error::NoSigner.to_string(),
959 "No signer configured. Use .credentials()/.signer() on NearBuilder, .with_signer() on the client, or .sign_with() on the transaction."
960 );
961 }
962
963 #[test]
964 fn test_error_no_signer_account_display() {
965 assert_eq!(
966 Error::NoSignerAccount.to_string(),
967 "No signer account ID. Call .default_account() on NearBuilder or use a signer with an account ID."
968 );
969 }
970
971 #[test]
972 fn test_error_config_display() {
973 assert_eq!(
974 Error::Config("invalid url".to_string()).to_string(),
975 "Invalid configuration: invalid url"
976 );
977 }
978
979 #[test]
980 fn test_error_invalid_tx_display() {
981 use crate::types::InvalidTxError;
982 let err = Error::InvalidTx(Box::new(InvalidTxError::Expired));
983 assert!(err.to_string().contains("expired"));
984 }
985
986 #[test]
987 fn test_error_from_rpc_invalid_tx_promotes() {
988 use crate::types::InvalidTxError;
989 let rpc_err = RpcError::InvalidTx(InvalidTxError::InvalidNonce {
991 tx_nonce: 5,
992 ak_nonce: 10,
993 });
994 let err: Error = rpc_err.into();
995 assert!(matches!(
996 err,
997 Error::InvalidTx(e) if matches!(*e, InvalidTxError::InvalidNonce { .. })
998 ));
999 }
1000
1001 #[test]
1002 fn test_error_borsh_display() {
1003 assert_eq!(
1004 Error::Borsh("deserialization failed".to_string()).to_string(),
1005 "Borsh error: deserialization failed"
1006 );
1007 }
1008
1009 #[test]
1010 fn test_error_from_parse_errors() {
1011 let parse_err = "".parse::<AccountId>().unwrap_err();
1013 let err: Error = parse_err.into();
1014 assert!(matches!(err, Error::ParseAccountId(_)));
1015
1016 let parse_err = ParseAmountError::Overflow;
1018 let err: Error = parse_err.into();
1019 assert!(matches!(err, Error::ParseAmount(_)));
1020
1021 let parse_err = ParseGasError::Overflow;
1023 let err: Error = parse_err.into();
1024 assert!(matches!(err, Error::ParseGas(_)));
1025
1026 let parse_err = ParseKeyError::InvalidFormat;
1028 let err: Error = parse_err.into();
1029 assert!(matches!(err, Error::ParseKey(_)));
1030 }
1031
1032 #[test]
1033 fn test_error_from_rpc_error() {
1034 let rpc_err = RpcError::Timeout(3);
1035 let err: Error = rpc_err.into();
1036 assert!(matches!(err, Error::Rpc(_)));
1037 }
1038
1039 #[test]
1040 fn test_error_from_signer_error() {
1041 let signer_err = SignerError::InvalidSeedPhrase;
1042 let err: Error = signer_err.into();
1043 assert!(matches!(err, Error::Signing(_)));
1044 }
1045}