From 795ac75842ff0df274bb4a0124c6285c577f8216 Mon Sep 17 00:00:00 2001 From: Yun Date: Thu, 25 Jan 2024 07:32:52 -0800 Subject: [PATCH] Refactor torii graphql object traits (#1446) * Refactor torii graphql object traits * more refactor --- crates/torii/graphql/src/constants.rs | 2 + .../graphql/src/object/connection/edge.rs | 14 +- .../graphql/src/object/connection/mod.rs | 4 +- .../src/object/connection/page_info.rs | 18 +- crates/torii/graphql/src/object/entity.rs | 70 ++---- crates/torii/graphql/src/object/event.rs | 64 +---- .../graphql/src/object/metadata/content.rs | 15 +- .../torii/graphql/src/object/metadata/mod.rs | 24 +- .../graphql/src/object/metadata/social.rs | 15 +- crates/torii/graphql/src/object/mod.rs | 232 ++++++++++-------- crates/torii/graphql/src/object/model.rs | 76 ++---- crates/torii/graphql/src/object/model_data.rs | 53 ++-- .../torii/graphql/src/object/transaction.rs | 67 +++-- crates/torii/graphql/src/schema.rs | 130 +++++----- 14 files changed, 329 insertions(+), 455 deletions(-) diff --git a/crates/torii/graphql/src/constants.rs b/crates/torii/graphql/src/constants.rs index bf08ddd028..85fe2e79cd 100644 --- a/crates/torii/graphql/src/constants.rs +++ b/crates/torii/graphql/src/constants.rs @@ -11,6 +11,7 @@ pub const ID_COLUMN: &str = "id"; pub const EVENT_ID_COLUMN: &str = "event_id"; pub const ENTITY_ID_COLUMN: &str = "entity_id"; pub const JSON_COLUMN: &str = "json"; +pub const TRANSACTION_HASH_COLUMN: &str = "transaction_hash"; pub const INTERNAL_ENTITY_ID_KEY: &str = "$entity_id$"; @@ -36,6 +37,7 @@ pub const SOCIAL_NAMES: (&str, &str) = ("social", "socials"); pub const CONTENT_NAMES: (&str, &str) = ("content", "contents"); pub const METADATA_NAMES: (&str, &str) = ("metadata", "metadatas"); pub const TRANSACTION_NAMES: (&str, &str) = ("transaction", "transactions"); +pub const PAGE_INFO_NAMES: (&str, &str) = ("pageInfo", ""); // misc pub const ORDER_DIR_TYPE_NAME: &str = "OrderDirection"; diff --git a/crates/torii/graphql/src/object/connection/edge.rs b/crates/torii/graphql/src/object/connection/edge.rs index e94275bd9d..b43e5814af 100644 --- a/crates/torii/graphql/src/object/connection/edge.rs +++ b/crates/torii/graphql/src/object/connection/edge.rs @@ -1,7 +1,7 @@ -use async_graphql::dynamic::{Field, TypeRef}; +use async_graphql::dynamic::TypeRef; use async_graphql::Name; -use crate::object::ObjectTrait; +use crate::object::BasicObject; use crate::types::{GraphqlType, TypeData, TypeMapping}; pub struct EdgeObject { @@ -28,7 +28,7 @@ impl EdgeObject { } } -impl ObjectTrait for EdgeObject { +impl BasicObject for EdgeObject { fn name(&self) -> (&str, &str) { (&self.name, "") } @@ -40,12 +40,4 @@ impl ObjectTrait for EdgeObject { fn type_mapping(&self) -> &TypeMapping { &self.type_mapping } - - fn resolve_one(&self) -> Option { - None - } - - fn resolve_many(&self) -> Option { - None - } } diff --git a/crates/torii/graphql/src/object/connection/mod.rs b/crates/torii/graphql/src/object/connection/mod.rs index d43d4a1461..9dfb2ae9e8 100644 --- a/crates/torii/graphql/src/object/connection/mod.rs +++ b/crates/torii/graphql/src/object/connection/mod.rs @@ -6,7 +6,7 @@ use sqlx::sqlite::SqliteRow; use sqlx::Row; use self::page_info::PageInfoObject; -use super::ObjectTrait; +use super::BasicObject; use crate::constants::PAGE_INFO_TYPE_NAME; use crate::query::order::Order; use crate::query::value_mapping_from_row; @@ -55,7 +55,7 @@ impl ConnectionObject { } } -impl ObjectTrait for ConnectionObject { +impl BasicObject for ConnectionObject { fn name(&self) -> (&str, &str) { (&self.name, "") } diff --git a/crates/torii/graphql/src/object/connection/page_info.rs b/crates/torii/graphql/src/object/connection/page_info.rs index 7a27b5b98e..5cf286df26 100644 --- a/crates/torii/graphql/src/object/connection/page_info.rs +++ b/crates/torii/graphql/src/object/connection/page_info.rs @@ -1,33 +1,25 @@ use async_graphql::connection::PageInfo; use async_graphql::dynamic::indexmap::IndexMap; -use async_graphql::dynamic::Field; use async_graphql::{Name, Value}; +use crate::constants::{PAGE_INFO_NAMES, PAGE_INFO_TYPE_NAME}; use crate::mapping::PAGE_INFO_TYPE_MAPPING; -use crate::object::{ObjectTrait, TypeMapping}; +use crate::object::{BasicObject, TypeMapping}; pub struct PageInfoObject; -impl ObjectTrait for PageInfoObject { +impl BasicObject for PageInfoObject { fn name(&self) -> (&str, &str) { - ("pageInfo", "") + PAGE_INFO_NAMES } fn type_name(&self) -> &str { - "World__PageInfo" + PAGE_INFO_TYPE_NAME } fn type_mapping(&self) -> &TypeMapping { &PAGE_INFO_TYPE_MAPPING } - - fn resolve_one(&self) -> Option { - None - } - - fn resolve_many(&self) -> Option { - None - } } impl PageInfoObject { diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index ff5a4a21ba..a37387dae4 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -10,18 +10,17 @@ use tokio_stream::StreamExt; use torii_core::simple_broker::SimpleBroker; use torii_core::types::Entity; -use super::connection::{connection_arguments, connection_output, parse_connection_arguments}; -use super::inputs::keys_input::{keys_argument, parse_keys_argument}; -use super::{ObjectTrait, TypeMapping, ValueMapping}; -use crate::constants::{ENTITY_NAMES, ENTITY_TABLE, ENTITY_TYPE_NAME, EVENT_ID_COLUMN}; +use super::inputs::keys_input::keys_argument; +use super::{BasicObject, ResolvableObject, TypeMapping, ValueMapping}; +use crate::constants::{ENTITY_NAMES, ENTITY_TABLE, ENTITY_TYPE_NAME, EVENT_ID_COLUMN, ID_COLUMN}; use crate::mapping::ENTITY_TYPE_MAPPING; -use crate::query::data::{count_rows, fetch_multiple_rows}; +use crate::object::{resolve_many, resolve_one}; use crate::query::{type_mapping_query, value_mapping_from_row}; use crate::types::TypeData; use crate::utils::extract; pub struct EntityObject; -impl ObjectTrait for EntityObject { +impl BasicObject for EntityObject { fn name(&self) -> (&str, &str) { ENTITY_NAMES } @@ -34,54 +33,31 @@ impl ObjectTrait for EntityObject { &ENTITY_TYPE_MAPPING } - fn table_name(&self) -> Option<&str> { - Some(ENTITY_TABLE) - } - fn related_fields(&self) -> Option> { Some(vec![model_union_field()]) } +} - fn resolve_many(&self) -> Option { - let mut field = Field::new( - self.name().1, - TypeRef::named(format!("{}Connection", self.type_name())), - |ctx| { - FieldFuture::new(async move { - let mut conn = ctx.data::>()?.acquire().await?; - let connection = parse_connection_arguments(&ctx)?; - let keys = parse_keys_argument(&ctx)?; - let total_count = count_rows(&mut conn, ENTITY_TABLE, &keys, &None).await?; - let (data, page_info) = fetch_multiple_rows( - &mut conn, - ENTITY_TABLE, - EVENT_ID_COLUMN, - &keys, - &None, - &None, - &connection, - total_count, - ) - .await?; - let results = connection_output( - &data, - &ENTITY_TYPE_MAPPING, - &None, - EVENT_ID_COLUMN, - total_count, - false, - page_info, - )?; - - Ok(Some(Value::Object(results))) - }) - }, +impl ResolvableObject for EntityObject { + fn resolvers(&self) -> Vec { + let resolve_one = resolve_one( + ENTITY_TABLE, + ID_COLUMN, + self.name().0, + self.type_name(), + self.type_mapping(), ); - field = connection_arguments(field); - field = keys_argument(field); + let mut resolve_many = resolve_many( + ENTITY_TABLE, + EVENT_ID_COLUMN, + self.name().1, + self.type_name(), + self.type_mapping(), + ); + resolve_many = keys_argument(resolve_many); - Some(field) + vec![resolve_one, resolve_many] } fn subscriptions(&self) -> Option> { diff --git a/crates/torii/graphql/src/object/event.rs b/crates/torii/graphql/src/object/event.rs index 7a7e28377f..81fb3d7938 100644 --- a/crates/torii/graphql/src/object/event.rs +++ b/crates/torii/graphql/src/object/event.rs @@ -1,24 +1,21 @@ use async_graphql::dynamic::{ - Field, FieldFuture, InputValue, SubscriptionField, SubscriptionFieldFuture, TypeRef, + Field, InputValue, SubscriptionField, SubscriptionFieldFuture, TypeRef, }; use async_graphql::{Name, Result, Value}; -use sqlx::{Pool, Sqlite}; use tokio_stream::{Stream, StreamExt}; use torii_core::simple_broker::SimpleBroker; use torii_core::sql::FELT_DELIMITER; use torii_core::types::Event; -use super::connection::{connection_arguments, connection_output, parse_connection_arguments}; use super::inputs::keys_input::{keys_argument, parse_keys_argument}; -use super::{ObjectTrait, TypeMapping}; +use super::{resolve_many, BasicObject, ResolvableObject, TypeMapping}; use crate::constants::{EVENT_NAMES, EVENT_TABLE, EVENT_TYPE_NAME, ID_COLUMN}; use crate::mapping::EVENT_TYPE_MAPPING; -use crate::query::data::{count_rows, fetch_multiple_rows}; use crate::types::ValueMapping; pub struct EventObject; -impl ObjectTrait for EventObject { +impl BasicObject for EventObject { fn name(&self) -> (&str, &str) { EVENT_NAMES } @@ -30,55 +27,20 @@ impl ObjectTrait for EventObject { fn type_mapping(&self) -> &TypeMapping { &EVENT_TYPE_MAPPING } +} - fn table_name(&self) -> Option<&str> { - Some(EVENT_TABLE) - } - - fn resolve_one(&self) -> Option { - None - } - - fn resolve_many(&self) -> Option { - let mut field = Field::new( +impl ResolvableObject for EventObject { + fn resolvers(&self) -> Vec { + let mut resolve_many = resolve_many( + EVENT_TABLE, + ID_COLUMN, self.name().1, - TypeRef::named(format!("{}Connection", self.type_name())), - |ctx| { - FieldFuture::new(async move { - let mut conn = ctx.data::>()?.acquire().await?; - let connection = parse_connection_arguments(&ctx)?; - let keys = parse_keys_argument(&ctx)?; - let total_count = count_rows(&mut conn, EVENT_TABLE, &keys, &None).await?; - let (data, page_info) = fetch_multiple_rows( - &mut conn, - EVENT_TABLE, - ID_COLUMN, - &keys, - &None, - &None, - &connection, - total_count, - ) - .await?; - let results = connection_output( - &data, - &EVENT_TYPE_MAPPING, - &None, - ID_COLUMN, - total_count, - false, - page_info, - )?; - - Ok(Some(Value::Object(results))) - }) - }, + self.type_name(), + self.type_mapping(), ); + resolve_many = keys_argument(resolve_many); - field = connection_arguments(field); - field = keys_argument(field); - - Some(field) + vec![resolve_many] } fn subscriptions(&self) -> Option> { diff --git a/crates/torii/graphql/src/object/metadata/content.rs b/crates/torii/graphql/src/object/metadata/content.rs index 8c5de128cb..b340b1593a 100644 --- a/crates/torii/graphql/src/object/metadata/content.rs +++ b/crates/torii/graphql/src/object/metadata/content.rs @@ -1,12 +1,11 @@ -use async_graphql::dynamic::Field; - -use super::{ObjectTrait, TypeMapping}; +use super::TypeMapping; use crate::constants::{CONTENT_NAMES, CONTENT_TYPE_NAME}; use crate::mapping::CONTENT_TYPE_MAPPING; +use crate::object::BasicObject; pub struct ContentObject; -impl ObjectTrait for ContentObject { +impl BasicObject for ContentObject { fn name(&self) -> (&str, &str) { CONTENT_NAMES } @@ -18,12 +17,4 @@ impl ObjectTrait for ContentObject { fn type_mapping(&self) -> &TypeMapping { &CONTENT_TYPE_MAPPING } - - fn resolve_one(&self) -> Option { - None - } - - fn resolve_many(&self) -> Option { - None - } } diff --git a/crates/torii/graphql/src/object/metadata/mod.rs b/crates/torii/graphql/src/object/metadata/mod.rs index cbb46c8c79..bbca30b5af 100644 --- a/crates/torii/graphql/src/object/metadata/mod.rs +++ b/crates/torii/graphql/src/object/metadata/mod.rs @@ -7,7 +7,7 @@ use sqlx::{Pool, Row, Sqlite}; use super::connection::page_info::PageInfoObject; use super::connection::{connection_arguments, cursor, parse_connection_arguments}; -use super::ObjectTrait; +use super::{BasicObject, ResolvableObject}; use crate::constants::{ ID_COLUMN, JSON_COLUMN, METADATA_NAMES, METADATA_TABLE, METADATA_TYPE_NAME, }; @@ -29,7 +29,7 @@ impl MetadataObject { } } -impl ObjectTrait for MetadataObject { +impl BasicObject for MetadataObject { fn name(&self) -> (&str, &str) { METADATA_NAMES } @@ -41,17 +41,10 @@ impl ObjectTrait for MetadataObject { fn type_mapping(&self) -> &TypeMapping { &METADATA_TYPE_MAPPING } +} - fn table_name(&self) -> Option<&str> { - Some(METADATA_TABLE) - } - - fn resolve_one(&self) -> Option { - None - } - - fn resolve_many(&self) -> Option { - let table_name = self.table_name().unwrap().to_string(); +impl ResolvableObject for MetadataObject { + fn resolvers(&self) -> Vec { let row_types = self.row_types(); let mut field = Field::new( @@ -59,15 +52,14 @@ impl ObjectTrait for MetadataObject { TypeRef::named(format!("{}Connection", self.type_name())), move |ctx| { let row_types = row_types.clone(); - let table_name = table_name.to_string(); FieldFuture::new(async move { let mut conn = ctx.data::>()?.acquire().await?; let connection = parse_connection_arguments(&ctx)?; - let total_count = count_rows(&mut conn, &table_name, &None, &None).await?; + let total_count = count_rows(&mut conn, METADATA_TABLE, &None, &None).await?; let (data, page_info) = fetch_multiple_rows( &mut conn, - &table_name, + METADATA_TABLE, ID_COLUMN, &None, &None, @@ -94,7 +86,7 @@ impl ObjectTrait for MetadataObject { field = connection_arguments(field); - Some(field) + vec![field] } } diff --git a/crates/torii/graphql/src/object/metadata/social.rs b/crates/torii/graphql/src/object/metadata/social.rs index d936564a25..293f00b139 100644 --- a/crates/torii/graphql/src/object/metadata/social.rs +++ b/crates/torii/graphql/src/object/metadata/social.rs @@ -1,12 +1,11 @@ -use async_graphql::dynamic::Field; - -use super::{ObjectTrait, TypeMapping}; +use super::TypeMapping; use crate::constants::{SOCIAL_NAMES, SOCIAL_TYPE_NAME}; use crate::mapping::SOCIAL_TYPE_MAPPING; +use crate::object::BasicObject; pub struct SocialObject; -impl ObjectTrait for SocialObject { +impl BasicObject for SocialObject { fn name(&self) -> (&str, &str) { SOCIAL_NAMES } @@ -18,12 +17,4 @@ impl ObjectTrait for SocialObject { fn type_mapping(&self) -> &TypeMapping { &SOCIAL_TYPE_MAPPING } - - fn resolve_one(&self) -> Option { - None - } - - fn resolve_many(&self) -> Option { - None - } } diff --git a/crates/torii/graphql/src/object/mod.rs b/crates/torii/graphql/src/object/mod.rs index bb82e96de8..d3ec37726e 100644 --- a/crates/torii/graphql/src/object/mod.rs +++ b/crates/torii/graphql/src/object/mod.rs @@ -11,19 +11,26 @@ use async_graphql::dynamic::{ Enum, Field, FieldFuture, InputObject, InputValue, Object, SubscriptionField, TypeRef, }; use async_graphql::Value; +use convert_case::{Case, Casing}; use sqlx::{Pool, Sqlite}; use self::connection::edge::EdgeObject; use self::connection::{ connection_arguments, connection_output, parse_connection_arguments, ConnectionObject, }; -use crate::constants::ID_COLUMN; +use self::inputs::keys_input::parse_keys_argument; +use self::inputs::order_input::parse_order_argument; use crate::query::data::{count_rows, fetch_multiple_rows, fetch_single_row}; use crate::query::value_mapping_from_row; use crate::types::{TypeMapping, ValueMapping}; use crate::utils::extract; -pub trait ObjectTrait: Send + Sync { +pub enum ObjectVariant { + Basic(Box), + Resolvable(Box), +} + +pub trait BasicObject: Send + Sync { // Name of the graphql object, singular and plural (eg "player" and "players") fn name(&self) -> (&str, &str); @@ -38,6 +45,41 @@ pub trait ObjectTrait: Send + Sync { None } + // Graphql objects that are created from the type mapping + fn objects(&self) -> Vec { + let mut object = Object::new(self.type_name()); + + for (field_name, type_data) in self.type_mapping().clone() { + let field = Field::new(field_name.to_string(), type_data.type_ref(), move |ctx| { + let field_name = field_name.clone(); + + FieldFuture::new(async move { + match ctx.parent_value.try_to_value()? { + Value::Object(values) => { + Ok(Some(values.get(&field_name).unwrap().clone())) // safe unwrap + } + _ => Err("incorrect value, requires Value::Object".into()), + } + }) + }); + + object = object.field(field); + } + + // Add related graphql objects (eg event, system) + if let Some(fields) = self.related_fields() { + for field in fields { + object = object.field(field); + } + } + vec![object] + } +} + +pub trait ResolvableObject: BasicObject { + // Resolvers that returns single and many objects + fn resolvers(&self) -> Vec; + // Resolves subscriptions, returns current object (eg "PlayerAdded") fn subscriptions(&self) -> Option> { None @@ -54,84 +96,8 @@ pub trait ObjectTrait: Send + Sync { None } - fn table_name(&self) -> Option<&str> { - None - } - - // Resolves single object queries, returns current object of type type_name (eg "Player") - fn resolve_one(&self) -> Option { - let type_mapping = self.type_mapping().clone(); - let table_name = self.table_name().unwrap().to_string(); - - Some( - Field::new(self.name().0, TypeRef::named_nn(self.type_name()), move |ctx| { - let type_mapping = type_mapping.clone(); - let table_name = table_name.to_string(); - - FieldFuture::new(async move { - let mut conn = ctx.data::>()?.acquire().await?; - let id = extract::(ctx.args.as_index_map(), ID_COLUMN)?; - let data = fetch_single_row(&mut conn, &table_name, ID_COLUMN, &id).await?; - let model = value_mapping_from_row(&data, &type_mapping, false)?; - Ok(Some(Value::Object(model))) - }) - }) - .argument(InputValue::new(ID_COLUMN, TypeRef::named_nn(TypeRef::ID))), - ) - } - - // Resolves plural object queries, returns type of {type_name}Connection (eg "PlayerConnection") - fn resolve_many(&self) -> Option { - let type_mapping = self.type_mapping().clone(); - let table_name = self.table_name().unwrap().to_string(); - - let mut field = Field::new( - self.name().1, - TypeRef::named(format!("{}Connection", self.type_name())), - move |ctx| { - let type_mapping = type_mapping.clone(); - let table_name = table_name.to_string(); - - FieldFuture::new(async move { - let mut conn = ctx.data::>()?.acquire().await?; - let connection = parse_connection_arguments(&ctx)?; - let total_count = count_rows(&mut conn, &table_name, &None, &None).await?; - let (data, page_info) = fetch_multiple_rows( - &mut conn, - &table_name, - ID_COLUMN, - &None, - &None, - &None, - &connection, - total_count, - ) - .await?; - let results = connection_output( - &data, - &type_mapping, - &None, - ID_COLUMN, - total_count, - false, - page_info, - )?; - - Ok(Some(Value::Object(results))) - }) - }, - ); - - field = connection_arguments(field); - - Some(field) - } - - // Connection type, if resolve_many is Some then register connection graphql obj, includes - // {type_name}Connection and {type_name}Edge according to relay spec https://relay.dev/graphql/connections.htm - fn connection(&self) -> Option> { - self.resolve_many()?; - + // Connection type includes {type_name}Connection and {type_name}Edge according to relay spec https://relay.dev/graphql/connections.htm + fn connection_objects(&self) -> Option> { let edge = EdgeObject::new(self.name().0.to_string(), self.type_name().to_string()); let connection = ConnectionObject::new(self.name().0.to_string(), self.type_name().to_string()); @@ -142,33 +108,89 @@ pub trait ObjectTrait: Send + Sync { Some(objects) } +} - fn objects(&self) -> Vec { - let mut object = Object::new(self.type_name()); - - for (field_name, type_data) in self.type_mapping().clone() { - let field = Field::new(field_name.to_string(), type_data.type_ref(), move |ctx| { - let field_name = field_name.clone(); +// Resolves single object queries, returns current object of type type_name (eg "Player") +pub fn resolve_one( + table_name: &str, + id_column: &str, + field_name: &str, + type_name: &str, + type_mapping: &TypeMapping, +) -> Field { + let type_mapping = type_mapping.clone(); + let table_name = table_name.to_owned(); + let id_column = id_column.to_owned(); + let argument = InputValue::new(id_column.to_case(Case::Camel), TypeRef::named_nn(TypeRef::ID)); + + Field::new(field_name, TypeRef::named_nn(type_name), move |ctx| { + let type_mapping = type_mapping.clone(); + let table_name = table_name.to_owned(); + let id_column = id_column.to_owned(); + + FieldFuture::new(async move { + let mut conn = ctx.data::>()?.acquire().await?; + let id: String = + extract::(ctx.args.as_index_map(), &id_column.to_case(Case::Camel))?; + let data = fetch_single_row(&mut conn, &table_name, &id_column, &id).await?; + let model = value_mapping_from_row(&data, &type_mapping, false)?; + Ok(Some(Value::Object(model))) + }) + }) + .argument(argument) +} - FieldFuture::new(async move { - match ctx.parent_value.try_to_value()? { - Value::Object(values) => { - Ok(Some(values.get(&field_name).unwrap().clone())) // safe unwrap - } - _ => Err("incorrect value, requires Value::Object".into()), - } - }) - }); +// Resolves plural object queries, returns type of {type_name}Connection (eg "PlayerConnection") +pub fn resolve_many( + table_name: &str, + id_column: &str, + field_name: &str, + type_name: &str, + type_mapping: &TypeMapping, +) -> Field { + let type_mapping = type_mapping.clone(); + let table_name = table_name.to_owned(); + let id_column = id_column.to_owned(); + + let mut field = + Field::new(field_name, TypeRef::named(format!("{}Connection", type_name)), move |ctx| { + let type_mapping = type_mapping.clone(); + let table_name = table_name.to_owned(); + let id_column = id_column.to_owned(); + + FieldFuture::new(async move { + let mut conn = ctx.data::>()?.acquire().await?; + let connection = parse_connection_arguments(&ctx)?; + let keys = parse_keys_argument(&ctx)?; + let order = parse_order_argument(&ctx); + let total_count = count_rows(&mut conn, &table_name, &keys, &None).await?; + + let (data, page_info) = fetch_multiple_rows( + &mut conn, + &table_name, + &id_column, + &keys, + &order, + &None, + &connection, + total_count, + ) + .await?; + let results = connection_output( + &data, + &type_mapping, + &order, + &id_column, + total_count, + false, + page_info, + )?; + + Ok(Some(Value::Object(results))) + }) + }); - object = object.field(field); - } + field = connection_arguments(field); - // Add related graphql objects (eg event, system) - if let Some(fields) = self.related_fields() { - for field in fields { - object = object.field(field); - } - } - vec![object] - } + field } diff --git a/crates/torii/graphql/src/object/model.rs b/crates/torii/graphql/src/object/model.rs index 59bd4cc89d..42c95a2127 100644 --- a/crates/torii/graphql/src/object/model.rs +++ b/crates/torii/graphql/src/object/model.rs @@ -1,30 +1,26 @@ use async_graphql::dynamic::indexmap::IndexMap; use async_graphql::dynamic::{ - Enum, Field, FieldFuture, InputObject, InputValue, SubscriptionField, SubscriptionFieldFuture, - TypeRef, + Enum, Field, InputObject, InputValue, SubscriptionField, SubscriptionFieldFuture, TypeRef, }; use async_graphql::{Name, Value}; -use sqlx::{Pool, Sqlite}; use tokio_stream::StreamExt; use torii_core::simple_broker::SimpleBroker; use torii_core::types::Model; -use super::connection::{connection_arguments, connection_output, parse_connection_arguments}; -use super::inputs::order_input::parse_order_argument; -use super::{ObjectTrait, TypeMapping, ValueMapping}; +use super::{resolve_many, BasicObject, ResolvableObject, TypeMapping, ValueMapping}; use crate::constants::{ ID_COLUMN, MODEL_NAMES, MODEL_ORDER_FIELD_TYPE_NAME, MODEL_ORDER_TYPE_NAME, MODEL_TABLE, MODEL_TYPE_NAME, ORDER_ASC, ORDER_DESC, ORDER_DIR_TYPE_NAME, }; use crate::mapping::MODEL_TYPE_MAPPING; -use crate::query::data::{count_rows, fetch_multiple_rows}; +use crate::object::resolve_one; const ORDER_BY_NAME: &str = "NAME"; const ORDER_BY_HASH: &str = "CLASS_HASH"; pub struct ModelObject; -impl ObjectTrait for ModelObject { +impl BasicObject for ModelObject { fn name(&self) -> (&str, &str) { MODEL_NAMES } @@ -36,11 +32,9 @@ impl ObjectTrait for ModelObject { fn type_mapping(&self) -> &TypeMapping { &MODEL_TYPE_MAPPING } +} - fn table_name(&self) -> Option<&str> { - Some(MODEL_TABLE) - } - +impl ResolvableObject for ModelObject { fn input_objects(&self) -> Option> { let order_input = InputObject::new(MODEL_ORDER_TYPE_NAME) .field(InputValue::new("direction", TypeRef::named_nn(ORDER_DIR_TYPE_NAME))) @@ -57,52 +51,26 @@ impl ObjectTrait for ModelObject { Some(vec![direction, field_order]) } - fn resolve_many(&self) -> Option { - let type_mapping = self.type_mapping().clone(); - let table_name = self.table_name().unwrap().to_string(); + fn resolvers(&self) -> Vec { + let resolve_one = resolve_one( + MODEL_TABLE, + ID_COLUMN, + self.name().0, + self.type_name(), + self.type_mapping(), + ); - let mut field = Field::new( + let mut resolve_many = resolve_many( + MODEL_TABLE, + ID_COLUMN, self.name().1, - TypeRef::named(format!("{}Connection", self.type_name())), - move |ctx| { - let type_mapping = type_mapping.clone(); - let table_name = table_name.to_string(); - - FieldFuture::new(async move { - let mut conn = ctx.data::>()?.acquire().await?; - let order = parse_order_argument(&ctx); - let connection = parse_connection_arguments(&ctx)?; - let total_count = count_rows(&mut conn, &table_name, &None, &None).await?; - let (data, page_info) = fetch_multiple_rows( - &mut conn, - &table_name, - ID_COLUMN, - &None, - &order, - &None, - &connection, - total_count, - ) - .await?; - let results = connection_output( - &data, - &type_mapping, - &order, - ID_COLUMN, - total_count, - false, - page_info, - )?; - - Ok(Some(Value::Object(results))) - }) - }, + self.type_name(), + self.type_mapping(), ); + resolve_many = + resolve_many.argument(InputValue::new("order", TypeRef::named(MODEL_ORDER_TYPE_NAME))); - field = connection_arguments(field); - field = field.argument(InputValue::new("order", TypeRef::named(MODEL_ORDER_TYPE_NAME))); - - Some(field) + vec![resolve_one, resolve_many] } fn subscriptions(&self) -> Option> { diff --git a/crates/torii/graphql/src/object/model_data.rs b/crates/torii/graphql/src/object/model_data.rs index b193674ce8..83263cd61b 100644 --- a/crates/torii/graphql/src/object/model_data.rs +++ b/crates/torii/graphql/src/object/model_data.rs @@ -8,8 +8,10 @@ use super::connection::{connection_arguments, connection_output, parse_connectio use super::inputs::order_input::{order_argument, parse_order_argument, OrderInputObject}; use super::inputs::where_input::{parse_where_argument, where_argument, WhereInputObject}; use super::inputs::InputObjectTrait; -use super::{ObjectTrait, TypeMapping, ValueMapping}; -use crate::constants::{ENTITY_ID_COLUMN, ENTITY_TABLE, ID_COLUMN, INTERNAL_ENTITY_ID_KEY}; +use super::{BasicObject, ResolvableObject, TypeMapping, ValueMapping}; +use crate::constants::{ + ENTITY_ID_COLUMN, ENTITY_TABLE, EVENT_ID_COLUMN, ID_COLUMN, INTERNAL_ENTITY_ID_KEY, +}; use crate::mapping::ENTITY_TYPE_MAPPING; use crate::query::data::{count_rows, fetch_multiple_rows, fetch_single_row}; use crate::query::value_mapping_from_row; @@ -47,7 +49,7 @@ impl ModelDataObject { } } -impl ObjectTrait for ModelDataObject { +impl BasicObject for ModelDataObject { fn name(&self) -> (&str, &str) { (&self.name, &self.plural_name) } @@ -60,6 +62,23 @@ impl ObjectTrait for ModelDataObject { &self.type_mapping } + fn objects(&self) -> Vec { + let mut objects = data_objects_recursion( + self.type_name(), + self.type_mapping(), + vec![self.type_name().to_string()], + ); + + // root object requires entity_field association + let mut root = objects.pop().unwrap(); + root = root.field(entity_field()); + + objects.push(root); + objects + } +} + +impl ResolvableObject for ModelDataObject { fn input_objects(&self) -> Option> { Some(vec![self.where_input.input_object(), self.order_input.input_object()]) } @@ -68,11 +87,7 @@ impl ObjectTrait for ModelDataObject { self.order_input.enum_objects() } - fn resolve_one(&self) -> Option { - None - } - - fn resolve_many(&self) -> Option { + fn resolvers(&self) -> Vec { let type_name = self.type_name.clone(); let type_mapping = self.type_mapping.clone(); let where_mapping = self.where_input.type_mapping.clone(); @@ -88,13 +103,12 @@ impl ObjectTrait for ModelDataObject { let order = parse_order_argument(&ctx); let filters = parse_where_argument(&ctx, &where_mapping)?; let connection = parse_connection_arguments(&ctx)?; - let id_column = "event_id"; let total_count = count_rows(&mut conn, &type_name, &None, &filters).await?; let (data, page_info) = fetch_multiple_rows( &mut conn, &type_name, - id_column, + EVENT_ID_COLUMN, &None, &order, &filters, @@ -106,7 +120,7 @@ impl ObjectTrait for ModelDataObject { &data, &type_mapping, &order, - id_column, + EVENT_ID_COLUMN, total_count, true, page_info, @@ -121,22 +135,7 @@ impl ObjectTrait for ModelDataObject { field = where_argument(field, self.type_name()); field = order_argument(field, self.type_name()); - Some(field) - } - - fn objects(&self) -> Vec { - let mut objects = data_objects_recursion( - self.type_name(), - self.type_mapping(), - vec![self.type_name().to_string()], - ); - - // root object requires entity_field association - let mut root = objects.pop().unwrap(); - root = root.field(entity_field()); - - objects.push(root); - objects + vec![field] } } diff --git a/crates/torii/graphql/src/object/transaction.rs b/crates/torii/graphql/src/object/transaction.rs index 1f5b0a75f0..34fc0583c2 100644 --- a/crates/torii/graphql/src/object/transaction.rs +++ b/crates/torii/graphql/src/object/transaction.rs @@ -1,17 +1,15 @@ -use async_graphql::dynamic::{Field, FieldFuture, InputValue, TypeRef}; -use async_graphql::Value; -use convert_case::{Case, Casing}; -use sqlx::{Pool, Sqlite}; +use async_graphql::dynamic::Field; -use super::{ObjectTrait, TypeMapping}; -use crate::constants::{TRANSACTION_NAMES, TRANSACTION_TABLE, TRANSACTION_TYPE_NAME}; +use super::{BasicObject, ResolvableObject, TypeMapping}; +use crate::constants::{ + ID_COLUMN, TRANSACTION_HASH_COLUMN, TRANSACTION_NAMES, TRANSACTION_TABLE, TRANSACTION_TYPE_NAME, +}; use crate::mapping::TRANSACTION_MAPPING; -use crate::query::data::fetch_single_row; -use crate::query::value_mapping_from_row; -use crate::utils::extract; +use crate::object::{resolve_many, resolve_one}; + pub struct TransactionObject; -impl ObjectTrait for TransactionObject { +impl BasicObject for TransactionObject { fn name(&self) -> (&str, &str) { TRANSACTION_NAMES } @@ -23,35 +21,26 @@ impl ObjectTrait for TransactionObject { fn type_mapping(&self) -> &TypeMapping { &TRANSACTION_MAPPING } +} - fn table_name(&self) -> Option<&str> { - Some(TRANSACTION_TABLE) - } - - fn resolve_one(&self) -> Option { - let type_mapping = self.type_mapping().clone(); - let table_name = self.table_name().unwrap().to_string(); - - Some( - Field::new(self.name().0, TypeRef::named_nn(self.type_name()), move |ctx| { - let type_mapping = type_mapping.clone(); - let table_name = table_name.to_string(); - - FieldFuture::new(async move { - let mut conn = ctx.data::>()?.acquire().await?; - let hash = - extract::(ctx.args.as_index_map(), &COLUMN.to_case(Case::Camel))?; - let data = fetch_single_row(&mut conn, &table_name, COLUMN, &hash).await?; - let model = value_mapping_from_row(&data, &type_mapping, false)?; - Ok(Some(Value::Object(model))) - }) - }) - .argument(InputValue::new( - COLUMN.to_case(Case::Camel), - TypeRef::named_nn(TypeRef::STRING), - )), - ) +impl ResolvableObject for TransactionObject { + fn resolvers(&self) -> Vec { + let resolve_one = resolve_one( + TRANSACTION_TABLE, + TRANSACTION_HASH_COLUMN, + self.name().0, + self.type_name(), + self.type_mapping(), + ); + + let resolve_many = resolve_many( + TRANSACTION_TABLE, + ID_COLUMN, + self.name().1, + self.type_name(), + self.type_mapping(), + ); + + vec![resolve_one, resolve_many] } } - -const COLUMN: &str = "transaction_hash"; diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index 1d08bfe0f2..417c0c6777 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -1,7 +1,5 @@ use anyhow::Result; -use async_graphql::dynamic::{ - Field, Object, Scalar, Schema, Subscription, SubscriptionField, Union, -}; +use async_graphql::dynamic::{Object, Scalar, Schema, Subscription, Union}; use convert_case::{Case, Casing}; use sqlx::SqlitePool; use torii_core::types::Model; @@ -10,7 +8,6 @@ use super::object::connection::page_info::PageInfoObject; use super::object::entity::EntityObject; use super::object::event::EventObject; use super::object::model_data::ModelDataObject; -use super::object::ObjectTrait; use super::types::ScalarType; use crate::constants::{QUERY_TYPE_NAME, SUBSCRIPTION_TYPE_NAME}; use crate::object::metadata::content::ContentObject; @@ -18,6 +15,7 @@ use crate::object::metadata::social::SocialObject; use crate::object::metadata::MetadataObject; use crate::object::model::ModelObject; use crate::object::transaction::TransactionObject; +use crate::object::ObjectVariant; use crate::query::type_mapping_query; // The graphql schema is built dynamically at runtime, this is because we won't know the schema of @@ -28,19 +26,9 @@ pub async fn build_schema(pool: &SqlitePool) -> Result { // build world gql objects let (objects, union) = build_objects(pool).await?; - // collect resolvers for single and plural queries - let queries: Vec = objects - .iter() - .flat_map(|object| vec![object.resolve_one(), object.resolve_many()].into_iter().flatten()) - .collect(); - - // add field resolvers to query root - let mut query_root = Object::new(QUERY_TYPE_NAME); - for query in queries { - query_root = query_root.field(query); - } - let mut schema_builder = Schema::build(QUERY_TYPE_NAME, None, Some(SUBSCRIPTION_TYPE_NAME)); + let mut query_root = Object::new(QUERY_TYPE_NAME); + let mut subscription_root = Subscription::new(SUBSCRIPTION_TYPE_NAME); // register model data unions schema_builder = schema_builder.register(union); @@ -50,49 +38,55 @@ pub async fn build_schema(pool: &SqlitePool) -> Result { schema_builder = schema_builder.register(Scalar::new(scalar_type)); } + // register objects for object in &objects { - // register enum objects - if let Some(input_objects) = object.enum_objects() { - for input in input_objects { - schema_builder = schema_builder.register(input); + match object { + ObjectVariant::Basic(object) => { + // register objects + for inner_object in object.objects() { + schema_builder = schema_builder.register(inner_object) + } } - } - - // register input objects, whereInput and orderBy - if let Some(input_objects) = object.input_objects() { - for input in input_objects { - schema_builder = schema_builder.register(input); - } - } - - // register connection types, relay - if let Some(conn_objects) = object.connection() { - for object in conn_objects { - schema_builder = schema_builder.register(object); + ObjectVariant::Resolvable(object) => { + // register objects + for inner_object in object.objects() { + schema_builder = schema_builder.register(inner_object) + } + + // register resolvers + for resolver in object.resolvers() { + query_root = query_root.field(resolver); + } + + // register connection types, relay + if let Some(conn_objects) = object.connection_objects() { + for conn in conn_objects { + schema_builder = schema_builder.register(conn); + } + } + + // register enum objects + if let Some(input_objects) = object.enum_objects() { + for input in input_objects { + schema_builder = schema_builder.register(input); + } + } + + // register input objects, whereInput and orderBy + if let Some(input_objects) = object.input_objects() { + for input in input_objects { + schema_builder = schema_builder.register(input); + } + } + + // register subscription + if let Some(subscriptions) = object.subscriptions() { + for sub in subscriptions { + subscription_root = subscription_root.field(sub); + } + } } } - - // register gql objects - let object_collection = object.objects(); - for object in object_collection { - schema_builder = schema_builder.register(object); - } - } - - // collect resolvers for single subscriptions - let mut subscription_fields: Vec = Vec::new(); - for object in &objects { - if let Some(subscriptions) = object.subscriptions() { - for sub in subscriptions { - subscription_fields.push(sub); - } - } - } - - // add field resolvers to subscription root - let mut subscription_root = Subscription::new(SUBSCRIPTION_TYPE_NAME); - for field in subscription_fields { - subscription_root = subscription_root.field(field); } schema_builder @@ -103,20 +97,20 @@ pub async fn build_schema(pool: &SqlitePool) -> Result { .map_err(|e| e.into()) } -async fn build_objects(pool: &SqlitePool) -> Result<(Vec>, Union)> { +async fn build_objects(pool: &SqlitePool) -> Result<(Vec, Union)> { let mut conn = pool.acquire().await?; let models: Vec = sqlx::query_as("SELECT * FROM models").fetch_all(&mut *conn).await?; // predefined objects - let mut objects: Vec> = vec![ - Box::new(EntityObject), - Box::new(EventObject), - Box::new(SocialObject), - Box::new(ContentObject), - Box::new(MetadataObject), - Box::new(ModelObject), - Box::new(PageInfoObject), - Box::new(TransactionObject), + let mut objects: Vec = vec![ + ObjectVariant::Resolvable(Box::new(EntityObject)), + ObjectVariant::Resolvable(Box::new(EventObject)), + ObjectVariant::Resolvable(Box::new(MetadataObject)), + ObjectVariant::Resolvable(Box::new(ModelObject)), + ObjectVariant::Resolvable(Box::new(TransactionObject)), + ObjectVariant::Basic(Box::new(SocialObject)), + ObjectVariant::Basic(Box::new(ContentObject)), + ObjectVariant::Basic(Box::new(PageInfoObject)), ]; // model union object @@ -132,7 +126,11 @@ async fn build_objects(pool: &SqlitePool) -> Result<(Vec>, union = union.possible_type(&type_name); - objects.push(Box::new(ModelDataObject::new(field_name, type_name, type_mapping))); + objects.push(ObjectVariant::Resolvable(Box::new(ModelDataObject::new( + field_name, + type_name, + type_mapping, + )))); } }