# Go Client

The Go client module contains the generated MXAccess Gateway protobuf bindings,
a small handwritten `mxgateway` package, and the `mxgw-go` test CLI scaffold.
The module uses the shared proto inputs documented in
`../../docs/ClientProtoGeneration.md` so gateway and client contracts stay in
sync.

## Layout

```text
clients/go/
  go.mod
  generate-proto.ps1
  internal/generated/
  mxgateway/
  cmd/mxgw-go/
```

`internal/generated` contains code produced by `protoc`, `protoc-gen-go`, and
`protoc-gen-go-grpc`. Do not edit generated files by hand.

## Regenerating Protobuf Bindings

Run generation after the shared `.proto` files or the Go output path changes:

```powershell
./generate-proto.ps1
```

The script uses the tool paths recorded in `../../docs/ToolchainLinks.md`.

## Build And Test

Run the Go module checks from `clients/go`:

```powershell
go test ./...
go build ./...
go vet ./...
```

The tests parse the shared JSON fixtures, exercise value and status conversion,
use `bufconn` for fake gateway auth and streaming behavior, and cover CLI JSON
redaction.

## Packaging

Build a local CLI executable from `clients/go`:

```powershell
New-Item -ItemType Directory -Force ../../artifacts/clients/go | Out-Null
go build -o ../../artifacts/clients/go/mxgw-go.exe ./cmd/mxgw-go
```

Install the CLI into the active `GOBIN` or `GOPATH/bin`:

```powershell
go install ./cmd/mxgw-go
```

Other Go modules can consume the library package with the module path
`gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/mxgateway`.

## Client API

Use `mxgateway.Dial` with `mxgateway.Options` to configure plaintext or TLS
transport, API-key metadata, dial timeout, and per-call timeout:

```go
client, err := mxgateway.Dial(ctx, mxgateway.Options{
    Endpoint:  "localhost:5000",
    APIKey:    os.Getenv("MXGATEWAY_API_KEY"),
    Plaintext: true,
})
```

The gateway can auto-generate its own self-signed certificate (it has no PKI), so
the client is **lenient by default**: a TLS connection (`Plaintext: false`) with
no `CACertFile`/`TLSConfig` accepts whatever certificate the gateway presents
(`InsecureSkipVerify`, with `ServerNameOverride` as the SNI when set). To verify
instead, set `CACertFile` to pin a CA, or set `RequireCertificateValidation:
true` to verify against the OS/system trust roots without pinning. See
[Gateway Configuration](../../docs/GatewayConfiguration.md#automatic-self-signed-certificate).

`Client.OpenSession` returns a `Session` with helpers for `Register`,
`AddItem`, `AddItem2`, `Advise`, `Write`, `Events`, and `Close`. Prefer
`SubscribeEvents` or `SubscribeEventsAfter` for long-running streams because the
returned subscription owns cancellation and exposes `Close` for deterministic
goroutine cleanup. Raw protobuf messages remain available through the
`mxgateway` package aliases and the `Raw` helper methods. Typed errors support
`errors.As` for `GatewayError`, `CommandError`, and `MxAccessError`; command
errors preserve the raw reply.

For alarms, the package exposes `Client.QueryActiveAlarms` for one-shot
snapshots, `Client.StreamAlarms` for the server-streaming feed, and
`Client.AcknowledgeAlarm` to ack an alarm by full reference. The streaming
call returns a `StreamAlarmsClient`; cancel its context to terminate the
stream. All three pass straight through to the gateway's central alarm
monitor.

## Galaxy Repository browse

The `GalaxyRepository` service (proto package `galaxy_repository.v1`) is a
read-only metadata-only browse over the AVEVA System Platform Galaxy
Repository. It uses the same API-key authentication as the MXAccess Gateway
and requires the `metadata:read` scope. Use `mxgateway.DialGalaxy` to obtain a
`*GalaxyClient` that mirrors the connection-management conventions of
`Client`:

```go
galaxy, err := mxgateway.DialGalaxy(ctx, mxgateway.Options{
    Endpoint:  "localhost:5000",
    APIKey:    os.Getenv("MXGATEWAY_API_KEY"),
    Plaintext: true,
})
if err != nil {
    return err
}
defer galaxy.Close()

ok, err := galaxy.TestConnection(ctx)
deployTime, present, err := galaxy.GetLastDeployTime(ctx)
objects, err := galaxy.DiscoverHierarchy(ctx)
```

`GetLastDeployTime` returns `(time.Time{}, false, nil)` when the server
reports `present=false` (no deploy recorded). `DiscoverHierarchy` returns
the generated `*GalaxyObject` slice with each object's dynamic attributes
populated for direct contract access.

### Browsing lazily

For UI trees or OPC UA bridges, use `BrowseChildren` to walk one level at a
time instead of loading the full hierarchy. Pass an empty request for root
objects; subsequent calls set `ParentGobjectId`, `ParentTagName`, or
`ParentContainedPath`. Filter fields match `DiscoverHierarchy`. Each response
pairs `Children` with `ChildHasChildren` so you know which nodes to expand. See
[Galaxy Repository](../../docs/GalaxyRepository.md#browsechildren) for full
request and filter semantics.

```go
import pb "gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated/galaxy_repository/v1"

reply, err := galaxy.BrowseChildren(ctx, &pb.BrowseChildrenRequest{})
if err != nil {
    return err
}
for i, child := range reply.GetChildren() {
    fmt.Printf("%s expand=%v\n", child.GetTagName(), reply.GetChildHasChildren()[i])
}
```

#### High-level walker

For UI trees, the client provides a `LazyBrowseNode` walker that handles
sibling pagination and the `child_has_children` hint for you:

```go
galaxy, err := mxgateway.DialGalaxy(ctx, mxgateway.Options{
    Endpoint:  "localhost:5000",
    APIKey:    os.Getenv("MXGATEWAY_API_KEY"),
    Plaintext: true,
})
if err != nil {
    log.Fatal(err)
}
defer galaxy.Close()

roots, err := galaxy.Browse(ctx, nil)
if err != nil {
    log.Fatal(err)
}
for _, root := range roots {
    if root.HasChildrenHint() {
        if err := root.Expand(ctx); err != nil {
            log.Fatal(err)
        }
    }
    for _, child := range root.Children() {
        kind := "leaf"
        if child.HasChildrenHint() {
            kind = "has children"
        }
        fmt.Printf("%s (%s)\n", child.Object().GetTagName(), kind)
    }
}
```

`Expand` is idempotent — calling it twice fires only one RPC,
and is safe under concurrent callers. To refresh after a Galaxy redeploy, call
`Browse` again from the root.

### Watching deploy events

`WatchDeployEvents` opens a server-streaming subscription. The server emits a
bootstrap event with the current Galaxy state immediately on subscribe, then
one `DeployEvent` per new deploy. `Sequence` is monotonic per server start;
gaps signal dropped events. Pass a non-nil `lastSeenDeployTime` to suppress the
bootstrap event when resuming from a known checkpoint:

```go
streamCtx, cancel := context.WithCancel(ctx)
defer cancel()

events, errs, err := galaxy.WatchDeployEvents(streamCtx, nil)
if err != nil {
    return err
}

for {
    select {
    case ev, ok := <-events:
        if !ok {
            return nil // stream completed (server EOF or ctx cancelled)
        }
        log.Printf("seq=%d objects=%d attrs=%d",
            ev.GetSequence(), ev.GetObjectCount(), ev.GetAttributeCount())
    case streamErr := <-errs:
        if streamErr != nil {
            return streamErr // *GatewayError
        }
    case <-ctx.Done():
        return ctx.Err()
    }
}
```

Cancel the supplied context to tear down the stream cleanly. Both channels
close after EOF, cancellation, or a terminal error; surfaced errors are wrapped
in `*GatewayError`.

The CLI exposes the same RPC via `galaxy-watch`:

```powershell
go run ./cmd/mxgw-go galaxy-watch -plaintext
go run ./cmd/mxgw-go galaxy-watch -plaintext -json
go run ./cmd/mxgw-go galaxy-watch -plaintext -last-seen-deploy-time 2026-04-28T10:00:00Z
go run ./cmd/mxgw-go galaxy-watch -plaintext -limit 5
```

The command runs until Ctrl+C (or the optional `-limit` is reached) and prints
one line per event in text mode or one JSON object per event with `-json`.

## CLI

The `mxgw-go` CLI emits JSON with redacted API keys for commands that connect to
the gateway:

```powershell
go run ./cmd/mxgw-go version -json
go run ./cmd/mxgw-go open-session -endpoint localhost:5000 -plaintext -json
go run ./cmd/mxgw-go register -session-id <id> -client-name mxgw-go -plaintext -json
go run ./cmd/mxgw-go add-item -session-id <id> -server-handle 1 -item Area001.Tag.Value -plaintext -json
go run ./cmd/mxgw-go advise -session-id <id> -server-handle 1 -item-handle 1 -plaintext -json
go run ./cmd/mxgw-go write -session-id <id> -server-handle 1 -item-handle 1 -type int32 -value 123 -plaintext -json
go run ./cmd/mxgw-go stream-events -session-id <id> -plaintext -json
go run ./cmd/mxgw-go smoke -item Area001.Tag.Value -plaintext -json
go run ./cmd/mxgw-go galaxy-test-connection -plaintext -json
go run ./cmd/mxgw-go galaxy-last-deploy -plaintext -json
go run ./cmd/mxgw-go galaxy-discover -plaintext -json
go run ./cmd/mxgw-go galaxy-watch -plaintext -json
```

Use `-api-key-env MXGATEWAY_API_KEY` or `-api-key <key>` when authentication is
enabled. CLI output redacts the key value and never writes the raw secret.

Use TLS options for a secured gateway:

```powershell
go run ./cmd/mxgw-go smoke -endpoint mxgateway.example.local:5001 -ca-cert C:\certs\mxgateway-ca.pem -server-name-override mxgateway.example.local -api-key-env MXGATEWAY_API_KEY -item Area001.Tag.Value -json
```

## Integration Checks

Run live checks only when a gateway and MXAccess-backed worker are available:

```powershell
$env:MXGATEWAY_INTEGRATION = '1'
$env:MXGATEWAY_ENDPOINT = 'localhost:5000'
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
$env:MXGATEWAY_TEST_ITEM = 'Area001.Tag.Value'
go run ./cmd/mxgw-go smoke -endpoint $env:MXGATEWAY_ENDPOINT -plaintext -api-key-env MXGATEWAY_API_KEY -item $env:MXGATEWAY_TEST_ITEM -json
```

## Installing the Go client

The module is resolved directly from the git repo — no package registry:

````bash
go get gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go@v0.1.0
````

Then import:

````go
import "gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/mxgateway"
````

If your build environment cannot reach `gitea.dohertylan.com` directly,
configure `GOPROXY` to point at an internal proxy that fronts the Gitea
repo, or use `GONOSUMCHECK` + `GOPRIVATE` to bypass the checksum database
for the internal module path.

## Releasing a new version

Go modules in monorepo subdirectories use prefixed tags. To tag a release
from this repo:

````bash
pwsh scripts/tag-go-module.ps1 -Version v0.1.1 -Push
````

The script validates semver, refuses to tag with uncommitted tracked
changes, creates an annotated tag `clients/go/v0.1.1`, and (with `-Push`)
pushes it to origin.

## Related Documentation

- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Go Client Detailed Design](./GoClientDesign.md)
