1use std::any::{Any, TypeId};
2use std::borrow::Cow;
3use std::collections::HashMap;
4use std::fmt;
5
6use crate::collector::{InputSourceKind, ResolvedInput};
7
8#[derive(Default)]
9pub struct Inputs {
10 entries: HashMap<Cow<'static, str>, Entry>,
11}
12
13struct Entry {
14 type_id: TypeId,
15 type_name: &'static str,
16 source: InputSourceKind,
17 value: Box<dyn Any + Send + Sync>,
18}
19
20impl Inputs {
21 pub fn new() -> Self {
22 Self {
23 entries: HashMap::new(),
24 }
25 }
26
27 pub fn insert<T>(
28 &mut self,
29 name: impl Into<Cow<'static, str>>,
30 resolved: ResolvedInput<T>,
31 ) -> Option<InputSourceKind>
32 where
33 T: Send + Sync + 'static,
34 {
35 let prev = self.entries.insert(
36 name.into(),
37 Entry {
38 type_id: TypeId::of::<T>(),
39 type_name: std::any::type_name::<T>(),
40 source: resolved.source,
41 value: Box::new(resolved.value),
42 },
43 );
44 prev.map(|e| e.source)
45 }
46
47 pub fn get<T: 'static>(&self, name: &str) -> Option<&T> {
48 let entry = self.entries.get(name)?;
49 if entry.type_id != TypeId::of::<T>() {
50 return None;
51 }
52 entry.value.downcast_ref::<T>()
53 }
54
55 pub fn get_required<T: 'static>(&self, name: &str) -> Result<&T, MissingInput> {
56 let Some(entry) = self.entries.get(name) else {
57 return Err(MissingInput::NotRegistered {
58 name: name.to_string(),
59 });
60 };
61 if entry.type_id != TypeId::of::<T>() {
62 return Err(MissingInput::TypeMismatch {
63 name: name.to_string(),
64 expected: std::any::type_name::<T>(),
65 actual: entry.type_name,
66 });
67 }
68 entry
69 .value
70 .downcast_ref::<T>()
71 .ok_or_else(|| MissingInput::TypeMismatch {
72 name: name.to_string(),
73 expected: std::any::type_name::<T>(),
74 actual: entry.type_name,
75 })
76 }
77
78 pub fn source_of(&self, name: &str) -> Option<InputSourceKind> {
79 self.entries.get(name).map(|e| e.source)
80 }
81
82 pub fn contains(&self, name: &str) -> bool {
83 self.entries.contains_key(name)
84 }
85
86 pub fn len(&self) -> usize {
87 self.entries.len()
88 }
89
90 pub fn is_empty(&self) -> bool {
91 self.entries.is_empty()
92 }
93
94 pub fn iter_sources(&self) -> impl Iterator<Item = (&str, InputSourceKind)> + '_ {
95 self.entries
96 .iter()
97 .map(|(name, entry)| (name.as_ref(), entry.source))
98 }
99}
100
101impl fmt::Debug for Inputs {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 let mut s = f.debug_struct("Inputs");
104 for (name, entry) in &self.entries {
105 s.field(
106 name.as_ref(),
107 &format_args!("{} from {}", entry.type_name, entry.source),
108 );
109 }
110 s.finish()
111 }
112}
113
114#[derive(Debug, thiserror::Error)]
115pub enum MissingInput {
116 #[error("no input named `{name}` was registered for this command")]
117 NotRegistered { name: String },
118 #[error("input `{name}` is registered as `{actual}`, not `{expected}`")]
119 TypeMismatch {
120 name: String,
121 expected: &'static str,
122 actual: &'static str,
123 },
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 fn arg<T>(value: T) -> ResolvedInput<T> {
131 ResolvedInput {
132 value,
133 source: InputSourceKind::Arg,
134 }
135 }
136
137 #[test]
138 fn insert_and_get() {
139 let mut inputs = Inputs::new();
140 inputs.insert("body", arg("hello".to_string()));
141
142 let body: &String = inputs.get("body").unwrap();
143 assert_eq!(body, "hello");
144 }
145
146 #[test]
147 fn get_missing_returns_none() {
148 let inputs = Inputs::new();
149 assert!(inputs.get::<String>("missing").is_none());
150 }
151
152 #[test]
153 fn get_wrong_type_returns_none() {
154 let mut inputs = Inputs::new();
155 inputs.insert("body", arg("hello".to_string()));
156 assert!(inputs.get::<u32>("body").is_none());
157 }
158
159 #[test]
160 fn get_required_reports_missing() {
161 let inputs = Inputs::new();
162 let err = inputs.get_required::<String>("body").unwrap_err();
163 assert!(matches!(err, MissingInput::NotRegistered { .. }));
164 assert!(err.to_string().contains("body"));
165 }
166
167 #[test]
168 fn get_required_reports_type_mismatch() {
169 let mut inputs = Inputs::new();
170 inputs.insert("body", arg("hello".to_string()));
171 let err = inputs.get_required::<u32>("body").unwrap_err();
172 match err {
173 MissingInput::TypeMismatch {
174 ref name,
175 expected,
176 actual,
177 } => {
178 assert_eq!(name, "body");
179 assert!(expected.contains("u32"));
180 assert!(actual.contains("String"));
181 }
182 other => panic!("expected TypeMismatch, got {:?}", other),
183 }
184 }
185
186 #[test]
187 fn accepts_owned_string_name() {
188 let mut inputs = Inputs::new();
189 let runtime_name: String = format!("input_{}", 42);
190 inputs.insert(runtime_name.clone(), arg("x".to_string()));
191
192 assert_eq!(inputs.get::<String>(runtime_name.as_str()).unwrap(), "x");
193 }
194
195 #[test]
196 fn two_inputs_of_same_type_do_not_collide() {
197 let mut inputs = Inputs::new();
198 inputs.insert("body", arg("the body".to_string()));
199 inputs.insert("title", arg("the title".to_string()));
200
201 assert_eq!(inputs.get::<String>("body").unwrap(), "the body");
202 assert_eq!(inputs.get::<String>("title").unwrap(), "the title");
203 }
204
205 #[test]
206 fn insert_returns_previous_source() {
207 let mut inputs = Inputs::new();
208 assert!(inputs.insert("body", arg("first".to_string())).is_none());
209 let prev = inputs.insert(
210 "body",
211 ResolvedInput {
212 value: "second".to_string(),
213 source: InputSourceKind::Stdin,
214 },
215 );
216 assert_eq!(prev, Some(InputSourceKind::Arg));
217 assert_eq!(inputs.source_of("body"), Some(InputSourceKind::Stdin));
218 }
219
220 #[test]
221 fn source_of_and_contains() {
222 let mut inputs = Inputs::new();
223 assert!(!inputs.contains("body"));
224 inputs.insert("body", arg("x".to_string()));
225 assert!(inputs.contains("body"));
226 assert_eq!(inputs.source_of("body"), Some(InputSourceKind::Arg));
227 assert_eq!(inputs.source_of("missing"), None);
228 }
229
230 #[test]
231 fn iter_sources_yields_all_entries() {
232 let mut inputs = Inputs::new();
233 inputs.insert("body", arg("x".to_string()));
234 inputs.insert(
235 "yes",
236 ResolvedInput {
237 value: true,
238 source: InputSourceKind::Flag,
239 },
240 );
241
242 let mut pairs: Vec<_> = inputs.iter_sources().collect();
243 pairs.sort_by_key(|(name, _)| *name);
244 assert_eq!(
245 pairs,
246 vec![
247 ("body", InputSourceKind::Arg),
248 ("yes", InputSourceKind::Flag)
249 ]
250 );
251 }
252
253 #[test]
254 fn len_and_is_empty() {
255 let mut inputs = Inputs::new();
256 assert!(inputs.is_empty());
257 assert_eq!(inputs.len(), 0);
258 inputs.insert("body", arg("x".to_string()));
259 assert!(!inputs.is_empty());
260 assert_eq!(inputs.len(), 1);
261 }
262}