Skip to content

Commit 407b222

Browse files
committed
Generate Rust enum shells from spec enumerations
This PR teaches the generator to emit Rust `enum` definitions from the spec's `EnumerationSpec` entries, replacing 9 previously hand-written enum shells with generated ones. The spec carries enumerations at the category level (today they all live under `shared`), so discovery is a simple walk over `category.enumerations` — no extra config and no additions to the override surface. Each generated enum gets the standard derive set (`Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize`) plus `#[serde(rename_all = "camelCase")]`. Variants are `pascalCase(spec name)` in declaration order, with enum-level and variant-level docs propagated from the spec. The generator deliberately does not emit `#[derive(Default)]`: the spec doesn't carry a "default variant" notion, so any required `Default` lives in a hand-written companion file alongside the generated shell — the same generated-type / hand-written-impls split already used for `Number`. Reconciling the spec with the existing Rust crate required one breaking rename: `Endianness::{Big, Little} → Endianness::{Be, Le}`, since the generator derives variant names directly from the spec's `[be, le]` rather than carrying a per-enum override. The hand-written `TryFrom` impls still accept both the short and long forms as input. `NumberFormat` variant ordering also shifts from semantic to alphabetic (matching spec order); no `match` site relies on the old ordering. `instruction_node.rs`, `instruction_status_node.rs`, `shared/bytes_encoding.rs`, and the `type_nodes/{number,pre_offset,post_offset}_type_node.rs` files each lose their inline enum definition and keep only the bespoke impls (constructors, `TryFrom`, `Default`) the spec can't express; two new files in `shared/` hold the `Default` impls for `InstructionLifecycle` and `OptionalAccountStrategy` so the per-type generated/hand-written symmetry stays clean. Workspace-wide: 1006 cargo tests pass unchanged, fmt and clippy stay clean, `pnpm test` grew by 6 enumPage tests (113 → 119), and `pnpm generate` round-trips deterministically.
1 parent f35c0fa commit 407b222

32 files changed

Lines changed: 379 additions & 130 deletions

codama-attributes/src/codama_directives/type_nodes/number_type_node.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ impl FromMeta for NumberTypeNode {
66
fn from_meta(meta: &Meta) -> syn::Result<Self> {
77
let pl = meta.assert_directive("number")?.as_path_list()?;
88
let mut format = SetOnce::<NumberFormat>::new("format");
9-
let mut endian = SetOnce::<Endianness>::new("endian").initial_value(Endianness::Little);
9+
let mut endian = SetOnce::<Endianness>::new("endian").initial_value(Endianness::Le);
1010

1111
pl.each(|ref meta| match meta.path_str().as_str() {
1212
"format" => {

codama-nodes/src/count_nodes/prefixed_count_node.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ mod tests {
2121
let node = PrefixedCountNode::new(NumberTypeNode::le(U32));
2222
assert_eq!(
2323
node.prefix,
24-
NestedTypeNode::Value(NumberTypeNode::new(U32, Endianness::Little))
24+
NestedTypeNode::Value(NumberTypeNode::new(U32, Endianness::Le))
2525
);
2626
assert_eq!(
2727
node.prefix.get_nested_type_node(),
28-
&NumberTypeNode::new(U32, Endianness::Little)
28+
&NumberTypeNode::new(U32, Endianness::Le)
2929
);
3030
}
3131

@@ -35,13 +35,13 @@ mod tests {
3535
assert_eq!(
3636
node.prefix,
3737
NestedTypeNode::PreOffset(PreOffsetTypeNode::absolute(
38-
NestedTypeNode::Value(NumberTypeNode::new(U32, Endianness::Little)),
38+
NestedTypeNode::Value(NumberTypeNode::new(U32, Endianness::Le)),
3939
0
4040
))
4141
);
4242
assert_eq!(
4343
node.prefix.get_nested_type_node(),
44-
&NumberTypeNode::new(U32, Endianness::Little)
44+
&NumberTypeNode::new(U32, Endianness::Le)
4545
);
4646
}
4747

codama-nodes/src/generated/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ mod count_nodes;
33
mod discriminator_nodes;
44
mod link_nodes;
55
mod pda_seed_nodes;
6+
mod shared;
67
mod value_nodes;
78

89
pub use contextual_value_nodes::*;
910
pub use count_nodes::*;
1011
pub use discriminator_nodes::*;
1112
pub use link_nodes::*;
1213
pub use pda_seed_nodes::*;
14+
pub use shared::*;
1315
pub use value_nodes::*;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
/// How a string of bytes is encoded for transport.
4+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
5+
#[serde(rename_all = "camelCase")]
6+
pub enum BytesEncoding {
7+
/// Hexadecimal encoding (two characters per byte).
8+
Base16,
9+
/// Base58 encoding, the standard for Solana addresses.
10+
Base58,
11+
/// Base64 encoding (RFC 4648).
12+
Base64,
13+
/// UTF-8 text encoding.
14+
Utf8,
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
/// How an attribute that carries a default value is exposed in generated APIs.
4+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
5+
#[serde(rename_all = "camelCase")]
6+
pub enum DefaultValueStrategy {
7+
/// The attribute is not exposed as a parameter in the generated API; the default value is always used.
8+
Omitted,
9+
/// The attribute is exposed as an optional parameter; callers may override the default value.
10+
Optional,
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
/// The byte order of a numeric serialization.
4+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
5+
#[serde(rename_all = "camelCase")]
6+
pub enum Endianness {
7+
/// Big-endian: the most significant byte is written first.
8+
Be,
9+
/// Little-endian: the least significant byte is written first.
10+
Le,
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
/// The lifecycle stage of an instruction.
4+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
5+
#[serde(rename_all = "camelCase")]
6+
pub enum InstructionLifecycle {
7+
/// No longer included in client SDKs. Retained in the IDL for historical reference only.
8+
Archived,
9+
/// Still callable but discouraged. Clients should migrate to a replacement instruction.
10+
Deprecated,
11+
/// Work-in-progress. The instruction may change before it stabilises.
12+
Draft,
13+
/// Stable and supported for production use.
14+
Live,
15+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
mod bytes_encoding;
2+
mod default_value_strategy;
3+
mod endianness;
4+
mod instruction_lifecycle;
5+
mod number_format;
6+
mod optional_account_strategy;
7+
mod post_offset_strategy;
8+
mod pre_offset_strategy;
9+
mod program_origin;
10+
11+
pub use bytes_encoding::*;
12+
pub use default_value_strategy::*;
13+
pub use endianness::*;
14+
pub use instruction_lifecycle::*;
15+
pub use number_format::*;
16+
pub use optional_account_strategy::*;
17+
pub use post_offset_strategy::*;
18+
pub use pre_offset_strategy::*;
19+
pub use program_origin::*;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
/// The wire format of a numeric serialization.
4+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
5+
#[serde(rename_all = "camelCase")]
6+
pub enum NumberFormat {
7+
/// IEEE-754 32-bit floating point.
8+
F32,
9+
/// IEEE-754 64-bit floating point.
10+
F64,
11+
/// Signed 8-bit integer.
12+
I8,
13+
/// Signed 16-bit integer.
14+
I16,
15+
/// Signed 32-bit integer.
16+
I32,
17+
/// Signed 64-bit integer.
18+
I64,
19+
/// Signed 128-bit integer.
20+
I128,
21+
/// Solana compact-u16 encoding: a variable-length unsigned integer occupying 1 to 3 bytes.
22+
ShortU16,
23+
/// Unsigned 8-bit integer.
24+
U8,
25+
/// Unsigned 16-bit integer.
26+
U16,
27+
/// Unsigned 32-bit integer.
28+
U32,
29+
/// Unsigned 64-bit integer.
30+
U64,
31+
/// Unsigned 128-bit integer.
32+
U128,
33+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
/// How an absent optional account is represented when serialising an instruction.
4+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
5+
#[serde(rename_all = "camelCase")]
6+
pub enum OptionalAccountStrategy {
7+
/// The account slot is left out of the instruction entirely. Subsequent accounts shift up.
8+
Omitted,
9+
/// The account slot is filled with the program ID as a placeholder, preserving positional indices.
10+
ProgramId,
11+
}

0 commit comments

Comments
 (0)