From 5b76cd433549c1d9ede86cddc29d10f0a1a74011 Mon Sep 17 00:00:00 2001 From: Danny Browning Date: Tue, 9 May 2023 12:35:08 -0600 Subject: [PATCH] Fix: add fmt and clippy to CI, fix ceramic-event dependency, require docs and error on warnings --- .github/workflows/rust.yml | 4 ++++ Cargo.toml | 4 ++-- README.md | 8 ++++++++ src/api.rs | 12 +++++------- src/lib.rs | 25 ++++++++++++++++++++++--- src/model_definition.rs | 13 +++++++++++++ 6 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 README.md diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 53ee30e..d35c764 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,6 +18,10 @@ jobs: - uses: actions/checkout@v3 - name: Build run: cargo build --verbose + - name: Check formatting + run: cargo fmt --all -- --check + - name: Check clippy + run: cargo clippy --workspace --all-targets --all-features -- -D warnings - name: Start Ceramic run: docker compose -f it/docker-compose.yml up -d - name: Wait for Ceramic diff --git a/Cargo.toml b/Cargo.toml index d36c869..2d16d8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,8 @@ publish = false [dependencies] anyhow = "1" -#ceramic-event = { git = "https://github.com/3box/rust-ceramic", branch = "main" } -ceramic-event = { path = "/Users/dbrowning/code/3box/rust-ceramic/event" } +ceramic-event = { git = "https://github.com/3box/rust-ceramic", branch = "feat-event-usage" } +#ceramic-event = { path = "/Users/dbrowning/code/3box/rust-ceramic/event" } json-patch = "0.3.0" reqwest = { version = "0.11.14", features = ["json"], optional = true } schemars = "0.8.12" diff --git a/README.md b/README.md new file mode 100644 index 0000000..3af8157 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# Ceramic HTTP API Client + +Ceramic [HTTP API](https://developers.ceramic.network/build/http/api/) client written in [rust](https://www.rust-lang.org/). +This library can either generate [serde](https://serde.rs/) compatible requests for use with any http client library, or make requests +against a Ceramic HTTP Api using [reqwest](https://docs.rs/reqwest/latest/reqwest/) when the `remote` feature flag is used (enabled by default). + +Please see the [tests](./src/lib.rs) for more information on how to use the library. + diff --git a/src/api.rs b/src/api.rs index e047a3d..040236a 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,4 +1,6 @@ -use ceramic_event::{Base64String, Jws, MultiBase32String, MultiBase36String, StreamId, StreamIdType}; +use ceramic_event::{ + Base64String, Jws, MultiBase32String, MultiBase36String, StreamId, StreamIdType, +}; use serde::{Deserialize, Serialize}; #[derive(Serialize)] @@ -69,9 +71,7 @@ pub struct PostResponse { #[derive(Deserialize)] #[serde(untagged)] pub enum PostResponseOrError { - Error { - error: String, - }, + Error { error: String }, Ok(PostResponse), } @@ -81,9 +81,7 @@ impl PostResponseOrError { Self::Error { error } => { anyhow::bail!(format!("{}: {}", context, error)) } - Self::Ok(resp) => { - Ok(resp) - } + Self::Ok(resp) => Ok(resp), } } } diff --git a/src/lib.rs b/src/lib.rs index 96f2311..d92402c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ //! Ceramic HTTP API //! //! This crate provides a client for interacting with the Ceramic HTTP API. +#![deny(warnings)] +#![deny(missing_docs)] mod api; mod model_definition; @@ -20,6 +22,7 @@ struct CeramicHttpClient { } impl CeramicHttpClient { + /// Create a new client, using a signer and private key pub fn new(signer: DidDocument, private_key: &str) -> Self { Self { signer, @@ -27,14 +30,17 @@ impl CeramicHttpClient { } } + /// Get the streams endpoint pub fn streams_endpoint(&self) -> &'static str { "/api/v0/streams" } + /// Get the commits endpoint pub fn commits_endpoint(&self) -> &'static str { "/api/v0/commits" } + /// Create a serde compatible request for model creation pub async fn create_model_request( &self, model: &ModelDefinition, @@ -61,6 +67,7 @@ impl CeramicHttpClient { }) } + /// Create a serde compatible request for a single instance per account creation of a model pub async fn create_single_instance_request( &self, model_id: &StreamId, @@ -88,6 +95,7 @@ impl CeramicHttpClient { }) } + /// Create a serde compatible request for a list instance per account creation of a model pub async fn create_list_instance_request( &self, model_id: &StreamId, @@ -117,6 +125,7 @@ impl CeramicHttpClient { }) } + /// Create a serde compatible request to update an existing model instance pub async fn create_update_request( &self, model: &StreamId, @@ -127,7 +136,7 @@ impl CeramicHttpClient { anyhow::bail!("StreamId was not a document"); } let tip = Cid::from_str(get.commits[0].cid.as_ref())?; - let args = EventArgs::new_with_parent(&self.signer, &model); + let args = EventArgs::new_with_parent(&self.signer, model); let commit = args.update(&patch, &self.private_key, &tip).await?; let controllers: Vec<_> = args.controllers().map(|c| c.id.clone()).collect(); let data = Base64String::from(commit.linked_block.as_ref()); @@ -151,9 +160,12 @@ impl CeramicHttpClient { } } +/// Remote HTTP Functionality +#[cfg(feature = "remote")] pub mod remote { use super::*; + /// Ceramic remote http client pub struct CeramicRemoteHttpClient { cli: CeramicHttpClient, remote: reqwest::Client, @@ -161,6 +173,7 @@ pub mod remote { } impl CeramicRemoteHttpClient { + /// Create a new ceramic remote http client for a signer, private key, and url pub fn new(signer: DidDocument, private_key: &str, remote: url::Url) -> Self { Self { cli: CeramicHttpClient::new(signer, private_key), @@ -169,11 +182,13 @@ pub mod remote { } } + /// Utility function to get a url for this client's base url, given a path pub fn url_for_path(&self, path: &str) -> anyhow::Result { let u = self.url.join(path)?; Ok(u) } + /// Create a model on the remote ceramic pub async fn create_model(&self, model: &ModelDefinition) -> anyhow::Result { let req = self.cli.create_model_request(model).await?; let resp: api::PostResponseOrError = self @@ -187,6 +202,7 @@ pub mod remote { Ok(resp.resolve("create_model")?.stream_id) } + /// Create an instance of a model that allows a single instance on the remote ceramic pub async fn create_single_instance( &self, model_id: &StreamId, @@ -203,6 +219,7 @@ pub mod remote { Ok(resp.resolve("create_single_instance")?.stream_id) } + /// Create an instance of a model allowing multiple instances on a remote ceramic pub async fn create_list_instance( &self, model_id: &StreamId, @@ -223,6 +240,7 @@ pub mod remote { Ok(resp.resolve("create_list_instance")?.stream_id) } + /// Update an instance that was previously created pub async fn update( &self, model: &StreamId, @@ -242,6 +260,7 @@ pub mod remote { res.resolve("Update failed") } + /// Get an instance of model pub async fn get(&self, stream_id: &StreamId) -> anyhow::Result { let endpoint = format!("{}/{}", self.cli.commits_endpoint(), stream_id); let endpoint = self.url_for_path(&endpoint)?; @@ -249,6 +268,7 @@ pub mod remote { Ok(resp) } + /// Get the content of an instance of a model as a serde compatible type pub async fn get_as(&self, stream_id: &StreamId) -> anyhow::Result { let mut resp = self.get(stream_id).await?; if let Some(commit) = resp.commits.pop() { @@ -261,8 +281,7 @@ pub mod remote { } } -//#[cfg(all(test, feature = "remote"))] -#[cfg(test)] +#[cfg(all(test, feature = "remote"))] pub mod tests { use super::remote::*; use super::*; diff --git a/src/model_definition.rs b/src/model_definition.rs index 258f7d4..ae55889 100644 --- a/src/model_definition.rs +++ b/src/model_definition.rs @@ -4,6 +4,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +/// Type of account relation, whether single instance per account or multiple (list) #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase", tag = "type")] pub enum ModelAccountRelation { @@ -11,6 +12,7 @@ pub enum ModelAccountRelation { Single, } +/// How a model is related, whether by account or document #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase", tag = "type")] pub enum ModelRelationDefinition { @@ -18,6 +20,7 @@ pub enum ModelRelationDefinition { Document { model: StreamId }, } +/// Describe how model views are created #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase", tag = "type")] pub enum ModelViewDefinition { @@ -28,10 +31,12 @@ pub enum ModelViewDefinition { RelationCountFrom { model: StreamId, property: String }, } +/// Schema encoded as Cbor #[derive(Debug, Deserialize, Serialize)] #[repr(transparent)] pub struct CborSchema(serde_json::Value); +/// Definition of a model for use when creating instances #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ModelDefinition { @@ -48,6 +53,7 @@ pub struct ModelDefinition { } impl ModelDefinition { + /// Create a new definition for a type that implements `GetRootSchema` pub fn new( name: &str, account_relation: ModelAccountRelation, @@ -65,28 +71,35 @@ impl ModelDefinition { }) } + /// Schema of this definition pub fn schema(&self) -> anyhow::Result { let s = serde_json::from_value(self.schema.0.clone())?; Ok(s) } + /// Apply description to this definition pub fn with_description(&mut self, description: String) -> &mut Self { self.description = Some(description); self } + /// Apply a relation to this definition pub fn with_relation(&mut self, key: String, relation: ModelRelationDefinition) -> &mut Self { self.relations.insert(key, relation); self } + /// Apply a view to this definition pub fn with_view(&mut self, key: String, view: ModelViewDefinition) -> &mut Self { self.views.insert(key, view); self } } +/// A trait which helps convert a type that implements `JsonSchema` into a `RootSchema` with +/// appropriate attributes pub trait GetRootSchema: JsonSchema { + /// Convert this object into a `RootSchema` with appropriate attributes fn root_schema() -> RootSchema { let settings = schemars::gen::SchemaSettings::default().with(|s| { s.meta_schema = Some("https://json-schema.org/draft/2020-12/schema".to_string());