proto: convert grpc to twirp
This commit is contained in:
parent
d0695adfb6
commit
d2cb12201e
|
@ -206,10 +206,12 @@
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/golang/protobuf"
|
name = "github.com/golang/protobuf"
|
||||||
packages = [
|
packages = [
|
||||||
|
"jsonpb",
|
||||||
"proto",
|
"proto",
|
||||||
"ptypes",
|
"ptypes",
|
||||||
"ptypes/any",
|
"ptypes/any",
|
||||||
"ptypes/duration",
|
"ptypes/duration",
|
||||||
|
"ptypes/struct",
|
||||||
"ptypes/timestamp"
|
"ptypes/timestamp"
|
||||||
]
|
]
|
||||||
revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845"
|
revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845"
|
||||||
|
@ -589,6 +591,16 @@
|
||||||
revision = "98aa888b79d8de04afe0fccf45ed10594efc858b"
|
revision = "98aa888b79d8de04afe0fccf45ed10594efc858b"
|
||||||
version = "v1.1"
|
version = "v1.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/twitchtv/twirp"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"ctxsetters",
|
||||||
|
"internal/contextkeys"
|
||||||
|
]
|
||||||
|
revision = "db96cdf354e8dc053e5ee5fe890bb0a7f18123ab"
|
||||||
|
version = "v5.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/ulikunitz/xz"
|
name = "github.com/ulikunitz/xz"
|
||||||
packages = [
|
packages = [
|
||||||
|
@ -753,6 +765,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "97c8282ef9b3abed71907d17ccf38379134714596610880b02d5ca03be634678"
|
inputs-digest = "51866d1bd0089290b4562a563c65db61b4973e66be0e14297f0680059dbdf138"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
3
mage.go
3
mage.go
|
@ -164,8 +164,7 @@ func Tools(ctx context.Context) {
|
||||||
tools := []string{
|
tools := []string{
|
||||||
"github.com/golang/dep/cmd/dep",
|
"github.com/golang/dep/cmd/dep",
|
||||||
"github.com/golang/protobuf/protoc-gen-go",
|
"github.com/golang/protobuf/protoc-gen-go",
|
||||||
// "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway",
|
"github.com/twitchtv/twirp/protoc-gen-twirp",
|
||||||
// "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, t := range tools {
|
for _, t := range tools {
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package routeclient
|
|
|
@ -1,2 +0,0 @@
|
||||||
// Package routeclient is a higer level convenience wrapper around the RPC layer for route.
|
|
||||||
package routeclient
|
|
|
@ -1,7 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
protoc -I/usr/local/include -I. \
|
protoc -I. \
|
||||||
-I$GOPATH/src \
|
--go_out=:. \
|
||||||
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
|
--twirp_out=. \
|
||||||
--go_out=Mgoogle/api/annotations.proto=github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api,plugins=grpc:. \
|
|
||||||
route.proto
|
route.proto
|
||||||
|
|
|
@ -26,11 +26,6 @@ import proto "github.com/golang/protobuf/proto"
|
||||||
import fmt "fmt"
|
import fmt "fmt"
|
||||||
import math "math"
|
import math "math"
|
||||||
|
|
||||||
import (
|
|
||||||
context "golang.org/x/net/context"
|
|
||||||
grpc "google.golang.org/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reference imports to suppress errors if they are not otherwise used.
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
var _ = proto.Marshal
|
var _ = proto.Marshal
|
||||||
var _ = fmt.Errorf
|
var _ = fmt.Errorf
|
||||||
|
@ -352,478 +347,6 @@ func init() {
|
||||||
proto.RegisterType((*BackendID)(nil), "route.BackendID")
|
proto.RegisterType((*BackendID)(nil), "route.BackendID")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reference imports to suppress errors if they are not otherwise used.
|
|
||||||
var _ context.Context
|
|
||||||
var _ grpc.ClientConn
|
|
||||||
|
|
||||||
// This is a compile-time assertion to ensure that this generated file
|
|
||||||
// is compatible with the grpc package it is being compiled against.
|
|
||||||
const _ = grpc.SupportPackageIsVersion4
|
|
||||||
|
|
||||||
// Client API for Routes service
|
|
||||||
|
|
||||||
type RoutesClient interface {
|
|
||||||
// Get fetches a single route based on the Host or ID.
|
|
||||||
Get(ctx context.Context, in *GetRouteRequest, opts ...grpc.CallOption) (*Route, error)
|
|
||||||
// GetAll fetches all of the routes that the user owns.
|
|
||||||
GetAll(ctx context.Context, in *Nil, opts ...grpc.CallOption) (*GetAllRoutesResponse, error)
|
|
||||||
// Put creates a new route based on user-supplied details.
|
|
||||||
Put(ctx context.Context, in *Route, opts ...grpc.CallOption) (*Route, error)
|
|
||||||
// Delete removes a route.
|
|
||||||
Delete(ctx context.Context, in *Route, opts ...grpc.CallOption) (*Nil, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type routesClient struct {
|
|
||||||
cc *grpc.ClientConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRoutesClient(cc *grpc.ClientConn) RoutesClient {
|
|
||||||
return &routesClient{cc}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *routesClient) Get(ctx context.Context, in *GetRouteRequest, opts ...grpc.CallOption) (*Route, error) {
|
|
||||||
out := new(Route)
|
|
||||||
err := grpc.Invoke(ctx, "/route.Routes/Get", in, out, c.cc, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *routesClient) GetAll(ctx context.Context, in *Nil, opts ...grpc.CallOption) (*GetAllRoutesResponse, error) {
|
|
||||||
out := new(GetAllRoutesResponse)
|
|
||||||
err := grpc.Invoke(ctx, "/route.Routes/GetAll", in, out, c.cc, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *routesClient) Put(ctx context.Context, in *Route, opts ...grpc.CallOption) (*Route, error) {
|
|
||||||
out := new(Route)
|
|
||||||
err := grpc.Invoke(ctx, "/route.Routes/Put", in, out, c.cc, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *routesClient) Delete(ctx context.Context, in *Route, opts ...grpc.CallOption) (*Nil, error) {
|
|
||||||
out := new(Nil)
|
|
||||||
err := grpc.Invoke(ctx, "/route.Routes/Delete", in, out, c.cc, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server API for Routes service
|
|
||||||
|
|
||||||
type RoutesServer interface {
|
|
||||||
// Get fetches a single route based on the Host or ID.
|
|
||||||
Get(context.Context, *GetRouteRequest) (*Route, error)
|
|
||||||
// GetAll fetches all of the routes that the user owns.
|
|
||||||
GetAll(context.Context, *Nil) (*GetAllRoutesResponse, error)
|
|
||||||
// Put creates a new route based on user-supplied details.
|
|
||||||
Put(context.Context, *Route) (*Route, error)
|
|
||||||
// Delete removes a route.
|
|
||||||
Delete(context.Context, *Route) (*Nil, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterRoutesServer(s *grpc.Server, srv RoutesServer) {
|
|
||||||
s.RegisterService(&_Routes_serviceDesc, srv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Routes_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(GetRouteRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(RoutesServer).Get(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/route.Routes/Get",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(RoutesServer).Get(ctx, req.(*GetRouteRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Routes_GetAll_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Nil)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(RoutesServer).GetAll(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/route.Routes/GetAll",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(RoutesServer).GetAll(ctx, req.(*Nil))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Routes_Put_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Route)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(RoutesServer).Put(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/route.Routes/Put",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(RoutesServer).Put(ctx, req.(*Route))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Routes_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Route)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(RoutesServer).Delete(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/route.Routes/Delete",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(RoutesServer).Delete(ctx, req.(*Route))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _Routes_serviceDesc = grpc.ServiceDesc{
|
|
||||||
ServiceName: "route.Routes",
|
|
||||||
HandlerType: (*RoutesServer)(nil),
|
|
||||||
Methods: []grpc.MethodDesc{
|
|
||||||
{
|
|
||||||
MethodName: "Get",
|
|
||||||
Handler: _Routes_Get_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "GetAll",
|
|
||||||
Handler: _Routes_GetAll_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "Put",
|
|
||||||
Handler: _Routes_Put_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "Delete",
|
|
||||||
Handler: _Routes_Delete_Handler,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Streams: []grpc.StreamDesc{},
|
|
||||||
Metadata: "route.proto",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client API for Tokens service
|
|
||||||
|
|
||||||
type TokensClient interface {
|
|
||||||
Get(ctx context.Context, in *GetTokenRequest, opts ...grpc.CallOption) (*Token, error)
|
|
||||||
GetAll(ctx context.Context, in *Nil, opts ...grpc.CallOption) (*TokenSet, error)
|
|
||||||
Put(ctx context.Context, in *Token, opts ...grpc.CallOption) (*Token, error)
|
|
||||||
Delete(ctx context.Context, in *Token, opts ...grpc.CallOption) (*Nil, error)
|
|
||||||
Deactivate(ctx context.Context, in *Token, opts ...grpc.CallOption) (*Nil, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type tokensClient struct {
|
|
||||||
cc *grpc.ClientConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTokensClient(cc *grpc.ClientConn) TokensClient {
|
|
||||||
return &tokensClient{cc}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tokensClient) Get(ctx context.Context, in *GetTokenRequest, opts ...grpc.CallOption) (*Token, error) {
|
|
||||||
out := new(Token)
|
|
||||||
err := grpc.Invoke(ctx, "/route.Tokens/Get", in, out, c.cc, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tokensClient) GetAll(ctx context.Context, in *Nil, opts ...grpc.CallOption) (*TokenSet, error) {
|
|
||||||
out := new(TokenSet)
|
|
||||||
err := grpc.Invoke(ctx, "/route.Tokens/GetAll", in, out, c.cc, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tokensClient) Put(ctx context.Context, in *Token, opts ...grpc.CallOption) (*Token, error) {
|
|
||||||
out := new(Token)
|
|
||||||
err := grpc.Invoke(ctx, "/route.Tokens/Put", in, out, c.cc, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tokensClient) Delete(ctx context.Context, in *Token, opts ...grpc.CallOption) (*Nil, error) {
|
|
||||||
out := new(Nil)
|
|
||||||
err := grpc.Invoke(ctx, "/route.Tokens/Delete", in, out, c.cc, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tokensClient) Deactivate(ctx context.Context, in *Token, opts ...grpc.CallOption) (*Nil, error) {
|
|
||||||
out := new(Nil)
|
|
||||||
err := grpc.Invoke(ctx, "/route.Tokens/Deactivate", in, out, c.cc, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server API for Tokens service
|
|
||||||
|
|
||||||
type TokensServer interface {
|
|
||||||
Get(context.Context, *GetTokenRequest) (*Token, error)
|
|
||||||
GetAll(context.Context, *Nil) (*TokenSet, error)
|
|
||||||
Put(context.Context, *Token) (*Token, error)
|
|
||||||
Delete(context.Context, *Token) (*Nil, error)
|
|
||||||
Deactivate(context.Context, *Token) (*Nil, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterTokensServer(s *grpc.Server, srv TokensServer) {
|
|
||||||
s.RegisterService(&_Tokens_serviceDesc, srv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Tokens_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(GetTokenRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(TokensServer).Get(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/route.Tokens/Get",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(TokensServer).Get(ctx, req.(*GetTokenRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Tokens_GetAll_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Nil)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(TokensServer).GetAll(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/route.Tokens/GetAll",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(TokensServer).GetAll(ctx, req.(*Nil))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Tokens_Put_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Token)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(TokensServer).Put(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/route.Tokens/Put",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(TokensServer).Put(ctx, req.(*Token))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Tokens_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Token)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(TokensServer).Delete(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/route.Tokens/Delete",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(TokensServer).Delete(ctx, req.(*Token))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Tokens_Deactivate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Token)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(TokensServer).Deactivate(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/route.Tokens/Deactivate",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(TokensServer).Deactivate(ctx, req.(*Token))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _Tokens_serviceDesc = grpc.ServiceDesc{
|
|
||||||
ServiceName: "route.Tokens",
|
|
||||||
HandlerType: (*TokensServer)(nil),
|
|
||||||
Methods: []grpc.MethodDesc{
|
|
||||||
{
|
|
||||||
MethodName: "Get",
|
|
||||||
Handler: _Tokens_Get_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "GetAll",
|
|
||||||
Handler: _Tokens_GetAll_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "Put",
|
|
||||||
Handler: _Tokens_Put_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "Delete",
|
|
||||||
Handler: _Tokens_Delete_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "Deactivate",
|
|
||||||
Handler: _Tokens_Deactivate_Handler,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Streams: []grpc.StreamDesc{},
|
|
||||||
Metadata: "route.proto",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client API for Backends service
|
|
||||||
|
|
||||||
type BackendsClient interface {
|
|
||||||
List(ctx context.Context, in *BackendSelector, opts ...grpc.CallOption) (*BackendList, error)
|
|
||||||
Kill(ctx context.Context, in *BackendID, opts ...grpc.CallOption) (*Nil, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type backendsClient struct {
|
|
||||||
cc *grpc.ClientConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBackendsClient(cc *grpc.ClientConn) BackendsClient {
|
|
||||||
return &backendsClient{cc}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *backendsClient) List(ctx context.Context, in *BackendSelector, opts ...grpc.CallOption) (*BackendList, error) {
|
|
||||||
out := new(BackendList)
|
|
||||||
err := grpc.Invoke(ctx, "/route.Backends/List", in, out, c.cc, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *backendsClient) Kill(ctx context.Context, in *BackendID, opts ...grpc.CallOption) (*Nil, error) {
|
|
||||||
out := new(Nil)
|
|
||||||
err := grpc.Invoke(ctx, "/route.Backends/Kill", in, out, c.cc, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server API for Backends service
|
|
||||||
|
|
||||||
type BackendsServer interface {
|
|
||||||
List(context.Context, *BackendSelector) (*BackendList, error)
|
|
||||||
Kill(context.Context, *BackendID) (*Nil, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterBackendsServer(s *grpc.Server, srv BackendsServer) {
|
|
||||||
s.RegisterService(&_Backends_serviceDesc, srv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Backends_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(BackendSelector)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(BackendsServer).List(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/route.Backends/List",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(BackendsServer).List(ctx, req.(*BackendSelector))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Backends_Kill_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(BackendID)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(BackendsServer).Kill(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/route.Backends/Kill",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(BackendsServer).Kill(ctx, req.(*BackendID))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _Backends_serviceDesc = grpc.ServiceDesc{
|
|
||||||
ServiceName: "route.Backends",
|
|
||||||
HandlerType: (*BackendsServer)(nil),
|
|
||||||
Methods: []grpc.MethodDesc{
|
|
||||||
{
|
|
||||||
MethodName: "List",
|
|
||||||
Handler: _Backends_List_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "Kill",
|
|
||||||
Handler: _Backends_Kill_Handler,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Streams: []grpc.StreamDesc{},
|
|
||||||
Metadata: "route.proto",
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { proto.RegisterFile("route.proto", fileDescriptor0) }
|
func init() { proto.RegisterFile("route.proto", fileDescriptor0) }
|
||||||
|
|
||||||
var fileDescriptor0 = []byte{
|
var fileDescriptor0 = []byte{
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,897 @@
|
||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package jsonpb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
pb "github.com/golang/protobuf/jsonpb/jsonpb_test_proto"
|
||||||
|
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||||
|
"github.com/golang/protobuf/ptypes"
|
||||||
|
anypb "github.com/golang/protobuf/ptypes/any"
|
||||||
|
durpb "github.com/golang/protobuf/ptypes/duration"
|
||||||
|
stpb "github.com/golang/protobuf/ptypes/struct"
|
||||||
|
tspb "github.com/golang/protobuf/ptypes/timestamp"
|
||||||
|
wpb "github.com/golang/protobuf/ptypes/wrappers"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
marshaler = Marshaler{}
|
||||||
|
|
||||||
|
marshalerAllOptions = Marshaler{
|
||||||
|
Indent: " ",
|
||||||
|
}
|
||||||
|
|
||||||
|
simpleObject = &pb.Simple{
|
||||||
|
OInt32: proto.Int32(-32),
|
||||||
|
OInt64: proto.Int64(-6400000000),
|
||||||
|
OUint32: proto.Uint32(32),
|
||||||
|
OUint64: proto.Uint64(6400000000),
|
||||||
|
OSint32: proto.Int32(-13),
|
||||||
|
OSint64: proto.Int64(-2600000000),
|
||||||
|
OFloat: proto.Float32(3.14),
|
||||||
|
ODouble: proto.Float64(6.02214179e23),
|
||||||
|
OBool: proto.Bool(true),
|
||||||
|
OString: proto.String("hello \"there\""),
|
||||||
|
OBytes: []byte("beep boop"),
|
||||||
|
}
|
||||||
|
|
||||||
|
simpleObjectJSON = `{` +
|
||||||
|
`"oBool":true,` +
|
||||||
|
`"oInt32":-32,` +
|
||||||
|
`"oInt64":"-6400000000",` +
|
||||||
|
`"oUint32":32,` +
|
||||||
|
`"oUint64":"6400000000",` +
|
||||||
|
`"oSint32":-13,` +
|
||||||
|
`"oSint64":"-2600000000",` +
|
||||||
|
`"oFloat":3.14,` +
|
||||||
|
`"oDouble":6.02214179e+23,` +
|
||||||
|
`"oString":"hello \"there\"",` +
|
||||||
|
`"oBytes":"YmVlcCBib29w"` +
|
||||||
|
`}`
|
||||||
|
|
||||||
|
simpleObjectPrettyJSON = `{
|
||||||
|
"oBool": true,
|
||||||
|
"oInt32": -32,
|
||||||
|
"oInt64": "-6400000000",
|
||||||
|
"oUint32": 32,
|
||||||
|
"oUint64": "6400000000",
|
||||||
|
"oSint32": -13,
|
||||||
|
"oSint64": "-2600000000",
|
||||||
|
"oFloat": 3.14,
|
||||||
|
"oDouble": 6.02214179e+23,
|
||||||
|
"oString": "hello \"there\"",
|
||||||
|
"oBytes": "YmVlcCBib29w"
|
||||||
|
}`
|
||||||
|
|
||||||
|
repeatsObject = &pb.Repeats{
|
||||||
|
RBool: []bool{true, false, true},
|
||||||
|
RInt32: []int32{-3, -4, -5},
|
||||||
|
RInt64: []int64{-123456789, -987654321},
|
||||||
|
RUint32: []uint32{1, 2, 3},
|
||||||
|
RUint64: []uint64{6789012345, 3456789012},
|
||||||
|
RSint32: []int32{-1, -2, -3},
|
||||||
|
RSint64: []int64{-6789012345, -3456789012},
|
||||||
|
RFloat: []float32{3.14, 6.28},
|
||||||
|
RDouble: []float64{299792458 * 1e20, 6.62606957e-34},
|
||||||
|
RString: []string{"happy", "days"},
|
||||||
|
RBytes: [][]byte{[]byte("skittles"), []byte("m&m's")},
|
||||||
|
}
|
||||||
|
|
||||||
|
repeatsObjectJSON = `{` +
|
||||||
|
`"rBool":[true,false,true],` +
|
||||||
|
`"rInt32":[-3,-4,-5],` +
|
||||||
|
`"rInt64":["-123456789","-987654321"],` +
|
||||||
|
`"rUint32":[1,2,3],` +
|
||||||
|
`"rUint64":["6789012345","3456789012"],` +
|
||||||
|
`"rSint32":[-1,-2,-3],` +
|
||||||
|
`"rSint64":["-6789012345","-3456789012"],` +
|
||||||
|
`"rFloat":[3.14,6.28],` +
|
||||||
|
`"rDouble":[2.99792458e+28,6.62606957e-34],` +
|
||||||
|
`"rString":["happy","days"],` +
|
||||||
|
`"rBytes":["c2tpdHRsZXM=","bSZtJ3M="]` +
|
||||||
|
`}`
|
||||||
|
|
||||||
|
repeatsObjectPrettyJSON = `{
|
||||||
|
"rBool": [
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"rInt32": [
|
||||||
|
-3,
|
||||||
|
-4,
|
||||||
|
-5
|
||||||
|
],
|
||||||
|
"rInt64": [
|
||||||
|
"-123456789",
|
||||||
|
"-987654321"
|
||||||
|
],
|
||||||
|
"rUint32": [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"rUint64": [
|
||||||
|
"6789012345",
|
||||||
|
"3456789012"
|
||||||
|
],
|
||||||
|
"rSint32": [
|
||||||
|
-1,
|
||||||
|
-2,
|
||||||
|
-3
|
||||||
|
],
|
||||||
|
"rSint64": [
|
||||||
|
"-6789012345",
|
||||||
|
"-3456789012"
|
||||||
|
],
|
||||||
|
"rFloat": [
|
||||||
|
3.14,
|
||||||
|
6.28
|
||||||
|
],
|
||||||
|
"rDouble": [
|
||||||
|
2.99792458e+28,
|
||||||
|
6.62606957e-34
|
||||||
|
],
|
||||||
|
"rString": [
|
||||||
|
"happy",
|
||||||
|
"days"
|
||||||
|
],
|
||||||
|
"rBytes": [
|
||||||
|
"c2tpdHRsZXM=",
|
||||||
|
"bSZtJ3M="
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
innerSimple = &pb.Simple{OInt32: proto.Int32(-32)}
|
||||||
|
innerSimple2 = &pb.Simple{OInt64: proto.Int64(25)}
|
||||||
|
innerRepeats = &pb.Repeats{RString: []string{"roses", "red"}}
|
||||||
|
innerRepeats2 = &pb.Repeats{RString: []string{"violets", "blue"}}
|
||||||
|
complexObject = &pb.Widget{
|
||||||
|
Color: pb.Widget_GREEN.Enum(),
|
||||||
|
RColor: []pb.Widget_Color{pb.Widget_RED, pb.Widget_GREEN, pb.Widget_BLUE},
|
||||||
|
Simple: innerSimple,
|
||||||
|
RSimple: []*pb.Simple{innerSimple, innerSimple2},
|
||||||
|
Repeats: innerRepeats,
|
||||||
|
RRepeats: []*pb.Repeats{innerRepeats, innerRepeats2},
|
||||||
|
}
|
||||||
|
|
||||||
|
complexObjectJSON = `{"color":"GREEN",` +
|
||||||
|
`"rColor":["RED","GREEN","BLUE"],` +
|
||||||
|
`"simple":{"oInt32":-32},` +
|
||||||
|
`"rSimple":[{"oInt32":-32},{"oInt64":"25"}],` +
|
||||||
|
`"repeats":{"rString":["roses","red"]},` +
|
||||||
|
`"rRepeats":[{"rString":["roses","red"]},{"rString":["violets","blue"]}]` +
|
||||||
|
`}`
|
||||||
|
|
||||||
|
complexObjectPrettyJSON = `{
|
||||||
|
"color": "GREEN",
|
||||||
|
"rColor": [
|
||||||
|
"RED",
|
||||||
|
"GREEN",
|
||||||
|
"BLUE"
|
||||||
|
],
|
||||||
|
"simple": {
|
||||||
|
"oInt32": -32
|
||||||
|
},
|
||||||
|
"rSimple": [
|
||||||
|
{
|
||||||
|
"oInt32": -32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"oInt64": "25"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"repeats": {
|
||||||
|
"rString": [
|
||||||
|
"roses",
|
||||||
|
"red"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"rRepeats": [
|
||||||
|
{
|
||||||
|
"rString": [
|
||||||
|
"roses",
|
||||||
|
"red"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rString": [
|
||||||
|
"violets",
|
||||||
|
"blue"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
colorPrettyJSON = `{
|
||||||
|
"color": 2
|
||||||
|
}`
|
||||||
|
|
||||||
|
colorListPrettyJSON = `{
|
||||||
|
"color": 1000,
|
||||||
|
"rColor": [
|
||||||
|
"RED"
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
nummyPrettyJSON = `{
|
||||||
|
"nummy": {
|
||||||
|
"1": 2,
|
||||||
|
"3": 4
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
objjyPrettyJSON = `{
|
||||||
|
"objjy": {
|
||||||
|
"1": {
|
||||||
|
"dub": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
realNumber = &pb.Real{Value: proto.Float64(3.14159265359)}
|
||||||
|
realNumberName = "Pi"
|
||||||
|
complexNumber = &pb.Complex{Imaginary: proto.Float64(0.5772156649)}
|
||||||
|
realNumberJSON = `{` +
|
||||||
|
`"value":3.14159265359,` +
|
||||||
|
`"[jsonpb.Complex.real_extension]":{"imaginary":0.5772156649},` +
|
||||||
|
`"[jsonpb.name]":"Pi"` +
|
||||||
|
`}`
|
||||||
|
|
||||||
|
anySimple = &pb.KnownTypes{
|
||||||
|
An: &anypb.Any{
|
||||||
|
TypeUrl: "something.example.com/jsonpb.Simple",
|
||||||
|
Value: []byte{
|
||||||
|
// &pb.Simple{OBool:true}
|
||||||
|
1 << 3, 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
anySimpleJSON = `{"an":{"@type":"something.example.com/jsonpb.Simple","oBool":true}}`
|
||||||
|
anySimplePrettyJSON = `{
|
||||||
|
"an": {
|
||||||
|
"@type": "something.example.com/jsonpb.Simple",
|
||||||
|
"oBool": true
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
anyWellKnown = &pb.KnownTypes{
|
||||||
|
An: &anypb.Any{
|
||||||
|
TypeUrl: "type.googleapis.com/google.protobuf.Duration",
|
||||||
|
Value: []byte{
|
||||||
|
// &durpb.Duration{Seconds: 1, Nanos: 212000000 }
|
||||||
|
1 << 3, 1, // seconds
|
||||||
|
2 << 3, 0x80, 0xba, 0x8b, 0x65, // nanos
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
anyWellKnownJSON = `{"an":{"@type":"type.googleapis.com/google.protobuf.Duration","value":"1.212s"}}`
|
||||||
|
anyWellKnownPrettyJSON = `{
|
||||||
|
"an": {
|
||||||
|
"@type": "type.googleapis.com/google.protobuf.Duration",
|
||||||
|
"value": "1.212s"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
nonFinites = &pb.NonFinites{
|
||||||
|
FNan: proto.Float32(float32(math.NaN())),
|
||||||
|
FPinf: proto.Float32(float32(math.Inf(1))),
|
||||||
|
FNinf: proto.Float32(float32(math.Inf(-1))),
|
||||||
|
DNan: proto.Float64(float64(math.NaN())),
|
||||||
|
DPinf: proto.Float64(float64(math.Inf(1))),
|
||||||
|
DNinf: proto.Float64(float64(math.Inf(-1))),
|
||||||
|
}
|
||||||
|
nonFinitesJSON = `{` +
|
||||||
|
`"fNan":"NaN",` +
|
||||||
|
`"fPinf":"Infinity",` +
|
||||||
|
`"fNinf":"-Infinity",` +
|
||||||
|
`"dNan":"NaN",` +
|
||||||
|
`"dPinf":"Infinity",` +
|
||||||
|
`"dNinf":"-Infinity"` +
|
||||||
|
`}`
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := proto.SetExtension(realNumber, pb.E_Name, &realNumberName); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := proto.SetExtension(realNumber, pb.E_Complex_RealExtension, complexNumber); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var marshalingTests = []struct {
|
||||||
|
desc string
|
||||||
|
marshaler Marshaler
|
||||||
|
pb proto.Message
|
||||||
|
json string
|
||||||
|
}{
|
||||||
|
{"simple flat object", marshaler, simpleObject, simpleObjectJSON},
|
||||||
|
{"simple pretty object", marshalerAllOptions, simpleObject, simpleObjectPrettyJSON},
|
||||||
|
{"non-finite floats fields object", marshaler, nonFinites, nonFinitesJSON},
|
||||||
|
{"repeated fields flat object", marshaler, repeatsObject, repeatsObjectJSON},
|
||||||
|
{"repeated fields pretty object", marshalerAllOptions, repeatsObject, repeatsObjectPrettyJSON},
|
||||||
|
{"nested message/enum flat object", marshaler, complexObject, complexObjectJSON},
|
||||||
|
{"nested message/enum pretty object", marshalerAllOptions, complexObject, complexObjectPrettyJSON},
|
||||||
|
{"enum-string flat object", Marshaler{},
|
||||||
|
&pb.Widget{Color: pb.Widget_BLUE.Enum()}, `{"color":"BLUE"}`},
|
||||||
|
{"enum-value pretty object", Marshaler{EnumsAsInts: true, Indent: " "},
|
||||||
|
&pb.Widget{Color: pb.Widget_BLUE.Enum()}, colorPrettyJSON},
|
||||||
|
{"unknown enum value object", marshalerAllOptions,
|
||||||
|
&pb.Widget{Color: pb.Widget_Color(1000).Enum(), RColor: []pb.Widget_Color{pb.Widget_RED}}, colorListPrettyJSON},
|
||||||
|
{"repeated proto3 enum", Marshaler{},
|
||||||
|
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
|
||||||
|
proto3pb.Message_PUNS,
|
||||||
|
proto3pb.Message_SLAPSTICK,
|
||||||
|
}},
|
||||||
|
`{"rFunny":["PUNS","SLAPSTICK"]}`},
|
||||||
|
{"repeated proto3 enum as int", Marshaler{EnumsAsInts: true},
|
||||||
|
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
|
||||||
|
proto3pb.Message_PUNS,
|
||||||
|
proto3pb.Message_SLAPSTICK,
|
||||||
|
}},
|
||||||
|
`{"rFunny":[1,2]}`},
|
||||||
|
{"empty value", marshaler, &pb.Simple3{}, `{}`},
|
||||||
|
{"empty value emitted", Marshaler{EmitDefaults: true}, &pb.Simple3{}, `{"dub":0}`},
|
||||||
|
{"empty repeated emitted", Marshaler{EmitDefaults: true}, &pb.SimpleSlice3{}, `{"slices":[]}`},
|
||||||
|
{"empty map emitted", Marshaler{EmitDefaults: true}, &pb.SimpleMap3{}, `{"stringy":{}}`},
|
||||||
|
{"nested struct null", Marshaler{EmitDefaults: true}, &pb.SimpleNull3{}, `{"simple":null}`},
|
||||||
|
{"map<int64, int32>", marshaler, &pb.Mappy{Nummy: map[int64]int32{1: 2, 3: 4}}, `{"nummy":{"1":2,"3":4}}`},
|
||||||
|
{"map<int64, int32>", marshalerAllOptions, &pb.Mappy{Nummy: map[int64]int32{1: 2, 3: 4}}, nummyPrettyJSON},
|
||||||
|
{"map<string, string>", marshaler,
|
||||||
|
&pb.Mappy{Strry: map[string]string{`"one"`: "two", "three": "four"}},
|
||||||
|
`{"strry":{"\"one\"":"two","three":"four"}}`},
|
||||||
|
{"map<int32, Object>", marshaler,
|
||||||
|
&pb.Mappy{Objjy: map[int32]*pb.Simple3{1: {Dub: 1}}}, `{"objjy":{"1":{"dub":1}}}`},
|
||||||
|
{"map<int32, Object>", marshalerAllOptions,
|
||||||
|
&pb.Mappy{Objjy: map[int32]*pb.Simple3{1: {Dub: 1}}}, objjyPrettyJSON},
|
||||||
|
{"map<int64, string>", marshaler, &pb.Mappy{Buggy: map[int64]string{1234: "yup"}},
|
||||||
|
`{"buggy":{"1234":"yup"}}`},
|
||||||
|
{"map<bool, bool>", marshaler, &pb.Mappy{Booly: map[bool]bool{false: true}}, `{"booly":{"false":true}}`},
|
||||||
|
// TODO: This is broken.
|
||||||
|
//{"map<string, enum>", marshaler, &pb.Mappy{Enumy: map[string]pb.Numeral{"XIV": pb.Numeral_ROMAN}}, `{"enumy":{"XIV":"ROMAN"}`},
|
||||||
|
{"map<string, enum as int>", Marshaler{EnumsAsInts: true}, &pb.Mappy{Enumy: map[string]pb.Numeral{"XIV": pb.Numeral_ROMAN}}, `{"enumy":{"XIV":2}}`},
|
||||||
|
{"map<int32, bool>", marshaler, &pb.Mappy{S32Booly: map[int32]bool{1: true, 3: false, 10: true, 12: false}}, `{"s32booly":{"1":true,"3":false,"10":true,"12":false}}`},
|
||||||
|
{"map<int64, bool>", marshaler, &pb.Mappy{S64Booly: map[int64]bool{1: true, 3: false, 10: true, 12: false}}, `{"s64booly":{"1":true,"3":false,"10":true,"12":false}}`},
|
||||||
|
{"map<uint32, bool>", marshaler, &pb.Mappy{U32Booly: map[uint32]bool{1: true, 3: false, 10: true, 12: false}}, `{"u32booly":{"1":true,"3":false,"10":true,"12":false}}`},
|
||||||
|
{"map<uint64, bool>", marshaler, &pb.Mappy{U64Booly: map[uint64]bool{1: true, 3: false, 10: true, 12: false}}, `{"u64booly":{"1":true,"3":false,"10":true,"12":false}}`},
|
||||||
|
{"proto2 map<int64, string>", marshaler, &pb.Maps{MInt64Str: map[int64]string{213: "cat"}},
|
||||||
|
`{"mInt64Str":{"213":"cat"}}`},
|
||||||
|
{"proto2 map<bool, Object>", marshaler,
|
||||||
|
&pb.Maps{MBoolSimple: map[bool]*pb.Simple{true: {OInt32: proto.Int32(1)}}},
|
||||||
|
`{"mBoolSimple":{"true":{"oInt32":1}}}`},
|
||||||
|
{"oneof, not set", marshaler, &pb.MsgWithOneof{}, `{}`},
|
||||||
|
{"oneof, set", marshaler, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Title{"Grand Poobah"}}, `{"title":"Grand Poobah"}`},
|
||||||
|
{"force orig_name", Marshaler{OrigName: true}, &pb.Simple{OInt32: proto.Int32(4)},
|
||||||
|
`{"o_int32":4}`},
|
||||||
|
{"proto2 extension", marshaler, realNumber, realNumberJSON},
|
||||||
|
{"Any with message", marshaler, anySimple, anySimpleJSON},
|
||||||
|
{"Any with message and indent", marshalerAllOptions, anySimple, anySimplePrettyJSON},
|
||||||
|
{"Any with WKT", marshaler, anyWellKnown, anyWellKnownJSON},
|
||||||
|
{"Any with WKT and indent", marshalerAllOptions, anyWellKnown, anyWellKnownPrettyJSON},
|
||||||
|
{"Duration", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 3}}, `{"dur":"3.000s"}`},
|
||||||
|
{"Duration", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 100000000, Nanos: 1}}, `{"dur":"100000000.000000001s"}`},
|
||||||
|
{"Struct", marshaler, &pb.KnownTypes{St: &stpb.Struct{
|
||||||
|
Fields: map[string]*stpb.Value{
|
||||||
|
"one": {Kind: &stpb.Value_StringValue{"loneliest number"}},
|
||||||
|
"two": {Kind: &stpb.Value_NullValue{stpb.NullValue_NULL_VALUE}},
|
||||||
|
},
|
||||||
|
}}, `{"st":{"one":"loneliest number","two":null}}`},
|
||||||
|
{"empty ListValue", marshaler, &pb.KnownTypes{Lv: &stpb.ListValue{}}, `{"lv":[]}`},
|
||||||
|
{"basic ListValue", marshaler, &pb.KnownTypes{Lv: &stpb.ListValue{Values: []*stpb.Value{
|
||||||
|
{Kind: &stpb.Value_StringValue{"x"}},
|
||||||
|
{Kind: &stpb.Value_NullValue{}},
|
||||||
|
{Kind: &stpb.Value_NumberValue{3}},
|
||||||
|
{Kind: &stpb.Value_BoolValue{true}},
|
||||||
|
}}}, `{"lv":["x",null,3,true]}`},
|
||||||
|
{"Timestamp", marshaler, &pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: 14e8, Nanos: 21e6}}, `{"ts":"2014-05-13T16:53:20.021Z"}`},
|
||||||
|
{"number Value", marshaler, &pb.KnownTypes{Val: &stpb.Value{Kind: &stpb.Value_NumberValue{1}}}, `{"val":1}`},
|
||||||
|
{"null Value", marshaler, &pb.KnownTypes{Val: &stpb.Value{Kind: &stpb.Value_NullValue{stpb.NullValue_NULL_VALUE}}}, `{"val":null}`},
|
||||||
|
{"string number value", marshaler, &pb.KnownTypes{Val: &stpb.Value{Kind: &stpb.Value_StringValue{"9223372036854775807"}}}, `{"val":"9223372036854775807"}`},
|
||||||
|
{"list of lists Value", marshaler, &pb.KnownTypes{Val: &stpb.Value{
|
||||||
|
Kind: &stpb.Value_ListValue{&stpb.ListValue{
|
||||||
|
Values: []*stpb.Value{
|
||||||
|
{Kind: &stpb.Value_StringValue{"x"}},
|
||||||
|
{Kind: &stpb.Value_ListValue{&stpb.ListValue{
|
||||||
|
Values: []*stpb.Value{
|
||||||
|
{Kind: &stpb.Value_ListValue{&stpb.ListValue{
|
||||||
|
Values: []*stpb.Value{{Kind: &stpb.Value_StringValue{"y"}}},
|
||||||
|
}}},
|
||||||
|
{Kind: &stpb.Value_StringValue{"z"}},
|
||||||
|
},
|
||||||
|
}}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}}, `{"val":["x",[["y"],"z"]]}`},
|
||||||
|
|
||||||
|
{"DoubleValue", marshaler, &pb.KnownTypes{Dbl: &wpb.DoubleValue{Value: 1.2}}, `{"dbl":1.2}`},
|
||||||
|
{"FloatValue", marshaler, &pb.KnownTypes{Flt: &wpb.FloatValue{Value: 1.2}}, `{"flt":1.2}`},
|
||||||
|
{"Int64Value", marshaler, &pb.KnownTypes{I64: &wpb.Int64Value{Value: -3}}, `{"i64":"-3"}`},
|
||||||
|
{"UInt64Value", marshaler, &pb.KnownTypes{U64: &wpb.UInt64Value{Value: 3}}, `{"u64":"3"}`},
|
||||||
|
{"Int32Value", marshaler, &pb.KnownTypes{I32: &wpb.Int32Value{Value: -4}}, `{"i32":-4}`},
|
||||||
|
{"UInt32Value", marshaler, &pb.KnownTypes{U32: &wpb.UInt32Value{Value: 4}}, `{"u32":4}`},
|
||||||
|
{"BoolValue", marshaler, &pb.KnownTypes{Bool: &wpb.BoolValue{Value: true}}, `{"bool":true}`},
|
||||||
|
{"StringValue", marshaler, &pb.KnownTypes{Str: &wpb.StringValue{Value: "plush"}}, `{"str":"plush"}`},
|
||||||
|
{"BytesValue", marshaler, &pb.KnownTypes{Bytes: &wpb.BytesValue{Value: []byte("wow")}}, `{"bytes":"d293"}`},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshaling(t *testing.T) {
|
||||||
|
for _, tt := range marshalingTests {
|
||||||
|
json, err := tt.marshaler.MarshalToString(tt.pb)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: marshaling error: %v", tt.desc, err)
|
||||||
|
} else if tt.json != json {
|
||||||
|
t.Errorf("%s: got [%v] want [%v]", tt.desc, json, tt.json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalJSONPBMarshaler(t *testing.T) {
|
||||||
|
rawJson := `{ "foo": "bar", "baz": [0, 1, 2, 3] }`
|
||||||
|
msg := dynamicMessage{rawJson: rawJson}
|
||||||
|
str, err := new(Marshaler).MarshalToString(&msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("an unexpected error occurred when marshalling JSONPBMarshaler: %v", err)
|
||||||
|
}
|
||||||
|
if str != rawJson {
|
||||||
|
t.Errorf("marshalling JSON produced incorrect output: got %s, wanted %s", str, rawJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalAnyJSONPBMarshaler(t *testing.T) {
|
||||||
|
msg := dynamicMessage{rawJson: `{ "foo": "bar", "baz": [0, 1, 2, 3] }`}
|
||||||
|
a, err := ptypes.MarshalAny(&msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("an unexpected error occurred when marshalling to Any: %v", err)
|
||||||
|
}
|
||||||
|
str, err := new(Marshaler).MarshalToString(a)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("an unexpected error occurred when marshalling Any to JSON: %v", err)
|
||||||
|
}
|
||||||
|
// after custom marshaling, it's round-tripped through JSON decoding/encoding already,
|
||||||
|
// so the keys are sorted, whitespace is compacted, and "@type" key has been added
|
||||||
|
expected := `{"@type":"type.googleapis.com/` + dynamicMessageName + `","baz":[0,1,2,3],"foo":"bar"}`
|
||||||
|
if str != expected {
|
||||||
|
t.Errorf("marshalling JSON produced incorrect output: got %s, wanted %s", str, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var unmarshalingTests = []struct {
|
||||||
|
desc string
|
||||||
|
unmarshaler Unmarshaler
|
||||||
|
json string
|
||||||
|
pb proto.Message
|
||||||
|
}{
|
||||||
|
{"simple flat object", Unmarshaler{}, simpleObjectJSON, simpleObject},
|
||||||
|
{"simple pretty object", Unmarshaler{}, simpleObjectPrettyJSON, simpleObject},
|
||||||
|
{"repeated fields flat object", Unmarshaler{}, repeatsObjectJSON, repeatsObject},
|
||||||
|
{"repeated fields pretty object", Unmarshaler{}, repeatsObjectPrettyJSON, repeatsObject},
|
||||||
|
{"nested message/enum flat object", Unmarshaler{}, complexObjectJSON, complexObject},
|
||||||
|
{"nested message/enum pretty object", Unmarshaler{}, complexObjectPrettyJSON, complexObject},
|
||||||
|
{"enum-string object", Unmarshaler{}, `{"color":"BLUE"}`, &pb.Widget{Color: pb.Widget_BLUE.Enum()}},
|
||||||
|
{"enum-value object", Unmarshaler{}, "{\n \"color\": 2\n}", &pb.Widget{Color: pb.Widget_BLUE.Enum()}},
|
||||||
|
{"unknown field with allowed option", Unmarshaler{AllowUnknownFields: true}, `{"unknown": "foo"}`, new(pb.Simple)},
|
||||||
|
{"proto3 enum string", Unmarshaler{}, `{"hilarity":"PUNS"}`, &proto3pb.Message{Hilarity: proto3pb.Message_PUNS}},
|
||||||
|
{"proto3 enum value", Unmarshaler{}, `{"hilarity":1}`, &proto3pb.Message{Hilarity: proto3pb.Message_PUNS}},
|
||||||
|
{"unknown enum value object",
|
||||||
|
Unmarshaler{},
|
||||||
|
"{\n \"color\": 1000,\n \"r_color\": [\n \"RED\"\n ]\n}",
|
||||||
|
&pb.Widget{Color: pb.Widget_Color(1000).Enum(), RColor: []pb.Widget_Color{pb.Widget_RED}}},
|
||||||
|
{"repeated proto3 enum", Unmarshaler{}, `{"rFunny":["PUNS","SLAPSTICK"]}`,
|
||||||
|
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
|
||||||
|
proto3pb.Message_PUNS,
|
||||||
|
proto3pb.Message_SLAPSTICK,
|
||||||
|
}}},
|
||||||
|
{"repeated proto3 enum as int", Unmarshaler{}, `{"rFunny":[1,2]}`,
|
||||||
|
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
|
||||||
|
proto3pb.Message_PUNS,
|
||||||
|
proto3pb.Message_SLAPSTICK,
|
||||||
|
}}},
|
||||||
|
{"repeated proto3 enum as mix of strings and ints", Unmarshaler{}, `{"rFunny":["PUNS",2]}`,
|
||||||
|
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
|
||||||
|
proto3pb.Message_PUNS,
|
||||||
|
proto3pb.Message_SLAPSTICK,
|
||||||
|
}}},
|
||||||
|
{"unquoted int64 object", Unmarshaler{}, `{"oInt64":-314}`, &pb.Simple{OInt64: proto.Int64(-314)}},
|
||||||
|
{"unquoted uint64 object", Unmarshaler{}, `{"oUint64":123}`, &pb.Simple{OUint64: proto.Uint64(123)}},
|
||||||
|
{"NaN", Unmarshaler{}, `{"oDouble":"NaN"}`, &pb.Simple{ODouble: proto.Float64(math.NaN())}},
|
||||||
|
{"Inf", Unmarshaler{}, `{"oFloat":"Infinity"}`, &pb.Simple{OFloat: proto.Float32(float32(math.Inf(1)))}},
|
||||||
|
{"-Inf", Unmarshaler{}, `{"oDouble":"-Infinity"}`, &pb.Simple{ODouble: proto.Float64(math.Inf(-1))}},
|
||||||
|
{"map<int64, int32>", Unmarshaler{}, `{"nummy":{"1":2,"3":4}}`, &pb.Mappy{Nummy: map[int64]int32{1: 2, 3: 4}}},
|
||||||
|
{"map<string, string>", Unmarshaler{}, `{"strry":{"\"one\"":"two","three":"four"}}`, &pb.Mappy{Strry: map[string]string{`"one"`: "two", "three": "four"}}},
|
||||||
|
{"map<int32, Object>", Unmarshaler{}, `{"objjy":{"1":{"dub":1}}}`, &pb.Mappy{Objjy: map[int32]*pb.Simple3{1: {Dub: 1}}}},
|
||||||
|
{"proto2 extension", Unmarshaler{}, realNumberJSON, realNumber},
|
||||||
|
{"Any with message", Unmarshaler{}, anySimpleJSON, anySimple},
|
||||||
|
{"Any with message and indent", Unmarshaler{}, anySimplePrettyJSON, anySimple},
|
||||||
|
{"Any with WKT", Unmarshaler{}, anyWellKnownJSON, anyWellKnown},
|
||||||
|
{"Any with WKT and indent", Unmarshaler{}, anyWellKnownPrettyJSON, anyWellKnown},
|
||||||
|
// TODO: This is broken.
|
||||||
|
//{"map<string, enum>", Unmarshaler{}, `{"enumy":{"XIV":"ROMAN"}`, &pb.Mappy{Enumy: map[string]pb.Numeral{"XIV": pb.Numeral_ROMAN}}},
|
||||||
|
{"map<string, enum as int>", Unmarshaler{}, `{"enumy":{"XIV":2}}`, &pb.Mappy{Enumy: map[string]pb.Numeral{"XIV": pb.Numeral_ROMAN}}},
|
||||||
|
{"oneof", Unmarshaler{}, `{"salary":31000}`, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Salary{31000}}},
|
||||||
|
{"oneof spec name", Unmarshaler{}, `{"Country":"Australia"}`, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Country{"Australia"}}},
|
||||||
|
{"oneof orig_name", Unmarshaler{}, `{"Country":"Australia"}`, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Country{"Australia"}}},
|
||||||
|
{"oneof spec name2", Unmarshaler{}, `{"homeAddress":"Australia"}`, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_HomeAddress{"Australia"}}},
|
||||||
|
{"oneof orig_name2", Unmarshaler{}, `{"home_address":"Australia"}`, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_HomeAddress{"Australia"}}},
|
||||||
|
{"orig_name input", Unmarshaler{}, `{"o_bool":true}`, &pb.Simple{OBool: proto.Bool(true)}},
|
||||||
|
{"camelName input", Unmarshaler{}, `{"oBool":true}`, &pb.Simple{OBool: proto.Bool(true)}},
|
||||||
|
|
||||||
|
{"Duration", Unmarshaler{}, `{"dur":"3.000s"}`, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 3}}},
|
||||||
|
{"null Duration", Unmarshaler{}, `{"dur":null}`, &pb.KnownTypes{Dur: nil}},
|
||||||
|
{"Timestamp", Unmarshaler{}, `{"ts":"2014-05-13T16:53:20.021Z"}`, &pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: 14e8, Nanos: 21e6}}},
|
||||||
|
{"PreEpochTimestamp", Unmarshaler{}, `{"ts":"1969-12-31T23:59:58.999999995Z"}`, &pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: -2, Nanos: 999999995}}},
|
||||||
|
{"ZeroTimeTimestamp", Unmarshaler{}, `{"ts":"0001-01-01T00:00:00Z"}`, &pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: -62135596800, Nanos: 0}}},
|
||||||
|
{"null Timestamp", Unmarshaler{}, `{"ts":null}`, &pb.KnownTypes{Ts: nil}},
|
||||||
|
{"null Struct", Unmarshaler{}, `{"st": null}`, &pb.KnownTypes{St: nil}},
|
||||||
|
{"empty Struct", Unmarshaler{}, `{"st": {}}`, &pb.KnownTypes{St: &stpb.Struct{}}},
|
||||||
|
{"basic Struct", Unmarshaler{}, `{"st": {"a": "x", "b": null, "c": 3, "d": true}}`, &pb.KnownTypes{St: &stpb.Struct{Fields: map[string]*stpb.Value{
|
||||||
|
"a": {Kind: &stpb.Value_StringValue{"x"}},
|
||||||
|
"b": {Kind: &stpb.Value_NullValue{}},
|
||||||
|
"c": {Kind: &stpb.Value_NumberValue{3}},
|
||||||
|
"d": {Kind: &stpb.Value_BoolValue{true}},
|
||||||
|
}}}},
|
||||||
|
{"nested Struct", Unmarshaler{}, `{"st": {"a": {"b": 1, "c": [{"d": true}, "f"]}}}`, &pb.KnownTypes{St: &stpb.Struct{Fields: map[string]*stpb.Value{
|
||||||
|
"a": {Kind: &stpb.Value_StructValue{&stpb.Struct{Fields: map[string]*stpb.Value{
|
||||||
|
"b": {Kind: &stpb.Value_NumberValue{1}},
|
||||||
|
"c": {Kind: &stpb.Value_ListValue{&stpb.ListValue{Values: []*stpb.Value{
|
||||||
|
{Kind: &stpb.Value_StructValue{&stpb.Struct{Fields: map[string]*stpb.Value{"d": {Kind: &stpb.Value_BoolValue{true}}}}}},
|
||||||
|
{Kind: &stpb.Value_StringValue{"f"}},
|
||||||
|
}}}},
|
||||||
|
}}}},
|
||||||
|
}}}},
|
||||||
|
{"null ListValue", Unmarshaler{}, `{"lv": null}`, &pb.KnownTypes{Lv: nil}},
|
||||||
|
{"empty ListValue", Unmarshaler{}, `{"lv": []}`, &pb.KnownTypes{Lv: &stpb.ListValue{}}},
|
||||||
|
{"basic ListValue", Unmarshaler{}, `{"lv": ["x", null, 3, true]}`, &pb.KnownTypes{Lv: &stpb.ListValue{Values: []*stpb.Value{
|
||||||
|
{Kind: &stpb.Value_StringValue{"x"}},
|
||||||
|
{Kind: &stpb.Value_NullValue{}},
|
||||||
|
{Kind: &stpb.Value_NumberValue{3}},
|
||||||
|
{Kind: &stpb.Value_BoolValue{true}},
|
||||||
|
}}}},
|
||||||
|
{"number Value", Unmarshaler{}, `{"val":1}`, &pb.KnownTypes{Val: &stpb.Value{Kind: &stpb.Value_NumberValue{1}}}},
|
||||||
|
{"null Value", Unmarshaler{}, `{"val":null}`, &pb.KnownTypes{Val: &stpb.Value{Kind: &stpb.Value_NullValue{stpb.NullValue_NULL_VALUE}}}},
|
||||||
|
{"bool Value", Unmarshaler{}, `{"val":true}`, &pb.KnownTypes{Val: &stpb.Value{Kind: &stpb.Value_BoolValue{true}}}},
|
||||||
|
{"string Value", Unmarshaler{}, `{"val":"x"}`, &pb.KnownTypes{Val: &stpb.Value{Kind: &stpb.Value_StringValue{"x"}}}},
|
||||||
|
{"string number value", Unmarshaler{}, `{"val":"9223372036854775807"}`, &pb.KnownTypes{Val: &stpb.Value{Kind: &stpb.Value_StringValue{"9223372036854775807"}}}},
|
||||||
|
{"list of lists Value", Unmarshaler{}, `{"val":["x", [["y"], "z"]]}`, &pb.KnownTypes{Val: &stpb.Value{
|
||||||
|
Kind: &stpb.Value_ListValue{&stpb.ListValue{
|
||||||
|
Values: []*stpb.Value{
|
||||||
|
{Kind: &stpb.Value_StringValue{"x"}},
|
||||||
|
{Kind: &stpb.Value_ListValue{&stpb.ListValue{
|
||||||
|
Values: []*stpb.Value{
|
||||||
|
{Kind: &stpb.Value_ListValue{&stpb.ListValue{
|
||||||
|
Values: []*stpb.Value{{Kind: &stpb.Value_StringValue{"y"}}},
|
||||||
|
}}},
|
||||||
|
{Kind: &stpb.Value_StringValue{"z"}},
|
||||||
|
},
|
||||||
|
}}},
|
||||||
|
},
|
||||||
|
}}}}},
|
||||||
|
|
||||||
|
{"DoubleValue", Unmarshaler{}, `{"dbl":1.2}`, &pb.KnownTypes{Dbl: &wpb.DoubleValue{Value: 1.2}}},
|
||||||
|
{"FloatValue", Unmarshaler{}, `{"flt":1.2}`, &pb.KnownTypes{Flt: &wpb.FloatValue{Value: 1.2}}},
|
||||||
|
{"Int64Value", Unmarshaler{}, `{"i64":"-3"}`, &pb.KnownTypes{I64: &wpb.Int64Value{Value: -3}}},
|
||||||
|
{"UInt64Value", Unmarshaler{}, `{"u64":"3"}`, &pb.KnownTypes{U64: &wpb.UInt64Value{Value: 3}}},
|
||||||
|
{"Int32Value", Unmarshaler{}, `{"i32":-4}`, &pb.KnownTypes{I32: &wpb.Int32Value{Value: -4}}},
|
||||||
|
{"UInt32Value", Unmarshaler{}, `{"u32":4}`, &pb.KnownTypes{U32: &wpb.UInt32Value{Value: 4}}},
|
||||||
|
{"BoolValue", Unmarshaler{}, `{"bool":true}`, &pb.KnownTypes{Bool: &wpb.BoolValue{Value: true}}},
|
||||||
|
{"StringValue", Unmarshaler{}, `{"str":"plush"}`, &pb.KnownTypes{Str: &wpb.StringValue{Value: "plush"}}},
|
||||||
|
{"BytesValue", Unmarshaler{}, `{"bytes":"d293"}`, &pb.KnownTypes{Bytes: &wpb.BytesValue{Value: []byte("wow")}}},
|
||||||
|
|
||||||
|
// Ensure that `null` as a value ends up with a nil pointer instead of a [type]Value struct.
|
||||||
|
{"null DoubleValue", Unmarshaler{}, `{"dbl":null}`, &pb.KnownTypes{Dbl: nil}},
|
||||||
|
{"null FloatValue", Unmarshaler{}, `{"flt":null}`, &pb.KnownTypes{Flt: nil}},
|
||||||
|
{"null Int64Value", Unmarshaler{}, `{"i64":null}`, &pb.KnownTypes{I64: nil}},
|
||||||
|
{"null UInt64Value", Unmarshaler{}, `{"u64":null}`, &pb.KnownTypes{U64: nil}},
|
||||||
|
{"null Int32Value", Unmarshaler{}, `{"i32":null}`, &pb.KnownTypes{I32: nil}},
|
||||||
|
{"null UInt32Value", Unmarshaler{}, `{"u32":null}`, &pb.KnownTypes{U32: nil}},
|
||||||
|
{"null BoolValue", Unmarshaler{}, `{"bool":null}`, &pb.KnownTypes{Bool: nil}},
|
||||||
|
{"null StringValue", Unmarshaler{}, `{"str":null}`, &pb.KnownTypes{Str: nil}},
|
||||||
|
{"null BytesValue", Unmarshaler{}, `{"bytes":null}`, &pb.KnownTypes{Bytes: nil}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshaling(t *testing.T) {
|
||||||
|
for _, tt := range unmarshalingTests {
|
||||||
|
// Make a new instance of the type of our expected object.
|
||||||
|
p := reflect.New(reflect.TypeOf(tt.pb).Elem()).Interface().(proto.Message)
|
||||||
|
|
||||||
|
err := tt.unmarshaler.Unmarshal(strings.NewReader(tt.json), p)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", tt.desc, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For easier diffs, compare text strings of the protos.
|
||||||
|
exp := proto.MarshalTextString(tt.pb)
|
||||||
|
act := proto.MarshalTextString(p)
|
||||||
|
if string(exp) != string(act) {
|
||||||
|
t.Errorf("%s: got [%s] want [%s]", tt.desc, act, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalNullArray(t *testing.T) {
|
||||||
|
var repeats pb.Repeats
|
||||||
|
if err := UnmarshalString(`{"rBool":null}`, &repeats); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(repeats, pb.Repeats{}) {
|
||||||
|
t.Errorf("got non-nil fields in [%#v]", repeats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalNullObject(t *testing.T) {
|
||||||
|
var maps pb.Maps
|
||||||
|
if err := UnmarshalString(`{"mInt64Str":null}`, &maps); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(maps, pb.Maps{}) {
|
||||||
|
t.Errorf("got non-nil fields in [%#v]", maps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalNext(t *testing.T) {
|
||||||
|
// We only need to check against a few, not all of them.
|
||||||
|
tests := unmarshalingTests[:5]
|
||||||
|
|
||||||
|
// Create a buffer with many concatenated JSON objects.
|
||||||
|
var b bytes.Buffer
|
||||||
|
for _, tt := range tests {
|
||||||
|
b.WriteString(tt.json)
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := json.NewDecoder(&b)
|
||||||
|
for _, tt := range tests {
|
||||||
|
// Make a new instance of the type of our expected object.
|
||||||
|
p := reflect.New(reflect.TypeOf(tt.pb).Elem()).Interface().(proto.Message)
|
||||||
|
|
||||||
|
err := tt.unmarshaler.UnmarshalNext(dec, p)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", tt.desc, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// For easier diffs, compare text strings of the protos.
|
||||||
|
exp := proto.MarshalTextString(tt.pb)
|
||||||
|
act := proto.MarshalTextString(p)
|
||||||
|
if string(exp) != string(act) {
|
||||||
|
t.Errorf("%s: got [%s] want [%s]", tt.desc, act, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &pb.Simple{}
|
||||||
|
err := new(Unmarshaler).UnmarshalNext(dec, p)
|
||||||
|
if err != io.EOF {
|
||||||
|
t.Errorf("eof: got %v, expected io.EOF", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var unmarshalingShouldError = []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
pb proto.Message
|
||||||
|
}{
|
||||||
|
{"a value", "666", new(pb.Simple)},
|
||||||
|
{"gibberish", "{adskja123;l23=-=", new(pb.Simple)},
|
||||||
|
{"unknown field", `{"unknown": "foo"}`, new(pb.Simple)},
|
||||||
|
{"unknown enum name", `{"hilarity":"DAVE"}`, new(proto3pb.Message)},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalingBadInput(t *testing.T) {
|
||||||
|
for _, tt := range unmarshalingShouldError {
|
||||||
|
err := UnmarshalString(tt.in, tt.pb)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("an error was expected when parsing %q instead of an object", tt.desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcResolver func(turl string) (proto.Message, error)
|
||||||
|
|
||||||
|
func (fn funcResolver) Resolve(turl string) (proto.Message, error) {
|
||||||
|
return fn(turl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnyWithCustomResolver(t *testing.T) {
|
||||||
|
var resolvedTypeUrls []string
|
||||||
|
resolver := funcResolver(func(turl string) (proto.Message, error) {
|
||||||
|
resolvedTypeUrls = append(resolvedTypeUrls, turl)
|
||||||
|
return new(pb.Simple), nil
|
||||||
|
})
|
||||||
|
msg := &pb.Simple{
|
||||||
|
OBytes: []byte{1, 2, 3, 4},
|
||||||
|
OBool: proto.Bool(true),
|
||||||
|
OString: proto.String("foobar"),
|
||||||
|
OInt64: proto.Int64(1020304),
|
||||||
|
}
|
||||||
|
msgBytes, err := proto.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("an unexpected error occurred when marshaling message: %v", err)
|
||||||
|
}
|
||||||
|
// make an Any with a type URL that won't resolve w/out custom resolver
|
||||||
|
any := &anypb.Any{
|
||||||
|
TypeUrl: "https://foobar.com/some.random.MessageKind",
|
||||||
|
Value: msgBytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
m := Marshaler{AnyResolver: resolver}
|
||||||
|
js, err := m.MarshalToString(any)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("an unexpected error occurred when marshaling any to JSON: %v", err)
|
||||||
|
}
|
||||||
|
if len(resolvedTypeUrls) != 1 {
|
||||||
|
t.Errorf("custom resolver was not invoked during marshaling")
|
||||||
|
} else if resolvedTypeUrls[0] != "https://foobar.com/some.random.MessageKind" {
|
||||||
|
t.Errorf("custom resolver was invoked with wrong URL: got %q, wanted %q", resolvedTypeUrls[0], "https://foobar.com/some.random.MessageKind")
|
||||||
|
}
|
||||||
|
wanted := `{"@type":"https://foobar.com/some.random.MessageKind","oBool":true,"oInt64":"1020304","oString":"foobar","oBytes":"AQIDBA=="}`
|
||||||
|
if js != wanted {
|
||||||
|
t.Errorf("marshalling JSON produced incorrect output: got %s, wanted %s", js, wanted)
|
||||||
|
}
|
||||||
|
|
||||||
|
u := Unmarshaler{AnyResolver: resolver}
|
||||||
|
roundTrip := &anypb.Any{}
|
||||||
|
err = u.Unmarshal(bytes.NewReader([]byte(js)), roundTrip)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("an unexpected error occurred when unmarshaling any from JSON: %v", err)
|
||||||
|
}
|
||||||
|
if len(resolvedTypeUrls) != 2 {
|
||||||
|
t.Errorf("custom resolver was not invoked during marshaling")
|
||||||
|
} else if resolvedTypeUrls[1] != "https://foobar.com/some.random.MessageKind" {
|
||||||
|
t.Errorf("custom resolver was invoked with wrong URL: got %q, wanted %q", resolvedTypeUrls[1], "https://foobar.com/some.random.MessageKind")
|
||||||
|
}
|
||||||
|
if !proto.Equal(any, roundTrip) {
|
||||||
|
t.Errorf("message contents not set correctly after unmarshalling JSON: got %s, wanted %s", roundTrip, any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalJSONPBUnmarshaler(t *testing.T) {
|
||||||
|
rawJson := `{ "foo": "bar", "baz": [0, 1, 2, 3] }`
|
||||||
|
var msg dynamicMessage
|
||||||
|
if err := Unmarshal(strings.NewReader(rawJson), &msg); err != nil {
|
||||||
|
t.Errorf("an unexpected error occurred when parsing into JSONPBUnmarshaler: %v", err)
|
||||||
|
}
|
||||||
|
if msg.rawJson != rawJson {
|
||||||
|
t.Errorf("message contents not set correctly after unmarshalling JSON: got %s, wanted %s", msg.rawJson, rawJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalNullWithJSONPBUnmarshaler(t *testing.T) {
|
||||||
|
rawJson := `{"stringField":null}`
|
||||||
|
var ptrFieldMsg ptrFieldMessage
|
||||||
|
if err := Unmarshal(strings.NewReader(rawJson), &ptrFieldMsg); err != nil {
|
||||||
|
t.Errorf("unmarshal error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := ptrFieldMessage{StringField: &stringField{IsSet: true, StringValue: "null"}}
|
||||||
|
if !proto.Equal(&ptrFieldMsg, &want) {
|
||||||
|
t.Errorf("unmarshal result StringField: got %v, want %v", ptrFieldMsg, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalAnyJSONPBUnmarshaler(t *testing.T) {
|
||||||
|
rawJson := `{ "@type": "blah.com/` + dynamicMessageName + `", "foo": "bar", "baz": [0, 1, 2, 3] }`
|
||||||
|
var got anypb.Any
|
||||||
|
if err := Unmarshal(strings.NewReader(rawJson), &got); err != nil {
|
||||||
|
t.Errorf("an unexpected error occurred when parsing into JSONPBUnmarshaler: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dm := &dynamicMessage{rawJson: `{"baz":[0,1,2,3],"foo":"bar"}`}
|
||||||
|
var want anypb.Any
|
||||||
|
if b, err := proto.Marshal(dm); err != nil {
|
||||||
|
t.Errorf("an unexpected error occurred when marshaling message: %v", err)
|
||||||
|
} else {
|
||||||
|
want.TypeUrl = "blah.com/" + dynamicMessageName
|
||||||
|
want.Value = b
|
||||||
|
}
|
||||||
|
|
||||||
|
if !proto.Equal(&got, &want) {
|
||||||
|
t.Errorf("message contents not set correctly after unmarshalling JSON: got %s, wanted %s", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
dynamicMessageName = "google.protobuf.jsonpb.testing.dynamicMessage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// we register the custom type below so that we can use it in Any types
|
||||||
|
proto.RegisterType((*dynamicMessage)(nil), dynamicMessageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ptrFieldMessage struct {
|
||||||
|
StringField *stringField `protobuf:"bytes,1,opt,name=stringField"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ptrFieldMessage) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ptrFieldMessage) String() string {
|
||||||
|
return m.StringField.StringValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ptrFieldMessage) ProtoMessage() {
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringField struct {
|
||||||
|
IsSet bool `protobuf:"varint,1,opt,name=isSet"`
|
||||||
|
StringValue string `protobuf:"bytes,2,opt,name=stringValue"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringField) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringField) String() string {
|
||||||
|
return s.StringValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringField) ProtoMessage() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringField) UnmarshalJSONPB(jum *Unmarshaler, js []byte) error {
|
||||||
|
s.IsSet = true
|
||||||
|
s.StringValue = string(js)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dynamicMessage implements protobuf.Message but is not a normal generated message type.
|
||||||
|
// It provides implementations of JSONPBMarshaler and JSONPBUnmarshaler for JSON support.
|
||||||
|
type dynamicMessage struct {
|
||||||
|
rawJson string `protobuf:"bytes,1,opt,name=rawJson"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *dynamicMessage) Reset() {
|
||||||
|
m.rawJson = "{}"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *dynamicMessage) String() string {
|
||||||
|
return m.rawJson
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *dynamicMessage) ProtoMessage() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *dynamicMessage) MarshalJSONPB(jm *Marshaler) ([]byte, error) {
|
||||||
|
return []byte(m.rawJson), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *dynamicMessage) UnmarshalJSONPB(jum *Unmarshaler, js []byte) error {
|
||||||
|
m.rawJson = string(js)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,380 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// source: google/protobuf/struct.proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package structpb is a generated protocol buffer package.
|
||||||
|
|
||||||
|
It is generated from these files:
|
||||||
|
google/protobuf/struct.proto
|
||||||
|
|
||||||
|
It has these top-level messages:
|
||||||
|
Struct
|
||||||
|
Value
|
||||||
|
ListValue
|
||||||
|
*/
|
||||||
|
package structpb
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// `NullValue` is a singleton enumeration to represent the null value for the
|
||||||
|
// `Value` type union.
|
||||||
|
//
|
||||||
|
// The JSON representation for `NullValue` is JSON `null`.
|
||||||
|
type NullValue int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Null value.
|
||||||
|
NullValue_NULL_VALUE NullValue = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
var NullValue_name = map[int32]string{
|
||||||
|
0: "NULL_VALUE",
|
||||||
|
}
|
||||||
|
var NullValue_value = map[string]int32{
|
||||||
|
"NULL_VALUE": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x NullValue) String() string {
|
||||||
|
return proto.EnumName(NullValue_name, int32(x))
|
||||||
|
}
|
||||||
|
func (NullValue) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||||
|
func (NullValue) XXX_WellKnownType() string { return "NullValue" }
|
||||||
|
|
||||||
|
// `Struct` represents a structured data value, consisting of fields
|
||||||
|
// which map to dynamically typed values. In some languages, `Struct`
|
||||||
|
// might be supported by a native representation. For example, in
|
||||||
|
// scripting languages like JS a struct is represented as an
|
||||||
|
// object. The details of that representation are described together
|
||||||
|
// with the proto support for the language.
|
||||||
|
//
|
||||||
|
// The JSON representation for `Struct` is JSON object.
|
||||||
|
type Struct struct {
|
||||||
|
// Unordered map of dynamically typed values.
|
||||||
|
Fields map[string]*Value `protobuf:"bytes,1,rep,name=fields" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Struct) Reset() { *m = Struct{} }
|
||||||
|
func (m *Struct) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Struct) ProtoMessage() {}
|
||||||
|
func (*Struct) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||||
|
func (*Struct) XXX_WellKnownType() string { return "Struct" }
|
||||||
|
|
||||||
|
func (m *Struct) GetFields() map[string]*Value {
|
||||||
|
if m != nil {
|
||||||
|
return m.Fields
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Value` represents a dynamically typed value which can be either
|
||||||
|
// null, a number, a string, a boolean, a recursive struct value, or a
|
||||||
|
// list of values. A producer of value is expected to set one of that
|
||||||
|
// variants, absence of any variant indicates an error.
|
||||||
|
//
|
||||||
|
// The JSON representation for `Value` is JSON value.
|
||||||
|
type Value struct {
|
||||||
|
// The kind of value.
|
||||||
|
//
|
||||||
|
// Types that are valid to be assigned to Kind:
|
||||||
|
// *Value_NullValue
|
||||||
|
// *Value_NumberValue
|
||||||
|
// *Value_StringValue
|
||||||
|
// *Value_BoolValue
|
||||||
|
// *Value_StructValue
|
||||||
|
// *Value_ListValue
|
||||||
|
Kind isValue_Kind `protobuf_oneof:"kind"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Value) Reset() { *m = Value{} }
|
||||||
|
func (m *Value) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Value) ProtoMessage() {}
|
||||||
|
func (*Value) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||||
|
func (*Value) XXX_WellKnownType() string { return "Value" }
|
||||||
|
|
||||||
|
type isValue_Kind interface {
|
||||||
|
isValue_Kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Value_NullValue struct {
|
||||||
|
NullValue NullValue `protobuf:"varint,1,opt,name=null_value,json=nullValue,enum=google.protobuf.NullValue,oneof"`
|
||||||
|
}
|
||||||
|
type Value_NumberValue struct {
|
||||||
|
NumberValue float64 `protobuf:"fixed64,2,opt,name=number_value,json=numberValue,oneof"`
|
||||||
|
}
|
||||||
|
type Value_StringValue struct {
|
||||||
|
StringValue string `protobuf:"bytes,3,opt,name=string_value,json=stringValue,oneof"`
|
||||||
|
}
|
||||||
|
type Value_BoolValue struct {
|
||||||
|
BoolValue bool `protobuf:"varint,4,opt,name=bool_value,json=boolValue,oneof"`
|
||||||
|
}
|
||||||
|
type Value_StructValue struct {
|
||||||
|
StructValue *Struct `protobuf:"bytes,5,opt,name=struct_value,json=structValue,oneof"`
|
||||||
|
}
|
||||||
|
type Value_ListValue struct {
|
||||||
|
ListValue *ListValue `protobuf:"bytes,6,opt,name=list_value,json=listValue,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Value_NullValue) isValue_Kind() {}
|
||||||
|
func (*Value_NumberValue) isValue_Kind() {}
|
||||||
|
func (*Value_StringValue) isValue_Kind() {}
|
||||||
|
func (*Value_BoolValue) isValue_Kind() {}
|
||||||
|
func (*Value_StructValue) isValue_Kind() {}
|
||||||
|
func (*Value_ListValue) isValue_Kind() {}
|
||||||
|
|
||||||
|
func (m *Value) GetKind() isValue_Kind {
|
||||||
|
if m != nil {
|
||||||
|
return m.Kind
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Value) GetNullValue() NullValue {
|
||||||
|
if x, ok := m.GetKind().(*Value_NullValue); ok {
|
||||||
|
return x.NullValue
|
||||||
|
}
|
||||||
|
return NullValue_NULL_VALUE
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Value) GetNumberValue() float64 {
|
||||||
|
if x, ok := m.GetKind().(*Value_NumberValue); ok {
|
||||||
|
return x.NumberValue
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Value) GetStringValue() string {
|
||||||
|
if x, ok := m.GetKind().(*Value_StringValue); ok {
|
||||||
|
return x.StringValue
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Value) GetBoolValue() bool {
|
||||||
|
if x, ok := m.GetKind().(*Value_BoolValue); ok {
|
||||||
|
return x.BoolValue
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Value) GetStructValue() *Struct {
|
||||||
|
if x, ok := m.GetKind().(*Value_StructValue); ok {
|
||||||
|
return x.StructValue
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Value) GetListValue() *ListValue {
|
||||||
|
if x, ok := m.GetKind().(*Value_ListValue); ok {
|
||||||
|
return x.ListValue
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX_OneofFuncs is for the internal use of the proto package.
|
||||||
|
func (*Value) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
|
||||||
|
return _Value_OneofMarshaler, _Value_OneofUnmarshaler, _Value_OneofSizer, []interface{}{
|
||||||
|
(*Value_NullValue)(nil),
|
||||||
|
(*Value_NumberValue)(nil),
|
||||||
|
(*Value_StringValue)(nil),
|
||||||
|
(*Value_BoolValue)(nil),
|
||||||
|
(*Value_StructValue)(nil),
|
||||||
|
(*Value_ListValue)(nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func _Value_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
|
||||||
|
m := msg.(*Value)
|
||||||
|
// kind
|
||||||
|
switch x := m.Kind.(type) {
|
||||||
|
case *Value_NullValue:
|
||||||
|
b.EncodeVarint(1<<3 | proto.WireVarint)
|
||||||
|
b.EncodeVarint(uint64(x.NullValue))
|
||||||
|
case *Value_NumberValue:
|
||||||
|
b.EncodeVarint(2<<3 | proto.WireFixed64)
|
||||||
|
b.EncodeFixed64(math.Float64bits(x.NumberValue))
|
||||||
|
case *Value_StringValue:
|
||||||
|
b.EncodeVarint(3<<3 | proto.WireBytes)
|
||||||
|
b.EncodeStringBytes(x.StringValue)
|
||||||
|
case *Value_BoolValue:
|
||||||
|
t := uint64(0)
|
||||||
|
if x.BoolValue {
|
||||||
|
t = 1
|
||||||
|
}
|
||||||
|
b.EncodeVarint(4<<3 | proto.WireVarint)
|
||||||
|
b.EncodeVarint(t)
|
||||||
|
case *Value_StructValue:
|
||||||
|
b.EncodeVarint(5<<3 | proto.WireBytes)
|
||||||
|
if err := b.EncodeMessage(x.StructValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case *Value_ListValue:
|
||||||
|
b.EncodeVarint(6<<3 | proto.WireBytes)
|
||||||
|
if err := b.EncodeMessage(x.ListValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case nil:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Value.Kind has unexpected type %T", x)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func _Value_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
|
||||||
|
m := msg.(*Value)
|
||||||
|
switch tag {
|
||||||
|
case 1: // kind.null_value
|
||||||
|
if wire != proto.WireVarint {
|
||||||
|
return true, proto.ErrInternalBadWireType
|
||||||
|
}
|
||||||
|
x, err := b.DecodeVarint()
|
||||||
|
m.Kind = &Value_NullValue{NullValue(x)}
|
||||||
|
return true, err
|
||||||
|
case 2: // kind.number_value
|
||||||
|
if wire != proto.WireFixed64 {
|
||||||
|
return true, proto.ErrInternalBadWireType
|
||||||
|
}
|
||||||
|
x, err := b.DecodeFixed64()
|
||||||
|
m.Kind = &Value_NumberValue{math.Float64frombits(x)}
|
||||||
|
return true, err
|
||||||
|
case 3: // kind.string_value
|
||||||
|
if wire != proto.WireBytes {
|
||||||
|
return true, proto.ErrInternalBadWireType
|
||||||
|
}
|
||||||
|
x, err := b.DecodeStringBytes()
|
||||||
|
m.Kind = &Value_StringValue{x}
|
||||||
|
return true, err
|
||||||
|
case 4: // kind.bool_value
|
||||||
|
if wire != proto.WireVarint {
|
||||||
|
return true, proto.ErrInternalBadWireType
|
||||||
|
}
|
||||||
|
x, err := b.DecodeVarint()
|
||||||
|
m.Kind = &Value_BoolValue{x != 0}
|
||||||
|
return true, err
|
||||||
|
case 5: // kind.struct_value
|
||||||
|
if wire != proto.WireBytes {
|
||||||
|
return true, proto.ErrInternalBadWireType
|
||||||
|
}
|
||||||
|
msg := new(Struct)
|
||||||
|
err := b.DecodeMessage(msg)
|
||||||
|
m.Kind = &Value_StructValue{msg}
|
||||||
|
return true, err
|
||||||
|
case 6: // kind.list_value
|
||||||
|
if wire != proto.WireBytes {
|
||||||
|
return true, proto.ErrInternalBadWireType
|
||||||
|
}
|
||||||
|
msg := new(ListValue)
|
||||||
|
err := b.DecodeMessage(msg)
|
||||||
|
m.Kind = &Value_ListValue{msg}
|
||||||
|
return true, err
|
||||||
|
default:
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func _Value_OneofSizer(msg proto.Message) (n int) {
|
||||||
|
m := msg.(*Value)
|
||||||
|
// kind
|
||||||
|
switch x := m.Kind.(type) {
|
||||||
|
case *Value_NullValue:
|
||||||
|
n += proto.SizeVarint(1<<3 | proto.WireVarint)
|
||||||
|
n += proto.SizeVarint(uint64(x.NullValue))
|
||||||
|
case *Value_NumberValue:
|
||||||
|
n += proto.SizeVarint(2<<3 | proto.WireFixed64)
|
||||||
|
n += 8
|
||||||
|
case *Value_StringValue:
|
||||||
|
n += proto.SizeVarint(3<<3 | proto.WireBytes)
|
||||||
|
n += proto.SizeVarint(uint64(len(x.StringValue)))
|
||||||
|
n += len(x.StringValue)
|
||||||
|
case *Value_BoolValue:
|
||||||
|
n += proto.SizeVarint(4<<3 | proto.WireVarint)
|
||||||
|
n += 1
|
||||||
|
case *Value_StructValue:
|
||||||
|
s := proto.Size(x.StructValue)
|
||||||
|
n += proto.SizeVarint(5<<3 | proto.WireBytes)
|
||||||
|
n += proto.SizeVarint(uint64(s))
|
||||||
|
n += s
|
||||||
|
case *Value_ListValue:
|
||||||
|
s := proto.Size(x.ListValue)
|
||||||
|
n += proto.SizeVarint(6<<3 | proto.WireBytes)
|
||||||
|
n += proto.SizeVarint(uint64(s))
|
||||||
|
n += s
|
||||||
|
case nil:
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("proto: unexpected type %T in oneof", x))
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// `ListValue` is a wrapper around a repeated field of values.
|
||||||
|
//
|
||||||
|
// The JSON representation for `ListValue` is JSON array.
|
||||||
|
type ListValue struct {
|
||||||
|
// Repeated field of dynamically typed values.
|
||||||
|
Values []*Value `protobuf:"bytes,1,rep,name=values" json:"values,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ListValue) Reset() { *m = ListValue{} }
|
||||||
|
func (m *ListValue) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*ListValue) ProtoMessage() {}
|
||||||
|
func (*ListValue) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
||||||
|
func (*ListValue) XXX_WellKnownType() string { return "ListValue" }
|
||||||
|
|
||||||
|
func (m *ListValue) GetValues() []*Value {
|
||||||
|
if m != nil {
|
||||||
|
return m.Values
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterType((*Struct)(nil), "google.protobuf.Struct")
|
||||||
|
proto.RegisterType((*Value)(nil), "google.protobuf.Value")
|
||||||
|
proto.RegisterType((*ListValue)(nil), "google.protobuf.ListValue")
|
||||||
|
proto.RegisterEnum("google.protobuf.NullValue", NullValue_name, NullValue_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { proto.RegisterFile("google/protobuf/struct.proto", fileDescriptor0) }
|
||||||
|
|
||||||
|
var fileDescriptor0 = []byte{
|
||||||
|
// 417 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0x41, 0x8b, 0xd3, 0x40,
|
||||||
|
0x14, 0xc7, 0x3b, 0xc9, 0x36, 0x98, 0x17, 0x59, 0x97, 0x11, 0xb4, 0xac, 0xa2, 0xa1, 0x7b, 0x09,
|
||||||
|
0x22, 0x29, 0xd6, 0x8b, 0x18, 0x2f, 0x06, 0xd6, 0x5d, 0x30, 0x2c, 0x31, 0xba, 0x15, 0xbc, 0x94,
|
||||||
|
0x26, 0x4d, 0x63, 0xe8, 0x74, 0x26, 0x24, 0x33, 0x4a, 0x8f, 0x7e, 0x0b, 0xcf, 0x1e, 0x3d, 0xfa,
|
||||||
|
0xe9, 0x3c, 0xca, 0xcc, 0x24, 0xa9, 0xb4, 0xf4, 0x94, 0xbc, 0xf7, 0x7e, 0xef, 0x3f, 0xef, 0xff,
|
||||||
|
0x66, 0xe0, 0x71, 0xc1, 0x58, 0x41, 0xf2, 0x49, 0x55, 0x33, 0xce, 0x52, 0xb1, 0x9a, 0x34, 0xbc,
|
||||||
|
0x16, 0x19, 0xf7, 0x55, 0x8c, 0xef, 0xe9, 0xaa, 0xdf, 0x55, 0xc7, 0x3f, 0x11, 0x58, 0x1f, 0x15,
|
||||||
|
0x81, 0x03, 0xb0, 0x56, 0x65, 0x4e, 0x96, 0xcd, 0x08, 0xb9, 0xa6, 0xe7, 0x4c, 0x2f, 0xfc, 0x3d,
|
||||||
|
0xd8, 0xd7, 0xa0, 0xff, 0x4e, 0x51, 0x97, 0x94, 0xd7, 0xdb, 0xa4, 0x6d, 0x39, 0xff, 0x00, 0xce,
|
||||||
|
0x7f, 0x69, 0x7c, 0x06, 0xe6, 0x3a, 0xdf, 0x8e, 0x90, 0x8b, 0x3c, 0x3b, 0x91, 0xbf, 0xf8, 0x39,
|
||||||
|
0x0c, 0xbf, 0x2d, 0x88, 0xc8, 0x47, 0x86, 0x8b, 0x3c, 0x67, 0xfa, 0xe0, 0x40, 0x7c, 0x26, 0xab,
|
||||||
|
0x89, 0x86, 0x5e, 0x1b, 0xaf, 0xd0, 0xf8, 0x8f, 0x01, 0x43, 0x95, 0xc4, 0x01, 0x00, 0x15, 0x84,
|
||||||
|
0xcc, 0xb5, 0x80, 0x14, 0x3d, 0x9d, 0x9e, 0x1f, 0x08, 0xdc, 0x08, 0x42, 0x14, 0x7f, 0x3d, 0x48,
|
||||||
|
0x6c, 0xda, 0x05, 0xf8, 0x02, 0xee, 0x52, 0xb1, 0x49, 0xf3, 0x7a, 0xbe, 0x3b, 0x1f, 0x5d, 0x0f,
|
||||||
|
0x12, 0x47, 0x67, 0x7b, 0xa8, 0xe1, 0x75, 0x49, 0x8b, 0x16, 0x32, 0xe5, 0xe0, 0x12, 0xd2, 0x59,
|
||||||
|
0x0d, 0x3d, 0x05, 0x48, 0x19, 0xeb, 0xc6, 0x38, 0x71, 0x91, 0x77, 0x47, 0x1e, 0x25, 0x73, 0x1a,
|
||||||
|
0x78, 0xa3, 0x54, 0x44, 0xc6, 0x5b, 0x64, 0xa8, 0xac, 0x3e, 0x3c, 0xb2, 0xc7, 0x56, 0x5e, 0x64,
|
||||||
|
0xbc, 0x77, 0x49, 0xca, 0xa6, 0xeb, 0xb5, 0x54, 0xef, 0xa1, 0xcb, 0xa8, 0x6c, 0x78, 0xef, 0x92,
|
||||||
|
0x74, 0x41, 0x68, 0xc1, 0xc9, 0xba, 0xa4, 0xcb, 0x71, 0x00, 0x76, 0x4f, 0x60, 0x1f, 0x2c, 0x25,
|
||||||
|
0xd6, 0xdd, 0xe8, 0xb1, 0xa5, 0xb7, 0xd4, 0xb3, 0x47, 0x60, 0xf7, 0x4b, 0xc4, 0xa7, 0x00, 0x37,
|
||||||
|
0xb7, 0x51, 0x34, 0x9f, 0xbd, 0x8d, 0x6e, 0x2f, 0xcf, 0x06, 0xe1, 0x0f, 0x04, 0xf7, 0x33, 0xb6,
|
||||||
|
0xd9, 0x97, 0x08, 0x1d, 0xed, 0x26, 0x96, 0x71, 0x8c, 0xbe, 0xbc, 0x28, 0x4a, 0xfe, 0x55, 0xa4,
|
||||||
|
0x7e, 0xc6, 0x36, 0x93, 0x82, 0x91, 0x05, 0x2d, 0x76, 0x4f, 0xb1, 0xe2, 0xdb, 0x2a, 0x6f, 0xda,
|
||||||
|
0x17, 0x19, 0xe8, 0x4f, 0x95, 0xfe, 0x45, 0xe8, 0x97, 0x61, 0x5e, 0xc5, 0xe1, 0x6f, 0xe3, 0xc9,
|
||||||
|
0x95, 0x16, 0x8f, 0xbb, 0xf9, 0x3e, 0xe7, 0x84, 0xbc, 0xa7, 0xec, 0x3b, 0xfd, 0x24, 0x3b, 0x53,
|
||||||
|
0x4b, 0x49, 0xbd, 0xfc, 0x17, 0x00, 0x00, 0xff, 0xff, 0xe8, 0x1b, 0x59, 0xf8, 0xe5, 0x02, 0x00,
|
||||||
|
0x00,
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2008 Google Inc. All rights reserved.
|
||||||
|
// https://developers.google.com/protocol-buffers/
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package google.protobuf;
|
||||||
|
|
||||||
|
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
|
||||||
|
option cc_enable_arenas = true;
|
||||||
|
option go_package = "github.com/golang/protobuf/ptypes/struct;structpb";
|
||||||
|
option java_package = "com.google.protobuf";
|
||||||
|
option java_outer_classname = "StructProto";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option objc_class_prefix = "GPB";
|
||||||
|
|
||||||
|
|
||||||
|
// `Struct` represents a structured data value, consisting of fields
|
||||||
|
// which map to dynamically typed values. In some languages, `Struct`
|
||||||
|
// might be supported by a native representation. For example, in
|
||||||
|
// scripting languages like JS a struct is represented as an
|
||||||
|
// object. The details of that representation are described together
|
||||||
|
// with the proto support for the language.
|
||||||
|
//
|
||||||
|
// The JSON representation for `Struct` is JSON object.
|
||||||
|
message Struct {
|
||||||
|
// Unordered map of dynamically typed values.
|
||||||
|
map<string, Value> fields = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Value` represents a dynamically typed value which can be either
|
||||||
|
// null, a number, a string, a boolean, a recursive struct value, or a
|
||||||
|
// list of values. A producer of value is expected to set one of that
|
||||||
|
// variants, absence of any variant indicates an error.
|
||||||
|
//
|
||||||
|
// The JSON representation for `Value` is JSON value.
|
||||||
|
message Value {
|
||||||
|
// The kind of value.
|
||||||
|
oneof kind {
|
||||||
|
// Represents a null value.
|
||||||
|
NullValue null_value = 1;
|
||||||
|
// Represents a double value.
|
||||||
|
double number_value = 2;
|
||||||
|
// Represents a string value.
|
||||||
|
string string_value = 3;
|
||||||
|
// Represents a boolean value.
|
||||||
|
bool bool_value = 4;
|
||||||
|
// Represents a structured value.
|
||||||
|
Struct struct_value = 5;
|
||||||
|
// Represents a repeated `Value`.
|
||||||
|
ListValue list_value = 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `NullValue` is a singleton enumeration to represent the null value for the
|
||||||
|
// `Value` type union.
|
||||||
|
//
|
||||||
|
// The JSON representation for `NullValue` is JSON `null`.
|
||||||
|
enum NullValue {
|
||||||
|
// Null value.
|
||||||
|
NULL_VALUE = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `ListValue` is a wrapper around a repeated field of values.
|
||||||
|
//
|
||||||
|
// The JSON representation for `ListValue` is JSON array.
|
||||||
|
message ListValue {
|
||||||
|
// Repeated field of dynamically typed values.
|
||||||
|
repeated Value values = 1;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
*.test
|
||||||
|
/bin
|
||||||
|
|
||||||
|
clientcompat/pycompat/ENV
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
build
|
|
@ -0,0 +1,62 @@
|
||||||
|
# Contributing #
|
||||||
|
|
||||||
|
Thanks for helping make Twirp better! This is great!
|
||||||
|
|
||||||
|
First, if you have run into a bug, please file an issue. We try to get back to
|
||||||
|
issue reporters within a day or two. We might be able to help you right away.
|
||||||
|
|
||||||
|
If you'd rather not publicly discuss the issue, please email spencer@twitch.tv
|
||||||
|
and/or security@twitch.tv.
|
||||||
|
|
||||||
|
Issues are also a good place to present experience reports or requests for new
|
||||||
|
features.
|
||||||
|
|
||||||
|
If you'd like to make changes to Twirp, read on:
|
||||||
|
|
||||||
|
## Setup Requirements ##
|
||||||
|
|
||||||
|
You will need git, Go 1.9+, and Python 2.7 installed and on your system's path.
|
||||||
|
Install them however you feel.
|
||||||
|
|
||||||
|
We work on a branch called `develop`. We periodically release this branch as a
|
||||||
|
new version, then accumulate more changes on the develop branch until the next
|
||||||
|
release. Use `develop` as the base for your branches.
|
||||||
|
|
||||||
|
## Developer Loop ##
|
||||||
|
|
||||||
|
Generally you want to make changes and run `make`, which will install all
|
||||||
|
dependencies we know about, build the core, and run all of the tests that we
|
||||||
|
have against all of the languages we support.
|
||||||
|
|
||||||
|
Most tests of the Go server are in `internal/twirptest/service_test.go`. Tests
|
||||||
|
of cross-language clients are in the [clientcompat](./clientcompat) directory.
|
||||||
|
|
||||||
|
## Contributing Code ##
|
||||||
|
|
||||||
|
Twirp uses github pull requests. Fork a branch from `develop`, hack away at your
|
||||||
|
changes, run the test suite with `make`, and submit a PR.
|
||||||
|
|
||||||
|
## Releasing Versions ##
|
||||||
|
|
||||||
|
Releasing versions is the responsibility of the core maintainers. Most people
|
||||||
|
don't need to know this stuff.
|
||||||
|
|
||||||
|
Twirp uses [Semantic versioning](http://semver.org/): `v<major>.<minor>.<patch>`.
|
||||||
|
|
||||||
|
* Increment major if you're making a backwards-incompatible change.
|
||||||
|
* Increment minor if you're adding a feature that's backwards-compatible.
|
||||||
|
* Increment patch if you're making a bugfix.
|
||||||
|
|
||||||
|
To make a release, remember to update the version number in
|
||||||
|
[internal/gen/version.go](./internal/gen/version.go).
|
||||||
|
|
||||||
|
Twirp uses Github releases. To make a new release:
|
||||||
|
1. Merge all changes that should be included in the release into the master
|
||||||
|
branch.
|
||||||
|
2. Add a new commit to master with a message like "Version vX.X.X release".
|
||||||
|
3. Tag the commit you just made: `git tag <version number>` and `git push
|
||||||
|
origin --tags`
|
||||||
|
3. Go to Github https://github.com/twitchtv/twirp/releases and
|
||||||
|
"Draft a new release".
|
||||||
|
4. Make sure to document changes, specially when upgrade instructions are
|
||||||
|
needed.
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2018 Twitch Interactive, Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,51 @@
|
||||||
|
RETOOL=$(CURDIR)/_tools/bin/retool
|
||||||
|
PATH := ${PWD}/bin:${PWD}/ENV/bin:${PATH}
|
||||||
|
.DEFAULT_GOAL := all
|
||||||
|
|
||||||
|
all: setup test_all
|
||||||
|
|
||||||
|
.PHONY: test test_all test_core test_clients test_go_client test_python_client generate
|
||||||
|
|
||||||
|
# Phony commands:
|
||||||
|
generate:
|
||||||
|
PATH=$(CURDIR)/_tools/bin:$(PATH) GOBIN="${PWD}/bin" go install -v ./protoc-gen-...
|
||||||
|
$(RETOOL) do go generate ./...
|
||||||
|
|
||||||
|
test_all: setup test_core test_clients
|
||||||
|
|
||||||
|
test_core: generate
|
||||||
|
$(RETOOL) do errcheck -blank ./internal/twirptest
|
||||||
|
go test -race $(shell go list ./... | grep -v /vendor/ | grep -v /_tools/)
|
||||||
|
|
||||||
|
test_clients: test_go_client test_python_client
|
||||||
|
|
||||||
|
test_go_client: generate build/clientcompat build/gocompat
|
||||||
|
./build/clientcompat -client ./build/gocompat
|
||||||
|
|
||||||
|
test_python_client: generate build/clientcompat build/pycompat
|
||||||
|
./build/clientcompat -client ./build/pycompat
|
||||||
|
|
||||||
|
setup:
|
||||||
|
./install_proto.bash
|
||||||
|
GOPATH=$(CURDIR)/_tools go install github.com/twitchtv/retool/...
|
||||||
|
$(RETOOL) build
|
||||||
|
|
||||||
|
# Actual files for testing clients:
|
||||||
|
./build:
|
||||||
|
mkdir build
|
||||||
|
|
||||||
|
./build/gocompat: ./build
|
||||||
|
go build -o build/gocompat ./clientcompat/gocompat
|
||||||
|
|
||||||
|
./build/clientcompat: ./build
|
||||||
|
go build -o build/clientcompat ./clientcompat
|
||||||
|
|
||||||
|
./build/venv: ./build
|
||||||
|
virtualenv ./build/venv
|
||||||
|
|
||||||
|
./build/venv/bin/pycompat.py: ./build/venv
|
||||||
|
./build/venv/bin/pip install --upgrade ./clientcompat/pycompat
|
||||||
|
|
||||||
|
./build/pycompat: ./build/venv/bin/pycompat.py
|
||||||
|
cp ./clientcompat/pycompat/pycompat.sh ./build/pycompat
|
||||||
|
chmod +x ./build/pycompat
|
|
@ -0,0 +1,34 @@
|
||||||
|
![Twirp Logo](./logo.png)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Twirp is a framework for service-to-service communication emphasizing simplicity
|
||||||
|
and minimalism. It generates routing and serialization from API definition files
|
||||||
|
and lets you focus on your application's logic instead of thinking about
|
||||||
|
folderol like HTTP methods and paths and JSON.
|
||||||
|
|
||||||
|
Define your service in a
|
||||||
|
[Protobuf](https://developers.google.com/protocol-buffers/docs/proto3) file and
|
||||||
|
then Twirp autogenerates Go code with a server interface and fully functional
|
||||||
|
clients. It's similar to [gRPC](http://www.grpc.io/), but without the custom
|
||||||
|
HTTP server and transport implementations: it runs on the standard library's
|
||||||
|
extremely-well-tested-and-high-performance `net/http` Server. It can run on HTTP
|
||||||
|
1.1, not just http/2, and supports JSON clients for easy integrations across
|
||||||
|
languages
|
||||||
|
|
||||||
|
Twirp handles routing and serialization for you in a well-tested, standardized,
|
||||||
|
thoughtful way so you don't have to. Serialization and deserialization code is
|
||||||
|
error-prone and tricky, and you shouldn't be wasting your time deciding whether
|
||||||
|
it should be "POST /friends/:id/new" or "POST /:id/friend" or whatever. Just
|
||||||
|
get to the real work of building services!
|
||||||
|
|
||||||
|
Along the way, you get an autogenerated client and a simple, smart framework for
|
||||||
|
passing error messages. Nice!
|
||||||
|
|
||||||
|
### Releases
|
||||||
|
Twirp follows semantic versioning through git tags, and uses Github Releases for
|
||||||
|
release notes and upgrade guides:
|
||||||
|
[Twirp Releases](https://github.com/twitchtv/twirp/releases)
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
Check out [CONTRIBUTING.md](./CONTRIBUTING.md) for notes on making contributions.
|
|
@ -0,0 +1,109 @@
|
||||||
|
package twirp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/twitchtv/twirp/internal/contextkeys"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MethodName extracts the name of the method being handled in the given
|
||||||
|
// context. If it is not known, it returns ("", false).
|
||||||
|
func MethodName(ctx context.Context) (string, bool) {
|
||||||
|
name, ok := ctx.Value(contextkeys.MethodNameKey).(string)
|
||||||
|
return name, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceName extracts the name of the service handling the given context. If
|
||||||
|
// it is not known, it returns ("", false).
|
||||||
|
func ServiceName(ctx context.Context) (string, bool) {
|
||||||
|
name, ok := ctx.Value(contextkeys.ServiceNameKey).(string)
|
||||||
|
return name, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackageName extracts the fully-qualified protobuf package name of the service
|
||||||
|
// handling the given context. If it is not known, it returns ("", false). If
|
||||||
|
// the service comes from a proto file that does not declare a package name, it
|
||||||
|
// returns ("", true).
|
||||||
|
//
|
||||||
|
// Note that the protobuf package name can be very different than the go package
|
||||||
|
// name; the two are unrelated.
|
||||||
|
func PackageName(ctx context.Context) (string, bool) {
|
||||||
|
name, ok := ctx.Value(contextkeys.PackageNameKey).(string)
|
||||||
|
return name, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusCode retrieves the status code of the response (as string like "200").
|
||||||
|
// If it is known returns (status, true).
|
||||||
|
// If it is not known, it returns ("", false).
|
||||||
|
func StatusCode(ctx context.Context) (string, bool) {
|
||||||
|
code, ok := ctx.Value(contextkeys.StatusCodeKey).(string)
|
||||||
|
return code, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHTTPRequestHeaders stores an http.Header in a context.Context. When
|
||||||
|
// using a Twirp-generated client, you can pass the returned context
|
||||||
|
// into any of the request methods, and the stored header will be
|
||||||
|
// included in outbound HTTP requests.
|
||||||
|
//
|
||||||
|
// This can be used to set custom HTTP headers like authorization tokens or
|
||||||
|
// client IDs. But note that HTTP headers are a Twirp implementation detail,
|
||||||
|
// only visible by middleware, not by the server implementtion.
|
||||||
|
//
|
||||||
|
// WithHTTPRequestHeaders returns an error if the provided http.Header
|
||||||
|
// would overwrite a header that is needed by Twirp, like "Content-Type".
|
||||||
|
func WithHTTPRequestHeaders(ctx context.Context, h http.Header) (context.Context, error) {
|
||||||
|
if _, ok := h["Content-Type"]; ok {
|
||||||
|
return nil, errors.New("provided header cannot set Content-Type")
|
||||||
|
}
|
||||||
|
if _, ok := h["Twirp-Version"]; ok {
|
||||||
|
return nil, errors.New("provided header cannot set Twirp-Version")
|
||||||
|
}
|
||||||
|
|
||||||
|
copied := make(http.Header, len(h))
|
||||||
|
for k, vv := range h {
|
||||||
|
if vv == nil {
|
||||||
|
copied[k] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
copied[k] = make([]string, len(vv))
|
||||||
|
copy(copied[k], vv)
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.WithValue(ctx, contextkeys.RequestHeaderKey, copied), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HTTPRequestHeaders(ctx context.Context) (http.Header, bool) {
|
||||||
|
h, ok := ctx.Value(contextkeys.RequestHeaderKey).(http.Header)
|
||||||
|
return h, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHTTPResponseHeader sets an HTTP header key-value pair using a context
|
||||||
|
// provided by a twirp-generated server, or a child of that context.
|
||||||
|
// The server will include the header in its response for that request context.
|
||||||
|
//
|
||||||
|
// This can be used to respond with custom HTTP headers like "Cache-Control".
|
||||||
|
// But note that HTTP headers are a Twirp implementation detail,
|
||||||
|
// only visible by middleware, not by the clients or their responses.
|
||||||
|
//
|
||||||
|
// The header will be ignored (noop) if the context is invalid (i.e. using a new
|
||||||
|
// context.Background() instead of passing the context from the handler).
|
||||||
|
//
|
||||||
|
// If called multiple times with the same key, it replaces any existing values
|
||||||
|
// associated with that key.
|
||||||
|
//
|
||||||
|
// SetHTTPResponseHeader returns an error if the provided header key
|
||||||
|
// would overwrite a header that is needed by Twirp, like "Content-Type".
|
||||||
|
func SetHTTPResponseHeader(ctx context.Context, key, value string) error {
|
||||||
|
if key == "Content-Type" {
|
||||||
|
return errors.New("header key can not be Content-Type")
|
||||||
|
}
|
||||||
|
|
||||||
|
responseWriter, ok := ctx.Value(contextkeys.ResponseWriterKey).(http.ResponseWriter)
|
||||||
|
if ok {
|
||||||
|
responseWriter.Header().Set(key, value)
|
||||||
|
} // invalid context is ignored, not an error, this is to allow easy unit testing with mock servers
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Package ctxsetters is an implementation detail for twirp generated code, used
|
||||||
|
// by the generated servers to set values in contexts for later access with the
|
||||||
|
// twirp package's accessors.
|
||||||
|
//
|
||||||
|
// Do not use ctxsetters outside of twirp's generated code.
|
||||||
|
package ctxsetters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/twitchtv/twirp/internal/contextkeys"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WithMethodName(ctx context.Context, name string) context.Context {
|
||||||
|
return context.WithValue(ctx, contextkeys.MethodNameKey, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithServiceName(ctx context.Context, name string) context.Context {
|
||||||
|
return context.WithValue(ctx, contextkeys.ServiceNameKey, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithPackageName(ctx context.Context, name string) context.Context {
|
||||||
|
return context.WithValue(ctx, contextkeys.PackageNameKey, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithStatusCode(ctx context.Context, code int) context.Context {
|
||||||
|
return context.WithValue(ctx, contextkeys.StatusCodeKey, strconv.Itoa(code))
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithResponseWriter(ctx context.Context, w http.ResponseWriter) context.Context {
|
||||||
|
return context.WithValue(ctx, contextkeys.ResponseWriterKey, w)
|
||||||
|
}
|
|
@ -0,0 +1,328 @@
|
||||||
|
// package twirp provides core types used in generated Twirp servers and client.
|
||||||
|
//
|
||||||
|
// Twirp services handle errors using the `twirp.Error` interface.
|
||||||
|
//
|
||||||
|
// For example, a server method may return an InvalidArgumentError:
|
||||||
|
//
|
||||||
|
// if req.Order != "DESC" && req.Order != "ASC" {
|
||||||
|
// return nil, twirp.InvalidArgumentError("Order", "must be DESC or ASC")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// And the same twirp.Error is returned by the client, for example:
|
||||||
|
//
|
||||||
|
// resp, err := twirpClient.RPCMethod(ctx, req)
|
||||||
|
// if err != nil {
|
||||||
|
// if twerr := err.(twirp.Error) {
|
||||||
|
// switch twerr.Code() {
|
||||||
|
// case twirp.InvalidArgument:
|
||||||
|
// log.Error("invalid argument "+twirp.Meta("argument"))
|
||||||
|
// default:
|
||||||
|
// log.Error(twerr.Error())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Clients may also return Internal errors if something failed on the system:
|
||||||
|
// the server, the network, or the client itself (i.e. failure parsing
|
||||||
|
// response).
|
||||||
|
//
|
||||||
|
package twirp
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Error represents an error in a Twirp service call.
|
||||||
|
type Error interface {
|
||||||
|
// Code is of the valid error codes.
|
||||||
|
Code() ErrorCode
|
||||||
|
|
||||||
|
// Msg returns a human-readable, unstructured messages describing the error.
|
||||||
|
Msg() string
|
||||||
|
|
||||||
|
// WithMeta returns a copy of the Error with the given key-value pair attached
|
||||||
|
// as metadata. If the key is already set, it is overwritten.
|
||||||
|
WithMeta(key string, val string) Error
|
||||||
|
|
||||||
|
// Meta returns the stored value for the given key. If the key has no set
|
||||||
|
// value, Meta returns an empty string. There is no way to distinguish between
|
||||||
|
// an unset value and an explicit empty string.
|
||||||
|
Meta(key string) string
|
||||||
|
|
||||||
|
// MetaMap returns the complete key-value metadata map stored on the error.
|
||||||
|
MetaMap() map[string]string
|
||||||
|
|
||||||
|
// Error returns a string of the form "twirp error <Type>: <Msg>"
|
||||||
|
Error() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewError is the generic constructor for a twirp.Error. The ErrorCode must be
|
||||||
|
// one of the valid predefined constants, otherwise it will be converted to an
|
||||||
|
// error {type: Internal, msg: "invalid error type {{code}}"}. If you need to
|
||||||
|
// add metadata, use .WithMeta(key, value) method after building the error.
|
||||||
|
func NewError(code ErrorCode, msg string) Error {
|
||||||
|
if IsValidErrorCode(code) {
|
||||||
|
return &twerr{
|
||||||
|
code: code,
|
||||||
|
msg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &twerr{
|
||||||
|
code: Internal,
|
||||||
|
msg: "invalid error type " + string(code),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotFoundError constructor for the common NotFound error.
|
||||||
|
func NotFoundError(msg string) Error {
|
||||||
|
return NewError(NotFound, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidArgumentError constructor for the common InvalidArgument error. Can be
|
||||||
|
// used when an argument has invalid format, is a number out of range, is a bad
|
||||||
|
// option, etc).
|
||||||
|
func InvalidArgumentError(argument string, validationMsg string) Error {
|
||||||
|
err := NewError(InvalidArgument, argument+" "+validationMsg)
|
||||||
|
err = err.WithMeta("argument", argument)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequiredArgumentError is a more scpecific constructor for InvalidArgument
|
||||||
|
// error. Should be used when the argument is required (expected to have a
|
||||||
|
// non-zero value).
|
||||||
|
func RequiredArgumentError(argument string) Error {
|
||||||
|
return InvalidArgumentError(argument, "is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalError constructor for the common Internal error. Should be used to
|
||||||
|
// specify that something bad or unexpected happened.
|
||||||
|
func InternalError(msg string) Error {
|
||||||
|
return NewError(Internal, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalErrorWith is an easy way to wrap another error. It adds the
|
||||||
|
// underlying error's type as metadata with a key of "cause", which can be
|
||||||
|
// useful for debugging. Should be used in the common case of an unexpected
|
||||||
|
// error returned from another API, but sometimes it is better to build a more
|
||||||
|
// specific error (like with NewError(Unknown, err.Error()), for example).
|
||||||
|
//
|
||||||
|
// The returned error also has a Cause() method which will return the original
|
||||||
|
// error, if it is known. This can be used with the github.com/pkg/errors
|
||||||
|
// package to extract the root cause of an error. Information about the root
|
||||||
|
// cause of an error is lost when it is serialized, so this doesn't let a client
|
||||||
|
// know the exact root cause of a server's error.
|
||||||
|
func InternalErrorWith(err error) Error {
|
||||||
|
msg := err.Error()
|
||||||
|
twerr := NewError(Internal, msg)
|
||||||
|
twerr = twerr.WithMeta("cause", fmt.Sprintf("%T", err)) // to easily tell apart wrapped internal errors from explicit ones
|
||||||
|
return &wrappedErr{
|
||||||
|
wrapper: twerr,
|
||||||
|
cause: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorCode represents a Twirp error type.
|
||||||
|
type ErrorCode string
|
||||||
|
|
||||||
|
// Valid Twirp error types. Most error types are equivalent to GRPC status codes
|
||||||
|
// and follow the same semantics.
|
||||||
|
const (
|
||||||
|
// Canceled indicates the operation was cancelled (typically by the caller).
|
||||||
|
Canceled ErrorCode = "canceled"
|
||||||
|
|
||||||
|
// Unknown error. For example when handling errors raised by APIs that do not
|
||||||
|
// return enough error information.
|
||||||
|
Unknown ErrorCode = "unknown"
|
||||||
|
|
||||||
|
// InvalidArgument indicates client specified an invalid argument. It
|
||||||
|
// indicates arguments that are problematic regardless of the state of the
|
||||||
|
// system (i.e. a malformed file name, required argument, number out of range,
|
||||||
|
// etc.).
|
||||||
|
InvalidArgument ErrorCode = "invalid_argument"
|
||||||
|
|
||||||
|
// DeadlineExceeded means operation expired before completion. For operations
|
||||||
|
// that change the state of the system, this error may be returned even if the
|
||||||
|
// operation has completed successfully (timeout).
|
||||||
|
DeadlineExceeded ErrorCode = "deadline_exceeded"
|
||||||
|
|
||||||
|
// NotFound means some requested entity was not found.
|
||||||
|
NotFound ErrorCode = "not_found"
|
||||||
|
|
||||||
|
// BadRoute means that the requested URL path wasn't routable to a Twirp
|
||||||
|
// service and method. This is returned by the generated server, and usually
|
||||||
|
// shouldn't be returned by applications. Instead, applications should use
|
||||||
|
// NotFound or Unimplemented.
|
||||||
|
BadRoute ErrorCode = "bad_route"
|
||||||
|
|
||||||
|
// AlreadyExists means an attempt to create an entity failed because one
|
||||||
|
// already exists.
|
||||||
|
AlreadyExists ErrorCode = "already_exists"
|
||||||
|
|
||||||
|
// PermissionDenied indicates the caller does not have permission to execute
|
||||||
|
// the specified operation. It must not be used if the caller cannot be
|
||||||
|
// identified (Unauthenticated).
|
||||||
|
PermissionDenied ErrorCode = "permission_denied"
|
||||||
|
|
||||||
|
// Unauthenticated indicates the request does not have valid authentication
|
||||||
|
// credentials for the operation.
|
||||||
|
Unauthenticated ErrorCode = "unauthenticated"
|
||||||
|
|
||||||
|
// ResourceExhausted indicates some resource has been exhausted, perhaps a
|
||||||
|
// per-user quota, or perhaps the entire file system is out of space.
|
||||||
|
ResourceExhausted ErrorCode = "resource_exhausted"
|
||||||
|
|
||||||
|
// FailedPrecondition indicates operation was rejected because the system is
|
||||||
|
// not in a state required for the operation's execution. For example, doing
|
||||||
|
// an rmdir operation on a directory that is non-empty, or on a non-directory
|
||||||
|
// object, or when having conflicting read-modify-write on the same resource.
|
||||||
|
FailedPrecondition ErrorCode = "failed_precondition"
|
||||||
|
|
||||||
|
// Aborted indicates the operation was aborted, typically due to a concurrency
|
||||||
|
// issue like sequencer check failures, transaction aborts, etc.
|
||||||
|
Aborted ErrorCode = "aborted"
|
||||||
|
|
||||||
|
// OutOfRange means operation was attempted past the valid range. For example,
|
||||||
|
// seeking or reading past end of a paginated collection.
|
||||||
|
//
|
||||||
|
// Unlike InvalidArgument, this error indicates a problem that may be fixed if
|
||||||
|
// the system state changes (i.e. adding more items to the collection).
|
||||||
|
//
|
||||||
|
// There is a fair bit of overlap between FailedPrecondition and OutOfRange.
|
||||||
|
// We recommend using OutOfRange (the more specific error) when it applies so
|
||||||
|
// that callers who are iterating through a space can easily look for an
|
||||||
|
// OutOfRange error to detect when they are done.
|
||||||
|
OutOfRange ErrorCode = "out_of_range"
|
||||||
|
|
||||||
|
// Unimplemented indicates operation is not implemented or not
|
||||||
|
// supported/enabled in this service.
|
||||||
|
Unimplemented ErrorCode = "unimplemented"
|
||||||
|
|
||||||
|
// Internal errors. When some invariants expected by the underlying system
|
||||||
|
// have been broken. In other words, something bad happened in the library or
|
||||||
|
// backend service. Do not confuse with HTTP Internal Server Error; an
|
||||||
|
// Internal error could also happen on the client code, i.e. when parsing a
|
||||||
|
// server response.
|
||||||
|
Internal ErrorCode = "internal"
|
||||||
|
|
||||||
|
// Unavailable indicates the service is currently unavailable. This is a most
|
||||||
|
// likely a transient condition and may be corrected by retrying with a
|
||||||
|
// backoff.
|
||||||
|
Unavailable ErrorCode = "unavailable"
|
||||||
|
|
||||||
|
// DataLoss indicates unrecoverable data loss or corruption.
|
||||||
|
DataLoss ErrorCode = "data_loss"
|
||||||
|
|
||||||
|
// NoError is the zero-value, is considered an empty error and should not be
|
||||||
|
// used.
|
||||||
|
NoError ErrorCode = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServerHTTPStatusFromErrorCode maps a Twirp error type into a similar HTTP
|
||||||
|
// response status. It is used by the Twirp server handler to set the HTTP
|
||||||
|
// response status code. Returns 0 if the ErrorCode is invalid.
|
||||||
|
func ServerHTTPStatusFromErrorCode(code ErrorCode) int {
|
||||||
|
switch code {
|
||||||
|
case Canceled:
|
||||||
|
return 408 // RequestTimeout
|
||||||
|
case Unknown:
|
||||||
|
return 500 // Internal Server Error
|
||||||
|
case InvalidArgument:
|
||||||
|
return 400 // BadRequest
|
||||||
|
case DeadlineExceeded:
|
||||||
|
return 408 // RequestTimeout
|
||||||
|
case NotFound:
|
||||||
|
return 404 // Not Found
|
||||||
|
case BadRoute:
|
||||||
|
return 404 // Not Found
|
||||||
|
case AlreadyExists:
|
||||||
|
return 409 // Conflict
|
||||||
|
case PermissionDenied:
|
||||||
|
return 403 // Forbidden
|
||||||
|
case Unauthenticated:
|
||||||
|
return 401 // Unauthorized
|
||||||
|
case ResourceExhausted:
|
||||||
|
return 403 // Forbidden
|
||||||
|
case FailedPrecondition:
|
||||||
|
return 412 // Precondition Failed
|
||||||
|
case Aborted:
|
||||||
|
return 409 // Conflict
|
||||||
|
case OutOfRange:
|
||||||
|
return 400 // Bad Request
|
||||||
|
case Unimplemented:
|
||||||
|
return 501 // Not Implemented
|
||||||
|
case Internal:
|
||||||
|
return 500 // Internal Server Error
|
||||||
|
case Unavailable:
|
||||||
|
return 503 // Service Unavailable
|
||||||
|
case DataLoss:
|
||||||
|
return 500 // Internal Server Error
|
||||||
|
case NoError:
|
||||||
|
return 200 // OK
|
||||||
|
default:
|
||||||
|
return 0 // Invalid!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidErrorCode returns true if is one of the valid predefined constants.
|
||||||
|
func IsValidErrorCode(code ErrorCode) bool {
|
||||||
|
return ServerHTTPStatusFromErrorCode(code) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// twirp.Error implementation
|
||||||
|
type twerr struct {
|
||||||
|
code ErrorCode
|
||||||
|
msg string
|
||||||
|
meta map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *twerr) Code() ErrorCode { return e.code }
|
||||||
|
func (e *twerr) Msg() string { return e.msg }
|
||||||
|
|
||||||
|
func (e *twerr) Meta(key string) string {
|
||||||
|
if e.meta != nil {
|
||||||
|
return e.meta[key] // also returns "" if key is not in meta map
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *twerr) WithMeta(key string, value string) Error {
|
||||||
|
newErr := &twerr{
|
||||||
|
code: e.code,
|
||||||
|
msg: e.msg,
|
||||||
|
meta: make(map[string]string, len(e.meta)),
|
||||||
|
}
|
||||||
|
for k, v := range e.meta {
|
||||||
|
newErr.meta[k] = v
|
||||||
|
}
|
||||||
|
newErr.meta[key] = value
|
||||||
|
return newErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *twerr) MetaMap() map[string]string {
|
||||||
|
return e.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *twerr) Error() string {
|
||||||
|
return fmt.Sprintf("twirp error %s: %s", e.code, e.msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrappedErr fulfills the twirp.Error interface and the
|
||||||
|
// github.com/pkg/errors.Causer interface. It exposes all the twirp error
|
||||||
|
// methods, but root cause of an error can be retrieved with
|
||||||
|
// (*wrappedErr).Cause. This is expected to be used with the InternalErrorWith
|
||||||
|
// function.
|
||||||
|
type wrappedErr struct {
|
||||||
|
wrapper Error
|
||||||
|
cause error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *wrappedErr) Code() ErrorCode { return e.wrapper.Code() }
|
||||||
|
func (e *wrappedErr) Msg() string { return e.wrapper.Msg() }
|
||||||
|
func (e *wrappedErr) Meta(key string) string { return e.wrapper.Meta(key) }
|
||||||
|
func (e *wrappedErr) MetaMap() map[string]string { return e.wrapper.MetaMap() }
|
||||||
|
func (e *wrappedErr) Error() string { return e.wrapper.Error() }
|
||||||
|
func (e *wrappedErr) WithMeta(key string, val string) Error {
|
||||||
|
return &wrappedErr{
|
||||||
|
wrapper: e.wrapper.WithMeta(key, val),
|
||||||
|
cause: e.cause,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (e *wrappedErr) Cause() error { return e.cause }
|
|
@ -0,0 +1,38 @@
|
||||||
|
package twirp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWithMetaRaces(t *testing.T) {
|
||||||
|
err := NewError(Internal, "msg")
|
||||||
|
err = err.WithMeta("k1", "v1")
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i int) {
|
||||||
|
_ = err.WithMeta(fmt.Sprintf("k-%d", i), "v")
|
||||||
|
wg.Done()
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if len(err.MetaMap()) != 1 {
|
||||||
|
t.Errorf("err was mutated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorCause(t *testing.T) {
|
||||||
|
rootCause := fmt.Errorf("this is only a test")
|
||||||
|
twerr := InternalErrorWith(rootCause)
|
||||||
|
cause := errors.Cause(twerr)
|
||||||
|
if cause != rootCause {
|
||||||
|
t.Errorf("got wrong cause for err. have=%q, want=%q", cause, rootCause)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package twirp
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// ServerHooks is a container for callbacks that can instrument a
|
||||||
|
// Twirp-generated server. These callbacks all accept a context and return a
|
||||||
|
// context. They can use this to add to the request context as it threads
|
||||||
|
// through the system, appending values or deadlines to it.
|
||||||
|
//
|
||||||
|
// The RequestReceived and RequestRouted hooks are special: they can return
|
||||||
|
// errors. If they return a non-nil error, handling for that request will be
|
||||||
|
// stopped at that point. The Error hook will be triggered, and the error will
|
||||||
|
// be sent to the client. This can be used for stuff like auth checks before
|
||||||
|
// deserializing a request.
|
||||||
|
//
|
||||||
|
// The RequestReceived hook is always called first, and it is called for every
|
||||||
|
// request that the Twirp server handles. The last hook to be called in a
|
||||||
|
// request's lifecycle is always ResponseSent, even in the case of an error.
|
||||||
|
//
|
||||||
|
// Details on the timing of each hook are documented as comments on the fields
|
||||||
|
// of the ServerHooks type.
|
||||||
|
type ServerHooks struct {
|
||||||
|
// RequestReceived is called as soon as a request enters the Twirp
|
||||||
|
// server at the earliest available moment.
|
||||||
|
RequestReceived func(context.Context) (context.Context, error)
|
||||||
|
|
||||||
|
// RequestRouted is called when a request has been routed to a
|
||||||
|
// particular method of the Twirp server.
|
||||||
|
RequestRouted func(context.Context) (context.Context, error)
|
||||||
|
|
||||||
|
// ResponsePrepared is called when a request has been handled and a
|
||||||
|
// response is ready to be sent to the client.
|
||||||
|
ResponsePrepared func(context.Context) context.Context
|
||||||
|
|
||||||
|
// ResponseSent is called when all bytes of a response (including an error
|
||||||
|
// response) have been written. Because the ResponseSent hook is terminal, it
|
||||||
|
// does not return a context.
|
||||||
|
ResponseSent func(context.Context)
|
||||||
|
|
||||||
|
// Error hook is called when a request responds with an Error,
|
||||||
|
// either by the service implementation or by Twirp itself.
|
||||||
|
// The Error is passed as argument to the hook.
|
||||||
|
Error func(context.Context, Error) context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainHooks creates a new *ServerHooks which chains the callbacks in
|
||||||
|
// each of the constituent hooks passed in. Each hook function will be
|
||||||
|
// called in the order of the ServerHooks values passed in.
|
||||||
|
//
|
||||||
|
// For the erroring hooks, RequestReceived and RequestRouted, any returned
|
||||||
|
// errors prevent processing by later hooks.
|
||||||
|
func ChainHooks(hooks ...*ServerHooks) *ServerHooks {
|
||||||
|
if len(hooks) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(hooks) == 1 {
|
||||||
|
return hooks[0]
|
||||||
|
}
|
||||||
|
return &ServerHooks{
|
||||||
|
RequestReceived: func(ctx context.Context) (context.Context, error) {
|
||||||
|
var err error
|
||||||
|
for _, h := range hooks {
|
||||||
|
if h != nil && h.RequestReceived != nil {
|
||||||
|
ctx, err = h.RequestReceived(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx, nil
|
||||||
|
},
|
||||||
|
RequestRouted: func(ctx context.Context) (context.Context, error) {
|
||||||
|
var err error
|
||||||
|
for _, h := range hooks {
|
||||||
|
if h != nil && h.RequestRouted != nil {
|
||||||
|
ctx, err = h.RequestRouted(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx, nil
|
||||||
|
},
|
||||||
|
ResponsePrepared: func(ctx context.Context) context.Context {
|
||||||
|
for _, h := range hooks {
|
||||||
|
if h != nil && h.ResponsePrepared != nil {
|
||||||
|
ctx = h.ResponsePrepared(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
},
|
||||||
|
ResponseSent: func(ctx context.Context) {
|
||||||
|
for _, h := range hooks {
|
||||||
|
if h != nil && h.ResponseSent != nil {
|
||||||
|
h.ResponseSent(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Error: func(ctx context.Context, twerr Error) context.Context {
|
||||||
|
for _, h := range hooks {
|
||||||
|
if h != nil && h.Error != nil {
|
||||||
|
ctx = h.Error(ctx, twerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package twirp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestChainHooks(t *testing.T) {
|
||||||
|
var (
|
||||||
|
hook1 = new(ServerHooks)
|
||||||
|
hook2 = new(ServerHooks)
|
||||||
|
hook3 = new(ServerHooks)
|
||||||
|
)
|
||||||
|
|
||||||
|
const key = "key"
|
||||||
|
|
||||||
|
hook1.RequestReceived = func(ctx context.Context) (context.Context, error) {
|
||||||
|
return context.WithValue(ctx, key, []string{"hook1"}), nil
|
||||||
|
}
|
||||||
|
hook2.RequestReceived = func(ctx context.Context) (context.Context, error) {
|
||||||
|
v := ctx.Value(key).([]string)
|
||||||
|
return context.WithValue(ctx, key, append(v, "hook2")), nil
|
||||||
|
}
|
||||||
|
hook3.RequestReceived = func(ctx context.Context) (context.Context, error) {
|
||||||
|
v := ctx.Value(key).([]string)
|
||||||
|
return context.WithValue(ctx, key, append(v, "hook3")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hook1.RequestRouted = func(ctx context.Context) (context.Context, error) {
|
||||||
|
return context.WithValue(ctx, key, []string{"hook1"}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hook2.ResponsePrepared = func(ctx context.Context) context.Context {
|
||||||
|
return context.WithValue(ctx, key, []string{"hook2"})
|
||||||
|
}
|
||||||
|
|
||||||
|
chain := ChainHooks(hook1, hook2, hook3)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// When all three chained hooks have a handler, all should be called in order.
|
||||||
|
want := []string{"hook1", "hook2", "hook3"}
|
||||||
|
haveCtx, err := chain.RequestReceived(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RequestReceived chain has unexpected err %v", err)
|
||||||
|
}
|
||||||
|
have := haveCtx.Value(key)
|
||||||
|
if !reflect.DeepEqual(want, have) {
|
||||||
|
t.Errorf("RequestReceived chain has unexpected ctx, have=%v, want=%v", have, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When only the first chained hook has a handler, it should be called, and
|
||||||
|
// there should be no panic.
|
||||||
|
want = []string{"hook1"}
|
||||||
|
haveCtx, err = chain.RequestRouted(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RequestRouted chain has unexpected err %v", err)
|
||||||
|
}
|
||||||
|
have = haveCtx.Value(key)
|
||||||
|
if !reflect.DeepEqual(want, have) {
|
||||||
|
t.Errorf("RequestRouted chain has unexpected ctx, have=%v, want=%v", have, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When only the second chained hook has a handler, it should be called, and
|
||||||
|
// there should be no panic.
|
||||||
|
want = []string{"hook2"}
|
||||||
|
have = chain.ResponsePrepared(ctx).Value(key)
|
||||||
|
if !reflect.DeepEqual(want, have) {
|
||||||
|
t.Errorf("RequestRouted chain has unexpected ctx, have=%v, want=%v", have, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When none of the chained hooks has a handler there should be no panic.
|
||||||
|
chain.ResponseSent(ctx)
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
which protoc
|
||||||
|
PROTOC_EXISTS=$?
|
||||||
|
if [ $PROTOC_EXISTS -eq 0 ]; then
|
||||||
|
echo "Protoc already installed"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$(uname)" == "Darwin" ]; then
|
||||||
|
brew install protobuf
|
||||||
|
elif [ `whoami` == "root" ]; then
|
||||||
|
mkdir -p /usr/local/src/protoc
|
||||||
|
pushd /usr/local/src/protoc
|
||||||
|
wget https://github.com/google/protobuf/releases/download/v3.1.0/protoc-3.1.0-linux-x86_64.zip -O /usr/local/src/protoc-3.1.0-linux-x86_64.zip
|
||||||
|
unzip -x ../protoc-3.1.0-linux-x86_64.zip
|
||||||
|
if [ ! -e /usr/local/bin/protoc ]; then
|
||||||
|
ln -s `pwd`/bin/protoc /usr/local/bin/protoc
|
||||||
|
fi
|
||||||
|
popd
|
||||||
|
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
|
||||||
|
echo "Project setup needs sudo to put protoc in /usr/local/src, so it will ask a few times"
|
||||||
|
sudo chmod a+w /usr/local/src
|
||||||
|
mkdir -p /usr/local/src/protoc
|
||||||
|
pushd /usr/local/src/protoc
|
||||||
|
wget https://github.com/google/protobuf/releases/download/v3.1.0/protoc-3.1.0-linux-x86_64.zip -O /usr/local/src/protoc-3.1.0-linux-x86_64.zip
|
||||||
|
unzip -x ../protoc-3.1.0-linux-x86_64.zip
|
||||||
|
if [ ! -e /usr/local/bin/protoc ]; then
|
||||||
|
sudo ln -s `pwd`/bin/protoc /usr/local/bin/protoc
|
||||||
|
fi
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
exit 0
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Package contextkeys stores the keys to the context accessor
|
||||||
|
// functions, letting generated code safely set values in contexts
|
||||||
|
// without exposing the setters to the outside world.
|
||||||
|
package contextkeys
|
||||||
|
|
||||||
|
type contextKey int
|
||||||
|
|
||||||
|
const (
|
||||||
|
MethodNameKey contextKey = 1 + iota
|
||||||
|
ServiceNameKey
|
||||||
|
PackageNameKey
|
||||||
|
StatusCodeKey
|
||||||
|
RequestHeaderKey
|
||||||
|
ResponseWriterKey
|
||||||
|
)
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# gogo_protoc_gen.sh foo.proto will compile foo.proto using
|
||||||
|
# github.com/gogo/protobuf/protoc-gen-gofast, an alternative generator used
|
||||||
|
# sometimes at Twitch.. Should be run in the same directory as its input.
|
||||||
|
# Handles multi-element GOPATHs so it works with retool.
|
||||||
|
|
||||||
|
# Append '/src' to every element in GOPATH.
|
||||||
|
PROTOPATH=${GOPATH/://src:}/src
|
||||||
|
|
||||||
|
protoc --proto_path="${PROTOPATH}:." --twirp_out=. --gofast_out=. "$@"
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# protoc_gen.sh foo.proto will compile foo.proto. Should be run in the same
|
||||||
|
# directory as its input. Handles multi-element GOPATHs so it works with retool.
|
||||||
|
|
||||||
|
# Append '/src' to every element in GOPATH.
|
||||||
|
PROTOPATH=${GOPATH/://src:}/src
|
||||||
|
|
||||||
|
protoc --proto_path="${PROTOPATH}:." --twirp_out=. --go_out=. "$@"
|
||||||
|
protoc --proto_path="${PROTOPATH}:." --python_out=. --twirp_python_out=. "$@"
|
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
|
@ -0,0 +1,7 @@
|
||||||
|
certifi==2017.4.17
|
||||||
|
chardet==3.0.4
|
||||||
|
idna==2.5
|
||||||
|
protobuf==3.3.0
|
||||||
|
requests==2.17.3
|
||||||
|
six==1.10.0
|
||||||
|
urllib3==1.21.1
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"Tools": [
|
||||||
|
{
|
||||||
|
"Repository": "github.com/golang/protobuf/protoc-gen-go",
|
||||||
|
"Commit": "c9c7427a2a70d2eb3bafa0ab2dc163e45f143317"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Repository": "github.com/kisielk/errcheck",
|
||||||
|
"Commit": "db0ca22445717d1b2c51ac1034440e0a2a2de645"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Repository": "github.com/gogo/protobuf/protoc-gen-gofast",
|
||||||
|
"Commit": "30433562cfbf487fe1df7cd26c7bab168d2f14d0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Repository": "github.com/twitchtv/retool",
|
||||||
|
"Commit": "6f6d4930d88c40e23d2b54d12e64f0444e1fb4ef"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue