@@ -178,8 +178,17 @@ type (
178178 RequestData struct {
179179 // Description is the request description.
180180 Description string
181- // Message is the gRPC request message.
181+ // Message is the gRPC request message used by the transport. For
182+ // streaming payload methods with an initial payload frame, this is the
183+ // synthesized stream envelope.
182184 Message * service.UserTypeData
185+ // PayloadMessage is the gRPC message that carries the one-shot method
186+ // payload fields before any stream envelope wrapping.
187+ PayloadMessage * service.UserTypeData
188+ // StreamEnvelope describes the synthesized stream envelope when the
189+ // transport must carry both the one-shot payload and streaming payload
190+ // items through the same streamed protobuf message.
191+ StreamEnvelope * StreamEnvelopeData
183192 // Metadata is the request metadata.
184193 Metadata []* MetadataData
185194 // ServerConvert is the request data with constructor function to
@@ -195,6 +204,23 @@ type (
195204 CLIArgs []* InitArgData
196205 }
197206
207+ // StreamEnvelopeData describes a synthesized streamed protobuf envelope.
208+ StreamEnvelopeData struct {
209+ // FieldName is the protobuf oneof field name on the envelope message.
210+ FieldName string
211+ // InitialFieldName is the name of the initial payload branch field.
212+ InitialFieldName string
213+ // InitialWrapperRef is the fully qualified protobuf wrapper type for the
214+ // initial payload branch.
215+ InitialWrapperRef string
216+ // StreamItemFieldName is the name of the streaming payload item branch
217+ // field.
218+ StreamItemFieldName string
219+ // StreamItemWrapperRef is the fully qualified protobuf wrapper type for
220+ // the streaming payload item branch.
221+ StreamItemWrapperRef string
222+ }
223+
198224 // ResponseData describes a gRPC success or error response.
199225 ResponseData struct {
200226 // StatusCode is the return code of the response.
@@ -462,10 +488,26 @@ func (d *ServicesData) analyze(gs *expr.GRPCServiceExpr) *ServiceData {
462488 }
463489 seen , imported := make (map [string ]struct {}), make (map [string ]struct {})
464490 for _ , e := range gs .GRPCEndpoints {
491+ hasRequestMessage := ! isEmpty (e .Request .Type )
492+ useStreamEnvelope := usesStreamEnvelope (e )
493+
465494 // convert request and response types to protocol buffer message types
466495 e .Request = makeProtoBufMessage (e .Request , protoBufify (e .Name ()+ "_request" , true , true ), sd )
467496 if e .MethodExpr .StreamingPayload .Type != expr .Empty {
468- e .StreamingRequest = makeProtoBufMessage (e .StreamingRequest , protoBufify (e .Name ()+ "_streaming_request" , true , true ), sd )
497+ streamMessageName := protoBufify (e .Name ()+ "_streaming_request" , true , true )
498+ if useStreamEnvelope {
499+ streamMessageName = protoBufify (e .Name ()+ "_stream_item" , true , true )
500+ }
501+ e .StreamingRequest = makeProtoBufMessage (e .StreamingRequest , streamMessageName , sd )
502+ }
503+ var requestEnvelope * expr.AttributeExpr
504+ if useStreamEnvelope {
505+ requestEnvelope = makeProtoBufStreamEnvelope (
506+ e .Request ,
507+ e .StreamingRequest ,
508+ protoBufify (e .Name ()+ "_streaming_request" , true , true ),
509+ sd ,
510+ )
469511 }
470512 e .Response .Message = makeProtoBufMessage (e .Response .Message , protoBufify (e .Name ()+ "_response" , true , true ), sd )
471513 for _ , er := range e .GRPCErrors {
@@ -540,6 +582,9 @@ func (d *ServicesData) analyze(gs *expr.GRPCServiceExpr) *ServiceData {
540582 ServerConvert : d .buildRequestConvertData (e .Request , e .MethodExpr .Payload , reqMD , e , sd , true ),
541583 ClientConvert : d .buildRequestConvertData (e .Request , e .MethodExpr .Payload , reqMD , e , sd , false ),
542584 }
585+ if hasRequestMessage {
586+ request .PayloadMessage = collect (e .Request )
587+ }
543588 if obj := expr .AsObject (e .Request .Type ); (obj != nil && len (* obj ) > 0 ) || expr .IsUnion (e .Request .Type ) {
544589 // add the request message as the first argument to the CLI
545590 request .CLIArgs = append (request .CLIArgs , & InitArgData {
@@ -567,9 +612,13 @@ func (d *ServicesData) analyze(gs *expr.GRPCServiceExpr) *ServiceData {
567612 DefaultValue : m .DefaultValue ,
568613 })
569614 }
570- if e .StreamingRequest .Type != expr .Empty {
615+ switch {
616+ case requestEnvelope != nil :
617+ request .Message = collect (requestEnvelope )
618+ request .StreamEnvelope = buildStreamEnvelopeData (requestEnvelope , request .Message , sd )
619+ case e .StreamingRequest .Type != expr .Empty :
571620 request .Message = collect (e .StreamingRequest )
572- } else {
621+ default :
573622 request .Message = collect (e .Request )
574623 }
575624
@@ -872,20 +921,20 @@ func userTypeAttribute(ut expr.UserType) *expr.AttributeExpr {
872921
873922// buildRequestConvertData builds the convert data for the server and client
874923// requests.
875- // - server side - converts generated gRPC request type in *.pb.go and the
876- // gRPC metadata to method payload type.
877- // - client side - converts method payload type to generated gRPC request
878- // type in *.pb.go .
924+ // - server side - converts the one-shot gRPC request message (if any) and
925+ // gRPC metadata to the method payload type.
926+ // - client side - converts the method payload type to the one-shot gRPC
927+ // request message sent before any stream items .
879928//
880929// svr param indicates that the convert data is generated for server side.
881930func (d * ServicesData ) buildRequestConvertData (request , payload * expr.AttributeExpr , md []* MetadataData , e * expr.GRPCEndpointExpr , sd * ServiceData , svr bool ) * ConvertData {
882- // Server-side: No need to build convert data if payload is empty or payload
883- // is not an object type and endpoint streams payload (the payload is
884- // encoded in metadata under "goa-payload" in this case).
885- if ( svr && ( isEmpty ( payload . Type ) || ! expr . IsObject ( payload . Type ) && e . MethodExpr . IsPayloadStreaming ())) ||
886- // Client-side: No need to build convert data if streaming payload since
887- // all attributes in method payload is encoded into request metadata.
888- ( ! svr && e .MethodExpr .IsPayloadStreaming ()) {
931+ if svr && isEmpty ( payload . Type ) {
932+ return nil
933+ }
934+ if ! svr && e . MethodExpr . IsPayloadStreaming ( ) && isEmpty ( request . Type ) {
935+ return nil
936+ }
937+ if svr && e .MethodExpr .IsPayloadStreaming () && isEmpty ( request . Type ) && ! expr . IsObject ( payload . Type ) {
889938 return nil
890939 }
891940
@@ -1297,6 +1346,60 @@ func extractMetadata(a *expr.MappedAttributeExpr, service *expr.AttributeExpr, s
12971346 return metadata
12981347}
12991348
1349+ // usesStreamEnvelope reports whether the transport needs a typed stream
1350+ // envelope to carry both the one-shot method payload and streaming payload
1351+ // items.
1352+ func usesStreamEnvelope (e * expr.GRPCEndpointExpr ) bool {
1353+ return e .MethodExpr .IsPayloadStreaming () && ! isEmpty (e .Request .Type )
1354+ }
1355+
1356+ // makeProtoBufStreamEnvelope builds the protobuf stream envelope that carries
1357+ // the initial request payload frame and subsequent stream item frames.
1358+ func makeProtoBufStreamEnvelope (request , stream * expr.AttributeExpr , tname string , sd * ServiceData ) * expr.AttributeExpr {
1359+ initial := expr .DupAtt (request )
1360+ initial .Meta = initial .Meta .Dup ()
1361+ initial .Meta ["rpc:tag" ] = []string {"1" }
1362+ streamItem := expr .DupAtt (stream )
1363+ streamItem .Meta = streamItem .Meta .Dup ()
1364+ streamItem .Meta ["rpc:tag" ] = []string {"2" }
1365+ envelope := & expr.AttributeExpr {
1366+ Type : & expr.Object {
1367+ & expr.NamedAttributeExpr {
1368+ Name : "body" ,
1369+ Attribute : & expr.AttributeExpr {
1370+ Type : & expr.Union {
1371+ TypeName : "body" ,
1372+ Values : []* expr.NamedAttributeExpr {
1373+ {Name : "initial_payload" , Attribute : initial },
1374+ {Name : "stream_item" , Attribute : streamItem },
1375+ },
1376+ },
1377+ },
1378+ },
1379+ },
1380+ Validation : & expr.ValidationExpr {Required : []string {"body" }},
1381+ }
1382+ return makeProtoBufMessage (envelope , tname , sd )
1383+ }
1384+
1385+ // buildStreamEnvelopeData computes the generated Go names for the protobuf
1386+ // oneof field and wrapper types of the synthesized stream envelope.
1387+ func buildStreamEnvelopeData (envelope * expr.AttributeExpr , message * service.UserTypeData , sd * ServiceData ) * StreamEnvelopeData {
1388+ body := envelope .Find ("body" )
1389+ union := expr .AsUnion (body .Type )
1390+ scope := & protoBufScope {scope : sd .Scope }
1391+ fieldName := scope .Field (body , union .TypeName , true )
1392+ initialFieldName := scope .Field (union .Values [0 ].Attribute , union .Values [0 ].Name , true )
1393+ streamItemFieldName := scope .Field (union .Values [1 ].Attribute , union .Values [1 ].Name , true )
1394+ return & StreamEnvelopeData {
1395+ FieldName : fieldName ,
1396+ InitialFieldName : initialFieldName ,
1397+ InitialWrapperRef : fmt .Sprintf ("%s.%s_%s" , sd .PkgName , message .VarName , initialFieldName ),
1398+ StreamItemFieldName : streamItemFieldName ,
1399+ StreamItemWrapperRef : fmt .Sprintf ("%s.%s_%s" , sd .PkgName , message .VarName , streamItemFieldName ),
1400+ }
1401+ }
1402+
13001403func unalias (att * expr.AttributeExpr ) * expr.AttributeExpr {
13011404 if ut , ok := att .Type .(expr.UserType ); ok {
13021405 if _ , ok := ut .Attribute ().Type .(expr.Primitive ); ok {
0 commit comments