1use crate::error::Error;
2use once_cell::sync::Lazy;
3use std::collections::HashMap;
4use std::fmt;
5use std::sync::{Arc, Mutex};
6
7pub type ValidatorFn = fn(&str) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
9
10pub struct ValidationRegistry {
12 chain_validators: HashMap<String, ValidatorFn>,
13 account_validators: HashMap<String, ValidatorFn>,
14 asset_validators: HashMap<String, ValidatorFn>,
15}
16
17impl Default for ValidationRegistry {
18 fn default() -> Self {
19 Self::new()
20 }
21}
22
23impl ValidationRegistry {
24 pub fn new() -> Self {
26 Self {
27 chain_validators: HashMap::new(),
28 account_validators: HashMap::new(),
29 asset_validators: HashMap::new(),
30 }
31 }
32
33 pub fn global() -> Arc<Mutex<Self>> {
35 static REGISTRY: Lazy<Arc<Mutex<ValidationRegistry>>> = Lazy::new(|| {
36 let registry = ValidationRegistry::new_with_defaults();
37 Arc::new(Mutex::new(registry))
38 });
39 REGISTRY.clone()
40 }
41
42 pub fn new_with_defaults() -> Self {
44 let mut registry = Self::new();
45
46 registry.register_account_validator("eip155", ethereum_address_validator);
48 registry.register_asset_validator("erc20", ethereum_address_validator);
49 registry.register_asset_validator("erc721", ethereum_address_validator);
50
51 registry.register_account_validator("bip122", bitcoin_address_validator);
53
54 registry
55 }
56
57 pub fn register_chain_validator(&mut self, namespace: &str, validator: ValidatorFn) {
59 self.chain_validators
60 .insert(namespace.to_string(), validator);
61 }
62
63 pub fn register_account_validator(&mut self, namespace: &str, validator: ValidatorFn) {
65 self.account_validators
66 .insert(namespace.to_string(), validator);
67 }
68
69 pub fn register_asset_validator(&mut self, namespace: &str, validator: ValidatorFn) {
71 self.asset_validators
72 .insert(namespace.to_string(), validator);
73 }
74
75 pub fn get_chain_validator(&self, namespace: &str) -> Option<ValidatorFn> {
77 self.chain_validators.get(namespace).copied()
78 }
79
80 pub fn get_account_validator(&self, namespace: &str) -> Option<ValidatorFn> {
82 self.account_validators.get(namespace).copied()
83 }
84
85 pub fn get_asset_validator(&self, namespace: &str) -> Option<ValidatorFn> {
87 self.asset_validators.get(namespace).copied()
88 }
89}
90
91impl fmt::Debug for ValidationRegistry {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 f.debug_struct("ValidationRegistry")
94 .field(
95 "chain_validators",
96 &format!("{} entries", self.chain_validators.len()),
97 )
98 .field(
99 "account_validators",
100 &format!("{} entries", self.account_validators.len()),
101 )
102 .field(
103 "asset_validators",
104 &format!("{} entries", self.asset_validators.len()),
105 )
106 .finish()
107 }
108}
109
110fn ethereum_address_validator(
112 address: &str,
113) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
114 if !address.starts_with("0x") || address.len() != 42 {
116 return Err(Error::InvalidEthereumAddress(address.to_string()).into());
117 }
118
119 if hex::decode(&address[2..]).is_err() {
121 return Err(Error::InvalidEthereumAddress(address.to_string()).into());
122 }
123
124 Ok(())
126}
127
128fn bitcoin_address_validator(
130 address: &str,
131) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
132 if address.len() < 26 || address.len() > 35 {
134 return Err(Error::InvalidBitcoinAddress(address.to_string()).into());
135 }
136
137 Ok(())
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_ethereum_validator() {
147 let valid_eth_address = "0x4b20993Bc481177ec7E8f571ceCaE8A9e22C02db";
148 let too_short_address = "0x4b20";
149 let not_hex_address = "0xZZZZ993Bc481177ec7E8f571ceCaE8A9e22C02db";
150 let no_prefix_address = "4b20993Bc481177ec7E8f571ceCaE8A9e22C02db";
151
152 assert!(ethereum_address_validator(valid_eth_address).is_ok());
153 assert!(ethereum_address_validator(too_short_address).is_err());
154 assert!(ethereum_address_validator(not_hex_address).is_err());
155 assert!(ethereum_address_validator(no_prefix_address).is_err());
156 }
157
158 #[test]
159 fn test_bitcoin_validator() {
160 let valid_btc_address = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"; let too_short_address = "1A1zP";
162 let too_long_address = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa12345678901234";
163
164 assert!(bitcoin_address_validator(valid_btc_address).is_ok());
165 assert!(bitcoin_address_validator(too_short_address).is_err());
166 assert!(bitcoin_address_validator(too_long_address).is_err());
167 }
168
169 #[test]
170 fn test_registry() {
171 let registry = ValidationRegistry::new_with_defaults();
172
173 assert!(registry.get_account_validator("eip155").is_some());
175 assert!(registry.get_asset_validator("erc20").is_some());
176 assert!(registry.get_asset_validator("erc721").is_some());
177
178 assert!(registry.get_account_validator("bip122").is_some());
180
181 assert!(registry.get_account_validator("polkadot").is_none());
183 assert!(registry.get_asset_validator("unknown").is_none());
184 }
185
186 #[test]
187 fn test_global_registry() {
188 let registry = ValidationRegistry::global();
189 let registry_guard = registry.lock().unwrap();
190
191 assert!(registry_guard.get_account_validator("eip155").is_some());
193 assert!(registry_guard.get_asset_validator("erc20").is_some());
194 }
195}