11use std:: { path:: Path , process:: Command } ;
22
33use error_stack:: { Result , ResultExt } ;
4+ use serde:: Serialize ;
45
56use crate :: {
67 cache:: { Cache , Caching , file:: GlobalCache , noop:: NoopCache } ,
@@ -181,61 +182,163 @@ impl Runner {
181182 Ok ( owners)
182183 }
183184
184- pub fn for_file_optimized ( & self , file_path : & str ) -> RunResult {
185+ pub fn for_file_derived ( & self , file_path : & str , json : bool ) -> RunResult {
185186 let file_owners = match self . owners_for_file ( file_path) {
186187 Ok ( v) => v,
187188 Err ( err) => {
188- return RunResult {
189- io_errors : vec ! [ err. to_string( ) ] ,
190- ..Default :: default ( )
191- } ;
189+ return RunResult :: from_io_error ( Error :: Io ( err. to_string ( ) ) , json) ;
192190 }
193191 } ;
194192
195- let info_messages : Vec < String > = match file_owners. len ( ) {
196- 0 => vec ! [ format! ( "{}" , FileOwner :: default ( ) ) ] ,
197- 1 => vec ! [ format! ( "{}" , file_owners [ 0 ] ) ] ,
198- _ => {
193+ match file_owners. as_slice ( ) {
194+ [ ] => RunResult :: from_file_owner ( & FileOwner :: default ( ) , json ) ,
195+ [ owner ] => RunResult :: from_file_owner ( owner , json ) ,
196+ many => {
199197 let mut error_messages = vec ! [ "Error: file is owned by multiple teams!" . to_string( ) ] ;
200- for file_owner in file_owners {
201- error_messages. push ( format ! ( "\n {}" , file_owner ) ) ;
198+ for owner in many {
199+ error_messages. push ( format ! ( "\n {}" , owner ) ) ;
202200 }
203- return RunResult {
204- validation_errors : error_messages,
205- ..Default :: default ( )
206- } ;
201+ RunResult :: from_validation_errors ( error_messages, json)
207202 }
208- } ;
209- RunResult {
210- info_messages,
211- ..Default :: default ( )
212203 }
213204 }
214205
215- pub fn for_file_codeowners_only ( & self , file_path : & str ) -> RunResult {
206+ pub fn for_file_codeowners_only ( & self , file_path : & str , json : bool ) -> RunResult {
216207 match team_for_file_from_codeowners ( & self . run_config , file_path) {
217208 Ok ( Some ( team) ) => {
218- let relative_team_path = crate :: path_utils:: relative_to ( & self . run_config . project_root , team. path . as_path ( ) )
209+ let team_yml = crate :: path_utils:: relative_to ( & self . run_config . project_root , team. path . as_path ( ) )
219210 . to_string_lossy ( )
220211 . to_string ( ) ;
221- RunResult {
222- info_messages : vec ! [ format!(
223- "Team: {}\n Github Team: {}\n Team YML: {}\n Description:\n - Owner inferred from codeowners file" ,
224- team. name, team. github_team, relative_team_path
225- ) ] ,
226- ..Default :: default ( )
212+ let result = ForFileResult {
213+ team_name : team. name . clone ( ) ,
214+ github_team : team. github_team . clone ( ) ,
215+ team_yml,
216+ description : vec ! [ "Owner inferred from codeowners file" . to_string( ) ] ,
217+ } ;
218+ if json {
219+ RunResult :: json_info ( result)
220+ } else {
221+ RunResult {
222+ info_messages : vec ! [ format!(
223+ "Team: {}\n Github Team: {}\n Team YML: {}\n Description:\n - {}" ,
224+ result. team_name,
225+ result. github_team,
226+ result. team_yml,
227+ result. description. join( "\n - " )
228+ ) ] ,
229+ ..Default :: default ( )
230+ }
231+ }
232+ }
233+ Ok ( None ) => RunResult :: from_file_owner ( & FileOwner :: default ( ) , json) ,
234+ Err ( err) => {
235+ if json {
236+ RunResult :: json_io_error ( Error :: Io ( err. to_string ( ) ) )
237+ } else {
238+ RunResult {
239+ io_errors : vec ! [ err. to_string( ) ] ,
240+ ..Default :: default ( )
241+ }
227242 }
228243 }
229- Ok ( None ) => RunResult :: default ( ) ,
230- Err ( err) => RunResult {
231- io_errors : vec ! [ err. to_string( ) ] ,
232- ..Default :: default ( )
233- } ,
234244 }
235245 }
236246}
237247
238- // removed free functions for for_file_* variants in favor of Runner methods
248+ #[ derive( Debug , Clone , Serialize ) ]
249+ pub struct ForFileResult {
250+ pub team_name : String ,
251+ pub github_team : String ,
252+ pub team_yml : String ,
253+ pub description : Vec < String > ,
254+ }
255+
256+ impl RunResult {
257+ pub fn has_errors ( & self ) -> bool {
258+ !self . validation_errors . is_empty ( ) || !self . io_errors . is_empty ( )
259+ }
260+
261+ fn from_io_error ( error : Error , json : bool ) -> Self {
262+ if json {
263+ Self :: json_io_error ( error)
264+ } else {
265+ Self {
266+ io_errors : vec ! [ error. to_string( ) ] ,
267+ ..Default :: default ( )
268+ }
269+ }
270+ }
271+
272+ fn from_file_owner ( file_owner : & FileOwner , json : bool ) -> Self {
273+ if json {
274+ let description: Vec < String > = if file_owner. sources . is_empty ( ) {
275+ vec ! [ ]
276+ } else {
277+ file_owner. sources . iter ( ) . map ( |source| source. to_string ( ) ) . collect ( )
278+ } ;
279+ Self :: json_info ( ForFileResult {
280+ team_name : file_owner. team . name . clone ( ) ,
281+ github_team : file_owner. team . github_team . clone ( ) ,
282+ team_yml : file_owner. team_config_file_path . clone ( ) ,
283+ description,
284+ } )
285+ } else {
286+ Self {
287+ info_messages : vec ! [ format!( "{}" , file_owner) ] ,
288+ ..Default :: default ( )
289+ }
290+ }
291+ }
292+
293+ fn from_validation_errors ( validation_errors : Vec < String > , json : bool ) -> Self {
294+ if json {
295+ Self :: json_validation_error ( validation_errors)
296+ } else {
297+ Self {
298+ validation_errors,
299+ ..Default :: default ( )
300+ }
301+ }
302+ }
303+
304+ pub fn json_info ( result : ForFileResult ) -> Self {
305+ let json = match serde_json:: to_string_pretty ( & result) {
306+ Ok ( json) => json,
307+ Err ( e) => return Self :: json_io_error ( Error :: Io ( e. to_string ( ) ) ) ,
308+ } ;
309+ Self {
310+ info_messages : vec ! [ json] ,
311+ ..Default :: default ( )
312+ }
313+ }
314+
315+ pub fn json_io_error ( error : Error ) -> Self {
316+ let message = match error {
317+ Error :: Io ( msg) => msg,
318+ Error :: ValidationFailed => "Error::ValidationFailed" . to_string ( ) ,
319+ } ;
320+ let json = match serde_json:: to_string ( & serde_json:: json!( { "error" : message} ) ) . map_err ( |e| Error :: Io ( e. to_string ( ) ) ) {
321+ Ok ( json) => json,
322+ Err ( e) => return Self :: json_io_error ( Error :: Io ( e. to_string ( ) ) ) ,
323+ } ;
324+ Self {
325+ io_errors : vec ! [ json] ,
326+ ..Default :: default ( )
327+ }
328+ }
329+
330+ pub fn json_validation_error ( validation_errors : Vec < String > ) -> Self {
331+ let json_obj = serde_json:: json!( { "validation_errors" : validation_errors} ) ;
332+ let json = match serde_json:: to_string_pretty ( & json_obj) {
333+ Ok ( json) => json,
334+ Err ( e) => return Self :: json_io_error ( Error :: Io ( e. to_string ( ) ) ) ,
335+ } ;
336+ Self {
337+ validation_errors : vec ! [ json] ,
338+ ..Default :: default ( )
339+ }
340+ }
341+ }
239342
240343#[ cfg( test) ]
241344mod tests {
@@ -245,4 +348,36 @@ mod tests {
245348 fn test_version ( ) {
246349 assert_eq ! ( version( ) , env!( "CARGO_PKG_VERSION" ) . to_string( ) ) ;
247350 }
351+ #[ test]
352+ fn test_json_info ( ) {
353+ let result = ForFileResult {
354+ team_name : "team1" . to_string ( ) ,
355+ github_team : "team1" . to_string ( ) ,
356+ team_yml : "config/teams/team1.yml" . to_string ( ) ,
357+ description : vec ! [ "file annotation" . to_string( ) ] ,
358+ } ;
359+ let result = RunResult :: json_info ( result) ;
360+ assert_eq ! ( result. info_messages. len( ) , 1 ) ;
361+ assert_eq ! (
362+ result. info_messages[ 0 ] ,
363+ "{\n \" team_name\" : \" team1\" ,\n \" github_team\" : \" team1\" ,\n \" team_yml\" : \" config/teams/team1.yml\" ,\n \" description\" : [\n \" file annotation\" \n ]\n }"
364+ ) ;
365+ }
366+
367+ #[ test]
368+ fn test_json_io_error ( ) {
369+ let result = RunResult :: json_io_error ( Error :: Io ( "unable to find file" . to_string ( ) ) ) ;
370+ assert_eq ! ( result. io_errors. len( ) , 1 ) ;
371+ assert_eq ! ( result. io_errors[ 0 ] , "{\" error\" :\" unable to find file\" }" ) ;
372+ }
373+
374+ #[ test]
375+ fn test_json_validation_error ( ) {
376+ let result = RunResult :: json_validation_error ( vec ! [ "file has multiple owners" . to_string( ) ] ) ;
377+ assert_eq ! ( result. validation_errors. len( ) , 1 ) ;
378+ assert_eq ! (
379+ result. validation_errors[ 0 ] ,
380+ "{\n \" validation_errors\" : [\n \" file has multiple owners\" \n ]\n }"
381+ ) ;
382+ }
248383}
0 commit comments