xref: /aosp_15_r20/external/bazelbuild-rules_rust/util/process_wrapper/output.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1 // Copyright 2020 The Bazel Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //    http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use std::error;
16 use std::fmt;
17 use std::io::{self, prelude::*};
18 
19 /// LineOutput tells process_output what to do when a line is processed.
20 /// If a Message is returned, it will be written to write_end, if
21 /// Skip is returned nothing will be printed and execution continues,
22 /// if Terminate is returned, process_output returns immediately.
23 /// Terminate is used to stop processing when we see an emit metadata
24 /// message.
25 #[derive(Debug)]
26 pub(crate) enum LineOutput {
27     Message(String),
28     Skip,
29     Terminate,
30 }
31 
32 #[derive(Debug)]
33 pub(crate) enum ProcessError {
34     IO(io::Error),
35     Process(String),
36 }
37 
38 impl fmt::Display for ProcessError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result39     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40         match self {
41             Self::IO(e) => write!(f, "{}", e),
42             Self::Process(p) => write!(f, "{}", p),
43         }
44     }
45 }
46 
47 impl error::Error for ProcessError {}
48 
49 impl From<io::Error> for ProcessError {
from(err: io::Error) -> Self50     fn from(err: io::Error) -> Self {
51         Self::IO(err)
52     }
53 }
54 
55 impl From<String> for ProcessError {
from(s: String) -> Self56     fn from(s: String) -> Self {
57         Self::Process(s)
58     }
59 }
60 
61 pub(crate) type ProcessResult = Result<(), ProcessError>;
62 
63 /// If this is Err we assume there were issues processing the line.
64 /// We will print the error returned and all following lines without
65 /// any more processing.
66 pub(crate) type LineResult = Result<LineOutput, String>;
67 
68 /// process_output reads lines from read_end and invokes process_line on each.
69 /// Depending on the result of process_line, the modified message may be written
70 /// to write_end.
process_output<F>( read_end: &mut dyn Read, output_write_end: &mut dyn Write, opt_file_write_end: Option<&mut std::fs::File>, mut process_line: F, ) -> ProcessResult where F: FnMut(String) -> LineResult,71 pub(crate) fn process_output<F>(
72     read_end: &mut dyn Read,
73     output_write_end: &mut dyn Write,
74     opt_file_write_end: Option<&mut std::fs::File>,
75     mut process_line: F,
76 ) -> ProcessResult
77 where
78     F: FnMut(String) -> LineResult,
79 {
80     let mut reader = io::BufReader::new(read_end);
81     let mut output_writer = io::LineWriter::new(output_write_end);
82     let mut file_writer = opt_file_write_end.map(io::LineWriter::new);
83     // If there was an error parsing a line failed_on contains the offending line
84     // and the error message.
85     let mut failed_on: Option<(String, String)> = None;
86     loop {
87         let mut line = String::new();
88         let read_bytes = reader.read_line(&mut line)?;
89         if read_bytes == 0 {
90             break;
91         }
92         if let Some(ref mut file) = file_writer {
93             file.write_all(line.as_bytes())?
94         }
95         match process_line(line.clone()) {
96             Ok(LineOutput::Message(to_write)) => output_writer.write_all(to_write.as_bytes())?,
97             Ok(LineOutput::Skip) => {}
98             Ok(LineOutput::Terminate) => return Ok(()),
99             Err(msg) => {
100                 failed_on = Some((line, msg));
101                 break;
102             }
103         };
104     }
105 
106     // If we encountered an error processing a line we want to flush the rest of
107     // reader into writer and return the error.
108     if let Some((line, msg)) = failed_on {
109         output_writer.write_all(line.as_bytes())?;
110         io::copy(&mut reader, &mut output_writer)?;
111         return Err(ProcessError::Process(msg));
112     }
113     Ok(())
114 }
115 
116 #[cfg(test)]
117 mod test {
118     use super::*;
119 
120     #[test]
test_json_parsing_error()121     fn test_json_parsing_error() {
122         let mut input = io::Cursor::new(b"ok text\nsome more\nerror text");
123         let mut output: Vec<u8> = vec![];
124         let result = process_output(&mut input, &mut output, None, move |line| {
125             if line == "ok text\n" {
126                 Ok(LineOutput::Skip)
127             } else {
128                 Err("error parsing output".to_owned())
129             }
130         });
131         assert!(result.is_err());
132         assert_eq!(&output, b"some more\nerror text");
133     }
134 }
135