reactive_graph_command_model/builder.rs
1use std::ops::Deref;
2
3use serde_json::Value;
4use serde_json::json;
5use typed_builder::TypedBuilder;
6use uuid::Uuid;
7
8use crate::component::COMPONENT_COMMAND;
9use crate::component::CommandProperties::COMMAND_ARGS;
10use crate::component::CommandProperties::COMMAND_HELP;
11use crate::component::CommandProperties::COMMAND_NAME;
12use crate::component::CommandProperties::COMMAND_NAMESPACE;
13use crate::component::CommandProperties::COMMAND_RESULT;
14use crate::entity::Command;
15use crate::entity::CommandArgs;
16use reactive_graph_graph::ComponentTypeIds;
17use reactive_graph_graph::EntityTypeId;
18use reactive_graph_graph::NamespacedTypeGetter;
19use reactive_graph_graph::PropertyInstanceSetter;
20use reactive_graph_graph::PropertyInstances;
21use reactive_graph_graph::PropertyTypeDefinition;
22use reactive_graph_reactive_model_impl::ReactiveEntity;
23use reactive_graph_reactive_model_impl::ReactiveProperties;
24use reactive_graph_runtime_model::ActionProperties::TRIGGER;
25use reactive_graph_runtime_model::COMPONENT_ACTION;
26use reactive_graph_runtime_model::COMPONENT_LABELED;
27use reactive_graph_runtime_model::LabeledProperties::LABEL;
28
29pub type CommandExecutor = dyn FnMut(&ReactiveEntity) -> Value + 'static + Send;
30
31#[derive(TypedBuilder)]
32#[builder(
33 build_method(vis="pub", into=Command),
34 builder_method(vis="pub"),
35 builder_type(vis="pub", name=CommandDefinitionBuilder),
36)]
37pub struct CommandDefinition {
38 #[builder(setter(into))]
39 ty: EntityTypeId,
40 #[builder(default=Uuid::new_v4(), setter(into))]
41 id: Uuid,
42 #[builder(default, setter(strip_option, into))]
43 namespace: Option<String>,
44 #[builder(default, setter(strip_option, into))]
45 name: Option<String>,
46 #[builder(default, setter(into))]
47 description: String,
48 #[builder(default, setter(into))]
49 help: String,
50 #[builder(default, setter(into))]
51 arguments: CommandArgs,
52 // #[builder(setter(into))]
53 executor: Box<CommandExecutor>,
54 // #[builder(setter(into))]
55 // scope: String,
56 // #[builder(setter(into))]
57 // scope: String,
58 // .scope("testing")
59 // .name("concat")
60 // .label("/io/reactive-graph/test/concat")
61 // .help("Concatenates two strings")
62}
63
64impl From<CommandDefinition> for Command {
65 fn from(definition: CommandDefinition) -> Self {
66 let handle_id = Uuid::new_v4().as_u128();
67
68 let namespace = definition.namespace.unwrap_or_else(|| definition.ty.namespace());
69 let name = definition.name.unwrap_or_else(|| definition.ty.type_name());
70
71 let label = format!("/io/reactive-graph/commands/{namespace}/{name}");
72
73 let components = ComponentTypeIds::new()
74 .component(COMPONENT_LABELED.deref())
75 .component(COMPONENT_ACTION.deref())
76 .component(COMPONENT_COMMAND.deref());
77 // components.insert(COMPONENT_ACTION.clone());
78 // components.insert(COMPONENT_COMMAND.clone());
79
80 // let properties = PropertyTypes::new()
81 // .property(PropertyType::string("help"));
82
83 // let label = format!("/io/reactive-graph/commands/{scope}/{name}");
84 // builder.property(COMMAND_NAMESPACE, json!(scope));
85 // builder.property(COMMAND_NAME, json!(name));
86 // builder.component(&COMPONENT_LABELED.clone());
87
88 let properties = PropertyInstances::new()
89 .property(LABEL.property_name(), json!(label))
90 .property(TRIGGER.property_name(), json!(false))
91 .property(COMMAND_NAMESPACE.property_name(), json!(namespace))
92 .property(COMMAND_NAME.property_name(), json!(name))
93 .property(COMMAND_ARGS.property_name(), definition.arguments.to_value())
94 .property(COMMAND_HELP.property_name(), json!(definition.help))
95 .property(COMMAND_RESULT, json!(0));
96
97 for arg in definition.arguments.to_vec() {
98 if !properties.contains_key(&arg.name) {
99 properties.insert(arg.name.clone(), json!(0));
100 }
101 }
102
103 let reactive_entity = ReactiveEntity::builder()
104 .ty(definition.ty)
105 .id(definition.id)
106 .description(definition.description)
107 .components(components)
108 .properties(ReactiveProperties::new_with_id_from_properties(definition.id, properties))
109 .build();
110 let reactive_entity_inner = reactive_entity.clone();
111 let mut executor = definition.executor;
112 if let Some(property_instance) = reactive_entity.properties.get(&TRIGGER.property_name()) {
113 property_instance.stream.read().unwrap().observe_with_handle(
114 move |trigger| {
115 if trigger.as_bool().unwrap_or_default() {
116 // let x = executor(&reactive_entity_inner);
117 reactive_entity_inner.set(COMMAND_RESULT, executor(&reactive_entity_inner));
118 }
119 },
120 handle_id,
121 );
122 };
123 Command::new_unchecked(reactive_entity)
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use crate::Command;
130 use crate::CommandArgs;
131 use reactive_graph_graph::EntityTypeId;
132 use reactive_graph_reactive_model_impl::ReactiveEntity;
133 use serde_json::json;
134
135 #[test]
136 fn command_builder_test() {
137 let args = CommandArgs::new();
138 // TODO: fill args
139 let executor = Box::new(move |_: &ReactiveEntity| json!("abc"));
140 let command = Command::builder()
141 .ty(("core", "num_commands"))
142 .description("The number of commands")
143 .help("Number of commands")
144 .arguments(args)
145 .executor(executor)
146 .build();
147
148 assert_eq!(command.ty(), EntityTypeId::new_from_type("core", "num_commands"));
149 // assert!(command.get
150 }
151}
152
153// pub struct CommandBuilder<S> {
154// ty: Option<EntityTypeId>,
155// builder: Option<ReactiveEntityBuilder>,
156// arguments: CommandArgs,
157// subscriber: Option<Box<CommandExecutor>>,
158// handle_id: Option<u128>,
159// state: PhantomData<S>,
160// }
161//
162// pub mod command_builder_state {
163// pub enum EntityType {}
164// pub enum Scope {}
165// pub enum Name {}
166// pub enum Label {}
167// pub enum Help {}
168// pub enum Components {}
169// pub enum Arguments {}
170// pub enum Properties {}
171// pub enum Executor {}
172// pub enum Finish {}
173// }
174//
175// impl Default for CommandBuilder<command_builder_state::EntityType> {
176// fn default() -> Self {
177// Self::new()
178// }
179// }
180//
181// impl CommandBuilder<command_builder_state::EntityType> {
182// pub fn new() -> CommandBuilder<command_builder_state::EntityType> {
183// Self {
184// ty: None,
185// builder: None,
186// arguments: CommandArgs::new(),
187// subscriber: None,
188// handle_id: None,
189// state: PhantomData,
190// }
191// }
192//
193// pub fn ty(self, ty: &EntityTypeId) -> CommandBuilder<command_builder_state::Scope> {
194// let mut builder = ReactiveEntityBuilder::new(ty.clone());
195// builder.component(&COMPONENT_ACTION.clone());
196// builder.component(&COMPONENT_COMMAND.clone());
197// builder.property(TRIGGER.property_name(), json!(false));
198// builder.property(COMMAND_RESULT, json!(0));
199// CommandBuilder {
200// ty: Some(ty.clone()),
201// builder: Some(builder),
202// arguments: CommandArgs::new(),
203// subscriber: None,
204// handle_id: None,
205// state: PhantomData,
206// }
207// }
208//
209// /// Uses the type information to build a command.
210// /// Useful for entity types with exactly one instance.
211// pub fn singleton(self, ty: &EntityTypeId) -> CommandBuilder<command_builder_state::Help> {
212// let command = ReactiveEntity::builder()
213// .ty(ty)
214// .components(vec![
215// &COMPONENT_ACTION,
216// &COMPONENT_COMMAND
217// ])
218// // .properties(vec![
219// // TRIGGER.property_name(), json!(false)
220// // ])
221// .build();
222//
223// let entity_instance = ReactiveEntity::builder()
224// .ty(ty)
225// .components(
226// Components::new()
227//
228// )
229// .build();
230// let mut builder = ReactiveEntityBuilder::new(ty.clone());
231// builder.component(&COMPONENT_ACTION.clone());
232// builder.component(&COMPONENT_COMMAND.clone());
233// builder.property(TRIGGER.property_name(), json!(false));
234// builder.property(COMMAND_RESULT, json!(0));
235// let scope = ty.namespace();
236// let name = ty.type_name();
237// let label = format!("/io/reactive-graph/commands/{scope}/{name}");
238// builder.property(COMMAND_NAMESPACE, json!(scope));
239// builder.property(COMMAND_NAME, json!(name));
240// builder.component(&COMPONENT_LABELED.clone());
241// builder.property("label", json!(label));
242// CommandBuilder {
243// ty: Some(ty.clone()),
244// builder: Some(builder),
245// arguments: CommandArgs::new(),
246// subscriber: None,
247// handle_id: None,
248// state: PhantomData,
249// }
250// }
251//
252// pub fn singleton_from_type<S1: Into<String>, S2: Into<String>>(self, namespace: S1, type_name: S2) -> CommandBuilder<command_builder_state::Help> {
253// let ty = EntityTypeId::new_from_type(namespace.into(), type_name.into());
254// self.singleton(&ty)
255// }
256// }
257//
258// impl CommandBuilder<command_builder_state::Scope> {
259// pub fn scope<S: Into<String>>(mut self, scope: S) -> CommandBuilder<command_builder_state::Name> {
260// if let Some(builder) = self.builder.as_mut() {
261// builder.property(COMMAND_NAMESPACE, json!(scope.into()));
262// }
263// CommandBuilder {
264// ty: self.ty,
265// builder: self.builder,
266// arguments: self.arguments,
267// subscriber: None,
268// handle_id: None,
269// state: PhantomData,
270// }
271// }
272//
273// pub fn scope_and_name<S1: Into<String>, S2: Into<String>>(mut self, scope: S1, name: S2) -> CommandBuilder<command_builder_state::Help> {
274// if let Some(builder) = self.builder.as_mut() {
275// let scope = scope.into();
276// let name = name.into();
277// let label = format!("/io/reactive-graph/commands/{scope}/{name}");
278// builder.property(COMMAND_NAMESPACE, json!(scope));
279// builder.property(COMMAND_NAME, json!(name));
280// builder.component(&COMPONENT_LABELED.clone());
281// builder.property("label", json!(label));
282// }
283// CommandBuilder {
284// ty: self.ty,
285// builder: self.builder,
286// arguments: self.arguments,
287// subscriber: None,
288// handle_id: None,
289// state: PhantomData,
290// }
291// }
292// }
293//
294// impl CommandBuilder<command_builder_state::Name> {
295// pub fn name<S: Into<String>>(mut self, name: S) -> CommandBuilder<command_builder_state::Label> {
296// if let Some(builder) = self.builder.as_mut() {
297// builder.property(COMMAND_NAME, json!(name.into()));
298// }
299// CommandBuilder {
300// ty: self.ty,
301// builder: self.builder,
302// arguments: self.arguments,
303// subscriber: None,
304// handle_id: None,
305// state: PhantomData,
306// }
307// }
308// }
309//
310// impl CommandBuilder<command_builder_state::Label> {
311// pub fn label<S: Into<String>>(mut self, label: S) -> CommandBuilder<command_builder_state::Help> {
312// if let Some(builder) = self.builder.as_mut() {
313// builder.component(&COMPONENT_LABELED.clone());
314// builder.property("label", json!(label.into()));
315// }
316// CommandBuilder {
317// ty: self.ty,
318// builder: self.builder,
319// arguments: self.arguments,
320// subscriber: None,
321// handle_id: None,
322// state: PhantomData,
323// }
324// }
325//
326// pub fn no_label(self) -> CommandBuilder<command_builder_state::Help> {
327// CommandBuilder {
328// ty: self.ty,
329// builder: self.builder,
330// arguments: self.arguments,
331// subscriber: None,
332// handle_id: None,
333// state: PhantomData,
334// }
335// }
336// }
337//
338// impl CommandBuilder<command_builder_state::Help> {
339// pub fn help<S: Into<String>>(mut self, help: S) -> CommandBuilder<command_builder_state::Components> {
340// if let Some(builder) = self.builder.as_mut() {
341// builder.property(COMMAND_HELP, json!(help.into()));
342// }
343// CommandBuilder {
344// ty: self.ty,
345// builder: self.builder,
346// arguments: self.arguments,
347// subscriber: None,
348// handle_id: None,
349// state: PhantomData,
350// }
351// }
352//
353// pub fn no_help(self) -> CommandBuilder<command_builder_state::Components> {
354// CommandBuilder {
355// ty: self.ty,
356// builder: self.builder,
357// arguments: self.arguments,
358// subscriber: None,
359// handle_id: None,
360// state: PhantomData,
361// }
362// }
363// }
364//
365// impl CommandBuilder<command_builder_state::Components> {
366// pub fn component(mut self, ty: &ComponentTypeId) -> CommandBuilder<command_builder_state::Components> {
367// if let Some(builder) = self.builder.as_mut() {
368// builder.component(ty.clone());
369// }
370// self
371// }
372//
373// pub fn component_from_type<S1: Into<String>, S2: Into<String>>(
374// mut self,
375// namespace: S1,
376// type_name: S2,
377// ) -> CommandBuilder<command_builder_state::Components> {
378// if let Some(builder) = self.builder.as_mut() {
379// let ty = ComponentTypeId::new_from_type(namespace.into(), type_name.into());
380// builder.component(&ty);
381// }
382// self
383// }
384//
385// pub fn arguments(self) -> CommandBuilder<command_builder_state::Arguments> {
386// CommandBuilder {
387// ty: self.ty,
388// builder: self.builder,
389// arguments: self.arguments,
390// subscriber: None,
391// handle_id: None,
392// state: PhantomData,
393// }
394// }
395//
396// pub fn no_arguments(self) -> CommandBuilder<command_builder_state::Executor> {
397// CommandBuilder {
398// ty: self.ty,
399// builder: self.builder,
400// arguments: self.arguments,
401// subscriber: None,
402// handle_id: None,
403// state: PhantomData,
404// }
405// }
406// }
407//
408// impl CommandBuilder<command_builder_state::Arguments> {
409// pub fn argument<A: Into<CommandArg>>(mut self, arg: A, value: Value) -> CommandBuilder<command_builder_state::Arguments> {
410// let arg = arg.into();
411// if let Some(builder) = self.builder.as_mut() {
412// builder.property(arg.name.clone(), value);
413// self.arguments.push(arg);
414// }
415// self
416// }
417//
418// fn create_arguments_property(&mut self) {
419// if let Some(builder) = self.builder.as_mut() {
420// builder.property(COMMAND_ARGS, self.arguments.to_value());
421// }
422// }
423//
424// pub fn properties(mut self) -> CommandBuilder<command_builder_state::Properties> {
425// self.create_arguments_property();
426// CommandBuilder {
427// ty: self.ty,
428// builder: self.builder,
429// arguments: self.arguments,
430// subscriber: None,
431// handle_id: None,
432// state: PhantomData,
433// }
434// }
435//
436// pub fn no_properties(mut self) -> CommandBuilder<command_builder_state::Executor> {
437// self.create_arguments_property();
438// CommandBuilder {
439// ty: self.ty,
440// builder: self.builder,
441// arguments: self.arguments,
442// subscriber: None,
443// handle_id: None,
444// state: PhantomData,
445// }
446// }
447// }
448//
449// impl CommandBuilder<command_builder_state::Properties> {
450// pub fn property<S: Into<String>>(mut self, property_name: S, value: Value) -> CommandBuilder<command_builder_state::Properties> {
451// if let Some(builder) = self.builder.as_mut() {
452// builder.property(property_name.into(), value);
453// }
454// self
455// }
456//
457// pub fn no_more_properties(self) -> CommandBuilder<command_builder_state::Executor> {
458// CommandBuilder {
459// ty: self.ty,
460// builder: self.builder,
461// arguments: self.arguments,
462// subscriber: None,
463// handle_id: None,
464// state: PhantomData,
465// }
466// }
467// }
468//
469// impl CommandBuilder<command_builder_state::Executor> {
470// pub fn executor<F>(self, subscriber: F) -> CommandBuilder<command_builder_state::Finish>
471// where
472// F: FnMut(&ReactiveEntity) -> Value + 'static + Send,
473// {
474// CommandBuilder {
475// ty: self.ty,
476// builder: self.builder,
477// arguments: self.arguments,
478// subscriber: Some(Box::new(subscriber)),
479// handle_id: self.handle_id,
480// state: PhantomData,
481// }
482// }
483//
484// pub fn executor_with_handle<F>(self, subscriber: F, handle_id: Option<u128>) -> CommandBuilder<command_builder_state::Finish>
485// where
486// F: FnMut(&ReactiveEntity) -> Value + 'static + Send,
487// {
488// CommandBuilder {
489// ty: self.ty,
490// builder: self.builder,
491// arguments: self.arguments,
492// subscriber: Some(Box::new(subscriber)),
493// handle_id,
494// state: PhantomData,
495// }
496// }
497// }
498//
499// impl CommandBuilder<command_builder_state::Finish> {
500// pub fn id(mut self, id: Uuid) -> CommandBuilder<command_builder_state::Finish> {
501// if let Some(builder) = self.builder.as_mut() {
502// builder.id(id);
503// };
504// self
505// }
506//
507// pub fn build(self) -> Result<Command, CommandBuilderError> {
508// let Some(builder) = self.builder else {
509// return Err(CommandBuilderError::NotACommand);
510// };
511// let Some(mut subscriber) = self.subscriber else {
512// return Err(CommandBuilderError::MissingExecutor);
513// };
514//
515// let entity_instance = builder.build();
516// let e = entity_instance.clone();
517// let Some(property_instance) = e.properties.get(&TRIGGER.property_name()) else {
518// return Err(CommandBuilderError::MissingTrigger);
519// };
520//
521// let entity_instance_inner = entity_instance.clone();
522// let handle_id = self.handle_id.unwrap_or(Uuid::new_v4().as_u128());
523// property_instance.stream.read().unwrap().observe_with_handle(
524// move |trigger| {
525// if trigger.as_bool().unwrap_or_default() {
526// entity_instance_inner.set(COMMAND_RESULT, subscriber(&entity_instance_inner));
527// }
528// },
529// handle_id,
530// );
531// Command::try_from(entity_instance).map_err(|_| CommandBuilderError::NotACommand)
532// }
533//
534// pub fn build_with_type(self) -> Result<(Command, EntityType), CommandBuilderError> {
535// let Some(builder) = self.builder else {
536// return Err(CommandBuilderError::NotACommand);
537// };
538// let Some(mut subscriber) = self.subscriber else {
539// return Err(CommandBuilderError::MissingExecutor);
540// };
541//
542// let entity_instance = builder.build();
543// let e = entity_instance.clone();
544// let Some(property_instance) = e.properties.get(&TRIGGER.property_name()) else {
545// return Err(CommandBuilderError::MissingTrigger);
546// };
547//
548// let entity_instance_inner = entity_instance.clone();
549// let handle_id = self.handle_id.unwrap_or(Uuid::new_v4().as_u128());
550// property_instance.stream.read().unwrap().observe_with_handle(
551// move |trigger| {
552// if trigger.as_bool().unwrap_or_default() {
553// entity_instance_inner.set(COMMAND_RESULT, subscriber(&entity_instance_inner));
554// }
555// },
556// handle_id,
557// );
558// let entity_type = EntityType::builder()
559// .ty(self.ty.unwrap())
560// .description(entity_instance.as_string(COMMAND_HELP).unwrap_or_default())
561// .components(ComponentTypeIds::from(&entity_instance.components))
562// .properties(self.arguments.to_property_types())
563// .build();
564// Command::try_from(entity_instance)
565// .map_err(|_| CommandBuilderError::NotACommand)
566// .map(|command| (command, entity_type))
567// }
568// }
569//
570// #[cfg(test)]
571// mod tests {
572// use std::collections::HashMap;
573//
574// use serde_json::json;
575//
576// use crate::builder::CommandBuilder;
577// use crate::entity::CommandArg;
578// use reactive_graph_graph::ComponentTypeId;
579// use reactive_graph_graph::EntityTypeId;
580// use reactive_graph_graph::PropertyInstanceGetter;
581// use reactive_graph_reactive_model_api::ReactivePropertyContainer;
582//
583// #[test]
584// fn test_builder() {
585// let command = CommandBuilder::new()
586// .ty(&EntityTypeId::new_from_type("testing", "concat"))
587// .scope("testing")
588// .name("concat")
589// .label("/io/reactive-graph/test/concat")
590// .help("Concatenates two strings")
591// // Additional components
592// .component(&ComponentTypeId::new_from_type("test", "test"))
593// // Arguments
594// .arguments()
595// .argument(
596// CommandArg::new("argument1")
597// .short('a')
598// .long("argument1")
599// .help("The first argument")
600// .required(true),
601// json!(""),
602// )
603// .argument(CommandArg::new("argument2").short('b').long("argument2").help("The second argument"), json!(""))
604// // Additional properties
605// .properties()
606// .property("something", json!(""))
607// .no_more_properties()
608// .executor(|e| {
609// let mut result = String::new();
610// if let Some(argument1) = e.as_string("argument1") {
611// result.push_str(&argument1);
612// }
613// if let Some(argument2) = e.as_string("argument2") {
614// result.push_str(&argument2);
615// }
616// json!(result)
617// })
618// .build()
619// .expect("Failed to create command");
620// assert_eq!("testing", command.namespace().expect("No command namespace"));
621// assert_eq!("concat", command.name().expect("No command name"));
622// assert_eq!("Concatenates two strings", command.help().expect("No help text"));
623//
624// assert!(command.get_instance().has_property("argument1"));
625// assert!(command.get_instance().has_property("argument2"));
626// assert!(command.get_instance().has_property("something"));
627//
628// let args = command.args().expect("No command args");
629// assert_eq!(2, args.len());
630// assert!(args.contains("argument1"));
631// assert!(args.contains("argument2"));
632// assert!(!args.contains("something"));
633//
634// let mut exec_args = HashMap::new();
635// exec_args.insert(String::from("argument1"), json!("Hello, "));
636// exec_args.insert(String::from("argument2"), json!("World"));
637// let command_result = command
638// .execute_with_args(exec_args)
639// .expect("Command execution failed")
640// .expect("No return value")
641// .as_str()
642// .expect("Failed to extract command result string")
643// .to_string();
644// assert_eq!("Hello, World", command_result);
645// }
646//
647// #[test]
648// fn test_builder_scope_and_name() {
649// let command = CommandBuilder::new()
650// .ty(&EntityTypeId::new_from_type("testing", "test"))
651// .scope_and_name("testing", "test")
652// .help("A test command")
653// .no_arguments()
654// .executor(|_| json!(""))
655// .build()
656// .expect("Failed to create command");
657// assert_eq!("testing", command.namespace().expect("No command namespace"));
658// assert_eq!("test", command.name().expect("No command name"));
659// // Automatically generated label
660// assert_eq!("/io/reactive-graph/commands/testing/test", command.label().expect("No label"));
661// assert_eq!("A test command", command.help().expect("No help text"));
662// }
663//
664// #[test]
665// fn test_builder_singleton() {
666// // Singleton Command
667// // command scope = entity type namespace
668// // command name = entity type name
669// let command = CommandBuilder::new()
670// .singleton_from_type("testing", "add")
671// .help("Adds two numbers")
672// .arguments()
673// .argument(CommandArg::new("lhs").short('l').long("lhs").help("The left hand side argument").required(true), json!(0))
674// .argument(
675// CommandArg::new("rhs")
676// .short('r')
677// .long("rhs")
678// .help("The right hand side argument")
679// .required(true),
680// json!(0),
681// )
682// .no_properties()
683// .executor(|e| {
684// let mut result = 0;
685// if let (Some(lhs), Some(rhs)) = (e.as_i64("lhs"), e.as_i64("rhs")) {
686// result = lhs + rhs;
687// }
688// json!(result)
689// })
690// .build()
691// .expect("Failed to create command");
692// assert_eq!("testing", command.namespace().expect("No command namespace"));
693// assert_eq!("add", command.name().expect("No command name"));
694// // Automatically generated label
695// assert_eq!("/io/reactive-graph/commands/testing/add", command.label().expect("No label"));
696// assert_eq!("Adds two numbers", command.help().expect("No help text"));
697// let mut exec_args = HashMap::new();
698// exec_args.insert(String::from("lhs"), json!(1));
699// exec_args.insert(String::from("rhs"), json!(2));
700// let command_result = command
701// .execute_with_args(exec_args)
702// .expect("Command execution failed")
703// .expect("No return value");
704// assert_eq!(3, command_result.as_i64().unwrap());
705// }
706// }