diff --git a/cluster/errors.go b/cluster/errors.go new file mode 100644 index 00000000..376ad5ea --- /dev/null +++ b/cluster/errors.go @@ -0,0 +1,82 @@ +package cluster + +import ( + "errors" + "fmt" +) + +const ( + ErrorReason_OK = "OK" + ErrorReason_CANCELLED = "CANCELLED" + ErrorReason_UNKNOWN = "UNKNOWN" + ErrorReason_INVALID_ARGUMENT = "INVALID_ARGUMENT" + ErrorReason_DEADLINE_EXCEEDED = "DEADLINE_EXCEEDED" + ErrorReason_NOT_FOUND = "NOT_FOUND" + ErrorReason_ALREADY_EXISTS = "ALREADY_EXISTS" + ErrorReason_PERMISSION_DENIED = "PERMISSION_DENIED" + ErrorReason_RESOURCE_EXHAUSTED = "RESOURCE_EXHAUSTED" + ErrorReason_FAILED_PRECONDITION = "FAILED_PRECONDITION" + ErrorReason_ABORTED = "ABORTED" + ErrorReason_OUT_OF_RANGE = "OUT_OF_RANGE" + ErrorReason_UNIMPLEMENTED = "UNIMPLEMENTED" + ErrorReason_INTERNAL = "INTERNAL" + ErrorReason_UNAVAILABLE = "UNAVAILABLE" + ErrorReason_DATA_LOSS = "DATA_LOSS" + ErrorReason_UNAUTHENTICATED = "UNAUTHENTICATED" +) + +func NewGrainErrorResponse(reason, message string) *GrainErrorResponse { + return &GrainErrorResponse{ + Reason: reason, + Message: message, + } +} + +func NewGrainErrorResponsef(reason, format string, args ...interface{}) *GrainErrorResponse { + return &GrainErrorResponse{ + Reason: reason, + Message: fmt.Sprintf(format, args...), + } +} + +func (m *GrainErrorResponse) Error() string { + return fmt.Sprintf("grain error response, reason: %s, message: %s, metadata: %v", m.Reason, m.Message, m.Metadata) +} + +func (m *GrainErrorResponse) Is(err error) bool { + if e := new(GrainErrorResponse); errors.As(err, &e) { + return e.Reason == m.Reason + } + return false +} + +func (m *GrainErrorResponse) Errorf(format string, args ...interface{}) error { + return NewGrainErrorResponse(m.Reason, fmt.Sprintf(format, args...)) +} + +func (m *GrainErrorResponse) WithMetadata(metadata map[string]string) *GrainErrorResponse { + m.Metadata = metadata + return m +} + +func Reason(err error) string { + if err == nil { + return ErrorReason_UNKNOWN + } + return FromError(err).Reason +} + +func FromError(err error) *GrainErrorResponse { + if err == nil { + return nil + } + if e := new(GrainErrorResponse); errors.As(err, &e) { + return e + } + + ret := NewGrainErrorResponse( + ErrorReason_UNKNOWN, + err.Error(), + ) + return ret +} diff --git a/cluster/grain.pb.go b/cluster/grain.pb.go index 570389d4..fc3f8bf1 100644 --- a/cluster/grain.pb.go +++ b/cluster/grain.pb.go @@ -143,7 +143,9 @@ type GrainErrorResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Err string `protobuf:"bytes,1,opt,name=err,proto3" json:"err,omitempty"` + Reason string `protobuf:"bytes,1,opt,name=reason,proto3" json:"reason,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *GrainErrorResponse) Reset() { @@ -178,13 +180,27 @@ func (*GrainErrorResponse) Descriptor() ([]byte, []int) { return file_grain_proto_rawDescGZIP(), []int{2} } -func (x *GrainErrorResponse) GetErr() string { +func (x *GrainErrorResponse) GetReason() string { if x != nil { - return x.Err + return x.Reason } return "" } +func (x *GrainErrorResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *GrainErrorResponse) GetMetadata() map[string]string { + if x != nil { + return x.Metadata + } + return nil +} + var File_grain_proto protoreflect.FileDescriptor var file_grain_proto_rawDesc = []byte{ @@ -203,13 +219,23 @@ var file_grain_proto_rawDesc = []byte{ 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x12, 0x47, 0x72, 0x61, - 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x72, - 0x72, 0x42, 0x2c, 0x5a, 0x2a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, - 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0xca, 0x01, 0x0a, 0x12, 0x47, 0x72, + 0x61, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x45, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x47, + 0x72, 0x61, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -224,18 +250,20 @@ func file_grain_proto_rawDescGZIP() []byte { return file_grain_proto_rawDescData } -var file_grain_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_grain_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_grain_proto_goTypes = []interface{}{ (*GrainRequest)(nil), // 0: cluster.GrainRequest (*GrainResponse)(nil), // 1: cluster.GrainResponse (*GrainErrorResponse)(nil), // 2: cluster.GrainErrorResponse + nil, // 3: cluster.GrainErrorResponse.MetadataEntry } var file_grain_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 3, // 0: cluster.GrainErrorResponse.metadata:type_name -> cluster.GrainErrorResponse.MetadataEntry + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_grain_proto_init() } @@ -287,7 +315,7 @@ func file_grain_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_grain_proto_rawDesc, NumEnums: 0, - NumMessages: 3, + NumMessages: 4, NumExtensions: 0, NumServices: 0, }, diff --git a/cluster/grain.proto b/cluster/grain.proto index 27436874..30bf4ef4 100644 --- a/cluster/grain.proto +++ b/cluster/grain.proto @@ -14,5 +14,7 @@ message GrainResponse { } message GrainErrorResponse { - string err = 1; -} + string reason = 1; + string message = 2; + map metadata = 3; +}; diff --git a/examples/cluster-broadcast/shared/protos_grain.pb.go b/examples/cluster-broadcast/shared/protos_grain.pb.go index d9c1b2df..19d702f8 100644 --- a/examples/cluster-broadcast/shared/protos_grain.pb.go +++ b/examples/cluster-broadcast/shared/protos_grain.pb.go @@ -1,13 +1,12 @@ // Code generated by protoc-gen-grain. DO NOT EDIT. // versions: -// protoc-gen-grain v0.4.1 +// protoc-gen-grain v0.5.0 // protoc v4.25.0 // source: protos.proto package shared import ( - errors "errors" fmt "fmt" actor "github.com/asynkron/protoactor-go/actor" cluster "github.com/asynkron/protoactor-go/cluster" @@ -88,7 +87,7 @@ func (g *CalculatorGrainClient) Add(r *NumberRequest, opts ...cluster.GrainCallO case *CountResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -109,7 +108,7 @@ func (g *CalculatorGrainClient) Subtract(r *NumberRequest, opts ...cluster.Grain case *CountResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -130,7 +129,7 @@ func (g *CalculatorGrainClient) GetCurrent(r *Noop, opts ...cluster.GrainCallOpt case *CountResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -169,14 +168,17 @@ func (a *CalculatorActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] Add(NumberRequest) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.Add(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -186,14 +188,17 @@ func (a *CalculatorActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] Subtract(NumberRequest) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.Subtract(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -203,14 +208,17 @@ func (a *CalculatorActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] GetCurrent(Noop) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.GetCurrent(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -224,7 +232,7 @@ func (a *CalculatorActor) Receive(ctx actor.Context) { // onError should be used in ctx.ReenterAfter // you can just return error in reenterable method for other errors func (a *CalculatorActor) onError(err error) { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) a.ctx.Respond(resp) } @@ -300,7 +308,7 @@ func (g *TrackerGrainClient) RegisterGrain(r *RegisterMessage, opts ...cluster.G case *Noop: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -321,7 +329,7 @@ func (g *TrackerGrainClient) DeregisterGrain(r *RegisterMessage, opts ...cluster case *Noop: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -342,7 +350,7 @@ func (g *TrackerGrainClient) BroadcastGetCounts(r *Noop, opts ...cluster.GrainCa case *TotalsResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -381,14 +389,17 @@ func (a *TrackerActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] RegisterGrain(RegisterMessage) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.RegisterGrain(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -398,14 +409,17 @@ func (a *TrackerActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] DeregisterGrain(RegisterMessage) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.DeregisterGrain(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -415,14 +429,17 @@ func (a *TrackerActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] BroadcastGetCounts(Noop) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.BroadcastGetCounts(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -436,7 +453,7 @@ func (a *TrackerActor) Receive(ctx actor.Context) { // onError should be used in ctx.ReenterAfter // you can just return error in reenterable method for other errors func (a *TrackerActor) onError(err error) { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) a.ctx.Respond(resp) } diff --git a/examples/cluster-error-response/build.sh b/examples/cluster-error-response/build.sh new file mode 100755 index 00000000..93e32078 --- /dev/null +++ b/examples/cluster-error-response/build.sh @@ -0,0 +1,3 @@ +protoc --go_out=. --go_opt=paths=source_relative \ + --plugin=protoc-gen-go-grain=../../protobuf/protoc-gen-go-grain/protoc-gen-go-grain.sh --go-grain_out=. --go-grain_opt=paths=source_relative \ + -I../../ -I. protos.proto diff --git a/examples/cluster-error-response/go.mod b/examples/cluster-error-response/go.mod new file mode 100644 index 00000000..f9167e0d --- /dev/null +++ b/examples/cluster-error-response/go.mod @@ -0,0 +1,42 @@ +module cluster-error-response + +go 1.21.5 + +require ( + github.com/asynkron/protoactor-go v0.0.0-20240116091649-93e384a26d0d + google.golang.org/protobuf v1.31.0 +) + +require ( + github.com/Workiva/go-datastructures v1.1.1 // indirect + github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/lithammer/shortuuid/v4 v4.0.0 // indirect + github.com/lmittmann/tint v1.0.3 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/orcaman/concurrent-map v1.0.0 // indirect + github.com/prometheus/client_golang v1.17.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.11.1 // indirect + github.com/twmb/murmur3 v1.1.8 // indirect + go.opentelemetry.io/otel v1.21.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.44.0 // indirect + go.opentelemetry.io/otel/metric v1.21.0 // indirect + go.opentelemetry.io/otel/sdk v1.21.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.21.0 // indirect + go.opentelemetry.io/otel/trace v1.21.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/grpc v1.60.1 // indirect +) diff --git a/examples/cluster-error-response/go.sum b/examples/cluster-error-response/go.sum new file mode 100644 index 00000000..a298ecb1 --- /dev/null +++ b/examples/cluster-error-response/go.sum @@ -0,0 +1,125 @@ +github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0= +github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= +github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A= +github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E= +github.com/asynkron/protoactor-go v0.0.0-20240116091649-93e384a26d0d h1:/hjPIUib6lReKTncaTIW6FMwO/2sa2Kgv9TCDODZDpo= +github.com/asynkron/protoactor-go v0.0.0-20240116091649-93e384a26d0d/go.mod h1:4KJf8vAca0fX3EShIBwyY77HV8NldoO1RzFGNlNkjWA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c= +github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y= +github.com/lmittmann/tint v1.0.3 h1:W5PHeA2D8bBJVvabNfQD/XW9HPLZK1XoPZH0cq8NouQ= +github.com/lmittmann/tint v1.0.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY= +github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= +github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= +github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= +github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= +github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/exporters/prometheus v0.44.0 h1:08qeJgaPC0YEBu2PQMbqU3rogTlyzpjhCI2b58Yn00w= +go.opentelemetry.io/otel/exporters/prometheus v0.44.0/go.mod h1:ERL2uIeBtg4TxZdojHUwzZfIFlUIjZtxubT5p4h1Gjg= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= +go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/cluster-error-response/hello_grain.go b/examples/cluster-error-response/hello_grain.go new file mode 100644 index 00000000..c4a87a3e --- /dev/null +++ b/examples/cluster-error-response/hello_grain.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + + "github.com/asynkron/protoactor-go/cluster" +) + +type HelloGrain struct{} + +func NewHelloGrain() Hello { + return &HelloGrain{} +} + +// Init implements Hello. +func (g *HelloGrain) Init(ctx cluster.GrainContext) { + ctx.Logger().Info("HelloGrain Init") +} + +// ReceiveDefault implements Hello. +func (g *HelloGrain) ReceiveDefault(ctx cluster.GrainContext) { + ctx.Logger().Info("HelloGrain ReceiveDefault") +} + +// Terminate implements Hello. +func (g *HelloGrain) Terminate(ctx cluster.GrainContext) { + ctx.Logger().Info("HelloGrain Terminate") +} + +// Hello implements Hello. +func (*HelloGrain) Hello(req *HelloRequest, ctx cluster.GrainContext) (*HelloResponse, error) { + if req.Name == "user-not-found" { + return nil, ErrUserNotFound("not found") + } + + if req.Name == "normal-error" { + return nil, fmt.Errorf("normal error") + } + + return &HelloResponse{Message: "Hello " + req.Name}, nil +} + +// Reenterable implements Hello. +func (*HelloGrain) Reenterable(req *ReenterableRequest, respond func(*ReenterableResponse), onError func(error), ctx cluster.GrainContext) error { + panic("unimplemented") +} diff --git a/examples/cluster-error-response/main.go b/examples/cluster-error-response/main.go new file mode 100644 index 00000000..58992389 --- /dev/null +++ b/examples/cluster-error-response/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + + actor "github.com/asynkron/protoactor-go/actor" + cluster "github.com/asynkron/protoactor-go/cluster" + "github.com/asynkron/protoactor-go/cluster/clusterproviders/test" + "github.com/asynkron/protoactor-go/cluster/identitylookup/disthash" + "github.com/asynkron/protoactor-go/remote" +) + +func main() { + system := actor.NewActorSystem() + provider := test.NewTestProvider(test.NewInMemAgent()) + lookup := disthash.New() + config := remote.Configure("localhost", 0) + helloKind := NewHelloKind(NewHelloGrain, 0) + clusterConfig := cluster.Configure("test", provider, lookup, config, cluster.WithKinds( + helloKind)) + cst := cluster.New(system, clusterConfig) + cst.StartMember() + + client := GetHelloGrainClient(cst, "test") + _, err := client.Hello(&HelloRequest{Name: "user-not-found"}) + if err != nil { + if IsUserNotFound(err) { + fmt.Println("user not found") + } else { + fmt.Printf("unknown error: %v\n", err) + } + } + + _, err = client.Hello(&HelloRequest{Name: "normal-error"}) + if err != nil { + fmt.Printf("unknown error: %v\n", err) + } +} diff --git a/examples/cluster-error-response/protos.pb.go b/examples/cluster-error-response/protos.pb.go new file mode 100644 index 00000000..d51f7a5a --- /dev/null +++ b/examples/cluster-error-response/protos.pb.go @@ -0,0 +1,400 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.32.0 +// protoc v4.25.0 +// source: protos.proto + +package main + +import ( + _ "github.com/asynkron/protoactor-go/protobuf/protoc-gen-go-grain/options" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ErrorReason int32 + +const ( + ErrorReason_USER_NOT_FOUND ErrorReason = 0 +) + +// Enum value maps for ErrorReason. +var ( + ErrorReason_name = map[int32]string{ + 0: "USER_NOT_FOUND", + } + ErrorReason_value = map[string]int32{ + "USER_NOT_FOUND": 0, + } +) + +func (x ErrorReason) Enum() *ErrorReason { + p := new(ErrorReason) + *p = x + return p +} + +func (x ErrorReason) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ErrorReason) Descriptor() protoreflect.EnumDescriptor { + return file_protos_proto_enumTypes[0].Descriptor() +} + +func (ErrorReason) Type() protoreflect.EnumType { + return &file_protos_proto_enumTypes[0] +} + +func (x ErrorReason) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ErrorReason.Descriptor instead. +func (ErrorReason) EnumDescriptor() ([]byte, []int) { + return file_protos_proto_rawDescGZIP(), []int{0} +} + +type ReenterableRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *ReenterableRequest) Reset() { + *x = ReenterableRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReenterableRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReenterableRequest) ProtoMessage() {} + +func (x *ReenterableRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReenterableRequest.ProtoReflect.Descriptor instead. +func (*ReenterableRequest) Descriptor() ([]byte, []int) { + return file_protos_proto_rawDescGZIP(), []int{0} +} + +func (x *ReenterableRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type ReenterableResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *ReenterableResponse) Reset() { + *x = ReenterableResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReenterableResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReenterableResponse) ProtoMessage() {} + +func (x *ReenterableResponse) ProtoReflect() protoreflect.Message { + mi := &file_protos_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReenterableResponse.ProtoReflect.Descriptor instead. +func (*ReenterableResponse) Descriptor() ([]byte, []int) { + return file_protos_proto_rawDescGZIP(), []int{1} +} + +func (x *ReenterableResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type HelloRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *HelloRequest) Reset() { + *x = HelloRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HelloRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelloRequest) ProtoMessage() {} + +func (x *HelloRequest) ProtoReflect() protoreflect.Message { + mi := &file_protos_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. +func (*HelloRequest) Descriptor() ([]byte, []int) { + return file_protos_proto_rawDescGZIP(), []int{2} +} + +func (x *HelloRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type HelloResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *HelloResponse) Reset() { + *x = HelloResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_protos_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HelloResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelloResponse) ProtoMessage() {} + +func (x *HelloResponse) ProtoReflect() protoreflect.Message { + mi := &file_protos_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HelloResponse.ProtoReflect.Descriptor instead. +func (*HelloResponse) Descriptor() ([]byte, []int) { + return file_protos_proto_rawDescGZIP(), []int{3} +} + +func (x *HelloResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_protos_proto protoreflect.FileDescriptor + +var file_protos_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, + 0x72, 0x65, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6e, 0x63, 0x79, 0x1a, 0x32, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, + 0x67, 0x6f, 0x2d, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x28, + 0x0a, 0x12, 0x52, 0x65, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2f, 0x0a, 0x13, 0x52, 0x65, 0x65, 0x6e, + 0x74, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, + 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x29, 0x0a, + 0x0d, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2a, 0x21, 0x0a, 0x0b, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x0e, 0x55, 0x53, 0x45, 0x52, 0x5f, + 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x00, 0x32, 0xa1, 0x01, 0x0a, 0x05, + 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x58, 0x0a, 0x0b, 0x52, 0x65, 0x65, 0x6e, 0x74, 0x65, 0x72, + 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1e, 0x2e, 0x72, 0x65, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6e, 0x63, + 0x79, 0x2e, 0x52, 0x65, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x72, 0x65, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6e, 0x63, + 0x79, 0x2e, 0x52, 0x65, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x08, 0x82, 0xb5, 0x18, 0x04, 0x08, 0x01, 0x10, 0x01, 0x12, + 0x3e, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x65, 0x6e, 0x74, + 0x72, 0x61, 0x6e, 0x63, 0x79, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6e, 0x63, 0x79, 0x2e, + 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, + 0x44, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, + 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, + 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x63, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x2d, 0x72, 0x65, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6e, 0x63, 0x79, + 0x2f, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_protos_proto_rawDescOnce sync.Once + file_protos_proto_rawDescData = file_protos_proto_rawDesc +) + +func file_protos_proto_rawDescGZIP() []byte { + file_protos_proto_rawDescOnce.Do(func() { + file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData) + }) + return file_protos_proto_rawDescData +} + +var file_protos_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_protos_proto_goTypes = []interface{}{ + (ErrorReason)(0), // 0: reentrancy.ErrorReason + (*ReenterableRequest)(nil), // 1: reentrancy.ReenterableRequest + (*ReenterableResponse)(nil), // 2: reentrancy.ReenterableResponse + (*HelloRequest)(nil), // 3: reentrancy.HelloRequest + (*HelloResponse)(nil), // 4: reentrancy.HelloResponse +} +var file_protos_proto_depIdxs = []int32{ + 1, // 0: reentrancy.Hello.Reenterable:input_type -> reentrancy.ReenterableRequest + 3, // 1: reentrancy.Hello.Hello:input_type -> reentrancy.HelloRequest + 2, // 2: reentrancy.Hello.Reenterable:output_type -> reentrancy.ReenterableResponse + 4, // 3: reentrancy.Hello.Hello:output_type -> reentrancy.HelloResponse + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_protos_proto_init() } +func file_protos_proto_init() { + if File_protos_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReenterableRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReenterableResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HelloRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_protos_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HelloResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_protos_proto_rawDesc, + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_protos_proto_goTypes, + DependencyIndexes: file_protos_proto_depIdxs, + EnumInfos: file_protos_proto_enumTypes, + MessageInfos: file_protos_proto_msgTypes, + }.Build() + File_protos_proto = out.File + file_protos_proto_rawDesc = nil + file_protos_proto_goTypes = nil + file_protos_proto_depIdxs = nil +} diff --git a/examples/cluster-error-response/protos.proto b/examples/cluster-error-response/protos.proto new file mode 100644 index 00000000..8a729a7a --- /dev/null +++ b/examples/cluster-error-response/protos.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; + +package reentrancy; + +import "protobuf/protoc-gen-go-grain/options/options.proto"; + +option go_package = "github.com/asynkron/protoactor-go/examples/cluster-reentrancy/main"; + +enum ErrorReason { + USER_NOT_FOUND = 0; +} + +message ReenterableRequest { + string name = 1; +} + +message ReenterableResponse { + string message = 1; +} + +message HelloRequest { + string name = 1; +} + +message HelloResponse { + string message = 1; +} + +service Hello { + rpc Reenterable (ReenterableRequest) returns (ReenterableResponse) { + option (options.method_options).reenterable = true; + option (options.method_options).future = true; + } + rpc Hello (HelloRequest) returns (HelloResponse) { + } +} \ No newline at end of file diff --git a/examples/cluster-error-response/protos_grain.pb.go b/examples/cluster-error-response/protos_grain.pb.go new file mode 100644 index 00000000..fbac0c14 --- /dev/null +++ b/examples/cluster-error-response/protos_grain.pb.go @@ -0,0 +1,227 @@ +// Code generated by protoc-gen-grain. DO NOT EDIT. +// versions: +// protoc-gen-grain v0.5.0 +// protoc v4.25.0 +// source: protos.proto + +package main + +import ( + fmt "fmt" + actor "github.com/asynkron/protoactor-go/actor" + cluster "github.com/asynkron/protoactor-go/cluster" + proto "google.golang.org/protobuf/proto" + slog "log/slog" + time "time" +) + +func ErrUserNotFound(format string, args ...interface{}) *cluster.GrainErrorResponse { + return cluster.NewGrainErrorResponse(ErrorReason_USER_NOT_FOUND.String(), fmt.Sprintf(format, args...)) +} + +func IsUserNotFound(err error) bool { + if err == nil { + return false + } + e := cluster.FromError(err) + return e.Reason == ErrorReason_USER_NOT_FOUND.String() +} + +var xHelloFactory func() Hello + +// HelloFactory produces a Hello +func HelloFactory(factory func() Hello) { + xHelloFactory = factory +} + +// GetHelloGrainClient instantiates a new HelloGrainClient with given Identity +func GetHelloGrainClient(c *cluster.Cluster, id string) *HelloGrainClient { + if c == nil { + panic(fmt.Errorf("nil cluster instance")) + } + if id == "" { + panic(fmt.Errorf("empty id")) + } + return &HelloGrainClient{Identity: id, cluster: c} +} + +// GetHelloKind instantiates a new cluster.Kind for Hello +func GetHelloKind(opts ...actor.PropsOption) *cluster.Kind { + props := actor.PropsFromProducer(func() actor.Actor { + return &HelloActor{ + Timeout: 60 * time.Second, + } + }, opts...) + kind := cluster.NewKind("Hello", props) + return kind +} + +// GetHelloKind instantiates a new cluster.Kind for Hello +func NewHelloKind(factory func() Hello, timeout time.Duration, opts ...actor.PropsOption) *cluster.Kind { + xHelloFactory = factory + props := actor.PropsFromProducer(func() actor.Actor { + return &HelloActor{ + Timeout: timeout, + } + }, opts...) + kind := cluster.NewKind("Hello", props) + return kind +} + +// Hello interfaces the services available to the Hello +type Hello interface { + Init(ctx cluster.GrainContext) + Terminate(ctx cluster.GrainContext) + ReceiveDefault(ctx cluster.GrainContext) + Reenterable(req *ReenterableRequest, respond func(*ReenterableResponse), onError func(error), ctx cluster.GrainContext) error + Hello(req *HelloRequest, ctx cluster.GrainContext) (*HelloResponse, error) +} + +// HelloGrainClient holds the base data for the HelloGrain +type HelloGrainClient struct { + Identity string + cluster *cluster.Cluster +} + +// ReenterableFuture return a future for the execution of Reenterable on the cluster +func (g *HelloGrainClient) ReenterableFuture(r *ReenterableRequest, opts ...cluster.GrainCallOption) (*actor.Future, error) { + bytes, err := proto.Marshal(r) + if err != nil { + return nil, err + } + + reqMsg := &cluster.GrainRequest{MethodIndex: 0, MessageData: bytes} + f, err := g.cluster.RequestFuture(g.Identity, "Hello", reqMsg, opts...) + if err != nil { + return nil, fmt.Errorf("error request future: %w", err) + } + + return f, nil +} + +// Reenterable requests the execution on to the cluster with CallOptions +func (g *HelloGrainClient) Reenterable(r *ReenterableRequest, opts ...cluster.GrainCallOption) (*ReenterableResponse, error) { + bytes, err := proto.Marshal(r) + if err != nil { + return nil, err + } + reqMsg := &cluster.GrainRequest{MethodIndex: 0, MessageData: bytes} + resp, err := g.cluster.Request(g.Identity, "Hello", reqMsg, opts...) + if err != nil { + return nil, fmt.Errorf("error request: %w", err) + } + switch msg := resp.(type) { + case *ReenterableResponse: + return msg, nil + case *cluster.GrainErrorResponse: + return nil, msg + default: + return nil, fmt.Errorf("unknown response type %T", resp) + } +} + +// Hello requests the execution on to the cluster with CallOptions +func (g *HelloGrainClient) Hello(r *HelloRequest, opts ...cluster.GrainCallOption) (*HelloResponse, error) { + bytes, err := proto.Marshal(r) + if err != nil { + return nil, err + } + reqMsg := &cluster.GrainRequest{MethodIndex: 1, MessageData: bytes} + resp, err := g.cluster.Request(g.Identity, "Hello", reqMsg, opts...) + if err != nil { + return nil, fmt.Errorf("error request: %w", err) + } + switch msg := resp.(type) { + case *HelloResponse: + return msg, nil + case *cluster.GrainErrorResponse: + return nil, msg + default: + return nil, fmt.Errorf("unknown response type %T", resp) + } +} + +// HelloActor represents the actor structure +type HelloActor struct { + ctx cluster.GrainContext + inner Hello + Timeout time.Duration +} + +// Receive ensures the lifecycle of the actor for the received message +func (a *HelloActor) Receive(ctx actor.Context) { + switch msg := ctx.Message().(type) { + case *actor.Started: //pass + case *cluster.ClusterInit: + a.ctx = cluster.NewGrainContext(ctx, msg.Identity, msg.Cluster) + a.inner = xHelloFactory() + a.inner.Init(a.ctx) + + if a.Timeout > 0 { + ctx.SetReceiveTimeout(a.Timeout) + } + case *actor.ReceiveTimeout: + ctx.Poison(ctx.Self()) + case *actor.Stopped: + a.inner.Terminate(a.ctx) + case actor.AutoReceiveMessage: // pass + case actor.SystemMessage: // pass + + case *cluster.GrainRequest: + switch msg.MethodIndex { + case 0: + req := &ReenterableRequest{} + err := proto.Unmarshal(msg.MessageData, req) + if err != nil { + ctx.Logger().Error("[Grain] Reenterable(ReenterableRequest) proto.Unmarshal failed.", slog.Any("error", err)) + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) + ctx.Respond(resp) + return + } + err = a.inner.Reenterable(req, respond[*ReenterableResponse](a.ctx), a.onError, a.ctx) + if err != nil { + resp := cluster.FromError(err) + ctx.Respond(resp) + return + } + case 1: + req := &HelloRequest{} + err := proto.Unmarshal(msg.MessageData, req) + if err != nil { + ctx.Logger().Error("[Grain] Hello(HelloRequest) proto.Unmarshal failed.", slog.Any("error", err)) + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) + ctx.Respond(resp) + return + } + + r0, err := a.inner.Hello(req, a.ctx) + if err != nil { + resp := cluster.FromError(err) + ctx.Respond(resp) + return + } + ctx.Respond(r0) + } + default: + a.inner.ReceiveDefault(a.ctx) + } +} + +// onError should be used in ctx.ReenterAfter +// you can just return error in reenterable method for other errors +func (a *HelloActor) onError(err error) { + resp := cluster.FromError(err) + a.ctx.Respond(resp) +} + +func respond[T proto.Message](ctx cluster.GrainContext) func(T) { + return func(resp T) { + ctx.Respond(resp) + } +} diff --git a/examples/cluster-grain/shared/protos_grain.pb.go b/examples/cluster-grain/shared/protos_grain.pb.go index 5bb61df4..1f8fc547 100644 --- a/examples/cluster-grain/shared/protos_grain.pb.go +++ b/examples/cluster-grain/shared/protos_grain.pb.go @@ -1,13 +1,12 @@ // Code generated by protoc-gen-grain. DO NOT EDIT. // versions: -// protoc-gen-grain v0.4.1 +// protoc-gen-grain v0.5.0 // protoc v4.25.0 // source: protos.proto package shared import ( - errors "errors" fmt "fmt" actor "github.com/asynkron/protoactor-go/actor" cluster "github.com/asynkron/protoactor-go/cluster" @@ -86,7 +85,7 @@ func (g *HelloGrainClient) SayHello(r *HelloRequest, opts ...cluster.GrainCallOp case *HelloResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -125,14 +124,17 @@ func (a *HelloActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] SayHello(HelloRequest) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.SayHello(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -146,7 +148,7 @@ func (a *HelloActor) Receive(ctx actor.Context) { // onError should be used in ctx.ReenterAfter // you can just return error in reenterable method for other errors func (a *HelloActor) onError(err error) { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) a.ctx.Respond(resp) } diff --git a/examples/cluster-metrics/shared/protos_grain.pb.go b/examples/cluster-metrics/shared/protos_grain.pb.go index d522bbab..16449b7d 100644 --- a/examples/cluster-metrics/shared/protos_grain.pb.go +++ b/examples/cluster-metrics/shared/protos_grain.pb.go @@ -1,13 +1,12 @@ // Code generated by protoc-gen-grain. DO NOT EDIT. // versions: -// protoc-gen-grain v0.4.1 +// protoc-gen-grain v0.5.0 // protoc v4.25.0 // source: protos.proto package shared import ( - errors "errors" fmt "fmt" actor "github.com/asynkron/protoactor-go/actor" cluster "github.com/asynkron/protoactor-go/cluster" @@ -88,7 +87,7 @@ func (g *HelloGrainClient) SayHello(r *HelloRequest, opts ...cluster.GrainCallOp case *HelloResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -109,7 +108,7 @@ func (g *HelloGrainClient) Add(r *AddRequest, opts ...cluster.GrainCallOption) ( case *AddResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -130,7 +129,7 @@ func (g *HelloGrainClient) VoidFunc(r *AddRequest, opts ...cluster.GrainCallOpti case *Unit: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -169,14 +168,17 @@ func (a *HelloActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] SayHello(HelloRequest) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.SayHello(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -186,14 +188,17 @@ func (a *HelloActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] Add(AddRequest) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.Add(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -203,14 +208,17 @@ func (a *HelloActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] VoidFunc(AddRequest) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.VoidFunc(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -224,7 +232,7 @@ func (a *HelloActor) Receive(ctx actor.Context) { // onError should be used in ctx.ReenterAfter // you can just return error in reenterable method for other errors func (a *HelloActor) onError(err error) { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) a.ctx.Respond(resp) } diff --git a/examples/cluster-pubsub/protos_grain.pb.go b/examples/cluster-pubsub/protos_grain.pb.go index baa2e04c..31936c5f 100644 --- a/examples/cluster-pubsub/protos_grain.pb.go +++ b/examples/cluster-pubsub/protos_grain.pb.go @@ -1,13 +1,12 @@ // Code generated by protoc-gen-grain. DO NOT EDIT. // versions: -// protoc-gen-grain v0.4.1 +// protoc-gen-grain v0.5.0 // protoc v4.25.0 // source: protos.proto package main import ( - errors "errors" fmt "fmt" actor "github.com/asynkron/protoactor-go/actor" cluster "github.com/asynkron/protoactor-go/cluster" @@ -86,7 +85,7 @@ func (g *UserActorGrainClient) Connect(r *Empty, opts ...cluster.GrainCallOption case *Empty: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -125,14 +124,17 @@ func (a *UserActorActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] Connect(Empty) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.Connect(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -146,7 +148,7 @@ func (a *UserActorActor) Receive(ctx actor.Context) { // onError should be used in ctx.ReenterAfter // you can just return error in reenterable method for other errors func (a *UserActorActor) onError(err error) { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) a.ctx.Respond(resp) } diff --git a/examples/cluster-reentrancy/hello_grain.pb.go b/examples/cluster-reentrancy/hello_grain.pb.go index e6298ebe..70a31a7e 100644 --- a/examples/cluster-reentrancy/hello_grain.pb.go +++ b/examples/cluster-reentrancy/hello_grain.pb.go @@ -1,13 +1,12 @@ // Code generated by protoc-gen-grain. DO NOT EDIT. // versions: -// protoc-gen-grain v0.4.1 +// protoc-gen-grain v0.5.0 // protoc v4.25.0 // source: hello.proto package main import ( - errors "errors" fmt "fmt" actor "github.com/asynkron/protoactor-go/actor" cluster "github.com/asynkron/protoactor-go/cluster" @@ -103,7 +102,7 @@ func (g *HelloGrainClient) InvokeService(r *InvokeServiceRequest, opts ...cluste case *InvokeServiceResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -140,7 +139,7 @@ func (g *HelloGrainClient) DoWork(r *DoWorkRequest, opts ...cluster.GrainCallOpt case *DoWorkResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -179,13 +178,16 @@ func (a *HelloActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] InvokeService(InvokeServiceRequest) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } err = a.inner.InvokeService(req, respond[*InvokeServiceResponse](a.ctx), a.onError, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -194,14 +196,17 @@ func (a *HelloActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] DoWork(DoWorkRequest) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.DoWork(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -215,7 +220,7 @@ func (a *HelloActor) Receive(ctx actor.Context) { // onError should be used in ctx.ReenterAfter // you can just return error in reenterable method for other errors func (a *HelloActor) onError(err error) { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) a.ctx.Respond(resp) } diff --git a/examples/cluster-restartgracefully/shared/protos_grain.pb.go b/examples/cluster-restartgracefully/shared/protos_grain.pb.go index 6f0826d8..b41c364a 100644 --- a/examples/cluster-restartgracefully/shared/protos_grain.pb.go +++ b/examples/cluster-restartgracefully/shared/protos_grain.pb.go @@ -1,13 +1,12 @@ // Code generated by protoc-gen-grain. DO NOT EDIT. // versions: -// protoc-gen-grain v0.4.1 +// protoc-gen-grain v0.5.0 // protoc v4.25.0 // source: protos.proto package shared import ( - errors "errors" fmt "fmt" actor "github.com/asynkron/protoactor-go/actor" cluster "github.com/asynkron/protoactor-go/cluster" @@ -88,7 +87,7 @@ func (g *CalculatorGrainClient) Add(r *NumberRequest, opts ...cluster.GrainCallO case *CountResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -109,7 +108,7 @@ func (g *CalculatorGrainClient) Subtract(r *NumberRequest, opts ...cluster.Grain case *CountResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -130,7 +129,7 @@ func (g *CalculatorGrainClient) GetCurrent(r *Void, opts ...cluster.GrainCallOpt case *CountResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -169,14 +168,17 @@ func (a *CalculatorActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] Add(NumberRequest) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.Add(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -186,14 +188,17 @@ func (a *CalculatorActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] Subtract(NumberRequest) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.Subtract(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -203,14 +208,17 @@ func (a *CalculatorActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] GetCurrent(Void) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.GetCurrent(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -224,7 +232,7 @@ func (a *CalculatorActor) Receive(ctx actor.Context) { // onError should be used in ctx.ReenterAfter // you can just return error in reenterable method for other errors func (a *CalculatorActor) onError(err error) { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) a.ctx.Respond(resp) } diff --git a/protobuf/protoc-gen-go-grain/Makefile b/protobuf/protoc-gen-go-grain/Makefile index 34a1c167..51503bc6 100644 --- a/protobuf/protoc-gen-go-grain/Makefile +++ b/protobuf/protoc-gen-go-grain/Makefile @@ -1,8 +1,9 @@ .PHONY: testdata testdata: - protoc --go_out=. --go_opt=paths=source_relative --plugin=protoc-gen-go-grain=protoc-gen-go-grain.sh --go-grain_out=. --go-grain_opt=paths=source_relative testdata/hello/*.proto + protoc --go_out=. --go_opt=paths=source_relative --plugin=protoc-gen-go-grain=protoc-gen-go-grain.sh --go-grain_out=. --go-grain_opt=paths=source_relative -I../../ -I. testdata/hello/*.proto protoc --go_out=. --go_opt=paths=source_relative --plugin=protoc-gen-go-grain=protoc-gen-go-grain.sh --go-grain_out=. --go-grain_opt=paths=source_relative -I../../ -I. testdata/reenter/*.proto protoc --go_out=. --go_opt=paths=source_relative --plugin=protoc-gen-go-grain=protoc-gen-go-grain.sh --go-grain_out=. --go-grain_opt=paths=source_relative -I../../ -I. testdata/multi-services/*.proto + protoc --go_out=. --go_opt=paths=source_relative --plugin=protoc-gen-go-grain=protoc-gen-go-grain.sh --go-grain_out=. --go-grain_opt=paths=source_relative -I../../ -I. testdata/error/*.proto .PHONY: options options: diff --git a/protobuf/protoc-gen-go-grain/generate.go b/protobuf/protoc-gen-go-grain/generate.go index 2a2e5b76..2486bac3 100644 --- a/protobuf/protoc-gen-go-grain/generate.go +++ b/protobuf/protoc-gen-go-grain/generate.go @@ -2,8 +2,11 @@ package main import ( "fmt" + "strings" "github.com/asynkron/protoactor-go/protobuf/protoc-gen-go-grain/options" + "golang.org/x/text/cases" + "golang.org/x/text/language" "google.golang.org/protobuf/compiler/protogen" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/descriptorpb" @@ -21,6 +24,11 @@ const ( clusterPackage = protogen.GoImportPath("github.com/asynkron/protoactor-go/cluster") ) +var ( + noLowerCaser = cases.Title(language.AmericanEnglish, cases.NoLower) + caser = cases.Title(language.AmericanEnglish) +) + func generateFile(gen *protogen.Plugin, file *protogen.File) { if len(file.Services) == 0 { return @@ -54,7 +62,6 @@ func generateHeader(gen *protogen.Plugin, g *protogen.GeneratedFile, file *proto func generateContent(gen *protogen.Plugin, g *protogen.GeneratedFile, file *protogen.File) { g.P("package ", file.GoPackageName) - g.P() if len(file.Services) == 0 { return @@ -66,13 +73,18 @@ func generateContent(gen *protogen.Plugin, g *protogen.GeneratedFile, file *prot g.QualifiedGoIdent(fmtPackage.Ident("")) g.QualifiedGoIdent(timePackage.Ident("")) g.QualifiedGoIdent(slogPackage.Ident("")) - g.QualifiedGoIdent(errorsPackage.Ident("")) + + for _, enum := range file.Enums { + if enum.Desc.Name() == "ErrorReason" { + generateErrorReasons(g, enum) + } + } for _, service := range file.Services { generateService(service, file, g) + g.P() } - g.P() generateRespond(g) } @@ -123,3 +135,40 @@ func generateRespond(g *protogen.GeneratedFile) { g.P("}") g.P("}") } + +func generateErrorReasons(g *protogen.GeneratedFile, enum *protogen.Enum) { + var es errorsWrapper + for _, v := range enum.Values { + comment := v.Comments.Leading.String() + if comment == "" { + comment = v.Comments.Trailing.String() + } + + err := &errorDesc{ + Name: string(enum.Desc.Name()), + Value: string(v.Desc.Name()), + CamelValue: toCamel(string(v.Desc.Name())), + Comment: comment, + HasComment: len(comment) > 0, + } + es.Errors = append(es.Errors, err) + } + if len(es.Errors) != 0 { + g.P(es.execute()) + } +} + +func toCamel(s string) string { + if !strings.Contains(s, "_") { + if s == strings.ToUpper(s) { + s = strings.ToLower(s) + } + return noLowerCaser.String(s) + } + + slice := strings.Split(s, "_") + for i := 0; i < len(slice); i++ { + slice[i] = caser.String(slice[i]) + } + return strings.Join(slice, "") +} diff --git a/protobuf/protoc-gen-go-grain/generate_test.go b/protobuf/protoc-gen-go-grain/generate_test.go new file mode 100644 index 00000000..0408e4e1 --- /dev/null +++ b/protobuf/protoc-gen-go-grain/generate_test.go @@ -0,0 +1,62 @@ +package main + +import "testing" + +func Test_toCamel(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "snake1", + args: args{"SYSTEM_ERROR"}, + want: "SystemError", + }, + { + name: "snake2", + args: args{"System_Error"}, + want: "SystemError", + }, + { + name: "snake3", + args: args{"system_error"}, + want: "SystemError", + }, + { + name: "snake4", + args: args{"System_error"}, + want: "SystemError", + }, + { + name: "upper1", + args: args{"UNKNOWN"}, + want: "Unknown", + }, + { + name: "camel1", + args: args{"SystemError"}, + want: "SystemError", + }, + { + name: "camel2", + args: args{"systemError"}, + want: "SystemError", + }, + { + name: "lower1", + args: args{"system"}, + want: "System", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := toCamel(tt.args.s); got != tt.want { + t.Errorf("toCamel() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/protobuf/protoc-gen-go-grain/template.go b/protobuf/protoc-gen-go-grain/template.go index 2beda48a..9d3f8eaa 100644 --- a/protobuf/protoc-gen-go-grain/template.go +++ b/protobuf/protoc-gen-go-grain/template.go @@ -12,6 +12,9 @@ import ( //go:embed templates/grain.tmpl var grainTemplate string +//go:embed templates/error.tmpl +var errorTemplate string + type serviceDesc struct { Name string // Greeter Methods []*methodDesc @@ -25,6 +28,31 @@ type methodDesc struct { Options *options.MethodOptions } +type errorDesc struct { + Name string + Value string + CamelValue string + Comment string + HasComment bool +} + +type errorsWrapper struct { + Errors []*errorDesc +} + +func (es *errorsWrapper) execute() string { + buf := new(bytes.Buffer) + tmpl, err := template.New("error").Parse(strings.TrimSpace(errorTemplate)) + if err != nil { + panic(err) + } + if err := tmpl.Execute(buf, es); err != nil { + panic(err) + } + + return strings.Trim(buf.String(), "\r\n") +} + func (s *serviceDesc) execute() string { buf := new(bytes.Buffer) tmpl, err := template.New("grain").Parse(strings.TrimSpace(grainTemplate)) diff --git a/protobuf/protoc-gen-go-grain/templates/error.tmpl b/protobuf/protoc-gen-go-grain/templates/error.tmpl new file mode 100644 index 00000000..f43984a9 --- /dev/null +++ b/protobuf/protoc-gen-go-grain/templates/error.tmpl @@ -0,0 +1,17 @@ +{{ range .Errors }} + +{{ if .HasComment }}{{ .Comment }}{{ end -}} +func Err{{ .CamelValue }}(format string, args ...interface{}) *cluster.GrainErrorResponse { + return cluster.NewGrainErrorResponse({{ .Name }}_{{ .Value }}.String(), fmt.Sprintf(format, args...)) +} + +{{ if .HasComment }}{{ .Comment }}{{ end -}} +func Is{{.CamelValue}}(err error) bool { + if err == nil { + return false + } + e := cluster.FromError(err) + return e.Reason == {{ .Name }}_{{ .Value }}.String() +} + +{{- end }} diff --git a/protobuf/protoc-gen-go-grain/templates/grain.tmpl b/protobuf/protoc-gen-go-grain/templates/grain.tmpl index 729487f8..5ca0c155 100644 --- a/protobuf/protoc-gen-go-grain/templates/grain.tmpl +++ b/protobuf/protoc-gen-go-grain/templates/grain.tmpl @@ -92,7 +92,7 @@ func (g *{{ $service.Name }}GrainClient) {{ $method.Name }}(r *{{ $method.Input case *{{ $method.Output }}: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -132,24 +132,24 @@ func (a *{{ $service.Name }}Actor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] {{ $method.Name }}({{ $method.Input }}) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } {{ if $method.Options.Reenterable -}} err = a.inner.{{ $method.Name }}(req, respond[*{{ $method.Output }}](a.ctx), a.onError, a.ctx) - if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} - ctx.Respond(resp) - return - } {{ else }} r0, err := a.inner.{{ $method.Name }}(req, a.ctx) + {{ end -}} if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } + {{ if not $method.Options.Reenterable -}} ctx.Respond(r0) {{ end -}} {{ end -}} @@ -162,6 +162,6 @@ func (a *{{ $service.Name }}Actor) Receive(ctx actor.Context) { // onError should be used in ctx.ReenterAfter // you can just return error in reenterable method for other errors func (a *{{ $service.Name }}Actor) onError(err error) { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) a.ctx.Respond(resp) } diff --git a/protobuf/protoc-gen-go-grain/testdata/error/hello.pb.go b/protobuf/protoc-gen-go-grain/testdata/error/hello.pb.go new file mode 100644 index 00000000..94389bc0 --- /dev/null +++ b/protobuf/protoc-gen-go-grain/testdata/error/hello.pb.go @@ -0,0 +1,209 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.32.0 +// protoc v4.25.0 +// source: testdata/error/hello.proto + +package hello + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ErrorReason int32 + +const ( + ErrorReason_USER_NOT_FOUND ErrorReason = 0 + ErrorReason_CONTENT_MISSING ErrorReason = 1 +) + +// Enum value maps for ErrorReason. +var ( + ErrorReason_name = map[int32]string{ + 0: "USER_NOT_FOUND", + 1: "CONTENT_MISSING", + } + ErrorReason_value = map[string]int32{ + "USER_NOT_FOUND": 0, + "CONTENT_MISSING": 1, + } +) + +func (x ErrorReason) Enum() *ErrorReason { + p := new(ErrorReason) + *p = x + return p +} + +func (x ErrorReason) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ErrorReason) Descriptor() protoreflect.EnumDescriptor { + return file_testdata_error_hello_proto_enumTypes[0].Descriptor() +} + +func (ErrorReason) Type() protoreflect.EnumType { + return &file_testdata_error_hello_proto_enumTypes[0] +} + +func (x ErrorReason) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ErrorReason.Descriptor instead. +func (ErrorReason) EnumDescriptor() ([]byte, []int) { + return file_testdata_error_hello_proto_rawDescGZIP(), []int{0} +} + +type SayHelloResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *SayHelloResponse) Reset() { + *x = SayHelloResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_testdata_error_hello_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SayHelloResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SayHelloResponse) ProtoMessage() {} + +func (x *SayHelloResponse) ProtoReflect() protoreflect.Message { + mi := &file_testdata_error_hello_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SayHelloResponse.ProtoReflect.Descriptor instead. +func (*SayHelloResponse) Descriptor() ([]byte, []int) { + return file_testdata_error_hello_proto_rawDescGZIP(), []int{0} +} + +func (x *SayHelloResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_testdata_error_hello_proto protoreflect.FileDescriptor + +var file_testdata_error_hello_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x68, 0x65, + 0x6c, 0x6c, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x22, 0x2c, 0x0a, 0x10, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2a, 0x36, + 0x0a, 0x0b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, + 0x0e, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, + 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x5f, 0x4d, 0x49, 0x53, + 0x53, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x32, 0x46, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, + 0x3d, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x53, 0x61, 0x79, 0x48, + 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x46, + 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, + 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, + 0x2d, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x67, + 0x6f, 0x2d, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, + 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_testdata_error_hello_proto_rawDescOnce sync.Once + file_testdata_error_hello_proto_rawDescData = file_testdata_error_hello_proto_rawDesc +) + +func file_testdata_error_hello_proto_rawDescGZIP() []byte { + file_testdata_error_hello_proto_rawDescOnce.Do(func() { + file_testdata_error_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_testdata_error_hello_proto_rawDescData) + }) + return file_testdata_error_hello_proto_rawDescData +} + +var file_testdata_error_hello_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_testdata_error_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_testdata_error_hello_proto_goTypes = []interface{}{ + (ErrorReason)(0), // 0: hello.ErrorReason + (*SayHelloResponse)(nil), // 1: hello.SayHelloResponse + (*emptypb.Empty)(nil), // 2: google.protobuf.Empty +} +var file_testdata_error_hello_proto_depIdxs = []int32{ + 2, // 0: hello.Hello.SayHello:input_type -> google.protobuf.Empty + 1, // 1: hello.Hello.SayHello:output_type -> hello.SayHelloResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_testdata_error_hello_proto_init() } +func file_testdata_error_hello_proto_init() { + if File_testdata_error_hello_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_testdata_error_hello_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SayHelloResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_testdata_error_hello_proto_rawDesc, + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_testdata_error_hello_proto_goTypes, + DependencyIndexes: file_testdata_error_hello_proto_depIdxs, + EnumInfos: file_testdata_error_hello_proto_enumTypes, + MessageInfos: file_testdata_error_hello_proto_msgTypes, + }.Build() + File_testdata_error_hello_proto = out.File + file_testdata_error_hello_proto_rawDesc = nil + file_testdata_error_hello_proto_goTypes = nil + file_testdata_error_hello_proto_depIdxs = nil +} diff --git a/protobuf/protoc-gen-go-grain/testdata/error/hello.proto b/protobuf/protoc-gen-go-grain/testdata/error/hello.proto new file mode 100644 index 00000000..3a2e0eca --- /dev/null +++ b/protobuf/protoc-gen-go-grain/testdata/error/hello.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package hello; + +import "google/protobuf/empty.proto"; + +option go_package = "github.com/asynkron/protoactor-go/protoc-gen-go-grain/testdata/hello"; + +enum ErrorReason { + USER_NOT_FOUND = 0; + CONTENT_MISSING = 1; +} + +message SayHelloResponse { + string message = 1; +} + +service Hello { + rpc SayHello (google.protobuf.Empty) returns (SayHelloResponse) {} +} \ No newline at end of file diff --git a/protobuf/protoc-gen-go-grain/testdata/error/hello_grain.pb.go b/protobuf/protoc-gen-go-grain/testdata/error/hello_grain.pb.go new file mode 100644 index 00000000..10d413ed --- /dev/null +++ b/protobuf/protoc-gen-go-grain/testdata/error/hello_grain.pb.go @@ -0,0 +1,184 @@ +// Code generated by protoc-gen-grain. DO NOT EDIT. +// versions: +// protoc-gen-grain v0.4.1 +// protoc v4.25.0 +// source: testdata/error/hello.proto + +package hello + +import ( + fmt "fmt" + actor "github.com/asynkron/protoactor-go/actor" + cluster "github.com/asynkron/protoactor-go/cluster" + proto "google.golang.org/protobuf/proto" + emptypb "google.golang.org/protobuf/types/known/emptypb" + slog "log/slog" + time "time" +) + +func ErrUserNotFound(format string, args ...interface{}) *cluster.GrainErrorResponse { + return cluster.NewGrainErrorResponse(ErrorReason_USER_NOT_FOUND.String(), fmt.Sprintf(format, args...)) +} + +func IsUserNotFound(err error) bool { + if err == nil { + return false + } + e := cluster.FromError(err) + return e.Reason == ErrorReason_USER_NOT_FOUND.String() +} + +func ErrContentMissing(format string, args ...interface{}) *cluster.GrainErrorResponse { + return cluster.NewGrainErrorResponse(ErrorReason_CONTENT_MISSING.String(), fmt.Sprintf(format, args...)) +} + +func IsContentMissing(err error) bool { + if err == nil { + return false + } + e := cluster.FromError(err) + return e.Reason == ErrorReason_CONTENT_MISSING.String() +} + +var xHelloFactory func() Hello + +// HelloFactory produces a Hello +func HelloFactory(factory func() Hello) { + xHelloFactory = factory +} + +// GetHelloGrainClient instantiates a new HelloGrainClient with given Identity +func GetHelloGrainClient(c *cluster.Cluster, id string) *HelloGrainClient { + if c == nil { + panic(fmt.Errorf("nil cluster instance")) + } + if id == "" { + panic(fmt.Errorf("empty id")) + } + return &HelloGrainClient{Identity: id, cluster: c} +} + +// GetHelloKind instantiates a new cluster.Kind for Hello +func GetHelloKind(opts ...actor.PropsOption) *cluster.Kind { + props := actor.PropsFromProducer(func() actor.Actor { + return &HelloActor{ + Timeout: 60 * time.Second, + } + }, opts...) + kind := cluster.NewKind("Hello", props) + return kind +} + +// GetHelloKind instantiates a new cluster.Kind for Hello +func NewHelloKind(factory func() Hello, timeout time.Duration, opts ...actor.PropsOption) *cluster.Kind { + xHelloFactory = factory + props := actor.PropsFromProducer(func() actor.Actor { + return &HelloActor{ + Timeout: timeout, + } + }, opts...) + kind := cluster.NewKind("Hello", props) + return kind +} + +// Hello interfaces the services available to the Hello +type Hello interface { + Init(ctx cluster.GrainContext) + Terminate(ctx cluster.GrainContext) + ReceiveDefault(ctx cluster.GrainContext) + SayHello(req *emptypb.Empty, ctx cluster.GrainContext) (*SayHelloResponse, error) +} + +// HelloGrainClient holds the base data for the HelloGrain +type HelloGrainClient struct { + Identity string + cluster *cluster.Cluster +} + +// SayHello requests the execution on to the cluster with CallOptions +func (g *HelloGrainClient) SayHello(r *emptypb.Empty, opts ...cluster.GrainCallOption) (*SayHelloResponse, error) { + bytes, err := proto.Marshal(r) + if err != nil { + return nil, err + } + reqMsg := &cluster.GrainRequest{MethodIndex: 0, MessageData: bytes} + resp, err := g.cluster.Request(g.Identity, "Hello", reqMsg, opts...) + if err != nil { + return nil, fmt.Errorf("error request: %w", err) + } + switch msg := resp.(type) { + case *SayHelloResponse: + return msg, nil + case *cluster.GrainErrorResponse: + return nil, msg + default: + return nil, fmt.Errorf("unknown response type %T", resp) + } +} + +// HelloActor represents the actor structure +type HelloActor struct { + ctx cluster.GrainContext + inner Hello + Timeout time.Duration +} + +// Receive ensures the lifecycle of the actor for the received message +func (a *HelloActor) Receive(ctx actor.Context) { + switch msg := ctx.Message().(type) { + case *actor.Started: //pass + case *cluster.ClusterInit: + a.ctx = cluster.NewGrainContext(ctx, msg.Identity, msg.Cluster) + a.inner = xHelloFactory() + a.inner.Init(a.ctx) + + if a.Timeout > 0 { + ctx.SetReceiveTimeout(a.Timeout) + } + case *actor.ReceiveTimeout: + ctx.Poison(ctx.Self()) + case *actor.Stopped: + a.inner.Terminate(a.ctx) + case actor.AutoReceiveMessage: // pass + case actor.SystemMessage: // pass + + case *cluster.GrainRequest: + switch msg.MethodIndex { + case 0: + req := &emptypb.Empty{} + err := proto.Unmarshal(msg.MessageData, req) + if err != nil { + ctx.Logger().Error("[Grain] SayHello(emptypb.Empty) proto.Unmarshal failed.", slog.Any("error", err)) + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) + ctx.Respond(resp) + return + } + + r0, err := a.inner.SayHello(req, a.ctx) + if err != nil { + resp := cluster.FromError(err) + ctx.Respond(resp) + return + } + ctx.Respond(r0) + } + default: + a.inner.ReceiveDefault(a.ctx) + } +} + +// onError should be used in ctx.ReenterAfter +// you can just return error in reenterable method for other errors +func (a *HelloActor) onError(err error) { + resp := cluster.FromError(err) + a.ctx.Respond(resp) +} + +func respond[T proto.Message](ctx cluster.GrainContext) func(T) { + return func(resp T) { + ctx.Respond(resp) + } +} diff --git a/protobuf/protoc-gen-go-grain/testdata/hello/hello_grain.pb.go b/protobuf/protoc-gen-go-grain/testdata/hello/hello_grain.pb.go index 303de6f5..ba88ea7f 100644 --- a/protobuf/protoc-gen-go-grain/testdata/hello/hello_grain.pb.go +++ b/protobuf/protoc-gen-go-grain/testdata/hello/hello_grain.pb.go @@ -7,7 +7,6 @@ package hello import ( - errors "errors" fmt "fmt" actor "github.com/asynkron/protoactor-go/actor" cluster "github.com/asynkron/protoactor-go/cluster" @@ -87,7 +86,7 @@ func (g *HelloGrainClient) SayHello(r *emptypb.Empty, opts ...cluster.GrainCallO case *SayHelloResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -126,14 +125,17 @@ func (a *HelloActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] SayHello(emptypb.Empty) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.SayHello(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -147,7 +149,7 @@ func (a *HelloActor) Receive(ctx actor.Context) { // onError should be used in ctx.ReenterAfter // you can just return error in reenterable method for other errors func (a *HelloActor) onError(err error) { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) a.ctx.Respond(resp) } diff --git a/protobuf/protoc-gen-go-grain/testdata/multi-services/services_grain.pb.go b/protobuf/protoc-gen-go-grain/testdata/multi-services/services_grain.pb.go index f15e3879..9e4eb6a8 100644 --- a/protobuf/protoc-gen-go-grain/testdata/multi-services/services_grain.pb.go +++ b/protobuf/protoc-gen-go-grain/testdata/multi-services/services_grain.pb.go @@ -7,7 +7,6 @@ package hello import ( - errors "errors" fmt "fmt" actor "github.com/asynkron/protoactor-go/actor" cluster "github.com/asynkron/protoactor-go/cluster" @@ -87,7 +86,7 @@ func (g *HelloGrainClient) SayHello(r *emptypb.Empty, opts ...cluster.GrainCallO case *SayHelloResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -126,14 +125,17 @@ func (a *HelloActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] SayHello(emptypb.Empty) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.SayHello(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -147,7 +149,7 @@ func (a *HelloActor) Receive(ctx actor.Context) { // onError should be used in ctx.ReenterAfter // you can just return error in reenterable method for other errors func (a *HelloActor) onError(err error) { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) a.ctx.Respond(resp) } @@ -221,7 +223,7 @@ func (g *WorkGrainClient) DoWork(r *DoWorkRequest, opts ...cluster.GrainCallOpti case *DoWorkResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -260,14 +262,17 @@ func (a *WorkActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] DoWork(DoWorkRequest) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.DoWork(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -281,7 +286,7 @@ func (a *WorkActor) Receive(ctx actor.Context) { // onError should be used in ctx.ReenterAfter // you can just return error in reenterable method for other errors func (a *WorkActor) onError(err error) { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) a.ctx.Respond(resp) } diff --git a/protobuf/protoc-gen-go-grain/testdata/reenter/hello_grain.pb.go b/protobuf/protoc-gen-go-grain/testdata/reenter/hello_grain.pb.go index 85026706..26a2a561 100644 --- a/protobuf/protoc-gen-go-grain/testdata/reenter/hello_grain.pb.go +++ b/protobuf/protoc-gen-go-grain/testdata/reenter/hello_grain.pb.go @@ -7,7 +7,6 @@ package hello import ( - errors "errors" fmt "fmt" actor "github.com/asynkron/protoactor-go/actor" cluster "github.com/asynkron/protoactor-go/cluster" @@ -87,7 +86,7 @@ func (g *HelloGrainClient) SayHello(r *SayHelloRequest, opts ...cluster.GrainCal case *SayHelloResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -124,7 +123,7 @@ func (g *HelloGrainClient) Dowork(r *DoworkRequest, opts ...cluster.GrainCallOpt case *DoworkResponse: return msg, nil case *cluster.GrainErrorResponse: - return nil, errors.New(msg.Err) + return nil, msg default: return nil, fmt.Errorf("unknown response type %T", resp) } @@ -163,13 +162,16 @@ func (a *HelloActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] SayHello(SayHelloRequest) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } err = a.inner.SayHello(req, respond[*SayHelloResponse](a.ctx), a.onError, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -178,14 +180,17 @@ func (a *HelloActor) Receive(ctx actor.Context) { err := proto.Unmarshal(msg.MessageData, req) if err != nil { ctx.Logger().Error("[Grain] Dowork(DoworkRequest) proto.Unmarshal failed.", slog.Any("error", err)) - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.NewGrainErrorResponse(cluster.ErrorReason_INVALID_ARGUMENT, err.Error()). + WithMetadata(map[string]string{ + "argument": req.String(), + }) ctx.Respond(resp) return } r0, err := a.inner.Dowork(req, a.ctx) if err != nil { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) ctx.Respond(resp) return } @@ -199,7 +204,7 @@ func (a *HelloActor) Receive(ctx actor.Context) { // onError should be used in ctx.ReenterAfter // you can just return error in reenterable method for other errors func (a *HelloActor) onError(err error) { - resp := &cluster.GrainErrorResponse{Err: err.Error()} + resp := cluster.FromError(err) a.ctx.Respond(resp) } diff --git a/protobuf/protoc-gen-go-grain/version.go b/protobuf/protoc-gen-go-grain/version.go index afb71cb8..2916cf14 100644 --- a/protobuf/protoc-gen-go-grain/version.go +++ b/protobuf/protoc-gen-go-grain/version.go @@ -1,3 +1,3 @@ package main -const version = "v0.4.1" +const version = "v0.5.0"