use crate::error::Error;
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::fmt;
use std::sync::{Arc, Mutex};
pub type ValidatorFn = fn(&str) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
pub struct ValidationRegistry {
chain_validators: HashMap<String, ValidatorFn>,
account_validators: HashMap<String, ValidatorFn>,
asset_validators: HashMap<String, ValidatorFn>,
}
impl Default for ValidationRegistry {
fn default() -> Self {
Self::new()
}
}
impl ValidationRegistry {
pub fn new() -> Self {
Self {
chain_validators: HashMap::new(),
account_validators: HashMap::new(),
asset_validators: HashMap::new(),
}
}
pub fn global() -> Arc<Mutex<Self>> {
static REGISTRY: Lazy<Arc<Mutex<ValidationRegistry>>> = Lazy::new(|| {
let registry = ValidationRegistry::new_with_defaults();
Arc::new(Mutex::new(registry))
});
REGISTRY.clone()
}
pub fn new_with_defaults() -> Self {
let mut registry = Self::new();
registry.register_account_validator("eip155", ethereum_address_validator);
registry.register_asset_validator("erc20", ethereum_address_validator);
registry.register_asset_validator("erc721", ethereum_address_validator);
registry.register_account_validator("bip122", bitcoin_address_validator);
registry
}
pub fn register_chain_validator(&mut self, namespace: &str, validator: ValidatorFn) {
self.chain_validators
.insert(namespace.to_string(), validator);
}
pub fn register_account_validator(&mut self, namespace: &str, validator: ValidatorFn) {
self.account_validators
.insert(namespace.to_string(), validator);
}
pub fn register_asset_validator(&mut self, namespace: &str, validator: ValidatorFn) {
self.asset_validators
.insert(namespace.to_string(), validator);
}
pub fn get_chain_validator(&self, namespace: &str) -> Option<ValidatorFn> {
self.chain_validators.get(namespace).copied()
}
pub fn get_account_validator(&self, namespace: &str) -> Option<ValidatorFn> {
self.account_validators.get(namespace).copied()
}
pub fn get_asset_validator(&self, namespace: &str) -> Option<ValidatorFn> {
self.asset_validators.get(namespace).copied()
}
}
impl fmt::Debug for ValidationRegistry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ValidationRegistry")
.field(
"chain_validators",
&format!("{} entries", self.chain_validators.len()),
)
.field(
"account_validators",
&format!("{} entries", self.account_validators.len()),
)
.field(
"asset_validators",
&format!("{} entries", self.asset_validators.len()),
)
.finish()
}
}
fn ethereum_address_validator(
address: &str,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if !address.starts_with("0x") || address.len() != 42 {
return Err(Error::InvalidEthereumAddress(address.to_string()).into());
}
if hex::decode(&address[2..]).is_err() {
return Err(Error::InvalidEthereumAddress(address.to_string()).into());
}
Ok(())
}
fn bitcoin_address_validator(
address: &str,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if address.len() < 26 || address.len() > 35 {
return Err(Error::InvalidBitcoinAddress(address.to_string()).into());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ethereum_validator() {
let valid_eth_address = "0x4b20993Bc481177ec7E8f571ceCaE8A9e22C02db";
let too_short_address = "0x4b20";
let not_hex_address = "0xZZZZ993Bc481177ec7E8f571ceCaE8A9e22C02db";
let no_prefix_address = "4b20993Bc481177ec7E8f571ceCaE8A9e22C02db";
assert!(ethereum_address_validator(valid_eth_address).is_ok());
assert!(ethereum_address_validator(too_short_address).is_err());
assert!(ethereum_address_validator(not_hex_address).is_err());
assert!(ethereum_address_validator(no_prefix_address).is_err());
}
#[test]
fn test_bitcoin_validator() {
let valid_btc_address = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"; let too_short_address = "1A1zP";
let too_long_address = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa12345678901234";
assert!(bitcoin_address_validator(valid_btc_address).is_ok());
assert!(bitcoin_address_validator(too_short_address).is_err());
assert!(bitcoin_address_validator(too_long_address).is_err());
}
#[test]
fn test_registry() {
let registry = ValidationRegistry::new_with_defaults();
assert!(registry.get_account_validator("eip155").is_some());
assert!(registry.get_asset_validator("erc20").is_some());
assert!(registry.get_asset_validator("erc721").is_some());
assert!(registry.get_account_validator("bip122").is_some());
assert!(registry.get_account_validator("polkadot").is_none());
assert!(registry.get_asset_validator("unknown").is_none());
}
#[test]
fn test_global_registry() {
let registry = ValidationRegistry::global();
let registry_guard = registry.lock().unwrap();
assert!(registry_guard.get_account_validator("eip155").is_some());
assert!(registry_guard.get_asset_validator("erc20").is_some());
}
}