Skip to content

Commit 459492c

Browse files
authored
Add fetch function helpers to accounts page (#299)
* Add fetch function helpers to accounts page * Move fetch types to shared page * Spelling fix * Add changeset and fix an issue with sharedPage not being rendered * Final tweaks * Run tests * Only render shared page when needed
1 parent a1fc563 commit 459492c

File tree

9 files changed

+206
-85
lines changed

9 files changed

+206
-85
lines changed

.changeset/violet-worms-move.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@codama/renderers-rust': patch
3+
---
4+
5+
Add account fetching helper functions to rust client

packages/renderers-rust/e2e/system/src/generated/accounts/nonce.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,69 @@ impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for Nonce {
5050
}
5151
}
5252

53+
#[cfg(feature = "fetch")]
54+
pub fn fetch_nonce(
55+
rpc: &solana_client::rpc_client::RpcClient,
56+
address: &Pubkey,
57+
) -> Result<super::DecodedAccount<Nonce>, Error> {
58+
let accounts = fetch_all_nonce(rpc, vec![address])?;
59+
Ok(accounts[0].clone())
60+
}
61+
62+
#[cfg(feature = "fetch")]
63+
pub fn fetch_all_nonce(
64+
rpc: &solana_client::rpc_client::RpcClient,
65+
addresses: Vec<Pubkey>,
66+
) -> Result<Vec<super::DecodedAccount<Nonce>>, Error> {
67+
let accounts = rpc.get_multiple_accounts(&addresses)?;
68+
let mut decoded_accounts: Vec<super::DecodedAccount<Nonce>> = Vec::new();
69+
for i in 0..addresses.len() {
70+
let address = addresses[i];
71+
let account = accounts[i]
72+
.as_ref()
73+
.ok_or(format!("Account not found: {}", address))?;
74+
let data = Nonce::from_bytes(&account.data)?;
75+
decoded_accounts.push(super::DecodedAccount {
76+
address,
77+
account: account.clone(),
78+
data,
79+
});
80+
}
81+
Ok(decoded_accounts)
82+
}
83+
84+
#[cfg(feature = "fetch")]
85+
pub fn fetch_maybe_nonce(
86+
rpc: &solana_client::rpc_client::RpcClient,
87+
address: &Pubkey,
88+
) -> Result<super::MaybeAccount<Nonce>, Error> {
89+
let accounts = fetch_all_maybe_nonce(rpc, vec![address])?;
90+
Ok(accounts[0].clone())
91+
}
92+
93+
#[cfg(feature = "fetch")]
94+
pub fn fetch_all_maybe_nonce(
95+
rpc: &solana_client::rpc_client::RpcClient,
96+
addresses: Vec<Pubkey>,
97+
) -> Result<Vec<super::MaybeAccount<Nonce>>, Error> {
98+
let accounts = rpc.get_multiple_accounts(&addresses)?;
99+
let mut decoded_accounts: Vec<super::MaybeAccount<Nonce>> = Vec::new();
100+
for i in 0..addresses.len() {
101+
let address = addresses[i];
102+
if let Some(account) = accounts[i].as_ref() {
103+
let data = Nonce::from_bytes(&account.data)?;
104+
decoded_accounts.push(super::MaybeAccount::Exists(super::DecodedAccount {
105+
address,
106+
account: account.clone(),
107+
data,
108+
}));
109+
} else {
110+
decoded_accounts.push(super::MaybeAccount::NotFound(address));
111+
}
112+
}
113+
Ok(decoded_accounts)
114+
}
115+
53116
#[cfg(feature = "anchor")]
54117
impl anchor_lang::AccountDeserialize for Nonce {
55118
fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {

packages/renderers-rust/e2e/system/src/generated/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod accounts;
99
pub mod errors;
1010
pub mod instructions;
1111
pub mod programs;
12+
pub mod shared;
1213
pub mod types;
1314

1415
pub(crate) use programs::*;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//! This code was AUTOGENERATED using the codama library.
2+
//! Please DO NOT EDIT THIS FILE, instead use visitors
3+
//! to add features, then rerun codama to update it.
4+
//!
5+
//! <https://github.com/codama-idl/codama>
6+
//!
7+
8+
#[cfg(feature = "fetch")]
9+
#[derive(Debug, Clone)]
10+
pub struct DecodedAccount<T> {
11+
pub address: solana_program::pubkey::Pubkey,
12+
pub account: solana_sdk::account::Account,
13+
pub data: T,
14+
}
15+
16+
#[cfg(feature = "fetch")]
17+
#[derive(Debug, Clone)]
18+
pub enum MaybeAccount<T> {
19+
Exists(DecodedAccount<T>),
20+
NotFound(solana_program::pubkey::Pubkey),
21+
}

packages/renderers-rust/public/templates/accountsPage.njk

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,59 @@ impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for {{ account.
128128
}
129129
}
130130

131+
#[cfg(feature = "fetch")]
132+
pub fn fetch_{{ account.name | snakeCase }}(
133+
rpc: &solana_client::rpc_client::RpcClient,
134+
address: &Pubkey,
135+
) -> Result<super::DecodedAccount<{{ account.name | pascalCase }}>, Error> {
136+
let accounts = fetch_all_{{ account.name | snakeCase }}(rpc, vec![address])?;
137+
Ok(accounts[0].clone())
138+
}
139+
140+
#[cfg(feature = "fetch")]
141+
pub fn fetch_all_{{ account.name | snakeCase }}(
142+
rpc: &solana_client::rpc_client::RpcClient,
143+
addresses: Vec<Pubkey>,
144+
) -> Result<Vec<super::DecodedAccount<{{ account.name | pascalCase }}>>, Error> {
145+
let accounts = rpc.get_multiple_accounts(&addresses)?;
146+
let mut decoded_accounts: Vec<super::DecodedAccount<{{ account.name | pascalCase }}>> = Vec::new();
147+
for i in 0..addresses.len() {
148+
let address = addresses[i];
149+
let account = accounts[i].as_ref().ok_or(format!("Account not found: {}", address))?;
150+
let data = {{ account.name | pascalCase }}::from_bytes(&account.data)?;
151+
decoded_accounts.push(super::DecodedAccount { address, account: account.clone(), data });
152+
}
153+
Ok(decoded_accounts)
154+
}
155+
156+
#[cfg(feature = "fetch")]
157+
pub fn fetch_maybe_{{ account.name | snakeCase }}(
158+
rpc: &solana_client::rpc_client::RpcClient,
159+
address: &Pubkey,
160+
) -> Result<super::MaybeAccount<{{ account.name | pascalCase }}>, Error> {
161+
let accounts = fetch_all_maybe_{{ account.name | snakeCase }}(rpc, vec![address])?;
162+
Ok(accounts[0].clone())
163+
}
164+
165+
#[cfg(feature = "fetch")]
166+
pub fn fetch_all_maybe_{{ account.name | snakeCase }}(
167+
rpc: &solana_client::rpc_client::RpcClient,
168+
addresses: Vec<Pubkey>,
169+
) -> Result<Vec<super::MaybeAccount<{{ account.name | pascalCase }}>>, Error> {
170+
let accounts = rpc.get_multiple_accounts(&addresses)?;
171+
let mut decoded_accounts: Vec<super::MaybeAccount<{{ account.name | pascalCase }}>> = Vec::new();
172+
for i in 0..addresses.len() {
173+
let address = addresses[i];
174+
if let Some(account) = accounts[i].as_ref() {
175+
let data = {{ account.name | pascalCase }}::from_bytes(&account.data)?;
176+
decoded_accounts.push(super::MaybeAccount::Exists(super::DecodedAccount { address, account: account.clone(), data }));
177+
} else {
178+
decoded_accounts.push(super::MaybeAccount::NotFound(address));
179+
}
180+
}
181+
Ok(decoded_accounts)
182+
}
183+
131184
{% if anchorTraits %}
132185
#[cfg(feature = "anchor")]
133186
impl anchor_lang::AccountDeserialize for {{ account.name | pascalCase }} {

packages/renderers-rust/public/templates/rootMod.njk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
{% if programsToExport.length > 0 %}
1616
pub mod programs;
1717
{% endif %}
18+
{% if accountsToExport.length > 0 %}
19+
pub mod shared;
20+
{% endif %}
1821
{% if definedTypesToExport.length > 0 %}
1922
pub mod types;
2023
{% endif %}
Lines changed: 25 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,33 @@
11
{% extends "layout.njk" %}
22
{% block main %}
33

4-
use std::fmt::Debug;
5-
use std::io::Write;
6-
use std::ops::{Deref, DerefMut};
7-
8-
use borsh::maybestd::io::Read;
9-
use borsh::{BorshDeserialize, BorshSerialize};
10-
11-
/// A vector that deserializes from a stream of bytes.
12-
///
13-
/// This is useful for deserializing a vector that does not have
14-
/// a length prefix. In order to determine how many elements to deserialize,
15-
/// the type of the elements must implement the trait `Sized`.
16-
pub struct RemainderVec<T: BorshSerialize + BorshDeserialize>(Vec<T>);
17-
18-
/// Deferences the inner `Vec` type.
19-
impl<T> Deref for RemainderVec<T>
20-
where
21-
T: BorshSerialize + BorshDeserialize,
22-
{
23-
type Target = Vec<T>;
24-
25-
fn deref(&self) -> &Self::Target {
26-
&self.0
27-
}
28-
}
29-
30-
/// Deferences the inner `Vec` type as mutable.
31-
impl<T> DerefMut for RemainderVec<T>
32-
where
33-
T: BorshSerialize + BorshDeserialize,
34-
{
35-
fn deref_mut(&mut self) -> &mut Self::Target {
36-
&mut self.0
4+
{#
5+
The following types are used as responses for function that fetch accounts.
6+
Ideally, these types live in a shared crate that can be used by the client but
7+
at time of writing, there are some unresolved questions about how to do this.
8+
9+
For now, we just define them here. This the following caveat:
10+
- These types are not compatible between different clients since the type
11+
exists in each client individually.
12+
#}
13+
14+
{% if accountsToExport.length > 0 %}
15+
16+
#[cfg(feature = "fetch")]
17+
#[derive(Debug, Clone)]
18+
pub struct DecodedAccount<T> {
19+
pub address: solana_program::pubkey::Pubkey,
20+
pub account: solana_sdk::account::Account,
21+
pub data: T,
3722
}
38-
}
3923

40-
/// `Debug` implementation for `RemainderVec`.
41-
///
42-
/// This implementation simply forwards to the inner `Vec` type.
43-
impl<T> Debug for RemainderVec<T>
44-
where
45-
T: BorshSerialize + BorshDeserialize + Debug,
46-
{
47-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48-
f.write_fmt(format_args!("{:?}", self.0))
24+
#[cfg(feature = "fetch")]
25+
#[derive(Debug, Clone)]
26+
pub enum MaybeAccount<T> {
27+
Exists(DecodedAccount<T>),
28+
NotFound(solana_program::pubkey::Pubkey),
4929
}
50-
}
5130

52-
impl<T> BorshDeserialize for RemainderVec<T>
53-
where
54-
T: BorshSerialize + BorshDeserialize,
55-
{
56-
fn deserialize_reader<R: Read>(reader: &mut R) -> borsh::maybestd::io::Result<Self> {
57-
let length = std::mem::size_of::<T>();
58-
// buffer to read the data
59-
let mut buffer = vec![0u8; length];
60-
// vec to store the items
61-
let mut items: Vec<T> = Vec::new();
31+
{% endif %}
6232

63-
loop {
64-
match reader.read(&mut buffer)? {
65-
0 => break,
66-
n if n == length => items.push(T::deserialize(&mut buffer.as_slice())?),
67-
e => {
68-
return Err(borsh::maybestd::io::Error::new(
69-
borsh::maybestd::io::ErrorKind::InvalidData,
70-
format!("unexpected number of bytes (read {e}, expected {length})"),
71-
))
72-
}
73-
}
74-
}
75-
76-
Ok(Self(items))
77-
}
78-
}
79-
80-
impl<T> BorshSerialize for RemainderVec<T>
81-
where
82-
T: BorshSerialize + BorshDeserialize,
83-
{
84-
fn serialize<W: Write>(&self, writer: &mut W) -> borsh::maybestd::io::Result<()> {
85-
// serialize each item without adding a prefix for the length
86-
for item in self.0.iter() {
87-
item.serialize(writer)?;
88-
}
89-
90-
Ok(())
91-
}
92-
}
93-
{% endblock %}
33+
{% endblock %}

packages/renderers-rust/src/getRenderMapVisitor.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,9 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) {
270270
};
271271

272272
const map = new RenderMap();
273+
if (accountsToExport.length > 0) {
274+
map.add('shared.rs', render('sharedPage.njk', ctx));
275+
}
273276
if (programsToExport.length > 0) {
274277
map.add('programs.rs', render('programsMod.njk', ctx)).add(
275278
'errors/mod.rs',

packages/renderers-rust/test/accountsPage.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,38 @@ test('it renders anchor traits impl', () => {
135135
]);
136136
});
137137

138+
test('it renders fetch functions', () => {
139+
// Given the following account.
140+
const node = programNode({
141+
accounts: [
142+
accountNode({
143+
discriminators: [
144+
{
145+
kind: 'fieldDiscriminatorNode',
146+
name: camelCase('discriminator'),
147+
offset: 0,
148+
},
149+
],
150+
name: 'testAccount',
151+
pda: pdaLinkNode('testPda'),
152+
}),
153+
],
154+
name: 'myProgram',
155+
publicKey: '1111',
156+
});
157+
158+
// When we render it.
159+
const renderMap = visit(node, getRenderMapVisitor());
160+
161+
// Then we expect the following fetch functions to be rendered.
162+
codeContains(renderMap.get('accounts/test_account.rs'), [
163+
'pub fn fetch_test_account',
164+
'pub fn fetch_maybe_test_account',
165+
'pub fn fetch_all_test_account',
166+
'pub fn fetch_all_maybe_test_account',
167+
]);
168+
});
169+
138170
test('it renders account without anchor traits', () => {
139171
// Given the following account.
140172
const node = programNode({

0 commit comments

Comments
 (0)