ad_plugins_rs/attr_plot_args.rs
1//! `NDAttrPlotConfig`'s positional-argument mapping, kept apart from the
2//! command that installs it.
3//!
4//! The mapping is the parity-critical half of that command and it is pure:
5//! `&[ArgValue]` in, six fields out, no port manager, no IOC, no reactor. It
6//! lives here rather than in `crate::ioc` because that module is gated on
7//! `tokio_backend` — it stands an IOC up on `epics_ca_rs::server::ioc_app`,
8//! which the reactor-free backend does not have — and a gate sized for the
9//! `IocApplication` type surface took this parsing and its three boundary
10//! cases down with it.
11//!
12//! The predicate here is `not(epics_embedded_target)` instead, which is what
13//! [`ArgValue`] itself is gated on: the iocsh registry is host-and-VxWorks-
14//! and-RTEMS-absent, not reactor-dependent, so a host `exec_backend` build
15//! keeps both it and this.
16//!
17//! `crate::ioc` is named in a code span rather than linked for the same
18//! reason this module exists: it is not there to link to in the very
19//! configuration this paragraph is explaining.
20
21use epics_base_rs::server::iocsh::registry::ArgValue;
22
23/// Parsed `NDAttrPlotConfig` arguments.
24pub struct AttrPlotArgs {
25 pub port_name: String,
26 pub n_attributes: usize,
27 pub cache_size: usize,
28 pub n_data_blocks: usize,
29 pub in_port: String,
30 pub queue_size: usize,
31}
32
33/// Parse `NDAttrPlotConfig` positional args in C order
34/// (`NDPluginAttrPlot.cpp:308`): `port, n_attributes, cache_size,
35/// n_selected_blocks, in_port, in_addr, queue_size, ...`.
36///
37/// A present integer is honoured exactly — including an explicit `0`, which is
38/// meaningful for `cache_size` (`0` = unlimited per-buffer cache). Fallbacks
39/// apply only when an arg is absent; a real st.cmd always passes them, so the
40/// fallbacks only affect malformed calls.
41pub fn parse_attr_plot_args(args: &[ArgValue]) -> Result<AttrPlotArgs, String> {
42 let port_name = match args.first() {
43 Some(ArgValue::String(s)) if !s.is_empty() => s.clone(),
44 _ => return Err("NDAttrPlotConfig: portName required".into()),
45 };
46 let usize_arg = |i: usize, default: usize| match args.get(i) {
47 Some(ArgValue::Int(n)) => (*n).max(0) as usize,
48 _ => default,
49 };
50 let in_port = match args.get(4) {
51 Some(ArgValue::String(s)) => s.clone(),
52 _ => String::new(),
53 };
54 Ok(AttrPlotArgs {
55 port_name,
56 n_attributes: usize_arg(1, 8),
57 cache_size: usize_arg(2, 1000),
58 n_data_blocks: usize_arg(3, 4),
59 in_port,
60 queue_size: usize_arg(6, 20),
61 })
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn parse_attr_plot_args_maps_c_positional_order() {
70 // C NDAttrPlotConfig(port, n_attributes, cache_size, n_selected_blocks,
71 // in_port, in_addr, queue_size, ...) — NDPluginAttrPlot.cpp:308. The
72 // distinct order (n_attributes/cache/blocks before in_port, queue at
73 // index 6) is the parity-critical mapping this guards.
74 let args = vec![
75 ArgValue::String("AP1".to_string()),
76 ArgValue::Int(10), // n_attributes
77 ArgValue::Int(500), // cache_size
78 ArgValue::Int(3), // n_selected_blocks
79 ArgValue::String("DET1".to_string()), // in_port
80 ArgValue::Int(0), // in_addr
81 ArgValue::Int(50), // queue_size
82 ArgValue::Int(0), // blocking_callbacks
83 ];
84 let p = parse_attr_plot_args(&args).unwrap();
85 assert_eq!(p.port_name, "AP1");
86 assert_eq!(p.n_attributes, 10);
87 assert_eq!(p.cache_size, 500);
88 assert_eq!(p.n_data_blocks, 3);
89 assert_eq!(p.in_port, "DET1");
90 assert_eq!(p.queue_size, 50);
91 }
92
93 #[test]
94 fn parse_attr_plot_args_requires_port_name() {
95 assert!(parse_attr_plot_args(&[]).is_err());
96 assert!(parse_attr_plot_args(&[ArgValue::String(String::new())]).is_err());
97 assert!(parse_attr_plot_args(&[ArgValue::Int(1)]).is_err());
98 }
99
100 #[test]
101 fn parse_attr_plot_args_honours_explicit_zero_and_defaults_absent() {
102 // Boundary: an explicit cache_size=0 is meaningful (unlimited) and must be
103 // honoured; absent n_attributes/n_data_blocks/queue_size fall back.
104 let args = vec![
105 ArgValue::String("AP2".to_string()),
106 ArgValue::Missing, // n_attributes absent
107 ArgValue::Int(0), // cache_size = unlimited (explicit 0, not a fallback)
108 ];
109 let p = parse_attr_plot_args(&args).unwrap();
110 assert_eq!(p.n_attributes, 8, "absent n_attributes -> fallback");
111 assert_eq!(
112 p.cache_size, 0,
113 "explicit 0 cache_size honoured (unlimited)"
114 );
115 assert_eq!(p.n_data_blocks, 4, "absent n_data_blocks -> fallback");
116 assert_eq!(p.in_port, "", "absent in_port -> empty");
117 assert_eq!(p.queue_size, 20, "absent queue_size -> fallback");
118 }
119}