From 4f47bc9ceb8394610d3d9a02b9ac3fae443668da Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Mon, 2 May 2022 15:30:06 +1200 Subject: [PATCH 01/35] Add graphgql replication endpoints --- Cargo.lock | 195 +++++++++++++++- aquadoggo/Cargo.toml | 12 +- aquadoggo/README.md | 10 + aquadoggo/src/bin/dump_gql_schema.rs | 19 ++ aquadoggo/src/context.rs | 7 +- aquadoggo/src/db/provider.rs | 3 +- aquadoggo/src/graphql/api.rs | 6 +- aquadoggo/src/graphql/context.rs | 29 +++ aquadoggo/src/graphql/mod.rs | 13 +- aquadoggo/src/graphql/ping/mod.rs | 17 ++ .../src/graphql/replication/aliased_author.rs | 10 + aquadoggo/src/graphql/replication/author.rs | 33 +++ .../src/graphql/replication/client/mod.rs | 12 + .../client/queries/get_entry_by_hash.graphql | 6 + .../graphql/replication/client/schema.graphql | 128 +++++++++++ aquadoggo/src/graphql/replication/context.rs | 208 ++++++++++++++++++ aquadoggo/src/graphql/replication/entry.rs | 21 ++ .../graphql/replication/entry_and_payload.rs | 24 ++ .../src/graphql/replication/entry_hash.rs | 21 ++ aquadoggo/src/graphql/replication/log_id.rs | 21 ++ aquadoggo/src/graphql/replication/mod.rs | 137 ++++++++++++ aquadoggo/src/graphql/replication/payload.rs | 9 + .../src/graphql/replication/public_key.rs | 9 + .../graphql/replication/sequence_number.rs | 25 +++ .../replication/single_entry_and_payload.rs | 50 +++++ aquadoggo/src/graphql/schema.rs | 35 ++- aquadoggo/src/lib.rs | 4 +- aquadoggo/src/server.rs | 6 +- 28 files changed, 1042 insertions(+), 28 deletions(-) create mode 100644 aquadoggo/src/bin/dump_gql_schema.rs create mode 100644 aquadoggo/src/graphql/context.rs create mode 100644 aquadoggo/src/graphql/ping/mod.rs create mode 100644 aquadoggo/src/graphql/replication/aliased_author.rs create mode 100644 aquadoggo/src/graphql/replication/author.rs create mode 100644 aquadoggo/src/graphql/replication/client/mod.rs create mode 100644 aquadoggo/src/graphql/replication/client/queries/get_entry_by_hash.graphql create mode 100644 aquadoggo/src/graphql/replication/client/schema.graphql create mode 100644 aquadoggo/src/graphql/replication/context.rs create mode 100644 aquadoggo/src/graphql/replication/entry.rs create mode 100644 aquadoggo/src/graphql/replication/entry_and_payload.rs create mode 100644 aquadoggo/src/graphql/replication/entry_hash.rs create mode 100644 aquadoggo/src/graphql/replication/log_id.rs create mode 100644 aquadoggo/src/graphql/replication/mod.rs create mode 100644 aquadoggo/src/graphql/replication/payload.rs create mode 100644 aquadoggo/src/graphql/replication/public_key.rs create mode 100644 aquadoggo/src/graphql/replication/sequence_number.rs create mode 100644 aquadoggo/src/graphql/replication/single_entry_and_payload.rs diff --git a/Cargo.lock b/Cargo.lock index 667532bad..7d3d75772 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,21 @@ dependencies = [ "regex", ] +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aead" version = "0.4.3" @@ -87,21 +102,25 @@ name = "aquadoggo" version = "0.2.0" dependencies = [ "anyhow", + "arrayvec 0.5.2", "async-graphql", "async-graphql-axum", "async-trait", "axum", "bamboo-rs-core-ed25519-yasmf", + "base64", "crossbeam-queue", "directories", "envy", - "futures", + "graphql_client", "hex", "http", "hyper", "jsonrpc-v2", "lipmaa-link 0.2.2", "log", + "lru", + "mockall", "openssl-probe", "p2panda-rs", "rand 0.8.5", @@ -147,6 +166,12 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "ascii" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" + [[package]] name = "ascii_utils" version = "0.9.3" @@ -504,6 +529,21 @@ dependencies = [ "mime", ] +[[package]] +name = "backtrace" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bamboo-rs-core-ed25519-yasmf" version = "0.1.1" @@ -771,6 +811,19 @@ dependencies = [ "vec_map", ] +[[package]] +name = "combine" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" +dependencies = [ + "ascii", + "byteorder", + "either", + "memchr", + "unreachable", +] + [[package]] name = "concurrent-queue" version = "1.2.2" @@ -1266,6 +1319,28 @@ dependencies = [ "fxhash", ] +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -1524,7 +1599,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.10.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1549,6 +1624,12 @@ dependencies = [ "syn", ] +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + [[package]] name = "gloo-timers" version = "0.2.4" @@ -1561,6 +1642,64 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "graphql-introspection-query" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2a4732cf5140bd6c082434494f785a19cfb566ab07d1382c3671f5812fed6d" +dependencies = [ + "serde", +] + +[[package]] +name = "graphql-parser" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5613c31f18676f164112732202124f373bb2103ff017b3b85ca954ea6a66ada" +dependencies = [ + "combine", + "failure", +] + +[[package]] +name = "graphql_client" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b58571cfc3cc42c3e8ff44fc6cfbb6c0dea17ed22d20f9d8f1efc4e8209a3f" +dependencies = [ + "graphql_query_derive", + "serde", + "serde_json", +] + +[[package]] +name = "graphql_client_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4bf9cd823359d74ad3d3ecf1afd4a975f4ff2f891cdf9a66744606daf52de8c" +dependencies = [ + "graphql-introspection-query", + "graphql-parser", + "heck", + "lazy_static", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", +] + +[[package]] +name = "graphql_query_derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56b093bfda71de1da99758b036f4cc811fd2511c8a76f75680e9ffbd2bb4251" +dependencies = [ + "graphql_client_codegen", + "proc-macro2", + "syn", +] + [[package]] name = "group" version = "0.10.0" @@ -2000,6 +2139,15 @@ dependencies = [ "value-bag", ] +[[package]] +name = "lru" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32613e41de4c47ab04970c348ca7ae7382cf116625755af070b008a15516a889" +dependencies = [ + "hashbrown", +] + [[package]] name = "maplit" version = "1.0.2" @@ -2076,6 +2224,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.2" @@ -2239,6 +2396,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.10.0" @@ -2925,6 +3091,12 @@ dependencies = [ "sha2 0.9.9", ] +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "rustc_version" version = "0.4.0" @@ -3857,6 +4029,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -3924,6 +4105,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "waker-fn" version = "1.1.0" @@ -3948,9 +4135,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasi" diff --git a/aquadoggo/Cargo.toml b/aquadoggo/Cargo.toml index b3a5f1118..10b83b3b2 100644 --- a/aquadoggo/Cargo.toml +++ b/aquadoggo/Cargo.toml @@ -15,15 +15,17 @@ edition = "2018" [dependencies] anyhow = "1.0.43" +arrayvec = "0.5.2" async-trait = "0.1.53" -async-graphql = "3.0.35" +async-graphql = "3.0.38" async-graphql-axum = "3.0.35" axum = "0.5.1" bamboo-rs-core-ed25519-yasmf = "0.1.1" +base64 = "0.13" crossbeam-queue = "0.3.5" directories = "3.0.2" envy = "0.4.2" -futures = "0.3.17" +graphql_client = "0.10" hex = "0.4.3" jsonrpc-v2 = { version = "0.10.1", features = [ "easy-errors", @@ -31,6 +33,7 @@ jsonrpc-v2 = { version = "0.10.1", features = [ ], default-features = false } lipmaa-link = "0.2.2" log = "0.4.14" +lru = "0.7.5" openssl-probe = "0.1.4" # We can not publish the `aquadoggo` crate yet, since `p2panda-rs` is an # unpublished dependency. @@ -58,12 +61,13 @@ tower-http = { version = "0.2.4", default-features = false, features = [ triggered = "0.1.2" [dev-dependencies] +hyper = "0.14.17" +http = "0.2.6" +mockall = "0.11.0" reqwest = { version = "0.11.9", default-features = false, features = [ "json", "stream", ] } rstest = "0.12.0" tower-service = "0.3.1" -hyper = "0.14.17" -http = "0.2.6" tower = "0.4.12" diff --git a/aquadoggo/README.md b/aquadoggo/README.md index 0582834f3..a0e7c04f1 100644 --- a/aquadoggo/README.md +++ b/aquadoggo/README.md @@ -76,6 +76,16 @@ $ cargo add aquadoggo [`cargo-edit`]: https://github.com/killercup/cargo-edit +## Development + +### Regenerate graphql schema + +When you update the graphql code you'll need to manually regenerate the schema files used to create the replication graphql client + +```sh +cargo run --bin dump_gql_schema > aquadoggo/src/graphql/replication/client/schema.graphql +``` + ## License GNU Affero General Public License v3.0 [`AGPL-3.0-or-later`](LICENSE) diff --git a/aquadoggo/src/bin/dump_gql_schema.rs b/aquadoggo/src/bin/dump_gql_schema.rs new file mode 100644 index 000000000..c8db7b9a4 --- /dev/null +++ b/aquadoggo/src/bin/dump_gql_schema.rs @@ -0,0 +1,19 @@ +use aquadoggo::db::provider::SqlStorage; +use aquadoggo::graphql::{PingRoot, QueryRoot, ReplicationRoot, ClientRoot}; +use async_graphql::{EmptyMutation, EmptySubscription, Schema}; + +fn main() { + let ping_root: PingRoot = Default::default(); + let replication_root = ReplicationRoot::::new(); + let client_root = Default::default(); + let query_root = QueryRoot(ping_root, replication_root, client_root); + let schema = Schema::build(query_root, EmptyMutation, EmptySubscription) + //.data(context.replication_context) + // Add more contexts here if you need, eg: + //.data(context.ping_context) + .finish(); + + let sdl = schema.sdl(); + + println!("{sdl}"); +} diff --git a/aquadoggo/src/context.rs b/aquadoggo/src/context.rs index 8f2b4a2c0..9de42fde7 100644 --- a/aquadoggo/src/context.rs +++ b/aquadoggo/src/context.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use crate::config::Configuration; use crate::db::Pool; +use crate::graphql::Context as GraphQLContext; use crate::graphql::{build_root_schema, RootSchema}; /// Inner data shared across all services. @@ -15,14 +16,16 @@ pub struct Data { /// Database connection pool. pub pool: Pool, - /// Static GraphQL schema. + /// Root GraphQL schema. pub schema: RootSchema, } impl Data { /// Initialize new data instance with shared database connection pool. pub fn new(pool: Pool, config: Configuration) -> Self { - let schema = build_root_schema(pool.clone()); + //let schema = build_root_schema(pool.clone()); + let graphql_context = GraphQLContext::new(pool.clone()); + let schema = build_root_schema(graphql_context); Self { config, diff --git a/aquadoggo/src/db/provider.rs b/aquadoggo/src/db/provider.rs index 9bf80ec58..83218afab 100644 --- a/aquadoggo/src/db/provider.rs +++ b/aquadoggo/src/db/provider.rs @@ -14,6 +14,7 @@ use crate::graphql::client::{ EntryArgsRequest, EntryArgsResponse, PublishEntryRequest, PublishEntryResponse, }; +#[derive(Debug)] pub struct SqlStorage { pub(crate) pool: Pool, } @@ -50,7 +51,7 @@ impl StorageProvider for SqlStorage { ) .bind(entry_hash.as_str()) .fetch_optional(&self.pool) - .await?; + .await.unwrap(); // Unwrap here since we validate hashes before storing them in the db. let hash = result.map(|str| { diff --git a/aquadoggo/src/graphql/api.rs b/aquadoggo/src/graphql/api.rs index 1ab22893b..f4523b8ac 100644 --- a/aquadoggo/src/graphql/api.rs +++ b/aquadoggo/src/graphql/api.rs @@ -7,10 +7,12 @@ use axum::response::{self, IntoResponse}; use crate::context::Context; -pub async fn handle_graphql_playground() -> impl IntoResponse { - response::Html(playground_source(GraphQLPlaygroundConfig::new("/"))) +/// Handle graphql playground requests at the given path +pub async fn handle_graphql_playground(path: &str) -> impl IntoResponse { + response::Html(playground_source(GraphQLPlaygroundConfig::new(path))) } +/// Handle graphql requests pub async fn handle_graphql_query( request: GraphQLRequest, Extension(context): Extension, diff --git a/aquadoggo/src/graphql/context.rs b/aquadoggo/src/graphql/context.rs new file mode 100644 index 000000000..3f99e2fa3 --- /dev/null +++ b/aquadoggo/src/graphql/context.rs @@ -0,0 +1,29 @@ +use crate::db::{provider::SqlStorage, Pool}; +use std::sync::Arc; +use tokio::sync::Mutex; + +use super::replication::context::Context as ReplicationContext; + +#[derive(Debug)] +/// The combined graphql contexts used by the various sub modules +pub struct Context { + /// The replication context + pub replication_context: Arc>>, + /// The db connection pool used by the client + pub pool: Pool, +} + +impl Context { + /// Create a new Context + pub fn new(pool: Pool) -> Self { + let storage_provider = SqlStorage { pool: pool.clone() }; + + let replication_context = + Arc::new(Mutex::new(ReplicationContext::new(1000, storage_provider))); + + Self { + replication_context, + pool + } + } +} diff --git a/aquadoggo/src/graphql/mod.rs b/aquadoggo/src/graphql/mod.rs index 8c56d6ebf..d4d8a9a84 100644 --- a/aquadoggo/src/graphql/mod.rs +++ b/aquadoggo/src/graphql/mod.rs @@ -2,7 +2,16 @@ mod api; pub mod client; -mod schema; pub use api::{handle_graphql_playground, handle_graphql_query}; -pub use schema::{build_root_schema, RootSchema}; + +mod context; +mod ping; +mod replication; +mod schema; + +pub use context::Context; +pub use ping::PingRoot; +pub use replication::ReplicationRoot; +pub use client::Query as ClientRoot; +pub use schema::{build_root_schema, QueryRoot, RootSchema}; diff --git a/aquadoggo/src/graphql/ping/mod.rs b/aquadoggo/src/graphql/ping/mod.rs new file mode 100644 index 000000000..fc712d118 --- /dev/null +++ b/aquadoggo/src/graphql/ping/mod.rs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +use std::str::FromStr; + +use async_graphql::Object; + +#[derive(Default, Debug, Copy, Clone)] +/// The root graphql object for ping +pub struct PingRoot; + +#[Object] +impl PingRoot { + // @TODO: Remove this example. + async fn ping(&self) -> String { + String::from_str("pong").unwrap() + } +} diff --git a/aquadoggo/src/graphql/replication/aliased_author.rs b/aquadoggo/src/graphql/replication/aliased_author.rs new file mode 100644 index 000000000..966177a40 --- /dev/null +++ b/aquadoggo/src/graphql/replication/aliased_author.rs @@ -0,0 +1,10 @@ +use super::public_key::PublicKey; +use async_graphql::*; + +#[derive(Debug, InputObject, SimpleObject)] +pub struct AliasedAuthor { + /// The author's public key + pub public_key: PublicKey, + /// The author alias + pub alias: ID, +} diff --git a/aquadoggo/src/graphql/replication/author.rs b/aquadoggo/src/graphql/replication/author.rs new file mode 100644 index 000000000..7f5dcbe2c --- /dev/null +++ b/aquadoggo/src/graphql/replication/author.rs @@ -0,0 +1,33 @@ +use super::public_key::PublicKey; +use anyhow::{anyhow, Error}; +use async_graphql::*; +use std::convert::TryFrom; + +/// Either the `public_key` or the `alias` of that author. +#[derive(Debug, InputObject)] +pub struct Author { + /// The author's public key + pub public_key: Option, + /// The author alias + pub alias: Option, +} + +#[derive(Debug)] +pub enum AuthorOrAlias { + PublicKey(PublicKey), + Alias(ID), +} + +impl TryFrom for AuthorOrAlias { + type Error = Error; + + fn try_from(author: Author) -> Result { + match (author.public_key, author.alias) { + (Some(key), None) => Ok(AuthorOrAlias::PublicKey(key)), + (None, Some(alias)) => Ok(AuthorOrAlias::Alias(alias)), + _ => Err(anyhow!( + "Author must have only one of public_key or alias set" + )), + } + } +} diff --git a/aquadoggo/src/graphql/replication/client/mod.rs b/aquadoggo/src/graphql/replication/client/mod.rs new file mode 100644 index 000000000..5800bf079 --- /dev/null +++ b/aquadoggo/src/graphql/replication/client/mod.rs @@ -0,0 +1,12 @@ +use super::Entry; +use super::EntryHash; +use graphql_client::GraphQLQuery; + +// The paths are relative to the directory where your `Cargo.toml` is located. +// Both json and the GraphQL schema language are supported as sources for the schema +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/replication/client/schema.graphql", + query_path = "src/graphql/replication/client/queries/get_entry_by_hash.graphql" +)] +pub struct GetEntryByHash; diff --git a/aquadoggo/src/graphql/replication/client/queries/get_entry_by_hash.graphql b/aquadoggo/src/graphql/replication/client/queries/get_entry_by_hash.graphql new file mode 100644 index 000000000..b8e510d2d --- /dev/null +++ b/aquadoggo/src/graphql/replication/client/queries/get_entry_by_hash.graphql @@ -0,0 +1,6 @@ +query GetEntryByHash($hash: EntryHash) { + entryByHash(hash: $hash){ + entry + } +} + diff --git a/aquadoggo/src/graphql/replication/client/schema.graphql b/aquadoggo/src/graphql/replication/client/schema.graphql new file mode 100644 index 000000000..6c9571fe1 --- /dev/null +++ b/aquadoggo/src/graphql/replication/client/schema.graphql @@ -0,0 +1,128 @@ +type AliasedAuthor { + """ + The author's public key + """ + publicKey: PublicKey! + """ + The author alias + """ + alias: ID! +} +""" +Either the `public_key` or the `alias` of that author. +""" +input Author { + """ + The author's public key + """ + publicKey: PublicKey + """ + The author alias + """ + alias: ID +} +scalar Entry +""" +An and entry with an optional payload +""" +type EntryAndPayload { + """ + Get the entry + """ + entry: Entry! + """ + Get the payload + """ + payload: Payload +} +type EntryAndPayloadConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + """ + A list of edges. + """ + edges: [EntryAndPayloadEdge] +} +""" +An edge in a connection. +""" +type EntryAndPayloadEdge { + """ + The item at the end of the edge + """ + node: EntryAndPayload! + """ + A cursor for use in pagination + """ + cursor: String! +} +scalar EntryHash +scalar LogId +""" +Information about pagination in a connection +""" +type PageInfo { + """ + When paginating backwards, are there more items? + """ + hasPreviousPage: Boolean! + """ + When paginating forwards, are there more items? + """ + hasNextPage: Boolean! + """ + When paginating backwards, the cursor to continue. + """ + startCursor: String + """ + When paginating forwards, the cursor to continue. + """ + endCursor: String +} +scalar Payload +scalar PublicKey +""" +All of the graphql sub modules merged into one top level root +""" +type QueryRoot { + ping: String! + """ + Get an entry by its hash + """ + entryByHash(hash: EntryHash!): SingleEntryAndPayload + """ + Get any entries that are newer than the provided sequence_number for a given author and + log_id + """ + getEntriesNewerThanSeq(logId: LogId!, sequenceNumber: SequenceNumber!, first: Int, after: String): EntryAndPayloadConnection! + """ + Get a single entry by its log_id, sequence_number and author + """ + entryByLogIdAndSequence(logId: LogId!, sequenceNumber: SequenceNumber!, author: Author!): SingleEntryAndPayload + """ + Get aliases of the provided `public_keys` that you can use in future requests to save + bandwidth. + """ + authorAliases(publicKeys: [PublicKey!]!): [AliasedAuthor!]! +} +scalar SequenceNumber +type SingleEntryAndPayload { + """ + The entry + """ + entry: Entry! + """ + The payload + """ + payload: Payload + """ + Get all the skiplinks for this entry that are required to verify the entry is valid + """ + skiplinks: [Entry!]! +} +schema { + query: QueryRoot +} + diff --git a/aquadoggo/src/graphql/replication/context.rs b/aquadoggo/src/graphql/replication/context.rs new file mode 100644 index 000000000..997672709 --- /dev/null +++ b/aquadoggo/src/graphql/replication/context.rs @@ -0,0 +1,208 @@ +use crate::db::stores::StorageEntry; + +pub use super::aliased_author::AliasedAuthor; +pub use super::public_key::PublicKey; +use super::AuthorOrAlias; +use super::Entry; +use super::EntryAndPayload; +use super::EntryHash; +use super::LogId; +use super::SequenceNumber; +use super::SingleEntryAndPayload; +use anyhow::{anyhow, Result}; +use async_graphql::ID; +use lru::LruCache; +use p2panda_rs::entry::SeqNum; +use p2panda_rs::storage_provider::traits::EntryStore; + +#[derive(Debug)] +pub struct Context> { + author_aliases: LruCache, + next_alias: usize, + entry_store: ES, +} + +impl> Context { + pub fn new(author_aliases_cache_size: usize, entry_store: ES) -> Self { + Self { + author_aliases: LruCache::new(author_aliases_cache_size), + next_alias: Default::default(), + entry_store, + } + } + + pub fn insert_author_aliases(&mut self, public_keys: Vec) -> Vec { + public_keys + .into_iter() + .map(|public_key| { + self.next_alias += 1; + self.author_aliases + .put(ID(self.next_alias.to_string()), public_key.clone()); + AliasedAuthor { + public_key, + alias: self.next_alias.into(), + } + }) + .collect() + } + + pub fn author_aliases_to_public_keys( + &mut self, + ids: Vec, + ) -> Result>> { + ids.into_iter() + .map(|id| { + let result = self.author_aliases.get(&id).map(|key| key.clone()); + Ok(result) + }) + .collect() + } + + pub async fn entry_by_log_id_and_sequence<'a>( + &mut self, + log_id: LogId, + sequence_number: SequenceNumber, + author_alias: AuthorOrAlias, + ) -> Result> { + let author = self.get_author(author_alias)?; + + let result = self + .entry_store + .get_entry_at_seq_num(&author.0, &log_id.0, &sequence_number.0) + .await? + .map(|entry| entry.into()); + + Ok(result) + } + + pub async fn entry_by_hash<'a>( + &mut self, + hash: EntryHash, + ) -> Result> { + let result = self + .entry_store + .get_entry_by_hash(&hash.into()) + .await? + .map(|entry_row| entry_row.into()); + + Ok(result) + } + + pub async fn get_skiplinks<'a>(&mut self, entry: &Entry) -> Result> { + let entry = entry.as_ref(); + todo!(); + //let result = self.entry_store + // .get_all_lipmaa_entries_for_entry(&entry.author(), &entry.log_id() ) + //todo!() + } + + pub async fn get_entries_newer_than_seq( + &mut self, + log_id: LogId, + author: AuthorOrAlias, + sequence_number: SequenceNumber, + first: usize, + after: u64, + ) -> Result> { + let author = self.get_author(author)?; + let seq_num = SeqNum::new(sequence_number.as_ref().as_u64() + after)?; + let result = self + .entry_store + .get_paginated_log_entries(&author.0, &log_id.0, &seq_num, first) + .await? + .into_iter() + .map(|entry| entry.into()) + .collect(); + + Ok(result) + } + + fn get_author(&mut self, author_alias: AuthorOrAlias) -> Result { + let author = match author_alias { + AuthorOrAlias::Alias(alias) => self + .author_aliases + .get(&alias) + .ok_or(anyhow!( + "author alias did not exist, you may need to re-alias your authors" + ))? + .clone(), + AuthorOrAlias::PublicKey(public_key) => public_key.clone(), + }; + Ok(author) + } +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU64; + + use super::Context; + use crate::db::models::EntryRow; + use crate::db::stores::StorageEntry; + use crate::graphql::replication::{ + Author as GraphQLAuthor, LogId as GraphQLLogId, PublicKey, SequenceNumber, ID, + }; + use async_trait::async_trait; + use mockall::mock; + use p2panda_rs::entry::{LogId, SeqNum}; + use p2panda_rs::identity::Author; + use p2panda_rs::schema::SchemaId; + use p2panda_rs::storage_provider::errors::EntryStorageError; + use p2panda_rs::storage_provider::traits::EntryStore; + use std::convert::TryInto; + +// #[tokio::test] +// async fn entry_by_log_id_and_sequence() { +// let expected_log_id = 123; +// let expected_seq_num = 345u64; +// let expected_author_id = 987u64; +// let expected_author_string = +// "7cf4f58a2d89e93313f2de99604a814ecea9800cf217b140e9c3a7ba59a5d982".to_string(); +// +// let log_id: GraphQLLogId = expected_log_id.into(); +// let sequence_number: SequenceNumber = expected_seq_num.try_into().unwrap(); +// let author = Author::new(&expected_author_string).unwrap(); +// let author_id = GraphQLAuthor { +// alias: None, +// public_key: Some(PublicKey(author)), +// }; +// +// mock! { +// pub MockEntryStore {} +// #[async_trait] +// impl EntryStore for MockEntryStore { +// async fn insert_entry(&self, _value: StorageEntry) -> Result; +// +// async fn get_entry_at_seq_num( +// &self, +// author: &Author, +// log_id: &LogId, +// seq_num: &SeqNum, +// ) -> Result, EntryStorageError>; +// +// async fn latest_entry( +// &self, +// _author: &Author, +// _log_id: &LogId, +// ) -> Result, EntryStorageError>; +// +// } +// } +// +// let mut mock_entry_store = MockMockEntryStore::new(); +// mock_entry_store +// .expect_entry_at_seq_num() +// .withf(move |author, log_id, seq_num| author.as_str() == expected_author_string) +// .times(1) +// .returning(|_, _, _| Ok(None)); +// +// let mut context = Context::new(1, mock_entry_store); +// +// let result = context +// .entry_by_log_id_and_sequence(log_id, sequence_number, author_id.try_into().unwrap()) +// .await; +// +// println!("{:?}", result); +// assert!(result.is_ok()); +// } +} diff --git a/aquadoggo/src/graphql/replication/entry.rs b/aquadoggo/src/graphql/replication/entry.rs new file mode 100644 index 000000000..4d90589fd --- /dev/null +++ b/aquadoggo/src/graphql/replication/entry.rs @@ -0,0 +1,21 @@ +use async_graphql::*; +use p2panda_rs::entry::EntrySigned as PandaEntry; +use serde::{Deserialize, Serialize}; + +/// A p2panda_rs entry encoded as a String +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Entry(pub PandaEntry); + +impl AsRef for Entry { + fn as_ref(&self) -> &PandaEntry { + &self.0 + } +} + +scalar!(Entry); + +impl From for Value { + fn from(entry: Entry) -> Self { + async_graphql::ScalarType::to_value(&entry) + } +} diff --git a/aquadoggo/src/graphql/replication/entry_and_payload.rs b/aquadoggo/src/graphql/replication/entry_and_payload.rs new file mode 100644 index 000000000..bc71856c2 --- /dev/null +++ b/aquadoggo/src/graphql/replication/entry_and_payload.rs @@ -0,0 +1,24 @@ +use crate::db::stores::StorageEntry; + +use super::payload::Payload; +use super::Entry; +use async_graphql::*; + +/// An and entry with an optional payload +#[derive(SimpleObject, Debug)] +pub struct EntryAndPayload { + /// Get the entry + pub entry: Entry, + /// Get the payload + pub payload: Option, +} + +impl From for EntryAndPayload { + fn from(entry_row: StorageEntry) -> Self { + let entry = Entry(entry_row.entry_signed().to_owned()); + let payload = entry_row + .operation_encoded() + .map(|encoded| Payload(encoded.to_owned())); + Self { entry, payload } + } +} diff --git a/aquadoggo/src/graphql/replication/entry_hash.rs b/aquadoggo/src/graphql/replication/entry_hash.rs new file mode 100644 index 000000000..aca657341 --- /dev/null +++ b/aquadoggo/src/graphql/replication/entry_hash.rs @@ -0,0 +1,21 @@ +use async_graphql::*; +use p2panda_rs::hash::Hash; +use serde::{Deserialize, Serialize}; + +/// The p2panda_rs hash of an entry +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct EntryHash(Hash); + +impl From for Hash { + fn from(e: EntryHash) -> Self { + e.0 + } +} + +scalar!(EntryHash); + +impl From for Value { + fn from(entry: EntryHash) -> Self { + async_graphql::ScalarType::to_value(&entry) + } +} diff --git a/aquadoggo/src/graphql/replication/log_id.rs b/aquadoggo/src/graphql/replication/log_id.rs new file mode 100644 index 000000000..2912304ae --- /dev/null +++ b/aquadoggo/src/graphql/replication/log_id.rs @@ -0,0 +1,21 @@ +use async_graphql::*; +use p2panda_rs::entry::LogId as PandaLogId; +use serde::{Deserialize, Serialize}; + +/// The log id of a bamboo entry +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct LogId(pub PandaLogId); + +impl From for LogId { + fn from(n: u64) -> Self { + Self(PandaLogId::new(n)) + } +} + +impl AsRef for LogId { + fn as_ref(&self) -> &PandaLogId { + &self.0 + } +} + +scalar!(LogId); diff --git a/aquadoggo/src/graphql/replication/mod.rs b/aquadoggo/src/graphql/replication/mod.rs new file mode 100644 index 000000000..7d27d30c6 --- /dev/null +++ b/aquadoggo/src/graphql/replication/mod.rs @@ -0,0 +1,137 @@ +use std::convert::TryInto; +use std::marker::PhantomData; + +use async_graphql::connection::{query, Connection, Edge, EmptyFields}; +use async_graphql::Object; +use async_graphql::*; +use std::sync::Arc; +use tokio::sync::Mutex; + +pub mod aliased_author; +pub mod author; +pub mod context; +pub mod entry; +pub mod entry_and_payload; +pub mod entry_hash; +pub mod log_id; +pub mod payload; +pub mod public_key; +pub mod sequence_number; +pub mod single_entry_and_payload; + +use crate::db::stores::StorageEntry; +pub use aliased_author::AliasedAuthor; +pub use author::{Author, AuthorOrAlias}; +pub use context::Context as ReplicationContext; +pub use entry::Entry; +pub use entry_and_payload::EntryAndPayload; +pub use entry_hash::EntryHash; +pub use log_id::LogId; +use p2panda_rs::storage_provider::traits::EntryStore; +pub use payload::Payload; +pub use public_key::PublicKey; +pub use sequence_number::SequenceNumber; +pub use single_entry_and_payload::SingleEntryAndPayload; + +pub mod client; + +#[derive(Debug)] +/// The root graphql object for replication +pub struct ReplicationRoot { + entry_store: PhantomData, +} + +impl ReplicationRoot { + /// Create a new ReplicationRoot + pub fn new() -> Self { + Self { + entry_store: PhantomData::default(), + } + } +} + +#[Object] +impl + Sync + Send> ReplicationRoot { + /// Get an entry by its hash + async fn entry_by_hash<'a>( + &self, + ctx: &Context<'a>, + hash: EntryHash, + ) -> Result> { + let ctx: &Arc>> = ctx.data()?; + + let result = ctx.lock().await.entry_by_hash(hash).await?; + + Ok(result) + } + + /// Get any entries that are newer than the provided sequence_number for a given author and + /// log_id + async fn get_entries_newer_than_seq<'a>( + &self, + ctx: &Context<'a>, + log_id: LogId, + author: Author, + sequence_number: SequenceNumber, + first: Option, + after: Option, + ) -> Result> { + let ctx: &Arc>> = ctx.data()?; + let author: AuthorOrAlias = author.try_into()?; + query(after, None, first, None, |after, _, first, _| async move { + let start = + sequence_number.as_ref().as_u64() + after.map(|a| a as u64 + 1).unwrap_or(0); + // TODO: clamp an upper limit here + let first = first.unwrap_or(10); + + let edges = ctx + .lock() + .await + .get_entries_newer_than_seq(log_id, author, sequence_number, first, start) + .await? + .into_iter() + // FIXME: need to create a cursor from the entry here, not pass 0 + .map(|entry| Edge::new(0usize, entry.into())); + + let mut connection = Connection::new(false, start < first as u64); + + connection.append(edges); + + Result::<_, Error>::Ok(connection) + }) + .await + } + + /// Get a single entry by its log_id, sequence_number and author + async fn entry_by_log_id_and_sequence<'a>( + &self, + ctx: &Context<'a>, + log_id: LogId, + sequence_number: SequenceNumber, + author: Author, + ) -> Result> { + let ctx: &Arc>> = ctx.data()?; + let author: AuthorOrAlias = author.try_into()?; + let result = ctx + .lock() + .await + .entry_by_log_id_and_sequence(log_id, sequence_number, author) + .await?; + + Ok(result) + } + + /// Get aliases of the provided `public_keys` that you can use in future requests to save + /// bandwidth. + // Maybe this should be a mutation + async fn author_aliases<'a>( + &self, + ctx: &Context<'a>, + public_keys: Vec, + ) -> Result> { + let ctx: &Arc>> = ctx.data()?; + let result = ctx.lock().await.insert_author_aliases(public_keys); + + Ok(result) + } +} diff --git a/aquadoggo/src/graphql/replication/payload.rs b/aquadoggo/src/graphql/replication/payload.rs new file mode 100644 index 000000000..c66615080 --- /dev/null +++ b/aquadoggo/src/graphql/replication/payload.rs @@ -0,0 +1,9 @@ +use async_graphql::*; +use p2panda_rs::operation::OperationEncoded; +use serde::{Deserialize, Serialize}; + +/// The payload of an entry +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Payload(pub OperationEncoded); + +scalar!(Payload); diff --git a/aquadoggo/src/graphql/replication/public_key.rs b/aquadoggo/src/graphql/replication/public_key.rs new file mode 100644 index 000000000..2d57956ff --- /dev/null +++ b/aquadoggo/src/graphql/replication/public_key.rs @@ -0,0 +1,9 @@ +use async_graphql::*; +use p2panda_rs::identity::Author; +use serde::{Deserialize, Serialize}; + +/// The public key of an entry +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PublicKey(pub Author); + +scalar!(PublicKey); diff --git a/aquadoggo/src/graphql/replication/sequence_number.rs b/aquadoggo/src/graphql/replication/sequence_number.rs new file mode 100644 index 000000000..4e96793ee --- /dev/null +++ b/aquadoggo/src/graphql/replication/sequence_number.rs @@ -0,0 +1,25 @@ +use async_graphql::*; +use p2panda_rs::entry::{SeqNum as PandaSeqNum, SeqNumError}; +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; + +/// The sequence number of an entry +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct SequenceNumber(pub PandaSeqNum); + +impl AsRef for SequenceNumber { + fn as_ref(&self) -> &PandaSeqNum { + &self.0 + } +} + +impl TryFrom for SequenceNumber { + type Error = SeqNumError; + + fn try_from(value: u64) -> Result { + let seq_num = PandaSeqNum::new(value)?; + Ok(Self(seq_num)) + } +} + +scalar!(SequenceNumber); diff --git a/aquadoggo/src/graphql/replication/single_entry_and_payload.rs b/aquadoggo/src/graphql/replication/single_entry_and_payload.rs new file mode 100644 index 000000000..3d2cdc3cb --- /dev/null +++ b/aquadoggo/src/graphql/replication/single_entry_and_payload.rs @@ -0,0 +1,50 @@ +use crate::db::stores::StorageEntry; + +use super::payload::Payload; +use super::Entry; +use crate::graphql::Context as GraphQLContext; +use async_graphql::*; + +/// A p2panda entry with optional payload and the collection of skiplinks required to verify it. +#[derive(Debug)] +pub struct SingleEntryAndPayload { + pub entry: Entry, + pub payload: Option, +} + +#[Object] +impl SingleEntryAndPayload { + /// The entry + async fn entry(&self) -> &Entry { + &self.entry + } + + /// The payload + async fn payload(&self) -> Option<&Payload> { + self.payload.as_ref() + } + + /// Get all the skiplinks for this entry that are required to verify the entry is valid + async fn skiplinks<'a>(&self, ctx: &Context<'a>) -> Result> { + let ctx: &GraphQLContext = ctx.data()?; + + let result = ctx + .replication_context + .lock() + .await + .get_skiplinks(&self.entry) + .await?; + + Ok(result) + } +} + +impl From for SingleEntryAndPayload { + fn from(entry_row: StorageEntry) -> Self { + let entry = Entry(entry_row.entry_signed().to_owned()); + let payload = entry_row + .operation_encoded() + .map(|encoded| Payload(encoded.to_owned())); + Self { entry, payload } + } +} diff --git a/aquadoggo/src/graphql/schema.rs b/aquadoggo/src/graphql/schema.rs index ec2392baf..74d7f6611 100644 --- a/aquadoggo/src/graphql/schema.rs +++ b/aquadoggo/src/graphql/schema.rs @@ -1,18 +1,35 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -use async_graphql::{EmptySubscription, Schema}; +use super::ping::PingRoot; +use super::replication::ReplicationRoot; +use super::client::{Query as ClientQueryRoot, Mutation as ClientMutationRoot }; +use super::Context; +use crate::db::provider::SqlStorage; +use async_graphql::{EmptySubscription, MergedObject, Schema}; -use crate::db::Pool; -use crate::graphql::client::{Mutation, Query}; +/// All of the graphql sub modules merged into one top level root +#[derive(MergedObject, Debug)] +pub struct QueryRoot(pub PingRoot, pub ReplicationRoot, pub ClientQueryRoot); + +#[derive(MergedObject, Debug, Copy, Clone)] +pub struct MutationRoot(pub ClientMutationRoot); /// GraphQL schema for p2panda node. -pub type RootSchema = Schema; +pub type RootSchema = Schema; -pub fn build_root_schema(pool: Pool) -> RootSchema { - let query = Query::default(); - let mutation = Mutation::default(); +/// Build the root graphql schema that can handle graphql requests. +pub fn build_root_schema(context: Context) -> RootSchema { + let ping_root: PingRoot = Default::default(); + let replication_root = ReplicationRoot::::new(); + let client_query_root = ClientQueryRoot::default(); + let query_root = QueryRoot(ping_root, replication_root, client_query_root); - Schema::build(query, mutation, EmptySubscription) - .data(pool) + let client_mutation_root = Default::default(); + let mutation_root = MutationRoot(client_mutation_root); + Schema::build(query_root, mutation_root, EmptySubscription) + .data(context.replication_context) + .data(context.pool) + // Add more contexts here if you need, eg: + //.data(context.ping_context) .finish() } diff --git a/aquadoggo/src/lib.rs b/aquadoggo/src/lib.rs index 718709da8..b8fa1e3ec 100644 --- a/aquadoggo/src/lib.rs +++ b/aquadoggo/src/lib.rs @@ -16,9 +16,9 @@ mod bus; mod config; mod context; -mod db; +pub mod db; mod errors; -mod graphql; +pub mod graphql; mod manager; mod materializer; mod node; diff --git a/aquadoggo/src/server.rs b/aquadoggo/src/server.rs index e96a7feb1..10e4ccb8c 100644 --- a/aquadoggo/src/server.rs +++ b/aquadoggo/src/server.rs @@ -14,6 +14,8 @@ use crate::context::Context; use crate::graphql::{handle_graphql_playground, handle_graphql_query}; use crate::manager::Shutdown; +const GRAPHQL_ROUTE: &str = "/graphql"; + /// Build HTTP server with GraphQL API. pub fn build_server(context: Context) -> Router { // Configure CORS middleware @@ -25,8 +27,8 @@ pub fn build_server(context: Context) -> Router { Router::new() // Add GraphQL routes .route( - "/graphql", - get(handle_graphql_playground).post(handle_graphql_query), + GRAPHQL_ROUTE, + get(|| handle_graphql_playground(GRAPHQL_ROUTE)).post(handle_graphql_query), ) // Add middlewares .layer(cors) From 779c02f59c28ae322f8ebcb17416dbc5ce2e86e8 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Mon, 23 May 2022 20:45:25 +1200 Subject: [PATCH 02/35] update gql schema --- .../src/graphql/replication/client/schema.graphql | 14 ++++++++++++-- aquadoggo/src/graphql/schema.rs | 11 ++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/aquadoggo/src/graphql/replication/client/schema.graphql b/aquadoggo/src/graphql/replication/client/schema.graphql index 6c9571fe1..429a4d6dd 100644 --- a/aquadoggo/src/graphql/replication/client/schema.graphql +++ b/aquadoggo/src/graphql/replication/client/schema.graphql @@ -58,6 +58,12 @@ type EntryAndPayloadEdge { """ cursor: String! } +type EntryArgsResponse { + logId: String! + seqNum: String! + backlink: String + skiplink: String +} scalar EntryHash scalar LogId """ @@ -84,7 +90,7 @@ type PageInfo { scalar Payload scalar PublicKey """ -All of the graphql sub modules merged into one top level root +All of the graphql query sub modules merged into one top level root """ type QueryRoot { ping: String! @@ -96,7 +102,7 @@ type QueryRoot { Get any entries that are newer than the provided sequence_number for a given author and log_id """ - getEntriesNewerThanSeq(logId: LogId!, sequenceNumber: SequenceNumber!, first: Int, after: String): EntryAndPayloadConnection! + getEntriesNewerThanSeq(logId: LogId!, author: Author!, sequenceNumber: SequenceNumber!, first: Int, after: String): EntryAndPayloadConnection! """ Get a single entry by its log_id, sequence_number and author """ @@ -106,6 +112,10 @@ type QueryRoot { bandwidth. """ authorAliases(publicKeys: [PublicKey!]!): [AliasedAuthor!]! + """ + Return required arguments for publishing the next entry. + """ + nextEntryArgs(publicKey: String!, documentId: String): EntryArgsResponse! } scalar SequenceNumber type SingleEntryAndPayload { diff --git a/aquadoggo/src/graphql/schema.rs b/aquadoggo/src/graphql/schema.rs index 74d7f6611..32610f24c 100644 --- a/aquadoggo/src/graphql/schema.rs +++ b/aquadoggo/src/graphql/schema.rs @@ -1,16 +1,21 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +use super::client::{Mutation as ClientMutationRoot, Query as ClientQueryRoot}; use super::ping::PingRoot; use super::replication::ReplicationRoot; -use super::client::{Query as ClientQueryRoot, Mutation as ClientMutationRoot }; use super::Context; use crate::db::provider::SqlStorage; use async_graphql::{EmptySubscription, MergedObject, Schema}; -/// All of the graphql sub modules merged into one top level root +/// All of the graphql query sub modules merged into one top level root #[derive(MergedObject, Debug)] -pub struct QueryRoot(pub PingRoot, pub ReplicationRoot, pub ClientQueryRoot); +pub struct QueryRoot( + pub PingRoot, + pub ReplicationRoot, + pub ClientQueryRoot, +); +/// All of the graphql mutation sub modules merged into one top level root #[derive(MergedObject, Debug, Copy, Clone)] pub struct MutationRoot(pub ClientMutationRoot); From 29019335939b9591967e8edbdaf36c3d3c0d2986 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 24 May 2022 17:42:01 +1200 Subject: [PATCH 03/35] Add licence headers --- aquadoggo/src/graphql/context.rs | 2 ++ aquadoggo/src/graphql/replication/aliased_author.rs | 2 ++ aquadoggo/src/graphql/replication/author.rs | 2 ++ aquadoggo/src/graphql/replication/client/mod.rs | 2 ++ aquadoggo/src/graphql/replication/context.rs | 2 ++ aquadoggo/src/graphql/replication/entry.rs | 2 ++ aquadoggo/src/graphql/replication/entry_and_payload.rs | 2 ++ aquadoggo/src/graphql/replication/entry_hash.rs | 2 ++ aquadoggo/src/graphql/replication/log_id.rs | 2 ++ aquadoggo/src/graphql/replication/mod.rs | 2 ++ aquadoggo/src/graphql/replication/payload.rs | 2 ++ aquadoggo/src/graphql/replication/public_key.rs | 2 ++ aquadoggo/src/graphql/replication/sequence_number.rs | 2 ++ aquadoggo/src/graphql/replication/single_entry_and_payload.rs | 2 ++ 14 files changed, 28 insertions(+) diff --git a/aquadoggo/src/graphql/context.rs b/aquadoggo/src/graphql/context.rs index 3f99e2fa3..7af95dbf2 100644 --- a/aquadoggo/src/graphql/context.rs +++ b/aquadoggo/src/graphql/context.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use crate::db::{provider::SqlStorage, Pool}; use std::sync::Arc; use tokio::sync::Mutex; diff --git a/aquadoggo/src/graphql/replication/aliased_author.rs b/aquadoggo/src/graphql/replication/aliased_author.rs index 966177a40..d4457d24a 100644 --- a/aquadoggo/src/graphql/replication/aliased_author.rs +++ b/aquadoggo/src/graphql/replication/aliased_author.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use super::public_key::PublicKey; use async_graphql::*; diff --git a/aquadoggo/src/graphql/replication/author.rs b/aquadoggo/src/graphql/replication/author.rs index 7f5dcbe2c..d86de72b4 100644 --- a/aquadoggo/src/graphql/replication/author.rs +++ b/aquadoggo/src/graphql/replication/author.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use super::public_key::PublicKey; use anyhow::{anyhow, Error}; use async_graphql::*; diff --git a/aquadoggo/src/graphql/replication/client/mod.rs b/aquadoggo/src/graphql/replication/client/mod.rs index 5800bf079..8a4398dcc 100644 --- a/aquadoggo/src/graphql/replication/client/mod.rs +++ b/aquadoggo/src/graphql/replication/client/mod.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use super::Entry; use super::EntryHash; use graphql_client::GraphQLQuery; diff --git a/aquadoggo/src/graphql/replication/context.rs b/aquadoggo/src/graphql/replication/context.rs index 997672709..510bc7461 100644 --- a/aquadoggo/src/graphql/replication/context.rs +++ b/aquadoggo/src/graphql/replication/context.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use crate::db::stores::StorageEntry; pub use super::aliased_author::AliasedAuthor; diff --git a/aquadoggo/src/graphql/replication/entry.rs b/aquadoggo/src/graphql/replication/entry.rs index 4d90589fd..82bb1be04 100644 --- a/aquadoggo/src/graphql/replication/entry.rs +++ b/aquadoggo/src/graphql/replication/entry.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use async_graphql::*; use p2panda_rs::entry::EntrySigned as PandaEntry; use serde::{Deserialize, Serialize}; diff --git a/aquadoggo/src/graphql/replication/entry_and_payload.rs b/aquadoggo/src/graphql/replication/entry_and_payload.rs index bc71856c2..158b76921 100644 --- a/aquadoggo/src/graphql/replication/entry_and_payload.rs +++ b/aquadoggo/src/graphql/replication/entry_and_payload.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use crate::db::stores::StorageEntry; use super::payload::Payload; diff --git a/aquadoggo/src/graphql/replication/entry_hash.rs b/aquadoggo/src/graphql/replication/entry_hash.rs index aca657341..2fc1f6dec 100644 --- a/aquadoggo/src/graphql/replication/entry_hash.rs +++ b/aquadoggo/src/graphql/replication/entry_hash.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use async_graphql::*; use p2panda_rs::hash::Hash; use serde::{Deserialize, Serialize}; diff --git a/aquadoggo/src/graphql/replication/log_id.rs b/aquadoggo/src/graphql/replication/log_id.rs index 2912304ae..f4d52a8e5 100644 --- a/aquadoggo/src/graphql/replication/log_id.rs +++ b/aquadoggo/src/graphql/replication/log_id.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use async_graphql::*; use p2panda_rs::entry::LogId as PandaLogId; use serde::{Deserialize, Serialize}; diff --git a/aquadoggo/src/graphql/replication/mod.rs b/aquadoggo/src/graphql/replication/mod.rs index 7d27d30c6..0a7022199 100644 --- a/aquadoggo/src/graphql/replication/mod.rs +++ b/aquadoggo/src/graphql/replication/mod.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use std::convert::TryInto; use std::marker::PhantomData; diff --git a/aquadoggo/src/graphql/replication/payload.rs b/aquadoggo/src/graphql/replication/payload.rs index c66615080..0e31f8ca9 100644 --- a/aquadoggo/src/graphql/replication/payload.rs +++ b/aquadoggo/src/graphql/replication/payload.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use async_graphql::*; use p2panda_rs::operation::OperationEncoded; use serde::{Deserialize, Serialize}; diff --git a/aquadoggo/src/graphql/replication/public_key.rs b/aquadoggo/src/graphql/replication/public_key.rs index 2d57956ff..78aabf77b 100644 --- a/aquadoggo/src/graphql/replication/public_key.rs +++ b/aquadoggo/src/graphql/replication/public_key.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use async_graphql::*; use p2panda_rs::identity::Author; use serde::{Deserialize, Serialize}; diff --git a/aquadoggo/src/graphql/replication/sequence_number.rs b/aquadoggo/src/graphql/replication/sequence_number.rs index 4e96793ee..8a23d3590 100644 --- a/aquadoggo/src/graphql/replication/sequence_number.rs +++ b/aquadoggo/src/graphql/replication/sequence_number.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use async_graphql::*; use p2panda_rs::entry::{SeqNum as PandaSeqNum, SeqNumError}; use serde::{Deserialize, Serialize}; diff --git a/aquadoggo/src/graphql/replication/single_entry_and_payload.rs b/aquadoggo/src/graphql/replication/single_entry_and_payload.rs index 3d2cdc3cb..b46eb6c43 100644 --- a/aquadoggo/src/graphql/replication/single_entry_and_payload.rs +++ b/aquadoggo/src/graphql/replication/single_entry_and_payload.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use crate::db::stores::StorageEntry; use super::payload::Payload; From 04abc7662bc0792da2c7e82c8d0224851a234aa4 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 24 May 2022 18:58:34 +1200 Subject: [PATCH 04/35] Refactor `dump_gql_schema` and regenerate schema.graphql --- aquadoggo/src/bin/dump_gql_schema.rs | 21 +++++++------------ .../graphql/replication/client/schema.graphql | 18 ++++++++++++++++ aquadoggo/src/graphql/schema.rs | 2 +- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/aquadoggo/src/bin/dump_gql_schema.rs b/aquadoggo/src/bin/dump_gql_schema.rs index c8db7b9a4..5bf1409f5 100644 --- a/aquadoggo/src/bin/dump_gql_schema.rs +++ b/aquadoggo/src/bin/dump_gql_schema.rs @@ -1,18 +1,11 @@ -use aquadoggo::db::provider::SqlStorage; -use aquadoggo::graphql::{PingRoot, QueryRoot, ReplicationRoot, ClientRoot}; -use async_graphql::{EmptyMutation, EmptySubscription, Schema}; - -fn main() { - let ping_root: PingRoot = Default::default(); - let replication_root = ReplicationRoot::::new(); - let client_root = Default::default(); - let query_root = QueryRoot(ping_root, replication_root, client_root); - let schema = Schema::build(query_root, EmptyMutation, EmptySubscription) - //.data(context.replication_context) - // Add more contexts here if you need, eg: - //.data(context.ping_context) - .finish(); +use aquadoggo::db::connection_pool; +use aquadoggo::graphql::{build_root_schema, Context}; +#[tokio::main] +async fn main() { + let pool = connection_pool("sqlite::memory:", 1).await.unwrap(); + let context = Context::new(pool); + let schema = build_root_schema(context); let sdl = schema.sdl(); println!("{sdl}"); diff --git a/aquadoggo/src/graphql/replication/client/schema.graphql b/aquadoggo/src/graphql/replication/client/schema.graphql index 429a4d6dd..1fd79220e 100644 --- a/aquadoggo/src/graphql/replication/client/schema.graphql +++ b/aquadoggo/src/graphql/replication/client/schema.graphql @@ -67,6 +67,17 @@ type EntryArgsResponse { scalar EntryHash scalar LogId """ +All of the graphql mutation sub modules merged into one top level root +""" +type MutationRoot { + """ + Publish an entry using parameters obtained through `nextEntryArgs` query. + + Returns arguments for publishing the next entry in the same log. + """ + publishEntry(entryEncoded: String!, operationEncoded: String!): PublishEntryResponse! +} +""" Information about pagination in a connection """ type PageInfo { @@ -89,6 +100,12 @@ type PageInfo { } scalar Payload scalar PublicKey +type PublishEntryResponse { + logId: String! + seqNum: String! + backlink: String + skiplink: String +} """ All of the graphql query sub modules merged into one top level root """ @@ -134,5 +151,6 @@ type SingleEntryAndPayload { } schema { query: QueryRoot + mutation: MutationRoot } diff --git a/aquadoggo/src/graphql/schema.rs b/aquadoggo/src/graphql/schema.rs index 32610f24c..fc1d58d07 100644 --- a/aquadoggo/src/graphql/schema.rs +++ b/aquadoggo/src/graphql/schema.rs @@ -16,7 +16,7 @@ pub struct QueryRoot( ); /// All of the graphql mutation sub modules merged into one top level root -#[derive(MergedObject, Debug, Copy, Clone)] +#[derive(MergedObject, Debug, Copy, Clone, Default)] pub struct MutationRoot(pub ClientMutationRoot); /// GraphQL schema for p2panda node. From 9a054d83b43ddc4a21a4dd3340470119455fc733 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 24 May 2022 19:07:36 +1200 Subject: [PATCH 05/35] Fix stuff I found in my review --- aquadoggo/src/context.rs | 1 - aquadoggo/src/db/provider.rs | 2 +- aquadoggo/src/graphql/mod.rs | 2 -- aquadoggo/src/graphql/ping/mod.rs | 17 ----------------- aquadoggo/src/graphql/schema.rs | 5 +---- 5 files changed, 2 insertions(+), 25 deletions(-) delete mode 100644 aquadoggo/src/graphql/ping/mod.rs diff --git a/aquadoggo/src/context.rs b/aquadoggo/src/context.rs index 9de42fde7..4c5795584 100644 --- a/aquadoggo/src/context.rs +++ b/aquadoggo/src/context.rs @@ -23,7 +23,6 @@ pub struct Data { impl Data { /// Initialize new data instance with shared database connection pool. pub fn new(pool: Pool, config: Configuration) -> Self { - //let schema = build_root_schema(pool.clone()); let graphql_context = GraphQLContext::new(pool.clone()); let schema = build_root_schema(graphql_context); diff --git a/aquadoggo/src/db/provider.rs b/aquadoggo/src/db/provider.rs index 83218afab..e86d5df9a 100644 --- a/aquadoggo/src/db/provider.rs +++ b/aquadoggo/src/db/provider.rs @@ -51,7 +51,7 @@ impl StorageProvider for SqlStorage { ) .bind(entry_hash.as_str()) .fetch_optional(&self.pool) - .await.unwrap(); + .await?; // Unwrap here since we validate hashes before storing them in the db. let hash = result.map(|str| { diff --git a/aquadoggo/src/graphql/mod.rs b/aquadoggo/src/graphql/mod.rs index d4d8a9a84..dc215317e 100644 --- a/aquadoggo/src/graphql/mod.rs +++ b/aquadoggo/src/graphql/mod.rs @@ -6,12 +6,10 @@ pub mod client; pub use api::{handle_graphql_playground, handle_graphql_query}; mod context; -mod ping; mod replication; mod schema; pub use context::Context; -pub use ping::PingRoot; pub use replication::ReplicationRoot; pub use client::Query as ClientRoot; pub use schema::{build_root_schema, QueryRoot, RootSchema}; diff --git a/aquadoggo/src/graphql/ping/mod.rs b/aquadoggo/src/graphql/ping/mod.rs deleted file mode 100644 index fc712d118..000000000 --- a/aquadoggo/src/graphql/ping/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -use std::str::FromStr; - -use async_graphql::Object; - -#[derive(Default, Debug, Copy, Clone)] -/// The root graphql object for ping -pub struct PingRoot; - -#[Object] -impl PingRoot { - // @TODO: Remove this example. - async fn ping(&self) -> String { - String::from_str("pong").unwrap() - } -} diff --git a/aquadoggo/src/graphql/schema.rs b/aquadoggo/src/graphql/schema.rs index fc1d58d07..97fb135ac 100644 --- a/aquadoggo/src/graphql/schema.rs +++ b/aquadoggo/src/graphql/schema.rs @@ -1,7 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later use super::client::{Mutation as ClientMutationRoot, Query as ClientQueryRoot}; -use super::ping::PingRoot; use super::replication::ReplicationRoot; use super::Context; use crate::db::provider::SqlStorage; @@ -10,7 +9,6 @@ use async_graphql::{EmptySubscription, MergedObject, Schema}; /// All of the graphql query sub modules merged into one top level root #[derive(MergedObject, Debug)] pub struct QueryRoot( - pub PingRoot, pub ReplicationRoot, pub ClientQueryRoot, ); @@ -24,10 +22,9 @@ pub type RootSchema = Schema; /// Build the root graphql schema that can handle graphql requests. pub fn build_root_schema(context: Context) -> RootSchema { - let ping_root: PingRoot = Default::default(); let replication_root = ReplicationRoot::::new(); let client_query_root = ClientQueryRoot::default(); - let query_root = QueryRoot(ping_root, replication_root, client_query_root); + let query_root = QueryRoot(replication_root, client_query_root); let client_mutation_root = Default::default(); let mutation_root = MutationRoot(client_mutation_root); From 045cace2e9591b4960bcd6f263136093616387be Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 24 May 2022 19:35:53 +1200 Subject: [PATCH 06/35] implement `get_skiplinks` --- aquadoggo/src/graphql/replication/context.rs | 126 ++++++++++--------- aquadoggo/src/graphql/replication/entry.rs | 6 + 2 files changed, 72 insertions(+), 60 deletions(-) diff --git a/aquadoggo/src/graphql/replication/context.rs b/aquadoggo/src/graphql/replication/context.rs index 510bc7461..c2acaada4 100644 --- a/aquadoggo/src/graphql/replication/context.rs +++ b/aquadoggo/src/graphql/replication/context.rs @@ -14,7 +14,7 @@ use super::SingleEntryAndPayload; use anyhow::{anyhow, Result}; use async_graphql::ID; use lru::LruCache; -use p2panda_rs::entry::SeqNum; +use p2panda_rs::entry::{decode_entry, SeqNum}; use p2panda_rs::storage_provider::traits::EntryStore; #[derive(Debug)] @@ -91,11 +91,17 @@ impl> Context { } pub async fn get_skiplinks<'a>(&mut self, entry: &Entry) -> Result> { - let entry = entry.as_ref(); - todo!(); - //let result = self.entry_store - // .get_all_lipmaa_entries_for_entry(&entry.author(), &entry.log_id() ) - //todo!() + let author = entry.as_ref().author(); + let entry = decode_entry(entry.as_ref(), None)?; + let result = self + .entry_store + .get_certificate_pool(&author, entry.log_id(), entry.seq_num()) + .await? + .into_iter() + .map(|entry| entry.entry_signed().clone().into()) + .collect(); + + Ok(result) } pub async fn get_entries_newer_than_seq( @@ -153,58 +159,58 @@ mod tests { use p2panda_rs::storage_provider::traits::EntryStore; use std::convert::TryInto; -// #[tokio::test] -// async fn entry_by_log_id_and_sequence() { -// let expected_log_id = 123; -// let expected_seq_num = 345u64; -// let expected_author_id = 987u64; -// let expected_author_string = -// "7cf4f58a2d89e93313f2de99604a814ecea9800cf217b140e9c3a7ba59a5d982".to_string(); -// -// let log_id: GraphQLLogId = expected_log_id.into(); -// let sequence_number: SequenceNumber = expected_seq_num.try_into().unwrap(); -// let author = Author::new(&expected_author_string).unwrap(); -// let author_id = GraphQLAuthor { -// alias: None, -// public_key: Some(PublicKey(author)), -// }; -// -// mock! { -// pub MockEntryStore {} -// #[async_trait] -// impl EntryStore for MockEntryStore { -// async fn insert_entry(&self, _value: StorageEntry) -> Result; -// -// async fn get_entry_at_seq_num( -// &self, -// author: &Author, -// log_id: &LogId, -// seq_num: &SeqNum, -// ) -> Result, EntryStorageError>; -// -// async fn latest_entry( -// &self, -// _author: &Author, -// _log_id: &LogId, -// ) -> Result, EntryStorageError>; -// -// } -// } -// -// let mut mock_entry_store = MockMockEntryStore::new(); -// mock_entry_store -// .expect_entry_at_seq_num() -// .withf(move |author, log_id, seq_num| author.as_str() == expected_author_string) -// .times(1) -// .returning(|_, _, _| Ok(None)); -// -// let mut context = Context::new(1, mock_entry_store); -// -// let result = context -// .entry_by_log_id_and_sequence(log_id, sequence_number, author_id.try_into().unwrap()) -// .await; -// -// println!("{:?}", result); -// assert!(result.is_ok()); -// } + // #[tokio::test] + // async fn entry_by_log_id_and_sequence() { + // let expected_log_id = 123; + // let expected_seq_num = 345u64; + // let expected_author_id = 987u64; + // let expected_author_string = + // "7cf4f58a2d89e93313f2de99604a814ecea9800cf217b140e9c3a7ba59a5d982".to_string(); + // + // let log_id: GraphQLLogId = expected_log_id.into(); + // let sequence_number: SequenceNumber = expected_seq_num.try_into().unwrap(); + // let author = Author::new(&expected_author_string).unwrap(); + // let author_id = GraphQLAuthor { + // alias: None, + // public_key: Some(PublicKey(author)), + // }; + // + // mock! { + // pub MockEntryStore {} + // #[async_trait] + // impl EntryStore for MockEntryStore { + // async fn insert_entry(&self, _value: StorageEntry) -> Result; + // + // async fn get_entry_at_seq_num( + // &self, + // author: &Author, + // log_id: &LogId, + // seq_num: &SeqNum, + // ) -> Result, EntryStorageError>; + // + // async fn latest_entry( + // &self, + // _author: &Author, + // _log_id: &LogId, + // ) -> Result, EntryStorageError>; + // + // } + // } + // + // let mut mock_entry_store = MockMockEntryStore::new(); + // mock_entry_store + // .expect_entry_at_seq_num() + // .withf(move |author, log_id, seq_num| author.as_str() == expected_author_string) + // .times(1) + // .returning(|_, _, _| Ok(None)); + // + // let mut context = Context::new(1, mock_entry_store); + // + // let result = context + // .entry_by_log_id_and_sequence(log_id, sequence_number, author_id.try_into().unwrap()) + // .await; + // + // println!("{:?}", result); + // assert!(result.is_ok()); + // } } diff --git a/aquadoggo/src/graphql/replication/entry.rs b/aquadoggo/src/graphql/replication/entry.rs index 82bb1be04..561908124 100644 --- a/aquadoggo/src/graphql/replication/entry.rs +++ b/aquadoggo/src/graphql/replication/entry.rs @@ -21,3 +21,9 @@ impl From for Value { async_graphql::ScalarType::to_value(&entry) } } + +impl From for Entry { + fn from(panda_entry: PandaEntry) -> Self { + Entry(panda_entry) + } +} From cd9cace608a99c56e36e66e699f3eff169f75c13 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 24 May 2022 19:52:28 +1200 Subject: [PATCH 07/35] Fix use orderings --- aquadoggo/src/graphql/context.rs | 6 ++++-- aquadoggo/src/graphql/mod.rs | 6 ++---- aquadoggo/src/graphql/replication/aliased_author.rs | 3 ++- aquadoggo/src/graphql/replication/author.rs | 6 ++++-- aquadoggo/src/graphql/replication/client/mod.rs | 3 ++- aquadoggo/src/graphql/replication/context.rs | 11 ++++++----- .../src/graphql/replication/entry_and_payload.rs | 3 ++- aquadoggo/src/graphql/replication/mod.rs | 10 +++++----- aquadoggo/src/graphql/replication/sequence_number.rs | 3 ++- .../graphql/replication/single_entry_and_payload.rs | 5 +++-- aquadoggo/src/graphql/schema.rs | 11 +++++------ 11 files changed, 37 insertions(+), 30 deletions(-) diff --git a/aquadoggo/src/graphql/context.rs b/aquadoggo/src/graphql/context.rs index 7af95dbf2..73169abe9 100644 --- a/aquadoggo/src/graphql/context.rs +++ b/aquadoggo/src/graphql/context.rs @@ -1,9 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -use crate::db::{provider::SqlStorage, Pool}; use std::sync::Arc; + use tokio::sync::Mutex; +use crate::db::{provider::SqlStorage, Pool}; + use super::replication::context::Context as ReplicationContext; #[derive(Debug)] @@ -25,7 +27,7 @@ impl Context { Self { replication_context, - pool + pool, } } } diff --git a/aquadoggo/src/graphql/mod.rs b/aquadoggo/src/graphql/mod.rs index dc215317e..6436bd355 100644 --- a/aquadoggo/src/graphql/mod.rs +++ b/aquadoggo/src/graphql/mod.rs @@ -2,14 +2,12 @@ mod api; pub mod client; - -pub use api::{handle_graphql_playground, handle_graphql_query}; - mod context; mod replication; mod schema; +pub use api::{handle_graphql_playground, handle_graphql_query}; +pub use client::Query as ClientRoot; pub use context::Context; pub use replication::ReplicationRoot; -pub use client::Query as ClientRoot; pub use schema::{build_root_schema, QueryRoot, RootSchema}; diff --git a/aquadoggo/src/graphql/replication/aliased_author.rs b/aquadoggo/src/graphql/replication/aliased_author.rs index d4457d24a..6b4310b95 100644 --- a/aquadoggo/src/graphql/replication/aliased_author.rs +++ b/aquadoggo/src/graphql/replication/aliased_author.rs @@ -1,8 +1,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -use super::public_key::PublicKey; use async_graphql::*; +use super::public_key::PublicKey; + #[derive(Debug, InputObject, SimpleObject)] pub struct AliasedAuthor { /// The author's public key diff --git a/aquadoggo/src/graphql/replication/author.rs b/aquadoggo/src/graphql/replication/author.rs index d86de72b4..893a50844 100644 --- a/aquadoggo/src/graphql/replication/author.rs +++ b/aquadoggo/src/graphql/replication/author.rs @@ -1,9 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -use super::public_key::PublicKey; +use std::convert::TryFrom; + use anyhow::{anyhow, Error}; use async_graphql::*; -use std::convert::TryFrom; + +use super::public_key::PublicKey; /// Either the `public_key` or the `alias` of that author. #[derive(Debug, InputObject)] diff --git a/aquadoggo/src/graphql/replication/client/mod.rs b/aquadoggo/src/graphql/replication/client/mod.rs index 8a4398dcc..d5832155a 100644 --- a/aquadoggo/src/graphql/replication/client/mod.rs +++ b/aquadoggo/src/graphql/replication/client/mod.rs @@ -1,8 +1,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +use graphql_client::GraphQLQuery; + use super::Entry; use super::EntryHash; -use graphql_client::GraphQLQuery; // The paths are relative to the directory where your `Cargo.toml` is located. // Both json and the GraphQL schema language are supported as sources for the schema diff --git a/aquadoggo/src/graphql/replication/context.rs b/aquadoggo/src/graphql/replication/context.rs index c2acaada4..2ee758511 100644 --- a/aquadoggo/src/graphql/replication/context.rs +++ b/aquadoggo/src/graphql/replication/context.rs @@ -1,5 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +use anyhow::{anyhow, Result}; +use async_graphql::ID; +use lru::LruCache; +use p2panda_rs::entry::{decode_entry, SeqNum}; +use p2panda_rs::storage_provider::traits::EntryStore; + use crate::db::stores::StorageEntry; pub use super::aliased_author::AliasedAuthor; @@ -11,11 +17,6 @@ use super::EntryHash; use super::LogId; use super::SequenceNumber; use super::SingleEntryAndPayload; -use anyhow::{anyhow, Result}; -use async_graphql::ID; -use lru::LruCache; -use p2panda_rs::entry::{decode_entry, SeqNum}; -use p2panda_rs::storage_provider::traits::EntryStore; #[derive(Debug)] pub struct Context> { diff --git a/aquadoggo/src/graphql/replication/entry_and_payload.rs b/aquadoggo/src/graphql/replication/entry_and_payload.rs index 158b76921..fe17457be 100644 --- a/aquadoggo/src/graphql/replication/entry_and_payload.rs +++ b/aquadoggo/src/graphql/replication/entry_and_payload.rs @@ -1,10 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +use async_graphql::*; + use crate::db::stores::StorageEntry; use super::payload::Payload; use super::Entry; -use async_graphql::*; /// An and entry with an optional payload #[derive(SimpleObject, Debug)] diff --git a/aquadoggo/src/graphql/replication/mod.rs b/aquadoggo/src/graphql/replication/mod.rs index 0a7022199..316f6d568 100644 --- a/aquadoggo/src/graphql/replication/mod.rs +++ b/aquadoggo/src/graphql/replication/mod.rs @@ -2,15 +2,19 @@ use std::convert::TryInto; use std::marker::PhantomData; +use std::sync::Arc; use async_graphql::connection::{query, Connection, Edge, EmptyFields}; use async_graphql::Object; use async_graphql::*; -use std::sync::Arc; +use p2panda_rs::storage_provider::traits::EntryStore; use tokio::sync::Mutex; +use crate::db::stores::StorageEntry; + pub mod aliased_author; pub mod author; +pub mod client; pub mod context; pub mod entry; pub mod entry_and_payload; @@ -21,7 +25,6 @@ pub mod public_key; pub mod sequence_number; pub mod single_entry_and_payload; -use crate::db::stores::StorageEntry; pub use aliased_author::AliasedAuthor; pub use author::{Author, AuthorOrAlias}; pub use context::Context as ReplicationContext; @@ -29,14 +32,11 @@ pub use entry::Entry; pub use entry_and_payload::EntryAndPayload; pub use entry_hash::EntryHash; pub use log_id::LogId; -use p2panda_rs::storage_provider::traits::EntryStore; pub use payload::Payload; pub use public_key::PublicKey; pub use sequence_number::SequenceNumber; pub use single_entry_and_payload::SingleEntryAndPayload; -pub mod client; - #[derive(Debug)] /// The root graphql object for replication pub struct ReplicationRoot { diff --git a/aquadoggo/src/graphql/replication/sequence_number.rs b/aquadoggo/src/graphql/replication/sequence_number.rs index 8a23d3590..79b5910e8 100644 --- a/aquadoggo/src/graphql/replication/sequence_number.rs +++ b/aquadoggo/src/graphql/replication/sequence_number.rs @@ -1,9 +1,10 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +use std::convert::TryFrom; + use async_graphql::*; use p2panda_rs::entry::{SeqNum as PandaSeqNum, SeqNumError}; use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; /// The sequence number of an entry #[derive(Clone, Copy, Debug, Serialize, Deserialize)] diff --git a/aquadoggo/src/graphql/replication/single_entry_and_payload.rs b/aquadoggo/src/graphql/replication/single_entry_and_payload.rs index b46eb6c43..8eca34db9 100644 --- a/aquadoggo/src/graphql/replication/single_entry_and_payload.rs +++ b/aquadoggo/src/graphql/replication/single_entry_and_payload.rs @@ -1,11 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +use async_graphql::*; + use crate::db::stores::StorageEntry; +use crate::graphql::Context as GraphQLContext; use super::payload::Payload; use super::Entry; -use crate::graphql::Context as GraphQLContext; -use async_graphql::*; /// A p2panda entry with optional payload and the collection of skiplinks required to verify it. #[derive(Debug)] diff --git a/aquadoggo/src/graphql/schema.rs b/aquadoggo/src/graphql/schema.rs index 97fb135ac..368e6b167 100644 --- a/aquadoggo/src/graphql/schema.rs +++ b/aquadoggo/src/graphql/schema.rs @@ -1,17 +1,16 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +use async_graphql::{EmptySubscription, MergedObject, Schema}; + +use crate::db::provider::SqlStorage; + use super::client::{Mutation as ClientMutationRoot, Query as ClientQueryRoot}; use super::replication::ReplicationRoot; use super::Context; -use crate::db::provider::SqlStorage; -use async_graphql::{EmptySubscription, MergedObject, Schema}; /// All of the graphql query sub modules merged into one top level root #[derive(MergedObject, Debug)] -pub struct QueryRoot( - pub ReplicationRoot, - pub ClientQueryRoot, -); +pub struct QueryRoot(pub ReplicationRoot, pub ClientQueryRoot); /// All of the graphql mutation sub modules merged into one top level root #[derive(MergedObject, Debug, Copy, Clone, Default)] From 354e9dcde1de97c26502fa47c026d2fc20d0b9b8 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 24 May 2022 19:57:10 +1200 Subject: [PATCH 08/35] Update note about generating schema file. --- aquadoggo/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aquadoggo/README.md b/aquadoggo/README.md index a0e7c04f1..97001cf96 100644 --- a/aquadoggo/README.md +++ b/aquadoggo/README.md @@ -80,7 +80,7 @@ $ cargo add aquadoggo ### Regenerate graphql schema -When you update the graphql code you'll need to manually regenerate the schema files used to create the replication graphql client +When you update the graphql code you'll need to manually regenerate the graphql schema files. ```sh cargo run --bin dump_gql_schema > aquadoggo/src/graphql/replication/client/schema.graphql From 4feb72ed66bb916e1ad3f1dfc17f84c1a3a24d3c Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 24 May 2022 20:27:03 +1200 Subject: [PATCH 09/35] Get test building + passing again --- aquadoggo/src/graphql/replication/context.rs | 141 +++++++++++-------- 1 file changed, 84 insertions(+), 57 deletions(-) diff --git a/aquadoggo/src/graphql/replication/context.rs b/aquadoggo/src/graphql/replication/context.rs index 2ee758511..31a5ca437 100644 --- a/aquadoggo/src/graphql/replication/context.rs +++ b/aquadoggo/src/graphql/replication/context.rs @@ -143,75 +143,102 @@ impl> Context { #[cfg(test)] mod tests { - use std::num::NonZeroU64; use super::Context; - use crate::db::models::EntryRow; use crate::db::stores::StorageEntry; use crate::graphql::replication::{ - Author as GraphQLAuthor, LogId as GraphQLLogId, PublicKey, SequenceNumber, ID, + Author as GraphQLAuthor, LogId as GraphQLLogId, PublicKey, SequenceNumber }; use async_trait::async_trait; use mockall::mock; use p2panda_rs::entry::{LogId, SeqNum}; + use p2panda_rs::hash::Hash; use p2panda_rs::identity::Author; use p2panda_rs::schema::SchemaId; use p2panda_rs::storage_provider::errors::EntryStorageError; use p2panda_rs::storage_provider::traits::EntryStore; use std::convert::TryInto; - // #[tokio::test] - // async fn entry_by_log_id_and_sequence() { - // let expected_log_id = 123; - // let expected_seq_num = 345u64; - // let expected_author_id = 987u64; - // let expected_author_string = - // "7cf4f58a2d89e93313f2de99604a814ecea9800cf217b140e9c3a7ba59a5d982".to_string(); - // - // let log_id: GraphQLLogId = expected_log_id.into(); - // let sequence_number: SequenceNumber = expected_seq_num.try_into().unwrap(); - // let author = Author::new(&expected_author_string).unwrap(); - // let author_id = GraphQLAuthor { - // alias: None, - // public_key: Some(PublicKey(author)), - // }; - // - // mock! { - // pub MockEntryStore {} - // #[async_trait] - // impl EntryStore for MockEntryStore { - // async fn insert_entry(&self, _value: StorageEntry) -> Result; - // - // async fn get_entry_at_seq_num( - // &self, - // author: &Author, - // log_id: &LogId, - // seq_num: &SeqNum, - // ) -> Result, EntryStorageError>; - // - // async fn latest_entry( - // &self, - // _author: &Author, - // _log_id: &LogId, - // ) -> Result, EntryStorageError>; - // - // } - // } - // - // let mut mock_entry_store = MockMockEntryStore::new(); - // mock_entry_store - // .expect_entry_at_seq_num() - // .withf(move |author, log_id, seq_num| author.as_str() == expected_author_string) - // .times(1) - // .returning(|_, _, _| Ok(None)); - // - // let mut context = Context::new(1, mock_entry_store); - // - // let result = context - // .entry_by_log_id_and_sequence(log_id, sequence_number, author_id.try_into().unwrap()) - // .await; - // - // println!("{:?}", result); - // assert!(result.is_ok()); - // } + mock! { + pub MockEntryStore {} + #[async_trait] + impl EntryStore for MockEntryStore { + async fn insert_entry(&self, value: StorageEntry) -> Result<(), EntryStorageError>; + + async fn get_entry_at_seq_num( + &self, + author: &Author, + log_id: &LogId, + seq_num: &SeqNum, + ) -> Result, EntryStorageError>; + + async fn get_entry_by_hash( + &self, + hash: &Hash, + ) -> Result, EntryStorageError>; + + async fn get_latest_entry( + &self, + author: &Author, + log_id: &LogId, + ) -> Result, EntryStorageError>; + + async fn get_entries_by_schema( + &self, + schema: &SchemaId, + ) -> Result, EntryStorageError>; + + async fn get_paginated_log_entries( + &self, + author: &Author, + log_id: &LogId, + seq_num: &SeqNum, + max_number_of_entries: usize, + ) -> Result, EntryStorageError>; + + async fn get_certificate_pool( + &self, + author_id: &Author, + log_id: &LogId, + seq_num: &SeqNum, + ) -> Result, EntryStorageError>; + + } + + } + + #[tokio::test] + async fn entry_by_log_id_and_sequence() { + let expected_log_id = 123; + let expected_seq_num = 345u64; + let expected_author_string = + "7cf4f58a2d89e93313f2de99604a814ecea9800cf217b140e9c3a7ba59a5d982".to_string(); + + let log_id: GraphQLLogId = expected_log_id.into(); + let sequence_number: SequenceNumber = expected_seq_num.try_into().unwrap(); + let author = Author::new(&expected_author_string).unwrap(); + let author_id = GraphQLAuthor { + alias: None, + public_key: Some(PublicKey(author)), + }; + + let mut mock_entry_store = MockMockEntryStore::new(); + mock_entry_store + .expect_get_entry_at_seq_num() + .withf(move |author, log_id, seq_num| { + author.as_str() == expected_author_string + && log_id.as_u64() == expected_log_id + && seq_num.as_u64() == expected_seq_num + }) + .times(1) + .returning(|_, _, _| Ok(None)); + + let mut context = Context::new(1, mock_entry_store); + + let result = context + .entry_by_log_id_and_sequence(log_id, sequence_number, author_id.try_into().unwrap()) + .await; + + assert!(result.is_ok()); + } } From c107124c4562b68fefc47e1409cbc7363cffcf27 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 24 May 2022 21:01:13 +1200 Subject: [PATCH 10/35] finish implementing pagination on `get_entries_newer_than_seq` --- aquadoggo/src/graphql/replication/mod.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/aquadoggo/src/graphql/replication/mod.rs b/aquadoggo/src/graphql/replication/mod.rs index 316f6d568..ea94cc858 100644 --- a/aquadoggo/src/graphql/replication/mod.rs +++ b/aquadoggo/src/graphql/replication/mod.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use async_graphql::connection::{query, Connection, Edge, EmptyFields}; use async_graphql::Object; use async_graphql::*; +use p2panda_rs::entry::decode_entry; use p2panda_rs::storage_provider::traits::EntryStore; use tokio::sync::Mutex; @@ -82,9 +83,9 @@ impl + Sync + Send> ReplicationRoot { let author: AuthorOrAlias = author.try_into()?; query(after, None, first, None, |after, _, first, _| async move { let start = - sequence_number.as_ref().as_u64() + after.map(|a| a as u64 + 1).unwrap_or(0); - // TODO: clamp an upper limit here - let first = first.unwrap_or(10); + sequence_number.as_ref().as_u64() + after.map(|a| a as u64).unwrap_or(0); + + let first = first.map(|n| n.clamp(0, 10000)).unwrap_or(10); let edges = ctx .lock() @@ -92,8 +93,10 @@ impl + Sync + Send> ReplicationRoot { .get_entries_newer_than_seq(log_id, author, sequence_number, first, start) .await? .into_iter() - // FIXME: need to create a cursor from the entry here, not pass 0 - .map(|entry| Edge::new(0usize, entry.into())); + .map(|entry| { + let decoded = decode_entry(entry.entry.as_ref(), None).unwrap(); + Edge::new(decoded.seq_num().as_u64() as usize, entry.into()) + }); let mut connection = Connection::new(false, start < first as u64); From 19eafbba8a8bd541afbb58e48951d4fb994f8829 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Wed, 25 May 2022 22:06:09 +1200 Subject: [PATCH 11/35] Working on testing --- Cargo.lock | 13 ++++ aquadoggo/Cargo.toml | 2 + aquadoggo/src/graphql/replication/context.rs | 65 ++-------------- aquadoggo/src/graphql/replication/mod.rs | 78 ++++++++++++++++++- .../src/graphql/replication/testing/mod.rs | 60 ++++++++++++++ 5 files changed, 157 insertions(+), 61 deletions(-) create mode 100644 aquadoggo/src/graphql/replication/testing/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 7d3d75772..02808f089 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,6 +121,7 @@ dependencies = [ "log", "lru", "mockall", + "mockall_double", "openssl-probe", "p2panda-rs", "rand 0.8.5", @@ -2283,6 +2284,18 @@ dependencies = [ "syn", ] +[[package]] +name = "mockall_double" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae71c7bb287375187c775cf82e2dcf1bef3388aaf58f0789a77f9c7ab28466f6" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "multer" version = "2.0.2" diff --git a/aquadoggo/Cargo.toml b/aquadoggo/Cargo.toml index 10b83b3b2..eccdb3e65 100644 --- a/aquadoggo/Cargo.toml +++ b/aquadoggo/Cargo.toml @@ -34,6 +34,8 @@ jsonrpc-v2 = { version = "0.10.1", features = [ lipmaa-link = "0.2.2" log = "0.4.14" lru = "0.7.5" +mockall = "0.11.0" +mockall_double = "0.3.0" openssl-probe = "0.1.4" # We can not publish the `aquadoggo` crate yet, since `p2panda-rs` is an # unpublished dependency. diff --git a/aquadoggo/src/graphql/replication/context.rs b/aquadoggo/src/graphql/replication/context.rs index 31a5ca437..36d93d5c7 100644 --- a/aquadoggo/src/graphql/replication/context.rs +++ b/aquadoggo/src/graphql/replication/context.rs @@ -5,6 +5,7 @@ use async_graphql::ID; use lru::LruCache; use p2panda_rs::entry::{decode_entry, SeqNum}; use p2panda_rs::storage_provider::traits::EntryStore; +use mockall::automock; use crate::db::stores::StorageEntry; @@ -19,13 +20,14 @@ use super::SequenceNumber; use super::SingleEntryAndPayload; #[derive(Debug)] -pub struct Context> { +pub struct Context> { author_aliases: LruCache, next_alias: usize, entry_store: ES, } -impl> Context { +#[automock] +impl> Context { pub fn new(author_aliases_cache_size: usize, entry_store: ES) -> Self { Self { author_aliases: LruCache::new(author_aliases_cache_size), @@ -145,67 +147,12 @@ impl> Context { mod tests { use super::Context; - use crate::db::stores::StorageEntry; use crate::graphql::replication::{ Author as GraphQLAuthor, LogId as GraphQLLogId, PublicKey, SequenceNumber }; - use async_trait::async_trait; - use mockall::mock; - use p2panda_rs::entry::{LogId, SeqNum}; - use p2panda_rs::hash::Hash; use p2panda_rs::identity::Author; - use p2panda_rs::schema::SchemaId; - use p2panda_rs::storage_provider::errors::EntryStorageError; - use p2panda_rs::storage_provider::traits::EntryStore; use std::convert::TryInto; - - mock! { - pub MockEntryStore {} - #[async_trait] - impl EntryStore for MockEntryStore { - async fn insert_entry(&self, value: StorageEntry) -> Result<(), EntryStorageError>; - - async fn get_entry_at_seq_num( - &self, - author: &Author, - log_id: &LogId, - seq_num: &SeqNum, - ) -> Result, EntryStorageError>; - - async fn get_entry_by_hash( - &self, - hash: &Hash, - ) -> Result, EntryStorageError>; - - async fn get_latest_entry( - &self, - author: &Author, - log_id: &LogId, - ) -> Result, EntryStorageError>; - - async fn get_entries_by_schema( - &self, - schema: &SchemaId, - ) -> Result, EntryStorageError>; - - async fn get_paginated_log_entries( - &self, - author: &Author, - log_id: &LogId, - seq_num: &SeqNum, - max_number_of_entries: usize, - ) -> Result, EntryStorageError>; - - async fn get_certificate_pool( - &self, - author_id: &Author, - log_id: &LogId, - seq_num: &SeqNum, - ) -> Result, EntryStorageError>; - - } - - } + use super::super::testing::MockEntryStore; #[tokio::test] async fn entry_by_log_id_and_sequence() { @@ -222,7 +169,7 @@ mod tests { public_key: Some(PublicKey(author)), }; - let mut mock_entry_store = MockMockEntryStore::new(); + let mut mock_entry_store = MockEntryStore::new(); mock_entry_store .expect_get_entry_at_seq_num() .withf(move |author, log_id, seq_num| { diff --git a/aquadoggo/src/graphql/replication/mod.rs b/aquadoggo/src/graphql/replication/mod.rs index ea94cc858..4360be11d 100644 --- a/aquadoggo/src/graphql/replication/mod.rs +++ b/aquadoggo/src/graphql/replication/mod.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use async_graphql::connection::{query, Connection, Edge, EmptyFields}; use async_graphql::Object; use async_graphql::*; +use mockall_double::double; use p2panda_rs::entry::decode_entry; use p2panda_rs::storage_provider::traits::EntryStore; use tokio::sync::Mutex; @@ -26,8 +27,13 @@ pub mod public_key; pub mod sequence_number; pub mod single_entry_and_payload; +#[cfg(test)] +mod testing; + pub use aliased_author::AliasedAuthor; pub use author::{Author, AuthorOrAlias}; + +#[double] pub use context::Context as ReplicationContext; pub use entry::Entry; pub use entry_and_payload::EntryAndPayload; @@ -82,8 +88,7 @@ impl + Sync + Send> ReplicationRoot { let ctx: &Arc>> = ctx.data()?; let author: AuthorOrAlias = author.try_into()?; query(after, None, first, None, |after, _, first, _| async move { - let start = - sequence_number.as_ref().as_u64() + after.map(|a| a as u64).unwrap_or(0); + let start = sequence_number.as_ref().as_u64() + after.map(|a| a as u64).unwrap_or(0); let first = first.map(|n| n.clamp(0, 10000)).unwrap_or(10); @@ -140,3 +145,72 @@ impl + Sync + Send> ReplicationRoot { Ok(result) } } + +#[cfg(test)] +mod tests { + use std::convert::TryFrom; + use std::sync::Arc; + + use async_graphql::{EmptyMutation, EmptySubscription, Request, Schema}; + use p2panda_rs::identity::Author; + use tokio::sync::Mutex; + + use super::testing::MockEntryStore; + use super::{ + Author as GraphQLAuthor, LogId, PublicKey, ReplicationContext, ReplicationRoot, + SequenceNumber, + AuthorOrAlias, + }; + + #[tokio::test] + async fn get_entries_newer_than_seq_cursor_addition_is_ok() { + let mut mock_entry_store = MockEntryStore::new(); + mock_entry_store.expect_get_entry_at_seq_num().never(); + + let mut replication_context: ReplicationContext = + ReplicationContext::default(); + + let log_id = 3u64; + let sequence_number = 123u64; + let author_string = + "7cf4f58a2d89e93313f2de99604a814ecea9800cf217b140e9c3a7ba59a5d982".to_string(); + + replication_context + .expect_get_entries_newer_than_seq() + .withf({ + let author_string = author_string.clone(); + + move|log_id_, author_, sequence_number_, first_, start_| { + let author_matches = match author_ { + AuthorOrAlias::PublicKey(public_key) => public_key.0.as_str() == author_string, + _ => false + }; + log_id_.as_ref().as_u64() == log_id && author_matches + + + }}) + .returning(|_, _, _, _, _| Ok(vec![])) + .once(); + + let replication_root = ReplicationRoot::::new(); + + let gql_query = format!( + " + query{{ + getEntriesNewerThanSeq(logId: {}, author: {{publicKey: \"{}\" }}, sequenceNumber:{}){{ + pageInfo {{ + hasNextPage + }} + }} + }}", + log_id, author_string, sequence_number + ); + + let schema = Schema::build(replication_root, EmptyMutation, EmptySubscription) + .data(Arc::new(Mutex::new(replication_context))) + .finish(); + + let result = schema.execute(Request::new(gql_query)).await; + println!("{:?}", result); + } +} diff --git a/aquadoggo/src/graphql/replication/testing/mod.rs b/aquadoggo/src/graphql/replication/testing/mod.rs new file mode 100644 index 000000000..2ff4ae1fa --- /dev/null +++ b/aquadoggo/src/graphql/replication/testing/mod.rs @@ -0,0 +1,60 @@ + +use async_trait::async_trait; +use mockall::mock; +use mockall_double::double; +use p2panda_rs::entry::{LogId, SeqNum}; +use p2panda_rs::hash::Hash; +use p2panda_rs::identity::Author; +use p2panda_rs::schema::SchemaId; +use p2panda_rs::storage_provider::errors::EntryStorageError; +use p2panda_rs::storage_provider::traits::EntryStore; + + +use crate::db::stores::StorageEntry; + +mock! { + pub EntryStore {} + #[async_trait] + impl EntryStore for EntryStore { + async fn insert_entry(&self, value: StorageEntry) -> Result<(), EntryStorageError>; + + async fn get_entry_at_seq_num( + &self, + author: &Author, + log_id: &LogId, + seq_num: &SeqNum, + ) -> Result, EntryStorageError>; + + async fn get_entry_by_hash( + &self, + hash: &Hash, + ) -> Result, EntryStorageError>; + + async fn get_latest_entry( + &self, + author: &Author, + log_id: &LogId, + ) -> Result, EntryStorageError>; + + async fn get_entries_by_schema( + &self, + schema: &SchemaId, + ) -> Result, EntryStorageError>; + + async fn get_paginated_log_entries( + &self, + author: &Author, + log_id: &LogId, + seq_num: &SeqNum, + max_number_of_entries: usize, + ) -> Result, EntryStorageError>; + + async fn get_certificate_pool( + &self, + author_id: &Author, + log_id: &LogId, + seq_num: &SeqNum, + ) -> Result, EntryStorageError>; + + } +} From 4f1d97fa6a99eb8f79469345f44eb367c779c9e7 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Fri, 27 May 2022 23:03:04 +1200 Subject: [PATCH 12/35] Tighten up pagination cursor test --- aquadoggo/src/graphql/replication/mod.rs | 44 ++++++++++++------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/aquadoggo/src/graphql/replication/mod.rs b/aquadoggo/src/graphql/replication/mod.rs index 4360be11d..06523c1d2 100644 --- a/aquadoggo/src/graphql/replication/mod.rs +++ b/aquadoggo/src/graphql/replication/mod.rs @@ -148,25 +148,16 @@ impl + Sync + Send> ReplicationRoot { #[cfg(test)] mod tests { - use std::convert::TryFrom; use std::sync::Arc; use async_graphql::{EmptyMutation, EmptySubscription, Request, Schema}; - use p2panda_rs::identity::Author; use tokio::sync::Mutex; use super::testing::MockEntryStore; - use super::{ - Author as GraphQLAuthor, LogId, PublicKey, ReplicationContext, ReplicationRoot, - SequenceNumber, - AuthorOrAlias, - }; + use super::{AuthorOrAlias, ReplicationContext, ReplicationRoot}; #[tokio::test] async fn get_entries_newer_than_seq_cursor_addition_is_ok() { - let mut mock_entry_store = MockEntryStore::new(); - mock_entry_store.expect_get_entry_at_seq_num().never(); - let mut replication_context: ReplicationContext = ReplicationContext::default(); @@ -174,21 +165,30 @@ mod tests { let sequence_number = 123u64; let author_string = "7cf4f58a2d89e93313f2de99604a814ecea9800cf217b140e9c3a7ba59a5d982".to_string(); + let after = 2; + let first = 3; + + let expected_start = sequence_number + after; replication_context .expect_get_entries_newer_than_seq() .withf({ let author_string = author_string.clone(); - move|log_id_, author_, sequence_number_, first_, start_| { - let author_matches = match author_ { - AuthorOrAlias::PublicKey(public_key) => public_key.0.as_str() == author_string, - _ => false - }; - log_id_.as_ref().as_u64() == log_id && author_matches - - - }}) + move |log_id_, author_, sequence_number_, first_, start_| { + let author_matches = match author_ { + AuthorOrAlias::PublicKey(public_key) => { + public_key.0.as_str() == author_string + } + _ => false, + }; + sequence_number_.as_ref().as_u64() == sequence_number + && *start_ == expected_start + && log_id_.as_ref().as_u64() == log_id + && author_matches + && *first_ == first as usize + } + }) .returning(|_, _, _, _, _| Ok(vec![])) .once(); @@ -197,13 +197,13 @@ mod tests { let gql_query = format!( " query{{ - getEntriesNewerThanSeq(logId: {}, author: {{publicKey: \"{}\" }}, sequenceNumber:{}){{ + getEntriesNewerThanSeq(logId: {}, author: {{publicKey: \"{}\" }}, sequenceNumber:{}, first: {}, after: \"{}\" ){{ pageInfo {{ hasNextPage }} }} }}", - log_id, author_string, sequence_number + log_id, author_string, sequence_number, first, after ); let schema = Schema::build(replication_root, EmptyMutation, EmptySubscription) @@ -211,6 +211,6 @@ mod tests { .finish(); let result = schema.execute(Request::new(gql_query)).await; - println!("{:?}", result); + assert!(result.is_ok()); } } From 85a5af7486e0d1ef8af6d35127c0b4651452ed5a Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Sat, 28 May 2022 11:47:06 +1200 Subject: [PATCH 13/35] SequenceNumber implements cursor type, more erogonomic gql types, fmt --- aquadoggo/src/graphql/replication/context.rs | 6 +- aquadoggo/src/graphql/replication/log_id.rs | 6 ++ aquadoggo/src/graphql/replication/mod.rs | 94 ++++++++++++------- .../graphql/replication/sequence_number.rs | 6 ++ .../src/graphql/replication/testing/mod.rs | 2 - 5 files changed, 77 insertions(+), 37 deletions(-) diff --git a/aquadoggo/src/graphql/replication/context.rs b/aquadoggo/src/graphql/replication/context.rs index 36d93d5c7..fb3961ab5 100644 --- a/aquadoggo/src/graphql/replication/context.rs +++ b/aquadoggo/src/graphql/replication/context.rs @@ -3,9 +3,9 @@ use anyhow::{anyhow, Result}; use async_graphql::ID; use lru::LruCache; +use mockall::automock; use p2panda_rs::entry::{decode_entry, SeqNum}; use p2panda_rs::storage_provider::traits::EntryStore; -use mockall::automock; use crate::db::stores::StorageEntry; @@ -146,13 +146,13 @@ impl> Context { #[cfg(test)] mod tests { + use super::super::testing::MockEntryStore; use super::Context; use crate::graphql::replication::{ - Author as GraphQLAuthor, LogId as GraphQLLogId, PublicKey, SequenceNumber + Author as GraphQLAuthor, LogId as GraphQLLogId, PublicKey, SequenceNumber, }; use p2panda_rs::identity::Author; use std::convert::TryInto; - use super::super::testing::MockEntryStore; #[tokio::test] async fn entry_by_log_id_and_sequence() { diff --git a/aquadoggo/src/graphql/replication/log_id.rs b/aquadoggo/src/graphql/replication/log_id.rs index f4d52a8e5..a8aed45fb 100644 --- a/aquadoggo/src/graphql/replication/log_id.rs +++ b/aquadoggo/src/graphql/replication/log_id.rs @@ -8,6 +8,12 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct LogId(pub PandaLogId); +impl LogId { + pub fn as_u64(&self) -> u64 { + self.0.as_u64() + } +} + impl From for LogId { fn from(n: u64) -> Self { Self(PandaLogId::new(n)) diff --git a/aquadoggo/src/graphql/replication/mod.rs b/aquadoggo/src/graphql/replication/mod.rs index 06523c1d2..ac742afd1 100644 --- a/aquadoggo/src/graphql/replication/mod.rs +++ b/aquadoggo/src/graphql/replication/mod.rs @@ -1,10 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +use std::convert::TryFrom; use std::convert::TryInto; use std::marker::PhantomData; use std::sync::Arc; -use async_graphql::connection::{query, Connection, Edge, EmptyFields}; +use anyhow::Error as AnyhowError; +use async_graphql::connection::{query, Connection, CursorType, Edge, EmptyFields}; use async_graphql::Object; use async_graphql::*; use mockall_double::double; @@ -84,31 +86,38 @@ impl + Sync + Send> ReplicationRoot { sequence_number: SequenceNumber, first: Option, after: Option, - ) -> Result> { + ) -> Result> { let ctx: &Arc>> = ctx.data()?; let author: AuthorOrAlias = author.try_into()?; - query(after, None, first, None, |after, _, first, _| async move { - let start = sequence_number.as_ref().as_u64() + after.map(|a| a as u64).unwrap_or(0); - - let first = first.map(|n| n.clamp(0, 10000)).unwrap_or(10); - - let edges = ctx - .lock() - .await - .get_entries_newer_than_seq(log_id, author, sequence_number, first, start) - .await? - .into_iter() - .map(|entry| { - let decoded = decode_entry(entry.entry.as_ref(), None).unwrap(); - Edge::new(decoded.seq_num().as_u64() as usize, entry.into()) - }); - - let mut connection = Connection::new(false, start < first as u64); - - connection.append(edges); - - Result::<_, Error>::Ok(connection) - }) + query( + after, + None, + first, + None, + |after: Option, _, first, _| async move { + let start: u64 = sequence_number.as_u64() + after.map(|a| a.as_u64()).unwrap_or(0); + + let first = first.map(|n| n.clamp(0, 10000)).unwrap_or(10); + + let edges = ctx + .lock() + .await + .get_entries_newer_than_seq(log_id, author, sequence_number, first, start) + .await? + .into_iter() + .map(|entry| { + let decoded = decode_entry(entry.entry.as_ref(), None).unwrap(); + let sequence_number = SequenceNumber(decoded.seq_num().clone()); + Edge::new(sequence_number, entry.into()) + }); + + let mut connection = Connection::new(false, start < first as u64); + + connection.append(edges); + + Result::<_, Error>::Ok(connection) + }, + ) .await } @@ -146,15 +155,32 @@ impl + Sync + Send> ReplicationRoot { } } +impl CursorType for SequenceNumber { + type Error = AnyhowError; + + fn decode_cursor(s: &str) -> Result { + let num: u64 = s.parse()?; + let result = SequenceNumber::try_from(num)?; + Ok(result) + } + + fn encode_cursor(&self) -> String { + self.0.as_u64().to_string() + } +} + #[cfg(test)] mod tests { + use std::convert::TryFrom; use std::sync::Arc; - use async_graphql::{EmptyMutation, EmptySubscription, Request, Schema}; + use async_graphql::{ + connection::CursorType, EmptyMutation, EmptySubscription, Request, Schema, + }; use tokio::sync::Mutex; use super::testing::MockEntryStore; - use super::{AuthorOrAlias, ReplicationContext, ReplicationRoot}; + use super::{AuthorOrAlias, ReplicationContext, ReplicationRoot, SequenceNumber}; #[tokio::test] async fn get_entries_newer_than_seq_cursor_addition_is_ok() { @@ -165,10 +191,14 @@ mod tests { let sequence_number = 123u64; let author_string = "7cf4f58a2d89e93313f2de99604a814ecea9800cf217b140e9c3a7ba59a5d982".to_string(); - let after = 2; + + // This isn't the best because it knows that the cursor is just a stringified number. It's + // supposed to be an opaque type. + let after = SequenceNumber::try_from(sequence_number).unwrap(); + let first = 3; - let expected_start = sequence_number + after; + let expected_start = sequence_number + after.as_u64(); replication_context .expect_get_entries_newer_than_seq() @@ -182,11 +212,11 @@ mod tests { } _ => false, }; - sequence_number_.as_ref().as_u64() == sequence_number + sequence_number_.as_u64() == sequence_number && *start_ == expected_start - && log_id_.as_ref().as_u64() == log_id + && log_id_.as_u64() == log_id && author_matches - && *first_ == first as usize + && *first_ == first } }) .returning(|_, _, _, _, _| Ok(vec![])) @@ -203,7 +233,7 @@ mod tests { }} }} }}", - log_id, author_string, sequence_number, first, after + log_id, author_string, sequence_number, first, after.encode_cursor() ); let schema = Schema::build(replication_root, EmptyMutation, EmptySubscription) diff --git a/aquadoggo/src/graphql/replication/sequence_number.rs b/aquadoggo/src/graphql/replication/sequence_number.rs index 79b5910e8..6527301bc 100644 --- a/aquadoggo/src/graphql/replication/sequence_number.rs +++ b/aquadoggo/src/graphql/replication/sequence_number.rs @@ -10,6 +10,12 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct SequenceNumber(pub PandaSeqNum); +impl SequenceNumber { + pub fn as_u64(self) -> u64 { + self.0.as_u64() + } +} + impl AsRef for SequenceNumber { fn as_ref(&self) -> &PandaSeqNum { &self.0 diff --git a/aquadoggo/src/graphql/replication/testing/mod.rs b/aquadoggo/src/graphql/replication/testing/mod.rs index 2ff4ae1fa..2a59bd790 100644 --- a/aquadoggo/src/graphql/replication/testing/mod.rs +++ b/aquadoggo/src/graphql/replication/testing/mod.rs @@ -1,4 +1,3 @@ - use async_trait::async_trait; use mockall::mock; use mockall_double::double; @@ -9,7 +8,6 @@ use p2panda_rs::schema::SchemaId; use p2panda_rs::storage_provider::errors::EntryStorageError; use p2panda_rs::storage_provider::traits::EntryStore; - use crate::db::stores::StorageEntry; mock! { From 9e33ea116aced436c296271557939d8f0fc04e5f Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Sat, 28 May 2022 12:33:27 +1200 Subject: [PATCH 14/35] Add more assertions and clarify intentions of test --- aquadoggo/src/graphql/replication/mod.rs | 59 +++++++++++++++--------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/aquadoggo/src/graphql/replication/mod.rs b/aquadoggo/src/graphql/replication/mod.rs index ac742afd1..afb12ec4e 100644 --- a/aquadoggo/src/graphql/replication/mod.rs +++ b/aquadoggo/src/graphql/replication/mod.rs @@ -97,6 +97,7 @@ impl + Sync + Send> ReplicationRoot { |after: Option, _, first, _| async move { let start: u64 = sequence_number.as_u64() + after.map(|a| a.as_u64()).unwrap_or(0); + // Limit the maximum number of entries to 10k, set a default value of 10 let first = first.map(|n| n.clamp(0, 10000)).unwrap_or(10); let edges = ctx @@ -165,7 +166,7 @@ impl CursorType for SequenceNumber { } fn encode_cursor(&self) -> String { - self.0.as_u64().to_string() + self.as_u64().to_string() } } @@ -175,7 +176,7 @@ mod tests { use std::sync::Arc; use async_graphql::{ - connection::CursorType, EmptyMutation, EmptySubscription, Request, Schema, + connection::CursorType, EmptyMutation, EmptySubscription, Request, Schema, Value, }; use tokio::sync::Mutex; @@ -184,22 +185,35 @@ mod tests { #[tokio::test] async fn get_entries_newer_than_seq_cursor_addition_is_ok() { - let mut replication_context: ReplicationContext = - ReplicationContext::default(); - + // Main point of this test is make sure the cursor + sequence_number logic addition is + // correct. let log_id = 3u64; let sequence_number = 123u64; let author_string = "7cf4f58a2d89e93313f2de99604a814ecea9800cf217b140e9c3a7ba59a5d982".to_string(); - - // This isn't the best because it knows that the cursor is just a stringified number. It's - // supposed to be an opaque type. let after = SequenceNumber::try_from(sequence_number).unwrap(); + let first = 5; + let expected_start = sequence_number + after.as_u64(); - let first = 3; + let gql_query = format!( + " + query{{ + getEntriesNewerThanSeq(logId: {}, author: {{publicKey: \"{}\" }}, sequenceNumber:{}, first: {}, after: \"{}\" ){{ + pageInfo {{ + hasNextPage + }} + }} + }}", + log_id, author_string, sequence_number, first, after.encode_cursor() + ); - let expected_start = sequence_number + after.as_u64(); + let mut replication_context: ReplicationContext = + ReplicationContext::default(); + // Prepare our main assertions. + // - Checks that get_entries_newer_than_seq is called with the values we expect + // - Checks that get_entries_newer_than_seq is called once + // - Configures get_entries_newer_than_seq to return an empty Vec replication_context .expect_get_entries_newer_than_seq() .withf({ @@ -222,25 +236,24 @@ mod tests { .returning(|_, _, _, _, _| Ok(vec![])) .once(); + // Build up a schema with our mocks that can handle gql query strings let replication_root = ReplicationRoot::::new(); - - let gql_query = format!( - " - query{{ - getEntriesNewerThanSeq(logId: {}, author: {{publicKey: \"{}\" }}, sequenceNumber:{}, first: {}, after: \"{}\" ){{ - pageInfo {{ - hasNextPage - }} - }} - }}", - log_id, author_string, sequence_number, first, after.encode_cursor() - ); - let schema = Schema::build(replication_root, EmptyMutation, EmptySubscription) .data(Arc::new(Mutex::new(replication_context))) .finish(); + // Act let result = schema.execute(Request::new(gql_query)).await; + + // Assert + + // Check that we get the Ok returned from get_entries_newer_than_seq assert!(result.is_ok()); + + // The should not be a next page because we returned an empty vec from + // get_entries_newer_than_seq + let json_value = result.data.into_json().unwrap(); + let has_next_page = &json_value["getEntriesNewerThanSeq"]["pageInfo"]["hasNextPage"]; + assert!(!has_next_page.as_bool().unwrap()); } } From 4f731ee203e9cf07c6e04f4f1cf29ccadc6fc7eb Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Sat, 28 May 2022 15:11:39 +1200 Subject: [PATCH 15/35] Refactor api of get_entries_newer_than_seq --- aquadoggo/src/graphql/replication/context.rs | 8 ++--- aquadoggo/src/graphql/replication/mod.rs | 32 ++++++++++++------- .../graphql/replication/sequence_number.rs | 8 +++++ 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/aquadoggo/src/graphql/replication/context.rs b/aquadoggo/src/graphql/replication/context.rs index fb3961ab5..c334ad51b 100644 --- a/aquadoggo/src/graphql/replication/context.rs +++ b/aquadoggo/src/graphql/replication/context.rs @@ -112,14 +112,12 @@ impl> Context { log_id: LogId, author: AuthorOrAlias, sequence_number: SequenceNumber, - first: usize, - after: u64, + max_number_of_entries: usize, ) -> Result> { let author = self.get_author(author)?; - let seq_num = SeqNum::new(sequence_number.as_ref().as_u64() + after)?; let result = self .entry_store - .get_paginated_log_entries(&author.0, &log_id.0, &seq_num, first) + .get_paginated_log_entries(&author.0, &log_id.0, sequence_number.as_ref(), max_number_of_entries) .await? .into_iter() .map(|entry| entry.into()) @@ -154,6 +152,8 @@ mod tests { use p2panda_rs::identity::Author; use std::convert::TryInto; + // TODO: test author aliases + #[tokio::test] async fn entry_by_log_id_and_sequence() { let expected_log_id = 123; diff --git a/aquadoggo/src/graphql/replication/mod.rs b/aquadoggo/src/graphql/replication/mod.rs index afb12ec4e..d43c0cb30 100644 --- a/aquadoggo/src/graphql/replication/mod.rs +++ b/aquadoggo/src/graphql/replication/mod.rs @@ -95,24 +95,33 @@ impl + Sync + Send> ReplicationRoot { first, None, |after: Option, _, first, _| async move { + // Add the sequence_number to the after cursor to get the starting sequence number. let start: u64 = sequence_number.as_u64() + after.map(|a| a.as_u64()).unwrap_or(0); + let start_sequence = SequenceNumber::new(start)?; // Limit the maximum number of entries to 10k, set a default value of 10 - let first = first.map(|n| n.clamp(0, 10000)).unwrap_or(10); + let max_number_of_entries = first.map(|n| n.clamp(0, 10000)).unwrap_or(10); let edges = ctx .lock() .await - .get_entries_newer_than_seq(log_id, author, sequence_number, first, start) + .get_entries_newer_than_seq( + log_id, + author, + start_sequence, + max_number_of_entries, + ) .await? .into_iter() .map(|entry| { let decoded = decode_entry(entry.entry.as_ref(), None).unwrap(); let sequence_number = SequenceNumber(decoded.seq_num().clone()); Edge::new(sequence_number, entry.into()) - }); + }) + .collect::>(); - let mut connection = Connection::new(false, start < first as u64); + let has_next_page = edges.len() == max_number_of_entries; + let mut connection = Connection::new(false, has_next_page); connection.append(edges); @@ -176,7 +185,7 @@ mod tests { use std::sync::Arc; use async_graphql::{ - connection::CursorType, EmptyMutation, EmptySubscription, Request, Schema, Value, + connection::CursorType, EmptyMutation, EmptySubscription, Request, Schema, }; use tokio::sync::Mutex; @@ -219,21 +228,20 @@ mod tests { .withf({ let author_string = author_string.clone(); - move |log_id_, author_, sequence_number_, first_, start_| { + move |log_id_, author_, sequence_number_, max_number_of_entries_| { let author_matches = match author_ { AuthorOrAlias::PublicKey(public_key) => { public_key.0.as_str() == author_string } _ => false, }; - sequence_number_.as_u64() == sequence_number - && *start_ == expected_start + sequence_number_.as_u64() == expected_start && log_id_.as_u64() == log_id && author_matches - && *first_ == first + && *max_number_of_entries_ == first } }) - .returning(|_, _, _, _, _| Ok(vec![])) + .returning(|_, _, _, _| Ok(vec![])) .once(); // Build up a schema with our mocks that can handle gql query strings @@ -250,8 +258,8 @@ mod tests { // Check that we get the Ok returned from get_entries_newer_than_seq assert!(result.is_ok()); - // The should not be a next page because we returned an empty vec from - // get_entries_newer_than_seq + // There should not be a next page because we returned an empty vec from + // get_entries_newer_than_seq and it's length is not == `first` == 5; let json_value = result.data.into_json().unwrap(); let has_next_page = &json_value["getEntriesNewerThanSeq"]["pageInfo"]["hasNextPage"]; assert!(!has_next_page.as_bool().unwrap()); diff --git a/aquadoggo/src/graphql/replication/sequence_number.rs b/aquadoggo/src/graphql/replication/sequence_number.rs index 6527301bc..5abba1e56 100644 --- a/aquadoggo/src/graphql/replication/sequence_number.rs +++ b/aquadoggo/src/graphql/replication/sequence_number.rs @@ -3,6 +3,7 @@ use std::convert::TryFrom; use async_graphql::*; +use anyhow::Result; use p2panda_rs::entry::{SeqNum as PandaSeqNum, SeqNumError}; use serde::{Deserialize, Serialize}; @@ -10,6 +11,13 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct SequenceNumber(pub PandaSeqNum); +impl SequenceNumber{ + pub fn new(seq: u64) -> Result{ + let panda_seq_num = PandaSeqNum::new(seq)?; + Ok(Self(panda_seq_num)) + } +} + impl SequenceNumber { pub fn as_u64(self) -> u64 { self.0.as_u64() From 4f47a00df96eb01404cc97b2b22a3923b99c5e3e Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Sat, 28 May 2022 15:17:24 +1200 Subject: [PATCH 16/35] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c8d4d4d1..909b49808 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implement new methods required for replication defined by `EntryStore` trait [#102](https://github.com/p2panda/aquadoggo/pull/102) - GraphQL client API with endpoint for retrieving next entry arguments [#119](https://github.com/p2panda/aquadoggo/pull/119) - GraphQL endpoint for publishing entries [#123](https://github.com/p2panda/aquadoggo/pull/132) +- GraphQL endpoints for replication ### Changed From 6a351c50134ee45e991e835e49780f32f4b16c6a Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Sat, 28 May 2022 15:21:46 +1200 Subject: [PATCH 17/35] Regenerate gql schema --- aquadoggo/src/graphql/replication/client/schema.graphql | 1 - 1 file changed, 1 deletion(-) diff --git a/aquadoggo/src/graphql/replication/client/schema.graphql b/aquadoggo/src/graphql/replication/client/schema.graphql index 1fd79220e..92510267c 100644 --- a/aquadoggo/src/graphql/replication/client/schema.graphql +++ b/aquadoggo/src/graphql/replication/client/schema.graphql @@ -110,7 +110,6 @@ type PublishEntryResponse { All of the graphql query sub modules merged into one top level root """ type QueryRoot { - ping: String! """ Get an entry by its hash """ From 2db338b2b9eb4a7a2f27235bc8fd4580dac17a98 Mon Sep 17 00:00:00 2001 From: Andreas Dzialocha Date: Tue, 31 May 2022 19:46:31 +0200 Subject: [PATCH 18/35] Run cargo fmt --- aquadoggo/src/graphql/replication/sequence_number.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aquadoggo/src/graphql/replication/sequence_number.rs b/aquadoggo/src/graphql/replication/sequence_number.rs index 5abba1e56..8d6cfc5f8 100644 --- a/aquadoggo/src/graphql/replication/sequence_number.rs +++ b/aquadoggo/src/graphql/replication/sequence_number.rs @@ -2,8 +2,8 @@ use std::convert::TryFrom; -use async_graphql::*; use anyhow::Result; +use async_graphql::*; use p2panda_rs::entry::{SeqNum as PandaSeqNum, SeqNumError}; use serde::{Deserialize, Serialize}; @@ -11,8 +11,8 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct SequenceNumber(pub PandaSeqNum); -impl SequenceNumber{ - pub fn new(seq: u64) -> Result{ +impl SequenceNumber { + pub fn new(seq: u64) -> Result { let panda_seq_num = PandaSeqNum::new(seq)?; Ok(Self(panda_seq_num)) } From 72d6097f98f3839e14d21dff862adad61348c8ff Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Fri, 3 Jun 2022 14:21:34 +1200 Subject: [PATCH 19/35] Remove mockall as dev dep --- aquadoggo/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/aquadoggo/Cargo.toml b/aquadoggo/Cargo.toml index b4ff05d90..dab092df6 100644 --- a/aquadoggo/Cargo.toml +++ b/aquadoggo/Cargo.toml @@ -65,7 +65,6 @@ futures = "0.3.21" [dev-dependencies] hyper = "0.14.17" http = "0.2.6" -mockall = "0.11.0" reqwest = { version = "0.11.9", default-features = false, features = [ "json", "stream", From d68e41800796acc78307896125d92401d09cb2da Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Fri, 3 Jun 2022 14:22:38 +1200 Subject: [PATCH 20/35] Add licence header to dump_schema --- aquadoggo/src/bin/dump_gql_schema.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aquadoggo/src/bin/dump_gql_schema.rs b/aquadoggo/src/bin/dump_gql_schema.rs index 5bf1409f5..c847dc8fd 100644 --- a/aquadoggo/src/bin/dump_gql_schema.rs +++ b/aquadoggo/src/bin/dump_gql_schema.rs @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + use aquadoggo::db::connection_pool; use aquadoggo::graphql::{build_root_schema, Context}; From 3f6eb72fed6cdf870d4c25f2900db4e7fd468bcb Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Fri, 3 Jun 2022 14:24:06 +1200 Subject: [PATCH 21/35] Make aquadoggo_cli the default workspace member --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index fc24a6d0e..44bb99af1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,3 +3,5 @@ members = [ "aquadoggo", "aquadoggo_cli", ] + +default-members = ["aquadoggo_cli"] From 5c274c2bb97faf02a90a099321e7ac471cff878b Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Fri, 3 Jun 2022 14:25:09 +1200 Subject: [PATCH 22/35] Add PR link to changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fb71d98c..e3dd963fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implement SQL `OperationStore` [#103](https://github.com/p2panda/aquadoggo/pull/103) - GraphQL client API with endpoint for retrieving next entry arguments [#119](https://github.com/p2panda/aquadoggo/pull/119) - GraphQL endpoint for publishing entries [#123](https://github.com/p2panda/aquadoggo/pull/132) -- GraphQL endpoints for replication +- GraphQL endpoints for replication [#100](https://github.com/p2panda/aquadoggo/pull/100) ### Changed From 40201efa6080976b3b7965520fce8f9da4b10c89 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Fri, 3 Jun 2022 14:27:06 +1200 Subject: [PATCH 23/35] Fix import order of tests --- aquadoggo/src/graphql/replication/context.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/aquadoggo/src/graphql/replication/context.rs b/aquadoggo/src/graphql/replication/context.rs index 7087c89b6..71c6531ce 100644 --- a/aquadoggo/src/graphql/replication/context.rs +++ b/aquadoggo/src/graphql/replication/context.rs @@ -148,14 +148,16 @@ impl> Context { #[cfg(test)] mod tests { + use std::convert::TryInto; + + use p2panda_rs::identity::Author; - use super::super::testing::MockEntryStore; - use super::Context; use crate::graphql::replication::{ Author as GraphQLAuthor, LogId as GraphQLLogId, PublicKey, SequenceNumber, }; - use p2panda_rs::identity::Author; - use std::convert::TryInto; + + use super::super::testing::MockEntryStore; + use super::Context; // TODO: test author aliases From b1331bd7812e85adb9a40c12c1a491e60882e42c Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Fri, 3 Jun 2022 14:34:08 +1200 Subject: [PATCH 24/35] Add docstring to the `AliasedAuthor` type. --- aquadoggo/src/graphql/replication/aliased_author.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/aquadoggo/src/graphql/replication/aliased_author.rs b/aquadoggo/src/graphql/replication/aliased_author.rs index 6b4310b95..4dd08ddca 100644 --- a/aquadoggo/src/graphql/replication/aliased_author.rs +++ b/aquadoggo/src/graphql/replication/aliased_author.rs @@ -4,6 +4,14 @@ use async_graphql::*; use super::public_key::PublicKey; +/// AliasedAuthor is one of either the public_key or an alias +/// +/// The intention of this is to reduce bandwidth when making requests by using a short "alias" +/// rather than the full author public_key +/// +/// To get an alias of an author, use the `author_aliases` method which will return this type. +/// +/// When using as an input to a query, exactly one of public_key or alias must be set otherwise it is an error. #[derive(Debug, InputObject, SimpleObject)] pub struct AliasedAuthor { /// The author's public key From d04edad3b740defb44dd9fd6fa6f181bd58b1cb1 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Fri, 3 Jun 2022 14:37:27 +1200 Subject: [PATCH 25/35] Fix naming of param in SingleEntryAndPayload --- .../src/graphql/replication/single_entry_and_payload.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aquadoggo/src/graphql/replication/single_entry_and_payload.rs b/aquadoggo/src/graphql/replication/single_entry_and_payload.rs index 8eca34db9..0c875dabc 100644 --- a/aquadoggo/src/graphql/replication/single_entry_and_payload.rs +++ b/aquadoggo/src/graphql/replication/single_entry_and_payload.rs @@ -43,9 +43,9 @@ impl SingleEntryAndPayload { } impl From for SingleEntryAndPayload { - fn from(entry_row: StorageEntry) -> Self { - let entry = Entry(entry_row.entry_signed().to_owned()); - let payload = entry_row + fn from(entry_and_payload: StorageEntry) -> Self { + let entry = Entry(entry_and_payload.entry_signed().to_owned()); + let payload = entry_and_payload .operation_encoded() .map(|encoded| Payload(encoded.to_owned())); Self { entry, payload } From 3d514e5f8d0f4f01c1f30a4d5a9d2aa1cb7497e6 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Fri, 3 Jun 2022 14:54:50 +1200 Subject: [PATCH 26/35] Add rt-multi-thread tokio feature to fix build of dump_schema --- aquadoggo/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/aquadoggo/Cargo.toml b/aquadoggo/Cargo.toml index dab092df6..5068f5645 100644 --- a/aquadoggo/Cargo.toml +++ b/aquadoggo/Cargo.toml @@ -53,6 +53,7 @@ tokio = { version = "1.17.0", features = [ "macros", "net", "rt", + "rt-multi-thread", "sync", "time", ] } From 1328da6032b83baaf98bd5b974c0148446a50d2e Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Fri, 3 Jun 2022 15:16:48 +1200 Subject: [PATCH 27/35] clippy --fix --- aquadoggo/src/graphql/replication/context.rs | 4 +-- aquadoggo/src/graphql/replication/mod.rs | 4 +-- .../src/graphql/replication/testing/mod.rs | 2 +- aquadoggo/src/materializer/worker.rs | 27 ++++++++----------- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/aquadoggo/src/graphql/replication/context.rs b/aquadoggo/src/graphql/replication/context.rs index 71c6531ce..469cba6ca 100644 --- a/aquadoggo/src/graphql/replication/context.rs +++ b/aquadoggo/src/graphql/replication/context.rs @@ -57,7 +57,7 @@ impl> Context { ) -> Result>> { ids.into_iter() .map(|id| { - let result = self.author_aliases.get(&id).map(|key| key.clone()); + let result = self.author_aliases.get(&id).cloned(); Ok(result) }) .collect() @@ -140,7 +140,7 @@ impl> Context { "author alias did not exist, you may need to re-alias your authors" ))? .clone(), - AuthorOrAlias::PublicKey(public_key) => public_key.clone(), + AuthorOrAlias::PublicKey(public_key) => public_key, }; Ok(author) } diff --git a/aquadoggo/src/graphql/replication/mod.rs b/aquadoggo/src/graphql/replication/mod.rs index d43c0cb30..f1e294bf6 100644 --- a/aquadoggo/src/graphql/replication/mod.rs +++ b/aquadoggo/src/graphql/replication/mod.rs @@ -115,8 +115,8 @@ impl + Sync + Send> ReplicationRoot { .into_iter() .map(|entry| { let decoded = decode_entry(entry.entry.as_ref(), None).unwrap(); - let sequence_number = SequenceNumber(decoded.seq_num().clone()); - Edge::new(sequence_number, entry.into()) + let sequence_number = SequenceNumber(*decoded.seq_num()); + Edge::new(sequence_number, entry) }) .collect::>(); diff --git a/aquadoggo/src/graphql/replication/testing/mod.rs b/aquadoggo/src/graphql/replication/testing/mod.rs index 2a59bd790..08ea673a8 100644 --- a/aquadoggo/src/graphql/replication/testing/mod.rs +++ b/aquadoggo/src/graphql/replication/testing/mod.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; use mockall::mock; -use mockall_double::double; + use p2panda_rs::entry::{LogId, SeqNum}; use p2panda_rs::hash::Hash; use p2panda_rs::identity::Author; diff --git a/aquadoggo/src/materializer/worker.rs b/aquadoggo/src/materializer/worker.rs index b4b8bc847..0549ec564 100644 --- a/aquadoggo/src/materializer/worker.rs +++ b/aquadoggo/src/materializer/worker.rs @@ -560,10 +560,7 @@ mod tests { let tasks: Vec> = input .relations .iter() - .filter_map(|id| match db.pieces.get(&id) { - Some(piece) => Some(Task::new("find", piece.clone())), - None => None, - }) + .filter_map(|id| db.pieces.get(id).map(|piece| Task::new("find", piece.clone()))) .collect(); Ok(Some(tasks)) @@ -585,7 +582,7 @@ mod tests { // Add another piece to list of ids. Unwrap as we know the list is not empty. let id = candidates.pop().unwrap(); - ids.push(id.clone()); + ids.push(id); // Get all related pieces of this piece match db.pieces.get(&id) { @@ -594,7 +591,7 @@ mod tests { // Check if we have already visited all relations of this piece, // otherwise add them to list if !ids.contains(relation_id) && !candidates.contains(relation_id) { - candidates.push(relation_id.clone()); + candidates.push(*relation_id); } } } @@ -611,7 +608,7 @@ mod tests { // of them as the future puzzle! if puzzle_id.is_none() { for id in &ids { - if puzzle.piece_ids.contains(&id) { + if puzzle.piece_ids.contains(id) { puzzle_id = Some(puzzle.id); } } @@ -659,19 +656,18 @@ mod tests { let puzzle: Option = db .puzzles .values() - .find(|item| item.piece_ids.contains(&input.id) && !item.complete) - .map(|item| item.clone()); + .find(|item| item.piece_ids.contains(&input.id) && !item.complete).cloned(); // 2. Check if all piece dependencies are met match puzzle { None => Err(TaskError::Failure), Some(mut puzzle) => { for piece_id in &puzzle.piece_ids { - match db.pieces.get(&piece_id) { + match db.pieces.get(piece_id) { None => return Err(TaskError::Failure), Some(piece) => { for relation_piece_id in &piece.relations { - if !puzzle.piece_ids.contains(&relation_piece_id) { + if !puzzle.piece_ids.contains(relation_piece_id) { return Err(TaskError::Failure); } } @@ -681,7 +677,7 @@ mod tests { // Mark puzzle as complete! We are done here! puzzle.complete = true; - db.puzzles.insert(puzzle.id, puzzle.clone()); + db.puzzles.insert(puzzle.id, puzzle); Ok(None) } } @@ -730,7 +726,7 @@ mod tests { for _ in 0..size { let mut relations: Vec = Vec::new(); - id = id + 1; + id += 1; if id % size != 0 { // Add related piece to the right @@ -759,7 +755,7 @@ mod tests { } } - offset = offset + (size * size); + offset += size * size; } // Mix all puzzle pieces to a large chaotic pile @@ -780,8 +776,7 @@ mod tests { .unwrap() .puzzles .values() - .filter(|puzzle| puzzle.complete) - .map(|puzzle| puzzle.clone()) + .filter(|puzzle| puzzle.complete).cloned() .collect(); assert_eq!(completed.len(), puzzles_count); } From 6e680d199cb91e396653f5a6d87f0c30dcbf097c Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Fri, 3 Jun 2022 15:29:12 +1200 Subject: [PATCH 28/35] Fix a clipp warn --- aquadoggo/src/graphql/replication/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aquadoggo/src/graphql/replication/context.rs b/aquadoggo/src/graphql/replication/context.rs index 469cba6ca..ab0cc79fe 100644 --- a/aquadoggo/src/graphql/replication/context.rs +++ b/aquadoggo/src/graphql/replication/context.rs @@ -136,7 +136,7 @@ impl> Context { AuthorOrAlias::Alias(alias) => self .author_aliases .get(&alias) - .ok_or(anyhow!( + .ok_or_else(|| anyhow!( "author alias did not exist, you may need to re-alias your authors" ))? .clone(), From a02c5c7620103aa04e04575f2cbd93aa2c98c783 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 7 Jun 2022 19:51:07 +1200 Subject: [PATCH 29/35] Fix typo --- aquadoggo/src/graphql/replication/entry_and_payload.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aquadoggo/src/graphql/replication/entry_and_payload.rs b/aquadoggo/src/graphql/replication/entry_and_payload.rs index fe17457be..124683a0d 100644 --- a/aquadoggo/src/graphql/replication/entry_and_payload.rs +++ b/aquadoggo/src/graphql/replication/entry_and_payload.rs @@ -7,7 +7,7 @@ use crate::db::stores::StorageEntry; use super::payload::Payload; use super::Entry; -/// An and entry with an optional payload +/// An entry with an optional payload #[derive(SimpleObject, Debug)] pub struct EntryAndPayload { /// Get the entry From e1955935011e75b68b69bd7c075283661fb3fa03 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 7 Jun 2022 20:01:06 +1200 Subject: [PATCH 30/35] Use cafca's wording for error message. --- aquadoggo/src/graphql/replication/author.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aquadoggo/src/graphql/replication/author.rs b/aquadoggo/src/graphql/replication/author.rs index 893a50844..888533881 100644 --- a/aquadoggo/src/graphql/replication/author.rs +++ b/aquadoggo/src/graphql/replication/author.rs @@ -30,7 +30,7 @@ impl TryFrom for AuthorOrAlias { (Some(key), None) => Ok(AuthorOrAlias::PublicKey(key)), (None, Some(alias)) => Ok(AuthorOrAlias::Alias(alias)), _ => Err(anyhow!( - "Author must have only one of public_key or alias set" + "Author must have either publicKey or alias set, but not both" )), } } From f710d7ac7dce4cc04f73ccb936cb4f821bebf3cf Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 7 Jun 2022 20:10:29 +1200 Subject: [PATCH 31/35] Rename `skiplinks` to `certificate_pool` and regen schema --- .../graphql/replication/client/schema.graphql | 16 +++++++++++++--- .../replication/single_entry_and_payload.rs | 6 +++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/aquadoggo/src/graphql/replication/client/schema.graphql b/aquadoggo/src/graphql/replication/client/schema.graphql index 92510267c..d9cc861ae 100644 --- a/aquadoggo/src/graphql/replication/client/schema.graphql +++ b/aquadoggo/src/graphql/replication/client/schema.graphql @@ -1,3 +1,13 @@ +""" +AliasedAuthor is one of either the public_key or an alias + +The intention of this is to reduce bandwidth when making requests by using a short "alias" +rather than the full author public_key + +To get an alias of an author, use the `author_aliases` method which will return this type. + +When using as an input to a query, exactly one of public_key or alias must be set otherwise it is an error. +""" type AliasedAuthor { """ The author's public key @@ -23,7 +33,7 @@ input Author { } scalar Entry """ -An and entry with an optional payload +An entry with an optional payload """ type EntryAndPayload { """ @@ -144,9 +154,9 @@ type SingleEntryAndPayload { """ payload: Payload """ - Get all the skiplinks for this entry that are required to verify the entry is valid + Get the certificate pool for this entry that can be used to verify the entry is valid """ - skiplinks: [Entry!]! + certificatePool: [Entry!]! } schema { query: QueryRoot diff --git a/aquadoggo/src/graphql/replication/single_entry_and_payload.rs b/aquadoggo/src/graphql/replication/single_entry_and_payload.rs index 0c875dabc..ca6483dd8 100644 --- a/aquadoggo/src/graphql/replication/single_entry_and_payload.rs +++ b/aquadoggo/src/graphql/replication/single_entry_and_payload.rs @@ -8,7 +8,7 @@ use crate::graphql::Context as GraphQLContext; use super::payload::Payload; use super::Entry; -/// A p2panda entry with optional payload and the collection of skiplinks required to verify it. +/// A p2panda entry with optional payload and the certificate pool required to verify it. #[derive(Debug)] pub struct SingleEntryAndPayload { pub entry: Entry, @@ -27,8 +27,8 @@ impl SingleEntryAndPayload { self.payload.as_ref() } - /// Get all the skiplinks for this entry that are required to verify the entry is valid - async fn skiplinks<'a>(&self, ctx: &Context<'a>) -> Result> { + /// Get the certificate pool for this entry that can be used to verify the entry is valid + async fn certificate_pool<'a>(&self, ctx: &Context<'a>) -> Result> { let ctx: &GraphQLContext = ctx.data()?; let result = ctx From 950aec84a5b96fa3400b5501201fc324ec69c2be Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 7 Jun 2022 20:23:04 +1200 Subject: [PATCH 32/35] Add better instructions for regenerating the schema file. --- aquadoggo/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/aquadoggo/README.md b/aquadoggo/README.md index 97001cf96..58771b673 100644 --- a/aquadoggo/README.md +++ b/aquadoggo/README.md @@ -82,10 +82,15 @@ $ cargo add aquadoggo When you update the graphql code you'll need to manually regenerate the graphql schema files. +In the `aquadoggo/aquadoggo` directory: + ```sh -cargo run --bin dump_gql_schema > aquadoggo/src/graphql/replication/client/schema.graphql +cargo build --bin dump_gql_schema +cargo run --bin dump_gql_schema > src/graphql/replication/client/schema.graphql ``` +Note that the `cargo build` step is required first because the `>` in the run step truncates the schema file which is used for code generation. + ## License GNU Affero General Public License v3.0 [`AGPL-3.0-or-later`](LICENSE) From bd383fbf404f7cd9627e34345d3dc53603eda274 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 7 Jun 2022 20:36:43 +1200 Subject: [PATCH 33/35] Rename `ES` generic type param to `EntryStore` --- aquadoggo/src/graphql/replication/context.rs | 10 +++++----- aquadoggo/src/graphql/replication/mod.rs | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/aquadoggo/src/graphql/replication/context.rs b/aquadoggo/src/graphql/replication/context.rs index b6d4fb8dd..a122ef429 100644 --- a/aquadoggo/src/graphql/replication/context.rs +++ b/aquadoggo/src/graphql/replication/context.rs @@ -5,7 +5,7 @@ use async_graphql::ID; use lru::LruCache; use mockall::automock; use p2panda_rs::entry::decode_entry; -use p2panda_rs::storage_provider::traits::EntryStore; +use p2panda_rs::storage_provider::traits::EntryStore as EntryStoreTrait; use crate::db::stores::StorageEntry; @@ -20,15 +20,15 @@ use super::SequenceNumber; use super::SingleEntryAndPayload; #[derive(Debug)] -pub struct Context> { +pub struct Context> { author_aliases: LruCache, next_alias: usize, - entry_store: ES, + entry_store: EntryStore, } #[automock] -impl> Context { - pub fn new(author_aliases_cache_size: usize, entry_store: ES) -> Self { +impl> Context { + pub fn new(author_aliases_cache_size: usize, entry_store: EntryStore) -> Self { Self { author_aliases: LruCache::new(author_aliases_cache_size), next_alias: Default::default(), diff --git a/aquadoggo/src/graphql/replication/mod.rs b/aquadoggo/src/graphql/replication/mod.rs index f1e294bf6..282c3b4ed 100644 --- a/aquadoggo/src/graphql/replication/mod.rs +++ b/aquadoggo/src/graphql/replication/mod.rs @@ -11,7 +11,7 @@ use async_graphql::Object; use async_graphql::*; use mockall_double::double; use p2panda_rs::entry::decode_entry; -use p2panda_rs::storage_provider::traits::EntryStore; +use p2panda_rs::storage_provider::traits::EntryStore as EntryStoreTrait; use tokio::sync::Mutex; use crate::db::stores::StorageEntry; @@ -48,11 +48,11 @@ pub use single_entry_and_payload::SingleEntryAndPayload; #[derive(Debug)] /// The root graphql object for replication -pub struct ReplicationRoot { - entry_store: PhantomData, +pub struct ReplicationRoot { + entry_store: PhantomData, } -impl ReplicationRoot { +impl ReplicationRoot { /// Create a new ReplicationRoot pub fn new() -> Self { Self { @@ -62,14 +62,14 @@ impl ReplicationRoot { } #[Object] -impl + Sync + Send> ReplicationRoot { +impl + Sync + Send> ReplicationRoot { /// Get an entry by its hash async fn entry_by_hash<'a>( &self, ctx: &Context<'a>, hash: EntryHash, ) -> Result> { - let ctx: &Arc>> = ctx.data()?; + let ctx: &Arc>> = ctx.data()?; let result = ctx.lock().await.entry_by_hash(hash).await?; @@ -87,7 +87,7 @@ impl + Sync + Send> ReplicationRoot { first: Option, after: Option, ) -> Result> { - let ctx: &Arc>> = ctx.data()?; + let ctx: &Arc>> = ctx.data()?; let author: AuthorOrAlias = author.try_into()?; query( after, @@ -139,7 +139,7 @@ impl + Sync + Send> ReplicationRoot { sequence_number: SequenceNumber, author: Author, ) -> Result> { - let ctx: &Arc>> = ctx.data()?; + let ctx: &Arc>> = ctx.data()?; let author: AuthorOrAlias = author.try_into()?; let result = ctx .lock() @@ -158,7 +158,7 @@ impl + Sync + Send> ReplicationRoot { ctx: &Context<'a>, public_keys: Vec, ) -> Result> { - let ctx: &Arc>> = ctx.data()?; + let ctx: &Arc>> = ctx.data()?; let result = ctx.lock().await.insert_author_aliases(public_keys); Ok(result) From e4ad9f233849bdb537bf4910ea162ec52c56dce3 Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Tue, 7 Jun 2022 20:40:15 +1200 Subject: [PATCH 34/35] fmt --- aquadoggo/src/graphql/replication/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aquadoggo/src/graphql/replication/mod.rs b/aquadoggo/src/graphql/replication/mod.rs index 282c3b4ed..cfe7a2578 100644 --- a/aquadoggo/src/graphql/replication/mod.rs +++ b/aquadoggo/src/graphql/replication/mod.rs @@ -62,7 +62,9 @@ impl ReplicationRoot { } #[Object] -impl + Sync + Send> ReplicationRoot { +impl + Sync + Send> + ReplicationRoot +{ /// Get an entry by its hash async fn entry_by_hash<'a>( &self, From c5123aa7e7df6daa0a085ede74301dd7e07dad0d Mon Sep 17 00:00:00 2001 From: Piet Geursen Date: Sat, 11 Jun 2022 20:33:03 +1200 Subject: [PATCH 35/35] Remove clippy warnings --- aquadoggo/src/bin/dump_gql_schema.rs | 2 +- aquadoggo/src/context.rs | 14 +++++++++++++- aquadoggo/src/db/provider.rs | 2 ++ aquadoggo/src/db/stores/log.rs | 1 + aquadoggo/src/graphql/client/request.rs | 4 ++++ aquadoggo/src/graphql/client/response.rs | 8 ++++++++ aquadoggo/src/graphql/mod.rs | 1 + aquadoggo/src/graphql/replication/context.rs | 1 + aquadoggo/src/graphql/replication/mod.rs | 2 +- aquadoggo/src/lib.rs | 4 +++- 10 files changed, 35 insertions(+), 4 deletions(-) diff --git a/aquadoggo/src/bin/dump_gql_schema.rs b/aquadoggo/src/bin/dump_gql_schema.rs index a067e64d0..44196833a 100644 --- a/aquadoggo/src/bin/dump_gql_schema.rs +++ b/aquadoggo/src/bin/dump_gql_schema.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -use aquadoggo::db::{connection_pool, provider::SqlStorage}; use aquadoggo::graphql::{build_root_schema, Context}; +use aquadoggo::{connection_pool, SqlStorage}; #[tokio::main] async fn main() { diff --git a/aquadoggo/src/context.rs b/aquadoggo/src/context.rs index e0eb3467c..de497730d 100644 --- a/aquadoggo/src/context.rs +++ b/aquadoggo/src/context.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +use std::fmt::Debug; use std::ops::Deref; use std::sync::Arc; @@ -10,7 +11,7 @@ use crate::graphql::{build_root_schema, RootSchema}; /// Inner data shared across all services. pub struct Data { - // Node configuration. + /// Node configuration. pub config: Configuration, /// Storage provider with database connection pool. @@ -20,6 +21,16 @@ pub struct Data { pub schema: RootSchema, } +impl Debug for Data { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + // Omit the the schema field for now, it doesn't implement debug. Some debug info is better than none. + fmt.debug_struct("Data") + .field("config", &self.config) + .field("store", &self.store) + .finish() + } +} + impl Data { pub fn new(store: SqlStorage, config: Configuration) -> Self { let graphql_context = GraphQLContext::new(store.clone()); @@ -34,6 +45,7 @@ impl Data { } /// Data shared across all services. +#[derive(Debug)] pub struct Context(pub Arc); impl Context { diff --git a/aquadoggo/src/db/provider.rs b/aquadoggo/src/db/provider.rs index db572fe79..8c40bfef8 100644 --- a/aquadoggo/src/db/provider.rs +++ b/aquadoggo/src/db/provider.rs @@ -15,11 +15,13 @@ use crate::graphql::client::{ }; #[derive(Debug, Clone)] +/// Sql based storage that implements `StorageProvider` pub struct SqlStorage { pub(crate) pool: Pool, } impl SqlStorage { + /// Create a new `SqlStorage` using the provided db `Pool` pub fn new(pool: Pool) -> Self { Self { pool } } diff --git a/aquadoggo/src/db/stores/log.rs b/aquadoggo/src/db/stores/log.rs index 5481ad432..f7364e19b 100644 --- a/aquadoggo/src/db/stores/log.rs +++ b/aquadoggo/src/db/stores/log.rs @@ -18,6 +18,7 @@ use crate::db::provider::SqlStorage; /// this data according to what it sees in the newly incoming entries. /// /// `StorageLog` implements the trait `AsStorageLog` which is required when defining a `LogStore`. +#[derive(Debug)] pub struct StorageLog { author: Author, log_id: LogId, diff --git a/aquadoggo/src/graphql/client/request.rs b/aquadoggo/src/graphql/client/request.rs index f026770dc..a7fd21301 100644 --- a/aquadoggo/src/graphql/client/request.rs +++ b/aquadoggo/src/graphql/client/request.rs @@ -14,7 +14,9 @@ use p2panda_rs::operation::OperationEncoded; /// Request body of `panda_getEntryArguments`. #[derive(Deserialize, Debug)] pub struct EntryArgsRequest { + /// The entry author pub author: Author, + /// The entry document pub document: Option, } @@ -50,7 +52,9 @@ impl Validate for EntryArgsRequest { #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct PublishEntryRequest { + /// The encoded entry pub entry_encoded: EntrySigned, + /// The encoded operation pub operation_encoded: OperationEncoded, } diff --git a/aquadoggo/src/graphql/client/response.rs b/aquadoggo/src/graphql/client/response.rs index 7b5538939..3481ca33f 100644 --- a/aquadoggo/src/graphql/client/response.rs +++ b/aquadoggo/src/graphql/client/response.rs @@ -15,14 +15,18 @@ use crate::db::models::EntryRow; #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct EntryArgsResponse { + /// The log id of the entry #[serde(with = "super::u64_string::log_id_string_serialisation")] pub log_id: LogId, + /// The sequence number of the entry #[serde(with = "super::u64_string::seq_num_string_serialisation")] pub seq_num: SeqNum, + /// The hash of the entry backlink pub backlink: Option, + /// The hash of the entry skiplink pub skiplink: Option, } @@ -64,14 +68,18 @@ impl AsEntryArgsResponse for EntryArgsResponse { #[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct PublishEntryResponse { + /// The log id of the entry #[serde(with = "super::u64_string::log_id_string_serialisation")] pub log_id: LogId, + /// The sequence number of the entry #[serde(with = "super::u64_string::seq_num_string_serialisation")] pub seq_num: SeqNum, + /// The optional hash of the backlink pub backlink: Option, + /// The optional hash of the skiplink pub skiplink: Option, } diff --git a/aquadoggo/src/graphql/mod.rs b/aquadoggo/src/graphql/mod.rs index 6436bd355..034b9e170 100644 --- a/aquadoggo/src/graphql/mod.rs +++ b/aquadoggo/src/graphql/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later mod api; +/// Graphql types for clients pub mod client; mod context; mod replication; diff --git a/aquadoggo/src/graphql/replication/context.rs b/aquadoggo/src/graphql/replication/context.rs index a122ef429..6ad827733 100644 --- a/aquadoggo/src/graphql/replication/context.rs +++ b/aquadoggo/src/graphql/replication/context.rs @@ -27,6 +27,7 @@ pub struct Context> { } #[automock] +#[allow(dead_code)] impl> Context { pub fn new(author_aliases_cache_size: usize, entry_store: EntryStore) -> Self { Self { diff --git a/aquadoggo/src/graphql/replication/mod.rs b/aquadoggo/src/graphql/replication/mod.rs index cfe7a2578..0029e22ab 100644 --- a/aquadoggo/src/graphql/replication/mod.rs +++ b/aquadoggo/src/graphql/replication/mod.rs @@ -46,7 +46,7 @@ pub use public_key::PublicKey; pub use sequence_number::SequenceNumber; pub use single_entry_and_payload::SingleEntryAndPayload; -#[derive(Debug)] +#[derive(Debug, Default)] /// The root graphql object for replication pub struct ReplicationRoot { entry_store: PhantomData, diff --git a/aquadoggo/src/lib.rs b/aquadoggo/src/lib.rs index b8fa1e3ec..81e8e86e2 100644 --- a/aquadoggo/src/lib.rs +++ b/aquadoggo/src/lib.rs @@ -16,8 +16,9 @@ mod bus; mod config; mod context; -pub mod db; +mod db; mod errors; +/// Aquadoggo graphql types for handling client and replication requests pub mod graphql; mod manager; mod materializer; @@ -28,4 +29,5 @@ mod server; mod test_helpers; pub use config::Configuration; +pub use db::{connection_pool, provider::SqlStorage}; pub use node::Node;