lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

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 }