use std::time::Duration;
use smallvec::SmallVec;
use crate::oracle::{AudienceOracleQuery, BlockOracleQuery, MuteOracleQuery};
use crate::sealed;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CapabilityClass {
User,
Channel,
Substrate,
Moderation,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CapabilityKind {
ViewPrivate,
ParticipatePrivate,
EditPrivatePost,
DeletePrivatePost,
ManageAudience,
EmitToSyncChannel,
AppViewSync,
GraphSync,
ScanShard,
ReplicatePrivate,
GarbageCollect,
ModeratorRead,
ModeratorTakedown,
ModeratorRestore,
}
impl CapabilityKind {
#[must_use]
pub fn class(&self) -> CapabilityClass {
match self {
CapabilityKind::ViewPrivate
| CapabilityKind::ParticipatePrivate
| CapabilityKind::EditPrivatePost
| CapabilityKind::DeletePrivatePost
| CapabilityKind::ManageAudience => CapabilityClass::User,
CapabilityKind::EmitToSyncChannel
| CapabilityKind::AppViewSync
| CapabilityKind::GraphSync => CapabilityClass::Channel,
CapabilityKind::ScanShard
| CapabilityKind::ReplicatePrivate
| CapabilityKind::GarbageCollect => CapabilityClass::Substrate,
CapabilityKind::ModeratorRead
| CapabilityKind::ModeratorTakedown
| CapabilityKind::ModeratorRestore => CapabilityClass::Moderation,
}
}
#[must_use]
pub fn is_wire_eligible(&self) -> bool {
matches!(
self.class(),
CapabilityClass::User | CapabilityClass::Channel
)
}
#[must_use]
pub fn wire_name(&self) -> &'static str {
match self {
CapabilityKind::ViewPrivate => "viewPrivate",
CapabilityKind::ParticipatePrivate => "participatePrivate",
CapabilityKind::EditPrivatePost => "editPrivatePost",
CapabilityKind::DeletePrivatePost => "deletePrivatePost",
CapabilityKind::ManageAudience => "manageAudience",
CapabilityKind::EmitToSyncChannel => "emitToSyncChannel",
CapabilityKind::AppViewSync => "appViewSync",
CapabilityKind::GraphSync => "graphSync",
CapabilityKind::ScanShard => "scanShard",
CapabilityKind::ReplicatePrivate => "replicatePrivate",
CapabilityKind::GarbageCollect => "garbageCollect",
CapabilityKind::ModeratorRead => "moderatorRead",
CapabilityKind::ModeratorTakedown => "moderatorTakedown",
CapabilityKind::ModeratorRestore => "moderatorRestore",
}
}
#[must_use]
pub fn from_wire_name(name: &str) -> Option<Self> {
Some(match name {
"viewPrivate" => CapabilityKind::ViewPrivate,
"participatePrivate" => CapabilityKind::ParticipatePrivate,
"editPrivatePost" => CapabilityKind::EditPrivatePost,
"deletePrivatePost" => CapabilityKind::DeletePrivatePost,
"manageAudience" => CapabilityKind::ManageAudience,
"emitToSyncChannel" => CapabilityKind::EmitToSyncChannel,
"appViewSync" => CapabilityKind::AppViewSync,
"graphSync" => CapabilityKind::GraphSync,
"scanShard" => CapabilityKind::ScanShard,
"replicatePrivate" => CapabilityKind::ReplicatePrivate,
"garbageCollect" => CapabilityKind::GarbageCollect,
"moderatorRead" => CapabilityKind::ModeratorRead,
"moderatorTakedown" => CapabilityKind::ModeratorTakedown,
"moderatorRestore" => CapabilityKind::ModeratorRestore,
_ => return None,
})
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CapabilitySemantics {
Read,
Write,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct CapabilitySet {
kinds: SmallVec<[CapabilityKind; 4]>,
}
impl CapabilitySet {
#[must_use]
pub fn empty() -> Self {
CapabilitySet::default()
}
pub fn from_kinds<I: IntoIterator<Item = CapabilityKind>>(iter: I) -> Self {
let mut sv: SmallVec<[CapabilityKind; 4]> = iter.into_iter().collect();
sv.sort_by_key(|k| *k as u8);
sv.dedup();
CapabilitySet { kinds: sv }
}
#[must_use]
pub fn kinds(&self) -> &[CapabilityKind] {
&self.kinds
}
#[must_use]
pub fn is_superset_of(&self, other: &CapabilitySet) -> bool {
other.kinds.iter().all(|k| self.kinds.contains(k))
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.kinds.is_empty()
}
#[must_use]
pub fn without(&self, other: &CapabilitySet) -> CapabilitySet {
CapabilitySet::from_kinds(
self.kinds
.iter()
.copied()
.filter(|k| !other.kinds.contains(k)),
)
}
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct OracleConsultations {
pub block: &'static [BlockOracleQuery],
pub audience: &'static [AudienceOracleQuery],
pub mute: &'static [MuteOracleQuery],
}
pub trait OracleResultsForCapability<C: ?Sized>: sealed::Sealed {}
pub trait UserCapability: sealed::Sealed + 'static {
type Subject;
type OracleResults: OracleResultsForCapability<Self>;
const KIND: CapabilityKind;
const MAX_AGE: Duration;
const NAME: &'static str;
const ORACLE_CONSULTATIONS: OracleConsultations;
const SEMANTICS: CapabilitySemantics;
}
pub trait Endpoint: sealed::Sealed + 'static {
type Subject;
const KIND: CapabilityKind;
const MAX_AGE: Duration;
const NAME: &'static str;
}
pub trait SubstrateScope: sealed::Sealed + 'static {
type Subject;
const KIND: CapabilityKind;
const MAX_AGE: Duration;
const NAME: &'static str;
}
pub trait ModerationCapability: sealed::Sealed + 'static {
type Subject;
const KIND: CapabilityKind;
const MAX_AGE: Duration;
const NAME: &'static str;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capability_kind_class_mapping_consistent() {
for k in [
CapabilityKind::ViewPrivate,
CapabilityKind::ParticipatePrivate,
CapabilityKind::EditPrivatePost,
CapabilityKind::DeletePrivatePost,
CapabilityKind::ManageAudience,
] {
assert_eq!(k.class(), CapabilityClass::User);
assert!(k.is_wire_eligible());
}
for k in [
CapabilityKind::EmitToSyncChannel,
CapabilityKind::AppViewSync,
CapabilityKind::GraphSync,
] {
assert_eq!(k.class(), CapabilityClass::Channel);
assert!(k.is_wire_eligible());
}
for k in [
CapabilityKind::ScanShard,
CapabilityKind::ReplicatePrivate,
CapabilityKind::GarbageCollect,
] {
assert_eq!(k.class(), CapabilityClass::Substrate);
assert!(!k.is_wire_eligible(), "substrate-class is NEVER wire-eligible (§4.8 W6)");
}
for k in [
CapabilityKind::ModeratorRead,
CapabilityKind::ModeratorTakedown,
CapabilityKind::ModeratorRestore,
] {
assert_eq!(k.class(), CapabilityClass::Moderation);
assert!(!k.is_wire_eligible(), "moderation-class is NEVER wire-eligible (§4.8 W6)");
}
}
#[test]
fn capability_set_normalizes_and_dedupes() {
let s = CapabilitySet::from_kinds([
CapabilityKind::EditPrivatePost,
CapabilityKind::ViewPrivate,
CapabilityKind::ViewPrivate,
]);
assert_eq!(s.kinds().len(), 2);
assert!(s.kinds().contains(&CapabilityKind::ViewPrivate));
assert!(s.kinds().contains(&CapabilityKind::EditPrivatePost));
}
#[test]
fn capability_set_superset_check() {
let big = CapabilitySet::from_kinds([
CapabilityKind::ViewPrivate,
CapabilityKind::EditPrivatePost,
]);
let small = CapabilitySet::from_kinds([CapabilityKind::ViewPrivate]);
assert!(big.is_superset_of(&small));
assert!(!small.is_superset_of(&big));
}
}