Skip to content

Commit

Permalink
Torii graphql custom types (#944)
Browse files Browse the repository at this point in the history
* chore(torii): types test proj

* feat(torii): graphql custom types support

* type definition enum

* rename model states to data

* resolve nested types

* child field objects

* use cairo types

* add nested structs to model

* more nested

* update types test

* refactor schema recursion

* refactor id query

* update cairotype to primitive

* fix nameWhereInput filtering

* fix clippy & fmt

* fix nameWhereInput requirement and add back order argument

* add back u256 in test
  • Loading branch information
broody authored Oct 3, 2023
1 parent 8360af8 commit cb79f2b
Show file tree
Hide file tree
Showing 23 changed files with 648 additions and 431 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion crates/torii/graphql/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,21 @@ indexmap = "1.9.3"
scarb-ui.workspace = true
serde.workspace = true
serde_json.workspace = true
strum.workspace = true
strum_macros.workspace = true
sqlx = { version = "0.6.2", features = [ "chrono", "macros", "offline", "runtime-actix-rustls", "sqlite", "uuid" ] }
tokio-stream = "0.1.11"
tokio-util = "0.7.7"
tokio.workspace = true
torii-core = { path = "../core" }
dojo-types = { path = "../../dojo-types" }
tracing.workspace = true
url.workspace = true
warp.workspace = true

[dev-dependencies]
camino.workspace = true
dojo-test-utils = { path = "../../dojo-test-utils" }
dojo-types = { path = "../../dojo-types" }
dojo-world = { path = "../../dojo-world" }
starknet-crypto.workspace = true
starknet.workspace = true
Expand Down
14 changes: 8 additions & 6 deletions crates/torii/graphql/src/object/connection/edge.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use async_graphql::dynamic::TypeRef;
use async_graphql::Name;
use indexmap::IndexMap;

use crate::object::{ObjectTrait, TypeMapping};
use crate::types::ScalarType;
use crate::object::ObjectTrait;
use crate::types::{GraphqlType, TypeData, TypeMapping};

pub struct EdgeObject {
pub name: String,
Expand All @@ -13,9 +12,12 @@ pub struct EdgeObject {

impl EdgeObject {
pub fn new(name: String, type_name: String) -> Self {
let type_mapping = IndexMap::from([
(Name::new("node"), TypeRef::named(type_name.clone())),
(Name::new("cursor"), TypeRef::named_nn(ScalarType::Cursor.to_string())),
let type_mapping = TypeMapping::from([
(Name::new("node"), TypeData::Simple(TypeRef::named(type_name.clone()))),
(
Name::new("cursor"),
TypeData::Simple(TypeRef::named(GraphqlType::Cursor.to_string())),
),
]);

Self {
Expand Down
18 changes: 10 additions & 8 deletions crates/torii/graphql/src/object/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ use async_graphql::dynamic::{Field, InputValue, ResolverContext, TypeRef};
use async_graphql::{Error, Name, Value};
use base64::engine::general_purpose;
use base64::Engine as _;
use indexmap::IndexMap;
use serde_json::Number;

use super::{ObjectTrait, TypeMapping, ValueMapping};
use crate::types::ScalarType;
use super::ObjectTrait;
use crate::types::{GraphqlType, TypeData, TypeMapping, ValueMapping};
use crate::utils::extract_value::extract;

pub mod edge;
Expand All @@ -28,9 +27,12 @@ pub struct ConnectionObject {

impl ConnectionObject {
pub fn new(name: String, type_name: String) -> Self {
let type_mapping = IndexMap::from([
(Name::new("edges"), TypeRef::named_list(format!("{}Edge", type_name))),
(Name::new("totalCount"), TypeRef::named_nn(TypeRef::INT)),
let type_mapping = TypeMapping::from([
(
Name::new("edges"),
TypeData::Simple(TypeRef::named_list(format!("{}Edge", type_name))),
),
(Name::new("totalCount"), TypeData::Simple(TypeRef::named_nn(TypeRef::INT))),
]);

Self {
Expand Down Expand Up @@ -93,8 +95,8 @@ pub fn connection_arguments(field: Field) -> Field {
field
.argument(InputValue::new("first", TypeRef::named(TypeRef::INT)))
.argument(InputValue::new("last", TypeRef::named(TypeRef::INT)))
.argument(InputValue::new("before", TypeRef::named(ScalarType::Cursor.to_string())))
.argument(InputValue::new("after", TypeRef::named(ScalarType::Cursor.to_string())))
.argument(InputValue::new("before", TypeRef::named(GraphqlType::Cursor.to_string())))
.argument(InputValue::new("after", TypeRef::named(GraphqlType::Cursor.to_string())))
}

pub fn connection_output(data: Vec<ValueMapping>, total_count: i64) -> ValueMapping {
Expand Down
19 changes: 12 additions & 7 deletions crates/torii/graphql/src/object/connection/page_info.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use async_graphql::dynamic::TypeRef;
use async_graphql::Name;
use indexmap::IndexMap;

use crate::object::{ObjectTrait, TypeMapping};
use crate::types::ScalarType;
use crate::types::{GraphqlType, TypeData};

pub struct PageInfoObject {
pub type_mapping: TypeMapping,
Expand All @@ -12,11 +11,17 @@ pub struct PageInfoObject {
impl Default for PageInfoObject {
fn default() -> Self {
Self {
type_mapping: IndexMap::from([
(Name::new("hasPreviousPage"), TypeRef::named(TypeRef::BOOLEAN)),
(Name::new("hasNextPage"), TypeRef::named(TypeRef::BOOLEAN)),
(Name::new("startCursor"), TypeRef::named(ScalarType::Cursor.to_string())),
(Name::new("endCursor"), TypeRef::named(ScalarType::Cursor.to_string())),
type_mapping: TypeMapping::from([
(Name::new("hasPreviousPage"), TypeData::Simple(TypeRef::named(TypeRef::BOOLEAN))),
(Name::new("hasNextPage"), TypeData::Simple(TypeRef::named(TypeRef::BOOLEAN))),
(
Name::new("startCursor"),
TypeData::Simple(TypeRef::named(GraphqlType::Cursor.to_string())),
),
(
Name::new("endCursor"),
TypeData::Simple(TypeRef::named(GraphqlType::Cursor.to_string())),
),
]),
}
}
Expand Down
81 changes: 44 additions & 37 deletions crates/torii/graphql/src/object/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ use super::connection::{
connection_arguments, connection_output, decode_cursor, parse_connection_arguments,
ConnectionArguments,
};
use super::model_state::{model_state_by_id_query, type_mapping_query};
use super::model_data::model_data_by_id_query;
use super::{ObjectTrait, TypeMapping, ValueMapping};
use crate::constants::DEFAULT_LIMIT;
use crate::query::{query_by_id, ID};
use crate::types::ScalarType;
use crate::query::{query_by_id, type_mapping_query};
use crate::types::{GraphqlType, TypeData};
use crate::utils::csv_to_vec;
use crate::utils::extract_value::extract;

Expand All @@ -29,11 +29,17 @@ impl Default for EntityObject {
fn default() -> Self {
Self {
type_mapping: IndexMap::from([
(Name::new("id"), TypeRef::named(TypeRef::ID)),
(Name::new("keys"), TypeRef::named_list(TypeRef::STRING)),
(Name::new("modelNames"), TypeRef::named(TypeRef::STRING)),
(Name::new("createdAt"), TypeRef::named(ScalarType::DateTime.to_string())),
(Name::new("updatedAt"), TypeRef::named(ScalarType::DateTime.to_string())),
(Name::new("id"), TypeData::Simple(TypeRef::named(TypeRef::ID))),
(Name::new("keys"), TypeData::Simple(TypeRef::named_list(TypeRef::STRING))),
(Name::new("modelNames"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(
Name::new("createdAt"),
TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())),
),
(
Name::new("updatedAt"),
TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())),
),
]),
}
}
Expand Down Expand Up @@ -71,34 +77,8 @@ impl ObjectTrait for EntityObject {
&self.type_mapping
}

fn nested_fields(&self) -> Option<Vec<Field>> {
Some(vec![Field::new("models", TypeRef::named_list("ModelUnion"), move |ctx| {
FieldFuture::new(async move {
match ctx.parent_value.try_to_value()? {
Value::Object(indexmap) => {
let mut conn = ctx.data::<Pool<Sqlite>>()?.acquire().await?;
let models = csv_to_vec(&extract::<String>(indexmap, "modelNames")?);
let id = extract::<String>(indexmap, "id")?;

let mut results: Vec<FieldValue<'_>> = Vec::new();
for model_name in models {
let table_name = model_name.to_lowercase();
let type_mapping = type_mapping_query(&mut conn, &table_name).await?;
let state =
model_state_by_id_query(&mut conn, &table_name, &id, &type_mapping)
.await?;
results.push(FieldValue::with_type(
FieldValue::owned_any(state),
model_name,
));
}

Ok(Some(FieldValue::list(results)))
}
_ => Err("incorrect value, requires Value::Object".into()),
}
})
})])
fn related_fields(&self) -> Option<Vec<Field>> {
Some(vec![model_union_field()])
}

fn resolve_one(&self) -> Option<Field> {
Expand All @@ -107,7 +87,7 @@ impl ObjectTrait for EntityObject {
FieldFuture::new(async move {
let mut conn = ctx.data::<Pool<Sqlite>>()?.acquire().await?;
let id = ctx.args.try_get("id")?.string()?.to_string();
let entity = query_by_id(&mut conn, "entities", ID::Str(id)).await?;
let entity = query_by_id(&mut conn, "entities", &id).await?;
let result = EntityObject::value_mapping(entity);
Ok(Some(Value::Object(result)))
})
Expand All @@ -134,6 +114,7 @@ impl ObjectTrait for EntityObject {
});

let (entities, total_count) = entities_by_sk(&mut conn, keys, args).await?;

Ok(Some(Value::Object(connection_output(entities, total_count))))
})
},
Expand Down Expand Up @@ -172,6 +153,32 @@ impl ObjectTrait for EntityObject {
}
}

fn model_union_field() -> Field {
Field::new("models", TypeRef::named_list("ModelUnion"), move |ctx| {
FieldFuture::new(async move {
match ctx.parent_value.try_to_value()? {
Value::Object(indexmap) => {
let mut conn = ctx.data::<Pool<Sqlite>>()?.acquire().await?;
let model_names = csv_to_vec(&extract::<String>(indexmap, "modelNames")?);
let entity_id = extract::<String>(indexmap, "id")?;

let mut results: Vec<FieldValue<'_>> = Vec::new();
for name in model_names {
let type_mapping = type_mapping_query(&mut conn, &name).await?;
let state =
model_data_by_id_query(&mut conn, &name, &entity_id, &type_mapping)
.await?;
results.push(FieldValue::with_type(FieldValue::owned_any(state), name));
}

Ok(Some(FieldValue::list(results)))
}
_ => Err("incorrect value, requires Value::Object".into()),
}
})
})
}

async fn entities_by_sk(
conn: &mut PoolConnection<Sqlite>,
keys: Option<Vec<String>>,
Expand Down
23 changes: 13 additions & 10 deletions crates/torii/graphql/src/object/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use super::connection::connection_output;
use super::system_call::{SystemCall, SystemCallObject};
use super::{ObjectTrait, TypeMapping, ValueMapping};
use crate::constants::DEFAULT_LIMIT;
use crate::query::{query_all, query_by_id, query_total_count, ID};
use crate::types::ScalarType;
use crate::query::{query_all, query_by_id, query_total_count};
use crate::types::{GraphqlType, TypeData};
use crate::utils::extract_value::extract;

#[derive(FromRow, Deserialize)]
Expand All @@ -31,11 +31,14 @@ impl Default for EventObject {
fn default() -> Self {
Self {
type_mapping: IndexMap::from([
(Name::new("id"), TypeRef::named(TypeRef::ID)),
(Name::new("keys"), TypeRef::named(TypeRef::STRING)),
(Name::new("data"), TypeRef::named(TypeRef::STRING)),
(Name::new("createdAt"), TypeRef::named(ScalarType::DateTime.to_string())),
(Name::new("transactionHash"), TypeRef::named(TypeRef::STRING)),
(Name::new("id"), TypeData::Simple(TypeRef::named(TypeRef::ID))),
(Name::new("keys"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(Name::new("data"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
(
Name::new("createdAt"),
TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())),
),
(Name::new("transactionHash"), TypeData::Simple(TypeRef::named(TypeRef::STRING))),
]),
}
}
Expand Down Expand Up @@ -74,7 +77,7 @@ impl ObjectTrait for EventObject {
FieldFuture::new(async move {
let mut conn = ctx.data::<Pool<Sqlite>>()?.acquire().await?;
let id = ctx.args.try_get("id")?.string()?.to_string();
let event = query_by_id(&mut conn, "events", ID::Str(id)).await?;
let event = query_by_id(&mut conn, "events", &id).await?;
let result = EventObject::value_mapping(event);
Ok(Some(Value::Object(result)))
})
Expand All @@ -101,14 +104,14 @@ impl ObjectTrait for EventObject {
))
}

fn nested_fields(&self) -> Option<Vec<Field>> {
fn related_fields(&self) -> Option<Vec<Field>> {
Some(vec![Field::new("systemCall", TypeRef::named_nn("SystemCall"), |ctx| {
FieldFuture::new(async move {
let mut conn = ctx.data::<Pool<Sqlite>>()?.acquire().await?;
let event_values = ctx.parent_value.try_downcast_ref::<ValueMapping>()?;
let syscall_id = extract::<i64>(event_values, "system_call_id")?;
let system_call: SystemCall =
query_by_id(&mut conn, "system_calls", ID::I64(syscall_id)).await?;
query_by_id(&mut conn, "system_calls", &syscall_id.to_string()).await?;
let result = SystemCallObject::value_mapping(system_call);
Ok(Some(Value::Object(result)))
})
Expand Down
Loading

0 comments on commit cb79f2b

Please sign in to comment.