@@ -87,8 +87,9 @@ pub(super) async fn stream_response_to_formatted_grpc_stdout(
8787 compression : CompressionMode ,
8888 copy : bool ,
8989 grpc_response_desc : Option < prost_reflect:: MessageDescriptor > ,
90+ use_color : bool ,
9091) -> Result < StreamedOutput , FetchError > {
91- let formatter = FormattedGrpcStream :: new ( & response_headers, grpc_response_desc) ;
92+ let formatter = FormattedGrpcStream :: new ( & response_headers, grpc_response_desc, use_color ) ;
9293 super :: stream:: stream_formatted_response_to_stdout (
9394 response,
9495 response_headers,
@@ -235,6 +236,7 @@ struct FormattedGrpcStream {
235236 decoder : crate :: grpc:: framing:: FrameDecoder ,
236237 grpc_message_encoding : grpc_encoding:: MessageEncoding ,
237238 grpc_response_desc : Option < prost_reflect:: MessageDescriptor > ,
239+ use_color : bool ,
238240 frame_index : usize ,
239241 descriptor_wrote_any : bool ,
240242 descriptor_output_ends_with_newline : bool ,
@@ -244,11 +246,13 @@ impl FormattedGrpcStream {
244246 fn new (
245247 response_headers : & HeaderMap ,
246248 grpc_response_desc : Option < prost_reflect:: MessageDescriptor > ,
249+ use_color : bool ,
247250 ) -> Self {
248251 Self {
249252 decoder : crate :: grpc:: framing:: FrameDecoder :: new ( ) ,
250253 grpc_message_encoding : grpc_encoding:: MessageEncoding :: from_headers ( response_headers) ,
251254 grpc_response_desc,
255+ use_color,
252256 frame_index : 0 ,
253257 descriptor_wrote_any : false ,
254258 descriptor_output_ends_with_newline : true ,
@@ -257,15 +261,18 @@ impl FormattedGrpcStream {
257261
258262 fn format_frame ( & mut self , frame : & crate :: grpc:: framing:: Frame ) -> Result < Vec < u8 > , FetchError > {
259263 if let Some ( desc) = self . grpc_response_desc . as_ref ( ) {
260- let formatted =
261- proto:: format_grpc_frame_with_descriptor ( frame, desc, & self . grpc_message_encoding )
262- . map_err ( |err| FetchError :: Message ( err. to_string ( ) ) ) ?;
264+ let formatted = format_grpc_frame_with_descriptor_json (
265+ frame,
266+ desc,
267+ & self . grpc_message_encoding ,
268+ self . use_color ,
269+ ) ?;
263270 let mut output = Vec :: new ( ) ;
264271 if self . descriptor_wrote_any && !self . descriptor_output_ends_with_newline {
265272 output. push ( b'\n' ) ;
266273 }
267- output . extend_from_slice ( formatted. as_bytes ( ) ) ;
268- self . descriptor_output_ends_with_newline = formatted . ends_with ( '\n' ) ;
274+ self . descriptor_output_ends_with_newline = formatted. ends_with ( b" \n " ) ;
275+ output . extend_from_slice ( & formatted ) ;
269276 self . descriptor_wrote_any = true ;
270277 return Ok ( output) ;
271278 }
@@ -282,6 +289,44 @@ impl FormattedGrpcStream {
282289 }
283290}
284291
292+ fn format_grpc_frame_with_descriptor_json (
293+ frame : & crate :: grpc:: framing:: Frame ,
294+ desc : & prost_reflect:: MessageDescriptor ,
295+ message_encoding : & grpc_encoding:: MessageEncoding ,
296+ use_color : bool ,
297+ ) -> Result < Vec < u8 > , FetchError > {
298+ let formatted = proto:: format_grpc_frame_with_descriptor ( frame, desc, message_encoding)
299+ . map_err ( |err| FetchError :: Message ( err. to_string ( ) ) ) ?;
300+ Ok ( format_printer_bytes ( use_color, |out| {
301+ json:: format_json_to ( formatted. as_bytes ( ) , out)
302+ } )
303+ . unwrap_or_else ( |_| formatted. into_bytes ( ) ) )
304+ }
305+
306+ fn format_grpc_stream_with_descriptor_json (
307+ bytes : & [ u8 ] ,
308+ desc : & prost_reflect:: MessageDescriptor ,
309+ message_encoding : & grpc_encoding:: MessageEncoding ,
310+ use_color : bool ,
311+ ) -> Result < Vec < u8 > , FetchError > {
312+ let frames = crate :: grpc:: framing:: read_frames ( bytes)
313+ . map_err ( |err| FetchError :: Message ( format ! ( "failed to read gRPC stream: {err}" ) ) ) ?;
314+ let mut out = Vec :: new ( ) ;
315+ let mut wrote_any = false ;
316+ let mut output_ends_with_newline = true ;
317+ for frame in & frames {
318+ let formatted =
319+ format_grpc_frame_with_descriptor_json ( frame, desc, message_encoding, use_color) ?;
320+ if wrote_any && !output_ends_with_newline {
321+ out. push ( b'\n' ) ;
322+ }
323+ output_ends_with_newline = formatted. ends_with ( b"\n " ) ;
324+ out. extend_from_slice ( & formatted) ;
325+ wrote_any = true ;
326+ }
327+ Ok ( out)
328+ }
329+
285330impl StdoutStreamFormatter for FormattedGrpcStream {
286331 fn push_chunk ( & mut self , chunk : & [ u8 ] ) -> Result < Vec < Vec < u8 > > , FetchError > {
287332 let frames = self
@@ -481,9 +526,12 @@ pub(super) fn format_stdout_bytes_with_terminal(
481526 ContentType :: Grpc => {
482527 let grpc_message_encoding = grpc_encoding:: MessageEncoding :: from_headers ( headers) ;
483528 if let Some ( desc) = grpc_response_desc {
484- proto:: format_grpc_stream_with_descriptor ( & bytes, & desc, & grpc_message_encoding)
485- . map ( |formatted| formatted. into_bytes ( ) )
486- . map_err ( |err| FetchError :: Message ( err. to_string ( ) ) )
529+ format_grpc_stream_with_descriptor_json (
530+ & bytes,
531+ & desc,
532+ & grpc_message_encoding,
533+ use_color,
534+ )
487535 } else {
488536 grpc_format:: format_grpc_stream ( & bytes, & grpc_message_encoding)
489537 . map ( |formatted| formatted. into_bytes ( ) )
@@ -618,6 +666,20 @@ mod tests {
618666 . output ( )
619667 }
620668
669+ fn test_response_body ( text : & str , count : i64 ) -> Vec < u8 > {
670+ let desc = test_response_descriptor ( ) ;
671+ let mut msg = DynamicMessage :: new ( desc. clone ( ) ) ;
672+ msg. set_field (
673+ & desc. get_field_by_name ( "response_text" ) . unwrap ( ) ,
674+ ReflectValue :: String ( text. to_string ( ) ) ,
675+ ) ;
676+ msg. set_field (
677+ & desc. get_field_by_name ( "count" ) . unwrap ( ) ,
678+ ReflectValue :: I64 ( count) ,
679+ ) ;
680+ msg. encode_to_vec ( )
681+ }
682+
621683 #[ test]
622684 fn image_off_returns_raw_image_bytes ( ) {
623685 let mut headers = HeaderMap :: new ( ) ;
@@ -989,6 +1051,60 @@ mod tests {
9891051 assert ! ( !text. contains( "1:" ) ) ;
9901052 }
9911053
1054+ #[ test]
1055+ fn grpc_descriptor_response_uses_json_color_policy ( ) {
1056+ let mut headers = HeaderMap :: new ( ) ;
1057+ headers. insert (
1058+ CONTENT_TYPE ,
1059+ HeaderValue :: from_static ( "application/grpc+proto" ) ,
1060+ ) ;
1061+ let cli = Cli :: try_parse_from ( [
1062+ "fetch" ,
1063+ "--grpc" ,
1064+ "--format" ,
1065+ "on" ,
1066+ "--color" ,
1067+ "on" ,
1068+ "https://example.com/testpkg.TestService/Get" ,
1069+ ] )
1070+ . unwrap ( ) ;
1071+ let body = crate :: grpc:: framing:: frame ( & test_response_body ( "hello" , 7 ) , false ) . unwrap ( ) ;
1072+
1073+ let out = format_stdout_bytes_with_terminal (
1074+ & cli,
1075+ & headers,
1076+ & body,
1077+ Some ( test_response_descriptor ( ) ) ,
1078+ false ,
1079+ 0 ,
1080+ )
1081+ . unwrap ( ) ;
1082+ let out = String :: from_utf8 ( out. bytes ) . unwrap ( ) ;
1083+
1084+ assert ! ( out. contains( "\x1b [34m\x1b [1mresponse_text\x1b [0m" ) ) ;
1085+ assert ! ( out. contains( "\x1b [32mhello\x1b [0m" ) ) ;
1086+ assert ! ( !out. contains( "1:" ) ) ;
1087+ }
1088+
1089+ #[ test]
1090+ fn streaming_grpc_descriptor_response_uses_json_color_policy ( ) {
1091+ let mut headers = HeaderMap :: new ( ) ;
1092+ headers. insert (
1093+ CONTENT_TYPE ,
1094+ HeaderValue :: from_static ( "application/grpc+proto" ) ,
1095+ ) ;
1096+ let mut formatter =
1097+ FormattedGrpcStream :: new ( & headers, Some ( test_response_descriptor ( ) ) , true ) ;
1098+ let body = crate :: grpc:: framing:: frame ( & test_response_body ( "hello" , 7 ) , false ) . unwrap ( ) ;
1099+
1100+ let chunks = formatter. push_chunk ( & body) . unwrap ( ) ;
1101+ let out = String :: from_utf8 ( chunks. into_iter ( ) . flatten ( ) . collect ( ) ) . unwrap ( ) ;
1102+
1103+ assert ! ( out. contains( "\x1b [34m\x1b [1mresponse_text\x1b [0m" ) ) ;
1104+ assert ! ( out. contains( "\x1b [32mhello\x1b [0m" ) ) ;
1105+ assert ! ( !out. contains( "1:" ) ) ;
1106+ }
1107+
9921108 #[ test]
9931109 fn protobuf_descriptor_decode_failure_falls_back_to_raw_bytes_like_go ( ) {
9941110 let mut headers = HeaderMap :: new ( ) ;
0 commit comments