Skip to content
This repository has been archived by the owner on Sep 21, 2024. It is now read-only.

Commit

Permalink
feat: Dot syntax when traversing by petname (#306)
Browse files Browse the repository at this point in the history
  • Loading branch information
cdata authored Apr 10, 2023
1 parent 3d6062d commit cd87b05
Show file tree
Hide file tree
Showing 24 changed files with 286 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"rust-analyzer.cargo.target": null,
"rust-analyzer.cargo.target": "wasm32-unknown-unknown",
"lldb.launch.cwd": "${workspaceFolder}",
"rust-analyzer.procMacro.enable": true,
"rust-analyzer.procMacro.ignored": {
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ members = [
resolver = "2"

[workspace.dependencies]
subtext = { version = "0.3.3" }
tracing = { version = "0.1" }
tracing-subscriber = { version = "~0.3", features = ["env-filter", "tracing-log"] }

Expand Down
2 changes: 1 addition & 1 deletion rust/noosphere-core/src/authority/author.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub enum Access {
/// content to a sphere. This construct collects the identity and the
/// authorization of that entity to make it easier to determine their level of
/// access to the content of a given sphere.
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct Author<K>
where
K: KeyMaterial + Clone + 'static,
Expand Down
2 changes: 1 addition & 1 deletion rust/noosphere-core/src/authority/authorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::data::Jwt;
/// [Authorization] instead.
/// TODO(ucan-wg/rs-ucan#32): Maybe swap this out is we get a substantially
/// similar construct to land in rs-ucan
#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum Authorization {
/// A fully instantiated UCAN
Ucan(Ucan),
Expand Down
4 changes: 2 additions & 2 deletions rust/noosphere-into/src/into/html/sphere.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ pub mod tests {
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn it_writes_a_file_from_the_sphere_to_the_target_as_html() {
let context = simulated_sphere_context(SimulationAccess::ReadWrite)
let context = simulated_sphere_context(SimulationAccess::ReadWrite, None)
.await
.unwrap();
let mut cursor = SphereCursor::latest(context);
Expand Down Expand Up @@ -246,7 +246,7 @@ pub mod tests {
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn it_symlinks_a_file_slug_to_the_latest_file_version() {
let context = simulated_sphere_context(SimulationAccess::ReadWrite)
let context = simulated_sphere_context(SimulationAccess::ReadWrite, None)
.await
.unwrap();
let mut cursor = SphereCursor::latest(context);
Expand Down
2 changes: 1 addition & 1 deletion rust/noosphere-ipfs/src/client/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use url::Url;
/// A high-level HTTP client for accessing IPFS
/// [HTTP Gateway](https://docs.ipfs.tech/reference/http/gateway/) and normalizing
/// their expected payloads to Noosphere-friendly formats.
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct GatewayClient {
client: Client,
api_url: Url,
Expand Down
2 changes: 1 addition & 1 deletion rust/noosphere-ipfs/src/client/kubo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fn get_codec(cid: &Cid) -> Result<String> {
/// A high-level HTTP client for accessing IPFS
/// [Kubo RPC APIs](https://docs.ipfs.tech/reference/kubo/rpc/) and normalizing
/// their expected payloads to Noosphere-friendly formats
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct KuboClient {
client: Client<HttpConnector<GaiResolver>>,
api_url: Url,
Expand Down
3 changes: 2 additions & 1 deletion rust/noosphere-ipfs/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub use kubo::KuboClient;
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use cid::Cid;
use std::fmt::Debug;
use tokio::io::AsyncRead;

#[cfg(not(target_arch = "wasm32"))]
Expand All @@ -30,7 +31,7 @@ impl<S> IpfsClientAsyncReadSendSync for S where S: AsyncRead {}
/// intended to be general enough to apply to other IPFS implementations.
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait IpfsClient: Clone {
pub trait IpfsClient: Clone + Debug {
/// Returns true if the block (referenced by [Cid]) is pinned by the IPFS
/// server
async fn block_is_pinned(&self, cid: &Cid) -> Result<bool>;
Expand Down
2 changes: 1 addition & 1 deletion rust/noosphere-ipfs/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use noosphere_storage::KeyValueStore;
/// implementation of [Storage] and an [IpfsClient].
/// [IpfsStorage] is generic over [BlockStore] and [KeyValueStore]
/// but will produce a [IpfsStore] wrapped [BlockStore]
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct IpfsStorage<S, C>
where
S: Storage,
Expand Down
200 changes: 199 additions & 1 deletion rust/noosphere-sphere/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,36 @@ where
})
}

/// The same as [SphereContext::traverse_by_petname], but accepts a linear
/// sequence of petnames and attempts to recursively traverse through
/// spheres using that sequence. The sequence is traversed from back to
/// front. So, if the sequence is "gold", "cat", "bob", it will traverse to
/// bob, then to bob's cat, then to bob's cat's gold.
pub async fn traverse_by_petnames(
&mut self,
petname_path: &[String],
) -> Result<SphereContext<K, S>> {
let mut sphere_context: Option<Self> = None;
let mut path = Vec::from(petname_path);

while let Some(petname) = path.pop() {
sphere_context = Some(match sphere_context {
None => self.traverse_by_petname(&petname).await?,
Some(mut sphere_context) => sphere_context.traverse_by_petname(&petname).await?,
})
}

Ok(sphere_context
.ok_or_else(|| anyhow!("Unable to traverse to {}", petname_path.join(".")))?)
}

/// Given a petname that has been assigned to a sphere identity within this
/// sphere's address book, produce a [SphereContext] backed by the same
/// credentials and storage primitives as this one, but that accesses the
/// sphere referred to by the provided [Did]. If the local data for the
/// sphere being traversed to is not available, an attempt will be made to
/// replicate the data from a Noosphere Gateway.
#[instrument(level = "debug", skip(self))]
pub async fn traverse_by_petname(&mut self, petname: &str) -> Result<SphereContext<K, S>> {
// Resolve petname to sphere version via address book entry

Expand All @@ -95,6 +119,8 @@ where
None => return Err(anyhow!("\"{petname}\" is not assigned to an identity")),
};

debug!("{:?}", identity);

let resolved_version = match identity.link_record(self.db()).await {
Some(link_record) => link_record.dereference().await,
None => None,
Expand All @@ -110,10 +136,18 @@ where
}
};

debug!("Resolved version is {}", resolved_version);

// Check for version in local sphere DB

let maybe_has_resolved_version = match self.db().get_version(&identity.did).await? {
Some(local_version) => local_version == resolved_version,
Some(local_version) => {
debug!(
"Local version: {}, resolved version: {}",
local_version, resolved_version
);
local_version == resolved_version
}
None => false,
};

Expand All @@ -133,6 +167,8 @@ where
));
}

debug!("Checking to see if we can get the sphere body...");

match self.db().load::<DagCborCodec, SphereIpld>(&memo.body).await {
Ok(_) => false,
Err(error) => {
Expand All @@ -153,6 +189,7 @@ where
// If no version available or memo/body missing, replicate from gateway

if should_replicate_from_gateway {
debug!("Attempting to replicate from gateway...");
let client = self.client().await?;
let stream = client.replicate(&resolved_version).await?;

Expand Down Expand Up @@ -308,3 +345,164 @@ where
self.access.take();
}
}

#[cfg(test)]
pub mod tests {
use std::sync::Arc;

use noosphere_core::{
authority::{SphereAction, SphereReference},
data::{ContentType, Jwt},
tracing::initialize_tracing,
};
use noosphere_storage::{MemoryStorage, TrackingStorage};
use serde_json::json;
use tokio::{io::AsyncReadExt, sync::Mutex};
use ucan::{
builder::UcanBuilder,
capability::{Capability, Resource, With},
};
use ucan_key_support::ed25519::Ed25519KeyMaterial;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test;

use crate::{
helpers::{simulated_sphere_context, SimulationAccess},
HasMutableSphereContext, HasSphereContext, SphereContentRead, SphereContentWrite,
SphereContext, SpherePetnameWrite,
};

#[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_traverse_a_sequence_of_petnames() {
initialize_tracing();

let origin_sphere_context = simulated_sphere_context(SimulationAccess::ReadWrite, None)
.await
.unwrap();

let name_seqeuence: Vec<String> = vec!["a".into(), "b".into(), "c".into()];

let mut db = origin_sphere_context
.sphere_context()
.await
.unwrap()
.db()
.clone();

let mut contexts = vec![origin_sphere_context.clone()];

for name in name_seqeuence.iter() {
let mut sphere_context =
simulated_sphere_context(SimulationAccess::ReadWrite, Some(db.clone()))
.await
.unwrap();

sphere_context
.write(
"my-name",
&ContentType::Subtext.to_string(),
name.as_bytes(),
None,
)
.await
.unwrap();
sphere_context.save(None).await.unwrap();

contexts.push(sphere_context);
}

let mut next_sphere_context: Option<
Arc<Mutex<SphereContext<Ed25519KeyMaterial, TrackingStorage<MemoryStorage>>>>,
> = None;

for mut sphere_context in contexts.into_iter().rev() {
if let Some(next_sphere_context) = next_sphere_context {
let version = next_sphere_context.version().await.unwrap();

let next_author = next_sphere_context
.sphere_context()
.await
.unwrap()
.author()
.clone();
let next_identity = next_sphere_context.identity().await.unwrap();

let link_record = Jwt(UcanBuilder::default()
.issued_by(&next_author.key)
.for_audience(&next_identity)
.witnessed_by(
&next_author
.authorization
.as_ref()
.unwrap()
.resolve_ucan(&db)
.await
.unwrap(),
)
.claiming_capability(&Capability {
with: With::Resource {
kind: Resource::Scoped(SphereReference {
did: next_identity.into(),
}),
},
can: SphereAction::Publish,
})
.with_lifetime(120)
.with_fact(json!({
"link": version.to_string()
}))
.build()
.unwrap()
.sign()
.await
.unwrap()
.encode()
.unwrap());

let mut name = String::new();
let mut file = next_sphere_context.read("my-name").await.unwrap().unwrap();
file.contents.read_to_string(&mut name).await.unwrap();

println!("Adopting {name}");

sphere_context
.adopt_petname(&name, &link_record)
.await
.unwrap();

db.set_version(
&sphere_context.identity().await.unwrap(),
&sphere_context.save(None).await.unwrap(),
)
.await
.unwrap();
}

next_sphere_context = Some(sphere_context);
}

let target_sphere_context = Arc::new(
origin_sphere_context
.sphere_context()
.await
.unwrap()
.traverse_by_petnames(&name_seqeuence.into_iter().rev().collect::<Vec<String>>())
.await
.unwrap(),
);

let mut name = String::new();
let mut file = target_sphere_context
.read("my-name")
.await
.unwrap()
.unwrap();
file.contents.read_to_string(&mut name).await.unwrap();

assert_eq!(name.as_str(), "c");
}
}
Loading

0 comments on commit cd87b05

Please sign in to comment.