# 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](../../docs/style-guides/GoStyleGuide.md) for handwritten
code and the [Protobuf Style Guide](../../docs/style-guides/ProtobufStyleGuide.md) for
generated contract inputs.

## Module Layout

Recommended layout:

```text
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:

```go
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:

```go
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:

```text
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:

```go
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:

```go
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:

```text
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:

```text
MXGATEWAY_INTEGRATION=1
```

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

## Related Documentation

- [Client Libraries Detailed Design](../../docs/ClientLibrariesDesign.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Client Packaging](../../docs/ClientPackaging.md)
- [Go Style Guide](../../docs/style-guides/GoStyleGuide.md)
