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
1 change: 1 addition & 0 deletions Cargo.lock

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

Original file line number Diff line number Diff line change
Expand Up @@ -477,10 +477,22 @@ def generate_client(
def _generate_base_files(self, context: dict[str, Any], output_dir: Path) -> dict[Path, str]:
"""Generate base library files."""
src_dir = output_dir / "src"
return {
files: dict[Path, str] = {
src_dir / "lib.rs": self.template_engine.render_template("base/lib.rs.j2", context),
}

# Inject additional base files for specific clients
# Detect client type
client_type_fn = self.template_engine.env.globals.get("get_client_type")
client_type = client_type_fn(context["spec"]) if callable(client_type_fn) else "Api"
if client_type == "Algod":
# Provide msgpack helper to encode/decode arbitrary msgpack values as bytes
files[src_dir / "msgpack_value_bytes.rs"] = self.template_engine.render_template(
"base/msgpack_value_bytes.rs.j2", context
)

return files

def _generate_model_files(
self,
schemas: dict[str, Schema],
Expand All @@ -501,6 +513,39 @@ def _generate_model_files(
models_context = {**context, "schemas": schemas}
files[models_dir / "mod.rs"] = self.template_engine.render_template("models/mod.rs.j2", models_context)

# Inject custom, hand-authored models for specific clients
# Detect client type
client_type_fn = self.template_engine.env.globals.get("get_client_type")
client_type = client_type_fn(context["spec"]) if callable(client_type_fn) else "Api"
if client_type == "Algod":
# Always generate/override the typed block models
files[models_dir / "block_eval_delta.rs"] = self.template_engine.render_template(
"models/block/block_eval_delta.rs.j2", context
)
files[models_dir / "block_state_delta.rs"] = self.template_engine.render_template(
"models/block/block_state_delta.rs.j2", context
)
files[models_dir / "block_account_state_delta.rs"] = self.template_engine.render_template(
"models/block/block_account_state_delta.rs.j2", context
)
files[models_dir / "block_app_eval_delta.rs"] = self.template_engine.render_template(
"models/block/block_app_eval_delta.rs.j2", context
)
files[models_dir / "block_state_proof_tracking_data.rs"] = self.template_engine.render_template(
"models/block/block_state_proof_tracking_data.rs.j2", context
)
files[models_dir / "block_state_proof_tracking.rs"] = self.template_engine.render_template(
"models/block/block_state_proof_tracking.rs.j2", context
)
files[models_dir / "signed_txn_in_block.rs"] = self.template_engine.render_template(
"models/block/signed_txn_in_block.rs.j2", context
)
files[models_dir / "block.rs"] = self.template_engine.render_template("models/block/block.rs.j2", context)
# Override GetBlock with a typed model that references Block
files[models_dir / "get_block.rs"] = self.template_engine.render_template(
"models/block/get_block.rs.j2", context
)

return files

def _generate_parameter_enums(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ algokit_transact_ffi = { optional = true, version = "0.1.0", path = "../algokit_
algokit_transact = { path = "../algokit_transact" }
# MessagePack serialization
rmp-serde = "^1.1"
rmpv = { version = "1.3.0", features = ["with-serde"] }
{% endif %}

# Error handling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ uniffi::setup_scaffolding!();

pub mod apis;
pub mod models;
{% if client_type == "Algod" %}
pub mod msgpack_value_bytes;
{% endif %}

// Re-export the main client for convenience
pub use apis::{{ client_type }}Client;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/// Custom serde module for handling msgpack-only fields as bytes.
///
/// This module provides serialization/deserialization for fields that:
/// 1. Contain complex msgpack structures (maps with integer keys, nested data, etc.)
/// 2. Need to be stored as Vec<u8> for uniffi compatibility
/// 3. Should preserve the exact msgpack encoding
///
/// When deserializing, it accepts any msgpack value and re-encodes it to bytes.
/// When serializing, it decodes the bytes back to a msgpack value.
use serde::{Deserialize, Deserializer, Serialize, Serializer};

/// Deserialize a msgpack value and re-encode it as bytes
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
// Deserialize as an optional rmpv::Value (accepts any msgpack structure)
let value: Option<rmpv::Value> = Option::deserialize(deserializer)?;

// If present, re-encode the value to msgpack bytes
match value {
Some(v) => {
let mut bytes = Vec::new();
rmpv::encode::write_value(&mut bytes, &v).map_err(|e| {
serde::de::Error::custom(format!("Failed to encode msgpack value: {}", e))
})?;
Ok(Some(bytes))
}
None => Ok(None),
}
}

/// Serialize bytes back to a msgpack value
pub fn serialize<S>(value: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(bytes) => {
// Decode the bytes back to a msgpack value
let value = rmpv::decode::read_value(&mut bytes.as_slice()).map_err(|e| {
serde::ser::Error::custom(format!("Failed to decode msgpack bytes: {}", e))
})?;
value.serialize(serializer)
}
None => serializer.serialize_none(),
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* Algod REST API.
*
* API endpoint for algod operations.
*
* The version of the OpenAPI document: 0.0.1
* Contact: contact@algorand.com
* Generated by: Rust OpenAPI Generator
*/

use crate::models;
#[cfg(not(feature = "ffi_uniffi"))]
use algokit_transact::SignedTransaction as AlgokitSignedTransaction;
use serde::{Deserialize, Serialize};
use serde_with::{Bytes, serde_as};

#[cfg(feature = "ffi_uniffi")]
use algokit_transact_ffi::SignedTransaction as AlgokitSignedTransaction;

use algokit_transact::AlgorandMsgpack;

use crate::models::SignedTxnInBlock;
use crate::models::BlockStateProofTracking;

/// Block contains the BlockHeader and the list of transactions (Payset).
#[serde_as]
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Record))]
pub struct Block {
/// [rnd] Round number.
#[serde(rename = "rnd", skip_serializing_if = "Option::is_none")]
pub round: Option<u64>,
/// [prev] Previous block hash.
#[serde_as(as = "Option<Bytes>")]
#[serde(rename = "prev", skip_serializing_if = "Option::is_none")]
pub previous_block_hash: Option<Vec<u8>>,
/// [prev512] Previous block hash using SHA-512.
#[serde_as(as = "Option<Bytes>")]
#[serde(rename = "prev512", skip_serializing_if = "Option::is_none")]
pub previous_block_hash_512: Option<Vec<u8>>,
/// [seed] Sortition seed.
#[serde_as(as = "Option<Bytes>")]
#[serde(rename = "seed", skip_serializing_if = "Option::is_none")]
pub seed: Option<Vec<u8>>,
/// [txn] Root of transaction merkle tree using SHA512_256.
#[serde_as(as = "Option<Bytes>")]
#[serde(rename = "txn", skip_serializing_if = "Option::is_none")]
pub transactions_root: Option<Vec<u8>>,
/// [txn256] Root of transaction vector commitment using SHA256.
#[serde_as(as = "Option<Bytes>")]
#[serde(rename = "txn256", skip_serializing_if = "Option::is_none")]
pub transactions_root_sha256: Option<Vec<u8>>,
/// [txn512] Root of transaction vector commitment using SHA512.
#[serde_as(as = "Option<Bytes>")]
#[serde(rename = "txn512", skip_serializing_if = "Option::is_none")]
pub transactions_root_sha512: Option<Vec<u8>>,
/// [ts] Block timestamp in seconds since epoch.
#[serde(rename = "ts", skip_serializing_if = "Option::is_none")]
pub timestamp: Option<u64>,
/// [gen] Genesis ID.
#[serde(rename = "gen", skip_serializing_if = "Option::is_none")]
pub genesis_id: Option<String>,
/// [gh] Genesis hash.
#[serde_as(as = "Option<Bytes>")]
#[serde(rename = "gh", skip_serializing_if = "Option::is_none")]
pub genesis_hash: Option<Vec<u8>>,
/// [prp] Proposer address.
#[serde_as(as = "Option<Bytes>")]
#[serde(rename = "prp", skip_serializing_if = "Option::is_none")]
pub proposer: Option<Vec<u8>>,
/// [fc] Fees collected in this block.
#[serde(rename = "fc", skip_serializing_if = "Option::is_none")]
pub fees_collected: Option<u64>,
/// [bi] Bonus incentive for block proposal.
#[serde(rename = "bi", skip_serializing_if = "Option::is_none")]
pub bonus: Option<u64>,
/// [pp] Proposer payout.
#[serde(rename = "pp", skip_serializing_if = "Option::is_none")]
pub proposer_payout: Option<u64>,
/// [fees] FeeSink address.
#[serde_as(as = "Option<Bytes>")]
#[serde(rename = "fees", skip_serializing_if = "Option::is_none")]
pub fee_sink: Option<Vec<u8>>,
/// [rwd] RewardsPool address.
#[serde_as(as = "Option<Bytes>")]
#[serde(rename = "rwd", skip_serializing_if = "Option::is_none")]
pub rewards_pool: Option<Vec<u8>>,
/// [earn] Rewards level.
#[serde(rename = "earn", skip_serializing_if = "Option::is_none")]
pub rewards_level: Option<u64>,
/// [rate] Rewards rate.
#[serde(rename = "rate", skip_serializing_if = "Option::is_none")]
pub rewards_rate: Option<u64>,
/// [frac] Rewards residue.
#[serde(rename = "frac", skip_serializing_if = "Option::is_none")]
pub rewards_residue: Option<u64>,
/// [rwcalr] Rewards recalculation round.
#[serde(rename = "rwcalr", skip_serializing_if = "Option::is_none")]
pub rewards_recalculation_round: Option<u64>,
/// [proto] Current consensus protocol.
#[serde(rename = "proto", skip_serializing_if = "Option::is_none")]
pub current_protocol: Option<String>,
/// [nextproto] Next proposed protocol.
#[serde(rename = "nextproto", skip_serializing_if = "Option::is_none")]
pub next_protocol: Option<String>,
/// [nextyes] Next protocol approvals.
#[serde(rename = "nextyes", skip_serializing_if = "Option::is_none")]
pub next_protocol_approvals: Option<u64>,
/// [nextbefore] Next protocol vote deadline.
#[serde(rename = "nextbefore", skip_serializing_if = "Option::is_none")]
pub next_protocol_vote_before: Option<u64>,
/// [nextswitch] Next protocol switch round.
#[serde(rename = "nextswitch", skip_serializing_if = "Option::is_none")]
pub next_protocol_switch_on: Option<u64>,
/// [upgradeprop] Upgrade proposal.
#[serde(rename = "upgradeprop", skip_serializing_if = "Option::is_none")]
pub upgrade_propose: Option<String>,
/// [upgradedelay] Upgrade delay in rounds.
#[serde(rename = "upgradedelay", skip_serializing_if = "Option::is_none")]
pub upgrade_delay: Option<u64>,
/// [upgradeyes] Upgrade approval flag.
#[serde(rename = "upgradeyes", skip_serializing_if = "Option::is_none")]
pub upgrade_approve: Option<bool>,
/// [tc] Transaction counter.
#[serde(rename = "tc", skip_serializing_if = "Option::is_none")]
pub txn_counter: Option<u64>,
/// [spt] State proof tracking data keyed by state proof type.
#[serde(rename = "spt", skip_serializing_if = "Option::is_none", default)]
pub state_proof_tracking: Option<BlockStateProofTracking>,
/// [partupdrmv] Expired participation accounts.
#[serde_as(as = "Option<Vec<Bytes>>")]
#[serde(rename = "partupdrmv", skip_serializing_if = "Option::is_none")]
pub expired_participation_accounts: Option<Vec<Vec<u8>>>,
/// [partupdabs] Absent participation accounts.
#[serde_as(as = "Option<Vec<Bytes>>")]
#[serde(rename = "partupdabs", skip_serializing_if = "Option::is_none")]
pub absent_participation_accounts: Option<Vec<Vec<u8>>>,
/// [txns] Block transactions (Payset).
#[serde(rename = "txns", skip_serializing_if = "Option::is_none")]
pub transactions: Option<Vec<SignedTxnInBlock>>,
}

impl AlgorandMsgpack for Block {
const PREFIX: &'static [u8] = b""; // Adjust prefix as needed for your specific type
}

impl Block {
/// Default constructor for Block
pub fn new() -> Block {
Block::default()
}

/// Encode this struct to msgpack bytes using AlgorandMsgpack trait
pub fn to_msgpack(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
Ok(self.encode()?)
}

/// Decode msgpack bytes to this struct using AlgorandMsgpack trait
pub fn from_msgpack(bytes: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
Ok(Self::decode(bytes)?)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* {{ spec.info.title }}
*
* {{ spec.info.description or "API client generated from OpenAPI specification" }}
*
* The version of the OpenAPI document: {{ spec.info.version }}
{% if spec.info.contact and spec.info.contact.email %} * Contact: {{ spec.info.contact.email }}
{% endif %} * Generated by: Rust OpenAPI Generator
*/

use crate::models;
use serde::{Deserialize, Serialize};
use crate::models::BlockStateDelta;

/// BlockAccountStateDelta pairs an address with a BlockStateDelta map.
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Record))]
pub struct BlockAccountStateDelta {
#[serde(rename = "address")]
pub address: String,
#[serde(rename = "delta")]
pub delta: BlockStateDelta,
}

impl BlockAccountStateDelta {
pub fn new(address: String, delta: BlockStateDelta) -> Self { Self { address, delta } }
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* {{ spec.info.title }}
*
* {{ spec.info.description or "API client generated from OpenAPI specification" }}
*
* The version of the OpenAPI document: {{ spec.info.version }}
{% if spec.info.contact and spec.info.contact.email %} * Contact: {{ spec.info.contact.email }}
{% endif %} * Generated by: Rust OpenAPI Generator
*/

use crate::models;
#[cfg(not(feature = "ffi_uniffi"))]
use algokit_transact::SignedTransaction as AlgokitSignedTransaction;
use serde::{Deserialize, Serialize};
use serde_with::{Bytes, serde_as};
use std::collections::HashMap;

#[cfg(feature = "ffi_uniffi")]
use algokit_transact_ffi::SignedTransaction as AlgokitSignedTransaction;

use algokit_transact::AlgorandMsgpack;

use crate::models::SignedTxnInBlock;
use crate::models::BlockStateDelta;

/// BlockAppEvalDelta matches msgpack wire for blocks; uses BlockStateDelta maps.
#[serde_as]
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ffi_uniffi", derive(uniffi::Record))]
pub struct BlockAppEvalDelta {
/// [gd] Global state delta for the application.
#[serde(rename = "gd", skip_serializing_if = "Option::is_none")]
pub global_delta: Option<BlockStateDelta>,
/// [ld] Local state deltas keyed by integer account index.
#[serde(rename = "ld", skip_serializing_if = "Option::is_none")]
pub local_deltas: Option<HashMap<u64, BlockStateDelta>>,
/// [itx] Inner transactions produced by this application execution.
#[serde(rename = "itx", skip_serializing_if = "Option::is_none")]
pub inner_txns: Option<Vec<SignedTxnInBlock>>,
/// [sa] Shared accounts referenced by local deltas.
#[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).
#[serde(rename = "lg", skip_serializing_if = "Option::is_none")]
pub logs: Option<Vec<String>>,
}

impl AlgorandMsgpack for BlockAppEvalDelta {
const PREFIX: &'static [u8] = b"";
}

impl BlockAppEvalDelta {
pub fn new() -> BlockAppEvalDelta { BlockAppEvalDelta::default() }
}


Loading