cor_args/lib.rs
1use serde_json::Value;
2use std::borrow::Cow;
3use std::env;
4use std::fs::File;
5use std::io::Read;
6use std::path::PathBuf;
7
8#[cfg(feature = "clap")]
9pub use self::internal_clap::*;
10#[cfg(feature = "config")]
11pub use self::internal_config::*;
12
13/// A trait for handling requests based on a key.
14///
15/// This trait provides a mechanism for handling requests by taking a key and
16/// returning an associated value wrapped in an `Option`.
17pub trait Handler {
18 /// Handles a request based on the provided key.
19 ///
20 /// # Arguments
21 ///
22 /// * `key` - The key associated with the request.
23 ///
24 /// # Returns
25 ///
26 /// An `Option` wrapping a `String` value associated with the key.
27 /// If there's no value associated with the key, it should return `None`.
28 fn handle_request(&self, key: &str) -> Option<String>;
29}
30
31/// A default implementation of the `Handler` trait.
32///
33/// This struct contains a single `value` that will be returned for any request,
34/// regardless of the provided key.
35///
36/// # Examples
37///
38/// ```
39/// use cor_args::{DefaultHandler, Handler};
40///
41/// // Create a new DefaultHandler for a specific value
42/// let handler = DefaultHandler::new("some_value");
43///
44/// // Add a fallback handler
45/// //let handler = handler.next(some_other_handler.into());
46///
47/// // Handle a configuration request
48/// let value = handler.handle_request("some_key");
49/// ```
50pub struct DefaultHandler {
51 value: String,
52}
53
54impl DefaultHandler {
55 /// Creates a new `DefaultHandler` with the specified value.
56 ///
57 /// # Arguments
58 ///
59 /// * `value` - The value to be returned for any request.
60 ///
61 /// # Examples
62 ///
63 /// ```
64 /// use cor_args::DefaultHandler;
65 ///
66 /// let handler = DefaultHandler::new("some_value");
67 /// ```
68 #[allow(dead_code)]
69 pub fn new(value: &str) -> Self {
70 DefaultHandler {
71 value: String::from(value),
72 }
73 }
74}
75
76impl Handler for DefaultHandler {
77 /// Always returns the stored value, regardless of the key.
78 ///
79 /// This implementation ignores the provided key and always returns the
80 /// value stored in the `DefaultHandler`.
81 fn handle_request(&self, _key: &str) -> Option<String> {
82 Some(self.value.clone())
83 }
84}
85
86impl Into<Box<dyn Handler>> for DefaultHandler {
87 fn into(self) -> Box<dyn Handler> {
88 Box::new(self)
89 }
90}
91
92#[cfg(feature = "clap")]
93pub mod internal_clap {
94 use super::*;
95 use clap::ArgMatches;
96 /// A handler for managing command-line arguments.
97 ///
98 /// This struct is responsible for handling command-line arguments passed to the application.
99 /// If a value for a given key is not found in the arguments, it delegates the request to the
100 /// next handler (if provided).
101 ///
102 /// # Examples
103 ///
104 /// ```
105 /// use cor_args::{ArgHandler, Handler};
106 ///
107 /// // Create a simple `clap` command
108 /// let args = clap::Command::new("myapp")
109 /// .arg(clap::Arg::new("example").long("example"))
110 /// .get_matches();
111 ///
112 /// // Create a new ArgHandler for a `clap::ArgMatches`
113 /// let handler = ArgHandler::new(&args);
114 ///
115 /// // Add a fallback handler
116 /// //let handler = handler.next(some_other_handler.into());
117 ///
118 /// // Handle a configuration request matching the `clap::Arg` name
119 /// let value = handler.handle_request("example");
120 /// ```
121 pub struct ArgHandler<'a> {
122 /// Parsed command-line arguments.
123 args: &'a ArgMatches,
124 /// An optional next handler to delegate requests if this handler can't fulfill them.
125 next: Option<Box<dyn Handler>>,
126 }
127
128 impl<'a> ArgHandler<'a> {
129 /// Creates a new `ArgHandler` with the specified arguments.
130 ///
131 /// # Arguments
132 ///
133 /// * `args` - The parsed command-line arguments.
134 ///
135 /// # Examples
136 ///
137 /// ```
138 /// use cor_args::ArgHandler;
139 ///
140 /// let args = clap::Command::new("myapp")
141 /// .arg(clap::Arg::new("config").long("some-option"))
142 /// .get_matches();
143 ///
144 /// let handler = ArgHandler::new(&args);
145 /// ```
146 #[allow(dead_code)]
147 pub fn new(args: &'a ArgMatches) -> Self {
148 ArgHandler { args, next: None }
149 }
150
151 #[allow(dead_code)]
152 pub fn next(mut self, handler: Box<dyn Handler>) -> Self {
153 self.next = Some(handler);
154 self
155 }
156 }
157
158 impl<'a> Handler for ArgHandler<'a> {
159 /// Retrieves a value for the specified key from the command-line arguments.
160 ///
161 /// If the key is not found in the arguments, and if a next handler is provided, it delegates the request
162 /// to the next handler. If there's no next handler or if the key is not found in both the arguments and
163 /// the next handler, it returns `None`.
164 ///
165 /// # Arguments
166 ///
167 /// * `key` - The key for which the value needs to be retrieved.
168 ///
169 /// # Returns
170 ///
171 /// An `Option` containing the value associated with the key, or `None` if the key is not found.
172 fn handle_request(&self, key: &str) -> Option<String> {
173 if let Ok(value) = self.args.try_get_one::<String>(key) {
174 if let Some(value) = value {
175 return Some(value.clone());
176 }
177 }
178 if let Some(next_handler) = &self.next {
179 return next_handler.handle_request(key);
180 }
181 None
182 }
183 }
184
185 impl<'a> Into<Box<dyn Handler + 'a>> for ArgHandler<'a> {
186 fn into(self) -> Box<dyn Handler + 'a> {
187 Box::new(self)
188 }
189 }
190}
191
192/// A handler for retrieving values from environment variables.
193///
194/// This struct is responsible for handling requests by checking for the existence of
195/// an environment variable corresponding to the provided key. If the environment variable
196/// is not found, it delegates the request to the next handler (if provided).
197///
198/// # Examples
199///
200/// ```
201/// use cor_args::{EnvHandler, Handler};
202///
203/// // Create a new EnvHandler specifying a prefix for environment variables
204/// let handler = EnvHandler::new().prefix("MYAPP_");
205///
206/// // Add a fallback handler
207/// //let handler = handler.next(some_other_handler.into());
208///
209/// // Handle a configuration request matching `MYAPP_some_key`
210/// let value = handler.handle_request("some_key");
211/// ```
212pub struct EnvHandler<'a> {
213 /// A prefix to prepend to the key passed to `handle_request()`.
214 prefix: Option<Cow<'a, str>>,
215 /// An optional next handler to delegate requests if this handler can't fulfill them.
216 next: Option<Box<dyn Handler>>,
217}
218
219impl<'a> EnvHandler<'a> {
220 /// Creates a new `EnvHandler`.
221 ///
222 /// # Arguments
223 ///
224 /// * `prefix` - An optional prefix to which requests will prepend when `handle_request()` is executed.` If `None`, an empty string is assigned.
225 ///
226 /// # Examples
227 ///
228 /// ```
229 /// use cor_args::EnvHandler;
230 ///
231 /// let handler = EnvHandler::new();
232 /// ```
233 #[allow(dead_code)]
234 pub fn new() -> Self {
235 EnvHandler {
236 prefix: None,
237 next: None,
238 }
239 }
240
241 #[allow(dead_code)]
242 pub fn next(mut self, handler: Box<dyn Handler>) -> Self {
243 self.next = Some(handler);
244 self
245 }
246
247 #[allow(dead_code)]
248 pub fn prefix<S>(mut self, prefix: S) -> Self
249 where
250 S: Into<Cow<'a, str>>,
251 {
252 self.prefix = Some(prefix.into());
253 self
254 }
255}
256
257impl<'a> Handler for EnvHandler<'a> {
258 /// Retrieves a value for the specified key from the environment variables.
259 ///
260 /// If the environment variable corresponding to the key is not found, and if a next handler is provided,
261 /// it delegates the request to the next handler. If there's no next handler or if the key is not found
262 /// both in the environment and the next handler, it returns `None`.
263 ///
264 /// # Arguments
265 ///
266 /// * `key` - The key for which the value needs to be retrieved from environment variables.
267 ///
268 /// # Returns
269 ///
270 /// An `Option` containing the value associated with the key, or `None` if the key is not found.
271 fn handle_request(&self, key: &str) -> Option<String> {
272 if let Some(prefix) = &self.prefix {
273 let key = format!("{prefix}{key}");
274 if let Ok(value) = env::var(key) {
275 return Some(value);
276 }
277 } else {
278 if let Ok(value) = env::var(key) {
279 return Some(value);
280 }
281 }
282 if let Some(next_handler) = &self.next {
283 return next_handler.handle_request(key);
284 }
285 None
286 }
287}
288
289impl<'a> Into<Box<dyn Handler + 'a>> for EnvHandler<'a> {
290 fn into(self) -> Box<dyn Handler + 'a> {
291 Box::new(self)
292 }
293}
294
295/// A handler for retrieving values from a file.
296///
297/// This struct is responsible for handling requests by checking for values within a specified file.
298///
299/// # Examples
300///
301/// ```
302/// use cor_args::{FileHandler, Handler};
303///
304/// // Create a new FileHandler specifying a path to a file.
305/// let handler = FileHandler::new("/path/to/file");
306///
307/// // Add a fallback handler
308/// //let handler = handler.next(some_other_handler.into());
309///
310/// // Handle a configuration request returning contents of `/path/to/file`
311/// let value = handler.handle_request("");
312/// ```
313pub struct FileHandler {
314 /// Path to the file from which values are to be retrieved.
315 file_path: PathBuf,
316 /// An optional next handler to delegate requests if this handler can't fulfill them.
317 next: Option<Box<dyn Handler>>,
318}
319
320impl FileHandler {
321 /// Creates a new `FileHandler` with the specified file path.
322 ///
323 /// # Arguments
324 ///
325 /// * `file_path` - The path to the file from which values are to be retrieved.
326 ///
327 /// # Examples
328 ///
329 /// ```
330 /// use cor_args::FileHandler;
331 ///
332 /// let handler = FileHandler::new("/path/to/file");
333 /// ```
334 #[allow(dead_code)]
335 pub fn new<P>(file_path: P) -> Self
336 where
337 P: Into<PathBuf>,
338 {
339 FileHandler {
340 file_path: file_path.into(),
341 next: None,
342 }
343 }
344
345 #[allow(dead_code)]
346 pub fn next(mut self, handler: Box<dyn Handler>) -> Self {
347 self.next = Some(handler);
348 self
349 }
350}
351
352impl Handler for FileHandler {
353 /// Retrieves content from the specified file.
354 ///
355 /// This implementation attempts to read content from the file specified by `file_path`.
356 /// If reading fails, and if a next handler is provided, it delegates the request
357 /// to the next handler. If there's no next handler or if the file reading fails,
358 /// it returns `None`.
359 ///
360 /// # Arguments
361 ///
362 /// * `key` - The key for which the value needs to be retrieved. (Note: The `key` is currently not used directly, just passed on to the next handler.)
363 ///
364 /// # Returns
365 ///
366 /// An `Option` containing the contents of the file, or `None` if the key is not found.
367 fn handle_request(&self, key: &str) -> Option<String> {
368 if let Ok(mut file) = File::open(&self.file_path) {
369 let mut content = String::new();
370 if let Ok(_byte_count) = file.read_to_string(&mut content) {
371 return Some(content);
372 }
373 }
374 if let Some(next_handler) = &self.next {
375 return next_handler.handle_request(key);
376 }
377 None
378 }
379}
380
381impl Into<Box<dyn Handler>> for FileHandler {
382 fn into(self) -> Box<dyn Handler> {
383 Box::new(self)
384 }
385}
386
387/// A handler for retrieving values from a specified JSON file.
388///
389/// This struct is responsible for handling requests by reading content from the file
390/// specified in the underlying `FileHandler`, and then searching for a specific key
391/// within the parsed JSON structure. If the key is not found in the JSON structure,
392/// it delegates the request to the next handler (if provided).
393///
394/// ```
395/// use cor_args::{JSONFileHandler, Handler};
396///
397/// // Create a new JSONFileHandler specifying a path to a file.
398/// let handler = JSONFileHandler::new("file.json");
399///
400/// // Add a fallback handler
401/// //let handler = handler.next(some_other_handler.into());
402///
403/// // Handle a configuration request matching a `"some_key"` within `file.json`
404/// let value = handler.handle_request("some_key");
405/// ```
406pub struct JSONFileHandler {
407 /// Underlying file handler used to read content from the specified file.
408 file_handler: FileHandler,
409}
410
411impl JSONFileHandler {
412 /// Creates a new `JSONFileHandler` with the specified file path.
413 ///
414 /// # Arguments
415 ///
416 /// * `file_path` - The path to the JSON file from which values are to be retrieved.
417 ///
418 /// # Examples
419 ///
420 /// ```
421 /// use cor_args::FileHandler;
422 ///
423 /// let handler = FileHandler::new("file.json");
424 /// ```
425 #[allow(dead_code)]
426 pub fn new<P>(file_path: P) -> Self
427 where
428 P: Into<PathBuf>,
429 {
430 JSONFileHandler {
431 file_handler: FileHandler::new(file_path),
432 }
433 }
434
435 #[allow(dead_code)]
436 pub fn next(mut self, handler: Box<dyn Handler>) -> Self {
437 self.file_handler.next = Some(handler);
438 self
439 }
440
441 /// Recursively searches for a key within the parsed JSON structure.
442 ///
443 /// # Arguments
444 ///
445 /// * `json_value` - The current JSON value being inspected.
446 /// * `key` - The key for which the value needs to be retrieved.
447 ///
448 /// # Returns
449 ///
450 /// If found, returns an `Option` wrapping a `String` value associated with the key.
451 /// Otherwise, returns `None`.
452 pub fn find_key_recursive(json_value: &Value, key: &str) -> Option<String> {
453 match json_value {
454 Value::Object(map) => {
455 if let Some(value) = map.get(key) {
456 match value {
457 serde_json::Value::String(value) => {
458 return Some(value.as_str().to_string())
459 }
460 _ => return Some(value.to_string()),
461 }
462 }
463 for (_, value) in map.iter() {
464 if let Some(found) = Self::find_key_recursive(value, key) {
465 return Some(found);
466 }
467 }
468 }
469 Value::Array(arr) => {
470 for value in arr.iter() {
471 if let Some(found) = Self::find_key_recursive(value, key) {
472 return Some(found);
473 }
474 }
475 }
476 _ => {}
477 }
478 None
479 }
480}
481
482impl Handler for JSONFileHandler {
483 /// Retrieves a value for the specified key from the JSON file.
484 ///
485 /// This implementation attempts to read content from the file specified in the underlying `FileHandler`,
486 /// parses the content as JSON, and then searches for the specified key within the parsed JSON structure.
487 /// If the key is not found in the JSON structure, and if a next handler is provided, it delegates the request
488 /// to the next handler. If there's no next handler, or if the key is not found in both the JSON structure
489 /// and the next handler, it returns `None`.
490 ///
491 /// # Arguments
492 ///
493 /// * `key` - The key for which the value needs to be retrieved from the JSON file.
494 ///
495 /// # Returns
496 ///
497 /// An `Option` containing the value associated with the key, or `None` if the key is not found.
498 fn handle_request(&self, key: &str) -> Option<String> {
499 if let Some(file_data) = self.file_handler.handle_request(key) {
500 if let Ok(parsed_json) = serde_json::from_str::<Value>(&file_data) {
501 if let Some(value) = Self::find_key_recursive(&parsed_json, key) {
502 return Some(value);
503 }
504 } else {
505 if let Some(next_handler) = &self.file_handler.next {
506 return next_handler.handle_request(key);
507 }
508 }
509 }
510 None
511 }
512}
513
514impl Into<Box<dyn Handler>> for JSONFileHandler {
515 fn into(self) -> Box<dyn Handler> {
516 Box::new(self)
517 }
518}
519
520#[cfg(feature = "config")]
521pub mod internal_config {
522 use super::*;
523 use config::Config;
524 /// A configuration file handler for reading key-value pairs from a file.
525 ///
526 /// The `ConfigHandler` is used to read configuration data from a file and provide it
527 /// as key-value pairs. It supports chaining multiple handlers for fallback behavior.
528 ///
529 /// # Examples
530 ///
531 /// ```
532 /// use cor_args::{ConfigHandler, Handler};
533 ///
534 /// // Example YAML file
535 /// // ---
536 /// // test_obj:
537 /// // some_key: "test_val"
538 ///
539 /// let config = config::Config::builder().build().unwrap();
540 /// // .add_source(config::File::new("/path/to/file",
541 /// // config::FileFormat::Yaml,
542 /// // ))
543 /// // .build()
544 /// // .unwrap();
545 ///
546 /// // Create a new ConfigHandler for a specific config::Config instance
547 /// let handler = ConfigHandler::new(Box::new(config));
548 ///
549 /// // Add a fallback handler
550 /// //let handler = handler.next(some_other_handler.into());
551 ///
552 /// // Handle a configuration request
553 /// let value = handler.handle_request("some_key");
554 /// ```
555 pub struct ConfigHandler {
556 /// The Config instance ultimately being queried.
557 config: Box<config::Config>,
558 next: Option<Box<dyn Handler>>,
559 }
560
561 impl ConfigHandler {
562 /// Create a new `ConfigHandler` for the specified file path.
563 ///
564 /// # Parameters
565 ///
566 /// - `config`: A `config::Config` reference.
567 ///
568 /// # Returns
569 ///
570 /// A new `ConfigHandler` instance.
571 ///
572 /// # Examples
573 ///
574 /// ```
575 /// use cor_args::ConfigHandler;
576 ///
577 /// let handler = ConfigHandler::new(Box::new(config::Config::builder().build().unwrap()));
578 /// ```
579 #[allow(dead_code)]
580 pub fn new(config: Box<Config>) -> Self {
581 ConfigHandler { config, next: None }
582 }
583
584 #[allow(dead_code)]
585 pub fn next(mut self, handler: Box<dyn Handler>) -> Self {
586 self.next = Some(handler);
587 self
588 }
589
590 /// Recursively searches for a key within the parsed Config structure.
591 ///
592 /// # Arguments
593 ///
594 /// * `config_value` - The current Config value being inspected.
595 /// * `key` - The key for which the value needs to be retrieved.
596 ///
597 /// # Returns
598 ///
599 /// If found, returns an `Option` wrapping a `String` value associated with the key.
600 /// Otherwise, returns `None`.
601 pub fn find_key_recursive(config_value: &config::Value, key: &str) -> Option<String> {
602 match &config_value.kind {
603 config::ValueKind::Table(map) => {
604 if let Some(value) = map.get(key) {
605 match &value.kind {
606 config::ValueKind::String(value) => {
607 return Some(value.as_str().to_string())
608 }
609 _ => return Some(value.to_string()),
610 }
611 }
612 for (_, value) in map.iter() {
613 if let Some(found) = Self::find_key_recursive(value, key) {
614 return Some(found);
615 }
616 }
617 }
618 config::ValueKind::Array(arr) => {
619 for value in arr.iter() {
620 if let Some(found) = Self::find_key_recursive(value, key) {
621 return Some(found);
622 }
623 }
624 }
625 _ => {}
626 }
627 None
628 }
629 }
630
631 impl Handler for ConfigHandler {
632 /// Handle a configuration request and return the value associated with the provided key.
633 ///
634 /// This method attempts to read the configuration file and retrieve the value associated
635 /// with the given key. If the key is not found, it may delegate the request to a fallback
636 /// handler if one is defined.
637 ///
638 /// # Parameters
639 ///
640 /// - `key`: A string representing the configuration key to retrieve.
641 ///
642 /// # Returns
643 ///
644 /// An `Option` containing the value associated with the key, or `None` if the key is not found.
645 fn handle_request(&self, key: &str) -> Option<String> {
646 if let Ok(parsed_config) = self.config.clone().try_deserialize::<config::Value>() {
647 if let Some(value) = Self::find_key_recursive(&parsed_config, key) {
648 return Some(value);
649 }
650 }
651 if let Some(next_handler) = &self.next {
652 return next_handler.handle_request(key);
653 }
654 None
655 }
656 }
657
658 impl Into<Box<dyn Handler>> for ConfigHandler {
659 fn into(self) -> Box<dyn Handler> {
660 Box::new(self)
661 }
662 }
663
664 impl From<Result<config::Config, config::ConfigError>> for ConfigHandler {
665 fn from(value: Result<config::Config, config::ConfigError>) -> Self {
666 if let Ok(config) = value {
667 ConfigHandler::new(Box::new(config))
668 } else {
669 panic!("Failed to convert into a Config")
670 }
671 }
672 }
673
674 impl From<config::Config> for ConfigHandler {
675 fn from(value: config::Config) -> Self {
676 ConfigHandler::new(Box::new(value))
677 }
678 }
679}
680
681#[cfg(test)]
682mod tests {
683 use std::io::Write;
684 use tempfile::NamedTempFile;
685
686 use super::*;
687
688 #[cfg(feature = "clap")]
689 #[test]
690 fn test_clap_features_chain_of_responsibility() {
691 env::set_var("TEST_KEY", "EnvHandler");
692 let args = clap::Command::new("test_app")
693 .arg(clap::Arg::new("example").long("example"))
694 .get_matches_from(vec!["test_app", "--example", "ArgHandler"]);
695 let temp_dir = tempfile::tempdir().unwrap();
696 // Don't create the temporary file so the chain keeps going to the end for this test.
697 let raw_file = temp_dir.path().join("should-not-exist.txt");
698 let mut json_file = NamedTempFile::new().unwrap();
699 writeln!(json_file, r#"{{"test_key": "JSONFileHandler"}}"#).unwrap();
700
701 let handler = ArgHandler::new(&args).next(Box::new(
702 EnvHandler::new().next(Box::new(
703 FileHandler::new(raw_file.as_path().to_str().unwrap())
704 .next(Box::new(JSONFileHandler::new(
705 json_file.path().to_str().unwrap(),
706 )))
707 .next(Box::new(DefaultHandler::new("DefaultHandler"))),
708 )),
709 ));
710 let actual = handler.handle_request("");
711 assert_eq!(actual, Some("DefaultHandler".to_string()));
712 }
713
714 #[test]
715 fn test_default_features_chain_of_responsibility() {
716 env::set_var("TEST_KEY", "EnvHandler");
717 let temp_dir = tempfile::tempdir().unwrap();
718 // Don't create the temporary file so the chain keeps going to the end for this test.
719 let raw_file = temp_dir.path().join("should-not-exist.txt");
720 let mut json_file = NamedTempFile::new().unwrap();
721 writeln!(json_file, r#"{{"test_key": "JSONFileHandler"}}"#).unwrap();
722
723 let handler = EnvHandler::new().next(Box::new(
724 FileHandler::new(raw_file.as_path().to_str().unwrap())
725 .next(Box::new(JSONFileHandler::new(
726 json_file.path().to_str().unwrap(),
727 )))
728 .next(Box::new(DefaultHandler::new("DefaultHandler"))),
729 ));
730 let actual = handler.handle_request("");
731 assert_eq!(actual, Some("DefaultHandler".to_string()));
732 }
733
734 #[cfg(feature = "config")]
735 #[test]
736 fn test_config_features_chain_of_responsibility() {
737 env::set_var("UNUSED", "EnvHandler");
738 let mut temp_file = tempfile::Builder::new().suffix(".yml").tempfile().unwrap();
739 let expected = r#"
740 ---
741 test_obj:
742 test_key: "test_val"
743 "#;
744 writeln!(temp_file, "{}", unindent::unindent(expected)).unwrap();
745
746 let config = config::Config::builder()
747 .add_source(config::File::new(
748 temp_file.path().to_str().unwrap(),
749 config::FileFormat::Yaml,
750 ))
751 .build()
752 .unwrap();
753
754 let handler = EnvHandler::new().next(Box::new(ConfigHandler::new(Box::new(config))));
755 // let handler = EnvHandler::new().next(Box::<ConfigHandler>::new(config.into()));
756 let actual = handler.handle_request("test_key");
757 assert_eq!(actual, Some("test_val".to_string()));
758 }
759
760 mod default_handler {
761 use super::*;
762
763 #[test]
764 fn test_retrieves_set_value() {
765 let handler = DefaultHandler::new("TEST_VAL");
766 let actual = handler.handle_request("");
767 assert_eq!(actual, Some("TEST_VAL".to_string()));
768 }
769 }
770
771 mod env_handler {
772 use super::*;
773
774 #[test]
775 fn test_retrieves_set_value_without_prefix() {
776 env::set_var("TEST_KEY", "test_value");
777 let handler = EnvHandler::new();
778 let actual = handler.handle_request("TEST_KEY");
779 assert_eq!(actual, Some("test_value".to_string()));
780 }
781
782 #[test]
783 fn test_retrieves_set_value_with_prefix() {
784 env::set_var("TEST_KEY", "test_value");
785 let handler = EnvHandler::new().prefix("TEST_");
786 let actual = handler.handle_request("KEY");
787 assert_eq!(actual, Some("test_value".to_string()));
788 }
789
790 #[test]
791 fn test_returns_none_for_unset_value() {
792 env::remove_var("UNSET_KEY"); // Ensure the variable is not set
793 let handler = EnvHandler::new();
794 let actual = handler.handle_request("UNSET_KEY");
795 assert_eq!(actual, None);
796 }
797
798 #[test]
799 fn test_next_handler_called() {
800 env::remove_var("UNSET_KEY"); // Ensure the variable is not set
801 let next_handler = Box::new(DefaultHandler::new("DEFAULT_VALUE"));
802 let handler = EnvHandler::new().next(next_handler);
803 let actual = handler.handle_request("UNSET_KEY");
804 assert_eq!(actual, Some("DEFAULT_VALUE".to_string()));
805 }
806 }
807
808 #[cfg(feature = "clap")]
809 mod arg_handler {
810 use clap::Arg;
811
812 use super::*;
813
814 #[test]
815 fn test_retrieves_set_value() {
816 let args = clap::Command::new("test_app")
817 .arg(Arg::new("example").long("example"))
818 .get_matches_from(vec!["test_app", "--example", "test_value"]);
819
820 let handler = ArgHandler::new(&args);
821 let result = handler.handle_request("example");
822 assert_eq!(result, Some("test_value".to_string()));
823 }
824
825 #[test]
826 fn test_returns_none_for_unset_value() {
827 let args = clap::Command::new("test_app")
828 .arg(Arg::new("example").long("example"))
829 .get_matches_from(vec!["test_app"]);
830
831 let handler = ArgHandler::new(&args);
832 let result = handler.handle_request("example");
833 assert_eq!(result, None);
834 }
835
836 #[test]
837 fn test_next_handler_called() {
838 let args = clap::Command::new("test_app")
839 .arg(Arg::new("example").long("example"))
840 .get_matches_from(vec!["test_app"]);
841 let next_handler = Box::new(DefaultHandler::new("DEFAULT_VALUE"));
842 let handler = ArgHandler::new(&args).next(next_handler);
843 let actual = handler.handle_request("example");
844 assert_eq!(actual, Some("DEFAULT_VALUE".to_string()));
845 }
846 }
847
848 mod file_handler {
849 use std::io::Write;
850 use tempfile::NamedTempFile;
851
852 use super::*;
853
854 #[test]
855 fn test_retrieves_set_value() {
856 let mut temp_file = NamedTempFile::new().unwrap();
857 writeln!(temp_file, "test_content").unwrap();
858
859 let handler = FileHandler::new(temp_file.path().to_str().unwrap());
860 let result = handler.handle_request(""); // key is not used in this handler
861 assert_eq!(result, Some("test_content\n".to_string()));
862 }
863
864 #[test]
865 fn test_returns_none_for_nonexistent_file() {
866 let handler = FileHandler::new("");
867 let result = handler.handle_request("example");
868 assert_eq!(result, None);
869 }
870
871 #[test]
872 fn test_next_handler_called() {
873 let next_handler = Box::new(DefaultHandler::new("DEFAULT_VALUE"));
874 let handler = FileHandler::new("").next(next_handler);
875 let actual = handler.handle_request("example");
876 assert_eq!(actual, Some("DEFAULT_VALUE".to_string()));
877 }
878 }
879
880 mod json_file_handler {
881 use std::io::Write;
882 use tempfile::NamedTempFile;
883
884 use super::*;
885
886 #[test]
887 fn test_retrieves_set_value_number() {
888 let mut temp_file = NamedTempFile::new().unwrap();
889 writeln!(temp_file, r#"{{"test_key": 123}}"#).unwrap();
890
891 let handler = JSONFileHandler::new(temp_file.path().to_str().unwrap());
892 let actual = handler.handle_request("test_key"); // key is not used in this handler
893 assert_eq!(actual, Some("123".to_string()));
894 }
895
896 #[test]
897 fn test_retrieves_set_value_string() {
898 let mut temp_file = NamedTempFile::new().unwrap();
899 writeln!(temp_file, r#"{{"test_key": "example"}}"#).unwrap();
900
901 let handler = JSONFileHandler::new(temp_file.path().to_str().unwrap());
902 let actual = handler.handle_request("test_key"); // key is not used in this handler
903 assert_eq!(actual, Some("example".to_string()));
904 }
905
906 #[test]
907 fn test_retrieves_set_value_nested_object() {
908 let mut temp_file = NamedTempFile::new().unwrap();
909 writeln!(temp_file, r#"{{"test_obj": {{"test_key": "example"}} }}"#).unwrap();
910
911 let handler = JSONFileHandler::new(temp_file.path().to_str().unwrap());
912 let actual = handler.handle_request("test_key"); // key is not used in this handler
913 assert_eq!(actual, Some("example".to_string()));
914 }
915
916 #[test]
917 fn test_retrieves_set_value_in_array() {
918 let mut temp_file = NamedTempFile::new().unwrap();
919 writeln!(temp_file, r#"[{{"test_key": "example"}}]"#).unwrap();
920
921 let handler = JSONFileHandler::new(temp_file.path().to_str().unwrap());
922 let actual = handler.handle_request("test_key"); // key is not used in this handler
923 assert_eq!(actual, Some("example".to_string()));
924 }
925
926 #[test]
927 fn test_returns_none_for_nonexistent_file() {
928 let handler = JSONFileHandler::new("");
929 let result = handler.handle_request("example");
930 assert_eq!(result, None);
931 }
932
933 #[test]
934 fn test_next_handler_called() {
935 let next_handler = Box::new(DefaultHandler::new("DEFAULT_VALUE"));
936 let handler = JSONFileHandler::new("").next(next_handler);
937 let actual = handler.handle_request("example");
938 assert_eq!(actual, Some("DEFAULT_VALUE".to_string()));
939 }
940 }
941
942 #[cfg(feature = "config")]
943 mod config_handler {
944 use config::Config;
945 use std::io::Write;
946 use tempfile::Builder;
947 use unindent::unindent;
948
949 use super::*;
950
951 #[test]
952 fn test_retrieves_set_value_number_as_yaml() {
953 let mut temp_file = Builder::new().suffix(".yaml").tempfile().unwrap();
954 let expected = r#"
955 ---
956 test_key: 123
957 "#;
958 writeln!(temp_file, "{}", unindent(expected)).unwrap();
959 let config = config::Config::builder()
960 .add_source(config::File::new(
961 temp_file.path().to_str().unwrap(),
962 config::FileFormat::Yaml,
963 ))
964 .build()
965 .unwrap();
966
967 let handler = ConfigHandler::new(Box::new(config));
968 let actual = handler.handle_request("test_key");
969 assert_eq!(actual, Some("123".to_string()));
970 }
971
972 #[test]
973 fn test_retrieves_set_value_string_as_yaml() {
974 let mut temp_file = Builder::new().suffix(".yaml").tempfile().unwrap();
975 let expected = r#"
976 ---
977 test_key: "example"
978 "#;
979 writeln!(temp_file, "{}", unindent(expected)).unwrap();
980 let config = config::Config::builder()
981 .add_source(config::File::new(
982 temp_file.path().to_str().unwrap(),
983 config::FileFormat::Yaml,
984 ))
985 .build()
986 .unwrap();
987
988 let handler = ConfigHandler::new(Box::new(config));
989 let actual = handler.handle_request("test_key");
990 assert_eq!(actual, Some("example".to_string()));
991 }
992
993 #[test]
994 fn test_retrieves_set_value_nested_object() {
995 let mut temp_file = Builder::new().suffix(".yaml").tempfile().unwrap();
996 let expected = r#"
997 ---
998 test_obj:
999 test_key: "test_val"
1000 "#;
1001 writeln!(temp_file, "{}", unindent(expected)).unwrap();
1002 let config = config::Config::builder()
1003 .add_source(config::File::new(
1004 temp_file.path().to_str().unwrap(),
1005 config::FileFormat::Yaml,
1006 ))
1007 .build()
1008 .unwrap();
1009
1010 let handler = ConfigHandler::new(Box::new(config));
1011 let actual = handler.handle_request("test_key");
1012 assert_eq!(actual, Some("test_val".to_string()));
1013 }
1014
1015 #[test]
1016 fn test_next_handler_called() {
1017 let config = Config::default();
1018 let next_handler = Box::new(DefaultHandler::new("DEFAULT_VALUE"));
1019 let handler = ConfigHandler::new(Box::new(config)).next(next_handler);
1020 let actual = handler.handle_request("example");
1021 assert_eq!(actual, Some("DEFAULT_VALUE".to_string()));
1022 }
1023 }
1024}