1use std::any::Any;
4use std::fmt::Debug;
5use std::sync::Arc;
6
7use proptest::prelude::*;
8use serde::de::DeserializeOwned;
9use serde::Serialize;
10use soroban_sdk::Env;
11use thiserror::Error;
12
13use crate::actor::Actor;
14use crate::config::AuthMode;
15
16#[derive(Clone, Debug, PartialEq, Eq)]
23pub enum CallOutcome {
24 Success,
26 SuccessWith(serde_json::Value),
29 ContractError(u32),
31 Unauthorized,
34}
35
36impl CallOutcome {
37 pub fn returning(value: impl Serialize) -> Self {
39 match serde_json::to_value(value) {
40 Ok(v) => CallOutcome::SuccessWith(v),
41 Err(_) => CallOutcome::Success,
42 }
43 }
44
45 pub fn is_success(&self) -> bool {
47 matches!(self, CallOutcome::Success | CallOutcome::SuccessWith(_))
48 }
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct CallContext {
54 pub step: usize,
56 pub auth: AuthMode,
58}
59
60impl CallContext {
61 pub fn nonce(&self) -> i64 {
64 self.step as i64 + 1
65 }
66
67 pub fn signature_expiration_ledger(&self, env: &Env) -> u32 {
69 env.ledger().sequence().saturating_add(100)
70 }
71}
72
73#[derive(Debug, Error)]
75pub enum HandlerError {
76 #[error("host error: {0}")]
78 Host(String),
79 #[error("argument type mismatch for handler `{0}`")]
81 ArgsMismatch(&'static str),
82 #[error("{0}")]
84 Other(String),
85}
86
87pub trait Handler: Send + Sync + 'static {
95 type Args: Clone + Debug + Serialize + DeserializeOwned + Send + Sync + 'static;
97
98 fn name(&self) -> &'static str;
100
101 fn actors(&self) -> &[Actor];
103
104 fn strategy(&self) -> BoxedStrategy<Self::Args>;
106
107 fn args_from_json(&self, value: &serde_json::Value) -> Option<Self::Args> {
110 serde_json::from_value(value.clone()).ok()
111 }
112
113 fn call(
119 &self,
120 env: &Env,
121 ctx: &CallContext,
122 actor: &Actor,
123 args: &Self::Args,
124 ) -> Result<CallOutcome, HandlerError>;
125}
126
127#[derive(Clone)]
129pub struct StepArgs(Arc<dyn ErasedArgs>);
130
131impl StepArgs {
132 pub fn to_json(&self) -> serde_json::Value {
134 self.0.to_json()
135 }
136
137 fn downcast<T: 'static>(&self) -> Option<&T> {
138 self.0.as_any().downcast_ref::<T>()
139 }
140}
141
142impl Debug for StepArgs {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 self.0.fmt(f)
145 }
146}
147
148trait ErasedArgs: Debug + Send + Sync + 'static {
149 fn as_any(&self) -> &dyn Any;
150 fn to_json(&self) -> serde_json::Value;
151}
152
153impl<T> ErasedArgs for T
154where
155 T: Clone + Debug + Serialize + Send + Sync + 'static,
156{
157 fn as_any(&self) -> &dyn Any {
158 self
159 }
160
161 fn to_json(&self) -> serde_json::Value {
162 serde_json::to_value(self)
163 .unwrap_or_else(|_| serde_json::Value::String(format!("{self:?}")))
164 }
165}
166
167pub(crate) trait DynHandler: Send + Sync {
169 fn name(&self) -> &'static str;
170 fn actors(&self) -> &[Actor];
171 fn strategy(&self) -> BoxedStrategy<StepArgs>;
172 fn args_from_json(&self, value: &serde_json::Value) -> Option<StepArgs>;
173 fn call(
174 &self,
175 env: &Env,
176 ctx: &CallContext,
177 actor: &Actor,
178 args: &StepArgs,
179 ) -> Result<CallOutcome, HandlerError>;
180}
181
182impl<H: Handler> DynHandler for H {
183 fn name(&self) -> &'static str {
184 Handler::name(self)
185 }
186
187 fn actors(&self) -> &[Actor] {
188 Handler::actors(self)
189 }
190
191 fn strategy(&self) -> BoxedStrategy<StepArgs> {
192 Handler::strategy(self)
193 .prop_map(|args| StepArgs(Arc::new(args)))
194 .boxed()
195 }
196
197 fn args_from_json(&self, value: &serde_json::Value) -> Option<StepArgs> {
198 Handler::args_from_json(self, value).map(|args| StepArgs(Arc::new(args)))
199 }
200
201 fn call(
202 &self,
203 env: &Env,
204 ctx: &CallContext,
205 actor: &Actor,
206 args: &StepArgs,
207 ) -> Result<CallOutcome, HandlerError> {
208 let args = args
209 .downcast::<H::Args>()
210 .ok_or_else(|| HandlerError::ArgsMismatch(Handler::name(self)))?;
211 Handler::call(self, env, ctx, actor, args)
212 }
213}