Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Torii graphql handle empty state #1095

Merged
merged 1 commit into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 36 additions & 10 deletions crates/torii/graphql/src/route.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
use std::convert::Infallible;

use async_graphql::dynamic::Schema;
use async_graphql::http::{playground_source, GraphQLPlaygroundConfig};
use async_graphql_warp::graphql_subscription;
use async_graphql::Request;
use async_graphql_warp::{graphql, graphql_subscription};
use serde_json::json;
use sqlx::{Pool, Sqlite};
use url::Url;
use warp::Filter;
use warp::{Filter, Rejection, Reply};

use super::schema::build_schema;
use crate::query::constants::MODEL_TABLE;
use crate::query::data::count_rows;

pub async fn filter(
pool: &Pool<Sqlite>,
external_url: Option<Url>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
let schema = build_schema(pool).await.unwrap();
let graphql_post = warp::path("graphql")
.and(async_graphql_warp::graphql(schema.clone()))
.and_then(|(schema, request): (Schema, async_graphql::Request)| async move {
let mut conn = pool.acquire().await.unwrap();
let num_models = count_rows(&mut conn, MODEL_TABLE, &None, &None).await.unwrap();

graphql_filter(schema, external_url, num_models == 0)
}

fn graphql_filter(
schema: Schema,
external_url: Option<Url>,
is_empty: bool,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
let graphql_post = warp::path("graphql").and(graphql(schema.clone())).and_then(
move |(schema, request): (Schema, Request)| async move {
if is_empty {
return Ok::<_, Rejection>(empty_response());
}

// Execute query
let response = schema.execute(request).await;
// Return result
Ok::<_, Infallible>(warp::reply::json(&response))
});
Ok::<_, Rejection>(warp::reply::json(&response))
},
);

let subscription_endpoint = if let Some(external_url) = external_url {
format!("{external_url}/graphql/ws").replace("http", "ws")
Expand All @@ -39,3 +56,12 @@ pub async fn filter(

graphql_subscription(schema).or(graphql_post).or(playground_filter)
}

fn empty_response() -> warp::reply::Json {
let empty_response = json!({
"errors": [{
"message": "World does not have any indexed data yet."
}]
});
warp::reply::json(&empty_response)
}
52 changes: 25 additions & 27 deletions crates/torii/graphql/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,8 @@ use crate::query::type_mapping_query;
// events, their schema is known but we generate them dynamically as well because async-graphql
// does not allow mixing of static and dynamic schemas.
pub async fn build_schema(pool: &SqlitePool) -> Result<Schema> {
let mut schema_builder = Schema::build("Query", None, Some("Subscription"));
// predefined objects
let mut objects: Vec<Box<dyn ObjectTrait>> = vec![
Box::new(EntityObject),
Box::new(MetadataObject),
Box::new(ModelObject),
Box::new(SystemObject),
Box::new(EventObject),
Box::new(SystemCallObject),
Box::new(PageInfoObject),
];

// build model data gql objects
let (data_objects, data_union) = build_data_objects(pool).await?;
objects.extend(data_objects);

// register model data unions
schema_builder = schema_builder.register(data_union);

// register default scalars
for scalar_type in ScalarType::all().iter() {
schema_builder = schema_builder.register(Scalar::new(scalar_type));
}
// build world gql objects
let (objects, union) = build_objects(pool).await?;

// collect resolvers for single and plural queries
let queries: Vec<Field> = objects
Expand All @@ -58,6 +37,16 @@ pub async fn build_schema(pool: &SqlitePool) -> Result<Schema> {
query_root = query_root.field(query);
}

let mut schema_builder = Schema::build("Query", None, Some("Subscription"));

// register model data unions
schema_builder = schema_builder.register(union);

// register default scalars
for scalar_type in ScalarType::all().iter() {
schema_builder = schema_builder.register(Scalar::new(scalar_type));
}

for object in &objects {
// register enum objects
if let Some(input_objects) = object.enum_objects() {
Expand Down Expand Up @@ -111,16 +100,25 @@ pub async fn build_schema(pool: &SqlitePool) -> Result<Schema> {
.map_err(|e| e.into())
}

async fn build_data_objects(pool: &SqlitePool) -> Result<(Vec<Box<dyn ObjectTrait>>, Union)> {
async fn build_objects(pool: &SqlitePool) -> Result<(Vec<Box<dyn ObjectTrait>>, Union)> {
let mut conn = pool.acquire().await?;
let mut objects: Vec<Box<dyn ObjectTrait>> = Vec::new();

let models: Vec<Model> = sqlx::query_as("SELECT * FROM models").fetch_all(&mut conn).await?;

// predefined objects
let mut objects: Vec<Box<dyn ObjectTrait>> = vec![
Box::new(EntityObject),
Box::new(MetadataObject),
Box::new(ModelObject),
Box::new(SystemObject),
Box::new(EventObject),
Box::new(SystemCallObject),
Box::new(PageInfoObject),
];

// model union object
let mut union = Union::new("ModelUnion");

// model state objects
// model data objects
for model in models {
let type_mapping = type_mapping_query(&mut conn, &model.id).await?;

Expand Down
Loading