Skip to content

Commit

Permalink
feat(torii): dynamic schema for storage components
Browse files Browse the repository at this point in the history
  • Loading branch information
broody committed May 31, 2023
1 parent a8c43b6 commit 7e39eab
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 33 deletions.
4 changes: 4 additions & 0 deletions crates/torii/scripts/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
pushd $(dirname "$0")/..

cargo test --bin torii -- --nocapture
54 changes: 54 additions & 0 deletions crates/torii/sqlx-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,60 @@
},
"query": "\n SELECT \n id,\n name,\n address,\n class_hash,\n transaction_hash,\n storage_schema,\n created_at as \"created_at: _\"\n FROM components WHERE id = $1\n "
},
"f026d7d5f73e91c5f342fa624b46b394dfe968cab5702776ab6968c1e864b695": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "address",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "class_hash",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "transaction_hash",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "storage_schema",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "created_at: _",
"ordinal": 6,
"type_info": "Datetime"
}
],
"nullable": [
false,
false,
false,
false,
false,
false,
false
],
"parameters": {
"Right": 0
}
},
"query": "\n SELECT \n id,\n name,\n address,\n class_hash,\n transaction_hash,\n storage_schema,\n created_at as \"created_at: _\"\n FROM components\n "
},
"f7b294431f97b0186f93a660ff986788f94febd42c44ebcceee2db9fa10a854f": {
"describe": {
"columns": [
Expand Down
24 changes: 23 additions & 1 deletion crates/torii/src/graphql/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use sqlx::pool::PoolConnection;
use sqlx::{FromRow, Pool, Result, Sqlite};

use super::types::ScalarType;
use super::utils::remove_quotes;
use super::{ObjectTraitInstance, ObjectTraitStatic, TypeMapping, ValueMapping};

#[derive(FromRow, Deserialize)]
Expand Down Expand Up @@ -63,7 +64,7 @@ impl ObjectTraitInstance for ComponentObject {
Field::new(self.name(), TypeRef::named_nn(self.type_name()), |ctx| {
FieldFuture::new(async move {
let mut conn = ctx.data::<Pool<Sqlite>>()?.acquire().await?;
let id = ctx.args.try_get("id")?.string()?.replace('\"', "");
let id = remove_quotes(ctx.args.try_get("id")?.string()?);
let component_values = component_by_id(&mut conn, &id).await?;
Ok(Some(FieldValue::owned_any(component_values)))
})
Expand Down Expand Up @@ -95,6 +96,27 @@ async fn component_by_id(conn: &mut PoolConnection<Sqlite>, id: &str) -> Result<
Ok(value_mapping(component))
}

pub async fn components(conn: &mut PoolConnection<Sqlite>) -> Result<Vec<ValueMapping>> {
let components = sqlx::query_as!(
Component,
r#"
SELECT
id,
name,
address,
class_hash,
transaction_hash,
storage_schema,
created_at as "created_at: _"
FROM components
"#
)
.fetch_all(conn)
.await?;

Ok(components.into_iter().map(value_mapping).collect())
}

fn value_mapping(component: Component) -> ValueMapping {
IndexMap::from([
(Name::new("id"), Value::from(component.id)),
Expand Down
3 changes: 2 additions & 1 deletion crates/torii/src/graphql/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use sqlx::pool::PoolConnection;
use sqlx::{FromRow, Pool, Result, Sqlite};

use super::types::ScalarType;
use super::utils::remove_quotes;
use super::{ObjectTraitInstance, ObjectTraitStatic, TypeMapping, ValueMapping};

#[derive(FromRow, Deserialize)]
Expand Down Expand Up @@ -60,7 +61,7 @@ impl ObjectTraitInstance for EntityObject {
Field::new(self.name(), TypeRef::named_nn(self.type_name()), |ctx| {
FieldFuture::new(async move {
let mut conn = ctx.data::<Pool<Sqlite>>()?.acquire().await?;
let id = ctx.args.try_get("id")?.string()?.replace('\"', "");
let id = remove_quotes(ctx.args.try_get("id")?.string()?);
let entity_values = entity_by_id(&mut conn, &id).await?;
Ok(Some(FieldValue::owned_any(entity_values)))
})
Expand Down
3 changes: 2 additions & 1 deletion crates/torii/src/graphql/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use sqlx::{FromRow, Pool, Result, Sqlite};

use super::system_call::system_call_by_id;
use super::types::ScalarType;
use super::utils::remove_quotes;
use super::utils::value_accessor::ObjectAccessor;
use super::{ObjectTraitInstance, ObjectTraitStatic, TypeMapping, ValueMapping};

Expand Down Expand Up @@ -63,7 +64,7 @@ impl ObjectTraitInstance for EventObject {
Field::new(self.name(), TypeRef::named_nn(self.type_name()), |ctx| {
FieldFuture::new(async move {
let mut conn = ctx.data::<Pool<Sqlite>>()?.acquire().await?;
let id = ctx.args.try_get("id")?.string()?.replace('\"', "");
let id = remove_quotes(ctx.args.try_get("id")?.string()?);
let event_values = event_by_id(&mut conn, &id).await?;

Ok(Some(FieldValue::owned_any(event_values)))
Expand Down
59 changes: 47 additions & 12 deletions crates/torii/src/graphql/schema.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
use async_graphql::dynamic::{Object, Scalar, Schema, SchemaError};
use async_graphql::dynamic::{Object, Scalar, Schema};
use async_graphql_parser::parse_schema;
use async_graphql_parser::types::TypeSystemDefinition;
use sqlx::SqlitePool;

use super::component::ComponentObject;
use super::component::{components, ComponentObject};
use super::entity::EntityObject;
use super::event::EventObject;
use super::system::SystemObject;
use super::system_call::SystemCallObject;
use super::types::ScalarType;
use super::utils::remove_quotes;
use super::{ObjectTraitInstance, ObjectTraitStatic};

pub async fn build_schema(pool: &SqlitePool) -> Result<Schema, SchemaError> {
// base gql objects
let objects: Vec<Box<dyn ObjectTraitInstance>> = vec![
Box::new(EntityObject::new()),
Box::new(ComponentObject::new()),
Box::new(SystemObject::new()),
Box::new(EventObject::new()),
Box::new(SystemCallObject::new()),
];
pub async fn build_schema(pool: &SqlitePool) -> anyhow::Result<Schema> {
// gql objects
let mut objects = base_objects();
objects.extend(storage_objects(pool).await?);

// collect field resolvers
let mut fields = Vec::new();
Expand All @@ -42,5 +40,42 @@ pub async fn build_schema(pool: &SqlitePool) -> Result<Schema, SchemaError> {
schema_builder = schema_builder.register(object.create());
}

schema_builder.register(query_root).data(pool.clone()).finish()
schema_builder.register(query_root).data(pool.clone()).finish().map_err(|e| e.into())
}

// predefined base objects
fn base_objects() -> Vec<Box<dyn ObjectTraitInstance>> {
vec![
Box::new(EntityObject::new()),
Box::new(ComponentObject::new()),
Box::new(SystemObject::new()),
Box::new(EventObject::new()),
Box::new(SystemCallObject::new()),
]
}

// storage objects built according to their schema
async fn storage_objects(pool: &SqlitePool) -> anyhow::Result<Vec<Box<dyn ObjectTraitInstance>>> {
let mut conn = pool.acquire().await?;
let objects: Vec<Box<dyn ObjectTraitInstance>> = Vec::new();

// query for all components
let components = components(&mut conn).await?;
for component in components {
let str = component.get("storageSchema").expect("storage schema not found");

let parsed = parse_schema(remove_quotes(&str.to_string()))?;
if parsed.definitions.len() != 1 {
return Err(anyhow::anyhow!("only one type definition per component"));
}

match parsed.definitions.first() {
Some(TypeSystemDefinition::Type(ty)) => {
// TODO: create storage objects
}
_ => return Err(anyhow::anyhow!("only type definition supported")),
}
}

Ok(objects)
}
4 changes: 2 additions & 2 deletions crates/torii/src/graphql/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ async fn graphql_playground() -> impl IntoResponse {
Html(playground_source(GraphQLPlaygroundConfig::new("/playground")))
}

pub async fn start_graphql(pool: &Pool<Sqlite>) -> std::io::Result<()> {
let schema = build_schema(pool).await.expect("failed to build schema");
pub async fn start_graphql(pool: &Pool<Sqlite>) -> anyhow::Result<()> {
let schema = build_schema(pool).await?;

let app = Route::new()
.at("/query", get(graphiql).post(GraphQL::new(schema.clone())))
Expand Down
3 changes: 2 additions & 1 deletion crates/torii/src/graphql/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use sqlx::{FromRow, Pool, Result, Sqlite};

use super::system_call::system_calls_by_system_id;
use super::types::ScalarType;
use super::utils::remove_quotes;
use super::utils::value_accessor::ObjectAccessor;
use super::{ObjectTraitInstance, ObjectTraitStatic, TypeMapping, ValueMapping};

Expand Down Expand Up @@ -65,7 +66,7 @@ impl ObjectTraitInstance for SystemObject {
Field::new(self.name(), TypeRef::named_nn(self.type_name()), |ctx| {
FieldFuture::new(async move {
let mut conn = ctx.data::<Pool<Sqlite>>()?.acquire().await?;
let id = ctx.args.try_get("id")?.string()?.replace('\"', "");
let id = remove_quotes(ctx.args.try_get("id")?.string()?);
let system_values = system_by_id(&mut conn, &id).await?;
Ok(Some(FieldValue::owned_any(system_values)))
})
Expand Down
4 changes: 4 additions & 0 deletions crates/torii/src/graphql/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
pub mod value_accessor;

pub fn remove_quotes(s: &str) -> String {
s.replace(&['\"', '\''][..], "")
}
27 changes: 15 additions & 12 deletions crates/torii/src/tests/components_test.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
// #[cfg(test)]
// mod tests {
// use async_graphql_parser::parse_schema;
// use sqlx::SqlitePool;
#[cfg(test)]
mod tests {
use sqlx::SqlitePool;

// use crate::graphql::entity::Entity;
// use crate::graphql::schema::{self, build_schema};
// use crate::tests::common::run_graphql_query;
use crate::graphql::schema::build_schema;

// #[sqlx::test(migrations = "./migrations", fixtures("components"))]
// async fn test_dynamic_component(pool: SqlitePool) {
// let schema = build_schema(&pool).await;
// }
// }
#[sqlx::test(migrations = "./migrations", fixtures("components"))]
async fn test_dynamic_component(pool: SqlitePool) {
let schema = build_schema(&pool).await.expect("failed to build schema");
}

#[sqlx::test(migrations = "./migrations", fixtures("components_bad_schema"))]
async fn test_bad_schema(pool: SqlitePool) {
let result = build_schema(&pool).await;
assert!(result.is_err());
}
}
6 changes: 3 additions & 3 deletions crates/torii/src/tests/fixtures/components.sql
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
INSERT INTO components (id, name, address, class_hash, transaction_hash, storage_schema)
VALUES ('component_1', 'Game', '0x0', '0x0', '0x0',
'type GameComponent { isFinished: Boolean! entity: Entity! component: Component! createdAt: DateTime! }');
'type Game { isFinished: Boolean! entity: Entity! component: Component! createdAt: DateTime! }');

INSERT INTO components (id, name, address, class_hash, transaction_hash, storage_schema)
VALUES ('component_2', 'Stats', '0x0', '0x0', '0x0',
'type StatsComponent { health: U8! entity: Entity! component: Component! createdAt: DateTime! }');
'type Stats { health: U8! entity: Entity! component: Component! createdAt: DateTime! }');

INSERT INTO components (id, name, address, class_hash, transaction_hash, storage_schema)
VALUES ('component_3', 'Cash', '0x0', '0x0', '0x0',
'type CashComponent { amount: U32! entity: Entity! component: Component! createdAt: DateTime! }');
'type Cash { amount: U32! entity: Entity! component: Component! createdAt: DateTime! }');
4 changes: 4 additions & 0 deletions crates/torii/src/tests/fixtures/components_bad_schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
INSERT INTO components (id, name, address, class_hash, transaction_hash, storage_schema)
VALUES ('component_1', 'Game', '0x0', '0x0', '0x0',
'type Game {{ isFinished: Boolean! }');

0 comments on commit 7e39eab

Please sign in to comment.