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