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