use crate::data::{Did, IdentityIpld, LinkRecord};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use noosphere_storage::Storage;
use ucan::store::UcanJwtStore;
use crate::context::{internal::SphereContextInternal, HasMutableSphereContext, SpherePetnameRead};
fn validate_petname(petname: &str) -> Result<()> {
if petname.is_empty() {
Err(anyhow!("Petname must not be empty."))
} else if petname.len() >= 4 && petname.starts_with("did:") {
Err(anyhow!("Petname must not be a DID."))
} else {
Ok(())
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait SpherePetnameWrite<S>: SpherePetnameRead<S>
where
S: Storage + 'static,
{
async fn set_petname(&mut self, name: &str, identity: Option<Did>) -> Result<()>;
async fn set_petname_record(&mut self, name: &str, record: &LinkRecord) -> Result<Option<Did>>;
#[deprecated(note = "Use set_petname_record instead")]
async fn adopt_petname(&mut self, name: &str, record: &LinkRecord) -> Result<Option<Did>>;
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl<C, S> SpherePetnameWrite<S> for C
where
C: HasMutableSphereContext<S>,
S: Storage + 'static,
{
async fn set_petname(&mut self, name: &str, identity: Option<Did>) -> Result<()> {
self.assert_write_access().await?;
validate_petname(name)?;
if identity.is_some()
&& self.sphere_context().await?.identity() == identity.as_ref().unwrap()
{
return Err(anyhow!("Sphere cannot assign itself to a petname."));
}
let current_address = self.get_petname(name).await?;
if identity != current_address {
let mut context = self.sphere_context_mut().await?;
match identity {
Some(identity) => {
context.mutation_mut().identities_mut().set(
&name.to_string(),
&IdentityIpld {
did: identity,
link_record: None,
},
);
}
None => context
.mutation_mut()
.identities_mut()
.remove(&name.to_string()),
};
}
Ok(())
}
async fn adopt_petname(&mut self, name: &str, record: &LinkRecord) -> Result<Option<Did>> {
self.set_petname_record(name, record).await
}
async fn set_petname_record(&mut self, name: &str, record: &LinkRecord) -> Result<Option<Did>> {
self.assert_write_access().await?;
validate_petname(name)?;
let identity = record.to_sphere_identity();
let expected_identity = self.get_petname(name).await?;
match expected_identity {
Some(expected_identity) => {
if expected_identity != identity {
return Err(anyhow!(
"Cannot adopt petname record for '{}'; expected record for {} but got record for {}",
name,
expected_identity,
identity
));
}
}
None => {
return Err(anyhow!(
"Cannot adopt petname record for '{}' (not assigned to a sphere identity)",
name
));
}
};
if self.sphere_context().await?.identity() == &identity {
return Err(anyhow!("Sphere cannot assign itself to a petname."));
}
if let Some(existing_record) = self.get_petname_record(name).await? {
if !existing_record.superceded_by(record) {
return Err(anyhow!(
"Previously stored record supercedes provided record."
));
}
}
let cid = self
.sphere_context_mut()
.await?
.db_mut()
.write_token(&record.encode()?)
.await?;
debug!(
"Adopting '{}' ({}), resolving to {}...",
name, identity, record
);
let new_address = IdentityIpld {
did: identity.clone(),
link_record: Some(cid.into()),
};
let identities = self
.sphere_context()
.await?
.sphere()
.await?
.get_address_book()
.await?
.get_identities()
.await?;
let previous_identity = identities.get(&name.into()).await?;
self.sphere_context_mut()
.await?
.mutation_mut()
.identities_mut()
.set(&name.into(), &new_address);
if let Some(previous_identity) = previous_identity {
if identity != previous_identity.did {
return Ok(Some(previous_identity.did.to_owned()));
}
}
Ok(None)
}
}