-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add support for the google.api.HttpBody proto as a response #458
Changes from all commits
136b143
a605dfe
78a226c
f0b42c6
787e198
d611609
e318b89
74f758e
a7ebb37
7ae723a
c5909a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package runtime | ||
|
||
import ( | ||
"io" | ||
"reflect" | ||
|
||
"github.com/golang/protobuf/proto" | ||
hb "google.golang.org/genproto/googleapis/api/httpbody" | ||
) | ||
|
||
var ( | ||
backupMarshaler = &JSONPb{OrigName: true} | ||
) | ||
|
||
// HTTPBodyMarshaler is a Marshaler which supports marshaling of a | ||
// google.api.HttpBody message as the full response body if it is | ||
// the actual message used as the response. If not, then this will | ||
// simply fallback to the JSONPb marshaler. | ||
type HTTPBodyMarshaler struct{} | ||
|
||
// ContentType returns the type specified in the google.api.HttpBody | ||
// proto if "v" is a google.api.HttpBody proto, otherwise returns | ||
// "application/json". | ||
func (*HTTPBodyMarshaler) ContentType(v interface{}) string { | ||
if h := tryHTTPBody(v); h != nil { | ||
return h.GetContentType() | ||
} | ||
return "application/json" | ||
} | ||
|
||
// Marshal marshals "v" by returning the body bytes if v is a | ||
// google.api.HttpBody message, or it marshals to JSON. | ||
func (*HTTPBodyMarshaler) Marshal(v interface{}) ([]byte, error) { | ||
if h := tryHTTPBody(v); h != nil { | ||
return h.GetData(), nil | ||
} | ||
return backupMarshaler.Marshal(v) | ||
} | ||
|
||
// Unmarshal unmarshals JSON data into "v". | ||
// google.api.HttpBody messages are not supported on the request. | ||
func (*HTTPBodyMarshaler) Unmarshal(data []byte, v interface{}) error { | ||
return backupMarshaler.Unmarshal(data, v) | ||
} | ||
|
||
// NewDecoder returns a Decoder which reads JSON stream from "r". | ||
func (*HTTPBodyMarshaler) NewDecoder(r io.Reader) Decoder { | ||
return backupMarshaler.NewDecoder(r) | ||
} | ||
|
||
// NewEncoder returns an Encoder which writes JSON stream into "w". | ||
func (*HTTPBodyMarshaler) NewEncoder(w io.Writer) Encoder { | ||
return backupMarshaler.NewEncoder(w) | ||
} | ||
|
||
func tryHTTPBody(v interface{}) *hb.HttpBody { | ||
rv := reflect.ValueOf(v) | ||
if rv.Kind() != reflect.Ptr { | ||
return nil | ||
} | ||
for rv.Kind() == reflect.Ptr { | ||
if rv.IsNil() { | ||
rv.Set(reflect.New(rv.Type().Elem())) | ||
} | ||
if rv.Type().ConvertibleTo(typeProtoMessage) { | ||
pb := rv.Interface().(proto.Message) | ||
if proto.MessageName(pb) == "google.api.HttpBody" { | ||
return v.(*hb.HttpBody) | ||
} | ||
} | ||
rv = rv.Elem() | ||
} | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,8 +15,8 @@ type Marshaler interface { | |
NewDecoder(r io.Reader) Decoder | ||
// NewEncoder returns an Encoder which writes bytes sequence into "w". | ||
NewEncoder(w io.Writer) Encoder | ||
// ContentType returns the Content-Type which this marshaler is responsible for. | ||
ContentType() string | ||
// ContentType returns the response Content-Type for "v". | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you elaborate on what "v" would be? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. v is the same thing that side note, I am not actually sure why all of these interfaces are not instead of type |
||
ContentType(v interface{}) string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is going to be a painful breaking change to the API. Is there ANY way to do this without passing in v? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm well the problem is that the content type right now is not dynamic. I suppose we could add another function like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bumping this - any thoughts here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately adding another method to the interface suffers from the same API breaking change problem so I would rather go with the simpler breaking change if it is possible. Just spitballing cause I don't think it's possible, there isn't a way to make a type union or something, is there? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually wow yea I think it is possible. so via a type union I think we can do something like this - https://play.golang.org/p/G4x7sEtEHbh - which in this case would mean we have something like this probably in this file:
then we'd update all the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been staring at this and I think you're right. That would not be API breaking. I think we should go that route. |
||
} | ||
|
||
// Decoder decodes a byte sequence | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ var ( | |
acceptHeader = http.CanonicalHeaderKey("Accept") | ||
contentTypeHeader = http.CanonicalHeaderKey("Content-Type") | ||
|
||
defaultMarshaler = &JSONPb{OrigName: true} | ||
defaultMarshaler = &HTTPBodyMarshaler{} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems incorrect. Why does the default marshaller need to change from this change? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why shouldn't the default behavior be to treat HTTP body protos specially? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a breaking change. I try not to make those except on major version bumps. If you want to be added to the list of 2.0 breaking changes and do it then, I'd be happy to do that. |
||
) | ||
|
||
// MarshalerForRequest returns the inbound/outbound marshalers for this request. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think you could add a test for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea sure, but let's finish the other discussion first so I don't have to change things too much.