use anyhow::{anyhow, Result};
use libipld_cbor::DagCborCodec;
use ucan::{builder::UcanBuilder, crypto::KeyMaterial};
use crate::{
authority::{generate_capability, Authorization, SphereAbility},
data::{
ChangelogIpld, DelegationIpld, Did, IdentityIpld, Jwt, Link, MapOperation, MemoIpld,
RevocationIpld, VersionedMapKey, VersionedMapValue,
},
};
use noosphere_storage::{BlockStore, UcanStore};
#[cfg(doc)]
use crate::{
data::VersionedMapIpld,
view::versioned_map::{Content, Delegations, Identities, Revocations},
};
pub type ContentMutation = VersionedMapMutation<String, Link<MemoIpld>>;
pub type IdentitiesMutation = VersionedMapMutation<String, IdentityIpld>;
pub type DelegationsMutation = VersionedMapMutation<Link<Jwt>, DelegationIpld>;
pub type RevocationsMutation = VersionedMapMutation<Link<Jwt>, RevocationIpld>;
#[cfg(doc)]
use crate::view::Sphere;
#[derive(Debug)]
pub struct SphereRevision<S: BlockStore> {
pub sphere_identity: Did,
pub store: S,
pub memo: MemoIpld,
}
impl<S: BlockStore> SphereRevision<S> {
pub async fn sign<Credential: KeyMaterial>(
&mut self,
credential: &Credential,
authorization: Option<&Authorization>,
) -> Result<Link<MemoIpld>> {
let proof = match authorization {
Some(authorization) => {
let witness_ucan = authorization
.as_ucan(&UcanStore(self.store.clone()))
.await?;
Some(
UcanBuilder::default()
.issued_by(credential)
.for_audience(&self.sphere_identity)
.with_lifetime(120)
.witnessed_by(&witness_ucan, None)
.claiming_capability(&generate_capability(
&self.sphere_identity,
SphereAbility::Publish,
))
.with_nonce()
.build()?
.sign()
.await?,
)
}
None => None,
};
self.memo.sign(credential, proof.as_ref()).await?;
Ok(self.store.save::<DagCborCodec, _>(&self.memo).await?.into())
}
}
#[derive(Debug)]
pub struct SphereMutation {
did: Did,
content: ContentMutation,
identities: IdentitiesMutation,
delegations: DelegationsMutation,
revocations: RevocationsMutation,
}
impl SphereMutation {
pub fn new(did: &str) -> Self {
SphereMutation {
did: did.into(),
content: ContentMutation::new(did),
identities: IdentitiesMutation::new(did),
delegations: DelegationsMutation::new(did),
revocations: RevocationsMutation::new(did),
}
}
pub fn author(&self) -> &Did {
&self.did
}
pub fn reset(&mut self) {
self.content = ContentMutation::new(&self.did);
self.identities = IdentitiesMutation::new(&self.did);
self.delegations = DelegationsMutation::new(&self.did);
self.revocations = RevocationsMutation::new(&self.did);
}
pub fn did(&self) -> &str {
&self.did
}
pub fn content_mut(&mut self) -> &mut ContentMutation {
&mut self.content
}
pub fn content(&self) -> &ContentMutation {
&self.content
}
pub fn identities_mut(&mut self) -> &mut IdentitiesMutation {
&mut self.identities
}
pub fn identities(&self) -> &IdentitiesMutation {
&self.identities
}
pub fn delegations_mut(&mut self) -> &mut DelegationsMutation {
&mut self.delegations
}
pub fn delegations(&self) -> &DelegationsMutation {
&self.delegations
}
pub fn revocations_mut(&mut self) -> &mut RevocationsMutation {
&mut self.revocations
}
pub fn revocations(&self) -> &RevocationsMutation {
&self.revocations
}
pub fn is_empty(&self) -> bool {
self.content.changes.len() == 0
&& self.identities.changes.len() == 0
&& self.delegations.changes.len() == 0
&& self.revocations.changes.len() == 0
}
pub fn append(&mut self, other: SphereMutation) {
append_changes(&mut self.content.changes, other.content.changes);
append_changes(&mut self.identities.changes, other.identities.changes);
append_changes(&mut self.delegations.changes, other.delegations.changes);
append_changes(&mut self.revocations.changes, other.revocations.changes);
}
}
fn append_changes<K, V>(destination: &mut Vec<MapOperation<K, V>>, source: Vec<MapOperation<K, V>>)
where
K: VersionedMapKey,
V: VersionedMapValue,
{
for change in source {
let op_key = match &change {
MapOperation::Add { key, .. } => key,
MapOperation::Remove { key } => key,
};
destination.retain(|op| {
let this_op_key = match &op {
MapOperation::Add { key, .. } => key,
MapOperation::Remove { key } => key,
};
this_op_key != op_key
});
destination.push(change);
}
}
#[derive(Default, Debug)]
pub struct VersionedMapMutation<K, V>
where
K: VersionedMapKey,
V: VersionedMapValue,
{
did: String,
changes: Vec<MapOperation<K, V>>,
}
impl<K, V> VersionedMapMutation<K, V>
where
K: VersionedMapKey,
V: VersionedMapValue,
{
pub fn apply_changelog(&mut self, changelog: &ChangelogIpld<MapOperation<K, V>>) -> Result<()> {
let did = changelog
.did
.as_ref()
.ok_or_else(|| anyhow!("Changelog did not have an author DID"))?;
if did != &self.did {
return Err(anyhow!(
"Changelog has unexpected author (was {}, expected {})",
did,
self.did
));
}
self.changes = changelog.changes.clone();
Ok(())
}
pub fn new(did: &str) -> Self {
VersionedMapMutation {
did: did.into(),
changes: Default::default(),
}
}
pub fn did(&self) -> &str {
&self.did
}
pub fn changes(&self) -> &[MapOperation<K, V>] {
&self.changes
}
pub fn set(&mut self, key: &K, value: &V) {
self.changes.push(MapOperation::Add {
key: key.clone(),
value: value.clone(),
});
}
pub fn remove(&mut self, key: &K) {
self.changes.push(MapOperation::Remove { key: key.clone() });
}
}