1717 along with this program. If not, see <http://www.gnu.org/licenses/>.
1818 */
1919
20+ use anyhow:: Context ;
21+ use serde_json:: { Deserializer , Error , Value } ;
2022use std:: collections:: HashMap ;
23+ use std:: fs:: { File , OpenOptions } ;
24+ use std:: io:: BufReader ;
2125use std:: path:: PathBuf ;
2226
23- use serde_json:: { Deserializer , Error , Value } ;
24-
27+ use super :: args;
2528use intercept:: Execution ;
2629
30+ /// Responsible for reading the build events from the intercept mode.
31+ ///
32+ /// The file syntax is defined by the `events` module, and the parsing logic is implemented there.
33+ /// Here we only handle the file opening and the error handling.
34+ pub struct EventFileReader {
35+ reader : BufReader < File > ,
36+ }
37+
38+ impl TryFrom < args:: BuildEvents > for EventFileReader {
39+ type Error = anyhow:: Error ;
40+
41+ /// Open the file and create a new instance of the event file reader.
42+ ///
43+ /// If the file cannot be opened, the error will be logged and escalated.
44+ fn try_from ( value : args:: BuildEvents ) -> Result < Self , Self :: Error > {
45+ let file_name = PathBuf :: from ( value. file_name ) ;
46+ let file = OpenOptions :: new ( )
47+ . read ( true )
48+ . open ( file_name. as_path ( ) )
49+ . with_context ( || format ! ( "Failed to open input file: {:?}" , file_name) ) ?;
50+ let reader = BufReader :: new ( file) ;
51+
52+ Ok ( EventFileReader { reader } )
53+ }
54+ }
55+
56+ impl EventFileReader {
57+ /// Generate the build events from the file.
58+ ///
59+ /// Returns an iterator over the build events. Any error during the reading
60+ /// of the file will be logged and the failed entries will be skipped.
61+ pub fn generate ( self ) -> impl Iterator < Item = Execution > {
62+ // Process the file line by line.
63+ from_reader ( self . reader )
64+ // Log the errors and skip the failed entries.
65+ . flat_map ( |candidate| match candidate {
66+ Ok ( execution) => Some ( execution) ,
67+ Err ( error) => {
68+ log:: warn!( "Failed to read entry from input: {}" , error) ;
69+ None
70+ }
71+ } )
72+ }
73+ }
74+
2775// Based on stream serializer from `serde_json` crate.
2876//
2977// https://docs.rs/serde_json/latest/serde_json/struct.StreamDeserializer.html
30- pub fn from_reader ( reader : impl std:: io:: Read ) -> impl Iterator < Item = Result < Execution , Error > > {
78+ pub fn from_reader ( reader : impl std:: io:: Read ) -> impl Iterator < Item = Result < Execution , Error > > {
3179 Deserializer :: from_reader ( reader)
3280 . into_iter :: < Value > ( )
33- . flat_map ( |value| {
34- match value {
35- Ok ( value) =>
36- into_execution ( value) . map ( Ok ) ,
37- Err ( error) =>
38- Some ( Err ( error) ) ,
39- }
81+ . flat_map ( |value| match value {
82+ Ok ( value) => into_execution ( value) . map ( Ok ) ,
83+ Err ( error) => Some ( Err ( error) ) ,
4084 } )
4185}
4286
4387fn into_execution ( value : Value ) -> Option < Execution > {
44- value. get ( "started" )
88+ value
89+ . get ( "started" )
4590 . and_then ( |started| started. get ( "execution" ) )
4691 . and_then ( |execution| execution. as_object ( ) )
4792 . and_then ( |map| {
48- let executable = map. get ( "executable" )
93+ let executable = map
94+ . get ( "executable" )
4995 . and_then ( Value :: as_str)
5096 . map ( PathBuf :: from) ;
51- let arguments = map. get ( "arguments" )
52- . and_then ( Value :: as_array)
53- . map ( |vs| vs. iter ( )
97+ let arguments = map. get ( "arguments" ) . and_then ( Value :: as_array) . map ( |vs| {
98+ vs. iter ( )
5499 . flat_map ( Value :: as_str)
55100 . map ( str:: to_string)
56101 . collect :: < Vec < String > > ( )
57- ) ;
58- let working_dir = map. get ( "working_dir" )
102+ } ) ;
103+ let working_dir = map
104+ . get ( "working_dir" )
59105 . and_then ( Value :: as_str)
60106 . map ( PathBuf :: from) ;
61- let environment = map. get ( "environment" )
62- . and_then ( Value :: as_object)
63- . map ( |m| m. iter ( )
107+ let environment = map. get ( "environment" ) . and_then ( Value :: as_object) . map ( |m| {
108+ m. iter ( )
64109 . map ( |kv| ( kv. 0 . clone ( ) , kv. 1 . as_str ( ) . unwrap ( ) . to_string ( ) ) )
65110 . collect :: < HashMap < String , String > > ( )
66- ) ;
67-
68- if executable. is_some ( ) && arguments. is_some ( ) && working_dir. is_some ( ) && environment. is_some ( ) {
69- Some (
70- Execution {
71- executable : executable. unwrap ( ) ,
72- arguments : arguments. unwrap ( ) ,
73- working_dir : working_dir. unwrap ( ) ,
74- environment : environment. unwrap ( ) ,
75- }
76- )
111+ } ) ;
112+
113+ if executable. is_some ( )
114+ && arguments. is_some ( )
115+ && working_dir. is_some ( )
116+ && environment. is_some ( )
117+ {
118+ Some ( Execution {
119+ executable : executable. unwrap ( ) ,
120+ arguments : arguments. unwrap ( ) ,
121+ working_dir : working_dir. unwrap ( ) ,
122+ environment : environment. unwrap ( ) ,
123+ } )
77124 } else {
78125 None
79126 }
@@ -82,15 +129,17 @@ fn into_execution(value: Value) -> Option<Execution> {
82129
83130#[ cfg( test) ]
84131mod test {
132+ use crate :: vec_of_strings;
85133 use std:: collections:: HashMap ;
86134 use std:: path:: PathBuf ;
87- use crate :: vec_of_strings;
88135
89136 use super :: * ;
90137
91138 #[ test]
92139 fn test_reading_events ( ) {
93- let content = [ into_single_line ( r#"
140+ let content = [
141+ into_single_line (
142+ r#"
94143 {
95144 "rid": "17014093296157802240",
96145 "started": {
@@ -117,8 +166,10 @@ mod test {
117166 },
118167 "timestamp": "2023-08-08T12:02:12.760865Z"
119168 }
120- "# ) ,
121- into_single_line ( r#"
169+ "# ,
170+ ) ,
171+ into_single_line (
172+ r#"
122173 {
123174 "rid": "8533747834426684686",
124175 "started": {
@@ -143,36 +194,38 @@ mod test {
143194 },
144195 "timestamp": "2023-08-08T12:02:12.771258Z"
145196 }
146- "# ) ,
147- into_single_line ( r#"
197+ "# ,
198+ ) ,
199+ into_single_line (
200+ r#"
148201 {
149202 "rid": "8533747834426684686",
150203 "terminated": {
151204 "status": "0"
152205 },
153206 "timestamp": "2023-08-08T12:02:12.772584Z"
154207 }
155- "# ) ,
156- into_single_line ( r#"
208+ "# ,
209+ ) ,
210+ into_single_line (
211+ r#"
157212 {
158213 "rid": "17014093296157802240",
159214 "terminated": {
160215 "status": "0"
161216 },
162217 "timestamp": "2023-08-08T12:02:12.773568Z"
163218 }
164- "# ) ]
165- . join ( "\n " ) ;
219+ "# ,
220+ ) ,
221+ ]
222+ . join ( "\n " ) ;
166223
167224 let mut result = from_reader ( content. as_bytes ( ) ) ;
168225
169226 let expected = Execution {
170227 executable : PathBuf :: from ( "/usr/bin/sh" ) ,
171- arguments : vec_of_strings ! [
172- "sh" ,
173- "-c" ,
174- "ls"
175- ] ,
228+ arguments : vec_of_strings ! [ "sh" , "-c" , "ls" ] ,
176229 working_dir : PathBuf :: from ( "/var/home/lnagy/Code/Bear.git" ) ,
177230 environment : HashMap :: from ( [
178231 ( "COLORTERM" . to_string ( ) , "truecolor" . to_string ( ) ) ,
0 commit comments