actor.rs (14109B)
1 #![forbid(unsafe_code)] 2 3 use crate::RadrootsAuthorityError; 4 use core::{fmt, str::FromStr}; 5 use radroots_events::contract::RadrootsActorRole; 6 use radroots_events::ids::RadrootsPublicKey; 7 8 #[cfg(not(feature = "std"))] 9 use alloc::{collections::BTreeSet, string::String}; 10 #[cfg(feature = "std")] 11 use std::{collections::BTreeSet, string::String}; 12 13 pub const MAX_ACTOR_ACCOUNT_ID_LEN: usize = 128; 14 15 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 16 pub struct RadrootsActorAccountId(String); 17 18 impl RadrootsActorAccountId { 19 pub fn parse(account_id: impl Into<String>) -> Result<Self, RadrootsAuthorityError> { 20 let account_id = account_id.into(); 21 if account_id.is_empty() { 22 return Err(RadrootsAuthorityError::InvalidActorAccountIdEmpty); 23 } 24 if account_id.as_str() != account_id.trim() { 25 return Err(RadrootsAuthorityError::InvalidActorAccountIdUntrimmed); 26 } 27 if account_id.chars().any(char::is_control) { 28 return Err(RadrootsAuthorityError::InvalidActorAccountIdControlCharacter); 29 } 30 if account_id.chars().count() > MAX_ACTOR_ACCOUNT_ID_LEN { 31 return Err(RadrootsAuthorityError::InvalidActorAccountIdTooLong { 32 max_len: MAX_ACTOR_ACCOUNT_ID_LEN, 33 }); 34 } 35 Ok(Self(account_id)) 36 } 37 38 pub fn as_str(&self) -> &str { 39 self.0.as_str() 40 } 41 42 pub fn into_string(self) -> String { 43 self.0 44 } 45 } 46 47 impl fmt::Display for RadrootsActorAccountId { 48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 49 f.write_str(self.as_str()) 50 } 51 } 52 53 impl FromStr for RadrootsActorAccountId { 54 type Err = RadrootsAuthorityError; 55 56 fn from_str(account_id: &str) -> Result<Self, Self::Err> { 57 Self::parse(account_id) 58 } 59 } 60 61 impl TryFrom<&str> for RadrootsActorAccountId { 62 type Error = RadrootsAuthorityError; 63 64 fn try_from(account_id: &str) -> Result<Self, Self::Error> { 65 Self::parse(account_id) 66 } 67 } 68 69 impl TryFrom<String> for RadrootsActorAccountId { 70 type Error = RadrootsAuthorityError; 71 72 fn try_from(account_id: String) -> Result<Self, Self::Error> { 73 Self::parse(account_id) 74 } 75 } 76 77 impl AsRef<str> for RadrootsActorAccountId { 78 fn as_ref(&self) -> &str { 79 self.as_str() 80 } 81 } 82 83 impl PartialEq<&str> for RadrootsActorAccountId { 84 fn eq(&self, other: &&str) -> bool { 85 self.as_str() == *other 86 } 87 } 88 89 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 90 pub enum RadrootsActorSource { 91 LocalAccount, 92 ExplicitPubkey, 93 RemoteSigner, 94 Service, 95 Test, 96 } 97 98 #[derive(Clone, Debug, PartialEq, Eq)] 99 pub enum RadrootsActorSelector { 100 SelectedAccount, 101 AccountId(RadrootsActorAccountId), 102 PublicKey(RadrootsPublicKey), 103 DraftExpectedPubkey, 104 } 105 106 impl RadrootsActorSelector { 107 pub fn account_id(account_id: impl Into<String>) -> Result<Self, RadrootsAuthorityError> { 108 Ok(Self::AccountId(RadrootsActorAccountId::parse(account_id)?)) 109 } 110 111 pub fn public_key(pubkey: impl AsRef<str>) -> Result<Self, RadrootsAuthorityError> { 112 let pubkey = RadrootsPublicKey::parse(pubkey.as_ref()) 113 .map_err(|_| RadrootsAuthorityError::InvalidActorPubkey)?; 114 Ok(Self::PublicKey(pubkey)) 115 } 116 } 117 118 #[derive(Clone, Debug, PartialEq, Eq)] 119 pub struct RadrootsActorResolutionRequest { 120 selector: RadrootsActorSelector, 121 required_role: RadrootsActorRole, 122 contract_id: Option<String>, 123 } 124 125 impl RadrootsActorResolutionRequest { 126 pub fn new( 127 selector: RadrootsActorSelector, 128 required_role: RadrootsActorRole, 129 contract_id: Option<String>, 130 ) -> Self { 131 Self { 132 selector, 133 required_role, 134 contract_id, 135 } 136 } 137 138 pub fn selector(&self) -> &RadrootsActorSelector { 139 &self.selector 140 } 141 142 pub fn required_role(&self) -> RadrootsActorRole { 143 self.required_role 144 } 145 146 pub fn contract_id(&self) -> Option<&str> { 147 self.contract_id.as_deref() 148 } 149 } 150 151 #[derive(Clone, Debug, PartialEq, Eq)] 152 pub struct RadrootsActorContext { 153 pubkey: RadrootsPublicKey, 154 roles: BTreeSet<RadrootsActorRole>, 155 account_id: Option<RadrootsActorAccountId>, 156 source: RadrootsActorSource, 157 } 158 159 impl RadrootsActorContext { 160 pub fn explicit_pubkey<I>( 161 pubkey: impl AsRef<str>, 162 roles: I, 163 ) -> Result<Self, RadrootsAuthorityError> 164 where 165 I: IntoIterator<Item = RadrootsActorRole>, 166 { 167 Self::with_provenance(pubkey, None, RadrootsActorSource::ExplicitPubkey, roles) 168 } 169 170 pub fn local_account<I>( 171 pubkey: impl AsRef<str>, 172 account_id: impl Into<String>, 173 roles: I, 174 ) -> Result<Self, RadrootsAuthorityError> 175 where 176 I: IntoIterator<Item = RadrootsActorRole>, 177 { 178 Self::with_provenance( 179 pubkey, 180 Some(RadrootsActorAccountId::parse(account_id)?), 181 RadrootsActorSource::LocalAccount, 182 roles, 183 ) 184 } 185 186 pub fn remote_signer<I>( 187 pubkey: impl AsRef<str>, 188 account_id: impl Into<String>, 189 roles: I, 190 ) -> Result<Self, RadrootsAuthorityError> 191 where 192 I: IntoIterator<Item = RadrootsActorRole>, 193 { 194 Self::with_provenance( 195 pubkey, 196 Some(RadrootsActorAccountId::parse(account_id)?), 197 RadrootsActorSource::RemoteSigner, 198 roles, 199 ) 200 } 201 202 pub fn service<I>( 203 pubkey: impl AsRef<str>, 204 account_id: impl Into<String>, 205 roles: I, 206 ) -> Result<Self, RadrootsAuthorityError> 207 where 208 I: IntoIterator<Item = RadrootsActorRole>, 209 { 210 Self::with_provenance( 211 pubkey, 212 Some(RadrootsActorAccountId::parse(account_id)?), 213 RadrootsActorSource::Service, 214 roles, 215 ) 216 } 217 218 pub fn test<I>(pubkey: impl AsRef<str>, roles: I) -> Result<Self, RadrootsAuthorityError> 219 where 220 I: IntoIterator<Item = RadrootsActorRole>, 221 { 222 Self::with_provenance(pubkey, None, RadrootsActorSource::Test, roles) 223 } 224 225 fn with_provenance<I>( 226 pubkey: impl AsRef<str>, 227 account_id: Option<RadrootsActorAccountId>, 228 source: RadrootsActorSource, 229 roles: I, 230 ) -> Result<Self, RadrootsAuthorityError> 231 where 232 I: IntoIterator<Item = RadrootsActorRole>, 233 { 234 let pubkey = RadrootsPublicKey::parse(pubkey.as_ref()) 235 .map_err(|_| RadrootsAuthorityError::InvalidActorPubkey)?; 236 Ok(Self { 237 pubkey, 238 roles: roles.into_iter().collect(), 239 account_id, 240 source, 241 }) 242 } 243 244 pub fn pubkey(&self) -> &RadrootsPublicKey { 245 &self.pubkey 246 } 247 248 pub fn roles(&self) -> &BTreeSet<RadrootsActorRole> { 249 &self.roles 250 } 251 252 pub fn account_id(&self) -> Option<&RadrootsActorAccountId> { 253 self.account_id.as_ref() 254 } 255 256 pub fn source(&self) -> RadrootsActorSource { 257 self.source 258 } 259 260 pub fn satisfies(&self, required_role: RadrootsActorRole) -> bool { 261 role_satisfies(&self.roles, required_role) 262 } 263 } 264 265 pub fn role_satisfies( 266 actor_roles: &BTreeSet<RadrootsActorRole>, 267 required_role: RadrootsActorRole, 268 ) -> bool { 269 match required_role { 270 RadrootsActorRole::Any => true, 271 role => actor_roles.contains(&role), 272 } 273 } 274 275 #[cfg(test)] 276 mod tests { 277 use super::*; 278 279 fn hex_64(character: char) -> String { 280 std::iter::repeat_n(character, 64).collect() 281 } 282 283 #[test] 284 fn any_is_satisfied_by_any_actor_context() { 285 let actor = RadrootsActorContext::test(hex_64('a'), []).expect("actor"); 286 287 assert!(actor.satisfies(RadrootsActorRole::Any)); 288 } 289 290 #[test] 291 fn specific_roles_require_explicit_membership() { 292 let actor = 293 RadrootsActorContext::test(hex_64('a'), [RadrootsActorRole::Farmer]).expect("actor"); 294 295 assert!(actor.satisfies(RadrootsActorRole::Farmer)); 296 assert!(!actor.satisfies(RadrootsActorRole::Seller)); 297 } 298 299 #[test] 300 fn farmer_does_not_globally_satisfy_seller() { 301 let actor = 302 RadrootsActorContext::test(hex_64('a'), [RadrootsActorRole::Farmer]).expect("actor"); 303 304 assert!(!actor.satisfies(RadrootsActorRole::Seller)); 305 } 306 307 #[test] 308 fn multi_role_actors_satisfy_each_assigned_role() { 309 let actor = RadrootsActorContext::test( 310 hex_64('a'), 311 [RadrootsActorRole::Farmer, RadrootsActorRole::Seller], 312 ) 313 .expect("actor"); 314 315 assert!(actor.satisfies(RadrootsActorRole::Farmer)); 316 assert!(actor.satisfies(RadrootsActorRole::Seller)); 317 assert!(!actor.satisfies(RadrootsActorRole::Buyer)); 318 } 319 320 #[test] 321 fn local_account_context_carries_validated_account_id() { 322 let actor = RadrootsActorContext::local_account( 323 hex_64('a'), 324 "acct-field-01", 325 [RadrootsActorRole::Farmer], 326 ) 327 .expect("actor"); 328 329 assert_eq!(actor.source(), RadrootsActorSource::LocalAccount); 330 assert_eq!(actor.pubkey().as_str(), hex_64('a')); 331 assert_eq!( 332 actor.roles().iter().copied().collect::<Vec<_>>(), 333 vec![RadrootsActorRole::Farmer] 334 ); 335 let account_id = actor.account_id().expect("account id"); 336 assert_eq!(account_id.as_str(), "acct-field-01"); 337 assert_eq!(account_id.to_string(), "acct-field-01"); 338 } 339 340 #[test] 341 fn explicit_pubkey_context_has_no_account_id() { 342 let actor = RadrootsActorContext::explicit_pubkey(hex_64('a'), [RadrootsActorRole::Seller]) 343 .expect("actor"); 344 345 assert_eq!(actor.source(), RadrootsActorSource::ExplicitPubkey); 346 assert_eq!(actor.account_id(), None); 347 } 348 349 #[test] 350 fn test_context_has_no_account_id() { 351 let actor = 352 RadrootsActorContext::test(hex_64('a'), [RadrootsActorRole::Farmer]).expect("actor"); 353 354 assert_eq!(actor.source(), RadrootsActorSource::Test); 355 assert_eq!(actor.account_id(), None); 356 } 357 358 #[test] 359 fn remote_signer_and_service_contexts_carry_account_ids() { 360 let remote = RadrootsActorContext::remote_signer( 361 hex_64('a'), 362 "acct-remote", 363 [RadrootsActorRole::Buyer], 364 ) 365 .expect("remote actor"); 366 let service = 367 RadrootsActorContext::service(hex_64('b'), "acct-service", [RadrootsActorRole::Any]) 368 .expect("service actor"); 369 370 assert_eq!(remote.source(), RadrootsActorSource::RemoteSigner); 371 assert_eq!( 372 remote.account_id().map(RadrootsActorAccountId::as_str), 373 Some("acct-remote") 374 ); 375 assert_eq!(service.source(), RadrootsActorSource::Service); 376 assert_eq!( 377 service.account_id().map(RadrootsActorAccountId::as_str), 378 Some("acct-service") 379 ); 380 } 381 382 #[test] 383 fn account_id_rejects_invalid_values() { 384 assert!(matches!( 385 RadrootsActorContext::local_account(hex_64('a'), "", []), 386 Err(RadrootsAuthorityError::InvalidActorAccountIdEmpty) 387 )); 388 assert!(matches!( 389 RadrootsActorContext::local_account(hex_64('a'), " account ", []), 390 Err(RadrootsAuthorityError::InvalidActorAccountIdUntrimmed) 391 )); 392 assert!(matches!( 393 RadrootsActorContext::local_account(hex_64('a'), "account\nid", []), 394 Err(RadrootsAuthorityError::InvalidActorAccountIdControlCharacter) 395 )); 396 assert!(matches!( 397 RadrootsActorContext::local_account( 398 hex_64('a'), 399 core::iter::repeat_n('a', MAX_ACTOR_ACCOUNT_ID_LEN + 1).collect::<String>(), 400 [] 401 ), 402 Err(RadrootsAuthorityError::InvalidActorAccountIdTooLong { 403 max_len: MAX_ACTOR_ACCOUNT_ID_LEN 404 }) 405 )); 406 } 407 408 #[test] 409 fn account_id_type_exposes_canonical_value() { 410 let parsed = RadrootsActorAccountId::parse("acct-field-01").expect("account id"); 411 let from_str = "acct-field-01" 412 .parse::<RadrootsActorAccountId>() 413 .expect("from str"); 414 let from_borrowed = 415 RadrootsActorAccountId::try_from("acct-field-01").expect("from borrowed"); 416 let from_owned = 417 RadrootsActorAccountId::try_from("acct-field-01".to_owned()).expect("from owned"); 418 419 assert_eq!(parsed, "acct-field-01"); 420 assert_eq!(from_str.as_ref(), "acct-field-01"); 421 assert_eq!(from_borrowed.as_str(), "acct-field-01"); 422 assert_eq!(from_owned.into_string(), "acct-field-01"); 423 } 424 425 #[test] 426 fn resolution_request_getters_return_constructor_values() { 427 let selector = RadrootsActorSelector::account_id("acct-field-01").expect("selector"); 428 let request = RadrootsActorResolutionRequest::new( 429 selector, 430 RadrootsActorRole::Seller, 431 Some("radroots.listing.published.v1".to_owned()), 432 ); 433 434 assert_eq!( 435 request.selector(), 436 &RadrootsActorSelector::account_id("acct-field-01").expect("selector") 437 ); 438 assert_eq!(request.required_role(), RadrootsActorRole::Seller); 439 assert_eq!(request.contract_id(), Some("radroots.listing.published.v1")); 440 } 441 442 #[test] 443 fn selector_supports_account_and_draft_resolution() { 444 assert_eq!( 445 RadrootsActorSelector::account_id("acct-field-01").expect("selector"), 446 RadrootsActorSelector::AccountId( 447 RadrootsActorAccountId::parse("acct-field-01").expect("account id") 448 ) 449 ); 450 assert!(matches!( 451 RadrootsActorSelector::SelectedAccount, 452 RadrootsActorSelector::SelectedAccount 453 )); 454 assert!(matches!( 455 RadrootsActorSelector::public_key(hex_64('b')).expect("selector"), 456 RadrootsActorSelector::PublicKey(_) 457 )); 458 assert!(matches!( 459 RadrootsActorSelector::DraftExpectedPubkey, 460 RadrootsActorSelector::DraftExpectedPubkey 461 )); 462 } 463 }