Files
2026-06-01 07:43:13 -04:00

5.7 KiB

Go Client Detailed Design

Purpose

Provide an idiomatic Go client module for MXAccess Gateway, plus a test CLI and unit tests. The Go client should be suitable for services and command-line automation.

Follow the Go Style Guide for handwritten code and the Protobuf Style Guide for generated contract inputs.

Module Layout

Recommended layout:

clients/go/
  go.mod
  mxgateway/
    client.go
    session.go
    options.go
    auth.go
    values.go
    errors.go
  internal/generated/
    mxaccess_gateway.pb.go
    mxaccess_gateway_grpc.pb.go
  cmd/mxgw-go/
    main.go
  tests/

Generated code should come from protoc plus:

  • protoc-gen-go
  • protoc-gen-go-grpc

Library API

Suggested API:

type Client struct {
    // owns grpc.ClientConn
}

type Options struct {
    Endpoint           string
    APIKey             string
    Plaintext          bool
    CACertFile         string
    ServerNameOverride string
    DialTimeout        time.Duration
    CallTimeout        time.Duration
}

func Dial(ctx context.Context, opts Options) (*Client, error)
func (c *Client) OpenSession(ctx context.Context, opts OpenSessionOptions) (*Session, error)
func (c *Client) Invoke(ctx context.Context, req *pb.MxCommandRequest) (*pb.MxCommandReply, error)
func (c *Client) Close() error

Session:

type Session struct {
    ID string
}

func (s *Session) Register(ctx context.Context, clientName string) (int32, error)
func (s *Session) Unregister(ctx context.Context, serverHandle int32) error
func (s *Session) AddItem(ctx context.Context, serverHandle int32, item string) (int32, error)
func (s *Session) AddItem2(ctx context.Context, serverHandle int32, item, context string) (int32, error)
func (s *Session) Advise(ctx context.Context, serverHandle, itemHandle int32) error
func (s *Session) AddItemBulk(ctx context.Context, serverHandle int32, tagAddresses []string) ([]*pb.SubscribeResult, error)
func (s *Session) AdviseItemBulk(ctx context.Context, serverHandle int32, itemHandles []int32) ([]*pb.SubscribeResult, error)
func (s *Session) RemoveItemBulk(ctx context.Context, serverHandle int32, itemHandles []int32) ([]*pb.SubscribeResult, error)
func (s *Session) UnAdviseItemBulk(ctx context.Context, serverHandle int32, itemHandles []int32) ([]*pb.SubscribeResult, error)
func (s *Session) SubscribeBulk(ctx context.Context, serverHandle int32, tagAddresses []string) ([]*pb.SubscribeResult, error)
func (s *Session) UnsubscribeBulk(ctx context.Context, serverHandle int32, itemHandles []int32) ([]*pb.SubscribeResult, error)
func (s *Session) Write(ctx context.Context, serverHandle, itemHandle int32, value Value, userID int32) error
func (s *Session) Events(ctx context.Context) (<-chan EventResult, error)
func (s *Session) Close(ctx context.Context) error

Authentication

Use a unary and stream interceptor to attach:

authorization: Bearer <api key>

The interceptor should use metadata.AppendToOutgoingContext or call options. Do not print API keys in errors.

TLS

Support:

  • credentials/insecure for local plaintext,
  • credentials.NewClientTLSFromFile,
  • custom tls.Config for advanced callers.

Trust posture

The gateway can serve a self-signed certificate it generates itself (it has no PKI). To make that usable, TLS is lenient by default: when Plaintext is false and no CACertFile/TLSConfig/TransportCredentials is supplied, buildCredentials dials with tls.Config{InsecureSkipVerify: true} (carrying ServerNameOverride as the SNI when set), so the gateway's self-signed certificate is accepted without verification.

To verify the gateway instead:

  • set CACertFile to pin a CA (full verification against that root), or
  • set RequireCertificateValidation: true to verify against the OS/system trust roots without pinning.

Pinning a CA always wins over the lenient default.

Streaming

Events(ctx) should return a receive channel of:

type EventResult struct {
    Event *pb.MxEvent
    Err   error
}

The receive goroutine exits on stream end, context cancellation, or error. The channel should be closed exactly once. Do not reorder events.

Error Handling

Expose typed errors:

type GatewayError struct { ... }
type CommandError struct { ... }
type MxAccessError struct { ... }

Use errors.Is / errors.As support. Preserve raw protobuf replies on command errors.

Test CLI

Binary: mxgw-go.

Recommended commands:

mxgw-go version
mxgw-go smoke --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --item TestChildObject.TestInt
mxgw-go write --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123
mxgw-go stream-events --session-id <id> --json

Recommended CLI library:

  • standard flag for minimalism, or
  • Cobra if subcommand ergonomics matter.

Unit Tests

Use bufconn for in-memory gRPC tests.

Required tests:

  • auth interceptor on unary calls,
  • auth interceptor on streaming calls,
  • plaintext and TLS dial options,
  • command helper request construction,
  • value conversion,
  • status conversion,
  • typed error wrapping,
  • stream channel closes on cancellation,
  • late stream error propagation,
  • CLI JSON redaction.

Integration Tests

Use Go build tags or environment skip:

MXGATEWAY_INTEGRATION=1

Integration test should run OpenSession, Register, AddItem, Advise, bounded StreamEvents, and CloseSession.