1use async_trait::async_trait;
2
3use crate::data::command::{DamlCommand, DamlCreateCommand, DamlExerciseCommand};
4use crate::data::event::{DamlCreatedEvent, DamlEvent};
5use crate::data::filter::{DamlEventFormat, DamlTransactionFormat, DamlTransactionShape};
6use crate::data::value::DamlValue;
7use crate::data::{DamlCommandsDeduplicationPeriod, DamlError, DamlMinLedgerTime, DamlResult, DamlTransaction};
8use crate::service::DamlCommandService;
9use crate::util::Required;
10use crate::{DamlCommandFactory, DamlGrpcClient};
11
12pub struct DamlSimpleExecutorBuilder<'a> {
14 ledger_client: &'a DamlGrpcClient,
15 act_as: Option<Vec<String>>,
16 read_as: Option<Vec<String>>,
17 workflow_id: Option<&'a str>,
18 user_id: Option<&'a str>,
19 deduplication_period: Option<DamlCommandsDeduplicationPeriod>,
20 min_ledger_time: Option<DamlMinLedgerTime>,
21 auth_token: Option<&'a str>,
22}
23
24impl<'a> DamlSimpleExecutorBuilder<'a> {
25 pub const fn new(ledger_client: &'a DamlGrpcClient) -> Self {
26 Self {
27 ledger_client,
28 act_as: None,
29 read_as: None,
30 workflow_id: None,
31 user_id: None,
32 deduplication_period: None,
33 min_ledger_time: None,
34 auth_token: None,
35 }
36 }
37
38 pub fn workflow_id(self, workflow_id: &'a str) -> Self {
39 Self {
40 workflow_id: Some(workflow_id),
41 ..self
42 }
43 }
44
45 pub fn act_as(self, act_as: impl Into<String>) -> Self {
46 Self {
47 act_as: Some(vec![act_as.into()]),
48 ..self
49 }
50 }
51
52 pub fn act_as_all(self, act_as_all: Vec<String>) -> Self {
53 Self {
54 act_as: Some(act_as_all),
55 ..self
56 }
57 }
58
59 pub fn read_as(self, read_as: impl Into<String>) -> Self {
60 Self {
61 read_as: Some(vec![read_as.into()]),
62 ..self
63 }
64 }
65
66 pub fn read_as_all(self, read_as_all: Vec<String>) -> Self {
67 Self {
68 read_as: Some(read_as_all),
69 ..self
70 }
71 }
72
73 pub fn user_id(self, user_id: &'a str) -> Self {
77 Self {
78 user_id: Some(user_id),
79 ..self
80 }
81 }
82
83 pub fn deduplication_period(self, deduplication_period: DamlCommandsDeduplicationPeriod) -> Self {
84 Self {
85 deduplication_period: Some(deduplication_period),
86 ..self
87 }
88 }
89
90 pub fn min_ledger_time(self, min_ledger_time: DamlMinLedgerTime) -> Self {
91 Self {
92 min_ledger_time: Some(min_ledger_time),
93 ..self
94 }
95 }
96
97 pub fn auth_token(self, auth_token: &'a str) -> Self {
99 Self {
100 auth_token: Some(auth_token),
101 ..self
102 }
103 }
104
105 pub fn build(self) -> DamlResult<DamlSimpleExecutor<'a>> {
106 if self.has_parties() {
107 Ok(DamlSimpleExecutor::new(
108 self.ledger_client,
109 self.act_as.unwrap_or_default(),
110 self.read_as.unwrap_or_default(),
111 self.workflow_id.unwrap_or("default-workflow"),
112 self.user_id.unwrap_or("default-user"),
113 self.deduplication_period,
114 self.min_ledger_time,
115 self.auth_token,
116 ))
117 } else {
118 Err(DamlError::InsufficientParties)
119 }
120 }
121
122 fn has_parties(&self) -> bool {
123 match (self.act_as.as_deref(), self.read_as.as_deref()) {
124 (None, None) => false,
125 (Some(act_as), None) => !act_as.is_empty(),
126 (None, Some(read_as)) => !read_as.is_empty(),
127 (Some(act_as), Some(read_as)) => !act_as.is_empty() || !read_as.is_empty(),
128 }
129 }
130}
131
132#[async_trait]
142pub trait CommandExecutor {
143 async fn execute_for_transaction(&self, command: DamlCommand) -> DamlResult<DamlTransaction>;
144 async fn execute_for_transaction_with_effects(&self, command: DamlCommand) -> DamlResult<DamlTransaction>;
145 async fn execute_create(&self, create_command: DamlCreateCommand) -> DamlResult<DamlCreatedEvent>;
146 async fn execute_exercise(&self, exercise_command: DamlExerciseCommand) -> DamlResult<DamlValue>;
147}
148
149pub struct DamlSimpleExecutor<'a> {
151 ledger_client: &'a DamlGrpcClient,
152 command_factory: DamlCommandFactory,
153 auth_token: Option<&'a str>,
154}
155
156impl<'a> DamlSimpleExecutor<'a> {
157 #[allow(clippy::too_many_arguments)]
158 pub fn new(
159 ledger_client: &'a DamlGrpcClient,
160 act_as: Vec<String>,
161 read_as: Vec<String>,
162 workflow_id: &str,
163 user_id: &str,
164 deduplication_period: Option<DamlCommandsDeduplicationPeriod>,
165 min_ledger_time: Option<DamlMinLedgerTime>,
166 auth_token: Option<&'a str>,
167 ) -> Self {
168 let command_factory =
169 DamlCommandFactory::new(workflow_id, user_id, act_as, read_as, deduplication_period, min_ledger_time);
170 Self {
171 ledger_client,
172 command_factory,
173 auth_token,
174 }
175 }
176
177 pub fn act_as(&self) -> &[String] {
178 self.command_factory.act_as()
179 }
180
181 pub fn read_as(&self) -> &[String] {
182 self.command_factory.read_as()
183 }
184
185 async fn submit_and_wait_for_transaction(&self, command: DamlCommand) -> DamlResult<DamlTransaction> {
186 let commands = self.command_factory.make_command(command);
187 self.client().submit_and_wait_for_transaction(commands, None).await
191 }
192
193 async fn submit_and_wait_for_transaction_with_effects(&self, command: DamlCommand) -> DamlResult<DamlTransaction> {
197 let commands = self.command_factory.make_command(command);
198 let mut filters_by_party = std::collections::HashMap::new();
201 let wildcard = crate::data::filter::DamlFilters::default();
202 for party in self.act_as() {
203 filters_by_party.insert(party.clone(), wildcard.clone());
204 }
205 for party in self.read_as() {
206 filters_by_party.insert(party.clone(), wildcard.clone());
207 }
208 let event_format = DamlEventFormat {
209 filters_by_party,
210 filters_for_any_party: None,
211 verbose: true,
212 };
213 let format = DamlTransactionFormat {
214 event_format,
215 transaction_shape: DamlTransactionShape::LedgerEffects,
216 };
217 self.client().submit_and_wait_for_transaction(commands, Some(format)).await
218 }
219
220 fn client(&self) -> DamlCommandService<'_> {
221 match self.auth_token {
222 Some(token) => self.ledger_client.command_service().with_token(token),
223 None => self.ledger_client.command_service(),
224 }
225 }
226}
227
228#[async_trait]
229#[allow(clippy::needless_lifetimes)]
230impl CommandExecutor for DamlSimpleExecutor<'_> {
231 async fn execute_for_transaction(&self, command: DamlCommand) -> DamlResult<DamlTransaction> {
232 self.submit_and_wait_for_transaction(command).await
233 }
234
235 async fn execute_for_transaction_with_effects(&self, command: DamlCommand) -> DamlResult<DamlTransaction> {
236 self.submit_and_wait_for_transaction_with_effects(command).await
237 }
238
239 async fn execute_create(&self, create_command: DamlCreateCommand) -> Result<DamlCreatedEvent, DamlError> {
240 let mut tx = self.submit_and_wait_for_transaction(DamlCommand::Create(create_command)).await?;
241 if tx.events.is_empty() {
242 return Err(DamlError::Other("execute_create: transaction had no events".to_owned()));
243 }
244 tx.events.swap_remove(0).try_created()
245 }
246
247 async fn execute_exercise(&self, exercise_command: DamlExerciseCommand) -> Result<DamlValue, DamlError> {
258 let tx = self.submit_and_wait_for_transaction_with_effects(DamlCommand::Exercise(exercise_command)).await?;
259 tx.events
260 .into_iter()
261 .find_map(|e| match e {
262 DamlEvent::Exercised(ex) => ex.exercise_result.clone(),
263 _ => None,
264 })
265 .req()
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[tokio::test]
274 async fn test_act_as() -> DamlResult<()> {
275 let client = DamlGrpcClient::dummy_for_testing();
276 let executor = DamlSimpleExecutorBuilder::new(&client).act_as("Alice").build()?;
277 assert_eq!(&["Alice"], executor.act_as());
278 assert_eq!(0, executor.read_as().len());
279 Ok(())
280 }
281
282 #[tokio::test]
283 async fn test_read_as() -> DamlResult<()> {
284 let client = DamlGrpcClient::dummy_for_testing();
285 let executor = DamlSimpleExecutorBuilder::new(&client).read_as("Alice").build()?;
286 assert_eq!(&["Alice"], executor.read_as());
287 assert_eq!(0, executor.act_as().len());
288 Ok(())
289 }
290
291 #[tokio::test]
292 async fn test_act_as_and_read_as() -> DamlResult<()> {
293 let client = DamlGrpcClient::dummy_for_testing();
294 let executor = DamlSimpleExecutorBuilder::new(&client).act_as("Alice").read_as("Bob").build()?;
295 assert_eq!(&["Alice"], executor.act_as());
296 assert_eq!(&["Bob"], executor.read_as());
297 Ok(())
298 }
299
300 #[tokio::test]
301 async fn test_act_as_all() -> DamlResult<()> {
302 let client = DamlGrpcClient::dummy_for_testing();
303 let executor =
304 DamlSimpleExecutorBuilder::new(&client).act_as_all(vec!["Alice".into(), "Bob".into()]).build()?;
305 assert_eq!(&["Alice", "Bob"], executor.act_as());
306 assert_eq!(0, executor.read_as().len());
307 Ok(())
308 }
309
310 #[tokio::test]
311 async fn test_read_as_all() -> DamlResult<()> {
312 let client = DamlGrpcClient::dummy_for_testing();
313 let executor =
314 DamlSimpleExecutorBuilder::new(&client).read_as_all(vec!["Alice".into(), "Bob".into()]).build()?;
315 assert_eq!(&["Alice", "Bob"], executor.read_as());
316 assert_eq!(0, executor.act_as().len());
317 Ok(())
318 }
319
320 #[tokio::test]
321 async fn test_act_as_all_and_read_as_all() -> DamlResult<()> {
322 let client = DamlGrpcClient::dummy_for_testing();
323 let executor = DamlSimpleExecutorBuilder::new(&client)
324 .act_as_all(vec!["Alice".into(), "Bob".into()])
325 .read_as_all(vec!["John".into(), "Jill".into()])
326 .build()?;
327 assert_eq!(&["Alice", "Bob"], executor.act_as());
328 assert_eq!(&["John", "Jill"], executor.read_as());
329 Ok(())
330 }
331
332 #[tokio::test]
333 async fn test_no_actors_should_fail() -> DamlResult<()> {
334 let client = DamlGrpcClient::dummy_for_testing();
335 let executor = DamlSimpleExecutorBuilder::new(&client).build();
336 match executor {
337 Err(DamlError::InsufficientParties) => (),
338 _ => panic!("expected DamlError::InsufficientParties"),
339 }
340 Ok(())
341 }
342}