streamson_lib/handler/
output.rs

1//! Handler which puts output into writeable struct
2
3use super::Handler;
4use crate::{error, path::Path, streamer::Token};
5use std::{any::Any, fs, io, str::FromStr};
6
7/// File handler responsible for storing data to a file.
8pub struct Output<W>
9where
10    W: io::Write,
11{
12    /// writable output
13    output: W,
14
15    /// Indicator whether the path will be displayed
16    /// e.g. `{"items"}: {"sub": 4}` vs `{"sub": 4}`
17    write_path: bool,
18
19    /// String which will be appended to the end of each record
20    /// to separate it with the next record (default '\n')
21    separator: String,
22}
23
24impl FromStr for Output<fs::File> {
25    type Err = error::Handler;
26    fn from_str(input: &str) -> Result<Self, Self::Err> {
27        Ok(Self::new(
28            fs::File::create(input).map_err(error::Handler::new)?,
29        ))
30    }
31}
32
33impl<W> Output<W>
34where
35    W: io::Write,
36{
37    /// Creates new Output handler
38    ///
39    /// # Arguments
40    /// * `output` - structur which implements `io::Write`
41    ///
42    pub fn new(output: W) -> Self {
43        Self {
44            output,
45            write_path: false,
46            separator: "\n".into(),
47        }
48    }
49
50    /// Set whether to show path
51    ///
52    /// # Arguments
53    /// * `use_path` - should path be shown in the output
54    ///
55    /// # Example
56    /// ```
57    /// use std::io::stdout;
58    /// use streamson_lib::handler;
59    /// let output = handler::Output::new(stdout())
60    ///     .set_write_path(true);
61    /// ```
62    pub fn set_write_path(mut self, write_path: bool) -> Self {
63        self.write_path = write_path;
64        self
65    }
66
67    /// Set which separator will be used in the output
68    ///
69    /// Note that every separator will be extended to every found item.
70    ///
71    /// # Arguments
72    /// * `separator` - how found record will be separated
73    ///
74    /// # Example
75    /// ```
76    /// use std::io::stdout;
77    /// use streamson_lib::handler;
78    /// let output = handler::Output::new(stdout())
79    ///     .set_separator("######\n");
80    /// ```
81    pub fn set_separator<S>(mut self, separator: S) -> Self
82    where
83        S: ToString,
84    {
85        self.separator = separator.to_string();
86        self
87    }
88}
89
90impl<W> Handler for Output<W>
91where
92    W: io::Write + Send + 'static,
93{
94    fn start(
95        &mut self,
96        path: &Path,
97        _matcher_idx: usize,
98        _token: Token,
99    ) -> Result<Option<Vec<u8>>, error::Handler> {
100        if self.write_path {
101            self.output
102                .write(format!("{}: ", path).as_bytes())
103                .map_err(|err| error::Handler::new(err.to_string()))?;
104        }
105        Ok(None)
106    }
107
108    fn feed(
109        &mut self,
110        data: &[u8],
111        _matcher_idx: usize,
112    ) -> Result<Option<Vec<u8>>, error::Handler> {
113        self.output
114            .write(data)
115            .map_err(|err| error::Handler::new(err.to_string()))?;
116        Ok(None)
117    }
118
119    fn end(
120        &mut self,
121        _path: &Path,
122        _matcher_idx: usize,
123        _token: Token,
124    ) -> Result<Option<Vec<u8>>, error::Handler> {
125        let separator = self.separator.to_string();
126        self.output
127            .write(separator.as_bytes())
128            .map_err(|err| error::Handler::new(err.to_string()))?;
129        Ok(None)
130    }
131
132    fn as_any(&self) -> &dyn Any {
133        self
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use crate::{
140        handler, matcher,
141        strategy::{self, Strategy},
142    };
143    use std::{
144        fs, str,
145        sync::{Arc, Mutex},
146    };
147    use tempfile::NamedTempFile;
148
149    fn make_output(
150        path: &str,
151        matcher: matcher::Simple,
152        handler: handler::Output<fs::File>,
153        input: &[u8],
154    ) -> String {
155        let handler = Arc::new(Mutex::new(handler));
156        let mut trigger = strategy::Trigger::new();
157        trigger.add_matcher(Box::new(matcher), handler);
158
159        trigger.process(input).unwrap();
160        fs::read_to_string(path).unwrap()
161    }
162
163    #[test]
164    fn basic() {
165        let tmp_path = NamedTempFile::new().unwrap().into_temp_path();
166        let str_path = tmp_path.to_str().unwrap();
167
168        let matcher = matcher::Simple::new(r#"{"aa"}[]"#).unwrap();
169        let file = fs::File::create(str_path).unwrap();
170        let handler = handler::Output::new(file);
171
172        let output = make_output(
173            str_path,
174            matcher,
175            handler,
176            br#"{"aa": [1, 2, "u"], "b": true}"#,
177        );
178
179        assert_eq!(
180            output,
181            str::from_utf8(
182                br#"1
1832
184"u"
185"#
186            )
187            .unwrap()
188        );
189    }
190
191    #[test]
192    fn separator() {
193        let tmp_path = NamedTempFile::new().unwrap().into_temp_path();
194        let str_path = tmp_path.to_str().unwrap();
195
196        let matcher = matcher::Simple::new(r#"{"aa"}[]"#).unwrap();
197        let file = fs::File::create(str_path).unwrap();
198        let handler = handler::Output::new(file).set_separator("XXX");
199
200        let output = make_output(
201            str_path,
202            matcher,
203            handler,
204            br#"{"aa": [1, 2, "u"], "b": true}"#,
205        );
206
207        assert_eq!(output, str::from_utf8(br#"1XXX2XXX"u"XXX"#).unwrap());
208    }
209
210    #[test]
211    fn use_path() {
212        let tmp_path = NamedTempFile::new().unwrap().into_temp_path();
213        let str_path = tmp_path.to_str().unwrap();
214
215        let matcher = matcher::Simple::new(r#"{"aa"}[]"#).unwrap();
216        let file = fs::File::create(str_path).unwrap();
217        let handler = handler::Output::new(file).set_write_path(true);
218
219        let output = make_output(
220            str_path,
221            matcher,
222            handler,
223            br#"{"aa": [1, 2, "u"], "b": true}"#,
224        );
225
226        assert_eq!(
227            output,
228            str::from_utf8(
229                br#"{"aa"}[0]: 1
230{"aa"}[1]: 2
231{"aa"}[2]: "u"
232"#
233            )
234            .unwrap()
235        );
236    }
237}