From 6c1a5fbd5b4f8c4e7132c5156be447f7d2204e51 Mon Sep 17 00:00:00 2001 From: henry0715-dev Date: Thu, 28 Nov 2024 18:16:44 +0900 Subject: [PATCH] Update GraphQL cursor encoding/decoding to use OpaqueCursor Close #355 The following sources are the main areas that have been modified. The rest of the changes are fixes for Clippy errors caused by the Rust version update. graphql.rs graphql directory sub-source --- CHANGELOG.md | 6 +++ src/archive.rs | 6 +-- src/graphql.rs | 82 +++++++++++++------------------ src/graphql/account.rs | 33 +++++++++---- src/graphql/allow_network.rs | 10 ++-- src/graphql/block_network.rs | 10 ++-- src/graphql/category.rs | 9 ++-- src/graphql/customer.rs | 19 +++---- src/graphql/data_source.rs | 10 ++-- src/graphql/filter.rs | 12 ++--- src/graphql/network.rs | 9 ++-- src/graphql/node.rs | 2 +- src/graphql/node/crud.rs | 9 ++-- src/graphql/node/status.rs | 29 ++++++----- src/graphql/outlier.rs | 70 ++++++++++++++++---------- src/graphql/qualifier.rs | 10 ++-- src/graphql/sampling.rs | 12 +++-- src/graphql/statistics.rs | 12 +++-- src/graphql/status.rs | 9 ++-- src/graphql/template.rs | 9 ++-- src/graphql/tor_exit_node.rs | 10 ++-- src/graphql/triage/policy.rs | 10 ++-- src/graphql/triage/response.rs | 12 +++-- src/graphql/trusted_domain.rs | 9 ++-- src/graphql/trusted_user_agent.rs | 18 +++++-- src/lib.rs | 2 +- 26 files changed, 250 insertions(+), 179 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e4c58b1..243859f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + +- Updated the encoding and decoding method for GraphQL cursors by removing + `graphql::encode_cursor` and `graphql::decode_cursor` methods and replacing + them with the encoding and decoding methods of `OpaqueCursor`. + ### Fixed - Resolved an issue in the `applyNode` GraphQL API, where configuration values diff --git a/src/archive.rs b/src/archive.rs index 0c77f4e5..fd802f4e 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -48,7 +48,7 @@ impl Config { pub(crate) fn configure_reverse_proxies( store: &Arc>, - client: &Option, + client: Option<&reqwest::Client>, reverse_proxies: &[Self], ) -> Vec<(ArchiveState, Router)> { reverse_proxies @@ -57,10 +57,10 @@ impl Config { ( crate::archive::ArchiveState { store: store.clone(), - client: client.clone(), + client: client.cloned(), config: rp.clone(), }, - crate::archive::reverse_proxy(store.clone(), client.clone(), rp.clone()), + crate::archive::reverse_proxy(store.clone(), client.cloned(), rp.clone()), ) }) .collect() diff --git a/src/graphql.rs b/src/graphql.rs index 9cb37172..f8ed6cc8 100644 --- a/src/graphql.rs +++ b/src/graphql.rs @@ -40,13 +40,12 @@ use std::future::Future; use std::net::SocketAddr; use std::sync::{Arc, Mutex}; -use async_graphql::connection::{ConnectionNameType, CursorType, EdgeNameType}; +use async_graphql::connection::{ConnectionNameType, CursorType, EdgeNameType, OpaqueCursor}; use async_graphql::{ connection::{Connection, Edge, EmptyFields}, Context, Guard, MergedObject, MergedSubscription, ObjectType, OutputType, Result, }; use chrono::TimeDelta; -use data_encoding::BASE64; use num_traits::ToPrimitive; #[cfg(test)] use review_database::HostNetworkGroup; @@ -236,13 +235,23 @@ async fn query_with_constraints( first: Option, last: Option, f: F, -) -> Result> +) -> Result>, Node, ConnectionFields, EmptyFields, Name>> where Node: OutputType, ConnectionFields: ObjectType, Name: ConnectionNameType, - F: FnOnce(Option, Option, Option, Option) -> R, - R: Future, E>>, + F: FnOnce( + Option>>, + Option>>, + Option, + Option, + ) -> R, + R: Future< + Output = Result< + Connection>, Node, ConnectionFields, EmptyFields, Name>, + E, + >, + >, E: Into, { extra_validate_pagination_params( @@ -322,49 +331,27 @@ async fn get_store<'a>(ctx: &Context<'a>) -> Result>>()?.read().await) } -/// Decodes a cursor used in pagination. -fn decode_cursor(cursor: &str) -> Option> { - BASE64.decode(cursor.as_bytes()).ok() -} - -/// Encodes a cursor used in pagination. -fn encode_cursor(cursor: &[u8]) -> String { - BASE64.encode(cursor) -} - -#[allow(clippy::type_complexity)] -fn decode_cursor_pair( - after: Option, - before: Option, -) -> Result<(Option>, Option>)> { - let after = if let Some(after) = after { - Some(decode_cursor(&after).ok_or("invalid cursor `after`")?) - } else { - None - }; - let before = if let Some(before) = before { - Some(decode_cursor(&before).ok_or("invalid cursor `before`")?) - } else { - None - }; - Ok((after, before)) -} - #[allow(clippy::type_complexity)] fn process_load_edges<'a, T, I, R>( table: &'a T, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, prefix: Option<&[u8]>, -) -> Result<(Vec>, bool, bool)> +) -> ( + std::vec::Vec>, + bool, + bool, +) where T: database::Iterable<'a, I>, I: std::iter::Iterator>, R: database::UniqueKey, { - let (after, before) = decode_cursor_pair(after, before)?; + let after = after.map(|cursor| cursor.0); + let before = before.map(|cursor| cursor.0); + let (nodes, has_previous, has_next) = if let Some(first) = first { let (nodes, has_more) = collect_edges(table, Direction::Forward, after, before, prefix, first); @@ -377,13 +364,13 @@ where (nodes, has_more, false) }; - Ok((nodes, has_previous, has_next)) + (nodes, has_previous, has_next) } fn load_edges_interim<'a, T, I, R>( table: &'a T, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, prefix: Option<&[u8]>, @@ -394,7 +381,7 @@ where R: database::UniqueKey, { let (nodes, has_previous, has_next) = - process_load_edges(table, after, before, first, last, prefix)?; + process_load_edges(table, after, before, first, last, prefix); let nodes = nodes .into_iter() @@ -403,14 +390,15 @@ where Ok((nodes, has_previous, has_next)) } +#[allow(clippy::type_complexity)] fn load_edges<'a, T, I, R, N, A, NodesField>( table: &'a T, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, additional_fields: A, -) -> Result> +) -> Result>, N, A, EmptyFields, NodesField>> where T: database::Iterable<'a, I>, I: std::iter::Iterator>, @@ -420,7 +408,7 @@ where NodesField: ConnectionNameType, { let (nodes, has_previous, has_next) = - process_load_edges(table, after, before, first, last, None)?; + process_load_edges(table, after, before, first, last, None); for node in &nodes { let Err(e) = node else { continue }; @@ -432,8 +420,8 @@ where Connection::with_additional_fields(has_previous, has_next, additional_fields); connection.edges.extend(nodes.into_iter().map(|node| { let Ok(node) = node else { unreachable!() }; - let encoded = encode_cursor(node.unique_key().as_ref()); - Edge::new(encoded, node.into()) + let key = node.unique_key().as_ref().to_vec(); + Edge::new(OpaqueCursor(key), node.into()) })); Ok(connection) } diff --git a/src/graphql/account.rs b/src/graphql/account.rs index 28b18f3a..1bc84674 100644 --- a/src/graphql/account.rs +++ b/src/graphql/account.rs @@ -4,6 +4,7 @@ use std::{ }; use anyhow::anyhow; +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, Context, Enum, InputObject, Object, Result, SimpleObject, StringNumber, @@ -58,7 +59,7 @@ impl AccountQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, Account, AccountTotalCount, EmptyFields>> { query_with_constraints( after, before, @@ -325,10 +326,16 @@ impl AccountMutation { if let Some(mut account) = account_map.get(&username)? { validate_password(&account, &username, &password)?; validate_last_signin_time(&account, &username)?; - validate_allow_access_from(&account, &client_ip, &username)?; + validate_allow_access_from(&account, client_ip.as_ref(), &username)?; validate_max_parallel_sessions(&account, &store, &username)?; - sign_in_actions(&mut account, &store, &account_map, &client_ip, &username) + sign_in_actions( + &mut account, + &store, + &account_map, + client_ip.as_ref(), + &username, + ) } else { info!("{username} is not a valid username"); Err("incorrect username or password".into()) @@ -355,13 +362,19 @@ impl AccountMutation { if let Some(mut account) = account_map.get(&username)? { validate_password(&account, &username, &password)?; - validate_allow_access_from(&account, &client_ip, &username)?; + validate_allow_access_from(&account, client_ip.as_ref(), &username)?; validate_max_parallel_sessions(&account, &store, &username)?; validate_update_new_password(&password, &new_password, &username)?; account.update_password(&new_password)?; - sign_in_actions(&mut account, &store, &account_map, &client_ip, &username) + sign_in_actions( + &mut account, + &store, + &account_map, + client_ip.as_ref(), + &username, + ) } else { info!("{username} is not a valid username"); Err("incorrect username or password".into()) @@ -474,7 +487,7 @@ fn validate_last_signin_time(account: &types::Account, username: &str) -> Result fn validate_allow_access_from( account: &types::Account, - client_ip: &Option, + client_ip: Option<&SocketAddr>, username: &str, ) -> Result<()> { if let Some(allow_access_from) = account.allow_access_from.as_ref() { @@ -533,7 +546,7 @@ fn sign_in_actions( account: &mut types::Account, store: &Store, account_map: &Table, - client_ip: &Option, + client_ip: Option<&SocketAddr>, username: &str, ) -> Result { let (token, expiration_time) = @@ -720,11 +733,11 @@ impl AccountTotalCount { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, Account, AccountTotalCount, EmptyFields>> { let store = crate::graphql::get_store(ctx).await?; let table = store.account_map(); super::load_edges(&table, after, before, first, last, AccountTotalCount) diff --git a/src/graphql/allow_network.rs b/src/graphql/allow_network.rs index a3b11882..7521271d 100644 --- a/src/graphql/allow_network.rs +++ b/src/graphql/allow_network.rs @@ -1,3 +1,4 @@ +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, Context, InputObject, Object, Result, ID, @@ -29,7 +30,8 @@ impl AllowNetworkQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, AllowNetwork, AllowNetworkTotalCount, EmptyFields>> + { query_with_constraints( after, before, @@ -197,11 +199,11 @@ impl AllowNetworkTotalCount { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, AllowNetwork, AllowNetworkTotalCount, EmptyFields>> { let store = crate::graphql::get_store(ctx).await?; let map = store.allow_network_map(); super::load_edges(&map, after, before, first, last, AllowNetworkTotalCount) diff --git a/src/graphql/block_network.rs b/src/graphql/block_network.rs index e84937ca..9c0812b6 100644 --- a/src/graphql/block_network.rs +++ b/src/graphql/block_network.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, Context, InputObject, Object, Result, ID, @@ -32,7 +33,8 @@ impl BlockNetworkQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, BlockNetwork, BlockNetworkTotalCount, EmptyFields>> + { query_with_constraints( after, before, @@ -198,11 +200,11 @@ impl BlockNetworkTotalCount { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, BlockNetwork, BlockNetworkTotalCount, EmptyFields>> { let db = super::get_store(ctx).await?; let map = db.block_network_map(); super::load_edges(&map, after, before, first, last, BlockNetworkTotalCount) diff --git a/src/graphql/category.rs b/src/graphql/category.rs index 49faeca3..7c49e905 100644 --- a/src/graphql/category.rs +++ b/src/graphql/category.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, types::ID, @@ -29,7 +30,7 @@ impl CategoryQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, Category, CategoryTotalCount, EmptyFields>> { query_with_constraints( after, before, @@ -108,11 +109,11 @@ impl CategoryTotalCount { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, Category, CategoryTotalCount, EmptyFields>> { let store = super::get_store(ctx).await?; let table = store.category_map(); super::load_edges(&table, after, before, first, last, CategoryTotalCount) diff --git a/src/graphql/customer.rs b/src/graphql/customer.rs index 92561399..6b16afa8 100644 --- a/src/graphql/customer.rs +++ b/src/graphql/customer.rs @@ -1,6 +1,7 @@ use std::convert::{TryFrom, TryInto}; use anyhow::Context as AnyhowContext; +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, types::ID, @@ -28,7 +29,7 @@ impl CustomerQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, Customer, CustomerTotalCount, EmptyFields>> { query_with_constraints( after, before, @@ -459,11 +460,11 @@ impl CustomerTotalCount { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, Customer, CustomerTotalCount, EmptyFields>> { let store = crate::graphql::get_store(ctx).await?; let map = store.customer_map(); super::load_edges(&map, after, before, first, last, CustomerTotalCount) @@ -614,11 +615,11 @@ mod tests { ); let res = schema - .execute(r#"{customerList(last: 10, before: "dDg="){edges{node{name}}totalCount,pageInfo{startCursor}}}"#) + .execute(r#"{customerList(last: 10, before: "WzExNiw1Nl0"){edges{node{name}}totalCount,pageInfo{startCursor}}}"#) .await; assert_eq!( res.data.to_string(), - r#"{customerList: {edges: [{node: {name: "t1"}}, {node: {name: "t10"}}, {node: {name: "t2"}}, {node: {name: "t3"}}, {node: {name: "t4"}}, {node: {name: "t5"}}, {node: {name: "t6"}}, {node: {name: "t7"}}], totalCount: 10, pageInfo: {startCursor: "dDE="}}}"# + r#"{customerList: {edges: [{node: {name: "t1"}}, {node: {name: "t10"}}, {node: {name: "t2"}}, {node: {name: "t3"}}, {node: {name: "t4"}}, {node: {name: "t5"}}, {node: {name: "t6"}}, {node: {name: "t7"}}], totalCount: 10, pageInfo: {startCursor: "WzExNiw0OV0"}}}"# ); let res = schema @@ -630,17 +631,17 @@ mod tests { let res = schema .execute( - r#"{customerList(first:10 after:"dDc=" ){edges{node{name}}totalCount,pageInfo{endCursor}}}"#, + r#"{customerList(first:10 after:"WzExNiw1NV0" ){edges{node{name}}totalCount,pageInfo{endCursor}}}"#, ) .await; assert_eq!( res.data.to_string(), - r#"{customerList: {edges: [{node: {name: "t8"}}, {node: {name: "t9"}}], totalCount: 10, pageInfo: {endCursor: "dDk="}}}"# + r#"{customerList: {edges: [{node: {name: "t8"}}, {node: {name: "t9"}}], totalCount: 10, pageInfo: {endCursor: "WzExNiw1N10"}}}"# ); let res = schema .execute( - r#"{customerList(first:10 before:"dDc=" ){edges{node{name}}totalCount,pageInfo{endCursor}}}"#, + r#"{customerList(first:10 before:"WzExNiw1NV0" ){edges{node{name}}totalCount,pageInfo{endCursor}}}"#, ) .await; assert!(res.is_err()); diff --git a/src/graphql/data_source.rs b/src/graphql/data_source.rs index fe4b1234..b5c9e029 100644 --- a/src/graphql/data_source.rs +++ b/src/graphql/data_source.rs @@ -1,3 +1,4 @@ +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, types::ID, @@ -25,7 +26,8 @@ impl DataSourceQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, DataSource, DataSourceTotalCount, EmptyFields>> + { query_with_constraints( after, before, @@ -259,11 +261,11 @@ impl DataSourceTotalCount { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, DataSource, DataSourceTotalCount, EmptyFields>> { let store = crate::graphql::get_store(ctx).await?; let map = store.data_source_map(); diff --git a/src/graphql/filter.rs b/src/graphql/filter.rs index dca6e078..f467dc1c 100644 --- a/src/graphql/filter.rs +++ b/src/graphql/filter.rs @@ -476,12 +476,12 @@ impl PartialEq for &database::Filter { _ => false, } && self.keywords == rhs.keywords - && cmp_option_vec_string_with_id(&self.network_tags, &rhs.network_tags) - && cmp_option_vec_string_with_id(&self.customers, &rhs.customers) + && cmp_option_vec_string_with_id(self.network_tags.as_ref(), rhs.network_tags.as_ref()) + && cmp_option_vec_string_with_id(self.customers.as_ref(), rhs.customers.as_ref()) && network_eq - && cmp_option_vec_string_with_id(&self.sensors, &rhs.sensors) - && cmp_option_vec_string_with_id(&self.os, &rhs.os) - && cmp_option_vec_string_with_id(&self.devices, &rhs.devices) + && cmp_option_vec_string_with_id(self.sensors.as_ref(), rhs.sensors.as_ref()) + && cmp_option_vec_string_with_id(self.os.as_ref(), rhs.os.as_ref()) + && cmp_option_vec_string_with_id(self.devices.as_ref(), rhs.devices.as_ref()) && self.host_names == rhs.host_names && self.user_ids == rhs.user_ids && self.user_names == rhs.user_names @@ -546,7 +546,7 @@ pub(super) enum TrafficDirection { To, } -fn cmp_option_vec_string_with_id(lhs: &Option>, rhs: &Option>) -> bool { +fn cmp_option_vec_string_with_id(lhs: Option<&Vec>, rhs: Option<&Vec>) -> bool { match (lhs, rhs) { (Some(lhs), Some(rhs)) => lhs .iter() diff --git a/src/graphql/network.rs b/src/graphql/network.rs index 52321e78..e25d2b30 100644 --- a/src/graphql/network.rs +++ b/src/graphql/network.rs @@ -1,5 +1,6 @@ use std::{convert::TryInto, mem::size_of}; +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, types::ID, @@ -31,7 +32,7 @@ impl NetworkQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, Network, NetworkTotalCount, EmptyFields>> { query_with_constraints( after, before, @@ -237,11 +238,11 @@ impl NetworkTotalCount { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, Network, NetworkTotalCount, EmptyFields>> { let store = crate::graphql::get_store(ctx).await?; let map = store.network_map(); super::load_edges(&map, after, before, first, last, NetworkTotalCount) diff --git a/src/graphql/node.rs b/src/graphql/node.rs index 6e492d39..eb9489b1 100644 --- a/src/graphql/node.rs +++ b/src/graphql/node.rs @@ -370,7 +370,7 @@ impl NodeStatus { impl NodeStatus { fn new( node: database::Node, - resource_usage: &Option, + resource_usage: Option<&roxy::ResourceUsage>, ping: Option, manager: bool, ) -> Self { diff --git a/src/graphql/node/crud.rs b/src/graphql/node/crud.rs index 4e9f4add..e2da70cf 100644 --- a/src/graphql/node/crud.rs +++ b/src/graphql/node/crud.rs @@ -1,5 +1,6 @@ #![allow(clippy::fn_params_excessive_bools)] +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, types::ID, @@ -30,7 +31,7 @@ impl NodeQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, Node, NodeTotalCount, EmptyFields>> { query_with_constraints( after, before, @@ -159,11 +160,11 @@ impl NodeMutation { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, Node, NodeTotalCount, EmptyFields>> { let store = crate::graphql::get_store(ctx).await?; let map = store.node_map(); super::super::load_edges(&map, after, before, first, last, NodeTotalCount) diff --git a/src/graphql/node/status.rs b/src/graphql/node/status.rs index 866cf354..4a2f233f 100644 --- a/src/graphql/node/status.rs +++ b/src/graphql/node/status.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, Edge, EmptyFields}, Context, Object, Result, @@ -25,7 +26,8 @@ impl NodeStatusQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, NodeStatus, NodeStatusTotalCount, EmptyFields>> + { query_with_constraints( after, before, @@ -39,11 +41,11 @@ impl NodeStatusQuery { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, NodeStatus, NodeStatusTotalCount, EmptyFields>> { let (node_list, has_previous, has_next) = { let store = crate::graphql::get_store(ctx).await?; let map = store.node_map(); @@ -67,9 +69,10 @@ async fn load( let (resource_usage, ping) = fetch_resource_usage_and_ping(agent_manager, hostname, is_manager).await; + let key = node.unique_key(); connection.edges.push(Edge::new( - crate::graphql::encode_cursor(node.unique_key()), - NodeStatus::new(node, &resource_usage, ping, is_manager), + OpaqueCursor(key.to_vec()), + NodeStatus::new(node, resource_usage.as_ref(), ping, is_manager), )); } Ok(connection) @@ -609,32 +612,32 @@ mod tests { .await; assert_eq!( res.data.to_string(), - r#"{nodeStatusList: {edges: [{node: {name: "test1"}}, {node: {name: "test2"}}, {node: {name: "test3"}}, {node: {name: "test4"}}, {node: {name: "test5"}}], pageInfo: {endCursor: "dGVzdDU="}}}"# + r#"{nodeStatusList: {edges: [{node: {name: "test1"}}, {node: {name: "test2"}}, {node: {name: "test3"}}, {node: {name: "test4"}}, {node: {name: "test5"}}], pageInfo: {endCursor: "WzExNiwxMDEsMTE1LDExNiw1M10"}}}"# ); let res = schema - .execute(r#"{nodeStatusList(last:3,before:"dGVzdDM="){edges{node{name}},pageInfo{startCursor}}}"#) + .execute(r#"{nodeStatusList(last:3,before:"WzExNiwxMDEsMTE1LDExNiw1MV0"){edges{node{name}},pageInfo{startCursor}}}"#) .await; assert_eq!( res.data.to_string(), - r#"{nodeStatusList: {edges: [{node: {name: "test1"}}, {node: {name: "test2"}}], pageInfo: {startCursor: "dGVzdDE="}}}"# + r#"{nodeStatusList: {edges: [{node: {name: "test1"}}, {node: {name: "test2"}}], pageInfo: {startCursor: "WzExNiwxMDEsMTE1LDExNiw0OV0"}}}"# ); let res = schema - .execute(r#"{nodeStatusList(first:3,after:"dGVzdDM="){edges{node{name}},pageInfo{endCursor}}}"#) + .execute(r#"{nodeStatusList(first:3,after:"WzExNiwxMDEsMTE1LDExNiw1MV0"){edges{node{name}},pageInfo{endCursor}}}"#) .await; assert_eq!( res.data.to_string(), - r#"{nodeStatusList: {edges: [{node: {name: "test4"}}, {node: {name: "test5"}}], pageInfo: {endCursor: "dGVzdDU="}}}"# + r#"{nodeStatusList: {edges: [{node: {name: "test4"}}, {node: {name: "test5"}}], pageInfo: {endCursor: "WzExNiwxMDEsMTE1LDExNiw1M10"}}}"# ); let res = schema - .execute(r#"{nodeStatusList(last:2, after:"dGVzdDU="){edges{node{name}}}}"#) + .execute(r#"{nodeStatusList(last:2, after:"WzExNiwxMDEsMTE1LDExNiw1M10"){edges{node{name}}}}"#) .await; assert!(res.is_err()); let res = schema - .execute(r#"{nodeStatusList(first:2, before:"dGVzdDU="){edges{node{name}}}}"#) + .execute(r#"{nodeStatusList(first:2, before:"WzExNiwxMDEsMTE1LDExNiw1M10"){edges{node{name}}}}"#) .await; assert!(res.is_err()); } diff --git a/src/graphql/outlier.rs b/src/graphql/outlier.rs index db648052..c8922502 100644 --- a/src/graphql/outlier.rs +++ b/src/graphql/outlier.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, sync::Arc}; use anyhow::anyhow; +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, Edge, EmptyFields}, types::ID, @@ -19,7 +20,7 @@ use serde::Serialize; use tokio::{sync::RwLock, time}; use tracing::error; -use super::{encode_cursor, model::ModelDigest, query, Role, RoleGuard}; +use super::{model::ModelDigest, query, Role, RoleGuard}; const MAX_EVENT_NUM_OF_OUTLIER: usize = 50; const DEFAULT_RANKED_OUTLIER_FETCH_TIME: u64 = 60; @@ -178,7 +179,7 @@ impl OutlierQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, Outlier, OutlierTotalCount, EmptyFields>> { let model = model.as_str().parse()?; query( after, @@ -208,7 +209,9 @@ impl OutlierQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result< + Connection>, RankedOutlier, RankedOutlierTotalCount, EmptyFields>, + > { let filter = |node: RankedOutlier| if node.saved { Some(node) } else { None }; query( after, @@ -239,7 +242,9 @@ impl OutlierQuery { first: Option, last: Option, filter: Option, - ) -> Result> { + ) -> Result< + Connection>, RankedOutlier, RankedOutlierTotalCount, EmptyFields>, + > { query( after, before, @@ -261,12 +266,13 @@ async fn load_outliers( ctx: &Context<'_>, model_id: ID, time: Option, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, filter: fn(RankedOutlier) -> Option, -) -> Result> { +) -> Result>, RankedOutlier, RankedOutlierTotalCount, EmptyFields>> +{ let model_id: i32 = model_id.as_str().parse()?; let timestamp = time.map(|t| t.and_utc().timestamp_nanos_opt().unwrap_or_default()); @@ -294,7 +300,7 @@ async fn load_outliers( }, ); connection.edges.extend(nodes.into_iter().filter_map(|n| { - let cursor = encode_cursor(&n.unique_key()); + let cursor = OpaqueCursor(n.unique_key()); let n: RankedOutlier = n.into(); filter(n).map(|n| Edge::new(cursor, n)) })); @@ -478,14 +484,17 @@ impl OutlierTotalCount { async fn load( ctx: &Context<'_>, model_id: i32, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, Outlier, OutlierTotalCount, EmptyFields>> { let store = crate::graphql::get_store(ctx).await?; let table = store.outlier_map(); - let (after, before) = super::decode_cursor_pair(after, before)?; + + let after = after.map(|cursor| cursor.0); + let before = before.map(|cursor| cursor.0); + let decoded_after = after .as_deref() .map(|input| bincode::DefaultOptions::new().deserialize(input)) @@ -545,9 +554,9 @@ async fn load( }; let edges = batches.into_values().filter_map(|(from, to, mut ev)| { let cursor = bincode::DefaultOptions::new().serialize(&(from, to)).ok()?; - let encoded = super::encode_cursor(&cursor); + let cursor = OpaqueCursor(cursor); ev.events.sort_unstable(); - Some(Edge::new(encoded, ev)) + Some(Edge::new(cursor, ev)) }); let mut connection = Connection::with_additional_fields(has_previous, has_next, OutlierTotalCount { model_id }); @@ -567,8 +576,8 @@ pub(crate) fn datetime_from_ts_nano(time: i64) -> Option> { fn check_filter_to_ranked_outlier( node: &OutlierInfo, - filter: &Option, - tag_id_list: &Option>, + filter: Option<&SearchFilterInput>, + tag_id_list: Option<&Vec>, remarks_map: &IndexedTable<'_, TriageResponse>, ) -> Result { if let Some(filter) = filter { @@ -626,15 +635,17 @@ async fn load_ranked_outliers_with_filter( ctx: &Context<'_>, model_id: ID, time: Option, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, filter: Option, -) -> Result> { +) -> Result>, RankedOutlier, RankedOutlierTotalCount, EmptyFields>> +{ let model_id: i32 = model_id.as_str().parse()?; let timestamp = time.map(|t| t.and_utc().timestamp_nanos_opt().unwrap_or_default()); - let (after, before) = super::decode_cursor_pair(after, before)?; + let after = after.map(|cursor| cursor.0); + let before = before.map(|cursor| cursor.0); let (direction, count, from) = if let Some(first) = first { (Direction::Forward, first, after.as_deref()) } else if let Some(last) = last { @@ -692,22 +703,31 @@ async fn load_ranked_outliers_with_filter( if from != key { return Err(anyhow!("invalid cursor").into()); } - } else if check_filter_to_ranked_outlier(&node, &filter, &tag_id_list, &remarks_map)? { - nodes.push((encode_cursor(&key), node)); + } else if check_filter_to_ranked_outlier( + &node, + filter.as_ref(), + tag_id_list.as_ref(), + &remarks_map, + )? { + nodes.push((OpaqueCursor(key), node)); } } for res in ranked_outlier_iter { let node = res?; let key = node.unique_key(); - let encoded_key = encode_cursor(&key); - if check_filter_to_ranked_outlier(&node, &filter, &tag_id_list, &remarks_map)? { + if check_filter_to_ranked_outlier( + &node, + filter.as_ref(), + tag_id_list.as_ref(), + &remarks_map, + )? { if nodes.len() >= count { has_more = true; break; } - nodes.push((encoded_key, node)); + nodes.push((OpaqueCursor(key), node)); } } diff --git a/src/graphql/qualifier.rs b/src/graphql/qualifier.rs index 02517a7c..54c2e6d2 100644 --- a/src/graphql/qualifier.rs +++ b/src/graphql/qualifier.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, types::ID, @@ -29,7 +30,8 @@ impl QualifierQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, Qualifier, QualifierTotalCount, EmptyFields>> + { query_with_constraints( after, before, @@ -105,11 +107,11 @@ impl QualifierTotalCount { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, Qualifier, QualifierTotalCount, EmptyFields>> { let store = super::get_store(ctx).await?; let table = store.qualifier_map(); super::load_edges(&table, after, before, first, last, QualifierTotalCount) diff --git a/src/graphql/sampling.rs b/src/graphql/sampling.rs index d6270cb1..140f2565 100644 --- a/src/graphql/sampling.rs +++ b/src/graphql/sampling.rs @@ -1,5 +1,6 @@ use std::net::IpAddr; +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, types::ID, @@ -243,7 +244,9 @@ impl SamplingPolicyQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result< + Connection>, SamplingPolicy, SamplingPolicyTotalCount, EmptyFields>, + > { query_with_constraints( after, before, @@ -271,11 +274,12 @@ impl SamplingPolicyQuery { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, SamplingPolicy, SamplingPolicyTotalCount, EmptyFields>> +{ let store = crate::graphql::get_store(ctx).await?; let map = store.sampling_policy_map(); super::load_edges(&map, after, before, first, last, SamplingPolicyTotalCount) diff --git a/src/graphql/statistics.rs b/src/graphql/statistics.rs index 4f3fc16a..d2c099e9 100644 --- a/src/graphql/statistics.rs +++ b/src/graphql/statistics.rs @@ -1,3 +1,4 @@ +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, ConnectionNameType, Edge, EdgeNameType, EmptyFields}, types::ID, @@ -83,7 +84,9 @@ impl StatisticsQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result< + Connection>, Round, TotalCountByModel, EmptyFields, RoundByModel>, + > { let model = model.as_str().parse()?; query_with_constraints( @@ -227,11 +230,12 @@ async fn load_rounds_by_cluster( async fn load_rounds_by_model( ctx: &Context<'_>, model: i32, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, Round, TotalCountByModel, EmptyFields, RoundByModel>> +{ let store = super::get_store(ctx).await?; let table = store.batch_info_map(); super::load_edges( diff --git a/src/graphql/status.rs b/src/graphql/status.rs index 0b2874bf..dd42f9ce 100644 --- a/src/graphql/status.rs +++ b/src/graphql/status.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, types::ID, @@ -29,7 +30,7 @@ impl StatusQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, Status, StatusTotalCount, EmptyFields>> { query_with_constraints( after, before, @@ -105,11 +106,11 @@ impl StatusTotalCount { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, Status, StatusTotalCount, EmptyFields>> { let store = super::get_store(ctx).await?; let table = store.status_map(); super::load_edges(&table, after, before, first, last, StatusTotalCount) diff --git a/src/graphql/template.rs b/src/graphql/template.rs index 636f9d4a..b6e4d23d 100644 --- a/src/graphql/template.rs +++ b/src/graphql/template.rs @@ -1,5 +1,6 @@ use std::convert::{TryFrom, TryInto}; +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, Context, Enum, InputObject, Object, Result, StringNumber, Union, @@ -26,7 +27,7 @@ impl TemplateQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, Template, TemplateTotalCount, EmptyFields>> { query_with_constraints( after, before, @@ -347,11 +348,11 @@ impl TemplateTotalCount { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, Template, TemplateTotalCount, EmptyFields>> { let store = crate::graphql::get_store(ctx).await?; let map = store.template_map(); super::load_edges(&map, after, before, first, last, TemplateTotalCount) diff --git a/src/graphql/tor_exit_node.rs b/src/graphql/tor_exit_node.rs index 1c23db92..35b7e55a 100644 --- a/src/graphql/tor_exit_node.rs +++ b/src/graphql/tor_exit_node.rs @@ -1,3 +1,4 @@ +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, Context, Object, Result, SimpleObject, @@ -25,7 +26,8 @@ impl TorExitNodeQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, TorExitNode, TorExitNodeTotalCount, EmptyFields>> + { query_with_constraints( after, before, @@ -95,11 +97,11 @@ impl TorExitNodeTotalCount { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, TorExitNode, TorExitNodeTotalCount, EmptyFields>> { let store = crate::graphql::get_store(ctx).await?; let map = store.tor_exit_node_map(); diff --git a/src/graphql/triage/policy.rs b/src/graphql/triage/policy.rs index c9ff3cc5..a5a5768e 100644 --- a/src/graphql/triage/policy.rs +++ b/src/graphql/triage/policy.rs @@ -1,3 +1,4 @@ +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, Context, Object, Result, ID, @@ -35,7 +36,8 @@ impl TriagePolicyQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, TriagePolicy, TriagePolicyTotalCount, EmptyFields>> + { query_with_constraints( after, before, @@ -63,11 +65,11 @@ impl TriagePolicyQuery { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, TriagePolicy, TriagePolicyTotalCount, EmptyFields>> { let store = crate::graphql::get_store(ctx).await?; let map = store.triage_policy_map(); super::super::load_edges(&map, after, before, first, last, TriagePolicyTotalCount) diff --git a/src/graphql/triage/response.rs b/src/graphql/triage/response.rs index e993af96..77d36963 100644 --- a/src/graphql/triage/response.rs +++ b/src/graphql/triage/response.rs @@ -1,3 +1,4 @@ +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, types::ID, @@ -73,7 +74,9 @@ impl super::TriageResponseQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result< + Connection>, TriageResponse, TriageResponseTotalCount, EmptyFields>, + > { query_with_constraints( after, before, @@ -101,11 +104,12 @@ impl super::TriageResponseQuery { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, TriageResponse, TriageResponseTotalCount, EmptyFields>> +{ let store = crate::graphql::get_store(ctx).await?; let table = store.triage_response_map(); crate::graphql::load_edges(&table, after, before, first, last, TriageResponseTotalCount) diff --git a/src/graphql/trusted_domain.rs b/src/graphql/trusted_domain.rs index 429b7c56..e7871736 100644 --- a/src/graphql/trusted_domain.rs +++ b/src/graphql/trusted_domain.rs @@ -1,3 +1,4 @@ +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, Context, Object, Result, SimpleObject, @@ -23,7 +24,7 @@ impl TrustedDomainQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result>, TrustedDomain, EmptyFields, EmptyFields>> { query_with_constraints( after, before, @@ -96,11 +97,11 @@ impl From for TrustedDomain { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result>, TrustedDomain, EmptyFields, EmptyFields>> { let store = crate::graphql::get_store(ctx).await?; let map = store.trusted_domain_map(); super::load_edges(&map, after, before, first, last, EmptyFields) diff --git a/src/graphql/trusted_user_agent.rs b/src/graphql/trusted_user_agent.rs index 97d18357..7a83404f 100644 --- a/src/graphql/trusted_user_agent.rs +++ b/src/graphql/trusted_user_agent.rs @@ -1,3 +1,4 @@ +use async_graphql::connection::OpaqueCursor; use async_graphql::{ connection::{Connection, EmptyFields}, Context, Object, Result, SimpleObject, @@ -27,7 +28,14 @@ impl UserAgentQuery { before: Option, first: Option, last: Option, - ) -> Result> { + ) -> Result< + Connection< + OpaqueCursor>, + TrustedUserAgent, + TrustedUserAgentTotalCount, + EmptyFields, + >, + > { query_with_constraints( after, before, @@ -158,11 +166,13 @@ pub fn get_trusted_user_agent_list(db: &Store) -> Result> { async fn load( ctx: &Context<'_>, - after: Option, - before: Option, + after: Option>>, + before: Option>>, first: Option, last: Option, -) -> Result> { +) -> Result< + Connection>, TrustedUserAgent, TrustedUserAgentTotalCount, EmptyFields>, +> { let store = crate::graphql::get_store(ctx).await?; let map = store.trusted_user_agent_map(); super::load_edges(&map, after, before, first, last, TrustedUserAgentTotalCount) diff --git a/src/lib.rs b/src/lib.rs index 396b2eb7..2ca9b12c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,7 +86,7 @@ where let proxies_config = crate::archive::Config::configure_reverse_proxies( &store, - &client, + client.as_ref(), &config.reverse_proxies, );