1
0
mirror of https://github.com/go-kit/kit.git synced 2025-07-17 01:12:38 +02:00

Merge pull request #287 from go-kit/addsvc-errors

examples/addsvc: more sophisticated error encoding
This commit is contained in:
Peter Bourgon
2016-06-07 18:33:33 +02:00
8 changed files with 144 additions and 36 deletions

View File

@ -42,7 +42,7 @@ func (e Endpoints) Sum(ctx context.Context, a, b int) (int, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
return response.(sumResponse).V, nil return response.(sumResponse).V, response.(sumResponse).Err
} }
// Concat implements Service. Primarily useful in a client. // Concat implements Service. Primarily useful in a client.
@ -52,7 +52,7 @@ func (e Endpoints) Concat(ctx context.Context, a, b string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
return response.(concatResponse).V, err return response.(concatResponse).V, response.(concatResponse).Err
} }
// MakeSumEndpoint returns an endpoint that invokes Sum on the service. // MakeSumEndpoint returns an endpoint that invokes Sum on the service.
@ -61,11 +61,12 @@ func MakeSumEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) { return func(ctx context.Context, request interface{}) (response interface{}, err error) {
sumReq := request.(sumRequest) sumReq := request.(sumRequest)
v, err := s.Sum(ctx, sumReq.A, sumReq.B) v, err := s.Sum(ctx, sumReq.A, sumReq.B)
if err != nil { if err == ErrIntOverflow {
return nil, err return nil, err // special case; see comment on ErrIntOverflow
} }
return sumResponse{ return sumResponse{
V: v, V: v,
Err: err,
}, nil }, nil
} }
} }
@ -76,11 +77,9 @@ func MakeConcatEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) { return func(ctx context.Context, request interface{}) (response interface{}, err error) {
concatReq := request.(concatRequest) concatReq := request.(concatRequest)
v, err := s.Concat(ctx, concatReq.A, concatReq.B) v, err := s.Concat(ctx, concatReq.A, concatReq.B)
if err != nil {
return nil, err
}
return concatResponse{ return concatResponse{
V: v, V: v,
Err: err,
}, nil }, nil
} }
} }
@ -124,8 +123,14 @@ func EndpointLoggingMiddleware(logger log.Logger) endpoint.Middleware {
type sumRequest struct{ A, B int } type sumRequest struct{ A, B int }
type sumResponse struct{ V int } type sumResponse struct {
V int
Err error
}
type concatRequest struct{ A, B string } type concatRequest struct{ A, B string }
type concatResponse struct{ V string } type concatResponse struct {
V string
Err error
}

View File

@ -47,7 +47,8 @@ func (*SumRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{
// The sum response contains the result of the calculation. // The sum response contains the result of the calculation.
type SumReply struct { type SumReply struct {
V int64 `protobuf:"varint,1,opt,name=v" json:"v,omitempty"` V int64 `protobuf:"varint,1,opt,name=v" json:"v,omitempty"`
Err string `protobuf:"bytes,2,opt,name=err" json:"err,omitempty"`
} }
func (m *SumReply) Reset() { *m = SumReply{} } func (m *SumReply) Reset() { *m = SumReply{} }
@ -68,7 +69,8 @@ func (*ConcatRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []i
// The Concat response contains the result of the concatenation. // The Concat response contains the result of the concatenation.
type ConcatReply struct { type ConcatReply struct {
V string `protobuf:"bytes,1,opt,name=v" json:"v,omitempty"` V string `protobuf:"bytes,1,opt,name=v" json:"v,omitempty"`
Err string `protobuf:"bytes,2,opt,name=err" json:"err,omitempty"`
} }
func (m *ConcatReply) Reset() { *m = ConcatReply{} } func (m *ConcatReply) Reset() { *m = ConcatReply{} }
@ -192,16 +194,17 @@ var _Add_serviceDesc = grpc.ServiceDesc{
} }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 174 bytes of a gzipped FileDescriptorProto // 188 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x4c, 0x49, 0x29, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x4c, 0x49, 0x29,
0x2e, 0x4b, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2a, 0x48, 0x52, 0xd2, 0xe0, 0xe2, 0x2e, 0x4b, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2a, 0x48, 0x52, 0xd2, 0xe0, 0xe2,
0x0a, 0x2e, 0xcd, 0x0d, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0xe2, 0xe1, 0x62, 0x4c, 0x94, 0x0a, 0x2e, 0xcd, 0x0d, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0xe2, 0xe1, 0x62, 0x4c, 0x94,
0x60, 0x54, 0x60, 0xd4, 0x60, 0x0e, 0x62, 0x4c, 0x04, 0xf1, 0x92, 0x24, 0x98, 0x20, 0xbc, 0x24, 0x60, 0x54, 0x60, 0xd4, 0x60, 0x0e, 0x62, 0x4c, 0x04, 0xf1, 0x92, 0x24, 0x98, 0x20, 0xbc, 0x24,
0x25, 0x09, 0x2e, 0x0e, 0xb0, 0xca, 0x82, 0x9c, 0x4a, 0x90, 0x4c, 0x19, 0x4c, 0x5d, 0x99, 0x92, 0x25, 0x2d, 0x2e, 0x0e, 0xb0, 0xca, 0x82, 0x9c, 0x4a, 0x90, 0x4c, 0x19, 0x4c, 0x5d, 0x99, 0x90,
0x36, 0x17, 0xaf, 0x73, 0x7e, 0x5e, 0x72, 0x62, 0x09, 0x86, 0x31, 0x9c, 0x28, 0xc6, 0x70, 0x82, 0x00, 0x17, 0x73, 0x6a, 0x51, 0x11, 0x58, 0x25, 0x67, 0x10, 0x88, 0xa9, 0xa4, 0xcd, 0xc5, 0xeb,
0x8c, 0x91, 0xe6, 0xe2, 0x86, 0x29, 0x46, 0x31, 0x09, 0x28, 0x59, 0x66, 0x14, 0xc3, 0xc5, 0xec, 0x9c, 0x9f, 0x97, 0x9c, 0x58, 0x82, 0x61, 0x30, 0x27, 0x8a, 0xc1, 0x9c, 0x20, 0x83, 0x75, 0xb9,
0x98, 0x92, 0x22, 0xa4, 0xca, 0xc5, 0x0c, 0xb4, 0x4a, 0x88, 0x4f, 0xaf, 0x20, 0x49, 0x0f, 0xe1, 0xb8, 0x61, 0x8a, 0x51, 0xcc, 0xe6, 0xc4, 0x6a, 0xb6, 0x51, 0x0c, 0x17, 0xb3, 0x63, 0x4a, 0x8a,
0x3a, 0x29, 0x1e, 0x38, 0x1f, 0xa8, 0x53, 0x89, 0x41, 0x48, 0x8f, 0x8b, 0x0d, 0x62, 0x94, 0x90, 0x90, 0x2a, 0x17, 0x33, 0xd0, 0x39, 0x42, 0x7c, 0x7a, 0x05, 0x49, 0x7a, 0x08, 0x1f, 0x48, 0xf1,
0x20, 0x48, 0x06, 0xc5, 0x0d, 0x52, 0xfc, 0xc8, 0x42, 0x60, 0xf5, 0x49, 0x6c, 0x60, 0x6f, 0x1b, 0xc0, 0xf9, 0x40, 0xb3, 0x94, 0x18, 0x84, 0xf4, 0xb8, 0xd8, 0x20, 0x86, 0x0b, 0x09, 0x82, 0x64,
0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x8b, 0x2c, 0x12, 0xb4, 0x06, 0x01, 0x00, 0x00, 0x50, 0x5c, 0x25, 0xc5, 0x8f, 0x2c, 0x04, 0x56, 0x9f, 0xc4, 0x06, 0x0e, 0x1a, 0x63, 0x40, 0x00,
0x00, 0x00, 0xff, 0xff, 0xdc, 0x37, 0x81, 0x99, 0x2a, 0x01, 0x00, 0x00,
} }

View File

@ -20,6 +20,7 @@ message SumRequest {
// The sum response contains the result of the calculation. // The sum response contains the result of the calculation.
message SumReply { message SumReply {
int64 v = 1; int64 v = 1;
string err = 2;
} }
// The Concat request contains two parameters. // The Concat request contains two parameters.
@ -31,4 +32,5 @@ message ConcatRequest {
// The Concat response contains the result of the concatenation. // The Concat response contains the result of the concatenation.
message ConcatReply { message ConcatReply {
string v = 1; string v = 1;
string err = 2;
} }

View File

@ -19,17 +19,46 @@ type Service interface {
Concat(ctx context.Context, a, b string) (string, error) Concat(ctx context.Context, a, b string) (string, error)
} }
// Business-domain errors like these may be served in two ways: returned
// directly by endpoints, or bundled into the response struct. Both methods can
// be made to work, but errors returned directly by endpoints are counted by
// middlewares that check errors, like circuit breakers.
//
// If you don't want that behavior -- and you probably don't -- then it's better
// to bundle errors into the response struct.
var ( var (
// ErrTwoZeroes is an arbitrary business rule for the Add method. // ErrTwoZeroes is an arbitrary business rule for the Add method.
ErrTwoZeroes = errors.New("can't sum two zeroes") ErrTwoZeroes = errors.New("can't sum two zeroes")
// ErrIntOverflow protects the Add method. // ErrIntOverflow protects the Add method. We've decided that this error
// indicates a misbehaving service and should count against e.g. circuit
// breakers. So, we return it directly in endpoints, to illustrate the
// difference. In a real service, this probably wouldn't be the case.
ErrIntOverflow = errors.New("integer overflow") ErrIntOverflow = errors.New("integer overflow")
// ErrMaxSizeExceeded protects the Concat method. // ErrMaxSizeExceeded protects the Concat method.
ErrMaxSizeExceeded = errors.New("result exceeds maximum size") ErrMaxSizeExceeded = errors.New("result exceeds maximum size")
) )
// These annoying helper functions are required to translate Go error types to
// and from strings, which is the type we use in our IDLs to represent errors.
// There is special casing to treat empty strings as nil errors.
func str2err(s string) error {
if s == "" {
return nil
}
return errors.New(s)
}
func err2str(err error) string {
if err == nil {
return ""
}
return err.Error()
}
// NewBasicService returns a naïve, stateless implementation of Service. // NewBasicService returns a naïve, stateless implementation of Service.
func NewBasicService() Service { func NewBasicService() Service {
return basicService{} return basicService{}

View File

@ -1,9 +1,11 @@
struct SumReply { struct SumReply {
1: i64 value 1: i64 value
2: string err
} }
struct ConcatReply { struct ConcatReply {
1: string value 1: string value
2: string err
} }
service AddService { service AddService {

View File

@ -18,8 +18,10 @@ var GoUnusedProtection__ int
// Attributes: // Attributes:
// - Value // - Value
// - Err
type SumReply struct { type SumReply struct {
Value int64 `thrift:"value,1" json:"value"` Value int64 `thrift:"value,1" json:"value"`
Err string `thrift:"err,2" json:"err"`
} }
func NewSumReply() *SumReply { func NewSumReply() *SumReply {
@ -29,6 +31,10 @@ func NewSumReply() *SumReply {
func (p *SumReply) GetValue() int64 { func (p *SumReply) GetValue() int64 {
return p.Value return p.Value
} }
func (p *SumReply) GetErr() string {
return p.Err
}
func (p *SumReply) Read(iprot thrift.TProtocol) error { func (p *SumReply) Read(iprot thrift.TProtocol) error {
if _, err := iprot.ReadStructBegin(); err != nil { if _, err := iprot.ReadStructBegin(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
@ -47,6 +53,10 @@ func (p *SumReply) Read(iprot thrift.TProtocol) error {
if err := p.readField1(iprot); err != nil { if err := p.readField1(iprot); err != nil {
return err return err
} }
case 2:
if err := p.readField2(iprot); err != nil {
return err
}
default: default:
if err := iprot.Skip(fieldTypeId); err != nil { if err := iprot.Skip(fieldTypeId); err != nil {
return err return err
@ -71,6 +81,15 @@ func (p *SumReply) readField1(iprot thrift.TProtocol) error {
return nil return nil
} }
func (p *SumReply) readField2(iprot thrift.TProtocol) error {
if v, err := iprot.ReadString(); err != nil {
return thrift.PrependError("error reading field 2: ", err)
} else {
p.Err = v
}
return nil
}
func (p *SumReply) Write(oprot thrift.TProtocol) error { func (p *SumReply) Write(oprot thrift.TProtocol) error {
if err := oprot.WriteStructBegin("SumReply"); err != nil { if err := oprot.WriteStructBegin("SumReply"); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
@ -78,6 +97,9 @@ func (p *SumReply) Write(oprot thrift.TProtocol) error {
if err := p.writeField1(oprot); err != nil { if err := p.writeField1(oprot); err != nil {
return err return err
} }
if err := p.writeField2(oprot); err != nil {
return err
}
if err := oprot.WriteFieldStop(); err != nil { if err := oprot.WriteFieldStop(); err != nil {
return thrift.PrependError("write field stop error: ", err) return thrift.PrependError("write field stop error: ", err)
} }
@ -100,6 +122,19 @@ func (p *SumReply) writeField1(oprot thrift.TProtocol) (err error) {
return err return err
} }
func (p *SumReply) writeField2(oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin("err", thrift.STRING, 2); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:err: ", p), err)
}
if err := oprot.WriteString(string(p.Err)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.err (2) field write error: ", p), err)
}
if err := oprot.WriteFieldEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 2:err: ", p), err)
}
return err
}
func (p *SumReply) String() string { func (p *SumReply) String() string {
if p == nil { if p == nil {
return "<nil>" return "<nil>"
@ -109,8 +144,10 @@ func (p *SumReply) String() string {
// Attributes: // Attributes:
// - Value // - Value
// - Err
type ConcatReply struct { type ConcatReply struct {
Value string `thrift:"value,1" json:"value"` Value string `thrift:"value,1" json:"value"`
Err string `thrift:"err,2" json:"err"`
} }
func NewConcatReply() *ConcatReply { func NewConcatReply() *ConcatReply {
@ -120,6 +157,10 @@ func NewConcatReply() *ConcatReply {
func (p *ConcatReply) GetValue() string { func (p *ConcatReply) GetValue() string {
return p.Value return p.Value
} }
func (p *ConcatReply) GetErr() string {
return p.Err
}
func (p *ConcatReply) Read(iprot thrift.TProtocol) error { func (p *ConcatReply) Read(iprot thrift.TProtocol) error {
if _, err := iprot.ReadStructBegin(); err != nil { if _, err := iprot.ReadStructBegin(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err) return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
@ -138,6 +179,10 @@ func (p *ConcatReply) Read(iprot thrift.TProtocol) error {
if err := p.readField1(iprot); err != nil { if err := p.readField1(iprot); err != nil {
return err return err
} }
case 2:
if err := p.readField2(iprot); err != nil {
return err
}
default: default:
if err := iprot.Skip(fieldTypeId); err != nil { if err := iprot.Skip(fieldTypeId); err != nil {
return err return err
@ -162,6 +207,15 @@ func (p *ConcatReply) readField1(iprot thrift.TProtocol) error {
return nil return nil
} }
func (p *ConcatReply) readField2(iprot thrift.TProtocol) error {
if v, err := iprot.ReadString(); err != nil {
return thrift.PrependError("error reading field 2: ", err)
} else {
p.Err = v
}
return nil
}
func (p *ConcatReply) Write(oprot thrift.TProtocol) error { func (p *ConcatReply) Write(oprot thrift.TProtocol) error {
if err := oprot.WriteStructBegin("ConcatReply"); err != nil { if err := oprot.WriteStructBegin("ConcatReply"); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
@ -169,6 +223,9 @@ func (p *ConcatReply) Write(oprot thrift.TProtocol) error {
if err := p.writeField1(oprot); err != nil { if err := p.writeField1(oprot); err != nil {
return err return err
} }
if err := p.writeField2(oprot); err != nil {
return err
}
if err := oprot.WriteFieldStop(); err != nil { if err := oprot.WriteFieldStop(); err != nil {
return thrift.PrependError("write field stop error: ", err) return thrift.PrependError("write field stop error: ", err)
} }
@ -191,6 +248,19 @@ func (p *ConcatReply) writeField1(oprot thrift.TProtocol) (err error) {
return err return err
} }
func (p *ConcatReply) writeField2(oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin("err", thrift.STRING, 2); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:err: ", p), err)
}
if err := oprot.WriteString(string(p.Err)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.err (2) field write error: ", p), err)
}
if err := oprot.WriteFieldEnd(); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 2:err: ", p), err)
}
return err
}
func (p *ConcatReply) String() string { func (p *ConcatReply) String() string {
if p == nil { if p == nil {
return "<nil>" return "<nil>"

View File

@ -76,7 +76,7 @@ func DecodeGRPCConcatRequest(_ context.Context, grpcReq interface{}) (interface{
// gRPC sum reply to a user-domain sum response. Primarily useful in a client. // gRPC sum reply to a user-domain sum response. Primarily useful in a client.
func DecodeGRPCSumResponse(_ context.Context, grpcReply interface{}) (interface{}, error) { func DecodeGRPCSumResponse(_ context.Context, grpcReply interface{}) (interface{}, error) {
reply := grpcReply.(*pb.SumReply) reply := grpcReply.(*pb.SumReply)
return sumResponse{V: int(reply.V)}, nil return sumResponse{V: int(reply.V), Err: str2err(reply.Err)}, nil
} }
// DecodeGRPCConcatResponse is a transport/grpc.DecodeResponseFunc that converts // DecodeGRPCConcatResponse is a transport/grpc.DecodeResponseFunc that converts
@ -84,14 +84,14 @@ func DecodeGRPCSumResponse(_ context.Context, grpcReply interface{}) (interface{
// client. // client.
func DecodeGRPCConcatResponse(_ context.Context, grpcReply interface{}) (interface{}, error) { func DecodeGRPCConcatResponse(_ context.Context, grpcReply interface{}) (interface{}, error) {
reply := grpcReply.(*pb.ConcatReply) reply := grpcReply.(*pb.ConcatReply)
return concatResponse{V: reply.V}, nil return concatResponse{V: reply.V, Err: str2err(reply.Err)}, nil
} }
// EncodeGRPCSumResponse is a transport/grpc.EncodeResponseFunc that converts a // EncodeGRPCSumResponse is a transport/grpc.EncodeResponseFunc that converts a
// user-domain sum response to a gRPC sum reply. Primarily useful in a server. // user-domain sum response to a gRPC sum reply. Primarily useful in a server.
func EncodeGRPCSumResponse(_ context.Context, response interface{}) (interface{}, error) { func EncodeGRPCSumResponse(_ context.Context, response interface{}) (interface{}, error) {
resp := response.(sumResponse) resp := response.(sumResponse)
return &pb.SumReply{V: int64(resp.V)}, nil return &pb.SumReply{V: int64(resp.V), Err: err2str(resp.Err)}, nil
} }
// EncodeGRPCConcatResponse is a transport/grpc.EncodeResponseFunc that converts // EncodeGRPCConcatResponse is a transport/grpc.EncodeResponseFunc that converts
@ -99,7 +99,7 @@ func EncodeGRPCSumResponse(_ context.Context, response interface{}) (interface{}
// server. // server.
func EncodeGRPCConcatResponse(_ context.Context, response interface{}) (interface{}, error) { func EncodeGRPCConcatResponse(_ context.Context, response interface{}) (interface{}, error) {
resp := response.(concatResponse) resp := response.(concatResponse)
return &pb.ConcatReply{V: resp.V}, nil return &pb.ConcatReply{V: resp.V, Err: err2str(resp.Err)}, nil
} }
// EncodeGRPCSumRequest is a transport/grpc.EncodeRequestFunc that converts a // EncodeGRPCSumRequest is a transport/grpc.EncodeRequestFunc that converts a

View File

@ -35,7 +35,7 @@ func (s *thriftServer) Sum(a int64, b int64) (*thriftadd.SumReply, error) {
return nil, err return nil, err
} }
resp := response.(sumResponse) resp := response.(sumResponse)
return &thriftadd.SumReply{Value: int64(resp.V)}, nil return &thriftadd.SumReply{Value: int64(resp.V), Err: err2str(resp.Err)}, nil
} }
func (s *thriftServer) Concat(a string, b string) (*thriftadd.ConcatReply, error) { func (s *thriftServer) Concat(a string, b string) (*thriftadd.ConcatReply, error) {
@ -45,7 +45,7 @@ func (s *thriftServer) Concat(a string, b string) (*thriftadd.ConcatReply, error
return nil, err return nil, err
} }
resp := response.(concatResponse) resp := response.(concatResponse)
return &thriftadd.ConcatReply{Value: resp.V}, nil return &thriftadd.ConcatReply{Value: resp.V, Err: err2str(resp.Err)}, nil
} }
// MakeThriftSumEndpoint returns an endpoint that invokes the passed Thrift client. // MakeThriftSumEndpoint returns an endpoint that invokes the passed Thrift client.
@ -54,10 +54,10 @@ func MakeThriftSumEndpoint(client *thriftadd.AddServiceClient) endpoint.Endpoint
return func(ctx context.Context, request interface{}) (interface{}, error) { return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(sumRequest) req := request.(sumRequest)
reply, err := client.Sum(int64(req.A), int64(req.B)) reply, err := client.Sum(int64(req.A), int64(req.B))
if err != nil { if err == ErrIntOverflow {
return nil, err return nil, err // special case; see comment on ErrIntOverflow
} }
return sumResponse{V: int(reply.Value)}, nil return sumResponse{V: int(reply.Value), Err: err}, nil
} }
} }
@ -68,9 +68,6 @@ func MakeThriftConcatEndpoint(client *thriftadd.AddServiceClient) endpoint.Endpo
return func(ctx context.Context, request interface{}) (interface{}, error) { return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(concatRequest) req := request.(concatRequest)
reply, err := client.Concat(req.A, req.B) reply, err := client.Concat(req.A, req.B)
if err != nil { return concatResponse{V: reply.Value, Err: err}, nil
return nil, err
}
return concatResponse{V: reply.Value}, nil
} }
} }