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