diff --git a/Cargo.toml b/Cargo.toml index 0e1579e..0520b67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,11 @@ publish = false [dependencies] anyhow = "1" -ceramic-event = { git = "https://github.com/ceramicnetwork/rust-ceramic", branch = "feat/wasi" } -json-patch = { version = "1.2.0", features = ["diff"] } +ceramic-event = { git = "https://github.com/ceramicnetwork/rust-ceramic", branch = "feat/event-builder" } +#ceramic-event = { path = "/Users/dbrowning/code/3box/rust-ceramic/event" } +json-patch = { version = "2.0.0", features = ["diff"] } +once_cell = "1.19.0" +rand = "0.8.5" reqwest = { version = "0.11.14", features = ["json"], optional = true } schemars = "0.8.12" serde = { version = "1.0", features = ["derive"] } diff --git a/src/api.rs b/src/api.rs index 5fb3dbf..51a9305 100644 --- a/src/api.rs +++ b/src/api.rs @@ -3,8 +3,18 @@ use ceramic_event::{ Base64String, Base64UrlString, Jws, MultiBase32String, MultiBase36String, StreamId, StreamIdType, }; +use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::str::FromStr; + +/// Separator used for model and MID +pub const SEP: &str = "model"; + +/// All models have this model StreamId, while all documents have the model stream id of the model +/// they use +pub static PARENT_STREAM_ID: Lazy = + Lazy::new(|| StreamId::from_str("kh4q0ozorrgaq2mezktnrmdwleo1d").unwrap()); /// Header for block data #[derive(Debug, Serialize)] diff --git a/src/lib.rs b/src/lib.rs index bc32a29..d9af357 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,13 +9,14 @@ mod model_definition; mod query; use ceramic_event::{ - Base64String, Cid, DeterministicInitEvent, EventArgs, Jws, MultiBase36String, Signer, StreamId, - StreamIdType, + event_builder::*, + unvalidated::{self, IntoSignedCeramicEvent}, + Base64String, Cid, EventBytes, Jws, MultiBase36String, Signer, StreamId, StreamIdType, }; use serde::Serialize; use std::str::FromStr; -use crate::api::ModelData; +use crate::api::{ModelData, PARENT_STREAM_ID, SEP}; pub use ceramic_event; pub use json_patch; pub use model_definition::{ @@ -27,11 +28,13 @@ pub use schemars; /// Client for interacting with the Ceramic HTTP API #[derive(Clone, Debug)] -pub struct CeramicHttpClient { +pub struct CeramicHttpClient { signer: S, } -impl CeramicHttpClient { +const FAMILY: &str = "rust-client"; + +impl CeramicHttpClient { /// Create a new client, using a signer and private key pub fn new(signer: S) -> Self { Self { signer } @@ -86,17 +89,27 @@ impl CeramicHttpClient { &self, model: &ModelDefinition, ) -> anyhow::Result> { - let args = EventArgs::new(&self.signer); - let commit = args.init_with_data(&model).await?; - let controllers: Vec<_> = args.controllers().map(|c| c.id.clone()).collect(); + let controller = self.signer.id().id.clone(); + let parent: EventBytes = PARENT_STREAM_ID.to_vec()?.into(); + let commit = Builder::default() + .with_sep(SEP.to_string()) + .with_additional(SEP.to_string(), parent.into()) + .init() + .with_controller(controller.clone()) + .with_data(&model) + .build() + .await?; + let commit: unvalidated::Payload<_> = commit.into(); + let commit = commit.signed(&self.signer).await?; + let controllers: Vec<_> = vec![controller]; let data = Base64String::from(commit.linked_block.as_ref()); - let model = Base64String::from(args.parent().to_vec()?); + let model = Base64String::from(PARENT_STREAM_ID.to_vec()?); Ok(api::CreateRequest { r#type: StreamIdType::Model, block: api::BlockData { header: api::BlockHeader { - family: "test".to_string(), + family: FAMILY.to_string(), controllers, model, }, @@ -124,7 +137,7 @@ impl CeramicHttpClient { request_path: self.index_endpoint().to_string(), request_body: data, }; - let jws = Jws::for_data(&self.signer, &req).await?; + let jws = Jws::builder(&self.signer).build_for_data(&req).await?; api::AdminApiRequest::try_from(jws) } @@ -139,7 +152,7 @@ impl CeramicHttpClient { request_path: self.models_endpoint().to_string(), request_body: data, }; - let jws = Jws::for_data(&self.signer, &req).await?; + let jws = Jws::builder(&self.signer).build_for_data(&req).await?; api::AdminApiRequest::try_from(jws) } @@ -147,19 +160,17 @@ impl CeramicHttpClient { pub async fn create_single_instance_request( &self, model_id: &StreamId, - ) -> anyhow::Result> { + ) -> anyhow::Result> { if !model_id.is_model() { anyhow::bail!("StreamId was not a model"); } - let args = EventArgs::new_with_parent(&self.signer, model_id); - let _commit = args.init()?; - let controllers: Vec<_> = args.controllers().map(|c| c.id.clone()).collect(); + let controllers: Vec<_> = vec![self.signer.id().id.clone()]; let model = Base64String::from(model_id.to_vec()?); Ok(api::CreateRequest { r#type: StreamIdType::ModelInstanceDocument, block: api::BlockData { header: api::BlockHeader { - family: "test".to_string(), + family: FAMILY.to_string(), controllers, model, }, @@ -171,8 +182,18 @@ impl CeramicHttpClient { }) } + fn gen_rand_bytes() -> [u8; SIZE] { + // can't take &mut rng cause of Send even if we drop it + let mut rng = rand::thread_rng(); + let mut arr = [0; SIZE]; + for x in &mut arr { + *x = rand::Rng::gen_range(&mut rng, 0..=255); + } + arr + } + /// Create a serde compatible request for a list instance per account creation of a model - pub async fn create_list_instance_request( + pub async fn create_list_instance_request( &self, model_id: &StreamId, data: T, @@ -180,16 +201,31 @@ impl CeramicHttpClient { if !model_id.is_model() { anyhow::bail!("StreamId was not a model"); } - let args = EventArgs::new_with_parent(&self.signer, model_id); - let commit = args.init_with_data(&data).await?; - let controllers: Vec<_> = args.controllers().map(|c| c.id.clone()).collect(); + let model_vec = model_id.to_vec()?; + let model = Base64String::from(model_vec.as_slice()); + let model_bytes = EventBytes::from(model_vec); + let unique = Self::gen_rand_bytes::<12>(); + let unique: EventBytes = unique.to_vec().into(); + let controller = self.signer.id().id.clone(); + let commit = Builder::default() + .with_sep(SEP.to_string()) + .with_additional(SEP.to_string(), model_bytes.into()) + .with_additional("unique".to_string(), unique.into()) + .init() + .with_controller(controller.clone()) + .with_data(data) + .build() + .await?; + let commit: unvalidated::Payload<_> = commit.into(); + let commit = commit.signed(&self.signer).await?; + let controllers: Vec<_> = vec![controller]; let data = Base64String::from(commit.linked_block.as_ref()); - let model = Base64String::from(model_id.to_vec()?); + Ok(api::CreateRequest { r#type: StreamIdType::ModelInstanceDocument, block: api::BlockData { header: api::BlockHeader { - family: "test".to_string(), + family: FAMILY.to_string(), controllers, model, }, @@ -213,17 +249,23 @@ impl CeramicHttpClient { } if let Some(tip) = get.state.as_ref().and_then(|s| s.log.last()) { let tip = Cid::from_str(tip.cid.as_ref())?; - let args = EventArgs::new_with_parent(&self.signer, model); - let commit = args.update(&get.stream_id.cid, &tip, &patch).await?; - let controllers: Vec<_> = args.controllers().map(|c| c.id.clone()).collect(); + let controller = self.signer.id().id.clone(); + let model_vec = model.to_vec()?; + let model = Base64String::from(model_vec.as_slice()); + let commit = Builder::default() + .data(get.stream_id.cid, tip, patch) + .build() + .await?; + let commit: unvalidated::Payload<_> = commit.into(); + let commit = commit.signed(&self.signer).await?; + let controllers: Vec<_> = vec![controller]; let data = Base64String::from(commit.linked_block.as_ref()); - let model = Base64String::from(model.to_vec()?); let stream = MultiBase36String::try_from(&get.stream_id)?; Ok(api::UpdateRequest { r#type: StreamIdType::ModelInstanceDocument, block: api::BlockData { header: api::BlockHeader { - family: "test".to_string(), + family: FAMILY.to_string(), controllers, model, }, @@ -285,7 +327,7 @@ impl CeramicHttpClient { request_path: self.node_status_endpoint().to_string(), request_body: data, }; - let jws = Jws::for_data(&self.signer, &req).await?; + let jws = Jws::builder(&self.signer).build_for_data(&req).await?; api::AdminApiRequest::try_from(jws) } } @@ -301,13 +343,13 @@ pub mod remote { #[derive(Clone)] /// Ceramic remote http client - pub struct CeramicRemoteHttpClient { + pub struct CeramicRemoteHttpClient { cli: CeramicHttpClient, remote: reqwest::Client, url: Url, } - impl CeramicRemoteHttpClient { + impl CeramicRemoteHttpClient { /// Create a new ceramic remote http client for a signer, private key, and url pub fn new(signer: S, remote: Url) -> Self { Self { @@ -413,7 +455,7 @@ pub mod remote { } /// Create an instance of a model allowing multiple instances on a remote ceramic - pub async fn create_list_instance( + pub async fn create_list_instance( &self, model_id: &StreamId, instance: T, @@ -588,7 +630,6 @@ pub mod tests { use crate::model_definition::{GetRootSchema, ModelAccountRelation, ModelDefinition}; use crate::query::{FilterQuery, OperationFilter}; use ceramic_event::{DidDocument, JwkSigner}; - use json_patch::{AddOperation, ReplaceOperation}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -657,28 +698,14 @@ pub mod tests { tokio::time::sleep(Duration::from_secs(1)).await; - let patch = json_patch::Patch(vec![ - json_patch::PatchOperation::Add(AddOperation { - path: "/creator".to_string(), - value: serde_json::Value::String(ceramic.client().signer().id().id.clone()), - }), - json_patch::PatchOperation::Add(AddOperation { - path: "/radius".to_string(), - value: serde_json::Value::Number(serde_json::Number::from(1)), - }), - json_patch::PatchOperation::Add(AddOperation { - path: "/red".to_string(), - value: serde_json::Value::Number(serde_json::Number::from(2)), - }), - json_patch::PatchOperation::Add(AddOperation { - path: "/green".to_string(), - value: serde_json::Value::Number(serde_json::Number::from(3)), - }), - json_patch::PatchOperation::Add(AddOperation { - path: "/blue".to_string(), - value: serde_json::Value::Number(serde_json::Number::from(4)), - }), - ]); + let patch: json_patch::Patch = serde_json::from_value(serde_json::json!([ + { "op": "add", "path": "/creator", "value": ceramic.client().signer().id().id }, + { "op": "add", "path": "/radius", "value": 1 }, + { "op": "add", "path": "/red", "value": 2 }, + { "op": "add", "path": "/green", "value": 3 }, + { "op": "add", "path": "/blue", "value": 4 }, + ])) + .unwrap(); let post_resp = ceramic.update(&model, &stream_id, patch).await.unwrap(); assert_eq!(post_resp.stream_id, stream_id); let post_resp: Ball = serde_json::from_value(post_resp.state.unwrap().content).unwrap(); @@ -706,12 +733,10 @@ pub mod tests { //give anchor time to complete tokio::time::sleep(Duration::from_secs(1)).await; - let patch = json_patch::Patch(vec![json_patch::PatchOperation::Replace( - ReplaceOperation { - path: "/red".to_string(), - value: serde_json::json!(5), - }, - )]); + let patch: json_patch::Patch = serde_json::from_value(serde_json::json!([ + { "op": "replace", "path": "/red", "value": 5 }, + ])) + .unwrap(); let post_resp = ceramic.update(&model, &stream_id, patch).await.unwrap(); assert_eq!(post_resp.stream_id, stream_id); let post_resp: Ball = serde_json::from_value(post_resp.state.unwrap().content).unwrap(); @@ -720,12 +745,10 @@ pub mod tests { //give anchor time to complete tokio::time::sleep(Duration::from_secs(1)).await; - let patch = json_patch::Patch(vec![json_patch::PatchOperation::Replace( - ReplaceOperation { - path: "/blue".to_string(), - value: serde_json::json!(8), - }, - )]); + let patch: json_patch::Patch = serde_json::from_value(serde_json::json!([ + { "op": "replace", "path": "/blue", "value": 8 }, + ])) + .unwrap(); let post_resp = ceramic.update(&model, &stream_id, patch).await.unwrap(); assert_eq!(post_resp.stream_id, stream_id); let post_resp: Ball = serde_json::from_value(post_resp.state.unwrap().content).unwrap(); @@ -927,12 +950,10 @@ pub mod tests { tokio::time::sleep(Duration::from_millis(100)).await; let replace_value = rand::random::() % 10i32; - let patch = json_patch::Patch(vec![json_patch::PatchOperation::Replace( - ReplaceOperation { - path: "/red".to_string(), - value: serde_json::json!(replace_value), - }, - )]); + let patch: json_patch::Patch = serde_json::from_value(serde_json::json!([ + { "op": "replace", "path": "/red", "value": replace_value }, + ])) + .unwrap(); let post_resp = ceramic.update(&model, &stream_id, patch).await.unwrap(); assert_eq!(post_resp.stream_id, stream_id); let post_resp: Ball = serde_json::from_value(post_resp.state.unwrap().content).unwrap();