Moved to google.golang.org/genproto/googleapis/api/annotations

Fixes #52
This commit is contained in:
Valerio Gheri
2017-03-31 18:01:58 +02:00
parent 024c5a4e4e
commit c40779224f
2037 changed files with 831329 additions and 1854 deletions

View File

@@ -0,0 +1,187 @@
package runtime
import (
"fmt"
"net"
"net/http"
"strconv"
"strings"
"time"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
)
// MetadataHeaderPrefix is the http prefix that represents custom metadata
// parameters to or from a gRPC call.
const MetadataHeaderPrefix = "Grpc-Metadata-"
// MetadataPrefix is the prefix for grpc-gateway supplied custom metadata fields.
const MetadataPrefix = "grpcgateway-"
// MetadataTrailerPrefix is prepended to gRPC metadata as it is converted to
// HTTP headers in a response handled by grpc-gateway
const MetadataTrailerPrefix = "Grpc-Trailer-"
const metadataGrpcTimeout = "Grpc-Timeout"
const xForwardedFor = "X-Forwarded-For"
const xForwardedHost = "X-Forwarded-Host"
var (
// DefaultContextTimeout is used for gRPC call context.WithTimeout whenever a Grpc-Timeout inbound
// header isn't present. If the value is 0 the sent `context` will not have a timeout.
DefaultContextTimeout = 0 * time.Second
)
/*
AnnotateContext adds context information such as metadata from the request.
At a minimum, the RemoteAddr is included in the fashion of "X-Forwarded-For",
except that the forwarded destination is not another HTTP service but rather
a gRPC service.
*/
func AnnotateContext(ctx context.Context, req *http.Request) (context.Context, error) {
var pairs []string
timeout := DefaultContextTimeout
if tm := req.Header.Get(metadataGrpcTimeout); tm != "" {
var err error
timeout, err = timeoutDecode(tm)
if err != nil {
return nil, grpc.Errorf(codes.InvalidArgument, "invalid grpc-timeout: %s", tm)
}
}
for key, vals := range req.Header {
for _, val := range vals {
// For backwards-compatibility, pass through 'authorization' header with no prefix.
if strings.ToLower(key) == "authorization" {
pairs = append(pairs, "authorization", val)
}
if isPermanentHTTPHeader(key) {
pairs = append(pairs, strings.ToLower(fmt.Sprintf("%s%s", MetadataPrefix, key)), val)
continue
}
if strings.HasPrefix(key, MetadataHeaderPrefix) {
pairs = append(pairs, key[len(MetadataHeaderPrefix):], val)
}
}
}
if host := req.Header.Get(xForwardedHost); host != "" {
pairs = append(pairs, strings.ToLower(xForwardedHost), host)
} else if req.Host != "" {
pairs = append(pairs, strings.ToLower(xForwardedHost), req.Host)
}
if addr := req.RemoteAddr; addr != "" {
if remoteIP, _, err := net.SplitHostPort(addr); err == nil {
if fwd := req.Header.Get(xForwardedFor); fwd == "" {
pairs = append(pairs, strings.ToLower(xForwardedFor), remoteIP)
} else {
pairs = append(pairs, strings.ToLower(xForwardedFor), fmt.Sprintf("%s, %s", fwd, remoteIP))
}
} else {
grpclog.Printf("invalid remote addr: %s", addr)
}
}
if timeout != 0 {
ctx, _ = context.WithTimeout(ctx, timeout)
}
if len(pairs) == 0 {
return ctx, nil
}
return metadata.NewContext(ctx, metadata.Pairs(pairs...)), nil
}
// ServerMetadata consists of metadata sent from gRPC server.
type ServerMetadata struct {
HeaderMD metadata.MD
TrailerMD metadata.MD
}
type serverMetadataKey struct{}
// NewServerMetadataContext creates a new context with ServerMetadata
func NewServerMetadataContext(ctx context.Context, md ServerMetadata) context.Context {
return context.WithValue(ctx, serverMetadataKey{}, md)
}
// ServerMetadataFromContext returns the ServerMetadata in ctx
func ServerMetadataFromContext(ctx context.Context) (md ServerMetadata, ok bool) {
md, ok = ctx.Value(serverMetadataKey{}).(ServerMetadata)
return
}
func timeoutDecode(s string) (time.Duration, error) {
size := len(s)
if size < 2 {
return 0, fmt.Errorf("timeout string is too short: %q", s)
}
d, ok := timeoutUnitToDuration(s[size-1])
if !ok {
return 0, fmt.Errorf("timeout unit is not recognized: %q", s)
}
t, err := strconv.ParseInt(s[:size-1], 10, 64)
if err != nil {
return 0, err
}
return d * time.Duration(t), nil
}
func timeoutUnitToDuration(u uint8) (d time.Duration, ok bool) {
switch u {
case 'H':
return time.Hour, true
case 'M':
return time.Minute, true
case 'S':
return time.Second, true
case 'm':
return time.Millisecond, true
case 'u':
return time.Microsecond, true
case 'n':
return time.Nanosecond, true
default:
}
return
}
// isPermanentHTTPHeader checks whether hdr belongs to the list of
// permenant request headers maintained by IANA.
// http://www.iana.org/assignments/message-headers/message-headers.xml
func isPermanentHTTPHeader(hdr string) bool {
switch hdr {
case
"Accept",
"Accept-Charset",
"Accept-Language",
"Accept-Ranges",
"Authorization",
"Cache-Control",
"Content-Type",
"Cookie",
"Date",
"Expect",
"From",
"Host",
"If-Match",
"If-Modified-Since",
"If-None-Match",
"If-Schedule-Tag-Match",
"If-Unmodified-Since",
"Max-Forwards",
"Origin",
"Pragma",
"Referer",
"User-Agent",
"Via",
"Warning":
return true
}
return false
}

View File

@@ -0,0 +1,172 @@
package runtime_test
import (
"net/http"
"reflect"
"testing"
"time"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc/metadata"
)
const (
emptyForwardMetaCount = 1
)
func TestAnnotateContext_WorksWithEmpty(t *testing.T) {
ctx := context.Background()
request, err := http.NewRequest("GET", "http://www.example.com", nil)
if err != nil {
t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", "GET", "http://www.example.com", err)
}
request.Header.Add("Some-Irrelevant-Header", "some value")
annotated, err := runtime.AnnotateContext(ctx, request)
if err != nil {
t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
return
}
md, ok := metadata.FromContext(annotated)
if !ok || len(md) != emptyForwardMetaCount {
t.Errorf("Expected %d metadata items in context; got %v", emptyForwardMetaCount, md)
}
}
func TestAnnotateContext_ForwardsGrpcMetadata(t *testing.T) {
ctx := context.Background()
request, err := http.NewRequest("GET", "http://www.example.com", nil)
if err != nil {
t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", "GET", "http://www.example.com", err)
}
request.Header.Add("Some-Irrelevant-Header", "some value")
request.Header.Add("Grpc-Metadata-FooBar", "Value1")
request.Header.Add("Grpc-Metadata-Foo-BAZ", "Value2")
request.Header.Add("Grpc-Metadata-foo-bAz", "Value3")
request.Header.Add("Authorization", "Token 1234567890")
annotated, err := runtime.AnnotateContext(ctx, request)
if err != nil {
t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
return
}
md, ok := metadata.FromContext(annotated)
if got, want := len(md), emptyForwardMetaCount+4; !ok || got != want {
t.Errorf("metadata items in context = %d want %d: %v", got, want, md)
}
if got, want := md["foobar"], []string{"Value1"}; !reflect.DeepEqual(got, want) {
t.Errorf(`md["grpcgateway-foobar"] = %q; want %q`, got, want)
}
if got, want := md["foo-baz"], []string{"Value2", "Value3"}; !reflect.DeepEqual(got, want) {
t.Errorf(`md["grpcgateway-foo-baz"] = %q want %q`, got, want)
}
if got, want := md["grpcgateway-authorization"], []string{"Token 1234567890"}; !reflect.DeepEqual(got, want) {
t.Errorf(`md["grpcgateway-authorization"] = %q want %q`, got, want)
}
if got, want := md["authorization"], []string{"Token 1234567890"}; !reflect.DeepEqual(got, want) {
t.Errorf(`md["authorization"] = %q want %q`, got, want)
}
}
func TestAnnotateContext_XForwardedFor(t *testing.T) {
ctx := context.Background()
request, err := http.NewRequest("GET", "http://bar.foo.example.com", nil)
if err != nil {
t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", "GET", "http://bar.foo.example.com", err)
}
request.Header.Add("X-Forwarded-For", "192.0.2.100") // client
request.RemoteAddr = "192.0.2.200:12345" // proxy
annotated, err := runtime.AnnotateContext(ctx, request)
if err != nil {
t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
return
}
md, ok := metadata.FromContext(annotated)
if !ok || len(md) != emptyForwardMetaCount+1 {
t.Errorf("Expected %d metadata items in context; got %v", emptyForwardMetaCount+1, md)
}
if got, want := md["x-forwarded-host"], []string{"bar.foo.example.com"}; !reflect.DeepEqual(got, want) {
t.Errorf(`md["host"] = %v; want %v`, got, want)
}
// Note: it must be in order client, proxy1, proxy2
if got, want := md["x-forwarded-for"], []string{"192.0.2.100, 192.0.2.200"}; !reflect.DeepEqual(got, want) {
t.Errorf(`md["x-forwarded-for"] = %v want %v`, got, want)
}
}
func TestAnnotateContext_SupportsTimeouts(t *testing.T) {
ctx := context.Background()
request, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil {
t.Fatalf(`http.NewRequest("GET", "http://example.com", nil failed with %v; want success`, err)
}
annotated, err := runtime.AnnotateContext(ctx, request)
if err != nil {
t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
return
}
if _, ok := annotated.Deadline(); ok {
// no deadline by default
t.Errorf("annotated.Deadline() = _, true; want _, false")
}
const acceptableError = 50 * time.Millisecond
runtime.DefaultContextTimeout = 10 * time.Second
annotated, err = runtime.AnnotateContext(ctx, request)
if err != nil {
t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
return
}
deadline, ok := annotated.Deadline()
if !ok {
t.Errorf("annotated.Deadline() = _, false; want _, true")
}
if got, want := deadline.Sub(time.Now()), runtime.DefaultContextTimeout; got-want > acceptableError || got-want < -acceptableError {
t.Errorf("deadline.Sub(time.Now()) = %v; want %v; with error %v", got, want, acceptableError)
}
for _, spec := range []struct {
timeout string
want time.Duration
}{
{
timeout: "17H",
want: 17 * time.Hour,
},
{
timeout: "19M",
want: 19 * time.Minute,
},
{
timeout: "23S",
want: 23 * time.Second,
},
{
timeout: "1009m",
want: 1009 * time.Millisecond,
},
{
timeout: "1000003u",
want: 1000003 * time.Microsecond,
},
{
timeout: "100000007n",
want: 100000007 * time.Nanosecond,
},
} {
request.Header.Set("Grpc-Timeout", spec.timeout)
annotated, err = runtime.AnnotateContext(ctx, request)
if err != nil {
t.Errorf("runtime.AnnotateContext(ctx, %#v) failed with %v; want success", request, err)
return
}
deadline, ok := annotated.Deadline()
if !ok {
t.Errorf("annotated.Deadline() = _, false; want _, true; timeout = %q", spec.timeout)
}
if got, want := deadline.Sub(time.Now()), spec.want; got-want > acceptableError || got-want < -acceptableError {
t.Errorf("deadline.Sub(time.Now()) = %v; want %v; with error %v; timeout= %q", got, want, acceptableError, spec.timeout)
}
}
}

View File

@@ -0,0 +1,58 @@
package runtime
import (
"strconv"
)
// String just returns the given string.
// It is just for compatibility to other types.
func String(val string) (string, error) {
return val, nil
}
// Bool converts the given string representation of a boolean value into bool.
func Bool(val string) (bool, error) {
return strconv.ParseBool(val)
}
// Float64 converts the given string representation into representation of a floating point number into float64.
func Float64(val string) (float64, error) {
return strconv.ParseFloat(val, 64)
}
// Float32 converts the given string representation of a floating point number into float32.
func Float32(val string) (float32, error) {
f, err := strconv.ParseFloat(val, 32)
if err != nil {
return 0, err
}
return float32(f), nil
}
// Int64 converts the given string representation of an integer into int64.
func Int64(val string) (int64, error) {
return strconv.ParseInt(val, 0, 64)
}
// Int32 converts the given string representation of an integer into int32.
func Int32(val string) (int32, error) {
i, err := strconv.ParseInt(val, 0, 32)
if err != nil {
return 0, err
}
return int32(i), nil
}
// Uint64 converts the given string representation of an integer into uint64.
func Uint64(val string) (uint64, error) {
return strconv.ParseUint(val, 0, 64)
}
// Uint32 converts the given string representation of an integer into uint32.
func Uint32(val string) (uint32, error) {
i, err := strconv.ParseUint(val, 0, 32)
if err != nil {
return 0, err
}
return uint32(i), nil
}

View File

@@ -0,0 +1,5 @@
/*
Package runtime contains runtime helper functions used by
servers which protoc-gen-grpc-gateway generates.
*/
package runtime

View File

@@ -0,0 +1,121 @@
package runtime
import (
"io"
"net/http"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
)
// HTTPStatusFromCode converts a gRPC error code into the corresponding HTTP response status.
func HTTPStatusFromCode(code codes.Code) int {
switch code {
case codes.OK:
return http.StatusOK
case codes.Canceled:
return http.StatusRequestTimeout
case codes.Unknown:
return http.StatusInternalServerError
case codes.InvalidArgument:
return http.StatusBadRequest
case codes.DeadlineExceeded:
return http.StatusRequestTimeout
case codes.NotFound:
return http.StatusNotFound
case codes.AlreadyExists:
return http.StatusConflict
case codes.PermissionDenied:
return http.StatusForbidden
case codes.Unauthenticated:
return http.StatusUnauthorized
case codes.ResourceExhausted:
return http.StatusForbidden
case codes.FailedPrecondition:
return http.StatusPreconditionFailed
case codes.Aborted:
return http.StatusConflict
case codes.OutOfRange:
return http.StatusBadRequest
case codes.Unimplemented:
return http.StatusNotImplemented
case codes.Internal:
return http.StatusInternalServerError
case codes.Unavailable:
return http.StatusServiceUnavailable
case codes.DataLoss:
return http.StatusInternalServerError
}
grpclog.Printf("Unknown gRPC error code: %v", code)
return http.StatusInternalServerError
}
var (
// HTTPError replies to the request with the error.
// You can set a custom function to this variable to customize error format.
HTTPError = DefaultHTTPError
// OtherErrorHandler handles the following error used by the gateway: StatusMethodNotAllowed StatusNotFound and StatusBadRequest
OtherErrorHandler = DefaultOtherErrorHandler
)
type errorBody struct {
Error string `protobuf:"bytes,1,name=error" json:"error"`
Code int32 `protobuf:"varint,2,name=code" json:"code"`
}
//Make this also conform to proto.Message for builtin JSONPb Marshaler
func (e *errorBody) Reset() { *e = errorBody{} }
func (e *errorBody) String() string { return proto.CompactTextString(e) }
func (*errorBody) ProtoMessage() {}
// DefaultHTTPError is the default implementation of HTTPError.
// If "err" is an error from gRPC system, the function replies with the status code mapped by HTTPStatusFromCode.
// If otherwise, it replies with http.StatusInternalServerError.
//
// The response body returned by this function is a JSON object,
// which contains a member whose key is "error" and whose value is err.Error().
func DefaultHTTPError(ctx context.Context, marshaler Marshaler, w http.ResponseWriter, _ *http.Request, err error) {
const fallback = `{"error": "failed to marshal error message"}`
w.Header().Del("Trailer")
w.Header().Set("Content-Type", marshaler.ContentType())
body := &errorBody{
Error: grpc.ErrorDesc(err),
Code: int32(grpc.Code(err)),
}
buf, merr := marshaler.Marshal(body)
if merr != nil {
grpclog.Printf("Failed to marshal error message %q: %v", body, merr)
w.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(w, fallback); err != nil {
grpclog.Printf("Failed to write response: %v", err)
}
return
}
md, ok := ServerMetadataFromContext(ctx)
if !ok {
grpclog.Printf("Failed to extract ServerMetadata from context")
}
handleForwardResponseServerMetadata(w, md)
handleForwardResponseTrailerHeader(w, md)
st := HTTPStatusFromCode(grpc.Code(err))
w.WriteHeader(st)
if _, err := w.Write(buf); err != nil {
grpclog.Printf("Failed to write response: %v", err)
}
handleForwardResponseTrailer(w, md)
}
// DefaultOtherErrorHandler is the default implementation of OtherErrorHandler.
// It simply writes a string representation of the given error into "w".
func DefaultOtherErrorHandler(w http.ResponseWriter, _ *http.Request, msg string, code int) {
http.Error(w, msg, code)
}

View File

@@ -0,0 +1,56 @@
package runtime_test
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
func TestDefaultHTTPError(t *testing.T) {
ctx := context.Background()
for _, spec := range []struct {
err error
status int
msg string
}{
{
err: fmt.Errorf("example error"),
status: http.StatusInternalServerError,
msg: "example error",
},
{
err: grpc.Errorf(codes.NotFound, "no such resource"),
status: http.StatusNotFound,
msg: "no such resource",
},
} {
w := httptest.NewRecorder()
req, _ := http.NewRequest("", "", nil) // Pass in an empty request to match the signature
runtime.DefaultHTTPError(ctx, &runtime.JSONBuiltin{}, w, req, spec.err)
if got, want := w.Header().Get("Content-Type"), "application/json"; got != want {
t.Errorf(`w.Header().Get("Content-Type") = %q; want %q; on spec.err=%v`, got, want, spec.err)
}
if got, want := w.Code, spec.status; got != want {
t.Errorf("w.Code = %d; want %d", got, want)
}
body := make(map[string]interface{})
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
t.Errorf("json.Unmarshal(%q, &body) failed with %v; want success", w.Body.Bytes(), err)
continue
}
if got, want := body["error"].(string), spec.msg; !strings.Contains(got, want) {
t.Errorf(`body["error"] = %q; want %q; on spec.err=%v`, got, want, spec.err)
}
}
}

View File

@@ -0,0 +1,164 @@
package runtime
import (
"fmt"
"io"
"net/http"
"net/textproto"
"github.com/golang/protobuf/proto"
"github.com/grpc-ecosystem/grpc-gateway/runtime/internal"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
)
// ForwardResponseStream forwards the stream from gRPC server to REST client.
func ForwardResponseStream(ctx context.Context, marshaler Marshaler, w http.ResponseWriter, req *http.Request, recv func() (proto.Message, error), opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
f, ok := w.(http.Flusher)
if !ok {
grpclog.Printf("Flush not supported in %T", w)
http.Error(w, "unexpected type of web server", http.StatusInternalServerError)
return
}
md, ok := ServerMetadataFromContext(ctx)
if !ok {
grpclog.Printf("Failed to extract ServerMetadata from context")
http.Error(w, "unexpected error", http.StatusInternalServerError)
return
}
handleForwardResponseServerMetadata(w, md)
w.Header().Set("Transfer-Encoding", "chunked")
w.Header().Set("Content-Type", marshaler.ContentType())
if err := handleForwardResponseOptions(ctx, w, nil, opts); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
f.Flush()
for {
resp, err := recv()
if err == io.EOF {
return
}
if err != nil {
handleForwardResponseStreamError(marshaler, w, err)
return
}
if err := handleForwardResponseOptions(ctx, w, resp, opts); err != nil {
handleForwardResponseStreamError(marshaler, w, err)
return
}
buf, err := marshaler.Marshal(streamChunk(resp, nil))
if err != nil {
grpclog.Printf("Failed to marshal response chunk: %v", err)
return
}
if _, err = fmt.Fprintf(w, "%s\n", buf); err != nil {
grpclog.Printf("Failed to send response chunk: %v", err)
return
}
f.Flush()
}
}
func handleForwardResponseServerMetadata(w http.ResponseWriter, md ServerMetadata) {
for k, vs := range md.HeaderMD {
hKey := fmt.Sprintf("%s%s", MetadataHeaderPrefix, k)
for i := range vs {
w.Header().Add(hKey, vs[i])
}
}
}
func handleForwardResponseTrailerHeader(w http.ResponseWriter, md ServerMetadata) {
for k := range md.TrailerMD {
tKey := textproto.CanonicalMIMEHeaderKey(fmt.Sprintf("%s%s", MetadataTrailerPrefix, k))
w.Header().Add("Trailer", tKey)
}
}
func handleForwardResponseTrailer(w http.ResponseWriter, md ServerMetadata) {
for k, vs := range md.TrailerMD {
tKey := fmt.Sprintf("%s%s", MetadataTrailerPrefix, k)
for i := range vs {
w.Header().Add(tKey, vs[i])
}
}
}
// ForwardResponseMessage forwards the message "resp" from gRPC server to REST client.
func ForwardResponseMessage(ctx context.Context, marshaler Marshaler, w http.ResponseWriter, req *http.Request, resp proto.Message, opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
md, ok := ServerMetadataFromContext(ctx)
if !ok {
grpclog.Printf("Failed to extract ServerMetadata from context")
}
handleForwardResponseServerMetadata(w, md)
handleForwardResponseTrailerHeader(w, md)
w.Header().Set("Content-Type", marshaler.ContentType())
if err := handleForwardResponseOptions(ctx, w, resp, opts); err != nil {
HTTPError(ctx, marshaler, w, req, err)
return
}
buf, err := marshaler.Marshal(resp)
if err != nil {
grpclog.Printf("Marshal error: %v", err)
HTTPError(ctx, marshaler, w, req, err)
return
}
if _, err = w.Write(buf); err != nil {
grpclog.Printf("Failed to write response: %v", err)
}
handleForwardResponseTrailer(w, md)
}
func handleForwardResponseOptions(ctx context.Context, w http.ResponseWriter, resp proto.Message, opts []func(context.Context, http.ResponseWriter, proto.Message) error) error {
if len(opts) == 0 {
return nil
}
for _, opt := range opts {
if err := opt(ctx, w, resp); err != nil {
grpclog.Printf("Error handling ForwardResponseOptions: %v", err)
return err
}
}
return nil
}
func handleForwardResponseStreamError(marshaler Marshaler, w http.ResponseWriter, err error) {
buf, merr := marshaler.Marshal(streamChunk(nil, err))
if merr != nil {
grpclog.Printf("Failed to marshal an error: %v", merr)
return
}
if _, werr := fmt.Fprintf(w, "%s\n", buf); werr != nil {
grpclog.Printf("Failed to notify error to client: %v", werr)
return
}
}
func streamChunk(result proto.Message, err error) map[string]proto.Message {
if err != nil {
grpcCode := grpc.Code(err)
httpCode := HTTPStatusFromCode(grpcCode)
return map[string]proto.Message{
"error": &internal.StreamError{
GrpcCode: int32(grpcCode),
HttpCode: int32(httpCode),
Message: err.Error(),
HttpStatus: http.StatusText(httpCode),
},
}
}
if result == nil {
return streamChunk(nil, fmt.Errorf("empty response"))
}
return map[string]proto.Message{"result": result}
}

View File

@@ -0,0 +1,93 @@
// Code generated by protoc-gen-go.
// source: runtime/internal/stream_chunk.proto
// DO NOT EDIT!
/*
Package internal is a generated protocol buffer package.
It is generated from these files:
runtime/internal/stream_chunk.proto
It has these top-level messages:
StreamError
*/
package internal
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// StreamError is a response type which is returned when
// streaming rpc returns an error.
type StreamError struct {
GrpcCode int32 `protobuf:"varint,1,opt,name=grpc_code,json=grpcCode" json:"grpc_code,omitempty"`
HttpCode int32 `protobuf:"varint,2,opt,name=http_code,json=httpCode" json:"http_code,omitempty"`
Message string `protobuf:"bytes,3,opt,name=message" json:"message,omitempty"`
HttpStatus string `protobuf:"bytes,4,opt,name=http_status,json=httpStatus" json:"http_status,omitempty"`
}
func (m *StreamError) Reset() { *m = StreamError{} }
func (m *StreamError) String() string { return proto.CompactTextString(m) }
func (*StreamError) ProtoMessage() {}
func (*StreamError) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *StreamError) GetGrpcCode() int32 {
if m != nil {
return m.GrpcCode
}
return 0
}
func (m *StreamError) GetHttpCode() int32 {
if m != nil {
return m.HttpCode
}
return 0
}
func (m *StreamError) GetMessage() string {
if m != nil {
return m.Message
}
return ""
}
func (m *StreamError) GetHttpStatus() string {
if m != nil {
return m.HttpStatus
}
return ""
}
func init() {
proto.RegisterType((*StreamError)(nil), "grpc.gateway.runtime.StreamError")
}
func init() { proto.RegisterFile("runtime/internal/stream_chunk.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 181 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x34, 0x8e, 0xbf, 0xee, 0x82, 0x30,
0x14, 0x85, 0xd3, 0xdf, 0x1f, 0x85, 0xcb, 0x46, 0x1c, 0x9a, 0x38, 0x48, 0x74, 0x61, 0x82, 0xc1,
0x37, 0xd0, 0xf8, 0x02, 0xb0, 0xb9, 0x90, 0x0a, 0x37, 0x40, 0x94, 0x96, 0xdc, 0x5e, 0x62, 0x5c,
0x7d, 0x72, 0xd3, 0x22, 0xe3, 0xf9, 0xbe, 0x73, 0x92, 0x03, 0x07, 0x9a, 0x34, 0xf7, 0x03, 0xe6,
0xbd, 0x66, 0x24, 0xad, 0x1e, 0xb9, 0x65, 0x42, 0x35, 0x54, 0x75, 0x37, 0xe9, 0x7b, 0x36, 0x92,
0x61, 0x13, 0x6f, 0x5a, 0x1a, 0xeb, 0xac, 0x55, 0x8c, 0x4f, 0xf5, 0xca, 0xbe, 0x8b, 0xfd, 0x5b,
0x40, 0x54, 0xfa, 0xf2, 0x85, 0xc8, 0x50, 0xbc, 0x85, 0xd0, 0xf5, 0xaa, 0xda, 0x34, 0x28, 0x45,
0x22, 0xd2, 0xff, 0x22, 0x70, 0xe0, 0x6c, 0x1a, 0x74, 0xb2, 0x63, 0x1e, 0x67, 0xf9, 0x33, 0x4b,
0x07, 0xbc, 0x94, 0xb0, 0x1e, 0xd0, 0x5a, 0xd5, 0xa2, 0xfc, 0x4d, 0x44, 0x1a, 0x16, 0x4b, 0x8c,
0x77, 0x10, 0xf9, 0x99, 0x65, 0xc5, 0x93, 0x95, 0x7f, 0xde, 0x82, 0x43, 0xa5, 0x27, 0x27, 0xb8,
0x06, 0xcb, 0xf3, 0xdb, 0xca, 0xbf, 0x3d, 0x7e, 0x02, 0x00, 0x00, 0xff, 0xff, 0xa9, 0x07, 0x92,
0xb6, 0xd4, 0x00, 0x00, 0x00,
}

View File

@@ -0,0 +1,12 @@
syntax = "proto3";
package grpc.gateway.runtime;
option go_package = "internal";
// StreamError is a response type which is returned when
// streaming rpc returns an error.
message StreamError {
int32 grpc_code = 1;
int32 http_code = 2;
string message = 3;
string http_status = 4;
}

View File

@@ -0,0 +1,37 @@
package runtime
import (
"encoding/json"
"io"
)
// JSONBuiltin is a Marshaler which marshals/unmarshals into/from JSON
// with the standard "encoding/json" package of Golang.
// Although it is generally faster for simple proto messages than JSONPb,
// it does not support advanced features of protobuf, e.g. map, oneof, ....
type JSONBuiltin struct{}
// ContentType always Returns "application/json".
func (*JSONBuiltin) ContentType() string {
return "application/json"
}
// Marshal marshals "v" into JSON
func (j *JSONBuiltin) Marshal(v interface{}) ([]byte, error) {
return json.Marshal(v)
}
// Unmarshal unmarshals JSON data into "v".
func (j *JSONBuiltin) Unmarshal(data []byte, v interface{}) error {
return json.Unmarshal(data, v)
}
// NewDecoder returns a Decoder which reads JSON stream from "r".
func (j *JSONBuiltin) NewDecoder(r io.Reader) Decoder {
return json.NewDecoder(r)
}
// NewEncoder returns an Encoder which writes JSON stream into "w".
func (j *JSONBuiltin) NewEncoder(w io.Writer) Encoder {
return json.NewEncoder(w)
}

View File

@@ -0,0 +1,245 @@
package runtime_test
import (
"bytes"
"encoding/json"
"reflect"
"strings"
"testing"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/empty"
structpb "github.com/golang/protobuf/ptypes/struct"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/golang/protobuf/ptypes/wrappers"
"github.com/grpc-ecosystem/grpc-gateway/examples/examplepb"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
)
func TestJSONBuiltinMarshal(t *testing.T) {
var m runtime.JSONBuiltin
msg := examplepb.SimpleMessage{
Id: "foo",
}
buf, err := m.Marshal(&msg)
if err != nil {
t.Errorf("m.Marshal(%v) failed with %v; want success", &msg, err)
}
var got examplepb.SimpleMessage
if err := json.Unmarshal(buf, &got); err != nil {
t.Errorf("json.Unmarshal(%q, &got) failed with %v; want success", buf, err)
}
if want := msg; !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want %v", &got, &want)
}
}
func TestJSONBuiltinMarshalField(t *testing.T) {
var m runtime.JSONBuiltin
for _, fixt := range builtinFieldFixtures {
buf, err := m.Marshal(fixt.data)
if err != nil {
t.Errorf("m.Marshal(%v) failed with %v; want success", fixt.data, err)
}
if got, want := string(buf), fixt.json; got != want {
t.Errorf("got = %q; want %q; data = %#v", got, want, fixt.data)
}
}
}
func TestJSONBuiltinMarshalFieldKnownErrors(t *testing.T) {
var m runtime.JSONBuiltin
for _, fixt := range builtinKnownErrors {
buf, err := m.Marshal(fixt.data)
if err != nil {
t.Errorf("m.Marshal(%v) failed with %v; want success", fixt.data, err)
}
if got, want := string(buf), fixt.json; got == want {
t.Errorf("surprisingly got = %q; as want %q; data = %#v", got, want, fixt.data)
}
}
}
func TestJSONBuiltinsnmarshal(t *testing.T) {
var (
m runtime.JSONBuiltin
got examplepb.SimpleMessage
data = []byte(`{"id": "foo"}`)
)
if err := m.Unmarshal(data, &got); err != nil {
t.Errorf("m.Unmarshal(%q, &got) failed with %v; want success", data, err)
}
want := examplepb.SimpleMessage{
Id: "foo",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want = %v", &got, &want)
}
}
func TestJSONBuiltinUnmarshalField(t *testing.T) {
var m runtime.JSONBuiltin
for _, fixt := range builtinFieldFixtures {
dest := reflect.New(reflect.TypeOf(fixt.data))
if err := m.Unmarshal([]byte(fixt.json), dest.Interface()); err != nil {
t.Errorf("m.Unmarshal(%q, dest) failed with %v; want success", fixt.json, err)
}
if got, want := dest.Elem().Interface(), fixt.data; !reflect.DeepEqual(got, want) {
t.Errorf("got = %#v; want = %#v; input = %q", got, want, fixt.json)
}
}
}
func TestJSONBuiltinUnmarshalFieldKnownErrors(t *testing.T) {
var m runtime.JSONBuiltin
for _, fixt := range builtinKnownErrors {
dest := reflect.New(reflect.TypeOf(fixt.data))
if err := m.Unmarshal([]byte(fixt.json), dest.Interface()); err == nil {
t.Errorf("m.Unmarshal(%q, dest) succeeded; want ane error", fixt.json)
}
}
}
func TestJSONBuiltinEncoder(t *testing.T) {
var m runtime.JSONBuiltin
msg := examplepb.SimpleMessage{
Id: "foo",
}
var buf bytes.Buffer
enc := m.NewEncoder(&buf)
if err := enc.Encode(&msg); err != nil {
t.Errorf("enc.Encode(%v) failed with %v; want success", &msg, err)
}
var got examplepb.SimpleMessage
if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
t.Errorf("json.Unmarshal(%q, &got) failed with %v; want success", buf.String(), err)
}
if want := msg; !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want %v", &got, &want)
}
}
func TestJSONBuiltinEncoderFields(t *testing.T) {
var m runtime.JSONBuiltin
for _, fixt := range builtinFieldFixtures {
var buf bytes.Buffer
enc := m.NewEncoder(&buf)
if err := enc.Encode(fixt.data); err != nil {
t.Errorf("enc.Encode(%#v) failed with %v; want success", fixt.data, err)
}
if got, want := buf.String(), fixt.json+"\n"; got != want {
t.Errorf("got = %q; want %q; data = %#v", got, want, fixt.data)
}
}
}
func TestJSONBuiltinDecoder(t *testing.T) {
var (
m runtime.JSONBuiltin
got examplepb.SimpleMessage
data = `{"id": "foo"}`
)
r := strings.NewReader(data)
dec := m.NewDecoder(r)
if err := dec.Decode(&got); err != nil {
t.Errorf("m.Unmarshal(&got) failed with %v; want success", err)
}
want := examplepb.SimpleMessage{
Id: "foo",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want = %v", &got, &want)
}
}
func TestJSONBuiltinDecoderFields(t *testing.T) {
var m runtime.JSONBuiltin
for _, fixt := range builtinFieldFixtures {
r := strings.NewReader(fixt.json)
dec := m.NewDecoder(r)
dest := reflect.New(reflect.TypeOf(fixt.data))
if err := dec.Decode(dest.Interface()); err != nil {
t.Errorf("dec.Decode(dest) failed with %v; want success; data = %q", err, fixt.json)
}
if got, want := dest.Elem().Interface(), fixt.data; !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want = %v; input = %q", got, want, fixt.json)
}
}
}
var (
builtinFieldFixtures = []struct {
data interface{}
json string
}{
{data: "", json: `""`},
{data: proto.String(""), json: `""`},
{data: "foo", json: `"foo"`},
{data: proto.String("foo"), json: `"foo"`},
{data: int32(-1), json: "-1"},
{data: proto.Int32(-1), json: "-1"},
{data: int64(-1), json: "-1"},
{data: proto.Int64(-1), json: "-1"},
{data: uint32(123), json: "123"},
{data: proto.Uint32(123), json: "123"},
{data: uint64(123), json: "123"},
{data: proto.Uint64(123), json: "123"},
{data: float32(-1.5), json: "-1.5"},
{data: proto.Float32(-1.5), json: "-1.5"},
{data: float64(-1.5), json: "-1.5"},
{data: proto.Float64(-1.5), json: "-1.5"},
{data: true, json: "true"},
{data: proto.Bool(true), json: "true"},
{data: (*string)(nil), json: "null"},
{data: new(empty.Empty), json: "{}"},
{data: examplepb.NumericEnum_ONE, json: "1"},
{
data: (*examplepb.NumericEnum)(proto.Int32(int32(examplepb.NumericEnum_ONE))),
json: "1",
},
}
builtinKnownErrors = []struct {
data interface{}
json string
}{
{data: examplepb.NumericEnum_ONE, json: "ONE"},
{
data: (*examplepb.NumericEnum)(proto.Int32(int32(examplepb.NumericEnum_ONE))),
json: "ONE",
},
{
data: &examplepb.ABitOfEverything_OneofString{OneofString: "abc"},
json: `"abc"`,
},
{
data: &timestamp.Timestamp{
Seconds: 1462875553,
Nanos: 123000000,
},
json: `"2016-05-10T10:19:13.123Z"`,
},
{
data: &wrappers.Int32Value{Value: 123},
json: "123",
},
{
data: &structpb.Value{
Kind: &structpb.Value_StringValue{
StringValue: "abc",
},
},
json: `"abc"`,
},
}
)

View File

@@ -0,0 +1,184 @@
package runtime
import (
"bytes"
"encoding/json"
"fmt"
"io"
"reflect"
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
)
// JSONPb is a Marshaler which marshals/unmarshals into/from JSON
// with the "github.com/golang/protobuf/jsonpb".
// It supports fully functionality of protobuf unlike JSONBuiltin.
type JSONPb jsonpb.Marshaler
// ContentType always returns "application/json".
func (*JSONPb) ContentType() string {
return "application/json"
}
// Marshal marshals "v" into JSON
// Currently it can marshal only proto.Message.
// TODO(yugui) Support fields of primitive types in a message.
func (j *JSONPb) Marshal(v interface{}) ([]byte, error) {
if _, ok := v.(proto.Message); !ok {
return j.marshalNonProtoField(v)
}
var buf bytes.Buffer
if err := j.marshalTo(&buf, v); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (j *JSONPb) marshalTo(w io.Writer, v interface{}) error {
p, ok := v.(proto.Message)
if !ok {
buf, err := j.marshalNonProtoField(v)
if err != nil {
return err
}
_, err = w.Write(buf)
return err
}
return (*jsonpb.Marshaler)(j).Marshal(w, p)
}
// marshalNonProto marshals a non-message field of a protobuf message.
// This function does not correctly marshals arbitary data structure into JSON,
// but it is only capable of marshaling non-message field values of protobuf,
// i.e. primitive types, enums; pointers to primitives or enums; maps from
// integer/string types to primitives/enums/pointers to messages.
func (j *JSONPb) marshalNonProtoField(v interface{}) ([]byte, error) {
rv := reflect.ValueOf(v)
for rv.Kind() == reflect.Ptr {
if rv.IsNil() {
return []byte("null"), nil
}
rv = rv.Elem()
}
if rv.Kind() == reflect.Map {
m := make(map[string]*json.RawMessage)
for _, k := range rv.MapKeys() {
buf, err := j.Marshal(rv.MapIndex(k).Interface())
if err != nil {
return nil, err
}
m[fmt.Sprintf("%v", k.Interface())] = (*json.RawMessage)(&buf)
}
if j.Indent != "" {
return json.MarshalIndent(m, "", j.Indent)
}
return json.Marshal(m)
}
if enum, ok := rv.Interface().(protoEnum); ok && !j.EnumsAsInts {
return json.Marshal(enum.String())
}
return json.Marshal(rv.Interface())
}
// Unmarshal unmarshals JSON "data" into "v"
// Currently it can marshal only proto.Message.
// TODO(yugui) Support fields of primitive types in a message.
func (j *JSONPb) Unmarshal(data []byte, v interface{}) error {
return unmarshalJSONPb(data, v)
}
// NewDecoder returns a Decoder which reads JSON stream from "r".
func (j *JSONPb) NewDecoder(r io.Reader) Decoder {
d := json.NewDecoder(r)
return DecoderFunc(func(v interface{}) error { return decodeJSONPb(d, v) })
}
// NewEncoder returns an Encoder which writes JSON stream into "w".
func (j *JSONPb) NewEncoder(w io.Writer) Encoder {
return EncoderFunc(func(v interface{}) error { return j.marshalTo(w, v) })
}
func unmarshalJSONPb(data []byte, v interface{}) error {
d := json.NewDecoder(bytes.NewReader(data))
return decodeJSONPb(d, v)
}
func decodeJSONPb(d *json.Decoder, v interface{}) error {
p, ok := v.(proto.Message)
if !ok {
return decodeNonProtoField(d, v)
}
unmarshaler := &jsonpb.Unmarshaler{AllowUnknownFields: true}
return unmarshaler.UnmarshalNext(d, p)
}
func decodeNonProtoField(d *json.Decoder, v interface{}) error {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr {
return fmt.Errorf("%T is not a pointer", v)
}
for rv.Kind() == reflect.Ptr {
if rv.IsNil() {
rv.Set(reflect.New(rv.Type().Elem()))
}
if rv.Type().ConvertibleTo(typeProtoMessage) {
unmarshaler := &jsonpb.Unmarshaler{AllowUnknownFields: true}
return unmarshaler.UnmarshalNext(d, rv.Interface().(proto.Message))
}
rv = rv.Elem()
}
if rv.Kind() == reflect.Map {
if rv.IsNil() {
rv.Set(reflect.MakeMap(rv.Type()))
}
conv, ok := convFromType[rv.Type().Key().Kind()]
if !ok {
return fmt.Errorf("unsupported type of map field key: %v", rv.Type().Key())
}
m := make(map[string]*json.RawMessage)
if err := d.Decode(&m); err != nil {
return err
}
for k, v := range m {
result := conv.Call([]reflect.Value{reflect.ValueOf(k)})
if err := result[1].Interface(); err != nil {
return err.(error)
}
bk := result[0]
bv := reflect.New(rv.Type().Elem())
if err := unmarshalJSONPb([]byte(*v), bv.Interface()); err != nil {
return err
}
rv.SetMapIndex(bk, bv.Elem())
}
return nil
}
if _, ok := rv.Interface().(protoEnum); ok {
var repr interface{}
if err := d.Decode(&repr); err != nil {
return err
}
switch repr.(type) {
case string:
// TODO(yugui) Should use proto.StructProperties?
return fmt.Errorf("unmarshaling of symbolic enum %q not supported: %T", repr, rv.Interface())
case float64:
rv.Set(reflect.ValueOf(int32(repr.(float64))).Convert(rv.Type()))
return nil
default:
return fmt.Errorf("cannot assign %#v into Go type %T", repr, rv.Interface())
}
}
return d.Decode(v)
}
type protoEnum interface {
fmt.Stringer
EnumDescriptor() ([]byte, []int)
}
var typeProtoMessage = reflect.TypeOf((*proto.Message)(nil)).Elem()

View File

@@ -0,0 +1,606 @@
package runtime_test
import (
"bytes"
"reflect"
"strings"
"testing"
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/duration"
"github.com/golang/protobuf/ptypes/empty"
structpb "github.com/golang/protobuf/ptypes/struct"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/golang/protobuf/ptypes/wrappers"
"github.com/grpc-ecosystem/grpc-gateway/examples/examplepb"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
)
func TestJSONPbMarshal(t *testing.T) {
msg := examplepb.ABitOfEverything{
Uuid: "6EC2446F-7E89-4127-B3E6-5C05E6BECBA7",
Nested: []*examplepb.ABitOfEverything_Nested{
{
Name: "foo",
Amount: 12345,
},
},
Uint64Value: 0xFFFFFFFFFFFFFFFF,
EnumValue: examplepb.NumericEnum_ONE,
OneofValue: &examplepb.ABitOfEverything_OneofString{
OneofString: "bar",
},
MapValue: map[string]examplepb.NumericEnum{
"a": examplepb.NumericEnum_ONE,
"b": examplepb.NumericEnum_ZERO,
},
}
for _, spec := range []struct {
enumsAsInts, emitDefaults bool
indent string
origName bool
verifier func(json string)
}{
{
verifier: func(json string) {
if strings.ContainsAny(json, " \t\r\n") {
t.Errorf("strings.ContainsAny(%q, %q) = true; want false", json, " \t\r\n")
}
if !strings.Contains(json, "ONE") {
t.Errorf(`strings.Contains(%q, "ONE") = false; want true`, json)
}
if want := "uint64Value"; !strings.Contains(json, want) {
t.Errorf(`strings.Contains(%q, %q) = false; want true`, json, want)
}
},
},
{
enumsAsInts: true,
verifier: func(json string) {
if strings.Contains(json, "ONE") {
t.Errorf(`strings.Contains(%q, "ONE") = true; want false`, json)
}
},
},
{
emitDefaults: true,
verifier: func(json string) {
if want := `"sfixed32Value"`; !strings.Contains(json, want) {
t.Errorf(`strings.Contains(%q, %q) = false; want true`, json, want)
}
},
},
{
indent: "\t\t",
verifier: func(json string) {
if want := "\t\t\"amount\":"; !strings.Contains(json, want) {
t.Errorf(`strings.Contains(%q, %q) = false; want true`, json, want)
}
},
},
{
origName: true,
verifier: func(json string) {
if want := "uint64_value"; !strings.Contains(json, want) {
t.Errorf(`strings.Contains(%q, %q) = false; want true`, json, want)
}
},
},
} {
m := runtime.JSONPb{
EnumsAsInts: spec.enumsAsInts,
EmitDefaults: spec.emitDefaults,
Indent: spec.indent,
OrigName: spec.origName,
}
buf, err := m.Marshal(&msg)
if err != nil {
t.Errorf("m.Marshal(%v) failed with %v; want success; spec=%v", &msg, err, spec)
}
var got examplepb.ABitOfEverything
if err := jsonpb.UnmarshalString(string(buf), &got); err != nil {
t.Errorf("jsonpb.UnmarshalString(%q, &got) failed with %v; want success; spec=%v", string(buf), err, spec)
}
if want := msg; !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want %v; spec=%v", &got, &want, spec)
}
if spec.verifier != nil {
spec.verifier(string(buf))
}
}
}
func TestJSONPbMarshalFields(t *testing.T) {
var m runtime.JSONPb
for _, spec := range []struct {
val interface{}
want string
}{} {
buf, err := m.Marshal(spec.val)
if err != nil {
t.Errorf("m.Marshal(%#v) failed with %v; want success", spec.val, err)
}
if got, want := string(buf), spec.want; got != want {
t.Errorf("m.Marshal(%#v) = %q; want %q", spec.val, got, want)
}
}
m.EnumsAsInts = true
buf, err := m.Marshal(examplepb.NumericEnum_ONE)
if err != nil {
t.Errorf("m.Marshal(%#v) failed with %v; want success", examplepb.NumericEnum_ONE, err)
}
if got, want := string(buf), "1"; got != want {
t.Errorf("m.Marshal(%#v) = %q; want %q", examplepb.NumericEnum_ONE, got, want)
}
}
func TestJSONPbUnmarshal(t *testing.T) {
var (
m runtime.JSONPb
got examplepb.ABitOfEverything
)
for _, data := range []string{
`{
"uuid": "6EC2446F-7E89-4127-B3E6-5C05E6BECBA7",
"nested": [
{"name": "foo", "amount": 12345}
],
"uint64Value": 18446744073709551615,
"enumValue": "ONE",
"oneofString": "bar",
"mapValue": {
"a": 1,
"b": 0
}
}`,
`{
"uuid": "6EC2446F-7E89-4127-B3E6-5C05E6BECBA7",
"nested": [
{"name": "foo", "amount": 12345}
],
"uint64Value": "18446744073709551615",
"enumValue": "ONE",
"oneofString": "bar",
"mapValue": {
"a": 1,
"b": 0
}
}`,
`{
"uuid": "6EC2446F-7E89-4127-B3E6-5C05E6BECBA7",
"nested": [
{"name": "foo", "amount": 12345}
],
"uint64Value": 18446744073709551615,
"enumValue": 1,
"oneofString": "bar",
"mapValue": {
"a": 1,
"b": 0
}
}`,
} {
if err := m.Unmarshal([]byte(data), &got); err != nil {
t.Errorf("m.Unmarshal(%q, &got) failed with %v; want success", data, err)
}
want := examplepb.ABitOfEverything{
Uuid: "6EC2446F-7E89-4127-B3E6-5C05E6BECBA7",
Nested: []*examplepb.ABitOfEverything_Nested{
{
Name: "foo",
Amount: 12345,
},
},
Uint64Value: 0xFFFFFFFFFFFFFFFF,
EnumValue: examplepb.NumericEnum_ONE,
OneofValue: &examplepb.ABitOfEverything_OneofString{
OneofString: "bar",
},
MapValue: map[string]examplepb.NumericEnum{
"a": examplepb.NumericEnum_ONE,
"b": examplepb.NumericEnum_ZERO,
},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want = %v", &got, &want)
}
}
}
func TestJSONPbUnmarshalFields(t *testing.T) {
var m runtime.JSONPb
for _, fixt := range fieldFixtures {
if fixt.skipUnmarshal {
continue
}
dest := reflect.New(reflect.TypeOf(fixt.data))
if err := m.Unmarshal([]byte(fixt.json), dest.Interface()); err != nil {
t.Errorf("m.Unmarshal(%q, %T) failed with %v; want success", fixt.json, dest.Interface(), err)
}
if got, want := dest.Elem().Interface(), fixt.data; !reflect.DeepEqual(got, want) {
t.Errorf("dest = %#v; want %#v; input = %v", got, want, fixt.json)
}
}
}
func TestJSONPbEncoder(t *testing.T) {
msg := examplepb.ABitOfEverything{
Uuid: "6EC2446F-7E89-4127-B3E6-5C05E6BECBA7",
Nested: []*examplepb.ABitOfEverything_Nested{
{
Name: "foo",
Amount: 12345,
},
},
Uint64Value: 0xFFFFFFFFFFFFFFFF,
OneofValue: &examplepb.ABitOfEverything_OneofString{
OneofString: "bar",
},
MapValue: map[string]examplepb.NumericEnum{
"a": examplepb.NumericEnum_ONE,
"b": examplepb.NumericEnum_ZERO,
},
}
for _, spec := range []struct {
enumsAsInts, emitDefaults bool
indent string
origName bool
verifier func(json string)
}{
{
verifier: func(json string) {
if strings.ContainsAny(json, " \t\r\n") {
t.Errorf("strings.ContainsAny(%q, %q) = true; want false", json, " \t\r\n")
}
if strings.Contains(json, "ONE") {
t.Errorf(`strings.Contains(%q, "ONE") = true; want false`, json)
}
if want := "uint64Value"; !strings.Contains(json, want) {
t.Errorf(`strings.Contains(%q, %q) = false; want true`, json, want)
}
},
},
{
enumsAsInts: true,
verifier: func(json string) {
if strings.Contains(json, "ONE") {
t.Errorf(`strings.Contains(%q, "ONE") = true; want false`, json)
}
},
},
{
emitDefaults: true,
verifier: func(json string) {
if want := `"sfixed32Value"`; !strings.Contains(json, want) {
t.Errorf(`strings.Contains(%q, %q) = false; want true`, json, want)
}
},
},
{
indent: "\t\t",
verifier: func(json string) {
if want := "\t\t\"amount\":"; !strings.Contains(json, want) {
t.Errorf(`strings.Contains(%q, %q) = false; want true`, json, want)
}
},
},
{
origName: true,
verifier: func(json string) {
if want := "uint64_value"; !strings.Contains(json, want) {
t.Errorf(`strings.Contains(%q, %q) = false; want true`, json, want)
}
},
},
} {
m := runtime.JSONPb{
EnumsAsInts: spec.enumsAsInts,
EmitDefaults: spec.emitDefaults,
Indent: spec.indent,
OrigName: spec.origName,
}
var buf bytes.Buffer
enc := m.NewEncoder(&buf)
if err := enc.Encode(&msg); err != nil {
t.Errorf("enc.Encode(%v) failed with %v; want success; spec=%v", &msg, err, spec)
}
var got examplepb.ABitOfEverything
if err := jsonpb.UnmarshalString(buf.String(), &got); err != nil {
t.Errorf("jsonpb.UnmarshalString(%q, &got) failed with %v; want success; spec=%v", buf.String(), err, spec)
}
if want := msg; !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want %v; spec=%v", &got, &want, spec)
}
if spec.verifier != nil {
spec.verifier(buf.String())
}
}
}
func TestJSONPbEncoderFields(t *testing.T) {
var m runtime.JSONPb
for _, fixt := range fieldFixtures {
var buf bytes.Buffer
enc := m.NewEncoder(&buf)
if err := enc.Encode(fixt.data); err != nil {
t.Errorf("enc.Encode(%#v) failed with %v; want success", fixt.data, err)
}
if got, want := buf.String(), fixt.json; got != want {
t.Errorf("enc.Encode(%#v) = %q; want %q", fixt.data, got, want)
}
}
m.EnumsAsInts = true
buf, err := m.Marshal(examplepb.NumericEnum_ONE)
if err != nil {
t.Errorf("m.Marshal(%#v) failed with %v; want success", examplepb.NumericEnum_ONE, err)
}
if got, want := string(buf), "1"; got != want {
t.Errorf("m.Marshal(%#v) = %q; want %q", examplepb.NumericEnum_ONE, got, want)
}
}
func TestJSONPbDecoder(t *testing.T) {
var (
m runtime.JSONPb
got examplepb.ABitOfEverything
)
for _, data := range []string{
`{
"uuid": "6EC2446F-7E89-4127-B3E6-5C05E6BECBA7",
"nested": [
{"name": "foo", "amount": 12345}
],
"uint64Value": 18446744073709551615,
"enumValue": "ONE",
"oneofString": "bar",
"mapValue": {
"a": 1,
"b": 0
}
}`,
`{
"uuid": "6EC2446F-7E89-4127-B3E6-5C05E6BECBA7",
"nested": [
{"name": "foo", "amount": 12345}
],
"uint64Value": "18446744073709551615",
"enumValue": "ONE",
"oneofString": "bar",
"mapValue": {
"a": 1,
"b": 0
}
}`,
`{
"uuid": "6EC2446F-7E89-4127-B3E6-5C05E6BECBA7",
"nested": [
{"name": "foo", "amount": 12345}
],
"uint64Value": 18446744073709551615,
"enumValue": 1,
"oneofString": "bar",
"mapValue": {
"a": 1,
"b": 0
}
}`,
} {
r := strings.NewReader(data)
dec := m.NewDecoder(r)
if err := dec.Decode(&got); err != nil {
t.Errorf("m.Unmarshal(&got) failed with %v; want success; data=%q", err, data)
}
want := examplepb.ABitOfEverything{
Uuid: "6EC2446F-7E89-4127-B3E6-5C05E6BECBA7",
Nested: []*examplepb.ABitOfEverything_Nested{
{
Name: "foo",
Amount: 12345,
},
},
Uint64Value: 0xFFFFFFFFFFFFFFFF,
EnumValue: examplepb.NumericEnum_ONE,
OneofValue: &examplepb.ABitOfEverything_OneofString{
OneofString: "bar",
},
MapValue: map[string]examplepb.NumericEnum{
"a": examplepb.NumericEnum_ONE,
"b": examplepb.NumericEnum_ZERO,
},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got = %v; want = %v; data = %v", &got, &want, data)
}
}
}
func TestJSONPbDecoderFields(t *testing.T) {
var m runtime.JSONPb
for _, fixt := range fieldFixtures {
if fixt.skipUnmarshal {
continue
}
dest := reflect.New(reflect.TypeOf(fixt.data))
dec := m.NewDecoder(strings.NewReader(fixt.json))
if err := dec.Decode(dest.Interface()); err != nil {
t.Errorf("dec.Decode(%T) failed with %v; want success; input = %q", dest.Interface(), err, fixt.json)
}
if got, want := dest.Elem().Interface(), fixt.data; !reflect.DeepEqual(got, want) {
t.Errorf("dest = %#v; want %#v; input = %v", got, want, fixt.json)
}
}
}
var (
fieldFixtures = []struct {
data interface{}
json string
skipUnmarshal bool
}{
{data: int32(1), json: "1"},
{data: proto.Int32(1), json: "1"},
{data: int64(1), json: "1"},
{data: proto.Int64(1), json: "1"},
{data: uint32(1), json: "1"},
{data: proto.Uint32(1), json: "1"},
{data: uint64(1), json: "1"},
{data: proto.Uint64(1), json: "1"},
{data: "abc", json: `"abc"`},
{data: proto.String("abc"), json: `"abc"`},
{data: float32(1.5), json: "1.5"},
{data: proto.Float32(1.5), json: "1.5"},
{data: float64(1.5), json: "1.5"},
{data: proto.Float64(1.5), json: "1.5"},
{data: true, json: "true"},
{data: false, json: "false"},
{data: (*string)(nil), json: "null"},
{
data: examplepb.NumericEnum_ONE,
json: `"ONE"`,
// TODO(yugui) support unmarshaling of symbolic enum
skipUnmarshal: true,
},
{
data: (*examplepb.NumericEnum)(proto.Int32(int32(examplepb.NumericEnum_ONE))),
json: `"ONE"`,
// TODO(yugui) support unmarshaling of symbolic enum
skipUnmarshal: true,
},
{
data: map[string]int32{
"foo": 1,
},
json: `{"foo":1}`,
},
{
data: map[string]*examplepb.SimpleMessage{
"foo": {Id: "bar"},
},
json: `{"foo":{"id":"bar"}}`,
},
{
data: map[int32]*examplepb.SimpleMessage{
1: {Id: "foo"},
},
json: `{"1":{"id":"foo"}}`,
},
{
data: map[bool]*examplepb.SimpleMessage{
true: {Id: "foo"},
},
json: `{"true":{"id":"foo"}}`,
},
{
data: &duration.Duration{
Seconds: 123,
Nanos: 456000000,
},
json: `"123.456s"`,
},
{
data: &timestamp.Timestamp{
Seconds: 1462875553,
Nanos: 123000000,
},
json: `"2016-05-10T10:19:13.123Z"`,
},
{
data: new(empty.Empty),
json: "{}",
},
// TODO(yugui) Enable unmarshaling of the following examples
// once jsonpb supports them.
{
data: &structpb.Value{
Kind: new(structpb.Value_NullValue),
},
json: "null",
skipUnmarshal: true,
},
{
data: &structpb.Value{
Kind: &structpb.Value_NumberValue{
NumberValue: 123.4,
},
},
json: "123.4",
skipUnmarshal: true,
},
{
data: &structpb.Value{
Kind: &structpb.Value_StringValue{
StringValue: "abc",
},
},
json: `"abc"`,
skipUnmarshal: true,
},
{
data: &structpb.Value{
Kind: &structpb.Value_BoolValue{
BoolValue: true,
},
},
json: "true",
skipUnmarshal: true,
},
{
data: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo_bar": {
Kind: &structpb.Value_BoolValue{
BoolValue: true,
},
},
},
},
json: `{"foo_bar":true}`,
skipUnmarshal: true,
},
{
data: &wrappers.BoolValue{Value: true},
json: "true",
},
{
data: &wrappers.DoubleValue{Value: 123.456},
json: "123.456",
},
{
data: &wrappers.FloatValue{Value: 123.456},
json: "123.456",
},
{
data: &wrappers.Int32Value{Value: -123},
json: "-123",
},
{
data: &wrappers.Int64Value{Value: -123},
json: `"-123"`,
},
{
data: &wrappers.UInt32Value{Value: 123},
json: "123",
},
{
data: &wrappers.UInt64Value{Value: 123},
json: `"123"`,
},
// TODO(yugui) Add other well-known types once jsonpb supports them
}
)

View File

@@ -0,0 +1,42 @@
package runtime
import (
"io"
)
// Marshaler defines a conversion between byte sequence and gRPC payloads / fields.
type Marshaler interface {
// Marshal marshals "v" into byte sequence.
Marshal(v interface{}) ([]byte, error)
// Unmarshal unmarshals "data" into "v".
// "v" must be a pointer value.
Unmarshal(data []byte, v interface{}) error
// NewDecoder returns a Decoder which reads byte sequence from "r".
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
}
// Decoder decodes a byte sequence
type Decoder interface {
Decode(v interface{}) error
}
// Encoder encodes gRPC payloads / fields into byte sequence.
type Encoder interface {
Encode(v interface{}) error
}
// DecoderFunc adapts an decoder function into Decoder.
type DecoderFunc func(v interface{}) error
// Decode delegates invocations to the underlying function itself.
func (f DecoderFunc) Decode(v interface{}) error { return f(v) }
// EncoderFunc adapts an encoder function into Encoder
type EncoderFunc func(v interface{}) error
// Encode delegates invocations to the underlying function itself.
func (f EncoderFunc) Encode(v interface{}) error { return f(v) }

View File

@@ -0,0 +1,91 @@
package runtime
import (
"errors"
"net/http"
)
// MIMEWildcard is the fallback MIME type used for requests which do not match
// a registered MIME type.
const MIMEWildcard = "*"
var (
acceptHeader = http.CanonicalHeaderKey("Accept")
contentTypeHeader = http.CanonicalHeaderKey("Content-Type")
defaultMarshaler = &JSONPb{OrigName: true}
)
// MarshalerForRequest returns the inbound/outbound marshalers for this request.
// It checks the registry on the ServeMux for the MIME type set by the Content-Type header.
// If it isn't set (or the request Content-Type is empty), checks for "*".
// If there are multiple Content-Type headers set, choose the first one that it can
// exactly match in the registry.
// Otherwise, it follows the above logic for "*"/InboundMarshaler/OutboundMarshaler.
func MarshalerForRequest(mux *ServeMux, r *http.Request) (inbound Marshaler, outbound Marshaler) {
for _, acceptVal := range r.Header[acceptHeader] {
if m, ok := mux.marshalers.mimeMap[acceptVal]; ok {
outbound = m
break
}
}
for _, contentTypeVal := range r.Header[contentTypeHeader] {
if m, ok := mux.marshalers.mimeMap[contentTypeVal]; ok {
inbound = m
break
}
}
if inbound == nil {
inbound = mux.marshalers.mimeMap[MIMEWildcard]
}
if outbound == nil {
outbound = inbound
}
return inbound, outbound
}
// marshalerRegistry is a mapping from MIME types to Marshalers.
type marshalerRegistry struct {
mimeMap map[string]Marshaler
}
// add adds a marshaler for a case-sensitive MIME type string ("*" to match any
// MIME type).
func (m marshalerRegistry) add(mime string, marshaler Marshaler) error {
if len(mime) == 0 {
return errors.New("empty MIME type")
}
m.mimeMap[mime] = marshaler
return nil
}
// makeMarshalerMIMERegistry returns a new registry of marshalers.
// It allows for a mapping of case-sensitive Content-Type MIME type string to runtime.Marshaler interfaces.
//
// For example, you could allow the client to specify the use of the runtime.JSONPb marshaler
// with a "applicaton/jsonpb" Content-Type and the use of the runtime.JSONBuiltin marshaler
// with a "application/json" Content-Type.
// "*" can be used to match any Content-Type.
// This can be attached to a ServerMux with the marshaler option.
func makeMarshalerMIMERegistry() marshalerRegistry {
return marshalerRegistry{
mimeMap: map[string]Marshaler{
MIMEWildcard: defaultMarshaler,
},
}
}
// WithMarshalerOption returns a ServeMuxOption which associates inbound and outbound
// Marshalers to a MIME type in mux.
func WithMarshalerOption(mime string, marshaler Marshaler) ServeMuxOption {
return func(mux *ServeMux) {
if err := mux.marshalers.add(mime, marshaler); err != nil {
panic(err)
}
}
}

View File

@@ -0,0 +1,107 @@
package runtime_test
import (
"errors"
"io"
"net/http"
"testing"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
)
func TestMarshalerForRequest(t *testing.T) {
r, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil {
t.Fatalf(`http.NewRequest("GET", "http://example.com", nil) failed with %v; want success`, err)
}
r.Header.Set("Accept", "application/x-out")
r.Header.Set("Content-Type", "application/x-in")
mux := runtime.NewServeMux()
in, out := runtime.MarshalerForRequest(mux, r)
if _, ok := in.(*runtime.JSONPb); !ok {
t.Errorf("in = %#v; want a runtime.JSONPb", in)
}
if _, ok := out.(*runtime.JSONPb); !ok {
t.Errorf("out = %#v; want a runtime.JSONPb", in)
}
var marshalers [3]dummyMarshaler
specs := []struct {
opt runtime.ServeMuxOption
wantIn runtime.Marshaler
wantOut runtime.Marshaler
}{
{
opt: runtime.WithMarshalerOption(runtime.MIMEWildcard, &marshalers[0]),
wantIn: &marshalers[0],
wantOut: &marshalers[0],
},
{
opt: runtime.WithMarshalerOption("application/x-in", &marshalers[1]),
wantIn: &marshalers[1],
wantOut: &marshalers[0],
},
{
opt: runtime.WithMarshalerOption("application/x-out", &marshalers[2]),
wantIn: &marshalers[1],
wantOut: &marshalers[2],
},
}
for i, spec := range specs {
var opts []runtime.ServeMuxOption
for _, s := range specs[:i+1] {
opts = append(opts, s.opt)
}
mux = runtime.NewServeMux(opts...)
in, out = runtime.MarshalerForRequest(mux, r)
if got, want := in, spec.wantIn; got != want {
t.Errorf("in = %#v; want %#v", got, want)
}
if got, want := out, spec.wantOut; got != want {
t.Errorf("out = %#v; want %#v", got, want)
}
}
r.Header.Set("Content-Type", "application/x-another")
in, out = runtime.MarshalerForRequest(mux, r)
if got, want := in, &marshalers[1]; got != want {
t.Errorf("in = %#v; want %#v", got, want)
}
if got, want := out, &marshalers[0]; got != want {
t.Errorf("out = %#v; want %#v", got, want)
}
}
type dummyMarshaler struct{}
func (dummyMarshaler) ContentType() string { return "" }
func (dummyMarshaler) Marshal(interface{}) ([]byte, error) {
return nil, errors.New("not implemented")
}
func (dummyMarshaler) Unmarshal([]byte, interface{}) error {
return errors.New("not implemented")
}
func (dummyMarshaler) NewDecoder(r io.Reader) runtime.Decoder {
return dummyDecoder{}
}
func (dummyMarshaler) NewEncoder(w io.Writer) runtime.Encoder {
return dummyEncoder{}
}
type dummyDecoder struct{}
func (dummyDecoder) Decode(interface{}) error {
return errors.New("not implemented")
}
type dummyEncoder struct{}
func (dummyEncoder) Encode(interface{}) error {
return errors.New("not implemented")
}

View File

@@ -0,0 +1,132 @@
package runtime
import (
"net/http"
"strings"
"golang.org/x/net/context"
"github.com/golang/protobuf/proto"
)
// A HandlerFunc handles a specific pair of path pattern and HTTP method.
type HandlerFunc func(w http.ResponseWriter, r *http.Request, pathParams map[string]string)
// ServeMux is a request multiplexer for grpc-gateway.
// It matches http requests to patterns and invokes the corresponding handler.
type ServeMux struct {
// handlers maps HTTP method to a list of handlers.
handlers map[string][]handler
forwardResponseOptions []func(context.Context, http.ResponseWriter, proto.Message) error
marshalers marshalerRegistry
}
// ServeMuxOption is an option that can be given to a ServeMux on construction.
type ServeMuxOption func(*ServeMux)
// WithForwardResponseOption returns a ServeMuxOption representing the forwardResponseOption.
//
// forwardResponseOption is an option that will be called on the relevant context.Context,
// http.ResponseWriter, and proto.Message before every forwarded response.
//
// The message may be nil in the case where just a header is being sent.
func WithForwardResponseOption(forwardResponseOption func(context.Context, http.ResponseWriter, proto.Message) error) ServeMuxOption {
return func(serveMux *ServeMux) {
serveMux.forwardResponseOptions = append(serveMux.forwardResponseOptions, forwardResponseOption)
}
}
// NewServeMux returns a new ServeMux whose internal mapping is empty.
func NewServeMux(opts ...ServeMuxOption) *ServeMux {
serveMux := &ServeMux{
handlers: make(map[string][]handler),
forwardResponseOptions: make([]func(context.Context, http.ResponseWriter, proto.Message) error, 0),
marshalers: makeMarshalerMIMERegistry(),
}
for _, opt := range opts {
opt(serveMux)
}
return serveMux
}
// Handle associates "h" to the pair of HTTP method and path pattern.
func (s *ServeMux) Handle(meth string, pat Pattern, h HandlerFunc) {
s.handlers[meth] = append(s.handlers[meth], handler{pat: pat, h: h})
}
// ServeHTTP dispatches the request to the first handler whose pattern matches to r.Method and r.Path.
func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
if !strings.HasPrefix(path, "/") {
OtherErrorHandler(w, r, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
components := strings.Split(path[1:], "/")
l := len(components)
var verb string
if idx := strings.LastIndex(components[l-1], ":"); idx == 0 {
OtherErrorHandler(w, r, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
} else if idx > 0 {
c := components[l-1]
components[l-1], verb = c[:idx], c[idx+1:]
}
if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && isPathLengthFallback(r) {
r.Method = strings.ToUpper(override)
if err := r.ParseForm(); err != nil {
OtherErrorHandler(w, r, err.Error(), http.StatusBadRequest)
return
}
}
for _, h := range s.handlers[r.Method] {
pathParams, err := h.pat.Match(components, verb)
if err != nil {
continue
}
h.h(w, r, pathParams)
return
}
// lookup other methods to handle fallback from GET to POST and
// to determine if it is MethodNotAllowed or NotFound.
for m, handlers := range s.handlers {
if m == r.Method {
continue
}
for _, h := range handlers {
pathParams, err := h.pat.Match(components, verb)
if err != nil {
continue
}
// X-HTTP-Method-Override is optional. Always allow fallback to POST.
if isPathLengthFallback(r) {
if err := r.ParseForm(); err != nil {
OtherErrorHandler(w, r, err.Error(), http.StatusBadRequest)
return
}
h.h(w, r, pathParams)
return
}
OtherErrorHandler(w, r, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
}
OtherErrorHandler(w, r, http.StatusText(http.StatusNotFound), http.StatusNotFound)
}
// GetForwardResponseOptions returns the ForwardResponseOptions associated with this ServeMux.
func (s *ServeMux) GetForwardResponseOptions() []func(context.Context, http.ResponseWriter, proto.Message) error {
return s.forwardResponseOptions
}
func isPathLengthFallback(r *http.Request) bool {
return r.Method == "POST" && r.Header.Get("Content-Type") == "application/x-www-form-urlencoded"
}
type handler struct {
pat Pattern
h HandlerFunc
}

View File

@@ -0,0 +1,213 @@
package runtime_test
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/grpc-ecosystem/grpc-gateway/utilities"
)
func TestMuxServeHTTP(t *testing.T) {
type stubPattern struct {
method string
ops []int
pool []string
verb string
}
for _, spec := range []struct {
patterns []stubPattern
reqMethod string
reqPath string
headers map[string]string
respStatus int
respContent string
}{
{
patterns: nil,
reqMethod: "GET",
reqPath: "/",
respStatus: http.StatusNotFound,
},
{
patterns: []stubPattern{
{
method: "GET",
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"foo"},
},
},
reqMethod: "GET",
reqPath: "/foo",
respStatus: http.StatusOK,
respContent: "GET /foo",
},
{
patterns: []stubPattern{
{
method: "GET",
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"foo"},
},
},
reqMethod: "GET",
reqPath: "/bar",
respStatus: http.StatusNotFound,
},
{
patterns: []stubPattern{
{
method: "GET",
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"foo"},
},
{
method: "GET",
ops: []int{int(utilities.OpPush), 0},
},
},
reqMethod: "GET",
reqPath: "/foo",
respStatus: http.StatusOK,
respContent: "GET /foo",
},
{
patterns: []stubPattern{
{
method: "GET",
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"foo"},
},
{
method: "POST",
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"foo"},
},
},
reqMethod: "POST",
reqPath: "/foo",
respStatus: http.StatusOK,
respContent: "POST /foo",
},
{
patterns: []stubPattern{
{
method: "GET",
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"foo"},
},
},
reqMethod: "DELETE",
reqPath: "/foo",
respStatus: http.StatusMethodNotAllowed,
},
{
patterns: []stubPattern{
{
method: "GET",
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"foo"},
},
},
reqMethod: "POST",
reqPath: "/foo",
headers: map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
},
respStatus: http.StatusOK,
respContent: "GET /foo",
},
{
patterns: []stubPattern{
{
method: "GET",
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"foo"},
},
{
method: "POST",
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"foo"},
},
},
reqMethod: "POST",
reqPath: "/foo",
headers: map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
"X-HTTP-Method-Override": "GET",
},
respStatus: http.StatusOK,
respContent: "GET /foo",
},
{
patterns: []stubPattern{
{
method: "GET",
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"foo"},
},
},
reqMethod: "POST",
reqPath: "/foo",
headers: map[string]string{
"Content-Type": "application/json",
},
respStatus: http.StatusMethodNotAllowed,
},
{
patterns: []stubPattern{
{
method: "POST",
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"foo"},
verb: "bar",
},
},
reqMethod: "POST",
reqPath: "/foo:bar",
headers: map[string]string{
"Content-Type": "application/json",
},
respStatus: http.StatusOK,
respContent: "POST /foo:bar",
},
} {
mux := runtime.NewServeMux()
for _, p := range spec.patterns {
func(p stubPattern) {
pat, err := runtime.NewPattern(1, p.ops, p.pool, p.verb)
if err != nil {
t.Fatalf("runtime.NewPattern(1, %#v, %#v, %q) failed with %v; want success", p.ops, p.pool, p.verb, err)
}
mux.Handle(p.method, pat, func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
fmt.Fprintf(w, "%s %s", p.method, pat.String())
})
}(p)
}
url := fmt.Sprintf("http://host.example%s", spec.reqPath)
r, err := http.NewRequest(spec.reqMethod, url, bytes.NewReader(nil))
if err != nil {
t.Fatalf("http.NewRequest(%q, %q, nil) failed with %v; want success", spec.reqMethod, url, err)
}
for name, value := range spec.headers {
r.Header.Set(name, value)
}
w := httptest.NewRecorder()
mux.ServeHTTP(w, r)
if got, want := w.Code, spec.respStatus; got != want {
t.Errorf("w.Code = %d; want %d; patterns=%v; req=%v", got, want, spec.patterns, r)
}
if spec.respContent != "" {
if got, want := w.Body.String(), spec.respContent; got != want {
t.Errorf("w.Body = %q; want %q; patterns=%v; req=%v", got, want, spec.patterns, r)
}
}
}
}

View File

@@ -0,0 +1,227 @@
package runtime
import (
"errors"
"fmt"
"strings"
"github.com/grpc-ecosystem/grpc-gateway/utilities"
"google.golang.org/grpc/grpclog"
)
var (
// ErrNotMatch indicates that the given HTTP request path does not match to the pattern.
ErrNotMatch = errors.New("not match to the path pattern")
// ErrInvalidPattern indicates that the given definition of Pattern is not valid.
ErrInvalidPattern = errors.New("invalid pattern")
)
type op struct {
code utilities.OpCode
operand int
}
// Pattern is a template pattern of http request paths defined in github.com/googleapis/googleapis/google/api/http.proto.
type Pattern struct {
// ops is a list of operations
ops []op
// pool is a constant pool indexed by the operands or vars.
pool []string
// vars is a list of variables names to be bound by this pattern
vars []string
// stacksize is the max depth of the stack
stacksize int
// tailLen is the length of the fixed-size segments after a deep wildcard
tailLen int
// verb is the VERB part of the path pattern. It is empty if the pattern does not have VERB part.
verb string
}
// NewPattern returns a new Pattern from the given definition values.
// "ops" is a sequence of op codes. "pool" is a constant pool.
// "verb" is the verb part of the pattern. It is empty if the pattern does not have the part.
// "version" must be 1 for now.
// It returns an error if the given definition is invalid.
func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) {
if version != 1 {
grpclog.Printf("unsupported version: %d", version)
return Pattern{}, ErrInvalidPattern
}
l := len(ops)
if l%2 != 0 {
grpclog.Printf("odd number of ops codes: %d", l)
return Pattern{}, ErrInvalidPattern
}
var (
typedOps []op
stack, maxstack int
tailLen int
pushMSeen bool
vars []string
)
for i := 0; i < l; i += 2 {
op := op{code: utilities.OpCode(ops[i]), operand: ops[i+1]}
switch op.code {
case utilities.OpNop:
continue
case utilities.OpPush:
if pushMSeen {
tailLen++
}
stack++
case utilities.OpPushM:
if pushMSeen {
grpclog.Printf("pushM appears twice")
return Pattern{}, ErrInvalidPattern
}
pushMSeen = true
stack++
case utilities.OpLitPush:
if op.operand < 0 || len(pool) <= op.operand {
grpclog.Printf("negative literal index: %d", op.operand)
return Pattern{}, ErrInvalidPattern
}
if pushMSeen {
tailLen++
}
stack++
case utilities.OpConcatN:
if op.operand <= 0 {
grpclog.Printf("negative concat size: %d", op.operand)
return Pattern{}, ErrInvalidPattern
}
stack -= op.operand
if stack < 0 {
grpclog.Print("stack underflow")
return Pattern{}, ErrInvalidPattern
}
stack++
case utilities.OpCapture:
if op.operand < 0 || len(pool) <= op.operand {
grpclog.Printf("variable name index out of bound: %d", op.operand)
return Pattern{}, ErrInvalidPattern
}
v := pool[op.operand]
op.operand = len(vars)
vars = append(vars, v)
stack--
if stack < 0 {
grpclog.Printf("stack underflow")
return Pattern{}, ErrInvalidPattern
}
default:
grpclog.Printf("invalid opcode: %d", op.code)
return Pattern{}, ErrInvalidPattern
}
if maxstack < stack {
maxstack = stack
}
typedOps = append(typedOps, op)
}
return Pattern{
ops: typedOps,
pool: pool,
vars: vars,
stacksize: maxstack,
tailLen: tailLen,
verb: verb,
}, nil
}
// MustPattern is a helper function which makes it easier to call NewPattern in variable initialization.
func MustPattern(p Pattern, err error) Pattern {
if err != nil {
grpclog.Fatalf("Pattern initialization failed: %v", err)
}
return p
}
// Match examines components if it matches to the Pattern.
// If it matches, the function returns a mapping from field paths to their captured values.
// If otherwise, the function returns an error.
func (p Pattern) Match(components []string, verb string) (map[string]string, error) {
if p.verb != verb {
return nil, ErrNotMatch
}
var pos int
stack := make([]string, 0, p.stacksize)
captured := make([]string, len(p.vars))
l := len(components)
for _, op := range p.ops {
switch op.code {
case utilities.OpNop:
continue
case utilities.OpPush, utilities.OpLitPush:
if pos >= l {
return nil, ErrNotMatch
}
c := components[pos]
if op.code == utilities.OpLitPush {
if lit := p.pool[op.operand]; c != lit {
return nil, ErrNotMatch
}
}
stack = append(stack, c)
pos++
case utilities.OpPushM:
end := len(components)
if end < pos+p.tailLen {
return nil, ErrNotMatch
}
end -= p.tailLen
stack = append(stack, strings.Join(components[pos:end], "/"))
pos = end
case utilities.OpConcatN:
n := op.operand
l := len(stack) - n
stack = append(stack[:l], strings.Join(stack[l:], "/"))
case utilities.OpCapture:
n := len(stack) - 1
captured[op.operand] = stack[n]
stack = stack[:n]
}
}
if pos < l {
return nil, ErrNotMatch
}
bindings := make(map[string]string)
for i, val := range captured {
bindings[p.vars[i]] = val
}
return bindings, nil
}
// Verb returns the verb part of the Pattern.
func (p Pattern) Verb() string { return p.verb }
func (p Pattern) String() string {
var stack []string
for _, op := range p.ops {
switch op.code {
case utilities.OpNop:
continue
case utilities.OpPush:
stack = append(stack, "*")
case utilities.OpLitPush:
stack = append(stack, p.pool[op.operand])
case utilities.OpPushM:
stack = append(stack, "**")
case utilities.OpConcatN:
n := op.operand
l := len(stack) - n
stack = append(stack[:l], strings.Join(stack[l:], "/"))
case utilities.OpCapture:
n := len(stack) - 1
stack[n] = fmt.Sprintf("{%s=%s}", p.vars[op.operand], stack[n])
}
}
segs := strings.Join(stack, "/")
if p.verb != "" {
return fmt.Sprintf("/%s:%s", segs, p.verb)
}
return "/" + segs
}

View File

@@ -0,0 +1,590 @@
package runtime
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/grpc-ecosystem/grpc-gateway/utilities"
)
const (
validVersion = 1
anything = 0
)
func TestNewPattern(t *testing.T) {
for _, spec := range []struct {
ops []int
pool []string
verb string
stackSizeWant, tailLenWant int
}{
{},
{
ops: []int{int(utilities.OpNop), anything},
stackSizeWant: 0,
tailLenWant: 0,
},
{
ops: []int{int(utilities.OpPush), anything},
stackSizeWant: 1,
tailLenWant: 0,
},
{
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"abc"},
stackSizeWant: 1,
tailLenWant: 0,
},
{
ops: []int{int(utilities.OpPushM), anything},
stackSizeWant: 1,
tailLenWant: 0,
},
{
ops: []int{
int(utilities.OpPush), anything,
int(utilities.OpConcatN), 1,
},
stackSizeWant: 1,
tailLenWant: 0,
},
{
ops: []int{
int(utilities.OpPush), anything,
int(utilities.OpConcatN), 1,
int(utilities.OpCapture), 0,
},
pool: []string{"abc"},
stackSizeWant: 1,
tailLenWant: 0,
},
{
ops: []int{
int(utilities.OpPush), anything,
int(utilities.OpLitPush), 0,
int(utilities.OpLitPush), 1,
int(utilities.OpPushM), anything,
int(utilities.OpConcatN), 2,
int(utilities.OpCapture), 2,
},
pool: []string{"lit1", "lit2", "var1"},
stackSizeWant: 4,
tailLenWant: 0,
},
{
ops: []int{
int(utilities.OpPushM), anything,
int(utilities.OpConcatN), 1,
int(utilities.OpCapture), 2,
int(utilities.OpLitPush), 0,
int(utilities.OpLitPush), 1,
},
pool: []string{"lit1", "lit2", "var1"},
stackSizeWant: 2,
tailLenWant: 2,
},
{
ops: []int{
int(utilities.OpLitPush), 0,
int(utilities.OpLitPush), 1,
int(utilities.OpPushM), anything,
int(utilities.OpLitPush), 2,
int(utilities.OpConcatN), 3,
int(utilities.OpLitPush), 3,
int(utilities.OpCapture), 4,
},
pool: []string{"lit1", "lit2", "lit3", "lit4", "var1"},
stackSizeWant: 4,
tailLenWant: 2,
},
{
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"abc"},
verb: "LOCK",
stackSizeWant: 1,
tailLenWant: 0,
},
} {
pat, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb)
if err != nil {
t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, spec.verb, err)
continue
}
if got, want := pat.stacksize, spec.stackSizeWant; got != want {
t.Errorf("pat.stacksize = %d; want %d", got, want)
}
if got, want := pat.tailLen, spec.tailLenWant; got != want {
t.Errorf("pat.stacksize = %d; want %d", got, want)
}
}
}
func TestNewPatternWithWrongOp(t *testing.T) {
for _, spec := range []struct {
ops []int
pool []string
verb string
}{
{
// op code out of bound
ops: []int{-1, anything},
},
{
// op code out of bound
ops: []int{int(utilities.OpEnd), 0},
},
{
// odd number of items
ops: []int{int(utilities.OpPush)},
},
{
// negative index
ops: []int{int(utilities.OpLitPush), -1},
pool: []string{"abc"},
},
{
// index out of bound
ops: []int{int(utilities.OpLitPush), 1},
pool: []string{"abc"},
},
{
// negative # of segments
ops: []int{int(utilities.OpConcatN), -1},
pool: []string{"abc"},
},
{
// negative index
ops: []int{int(utilities.OpCapture), -1},
pool: []string{"abc"},
},
{
// index out of bound
ops: []int{int(utilities.OpCapture), 1},
pool: []string{"abc"},
},
{
// pushM appears twice
ops: []int{
int(utilities.OpPushM), anything,
int(utilities.OpLitPush), 0,
int(utilities.OpPushM), anything,
},
pool: []string{"abc"},
},
} {
_, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb)
if err == nil {
t.Errorf("NewPattern(%d, %v, %q, %q) succeeded; want failure with %v", validVersion, spec.ops, spec.pool, spec.verb, ErrInvalidPattern)
continue
}
if err != ErrInvalidPattern {
t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want failure with %v", validVersion, spec.ops, spec.pool, spec.verb, err, ErrInvalidPattern)
continue
}
}
}
func TestNewPatternWithStackUnderflow(t *testing.T) {
for _, spec := range []struct {
ops []int
pool []string
verb string
}{
{
ops: []int{int(utilities.OpConcatN), 1},
},
{
ops: []int{int(utilities.OpCapture), 0},
pool: []string{"abc"},
},
} {
_, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb)
if err == nil {
t.Errorf("NewPattern(%d, %v, %q, %q) succeeded; want failure with %v", validVersion, spec.ops, spec.pool, spec.verb, ErrInvalidPattern)
continue
}
if err != ErrInvalidPattern {
t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want failure with %v", validVersion, spec.ops, spec.pool, spec.verb, err, ErrInvalidPattern)
continue
}
}
}
func TestMatch(t *testing.T) {
for _, spec := range []struct {
ops []int
pool []string
verb string
match []string
notMatch []string
}{
{
match: []string{""},
notMatch: []string{"example"},
},
{
ops: []int{int(utilities.OpNop), anything},
match: []string{""},
notMatch: []string{"example", "path/to/example"},
},
{
ops: []int{int(utilities.OpPush), anything},
match: []string{"abc", "def"},
notMatch: []string{"", "abc/def"},
},
{
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"v1"},
match: []string{"v1"},
notMatch: []string{"", "v2"},
},
{
ops: []int{int(utilities.OpPushM), anything},
match: []string{"", "abc", "abc/def", "abc/def/ghi"},
},
{
ops: []int{
int(utilities.OpPushM), anything,
int(utilities.OpLitPush), 0,
},
pool: []string{"tail"},
match: []string{"tail", "abc/tail", "abc/def/tail"},
notMatch: []string{
"", "abc", "abc/def",
"tail/extra", "abc/tail/extra", "abc/def/tail/extra",
},
},
{
ops: []int{
int(utilities.OpLitPush), 0,
int(utilities.OpLitPush), 1,
int(utilities.OpPush), anything,
int(utilities.OpConcatN), 1,
int(utilities.OpCapture), 2,
},
pool: []string{"v1", "bucket", "name"},
match: []string{"v1/bucket/my-bucket", "v1/bucket/our-bucket"},
notMatch: []string{
"",
"v1",
"v1/bucket",
"v2/bucket/my-bucket",
"v1/pubsub/my-topic",
},
},
{
ops: []int{
int(utilities.OpLitPush), 0,
int(utilities.OpLitPush), 1,
int(utilities.OpPushM), anything,
int(utilities.OpConcatN), 2,
int(utilities.OpCapture), 2,
},
pool: []string{"v1", "o", "name"},
match: []string{
"v1/o",
"v1/o/my-bucket",
"v1/o/our-bucket",
"v1/o/my-bucket/dir",
"v1/o/my-bucket/dir/dir2",
"v1/o/my-bucket/dir/dir2/obj",
},
notMatch: []string{
"",
"v1",
"v2/o/my-bucket",
"v1/b/my-bucket",
},
},
{
ops: []int{
int(utilities.OpLitPush), 0,
int(utilities.OpLitPush), 1,
int(utilities.OpPush), anything,
int(utilities.OpConcatN), 2,
int(utilities.OpCapture), 2,
int(utilities.OpLitPush), 3,
int(utilities.OpPush), anything,
int(utilities.OpConcatN), 1,
int(utilities.OpCapture), 4,
},
pool: []string{"v2", "b", "name", "o", "oname"},
match: []string{
"v2/b/my-bucket/o/obj",
"v2/b/our-bucket/o/obj",
"v2/b/my-bucket/o/dir",
},
notMatch: []string{
"",
"v2",
"v2/b",
"v2/b/my-bucket",
"v2/b/my-bucket/o",
},
},
{
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"v1"},
verb: "LOCK",
match: []string{"v1:LOCK"},
notMatch: []string{"v1", "LOCK"},
},
} {
pat, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb)
if err != nil {
t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, spec.verb, err)
continue
}
for _, path := range spec.match {
_, err = pat.Match(segments(path))
if err != nil {
t.Errorf("pat.Match(%q) failed with %v; want success; pattern = (%v, %q)", path, err, spec.ops, spec.pool)
}
}
for _, path := range spec.notMatch {
_, err = pat.Match(segments(path))
if err == nil {
t.Errorf("pat.Match(%q) succeeded; want failure with %v; pattern = (%v, %q)", path, ErrNotMatch, spec.ops, spec.pool)
continue
}
if err != ErrNotMatch {
t.Errorf("pat.Match(%q) failed with %v; want failure with %v; pattern = (%v, %q)", spec.notMatch, err, ErrNotMatch, spec.ops, spec.pool)
}
}
}
}
func TestMatchWithBinding(t *testing.T) {
for _, spec := range []struct {
ops []int
pool []string
path string
verb string
want map[string]string
}{
{
want: make(map[string]string),
},
{
ops: []int{int(utilities.OpNop), anything},
want: make(map[string]string),
},
{
ops: []int{int(utilities.OpPush), anything},
path: "abc",
want: make(map[string]string),
},
{
ops: []int{int(utilities.OpPush), anything},
verb: "LOCK",
path: "abc:LOCK",
want: make(map[string]string),
},
{
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"endpoint"},
path: "endpoint",
want: make(map[string]string),
},
{
ops: []int{int(utilities.OpPushM), anything},
path: "abc/def/ghi",
want: make(map[string]string),
},
{
ops: []int{
int(utilities.OpLitPush), 0,
int(utilities.OpLitPush), 1,
int(utilities.OpPush), anything,
int(utilities.OpConcatN), 1,
int(utilities.OpCapture), 2,
},
pool: []string{"v1", "bucket", "name"},
path: "v1/bucket/my-bucket",
want: map[string]string{
"name": "my-bucket",
},
},
{
ops: []int{
int(utilities.OpLitPush), 0,
int(utilities.OpLitPush), 1,
int(utilities.OpPush), anything,
int(utilities.OpConcatN), 1,
int(utilities.OpCapture), 2,
},
pool: []string{"v1", "bucket", "name"},
verb: "LOCK",
path: "v1/bucket/my-bucket:LOCK",
want: map[string]string{
"name": "my-bucket",
},
},
{
ops: []int{
int(utilities.OpLitPush), 0,
int(utilities.OpLitPush), 1,
int(utilities.OpPushM), anything,
int(utilities.OpConcatN), 2,
int(utilities.OpCapture), 2,
},
pool: []string{"v1", "o", "name"},
path: "v1/o/my-bucket/dir/dir2/obj",
want: map[string]string{
"name": "o/my-bucket/dir/dir2/obj",
},
},
{
ops: []int{
int(utilities.OpLitPush), 0,
int(utilities.OpLitPush), 1,
int(utilities.OpPushM), anything,
int(utilities.OpLitPush), 2,
int(utilities.OpConcatN), 3,
int(utilities.OpCapture), 4,
int(utilities.OpLitPush), 3,
},
pool: []string{"v1", "o", ".ext", "tail", "name"},
path: "v1/o/my-bucket/dir/dir2/obj/.ext/tail",
want: map[string]string{
"name": "o/my-bucket/dir/dir2/obj/.ext",
},
},
{
ops: []int{
int(utilities.OpLitPush), 0,
int(utilities.OpLitPush), 1,
int(utilities.OpPush), anything,
int(utilities.OpConcatN), 2,
int(utilities.OpCapture), 2,
int(utilities.OpLitPush), 3,
int(utilities.OpPush), anything,
int(utilities.OpConcatN), 1,
int(utilities.OpCapture), 4,
},
pool: []string{"v2", "b", "name", "o", "oname"},
path: "v2/b/my-bucket/o/obj",
want: map[string]string{
"name": "b/my-bucket",
"oname": "obj",
},
},
} {
pat, err := NewPattern(validVersion, spec.ops, spec.pool, spec.verb)
if err != nil {
t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, spec.verb, err)
continue
}
got, err := pat.Match(segments(spec.path))
if err != nil {
t.Errorf("pat.Match(%q) failed with %v; want success; pattern = (%v, %q)", spec.path, err, spec.ops, spec.pool)
}
if !reflect.DeepEqual(got, spec.want) {
t.Errorf("pat.Match(%q) = %q; want %q; pattern = (%v, %q)", spec.path, got, spec.want, spec.ops, spec.pool)
}
}
}
func segments(path string) (components []string, verb string) {
if path == "" {
return nil, ""
}
components = strings.Split(path, "/")
l := len(components)
c := components[l-1]
if idx := strings.LastIndex(c, ":"); idx >= 0 {
components[l-1], verb = c[:idx], c[idx+1:]
}
return components, verb
}
func TestPatternString(t *testing.T) {
for _, spec := range []struct {
ops []int
pool []string
want string
}{
{
want: "/",
},
{
ops: []int{int(utilities.OpNop), anything},
want: "/",
},
{
ops: []int{int(utilities.OpPush), anything},
want: "/*",
},
{
ops: []int{int(utilities.OpLitPush), 0},
pool: []string{"endpoint"},
want: "/endpoint",
},
{
ops: []int{int(utilities.OpPushM), anything},
want: "/**",
},
{
ops: []int{
int(utilities.OpPush), anything,
int(utilities.OpConcatN), 1,
},
want: "/*",
},
{
ops: []int{
int(utilities.OpPush), anything,
int(utilities.OpConcatN), 1,
int(utilities.OpCapture), 0,
},
pool: []string{"name"},
want: "/{name=*}",
},
{
ops: []int{
int(utilities.OpLitPush), 0,
int(utilities.OpLitPush), 1,
int(utilities.OpPush), anything,
int(utilities.OpConcatN), 2,
int(utilities.OpCapture), 2,
int(utilities.OpLitPush), 3,
int(utilities.OpPushM), anything,
int(utilities.OpLitPush), 4,
int(utilities.OpConcatN), 3,
int(utilities.OpCapture), 6,
int(utilities.OpLitPush), 5,
},
pool: []string{"v1", "buckets", "bucket_name", "objects", ".ext", "tail", "name"},
want: "/v1/{bucket_name=buckets/*}/{name=objects/**/.ext}/tail",
},
} {
p, err := NewPattern(validVersion, spec.ops, spec.pool, "")
if err != nil {
t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, "", err)
continue
}
if got, want := p.String(), spec.want; got != want {
t.Errorf("%#v.String() = %q; want %q", p, got, want)
}
verb := "LOCK"
p, err = NewPattern(validVersion, spec.ops, spec.pool, verb)
if err != nil {
t.Errorf("NewPattern(%d, %v, %q, %q) failed with %v; want success", validVersion, spec.ops, spec.pool, verb, err)
continue
}
if got, want := p.String(), fmt.Sprintf("%s:%s", spec.want, verb); got != want {
t.Errorf("%#v.String() = %q; want %q", p, got, want)
}
}
}

View File

@@ -0,0 +1,80 @@
package runtime
import (
"github.com/golang/protobuf/proto"
)
// StringP returns a pointer to a string whose pointee is same as the given string value.
func StringP(val string) (*string, error) {
return proto.String(val), nil
}
// BoolP parses the given string representation of a boolean value,
// and returns a pointer to a bool whose value is same as the parsed value.
func BoolP(val string) (*bool, error) {
b, err := Bool(val)
if err != nil {
return nil, err
}
return proto.Bool(b), nil
}
// Float64P parses the given string representation of a floating point number,
// and returns a pointer to a float64 whose value is same as the parsed number.
func Float64P(val string) (*float64, error) {
f, err := Float64(val)
if err != nil {
return nil, err
}
return proto.Float64(f), nil
}
// Float32P parses the given string representation of a floating point number,
// and returns a pointer to a float32 whose value is same as the parsed number.
func Float32P(val string) (*float32, error) {
f, err := Float32(val)
if err != nil {
return nil, err
}
return proto.Float32(f), nil
}
// Int64P parses the given string representation of an integer
// and returns a pointer to a int64 whose value is same as the parsed integer.
func Int64P(val string) (*int64, error) {
i, err := Int64(val)
if err != nil {
return nil, err
}
return proto.Int64(i), nil
}
// Int32P parses the given string representation of an integer
// and returns a pointer to a int32 whose value is same as the parsed integer.
func Int32P(val string) (*int32, error) {
i, err := Int32(val)
if err != nil {
return nil, err
}
return proto.Int32(i), err
}
// Uint64P parses the given string representation of an integer
// and returns a pointer to a uint64 whose value is same as the parsed integer.
func Uint64P(val string) (*uint64, error) {
i, err := Uint64(val)
if err != nil {
return nil, err
}
return proto.Uint64(i), err
}
// Uint32P parses the given string representation of an integer
// and returns a pointer to a uint32 whose value is same as the parsed integer.
func Uint32P(val string) (*uint32, error) {
i, err := Uint32(val)
if err != nil {
return nil, err
}
return proto.Uint32(i), err
}

View File

@@ -0,0 +1,222 @@
package runtime
import (
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"time"
"github.com/golang/protobuf/proto"
"github.com/grpc-ecosystem/grpc-gateway/utilities"
"google.golang.org/grpc/grpclog"
)
// PopulateQueryParameters populates "values" into "msg".
// A value is ignored if its key starts with one of the elements in "filter".
func PopulateQueryParameters(msg proto.Message, values url.Values, filter *utilities.DoubleArray) error {
for key, values := range values {
fieldPath := strings.Split(key, ".")
if filter.HasCommonPrefix(fieldPath) {
continue
}
if err := populateFieldValueFromPath(msg, fieldPath, values); err != nil {
return err
}
}
return nil
}
// PopulateFieldFromPath sets a value in a nested Protobuf structure.
// It instantiates missing protobuf fields as it goes.
func PopulateFieldFromPath(msg proto.Message, fieldPathString string, value string) error {
fieldPath := strings.Split(fieldPathString, ".")
return populateFieldValueFromPath(msg, fieldPath, []string{value})
}
func populateFieldValueFromPath(msg proto.Message, fieldPath []string, values []string) error {
m := reflect.ValueOf(msg)
if m.Kind() != reflect.Ptr {
return fmt.Errorf("unexpected type %T: %v", msg, msg)
}
var props *proto.Properties
m = m.Elem()
for i, fieldName := range fieldPath {
isLast := i == len(fieldPath)-1
if !isLast && m.Kind() != reflect.Struct {
return fmt.Errorf("non-aggregate type in the mid of path: %s", strings.Join(fieldPath, "."))
}
var f reflect.Value
f, props = fieldByProtoName(m, fieldName)
if !f.IsValid() {
grpclog.Printf("field not found in %T: %s", msg, strings.Join(fieldPath, "."))
return nil
}
switch f.Kind() {
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, reflect.String, reflect.Uint32, reflect.Uint64:
if !isLast {
return fmt.Errorf("unexpected nested field %s in %s", fieldPath[i+1], strings.Join(fieldPath[:i+1], "."))
}
m = f
case reflect.Slice:
// TODO(yugui) Support []byte
if !isLast {
return fmt.Errorf("unexpected repeated field in %s", strings.Join(fieldPath, "."))
}
return populateRepeatedField(f, values, props)
case reflect.Ptr:
if f.IsNil() {
m = reflect.New(f.Type().Elem())
f.Set(m.Convert(f.Type()))
}
m = f.Elem()
continue
case reflect.Struct:
m = f
continue
default:
return fmt.Errorf("unexpected type %s in %T", f.Type(), msg)
}
}
switch len(values) {
case 0:
return fmt.Errorf("no value of field: %s", strings.Join(fieldPath, "."))
case 1:
default:
grpclog.Printf("too many field values: %s", strings.Join(fieldPath, "."))
}
return populateField(m, values[0], props)
}
// fieldByProtoName looks up a field whose corresponding protobuf field name is "name".
// "m" must be a struct value. It returns zero reflect.Value if no such field found.
func fieldByProtoName(m reflect.Value, name string) (reflect.Value, *proto.Properties) {
props := proto.GetProperties(m.Type())
for _, p := range props.Prop {
if p.OrigName == name {
return m.FieldByName(p.Name), p
}
}
return reflect.Value{}, nil
}
func populateRepeatedField(f reflect.Value, values []string, props *proto.Properties) error {
elemType := f.Type().Elem()
// is the destination field a slice of an enumeration type?
if enumValMap := proto.EnumValueMap(props.Enum); enumValMap != nil {
return populateFieldEnumRepeated(f, values, enumValMap)
}
conv, ok := convFromType[elemType.Kind()]
if !ok {
return fmt.Errorf("unsupported field type %s", elemType)
}
f.Set(reflect.MakeSlice(f.Type(), len(values), len(values)).Convert(f.Type()))
for i, v := range values {
result := conv.Call([]reflect.Value{reflect.ValueOf(v)})
if err := result[1].Interface(); err != nil {
return err.(error)
}
f.Index(i).Set(result[0].Convert(f.Index(i).Type()))
}
return nil
}
func populateField(f reflect.Value, value string, props *proto.Properties) error {
// Handle well known type
type wkt interface {
XXX_WellKnownType() string
}
if wkt, ok := f.Addr().Interface().(wkt); ok {
switch wkt.XXX_WellKnownType() {
case "Timestamp":
if value == "null" {
f.Field(0).SetInt(0)
f.Field(1).SetInt(0)
return nil
}
t, err := time.Parse(time.RFC3339Nano, value)
if err != nil {
return fmt.Errorf("bad Timestamp: %v", err)
}
f.Field(0).SetInt(int64(t.Unix()))
f.Field(1).SetInt(int64(t.Nanosecond()))
return nil
}
}
// is the destination field an enumeration type?
if enumValMap := proto.EnumValueMap(props.Enum); enumValMap != nil {
return populateFieldEnum(f, value, enumValMap)
}
conv, ok := convFromType[f.Kind()]
if !ok {
return fmt.Errorf("unsupported field type %T", f)
}
result := conv.Call([]reflect.Value{reflect.ValueOf(value)})
if err := result[1].Interface(); err != nil {
return err.(error)
}
f.Set(result[0].Convert(f.Type()))
return nil
}
func convertEnum(value string, t reflect.Type, enumValMap map[string]int32) (reflect.Value, error) {
// see if it's an enumeration string
if enumVal, ok := enumValMap[value]; ok {
return reflect.ValueOf(enumVal).Convert(t), nil
}
// check for an integer that matches an enumeration value
eVal, err := strconv.Atoi(value)
if err != nil {
return reflect.Value{}, fmt.Errorf("%s is not a valid %s", value, t)
}
for _, v := range enumValMap {
if v == int32(eVal) {
return reflect.ValueOf(eVal).Convert(t), nil
}
}
return reflect.Value{}, fmt.Errorf("%s is not a valid %s", value, t)
}
func populateFieldEnum(f reflect.Value, value string, enumValMap map[string]int32) error {
cval, err := convertEnum(value, f.Type(), enumValMap)
if err != nil {
return err
}
f.Set(cval)
return nil
}
func populateFieldEnumRepeated(f reflect.Value, values []string, enumValMap map[string]int32) error {
elemType := f.Type().Elem()
f.Set(reflect.MakeSlice(f.Type(), len(values), len(values)).Convert(f.Type()))
for i, v := range values {
result, err := convertEnum(v, elemType, enumValMap)
if err != nil {
return err
}
f.Index(i).Set(result)
}
return nil
}
var (
convFromType = map[reflect.Kind]reflect.Value{
reflect.String: reflect.ValueOf(String),
reflect.Bool: reflect.ValueOf(Bool),
reflect.Float64: reflect.ValueOf(Float64),
reflect.Float32: reflect.ValueOf(Float32),
reflect.Int64: reflect.ValueOf(Int64),
reflect.Int32: reflect.ValueOf(Int32),
reflect.Uint64: reflect.ValueOf(Uint64),
reflect.Uint32: reflect.ValueOf(Uint32),
// TODO(yugui) Support []byte
}
)

View File

@@ -0,0 +1,470 @@
package runtime_test
import (
"net/url"
"testing"
"time"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/grpc-ecosystem/grpc-gateway/utilities"
)
func TestPopulateParameters(t *testing.T) {
timeT := time.Date(2016, time.December, 15, 12, 23, 32, 49, time.UTC)
timeStr := timeT.Format(time.RFC3339Nano)
timePb, err := ptypes.TimestampProto(timeT)
if err != nil {
t.Fatalf("Couldn't setup timestamp in Protobuf format: %v", err)
}
for _, spec := range []struct {
values url.Values
filter *utilities.DoubleArray
want proto.Message
}{
{
values: url.Values{
"float_value": {"1.5"},
"double_value": {"2.5"},
"int64_value": {"-1"},
"int32_value": {"-2"},
"uint64_value": {"3"},
"uint32_value": {"4"},
"bool_value": {"true"},
"string_value": {"str"},
"repeated_value": {"a", "b", "c"},
"enum_value": {"1"},
"repeated_enum": {"1", "2", "0"},
"timestamp_value": {timeStr},
},
filter: utilities.NewDoubleArray(nil),
want: &proto3Message{
FloatValue: 1.5,
DoubleValue: 2.5,
Int64Value: -1,
Int32Value: -2,
Uint64Value: 3,
Uint32Value: 4,
BoolValue: true,
StringValue: "str",
RepeatedValue: []string{"a", "b", "c"},
EnumValue: EnumValue_Y,
RepeatedEnum: []EnumValue{EnumValue_Y, EnumValue_Z, EnumValue_X},
TimestampValue: timePb,
},
},
{
values: url.Values{
"enum_value": {"EnumValue_Z"},
"repeated_enum": {"EnumValue_X", "2", "0"},
},
filter: utilities.NewDoubleArray(nil),
want: &proto3Message{
EnumValue: EnumValue_Z,
RepeatedEnum: []EnumValue{EnumValue_X, EnumValue_Z, EnumValue_X},
},
},
{
values: url.Values{
"float_value": {"1.5"},
"double_value": {"2.5"},
"int64_value": {"-1"},
"int32_value": {"-2"},
"uint64_value": {"3"},
"uint32_value": {"4"},
"bool_value": {"true"},
"string_value": {"str"},
"repeated_value": {"a", "b", "c"},
"enum_value": {"1"},
"repeated_enum": {"1", "2", "0"},
},
filter: utilities.NewDoubleArray(nil),
want: &proto2Message{
FloatValue: proto.Float32(1.5),
DoubleValue: proto.Float64(2.5),
Int64Value: proto.Int64(-1),
Int32Value: proto.Int32(-2),
Uint64Value: proto.Uint64(3),
Uint32Value: proto.Uint32(4),
BoolValue: proto.Bool(true),
StringValue: proto.String("str"),
RepeatedValue: []string{"a", "b", "c"},
EnumValue: EnumValue_Y,
RepeatedEnum: []EnumValue{EnumValue_Y, EnumValue_Z, EnumValue_X},
},
},
{
values: url.Values{
"nested.nested.nested.repeated_value": {"a", "b", "c"},
"nested.nested.nested.string_value": {"s"},
"nested.nested.string_value": {"t"},
"nested.string_value": {"u"},
"nested_non_null.string_value": {"v"},
},
filter: utilities.NewDoubleArray(nil),
want: &proto3Message{
Nested: &proto2Message{
Nested: &proto3Message{
Nested: &proto2Message{
RepeatedValue: []string{"a", "b", "c"},
StringValue: proto.String("s"),
},
StringValue: "t",
},
StringValue: proto.String("u"),
},
NestedNonNull: proto2Message{
StringValue: proto.String("v"),
},
},
},
{
values: url.Values{
"uint64_value": {"1", "2", "3", "4", "5"},
},
filter: utilities.NewDoubleArray(nil),
want: &proto3Message{
Uint64Value: 1,
},
},
} {
msg := proto.Clone(spec.want)
msg.Reset()
err := runtime.PopulateQueryParameters(msg, spec.values, spec.filter)
if err != nil {
t.Errorf("runtime.PopulateQueryParameters(msg, %v, %v) failed with %v; want success", spec.values, spec.filter, err)
continue
}
if got, want := msg, spec.want; !proto.Equal(got, want) {
t.Errorf("runtime.PopulateQueryParameters(msg, %v, %v = %v; want %v", spec.values, spec.filter, got, want)
}
}
}
func TestPopulateParametersWithFilters(t *testing.T) {
for _, spec := range []struct {
values url.Values
filter *utilities.DoubleArray
want proto.Message
}{
{
values: url.Values{
"bool_value": {"true"},
"string_value": {"str"},
"repeated_value": {"a", "b", "c"},
},
filter: utilities.NewDoubleArray([][]string{
{"bool_value"}, {"repeated_value"},
}),
want: &proto3Message{
StringValue: "str",
},
},
{
values: url.Values{
"nested.nested.bool_value": {"true"},
"nested.nested.string_value": {"str"},
"nested.string_value": {"str"},
"string_value": {"str"},
},
filter: utilities.NewDoubleArray([][]string{
{"nested"},
}),
want: &proto3Message{
StringValue: "str",
},
},
{
values: url.Values{
"nested.nested.bool_value": {"true"},
"nested.nested.string_value": {"str"},
"nested.string_value": {"str"},
"string_value": {"str"},
},
filter: utilities.NewDoubleArray([][]string{
{"nested", "nested"},
}),
want: &proto3Message{
Nested: &proto2Message{
StringValue: proto.String("str"),
},
StringValue: "str",
},
},
{
values: url.Values{
"nested.nested.bool_value": {"true"},
"nested.nested.string_value": {"str"},
"nested.string_value": {"str"},
"string_value": {"str"},
},
filter: utilities.NewDoubleArray([][]string{
{"nested", "nested", "string_value"},
}),
want: &proto3Message{
Nested: &proto2Message{
StringValue: proto.String("str"),
Nested: &proto3Message{
BoolValue: true,
},
},
StringValue: "str",
},
},
} {
msg := proto.Clone(spec.want)
msg.Reset()
err := runtime.PopulateQueryParameters(msg, spec.values, spec.filter)
if err != nil {
t.Errorf("runtime.PoplateQueryParameters(msg, %v, %v) failed with %v; want success", spec.values, spec.filter, err)
continue
}
if got, want := msg, spec.want; !proto.Equal(got, want) {
t.Errorf("runtime.PopulateQueryParameters(msg, %v, %v = %v; want %v", spec.values, spec.filter, got, want)
}
}
}
func TestPopulateQueryParametersWithInvalidNestedParameters(t *testing.T) {
for _, spec := range []struct {
msg proto.Message
values url.Values
filter *utilities.DoubleArray
}{
{
msg: &proto3Message{},
values: url.Values{
"float_value.nested": {"test"},
},
filter: utilities.NewDoubleArray(nil),
},
{
msg: &proto3Message{},
values: url.Values{
"double_value.nested": {"test"},
},
filter: utilities.NewDoubleArray(nil),
},
{
msg: &proto3Message{},
values: url.Values{
"int64_value.nested": {"test"},
},
filter: utilities.NewDoubleArray(nil),
},
{
msg: &proto3Message{},
values: url.Values{
"int32_value.nested": {"test"},
},
filter: utilities.NewDoubleArray(nil),
},
{
msg: &proto3Message{},
values: url.Values{
"uint64_value.nested": {"test"},
},
filter: utilities.NewDoubleArray(nil),
},
{
msg: &proto3Message{},
values: url.Values{
"uint32_value.nested": {"test"},
},
filter: utilities.NewDoubleArray(nil),
},
{
msg: &proto3Message{},
values: url.Values{
"bool_value.nested": {"test"},
},
filter: utilities.NewDoubleArray(nil),
},
{
msg: &proto3Message{},
values: url.Values{
"string_value.nested": {"test"},
},
filter: utilities.NewDoubleArray(nil),
},
{
msg: &proto3Message{},
values: url.Values{
"repeated_value.nested": {"test"},
},
filter: utilities.NewDoubleArray(nil),
},
{
msg: &proto3Message{},
values: url.Values{
"enum_value.nested": {"test"},
},
filter: utilities.NewDoubleArray(nil),
},
{
msg: &proto3Message{},
values: url.Values{
"enum_value.nested": {"test"},
},
filter: utilities.NewDoubleArray(nil),
},
{
msg: &proto3Message{},
values: url.Values{
"repeated_enum.nested": {"test"},
},
filter: utilities.NewDoubleArray(nil),
},
} {
spec.msg.Reset()
err := runtime.PopulateQueryParameters(spec.msg, spec.values, spec.filter)
if err == nil {
t.Errorf("runtime.PopulateQueryParameters(msg, %v, %v) did not fail; want error", spec.values, spec.filter)
}
}
}
type proto3Message struct {
Nested *proto2Message `protobuf:"bytes,1,opt,name=nested" json:"nested,omitempty"`
NestedNonNull proto2Message `protobuf:"bytes,11,opt,name=nested_non_null" json:"nested_non_null,omitempty"`
FloatValue float32 `protobuf:"fixed32,2,opt,name=float_value" json:"float_value,omitempty"`
DoubleValue float64 `protobuf:"fixed64,3,opt,name=double_value" json:"double_value,omitempty"`
Int64Value int64 `protobuf:"varint,4,opt,name=int64_value" json:"int64_value,omitempty"`
Int32Value int32 `protobuf:"varint,5,opt,name=int32_value" json:"int32_value,omitempty"`
Uint64Value uint64 `protobuf:"varint,6,opt,name=uint64_value" json:"uint64_value,omitempty"`
Uint32Value uint32 `protobuf:"varint,7,opt,name=uint32_value" json:"uint32_value,omitempty"`
BoolValue bool `protobuf:"varint,8,opt,name=bool_value" json:"bool_value,omitempty"`
StringValue string `protobuf:"bytes,9,opt,name=string_value" json:"string_value,omitempty"`
RepeatedValue []string `protobuf:"bytes,10,rep,name=repeated_value" json:"repeated_value,omitempty"`
EnumValue EnumValue `protobuf:"varint,11,opt,name=enum_value,json=enumValue,enum=runtime_test_api.EnumValue" json:"enum_value,omitempty"`
RepeatedEnum []EnumValue `protobuf:"varint,12,rep,packed,name=repeated_enum,json=repeated_enum,enum=runtime_test_api.EnumValue" json:"repeated_enum,omitempty"`
TimestampValue *timestamp.Timestamp `protobuf:"bytes,11,opt,name=timestamp_value" json:"timestamp_value,omitempty"`
}
func (m *proto3Message) Reset() { *m = proto3Message{} }
func (m *proto3Message) String() string { return proto.CompactTextString(m) }
func (*proto3Message) ProtoMessage() {}
func (m *proto3Message) GetNested() *proto2Message {
if m != nil {
return m.Nested
}
return nil
}
type proto2Message struct {
Nested *proto3Message `protobuf:"bytes,1,opt,name=nested" json:"nested,omitempty"`
FloatValue *float32 `protobuf:"fixed32,2,opt,name=float_value" json:"float_value,omitempty"`
DoubleValue *float64 `protobuf:"fixed64,3,opt,name=double_value" json:"double_value,omitempty"`
Int64Value *int64 `protobuf:"varint,4,opt,name=int64_value" json:"int64_value,omitempty"`
Int32Value *int32 `protobuf:"varint,5,opt,name=int32_value" json:"int32_value,omitempty"`
Uint64Value *uint64 `protobuf:"varint,6,opt,name=uint64_value" json:"uint64_value,omitempty"`
Uint32Value *uint32 `protobuf:"varint,7,opt,name=uint32_value" json:"uint32_value,omitempty"`
BoolValue *bool `protobuf:"varint,8,opt,name=bool_value" json:"bool_value,omitempty"`
StringValue *string `protobuf:"bytes,9,opt,name=string_value" json:"string_value,omitempty"`
RepeatedValue []string `protobuf:"bytes,10,rep,name=repeated_value" json:"repeated_value,omitempty"`
EnumValue EnumValue `protobuf:"varint,11,opt,name=enum_value,json=enumValue,enum=runtime_test_api.EnumValue" json:"enum_value,omitempty"`
RepeatedEnum []EnumValue `protobuf:"varint,12,rep,packed,name=repeated_enum,json=repeated_enum,enum=runtime_test_api.EnumValue" json:"repeated_enum,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *proto2Message) Reset() { *m = proto2Message{} }
func (m *proto2Message) String() string { return proto.CompactTextString(m) }
func (*proto2Message) ProtoMessage() {}
func (m *proto2Message) GetNested() *proto3Message {
if m != nil {
return m.Nested
}
return nil
}
func (m *proto2Message) GetFloatValue() float32 {
if m != nil && m.FloatValue != nil {
return *m.FloatValue
}
return 0
}
func (m *proto2Message) GetDoubleValue() float64 {
if m != nil && m.DoubleValue != nil {
return *m.DoubleValue
}
return 0
}
func (m *proto2Message) GetInt64Value() int64 {
if m != nil && m.Int64Value != nil {
return *m.Int64Value
}
return 0
}
func (m *proto2Message) GetInt32Value() int32 {
if m != nil && m.Int32Value != nil {
return *m.Int32Value
}
return 0
}
func (m *proto2Message) GetUint64Value() uint64 {
if m != nil && m.Uint64Value != nil {
return *m.Uint64Value
}
return 0
}
func (m *proto2Message) GetUint32Value() uint32 {
if m != nil && m.Uint32Value != nil {
return *m.Uint32Value
}
return 0
}
func (m *proto2Message) GetBoolValue() bool {
if m != nil && m.BoolValue != nil {
return *m.BoolValue
}
return false
}
func (m *proto2Message) GetStringValue() string {
if m != nil && m.StringValue != nil {
return *m.StringValue
}
return ""
}
func (m *proto2Message) GetRepeatedValue() []string {
if m != nil {
return m.RepeatedValue
}
return nil
}
type EnumValue int32
const (
EnumValue_X EnumValue = 0
EnumValue_Y EnumValue = 1
EnumValue_Z EnumValue = 2
)
var EnumValue_name = map[int32]string{
0: "EnumValue_X",
1: "EnumValue_Y",
2: "EnumValue_Z",
}
var EnumValue_value = map[string]int32{
"EnumValue_X": 0,
"EnumValue_Y": 1,
"EnumValue_Z": 2,
}
func init() {
proto.RegisterEnum("runtime_test_api.EnumValue", EnumValue_name, EnumValue_value)
}