diff --git a/README.md b/README.md index 37cb1fc..70deaaf 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,39 @@ # Weir public protocol -Shared public schemas, generated Go types, validation, and owned DNS helpers for -Weir servers and clients. Requires Go 1.27.1. +Shared public schemas, generated Go types and validation for Weir servers and +clients. Requires Go 1.27.1. ```sh -go get github.com/batchstream/weir-protocol@v0.14.0 +go get github.com/batchstream/weir-protocol@v0.15.0 ``` The dependency direction is one-way: Weir and the Go SDK depend on this module. This module depends on neither repository, including through test dependencies. -It contains no peer discovery protocol, server lifecycle, backend adapter, or SDK. +It contains no server lifecycle, backend adapter, or SDK. -- `api/weir/v1`: unary `weir.v1.StoreService.ResolveStore` and bidirectional - `Execute` for Read, Mutate, and Native. -- `api/protocol`: common envelope, endpoint, resource, record, and Native validation. -- `api/netlimit`: standard Go DNS transport with joined cancellation. +- `api/weir/v1`: the bidirectional `weir.v1.StoreService.Execute` RPC for Read, + Mutate, and Native. Clients send it to a Weir server that hosts the Store; any + other server fails the RPC with NOT_FOUND. +- `api/protocol`: common envelope, Store name, resource, record, and Native + validation. The public schema lives in `api/weir/v1/store.proto`. Servers and SDKs consume the module's generated Go types. ## Streaming semantics -Execute selects one Store and operation kind for its lifetime. Each Read or Mutate -request carries one record. Servers aggregate admitted records into database -batches; clients send and consume incrementally without collecting a wire batch. -There is no Weir-imposed item or payload-size limit on business calls or individual -records. Backend protocols govern accepted documents and commands; gRPC carries -one request or event per message. Native carries a required opaque Document. -NativeHead carries optional opaque metadata and a content type for subsequent -body chunks. Request and metadata bytes are opaque to the public protocol. Their content types select adapter-owned -formats; new adapters need no protobuf fields or schema changes. Empty request -data is permitted. Unknown valid content types are left to the selected adapter. +Execute selects one Store and operation kind for its lifetime. Each Read or +Mutate request carries one record. Servers aggregate admitted records into +database batches; clients send and consume incrementally without collecting a +wire batch. The protocol imposes no item or payload-size limit on business calls +or individual records; servers may bound the size of one message. Backend +protocols govern accepted documents and commands; gRPC carries one request or +event per message. Native carries a required opaque Document. NativeHead carries +optional opaque metadata and a content type for subsequent body chunks. Request +and metadata bytes are opaque to the public protocol. Their content types select +adapter-owned formats; new adapters need no protobuf fields or schema changes. +Empty request data is permitted. Unknown valid content types are left to the +selected adapter. Each request is validated before execution. Earlier requests may have effects when a later request fails validation. The first record index is 1; every later @@ -142,8 +145,8 @@ Prepare the complete module cache in a temporary module copy before disabling network access. `download all` includes graph/test metadata that lazy runtime downloads omit; its extra ZIP checksums stay outside the committed `go.sum`. -Default tests use task-owned loopback DNS and in-memory protocol data. They do not -contact external endpoints, start databases, or change host DNS. Descriptor tests +Default tests use in-memory protocol data. They do not contact external +endpoints or start databases. Descriptor tests protect the public RPC and field contract. The dependency check examines every selected module, including transitive and test requirements, rejects Server/SDK dependencies and replacements, examines diff --git a/api/netlimit/dns.go b/api/netlimit/dns.go deleted file mode 100644 index d5707ef..0000000 --- a/api/netlimit/dns.go +++ /dev/null @@ -1,125 +0,0 @@ -// Package netlimit provides owned standard Go DNS I/O for clients and servers. -// Callers retain lifecycle, concurrency and address selection. -package netlimit - -import ( - "context" - "errors" - "net" - "strings" - "sync" - "time" -) - -// LookupHost joins the pure-Go A/AAAA queries and closes all owned sockets before -// returning. The caller must provide a deadline; there is no cache or worker. -func LookupHost(ctx context.Context, base *net.Resolver, host string) ([]string, error) { - if _, ok := ctx.Deadline(); !ok { - return nil, errors.New("DNS requires a deadline") - } - ctx, cancel := context.WithCancel(ctx) - transport := &dnsTransport{ctx: ctx, base: base, conns: make(map[*dnsConn]struct{})} - stop := context.AfterFunc(ctx, transport.close) - defer func() { stop(); cancel(); transport.close() }() - native := &net.Resolver{PreferGo: true, StrictErrors: true, Dial: transport.dial} - // Match Go's host-file canonicalization: a bare name such as localhost - // is not stored with a trailing dot. Qualified names stay absolute. - if strings.Contains(host, ".") && !strings.HasSuffix(host, ".") { - host += "." - } - ips, err := native.LookupHost(ctx, host) - if err != nil || ctx.Err() != nil || len(ips) == 0 { - return nil, errors.New("DNS failed or returned no addresses") - } - return ips, nil -} - -// A lookup can issue A and AAAA concurrently. Cancellation closes their sockets, -// and close waits for all owned I/O, so LookupHost returns only after it ends. -type dnsTransport struct { - ctx context.Context - base *net.Resolver - mu sync.Mutex - conns map[*dnsConn]struct{} - io sync.WaitGroup -} - -func (d *dnsTransport) dial(ctx context.Context, network, address string) (net.Conn, error) { - d.mu.Lock() - if d.ctx.Err() != nil { - d.mu.Unlock() - return nil, errors.New("DNS I/O closed") - } - d.io.Add(1) - d.mu.Unlock() - ctx, cancel := context.WithCancel(ctx) - stop := context.AfterFunc(d.ctx, cancel) - defer func() { stop(); cancel() }() - var conn net.Conn - var err error - if d.base != nil && d.base.Dial != nil { - conn, err = d.base.Dial(ctx, network, address) - } else { - dialer := net.Dialer{Timeout: 2 * time.Second} - conn, err = dialer.DialContext(ctx, network, address) - } - d.mu.Lock() - defer d.mu.Unlock() - if err == nil && d.ctx.Err() != nil { - _ = conn.Close() - err = d.ctx.Err() - } - if err != nil { - d.io.Done() - return nil, err - } - wrapped := &dnsConn{Conn: conn, owner: d} - d.conns[wrapped] = struct{}{} - // net.Resolver detects PacketConn to choose DNS's UDP framing. - if packet, ok := conn.(net.PacketConn); ok { - udp := &dnsPacketConn{dnsConn: wrapped, packet: packet} - return udp, nil - } - return wrapped, nil -} - -func (d *dnsTransport) close() { - d.mu.Lock() - conns := make([]*dnsConn, 0, len(d.conns)) - for conn := range d.conns { - conns = append(conns, conn) - } - d.mu.Unlock() - for _, conn := range conns { - _ = conn.Close() - } - d.io.Wait() -} - -type dnsConn struct { - net.Conn - owner *dnsTransport - once sync.Once - err error -} - -func (c *dnsConn) Close() error { - c.once.Do(func() { - c.err = c.Conn.Close() - c.owner.mu.Lock() - delete(c.owner.conns, c) - c.owner.mu.Unlock() - c.owner.io.Done() - }) - return c.err -} - -type dnsPacketConn struct { - *dnsConn - packet net.PacketConn -} - -func (c *dnsPacketConn) ReadFrom(p []byte) (int, net.Addr, error) { return c.packet.ReadFrom(p) } -func (c *dnsPacketConn) WriteTo(p []byte, addr net.Addr) (int, error) { - return c.packet.WriteTo(p, addr) -} diff --git a/api/netlimit/dns_test.go b/api/netlimit/dns_test.go deleted file mode 100644 index d2594fa..0000000 --- a/api/netlimit/dns_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package netlimit - -import ( - "context" - "net/netip" - "testing" - "time" - - "github.com/batchstream/weir-protocol/internal/testutil/testdns" -) - -func TestLookupHostReturnsLargeTCPAnswer(t *testing.T) { - dns := testdns.Start(t) - addresses := make([]netip.Addr, 100) - for i := range addresses { - value := [4]byte{127, 0, 0, byte(i + 1)} - addresses[i] = netip.AddrFrom4(value) - } - answer := testdns.Answer{Addresses: addresses, TCP: true} - dns.Set("replicas.test", answer) - ctx, cancel := context.WithTimeout(t.Context(), time.Second) - defer cancel() - ips, err := LookupHost(ctx, dns.Resolver(), "replicas.test") - if err != nil || len(ips) != len(addresses) { - t.Fatal("large DNS answer rejected", len(ips), err) - } -} diff --git a/api/protocol/endpoints.go b/api/protocol/endpoints.go deleted file mode 100644 index de9a4be..0000000 --- a/api/protocol/endpoints.go +++ /dev/null @@ -1,73 +0,0 @@ -package protocol - -import ( - "errors" - "net" - "net/netip" - "slices" - "strconv" - "strings" - "time" -) - -const ( - MaxDiscoveryCacheTTL = 30 * time.Second -) - -func ValidStoreName(name string) bool { - return storePattern.MatchString(name) -} - -// CanonicalEndpoint accepts portable IP or DNS host:port addresses without I/O. -func CanonicalEndpoint(value string) (string, error) { - if strings.ContainsAny(value, "/@?#%\\ \t\r\n") { - return "", errors.New("invalid discovery endpoint") - } - host, port, err := net.SplitHostPort(value) - number, portErr := strconv.Atoi(port) - if err != nil || portErr != nil || strings.Trim(port, "0123456789") != "" || number < 1 || number > 65535 { - return "", errors.New("discovery endpoint requires host and explicit port 1-65535") - } - if ip, err := netip.ParseAddr(host); err == nil { - ip = ip.Unmap() - if ip.IsUnspecified() || ip.IsMulticast() || ip.Zone() != "" { - return "", errors.New("discovery endpoint must be reachable unicast") - } - host = ip.String() - } else { - host = strings.TrimSuffix(strings.ToLower(host), ".") - if len(host) < 1 || len(host) > 253 || strings.Trim(host, "0123456789.") == "" || strings.ContainsAny(value, "[]") { - return "", errors.New("invalid discovery DNS name") - } - for _, label := range strings.Split(host, ".") { - if len(label) < 1 || len(label) > 63 || label[0] == '-' || label[len(label)-1] == '-' { - return "", errors.New("invalid discovery DNS label") - } - for _, c := range label { - if !(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-') { - return "", errors.New("discovery DNS name requires ASCII labels") - } - } - } - } - return net.JoinHostPort(host, strconv.Itoa(number)), nil -} - -func CanonicalEndpoints(input []string) ([]string, error) { - if len(input) < 1 { - return nil, errors.New("discovery requires at least one endpoint") - } - out := make([]string, 0, len(input)) - for _, value := range input { - address, err := CanonicalEndpoint(value) - if err != nil { - return nil, err - } - out = append(out, address) - } - slices.Sort(out) - if len(slices.Compact(out)) != len(out) { - return nil, errors.New("duplicate discovery endpoint") - } - return out, nil -} diff --git a/api/protocol/endpoints_test.go b/api/protocol/endpoints_test.go deleted file mode 100644 index 1f27eed..0000000 --- a/api/protocol/endpoints_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package protocol - -import ( - "fmt" - "slices" - "strings" - "testing" -) - -func TestPortableEndpointValidation(t *testing.T) { - input := []string{"EXAMPLE.com.:07447", "[::ffff:127.0.0.1]:7447", "[0:0::1]:7447"} - original := slices.Clone(input) - targets, err := CanonicalEndpoints(input) - want := []string{"127.0.0.1:7447", "[::1]:7447", "example.com:7447"} - if err != nil || !slices.Equal(targets, want) { - t.Fatal(targets, err) - } - if !slices.Equal(input, original) { - t.Fatal("canonicalization modified its input", input) - } - for _, bad := range []string{"0.0.0.0:1", "[::ffff:0.0.0.0]:1", "[::ffff:224.0.0.1]:1", "224.0.0.1:1", "[ff02::1]:1", "[::]:1", "[fe80::1%en0]:1", "bad:0", "dns:///bad:1", "bad:65536", "bad:1/path", "bad..name:1", "[bad]:1", "bad:-1", "_name:1", "中文:1", "peer :1"} { - if _, err := CanonicalEndpoint(bad); err == nil { - t.Errorf("accepted %q", bad) - } - } - duplicate := []string{"example.com:1", "EXAMPLE.COM.:01"} - original = slices.Clone(duplicate) - if targets, err := CanonicalEndpoints(duplicate); err == nil || targets != nil { - t.Fatal("canonical duplicate accepted") - } - if !slices.Equal(duplicate, original) { - t.Fatal("duplicate rejection modified its input", duplicate) - } -} - -func TestCanonicalEndpointsAcceptsLargeSetsAndRejectsEmpty(t *testing.T) { - endpoints := make([]string, 1000) - for i := range endpoints { - endpoints[i] = fmt.Sprintf("replica-%04d.example:7447", i) - } - canonical, err := CanonicalEndpoints(endpoints) - if err != nil || len(canonical) != len(endpoints) { - t.Fatal("large endpoint set rejected", len(canonical), err) - } - if _, err := CanonicalEndpoints(nil); err == nil { - t.Fatal("empty endpoint set accepted") - } -} - -func TestDiscoveryStoreNames(t *testing.T) { - for _, name := range []string{"data", "mongo-1", strings.Repeat("a", 128)} { - if !ValidStoreName(name) { - t.Errorf("valid Store name rejected: %q", name) - } - } - for _, name := range []string{"", "Data", "data/path", "data?filter", "data:7447", "a-", "weir://data"} { - if ValidStoreName(name) { - t.Errorf("invalid Store name accepted: %q", name) - } - } -} diff --git a/api/protocol/protocol.go b/api/protocol/protocol.go index 980febf..ab55eee 100644 --- a/api/protocol/protocol.go +++ b/api/protocol/protocol.go @@ -23,6 +23,12 @@ const MaxTransportMessageBytes = int(^uint(0) >> 1) var storePattern = regexp.MustCompile(`^[a-z](?:[a-z0-9]|-[a-z0-9])*$`) var mediaPattern = regexp.MustCompile(`^[a-z0-9!#$&^_.+-]+/[a-z0-9!#$&^_.+-]+$`) +// ValidStoreName reports whether name is a well-formed Store name: a lowercase +// letter followed by lowercase letters, digits and single internal hyphens. +func ValidStoreName(name string) bool { + return storePattern.MatchString(name) +} + // ParseRelativeResource validates a canonical Store-relative path and returns its // decoded segments. Store identity is supplied separately by the request envelope. func ParseRelativeResource(resource string) ([]string, error) { diff --git a/api/protocol/protocol_test.go b/api/protocol/protocol_test.go index 1309942..2120e49 100644 --- a/api/protocol/protocol_test.go +++ b/api/protocol/protocol_test.go @@ -10,6 +10,19 @@ import ( "google.golang.org/protobuf/reflect/protoreflect" ) +func TestStoreNames(t *testing.T) { + for _, name := range []string{"data", "mongo-1", strings.Repeat("a", 128)} { + if !ValidStoreName(name) { + t.Errorf("valid Store name rejected: %q", name) + } + } + for _, name := range []string{"", "Data", "1data", "a--b", "data/path", "data?filter", "data:7447", "a-", "weir://data"} { + if ValidStoreName(name) { + t.Errorf("invalid Store name accepted: %q", name) + } + } +} + func TestCanonicalRelativeResource(t *testing.T) { valid := []string{"db/c/s:a%2Fb", "a/s:%E4%B8%AD", "a/i:-3", "records"} for _, resource := range valid { diff --git a/api/weir/v1/contract_test.go b/api/weir/v1/contract_test.go index 8be6098..52eaee0 100644 --- a/api/weir/v1/contract_test.go +++ b/api/weir/v1/contract_test.go @@ -51,24 +51,16 @@ func TestPublicStoreContract(t *testing.T) { if service == nil { t.Fatal("public Store service missing") } - if service.Methods().Len() != 2 || len(StoreService_ServiceDesc.Streams) != 1 || StoreService_ServiceDesc.Streams[0].StreamName != "Execute" { + if service.Methods().Len() != 1 || len(StoreService_ServiceDesc.Methods) != 0 || len(StoreService_ServiceDesc.Streams) != 1 || StoreService_ServiceDesc.Streams[0].StreamName != "Execute" { t.Fatal("public service must expose exactly one Execute RPC") } - resolve := service.Methods().ByName("ResolveStore") execute := service.Methods().ByName("Execute") - if resolve == nil || resolve.Input().FullName() != "weir.v1.ResolveStoreRequest" || resolve.Output().FullName() != "weir.v1.ResolveStoreResponse" || resolve.IsStreamingClient() || resolve.IsStreamingServer() { - t.Fatal("ResolveStore unary contract changed", resolve) - } if execute == nil || execute.Input().FullName() != "weir.v1.ExecuteRequest" || execute.Output().FullName() != "weir.v1.ExecuteResponse" || !execute.IsStreamingClient() || !execute.IsStreamingServer() { t.Fatal("Execute bidirectional contract changed", execute) } - if StoreService_ResolveStore_FullMethodName != "/weir.v1.StoreService/ResolveStore" || StoreService_Execute_FullMethodName != "/weir.v1.StoreService/Execute" { - t.Fatal("generated public method paths changed") + if StoreService_Execute_FullMethodName != "/weir.v1.StoreService/Execute" { + t.Fatal("generated public method path changed") } - request := []fieldContract{{name: "store_name", kind: protoreflect.StringKind}} - assertFields(t, resolve.Input(), request) - response := []fieldContract{{name: "store_name", kind: protoreflect.StringKind}, {name: "endpoints", kind: protoreflect.StringKind, repeated: true}, {name: "cache_ttl_ms", kind: protoreflect.Uint64Kind}} - assertFields(t, resolve.Output(), response) executeRequest := []fieldContract{{name: "store_name", kind: protoreflect.StringKind}, {name: "index", kind: protoreflect.Uint64Kind}, {name: "command", kind: protoreflect.MessageKind, message: "weir.v1.Command"}} assertFields(t, execute.Input(), executeRequest) executeResponse := []fieldContract{{name: "index", kind: protoreflect.Uint64Kind}, {name: "event", kind: protoreflect.MessageKind, message: "weir.v1.Event"}} diff --git a/api/weir/v1/store.pb.go b/api/weir/v1/store.pb.go index 6b05431..c91745b 100644 --- a/api/weir/v1/store.pb.go +++ b/api/weir/v1/store.pb.go @@ -287,117 +287,7 @@ func (NativeCompletion) EnumDescriptor() ([]byte, []int) { return file_api_weir_v1_store_proto_rawDescGZIP(), []int{3} } -// ResolveStoreRequest selects the Store whose owner endpoints are required. -type ResolveStoreRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Configured Store name, independent of the deployment platform. - StoreName string `protobuf:"bytes,1,opt,name=store_name,json=storeName,proto3" json:"store_name,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ResolveStoreRequest) Reset() { - *x = ResolveStoreRequest{} - mi := &file_api_weir_v1_store_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ResolveStoreRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ResolveStoreRequest) ProtoMessage() {} - -func (x *ResolveStoreRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ResolveStoreRequest.ProtoReflect.Descriptor instead. -func (*ResolveStoreRequest) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{0} -} - -func (x *ResolveStoreRequest) GetStoreName() string { - if x != nil { - return x.StoreName - } - return "" -} - -// ResolveStoreResponse supplies addresses for direct execution connections. -type ResolveStoreResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The Store selected by the request. - StoreName string `protobuf:"bytes,1,opt,name=store_name,json=storeName,proto3" json:"store_name,omitempty"` - // Portable IP or DNS host:port addresses. DNS may identify several replicas. - Endpoints []string `protobuf:"bytes,2,rep,name=endpoints,proto3" json:"endpoints,omitempty"` - // Maximum client cache lifetime for this endpoint set, in milliseconds. - CacheTtlMs uint64 `protobuf:"varint,3,opt,name=cache_ttl_ms,json=cacheTtlMs,proto3" json:"cache_ttl_ms,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ResolveStoreResponse) Reset() { - *x = ResolveStoreResponse{} - mi := &file_api_weir_v1_store_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ResolveStoreResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ResolveStoreResponse) ProtoMessage() {} - -func (x *ResolveStoreResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ResolveStoreResponse.ProtoReflect.Descriptor instead. -func (*ResolveStoreResponse) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{1} -} - -func (x *ResolveStoreResponse) GetStoreName() string { - if x != nil { - return x.StoreName - } - return "" -} - -func (x *ResolveStoreResponse) GetEndpoints() []string { - if x != nil { - return x.Endpoints - } - return nil -} - -func (x *ResolveStoreResponse) GetCacheTtlMs() uint64 { - if x != nil { - return x.CacheTtlMs - } - return 0 -} - -// ExecuteRequest binds one operation to its owning Store and index. +// ExecuteRequest binds one operation to its Store and index. type ExecuteRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Store that owns all resources; must remain identical throughout the stream. @@ -413,7 +303,7 @@ type ExecuteRequest struct { func (x *ExecuteRequest) Reset() { *x = ExecuteRequest{} - mi := &file_api_weir_v1_store_proto_msgTypes[2] + mi := &file_api_weir_v1_store_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -425,7 +315,7 @@ func (x *ExecuteRequest) String() string { func (*ExecuteRequest) ProtoMessage() {} func (x *ExecuteRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[2] + mi := &file_api_weir_v1_store_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -438,7 +328,7 @@ func (x *ExecuteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteRequest.ProtoReflect.Descriptor instead. func (*ExecuteRequest) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{2} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{0} } func (x *ExecuteRequest) GetStoreName() string { @@ -476,7 +366,7 @@ type ExecuteResponse struct { func (x *ExecuteResponse) Reset() { *x = ExecuteResponse{} - mi := &file_api_weir_v1_store_proto_msgTypes[3] + mi := &file_api_weir_v1_store_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -488,7 +378,7 @@ func (x *ExecuteResponse) String() string { func (*ExecuteResponse) ProtoMessage() {} func (x *ExecuteResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[3] + mi := &file_api_weir_v1_store_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -501,7 +391,7 @@ func (x *ExecuteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteResponse.ProtoReflect.Descriptor instead. func (*ExecuteResponse) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{3} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{1} } func (x *ExecuteResponse) GetIndex() uint64 { @@ -535,7 +425,7 @@ type Command struct { func (x *Command) Reset() { *x = Command{} - mi := &file_api_weir_v1_store_proto_msgTypes[4] + mi := &file_api_weir_v1_store_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -547,7 +437,7 @@ func (x *Command) String() string { func (*Command) ProtoMessage() {} func (x *Command) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[4] + mi := &file_api_weir_v1_store_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -560,7 +450,7 @@ func (x *Command) ProtoReflect() protoreflect.Message { // Deprecated: Use Command.ProtoReflect.Descriptor instead. func (*Command) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{4} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{2} } func (x *Command) GetOperation() isCommand_Operation { @@ -638,7 +528,7 @@ type NativeRequest struct { func (x *NativeRequest) Reset() { *x = NativeRequest{} - mi := &file_api_weir_v1_store_proto_msgTypes[5] + mi := &file_api_weir_v1_store_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -650,7 +540,7 @@ func (x *NativeRequest) String() string { func (*NativeRequest) ProtoMessage() {} func (x *NativeRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[5] + mi := &file_api_weir_v1_store_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -663,7 +553,7 @@ func (x *NativeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NativeRequest.ProtoReflect.Descriptor instead. func (*NativeRequest) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{5} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{3} } func (x *NativeRequest) GetResource() string { @@ -699,7 +589,7 @@ type Event struct { func (x *Event) Reset() { *x = Event{} - mi := &file_api_weir_v1_store_proto_msgTypes[6] + mi := &file_api_weir_v1_store_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -711,7 +601,7 @@ func (x *Event) String() string { func (*Event) ProtoMessage() {} func (x *Event) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[6] + mi := &file_api_weir_v1_store_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -724,7 +614,7 @@ func (x *Event) ProtoReflect() protoreflect.Message { // Deprecated: Use Event.ProtoReflect.Descriptor instead. func (*Event) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{6} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{4} } func (x *Event) GetValue() isEvent_Value { @@ -827,7 +717,7 @@ type Empty struct { func (x *Empty) Reset() { *x = Empty{} - mi := &file_api_weir_v1_store_proto_msgTypes[7] + mi := &file_api_weir_v1_store_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -839,7 +729,7 @@ func (x *Empty) String() string { func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[7] + mi := &file_api_weir_v1_store_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -852,7 +742,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message { // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{7} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{5} } // Document binds opaque bytes to an explicit content type; adapters own their format. @@ -868,7 +758,7 @@ type Document struct { func (x *Document) Reset() { *x = Document{} - mi := &file_api_weir_v1_store_proto_msgTypes[8] + mi := &file_api_weir_v1_store_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -880,7 +770,7 @@ func (x *Document) String() string { func (*Document) ProtoMessage() {} func (x *Document) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[8] + mi := &file_api_weir_v1_store_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -893,7 +783,7 @@ func (x *Document) ProtoReflect() protoreflect.Message { // Deprecated: Use Document.ProtoReflect.Descriptor instead. func (*Document) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{8} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{6} } func (x *Document) GetContentType() string { @@ -923,7 +813,7 @@ type Failure struct { func (x *Failure) Reset() { *x = Failure{} - mi := &file_api_weir_v1_store_proto_msgTypes[9] + mi := &file_api_weir_v1_store_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -935,7 +825,7 @@ func (x *Failure) String() string { func (*Failure) ProtoMessage() {} func (x *Failure) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[9] + mi := &file_api_weir_v1_store_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -948,7 +838,7 @@ func (x *Failure) ProtoReflect() protoreflect.Message { // Deprecated: Use Failure.ProtoReflect.Descriptor instead. func (*Failure) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{9} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{7} } func (x *Failure) GetCode() FailureCode { @@ -976,7 +866,7 @@ type ReadRequest struct { func (x *ReadRequest) Reset() { *x = ReadRequest{} - mi := &file_api_weir_v1_store_proto_msgTypes[10] + mi := &file_api_weir_v1_store_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -988,7 +878,7 @@ func (x *ReadRequest) String() string { func (*ReadRequest) ProtoMessage() {} func (x *ReadRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[10] + mi := &file_api_weir_v1_store_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1001,7 +891,7 @@ func (x *ReadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReadRequest.ProtoReflect.Descriptor instead. func (*ReadRequest) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{10} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{8} } func (x *ReadRequest) GetResource() string { @@ -1028,7 +918,7 @@ type ReadResult struct { func (x *ReadResult) Reset() { *x = ReadResult{} - mi := &file_api_weir_v1_store_proto_msgTypes[11] + mi := &file_api_weir_v1_store_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1040,7 +930,7 @@ func (x *ReadResult) String() string { func (*ReadResult) ProtoMessage() {} func (x *ReadResult) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[11] + mi := &file_api_weir_v1_store_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1053,7 +943,7 @@ func (x *ReadResult) ProtoReflect() protoreflect.Message { // Deprecated: Use ReadResult.ProtoReflect.Descriptor instead. func (*ReadResult) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{11} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{9} } func (x *ReadResult) GetResult() isReadResult_Result { @@ -1130,7 +1020,7 @@ type MutationOptions struct { func (x *MutationOptions) Reset() { *x = MutationOptions{} - mi := &file_api_weir_v1_store_proto_msgTypes[12] + mi := &file_api_weir_v1_store_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1142,7 +1032,7 @@ func (x *MutationOptions) String() string { func (*MutationOptions) ProtoMessage() {} func (x *MutationOptions) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[12] + mi := &file_api_weir_v1_store_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1155,7 +1045,7 @@ func (x *MutationOptions) ProtoReflect() protoreflect.Message { // Deprecated: Use MutationOptions.ProtoReflect.Descriptor instead. func (*MutationOptions) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{12} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{10} } func (x *MutationOptions) GetVisibility() MutationVisibility { @@ -1189,7 +1079,7 @@ type MutateRequest struct { func (x *MutateRequest) Reset() { *x = MutateRequest{} - mi := &file_api_weir_v1_store_proto_msgTypes[13] + mi := &file_api_weir_v1_store_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1201,7 +1091,7 @@ func (x *MutateRequest) String() string { func (*MutateRequest) ProtoMessage() {} func (x *MutateRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[13] + mi := &file_api_weir_v1_store_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1214,7 +1104,7 @@ func (x *MutateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MutateRequest.ProtoReflect.Descriptor instead. func (*MutateRequest) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{13} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{11} } func (x *MutateRequest) GetResource() string { @@ -1335,7 +1225,7 @@ type MutationResult struct { func (x *MutationResult) Reset() { *x = MutationResult{} - mi := &file_api_weir_v1_store_proto_msgTypes[14] + mi := &file_api_weir_v1_store_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1347,7 +1237,7 @@ func (x *MutationResult) String() string { func (*MutationResult) ProtoMessage() {} func (x *MutationResult) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[14] + mi := &file_api_weir_v1_store_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1360,7 +1250,7 @@ func (x *MutationResult) ProtoReflect() protoreflect.Message { // Deprecated: Use MutationResult.ProtoReflect.Descriptor instead. func (*MutationResult) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{14} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{12} } func (x *MutationResult) GetOutcome() MutationOutcome { @@ -1393,7 +1283,7 @@ type Transform struct { func (x *Transform) Reset() { *x = Transform{} - mi := &file_api_weir_v1_store_proto_msgTypes[15] + mi := &file_api_weir_v1_store_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1405,7 +1295,7 @@ func (x *Transform) String() string { func (*Transform) ProtoMessage() {} func (x *Transform) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[15] + mi := &file_api_weir_v1_store_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1418,7 +1308,7 @@ func (x *Transform) ProtoReflect() protoreflect.Message { // Deprecated: Use Transform.ProtoReflect.Descriptor instead. func (*Transform) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{15} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{13} } func (x *Transform) GetForm() isTransform_Form { @@ -1486,7 +1376,7 @@ type LuaTransform struct { func (x *LuaTransform) Reset() { *x = LuaTransform{} - mi := &file_api_weir_v1_store_proto_msgTypes[16] + mi := &file_api_weir_v1_store_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1498,7 +1388,7 @@ func (x *LuaTransform) String() string { func (*LuaTransform) ProtoMessage() {} func (x *LuaTransform) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[16] + mi := &file_api_weir_v1_store_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1511,7 +1401,7 @@ func (x *LuaTransform) ProtoReflect() protoreflect.Message { // Deprecated: Use LuaTransform.ProtoReflect.Descriptor instead. func (*LuaTransform) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{16} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{14} } func (x *LuaTransform) GetSource() []byte { @@ -1542,7 +1432,7 @@ type NativeHead struct { func (x *NativeHead) Reset() { *x = NativeHead{} - mi := &file_api_weir_v1_store_proto_msgTypes[17] + mi := &file_api_weir_v1_store_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1554,7 +1444,7 @@ func (x *NativeHead) String() string { func (*NativeHead) ProtoMessage() {} func (x *NativeHead) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[17] + mi := &file_api_weir_v1_store_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1567,7 +1457,7 @@ func (x *NativeHead) ProtoReflect() protoreflect.Message { // Deprecated: Use NativeHead.ProtoReflect.Descriptor instead. func (*NativeHead) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{17} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{15} } func (x *NativeHead) GetMetadata() *Document { @@ -1597,7 +1487,7 @@ type NativeEnd struct { func (x *NativeEnd) Reset() { *x = NativeEnd{} - mi := &file_api_weir_v1_store_proto_msgTypes[18] + mi := &file_api_weir_v1_store_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1609,7 +1499,7 @@ func (x *NativeEnd) String() string { func (*NativeEnd) ProtoMessage() {} func (x *NativeEnd) ProtoReflect() protoreflect.Message { - mi := &file_api_weir_v1_store_proto_msgTypes[18] + mi := &file_api_weir_v1_store_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1622,7 +1512,7 @@ func (x *NativeEnd) ProtoReflect() protoreflect.Message { // Deprecated: Use NativeEnd.ProtoReflect.Descriptor instead. func (*NativeEnd) Descriptor() ([]byte, []int) { - return file_api_weir_v1_store_proto_rawDescGZIP(), []int{18} + return file_api_weir_v1_store_proto_rawDescGZIP(), []int{16} } func (x *NativeEnd) GetCompletion() NativeCompletion { @@ -1643,16 +1533,7 @@ var File_api_weir_v1_store_proto protoreflect.FileDescriptor const file_api_weir_v1_store_proto_rawDesc = "" + "\n" + - "\x17api/weir/v1/store.proto\x12\aweir.v1\"4\n" + - "\x13ResolveStoreRequest\x12\x1d\n" + - "\n" + - "store_name\x18\x01 \x01(\tR\tstoreName\"u\n" + - "\x14ResolveStoreResponse\x12\x1d\n" + - "\n" + - "store_name\x18\x01 \x01(\tR\tstoreName\x12\x1c\n" + - "\tendpoints\x18\x02 \x03(\tR\tendpoints\x12 \n" + - "\fcache_ttl_ms\x18\x03 \x01(\x04R\n" + - "cacheTtlMs\"q\n" + + "\x17api/weir/v1/store.proto\x12\aweir.v1\"q\n" + "\x0eExecuteRequest\x12\x1d\n" + "\n" + "store_name\x18\x01 \x01(\tR\tstoreName\x12\x14\n" + @@ -1753,9 +1634,8 @@ const file_api_weir_v1_store_proto_rawDesc = "" + "\x1dNATIVE_COMPLETION_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12NATIVE_NOT_STARTED\x10\x01\x12\x15\n" + "\x11RESPONSE_COMPLETE\x10\x02\x12\x17\n" + - "\x13RESPONSE_INCOMPLETE\x10\x032\x9d\x01\n" + - "\fStoreService\x12K\n" + - "\fResolveStore\x12\x1c.weir.v1.ResolveStoreRequest\x1a\x1d.weir.v1.ResolveStoreResponse\x12@\n" + + "\x13RESPONSE_INCOMPLETE\x10\x032P\n" + + "\fStoreService\x12@\n" + "\aExecute\x12\x17.weir.v1.ExecuteRequest\x1a\x18.weir.v1.ExecuteResponse(\x010\x01B9Z7github.com/batchstream/weir-protocol/api/weir/v1;weirv1b\x06proto3" var ( @@ -1771,68 +1651,64 @@ func file_api_weir_v1_store_proto_rawDescGZIP() []byte { } var file_api_weir_v1_store_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_api_weir_v1_store_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_api_weir_v1_store_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_api_weir_v1_store_proto_goTypes = []any{ - (FailureCode)(0), // 0: weir.v1.FailureCode - (MutationOutcome)(0), // 1: weir.v1.MutationOutcome - (MutationVisibility)(0), // 2: weir.v1.MutationVisibility - (NativeCompletion)(0), // 3: weir.v1.NativeCompletion - (*ResolveStoreRequest)(nil), // 4: weir.v1.ResolveStoreRequest - (*ResolveStoreResponse)(nil), // 5: weir.v1.ResolveStoreResponse - (*ExecuteRequest)(nil), // 6: weir.v1.ExecuteRequest - (*ExecuteResponse)(nil), // 7: weir.v1.ExecuteResponse - (*Command)(nil), // 8: weir.v1.Command - (*NativeRequest)(nil), // 9: weir.v1.NativeRequest - (*Event)(nil), // 10: weir.v1.Event - (*Empty)(nil), // 11: weir.v1.Empty - (*Document)(nil), // 12: weir.v1.Document - (*Failure)(nil), // 13: weir.v1.Failure - (*ReadRequest)(nil), // 14: weir.v1.ReadRequest - (*ReadResult)(nil), // 15: weir.v1.ReadResult - (*MutationOptions)(nil), // 16: weir.v1.MutationOptions - (*MutateRequest)(nil), // 17: weir.v1.MutateRequest - (*MutationResult)(nil), // 18: weir.v1.MutationResult - (*Transform)(nil), // 19: weir.v1.Transform - (*LuaTransform)(nil), // 20: weir.v1.LuaTransform - (*NativeHead)(nil), // 21: weir.v1.NativeHead - (*NativeEnd)(nil), // 22: weir.v1.NativeEnd + (FailureCode)(0), // 0: weir.v1.FailureCode + (MutationOutcome)(0), // 1: weir.v1.MutationOutcome + (MutationVisibility)(0), // 2: weir.v1.MutationVisibility + (NativeCompletion)(0), // 3: weir.v1.NativeCompletion + (*ExecuteRequest)(nil), // 4: weir.v1.ExecuteRequest + (*ExecuteResponse)(nil), // 5: weir.v1.ExecuteResponse + (*Command)(nil), // 6: weir.v1.Command + (*NativeRequest)(nil), // 7: weir.v1.NativeRequest + (*Event)(nil), // 8: weir.v1.Event + (*Empty)(nil), // 9: weir.v1.Empty + (*Document)(nil), // 10: weir.v1.Document + (*Failure)(nil), // 11: weir.v1.Failure + (*ReadRequest)(nil), // 12: weir.v1.ReadRequest + (*ReadResult)(nil), // 13: weir.v1.ReadResult + (*MutationOptions)(nil), // 14: weir.v1.MutationOptions + (*MutateRequest)(nil), // 15: weir.v1.MutateRequest + (*MutationResult)(nil), // 16: weir.v1.MutationResult + (*Transform)(nil), // 17: weir.v1.Transform + (*LuaTransform)(nil), // 18: weir.v1.LuaTransform + (*NativeHead)(nil), // 19: weir.v1.NativeHead + (*NativeEnd)(nil), // 20: weir.v1.NativeEnd } var file_api_weir_v1_store_proto_depIdxs = []int32{ - 8, // 0: weir.v1.ExecuteRequest.command:type_name -> weir.v1.Command - 10, // 1: weir.v1.ExecuteResponse.event:type_name -> weir.v1.Event - 14, // 2: weir.v1.Command.read:type_name -> weir.v1.ReadRequest - 17, // 3: weir.v1.Command.mutate:type_name -> weir.v1.MutateRequest - 9, // 4: weir.v1.Command.native:type_name -> weir.v1.NativeRequest - 12, // 5: weir.v1.NativeRequest.request:type_name -> weir.v1.Document - 15, // 6: weir.v1.Event.read_result:type_name -> weir.v1.ReadResult - 18, // 7: weir.v1.Event.mutation_result:type_name -> weir.v1.MutationResult - 21, // 8: weir.v1.Event.head:type_name -> weir.v1.NativeHead - 22, // 9: weir.v1.Event.native_end:type_name -> weir.v1.NativeEnd + 6, // 0: weir.v1.ExecuteRequest.command:type_name -> weir.v1.Command + 8, // 1: weir.v1.ExecuteResponse.event:type_name -> weir.v1.Event + 12, // 2: weir.v1.Command.read:type_name -> weir.v1.ReadRequest + 15, // 3: weir.v1.Command.mutate:type_name -> weir.v1.MutateRequest + 7, // 4: weir.v1.Command.native:type_name -> weir.v1.NativeRequest + 10, // 5: weir.v1.NativeRequest.request:type_name -> weir.v1.Document + 13, // 6: weir.v1.Event.read_result:type_name -> weir.v1.ReadResult + 16, // 7: weir.v1.Event.mutation_result:type_name -> weir.v1.MutationResult + 19, // 8: weir.v1.Event.head:type_name -> weir.v1.NativeHead + 20, // 9: weir.v1.Event.native_end:type_name -> weir.v1.NativeEnd 0, // 10: weir.v1.Failure.code:type_name -> weir.v1.FailureCode - 12, // 11: weir.v1.ReadResult.document:type_name -> weir.v1.Document - 11, // 12: weir.v1.ReadResult.missing:type_name -> weir.v1.Empty - 13, // 13: weir.v1.ReadResult.failure:type_name -> weir.v1.Failure + 10, // 11: weir.v1.ReadResult.document:type_name -> weir.v1.Document + 9, // 12: weir.v1.ReadResult.missing:type_name -> weir.v1.Empty + 11, // 13: weir.v1.ReadResult.failure:type_name -> weir.v1.Failure 2, // 14: weir.v1.MutationOptions.visibility:type_name -> weir.v1.MutationVisibility - 12, // 15: weir.v1.MutateRequest.put:type_name -> weir.v1.Document - 12, // 16: weir.v1.MutateRequest.create:type_name -> weir.v1.Document - 12, // 17: weir.v1.MutateRequest.replace:type_name -> weir.v1.Document - 11, // 18: weir.v1.MutateRequest.delete:type_name -> weir.v1.Empty - 19, // 19: weir.v1.MutateRequest.atomic_transform:type_name -> weir.v1.Transform - 16, // 20: weir.v1.MutateRequest.options:type_name -> weir.v1.MutationOptions + 10, // 15: weir.v1.MutateRequest.put:type_name -> weir.v1.Document + 10, // 16: weir.v1.MutateRequest.create:type_name -> weir.v1.Document + 10, // 17: weir.v1.MutateRequest.replace:type_name -> weir.v1.Document + 9, // 18: weir.v1.MutateRequest.delete:type_name -> weir.v1.Empty + 17, // 19: weir.v1.MutateRequest.atomic_transform:type_name -> weir.v1.Transform + 14, // 20: weir.v1.MutateRequest.options:type_name -> weir.v1.MutationOptions 1, // 21: weir.v1.MutationResult.outcome:type_name -> weir.v1.MutationOutcome - 13, // 22: weir.v1.MutationResult.failure:type_name -> weir.v1.Failure - 20, // 23: weir.v1.Transform.lua:type_name -> weir.v1.LuaTransform - 12, // 24: weir.v1.Transform.backend_expression:type_name -> weir.v1.Document - 12, // 25: weir.v1.LuaTransform.input:type_name -> weir.v1.Document - 12, // 26: weir.v1.NativeHead.metadata:type_name -> weir.v1.Document + 11, // 22: weir.v1.MutationResult.failure:type_name -> weir.v1.Failure + 18, // 23: weir.v1.Transform.lua:type_name -> weir.v1.LuaTransform + 10, // 24: weir.v1.Transform.backend_expression:type_name -> weir.v1.Document + 10, // 25: weir.v1.LuaTransform.input:type_name -> weir.v1.Document + 10, // 26: weir.v1.NativeHead.metadata:type_name -> weir.v1.Document 3, // 27: weir.v1.NativeEnd.completion:type_name -> weir.v1.NativeCompletion - 13, // 28: weir.v1.NativeEnd.failure:type_name -> weir.v1.Failure - 4, // 29: weir.v1.StoreService.ResolveStore:input_type -> weir.v1.ResolveStoreRequest - 6, // 30: weir.v1.StoreService.Execute:input_type -> weir.v1.ExecuteRequest - 5, // 31: weir.v1.StoreService.ResolveStore:output_type -> weir.v1.ResolveStoreResponse - 7, // 32: weir.v1.StoreService.Execute:output_type -> weir.v1.ExecuteResponse - 31, // [31:33] is the sub-list for method output_type - 29, // [29:31] is the sub-list for method input_type + 11, // 28: weir.v1.NativeEnd.failure:type_name -> weir.v1.Failure + 4, // 29: weir.v1.StoreService.Execute:input_type -> weir.v1.ExecuteRequest + 5, // 30: weir.v1.StoreService.Execute:output_type -> weir.v1.ExecuteResponse + 30, // [30:31] is the sub-list for method output_type + 29, // [29:30] is the sub-list for method input_type 29, // [29:29] is the sub-list for extension type_name 29, // [29:29] is the sub-list for extension extendee 0, // [0:29] is the sub-list for field type_name @@ -1843,31 +1719,31 @@ func file_api_weir_v1_store_proto_init() { if File_api_weir_v1_store_proto != nil { return } - file_api_weir_v1_store_proto_msgTypes[4].OneofWrappers = []any{ + file_api_weir_v1_store_proto_msgTypes[2].OneofWrappers = []any{ (*Command_Read)(nil), (*Command_Mutate)(nil), (*Command_Native)(nil), } - file_api_weir_v1_store_proto_msgTypes[6].OneofWrappers = []any{ + file_api_weir_v1_store_proto_msgTypes[4].OneofWrappers = []any{ (*Event_ReadResult)(nil), (*Event_MutationResult)(nil), (*Event_Head)(nil), (*Event_Chunk)(nil), (*Event_NativeEnd)(nil), } - file_api_weir_v1_store_proto_msgTypes[11].OneofWrappers = []any{ + file_api_weir_v1_store_proto_msgTypes[9].OneofWrappers = []any{ (*ReadResult_Document)(nil), (*ReadResult_Missing)(nil), (*ReadResult_Failure)(nil), } - file_api_weir_v1_store_proto_msgTypes[13].OneofWrappers = []any{ + file_api_weir_v1_store_proto_msgTypes[11].OneofWrappers = []any{ (*MutateRequest_Put)(nil), (*MutateRequest_Create)(nil), (*MutateRequest_Replace)(nil), (*MutateRequest_Delete)(nil), (*MutateRequest_AtomicTransform)(nil), } - file_api_weir_v1_store_proto_msgTypes[15].OneofWrappers = []any{ + file_api_weir_v1_store_proto_msgTypes[13].OneofWrappers = []any{ (*Transform_Lua)(nil), (*Transform_BackendExpression)(nil), } @@ -1878,7 +1754,7 @@ func file_api_weir_v1_store_proto_init() { GoPackagePath: reflect.TypeOf(packageMarker).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_weir_v1_store_proto_rawDesc), len(file_api_weir_v1_store_proto_rawDesc)), NumEnums: 4, - NumMessages: 19, + NumMessages: 17, NumExtensions: 0, NumServices: 1, }, diff --git a/api/weir/v1/store.proto b/api/weir/v1/store.proto index fb6b05a..5e03ddf 100644 --- a/api/weir/v1/store.proto +++ b/api/weir/v1/store.proto @@ -7,12 +7,9 @@ option go_package = "github.com/batchstream/weir-protocol/api/weir/v1;weirv1"; // Requests and responses reject unknown fields recursively. Responses require // recognized results/events and enum values. -// StoreService discovers Store endpoints and executes requests on their owners. -// Initialize through ResolveStore, then connect directly to a returned endpoint. +// StoreService executes requests on a Weir server that hosts the named Store. +// A server that does not host the Store fails the RPC with NOT_FOUND. service StoreService { - // ResolveStore returns the current endpoints for one configured Store. - rpc ResolveStore(ResolveStoreRequest) returns (ResolveStoreResponse); - // Execute accepts requests and emits indexed results incrementally. // A stream selects one Store and operation kind. Each Read or Mutate request // carries one record with a consecutive one-based index. It is validated @@ -26,25 +23,7 @@ service StoreService { rpc Execute(stream ExecuteRequest) returns (stream ExecuteResponse); } -// ResolveStoreRequest selects the Store whose owner endpoints are required. -message ResolveStoreRequest { - // Configured Store name, independent of the deployment platform. - string store_name = 1; -} - -// ResolveStoreResponse supplies addresses for direct execution connections. -message ResolveStoreResponse { - // The Store selected by the request. - string store_name = 1; - - // Portable IP or DNS host:port addresses. DNS may identify several replicas. - repeated string endpoints = 2; - - // Maximum client cache lifetime for this endpoint set, in milliseconds. - uint64 cache_ttl_ms = 3; -} - -// ExecuteRequest binds one operation to its owning Store and index. +// ExecuteRequest binds one operation to its Store and index. message ExecuteRequest { // Store that owns all resources; must remain identical throughout the stream. string store_name = 1; diff --git a/api/weir/v1/store_grpc.pb.go b/api/weir/v1/store_grpc.pb.go index aa28fec..c7b5a45 100644 --- a/api/weir/v1/store_grpc.pb.go +++ b/api/weir/v1/store_grpc.pb.go @@ -17,19 +17,16 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - StoreService_ResolveStore_FullMethodName = "/weir.v1.StoreService/ResolveStore" - StoreService_Execute_FullMethodName = "/weir.v1.StoreService/Execute" + StoreService_Execute_FullMethodName = "/weir.v1.StoreService/Execute" ) // StoreServiceClient is the client API for StoreService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // -// StoreService discovers Store endpoints and executes requests on their owners. -// Initialize through ResolveStore, then connect directly to a returned endpoint. +// StoreService executes requests on a Weir server that hosts the named Store. +// A server that does not host the Store fails the RPC with NOT_FOUND. type StoreServiceClient interface { - // ResolveStore returns the current endpoints for one configured Store. - ResolveStore(ctx context.Context, in *ResolveStoreRequest, opts ...grpc.CallOption) (*ResolveStoreResponse, error) // Execute accepts requests and emits indexed results incrementally. // A stream selects one Store and operation kind. Each Read or Mutate request // carries one record with a consecutive one-based index. It is validated @@ -52,16 +49,6 @@ func NewStoreServiceClient(cc grpc.ClientConnInterface) StoreServiceClient { return client } -func (c *storeServiceClient) ResolveStore(ctx context.Context, in *ResolveStoreRequest, opts ...grpc.CallOption) (*ResolveStoreResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(ResolveStoreResponse) - err := c.cc.Invoke(ctx, StoreService_ResolveStore_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *storeServiceClient) Execute(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ExecuteRequest, ExecuteResponse], error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) stream, err := c.cc.NewStream(ctx, &StoreService_ServiceDesc.Streams[0], StoreService_Execute_FullMethodName, cOpts...) @@ -74,11 +61,9 @@ func (c *storeServiceClient) Execute(ctx context.Context, opts ...grpc.CallOptio // StoreServiceServer is the server API for StoreService service. // -// StoreService discovers Store endpoints and executes requests on their owners. -// Initialize through ResolveStore, then connect directly to a returned endpoint. +// StoreService executes requests on a Weir server that hosts the named Store. +// A server that does not host the Store fails the RPC with NOT_FOUND. type StoreServiceServer interface { - // ResolveStore returns the current endpoints for one configured Store. - ResolveStore(context.Context, *ResolveStoreRequest) (*ResolveStoreResponse, error) // Execute accepts requests and emits indexed results incrementally. // A stream selects one Store and operation kind. Each Read or Mutate request // carries one record with a consecutive one-based index. It is validated @@ -95,24 +80,6 @@ type StoreServiceServer interface { func RegisterStoreServiceServer(s grpc.ServiceRegistrar, srv StoreServiceServer) { s.RegisterService(&StoreService_ServiceDesc, srv) } -func _StoreService_ResolveStore_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ResolveStoreRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(StoreServiceServer).ResolveStore(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: StoreService_ResolveStore_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(StoreServiceServer).ResolveStore(ctx, req.(*ResolveStoreRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _StoreService_Execute_Handler(srv interface{}, stream grpc.ServerStream) error { serverStream := &grpc.GenericServerStream[ExecuteRequest, ExecuteResponse]{ServerStream: stream} return srv.(StoreServiceServer).Execute(serverStream) @@ -124,12 +91,7 @@ func _StoreService_Execute_Handler(srv interface{}, stream grpc.ServerStream) er var StoreService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "weir.v1.StoreService", HandlerType: (*StoreServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "ResolveStore", - Handler: _StoreService_ResolveStore_Handler, - }, - }, + Methods: []grpc.MethodDesc{}, Streams: []grpc.StreamDesc{ { StreamName: "Execute", diff --git a/go.mod b/go.mod index ca603b6..7e44327 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/batchstream/weir-protocol go 1.27.1 require ( - golang.org/x/net v0.58.0 google.golang.org/grpc v1.83.2 google.golang.org/protobuf v1.36.11 ) require ( + golang.org/x/net v0.58.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect diff --git a/internal/testutil/testdns/server.go b/internal/testutil/testdns/server.go deleted file mode 100644 index 3724a6a..0000000 --- a/internal/testutil/testdns/server.go +++ /dev/null @@ -1,280 +0,0 @@ -// Package testdns serves only task-owned loopback DNS. It never edits host DNS. -package testdns - -import ( - "context" - "encoding/binary" - "errors" - "fmt" - "io" - "net" - "net/netip" - "runtime" - "sync" - "sync/atomic" - "syscall" - "testing" - "time" - - "golang.org/x/net/dns/dnsmessage" -) - -type Answer struct { - Addresses []netip.Addr - Code dnsmessage.RCode - Delay time.Duration - Drop bool - TCP bool -} - -type Server struct { - Address string - udp net.PacketConn - tcp net.Listener - mu sync.Mutex - answers map[string]Answer - conns map[net.Conn]bool - ctx context.Context - cancel context.CancelFunc - workers sync.WaitGroup - slots chan struct{} - Queries atomic.Int32 - A, AAAA, Other, TCP atomic.Int32 - Active, Peak atomic.Int32 -} - -func Start(t *testing.T) *Server { - t.Helper() - udp, tcp, err := listen("127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - ctx, cancel := context.WithCancel(context.Background()) - s := &Server{ - Address: udp.LocalAddr().String(), - udp: udp, - tcp: tcp, - answers: make(map[string]Answer), - conns: make(map[net.Conn]bool), - ctx: ctx, - cancel: cancel, - slots: make(chan struct{}, 64), - } - s.workers.Go(s.serveUDP) - s.workers.Go(s.serveTCP) - t.Cleanup(func() { - cancel() - _ = udp.Close() - _ = tcp.Close() - s.mu.Lock() - for conn := range s.conns { - _ = conn.Close() - } - s.mu.Unlock() - s.workers.Wait() - if s.Active.Load() != 0 { - t.Error("DNS fixture leaked queries") - } - }) - return s -} - -const maxListenAttempts = 16 - -// listen retains both sockets before publishing a shared DNS address. UDP's -// ephemeral port may already be occupied in TCP's independent port namespace. -func listen(address string) (net.PacketConn, net.Listener, error) { - addressInUse := syscall.EADDRINUSE - if runtime.GOOS == "windows" { - // Winsock WSAEADDRINUSE differs from Go's synthetic Windows EADDRINUSE. - addressInUse = syscall.Errno(10048) - } - var err error - for range maxListenAttempts { - var udp net.PacketConn - udp, err = net.ListenPacket("udp", address) - if err != nil { - return nil, nil, err - } - var tcp net.Listener - tcp, err = net.Listen("tcp", udp.LocalAddr().String()) - if err == nil { - return udp, tcp, nil - } - _ = udp.Close() - if !errors.Is(err, addressInUse) { - return nil, nil, err - } - } - return nil, nil, fmt.Errorf("bind DNS UDP/TCP after %d attempts: %w", maxListenAttempts, err) -} - -func (s *Server) Set(name string, answer Answer) { - s.mu.Lock() - defer s.mu.Unlock() - answer.Addresses = append([]netip.Addr(nil), answer.Addresses...) - s.answers[name+"."] = answer -} - -func (s *Server) Resolver() *net.Resolver { - r := &net.Resolver{PreferGo: true, StrictErrors: true, Dial: s.Dial} - return r -} - -func (s *Server) Dial(ctx context.Context, network, _ string) (net.Conn, error) { - d := net.Dialer{Timeout: time.Second} - return d.DialContext(ctx, network, s.Address) -} - -func (s *Server) serveUDP() { - for { - raw := make([]byte, 2048) - n, addr, err := s.udp.ReadFrom(raw) - if err != nil { - return - } - select { - case s.slots <- struct{}{}: - default: - continue - } - s.workers.Go(func() { - defer func() { <-s.slots }() - response := s.reply(raw[:n], false) - if response != nil { - _, _ = s.udp.WriteTo(response, addr) - } - }) - } -} - -func (s *Server) serveTCP() { - for { - conn, err := s.tcp.Accept() - if err != nil { - return - } - s.mu.Lock() - if s.ctx.Err() != nil { - s.mu.Unlock() - _ = conn.Close() - return - } - s.conns[conn] = true - s.mu.Unlock() - select { - case s.slots <- struct{}{}: - default: - _ = conn.Close() - s.mu.Lock() - delete(s.conns, conn) - s.mu.Unlock() - continue - } - s.workers.Go(func() { - defer func() { - _ = conn.Close() - s.mu.Lock() - delete(s.conns, conn) - s.mu.Unlock() - <-s.slots - }() - _ = conn.SetDeadline(time.Now().Add(3 * time.Second)) - var length [2]byte - if _, err := io.ReadFull(conn, length[:]); err != nil { - return - } - raw := make([]byte, binary.BigEndian.Uint16(length[:])) - if _, err := io.ReadFull(conn, raw); err != nil { - return - } - response := s.reply(raw, true) - if response == nil { - return - } - wire := binary.BigEndian.AppendUint16(nil, uint16(len(response))) - wire = append(wire, response...) - _, _ = conn.Write(wire) - }) - } -} - -func (s *Server) reply(raw []byte, tcp bool) []byte { - var p dnsmessage.Parser - header, err := p.Start(raw) - if err != nil { - return nil - } - q, err := p.Question() - if err != nil { - return nil - } - s.Queries.Add(1) - if tcp { - s.TCP.Add(1) - } - switch q.Type { - case dnsmessage.TypeA: - s.A.Add(1) - case dnsmessage.TypeAAAA: - s.AAAA.Add(1) - default: - s.Other.Add(1) - } - active := s.Active.Add(1) - defer s.Active.Add(-1) - for peak := s.Peak.Load(); active > peak && !s.Peak.CompareAndSwap(peak, active); peak = s.Peak.Load() { - } - s.mu.Lock() - answer, ok := s.answers[q.Name.String()] - s.mu.Unlock() - if !ok { - answer.Code = dnsmessage.RCodeNameError - } - if answer.Drop { - return nil - } - if answer.Delay > 0 { - timer := time.NewTimer(answer.Delay) - defer timer.Stop() - select { - case <-s.ctx.Done(): - return nil - case <-timer.C: - } - } - head := dnsmessage.Header{ - ID: header.ID, - Response: true, - Authoritative: true, - RecursionAvailable: true, - RCode: answer.Code, - Truncated: answer.TCP && !tcp, - } - b := dnsmessage.NewBuilder(nil, head) - b.EnableCompression() - if b.StartQuestions() != nil || b.Question(q) != nil || b.StartAnswers() != nil { - return nil - } - if !head.Truncated && answer.Code == dnsmessage.RCodeSuccess { - for _, ip := range answer.Addresses { - h := dnsmessage.ResourceHeader{Name: q.Name, Class: dnsmessage.ClassINET, TTL: 1} - if ip.Is4() && q.Type == dnsmessage.TypeA { - a := dnsmessage.AResource{A: ip.As4()} - if b.AResource(h, a) != nil { - return nil - } - } else if ip.Is6() && q.Type == dnsmessage.TypeAAAA { - a := dnsmessage.AAAAResource{AAAA: ip.As16()} - if b.AAAAResource(h, a) != nil { - return nil - } - } - } - } - response, err := b.Finish() - if err != nil { - return nil - } - return response -} diff --git a/internal/testutil/testdns/server_test.go b/internal/testutil/testdns/server_test.go deleted file mode 100644 index e8a4419..0000000 --- a/internal/testutil/testdns/server_test.go +++ /dev/null @@ -1,232 +0,0 @@ -package testdns - -import ( - "context" - "errors" - "fmt" - "io" - "net" - "net/netip" - "slices" - "strings" - "testing" - "time" - - "golang.org/x/net/dns/dnsmessage" -) - -func TestListenTCPPortCollision(t *testing.T) { - udp, blocker, err := listen("127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - defer udp.Close() - defer blocker.Close() - address := udp.LocalAddr().String() - // Release only our UDP socket. Our TCP listener keeps every candidate at - // this explicit address in collision, without depending on ephemeral luck. - if err := udp.Close(); err != nil { - t.Fatal(err) - } - started := time.Now() - failedUDP, failedTCP, err := listen(address) - if failedUDP != nil { - _ = failedUDP.Close() - } - if failedTCP != nil { - _ = failedTCP.Close() - } - var op *net.OpError - if failedUDP != nil || failedTCP != nil || !errors.As(err, &op) || op.Net != "tcp" { - t.Fatalf("partial pair or wrong collision: udp=%v tcp=%v err=%v", failedUDP, failedTCP, err) - } - if !strings.Contains(err.Error(), fmt.Sprintf("after %d attempts", maxListenAttempts)) || time.Since(started) > time.Second { - t.Fatal("collision retry was not bounded", err, time.Since(started)) - } - // Rebinding UDP proves every failed attempt released its partial pair. - rebound, err := net.ListenPacket("udp", address) - if err != nil { - t.Fatal("failed attempt leaked UDP", err) - } - defer rebound.Close() - dialer := net.Dialer{Timeout: time.Second} - client, err := dialer.DialContext(t.Context(), "tcp", address) - if err != nil { - t.Fatal("collision closed the existing TCP listener", err) - } - t.Cleanup(func() { _ = client.Close() }) - if err := blocker.(*net.TCPListener).SetDeadline(time.Now().Add(time.Second)); err != nil { - t.Fatal(err) - } - accepted, err := blocker.Accept() - if err != nil { - t.Fatal(err) - } - _ = accepted.Close() - _ = client.Close() - _ = blocker.Close() - _ = rebound.Close() - recoveredUDP, recoveredTCP, err := listen(address) - if err != nil { - t.Fatal("released pair could not be acquired", err) - } - defer recoveredUDP.Close() - defer recoveredTCP.Close() - if recoveredUDP.LocalAddr().String() != address || recoveredTCP.Addr().String() != address { - t.Fatal("pair did not retain the shared address") - } -} - -func TestListenUDPBindFailure(t *testing.T) { - blocker, err := net.ListenPacket("udp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - defer blocker.Close() - for _, address := range []string{blocker.LocalAddr().String(), "127.0.0.1:-1"} { - t.Run(address, func(t *testing.T) { - udp, tcp, err := listen(address) - if udp != nil { - _ = udp.Close() - } - if tcp != nil { - _ = tcp.Close() - } - var op *net.OpError - if udp != nil || tcp != nil || !errors.As(err, &op) || op.Net != "udp" || strings.Contains(err.Error(), "attempts") { - t.Fatal("UDP bind failure was hidden or retried", err) - } - }) - } -} - -func TestStartIndependentFixtures(t *testing.T) { - var fixtures []*Server - t.Run("concurrent", func(t *testing.T) { - addresses := make(map[string]bool) - for i := range 8 { - s := Start(t) - fixtures = append(fixtures, s) - if addresses[s.Address] || s.udp.LocalAddr().String() != s.Address || s.tcp.Addr().String() != s.Address { - t.Fatal("fixtures did not retain distinct complete pairs", s.Address) - } - addresses[s.Address] = true - t.Run(fmt.Sprint(i), func(t *testing.T) { - t.Parallel() - want := []netip.Addr{netip.MustParseAddr(fmt.Sprintf("127.0.0.%d", i+1)), netip.MustParseAddr("::1")} - for _, tcp := range []bool{false, true} { - answer := Answer{Addresses: want, TCP: tcp} - s.Set("fixture.weir.test", answer) - ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second) - got, err := s.Resolver().LookupNetIP(ctx, "ip", "fixture.weir.test.") - cancel() - if err != nil || len(got) != 2 || !slices.Contains(got, want[0]) || !slices.Contains(got, want[1]) { - t.Fatal("fixture answers crossed or lookup failed", got, err) - } - } - if s.A.Load() != 3 || s.AAAA.Load() != 3 || s.TCP.Load() != 2 || s.Other.Load() != 0 { - t.Fatal("real UDP A/AAAA and truncated TCP fallback not observed") - } - }) - } - }) - for _, s := range fixtures { - assertClosed(t, s) - } -} - -func TestStartCleanupJoinsBoundedWorkers(t *testing.T) { - var s *Server - var pending net.Conn - var cleanupStarted time.Time - t.Cleanup(func() { - if pending != nil { - _ = pending.Close() - } - }) - t.Run("active", func(t *testing.T) { - s = Start(t) - answer := Answer{Delay: time.Hour} - s.Set("fixture.weir.test", answer) - var err error - pending, err = s.Dial(t.Context(), "tcp", "") - if err != nil { - t.Fatal(err) - } - await(t, func() bool { return len(s.slots) == 1 }) - udp, err := s.Dial(t.Context(), "udp", "") - if err != nil { - t.Fatal(err) - } - defer udp.Close() - question := dnsmessage.Question{ - Name: dnsmessage.MustNewName("fixture.weir.test."), - Type: dnsmessage.TypeA, - Class: dnsmessage.ClassINET, - } - message := dnsmessage.Message{Questions: []dnsmessage.Question{question}} - wire, err := message.Pack() - if err != nil { - t.Fatal(err) - } - for i := 1; i < cap(s.slots); i++ { - if _, err := udp.Write(wire); err != nil { - t.Fatal(err) - } - await(t, func() bool { return s.Active.Load() == int32(i) }) - } - overflow, err := s.Dial(t.Context(), "tcp", "") - if err != nil { - t.Fatal(err) - } - defer overflow.Close() - _ = overflow.SetReadDeadline(time.Now().Add(time.Second)) - var raw [1]byte - if _, err := overflow.Read(raw[:]); err != io.EOF { - t.Fatal("full fixture did not close excess connection", err) - } - if len(s.slots) != 64 || s.Peak.Load() != 63 { - t.Fatal("fixture worker bound changed", len(s.slots), s.Peak.Load()) - } - cleanupStarted = time.Now() - }) - if s == nil || pending == nil { - t.Fatal("fixture setup failed") - } - assertClosed(t, s) - if time.Since(cleanupStarted) > time.Second { - t.Fatal("cleanup did not promptly cancel delayed queries") - } - _ = pending.SetReadDeadline(time.Now().Add(time.Second)) - var raw [1]byte - if _, err := pending.Read(raw[:]); err != io.EOF { - t.Fatal("cleanup retained accepted TCP connection", err) - } -} - -func await(t *testing.T, ready func() bool) { - t.Helper() - deadline := time.Now().Add(2 * time.Second) - for !ready() { - if time.Now().After(deadline) { - t.Fatal("fixture state did not settle within 2s") - } - time.Sleep(time.Millisecond) - } -} - -func assertClosed(t *testing.T, s *Server) { - t.Helper() - s.mu.Lock() - connections := len(s.conns) - s.mu.Unlock() - if s.ctx.Err() == nil || s.Active.Load() != 0 || len(s.slots) != 0 || connections != 0 { - t.Fatal("cleanup retained fixture work", s.Active.Load(), len(s.slots), connections) - } - udp, tcp, err := listen(s.Address) - if err != nil { - t.Fatal("cleanup did not release both listener ports", err) - } - _ = udp.Close() - _ = tcp.Close() -} diff --git a/scripts/check_dependencies_test.py b/scripts/check_dependencies_test.py index 64e4ecf..27db333 100644 --- a/scripts/check_dependencies_test.py +++ b/scripts/check_dependencies_test.py @@ -29,8 +29,8 @@ def test_test_packages_cannot_import_server_or_sdk(self): with self.assertRaisesRegex(ValueError, 'Server or SDK'): check_dependencies.check_packages(packages) - def test_own_fixture_and_api_are_allowed(self): - packages = [{'ImportPath': path} for path in ('github.com/batchstream/weir-protocol/api/protocol', 'github.com/batchstream/weir-protocol/internal/testutil/testdns', 'context')] + def test_own_api_is_allowed(self): + packages = [{'ImportPath': path} for path in ('github.com/batchstream/weir-protocol/api/protocol', 'github.com/batchstream/weir-protocol/api/weir/v1', 'context')] check_dependencies.check_packages(packages) def test_raw_graph_rejects_server_and_sdk_edges(self): diff --git a/scripts/generate.sh b/scripts/generate.sh index ebf4a4c..135e792 100755 --- a/scripts/generate.sh +++ b/scripts/generate.sh @@ -33,7 +33,7 @@ awk ' print; print "\ts.RegisterService(&StoreService_ServiceDesc, srv)"; print "}"; next } scaffolding { next } - registration && /^func _StoreService_ResolveStore_Handler\(/ { registration=0 } + registration && /^func _StoreService_/ { registration=0 } registration { next } $0 == "\treturn &storeServiceClient{cc}" { print "\tclient := &storeServiceClient{cc}"; print "\treturn client"; count++; next