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

feat(torii-core): json value for ty for efficient json ser #2730

Merged
merged 10 commits into from
Nov 28, 2024

Conversation

Larkooo
Copy link
Collaborator

@Larkooo Larkooo commented Nov 28, 2024

Summary by CodeRabbit

  • New Features

    • Enhanced JSON serialization and deserialization capabilities for the Ty enum, allowing conversion to and from JSON values.
    • Added new methods: to_json_value and from_json_value for improved data handling.
    • Updated data processing and retrieval methods in the DojoWorld service for better handling of complex queries.
  • Chores

    • Updated the handling of event message serialization for improved maintainability and readability.
  • Bug Fixes

    • Improved consistency in serialization processes across various data types.
    • Enhanced error handling for various failure scenarios in data queries.

Copy link

codecov bot commented Nov 28, 2024

Codecov Report

Attention: Patch coverage is 0% with 154 lines in your changes missing coverage. Please review.

Project coverage is 56.08%. Comparing base (ebcc23d) to head (d4ecbb9).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
crates/dojo/types/src/schema.rs 0.00% 151 Missing ⚠️
crates/torii/grpc/src/server/mod.rs 0.00% 2 Missing ⚠️
crates/torii/core/src/executor/mod.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2730      +/-   ##
==========================================
- Coverage   56.24%   56.08%   -0.17%     
==========================================
  Files         415      415              
  Lines       53241    53386     +145     
==========================================
- Hits        29948    29943       -5     
- Misses      23293    23443     +150     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@Larkooo Larkooo marked this pull request as ready for review November 28, 2024 05:20
Copy link

coderabbitai bot commented Nov 28, 2024

Walkthrough

Ohayo! This pull request introduces several changes, including the addition of the indexmap dependency to the Cargo.toml file of the dojo-types package, enabling shared access within the workspace. Additionally, the Ty enum in schema.rs has been enhanced with new methods for JSON serialization and deserialization, allowing for better handling of various data types. Lastly, the handle_query_message method in executor/mod.rs has been updated to simplify the serialization process for event messages.

Changes

File Path Change Summary
crates/dojo/types/Cargo.toml Added dependency: indexmap.workspace = true
crates/dojo/types/src/schema.rs Added methods: to_json_value and from_json_value in Ty enum for JSON serialization/deserialization. Updated serialize and deserialize methods to handle ByteArray.
crates/torii/core/src/executor/mod.rs Updated handle_query_message to use serde_json::to_string() for ty field serialization in EventMessageQuery. Minor formatting adjustments made.
crates/torii/grpc/src/server/mod.rs Updated fetch_historical_event_messages and retrieve_entities method signatures for improved data handling and deserialization.

Possibly related PRs

Suggested reviewers

  • glihm: A suitable reviewer for these changes.

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (1)
crates/torii/core/src/executor/mod.rs (1)

551-551: Ohayo! The JSON serialization change looks good, sensei!

The switch to using to_json_value() with serde_json::to_string() is a more idiomatic approach. However, consider adding more specific error handling for serialization failures.

Consider wrapping the serialization in a more specific error type:

-    let data = serde_json::to_string(&em_query.ty.to_json_value()?)?;
+    let data = serde_json::to_string(&em_query.ty.to_json_value()?)
+        .map_err(|e| anyhow::anyhow!("Failed to serialize event message type: {}", e))?;
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ebcc23d and bc56e52.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • crates/dojo/types/Cargo.toml (1 hunks)
  • crates/dojo/types/src/schema.rs (2 hunks)
  • crates/torii/core/src/executor/mod.rs (1 hunks)
🔇 Additional comments (4)
crates/dojo/types/Cargo.toml (1)

23-23: Ohayo! The indexmap dependency addition looks good, sensei!

The workspace configuration is properly set up, maintaining consistency with other dependencies.

Let's verify the IndexMap usage in the codebase:

✅ Verification successful

Ohayo! The IndexMap dependency is well-justified and actively used, sensei!

The verification confirms that IndexMap is actively used in the codebase, particularly in crates/dojo/types/src/schema.rs as mentioned in the PR context, along with several other legitimate uses across the codebase. The workspace configuration ensures consistent versioning across all these usages.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify IndexMap usage in the codebase
# Expected: Find usage in schema.rs and potentially other files

# Search for IndexMap type usage
rg "IndexMap|use indexmap" --type rust

# Search for specific JSON serialization related usage
ast-grep --pattern 'impl $_ for Ty {
  $$$
  IndexMap
  $$$
}'

Length of output: 6775

crates/dojo/types/src/schema.rs (3)

2-2: Import FromStr Is Necessary

Ohayo sensei, the addition of use std::str::FromStr; is appropriate as it is required for parsing strings into Felt values later in the code.


5-6: Include crypto_bigint and indexmap Dependencies

The imports use crypto_bigint::{Encoding, U256}; and use indexmap::IndexMap; are necessary for handling big integers and maintaining insertion order in maps, respectively.


10-10: Import serde_json for JSON Handling

Ohayo sensei, the import use serde_json::{json, Value as JsonValue}; is essential for JSON serialization and deserialization in the newly added methods.

crates/dojo/types/src/schema.rs Outdated Show resolved Hide resolved
Comment on lines +371 to +504
Primitive::U8(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_u64().map(|n| n as u8);
}
}
Primitive::U16(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_u64().map(|n| n as u16);
}
}
Primitive::U32(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_u64().map(|n| n as u32);
}
}
Primitive::U64(v) => {
if let JsonValue::String(s) = value {
*v = s.parse().ok();
}
}
Primitive::U128(v) => {
if let JsonValue::String(s) = value {
*v = s.parse().ok();
}
}
Primitive::USize(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_u64().map(|n| n as u32);
}
}
Primitive::U256(v) => {
if let JsonValue::Object(obj) = value {
if let (Some(JsonValue::String(high)), Some(JsonValue::String(low))) =
(obj.get("high"), obj.get("low"))
{
if let (Ok(high), Ok(low)) = (high.parse::<u128>(), low.parse::<u128>())
{
let mut bytes = [0u8; 32];
bytes[..16].copy_from_slice(&high.to_be_bytes());
bytes[16..].copy_from_slice(&low.to_be_bytes());
*v = Some(U256::from_be_slice(&bytes));
}
}
}
}
Primitive::Felt252(v) => {
if let JsonValue::String(s) = value {
*v = Felt::from_str(&s).ok();
}
}
Primitive::ClassHash(v) => {
if let JsonValue::String(s) = value {
*v = Felt::from_str(&s).ok();
}
}
Primitive::ContractAddress(v) => {
if let JsonValue::String(s) = value {
*v = Felt::from_str(&s).ok();
}
}
},
(Ty::Struct(s), JsonValue::Object(obj)) => {
for member in &mut s.children {
if let Some(value) = obj.get(&member.name) {
member.ty.from_json_value(value.clone())?;
}
}
}
(Ty::Enum(e), JsonValue::Object(obj)) => {
if let Some((name, value)) = obj.into_iter().next() {
e.set_option(&name).map_err(|_| PrimitiveError::TypeMismatch)?;
if let Some(option) = e.option {
e.options[option as usize].ty.from_json_value(value)?;
}
}
}
(Ty::Array(items), JsonValue::Array(values)) => {
let template = items[0].clone();
items.clear();
for value in values {
let mut item = template.clone();
item.from_json_value(value)?;
items.push(item);
}
}
(Ty::Tuple(items), JsonValue::Array(values)) => {
if items.len() != values.len() {
return Err(PrimitiveError::TypeMismatch);
}
for (item, value) in items.iter_mut().zip(values) {
item.from_json_value(value)?;
}
}
(Ty::ByteArray(bytes), JsonValue::String(s)) => {
*bytes = s;
}
_ => return Err(PrimitiveError::TypeMismatch),
}
Ok(())
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improving Error Handling in from_json_value

The from_json_value method effectively deserializes JSON values into Ty instances. However, there are several areas where error handling can be enhanced to prevent silent failures.

Handle parsing errors for numeric primitives

Ohayo sensei, when parsing numeric values from JSON, assigning None on failure may lead to unexpected behavior. It's better to return an error if parsing fails.

Example modification for Primitive::I64 (lines 396-398):

 if let JsonValue::String(s) = value {
-    *v = s.parse().ok();
+    *v = Some(s.parse().map_err(|_| PrimitiveError::InvalidValue)?);
 }

Apply similar changes for other numeric primitives (I128, U64, U128), ensuring that parsing errors are properly propagated.

Ensure proper error handling for Primitive::U256 parsing

In lines 436-449, when parsing high and low values, failure to parse should result in an error rather than being silently ignored.

 if let JsonValue::Object(obj) = value {
     if let (Some(JsonValue::String(high)), Some(JsonValue::String(low))) =
         (obj.get("high"), obj.get("low"))
     {
-        if let (Ok(high), Ok(low)) = (high.parse::<u128>(), low.parse::<u128>()) {
+        let high = high.parse::<u128>().map_err(|_| PrimitiveError::InvalidValue)?;
+        let low = low.parse::<u128>().map_err(|_| PrimitiveError::InvalidValue)?;
         let mut bytes = [0u8; 32];
         bytes[..16].copy_from_slice(&high.to_be_bytes());
         bytes[16..].copy_from_slice(&low.to_be_bytes());
         *v = Some(U256::from_be_slice(&bytes));
     }
 }

Consistent error handling across all primitive types will enhance the robustness of the deserialization process.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Parse a JSON Value into a Ty
pub fn from_json_value(&mut self, value: JsonValue) -> Result<(), PrimitiveError> {
match (self, value) {
(Ty::Primitive(primitive), value) => match primitive {
Primitive::Bool(v) => {
if let JsonValue::Bool(b) = value {
*v = Some(b);
}
}
Primitive::I8(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_i64().map(|n| n as i8);
}
}
Primitive::I16(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_i64().map(|n| n as i16);
}
}
Primitive::I32(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_i64().map(|n| n as i32);
}
}
Primitive::I64(v) => {
if let JsonValue::String(s) = value {
*v = s.parse().ok();
}
}
Primitive::I128(v) => {
if let JsonValue::String(s) = value {
*v = s.parse().ok();
}
}
Primitive::U8(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_u64().map(|n| n as u8);
}
}
Primitive::U16(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_u64().map(|n| n as u16);
}
}
Primitive::U32(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_u64().map(|n| n as u32);
}
}
Primitive::U64(v) => {
if let JsonValue::String(s) = value {
*v = s.parse().ok();
}
}
Primitive::U128(v) => {
if let JsonValue::String(s) = value {
*v = s.parse().ok();
}
}
Primitive::USize(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_u64().map(|n| n as u32);
}
}
Primitive::U256(v) => {
if let JsonValue::Object(obj) = value {
if let (Some(JsonValue::String(high)), Some(JsonValue::String(low))) =
(obj.get("high"), obj.get("low"))
{
if let (Ok(high), Ok(low)) = (high.parse::<u128>(), low.parse::<u128>())
{
let mut bytes = [0u8; 32];
bytes[..16].copy_from_slice(&high.to_be_bytes());
bytes[16..].copy_from_slice(&low.to_be_bytes());
*v = Some(U256::from_be_slice(&bytes));
}
}
}
}
Primitive::Felt252(v) => {
if let JsonValue::String(s) = value {
*v = Felt::from_str(&s).ok();
}
}
Primitive::ClassHash(v) => {
if let JsonValue::String(s) = value {
*v = Felt::from_str(&s).ok();
}
}
Primitive::ContractAddress(v) => {
if let JsonValue::String(s) = value {
*v = Felt::from_str(&s).ok();
}
}
},
(Ty::Struct(s), JsonValue::Object(obj)) => {
for member in &mut s.children {
if let Some(value) = obj.get(&member.name) {
member.ty.from_json_value(value.clone())?;
}
}
}
(Ty::Enum(e), JsonValue::Object(obj)) => {
if let Some((name, value)) = obj.into_iter().next() {
e.set_option(&name).map_err(|_| PrimitiveError::TypeMismatch)?;
if let Some(option) = e.option {
e.options[option as usize].ty.from_json_value(value)?;
}
}
}
(Ty::Array(items), JsonValue::Array(values)) => {
let template = items[0].clone();
items.clear();
for value in values {
let mut item = template.clone();
item.from_json_value(value)?;
items.push(item);
}
}
(Ty::Tuple(items), JsonValue::Array(values)) => {
if items.len() != values.len() {
return Err(PrimitiveError::TypeMismatch);
}
for (item, value) in items.iter_mut().zip(values) {
item.from_json_value(value)?;
}
}
(Ty::ByteArray(bytes), JsonValue::String(s)) => {
*bytes = s;
}
_ => return Err(PrimitiveError::TypeMismatch),
}
Ok(())
}
/// Parse a JSON Value into a Ty
pub fn from_json_value(&mut self, value: JsonValue) -> Result<(), PrimitiveError> {
match (self, value) {
(Ty::Primitive(primitive), value) => match primitive {
Primitive::Bool(v) => {
if let JsonValue::Bool(b) = value {
*v = Some(b);
}
}
Primitive::I8(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_i64().map(|n| n as i8);
}
}
Primitive::I16(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_i64().map(|n| n as i16);
}
}
Primitive::I32(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_i64().map(|n| n as i32);
}
}
Primitive::I64(v) => {
if let JsonValue::String(s) = value {
*v = Some(s.parse().map_err(|_| PrimitiveError::InvalidValue)?);
}
}
Primitive::I128(v) => {
if let JsonValue::String(s) = value {
*v = Some(s.parse().map_err(|_| PrimitiveError::InvalidValue)?);
}
}
Primitive::U8(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_u64().map(|n| n as u8);
}
}
Primitive::U16(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_u64().map(|n| n as u16);
}
}
Primitive::U32(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_u64().map(|n| n as u32);
}
}
Primitive::U64(v) => {
if let JsonValue::String(s) = value {
*v = Some(s.parse().map_err(|_| PrimitiveError::InvalidValue)?);
}
}
Primitive::U128(v) => {
if let JsonValue::String(s) = value {
*v = Some(s.parse().map_err(|_| PrimitiveError::InvalidValue)?);
}
}
Primitive::USize(v) => {
if let JsonValue::Number(n) = value {
*v = n.as_u64().map(|n| n as u32);
}
}
Primitive::U256(v) => {
if let JsonValue::Object(obj) = value {
if let (Some(JsonValue::String(high)), Some(JsonValue::String(low))) =
(obj.get("high"), obj.get("low"))
{
let high = high.parse::<u128>().map_err(|_| PrimitiveError::InvalidValue)?;
let low = low.parse::<u128>().map_err(|_| PrimitiveError::InvalidValue)?;
let mut bytes = [0u8; 32];
bytes[..16].copy_from_slice(&high.to_be_bytes());
bytes[16..].copy_from_slice(&low.to_be_bytes());
*v = Some(U256::from_be_slice(&bytes));
}
}
}
Primitive::Felt252(v) => {
if let JsonValue::String(s) = value {
*v = Felt::from_str(&s).ok();
}
}
Primitive::ClassHash(v) => {
if let JsonValue::String(s) = value {
*v = Felt::from_str(&s).ok();
}
}
Primitive::ContractAddress(v) => {
if let JsonValue::String(s) = value {
*v = Felt::from_str(&s).ok();
}
}
},
(Ty::Struct(s), JsonValue::Object(obj)) => {
for member in &mut s.children {
if let Some(value) = obj.get(&member.name) {
member.ty.from_json_value(value.clone())?;
}
}
}
(Ty::Enum(e), JsonValue::Object(obj)) => {
if let Some((name, value)) = obj.into_iter().next() {
e.set_option(&name).map_err(|_| PrimitiveError::TypeMismatch)?;
if let Some(option) = e.option {
e.options[option as usize].ty.from_json_value(value)?;
}
}
}
(Ty::Array(items), JsonValue::Array(values)) => {
let template = items[0].clone();
items.clear();
for value in values {
let mut item = template.clone();
item.from_json_value(value)?;
items.push(item);
}
}
(Ty::Tuple(items), JsonValue::Array(values)) => {
if items.len() != values.len() {
return Err(PrimitiveError::TypeMismatch);
}
for (item, value) in items.iter_mut().zip(values) {
item.from_json_value(value)?;
}
}
(Ty::ByteArray(bytes), JsonValue::String(s)) => {
*bytes = s;
}
_ => return Err(PrimitiveError::TypeMismatch),
}
Ok(())
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between bc56e52 and 22fe5e4.

📒 Files selected for processing (1)
  • crates/torii/grpc/src/server/mod.rs (1 hunks)

crates/torii/grpc/src/server/mod.rs Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
crates/torii/grpc/src/server/mod.rs (1)

Line range hint 366-383: Ohayo sensei! Consider optimizing the event message processing

The current implementation has some performance considerations:

  1. Multiple database queries for model cache within the loop
  2. Repeated string-to-felt conversions
  3. Building a HashMap in memory for all entities

Consider these optimizations:

  1. Batch fetch models from cache before the loop
  2. Pre-compute Felt values where possible
  3. Stream results instead of collecting all in memory

Here's a suggested approach:

 async fn fetch_historical_event_messages(
     &self,
     query: &str,
     keys_pattern: Option<&str>,
     limit: Option<u32>,
     offset: Option<u32>,
 ) -> Result<Vec<proto::types::Entity>, Error> {
+    // Pre-fetch unique model IDs
+    let model_ids: HashSet<Felt> = db_entities
+        .iter()
+        .map(|(_, _, model_id, _)| Felt::from_str(model_id))
+        .collect::<Result<_, _>>()?;
+    
+    // Batch fetch models
+    let model_cache: HashMap<Felt, _> = self.model_cache
+        .models(&model_ids.into_iter().collect::<Vec<_>>())
+        .await?
+        .into_iter()
+        .map(|m| (m.id, m))
+        .collect();

     let mut entities = HashMap::new();
     for (id, data, model_id, _) in db_entities {
-        let hashed_keys = Felt::from_str(&id).map_err(ParseError::FromStr)?.to_bytes_be().to_vec();
-        let model = self
-            .model_cache
-            .model(&Felt::from_str(&model_id).map_err(ParseError::FromStr)?)
-            .await?;
+        let hashed_keys = cached_felts.get(&id).unwrap_or_else(|| {
+            let felt = Felt::from_str(&id).map_err(ParseError::FromStr)?;
+            cached_felts.insert(id.clone(), felt.to_bytes_be().to_vec());
+            cached_felts.get(&id).unwrap()
+        });
+        let model = model_cache.get(&Felt::from_str(&model_id)?).unwrap();
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 22fe5e4 and fd49da8.

📒 Files selected for processing (1)
  • crates/torii/grpc/src/server/mod.rs (1 hunks)
🔇 Additional comments (1)
crates/torii/grpc/src/server/mod.rs (1)

370-370: ⚠️ Potential issue

Ohayo sensei! Avoid using unwrap(); handle potential JSON deserialization errors

Using unwrap() on serde_json::from_str(&data) can cause a panic if data is not valid JSON. It's better to handle the error properly and propagate it.

Apply this diff to handle the error properly:

-schema.from_json_value(serde_json::from_str(&data).unwrap())?;
+schema.from_json_value(serde_json::from_str(&data)?)?;

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 30938be and 2487b19.

📒 Files selected for processing (1)
  • crates/dojo/types/src/schema.rs (2 hunks)
🔇 Additional comments (4)
crates/dojo/types/src/schema.rs (4)

316-369: LGTM! Comprehensive JSON serialization implementation

The implementation handles all types correctly and provides proper error handling (except for the U256 case noted above).


334-335: ⚠️ Potential issue

Ohayo sensei! Replace unwrap() with proper error handling

The use of unwrap() in the U256 conversion could cause panics. Let's handle potential errors gracefully.

Apply this diff:

-    let high = u128::from_be_bytes(bytes[..16].try_into().unwrap());
-    let low = u128::from_be_bytes(bytes[16..].try_into().unwrap());
+    let high = u128::from_be_bytes(bytes[..16].try_into().map_err(|_| PrimitiveError::TypeMismatch)?);
+    let low = u128::from_be_bytes(bytes[16..].try_into().map_err(|_| PrimitiveError::TypeMismatch)?);

Likely invalid or redundant comment.


436-449: 🛠️ Refactor suggestion

Simplify U256 parsing and improve error handling

The nested if-let patterns make the code harder to read and error handling is inconsistent.

Consider this cleaner approach:

-    if let JsonValue::Object(obj) = value {
-        if let (Some(JsonValue::String(high)), Some(JsonValue::String(low))) =
-            (obj.get("high"), obj.get("low"))
-        {
-            if let (Ok(high), Ok(low)) = (high.parse::<u128>(), low.parse::<u128>())
-            {
-                let mut bytes = [0u8; 32];
-                bytes[..16].copy_from_slice(&high.to_be_bytes());
-                bytes[16..].copy_from_slice(&low.to_be_bytes());
-                *v = Some(U256::from_be_slice(&bytes));
-            }
-        }
-    }
+    let obj = match value {
+        JsonValue::Object(obj) => obj,
+        _ => return Err(PrimitiveError::TypeMismatch),
+    };
+    
+    let high = obj.get("high")
+        .and_then(|v| v.as_str())
+        .ok_or(PrimitiveError::TypeMismatch)?
+        .parse::<u128>()
+        .map_err(|_| PrimitiveError::InvalidValue)?;
+        
+    let low = obj.get("low")
+        .and_then(|v| v.as_str())
+        .ok_or(PrimitiveError::TypeMismatch)?
+        .parse::<u128>()
+        .map_err(|_| PrimitiveError::InvalidValue)?;
+        
+    let mut bytes = [0u8; 32];
+    bytes[..16].copy_from_slice(&high.to_be_bytes());
+    bytes[16..].copy_from_slice(&low.to_be_bytes());
+    *v = Some(U256::from_be_slice(&bytes));

Likely invalid or redundant comment.


395-429: ⚠️ Potential issue

Improve error handling for numeric string parsing

Ohayo sensei! Currently, parsing errors are silently ignored using .ok(). We should propagate these errors to handle invalid inputs properly.

Apply this pattern for all numeric string parsing:

-    if let JsonValue::String(s) = value {
-        *v = s.parse().ok();
-    }
+    if let JsonValue::String(s) = value {
+        *v = Some(s.parse().map_err(|_| PrimitiveError::InvalidValue)?);
+    }

Likely invalid or redundant comment.

Comment on lines +481 to +489
(Ty::Array(items), JsonValue::Array(values)) => {
let template = items[0].clone();
items.clear();
for value in values {
let mut item = template.clone();
item.from_json_value(value)?;
items.push(item);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle empty arrays safely

The current implementation assumes arrays are non-empty and could panic when deserializing an empty array.

Apply this fix:

     (Ty::Array(items), JsonValue::Array(values)) => {
+        if items.is_empty() {
+            return Err(PrimitiveError::TypeMismatch);
+        }
         let template = items[0].clone();
         items.clear();
         for value in values {
             let mut item = template.clone();
             item.from_json_value(value)?;
             items.push(item);
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
(Ty::Array(items), JsonValue::Array(values)) => {
let template = items[0].clone();
items.clear();
for value in values {
let mut item = template.clone();
item.from_json_value(value)?;
items.push(item);
}
}
(Ty::Array(items), JsonValue::Array(values)) => {
if items.is_empty() {
return Err(PrimitiveError::TypeMismatch);
}
let template = items[0].clone();
items.clear();
for value in values {
let mut item = template.clone();
item.from_json_value(value)?;
items.push(item);
}
}

Comment on lines +341 to +343
Primitive::Felt252(Some(v)) => Ok(json!(format!("{:#x}", v))),
Primitive::ClassHash(Some(v)) => Ok(json!(format!("{:#x}", v))),
Primitive::ContractAddress(Some(v)) => Ok(json!(format!("{:#x}", v))),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should in the future have this format being factorized. I always asking myself if it needs to be padded or not. x) Having this in functions will help for clearer context across torii/the stack.

Copy link
Collaborator

@glihm glihm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clippy and ready to go @Larkooo.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
crates/torii/grpc/src/server/mod.rs (2)

Line range hint 1012-1015: Consider more specific error handling for composite clauses, sensei

The error conversion in the composite clause builder uses a generic Status::internal for all error types. Consider mapping specific error cases to appropriate gRPC status codes for better error reporting.

Example improvement:

- .map_err(|e| Status::internal(e.to_string()))?;
+ .map_err(|e| match e {
+     Error::Parse(_) => Status::invalid_argument(e.to_string()),
+     Error::Query(_) => Status::failed_precondition(e.to_string()),
+     _ => Status::internal(e.to_string()),
+ })?;

Line range hint 516-524: Optimize query building for better performance, sensei

The dynamic SQL query building with string concatenation could be optimized. Consider:

  1. Using query builders or prepared statements where possible
  2. Caching frequently used query templates
  3. Using bind parameters instead of string interpolation for better security and performance

Example improvement for the count query:

const COUNT_QUERY_TEMPLATE: &str = r#"
    SELECT count(*)
    FROM {table}
    WHERE {table}.keys REGEXP ?
"#;

// Cache and reuse the prepared statement
let count_query = sqlx::query(COUNT_QUERY_TEMPLATE.replace("{table}", table))
    .bind(&keys_pattern);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c7a3708 and d4ecbb9.

📒 Files selected for processing (1)
  • crates/torii/grpc/src/server/mod.rs (1 hunks)
🔇 Additional comments (1)
crates/torii/grpc/src/server/mod.rs (1)

370-371: Ohayo! Nice error handling improvement, sensei!

The JSON deserialization error handling has been improved by properly mapping JSON parsing errors to ParseError::FromJsonStr instead of using unwrap().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants