Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions examples/ore-server/Cargo.lock

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

26 changes: 1 addition & 25 deletions hyperstack-macros/src/stream_spec/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1053,38 +1053,14 @@ fn generate_computed_fields_hook(
}).collect();

quote! {
#[allow(clippy::too_many_arguments)]
fn #eval_fn_name(
section_obj: &mut hyperstack::runtime::serde_json::Map<String, hyperstack::runtime::serde_json::Value>,
section_parent_state: &hyperstack::runtime::serde_json::Value,
__context_slot: Option<u64>,
__context_timestamp: i64,
#(#cross_section_params),*
) -> Result<(), Box<dyn std::error::Error>> {
// Create local bindings for all fields in the current section
// Helper macro to get field values with proper type inference
macro_rules! extract_field {
($name:ident, $ty:ty) => {
let $name: Option<$ty> = section_obj
.get(stringify!($name))
.and_then(|v| hyperstack::runtime::serde_json::from_value(v.clone()).ok());
};
}

// Extract all numeric/common fields that might be referenced
extract_field!(total_buy_volume, u64);
extract_field!(total_sell_volume, u64);
extract_field!(total_trades, u64);
extract_field!(total_volume, u64);
extract_field!(buy_count, u64);
extract_field!(sell_count, u64);
extract_field!(unique_traders, u64);
extract_field!(largest_trade, u64);
extract_field!(smallest_trade, u64);
extract_field!(last_trade_timestamp, i64);
extract_field!(last_trade_price, f64);
extract_field!(whale_trade_count, u64);
extract_field!(average_trade_size, f64);

// Initialize cache with current section values for intra-section computed field dependencies
let mut computed_cache: std::collections::HashMap<String, hyperstack::runtime::serde_json::Value> = std::collections::HashMap::new();
for (key, value) in section_obj.iter() {
Expand Down
9 changes: 7 additions & 2 deletions interpreter/src/resolvers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,12 @@ impl SlotHashResolver {
return Ok(Value::Null);
}

let slot_hash = Self::json_array_to_bytes(&args[0], 32);
// slot_hash() returns { bytes: [...] }, so extract the bytes array
let slot_hash_bytes = match &args[0] {
Value::Object(obj) => obj.get("bytes").cloned().unwrap_or(Value::Null),
_ => args[0].clone(),
};
let slot_hash = Self::json_array_to_bytes(&slot_hash_bytes, 32);
let seed = Self::json_array_to_bytes(&args[1], 32);
let samples = match &args[2] {
Value::Number(n) => n.as_u64(),
Expand Down Expand Up @@ -788,7 +793,7 @@ impl SlotHashResolver {
let r4 = u64::from_le_bytes(hash[24..32].try_into()?);
let rng = r1 ^ r2 ^ r3 ^ r4;

Ok(Value::String(rng.to_string()))
Ok(Value::Number(serde_json::Number::from(rng)))
}

/// Extract a byte array of expected length from a JSON array value.
Expand Down
90 changes: 81 additions & 9 deletions interpreter/src/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ pub use hyperstack_sdk::{{ConnectionState, HyperStack, Stack, Update, Views}};

fn generate_types_rs(&self) -> String {
let mut output = String::new();
output.push_str("use serde::{Deserialize, Serialize};\n\n");
output.push_str("use serde::{Deserialize, Serialize};\n");
output.push_str("use hyperstack_sdk::serde_utils;\n\n");

let mut generated = HashSet::new();

Expand Down Expand Up @@ -170,10 +171,11 @@ pub use hyperstack_sdk::{{ConnectionState, HyperStack, Stack, Update, Views}};
}
let field_name = to_snake_case(&field.field_name);
let rust_type = self.field_type_to_rust(field);
let serde_attr = self.serde_attr_for_field(field);

fields.push(format!(
" #[serde(default)]\n pub {}: {},",
field_name, rust_type
" {}\n pub {}: {},",
serde_attr, field_name, rust_type
));
}

Expand Down Expand Up @@ -212,9 +214,10 @@ pub use hyperstack_sdk::{{ConnectionState, HyperStack, Stack, Update, Views}};
}
let field_name = to_snake_case(&field.field_name);
let rust_type = self.field_type_to_rust(field);
let serde_attr = self.serde_attr_for_field(field);
fields.push(format!(
" #[serde(default)]\n pub {}: {},",
field_name, rust_type
" {}\n pub {}: {},",
serde_attr, field_name, rust_type
));
}
}
Expand Down Expand Up @@ -266,8 +269,10 @@ pub use hyperstack_sdk::{{ConnectionState, HyperStack, Stack, Update, Views}};
.iter()
.map(|f| {
let rust_type = self.resolved_field_to_rust(f);
let serde_attr = self.serde_attr_for_resolved_field(f);
format!(
" #[serde(default)]\n pub {}: {},",
" {}\n pub {}: {},",
serde_attr,
to_snake_case(&f.field_name),
rust_type
)
Expand All @@ -287,7 +292,7 @@ pub use hyperstack_sdk::{{ConnectionState, HyperStack, Stack, Update, Views}};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventWrapper<T> {
#[serde(default)]
#[serde(default, deserialize_with = "serde_utils::deserialize_i64")]
pub timestamp: i64,
pub data: T,
#[serde(default)]
Expand Down Expand Up @@ -508,6 +513,72 @@ impl {entity_name}EntityViews {{
}
}

/// Return the `#[serde(...)]` attribute for a field.
/// Integer fields get a `deserialize_with` pointing to the appropriate
/// `serde_utils` function so that string-encoded big integers are handled.
fn serde_attr_for_field(&self, field: &FieldTypeInfo) -> String {
if let Some(deser_fn) = self.deserialize_with_for_type(
&field.base_type,
field.is_optional,
field.is_array && !matches!(field.base_type, BaseType::Array),
&field.rust_type_name,
) {
format!("#[serde(default, deserialize_with = \"{}\")]", deser_fn)
} else {
"#[serde(default)]".to_string()
}
}

/// Same as `serde_attr_for_field` but for resolved struct fields.
fn serde_attr_for_resolved_field(&self, field: &ResolvedField) -> String {
if let Some(deser_fn) = self.deserialize_with_for_type(
&field.base_type,
field.is_optional,
field.is_array,
&field.field_type,
) {
format!("#[serde(default, deserialize_with = \"{}\")]", deser_fn)
} else {
"#[serde(default)]".to_string()
}
}

/// Determine the appropriate `serde_utils::deserialize_*` function for a
/// given type combination, or `None` if no custom deserializer is needed.
fn deserialize_with_for_type(
&self,
base_type: &BaseType,
is_optional: bool,
is_array: bool,
rust_type_name: &str,
) -> Option<String> {
// Only integer and timestamp types need the string-or-number treatment
let int_kind = match base_type {
BaseType::Integer => {
if rust_type_name.contains("i64") {
"i64"
} else if rust_type_name.contains("i32") {
"i32"
} else if rust_type_name.contains("u32") {
"u32"
} else {
"u64"
}
}
BaseType::Timestamp => "i64",
_ => return None,
};

let fn_name = match (is_optional, is_array) {
(false, false) => format!("serde_utils::deserialize_option_{}", int_kind),
(true, false) => format!("serde_utils::deserialize_option_option_{}", int_kind),
(false, true) => format!("serde_utils::deserialize_option_vec_{}", int_kind),
(true, true) => format!("serde_utils::deserialize_option_option_vec_{}", int_kind),
};

Some(fn_name)
}

fn resolved_field_to_rust(&self, field: &ResolvedField) -> String {
let base = self.base_type_to_rust(&field.base_type, &field.field_type);

Expand Down Expand Up @@ -638,7 +709,8 @@ fn generate_stack_types_rs(
entity_names: &[String],
) -> String {
let mut output = String::new();
output.push_str("use serde::{Deserialize, Serialize};\n\n");
output.push_str("use serde::{Deserialize, Serialize};\n");
output.push_str("use hyperstack_sdk::serde_utils;\n\n");

let mut generated = HashSet::new();

Expand Down Expand Up @@ -673,7 +745,7 @@ fn generate_stack_types_rs(
r#"
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventWrapper<T> {
#[serde(default)]
#[serde(default, deserialize_with = "serde_utils::deserialize_i64")]
pub timestamp: i64,
pub data: T,
#[serde(default)]
Expand Down
8 changes: 6 additions & 2 deletions interpreter/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4386,7 +4386,9 @@ impl VmContext {
let arr: [u8; 8] = byte_vec[..8]
.try_into()
.map_err(|_| "Failed to convert to [u8; 8]")?;
Ok(Value::String(u64::from_le_bytes(arr).to_string()))
Ok(Value::Number(serde_json::Number::from(u64::from_le_bytes(
arr,
))))
}

ComputedExpr::U64FromBeBytes { bytes } => {
Expand All @@ -4402,7 +4404,9 @@ impl VmContext {
let arr: [u8; 8] = byte_vec[..8]
.try_into()
.map_err(|_| "Failed to convert to [u8; 8]")?;
Ok(Value::String(u64::from_be_bytes(arr).to_string()))
Ok(Value::Number(serde_json::Number::from(u64::from_be_bytes(
arr,
))))
}

ComputedExpr::ByteArray { bytes } => {
Expand Down
1 change: 1 addition & 0 deletions rust/hyperstack-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod entity;
mod error;
mod frame;
pub mod prelude;
pub mod serde_utils;
mod store;
mod stream;
mod subscription;
Expand Down
Loading
Loading