1use std::collections::HashMap;
11
12use async_trait::async_trait;
13use thiserror::Error;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct AccountData {
18 pub data: Vec<u8>,
19 pub owner: [u8; 32],
20 pub lamports: u64,
21}
22
23#[derive(Debug, Error)]
27pub enum UpstreamError {
28 #[error("upstream transport error: {0}")]
29 Transport(String),
30 #[error("upstream returned an RPC error: {0}")]
31 Rpc(String),
32 #[error("no fixture stub registered for RPC method '{method}'")]
33 MethodNotStubbed { method: String },
34 #[error("upstream request timed out after {millis}ms")]
35 Timeout { millis: u64 },
36}
37
38pub type UpstreamResult<T> = Result<T, UpstreamError>;
39
40#[async_trait]
43pub trait UpstreamClient: Send + Sync {
44 async fn rpc_call(&self, method: &str, params: serde_json::Value) -> UpstreamResult<Vec<u8>>;
47
48 async fn get_account(&self, address: &str) -> UpstreamResult<Option<AccountData>>;
53}
54
55type FixtureRpcHandler =
59 Box<dyn Fn(&serde_json::Value) -> UpstreamResult<serde_json::Value> + Send + Sync>;
60
61pub struct FixtureUpstream {
66 accounts: HashMap<String, AccountData>,
67 rpc_responses: HashMap<String, FixtureRpcHandler>,
68}
69
70impl FixtureUpstream {
71 #[must_use]
72 pub fn new() -> Self {
73 Self {
74 accounts: HashMap::new(),
75 rpc_responses: HashMap::new(),
76 }
77 }
78
79 #[must_use]
83 pub fn with_account(mut self, address: impl Into<String>, data: AccountData) -> Self {
84 self.accounts.insert(address.into(), data);
85 self
86 }
87
88 #[must_use]
91 pub fn with_method<F>(mut self, method: impl Into<String>, handler: F) -> Self
92 where
93 F: Fn(&serde_json::Value) -> UpstreamResult<serde_json::Value> + Send + Sync + 'static,
94 {
95 self.rpc_responses.insert(method.into(), Box::new(handler));
96 self
97 }
98}
99
100impl Default for FixtureUpstream {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106#[async_trait]
107impl UpstreamClient for FixtureUpstream {
108 async fn rpc_call(&self, method: &str, params: serde_json::Value) -> UpstreamResult<Vec<u8>> {
109 if let Some(handler) = self.rpc_responses.get(method) {
110 let value = handler(¶ms)?;
111 return serde_json::to_vec(&value)
112 .map_err(|e| UpstreamError::Transport(format!("serialize fixture result: {e}")));
113 }
114 Err(UpstreamError::MethodNotStubbed {
115 method: method.to_string(),
116 })
117 }
118
119 async fn get_account(&self, address: &str) -> UpstreamResult<Option<AccountData>> {
120 Ok(self.accounts.get(address).cloned())
121 }
122}