diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7d71e7d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: go +os: + - linux + - osx + - windows +go: + - 1.13.x +before_install: + - go get -t -v ./... +script: + - go test -v -race -coverprofile=coverage.txt -covermode=atomic -bench=. ./... +after_success: + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then bash <(curl -s https://codecov.io/bash); fi diff --git a/README.md b/README.md index 0bd8abc..7cb731e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ -# go-support -GoLang support for [CloudState](https://github.com/cloudstateio/cloudstate) +# CloudState stateful service support in Go +[![Build Status](https://travis-ci.com/marcellanz/go-support.svg?branch=feature%2Fgo-support)](https://travis-ci.com/marcellanz/go-support) +[![codecov](https://codecov.io/gh/marcellanz/go-support/branch/master/graph/badge.svg)](https://codecov.io/gh/marcellanz/go-support) +[![GoDoc](https://godoc.org/github.com/marcellanz/go-support?status.svg)](https://godoc.org/github.com/marcellanz/go-support) + +This package provides support for writing [CloudState](https://github.com/cloudstateio/cloudstate) stateful functions in Go. + +For more information see https://cloudstate.io. \ No newline at end of file diff --git a/build/TCK.Dockerfile b/build/TCK.Dockerfile new file mode 100644 index 0000000..d31efa8 --- /dev/null +++ b/build/TCK.Dockerfile @@ -0,0 +1,23 @@ +FROM golang:1.13.1-alpine3.10 + +RUN apk --no-cache add git + +WORKDIR /go/src/app +COPY . . + +# -race and therefore CGO needs gcc, we don't want it to have in our build +RUN CGO_ENABLED=0 go build -v -o tck_shoppingcart ./tck/cmd/tck_shoppingcart +RUN go install -v ./... + +# multistage – copy over the binary +FROM alpine:latest +RUN apk --no-cache add ca-certificates + +WORKDIR /root/ +COPY --from=0 /go/bin/tck_shoppingcart . + +EXPOSE 8080 +ENV HOST 0.0.0.0 +ENV PORT 8080 + +CMD ["./tck_shoppingcart"] diff --git a/build/build-and-publish-docker-image-tck.sh b/build/build-and-publish-docker-image-tck.sh new file mode 100755 index 0000000..933b86a --- /dev/null +++ b/build/build-and-publish-docker-image-tck.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -o nounset +set -o errexit +set -o pipefail + +docker build -t gcr.io/mrcllnz/cloudstate-go-tck:latest -f ./build/TCK.Dockerfile . +docker push gcr.io/mrcllnz/cloudstate-go-tck:latest diff --git a/build/compile-pb.sh b/build/compile-pb.sh new file mode 100755 index 0000000..93d65d0 --- /dev/null +++ b/build/compile-pb.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -o nounset +set -o errexit +set -o pipefail + +# CloudState protocol +protoc --go_out=plugins=grpc,paths=source_relative:. --proto_path=protobuf/frontend/ protobuf/frontend/cloudstate/entity_key.proto +protoc --go_out=plugins=grpc:. --proto_path=protobuf/protocol/ protobuf/protocol/cloudstate/entity.proto +protoc --go_out=plugins=grpc:. --proto_path=protobuf/protocol/ protobuf/protocol/cloudstate/event_sourced.proto +protoc --go_out=plugins=grpc:. --proto_path=protobuf/protocol/ protobuf/protocol/cloudstate/function.proto + +# TCK shopping cart sample +protoc --go_out=plugins=grpc:. --proto_path=protobuf/protocol/ --proto_path=protobuf/frontend/ --proto_path=protobuf/proxy/ --proto_path=protobuf/example/ protobuf/example/shoppingcart/shoppingcart.proto +protoc --go_out=plugins=grpc,paths=source_relative:tck/shoppingcart/persistence --proto_path=protobuf/protocol/ --proto_path=protobuf/frontend/ --proto_path=protobuf/proxy/ --proto_path=protobuf/example/shoppingcart/persistence/ protobuf/example/shoppingcart/persistence/domain.proto diff --git a/build/fetch-cloudstate-pb.sh b/build/fetch-cloudstate-pb.sh new file mode 100755 index 0000000..3e8714d --- /dev/null +++ b/build/fetch-cloudstate-pb.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -o nounset +set -o errexit +set -o pipefail + +function fetch() { + local path=$1 + local tag=$2 + mkdir -p "protobuf/$(dirname $path)" + curl -o "protobuf/${path}" "https://raw.githubusercontent.com/cloudstateio/cloudstate/${tag}/protocols/${path}" +} + +tag=$1 + +# CloudState protocol +fetch "protocol/cloudstate/entity.proto" "${tag}" +fetch "protocol/cloudstate/event_sourced.proto" "${tag}" +fetch "protocol/cloudstate/function.proto" "${tag}" +fetch "protocol/cloudstate/crdt.proto" "${tag}" + +# TCK shopping cart example +fetch "example/shoppingcart/shoppingcart.proto" "${tag}" +fetch "example/shoppingcart/persistence/domain.proto" "${tag}" + +# CloudState frontend +fetch "frontend/cloudstate/entity_key.proto" "${tag}" + +# dependencies +fetch "proxy/grpc/reflection/v1alpha/reflection.proto" "${tag}" +fetch "frontend/google/api/annotations.proto" "${tag}" +fetch "frontend/google/api/http.proto" "${tag}" diff --git a/cloudstate/cloudstate.go b/cloudstate/cloudstate.go new file mode 100644 index 0000000..9813f2d --- /dev/null +++ b/cloudstate/cloudstate.go @@ -0,0 +1,245 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudstate + +import ( + "context" + "errors" + "fmt" + "log" + "net" + "os" + "runtime" + + "github.com/cloudstateio/go-support/cloudstate/protocol" + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + filedescr "github.com/golang/protobuf/protoc-gen-go/descriptor" + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc" +) + +const ( + SupportLibraryVersion = "0.1.0" + SupportLibraryName = "cloudstate-go-support" +) + +// CloudState is an instance of a CloudState User Function +type CloudState struct { + server *grpc.Server + entityDiscoveryServer *EntityDiscoveryServer + eventSourcedServer *EventSourcedServer +} + +// New returns a new CloudState instance. +func New(options Options) (*CloudState, error) { + cs := &CloudState{ + server: grpc.NewServer(), + entityDiscoveryServer: newEntityDiscoveryResponder(options), + eventSourcedServer: newEventSourcedServer(), + } + protocol.RegisterEntityDiscoveryServer(cs.server, cs.entityDiscoveryServer) + protocol.RegisterEventSourcedServer(cs.server, cs.eventSourcedServer) + return cs, nil +} + +// Options go get a CloudState instance configured. +type Options struct { + ServiceName string + ServiceVersion string +} + +// DescriptorConfig configures service and dependent descriptors. +type DescriptorConfig struct { + Service string + ServiceMsg descriptor.Message + Domain []string + DomainMessages []descriptor.Message +} + +func (dc DescriptorConfig) AddDomainMessage(m descriptor.Message) DescriptorConfig { + dc.DomainMessages = append(dc.DomainMessages, m) + return dc +} + +func (dc DescriptorConfig) AddDomainDescriptor(filename string) DescriptorConfig { + dc.Domain = append(dc.Domain, filename) + return dc +} + +// RegisterEventSourcedEntity registers an event sourced entity for CloudState. +func (cs *CloudState) RegisterEventSourcedEntity(ese *EventSourcedEntity, config DescriptorConfig) (err error) { + ese.registerOnce.Do(func() { + if err = ese.initZeroValue(); err != nil { + return + } + if err = cs.eventSourcedServer.registerEntity(ese); err != nil { + return + } + if err = cs.entityDiscoveryServer.registerEntity(ese, config); err != nil { + return + } + }) + return +} + +// Run runs the CloudState instance. +func (cs *CloudState) Run() error { + host, ok := os.LookupEnv("HOST") + if !ok { + return fmt.Errorf("unable to get environment variable \"HOST\"") + } + port, ok := os.LookupEnv("PORT") + if !ok { + return fmt.Errorf("unable to get environment variable \"PORT\"") + } + lis, err := net.Listen("tcp", fmt.Sprintf("%s:%s", host, port)) + if err != nil { + return fmt.Errorf("failed to listen: %v", err) + } + if e := cs.server.Serve(lis); e != nil { + return fmt.Errorf("failed to grpcServer.Serve for: %v", lis) + } + return nil +} + +// EntityDiscoveryServer implements the CloudState discovery protocol. +type EntityDiscoveryServer struct { + fileDescriptorSet *filedescr.FileDescriptorSet + entitySpec *protocol.EntitySpec + message *descriptor.Message +} + +// newEntityDiscoveryResponder returns a new and initialized EntityDiscoveryServer. +func newEntityDiscoveryResponder(options Options) *EntityDiscoveryServer { + responder := &EntityDiscoveryServer{} + responder.entitySpec = &protocol.EntitySpec{ + Entities: make([]*protocol.Entity, 0), + ServiceInfo: &protocol.ServiceInfo{ + ServiceName: options.ServiceName, + ServiceVersion: options.ServiceVersion, + ServiceRuntime: fmt.Sprintf("%s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH), + SupportLibraryName: SupportLibraryName, + SupportLibraryVersion: SupportLibraryVersion, + }, + } + responder.fileDescriptorSet = &filedescr.FileDescriptorSet{ + File: make([]*filedescr.FileDescriptorProto, 0), + } + return responder +} + +// Discover returns an entity spec for +func (r *EntityDiscoveryServer) Discover(c context.Context, pi *protocol.ProxyInfo) (*protocol.EntitySpec, error) { + log.Printf("Received discovery call from sidecar [%s w%s] supporting CloudState %v.%v\n", + pi.ProxyName, + pi.ProxyVersion, + pi.ProtocolMajorVersion, + pi.ProtocolMinorVersion, + ) + for _, filename := range []string{ + "google/protobuf/empty.proto", + "google/protobuf/any.proto", + "google/protobuf/descriptor.proto", + "google/api/annotations.proto", + "google/api/http.proto", + "cloudstate/event_sourced.proto", + "cloudstate/entity.proto", + "cloudstate/entity_key.proto", + } { + if err := r.registerFileDescriptorProto(filename); err != nil { + return nil, err + } + } + log.Printf("Responding with: %v\n", r.entitySpec.GetServiceInfo()) + return r.entitySpec, nil +} + +// ReportError logs any user function error reported by the CloudState proxy. +func (r *EntityDiscoveryServer) ReportError(c context.Context, fe *protocol.UserFunctionError) (*empty.Empty, error) { + log.Printf("ReportError: %v\n", fe) + return &empty.Empty{}, nil +} + +func (r *EntityDiscoveryServer) updateSpec() (err error) { + protoBytes, err := proto.Marshal(r.fileDescriptorSet) + if err != nil { + return errors.New("unable to Marshal FileDescriptorSet") + } + r.entitySpec.Proto = protoBytes + return nil +} + +func (r *EntityDiscoveryServer) resolveFileDescriptors(dc DescriptorConfig) error { + // service + if dc.Service != "" { + if err := r.registerFileDescriptorProto(dc.Service); err != nil { + return err + } + } else { + if dc.ServiceMsg != nil { + if err := r.registerFileDescriptor(dc.ServiceMsg); err != nil { + return err + } + } + } + // and dependent domain descriptors + for _, dp := range dc.Domain { + if err := r.registerFileDescriptorProto(dp); err != nil { + return err + } + } + for _, dm := range dc.DomainMessages { + if err := r.registerFileDescriptor(dm); err != nil { + return err + } + } + return nil +} + +func (r *EntityDiscoveryServer) registerEntity(e *EventSourcedEntity, config DescriptorConfig) error { + if err := r.resolveFileDescriptors(config); err != nil { + return fmt.Errorf("failed to resolveFileDescriptor for DescriptorConfig: %+v: %w", config, err) + } + persistenceID := e.entityName + if e.PersistenceID != "" { + persistenceID = e.PersistenceID + } + r.entitySpec.Entities = append(r.entitySpec.Entities, &protocol.Entity{ + EntityType: EventSourced, + ServiceName: e.ServiceName, + PersistenceId: persistenceID, + }) + return r.updateSpec() +} + +func (r *EntityDiscoveryServer) registerFileDescriptorProto(filename string) error { + descriptorProto, err := unpackFile(proto.FileDescriptor(filename)) + if err != nil { + return fmt.Errorf("failed to registerFileDescriptorProto for filename: %s: %w", filename, err) + } + r.fileDescriptorSet.File = append(r.fileDescriptorSet.File, descriptorProto) + return r.updateSpec() +} + +func (r *EntityDiscoveryServer) registerFileDescriptor(msg descriptor.Message) error { + fd, _ := descriptor.ForMessage(msg) // this can panic + if r := recover(); r != nil { + return fmt.Errorf("descriptor.ForMessage panicked (%v) for: %+v", r, msg) + } + r.fileDescriptorSet.File = append(r.fileDescriptorSet.File, fd) + return nil +} diff --git a/cloudstate/cloudstate_test.go b/cloudstate/cloudstate_test.go new file mode 100644 index 0000000..53b281a --- /dev/null +++ b/cloudstate/cloudstate_test.go @@ -0,0 +1,97 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package cloudstate implements the CloudState event sourced and entity discovery protocol. +package cloudstate + +import ( + "bytes" + "context" + "log" + "os" + "strings" + "testing" + + "github.com/cloudstateio/go-support/cloudstate/protocol" + _ "google.golang.org/genproto/googleapis/api/annotations" +) + +func TestNewCloudState(t *testing.T) { + cloudState, _ := New(Options{}) + si := cloudState.server.GetServiceInfo() + if si == nil { + t.Fail() + } +} + +func TestEntityDiscoveryResponderDiscover(t *testing.T) { + responder := newEntityDiscoveryResponder(Options{ + ServiceName: "service.one", + ServiceVersion: "0.0.1", + }) + info := &protocol.ProxyInfo{ + ProtocolMajorVersion: 0, + ProtocolMinorVersion: 0, + ProxyName: "test-proxy", + ProxyVersion: "9.8.7", + SupportedEntityTypes: nil, + } + spec, err := responder.Discover(context.Background(), info) + if err != nil { + t.Errorf("responder.Discover returned err: %v", err) + } + if spec == nil { + t.Errorf("no EntitySpec returned by responder.Discover") + } + + if spec.ServiceInfo.ServiceName != "service.one" { + t.Errorf("spec.ServiceInfo.ServiceName != 'service.one' but is: %s", spec.ServiceInfo.ServiceName) + } + if spec.ServiceInfo.ServiceVersion != "0.0.1" { + t.Errorf("spec.ServiceInfo.ServiceVersion != '0.0.1' but is: %s", spec.ServiceInfo.ServiceVersion) + } + if !strings.HasPrefix(spec.ServiceInfo.ServiceRuntime, "go") { + t.Errorf("spec.ServiceInfo.ServiceRuntime does not start with prefix: go") + } + if spec.ServiceInfo.SupportLibraryName != "cloudstate-go-support" { + t.Errorf("spec.ServiceInfo.SupportLibraryName != 'cloudstate-go-support'") + } +} + +func captureOutput(f func()) string { + var buf bytes.Buffer + log.SetOutput(&buf) + defer log.SetOutput(os.Stderr) + f() + return buf.String() +} + +func TestEntityDiscoveryResponderReportError(t *testing.T) { + responder := newEntityDiscoveryResponder(Options{ + ServiceName: "service.one", + ServiceVersion: "0.0.1", + }) + output := captureOutput(func() { + empty, err := responder.ReportError(context.Background(), &protocol.UserFunctionError{ + Message: "unable to do XYZ", + }) + if err != nil || empty == nil { + t.Errorf("responder.ReportError failed with err: %v, empty: %v", err, empty) + } + }) + if !strings.Contains(output, "unable to do XYZ") { + t.Errorf("'unable to do XYZ' not found in output: %s", output) + } +} diff --git a/cloudstate/doc.go b/cloudstate/doc.go new file mode 100644 index 0000000..fe79b31 --- /dev/null +++ b/cloudstate/doc.go @@ -0,0 +1,17 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package cloudstate implements the CloudState event sourced and entity discovery protocol. +package cloudstate diff --git a/cloudstate/encoding/any.go b/cloudstate/encoding/any.go new file mode 100644 index 0000000..affc75c --- /dev/null +++ b/cloudstate/encoding/any.go @@ -0,0 +1,21 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package encoding + +import "errors" + +var ErrNotMarshalled = errors.New("not marshalled") +var ErrNotUnmarshalled = errors.New("not unmarshalled") diff --git a/cloudstate/encoding/any_json.go b/cloudstate/encoding/any_json.go new file mode 100644 index 0000000..014066a --- /dev/null +++ b/cloudstate/encoding/any_json.go @@ -0,0 +1,66 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package encoding + +import ( + "encoding/json" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes/any" + "reflect" + "strings" +) + +const ( + jsonTypeURLPrefix = "json.cloudstate.io" +) + +func MarshalJSON(value interface{}) (*any.Any, error) { + typeOf := reflect.TypeOf(value) + if typeOf.Kind() != reflect.Struct { + return nil, ErrNotMarshalled + } + buffer := proto.NewBuffer(make([]byte, 0)) + buffer.SetDeterministic(true) + typeUrl := jsonTypeURLPrefix + "/" + typeOf.PkgPath() + "." + typeOf.Name() + _ = buffer.EncodeVarint(fieldKey | proto.WireBytes) + bytes, err := json.Marshal(value) + if err != nil { + return nil, err + } + _ = buffer.EncodeRawBytes(bytes) + return &any.Any{ + TypeUrl: typeUrl, + Value: buffer.Bytes(), + }, nil +} + +// UnmarshalPrimitive decodes a CloudState Any proto message +// into its JSON value. +func UnmarshalJSON(any *any.Any, target interface{}) error { + if !strings.HasPrefix(any.GetTypeUrl(), jsonTypeURLPrefix) { + return ErrNotMarshalled + } + buffer := proto.NewBuffer(any.GetValue()) + _, err := buffer.DecodeVarint() + if err != nil { + return ErrNotUnmarshalled + } + bytes, err := buffer.DecodeRawBytes(true) + if err != nil { + return ErrNotUnmarshalled + } + return json.Unmarshal(bytes, target) +} diff --git a/cloudstate/encoding/any_json_test.go b/cloudstate/encoding/any_json_test.go new file mode 100644 index 0000000..e9e3c18 --- /dev/null +++ b/cloudstate/encoding/any_json_test.go @@ -0,0 +1,92 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package encoding + +import ( + "fmt" + "github.com/golang/protobuf/ptypes/any" + "reflect" + "testing" +) + +var testsJSON = []struct { + name string + value interface{} + zero interface{} + typeURL string + shouldFail bool +}{ + {jsonTypeURLPrefix + "/github.com/cloudstateio/go-support/cloudstate/encoding.a", + a{B: "29", C: 29}, a{}, jsonTypeURLPrefix + "/github.com/cloudstateio/go-support/cloudstate/encoding.a", false}, + {jsonTypeURLPrefix + "/github.com/cloudstateio/go-support/cloudstate/encoding.aDefault" + "_defaultValue", + aDefault{}, aDefault{}, jsonTypeURLPrefix + "/github.com/cloudstateio/go-support/cloudstate/encoding.aDefault", false}, +} + +func TestMarshallerJSON(t *testing.T) { + for _, test := range testsJSON { + test0 := test + t.Run(fmt.Sprintf("%v", test0.name), func(t *testing.T) { + any0, err := MarshalJSON(test0.value) + hasErr := err != nil + if hasErr && !test0.shouldFail { + t.Errorf("err: %v, got: %+v, expected: %+v", err, any0, test0) + return + } else if !hasErr && test0.shouldFail { + t.Errorf("err: %v, got: %+v, expected: %+v", err, any0, test0) + return + } + failed := any0.GetTypeUrl() != test0.typeURL + if failed && !test0.shouldFail { + t.Errorf("err: %v, got: %+v, expected: %+v", err, any0, test0) + } else if !failed && test0.shouldFail { + t.Errorf("err: %v, got: %+v, expected: %+v", err, any0, test0) + } + value := reflect.New(reflect.TypeOf(test0.value)) + err = UnmarshalJSON(any0, value.Interface()) + if err != nil { + t.Error(err) + } + if test0.value != value.Elem().Interface() { + t.Errorf("err: %v. got: %+v, expected: %+v", err, value.Elem().Interface(), test0.value) + } + }) + } +} + +func BenchmarkMarshallerJSON(b *testing.B) { + var any0 *any.Any + for _, bench := range testsJSON { + if !bench.shouldFail { + b.Run(bench.name, func(b *testing.B) { + b.ReportAllocs() + var any1 *any.Any + for i := 0; i < b.N; i++ { + any0, err := MarshalJSON(bench.value) + if err != nil { + b.Error(err) + } + value := bench.zero + err = UnmarshalJSON(any0, &value) + if err != nil { + b.Error(err) + } + } + any0 = any1 //prevent the call optimized away + }) + } + } + _ = any0 == nil //use any0 +} diff --git a/cloudstate/encoding/any_primitive.go b/cloudstate/encoding/any_primitive.go new file mode 100644 index 0000000..8cde6d0 --- /dev/null +++ b/cloudstate/encoding/any_primitive.go @@ -0,0 +1,173 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package encoding + +import ( + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes/any" + "math" +) + +const ( + PrimitiveTypeURLPrefix = "p.cloudstate.io" + + primitiveTypeURLPrefixInt32 = PrimitiveTypeURLPrefix + "/int32" + primitiveTypeURLPrefixInt64 = PrimitiveTypeURLPrefix + "/int64" + primitiveTypeURLPrefixString = PrimitiveTypeURLPrefix + "/string" + primitiveTypeURLPrefixFloat = PrimitiveTypeURLPrefix + "/float" + primitiveTypeURLPrefixDouble = PrimitiveTypeURLPrefix + "/double" + primitiveTypeURLPrefixBool = PrimitiveTypeURLPrefix + "/bool" + primitiveTypeURLPrefixBytes = PrimitiveTypeURLPrefix + "/bytes" +) + +const fieldKey = 1 << 3 + +func MarshalPrimitive(i interface{}) (*any.Any, error) { + buf := make([]byte, 0) + buffer := proto.NewBuffer(buf) + buffer.SetDeterministic(true) + // see https://developers.google.com/protocol-buffers/docs/encoding#structure + var typeUrl string + switch val := i.(type) { + case int32: + typeUrl = primitiveTypeURLPrefixInt32 + _ = buffer.EncodeVarint(fieldKey | proto.WireVarint) + _ = buffer.EncodeVarint(uint64(val)) + case int64: + typeUrl = primitiveTypeURLPrefixInt64 + _ = buffer.EncodeVarint(fieldKey | proto.WireVarint) + _ = buffer.EncodeVarint(uint64(val)) + case string: + typeUrl = primitiveTypeURLPrefixString + _ = buffer.EncodeVarint(fieldKey | proto.WireBytes) + if err := buffer.EncodeStringBytes(val); err != nil { + return nil, err + } + case float32: + typeUrl = primitiveTypeURLPrefixFloat + _ = buffer.EncodeVarint(fieldKey | proto.WireFixed32) + _ = buffer.EncodeFixed32(uint64(math.Float32bits(val))) + case float64: + typeUrl = primitiveTypeURLPrefixDouble + _ = buffer.EncodeVarint(fieldKey | proto.WireFixed64) + _ = buffer.EncodeFixed64(math.Float64bits(val)) + case bool: + typeUrl = primitiveTypeURLPrefixBool + _ = buffer.EncodeVarint(fieldKey | proto.WireVarint) + switch val { + case true: + _ = buffer.EncodeVarint(1) + case false: + _ = buffer.EncodeVarint(0) + } + case []byte: + typeUrl = primitiveTypeURLPrefixBytes + _ = buffer.EncodeVarint(fieldKey | proto.WireBytes) + if err := buffer.EncodeRawBytes(val); err != nil { + return nil, err + } + default: + return nil, ErrNotMarshalled + } + return &any.Any{ + TypeUrl: typeUrl, + Value: buffer.Bytes(), + }, nil +} + +// UnmarshalPrimitive decodes a CloudState Any proto message +// into its primitive value. +func UnmarshalPrimitive(any *any.Any) (interface{}, error) { + buffer := proto.NewBuffer(any.GetValue()) + if any.GetTypeUrl() == primitiveTypeURLPrefixInt32 { + _, err := buffer.DecodeVarint() + if err != nil { + return nil, ErrNotUnmarshalled + } + value, err := buffer.DecodeVarint() + if err != nil { + return nil, ErrNotUnmarshalled + } + return int32(value), nil + } + if any.GetTypeUrl() == primitiveTypeURLPrefixInt64 { + _, err := buffer.DecodeVarint() + if err != nil { + return nil, ErrNotUnmarshalled + } + value, err := buffer.DecodeVarint() + if err != nil { + return nil, ErrNotUnmarshalled + } + return int64(value), nil + } + if any.GetTypeUrl() == primitiveTypeURLPrefixString { + _, err := buffer.DecodeVarint() + if err != nil { + return nil, ErrNotUnmarshalled + } + value, err := buffer.DecodeStringBytes() + if err != nil { + return nil, ErrNotUnmarshalled + } + return value, nil + } + if any.GetTypeUrl() == primitiveTypeURLPrefixFloat { + _, err := buffer.DecodeVarint() + if err != nil { + return nil, ErrNotUnmarshalled + } + value, err := buffer.DecodeFixed32() + if err != nil { + return nil, ErrNotUnmarshalled + } + return math.Float32frombits(uint32(value)), nil + } + if any.GetTypeUrl() == primitiveTypeURLPrefixDouble { + _, err := buffer.DecodeVarint() + if err != nil { + return nil, ErrNotUnmarshalled + } + value, err := buffer.DecodeFixed64() + if err != nil { + return nil, ErrNotUnmarshalled + } + return math.Float64frombits(value), nil + } + if any.GetTypeUrl() == primitiveTypeURLPrefixBool { + _, err := buffer.DecodeVarint() + if err != nil { + return nil, ErrNotUnmarshalled + } + value, err := buffer.DecodeVarint() + if err != nil { + return nil, ErrNotUnmarshalled + } + return value == 1, nil + } + if any.GetTypeUrl() == primitiveTypeURLPrefixBytes { + _, err := buffer.DecodeVarint() + if err != nil { + return nil, ErrNotUnmarshalled + } + value, err := buffer.DecodeRawBytes(true) + if err != nil { + return nil, ErrNotUnmarshalled + } + return value, nil + } + return nil, nil +} diff --git a/cloudstate/encoding/any_primitive_test.go b/cloudstate/encoding/any_primitive_test.go new file mode 100644 index 0000000..9b97298 --- /dev/null +++ b/cloudstate/encoding/any_primitive_test.go @@ -0,0 +1,163 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package encoding + +import ( + "bytes" + "fmt" + "github.com/golang/protobuf/ptypes/any" + "testing" +) + +type a struct { + B string `json:"b"` + C int32 `json:"c"` +} + +type aDefault struct { +} + +var tests = []struct { + name string + value interface{} + typeURL string + shouldFail bool +}{ + {primitiveTypeURLPrefixInt32, uint32(28), primitiveTypeURLPrefixInt32, true}, + {primitiveTypeURLPrefixInt32 + "_defaultValue", uint32(0), primitiveTypeURLPrefixInt32, true}, + {primitiveTypeURLPrefixInt32, int32(29), primitiveTypeURLPrefixInt32, false}, + {primitiveTypeURLPrefixInt32 + "_defaultValue", int32(0), primitiveTypeURLPrefixInt32, false}, + {primitiveTypeURLPrefixInt64, int64(29), primitiveTypeURLPrefixInt64, false}, + {primitiveTypeURLPrefixInt64 + "_defaultValue", int64(0), primitiveTypeURLPrefixInt64, false}, + {primitiveTypeURLPrefixFloat, float32(2.9), primitiveTypeURLPrefixFloat, false}, + {primitiveTypeURLPrefixFloat + "_defaultValue", float32(2.9), primitiveTypeURLPrefixFloat, false}, + {primitiveTypeURLPrefixDouble, float64(2.9), primitiveTypeURLPrefixDouble, false}, + {primitiveTypeURLPrefixDouble + "_defaultValue", float64(0), primitiveTypeURLPrefixDouble, false}, + {primitiveTypeURLPrefixString, "29", primitiveTypeURLPrefixString, false}, + {primitiveTypeURLPrefixString + "_defaultValue", "", primitiveTypeURLPrefixString, false}, + {primitiveTypeURLPrefixBool + "_true", true, primitiveTypeURLPrefixBool, false}, + {primitiveTypeURLPrefixBool + "_false", false, primitiveTypeURLPrefixBool, false}, + {primitiveTypeURLPrefixBool + "_defaultValue", false, primitiveTypeURLPrefixBool, false}, + {primitiveTypeURLPrefixBytes, make([]byte, 29), primitiveTypeURLPrefixBytes, false}, + {primitiveTypeURLPrefixBytes + "_defaultValue", make([]byte, 0), primitiveTypeURLPrefixBytes, false}, +} + +func TestMarshallerPrimitives(t *testing.T) { + for _, test := range tests { + tc := test + t.Run(fmt.Sprintf("%v", tc.name), func(t *testing.T) { + any0, err := MarshalPrimitive(tc.value) + hasErr := err != nil + if hasErr && !tc.shouldFail { + t.Errorf("err: %v, got: %+v, expected: %+v", err, any0, tc) + return + } else if !hasErr && tc.shouldFail { + t.Errorf("err: %v, got: %+v, expected: %+v", err, any0, tc) + return + } + failed := any0.GetTypeUrl() != tc.typeURL + if failed && !tc.shouldFail { + t.Errorf("err: %v, got: %+v, expected: %+v", err, any0, tc) + } else if !failed && tc.shouldFail { + t.Errorf("err: %v, got: %+v, expected: %+v", err, any0, tc) + } + }) + } +} + +func TestMarshalUnmarshalPrimitive(t *testing.T) { + for _, test := range tests { + if test.shouldFail { + continue + } + tc := test + t.Run(fmt.Sprintf("%v", tc.name), func(t *testing.T) { + a, err := MarshalPrimitive(tc.value) + if err != nil { + t.Error(err) + } + u, err := UnmarshalPrimitive(a) + if err != nil { + t.Error(err) + } + switch ut := u.(type) { + case []byte: + byt := tc.value.([]byte) + if bytes.Compare(byt, ut) != 0 { + t.Errorf("err: %v. got: %+v, expected: %+v", err, u, tc.value) + } + default: + if tc.value != u { + t.Errorf("err: %v. got: %+v, expected: %+v", err, u, tc.value) + } + } + }) + } +} + +func BenchmarkMarshallerPrimitives(b *testing.B) { + var any0 *any.Any + for _, i := range tests { + tc := i + if !tc.shouldFail { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + var any1 *any.Any + for i := 0; i < b.N; i++ { + any1, _ = MarshalPrimitive(tc.value) + } + any0 = any1 //prevent the call optimized away + }) + } + } + _ = any0 == nil //use any0 +} + +func BenchmarkMarshalUnmarshal(b *testing.B) { + var any0 *any.Any + for _, i := range tests { + tc := i + if !tc.shouldFail { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + var any1 *any.Any + for i := 0; i < b.N; i++ { + a, err := MarshalPrimitive(tc.value) + if err != nil { + b.Error(err) + } + u, err := UnmarshalPrimitive(a) + if err != nil { + b.Error(err) + } + switch ut := u.(type) { + case []byte: + byt := tc.value.([]byte) + if bytes.Compare(byt, ut) != 0 { + b.Errorf("err: %v. got: %+v, expected: %+v", err, u, tc.value) + } + default: + if tc.value != u { + b.Errorf("err: %v. got: %+v, expected: %+v", err, u, tc.value) + } + } + } + any0 = any1 //prevent the call optimized away + }) + } + } + _ = any0 == nil //use any0 +} diff --git a/cloudstate/entity_key.pb.go b/cloudstate/entity_key.pb.go new file mode 100644 index 0000000..b0094a9 --- /dev/null +++ b/cloudstate/entity_key.pb.go @@ -0,0 +1,52 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: cloudstate/entity_key.proto + +package cloudstate + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + descriptor "github.com/golang/protobuf/protoc-gen-go/descriptor" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +var E_EntityKey = &proto.ExtensionDesc{ + ExtendedType: (*descriptor.FieldOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50002, + Name: "cloudstate.entity_key", + Tag: "varint,50002,opt,name=entity_key", + Filename: "cloudstate/entity_key.proto", +} + +func init() { + proto.RegisterExtension(E_EntityKey) +} + +func init() { proto.RegisterFile("cloudstate/entity_key.proto", fileDescriptor_7bcabc3af9eb79b9) } + +var fileDescriptor_7bcabc3af9eb79b9 = []byte{ + // 174 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4e, 0xce, 0xc9, 0x2f, + 0x4d, 0x29, 0x2e, 0x49, 0x2c, 0x49, 0xd5, 0x4f, 0xcd, 0x2b, 0xc9, 0x2c, 0xa9, 0x8c, 0xcf, 0x4e, + 0xad, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x48, 0x4a, 0x29, 0xa4, 0xe7, 0xe7, + 0xa7, 0xe7, 0xa4, 0xea, 0x83, 0x65, 0x92, 0x4a, 0xd3, 0xf4, 0x53, 0x52, 0x8b, 0x93, 0x8b, 0x32, + 0x0b, 0x4a, 0xf2, 0x8b, 0x20, 0xaa, 0xad, 0xec, 0xb8, 0xb8, 0x10, 0x26, 0x08, 0xc9, 0xea, 0x41, + 0x34, 0xe8, 0xc1, 0x34, 0xe8, 0xb9, 0x65, 0xa6, 0xe6, 0xa4, 0xf8, 0x17, 0x94, 0x64, 0xe6, 0xe7, + 0x15, 0x4b, 0x5c, 0x6a, 0x63, 0x56, 0x60, 0xd4, 0xe0, 0x08, 0xe2, 0x84, 0x68, 0xf1, 0x4e, 0xad, + 0x74, 0xf2, 0xe2, 0xe2, 0xcd, 0xcc, 0xd7, 0x43, 0x58, 0x19, 0x65, 0x99, 0x9e, 0x59, 0x92, 0x51, + 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x8f, 0x10, 0xce, 0xcc, 0xd7, 0x4f, 0xcf, 0xd7, 0x2d, 0x2e, + 0x2d, 0x28, 0xc8, 0x2f, 0x2a, 0x41, 0x12, 0xd7, 0xb7, 0x46, 0xb0, 0x93, 0xd8, 0xc0, 0xb6, 0x1a, + 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xd9, 0xd8, 0x72, 0x17, 0xdf, 0x00, 0x00, 0x00, +} diff --git a/cloudstate/event.go b/cloudstate/event.go new file mode 100644 index 0000000..24cef4c --- /dev/null +++ b/cloudstate/event.go @@ -0,0 +1,96 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudstate + +import "fmt" + +type OnNext func(event interface{}) error +type OnErr func(err error) +type Subscription struct { + OnNext + OnErr + active bool +} + +func (s *Subscription) Unsubscribe() { + s.active = false +} + +type EventEmitter interface { + Emit(event interface{}) + Subscribe(subs *Subscription) *Subscription + Events() []interface{} + Clear() +} + +func NewEmitter() *eventEmitter { + return &eventEmitter{ + events: make([]interface{}, 0), + subscriptions: make([]*Subscription, 0), + } +} + +type eventEmitter struct { + events []interface{} + subscriptions []*Subscription +} + +// Emit will immediately invoke the associated event handler for that event. +// This both validates that the event can be applied to the current state, as well as +// updates the state so that subsequent processing in the command handler can use it. +func (e *eventEmitter) Emit(event interface{}) { + for _, subs := range e.subscriptions { + if !subs.active { + continue + } + err := subs.OnNext(event) + if r := recover(); r != nil { + subs.OnErr(fmt.Errorf("panicked with: %v", r)) + continue + } + if err != nil && subs.OnErr != nil { + subs.OnErr(err) + // TODO: we have no context here to fail to the proxy + } + } + e.events = append(e.events, event) +} + +func (e *eventEmitter) Events() []interface{} { + return e.events +} + +func (e *eventEmitter) Subscribe(subs *Subscription) *Subscription { + subs.active = true + e.subscriptions = append(e.subscriptions, subs) + return subs +} + +func (e *eventEmitter) Clear() { + e.events = make([]interface{}, 0) +} + +type EventHandler interface { + HandleEvent(event interface{}) (handled bool, err error) +} + +type Snapshotter interface { + Snapshot() (snapshot interface{}, err error) +} + +type SnapshotHandler interface { + HandleSnapshot(snapshot interface{}) (handled bool, err error) +} diff --git a/cloudstate/event_test.go b/cloudstate/event_test.go new file mode 100644 index 0000000..bec88db --- /dev/null +++ b/cloudstate/event_test.go @@ -0,0 +1,115 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudstate + +import ( + "fmt" + "sync/atomic" + "testing" +) + +type AnEntity struct { + EventEmitter +} + +func TestMultipleSubscribers(t *testing.T) { + e := AnEntity{EventEmitter: NewEmitter()} + n := int64(0) + e.Subscribe(&Subscription{ + OnNext: func(event interface{}) error { + atomic.AddInt64(&n, 1) + return nil + }, + }) + e.Emit(1) + if n != 1 { + t.Fail() + } + e.Emit(1) + if n != 2 { + t.Fail() + } + e.Subscribe(&Subscription{ + OnNext: func(event interface{}) error { + atomic.AddInt64(&n, 1) + return nil + }, + }) + e.Emit(1) + if n != 4 { + t.Fail() + } +} + +func TestUnsubscribe(t *testing.T) { + e := AnEntity{EventEmitter: NewEmitter()} + n := int64(0) + sub1 := &Subscription{ + OnNext: func(event interface{}) error { + atomic.AddInt64(&n, 1) + return nil + }, + } + e.Subscribe(sub1) + e.Emit(1) + if n != 1 { + t.Fail() + } + e.Emit(1) + if n != 2 { + t.Fail() + } + e.Subscribe(&Subscription{ + OnNext: func(event interface{}) error { + atomic.AddInt64(&n, 1) + return nil + }, + }) + sub1.Unsubscribe() + e.Emit(1) + if n != 3 { + t.Fail() + } +} + +func TestEventEmitter(t *testing.T) { + e := AnEntity{EventEmitter: NewEmitter()} + s := make([]string, 0) + ee := fmt.Errorf("int types are no supported") + e.Subscribe(&Subscription{ + OnNext: func(event interface{}) error { + switch v := event.(type) { + case int: + return ee + case string: + s = append(s, v) + } + return nil + }, + OnErr: func(err error) { + if err != ee { + t.Errorf("received unexpected error: %v", err) + } + }, + }) + // emit something that triggers an error we'd expect to happen + e.Emit(1) + // emit a string that gets to the list + e.Emit("john") + if len(s) != 1 || s[0] != "john" { + t.Errorf("john was not in the list: %+v", s) + } +} diff --git a/cloudstate/eventsourced.go b/cloudstate/eventsourced.go new file mode 100644 index 0000000..48b0ecc --- /dev/null +++ b/cloudstate/eventsourced.go @@ -0,0 +1,563 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudstate + +import ( + "context" + "errors" + "fmt" + "github.com/cloudstateio/go-support/cloudstate/encoding" + "github.com/cloudstateio/go-support/cloudstate/protocol" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes/any" + "io" + "net/url" + "reflect" + "strings" + "sync" +) + +// Entity +type Entity interface { + EntityInitializer +} + +// An EntityInitializer knows how to initialize an Entity +type EntityInitializer interface { + New() interface{} +} + +const snapshotEveryDefault = 100 + +// EventSourcedEntity captures an Entity, its ServiceName and PersistenceID. +// It is used to be registered as an event sourced entity on a CloudState instance. +type EventSourcedEntity struct { + // Entity is a nil or Zero-Initialized reference + // to the entity to be event sourced. It has to + // implement the EntityInitializer interface + // so that CloudState can create new entity instances. + Entity Entity + // ServiceName is used to… + // Setting it is optional. + ServiceName string + // PersistenceID is used to namespace events in the journal, useful for + // when you share the same database between multiple entities. It defaults to + // the simple name for the entity type. + // It’s good practice to select one explicitly, this means your database + // isn’t depend on type names in your code. + // Setting it is optional. + PersistenceID string + // The snapshotEvery parameter controls how often snapshots are taken, + // so that the entity doesn't need to be recovered from the whole journal + // each time it’s loaded. If left unset, it defaults to 100. + // Setting it to a negative number will result in snapshots never being taken. + SnapshotEvery int64 + + // internal + entityName string + registerOnce sync.Once +} + +// initZeroValue get its Entity type and Zero-Value it to +// something we can use as an initializer. +func (e *EventSourcedEntity) initZeroValue() error { + if reflect.ValueOf(e.Entity).IsNil() { + t := reflect.TypeOf(e.Entity) + if t.Kind() == reflect.Ptr { // TODO: how deep can that go? + t = t.Elem() + } + value := reflect.New(t).Interface() + if ei, ok := value.(EntityInitializer); ok { + e.Entity = ei + } else { + return errors.New("the Entity does not implement EntityInitializer") + } + e.entityName = t.Name() + e.SnapshotEvery = snapshotEveryDefault + } + return nil +} + +// The EntityInstance represents a concrete instance of +// a event sourced entity +type EntityInstance struct { + // Instance is an instance of the EventSourcedEntity.Entity + Instance interface{} + // EventSourcedEntity describes the instance + EventSourcedEntity *EventSourcedEntity + + eventSequence int64 +} + +func (e *EntityInstance) shouldSnapshot() bool { + return e.eventSequence >= e.EventSourcedEntity.SnapshotEvery +} + +func (e *EntityInstance) resetSnapshotEvery() { + e.eventSequence = 0 +} + +// A EntityInstanceContext represents a event sourced entity together with its +// associated service. +// Commands are dispatched through this context. +type EntityInstanceContext struct { // TODO: EntityInstanceContext might be actually a EntityInstance + // EntityInstance is the entity instance of this context + EntityInstance *EntityInstance + // active indicates if this context is active + active bool // TODO: inactivate a context in case of errors +} + +// ServiceName returns the contexts service name. +func (c EntityInstanceContext) ServiceName() string { + return c.EntityInstance.EventSourcedEntity.ServiceName +} + +// EventSourcedServer is the implementation of the EventSourcedServer server API for EventSourced service. +type EventSourcedServer struct { + // entities are indexed by their service name + entities map[string]*EventSourcedEntity + // contexts are entity instance contexts indexed by their entity ids + contexts map[string]*EntityInstanceContext + // cmdMethodCache is the command handler method cache + cmdMethodCache map[string]reflect.Method +} + +// newEventSourcedServer returns an initialized EventSourcedServer +func newEventSourcedServer() *EventSourcedServer { + return &EventSourcedServer{ + entities: make(map[string]*EventSourcedEntity), + contexts: make(map[string]*EntityInstanceContext), + cmdMethodCache: make(map[string]reflect.Method), + } +} + +func (esh *EventSourcedServer) registerEntity(ese *EventSourcedEntity) error { + esh.entities[ese.ServiceName] = ese + return nil +} + +// Handle +// +// The stream. One stream will be established per active entity. +// Once established, the first message sent will be Init, which contains the entity ID, and, +// if the entity has previously persisted a snapshot, it will contain that snapshot. It will +// then send zero to many event messages, one for each event previously persisted. The entity +// is expected to apply these to its state in a deterministic fashion. Once all the events +// are sent, one to many commands are sent, with new commands being sent as new requests for +// the entity come in. The entity is expected to reply to each command with exactly one reply +// message. The entity should reply in order, and any events that the entity requests to be +// persisted the entity should handle itself, applying them to its own state, as if they had +// arrived as events when the event stream was being replayed on load. +func (esh *EventSourcedServer) Handle(stream protocol.EventSourced_HandleServer) error { + var entityId string + var failed error + for { + if failed != nil { + return failed + } + msg, recvErr := stream.Recv() + if recvErr == io.EOF { + return nil + } + if recvErr != nil { + return recvErr + } + if cmd := msg.GetCommand(); cmd != nil { + if err := esh.handleCommand(cmd, stream); err != nil { + // TODO: in general, what happens with the stream here if an error happens? + failed = handleFailure(err, stream, cmd.GetId()) + } + continue + } + if event := msg.GetEvent(); event != nil { + // TODO spec: Why does command carry the entityId and an event not? + if err := esh.handleEvent(entityId, event); err != nil { + failed = handleFailure(err, stream, 0) + } + continue + } + if init := msg.GetInit(); init != nil { + if err := esh.handleInit(init, stream); err != nil { + failed = handleFailure(err, stream, 0) + } + entityId = init.GetEntityId() + continue + } + } +} + +func (esh *EventSourcedServer) handleInit(init *protocol.EventSourcedInit, server protocol.EventSourced_HandleServer) error { + eid := init.GetEntityId() + if _, present := esh.contexts[eid]; present { + return NewFailureError("unable to server.Send") + } + entity := esh.entities[init.GetServiceName()] + if initializer, ok := entity.Entity.(EntityInitializer); ok { + instance := initializer.New() + esh.contexts[eid] = &EntityInstanceContext{ + EntityInstance: &EntityInstance{ + Instance: instance, + EventSourcedEntity: entity, + }, + active: true, + } + } else { + return fmt.Errorf("unable to handle init entity.Entity does not implement EntityInitializer") + } + + if err := esh.handleInitSnapshot(init); err != nil { + return NewFailureError("unable to server.Send. %w", err) + } + esh.subscribeEvents(esh.contexts[eid].EntityInstance) + return nil +} + +func (esh *EventSourcedServer) handleInitSnapshot(init *protocol.EventSourcedInit) error { + if init.Snapshot == nil { + return nil + } + entityId := init.GetEntityId() + if snapshotHandler, ok := esh.contexts[entityId].EntityInstance.Instance.(SnapshotHandler); ok { + snapshot, err := esh.unmarshalSnapshot(init) + if snapshot == nil || err != nil { + return NewFailureError("handling snapshot failed with: %v", err) + } + handled, err := snapshotHandler.HandleSnapshot(snapshot) + if err != nil { + return NewFailureError("handling snapshot failed with: %v", err) + } + if handled { + esh.contexts[entityId].EntityInstance.eventSequence = init.GetSnapshot().SnapshotSequence + } + return nil + } + return nil +} + +func (EventSourcedServer) unmarshalSnapshot(init *protocol.EventSourcedInit) (interface{}, error) { + // see: https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/well-known-types/any#typeurl + typeUrl := init.Snapshot.Snapshot.GetTypeUrl() + if !strings.Contains(typeUrl, "://") { + typeUrl = "https://" + typeUrl + } + typeURL, err := url.Parse(typeUrl) + if err != nil { + return nil, err + } + switch typeURL.Host { + case encoding.PrimitiveTypeURLPrefix: + snapshot, err := encoding.UnmarshalPrimitive(init.Snapshot.Snapshot) + if err != nil { + return nil, fmt.Errorf("unmarshalling snapshot failed with: %v", err) + } + return snapshot, nil + case protoAnyBase: + msgName := strings.TrimPrefix(init.Snapshot.Snapshot.GetTypeUrl(), protoAnyBase+"/") // TODO: this might be something else than a proto message + messageType := proto.MessageType(msgName) + if messageType.Kind() == reflect.Ptr { + if message, ok := reflect.New(messageType.Elem()).Interface().(proto.Message); ok { + err := proto.Unmarshal(init.Snapshot.Snapshot.Value, message) + if err != nil { + return nil, fmt.Errorf("unmarshalling snapshot failed with: %v", err) + } + return message, nil + } + } + } + return nil, fmt.Errorf("unmarshalling snapshot failed with: no snapshot unmarshaller found for: %v", typeURL.String()) +} + +func (esh *EventSourcedServer) subscribeEvents(instance *EntityInstance) { + if emitter, ok := instance.Instance.(EventEmitter); ok { + emitter.Subscribe(&Subscription{ + OnNext: func(event interface{}) error { + err := esh.applyEvent(instance, event) + if err == nil { + instance.eventSequence++ + } + return err + }, + OnErr: func(err error) { + }, // TODO: investigate what to report to the proxy + }) + } +} + +func (esh *EventSourcedServer) handleEvent(entityId string, event *protocol.EventSourcedEvent) error { + if entityId == "" { + return NewFailureError("no entityId was found from a previous init message for event sequence: %v", event.Sequence) + } + entityContext := esh.contexts[entityId] + if entityContext == nil { + return NewFailureError("no entity with entityId registered: %v", entityId) + } + err := esh.handleEvents(entityContext.EntityInstance, event) + if err != nil { + return NewFailureError("handle event failed: %v", err) + } + return err +} + +// handleCommand handles a command received from the CloudState proxy. +// +// TODO: remove these following lines of comment +// "Unary RPCs where the client sends a single request to the server and +// gets a single response back, just like a normal function call." are supported right now. +// +// to handle a command we need +// - the entity id, which identifies the entity (its instance) uniquely(?) for this user function instance +// - the service name, like "com.example.shoppingcart.ShoppingCart" +// - a command id +// - a command name, which is one of the gRPC service rpcs defined by this entities service +// - the command payload, which is the message sent for the command as a protobuf.Any blob +// - a streamed flag, (TODO: for what?) +// +// together, these properties allow to call a method of the entities registered service and +// return its response as a reply to the CloudState proxy. +// +// Events: +// Beside calling the service method, we have to collect "events" the service might emit. +// These events afterwards have to be handled by a EventHandler to update the state of the +// entity. The CloudState proxy can re-play these events at any time +func (esh *EventSourcedServer) handleCommand(cmd *protocol.Command, server protocol.EventSourced_HandleServer) error { + // method to call + method, err := esh.methodToCall(cmd) + if err != nil { + return NewProtocolFailure(protocol.Failure{ + CommandId: cmd.GetId(), + Description: err.Error(), + }) + } + entityContext := esh.contexts[cmd.GetEntityId()] + // build the input arguments for the method we're about to call + inputs, err := esh.buildInputs(entityContext, method, cmd, server.Context()) + if err != nil { + return NewProtocolFailure(protocol.Failure{ + CommandId: cmd.GetId(), + Description: err.Error(), + }) + } + // call it + called := method.Func.Call(inputs) + // The gRPC implementation returns the rpc return method + // and an error as a second return value. + errReturned := called[1] + if errReturned.CanInterface() && errReturned.Interface() != nil && errReturned.Type().Name() == "error" { + // TCK says: TODO Expects entity.Failure, but gets lientAction.Action.Failure(Failure(commandId, msg))) + return NewProtocolFailure(protocol.Failure{ + CommandId: cmd.GetId(), + Description: errReturned.Interface().(error).Error(), + }) + } + // the reply + callReply, err := marshalAny(called[0].Interface()) + if err != nil { // this should never happen + return NewProtocolFailure(protocol.Failure{ + CommandId: cmd.GetId(), + Description: fmt.Errorf("called return value at index 0 is no proto.Message. %w", err).Error(), + }) + } + // emitted events + events, err := marshalEventsAny(entityContext) + if err != nil { + return NewProtocolFailure(protocol.Failure{ + CommandId: cmd.GetId(), + Description: err.Error(), + }) + } + // snapshot + snapshot, err := esh.handleSnapshots(entityContext) + if err != nil { + return NewProtocolFailure(protocol.Failure{ + CommandId: cmd.GetId(), + Description: err.Error(), + }) + } + return sendEventSourcedReply(&protocol.EventSourcedReply{ + CommandId: cmd.GetId(), + ClientAction: &protocol.ClientAction{ + Action: &protocol.ClientAction_Reply{ + Reply: &protocol.Reply{ + Payload: callReply, + }, + }, + }, + Events: events, + Snapshot: snapshot, + }, server) +} + +func (*EventSourcedServer) buildInputs(entityContext *EntityInstanceContext, method reflect.Method, cmd *protocol.Command, ctx context.Context) ([]reflect.Value, error) { + inputs := make([]reflect.Value, method.Type.NumIn()) + inputs[0] = reflect.ValueOf(entityContext.EntityInstance.Instance) + inputs[1] = reflect.ValueOf(ctx) + // create a zero-value for the type of the message we call the method with + arg1 := method.Type.In(2) + ptr := false + for arg1.Kind() == reflect.Ptr { + ptr = true + arg1 = arg1.Elem() + } + var msg proto.Message + if ptr { + msg = reflect.New(arg1).Interface().(proto.Message) + } else { + msg = reflect.Zero(arg1).Interface().(proto.Message) + } + if err := proto.Unmarshal(cmd.GetPayload().GetValue(), msg); err != nil { + return nil, fmt.Errorf("failed to unmarshal: %w", err) + } + inputs[2] = reflect.ValueOf(msg) + return inputs, nil +} + +func (esh *EventSourcedServer) methodToCall(cmd *protocol.Command) (reflect.Method, error) { + entityContext := esh.contexts[cmd.GetEntityId()] + cacheKey := entityContext.ServiceName() + cmd.Name + method, hit := esh.cmdMethodCache[cacheKey] + // as measured this cache saves us about 75% of a call + // to be prepared with 4.4µs vs. 17.6µs where a typical + // call by reflection like GetCart() with Func.Call() + // takes ~10µs and to get return values processed somewhere 0.7µs. + if !hit { + entityValue := reflect.ValueOf(entityContext.EntityInstance.Instance) + // entities implement the proxied grpc service + // we try to find the method we're called by name with the + // received command. + methodByName := entityValue.MethodByName(cmd.Name) + if !methodByName.IsValid() { + entity := esh.entities[entityContext.ServiceName()] + return reflect.Method{}, fmt.Errorf("no method named: %s found for: %v", cmd.Name, entity) + } + // gRPC services are unary rpc methods, always. + // They have one message in and one message out. + if err := checkUnary(methodByName); err != nil { + return reflect.Method{}, err + } + // The first argument in the gRPC implementation + // is always a context.Context. + methodArg0Type := methodByName.Type().In(0) + contextType := reflect.TypeOf(context.Background()) + if !contextType.Implements(methodArg0Type) { + return reflect.Method{}, fmt.Errorf( + "first argument for method: %s is not of type: %s", + methodByName.String(), contextType.Name(), + ) + } + // we'll find one for sure as we found one on the entityValue + method, _ = reflect.TypeOf(entityContext.EntityInstance.Instance).MethodByName(cmd.Name) + esh.cmdMethodCache[cacheKey] = method + } + return method, nil +} + +func (*EventSourcedServer) handleSnapshots(entityContext *EntityInstanceContext) (*any.Any, error) { + if !entityContext.EntityInstance.shouldSnapshot() { + return nil, nil + } + if snapshotter, canSnapshot := entityContext.EntityInstance.Instance.(Snapshotter); canSnapshot { + snap, err := snapshotter.Snapshot() + if err != nil { + return nil, fmt.Errorf("getting a snapshot has failed: %v. %w", err, ErrFailure) + } + // TODO: we expect a proto.Message but should support other formats + snapshot, err := marshalAny(snap) + if err != nil { + return nil, err + } + entityContext.EntityInstance.resetSnapshotEvery() + return snapshot, nil + } else { + // TODO: every entity should implement snapshotting, right? + } + return nil, nil +} + +func checkUnary(methodByName reflect.Value) error { + if methodByName.Type().NumIn() != 2 { + return NewFailureError("method: %s is no unary method", methodByName.String()) + } + return nil +} + +// applyEvent applies an event to a local entity +func (esh EventSourcedServer) applyEvent(entityInstance *EntityInstance, event interface{}) error { + payload, err := marshalAny(event) + if err != nil { + return err + } + return esh.handleEvents(entityInstance, &protocol.EventSourcedEvent{Payload: payload}) +} + +// handleEvents handles a list of events encoded as protobuf Any messages. +// +// Event sourced entities persist events and snapshots, and these need to be +// serialized when persisted. The most straight forward way to persist events +// and snapshots is to use protobufs. CloudState will automatically detect if +// an emitted event is a protobuf, and serialize it as such. For other +// serialization options, including JSON, see Serialization. +func (EventSourcedServer) handleEvents(entityInstance *EntityInstance, events ...*protocol.EventSourcedEvent) error { + eventHandler, implementsEventHandler := entityInstance.Instance.(EventHandler) + for _, event := range events { + // TODO: here's the point where events can be protobufs, serialized as json or other formats + msgName := strings.TrimPrefix(event.Payload.GetTypeUrl(), protoAnyBase+"/") + messageType := proto.MessageType(msgName) + + if messageType.Kind() == reflect.Ptr { + // get a zero-ed message of this type + if message, ok := reflect.New(messageType.Elem()).Interface().(proto.Message); ok { + // and marshal onto it what we got as an any.Any onto it + err := proto.Unmarshal(event.Payload.Value, message) + if err != nil { + return fmt.Errorf("%s, %w", err, ErrMarshal) + } else { + // we're ready to handle the proto message + // and we might have a handler + handled := false + if implementsEventHandler { + handled, err = eventHandler.HandleEvent(message) + if err != nil { + return err // FIXME/TODO: is this correct? if we fail here, nothing is safe afterwards. + } + } + // if not, we try to find one + // currently we support a method that has one argument that equals + // to the type of the message received. + if !handled { + // find a concrete handling method + entityValue := reflect.ValueOf(entityInstance.Instance) + entityType := entityValue.Type() + for n := 0; n < entityType.NumMethod(); n++ { + method := entityType.Method(n) + // we expect one argument for now, the domain message + // the first argument is the receiver itself + if method.Func.Type().NumIn() == 2 { + argumentType := method.Func.Type().In(1) + if argumentType.AssignableTo(messageType) { + entityValue.MethodByName(method.Name).Call([]reflect.Value{reflect.ValueOf(message)}) + } + } else { + // we have not found a one-argument method matching the events type as an argument + // TODO: what to do here? we might support more variations of possible handlers we can detect + } + } + } + } + } + } // TODO: what do we do if we haven't handled the events? + } + return nil +} diff --git a/cloudstate/eventsourced_reply.go b/cloudstate/eventsourced_reply.go new file mode 100644 index 0000000..0d1efe0 --- /dev/null +++ b/cloudstate/eventsourced_reply.go @@ -0,0 +1,132 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudstate + +import ( + "errors" + "fmt" + "github.com/cloudstateio/go-support/cloudstate/protocol" +) + +var ErrSendFailure = errors.New("unable to send a failure message") +var ErrSend = errors.New("unable to send a message") +var ErrMarshal = errors.New("unable to marshal a message") + +var ErrFailure = errors.New("cloudstate failure") +var ErrClientActionFailure = errors.New("cloudstate client action failure") + +func NewFailureError(format string, a ...interface{}) error { + if len(a) != 0 { + return fmt.Errorf(fmt.Sprintf(format, a...)+". %w", ErrFailure) + } else { + return fmt.Errorf(format+". %w", ErrFailure) + } +} + +func NewClientActionFailureError(format string, a ...interface{}) error { + if len(a) != 0 { + return fmt.Errorf(fmt.Sprintf(format, a...)+". %w", ErrClientActionFailure) + } else { + return fmt.Errorf(format+". %w", ErrClientActionFailure) + } +} + +type ProtocolFailure struct { + protocol.Failure + err error +} + +func (f ProtocolFailure) Error() string { + return f.err.Error() +} + +func (f ProtocolFailure) Unwrap() error { + return f.err +} + +func NewProtocolFailure(failure protocol.Failure) error { + return ProtocolFailure{ + Failure: failure, + err: ErrFailure, + } +} + +// handleFailure checks if a CloudState failure or client action failure should +// be sent to the proxy, otherwise handleFailure returns the original failure +func handleFailure(failure error, server protocol.EventSourced_HandleServer, cmdId int64) error { + if errors.Is(failure, ErrFailure) { + // FIXME: why not getting the failure from the ProtocolFailure + // TCK says: Failure was not received, or not well-formed: Failure(Failure(0,cloudstate failure)) was not reply (CloudStateTCK.scala:339) + //return sendFailure(&protocol.Failure{Description: failure.Error()}, server) + return sendClientActionFailure(&protocol.Failure{ + CommandId: cmdId, + Description: failure.Error(), + }, server) + } + if errors.Is(failure, ErrClientActionFailure) { + return sendClientActionFailure(&protocol.Failure{ + CommandId: cmdId, + Description: failure.Error(), + }, server) + } + return failure +} + +// sendEventSourcedReply sends a given EventSourcedReply and if it fails, handles the error wrapping +func sendEventSourcedReply(reply *protocol.EventSourcedReply, server protocol.EventSourced_HandleServer) error { + err := server.Send(&protocol.EventSourcedStreamOut{ + Message: &protocol.EventSourcedStreamOut_Reply{ + Reply: reply, + }, + }) + if err != nil { + return fmt.Errorf("%s, %w", err, ErrSend) + } + return err +} + +// sendFailure sends a given EventSourcedReply and if it fails, handles the error wrapping +func sendFailure(failure *protocol.Failure, server protocol.EventSourced_HandleServer) error { + err := server.Send(&protocol.EventSourcedStreamOut{ + Message: &protocol.EventSourcedStreamOut_Failure{ + Failure: failure, + }, + }) + if err != nil { + err = fmt.Errorf("%s, %w", err, ErrSendFailure) + } + return err +} + +// sendClientActionFailure sends a given EventSourcedReply and if it fails, handles the error wrapping +func sendClientActionFailure(failure *protocol.Failure, server protocol.EventSourced_HandleServer) error { + err := server.Send(&protocol.EventSourcedStreamOut{ + Message: &protocol.EventSourcedStreamOut_Reply{ + Reply: &protocol.EventSourcedReply{ + CommandId: failure.CommandId, + ClientAction: &protocol.ClientAction{ + Action: &protocol.ClientAction_Failure{ + Failure: failure, + }, + }, + }, + }, + }) + if err != nil { + err = fmt.Errorf("%s, %w", err, ErrSendFailure) + } + return err +} diff --git a/cloudstate/eventsourced_test.go b/cloudstate/eventsourced_test.go new file mode 100644 index 0000000..5924169 --- /dev/null +++ b/cloudstate/eventsourced_test.go @@ -0,0 +1,320 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudstate + +import ( + "context" + "errors" + "fmt" + "github.com/cloudstateio/go-support/cloudstate/encoding" + "github.com/cloudstateio/go-support/cloudstate/protocol" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes/any" + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc" + "os" + "sync" + "testing" +) + +type TestEntity struct { + Value int64 + EventEmitter +} + +func (inc TestEntity) String() string { + return proto.CompactTextString(inc) +} + +func (inc TestEntity) ProtoMessage() { +} + +func (inc TestEntity) Reset() { +} + +func (te *TestEntity) Snapshot() (snapshot interface{}, err error) { + return encoding.MarshalPrimitive(te.Value) +} + +func (te *TestEntity) HandleSnapshot(snapshot interface{}) (handled bool, err error) { + switch v := snapshot.(type) { + case int64: + te.Value = v + } + return true, nil +} + +func (te *TestEntity) IncrementBy(n int64) (int64, error) { + te.Value += n + return te.Value, nil +} + +func (te *TestEntity) DecrementBy(n int64) (int64, error) { + te.Value -= n + return te.Value, nil +} + +// initialize value to <0 let us check whether an initCommand works +var testEntity = &TestEntity{ + Value: -1, + EventEmitter: NewEmitter(), +} + +func resetTestEntity() { + testEntity = &TestEntity{ + Value: -1, + EventEmitter: NewEmitter(), + } +} + +func (te *TestEntity) New() interface{} { + testEntity.Value = 0 + return testEntity +} + +// IncrementByCommand with value receiver +func (te TestEntity) IncrementByCommand(_ context.Context, ibc *IncrementByCommand) (*empty.Empty, error) { + te.Emit(&IncrementByEvent{ + Value: ibc.Amount, + }) + return &empty.Empty{}, nil +} + +// DecrementByCommand with pointer receiver +func (te *TestEntity) DecrementByCommand(_ context.Context, ibc *DecrementByCommand) (*empty.Empty, error) { + te.Emit(&DecrementByEvent{ + Value: ibc.Amount, + }) + return &empty.Empty{}, nil +} + +type IncrementByEvent struct { + Value int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` +} + +func (inc IncrementByEvent) String() string { + return proto.CompactTextString(inc) +} + +func (inc IncrementByEvent) ProtoMessage() { +} + +func (inc IncrementByEvent) Reset() { +} + +type DecrementByEvent struct { + Value int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` +} + +func (inc DecrementByEvent) String() string { + return proto.CompactTextString(inc) +} + +func (inc DecrementByEvent) ProtoMessage() { +} + +func (inc DecrementByEvent) Reset() { +} + +func (te *TestEntity) DecrementByEvent(d *DecrementByEvent) error { + _, err := te.DecrementBy(d.Value) + return err +} + +func (te *TestEntity) HandleEvent(event interface{}) (handled bool, err error) { + switch e := event.(type) { + case *IncrementByEvent: + _, err := te.IncrementBy(e.Value) + return true, err + default: + return false, nil + } +} + +type IncrementByCommand struct { + Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` +} + +func (inc IncrementByCommand) String() string { + return proto.CompactTextString(inc) +} + +func (inc IncrementByCommand) ProtoMessage() { +} + +func (inc IncrementByCommand) Reset() { +} + +type DecrementByCommand struct { + Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` +} + +func (inc DecrementByCommand) String() string { + return proto.CompactTextString(inc) +} + +func (inc DecrementByCommand) ProtoMessage() { +} + +func (inc DecrementByCommand) Reset() { +} + +// TestEventSourcedHandleServer is a grpc.ServerStream mock +type TestEventSourcedHandleServer struct { + grpc.ServerStream +} + +func (t TestEventSourcedHandleServer) Context() context.Context { + return context.Background() +} + +func (t TestEventSourcedHandleServer) Send(out *protocol.EventSourcedStreamOut) error { + return nil +} +func (t TestEventSourcedHandleServer) Recv() (*protocol.EventSourcedStreamIn, error) { + return nil, nil +} + +func newHandler(t *testing.T) *EventSourcedServer { + handler := newEventSourcedServer() + entity := EventSourcedEntity{ + Entity: (*TestEntity)(nil), + ServiceName: "TestEventSourcedServer-Service", + SnapshotEvery: 0, + registerOnce: sync.Once{}, + } + err := entity.initZeroValue() + if err != nil { + t.Errorf("%v", err) + } + err = handler.registerEntity(&entity) + if err != nil { + t.Errorf("%v", err) + } + return handler +} + +func initHandler(handler *EventSourcedServer, t *testing.T) { + err := handler.handleInit(&protocol.EventSourcedInit{ + ServiceName: "TestEventSourcedServer-Service", + EntityId: "entity-0", + }, nil) + if err != nil { + t.Errorf("%v", err) + t.Fail() + } +} + +func marshal(msg proto.Message, t *testing.T) ([]byte, error) { + cmd, err := proto.Marshal(msg) + if err != nil { + t.Errorf("%v", err) + } + return cmd, err +} + +func TestMain(m *testing.M) { + proto.RegisterType((*IncrementByEvent)(nil), "IncrementByEvent") + proto.RegisterType((*DecrementByEvent)(nil), "DecrementByEvent") + proto.RegisterType((*TestEntity)(nil), "TestEntity") + resetTestEntity() + defer resetTestEntity() + os.Exit(m.Run()) +} + +func TestErrSend(t *testing.T) { + err0 := ErrSendFailure + err1 := fmt.Errorf("on reply: %w", ErrSendFailure) + if !errors.Is(err1, err0) { + t.Fatalf("err1 is no err0 but should") + } +} +func TestSnapshot(t *testing.T) { + resetTestEntity() + handler := newHandler(t) + if testEntity.Value >= 0 { + t.Fatalf("testEntity.Value should be <0 but was not: %+v", testEntity) + } + primitive, err := encoding.MarshalPrimitive(int64(987)) + if err != nil { + t.Fatalf("%v", err) + } + err = handler.handleInit(&protocol.EventSourcedInit{ + ServiceName: "TestEventSourcedServer-Service", + EntityId: "entity-0", + Snapshot: &protocol.EventSourcedSnapshot{ + SnapshotSequence: 0, + Snapshot: primitive, + }, + }, nil) + if err != nil { + t.Fatalf("%v", err) + } + if testEntity.Value != 987 { + t.Fatalf("testEntity.Value should be 0 but was not: %+v", testEntity) + } +} + +func TestEventSourcedServerHandlesCommandAndEvents(t *testing.T) { + resetTestEntity() + handler := newHandler(t) + if testEntity.Value >= 0 { + t.Fatalf("testEntity.Value should be <0 but was not: %+v", testEntity) + } + initHandler(handler, t) + if testEntity.Value != 0 { + t.Fatalf("testEntity.Value should be 0 but was not: %+v", testEntity) + } + incrementedTo := int64(7) + incrCmdValue, err := marshal(&IncrementByCommand{Amount: incrementedTo}, t) + incrCommand := protocol.Command{ + EntityId: "entity-0", + Id: 1, + Name: "IncrementByCommand", + Payload: &any.Any{ + TypeUrl: "type.googleapis.com/IncrementByCommand", + Value: incrCmdValue, + }, + } + err = handler.handleCommand(&incrCommand, TestEventSourcedHandleServer{}) + if err != nil { + t.Fatalf("%v", err) + } + if testEntity.Value != incrementedTo { + t.Fatalf("testEntity.Value: (%v) != incrementedTo: (%v)", testEntity.Value, incrementedTo) + } + + decrCmdValue, err := proto.Marshal(&DecrementByCommand{Amount: incrementedTo}) + if err != nil { + t.Fatalf("%v", err) + } + decrCommand := protocol.Command{ + EntityId: "entity-0", + Id: 1, + Name: "DecrementByCommand", + Payload: &any.Any{ + TypeUrl: "type.googleapis.com/DecrementByCommand", + Value: decrCmdValue, + }, + } + err = handler.handleCommand(&decrCommand, TestEventSourcedHandleServer{}) + if err != nil { + t.Fatalf("%v", err) + } + if testEntity.Value != 0 { + t.Fatalf("testEntity.Value != 0") + } +} diff --git a/cloudstate/marshal_proto.go b/cloudstate/marshal_proto.go new file mode 100644 index 0000000..5e87b7c --- /dev/null +++ b/cloudstate/marshal_proto.go @@ -0,0 +1,56 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudstate + +import ( + "fmt" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes/any" +) + +// marshalAny marshals a proto.Message to a any.Any value. +func marshalAny(pb interface{}) (*any.Any, error) { + // TODO: protobufs are expected here, but CloudState supports other formats + message, ok := pb.(proto.Message) + if !ok { + return nil, fmt.Errorf("got a non-proto message as protobuf: %v", pb) + } + bytes, err := proto.Marshal(message) + if err != nil { + return nil, fmt.Errorf("%s, %w", err, ErrMarshal) + } + return &any.Any{ + TypeUrl: fmt.Sprintf("%s/%s", protoAnyBase, proto.MessageName(message)), + Value: bytes, + }, nil +} + +// marshalEventsAny receives the events emitted through the handling of a command +// and marshals them to the event serialized form. +func marshalEventsAny(entityContext *EntityInstanceContext) ([]*any.Any, error) { + events := make([]*any.Any, 0) + if emitter, ok := entityContext.EntityInstance.Instance.(EventEmitter); ok { + for _, evt := range emitter.Events() { + event, err := marshalAny(evt) + if err != nil { + return nil, err + } + events = append(events, event) + } + emitter.Clear() + } + return events, nil +} diff --git a/cloudstate/marshal_proto_test.go b/cloudstate/marshal_proto_test.go new file mode 100644 index 0000000..2903054 --- /dev/null +++ b/cloudstate/marshal_proto_test.go @@ -0,0 +1,41 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudstate + +import ( + "fmt" + "github.com/golang/protobuf/proto" + "testing" +) + +func TestMarshalAnyProto(t *testing.T) { + event := IncrementByEvent{Value: 29} + any, err := marshalAny(&event) + if err != nil { + t.Fatalf("failed to marshalAny: %v", err) + } + expected := fmt.Sprintf("%s/%s", protoAnyBase, "IncrementByEvent") + if expected != any.GetTypeUrl() { + t.Fatalf("any.GetTypeUrl: %s is not: %s", any.GetTypeUrl(), expected) + } + event2 := &IncrementByEvent{} + if err := proto.Unmarshal(any.Value, event2); err != nil { + t.Fatalf("%v", err) + } + if event2.Value != event.Value { + t.Fatalf("event2.Value: %d != event.Value: %d", event2.Value, event.Value) + } +} diff --git a/cloudstate/protocol/entity.pb.go b/cloudstate/protocol/entity.pb.go new file mode 100644 index 0000000..0e4297f --- /dev/null +++ b/cloudstate/protocol/entity.pb.go @@ -0,0 +1,979 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: cloudstate/entity.proto + +package protocol + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + _ "github.com/golang/protobuf/protoc-gen-go/descriptor" + any "github.com/golang/protobuf/ptypes/any" + empty "github.com/golang/protobuf/ptypes/empty" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +// A reply to the sender. +type Reply struct { + // The reply payload + Payload *any.Any `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Reply) Reset() { *m = Reply{} } +func (m *Reply) String() string { return proto.CompactTextString(m) } +func (*Reply) ProtoMessage() {} +func (*Reply) Descriptor() ([]byte, []int) { + return fileDescriptor_a4a280e6d8a8fec2, []int{0} +} + +func (m *Reply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Reply.Unmarshal(m, b) +} +func (m *Reply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Reply.Marshal(b, m, deterministic) +} +func (m *Reply) XXX_Merge(src proto.Message) { + xxx_messageInfo_Reply.Merge(m, src) +} +func (m *Reply) XXX_Size() int { + return xxx_messageInfo_Reply.Size(m) +} +func (m *Reply) XXX_DiscardUnknown() { + xxx_messageInfo_Reply.DiscardUnknown(m) +} + +var xxx_messageInfo_Reply proto.InternalMessageInfo + +func (m *Reply) GetPayload() *any.Any { + if m != nil { + return m.Payload + } + return nil +} + +// Forwards handling of this request to another entity. +type Forward struct { + // The name of the service to forward to. + ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // The name of the command. + CommandName string `protobuf:"bytes,2,opt,name=command_name,json=commandName,proto3" json:"command_name,omitempty"` + // The payload. + Payload *any.Any `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Forward) Reset() { *m = Forward{} } +func (m *Forward) String() string { return proto.CompactTextString(m) } +func (*Forward) ProtoMessage() {} +func (*Forward) Descriptor() ([]byte, []int) { + return fileDescriptor_a4a280e6d8a8fec2, []int{1} +} + +func (m *Forward) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Forward.Unmarshal(m, b) +} +func (m *Forward) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Forward.Marshal(b, m, deterministic) +} +func (m *Forward) XXX_Merge(src proto.Message) { + xxx_messageInfo_Forward.Merge(m, src) +} +func (m *Forward) XXX_Size() int { + return xxx_messageInfo_Forward.Size(m) +} +func (m *Forward) XXX_DiscardUnknown() { + xxx_messageInfo_Forward.DiscardUnknown(m) +} + +var xxx_messageInfo_Forward proto.InternalMessageInfo + +func (m *Forward) GetServiceName() string { + if m != nil { + return m.ServiceName + } + return "" +} + +func (m *Forward) GetCommandName() string { + if m != nil { + return m.CommandName + } + return "" +} + +func (m *Forward) GetPayload() *any.Any { + if m != nil { + return m.Payload + } + return nil +} + +// An action for the client +type ClientAction struct { + // Types that are valid to be assigned to Action: + // *ClientAction_Reply + // *ClientAction_Forward + // *ClientAction_Failure + Action isClientAction_Action `protobuf_oneof:"action"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ClientAction) Reset() { *m = ClientAction{} } +func (m *ClientAction) String() string { return proto.CompactTextString(m) } +func (*ClientAction) ProtoMessage() {} +func (*ClientAction) Descriptor() ([]byte, []int) { + return fileDescriptor_a4a280e6d8a8fec2, []int{2} +} + +func (m *ClientAction) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ClientAction.Unmarshal(m, b) +} +func (m *ClientAction) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ClientAction.Marshal(b, m, deterministic) +} +func (m *ClientAction) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientAction.Merge(m, src) +} +func (m *ClientAction) XXX_Size() int { + return xxx_messageInfo_ClientAction.Size(m) +} +func (m *ClientAction) XXX_DiscardUnknown() { + xxx_messageInfo_ClientAction.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientAction proto.InternalMessageInfo + +type isClientAction_Action interface { + isClientAction_Action() +} + +type ClientAction_Reply struct { + Reply *Reply `protobuf:"bytes,1,opt,name=reply,proto3,oneof"` +} + +type ClientAction_Forward struct { + Forward *Forward `protobuf:"bytes,2,opt,name=forward,proto3,oneof"` +} + +type ClientAction_Failure struct { + Failure *Failure `protobuf:"bytes,3,opt,name=failure,proto3,oneof"` +} + +func (*ClientAction_Reply) isClientAction_Action() {} + +func (*ClientAction_Forward) isClientAction_Action() {} + +func (*ClientAction_Failure) isClientAction_Action() {} + +func (m *ClientAction) GetAction() isClientAction_Action { + if m != nil { + return m.Action + } + return nil +} + +func (m *ClientAction) GetReply() *Reply { + if x, ok := m.GetAction().(*ClientAction_Reply); ok { + return x.Reply + } + return nil +} + +func (m *ClientAction) GetForward() *Forward { + if x, ok := m.GetAction().(*ClientAction_Forward); ok { + return x.Forward + } + return nil +} + +func (m *ClientAction) GetFailure() *Failure { + if x, ok := m.GetAction().(*ClientAction_Failure); ok { + return x.Failure + } + return nil +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*ClientAction) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*ClientAction_Reply)(nil), + (*ClientAction_Forward)(nil), + (*ClientAction_Failure)(nil), + } +} + +// A side effect to be done after this command is handled. +type SideEffect struct { + // The name of the service to perform the side effect on. + ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // The name of the command. + CommandName string `protobuf:"bytes,2,opt,name=command_name,json=commandName,proto3" json:"command_name,omitempty"` + // The payload of the command. + Payload *any.Any `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` + // Whether this side effect should be performed synchronously, ie, before the reply is eventually + // sent, or not. + Synchronous bool `protobuf:"varint,4,opt,name=synchronous,proto3" json:"synchronous,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SideEffect) Reset() { *m = SideEffect{} } +func (m *SideEffect) String() string { return proto.CompactTextString(m) } +func (*SideEffect) ProtoMessage() {} +func (*SideEffect) Descriptor() ([]byte, []int) { + return fileDescriptor_a4a280e6d8a8fec2, []int{3} +} + +func (m *SideEffect) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SideEffect.Unmarshal(m, b) +} +func (m *SideEffect) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SideEffect.Marshal(b, m, deterministic) +} +func (m *SideEffect) XXX_Merge(src proto.Message) { + xxx_messageInfo_SideEffect.Merge(m, src) +} +func (m *SideEffect) XXX_Size() int { + return xxx_messageInfo_SideEffect.Size(m) +} +func (m *SideEffect) XXX_DiscardUnknown() { + xxx_messageInfo_SideEffect.DiscardUnknown(m) +} + +var xxx_messageInfo_SideEffect proto.InternalMessageInfo + +func (m *SideEffect) GetServiceName() string { + if m != nil { + return m.ServiceName + } + return "" +} + +func (m *SideEffect) GetCommandName() string { + if m != nil { + return m.CommandName + } + return "" +} + +func (m *SideEffect) GetPayload() *any.Any { + if m != nil { + return m.Payload + } + return nil +} + +func (m *SideEffect) GetSynchronous() bool { + if m != nil { + return m.Synchronous + } + return false +} + +// A command. For each command received, a reply must be sent with a matching command id. +type Command struct { + // The ID of the entity. + EntityId string `protobuf:"bytes,1,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"` + // A command id. + Id int64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` + // Command name + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + // The command payload. + Payload *any.Any `protobuf:"bytes,4,opt,name=payload,proto3" json:"payload,omitempty"` + // Whether the command is streamed or not + Streamed bool `protobuf:"varint,5,opt,name=streamed,proto3" json:"streamed,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Command) Reset() { *m = Command{} } +func (m *Command) String() string { return proto.CompactTextString(m) } +func (*Command) ProtoMessage() {} +func (*Command) Descriptor() ([]byte, []int) { + return fileDescriptor_a4a280e6d8a8fec2, []int{4} +} + +func (m *Command) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Command.Unmarshal(m, b) +} +func (m *Command) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Command.Marshal(b, m, deterministic) +} +func (m *Command) XXX_Merge(src proto.Message) { + xxx_messageInfo_Command.Merge(m, src) +} +func (m *Command) XXX_Size() int { + return xxx_messageInfo_Command.Size(m) +} +func (m *Command) XXX_DiscardUnknown() { + xxx_messageInfo_Command.DiscardUnknown(m) +} + +var xxx_messageInfo_Command proto.InternalMessageInfo + +func (m *Command) GetEntityId() string { + if m != nil { + return m.EntityId + } + return "" +} + +func (m *Command) GetId() int64 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *Command) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Command) GetPayload() *any.Any { + if m != nil { + return m.Payload + } + return nil +} + +func (m *Command) GetStreamed() bool { + if m != nil { + return m.Streamed + } + return false +} + +type StreamCancelled struct { + // The ID of the entity + EntityId string `protobuf:"bytes,1,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"` + // The command id + Id int64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *StreamCancelled) Reset() { *m = StreamCancelled{} } +func (m *StreamCancelled) String() string { return proto.CompactTextString(m) } +func (*StreamCancelled) ProtoMessage() {} +func (*StreamCancelled) Descriptor() ([]byte, []int) { + return fileDescriptor_a4a280e6d8a8fec2, []int{5} +} + +func (m *StreamCancelled) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_StreamCancelled.Unmarshal(m, b) +} +func (m *StreamCancelled) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_StreamCancelled.Marshal(b, m, deterministic) +} +func (m *StreamCancelled) XXX_Merge(src proto.Message) { + xxx_messageInfo_StreamCancelled.Merge(m, src) +} +func (m *StreamCancelled) XXX_Size() int { + return xxx_messageInfo_StreamCancelled.Size(m) +} +func (m *StreamCancelled) XXX_DiscardUnknown() { + xxx_messageInfo_StreamCancelled.DiscardUnknown(m) +} + +var xxx_messageInfo_StreamCancelled proto.InternalMessageInfo + +func (m *StreamCancelled) GetEntityId() string { + if m != nil { + return m.EntityId + } + return "" +} + +func (m *StreamCancelled) GetId() int64 { + if m != nil { + return m.Id + } + return 0 +} + +// A failure reply. If this is returned, it will be translated into a gRPC unknown +// error with the corresponding description if supplied. +type Failure struct { + // The id of the command being replied to. Must match the input command. + CommandId int64 `protobuf:"varint,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + // A description of the error. + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Failure) Reset() { *m = Failure{} } +func (m *Failure) String() string { return proto.CompactTextString(m) } +func (*Failure) ProtoMessage() {} +func (*Failure) Descriptor() ([]byte, []int) { + return fileDescriptor_a4a280e6d8a8fec2, []int{6} +} + +func (m *Failure) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Failure.Unmarshal(m, b) +} +func (m *Failure) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Failure.Marshal(b, m, deterministic) +} +func (m *Failure) XXX_Merge(src proto.Message) { + xxx_messageInfo_Failure.Merge(m, src) +} +func (m *Failure) XXX_Size() int { + return xxx_messageInfo_Failure.Size(m) +} +func (m *Failure) XXX_DiscardUnknown() { + xxx_messageInfo_Failure.DiscardUnknown(m) +} + +var xxx_messageInfo_Failure proto.InternalMessageInfo + +func (m *Failure) GetCommandId() int64 { + if m != nil { + return m.CommandId + } + return 0 +} + +func (m *Failure) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +type EntitySpec struct { + // This should be the Descriptors.FileDescriptorSet in proto serialized from as generated by: + // protoc --include_imports \ + // --proto_path= \ + // --descriptor_set_out=user-function.desc \ + // + Proto []byte `protobuf:"bytes,1,opt,name=proto,proto3" json:"proto,omitempty"` + // The entities being served. + Entities []*Entity `protobuf:"bytes,2,rep,name=entities,proto3" json:"entities,omitempty"` + // Optional information about the service. + ServiceInfo *ServiceInfo `protobuf:"bytes,3,opt,name=service_info,json=serviceInfo,proto3" json:"service_info,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EntitySpec) Reset() { *m = EntitySpec{} } +func (m *EntitySpec) String() string { return proto.CompactTextString(m) } +func (*EntitySpec) ProtoMessage() {} +func (*EntitySpec) Descriptor() ([]byte, []int) { + return fileDescriptor_a4a280e6d8a8fec2, []int{7} +} + +func (m *EntitySpec) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EntitySpec.Unmarshal(m, b) +} +func (m *EntitySpec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EntitySpec.Marshal(b, m, deterministic) +} +func (m *EntitySpec) XXX_Merge(src proto.Message) { + xxx_messageInfo_EntitySpec.Merge(m, src) +} +func (m *EntitySpec) XXX_Size() int { + return xxx_messageInfo_EntitySpec.Size(m) +} +func (m *EntitySpec) XXX_DiscardUnknown() { + xxx_messageInfo_EntitySpec.DiscardUnknown(m) +} + +var xxx_messageInfo_EntitySpec proto.InternalMessageInfo + +func (m *EntitySpec) GetProto() []byte { + if m != nil { + return m.Proto + } + return nil +} + +func (m *EntitySpec) GetEntities() []*Entity { + if m != nil { + return m.Entities + } + return nil +} + +func (m *EntitySpec) GetServiceInfo() *ServiceInfo { + if m != nil { + return m.ServiceInfo + } + return nil +} + +// Information about the service that proxy is proxying to. +// All of the information in here is optional. It may be useful for debug purposes. +type ServiceInfo struct { + // The name of the service, eg, "shopping-cart". + ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // The version of the service. + ServiceVersion string `protobuf:"bytes,2,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` + // A description of the runtime for the service. Can be anything, but examples might be: + // - node v10.15.2 + // - OpenJDK Runtime Environment 1.8.0_192-b12 + ServiceRuntime string `protobuf:"bytes,3,opt,name=service_runtime,json=serviceRuntime,proto3" json:"service_runtime,omitempty"` + // If using a support library, the name of that library, eg "cloudstate" + SupportLibraryName string `protobuf:"bytes,4,opt,name=support_library_name,json=supportLibraryName,proto3" json:"support_library_name,omitempty"` + // The version of the support library being used. + SupportLibraryVersion string `protobuf:"bytes,5,opt,name=support_library_version,json=supportLibraryVersion,proto3" json:"support_library_version,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ServiceInfo) Reset() { *m = ServiceInfo{} } +func (m *ServiceInfo) String() string { return proto.CompactTextString(m) } +func (*ServiceInfo) ProtoMessage() {} +func (*ServiceInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_a4a280e6d8a8fec2, []int{8} +} + +func (m *ServiceInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ServiceInfo.Unmarshal(m, b) +} +func (m *ServiceInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ServiceInfo.Marshal(b, m, deterministic) +} +func (m *ServiceInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServiceInfo.Merge(m, src) +} +func (m *ServiceInfo) XXX_Size() int { + return xxx_messageInfo_ServiceInfo.Size(m) +} +func (m *ServiceInfo) XXX_DiscardUnknown() { + xxx_messageInfo_ServiceInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_ServiceInfo proto.InternalMessageInfo + +func (m *ServiceInfo) GetServiceName() string { + if m != nil { + return m.ServiceName + } + return "" +} + +func (m *ServiceInfo) GetServiceVersion() string { + if m != nil { + return m.ServiceVersion + } + return "" +} + +func (m *ServiceInfo) GetServiceRuntime() string { + if m != nil { + return m.ServiceRuntime + } + return "" +} + +func (m *ServiceInfo) GetSupportLibraryName() string { + if m != nil { + return m.SupportLibraryName + } + return "" +} + +func (m *ServiceInfo) GetSupportLibraryVersion() string { + if m != nil { + return m.SupportLibraryVersion + } + return "" +} + +type Entity struct { + // The type of entity. By convention, this should be a fully qualified entity protocol grpc + // service name, for example, cloudstate.eventsourced.EventSourced. + EntityType string `protobuf:"bytes,1,opt,name=entity_type,json=entityType,proto3" json:"entity_type,omitempty"` + // The name of the service to load from the protobuf file. + ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // The ID to namespace state by. How this is used depends on the type of entity, for example, + // event sourced entities will prefix this to the persistence id. + PersistenceId string `protobuf:"bytes,3,opt,name=persistence_id,json=persistenceId,proto3" json:"persistence_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Entity) Reset() { *m = Entity{} } +func (m *Entity) String() string { return proto.CompactTextString(m) } +func (*Entity) ProtoMessage() {} +func (*Entity) Descriptor() ([]byte, []int) { + return fileDescriptor_a4a280e6d8a8fec2, []int{9} +} + +func (m *Entity) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Entity.Unmarshal(m, b) +} +func (m *Entity) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Entity.Marshal(b, m, deterministic) +} +func (m *Entity) XXX_Merge(src proto.Message) { + xxx_messageInfo_Entity.Merge(m, src) +} +func (m *Entity) XXX_Size() int { + return xxx_messageInfo_Entity.Size(m) +} +func (m *Entity) XXX_DiscardUnknown() { + xxx_messageInfo_Entity.DiscardUnknown(m) +} + +var xxx_messageInfo_Entity proto.InternalMessageInfo + +func (m *Entity) GetEntityType() string { + if m != nil { + return m.EntityType + } + return "" +} + +func (m *Entity) GetServiceName() string { + if m != nil { + return m.ServiceName + } + return "" +} + +func (m *Entity) GetPersistenceId() string { + if m != nil { + return m.PersistenceId + } + return "" +} + +type UserFunctionError struct { + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UserFunctionError) Reset() { *m = UserFunctionError{} } +func (m *UserFunctionError) String() string { return proto.CompactTextString(m) } +func (*UserFunctionError) ProtoMessage() {} +func (*UserFunctionError) Descriptor() ([]byte, []int) { + return fileDescriptor_a4a280e6d8a8fec2, []int{10} +} + +func (m *UserFunctionError) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UserFunctionError.Unmarshal(m, b) +} +func (m *UserFunctionError) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UserFunctionError.Marshal(b, m, deterministic) +} +func (m *UserFunctionError) XXX_Merge(src proto.Message) { + xxx_messageInfo_UserFunctionError.Merge(m, src) +} +func (m *UserFunctionError) XXX_Size() int { + return xxx_messageInfo_UserFunctionError.Size(m) +} +func (m *UserFunctionError) XXX_DiscardUnknown() { + xxx_messageInfo_UserFunctionError.DiscardUnknown(m) +} + +var xxx_messageInfo_UserFunctionError proto.InternalMessageInfo + +func (m *UserFunctionError) GetMessage() string { + if m != nil { + return m.Message + } + return "" +} + +type ProxyInfo struct { + ProtocolMajorVersion int32 `protobuf:"varint,1,opt,name=protocol_major_version,json=protocolMajorVersion,proto3" json:"protocol_major_version,omitempty"` + ProtocolMinorVersion int32 `protobuf:"varint,2,opt,name=protocol_minor_version,json=protocolMinorVersion,proto3" json:"protocol_minor_version,omitempty"` + ProxyName string `protobuf:"bytes,3,opt,name=proxy_name,json=proxyName,proto3" json:"proxy_name,omitempty"` + ProxyVersion string `protobuf:"bytes,4,opt,name=proxy_version,json=proxyVersion,proto3" json:"proxy_version,omitempty"` + SupportedEntityTypes []string `protobuf:"bytes,5,rep,name=supported_entity_types,json=supportedEntityTypes,proto3" json:"supported_entity_types,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ProxyInfo) Reset() { *m = ProxyInfo{} } +func (m *ProxyInfo) String() string { return proto.CompactTextString(m) } +func (*ProxyInfo) ProtoMessage() {} +func (*ProxyInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_a4a280e6d8a8fec2, []int{11} +} + +func (m *ProxyInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ProxyInfo.Unmarshal(m, b) +} +func (m *ProxyInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ProxyInfo.Marshal(b, m, deterministic) +} +func (m *ProxyInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_ProxyInfo.Merge(m, src) +} +func (m *ProxyInfo) XXX_Size() int { + return xxx_messageInfo_ProxyInfo.Size(m) +} +func (m *ProxyInfo) XXX_DiscardUnknown() { + xxx_messageInfo_ProxyInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_ProxyInfo proto.InternalMessageInfo + +func (m *ProxyInfo) GetProtocolMajorVersion() int32 { + if m != nil { + return m.ProtocolMajorVersion + } + return 0 +} + +func (m *ProxyInfo) GetProtocolMinorVersion() int32 { + if m != nil { + return m.ProtocolMinorVersion + } + return 0 +} + +func (m *ProxyInfo) GetProxyName() string { + if m != nil { + return m.ProxyName + } + return "" +} + +func (m *ProxyInfo) GetProxyVersion() string { + if m != nil { + return m.ProxyVersion + } + return "" +} + +func (m *ProxyInfo) GetSupportedEntityTypes() []string { + if m != nil { + return m.SupportedEntityTypes + } + return nil +} + +func init() { + proto.RegisterType((*Reply)(nil), "cloudstate.Reply") + proto.RegisterType((*Forward)(nil), "cloudstate.Forward") + proto.RegisterType((*ClientAction)(nil), "cloudstate.ClientAction") + proto.RegisterType((*SideEffect)(nil), "cloudstate.SideEffect") + proto.RegisterType((*Command)(nil), "cloudstate.Command") + proto.RegisterType((*StreamCancelled)(nil), "cloudstate.StreamCancelled") + proto.RegisterType((*Failure)(nil), "cloudstate.Failure") + proto.RegisterType((*EntitySpec)(nil), "cloudstate.EntitySpec") + proto.RegisterType((*ServiceInfo)(nil), "cloudstate.ServiceInfo") + proto.RegisterType((*Entity)(nil), "cloudstate.Entity") + proto.RegisterType((*UserFunctionError)(nil), "cloudstate.UserFunctionError") + proto.RegisterType((*ProxyInfo)(nil), "cloudstate.ProxyInfo") +} + +func init() { proto.RegisterFile("cloudstate/entity.proto", fileDescriptor_a4a280e6d8a8fec2) } + +var fileDescriptor_a4a280e6d8a8fec2 = []byte{ + // 795 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x55, 0xcd, 0x6e, 0xf3, 0x44, + 0x14, 0x8d, 0xf3, 0xd3, 0x24, 0xd7, 0x69, 0xab, 0x4e, 0xd3, 0x34, 0xa4, 0xaa, 0x08, 0x46, 0x88, + 0xb0, 0xa8, 0x83, 0x02, 0x02, 0x09, 0x24, 0xa4, 0xb6, 0xa4, 0x6a, 0x10, 0x20, 0xe4, 0x00, 0x0b, + 0x36, 0x91, 0x6b, 0x4f, 0xca, 0x20, 0x7b, 0xc6, 0x9a, 0x71, 0x0a, 0x5e, 0xf1, 0x06, 0x2c, 0xfb, + 0x04, 0xf0, 0x76, 0x6c, 0x78, 0x03, 0xe4, 0xf9, 0x71, 0xa6, 0xc9, 0xa6, 0xdf, 0xea, 0xdb, 0x79, + 0xee, 0x39, 0x67, 0xe6, 0xdc, 0x33, 0x77, 0x12, 0x38, 0x8f, 0x12, 0xb6, 0x89, 0x45, 0x1e, 0xe6, + 0x78, 0x8a, 0x69, 0x4e, 0xf2, 0xc2, 0xcf, 0x38, 0xcb, 0x19, 0x82, 0x2d, 0x30, 0x7a, 0xe7, 0x91, + 0xb1, 0xc7, 0x04, 0x4f, 0x25, 0xf2, 0xb0, 0x59, 0x4f, 0x43, 0xaa, 0x69, 0xa3, 0x8b, 0x5d, 0x08, + 0xa7, 0x99, 0xd9, 0x63, 0x34, 0xde, 0x05, 0x63, 0x2c, 0x22, 0x4e, 0xb2, 0x9c, 0x71, 0xc5, 0xf0, + 0x3e, 0x87, 0x56, 0x80, 0xb3, 0xa4, 0x40, 0x3e, 0xb4, 0xb3, 0xb0, 0x48, 0x58, 0x18, 0x0f, 0x9d, + 0xb1, 0x33, 0x71, 0x67, 0x7d, 0x5f, 0x89, 0x7d, 0x23, 0xf6, 0xaf, 0x69, 0x11, 0x18, 0x92, 0xf7, + 0x27, 0xb4, 0xef, 0x18, 0xff, 0x3d, 0xe4, 0x31, 0x7a, 0x0f, 0x7a, 0x02, 0xf3, 0x27, 0x12, 0xe1, + 0x15, 0x0d, 0x53, 0x2c, 0xf5, 0xdd, 0xc0, 0xd5, 0xb5, 0xef, 0xc3, 0x14, 0x97, 0x94, 0x88, 0xa5, + 0x69, 0x48, 0x63, 0x45, 0xa9, 0x2b, 0x8a, 0xae, 0x49, 0x8a, 0x65, 0xa0, 0xf1, 0x1a, 0x03, 0xff, + 0x38, 0xd0, 0xbb, 0x4d, 0x08, 0xa6, 0xf9, 0x75, 0x94, 0x13, 0x46, 0xd1, 0x47, 0xd0, 0xe2, 0x65, + 0x2b, 0xda, 0xff, 0x89, 0xbf, 0x0d, 0xd0, 0x97, 0x3d, 0xde, 0xd7, 0x02, 0xc5, 0x40, 0x53, 0x68, + 0xaf, 0x95, 0x79, 0xe9, 0xc4, 0x9d, 0x9d, 0xda, 0x64, 0xdd, 0xd7, 0x7d, 0x2d, 0x30, 0x2c, 0x29, + 0x08, 0x49, 0xb2, 0xe1, 0x58, 0x9b, 0x7b, 0x29, 0x50, 0x90, 0x14, 0xa8, 0xcf, 0x9b, 0x0e, 0x1c, + 0x84, 0xd2, 0x96, 0xf7, 0xb7, 0x03, 0xb0, 0x24, 0x31, 0x9e, 0xaf, 0xd7, 0x38, 0xca, 0xdf, 0x4e, + 0x58, 0x68, 0x0c, 0xae, 0x28, 0x68, 0xf4, 0x2b, 0x67, 0x94, 0x6d, 0xc4, 0xb0, 0x39, 0x76, 0x26, + 0x9d, 0xc0, 0x2e, 0x79, 0xcf, 0x0e, 0xb4, 0x6f, 0xd5, 0x09, 0xe8, 0x02, 0xba, 0x6a, 0x14, 0x57, + 0x24, 0xd6, 0x06, 0x3b, 0xaa, 0xb0, 0x88, 0xd1, 0x11, 0xd4, 0x89, 0x8a, 0xad, 0x11, 0xd4, 0x49, + 0x8c, 0x10, 0x34, 0xa5, 0xcb, 0x86, 0xe4, 0xc9, 0x6f, 0xdb, 0x5e, 0xf3, 0x35, 0xf6, 0x46, 0xd0, + 0x11, 0x39, 0xc7, 0x61, 0x8a, 0xe3, 0x61, 0x4b, 0x7a, 0xab, 0xd6, 0xde, 0x57, 0x70, 0xbc, 0x94, + 0xdf, 0xb7, 0x21, 0x8d, 0x70, 0x92, 0xe0, 0x37, 0xf3, 0xe7, 0x7d, 0x03, 0x6d, 0x7d, 0x3f, 0xe8, + 0x12, 0xc0, 0x04, 0xab, 0x85, 0x8d, 0xa0, 0xab, 0x2b, 0x0b, 0x19, 0x92, 0x79, 0x1f, 0x84, 0x51, + 0x13, 0xbb, 0x55, 0xf2, 0xfe, 0x72, 0x00, 0xe6, 0xf2, 0xa0, 0x65, 0x86, 0x23, 0xd4, 0x87, 0x96, + 0xec, 0x47, 0x6e, 0xd5, 0x0b, 0xd4, 0x02, 0xf9, 0xa0, 0xcc, 0x10, 0x2c, 0x86, 0xf5, 0x71, 0x63, + 0xe2, 0xce, 0x90, 0x3d, 0x2c, 0x4a, 0x1f, 0x54, 0x1c, 0xf4, 0xc5, 0x76, 0x22, 0x08, 0x5d, 0x33, + 0x7d, 0xa1, 0xe7, 0xb6, 0x66, 0xa9, 0xf0, 0x05, 0x5d, 0xb3, 0x6a, 0x54, 0xca, 0x85, 0xf7, 0xaf, + 0x03, 0xae, 0x05, 0xbe, 0x66, 0xba, 0x3e, 0x84, 0x63, 0x43, 0x79, 0xc2, 0x5c, 0x6c, 0x3b, 0x3d, + 0xd2, 0xe5, 0x9f, 0x55, 0xd5, 0x26, 0xf2, 0x0d, 0xcd, 0x49, 0x75, 0xc7, 0x86, 0x18, 0xa8, 0x2a, + 0xfa, 0x18, 0xfa, 0x62, 0x93, 0x65, 0x8c, 0xe7, 0xab, 0x84, 0x3c, 0xf0, 0x90, 0x17, 0xea, 0xf0, + 0xa6, 0x64, 0x23, 0x8d, 0x7d, 0xab, 0x20, 0xe9, 0xe1, 0x33, 0x38, 0xdf, 0x55, 0x18, 0x2f, 0x2d, + 0x29, 0x3a, 0x7b, 0x29, 0xd2, 0x96, 0x3c, 0x01, 0x07, 0x2a, 0x3e, 0xf4, 0x2e, 0xb8, 0x7a, 0x04, + 0xf2, 0x22, 0x33, 0x7d, 0x82, 0x2a, 0xfd, 0x58, 0x64, 0x78, 0x2f, 0x89, 0xfa, 0x7e, 0x12, 0x1f, + 0xc0, 0x51, 0x56, 0x6e, 0x2c, 0x72, 0x4c, 0xcb, 0xf0, 0x63, 0xdd, 0xdf, 0xa1, 0x55, 0x5d, 0xc4, + 0xde, 0x15, 0x9c, 0xfc, 0x24, 0x30, 0xbf, 0xdb, 0x50, 0xf9, 0xa0, 0xe7, 0x9c, 0x33, 0x8e, 0x86, + 0xd0, 0x4e, 0xb1, 0x10, 0xe1, 0xa3, 0x39, 0xdb, 0x2c, 0xbd, 0xff, 0x1c, 0xe8, 0xfe, 0xc0, 0xd9, + 0x1f, 0x85, 0xbc, 0x90, 0x4f, 0x61, 0x20, 0xa7, 0x22, 0x62, 0xc9, 0x2a, 0x0d, 0x7f, 0x63, 0xbc, + 0x6a, 0xb4, 0x94, 0xb5, 0x82, 0xbe, 0x41, 0xbf, 0x2b, 0x41, 0x13, 0xfd, 0x0b, 0x15, 0xa1, 0x96, + 0xaa, 0xbe, 0xa3, 0x2a, 0x41, 0xa3, 0xba, 0x04, 0xc8, 0xca, 0x83, 0x57, 0xd6, 0x7b, 0xec, 0xca, + 0x8a, 0x6c, 0xf7, 0x7d, 0x38, 0x54, 0xb0, 0xd9, 0x4b, 0xdd, 0x4f, 0x4f, 0x16, 0xad, 0x93, 0x75, + 0xf4, 0x38, 0x5e, 0x59, 0x09, 0x8b, 0x61, 0x6b, 0xdc, 0x98, 0x74, 0x83, 0x7e, 0x85, 0xce, 0xab, + 0xac, 0xc5, 0xec, 0xd9, 0x81, 0x63, 0xb5, 0xfe, 0x9a, 0x88, 0x88, 0x3d, 0x61, 0x5e, 0xa0, 0x2f, + 0xa1, 0x13, 0xeb, 0x05, 0x3a, 0xb3, 0x87, 0xb9, 0x0a, 0x67, 0x34, 0xd8, 0x7f, 0x17, 0xe5, 0xbb, + 0xf2, 0x6a, 0xe8, 0x0e, 0x5c, 0x8e, 0xcb, 0x73, 0x54, 0xda, 0x97, 0x36, 0x71, 0xef, 0x32, 0x46, + 0x83, 0xbd, 0x5f, 0x97, 0x79, 0xf9, 0x27, 0xe8, 0xd5, 0x6e, 0xae, 0x60, 0x40, 0x98, 0x2d, 0x36, + 0xc1, 0xfd, 0x72, 0x6a, 0xfd, 0xef, 0x9a, 0xe2, 0xc3, 0x81, 0xfc, 0xfa, 0xe4, 0xff, 0x00, 0x00, + 0x00, 0xff, 0xff, 0xb9, 0x13, 0x29, 0x57, 0x95, 0x07, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// EntityDiscoveryClient is the client API for EntityDiscovery service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type EntityDiscoveryClient interface { + // Discover what entities the user function wishes to serve. + Discover(ctx context.Context, in *ProxyInfo, opts ...grpc.CallOption) (*EntitySpec, error) + // Report an error back to the user function. This will only be invoked to tell the user function + // that it has done something wrong, eg, violated the protocol, tried to use an entity type that + // isn't supported, or attempted to forward to an entity that doesn't exist, etc. These messages + // should be logged clearly for debugging purposes. + ReportError(ctx context.Context, in *UserFunctionError, opts ...grpc.CallOption) (*empty.Empty, error) +} + +type entityDiscoveryClient struct { + cc *grpc.ClientConn +} + +func NewEntityDiscoveryClient(cc *grpc.ClientConn) EntityDiscoveryClient { + return &entityDiscoveryClient{cc} +} + +func (c *entityDiscoveryClient) Discover(ctx context.Context, in *ProxyInfo, opts ...grpc.CallOption) (*EntitySpec, error) { + out := new(EntitySpec) + err := c.cc.Invoke(ctx, "/cloudstate.EntityDiscovery/discover", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *entityDiscoveryClient) ReportError(ctx context.Context, in *UserFunctionError, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/cloudstate.EntityDiscovery/reportError", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// EntityDiscoveryServer is the server API for EntityDiscovery service. +type EntityDiscoveryServer interface { + // Discover what entities the user function wishes to serve. + Discover(context.Context, *ProxyInfo) (*EntitySpec, error) + // Report an error back to the user function. This will only be invoked to tell the user function + // that it has done something wrong, eg, violated the protocol, tried to use an entity type that + // isn't supported, or attempted to forward to an entity that doesn't exist, etc. These messages + // should be logged clearly for debugging purposes. + ReportError(context.Context, *UserFunctionError) (*empty.Empty, error) +} + +// UnimplementedEntityDiscoveryServer can be embedded to have forward compatible implementations. +type UnimplementedEntityDiscoveryServer struct { +} + +func (*UnimplementedEntityDiscoveryServer) Discover(ctx context.Context, req *ProxyInfo) (*EntitySpec, error) { + return nil, status.Errorf(codes.Unimplemented, "method Discover not implemented") +} +func (*UnimplementedEntityDiscoveryServer) ReportError(ctx context.Context, req *UserFunctionError) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ReportError not implemented") +} + +func RegisterEntityDiscoveryServer(s *grpc.Server, srv EntityDiscoveryServer) { + s.RegisterService(&_EntityDiscovery_serviceDesc, srv) +} + +func _EntityDiscovery_Discover_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ProxyInfo) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EntityDiscoveryServer).Discover(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloudstate.EntityDiscovery/Discover", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EntityDiscoveryServer).Discover(ctx, req.(*ProxyInfo)) + } + return interceptor(ctx, in, info, handler) +} + +func _EntityDiscovery_ReportError_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UserFunctionError) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EntityDiscoveryServer).ReportError(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloudstate.EntityDiscovery/ReportError", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EntityDiscoveryServer).ReportError(ctx, req.(*UserFunctionError)) + } + return interceptor(ctx, in, info, handler) +} + +var _EntityDiscovery_serviceDesc = grpc.ServiceDesc{ + ServiceName: "cloudstate.EntityDiscovery", + HandlerType: (*EntityDiscoveryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "discover", + Handler: _EntityDiscovery_Discover_Handler, + }, + { + MethodName: "reportError", + Handler: _EntityDiscovery_ReportError_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "cloudstate/entity.proto", +} diff --git a/cloudstate/protocol/event_sourced.pb.go b/cloudstate/protocol/event_sourced.pb.go new file mode 100644 index 0000000..ebd90d6 --- /dev/null +++ b/cloudstate/protocol/event_sourced.pb.go @@ -0,0 +1,624 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: cloudstate/event_sourced.proto + +package protocol + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + any "github.com/golang/protobuf/ptypes/any" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +// The init message. This will always be the first message sent to the entity when +// it is loaded. +type EventSourcedInit struct { + ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // The ID of the entity. + EntityId string `protobuf:"bytes,2,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"` + // If present the entity should initialise its state using this snapshot. + Snapshot *EventSourcedSnapshot `protobuf:"bytes,3,opt,name=snapshot,proto3" json:"snapshot,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EventSourcedInit) Reset() { *m = EventSourcedInit{} } +func (m *EventSourcedInit) String() string { return proto.CompactTextString(m) } +func (*EventSourcedInit) ProtoMessage() {} +func (*EventSourcedInit) Descriptor() ([]byte, []int) { + return fileDescriptor_e62d03156a02a758, []int{0} +} + +func (m *EventSourcedInit) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EventSourcedInit.Unmarshal(m, b) +} +func (m *EventSourcedInit) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EventSourcedInit.Marshal(b, m, deterministic) +} +func (m *EventSourcedInit) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventSourcedInit.Merge(m, src) +} +func (m *EventSourcedInit) XXX_Size() int { + return xxx_messageInfo_EventSourcedInit.Size(m) +} +func (m *EventSourcedInit) XXX_DiscardUnknown() { + xxx_messageInfo_EventSourcedInit.DiscardUnknown(m) +} + +var xxx_messageInfo_EventSourcedInit proto.InternalMessageInfo + +func (m *EventSourcedInit) GetServiceName() string { + if m != nil { + return m.ServiceName + } + return "" +} + +func (m *EventSourcedInit) GetEntityId() string { + if m != nil { + return m.EntityId + } + return "" +} + +func (m *EventSourcedInit) GetSnapshot() *EventSourcedSnapshot { + if m != nil { + return m.Snapshot + } + return nil +} + +// A snapshot +type EventSourcedSnapshot struct { + // The sequence number when the snapshot was taken. + SnapshotSequence int64 `protobuf:"varint,1,opt,name=snapshot_sequence,json=snapshotSequence,proto3" json:"snapshot_sequence,omitempty"` + // The snapshot. + Snapshot *any.Any `protobuf:"bytes,2,opt,name=snapshot,proto3" json:"snapshot,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EventSourcedSnapshot) Reset() { *m = EventSourcedSnapshot{} } +func (m *EventSourcedSnapshot) String() string { return proto.CompactTextString(m) } +func (*EventSourcedSnapshot) ProtoMessage() {} +func (*EventSourcedSnapshot) Descriptor() ([]byte, []int) { + return fileDescriptor_e62d03156a02a758, []int{1} +} + +func (m *EventSourcedSnapshot) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EventSourcedSnapshot.Unmarshal(m, b) +} +func (m *EventSourcedSnapshot) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EventSourcedSnapshot.Marshal(b, m, deterministic) +} +func (m *EventSourcedSnapshot) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventSourcedSnapshot.Merge(m, src) +} +func (m *EventSourcedSnapshot) XXX_Size() int { + return xxx_messageInfo_EventSourcedSnapshot.Size(m) +} +func (m *EventSourcedSnapshot) XXX_DiscardUnknown() { + xxx_messageInfo_EventSourcedSnapshot.DiscardUnknown(m) +} + +var xxx_messageInfo_EventSourcedSnapshot proto.InternalMessageInfo + +func (m *EventSourcedSnapshot) GetSnapshotSequence() int64 { + if m != nil { + return m.SnapshotSequence + } + return 0 +} + +func (m *EventSourcedSnapshot) GetSnapshot() *any.Any { + if m != nil { + return m.Snapshot + } + return nil +} + +// An event. These will be sent to the entity when the entity starts up. +type EventSourcedEvent struct { + // The sequence number of the event. + Sequence int64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + // The event payload. + Payload *any.Any `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EventSourcedEvent) Reset() { *m = EventSourcedEvent{} } +func (m *EventSourcedEvent) String() string { return proto.CompactTextString(m) } +func (*EventSourcedEvent) ProtoMessage() {} +func (*EventSourcedEvent) Descriptor() ([]byte, []int) { + return fileDescriptor_e62d03156a02a758, []int{2} +} + +func (m *EventSourcedEvent) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EventSourcedEvent.Unmarshal(m, b) +} +func (m *EventSourcedEvent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EventSourcedEvent.Marshal(b, m, deterministic) +} +func (m *EventSourcedEvent) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventSourcedEvent.Merge(m, src) +} +func (m *EventSourcedEvent) XXX_Size() int { + return xxx_messageInfo_EventSourcedEvent.Size(m) +} +func (m *EventSourcedEvent) XXX_DiscardUnknown() { + xxx_messageInfo_EventSourcedEvent.DiscardUnknown(m) +} + +var xxx_messageInfo_EventSourcedEvent proto.InternalMessageInfo + +func (m *EventSourcedEvent) GetSequence() int64 { + if m != nil { + return m.Sequence + } + return 0 +} + +func (m *EventSourcedEvent) GetPayload() *any.Any { + if m != nil { + return m.Payload + } + return nil +} + +// A reply to a command. +type EventSourcedReply struct { + // The id of the command being replied to. Must match the input command. + CommandId int64 `protobuf:"varint,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + // The action to take + ClientAction *ClientAction `protobuf:"bytes,2,opt,name=client_action,json=clientAction,proto3" json:"client_action,omitempty"` + // Any side effects to perform + SideEffects []*SideEffect `protobuf:"bytes,3,rep,name=side_effects,json=sideEffects,proto3" json:"side_effects,omitempty"` + // A list of events to persist - these will be persisted before the reply + // is sent. + Events []*any.Any `protobuf:"bytes,4,rep,name=events,proto3" json:"events,omitempty"` + // An optional snapshot to persist. It is assumed that this snapshot will have + // the state of any events in the events field applied to it. It is illegal to + // send a snapshot without sending any events. + Snapshot *any.Any `protobuf:"bytes,5,opt,name=snapshot,proto3" json:"snapshot,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EventSourcedReply) Reset() { *m = EventSourcedReply{} } +func (m *EventSourcedReply) String() string { return proto.CompactTextString(m) } +func (*EventSourcedReply) ProtoMessage() {} +func (*EventSourcedReply) Descriptor() ([]byte, []int) { + return fileDescriptor_e62d03156a02a758, []int{3} +} + +func (m *EventSourcedReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EventSourcedReply.Unmarshal(m, b) +} +func (m *EventSourcedReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EventSourcedReply.Marshal(b, m, deterministic) +} +func (m *EventSourcedReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventSourcedReply.Merge(m, src) +} +func (m *EventSourcedReply) XXX_Size() int { + return xxx_messageInfo_EventSourcedReply.Size(m) +} +func (m *EventSourcedReply) XXX_DiscardUnknown() { + xxx_messageInfo_EventSourcedReply.DiscardUnknown(m) +} + +var xxx_messageInfo_EventSourcedReply proto.InternalMessageInfo + +func (m *EventSourcedReply) GetCommandId() int64 { + if m != nil { + return m.CommandId + } + return 0 +} + +func (m *EventSourcedReply) GetClientAction() *ClientAction { + if m != nil { + return m.ClientAction + } + return nil +} + +func (m *EventSourcedReply) GetSideEffects() []*SideEffect { + if m != nil { + return m.SideEffects + } + return nil +} + +func (m *EventSourcedReply) GetEvents() []*any.Any { + if m != nil { + return m.Events + } + return nil +} + +func (m *EventSourcedReply) GetSnapshot() *any.Any { + if m != nil { + return m.Snapshot + } + return nil +} + +// Input message type for the gRPC stream in. +type EventSourcedStreamIn struct { + // Types that are valid to be assigned to Message: + // *EventSourcedStreamIn_Init + // *EventSourcedStreamIn_Event + // *EventSourcedStreamIn_Command + Message isEventSourcedStreamIn_Message `protobuf_oneof:"message"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EventSourcedStreamIn) Reset() { *m = EventSourcedStreamIn{} } +func (m *EventSourcedStreamIn) String() string { return proto.CompactTextString(m) } +func (*EventSourcedStreamIn) ProtoMessage() {} +func (*EventSourcedStreamIn) Descriptor() ([]byte, []int) { + return fileDescriptor_e62d03156a02a758, []int{4} +} + +func (m *EventSourcedStreamIn) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EventSourcedStreamIn.Unmarshal(m, b) +} +func (m *EventSourcedStreamIn) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EventSourcedStreamIn.Marshal(b, m, deterministic) +} +func (m *EventSourcedStreamIn) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventSourcedStreamIn.Merge(m, src) +} +func (m *EventSourcedStreamIn) XXX_Size() int { + return xxx_messageInfo_EventSourcedStreamIn.Size(m) +} +func (m *EventSourcedStreamIn) XXX_DiscardUnknown() { + xxx_messageInfo_EventSourcedStreamIn.DiscardUnknown(m) +} + +var xxx_messageInfo_EventSourcedStreamIn proto.InternalMessageInfo + +type isEventSourcedStreamIn_Message interface { + isEventSourcedStreamIn_Message() +} + +type EventSourcedStreamIn_Init struct { + Init *EventSourcedInit `protobuf:"bytes,1,opt,name=init,proto3,oneof"` +} + +type EventSourcedStreamIn_Event struct { + Event *EventSourcedEvent `protobuf:"bytes,2,opt,name=event,proto3,oneof"` +} + +type EventSourcedStreamIn_Command struct { + Command *Command `protobuf:"bytes,3,opt,name=command,proto3,oneof"` +} + +func (*EventSourcedStreamIn_Init) isEventSourcedStreamIn_Message() {} + +func (*EventSourcedStreamIn_Event) isEventSourcedStreamIn_Message() {} + +func (*EventSourcedStreamIn_Command) isEventSourcedStreamIn_Message() {} + +func (m *EventSourcedStreamIn) GetMessage() isEventSourcedStreamIn_Message { + if m != nil { + return m.Message + } + return nil +} + +func (m *EventSourcedStreamIn) GetInit() *EventSourcedInit { + if x, ok := m.GetMessage().(*EventSourcedStreamIn_Init); ok { + return x.Init + } + return nil +} + +func (m *EventSourcedStreamIn) GetEvent() *EventSourcedEvent { + if x, ok := m.GetMessage().(*EventSourcedStreamIn_Event); ok { + return x.Event + } + return nil +} + +func (m *EventSourcedStreamIn) GetCommand() *Command { + if x, ok := m.GetMessage().(*EventSourcedStreamIn_Command); ok { + return x.Command + } + return nil +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*EventSourcedStreamIn) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*EventSourcedStreamIn_Init)(nil), + (*EventSourcedStreamIn_Event)(nil), + (*EventSourcedStreamIn_Command)(nil), + } +} + +// Output message type for the gRPC stream out. +type EventSourcedStreamOut struct { + // Types that are valid to be assigned to Message: + // *EventSourcedStreamOut_Reply + // *EventSourcedStreamOut_Failure + Message isEventSourcedStreamOut_Message `protobuf_oneof:"message"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EventSourcedStreamOut) Reset() { *m = EventSourcedStreamOut{} } +func (m *EventSourcedStreamOut) String() string { return proto.CompactTextString(m) } +func (*EventSourcedStreamOut) ProtoMessage() {} +func (*EventSourcedStreamOut) Descriptor() ([]byte, []int) { + return fileDescriptor_e62d03156a02a758, []int{5} +} + +func (m *EventSourcedStreamOut) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EventSourcedStreamOut.Unmarshal(m, b) +} +func (m *EventSourcedStreamOut) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EventSourcedStreamOut.Marshal(b, m, deterministic) +} +func (m *EventSourcedStreamOut) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventSourcedStreamOut.Merge(m, src) +} +func (m *EventSourcedStreamOut) XXX_Size() int { + return xxx_messageInfo_EventSourcedStreamOut.Size(m) +} +func (m *EventSourcedStreamOut) XXX_DiscardUnknown() { + xxx_messageInfo_EventSourcedStreamOut.DiscardUnknown(m) +} + +var xxx_messageInfo_EventSourcedStreamOut proto.InternalMessageInfo + +type isEventSourcedStreamOut_Message interface { + isEventSourcedStreamOut_Message() +} + +type EventSourcedStreamOut_Reply struct { + Reply *EventSourcedReply `protobuf:"bytes,1,opt,name=reply,proto3,oneof"` +} + +type EventSourcedStreamOut_Failure struct { + Failure *Failure `protobuf:"bytes,2,opt,name=failure,proto3,oneof"` +} + +func (*EventSourcedStreamOut_Reply) isEventSourcedStreamOut_Message() {} + +func (*EventSourcedStreamOut_Failure) isEventSourcedStreamOut_Message() {} + +func (m *EventSourcedStreamOut) GetMessage() isEventSourcedStreamOut_Message { + if m != nil { + return m.Message + } + return nil +} + +func (m *EventSourcedStreamOut) GetReply() *EventSourcedReply { + if x, ok := m.GetMessage().(*EventSourcedStreamOut_Reply); ok { + return x.Reply + } + return nil +} + +func (m *EventSourcedStreamOut) GetFailure() *Failure { + if x, ok := m.GetMessage().(*EventSourcedStreamOut_Failure); ok { + return x.Failure + } + return nil +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*EventSourcedStreamOut) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*EventSourcedStreamOut_Reply)(nil), + (*EventSourcedStreamOut_Failure)(nil), + } +} + +func init() { + proto.RegisterType((*EventSourcedInit)(nil), "cloudstate.eventsourced.EventSourcedInit") + proto.RegisterType((*EventSourcedSnapshot)(nil), "cloudstate.eventsourced.EventSourcedSnapshot") + proto.RegisterType((*EventSourcedEvent)(nil), "cloudstate.eventsourced.EventSourcedEvent") + proto.RegisterType((*EventSourcedReply)(nil), "cloudstate.eventsourced.EventSourcedReply") + proto.RegisterType((*EventSourcedStreamIn)(nil), "cloudstate.eventsourced.EventSourcedStreamIn") + proto.RegisterType((*EventSourcedStreamOut)(nil), "cloudstate.eventsourced.EventSourcedStreamOut") +} + +func init() { proto.RegisterFile("cloudstate/event_sourced.proto", fileDescriptor_e62d03156a02a758) } + +var fileDescriptor_e62d03156a02a758 = []byte{ + // 549 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, + 0x14, 0x8d, 0x9b, 0x36, 0x3f, 0x37, 0xf9, 0xa4, 0x76, 0xda, 0xaf, 0x35, 0x41, 0xa0, 0x90, 0x55, + 0xf8, 0xa9, 0x53, 0x85, 0x15, 0x0b, 0x84, 0x1a, 0x54, 0x94, 0x6c, 0xa8, 0xe4, 0xec, 0xd8, 0x58, + 0x53, 0xfb, 0x26, 0x1d, 0xc9, 0x9e, 0x09, 0x9e, 0x71, 0xa5, 0x2c, 0x78, 0x03, 0xf6, 0xac, 0x78, + 0x2e, 0x5e, 0x07, 0x79, 0x66, 0x1c, 0x26, 0xa5, 0xa0, 0xb0, 0x1b, 0xcf, 0x39, 0xe7, 0xde, 0x33, + 0xe7, 0xde, 0x04, 0x9e, 0xc6, 0xa9, 0x28, 0x12, 0xa9, 0xa8, 0xc2, 0x11, 0xde, 0x21, 0x57, 0x91, + 0x14, 0x45, 0x1e, 0x63, 0x12, 0xac, 0x72, 0xa1, 0x04, 0x39, 0xfb, 0x85, 0x07, 0x1a, 0xb7, 0x70, + 0xef, 0xd1, 0x52, 0x88, 0x65, 0x8a, 0x23, 0x4d, 0xbb, 0x29, 0x16, 0x23, 0xca, 0xd7, 0x46, 0xd3, + 0x3b, 0x73, 0x6b, 0x72, 0xc5, 0x94, 0x05, 0x06, 0xdf, 0x3d, 0x38, 0xbc, 0x2a, 0x8b, 0xcc, 0x4d, + 0x91, 0x19, 0x67, 0x8a, 0x3c, 0x83, 0xae, 0xc4, 0xfc, 0x8e, 0xc5, 0x18, 0x71, 0x9a, 0xa1, 0xef, + 0xf5, 0xbd, 0x61, 0x3b, 0xec, 0xd8, 0xbb, 0x8f, 0x34, 0x43, 0xf2, 0x18, 0xda, 0xa6, 0x4e, 0xc4, + 0x12, 0x7f, 0x4f, 0xe3, 0x2d, 0x73, 0x31, 0x4b, 0xc8, 0x0c, 0x5a, 0x92, 0xd3, 0x95, 0xbc, 0x15, + 0xca, 0xaf, 0xf7, 0xbd, 0x61, 0x67, 0x7c, 0x1e, 0xfc, 0xc1, 0x74, 0xe0, 0x36, 0x9f, 0x5b, 0x51, + 0xb8, 0x91, 0x0f, 0x0a, 0x38, 0x79, 0x88, 0x41, 0x5e, 0xc2, 0x51, 0xc5, 0x89, 0x24, 0x7e, 0x2e, + 0x90, 0xc7, 0xc6, 0x67, 0x3d, 0x3c, 0xac, 0x80, 0xb9, 0xbd, 0x27, 0x17, 0x8e, 0x9f, 0x3d, 0xed, + 0xe7, 0x24, 0x30, 0x59, 0x05, 0x55, 0x56, 0xc1, 0x25, 0x5f, 0x3b, 0x6d, 0x23, 0x38, 0x72, 0xdb, + 0xea, 0x33, 0xe9, 0x41, 0xeb, 0x5e, 0xab, 0xcd, 0x37, 0x09, 0xa0, 0xb9, 0xa2, 0xeb, 0x54, 0xd0, + 0xe4, 0xaf, 0x1d, 0x2a, 0xd2, 0xe0, 0xeb, 0xde, 0x76, 0x87, 0x10, 0x57, 0xe9, 0x9a, 0x3c, 0x01, + 0x88, 0x45, 0x96, 0x51, 0x9e, 0x94, 0xb1, 0x9a, 0x1e, 0x6d, 0x7b, 0x33, 0x4b, 0xc8, 0x5b, 0xf8, + 0x2f, 0x4e, 0x59, 0xb9, 0x11, 0x34, 0x56, 0x4c, 0x70, 0xdb, 0xca, 0x77, 0xc3, 0x7d, 0xaf, 0x09, + 0x97, 0x1a, 0x0f, 0xbb, 0xb1, 0xf3, 0x45, 0xde, 0x40, 0x57, 0xb2, 0x04, 0x23, 0x5c, 0x2c, 0x30, + 0x56, 0xd2, 0xaf, 0xf7, 0xeb, 0xc3, 0xce, 0xf8, 0xd4, 0x55, 0xcf, 0x59, 0x82, 0x57, 0x1a, 0x0e, + 0x3b, 0x72, 0x73, 0x96, 0xe4, 0x15, 0x34, 0xcc, 0xd4, 0xfc, 0x7d, 0x2d, 0x7a, 0xf8, 0x75, 0x96, + 0xb3, 0x95, 0xf7, 0xc1, 0x4e, 0x79, 0xff, 0xf0, 0xee, 0xcd, 0x59, 0xe5, 0x48, 0xb3, 0x19, 0x27, + 0xef, 0x60, 0x9f, 0x71, 0xa6, 0x74, 0x16, 0x9d, 0xf1, 0xf3, 0x9d, 0xd6, 0xa8, 0xdc, 0xe1, 0x69, + 0x2d, 0xd4, 0x42, 0x32, 0x81, 0x03, 0x4d, 0xb4, 0x59, 0xbd, 0xd8, 0xa9, 0x82, 0x3e, 0x4f, 0x6b, + 0xa1, 0x91, 0x92, 0x11, 0x34, 0xed, 0x10, 0xec, 0x3a, 0x1f, 0x6f, 0x25, 0x6e, 0xa0, 0x69, 0x2d, + 0xac, 0x58, 0x93, 0x36, 0x34, 0x33, 0x94, 0x92, 0x2e, 0x71, 0xf0, 0xcd, 0x83, 0xff, 0x7f, 0x7f, + 0xd9, 0x75, 0xa1, 0x9d, 0xe5, 0xe5, 0xd4, 0xed, 0xdb, 0x76, 0x73, 0xa6, 0xf7, 0xa4, 0x74, 0xa6, + 0xa5, 0xa5, 0xb3, 0x05, 0x65, 0x69, 0x91, 0xa3, 0x7d, 0xdf, 0x96, 0xb3, 0x0f, 0x06, 0x2a, 0x9d, + 0x59, 0x96, 0xe3, 0x6c, 0xfc, 0x05, 0xba, 0x6e, 0x65, 0x92, 0x41, 0xe3, 0x96, 0xf2, 0x24, 0x45, + 0xb2, 0xe3, 0xaf, 0xd5, 0xce, 0xa8, 0x17, 0xfc, 0x03, 0xfd, 0xba, 0x50, 0x83, 0xda, 0xd0, 0xbb, + 0xf0, 0x26, 0xe7, 0x70, 0xca, 0x84, 0xab, 0xd4, 0xab, 0x11, 0x8b, 0xf4, 0xd3, 0xb1, 0xf3, 0x67, + 0x55, 0x5d, 0xde, 0x34, 0xf4, 0xe9, 0xf5, 0xcf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x29, 0xfc, 0xf0, + 0xf2, 0x1e, 0x05, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// EventSourcedClient is the client API for EventSourced service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type EventSourcedClient interface { + // The stream. One stream will be established per active entity. + // Once established, the first message sent will be Init, which contains the entity ID, and, + // if the entity has previously persisted a snapshot, it will contain that snapshot. It will + // then send zero to many event messages, one for each event previously persisted. The entity + // is expected to apply these to its state in a deterministic fashion. Once all the events + // are sent, one to many commands are sent, with new commands being sent as new requests for + // the entity come in. The entity is expected to reply to each command with exactly one reply + // message. The entity should reply in order, and any events that the entity requests to be + // persisted the entity should handle itself, applying them to its own state, as if they had + // arrived as events when the event stream was being replayed on load. + Handle(ctx context.Context, opts ...grpc.CallOption) (EventSourced_HandleClient, error) +} + +type eventSourcedClient struct { + cc *grpc.ClientConn +} + +func NewEventSourcedClient(cc *grpc.ClientConn) EventSourcedClient { + return &eventSourcedClient{cc} +} + +func (c *eventSourcedClient) Handle(ctx context.Context, opts ...grpc.CallOption) (EventSourced_HandleClient, error) { + stream, err := c.cc.NewStream(ctx, &_EventSourced_serviceDesc.Streams[0], "/cloudstate.eventsourced.EventSourced/handle", opts...) + if err != nil { + return nil, err + } + x := &eventSourcedHandleClient{stream} + return x, nil +} + +type EventSourced_HandleClient interface { + Send(*EventSourcedStreamIn) error + Recv() (*EventSourcedStreamOut, error) + grpc.ClientStream +} + +type eventSourcedHandleClient struct { + grpc.ClientStream +} + +func (x *eventSourcedHandleClient) Send(m *EventSourcedStreamIn) error { + return x.ClientStream.SendMsg(m) +} + +func (x *eventSourcedHandleClient) Recv() (*EventSourcedStreamOut, error) { + m := new(EventSourcedStreamOut) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// EventSourcedServer is the server API for EventSourced service. +type EventSourcedServer interface { + // The stream. One stream will be established per active entity. + // Once established, the first message sent will be Init, which contains the entity ID, and, + // if the entity has previously persisted a snapshot, it will contain that snapshot. It will + // then send zero to many event messages, one for each event previously persisted. The entity + // is expected to apply these to its state in a deterministic fashion. Once all the events + // are sent, one to many commands are sent, with new commands being sent as new requests for + // the entity come in. The entity is expected to reply to each command with exactly one reply + // message. The entity should reply in order, and any events that the entity requests to be + // persisted the entity should handle itself, applying them to its own state, as if they had + // arrived as events when the event stream was being replayed on load. + Handle(EventSourced_HandleServer) error +} + +// UnimplementedEventSourcedServer can be embedded to have forward compatible implementations. +type UnimplementedEventSourcedServer struct { +} + +func (*UnimplementedEventSourcedServer) Handle(srv EventSourced_HandleServer) error { + return status.Errorf(codes.Unimplemented, "method Handle not implemented") +} + +func RegisterEventSourcedServer(s *grpc.Server, srv EventSourcedServer) { + s.RegisterService(&_EventSourced_serviceDesc, srv) +} + +func _EventSourced_Handle_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(EventSourcedServer).Handle(&eventSourcedHandleServer{stream}) +} + +type EventSourced_HandleServer interface { + Send(*EventSourcedStreamOut) error + Recv() (*EventSourcedStreamIn, error) + grpc.ServerStream +} + +type eventSourcedHandleServer struct { + grpc.ServerStream +} + +func (x *eventSourcedHandleServer) Send(m *EventSourcedStreamOut) error { + return x.ServerStream.SendMsg(m) +} + +func (x *eventSourcedHandleServer) Recv() (*EventSourcedStreamIn, error) { + m := new(EventSourcedStreamIn) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +var _EventSourced_serviceDesc = grpc.ServiceDesc{ + ServiceName: "cloudstate.eventsourced.EventSourced", + HandlerType: (*EventSourcedServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "handle", + Handler: _EventSourced_Handle_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "cloudstate/event_sourced.proto", +} diff --git a/cloudstate/protocol/function.pb.go b/cloudstate/protocol/function.pb.go new file mode 100644 index 0000000..02696c2 --- /dev/null +++ b/cloudstate/protocol/function.pb.go @@ -0,0 +1,489 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: cloudstate/function.proto + +package protocol + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + any "github.com/golang/protobuf/ptypes/any" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type FunctionCommand struct { + // The name of the service this function is on. + ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // Command name + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + // The command payload. + Payload *any.Any `protobuf:"bytes,4,opt,name=payload,proto3" json:"payload,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *FunctionCommand) Reset() { *m = FunctionCommand{} } +func (m *FunctionCommand) String() string { return proto.CompactTextString(m) } +func (*FunctionCommand) ProtoMessage() {} +func (*FunctionCommand) Descriptor() ([]byte, []int) { + return fileDescriptor_876d54e6158c20c4, []int{0} +} + +func (m *FunctionCommand) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_FunctionCommand.Unmarshal(m, b) +} +func (m *FunctionCommand) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_FunctionCommand.Marshal(b, m, deterministic) +} +func (m *FunctionCommand) XXX_Merge(src proto.Message) { + xxx_messageInfo_FunctionCommand.Merge(m, src) +} +func (m *FunctionCommand) XXX_Size() int { + return xxx_messageInfo_FunctionCommand.Size(m) +} +func (m *FunctionCommand) XXX_DiscardUnknown() { + xxx_messageInfo_FunctionCommand.DiscardUnknown(m) +} + +var xxx_messageInfo_FunctionCommand proto.InternalMessageInfo + +func (m *FunctionCommand) GetServiceName() string { + if m != nil { + return m.ServiceName + } + return "" +} + +func (m *FunctionCommand) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *FunctionCommand) GetPayload() *any.Any { + if m != nil { + return m.Payload + } + return nil +} + +type FunctionReply struct { + // Types that are valid to be assigned to Response: + // *FunctionReply_Reply + // *FunctionReply_Forward + Response isFunctionReply_Response `protobuf_oneof:"response"` + SideEffects []*SideEffect `protobuf:"bytes,4,rep,name=side_effects,json=sideEffects,proto3" json:"side_effects,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *FunctionReply) Reset() { *m = FunctionReply{} } +func (m *FunctionReply) String() string { return proto.CompactTextString(m) } +func (*FunctionReply) ProtoMessage() {} +func (*FunctionReply) Descriptor() ([]byte, []int) { + return fileDescriptor_876d54e6158c20c4, []int{1} +} + +func (m *FunctionReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_FunctionReply.Unmarshal(m, b) +} +func (m *FunctionReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_FunctionReply.Marshal(b, m, deterministic) +} +func (m *FunctionReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_FunctionReply.Merge(m, src) +} +func (m *FunctionReply) XXX_Size() int { + return xxx_messageInfo_FunctionReply.Size(m) +} +func (m *FunctionReply) XXX_DiscardUnknown() { + xxx_messageInfo_FunctionReply.DiscardUnknown(m) +} + +var xxx_messageInfo_FunctionReply proto.InternalMessageInfo + +type isFunctionReply_Response interface { + isFunctionReply_Response() +} + +type FunctionReply_Reply struct { + Reply *Reply `protobuf:"bytes,2,opt,name=reply,proto3,oneof"` +} + +type FunctionReply_Forward struct { + Forward *Forward `protobuf:"bytes,3,opt,name=forward,proto3,oneof"` +} + +func (*FunctionReply_Reply) isFunctionReply_Response() {} + +func (*FunctionReply_Forward) isFunctionReply_Response() {} + +func (m *FunctionReply) GetResponse() isFunctionReply_Response { + if m != nil { + return m.Response + } + return nil +} + +func (m *FunctionReply) GetReply() *Reply { + if x, ok := m.GetResponse().(*FunctionReply_Reply); ok { + return x.Reply + } + return nil +} + +func (m *FunctionReply) GetForward() *Forward { + if x, ok := m.GetResponse().(*FunctionReply_Forward); ok { + return x.Forward + } + return nil +} + +func (m *FunctionReply) GetSideEffects() []*SideEffect { + if m != nil { + return m.SideEffects + } + return nil +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*FunctionReply) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*FunctionReply_Reply)(nil), + (*FunctionReply_Forward)(nil), + } +} + +func init() { + proto.RegisterType((*FunctionCommand)(nil), "cloudstate.function.FunctionCommand") + proto.RegisterType((*FunctionReply)(nil), "cloudstate.function.FunctionReply") +} + +func init() { proto.RegisterFile("cloudstate/function.proto", fileDescriptor_876d54e6158c20c4) } + +var fileDescriptor_876d54e6158c20c4 = []byte{ + // 383 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0x4f, 0xaf, 0xd2, 0x40, + 0x14, 0xc5, 0xa9, 0xa0, 0xe8, 0x2d, 0xfe, 0x61, 0x30, 0x08, 0xac, 0xb0, 0x71, 0x51, 0x17, 0x4e, + 0x49, 0x5d, 0xb9, 0x14, 0x23, 0xc1, 0x8d, 0x26, 0x25, 0x2e, 0x74, 0x03, 0x43, 0xe7, 0x16, 0x9b, + 0xb4, 0x33, 0xcd, 0xcc, 0x54, 0xed, 0x07, 0xf1, 0x8b, 0xf8, 0x09, 0x5f, 0x98, 0xd2, 0xf7, 0xca, + 0xcb, 0xcb, 0x5b, 0xb1, 0x3b, 0x99, 0xf3, 0x9b, 0x7b, 0xce, 0xed, 0x1f, 0x98, 0xc6, 0x99, 0x2c, + 0xb9, 0x36, 0xcc, 0x60, 0x90, 0x94, 0x22, 0x36, 0xa9, 0x14, 0xb4, 0x50, 0xd2, 0x48, 0x32, 0xba, + 0xb1, 0x68, 0x63, 0xcd, 0xa6, 0x07, 0x29, 0x0f, 0x19, 0x06, 0x16, 0xd9, 0x97, 0x49, 0xc0, 0x44, + 0x55, 0xf3, 0xb3, 0x57, 0xad, 0x51, 0x28, 0x4c, 0x6a, 0x4e, 0x86, 0xf7, 0x17, 0x9e, 0xaf, 0x4e, + 0xf7, 0x3f, 0xc9, 0x3c, 0x67, 0x82, 0x93, 0xd7, 0x30, 0xd0, 0xa8, 0x7e, 0xa7, 0x31, 0x6e, 0x05, + 0xcb, 0x71, 0xf2, 0x60, 0xee, 0xf8, 0x4f, 0x22, 0xf7, 0x74, 0xf6, 0x95, 0xe5, 0x48, 0x08, 0xf4, + 0xac, 0xd5, 0xb5, 0x96, 0xd5, 0x84, 0x42, 0xbf, 0x60, 0x55, 0x26, 0x19, 0x9f, 0xf4, 0xe6, 0x8e, + 0xef, 0x86, 0x2f, 0x69, 0xdd, 0x87, 0x36, 0x7d, 0xe8, 0x47, 0x51, 0x45, 0x0d, 0xe4, 0xfd, 0x77, + 0xe0, 0x69, 0x13, 0x1d, 0x61, 0x91, 0x55, 0xe4, 0x2d, 0x3c, 0x54, 0x47, 0x61, 0x13, 0xdd, 0x70, + 0x48, 0x5b, 0x4b, 0x5a, 0x62, 0xdd, 0x89, 0x6a, 0x82, 0x04, 0xd0, 0x4f, 0xa4, 0xfa, 0xc3, 0x14, + 0xb7, 0x1d, 0xdc, 0x70, 0xd4, 0x86, 0x57, 0xb5, 0xb5, 0xee, 0x44, 0x0d, 0x45, 0x3e, 0xc0, 0x40, + 0xa7, 0x1c, 0xb7, 0x98, 0x24, 0x18, 0x1b, 0x3d, 0xe9, 0xcd, 0xbb, 0xbe, 0x1b, 0x8e, 0xdb, 0xb7, + 0x36, 0x29, 0xc7, 0xcf, 0xd6, 0x8e, 0x5c, 0x7d, 0xad, 0xf5, 0x12, 0xe0, 0xb1, 0x42, 0x5d, 0x48, + 0xa1, 0x31, 0xfc, 0xd7, 0x85, 0xe1, 0xe6, 0x48, 0x67, 0xa8, 0x75, 0xd3, 0x9e, 0xfc, 0x00, 0xf7, + 0x17, 0x13, 0x3c, 0xc3, 0xef, 0x82, 0xa9, 0x8a, 0xbc, 0xa1, 0x77, 0xbc, 0x1d, 0x7a, 0xeb, 0x31, + 0xcf, 0xbc, 0x7b, 0x29, 0xbb, 0xaf, 0xd7, 0x21, 0x3b, 0x78, 0x51, 0x8f, 0xde, 0x18, 0x85, 0x2c, + 0x47, 0xfe, 0x45, 0x5c, 0x72, 0xbe, 0xef, 0x10, 0x06, 0xc3, 0xf3, 0x84, 0x6f, 0xa5, 0xb9, 0x64, + 0xc4, 0xc2, 0x21, 0x3b, 0x78, 0x76, 0x1e, 0x71, 0xd9, 0x15, 0x16, 0xce, 0xf2, 0x1d, 0x8c, 0x53, + 0xd9, 0xa6, 0xed, 0x37, 0x17, 0xcb, 0xec, 0x67, 0xeb, 0x4f, 0x09, 0x9a, 0xc3, 0xfd, 0x23, 0xab, + 0xde, 0x5f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x25, 0xcb, 0xb1, 0x9a, 0x62, 0x03, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// StatelessFunctionClient is the client API for StatelessFunction service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type StatelessFunctionClient interface { + HandleUnary(ctx context.Context, in *FunctionCommand, opts ...grpc.CallOption) (*FunctionReply, error) + HandleStreamedIn(ctx context.Context, opts ...grpc.CallOption) (StatelessFunction_HandleStreamedInClient, error) + HandleStreamedOut(ctx context.Context, in *FunctionCommand, opts ...grpc.CallOption) (StatelessFunction_HandleStreamedOutClient, error) + HandleStreamed(ctx context.Context, opts ...grpc.CallOption) (StatelessFunction_HandleStreamedClient, error) +} + +type statelessFunctionClient struct { + cc *grpc.ClientConn +} + +func NewStatelessFunctionClient(cc *grpc.ClientConn) StatelessFunctionClient { + return &statelessFunctionClient{cc} +} + +func (c *statelessFunctionClient) HandleUnary(ctx context.Context, in *FunctionCommand, opts ...grpc.CallOption) (*FunctionReply, error) { + out := new(FunctionReply) + err := c.cc.Invoke(ctx, "/cloudstate.function.StatelessFunction/handleUnary", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *statelessFunctionClient) HandleStreamedIn(ctx context.Context, opts ...grpc.CallOption) (StatelessFunction_HandleStreamedInClient, error) { + stream, err := c.cc.NewStream(ctx, &_StatelessFunction_serviceDesc.Streams[0], "/cloudstate.function.StatelessFunction/handleStreamedIn", opts...) + if err != nil { + return nil, err + } + x := &statelessFunctionHandleStreamedInClient{stream} + return x, nil +} + +type StatelessFunction_HandleStreamedInClient interface { + Send(*FunctionCommand) error + CloseAndRecv() (*FunctionReply, error) + grpc.ClientStream +} + +type statelessFunctionHandleStreamedInClient struct { + grpc.ClientStream +} + +func (x *statelessFunctionHandleStreamedInClient) Send(m *FunctionCommand) error { + return x.ClientStream.SendMsg(m) +} + +func (x *statelessFunctionHandleStreamedInClient) CloseAndRecv() (*FunctionReply, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(FunctionReply) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *statelessFunctionClient) HandleStreamedOut(ctx context.Context, in *FunctionCommand, opts ...grpc.CallOption) (StatelessFunction_HandleStreamedOutClient, error) { + stream, err := c.cc.NewStream(ctx, &_StatelessFunction_serviceDesc.Streams[1], "/cloudstate.function.StatelessFunction/handleStreamedOut", opts...) + if err != nil { + return nil, err + } + x := &statelessFunctionHandleStreamedOutClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type StatelessFunction_HandleStreamedOutClient interface { + Recv() (*FunctionReply, error) + grpc.ClientStream +} + +type statelessFunctionHandleStreamedOutClient struct { + grpc.ClientStream +} + +func (x *statelessFunctionHandleStreamedOutClient) Recv() (*FunctionReply, error) { + m := new(FunctionReply) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *statelessFunctionClient) HandleStreamed(ctx context.Context, opts ...grpc.CallOption) (StatelessFunction_HandleStreamedClient, error) { + stream, err := c.cc.NewStream(ctx, &_StatelessFunction_serviceDesc.Streams[2], "/cloudstate.function.StatelessFunction/handleStreamed", opts...) + if err != nil { + return nil, err + } + x := &statelessFunctionHandleStreamedClient{stream} + return x, nil +} + +type StatelessFunction_HandleStreamedClient interface { + Send(*FunctionCommand) error + Recv() (*FunctionReply, error) + grpc.ClientStream +} + +type statelessFunctionHandleStreamedClient struct { + grpc.ClientStream +} + +func (x *statelessFunctionHandleStreamedClient) Send(m *FunctionCommand) error { + return x.ClientStream.SendMsg(m) +} + +func (x *statelessFunctionHandleStreamedClient) Recv() (*FunctionReply, error) { + m := new(FunctionReply) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// StatelessFunctionServer is the server API for StatelessFunction service. +type StatelessFunctionServer interface { + HandleUnary(context.Context, *FunctionCommand) (*FunctionReply, error) + HandleStreamedIn(StatelessFunction_HandleStreamedInServer) error + HandleStreamedOut(*FunctionCommand, StatelessFunction_HandleStreamedOutServer) error + HandleStreamed(StatelessFunction_HandleStreamedServer) error +} + +// UnimplementedStatelessFunctionServer can be embedded to have forward compatible implementations. +type UnimplementedStatelessFunctionServer struct { +} + +func (*UnimplementedStatelessFunctionServer) HandleUnary(ctx context.Context, req *FunctionCommand) (*FunctionReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method HandleUnary not implemented") +} +func (*UnimplementedStatelessFunctionServer) HandleStreamedIn(srv StatelessFunction_HandleStreamedInServer) error { + return status.Errorf(codes.Unimplemented, "method HandleStreamedIn not implemented") +} +func (*UnimplementedStatelessFunctionServer) HandleStreamedOut(req *FunctionCommand, srv StatelessFunction_HandleStreamedOutServer) error { + return status.Errorf(codes.Unimplemented, "method HandleStreamedOut not implemented") +} +func (*UnimplementedStatelessFunctionServer) HandleStreamed(srv StatelessFunction_HandleStreamedServer) error { + return status.Errorf(codes.Unimplemented, "method HandleStreamed not implemented") +} + +func RegisterStatelessFunctionServer(s *grpc.Server, srv StatelessFunctionServer) { + s.RegisterService(&_StatelessFunction_serviceDesc, srv) +} + +func _StatelessFunction_HandleUnary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(FunctionCommand) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StatelessFunctionServer).HandleUnary(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloudstate.function.StatelessFunction/HandleUnary", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StatelessFunctionServer).HandleUnary(ctx, req.(*FunctionCommand)) + } + return interceptor(ctx, in, info, handler) +} + +func _StatelessFunction_HandleStreamedIn_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(StatelessFunctionServer).HandleStreamedIn(&statelessFunctionHandleStreamedInServer{stream}) +} + +type StatelessFunction_HandleStreamedInServer interface { + SendAndClose(*FunctionReply) error + Recv() (*FunctionCommand, error) + grpc.ServerStream +} + +type statelessFunctionHandleStreamedInServer struct { + grpc.ServerStream +} + +func (x *statelessFunctionHandleStreamedInServer) SendAndClose(m *FunctionReply) error { + return x.ServerStream.SendMsg(m) +} + +func (x *statelessFunctionHandleStreamedInServer) Recv() (*FunctionCommand, error) { + m := new(FunctionCommand) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _StatelessFunction_HandleStreamedOut_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(FunctionCommand) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(StatelessFunctionServer).HandleStreamedOut(m, &statelessFunctionHandleStreamedOutServer{stream}) +} + +type StatelessFunction_HandleStreamedOutServer interface { + Send(*FunctionReply) error + grpc.ServerStream +} + +type statelessFunctionHandleStreamedOutServer struct { + grpc.ServerStream +} + +func (x *statelessFunctionHandleStreamedOutServer) Send(m *FunctionReply) error { + return x.ServerStream.SendMsg(m) +} + +func _StatelessFunction_HandleStreamed_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(StatelessFunctionServer).HandleStreamed(&statelessFunctionHandleStreamedServer{stream}) +} + +type StatelessFunction_HandleStreamedServer interface { + Send(*FunctionReply) error + Recv() (*FunctionCommand, error) + grpc.ServerStream +} + +type statelessFunctionHandleStreamedServer struct { + grpc.ServerStream +} + +func (x *statelessFunctionHandleStreamedServer) Send(m *FunctionReply) error { + return x.ServerStream.SendMsg(m) +} + +func (x *statelessFunctionHandleStreamedServer) Recv() (*FunctionCommand, error) { + m := new(FunctionCommand) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +var _StatelessFunction_serviceDesc = grpc.ServiceDesc{ + ServiceName: "cloudstate.function.StatelessFunction", + HandlerType: (*StatelessFunctionServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "handleUnary", + Handler: _StatelessFunction_HandleUnary_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "handleStreamedIn", + Handler: _StatelessFunction_HandleStreamedIn_Handler, + ClientStreams: true, + }, + { + StreamName: "handleStreamedOut", + Handler: _StatelessFunction_HandleStreamedOut_Handler, + ServerStreams: true, + }, + { + StreamName: "handleStreamed", + Handler: _StatelessFunction_HandleStreamed_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "cloudstate/function.proto", +} diff --git a/cloudstate/protosupport.go b/cloudstate/protosupport.go new file mode 100644 index 0000000..d5c07d3 --- /dev/null +++ b/cloudstate/protosupport.go @@ -0,0 +1,44 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudstate + +import ( + "bytes" + "compress/gzip" + "errors" + "github.com/golang/protobuf/proto" + filedescr "github.com/golang/protobuf/protoc-gen-go/descriptor" + "io/ioutil" +) + +const protoAnyBase = "type.googleapis.com" + +func unpackFile(gz []byte) (*filedescr.FileDescriptorProto, error) { + r, err := gzip.NewReader(bytes.NewReader(gz)) + if err != nil { + return nil, errors.New("failed to open gzip reader") + } + defer r.Close() + b, err := ioutil.ReadAll(r) + if err != nil { + return nil, errors.New("failed to uncompress descriptor") + } + fd := new(filedescr.FileDescriptorProto) + if err := proto.Unmarshal(b, fd); err != nil { + return nil, errors.New("malformed FileDescriptorProto") + } + return fd, nil +} diff --git a/cloudstate/types.go b/cloudstate/types.go new file mode 100644 index 0000000..1e53adc --- /dev/null +++ b/cloudstate/types.go @@ -0,0 +1,20 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudstate + +const ( + EventSourced = "cloudstate.eventsourced.EventSourced" +) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..703a9aa --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/cloudstateio/go-support + +go 1.13 + +require ( + github.com/golang/protobuf v1.3.2 + google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 + google.golang.org/grpc v1.24.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..72cc347 --- /dev/null +++ b/go.sum @@ -0,0 +1,28 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/protobuf/example/shoppingcart/persistence/domain.proto b/protobuf/example/shoppingcart/persistence/domain.proto new file mode 100644 index 0000000..c827b80 --- /dev/null +++ b/protobuf/example/shoppingcart/persistence/domain.proto @@ -0,0 +1,27 @@ +// These are the messages that get persisted - the events, plus the current state (Cart) for snapshots. +syntax = "proto3"; + +package com.example.shoppingcart.persistence; + +option go_package = "persistence"; + +message LineItem { + string productId = 1; + string name = 2; + int32 quantity = 3; +} + +// The item added event. +message ItemAdded { + LineItem item = 1; +} + +// The item removed event. +message ItemRemoved { + string productId = 1; +} + +// The shopping cart state. +message Cart { + repeated LineItem items = 1; +} diff --git a/protobuf/example/shoppingcart/shoppingcart.proto b/protobuf/example/shoppingcart/shoppingcart.proto new file mode 100644 index 0000000..f7b3cf6 --- /dev/null +++ b/protobuf/example/shoppingcart/shoppingcart.proto @@ -0,0 +1,60 @@ +// This is the public API offered by the shopping cart entity. +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "cloudstate/entity_key.proto"; +import "google/api/annotations.proto"; +import "google/api/http.proto"; + +package com.example.shoppingcart; + +option go_package = "tck/shoppingcart"; + +message AddLineItem { + string user_id = 1 [(.cloudstate.entity_key) = true]; + string product_id = 2; + string name = 3; + int32 quantity = 4; +} + +message RemoveLineItem { + string user_id = 1 [(.cloudstate.entity_key) = true]; + string product_id = 2; +} + +message GetShoppingCart { + string user_id = 1 [(.cloudstate.entity_key) = true]; +} + +message LineItem { + string product_id = 1; + string name = 2; + int32 quantity = 3; +} + +message Cart { + repeated LineItem items = 1; +} + +service ShoppingCart { + rpc AddItem(AddLineItem) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/cart/{user_id}/items/add", + body: "*", + }; + } + + rpc RemoveItem(RemoveLineItem) returns (google.protobuf.Empty) { + option (google.api.http).post = "/cart/{user_id}/items/{product_id}/remove"; + } + + rpc GetCart(GetShoppingCart) returns (Cart) { + option (google.api.http) = { + get: "/carts/{user_id}", + additional_bindings: { + get: "/carts/{user_id}/items", + response_body: "items" + } + }; + } +} diff --git a/protobuf/frontend/cloudstate/entity_key.proto b/protobuf/frontend/cloudstate/entity_key.proto new file mode 100644 index 0000000..d208121 --- /dev/null +++ b/protobuf/frontend/cloudstate/entity_key.proto @@ -0,0 +1,30 @@ +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Extension for specifying which field in a message is to be considered an +// entity key, for the purposes associating gRPC calls with entities and +// sharding. + +syntax = "proto3"; + +import "google/protobuf/descriptor.proto"; + +package cloudstate; + +option java_package = "io.cloudstate"; +option go_package = "github.com/cloudstateio/go-support/cloudstate/;cloudstate"; + +extend google.protobuf.FieldOptions { + bool entity_key = 50002; +} diff --git a/protobuf/frontend/google/api/annotations.proto b/protobuf/frontend/google/api/annotations.proto new file mode 100644 index 0000000..85c361b --- /dev/null +++ b/protobuf/frontend/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/protobuf/frontend/google/api/http.proto b/protobuf/frontend/google/api/http.proto new file mode 100644 index 0000000..b2977f5 --- /dev/null +++ b/protobuf/frontend/google/api/http.proto @@ -0,0 +1,376 @@ +// Copyright 2019 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/protobuf/protocol/cloudstate/crdt.proto b/protobuf/protocol/cloudstate/crdt.proto new file mode 100644 index 0000000..691242e --- /dev/null +++ b/protobuf/protocol/cloudstate/crdt.proto @@ -0,0 +1,379 @@ +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// gRPC interface for Event Sourced Entity user functions. + +syntax = "proto3"; + +package cloudstate.crdt; + +// Any is used so that domain events defined according to the functions business domain can be embedded inside +// the protocol. +import "google/protobuf/any.proto"; +import "cloudstate/entity.proto"; + +option java_package = "io.cloudstate.protocol"; +option go_package = "cloudstate/protocol"; + +// CRDT Protocol +// +// Note that while this protocol provides support for CRDTs, the data types sent across the protocol are not CRDTs +// themselves. It is the responsibility of the CloudState proxy to implement the CRDTs, merge functions, vector clocks +// etc, not the user function. The user function need only hold the current value in memory, and this protocol sends +// deltas to the user function to update its in memory value as necessary. These deltas have no way of dealing with +// conflicts, hence it important that the CloudState proxy always know what the state of the user functions in memory +// value is before sending a delta. If the CloudState proxy is not sure what the value is, eg because it has just sent +// an operation to the user function may have updated its value as a result, the proxy should wait until it gets the +// result of the operation back, to ensure its in memory value is in sync with the user function so that it can +// calculate deltas that won't conflict. +// +// The user function is expected to update its value both as the result of receiving deltas from the proxy, as well as +// when it sends deltas. It must not update its value in any other circumstance, updating the value in response to any +// other stimuli risks the value becoming out of sync with the CloudState proxy. The user function will not be sent +// back deltas as a result of its own changes. +// +// An invocation of handle is made for each entity being handled. It may be kept alive and used to handle multiple +// commands, and may subsequently be terminated if that entity becomes idle, or if the entity is deleted. Shutdown is +// typically done for efficiency reasons, unless the entity is explicitly deleted, a terminated handle stream does not +// mean the proxy has stopped tracking the state of the entity in its memory. +// +// Special care must be taken when working with maps and sets. The keys/values are google.protobuf.Any, which encodes +// the value as binary protobuf, however, serialized protobufs are not stable, two semantically equal objects could +// encode to different bytes. It is the responsibility of the user function to ensure that stable encodings are used. +service Crdt { + + // After invoking handle, the first message sent will always be a CrdtInit message, containing the entity ID, and, + // if it exists or is available, the current state of the entity. After that, one or more commands may be sent, + // as well as deltas as they arrive, and the entire state if either the entity is created, or the proxy wishes the + // user function to replace its entire state. + // + // The user function must respond with one reply per command in. They do not necessarily have to be sent in the same + // order that the commands were sent, the command ID is used to correlate commands to replies. + rpc handle(stream CrdtStreamIn) returns (stream CrdtStreamOut); +} + +// Message for the Crdt handle stream in. +message CrdtStreamIn { + oneof message { + + // Always sent first, and only once. + CrdtInit init = 1; + + // Sent to indicate the user function should replace its current state with this state. If the user function + // does not have a current state, either because the init function didn't send one and the user function hasn't + // updated the state itself in response to a command, or because the state was deleted, this must be sent before + // any deltas. + CrdtState state = 2; + + // A delta to be applied to the current state. May be sent at any time as long as the user function already has + // state. + CrdtDelta changed = 3; + + // Delete the entity. May be sent at any time. The user function should clear its state when it receives this. + // A proxy may decide to terminate the stream after sending this. + CrdtDelete deleted = 4; + + // A command, may be sent at any time. + Command command = 5; + + // A stream has been cancelled. + StreamCancelled stream_cancelled = 6; + } +} + +// Message for the Crdt handle stream out. +message CrdtStreamOut { + oneof message { + // A reply to an incoming command. Either one reply, or one failure, must be sent in response to each command. + CrdtReply reply = 1; + // A streamed message. + CrdtStreamedMessage streamed_message = 2; + // A stream cancelled response, may be sent in response to stream_cancelled. + CrdtStreamCancelledResponse stream_cancelled_response = 3; + // A failure. Either sent in response to a command, or sent if some other error occurs. + Failure failure = 4; + } +} + +// The CRDT state. This represents the full state of a CRDT. When received, a user function should replace the current +// state with this, not apply it as a delta. This includes both for the top level CRDT, and embedded CRDTs, such as +// the values of an ORMap. +message CrdtState { + oneof state { + // A Grow-only Counter + GCounterState gcounter = 1; + + // A Positve-Negative Counter + PNCounterState pncounter = 2; + + // A Grow-only Set + GSetState gset = 3; + + // An Observed-Removed Set + ORSetState orset = 4; + + // A Last-Write-Wins Register + LWWRegisterState lwwregister = 5; + + // A Flag + FlagState flag = 6; + + // An Observed-Removed Map + ORMapState ormap = 7; + + // A vote + VoteState vote = 8; + } +} + +// A Grow-only counter +// +// A G-Counter can only be incremented, it can't be decremented. +message GCounterState { + + // The current value of the counter. + uint64 value = 1; +} + +// A Positve-Negative Counter +// +// A PN-Counter can be both incremented and decremented. +message PNCounterState { + + // The current value of the counter. + int64 value = 1; +} + +// A Grow-only Set +// +// A G-Set can only have items added, items cannot be removed. +message GSetState { + + // The current items in the set. + repeated google.protobuf.Any items = 1; +} + +// An Observed-Removed Set +// +// An OR-Set may have items added and removed, with the condition that an item must be observed to be in the set before +// it is removed. +message ORSetState { + + // The current items in the set. + repeated google.protobuf.Any items = 1; +} + +// A Last-Write-Wins Register +// +// A LWW-Register holds a single value, with the current value being selected based on when it was last written. +// The time of the last write may either be determined using the proxies clock, or may be based on a custom, domain +// specific value. +message LWWRegisterState { + + // The current value of the register. + google.protobuf.Any value = 1; + + // The clock to use if this state needs to be merged with another one. + CrdtClock clock = 2; + + // The clock value if the clock in use is a custom clock. + int64 custom_clock_value = 3; +} + +// A Flag +// +// A Flag is a boolean value, that once set to true, stays true. +message FlagState { + + // The current value of the flag. + bool value = 1; +} + +// An Observed-Removed Map +// +// Like an OR-Set, an OR-Map may have items added and removed, with the condition that an item must be observed to be +// in the map before it is removed. The values of the map are CRDTs themselves. Different keys are allowed to use +// different CRDTs, and if an item is removed, and then replaced, the new value may be a different CRDT. +message ORMapState { + + // The entries of the map. + repeated ORMapEntry entries = 1; +} + +// An OR-Map entry. +message ORMapEntry { + + // The entry key. + google.protobuf.Any key = 1; + + // The value of the entry, a CRDT itself. + CrdtState value = 2; +} + +// A Vote. This allows nodes to vote on something. +message VoteState { + + // The number of votes for + uint32 votes_for = 1; + + // The total number of voters + uint32 total_voters = 2; + + // The vote of the current node, which is included in the above two numbers + bool self_vote = 3; +} + +// A CRDT delta +// +// Deltas only carry the change in value, not the full value (unless +message CrdtDelta { + oneof delta { + GCounterDelta gcounter = 1; + PNCounterDelta pncounter = 2; + GSetDelta gset = 3; + ORSetDelta orset = 4; + LWWRegisterDelta lwwregister = 5; + FlagDelta flag = 6; + ORMapDelta ormap = 7; + VoteDelta vote = 8; + } +} + +message GCounterDelta { + uint64 increment = 1; +} + +message PNCounterDelta { + sint64 change = 1; +} + +message GSetDelta { + repeated google.protobuf.Any added = 1; +} + +message ORSetDelta { + // If cleared is set, the set must be cleared before added is processed. + bool cleared = 1; + repeated google.protobuf.Any removed = 2; + repeated google.protobuf.Any added = 3; +} + +message LWWRegisterDelta { + google.protobuf.Any value = 1; + CrdtClock clock = 2; + int64 custom_clock_value = 3; +} + +message FlagDelta { + bool value = 1; +} + +message ORMapDelta { + bool cleared = 1; + repeated google.protobuf.Any removed = 2; + repeated ORMapEntryDelta updated = 3; + repeated ORMapEntry added = 4; +} + +message ORMapEntryDelta { + // The entry key. + google.protobuf.Any key = 1; + + CrdtDelta delta = 2; +} + +message VoteDelta { + // Only set by the user function to change the nodes current vote. + bool self_vote = 1; + + // Only set by the proxy to change the votes for and total voters. + int32 votes_for = 2; + int32 total_voters = 3; +} + +message CrdtInit { + string service_name = 1; + string entity_id = 2; + CrdtState state = 3; +} + +message CrdtDelete { +} + +message CrdtReply { + + int64 command_id = 1; + + ClientAction client_action = 2; + + repeated SideEffect side_effects = 4; + + CrdtStateAction state_action = 5; + + // If the request was streamed, setting this to true indicates that the command should + // be handled as a stream. Subsequently, the user function may send CrdtStreamedMessage, + // and a CrdtStreamCancelled message will be sent if the stream is cancelled (though + // not if the a CrdtStreamedMessage ends the stream first). + bool streamed = 6; +} + +message CrdtStateAction { + oneof action { + CrdtState create = 5; + CrdtDelta update = 6; + CrdtDelete delete = 7; + } + + CrdtWriteConsistency write_consistency = 8; +} + +// May be sent as often as liked if the first reply set streamed to true +message CrdtStreamedMessage { + + int64 command_id = 1; + + ClientAction client_action = 2; + + repeated SideEffect side_effects = 3; + + // Indicates the stream should end, no messages may be sent for this command after this. + bool end_stream = 4; +} + +message CrdtStreamCancelledResponse { + int64 command_id = 1; + + repeated SideEffect side_effects = 2; + + CrdtStateAction state_action = 3; +} + +enum CrdtWriteConsistency { + LOCAL = 0; + MAJORITY = 1; + ALL = 2; +} + +enum CrdtClock { + // Use the default clock for deciding the last write, which is the system clocks + // milliseconds since epoch. + DEFAULT = 0; + // Use the reverse semantics with the default clock, to enable first write wins. + REVERSE = 1; + // Use a custom clock value, set using custom_clock_value. + CUSTOM = 2; + // Use a custom clock value, but automatically increment it by one if the clock + // value from the current value is equal to the custom_clock_value. + CUSTOM_AUTO_INCREMENT = 3; +} diff --git a/protobuf/protocol/cloudstate/entity.proto b/protobuf/protocol/cloudstate/entity.proto new file mode 100644 index 0000000..05cd7be --- /dev/null +++ b/protobuf/protocol/cloudstate/entity.proto @@ -0,0 +1,191 @@ +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// gRPC interface for Event Sourced Entity user functions. + +syntax = "proto3"; + +package cloudstate; + +// Any is used so that domain events defined according to the functions business domain can be embedded inside +// the protocol. +import "google/protobuf/any.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/descriptor.proto"; + +option java_package = "io.cloudstate.protocol"; +option go_package = "cloudstate/protocol"; + +// A reply to the sender. +message Reply { + // The reply payload + google.protobuf.Any payload = 1; +} + +// Forwards handling of this request to another entity. +message Forward { + // The name of the service to forward to. + string service_name = 1; + // The name of the command. + string command_name = 2; + // The payload. + google.protobuf.Any payload = 3; +} + +// An action for the client +message ClientAction { + oneof action { + + // Send a reply + Reply reply = 1; + + // Forward to another entity + Forward forward = 2; + + // Send a failure to the client + Failure failure = 3; + } +} + +// A side effect to be done after this command is handled. +message SideEffect { + + // The name of the service to perform the side effect on. + string service_name = 1; + + // The name of the command. + string command_name = 2; + + // The payload of the command. + google.protobuf.Any payload = 3; + + // Whether this side effect should be performed synchronously, ie, before the reply is eventually + // sent, or not. + bool synchronous = 4; +} + +// A command. For each command received, a reply must be sent with a matching command id. +message Command { + + // The ID of the entity. + string entity_id = 1; + + // A command id. + int64 id = 2; + + // Command name + string name = 3; + + // The command payload. + google.protobuf.Any payload = 4; + + // Whether the command is streamed or not + bool streamed = 5; +} + +message StreamCancelled { + + // The ID of the entity + string entity_id = 1; + + // The command id + int64 id = 2; +} + +// A failure reply. If this is returned, it will be translated into a gRPC unknown +// error with the corresponding description if supplied. +message Failure { + + // The id of the command being replied to. Must match the input command. + int64 command_id = 1; + + // A description of the error. + string description = 2; +} + +message EntitySpec { + // This should be the Descriptors.FileDescriptorSet in proto serialized from as generated by: + // protoc --include_imports \ + // --proto_path= \ + // --descriptor_set_out=user-function.desc \ + // + bytes proto = 1; + + // The entities being served. + repeated Entity entities = 2; + + // Optional information about the service. + ServiceInfo service_info = 3; +} + +// Information about the service that proxy is proxying to. +// All of the information in here is optional. It may be useful for debug purposes. +message ServiceInfo { + + // The name of the service, eg, "shopping-cart". + string service_name = 1; + + // The version of the service. + string service_version = 2; + + // A description of the runtime for the service. Can be anything, but examples might be: + // - node v10.15.2 + // - OpenJDK Runtime Environment 1.8.0_192-b12 + string service_runtime = 3; + + // If using a support library, the name of that library, eg "cloudstate" + string support_library_name = 4; + + // The version of the support library being used. + string support_library_version = 5; +} + +message Entity { + + // The type of entity. By convention, this should be a fully qualified entity protocol grpc + // service name, for example, cloudstate.eventsourced.EventSourced. + string entity_type = 1; + + // The name of the service to load from the protobuf file. + string service_name = 2; + + // The ID to namespace state by. How this is used depends on the type of entity, for example, + // event sourced entities will prefix this to the persistence id. + string persistence_id = 3; +} + +message UserFunctionError { + string message = 1; +} + +message ProxyInfo { + int32 protocol_major_version = 1; + int32 protocol_minor_version = 2; + string proxy_name = 3; + string proxy_version = 4; + repeated string supported_entity_types = 5; +} + +// Entity discovery service. +service EntityDiscovery { + + // Discover what entities the user function wishes to serve. + rpc discover(ProxyInfo) returns (EntitySpec) {} + + // Report an error back to the user function. This will only be invoked to tell the user function + // that it has done something wrong, eg, violated the protocol, tried to use an entity type that + // isn't supported, or attempted to forward to an entity that doesn't exist, etc. These messages + // should be logged clearly for debugging purposes. + rpc reportError(UserFunctionError) returns (google.protobuf.Empty) {} +} diff --git a/protobuf/protocol/cloudstate/event_sourced.proto b/protobuf/protocol/cloudstate/event_sourced.proto new file mode 100644 index 0000000..0417f14 --- /dev/null +++ b/protobuf/protocol/cloudstate/event_sourced.proto @@ -0,0 +1,115 @@ +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// gRPC interface for Event Sourced Entity user functions. + +syntax = "proto3"; + +package cloudstate.eventsourced; + +// Any is used so that domain events defined according to the functions business domain can be embedded inside +// the protocol. +import "google/protobuf/any.proto"; +import "cloudstate/entity.proto"; + +option java_package = "io.cloudstate.protocol"; +option go_package = "cloudstate/protocol"; + +// The init message. This will always be the first message sent to the entity when +// it is loaded. +message EventSourcedInit { + + string service_name = 1; + + // The ID of the entity. + string entity_id = 2; + + // If present the entity should initialise its state using this snapshot. + EventSourcedSnapshot snapshot = 3; +} + +// A snapshot +message EventSourcedSnapshot { + + // The sequence number when the snapshot was taken. + int64 snapshot_sequence = 1; + + // The snapshot. + google.protobuf.Any snapshot = 2; +} + +// An event. These will be sent to the entity when the entity starts up. +message EventSourcedEvent { + + // The sequence number of the event. + int64 sequence = 1; + + // The event payload. + google.protobuf.Any payload = 2; +} + +// A reply to a command. +message EventSourcedReply { + + // The id of the command being replied to. Must match the input command. + int64 command_id = 1; + + // The action to take + ClientAction client_action = 2; + + // Any side effects to perform + repeated SideEffect side_effects = 3; + + // A list of events to persist - these will be persisted before the reply + // is sent. + repeated google.protobuf.Any events = 4; + + // An optional snapshot to persist. It is assumed that this snapshot will have + // the state of any events in the events field applied to it. It is illegal to + // send a snapshot without sending any events. + google.protobuf.Any snapshot = 5; +} + +// Input message type for the gRPC stream in. +message EventSourcedStreamIn { + oneof message { + EventSourcedInit init = 1; + EventSourcedEvent event = 2; + Command command = 3; + } +} + +// Output message type for the gRPC stream out. +message EventSourcedStreamOut { + oneof message { + EventSourcedReply reply = 1; + Failure failure = 2; + } +} + +// The Entity service +service EventSourced { + + // The stream. One stream will be established per active entity. + // Once established, the first message sent will be Init, which contains the entity ID, and, + // if the entity has previously persisted a snapshot, it will contain that snapshot. It will + // then send zero to many event messages, one for each event previously persisted. The entity + // is expected to apply these to its state in a deterministic fashion. Once all the events + // are sent, one to many commands are sent, with new commands being sent as new requests for + // the entity come in. The entity is expected to reply to each command with exactly one reply + // message. The entity should reply in order, and any events that the entity requests to be + // persisted the entity should handle itself, applying them to its own state, as if they had + // arrived as events when the event stream was being replayed on load. + rpc handle(stream EventSourcedStreamIn) returns (stream EventSourcedStreamOut) {} +} diff --git a/protobuf/protocol/cloudstate/function.proto b/protobuf/protocol/cloudstate/function.proto new file mode 100644 index 0000000..3ca581b --- /dev/null +++ b/protobuf/protocol/cloudstate/function.proto @@ -0,0 +1,60 @@ +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// gRPC interface for Event Sourced Entity user functions. + +syntax = "proto3"; + +package cloudstate.function; + +// Any is used so that domain events defined according to the functions business domain can be embedded inside +// the protocol. +import "google/protobuf/any.proto"; +import "cloudstate/entity.proto"; + +option java_package = "io.cloudstate.protocol"; +option go_package = "cloudstate/protocol"; + +message FunctionCommand { + // The name of the service this function is on. + string service_name = 2; + + // Command name + string name = 3; + + // The command payload. + google.protobuf.Any payload = 4; +} + +message FunctionReply { + + oneof response { + Reply reply = 2; + Forward forward = 3; + } + + repeated SideEffect side_effects = 4; +} + +service StatelessFunction { + + rpc handleUnary(FunctionCommand) returns (FunctionReply) {} + + rpc handleStreamedIn(stream FunctionCommand) returns (FunctionReply) {} + + rpc handleStreamedOut(FunctionCommand) returns (stream FunctionReply) {} + + rpc handleStreamed(stream FunctionCommand) returns (stream FunctionReply) {} + +} diff --git a/protobuf/proxy/grpc/reflection/v1alpha/reflection.proto b/protobuf/proxy/grpc/reflection/v1alpha/reflection.proto new file mode 100644 index 0000000..816852f --- /dev/null +++ b/protobuf/proxy/grpc/reflection/v1alpha/reflection.proto @@ -0,0 +1,136 @@ +// Copyright 2016 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Service exported by server reflection + +syntax = "proto3"; + +package grpc.reflection.v1alpha; + +service ServerReflection { + // The reflection service is structured as a bidirectional stream, ensuring + // all related requests go to a single server. + rpc ServerReflectionInfo(stream ServerReflectionRequest) + returns (stream ServerReflectionResponse); +} + +// The message sent by the client when calling ServerReflectionInfo method. +message ServerReflectionRequest { + string host = 1; + // To use reflection service, the client should set one of the following + // fields in message_request. The server distinguishes requests by their + // defined field and then handles them using corresponding methods. + oneof message_request { + // Find a proto file by the file name. + string file_by_filename = 3; + + // Find the proto file that declares the given fully-qualified symbol name. + // This field should be a fully-qualified symbol name + // (e.g. .[.] or .). + string file_containing_symbol = 4; + + // Find the proto file which defines an extension extending the given + // message type with the given field number. + ExtensionRequest file_containing_extension = 5; + + // Finds the tag numbers used by all known extensions of the given message + // type, and appends them to ExtensionNumberResponse in an undefined order. + // Its corresponding method is best-effort: it's not guaranteed that the + // reflection service will implement this method, and it's not guaranteed + // that this method will provide all extensions. Returns + // StatusCode::UNIMPLEMENTED if it's not implemented. + // This field should be a fully-qualified type name. The format is + // . + string all_extension_numbers_of_type = 6; + + // List the full names of registered services. The content will not be + // checked. + string list_services = 7; + } +} + +// The type name and extension number sent by the client when requesting +// file_containing_extension. +message ExtensionRequest { + // Fully-qualified type name. The format should be . + string containing_type = 1; + int32 extension_number = 2; +} + +// The message sent by the server to answer ServerReflectionInfo method. +message ServerReflectionResponse { + string valid_host = 1; + ServerReflectionRequest original_request = 2; + // The server set one of the following fields accroding to the message_request + // in the request. + oneof message_response { + // This message is used to answer file_by_filename, file_containing_symbol, + // file_containing_extension requests with transitive dependencies. As + // the repeated label is not allowed in oneof fields, we use a + // FileDescriptorResponse message to encapsulate the repeated fields. + // The reflection service is allowed to avoid sending FileDescriptorProtos + // that were previously sent in response to earlier requests in the stream. + FileDescriptorResponse file_descriptor_response = 4; + + // This message is used to answer all_extension_numbers_of_type requst. + ExtensionNumberResponse all_extension_numbers_response = 5; + + // This message is used to answer list_services request. + ListServiceResponse list_services_response = 6; + + // This message is used when an error occurs. + ErrorResponse error_response = 7; + } +} + +// Serialized FileDescriptorProto messages sent by the server answering +// a file_by_filename, file_containing_symbol, or file_containing_extension +// request. +message FileDescriptorResponse { + // Serialized FileDescriptorProto messages. We avoid taking a dependency on + // descriptor.proto, which uses proto2 only features, by making them opaque + // bytes instead. + repeated bytes file_descriptor_proto = 1; +} + +// A list of extension numbers sent by the server answering +// all_extension_numbers_of_type request. +message ExtensionNumberResponse { + // Full name of the base type, including the package name. The format + // is . + string base_type_name = 1; + repeated int32 extension_number = 2; +} + +// A list of ServiceResponse sent by the server answering list_services request. +message ListServiceResponse { + // The information of each service may be expanded in the future, so we use + // ServiceResponse message to encapsulate it. + repeated ServiceResponse service = 1; +} + +// The information of a single service used by ListServiceResponse to answer +// list_services request. +message ServiceResponse { + // Full name of a registered service, including its package name. The format + // is . + string name = 1; +} + +// The error code and error message sent by the server when an error occurs. +message ErrorResponse { + // This field uses the error codes defined in grpc::StatusCode. + int32 error_code = 1; + string error_message = 2; +} diff --git a/tck/cmd/tck_shoppingcart/shoppingcart.go b/tck/cmd/tck_shoppingcart/shoppingcart.go new file mode 100644 index 0000000..0741b92 --- /dev/null +++ b/tck/cmd/tck_shoppingcart/shoppingcart.go @@ -0,0 +1,199 @@ +// +// Copyright 2019 Lightbend Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package main implements an event sourced entity shopping cart example +package main + +import ( + "context" + "errors" + "fmt" + "log" + + "github.com/cloudstateio/go-support/cloudstate" + "github.com/cloudstateio/go-support/tck/shoppingcart" + domain "github.com/cloudstateio/go-support/tck/shoppingcart/persistence" + "github.com/golang/protobuf/ptypes/empty" +) + +// main creates a CloudState instance and registers the ShoppingCart +// as a event sourced entity. +func main() { + cloudState, err := cloudstate.New(cloudstate.Options{ + ServiceName: "shopping-cart", + ServiceVersion: "0.1.0", + }) + if err != nil { + log.Fatalf("CloudState.New failed: %v", err) + } + err = cloudState.RegisterEventSourcedEntity( + &cloudstate.EventSourcedEntity{ + Entity: (*ShoppingCart)(nil), + ServiceName: "com.example.shoppingcart.ShoppingCart", + }, + cloudstate.DescriptorConfig{ + Service: "shoppingcart/shoppingcart.proto", + }.AddDomainDescriptor("domain.proto"), + ) + + if err != nil { + log.Fatalf("CloudState failed to register entity: %v", err) + } + err = cloudState.Run() + if err != nil { + log.Fatalf("CloudState failed to run: %v", err) + } +} + +// A CloudState event sourced entity. +type ShoppingCart struct { + // our domain object + cart []*domain.LineItem + // as an Emitter we can emit events + cloudstate.EventEmitter +} + +// New implements EntityInitializer and returns a new +// and initialized instance of the ShoppingCart entity. +func (sc ShoppingCart) New() interface{} { + return NewShoppingCart() +} + +// NewShoppingCart returns a new and initialized +// instance of the ShoppingCart entity. +func NewShoppingCart() *ShoppingCart { + return &ShoppingCart{ + cart: make([]*domain.LineItem, 0), + EventEmitter: cloudstate.NewEmitter(), // TODO: the EventEmitter could be provided by the event sourced handler + } +} + +// ItemAdded is a event handler function for the ItemAdded event. +func (sc *ShoppingCart) ItemAdded(added *domain.ItemAdded) error { // TODO: enable handling for values + if item, _ := sc.find(added.Item.ProductId); item != nil { + item.Quantity += added.Item.Quantity + } else { + sc.cart = append(sc.cart, &domain.LineItem{ + ProductId: added.Item.ProductId, + Name: added.Item.Name, + Quantity: added.Item.Quantity, + }) + } + return nil +} + +// ItemRemoved is a event handler function for the ItemRemoved event. +func (sc *ShoppingCart) ItemRemoved(removed *domain.ItemRemoved) error { + if !sc.remove(removed.ProductId) { + // this should never happen + return errors.New("unable to remove product") + } + return nil +} + +// Handle lets us handle events by ourselves. +// +// returns handle set to true if we have handled the event +// and any error that happened during the handling +func (sc *ShoppingCart) HandleEvent(event interface{}) (handled bool, err error) { + switch e := event.(type) { + case *domain.ItemAdded: + return true, sc.ItemAdded(e) + //case *domain.ItemRemoved: + // *domain.ItemRemoved is handled by reflection + default: + return false, nil + } +} + +// AddItem implements the AddItem command handling of the shopping cart service. +func (sc *ShoppingCart) AddItem(c context.Context, li *shoppingcart.AddLineItem) (*empty.Empty, error) { + if li.GetQuantity() <= 0 { + return nil, fmt.Errorf("cannot add negative quantity of to item %s", li.GetProductId()) + } + sc.Emit(&domain.ItemAdded{ + Item: &domain.LineItem{ + ProductId: li.ProductId, + Name: li.Name, + Quantity: li.Quantity, + }}) + return &empty.Empty{}, nil +} + +// RemoveItem implements the RemoveItem command handling of the shopping cart service. +func (sc *ShoppingCart) RemoveItem(c context.Context, li *shoppingcart.RemoveLineItem) (*empty.Empty, error) { + if item, _ := sc.find(li.GetProductId()); item == nil { + return nil, fmt.Errorf("cannot remove item %s because it is not in the cart", li.GetProductId()) + } + sc.Emit(&domain.ItemRemoved{ProductId: li.ProductId}) + return &empty.Empty{}, nil +} + +// GetCart implements the GetCart command handling of the shopping cart service. +func (sc *ShoppingCart) GetCart(c context.Context, _ *shoppingcart.GetShoppingCart) (*shoppingcart.Cart, error) { + cart := &shoppingcart.Cart{} + for _, item := range sc.cart { + cart.Items = append(cart.Items, &shoppingcart.LineItem{ + ProductId: item.ProductId, + Name: item.Name, + Quantity: item.Quantity, + }) + } + return cart, nil +} + +func (sc *ShoppingCart) Snapshot() (snapshot interface{}, err error) { + return domain.Cart{ + Items: append(make([]*domain.LineItem, len(sc.cart)), sc.cart...), + }, nil +} + +func (sc *ShoppingCart) HandleSnapshot(snapshot interface{}) (handled bool, err error) { + switch value := snapshot.(type) { + case domain.Cart: + sc.cart = append(sc.cart[:0], value.Items...) + return true, nil + default: + return false, nil + } +} + +// find finds a product in the shopping cart by productId and returns it as a LineItem. +func (sc *ShoppingCart) find(productId string) (item *domain.LineItem, index int) { + for i, item := range sc.cart { + if productId == item.ProductId { + return item, i + } + } + return nil, 0 +} + +// remove removes a product from the shopping cart. +// +// A ok flag is returned to indicate that the product was present and removed. +func (sc *ShoppingCart) remove(productId string) (ok bool) { + if item, i := sc.find(productId); item != nil { + // remove and re-slice + copy(sc.cart[i:], sc.cart[i+1:]) + sc.cart = sc.cart[:len(sc.cart)-1] + return true + } else { + return false + } +} + +func init() { + log.SetFlags(log.LstdFlags | log.Lmicroseconds) +} diff --git a/tck/shoppingcart/persistence/domain.pb.go b/tck/shoppingcart/persistence/domain.pb.go new file mode 100644 index 0000000..a689beb --- /dev/null +++ b/tck/shoppingcart/persistence/domain.pb.go @@ -0,0 +1,223 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: domain.proto + +package persistence + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type LineItem struct { + ProductId string `protobuf:"bytes,1,opt,name=productId,proto3" json:"productId,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Quantity int32 `protobuf:"varint,3,opt,name=quantity,proto3" json:"quantity,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *LineItem) Reset() { *m = LineItem{} } +func (m *LineItem) String() string { return proto.CompactTextString(m) } +func (*LineItem) ProtoMessage() {} +func (*LineItem) Descriptor() ([]byte, []int) { + return fileDescriptor_73e6234e76dbdb84, []int{0} +} + +func (m *LineItem) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_LineItem.Unmarshal(m, b) +} +func (m *LineItem) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_LineItem.Marshal(b, m, deterministic) +} +func (m *LineItem) XXX_Merge(src proto.Message) { + xxx_messageInfo_LineItem.Merge(m, src) +} +func (m *LineItem) XXX_Size() int { + return xxx_messageInfo_LineItem.Size(m) +} +func (m *LineItem) XXX_DiscardUnknown() { + xxx_messageInfo_LineItem.DiscardUnknown(m) +} + +var xxx_messageInfo_LineItem proto.InternalMessageInfo + +func (m *LineItem) GetProductId() string { + if m != nil { + return m.ProductId + } + return "" +} + +func (m *LineItem) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *LineItem) GetQuantity() int32 { + if m != nil { + return m.Quantity + } + return 0 +} + +// The item added event. +type ItemAdded struct { + Item *LineItem `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ItemAdded) Reset() { *m = ItemAdded{} } +func (m *ItemAdded) String() string { return proto.CompactTextString(m) } +func (*ItemAdded) ProtoMessage() {} +func (*ItemAdded) Descriptor() ([]byte, []int) { + return fileDescriptor_73e6234e76dbdb84, []int{1} +} + +func (m *ItemAdded) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ItemAdded.Unmarshal(m, b) +} +func (m *ItemAdded) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ItemAdded.Marshal(b, m, deterministic) +} +func (m *ItemAdded) XXX_Merge(src proto.Message) { + xxx_messageInfo_ItemAdded.Merge(m, src) +} +func (m *ItemAdded) XXX_Size() int { + return xxx_messageInfo_ItemAdded.Size(m) +} +func (m *ItemAdded) XXX_DiscardUnknown() { + xxx_messageInfo_ItemAdded.DiscardUnknown(m) +} + +var xxx_messageInfo_ItemAdded proto.InternalMessageInfo + +func (m *ItemAdded) GetItem() *LineItem { + if m != nil { + return m.Item + } + return nil +} + +// The item removed event. +type ItemRemoved struct { + ProductId string `protobuf:"bytes,1,opt,name=productId,proto3" json:"productId,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ItemRemoved) Reset() { *m = ItemRemoved{} } +func (m *ItemRemoved) String() string { return proto.CompactTextString(m) } +func (*ItemRemoved) ProtoMessage() {} +func (*ItemRemoved) Descriptor() ([]byte, []int) { + return fileDescriptor_73e6234e76dbdb84, []int{2} +} + +func (m *ItemRemoved) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ItemRemoved.Unmarshal(m, b) +} +func (m *ItemRemoved) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ItemRemoved.Marshal(b, m, deterministic) +} +func (m *ItemRemoved) XXX_Merge(src proto.Message) { + xxx_messageInfo_ItemRemoved.Merge(m, src) +} +func (m *ItemRemoved) XXX_Size() int { + return xxx_messageInfo_ItemRemoved.Size(m) +} +func (m *ItemRemoved) XXX_DiscardUnknown() { + xxx_messageInfo_ItemRemoved.DiscardUnknown(m) +} + +var xxx_messageInfo_ItemRemoved proto.InternalMessageInfo + +func (m *ItemRemoved) GetProductId() string { + if m != nil { + return m.ProductId + } + return "" +} + +// The shopping cart state. +type Cart struct { + Items []*LineItem `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cart) Reset() { *m = Cart{} } +func (m *Cart) String() string { return proto.CompactTextString(m) } +func (*Cart) ProtoMessage() {} +func (*Cart) Descriptor() ([]byte, []int) { + return fileDescriptor_73e6234e76dbdb84, []int{3} +} + +func (m *Cart) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cart.Unmarshal(m, b) +} +func (m *Cart) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cart.Marshal(b, m, deterministic) +} +func (m *Cart) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cart.Merge(m, src) +} +func (m *Cart) XXX_Size() int { + return xxx_messageInfo_Cart.Size(m) +} +func (m *Cart) XXX_DiscardUnknown() { + xxx_messageInfo_Cart.DiscardUnknown(m) +} + +var xxx_messageInfo_Cart proto.InternalMessageInfo + +func (m *Cart) GetItems() []*LineItem { + if m != nil { + return m.Items + } + return nil +} + +func init() { + proto.RegisterType((*LineItem)(nil), "com.example.shoppingcart.persistence.LineItem") + proto.RegisterType((*ItemAdded)(nil), "com.example.shoppingcart.persistence.ItemAdded") + proto.RegisterType((*ItemRemoved)(nil), "com.example.shoppingcart.persistence.ItemRemoved") + proto.RegisterType((*Cart)(nil), "com.example.shoppingcart.persistence.Cart") +} + +func init() { proto.RegisterFile("domain.proto", fileDescriptor_73e6234e76dbdb84) } + +var fileDescriptor_73e6234e76dbdb84 = []byte{ + // 223 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x90, 0x3f, 0x4b, 0x04, 0x31, + 0x10, 0xc5, 0x89, 0xb7, 0x27, 0xb7, 0xb3, 0xda, 0xa4, 0x0a, 0x62, 0xb1, 0x04, 0x8b, 0x03, 0x21, + 0x85, 0x7e, 0x02, 0x4f, 0x9b, 0x85, 0x05, 0x61, 0x2b, 0xb1, 0x8b, 0xc9, 0xa0, 0x01, 0xf3, 0xc7, + 0x64, 0x56, 0xf4, 0xdb, 0xcb, 0x46, 0xfc, 0x53, 0x89, 0x5c, 0x37, 0x6f, 0x1e, 0xef, 0xcd, 0x8f, + 0x81, 0x23, 0x1b, 0xbd, 0x76, 0x41, 0xa5, 0x1c, 0x29, 0xf2, 0x33, 0x13, 0xbd, 0xc2, 0x37, 0xed, + 0xd3, 0x33, 0xaa, 0xf2, 0x14, 0x53, 0x72, 0xe1, 0xd1, 0xe8, 0x4c, 0x2a, 0x61, 0x2e, 0xae, 0x10, + 0x06, 0x83, 0xf2, 0x0e, 0x36, 0xa3, 0x0b, 0x38, 0x10, 0x7a, 0x7e, 0x0a, 0x6d, 0xca, 0xd1, 0xce, + 0x86, 0x06, 0x2b, 0x58, 0xcf, 0xb6, 0xed, 0xf4, 0xb3, 0xe0, 0x1c, 0x9a, 0xa0, 0x3d, 0x8a, 0x83, + 0x6a, 0xd4, 0x99, 0x9f, 0xc0, 0xe6, 0x65, 0xd6, 0x81, 0x1c, 0xbd, 0x8b, 0x55, 0xcf, 0xb6, 0xeb, + 0xe9, 0x5b, 0xcb, 0x5b, 0x68, 0x97, 0xd6, 0x2b, 0x6b, 0xd1, 0xf2, 0x1d, 0x34, 0x8e, 0xd0, 0xd7, + 0xd6, 0xee, 0x42, 0xa9, 0xff, 0xb0, 0xa9, 0x2f, 0xb0, 0xa9, 0x66, 0xe5, 0x39, 0x74, 0x55, 0xa1, + 0x8f, 0xaf, 0x68, 0xff, 0xa6, 0x95, 0x23, 0x34, 0xd7, 0x3a, 0x13, 0xbf, 0x81, 0xf5, 0x12, 0x2e, + 0x82, 0xf5, 0xab, 0x3d, 0x2e, 0x7f, 0x86, 0x77, 0xc7, 0xf7, 0xdd, 0x2f, 0xfb, 0xe1, 0xb0, 0x7e, + 0xf8, 0xf2, 0x23, 0x00, 0x00, 0xff, 0xff, 0xc7, 0xe9, 0xbb, 0x2b, 0x71, 0x01, 0x00, 0x00, +} diff --git a/tck/shoppingcart/shoppingcart.pb.go b/tck/shoppingcart/shoppingcart.pb.go new file mode 100644 index 0000000..1e4037d --- /dev/null +++ b/tck/shoppingcart/shoppingcart.pb.go @@ -0,0 +1,466 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: shoppingcart/shoppingcart.proto + +package shoppingcart + +import ( + context "context" + fmt "fmt" + _ "github.com/cloudstateio/go-support/cloudstate/protocol" + proto "github.com/golang/protobuf/proto" + empty "github.com/golang/protobuf/ptypes/empty" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type AddLineItem struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + ProductId string `protobuf:"bytes,2,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Quantity int32 `protobuf:"varint,4,opt,name=quantity,proto3" json:"quantity,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *AddLineItem) Reset() { *m = AddLineItem{} } +func (m *AddLineItem) String() string { return proto.CompactTextString(m) } +func (*AddLineItem) ProtoMessage() {} +func (*AddLineItem) Descriptor() ([]byte, []int) { + return fileDescriptor_230c614558c2d7f8, []int{0} +} + +func (m *AddLineItem) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_AddLineItem.Unmarshal(m, b) +} +func (m *AddLineItem) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_AddLineItem.Marshal(b, m, deterministic) +} +func (m *AddLineItem) XXX_Merge(src proto.Message) { + xxx_messageInfo_AddLineItem.Merge(m, src) +} +func (m *AddLineItem) XXX_Size() int { + return xxx_messageInfo_AddLineItem.Size(m) +} +func (m *AddLineItem) XXX_DiscardUnknown() { + xxx_messageInfo_AddLineItem.DiscardUnknown(m) +} + +var xxx_messageInfo_AddLineItem proto.InternalMessageInfo + +func (m *AddLineItem) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *AddLineItem) GetProductId() string { + if m != nil { + return m.ProductId + } + return "" +} + +func (m *AddLineItem) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *AddLineItem) GetQuantity() int32 { + if m != nil { + return m.Quantity + } + return 0 +} + +type RemoveLineItem struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + ProductId string `protobuf:"bytes,2,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RemoveLineItem) Reset() { *m = RemoveLineItem{} } +func (m *RemoveLineItem) String() string { return proto.CompactTextString(m) } +func (*RemoveLineItem) ProtoMessage() {} +func (*RemoveLineItem) Descriptor() ([]byte, []int) { + return fileDescriptor_230c614558c2d7f8, []int{1} +} + +func (m *RemoveLineItem) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RemoveLineItem.Unmarshal(m, b) +} +func (m *RemoveLineItem) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RemoveLineItem.Marshal(b, m, deterministic) +} +func (m *RemoveLineItem) XXX_Merge(src proto.Message) { + xxx_messageInfo_RemoveLineItem.Merge(m, src) +} +func (m *RemoveLineItem) XXX_Size() int { + return xxx_messageInfo_RemoveLineItem.Size(m) +} +func (m *RemoveLineItem) XXX_DiscardUnknown() { + xxx_messageInfo_RemoveLineItem.DiscardUnknown(m) +} + +var xxx_messageInfo_RemoveLineItem proto.InternalMessageInfo + +func (m *RemoveLineItem) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *RemoveLineItem) GetProductId() string { + if m != nil { + return m.ProductId + } + return "" +} + +type GetShoppingCart struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetShoppingCart) Reset() { *m = GetShoppingCart{} } +func (m *GetShoppingCart) String() string { return proto.CompactTextString(m) } +func (*GetShoppingCart) ProtoMessage() {} +func (*GetShoppingCart) Descriptor() ([]byte, []int) { + return fileDescriptor_230c614558c2d7f8, []int{2} +} + +func (m *GetShoppingCart) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetShoppingCart.Unmarshal(m, b) +} +func (m *GetShoppingCart) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetShoppingCart.Marshal(b, m, deterministic) +} +func (m *GetShoppingCart) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetShoppingCart.Merge(m, src) +} +func (m *GetShoppingCart) XXX_Size() int { + return xxx_messageInfo_GetShoppingCart.Size(m) +} +func (m *GetShoppingCart) XXX_DiscardUnknown() { + xxx_messageInfo_GetShoppingCart.DiscardUnknown(m) +} + +var xxx_messageInfo_GetShoppingCart proto.InternalMessageInfo + +func (m *GetShoppingCart) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +type LineItem struct { + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Quantity int32 `protobuf:"varint,3,opt,name=quantity,proto3" json:"quantity,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *LineItem) Reset() { *m = LineItem{} } +func (m *LineItem) String() string { return proto.CompactTextString(m) } +func (*LineItem) ProtoMessage() {} +func (*LineItem) Descriptor() ([]byte, []int) { + return fileDescriptor_230c614558c2d7f8, []int{3} +} + +func (m *LineItem) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_LineItem.Unmarshal(m, b) +} +func (m *LineItem) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_LineItem.Marshal(b, m, deterministic) +} +func (m *LineItem) XXX_Merge(src proto.Message) { + xxx_messageInfo_LineItem.Merge(m, src) +} +func (m *LineItem) XXX_Size() int { + return xxx_messageInfo_LineItem.Size(m) +} +func (m *LineItem) XXX_DiscardUnknown() { + xxx_messageInfo_LineItem.DiscardUnknown(m) +} + +var xxx_messageInfo_LineItem proto.InternalMessageInfo + +func (m *LineItem) GetProductId() string { + if m != nil { + return m.ProductId + } + return "" +} + +func (m *LineItem) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *LineItem) GetQuantity() int32 { + if m != nil { + return m.Quantity + } + return 0 +} + +type Cart struct { + Items []*LineItem `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Cart) Reset() { *m = Cart{} } +func (m *Cart) String() string { return proto.CompactTextString(m) } +func (*Cart) ProtoMessage() {} +func (*Cart) Descriptor() ([]byte, []int) { + return fileDescriptor_230c614558c2d7f8, []int{4} +} + +func (m *Cart) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Cart.Unmarshal(m, b) +} +func (m *Cart) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Cart.Marshal(b, m, deterministic) +} +func (m *Cart) XXX_Merge(src proto.Message) { + xxx_messageInfo_Cart.Merge(m, src) +} +func (m *Cart) XXX_Size() int { + return xxx_messageInfo_Cart.Size(m) +} +func (m *Cart) XXX_DiscardUnknown() { + xxx_messageInfo_Cart.DiscardUnknown(m) +} + +var xxx_messageInfo_Cart proto.InternalMessageInfo + +func (m *Cart) GetItems() []*LineItem { + if m != nil { + return m.Items + } + return nil +} + +func init() { + proto.RegisterType((*AddLineItem)(nil), "com.example.shoppingcart.AddLineItem") + proto.RegisterType((*RemoveLineItem)(nil), "com.example.shoppingcart.RemoveLineItem") + proto.RegisterType((*GetShoppingCart)(nil), "com.example.shoppingcart.GetShoppingCart") + proto.RegisterType((*LineItem)(nil), "com.example.shoppingcart.LineItem") + proto.RegisterType((*Cart)(nil), "com.example.shoppingcart.Cart") +} + +func init() { proto.RegisterFile("shoppingcart/shoppingcart.proto", fileDescriptor_230c614558c2d7f8) } + +var fileDescriptor_230c614558c2d7f8 = []byte{ + // 461 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x53, 0xc1, 0x6e, 0xd3, 0x40, + 0x10, 0xd5, 0x26, 0x69, 0xd3, 0x4e, 0x11, 0x54, 0x2b, 0x51, 0x19, 0x97, 0xb6, 0xd1, 0x0a, 0xa4, + 0x14, 0x24, 0x2f, 0xb4, 0x17, 0xe0, 0x44, 0x8b, 0x50, 0x15, 0x09, 0x71, 0x30, 0x27, 0x7a, 0xa9, + 0x36, 0xde, 0x21, 0xb5, 0x1a, 0x7b, 0x8d, 0x3d, 0x46, 0x54, 0x51, 0x0f, 0xf0, 0x03, 0x20, 0xc1, + 0xaf, 0xf0, 0x25, 0xfc, 0x02, 0x1f, 0x82, 0xbc, 0x76, 0xa8, 0x13, 0xb1, 0xe2, 0xc2, 0xcd, 0x3b, + 0x6f, 0x76, 0xde, 0x7b, 0xfb, 0xc6, 0xb0, 0x57, 0x9c, 0x9b, 0x2c, 0x8b, 0xd3, 0x49, 0xa4, 0x72, + 0x92, 0xed, 0x43, 0x90, 0xe5, 0x86, 0x0c, 0xf7, 0x22, 0x93, 0x04, 0xf8, 0x51, 0x25, 0xd9, 0x14, + 0x83, 0x36, 0xee, 0x6f, 0x4f, 0x8c, 0x99, 0x4c, 0x51, 0xda, 0xbe, 0x71, 0xf9, 0x4e, 0x62, 0x92, + 0xd1, 0x65, 0x7d, 0xcd, 0xdf, 0x8e, 0xa6, 0xa6, 0xd4, 0x05, 0x29, 0x42, 0x89, 0x29, 0xc5, 0x74, + 0x79, 0x76, 0x81, 0x73, 0xf0, 0x6e, 0x73, 0x53, 0x65, 0xb1, 0x54, 0x69, 0x6a, 0x48, 0x51, 0x6c, + 0xd2, 0xa2, 0x41, 0x6f, 0xb7, 0xd0, 0x73, 0xa2, 0xac, 0x2e, 0x8b, 0x19, 0x6c, 0x1c, 0x69, 0xfd, + 0x2a, 0x4e, 0x71, 0x44, 0x98, 0xf0, 0x1d, 0xe8, 0x97, 0x05, 0xe6, 0x67, 0xb1, 0xf6, 0xd8, 0x80, + 0x0d, 0xd7, 0x8f, 0x7b, 0x5f, 0x7f, 0x78, 0x2c, 0x5c, 0xad, 0x8a, 0x23, 0xcd, 0x77, 0x00, 0xb2, + 0xdc, 0xe8, 0x32, 0xa2, 0xaa, 0xa3, 0x53, 0x75, 0x84, 0xeb, 0x4d, 0x65, 0xa4, 0x39, 0x87, 0x5e, + 0xaa, 0x12, 0xf4, 0xba, 0x16, 0xb0, 0xdf, 0xdc, 0x87, 0xb5, 0xf7, 0xa5, 0xb2, 0x5a, 0xbd, 0xde, + 0x80, 0x0d, 0x57, 0xc2, 0x3f, 0x67, 0xf1, 0x1a, 0x6e, 0x86, 0x98, 0x98, 0x0f, 0xf8, 0x7f, 0xf8, + 0xc5, 0x23, 0xb8, 0x75, 0x82, 0xf4, 0xa6, 0x79, 0xce, 0x17, 0x2a, 0xa7, 0x7f, 0x0c, 0x14, 0x6f, + 0x61, 0xad, 0xc5, 0xdd, 0x1e, 0xce, 0x5c, 0xe6, 0x3a, 0x0e, 0x73, 0xdd, 0x25, 0x73, 0xcf, 0xa1, + 0x67, 0x15, 0x3c, 0x81, 0x95, 0x98, 0x30, 0x29, 0x3c, 0x36, 0xe8, 0x0e, 0x37, 0x0e, 0x44, 0xe0, + 0x8a, 0x3e, 0x98, 0x2b, 0x09, 0xeb, 0x0b, 0x07, 0xdf, 0xbb, 0x70, 0x63, 0xc1, 0x4c, 0x0a, 0xfd, + 0x23, 0xad, 0xad, 0xd8, 0xfb, 0xee, 0x31, 0xad, 0x3c, 0xfd, 0xad, 0xa0, 0x8e, 0x3d, 0x98, 0xaf, + 0x53, 0xf0, 0xb2, 0x5a, 0x27, 0x71, 0xef, 0xf3, 0xcf, 0x5f, 0xdf, 0x3a, 0xbb, 0xe2, 0x8e, 0xb4, + 0x1b, 0x3a, 0x6b, 0xde, 0xe8, 0x4a, 0x5a, 0x66, 0xa9, 0xb4, 0x7e, 0xc6, 0x1e, 0xf0, 0x4f, 0x0c, + 0xa0, 0x0e, 0xc8, 0x72, 0x0e, 0xdd, 0x9c, 0x8b, 0x31, 0x3a, 0x69, 0x1f, 0x5b, 0xda, 0x87, 0x62, + 0xff, 0xef, 0xb4, 0xb3, 0xeb, 0xf7, 0xbf, 0x92, 0xb9, 0x1d, 0xc9, 0xbf, 0x30, 0xe8, 0x9f, 0x20, + 0x59, 0xff, 0xfb, 0x6e, 0x01, 0x4b, 0xb9, 0xfb, 0xbb, 0xee, 0xd6, 0x0a, 0x17, 0x4f, 0xad, 0x92, + 0x43, 0xbe, 0x69, 0x95, 0x14, 0xd7, 0x52, 0x4e, 0xf7, 0xf8, 0xd6, 0x72, 0xad, 0x96, 0x37, 0xae, + 0x63, 0x39, 0xe6, 0xa7, 0x9b, 0x14, 0x5d, 0x2c, 0xfc, 0xd5, 0xe3, 0x55, 0x6b, 0xf4, 0xf0, 0x77, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xee, 0x63, 0x38, 0x32, 0xf9, 0x03, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// ShoppingCartClient is the client API for ShoppingCart service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ShoppingCartClient interface { + AddItem(ctx context.Context, in *AddLineItem, opts ...grpc.CallOption) (*empty.Empty, error) + RemoveItem(ctx context.Context, in *RemoveLineItem, opts ...grpc.CallOption) (*empty.Empty, error) + GetCart(ctx context.Context, in *GetShoppingCart, opts ...grpc.CallOption) (*Cart, error) +} + +type shoppingCartClient struct { + cc *grpc.ClientConn +} + +func NewShoppingCartClient(cc *grpc.ClientConn) ShoppingCartClient { + return &shoppingCartClient{cc} +} + +func (c *shoppingCartClient) AddItem(ctx context.Context, in *AddLineItem, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/com.example.shoppingcart.ShoppingCart/AddItem", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *shoppingCartClient) RemoveItem(ctx context.Context, in *RemoveLineItem, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/com.example.shoppingcart.ShoppingCart/RemoveItem", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *shoppingCartClient) GetCart(ctx context.Context, in *GetShoppingCart, opts ...grpc.CallOption) (*Cart, error) { + out := new(Cart) + err := c.cc.Invoke(ctx, "/com.example.shoppingcart.ShoppingCart/GetCart", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ShoppingCartServer is the server API for ShoppingCart service. +type ShoppingCartServer interface { + AddItem(context.Context, *AddLineItem) (*empty.Empty, error) + RemoveItem(context.Context, *RemoveLineItem) (*empty.Empty, error) + GetCart(context.Context, *GetShoppingCart) (*Cart, error) +} + +// UnimplementedShoppingCartServer can be embedded to have forward compatible implementations. +type UnimplementedShoppingCartServer struct { +} + +func (*UnimplementedShoppingCartServer) AddItem(ctx context.Context, req *AddLineItem) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddItem not implemented") +} +func (*UnimplementedShoppingCartServer) RemoveItem(ctx context.Context, req *RemoveLineItem) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemoveItem not implemented") +} +func (*UnimplementedShoppingCartServer) GetCart(ctx context.Context, req *GetShoppingCart) (*Cart, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCart not implemented") +} + +func RegisterShoppingCartServer(s *grpc.Server, srv ShoppingCartServer) { + s.RegisterService(&_ShoppingCart_serviceDesc, srv) +} + +func _ShoppingCart_AddItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddLineItem) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ShoppingCartServer).AddItem(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/com.example.shoppingcart.ShoppingCart/AddItem", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ShoppingCartServer).AddItem(ctx, req.(*AddLineItem)) + } + return interceptor(ctx, in, info, handler) +} + +func _ShoppingCart_RemoveItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RemoveLineItem) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ShoppingCartServer).RemoveItem(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/com.example.shoppingcart.ShoppingCart/RemoveItem", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ShoppingCartServer).RemoveItem(ctx, req.(*RemoveLineItem)) + } + return interceptor(ctx, in, info, handler) +} + +func _ShoppingCart_GetCart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetShoppingCart) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ShoppingCartServer).GetCart(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/com.example.shoppingcart.ShoppingCart/GetCart", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ShoppingCartServer).GetCart(ctx, req.(*GetShoppingCart)) + } + return interceptor(ctx, in, info, handler) +} + +var _ShoppingCart_serviceDesc = grpc.ServiceDesc{ + ServiceName: "com.example.shoppingcart.ShoppingCart", + HandlerType: (*ShoppingCartServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "AddItem", + Handler: _ShoppingCart_AddItem_Handler, + }, + { + MethodName: "RemoveItem", + Handler: _ShoppingCart_RemoveItem_Handler, + }, + { + MethodName: "GetCart", + Handler: _ShoppingCart_GetCart_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "shoppingcart/shoppingcart.proto", +}