From 13c7d2772fa1405bde32b9ac9627a77967a7a4f8 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 4 Nov 2022 19:22:33 +0100 Subject: [PATCH] Move the GraphQL schema to its own crate --- Cargo.lock | 13 ++- crates/graphql/Cargo.toml | 17 +++ crates/graphql/src/bin/schema.rs | 27 +++++ crates/graphql/src/lib.rs | 108 ++++++++++++++++++ crates/handlers/Cargo.toml | 2 +- crates/handlers/src/app_state.rs | 6 +- .../src/{graphql/mod.rs => graphql.rs} | 52 ++------- crates/handlers/src/lib.rs | 9 +- crates/http/src/layers/otel/on_response.rs | 2 + 9 files changed, 180 insertions(+), 56 deletions(-) create mode 100644 crates/graphql/Cargo.toml create mode 100644 crates/graphql/src/bin/schema.rs create mode 100644 crates/graphql/src/lib.rs rename crates/handlers/src/{graphql/mod.rs => graphql.rs} (83%) diff --git a/Cargo.lock b/Cargo.lock index f8dcfd0f..ceef08df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2592,6 +2592,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "mas-graphql" +version = "0.1.0" +dependencies = [ + "async-graphql", + "mas-axum-utils", + "sqlx", + "tokio", + "tokio-stream", +] + [[package]] name = "mas-handlers" version = "0.1.0" @@ -2611,6 +2622,7 @@ dependencies = [ "mas-axum-utils", "mas-data-model", "mas-email", + "mas-graphql", "mas-http", "mas-iana", "mas-jose", @@ -2630,7 +2642,6 @@ dependencies = [ "sqlx", "thiserror", "tokio", - "tokio-stream", "tower", "tower-http", "tracing", diff --git a/crates/graphql/Cargo.toml b/crates/graphql/Cargo.toml new file mode 100644 index 00000000..a65972f0 --- /dev/null +++ b/crates/graphql/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "mas-graphql" +version = "0.1.0" +authors = ["Quentin Gliech "] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +async-graphql = "4.0.16" +sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "postgres"] } +tokio = { version = "1.21.2", features = ["time"] } +tokio-stream = "0.1.11" + +mas-axum-utils = { path = "../axum-utils" } + +[[bin]] +name = "schema" diff --git a/crates/graphql/src/bin/schema.rs b/crates/graphql/src/bin/schema.rs new file mode 100644 index 00000000..1ca27c2e --- /dev/null +++ b/crates/graphql/src/bin/schema.rs @@ -0,0 +1,27 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![forbid(unsafe_code)] +#![deny( + clippy::all, + clippy::str_to_string, + rustdoc::broken_intra_doc_links, + clippy::future_not_send +)] +#![warn(clippy::pedantic)] + +fn main() { + let schema = mas_graphql::schema_builder().finish(); + println!("{}", schema.sdl()); +} diff --git a/crates/graphql/src/lib.rs b/crates/graphql/src/lib.rs new file mode 100644 index 00000000..5831b891 --- /dev/null +++ b/crates/graphql/src/lib.rs @@ -0,0 +1,108 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![forbid(unsafe_code)] +#![deny( + clippy::all, + clippy::str_to_string, + rustdoc::broken_intra_doc_links, + clippy::future_not_send +)] +#![warn(clippy::pedantic)] +#![allow(clippy::module_name_repetitions, clippy::missing_errors_doc)] + +use std::time::Duration; + +use async_graphql::Context; +use mas_axum_utils::SessionInfo; +use sqlx::PgPool; +use tokio_stream::{Stream, StreamExt}; + +pub type Schema = async_graphql::Schema; +pub type SchemaBuilder = async_graphql::SchemaBuilder; + +#[must_use] +pub fn schema_builder() -> SchemaBuilder { + async_graphql::Schema::build(Query::new(), Mutation::new(), Subscription::new()) +} + +#[derive(Default)] +pub struct Query { + _private: (), +} + +impl Query { + #[must_use] + pub fn new() -> Self { + Self::default() + } +} + +#[async_graphql::Object] +impl Query { + /// A simple property which uses the DB pool and the current session + async fn username(&self, ctx: &Context<'_>) -> Result, async_graphql::Error> { + let database = ctx.data::()?; + let session_info = ctx.data::()?; + let mut conn = database.acquire().await?; + let session = session_info.load_session(&mut conn).await?; + + Ok(session.map(|s| s.user.username)) + } +} + +#[derive(Default)] +pub struct Mutation { + _private: (), +} + +impl Mutation { + #[must_use] + pub fn new() -> Self { + Self::default() + } +} + +#[async_graphql::Object] +impl Mutation { + /// A dummy mutation so that the mutation object is not empty + async fn hello(&self) -> bool { + true + } +} + +#[derive(Default)] +pub struct Subscription { + _private: (), +} + +impl Subscription { + #[must_use] + pub fn new() -> Self { + Self::default() + } +} + +#[async_graphql::Subscription] +impl Subscription { + /// A dump subscription to try out the websocket + async fn integers(&self, #[graphql(default = 1)] step: i32) -> impl Stream { + let mut value = 0; + tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(Duration::from_secs(1))) + .map(move |_| { + value += step; + value + }) + } +} diff --git a/crates/handlers/Cargo.toml b/crates/handlers/Cargo.toml index 8bd4b270..4349fd3d 100644 --- a/crates/handlers/Cargo.toml +++ b/crates/handlers/Cargo.toml @@ -8,7 +8,6 @@ license = "Apache-2.0" [dependencies] # Async runtime tokio = { version = "1.21.2", features = ["macros"] } -tokio-stream = "0.1.11" futures-util = "0.3.25" # Logging and tracing @@ -56,6 +55,7 @@ oauth2-types = { path = "../oauth2-types" } mas-axum-utils = { path = "../axum-utils", default-features = false } mas-data-model = { path = "../data-model" } mas-email = { path = "../email" } +mas-graphql = { path = "../graphql" } mas-http = { path = "../http", default-features = false } mas-iana = { path = "../iana" } mas-jose = { path = "../jose" } diff --git a/crates/handlers/src/app_state.rs b/crates/handlers/src/app_state.rs index 3ab6d1b5..fb8a149d 100644 --- a/crates/handlers/src/app_state.rs +++ b/crates/handlers/src/app_state.rs @@ -22,7 +22,7 @@ use mas_router::UrlBuilder; use mas_templates::Templates; use sqlx::PgPool; -use crate::{GraphQLSchema, MatrixHomeserver}; +use crate::MatrixHomeserver; #[derive(Clone)] pub struct AppState { @@ -34,7 +34,7 @@ pub struct AppState { pub mailer: Mailer, pub homeserver: MatrixHomeserver, pub policy_factory: Arc, - pub graphql_schema: GraphQLSchema, + pub graphql_schema: mas_graphql::Schema, } impl FromRef for PgPool { @@ -43,7 +43,7 @@ impl FromRef for PgPool { } } -impl FromRef for GraphQLSchema { +impl FromRef for mas_graphql::Schema { fn from_ref(input: &AppState) -> Self { input.graphql_schema.clone() } diff --git a/crates/handlers/src/graphql/mod.rs b/crates/handlers/src/graphql.rs similarity index 83% rename from crates/handlers/src/graphql/mod.rs rename to crates/handlers/src/graphql.rs index b6fc8c0c..4adf1fe4 100644 --- a/crates/handlers/src/graphql/mod.rs +++ b/crates/handlers/src/graphql.rs @@ -12,16 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{borrow::Cow, str::FromStr, time::Duration}; +use std::{borrow::Cow, str::FromStr}; use async_graphql::{ extensions::{ApolloTracing, Tracing}, - futures_util::TryStreamExt, http::{ playground_source, GraphQLPlaygroundConfig, MultipartOptions, WebSocketProtocols, WsMessage, ALL_WEBSOCKET_PROTOCOLS, }, - Context, Data, EmptyMutation, + Data, }; use axum::{ extract::{ @@ -32,19 +31,19 @@ use axum::{ Json, TypedHeader, }; use axum_extra::extract::PrivateCookieJar; -use futures_util::{SinkExt, Stream, StreamExt}; +use futures_util::{SinkExt, StreamExt, TryStreamExt}; use headers::{ContentType, Header, HeaderValue}; use hyper::header::{CACHE_CONTROL, SEC_WEBSOCKET_PROTOCOL}; -use mas_axum_utils::{FancyError, SessionInfo, SessionInfoExt}; +use mas_axum_utils::{FancyError, SessionInfoExt}; +use mas_graphql::Schema; use mas_keystore::Encrypter; use sqlx::PgPool; use tracing::{info_span, Instrument}; -pub type Schema = async_graphql::Schema; - #[must_use] pub fn schema(pool: &PgPool) -> Schema { - async_graphql::Schema::build(Query::new(pool), EmptyMutation, Subscription) + mas_graphql::schema_builder() + .data(pool.clone()) .extension(Tracing) .extension(ApolloTracing) .finish() @@ -212,40 +211,3 @@ pub async fn playground() -> impl IntoResponse { .with_setting("request.credentials", "include"), )) } - -pub struct Query { - database: PgPool, -} - -impl Query { - fn new(pool: &PgPool) -> Self { - Self { - database: pool.clone(), - } - } -} - -#[async_graphql::Object] -impl Query { - async fn username(&self, ctx: &Context<'_>) -> Result, async_graphql::Error> { - let mut conn = self.database.acquire().await?; - let session_info = ctx.data::()?; - let session = session_info.load_session(&mut conn).await?; - - Ok(session.map(|s| s.user.username)) - } -} - -pub struct Subscription; - -#[async_graphql::Subscription] -impl Subscription { - async fn integers(&self, #[graphql(default = 1)] step: i32) -> impl Stream { - let mut value = 0; - tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(Duration::from_secs(1))) - .map(move |_| { - value += step; - value - }) - } -} diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index 120472c4..43afb8fc 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -56,10 +56,7 @@ mod views; pub use compat::MatrixHomeserver; -pub use self::{ - app_state::AppState, - graphql::{schema as graphql_schema, Schema as GraphQLSchema}, -}; +pub use self::{app_state::AppState, graphql::schema as graphql_schema}; #[must_use] pub fn empty_router(state: Arc) -> Router @@ -87,7 +84,7 @@ where ::Data: Into, ::Error: std::error::Error + Send + Sync, S: Send + Sync + 'static, - GraphQLSchema: FromRef, + mas_graphql::Schema: FromRef, Encrypter: FromRef, { let mut router = Router::with_state_arc(state) @@ -344,7 +341,7 @@ where Templates: FromRef, Mailer: FromRef, MatrixHomeserver: FromRef, - GraphQLSchema: FromRef, + mas_graphql::Schema: FromRef, { let healthcheck_router = healthcheck_router(state.clone()); let discovery_router = discovery_router(state.clone()); diff --git a/crates/http/src/layers/otel/on_response.rs b/crates/http/src/layers/otel/on_response.rs index 98d2f83a..0b3bccce 100644 --- a/crates/http/src/layers/otel/on_response.rs +++ b/crates/http/src/layers/otel/on_response.rs @@ -63,9 +63,11 @@ impl OnResponse> for OnHttpResponse { } } +#[cfg(feature = "aws-sdk")] #[derive(Debug, Clone, Copy, Default)] pub struct OnAwsResponse; +#[cfg(feature = "aws-sdk")] impl OnResponse for OnAwsResponse { fn on_response( &self,