mod create;
mod join;
mod open;
mod recover;
use create::*;
use join::*;
use open::*;
use recover::*;
use crate::{
platform::{PlatformKeyStorage, PlatformStorage},
storage::{create_platform_storage, StorageLayout},
};
use anyhow::{anyhow, Result};
use noosphere_core::context::SphereContext;
use noosphere_core::{
authority::Authorization,
data::{Did, Mnemonic},
};
use noosphere_storage::{SphereDb, StorageConfig};
use std::path::{Path, PathBuf};
use url::Url;
#[derive(Default, Clone)]
enum SphereInitialization {
#[default]
Create,
Join(Did),
Recover(Did),
Open(Option<Did>),
}
pub enum SphereContextBuilderArtifacts {
SphereCreated {
context: SphereContext<PlatformStorage>,
mnemonic: Mnemonic,
},
SphereOpened(SphereContext<PlatformStorage>),
}
impl SphereContextBuilderArtifacts {
pub fn require_mnemonic(&self) -> Result<&str> {
match self {
SphereContextBuilderArtifacts::SphereCreated { mnemonic, .. } => Ok(mnemonic.as_str()),
_ => Err(anyhow!(
"The sphere builder artifacts do not include a mnemonic!"
)),
}
}
}
impl From<SphereContextBuilderArtifacts> for SphereContext<PlatformStorage> {
fn from(artifacts: SphereContextBuilderArtifacts) -> Self {
match artifacts {
SphereContextBuilderArtifacts::SphereCreated { context, .. } => context,
SphereContextBuilderArtifacts::SphereOpened(context) => context,
}
}
}
pub struct SphereContextBuilder {
initialization: SphereInitialization,
pub(crate) scoped_storage_layout: bool,
pub(crate) gateway_api: Option<Url>,
pub(crate) ipfs_gateway_url: Option<Url>,
pub(crate) storage_path: Option<PathBuf>,
pub(crate) authorization: Option<Authorization>,
pub(crate) key_storage: Option<PlatformKeyStorage>,
pub(crate) key_name: Option<String>,
pub(crate) mnemonic: Option<Mnemonic>,
pub(crate) storage_config: Option<StorageConfig>,
}
impl SphereContextBuilder {
pub fn join_sphere(mut self, sphere_identity: &Did) -> Self {
self.initialization = SphereInitialization::Join(sphere_identity.to_owned());
self
}
pub fn create_sphere(mut self) -> Self {
self.initialization = SphereInitialization::Create;
self
}
pub fn recover_sphere(mut self, sphere_id: &Did) -> Self {
self.initialization = SphereInitialization::Recover(sphere_id.to_owned());
self
}
pub fn open_sphere(mut self, sphere_identity: Option<&Did>) -> Self {
self.initialization = SphereInitialization::Open(sphere_identity.cloned());
self
}
pub fn syncing_to(mut self, gateway_url: Option<&Url>) -> Self {
self.gateway_api = gateway_url.cloned();
self
}
pub fn reading_ipfs_from(mut self, ipfs_gateway_url: Option<&Url>) -> Self {
self.ipfs_gateway_url = ipfs_gateway_url.cloned();
self
}
pub fn using_scoped_storage_layout(mut self) -> Self {
self.scoped_storage_layout = true;
self
}
pub fn at_storage_path(mut self, path: &Path) -> Self {
self.storage_path = Some(path.into());
self
}
pub fn authorized_by(mut self, authorization: Option<&Authorization>) -> Self {
self.authorization = authorization.cloned();
self
}
pub fn using_mnemonic(mut self, mnemonic: Option<&Mnemonic>) -> Self {
self.mnemonic = mnemonic.cloned();
self
}
pub fn reading_keys_from(mut self, key_storage: PlatformKeyStorage) -> Self {
self.key_storage = Some(key_storage);
self
}
pub fn using_key(mut self, key_name: &str) -> Self {
self.key_name = Some(key_name.to_owned());
self
}
pub fn with_storage_config(mut self, storage_config: &StorageConfig) -> Self {
self.storage_config = Some(storage_config.to_owned());
self
}
pub async fn build(self) -> Result<SphereContextBuilderArtifacts> {
let initialization = self.initialization.clone();
match initialization {
SphereInitialization::Create => create_a_sphere(self).await,
SphereInitialization::Join(sphere_identity) => {
join_a_sphere(self, sphere_identity).await
}
SphereInitialization::Recover(sphere_identity) => {
recover_a_sphere(self, sphere_identity).await
}
SphereInitialization::Open(sphere_identity) => {
open_a_sphere(self, sphere_identity).await
}
}
}
pub(crate) fn require_storage_path(&self) -> Result<&Path> {
self.storage_path
.as_deref()
.ok_or_else(|| anyhow!("Storage path required but not configured"))
}
pub(crate) fn require_gateway_api(&self) -> Result<&Url> {
self.gateway_api
.as_ref()
.ok_or_else(|| anyhow!("Gateway API URL required but not configured"))
}
pub(crate) fn require_mnemonic(&self) -> Result<&Mnemonic> {
self.mnemonic
.as_ref()
.ok_or_else(|| anyhow!("Mnemonic required but not configured"))
}
pub(crate) fn require_key_storage(&self) -> Result<&PlatformKeyStorage> {
self.key_storage
.as_ref()
.ok_or_else(|| anyhow!("Key storage required but not configured"))
}
pub(crate) fn require_key_name(&self) -> Result<&str> {
self.key_name
.as_deref()
.ok_or_else(|| anyhow!("Key name required but not configured"))
}
}
impl Default for SphereContextBuilder {
fn default() -> Self {
Self {
initialization: SphereInitialization::Create,
scoped_storage_layout: false,
gateway_api: None,
ipfs_gateway_url: None,
storage_path: None,
authorization: None,
key_storage: None as Option<PlatformKeyStorage>,
key_name: None,
mnemonic: None,
storage_config: None,
}
}
}
impl TryFrom<(PathBuf, bool, Option<Did>)> for StorageLayout {
type Error = anyhow::Error;
fn try_from(
(storage_path, scoped_storage_layout, sphere_identity): (PathBuf, bool, Option<Did>),
) -> std::result::Result<Self, Self::Error> {
Ok(match scoped_storage_layout {
true => StorageLayout::Scoped(
storage_path,
sphere_identity.ok_or_else(|| anyhow!("A sphere identity must be provided!"))?,
),
false => StorageLayout::Unscoped(storage_path),
})
}
}
#[allow(unused_variables)]
pub(crate) async fn generate_db(
storage_path: PathBuf,
scoped_storage_layout: bool,
sphere_identity: Option<Did>,
ipfs_gateway_url: Option<Url>,
storage_config: Option<StorageConfig>,
) -> Result<SphereDb<PlatformStorage>> {
let storage_layout: StorageLayout =
(storage_path, scoped_storage_layout, sphere_identity).try_into()?;
let storage = create_platform_storage(storage_layout, ipfs_gateway_url, storage_config).await?;
SphereDb::new(&storage).await
}
#[cfg(test)]
mod tests {
use super::SphereContextBuilder;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test;
use crate::{key::KeyStorage, platform::make_temporary_platform_primitives};
use noosphere_core::context::SphereContext;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn it_can_create_a_sphere_and_later_open_it() {
let (storage_path, key_storage, _temporary_directories) =
make_temporary_platform_primitives().await.unwrap();
key_storage.create_key("foo").await.unwrap();
let sphere_identity = {
let artifacts = SphereContextBuilder::default()
.create_sphere()
.at_storage_path(&storage_path)
.reading_keys_from(key_storage.clone())
.using_key("foo")
.build()
.await
.unwrap();
artifacts.require_mnemonic().unwrap();
let sphere_context: SphereContext<_> = artifacts.into();
sphere_context.identity().clone()
};
let context: SphereContext<_> = SphereContextBuilder::default()
.open_sphere(None)
.at_storage_path(&storage_path)
.reading_keys_from(key_storage)
.build()
.await
.unwrap()
.into();
assert_eq!(&sphere_identity, context.identity());
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn it_can_create_a_scoped_sphere_and_later_open_it() {
let (storage_path, key_storage, _temporary_directories) =
make_temporary_platform_primitives().await.unwrap();
key_storage.create_key("foo").await.unwrap();
let sphere_identity = {
let artifacts = SphereContextBuilder::default()
.create_sphere()
.using_scoped_storage_layout()
.at_storage_path(&storage_path)
.reading_keys_from(key_storage.clone())
.using_key("foo")
.build()
.await
.unwrap();
artifacts.require_mnemonic().unwrap();
let sphere_context: SphereContext<_> = artifacts.into();
sphere_context.identity().clone()
};
let context: SphereContext<_> = SphereContextBuilder::default()
.open_sphere(Some(&sphere_identity))
.using_scoped_storage_layout()
.at_storage_path(&storage_path)
.reading_keys_from(key_storage)
.build()
.await
.unwrap()
.into();
assert_eq!(&sphere_identity, context.identity());
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn it_can_initialize_a_sphere_to_sync_from_elsewhere() {
let (storage_path, key_storage, _temporary_directories) =
make_temporary_platform_primitives().await.unwrap();
key_storage.create_key("foo").await.unwrap();
let artifacts = SphereContextBuilder::default()
.join_sphere(&"did:key:foo".into())
.at_storage_path(&storage_path)
.reading_keys_from(key_storage)
.authorized_by(None)
.using_key("foo")
.build()
.await
.unwrap();
let context: SphereContext<_> = artifacts.into();
assert_eq!(context.identity().as_str(), "did:key:foo");
}
}