serde_file_value/
lib.rs

1//! A Serde deserializer which transparently loads files as string values.
2//!
3//! # Usage
4//!
5//! Assume we have a `/mnt/secrets/my_secret` file that looks like:
6//!
7//! ```text
8//! hunter2
9//! ```
10//!
11//! And a `conf/config.json` that looks like:
12//!
13//! ```json
14//! {
15//!     "secret_value": "${file:/mnt/secrets/my_secret}"
16//! }
17//! ```
18//! ```no_run
19//! use std::{fs, io, path::Path};
20//!
21//! use serde::Deserialize;
22//!
23//! #[derive(Deserialize)]
24//! struct Config {
25//!     secret_value: String,
26//! }
27//!
28//! let config = fs::read("conf/config.json").unwrap();
29//!
30//! let mut deserializer = serde_json::Deserializer::from_slice(&config);
31//! let config: Config = serde_file_value::deserialize(&mut deserializer, |_, _| ()).unwrap();
32//!
33//! assert_eq!(config.secret_value, "hunter2");
34//! ```
35#![warn(missing_docs)]
36
37use std::{io, path::Path};
38
39pub use de::Deserializer;
40use serde::Deserialize;
41
42mod de;
43
44/// Entry point.
45///
46/// The listener will be called on every referenced file read along with the result of the read.
47///
48/// See crate documentation for an example.
49pub fn deserialize<'de, D, F, T>(deserializer: D, mut listener: F) -> Result<T, D::Error>
50where
51    D: serde::Deserializer<'de>,
52    F: FnMut(&Path, &io::Result<Vec<u8>>),
53    T: Deserialize<'de>,
54{
55    T::deserialize(Deserializer::new(deserializer, &mut listener))
56}
57
58#[cfg(test)]
59mod test {
60    use std::{fs, io, path::Path};
61
62    use serde::Deserialize;
63    use tempfile::NamedTempFile;
64
65    use super::*;
66
67    #[derive(Deserialize, PartialEq, Debug)]
68    struct Config {
69        sub: Subconfig,
70    }
71
72    #[derive(Deserialize, PartialEq, Debug)]
73    struct Subconfig {
74        file: Vec<String>,
75        inline: String,
76    }
77
78    #[test]
79    fn smoke() {
80        let file = NamedTempFile::new().unwrap();
81        fs::write(file.path(), "hunter2").unwrap();
82
83        let config = format!(
84            r#"
85{{
86    "sub": {{
87        "file": [
88            "${{file:{}}}"
89        ],
90        "inline": "${{foobar}}"
91    }}
92}}
93        "#,
94            file.path().display(),
95        );
96
97        let mut deserializer = serde_json::Deserializer::from_str(&config);
98        let mut files = vec![];
99        let mut cb = |path: &Path, r: &io::Result<Vec<u8>>| {
100            files.push((path.to_owned(), r.as_ref().ok().cloned()))
101        };
102        let deserializer = Deserializer::new(&mut deserializer, &mut cb);
103
104        let config = Config::deserialize(deserializer).unwrap();
105
106        let expected = Config {
107            sub: Subconfig {
108                file: vec!["hunter2".to_string()],
109                inline: "${foobar}".to_string(),
110            },
111        };
112
113        assert_eq!(config, expected);
114
115        let expected = vec![(file.path().to_owned(), Some("hunter2".as_bytes().to_vec()))];
116        assert_eq!(files, expected);
117    }
118
119    #[test]
120    fn io_error() {
121        let dir = tempfile::tempdir().unwrap();
122        let file = dir.path().join("bogus");
123
124        let config = format!("\"${{file:{}}}\"", file.display());
125
126        let mut deserializer = serde_json::Deserializer::from_str(&config);
127        let mut files = vec![];
128        let mut cb = |path: &Path, r: &io::Result<Vec<u8>>| {
129            files.push((path.to_owned(), r.as_ref().ok().cloned()))
130        };
131        let deserializer = Deserializer::new(&mut deserializer, &mut cb);
132
133        String::deserialize(deserializer).unwrap_err();
134
135        let expected = vec![(file.to_path_buf(), None)];
136        assert_eq!(files, expected);
137    }
138}