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::enter_web_debugger;
113 use crate::{info, 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 ArgsEnum::Set { time, console: _ } => {
184 if *time < context.get_current_time() {
185 return Err(IxaError::from(format!(
186 "Breakpoint time {time} is in the past"
187 )));
188 }
189
190 #[cfg(feature = "web_api")]
191 if *console {
192 context.schedule_debugger(*time, None, Box::new(enter_debugger));
193 } else {
194 context.schedule_debugger(*time, None, Box::new(enter_web_debugger));
195 }
196 #[cfg(not(feature = "web_api"))]
197 context.schedule_debugger(*time, None, Box::new(enter_debugger));
198
199 info!("Breakpoint set at t={time}");
200 Ok(Retval::Ok)
201 }
202
203 ArgsEnum::Delete { id, all } => {
204 if let Some(id) = id {
205 assert!(!all);
206 trace!("Deleting breakpoint {id}");
207 let cancelled = context.delete_breakpoint(u64::from(*id));
208 if cancelled.is_none() {
209 Err(IxaError::from(format!(
210 "Attempted to delete a nonexistent breakpoint {id}",
211 )))
212 } else {
213 Ok(Retval::Ok)
214 }
215 } else {
216 assert!(all);
217 trace!("Deleting all breakpoints");
218 context.clear_breakpoints();
219 Ok(Retval::Ok)
220 }
221 }
222
223 ArgsEnum::Disable => {
224 trace!("Disabling all breakpoints");
225 context.disable_breakpoints();
226 Ok(Retval::Ok)
227 }
228
229 ArgsEnum::Enable => {
230 trace!("Enabling all breakpoints");
231 context.enable_breakpoints();
232 Ok(Retval::Ok)
233 }
234 }
235 }
236 }
237}
238
239pub(crate) mod next {
240 use crate::context::Context;
241 use crate::external_api::EmptyArgs;
242 use crate::IxaError;
243 use clap::Parser;
244 use serde::Serialize;
245 use serde_derive::Deserialize;
246
247 #[derive(Parser, Debug, Serialize, Deserialize)]
248 pub enum Args {
249 Next,
251 }
252
253 #[derive(Serialize)]
254 pub(crate) enum Retval {
255 Ok,
256 }
257 #[allow(unused)]
258 pub(crate) struct Api {}
259 impl super::ExtApi for Api {
260 type Args = EmptyArgs;
261 type Retval = Retval;
262
263 fn run(_context: &mut Context, _args: &EmptyArgs) -> Result<Retval, IxaError> {
264 Ok(Retval::Ok)
266 }
267 }
268}
269
270pub(crate) mod halt {
271 use crate::context::Context;
272 use crate::external_api::EmptyArgs;
273 use crate::IxaError;
274 use clap::Parser;
275 use serde::Serialize;
276 use serde_derive::Deserialize;
277
278 #[derive(Parser, Debug, Serialize, Deserialize)]
279 pub enum Args {
280 Halt,
282 }
283
284 #[derive(Serialize)]
285 pub(crate) enum Retval {
286 Ok,
287 }
288 #[allow(unused)]
289 pub(crate) struct Api {}
290 impl super::ExtApi for Api {
291 type Args = EmptyArgs;
292 type Retval = Retval;
293
294 fn run(_context: &mut Context, _args: &EmptyArgs) -> Result<Retval, IxaError> {
295 Ok(Retval::Ok)
297 }
298 }
299}
300
301pub(crate) mod r#continue {
302 use crate::context::Context;
303 use crate::external_api::EmptyArgs;
304 use crate::IxaError;
305 use clap::Parser;
306 use serde_derive::{Deserialize, Serialize};
307
308 #[derive(Parser, Debug, Serialize, Deserialize)]
309 pub enum Args {
310 Continue,
312 }
313
314 #[derive(Serialize)]
315 pub(crate) enum Retval {
316 Ok,
317 }
318 #[allow(unused)]
319 pub(crate) struct Api {}
320 impl super::ExtApi for Api {
321 type Args = EmptyArgs;
322 type Retval = Retval;
323
324 fn run(_context: &mut Context, _args: &EmptyArgs) -> Result<Retval, IxaError> {
325 Ok(Retval::Ok)
327 }
328 }
329}
330
331pub(crate) mod people {
332 use crate::{HashMap, HashMapExt};
333 use std::cell::RefCell;
334
335 use crate::people::{external_api::ContextPeopleExtCrate, ContextPeopleExt, PersonId};
336 use crate::Context;
337 use crate::IxaError;
338 use clap::{Parser, Subcommand};
339 use serde::{Deserialize, Serialize};
340
341 fn person_id_from_str(s: &str) -> Result<PersonId, String> {
342 match s.parse::<usize>() {
343 Ok(id) => Ok(PersonId(id)),
344 Err(_) => Err("Person id must be an integer".to_string()),
345 }
346 }
347
348 #[derive(Subcommand, Clone, Debug, Serialize, Deserialize)]
349 pub(crate) enum ArgsEnum {
350 Get {
352 #[arg(value_parser = person_id_from_str)]
353 person_id: PersonId,
354 property: String,
355 },
356 Query {
357 #[clap(skip)]
358 properties: Vec<(String, String)>,
359 },
360 Tabulate {
362 properties: Vec<String>,
363 },
364 Properties,
365 }
366
367 #[derive(Parser, Debug, Serialize, Deserialize)]
368 pub(crate) enum Args {
369 #[command(subcommand)]
371 People(ArgsEnum),
372 }
373
374 #[derive(Serialize, Debug, Eq, PartialEq)]
375 pub(crate) enum Retval {
376 Properties(Vec<(String, String)>),
377 Tabulated(Vec<(HashMap<String, String>, usize)>),
378 PropertyNames(Vec<String>),
379 }
380 pub(crate) struct Api {}
381
382 impl super::ExtApi for Api {
383 type Args = Args;
384 type Retval = Retval;
385
386 fn run(context: &mut Context, args: &Args) -> Result<Retval, IxaError> {
387 match args {
388 Args::People(args_enum) => match args_enum {
389 ArgsEnum::Get {
390 person_id,
391 property,
392 } => {
393 if person_id.0 >= context.get_current_population() {
394 return Err(IxaError::IxaError(format!(
395 "No person with id {person_id:?}"
396 )));
397 }
398 let value = context.get_person_property_by_name(property, *person_id)?;
399 Ok(Retval::Properties(vec![(property.clone(), value)]))
400 }
401 ArgsEnum::Query { properties: _ } => Err(IxaError::IxaError(String::from(
402 "People querying not implemented",
403 ))),
404 ArgsEnum::Tabulate { properties } => {
405 let results = RefCell::new(Vec::new());
406
407 context.tabulate_person_properties_by_name(
408 properties.clone(),
409 |_, values, count| {
410 let mut hm = HashMap::new();
411 for (key, value) in properties.iter().zip(values.iter()) {
412 hm.insert(key.clone(), value.clone());
413 }
414 results.borrow_mut().push((hm, count));
415 },
416 )?;
417 Ok(Retval::Tabulated(results.take()))
418 }
419 ArgsEnum::Properties => {
420 Ok(Retval::PropertyNames(context.get_person_property_names()))
421 }
422 },
423 }
424 }
425 }
426
427 #[cfg(test)]
428 mod test {
429 use super::*;
430 use crate::external_api::run_ext_api;
431 use crate::{define_person_property, Context};
432 use crate::{HashSet, HashSetExt};
433 #[test]
434 fn query_nonexistent_user() {
435 let mut context = Context::new();
436
437 let res = run_ext_api::<super::Api>(
438 &mut context,
439 &Args::People(ArgsEnum::Get {
440 person_id: PersonId(0),
441 property: String::from("abc"),
442 }),
443 );
444
445 println!("{res:?}");
446 assert!(matches!(res, Err(IxaError::IxaError(_))));
447 }
448
449 #[test]
450 fn query_nonexistent_property() {
451 let mut context = Context::new();
452 let _ = context.add_person(());
453 let res = run_ext_api::<super::Api>(
454 &mut context,
455 &Args::People(ArgsEnum::Get {
456 person_id: PersonId(0),
457 property: String::from("abc"),
458 }),
459 );
460
461 println!("{res:?}");
462 assert!(matches!(res, Err(IxaError::IxaError(_))));
463 }
464
465 define_person_property!(Age, u8);
466
467 #[test]
468 fn query_valid_property() {
469 let mut context = Context::new();
470 let _ = context.add_person((Age, 10));
471 let res = run_ext_api::<super::Api>(
472 &mut context,
473 &Args::People(ArgsEnum::Get {
474 person_id: PersonId(0),
475 property: String::from("Age"),
476 }),
477 );
478
479 println!("{res:?}");
480 let res = res.unwrap();
481 #[allow(clippy::match_wildcard_for_single_variants)]
482 match res {
483 Retval::Properties(val) => {
484 assert_eq!(val, vec![(String::from("Age"), String::from("10"))]);
485 }
486 _ => panic!("Unexpected result"),
487 }
488 }
489
490 #[test]
491 fn tabulate() {
492 let mut context = Context::new();
493 let _ = context.add_person((Age, 10));
494 let _ = context.add_person((Age, 20));
495
496 let res = run_ext_api::<super::Api>(
497 &mut context,
498 &Args::People(ArgsEnum::Tabulate {
499 properties: vec![String::from("Age")],
500 }),
501 );
502 println!("{res:?}");
503 let res = res.unwrap();
504 let mut expected = HashSet::new();
505 expected.insert(String::from("10"));
506 expected.insert(String::from("20"));
507
508 #[allow(clippy::match_wildcard_for_single_variants)]
509 match res {
510 Retval::Tabulated(val) => {
511 for (columns, ct) in val {
512 assert_eq!(ct, 1);
513 let age = columns.get("Age").unwrap();
514 assert!(expected.remove(age));
515 }
516 assert_eq!(expected.len(), 0);
517 }
518 _ => panic!("Unexpected result"),
519 }
520 }
521
522 #[test]
523 fn get_person_property_names() {
524 let mut context = Context::new();
525 let _ = context.add_person((Age, 10));
526 let _ = context.add_person((Age, 20));
527
528 let res = run_ext_api::<super::Api>(&mut context, &Args::People(ArgsEnum::Properties));
529 println!("{res:?}");
530 let res = res.unwrap();
531
532 #[allow(clippy::match_wildcard_for_single_variants)]
533 match res {
534 Retval::PropertyNames(names) => assert_eq!(names, vec!["Age"]),
535 _ => panic!("Unexpected result"),
536 }
537 }
538 }
539}
540
541pub(crate) mod time {
542 #![allow(clippy::float_cmp)]
543 use crate::context::Context;
544 use crate::external_api::EmptyArgs;
545 use crate::IxaError;
546 use clap::Parser;
547 use serde::{Deserialize, Serialize};
548
549 pub(crate) struct Api {}
550 #[derive(Parser, Debug, Deserialize)]
551 pub(crate) enum Args {
552 Time,
554 }
555
556 #[derive(Serialize)]
557 pub(crate) struct Retval {
558 pub time: f64,
559 }
560 impl super::ExtApi for Api {
561 type Args = super::EmptyArgs;
562 type Retval = Retval;
563
564 fn run(context: &mut Context, _args: &EmptyArgs) -> Result<Retval, IxaError> {
565 Ok(Retval {
566 time: context.get_current_time(),
567 })
568 }
569 }
570
571 #[cfg(test)]
572 mod test {
573 use crate::Context;
574
575 #[test]
576 fn test() {
577 let mut context = Context::new();
578
579 let result = crate::external_api::run_ext_api::<super::Api>(
580 &mut context,
581 &crate::external_api::EmptyArgs {},
582 );
583
584 assert_eq!(result.unwrap().time, 0.0);
585 }
586 }
587}