Skip to content

Commit

Permalink
feat: single models
Browse files Browse the repository at this point in the history
Adds support for creating models of single account relations. Utilizes wasi
branches in support of fluence deployments.
  • Loading branch information
dbcfd committed Feb 14, 2024
1 parent be4b848 commit 9b4b736
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 23 deletions.
11 changes: 7 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,23 @@ publish = false

[dependencies]
anyhow = "1"
ceramic-event = { git = "https://github.com/3box/rust-ceramic", branch = "main" }
json-patch = { version = "1.0.0", features = ["diff"] }
#ceramic-event = { git = "https://github.com/ceramicnetwork/rust-ceramic", branch = "main" }
#ceramic-event = { path = "/Users/dbrowning/code/3box/rust-ceramic/event", default-features = false }
ceramic-event = { git = "https://github.com/ceramicnetwork/rust-ceramic", branch = "feat/wasi" }
json-patch = { version = "1.2.0", features = ["diff"] }
reqwest = { version = "0.11.14", features = ["json"], optional = true }
schemars = "0.8.12"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
ssi = { version = "0.7", features = ["ed25519"] }
url = { version = "2.2.2", optional = true }
#ssi = { version = "0.7", features = ["ed25519"] }
url = { version = "2.5.0", optional = true }

[features]
default = ["remote"]
remote = ["reqwest", "url"]

[dev-dependencies]
rand = "0.8.5"
test-log = { version = "0.2", default-features = false, features = ["trace"] }
tokio = { version = "1", default-features = false, features = ["macros", "rt"] }
tracing = "0.1"
Expand Down
2 changes: 1 addition & 1 deletion src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub struct BlockData<T: Serialize> {
/// Header for block
pub header: BlockHeader,
/// Data for block
#[serde(skip_serializing_if = "Option::is_none")]
//#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<T>,
/// Signature for block
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down
178 changes: 167 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ mod model_definition;
mod query;

use ceramic_event::{
Base64String, Cid, DagCborEncoded, EventArgs, Jws, MultiBase36String, Signer, StreamId,
Base64String, Cid, DeterministicInitEvent, EventArgs, Jws, MultiBase36String, Signer, StreamId,
StreamIdType,
};
use serde::Serialize;
use std::str::FromStr;

use crate::api::ModelData;
pub use ceramic_event;
pub use json_patch;
pub use model_definition::{
GetRootSchema, ModelAccountRelation, ModelDefinition, ModelRelationDefinition,
ModelViewDefinition,
Expand Down Expand Up @@ -146,12 +147,12 @@ impl<S: Signer> CeramicHttpClient<S> {
pub async fn create_single_instance_request(
&self,
model_id: &StreamId,
) -> anyhow::Result<api::CreateRequest<DagCborEncoded>> {
) -> anyhow::Result<api::CreateRequest<DeterministicInitEvent>> {
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 _commit = args.init()?;
let controllers: Vec<_> = args.controllers().map(|c| c.id.clone()).collect();
let model = Base64String::from(model_id.to_vec()?);
Ok(api::CreateRequest {
Expand All @@ -164,7 +165,7 @@ impl<S: Signer> CeramicHttpClient<S> {
},
linked_block: None,
jws: None,
data: Some(commit.encoded),
data: None,
cacao_block: None,
},
})
Expand Down Expand Up @@ -587,7 +588,7 @@ pub mod tests {
use crate::model_definition::{GetRootSchema, ModelAccountRelation, ModelDefinition};
use crate::query::{FilterQuery, OperationFilter};
use ceramic_event::{DidDocument, JwkSigner};
use json_patch::ReplaceOperation;
use json_patch::{AddOperation, ReplaceOperation};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
Expand Down Expand Up @@ -629,19 +630,60 @@ pub mod tests {
cli.create_model(&model).await.unwrap()
}

pub async fn create_single_model(cli: &CeramicRemoteHttpClient<JwkSigner>) -> StreamId {
let model = ModelDefinition::new::<Ball>("TestBall", ModelAccountRelation::Single).unwrap();
cli.create_model(&model).await.unwrap()
}

#[tokio::test]
async fn should_create_model() {
let ceramic = CeramicRemoteHttpClient::new(signer().await, ceramic_url());
let model = ModelDefinition::new::<Ball>("TestBall", ModelAccountRelation::List).unwrap();
ceramic.create_model(&model).await.unwrap();
}

// #[tokio::test]
// async fn should_create_single_instance() {
// let ceramic = CeramicRemoteHttpClient::new(did(), &did_private_key(), ceramic_url());
// let model = create_model(&ceramic).await;
// ceramic.create_single_instance(&model).await.unwrap();
// }
#[tokio::test]
async fn should_create_single_instance() {
let ceramic = CeramicRemoteHttpClient::new(signer().await, ceramic_url());
let model = create_single_model(&ceramic).await;
ceramic.create_single_instance(&model).await.unwrap();
}

#[tokio::test]
async fn should_create_and_update_single_instance() {
let ceramic = CeramicRemoteHttpClient::new(signer().await, ceramic_url());
let model = create_single_model(&ceramic).await;
let stream_id = ceramic.create_single_instance(&model).await.unwrap();

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 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();
assert_eq!(post_resp.red, 2);
}

#[tokio::test]
async fn should_create_and_update_list() {
Expand Down Expand Up @@ -795,5 +837,119 @@ pub mod tests {
.await
.unwrap();
assert_eq!(res.edges.len(), 1);
let node = &res.edges[0].node;
let result: Ball = serde_json::from_value(node.content.clone()).unwrap();
assert_eq!(result.blue, 5);
}

#[tokio::test]
async fn should_query_models_after_update() {
let ceramic = CeramicRemoteHttpClient::new(signer().await, ceramic_url());
let model = create_model(&ceramic).await;
ceramic.index_model(&model).await.unwrap();
let _instance1 = ceramic
.create_list_instance(
&model,
&Ball {
creator: ceramic.client().signer().id().id.clone(),
radius: 1,
red: 2,
green: 3,
blue: 4,
},
)
.await
.unwrap();

let instance2 = ceramic
.create_list_instance(
&model,
&Ball {
creator: ceramic.client().signer().id().id.clone(),
radius: 2,
red: 3,
green: 4,
blue: 5,
},
)
.await
.unwrap();

//give anchor time to complete
tokio::time::sleep(Duration::from_secs(1)).await;

let replace = Ball {
creator: ceramic.client().signer().id().id.clone(),
radius: 1,
red: 0,
green: 3,
blue: 10,
};
let post_resp = ceramic.replace(&model, &instance2, &replace).await.unwrap();
assert_eq!(post_resp.stream_id, instance2);

//give anchor time to complete
tokio::time::sleep(Duration::from_secs(1)).await;

let mut where_filter = HashMap::new();
where_filter.insert("blue".to_string(), OperationFilter::EqualTo(10.into()));
let filter = FilterQuery::Where(where_filter);
let res = ceramic
.query(&model, Some(filter), Pagination::default())
.await
.unwrap();
assert_eq!(res.edges.len(), 1);
let node = &res.edges[0].node;
let result: Ball = serde_json::from_value(node.content.clone()).unwrap();
assert_eq!(result.blue, 10);
}

#[tokio::test]
async fn should_create_and_repeatedly_update_list() {
let ceramic = CeramicRemoteHttpClient::new(signer().await, ceramic_url());
let model = create_model(&ceramic).await;
let stream_id = ceramic
.create_list_instance(
&model,
&Ball {
creator: ceramic.client().signer().id().id.clone(),
radius: 1,
red: 2,
green: 3,
blue: 4,
},
)
.await
.unwrap();

for i in 0..100 {
//give anchor time to complete
tokio::time::sleep(Duration::from_millis(100)).await;

let replace_value = rand::random::<i32>() % 10i32;
let patch = json_patch::Patch(vec![json_patch::PatchOperation::Replace(
ReplaceOperation {
path: "/red".to_string(),
value: serde_json::json!(replace_value),
},
)]);
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();
assert_eq!(
post_resp.red, replace_value,
"Failed to return expected value on iteration {}",
i
);

let get_resp: Ball = ceramic.get_as(&stream_id).await.unwrap();
assert_eq!(get_resp.red, replace_value);
assert_eq!(get_resp.blue, 4);
assert_eq!(
get_resp, post_resp,
"Failed to retrieve expected value on iteration {}",
i
);
}
}
}
9 changes: 9 additions & 0 deletions src/model_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ impl ModelDefinition {
) -> anyhow::Result<Self> {
let schema = T::root_schema();
let schema = serde_json::to_value(&schema)?;
Self::new_for_value(name, account_relation, schema)
}

/// Create a new definition from schema that is already json
pub fn new_for_value(
name: &str,
account_relation: ModelAccountRelation,
schema: serde_json::Value,
) -> anyhow::Result<Self> {
Ok(Self {
version: "1.0",
name: name.to_string(),
Expand Down
14 changes: 7 additions & 7 deletions src/query.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Valid values for operation Filter
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum NumberFilter {
/// I64 Value
Expand Down Expand Up @@ -40,7 +40,7 @@ impl From<f32> for NumberFilter {
}

/// Valid values for operation Filter
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum ValueFilter {
/// String value
Expand Down Expand Up @@ -86,7 +86,7 @@ impl From<f32> for ValueFilter {
}

/// Valid values for operation Filter
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum EqualValueFilter {
/// Boolean value
Expand Down Expand Up @@ -138,7 +138,7 @@ impl From<f32> for EqualValueFilter {
}

/// Operation Filter
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum OperationFilter {
/// Filter by null or not null
Expand All @@ -162,7 +162,7 @@ pub enum OperationFilter {
}

/// Combination query
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct CombinationQuery(Vec<FilterQuery>);

impl CombinationQuery {
Expand All @@ -189,7 +189,7 @@ macro_rules! or {
}

/// Filter Query
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum FilterQuery {
/// Filter by where
#[serde(rename = "where")]
Expand Down

0 comments on commit 9b4b736

Please sign in to comment.