1#![allow(dead_code)]
3
4use serde::de::DeserializeOwned;
5use serde::{Deserialize, Serialize};
6
7use crate::context::Context;
8use crate::error::IxaError;
9
10pub(crate) trait ExtApi {
11 type Args: DeserializeOwned;
12 type Retval: Serialize;
13
14 fn run(context: &mut Context, args: &Self::Args) -> Result<Self::Retval, IxaError>;
15}
16
17#[derive(Serialize, Deserialize, Debug)]
18pub(crate) struct EmptyArgs {}
19
20pub(crate) fn run_ext_api<T: ExtApi>(
21 context: &mut Context,
22 args: &T::Args,
23) -> Result<T::Retval, IxaError> {
24 T::run(context, args)
25}
26
27pub(crate) mod population {
28 use clap::Parser;
29 use serde::{Deserialize, Serialize};
30
31 use crate::context::Context;
32 use crate::external_api::EmptyArgs;
33 use crate::people::ContextPeopleExt;
34 use crate::IxaError;
35
36 pub(crate) struct Api {}
37 #[derive(Parser, Debug, Deserialize)]
38 pub(crate) enum Args {
39 Population,
41 }
42
43 #[derive(Serialize)]
44 pub(crate) struct Retval {
45 pub population: usize,
46 }
47 impl super::ExtApi for Api {
48 type Args = EmptyArgs;
49 type Retval = Retval;
50
51 fn run(context: &mut Context, _args: &EmptyArgs) -> Result<Retval, IxaError> {
52 Ok(Retval {
53 population: context.get_current_population(),
54 })
55 }
56 }
57}
58
59pub(crate) mod global_properties {
60 use clap::{Parser, Subcommand};
61 use serde::{Deserialize, Serialize};
62
63 use crate::context::Context;
64 use crate::global_properties::ContextGlobalPropertiesExt;
65 use crate::IxaError;
66
67 pub(crate) struct Api {}
68 #[derive(Serialize, Deserialize, Debug)]
69 pub(crate) enum Retval {
70 List(Vec<String>),
71 Value(String),
72 }
73 #[derive(Subcommand, Clone, Debug, Serialize, Deserialize)]
74 pub(crate) enum ArgsEnum {
76 List,
78
79 Get {
81 property: String,
83 },
84 }
85
86 #[derive(Parser, Debug, Serialize, Deserialize)]
87 pub(crate) enum Args {
88 #[command(subcommand)]
89 Global(ArgsEnum),
90 }
91 impl super::ExtApi for Api {
92 type Args = Args;
93 type Retval = Retval;
94
95 fn run(context: &mut Context, args: &Args) -> Result<Retval, IxaError> {
96 let Args::Global(global_args) = args;
97
98 match global_args {
99 ArgsEnum::List => Ok(Retval::List(context.list_registered_global_properties())),
100 ArgsEnum::Get { property: name } => {
101 let output = context.get_serialized_value_by_string(name)?;
102 match output {
103 Some(value) => Ok(Retval::Value(value)),
104 None => Err(IxaError::IxaError(format!("Property {name} is not set"))),
105 }
106 }
107 }
108 }
109 }
110}
111
112pub(crate) mod breakpoint {
113 use clap::{Parser, Subcommand};
114 use serde::{Deserialize, Serialize};
115
116 use crate::context::Context;
117 use crate::debugger::enter_debugger;
118 #[cfg(feature = "web_api")]
119 use crate::web_api::handle_web_api_with_plugin;
120 use crate::{trace, IxaError};
121
122 #[derive(Subcommand, Clone, Debug, Serialize, Deserialize)]
123 pub(crate) enum ArgsEnum {
125 List,
127 Set {
129 #[arg(required = true)]
130 time: f64,
131 #[arg(long, hide = true, default_value_t = true)]
132 console: bool,
133 },
134 #[group(multiple = false, required = true)]
137 Delete {
138 #[arg(value_name = "ID")]
140 id: Option<u32>,
141
142 #[arg(long, action)]
144 all: bool,
145 },
146 Disable,
148 Enable,
150 }
151
152 #[derive(Parser, Debug, Serialize, Deserialize)]
153 pub(crate) enum Args {
154 #[command(subcommand)]
155 Breakpoint(ArgsEnum),
156 }
157
158 #[derive(Serialize, Deserialize, Debug)]
159 pub(crate) enum Retval {
160 List(Vec<String>),
161 Ok,
162 }
163
164 pub(crate) struct Api {}
165 impl super::ExtApi for Api {
166 type Args = Args;
167 type Retval = Retval;
168
169 fn run(context: &mut Context, args: &Args) -> Result<Retval, IxaError> {
170 let Args::Breakpoint(breakpoint_args) = args;
171
172 match breakpoint_args {
173 ArgsEnum::List => {
174 trace!("Listing breakpoints");
175 let list = context.list_breakpoints(0);
176 let list = list
177 .iter()
178 .map(|schedule| {
179 format!(
180 "{}: t={} ({})",
181 schedule.plan_id, schedule.time, schedule.priority
182 )
183 })
184 .collect::<Vec<String>>();
185 Ok(Retval::List(list))
186 }
187
188 #[allow(unused_variables)]
189 ArgsEnum::Set { time, console } => {
190 if *time < context.get_current_time() {
191 return Err(IxaError::from(format!(
192 "Breakpoint time {time} is in the past"
193 )));
194 }
195
196 #[cfg(feature = "web_api")]
197 if *console {
198 context.schedule_debugger(*time, None, Box::new(enter_debugger));
199 } else {
200 context.schedule_debugger(
201 *time,
202 None,
203 Box::new(handle_web_api_with_plugin),
204 );
205 }
206 #[cfg(not(feature = "web_api"))]
207 context.schedule_debugger(*time, None, Box::new(enter_debugger));
208
209 trace!("Breakpoint set at t={time}");
210 Ok(Retval::Ok)
211 }
212
213 ArgsEnum::Delete { id, all } => {
214 if let Some(id) = id {
215 assert!(!all);
216 trace!("Deleting breakpoint {id}");
217 let cancelled = context.delete_breakpoint(u64::from(*id));
218 if cancelled.is_none() {
219 Err(IxaError::from(format!(
220 "Attempted to delete a nonexistent breakpoint {id}",
221 )))
222 } else {
223 Ok(Retval::Ok)
224 }
225 } else {
226 assert!(all);
227 trace!("Deleting all breakpoints");
228 context.clear_breakpoints();
229 Ok(Retval::Ok)
230 }
231 }
232
233 ArgsEnum::Disable => {
234 trace!("Disabling all breakpoints");
235 context.disable_breakpoints();
236 Ok(Retval::Ok)
237 }
238
239 ArgsEnum::Enable => {
240 trace!("Enabling all breakpoints");
241 context.enable_breakpoints();
242 Ok(Retval::Ok)
243 }
244 }
245 }
246 }
247}
248
249pub(crate) mod next {
250 use clap::Parser;
251 use serde::Serialize;
252 use serde_derive::Deserialize;
253
254 use crate::context::Context;
255 use crate::external_api::EmptyArgs;
256 use crate::IxaError;
257
258 #[derive(Parser, Debug, Serialize, Deserialize)]
259 pub enum Args {
260 Next,
262 }
263
264 #[derive(Serialize)]
265 pub(crate) enum Retval {
266 Ok,
267 }
268 #[allow(unused)]
269 pub(crate) struct Api {}
270 impl super::ExtApi for Api {
271 type Args = EmptyArgs;
272 type Retval = Retval;
273
274 fn run(_context: &mut Context, _args: &EmptyArgs) -> Result<Retval, IxaError> {
275 Ok(Retval::Ok)
277 }
278 }
279}
280
281pub(crate) mod halt {
282 use clap::Parser;
283 use serde::Serialize;
284 use serde_derive::Deserialize;
285
286 use crate::context::Context;
287 use crate::external_api::EmptyArgs;
288 use crate::IxaError;
289
290 #[derive(Parser, Debug, Serialize, Deserialize)]
291 pub enum Args {
292 Halt,
294 }
295
296 #[derive(Serialize)]
297 pub(crate) enum Retval {
298 Ok,
299 }
300 #[allow(unused)]
301 pub(crate) struct Api {}
302 impl super::ExtApi for Api {
303 type Args = EmptyArgs;
304 type Retval = Retval;
305
306 fn run(_context: &mut Context, _args: &EmptyArgs) -> Result<Retval, IxaError> {
307 Ok(Retval::Ok)
309 }
310 }
311}
312
313pub(crate) mod r#continue {
314 use clap::Parser;
315 use serde_derive::{Deserialize, Serialize};
316
317 use crate::context::Context;
318 use crate::external_api::EmptyArgs;
319 use crate::IxaError;
320
321 #[derive(Parser, Debug, Serialize, Deserialize)]
322 pub enum Args {
323 Continue,
325 }
326
327 #[derive(Serialize)]
328 pub(crate) enum Retval {
329 Ok,
330 }
331 #[allow(unused)]
332 pub(crate) struct Api {}
333 impl super::ExtApi for Api {
334 type Args = EmptyArgs;
335 type Retval = Retval;
336
337 fn run(_context: &mut Context, _args: &EmptyArgs) -> Result<Retval, IxaError> {
338 Ok(Retval::Ok)
340 }
341 }
342}
343
344pub(crate) mod people {
345 use std::cell::RefCell;
346
347 use clap::{Parser, Subcommand};
348 use serde::{Deserialize, Serialize};
349
350 use crate::people::external_api::ContextPeopleExtCrate;
351 use crate::people::{ContextPeopleExt, PersonId};
352 use crate::{Context, HashMap, HashMapExt, IxaError};
353
354 fn person_id_from_str(s: &str) -> Result<PersonId, String> {
355 match s.parse::<usize>() {
356 Ok(id) => Ok(PersonId(id)),
357 Err(_) => Err("Person id must be an integer".to_string()),
358 }
359 }
360
361 #[derive(Subcommand, Clone, Debug, Serialize, Deserialize)]
362 pub(crate) enum ArgsEnum {
363 Get {
365 #[arg(value_parser = person_id_from_str)]
366 person_id: PersonId,
367 property: String,
368 },
369 Query {
370 #[clap(skip)]
371 properties: Vec<(String, String)>,
372 },
373 Tabulate {
375 properties: Vec<String>,
376 },
377 Properties,
378 }
379
380 #[derive(Parser, Debug, Serialize, Deserialize)]
381 pub(crate) enum Args {
382 #[command(subcommand)]
384 People(ArgsEnum),
385 }
386
387 #[derive(Serialize, Debug, Eq, PartialEq)]
388 pub(crate) enum Retval {
389 Properties(Vec<(String, String)>),
390 Tabulated(Vec<(HashMap<String, String>, usize)>),
391 PropertyNames(Vec<String>),
392 }
393 pub(crate) struct Api {}
394
395 impl super::ExtApi for Api {
396 type Args = Args;
397 type Retval = Retval;
398
399 fn run(context: &mut Context, args: &Args) -> Result<Retval, IxaError> {
400 match args {
401 Args::People(args_enum) => match args_enum {
402 ArgsEnum::Get {
403 person_id,
404 property,
405 } => {
406 if person_id.0 >= context.get_current_population() {
407 return Err(IxaError::IxaError(format!(
408 "No person with id {person_id:?}"
409 )));
410 }
411 let value = context.get_person_property_by_name(property, *person_id)?;
412 Ok(Retval::Properties(vec![(property.clone(), value)]))
413 }
414 ArgsEnum::Query { properties: _ } => Err(IxaError::IxaError(String::from(
415 "People querying not implemented",
416 ))),
417 ArgsEnum::Tabulate { properties } => {
418 let results = RefCell::new(Vec::new());
419
420 context.tabulate_person_properties_by_name(
421 properties.clone(),
422 |_, values, count| {
423 let mut hm = HashMap::new();
424 for (key, value) in properties.iter().zip(values.iter()) {
425 hm.insert(key.clone(), value.clone());
426 }
427 results.borrow_mut().push((hm, count));
428 },
429 )?;
430 Ok(Retval::Tabulated(results.take()))
431 }
432 ArgsEnum::Properties => {
433 Ok(Retval::PropertyNames(context.get_person_property_names()))
434 }
435 },
436 }
437 }
438 }
439
440 #[cfg(test)]
441 mod test {
442 use super::*;
443 use crate::external_api::run_ext_api;
444 use crate::{define_person_property, Context, HashSet, HashSetExt};
445 #[test]
446 fn query_nonexistent_user() {
447 let mut context = Context::new();
448
449 let res = run_ext_api::<super::Api>(
450 &mut context,
451 &Args::People(ArgsEnum::Get {
452 person_id: PersonId(0),
453 property: String::from("abc"),
454 }),
455 );
456
457 println!("{res:?}");
458 assert!(matches!(res, Err(IxaError::IxaError(_))));
459 }
460
461 #[test]
462 fn query_nonexistent_property() {
463 let mut context = Context::new();
464 let _ = context.add_person(());
465 let res = run_ext_api::<super::Api>(
466 &mut context,
467 &Args::People(ArgsEnum::Get {
468 person_id: PersonId(0),
469 property: String::from("abc"),
470 }),
471 );
472
473 println!("{res:?}");
474 assert!(matches!(res, Err(IxaError::IxaError(_))));
475 }
476
477 define_person_property!(Age, u8);
478
479 #[test]
480 fn query_valid_property() {
481 let mut context = Context::new();
482 let _ = context.add_person((Age, 10));
483 let res = run_ext_api::<super::Api>(
484 &mut context,
485 &Args::People(ArgsEnum::Get {
486 person_id: PersonId(0),
487 property: String::from("Age"),
488 }),
489 );
490
491 println!("{res:?}");
492 let res = res.unwrap();
493 #[allow(clippy::match_wildcard_for_single_variants)]
494 match res {
495 Retval::Properties(val) => {
496 assert_eq!(val, vec![(String::from("Age"), String::from("10"))]);
497 }
498 _ => panic!("Unexpected result"),
499 }
500 }
501
502 #[test]
503 fn tabulate() {
504 let mut context = Context::new();
505 let _ = context.add_person((Age, 10));
506 let _ = context.add_person((Age, 20));
507
508 let res = run_ext_api::<super::Api>(
509 &mut context,
510 &Args::People(ArgsEnum::Tabulate {
511 properties: vec![String::from("Age")],
512 }),
513 );
514 println!("{res:?}");
515 let res = res.unwrap();
516 let mut expected = HashSet::new();
517 expected.insert(String::from("10"));
518 expected.insert(String::from("20"));
519
520 #[allow(clippy::match_wildcard_for_single_variants)]
521 match res {
522 Retval::Tabulated(val) => {
523 for (columns, ct) in val {
524 assert_eq!(ct, 1);
525 let age = columns.get("Age").unwrap();
526 assert!(expected.remove(age));
527 }
528 assert_eq!(expected.len(), 0);
529 }
530 _ => panic!("Unexpected result"),
531 }
532 }
533
534 #[test]
535 fn get_person_property_names() {
536 let mut context = Context::new();
537 let _ = context.add_person((Age, 10));
538 let _ = context.add_person((Age, 20));
539
540 let res = run_ext_api::<super::Api>(&mut context, &Args::People(ArgsEnum::Properties));
541 println!("{res:?}");
542 let res = res.unwrap();
543
544 #[allow(clippy::match_wildcard_for_single_variants)]
545 match res {
546 Retval::PropertyNames(names) => assert_eq!(names, vec!["Age"]),
547 _ => panic!("Unexpected result"),
548 }
549 }
550 }
551}
552
553pub(crate) mod time {
554 #![allow(clippy::float_cmp)]
555 use clap::Parser;
556 use serde::{Deserialize, Serialize};
557
558 use crate::context::Context;
559 use crate::external_api::EmptyArgs;
560 use crate::IxaError;
561
562 pub(crate) struct Api {}
563 #[derive(Parser, Debug, Deserialize)]
564 pub(crate) enum Args {
565 Time,
567 }
568
569 #[derive(Serialize)]
570 pub(crate) struct Retval {
571 pub time: f64,
572 }
573 impl super::ExtApi for Api {
574 type Args = super::EmptyArgs;
575 type Retval = Retval;
576
577 fn run(context: &mut Context, _args: &EmptyArgs) -> Result<Retval, IxaError> {
578 Ok(Retval {
579 time: context.get_current_time(),
580 })
581 }
582 }
583
584 #[cfg(test)]
585 mod test {
586 use crate::Context;
587
588 #[test]
589 fn test() {
590 let mut context = Context::new();
591
592 let result = crate::external_api::run_ext_api::<super::Api>(
593 &mut context,
594 &crate::external_api::EmptyArgs {},
595 );
596
597 assert_eq!(result.unwrap().time, 0.0);
598 }
599 }
600}