Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 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
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,10 @@ def _generate_base_files(self, context: dict[str, Any], output_dir: Path) -> dic
files[src_dir / "msgpack_value_bytes.rs"] = self.template_engine.render_template(
"base/msgpack_value_bytes.rs.j2", context
)
# Provide msgpack helper to deserialize msgpack strings as raw bytes
files[src_dir / "msgpack_string_bytes.rs"] = self.template_engine.render_template(
"base/msgpack_string_bytes.rs.j2", context
)

return files

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ uniffi::setup_scaffolding!();
pub mod apis;
pub mod models;
{% if client_type == "Algod" %}
pub mod msgpack_string_bytes;
pub mod msgpack_value_bytes;
{% endif %}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/// Custom serde module for deserializing msgpack strings as raw bytes.
///
/// Msgpack strings may contain arbitrary bytes that aren't valid UTF-8.
/// This module deserializes the raw string bytes into Vec<u8> without
/// requiring UTF-8 validity.
use serde::{Deserialize, Deserializer, Serializer};

/// Deserialize a msgpack string as raw bytes
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
// Use rmpv::Value to capture the raw msgpack value
let value: Option<rmpv::Value> = Option::deserialize(deserializer)?;

match value {
Some(rmpv::Value::String(s)) => {
// rmpv::Utf8String gives us access to raw bytes even if not valid UTF-8
Ok(Some(s.into_bytes()))
}
Some(rmpv::Value::Binary(b)) => Ok(Some(b)),
Some(_) => Err(serde::de::Error::custom(
"expected string or binary, got other type",
)),
None => Ok(None),
}
}

/// Serialize bytes as a msgpack binary
pub fn serialize<S>(value: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(bytes) => serializer.serialize_bytes(bytes),
None => serializer.serialize_none(),
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ pub struct BlockAppEvalDelta {
#[serde_as(as = "Option<Vec<Bytes>>")]
#[serde(rename = "sa", skip_serializing_if = "Option::is_none")]
pub shared_accounts: Option<Vec<Vec<u8>>>,
/// [lg] Application log outputs as strings (msgpack strings).
/// [lg] Application log outputs.
#[serde_as(as = "Option<Vec<Bytes>>")]
#[serde(rename = "lg", skip_serializing_if = "Option::is_none")]
pub logs: Option<Vec<String>>,
pub logs: Option<Vec<Vec<u8>>>,
}

impl AlgorandMsgpack for BlockAppEvalDelta {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@ pub struct BlockEvalDelta {
#[serde(rename = "at")]
pub action: u32,
/// [bs] bytes value.
#[serde(rename = "bs", skip_serializing_if = "Option::is_none")]
pub bytes: Option<String>,
#[serde(
with = "crate::msgpack_string_bytes",
default,
rename = "bs",
skip_serializing_if = "Option::is_none"
)]
pub bytes: Option<Vec<u8>>,
/// [ui] uint value.
#[serde(rename = "ui", skip_serializing_if = "Option::is_none")]
pub uint: Option<u64>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,90 @@
{% endif %} * Generated by: Rust OpenAPI Generator
*/

use crate::models;
use std::collections::HashMap;

use crate::models::BlockEvalDelta;
use serde::de::{MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
use std::fmt;
use std::ops::{Deref, DerefMut};

/// BlockStateDelta is a map keyed by state key to BlockEvalDelta.
pub type BlockStateDelta = HashMap<String, BlockEvalDelta>;
/// Keys are raw bytes that may come as msgpack strings (which can contain non-UTF8 data).
#[derive(Clone, Default, Debug, PartialEq)]
#[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Record))]
pub struct BlockStateDelta {
pub entries: HashMap<Vec<u8>, BlockEvalDelta>,
}

impl Deref for BlockStateDelta {
type Target = HashMap<Vec<u8>, BlockEvalDelta>;

fn deref(&self) -> &Self::Target {
&self.entries
}
}

impl DerefMut for BlockStateDelta {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.entries
}
}

struct BlockStateDeltaVisitor;

impl<'de> Visitor<'de> for BlockStateDeltaVisitor {
type Value = BlockStateDelta;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map with string or binary keys")
}

fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut entries = HashMap::with_capacity(access.size_hint().unwrap_or(0));

// Use rmpv::Value for keys to handle both string and binary
while let Some((key, value)) = access.next_entry::<rmpv::Value, BlockEvalDelta>()? {
let key_bytes = match key {
rmpv::Value::String(s) => s.into_bytes(),
rmpv::Value::Binary(b) => b,
_ => {
return Err(serde::de::Error::custom(
"expected string or binary key in BlockStateDelta",
))
}
};
entries.insert(key_bytes, value);
}

Ok(BlockStateDelta { entries })
}
}

impl<'de> Deserialize<'de> for BlockStateDelta {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(BlockStateDeltaVisitor)
}
}

impl Serialize for BlockStateDelta {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(self.entries.len()))?;
for (k, v) in &self.entries {
// Serialize keys as bytes
map.serialize_entry(&serde_bytes::Bytes::new(k), v)?;
}
map.end()
}
}


1 change: 1 addition & 0 deletions crates/algod_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ uniffi::setup_scaffolding!();

pub mod apis;
pub mod models;
pub mod msgpack_string_bytes;
pub mod msgpack_value_bytes;

// Re-export the main client for convenience
Expand Down
5 changes: 3 additions & 2 deletions crates/algod_client/src/models/block_app_eval_delta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ pub struct BlockAppEvalDelta {
#[serde_as(as = "Option<Vec<Bytes>>")]
#[serde(rename = "sa", skip_serializing_if = "Option::is_none")]
pub shared_accounts: Option<Vec<Vec<u8>>>,
/// [lg] Application log outputs as strings (msgpack strings).
/// [lg] Application log outputs.
#[serde_as(as = "Option<Vec<Bytes>>")]
#[serde(rename = "lg", skip_serializing_if = "Option::is_none")]
pub logs: Option<Vec<String>>,
pub logs: Option<Vec<Vec<u8>>>,
}

impl AlgorandMsgpack for BlockAppEvalDelta {
Expand Down
9 changes: 7 additions & 2 deletions crates/algod_client/src/models/block_eval_delta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@ pub struct BlockEvalDelta {
#[serde(rename = "at")]
pub action: u32,
/// [bs] bytes value.
#[serde(rename = "bs", skip_serializing_if = "Option::is_none")]
pub bytes: Option<String>,
#[serde(
with = "crate::msgpack_string_bytes",
default,
rename = "bs",
skip_serializing_if = "Option::is_none"
)]
pub bytes: Option<Vec<u8>>,
/// [ui] uint value.
#[serde(rename = "ui", skip_serializing_if = "Option::is_none")]
pub uint: Option<u64>,
Expand Down
86 changes: 82 additions & 4 deletions crates/algod_client/src/models/block_state_delta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,88 @@
* Generated by: Rust OpenAPI Generator
*/

use crate::models;
use std::collections::HashMap;

use crate::models::BlockEvalDelta;
use serde::de::{MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
use std::fmt;
use std::ops::{Deref, DerefMut};

/// BlockStateDelta is a map keyed by state key to BlockEvalDelta.
pub type BlockStateDelta = HashMap<String, BlockEvalDelta>;
/// Keys are raw bytes that may come as msgpack strings (which can contain non-UTF8 data).
#[derive(Clone, Default, Debug, PartialEq)]
#[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Record))]
pub struct BlockStateDelta {
pub entries: HashMap<Vec<u8>, BlockEvalDelta>,
}

impl Deref for BlockStateDelta {
type Target = HashMap<Vec<u8>, BlockEvalDelta>;

fn deref(&self) -> &Self::Target {
&self.entries
}
}

impl DerefMut for BlockStateDelta {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.entries
}
}

struct BlockStateDeltaVisitor;

impl<'de> Visitor<'de> for BlockStateDeltaVisitor {
type Value = BlockStateDelta;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a map with string or binary keys")
}

fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut entries = HashMap::with_capacity(access.size_hint().unwrap_or(0));

// Use rmpv::Value for keys to handle both string and binary
while let Some((key, value)) = access.next_entry::<rmpv::Value, BlockEvalDelta>()? {
let key_bytes = match key {
rmpv::Value::String(s) => s.into_bytes(),
rmpv::Value::Binary(b) => b,
_ => {
return Err(serde::de::Error::custom(
"expected string or binary key in BlockStateDelta",
));
}
};
entries.insert(key_bytes, value);
}

Ok(BlockStateDelta { entries })
}
}

impl<'de> Deserialize<'de> for BlockStateDelta {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(BlockStateDeltaVisitor)
}
}

impl Serialize for BlockStateDelta {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(self.entries.len()))?;
for (k, v) in &self.entries {
// Serialize keys as bytes
map.serialize_entry(&serde_bytes::Bytes::new(k), v)?;
}
map.end()
}
}
Loading
Loading