Align docs with StyleGuide and add CLAUDE.md

- Rename 16 kebab-case docs to PascalCase per StyleGuide
- Move per-language client design docs from docs/ to clients/<lang>/
  alongside their READMEs
- Add ## Related Documentation sections to 15 docs that lacked one
- Fix sentence-case violations in H3 headings (StyleGuide rule)
- Update cross-references in gateway.md, client READMEs, scripts,
  and generate-proto.ps1 helpers to follow the new paths
- Add CLAUDE.md with build/test commands, the source-update
  verification matrix, the parity-first contract, and pointers
  to MXAccess and Galaxy Repository analysis sources

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-30 10:19:22 -04:00
parent 133c83029b
commit 51a9dadf62
45 changed files with 522 additions and 134 deletions
+220
View File
@@ -0,0 +1,220 @@
# .NET 10 C# Client Detailed Design
## Purpose
Provide an idiomatic .NET 10 C# client library for MXAccess Gateway, plus a test
CLI and unit tests. This client is for modern .NET callers and must not load
MXAccess COM.
Follow the [C# Style Guide](../../docs/style-guides/CSharpStyleGuide.md) for
handwritten code and the [Protobuf Style Guide](../../docs/style-guides/ProtobufStyleGuide.md)
for generated contract inputs.
## Projects
Recommended layout:
```text
clients/dotnet/
MxGateway.Client.sln
MxGateway.Client/
MxGateway.Client.csproj
GatewayClient.cs
MxGatewaySession.cs
MxGatewayClientOptions.cs
Authentication/
Conversion/
Errors/
Generated/
MxGateway.Client.Cli/
MxGateway.Client.Cli.csproj
Program.cs
Commands/
MxGateway.Client.Tests/
MxGateway.Client.Tests.csproj
MxGateway.Client.IntegrationTests/
MxGateway.Client.IntegrationTests.csproj
```
Target framework:
```xml
<TargetFramework>net10.0</TargetFramework>
```
The scaffold uses a project reference to
`src/MxGateway.Contracts/MxGateway.Contracts.csproj` for generated protobuf and
gRPC types. `clients/dotnet/generated` remains reserved for client-local
generator output if the .NET client later needs to decouple from the contracts
project.
Expected packages:
- `Grpc.Net.Client`
- `Google.Protobuf`
- `Grpc.Tools` for generation
- `Microsoft.Extensions.Logging.Abstractions`
- `System.CommandLine` or similar for CLI
- test framework: xUnit or NUnit
## Library API
Suggested public types:
```csharp
public sealed class MxGatewayClient : IAsyncDisposable
{
public static MxGatewayClient Create(MxGatewayClientOptions options);
public Task<MxGatewaySession> OpenSessionAsync(
OpenSessionOptions? options = null,
CancellationToken cancellationToken = default);
public Task<MxCommandReply> InvokeAsync(
MxCommandRequest request,
CancellationToken cancellationToken = default);
}
public sealed class MxGatewaySession : IAsyncDisposable
{
public string SessionId { get; }
public Task<int> RegisterAsync(string clientName, CancellationToken ct = default);
public Task UnregisterAsync(int serverHandle, CancellationToken ct = default);
public Task<int> AddItemAsync(int serverHandle, string item, CancellationToken ct = default);
public Task<int> AddItem2Async(int serverHandle, string item, string context, CancellationToken ct = default);
public Task AdviseAsync(int serverHandle, int itemHandle, CancellationToken ct = default);
public Task UnAdviseAsync(int serverHandle, int itemHandle, CancellationToken ct = default);
public Task<IReadOnlyList<SubscribeResult>> AddItemBulkAsync(int serverHandle, IReadOnlyList<string> tagAddresses, CancellationToken ct = default);
public Task<IReadOnlyList<SubscribeResult>> AdviseItemBulkAsync(int serverHandle, IReadOnlyList<int> itemHandles, CancellationToken ct = default);
public Task<IReadOnlyList<SubscribeResult>> RemoveItemBulkAsync(int serverHandle, IReadOnlyList<int> itemHandles, CancellationToken ct = default);
public Task<IReadOnlyList<SubscribeResult>> UnAdviseItemBulkAsync(int serverHandle, IReadOnlyList<int> itemHandles, CancellationToken ct = default);
public Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(int serverHandle, IReadOnlyList<string> tagAddresses, CancellationToken ct = default);
public Task<IReadOnlyList<SubscribeResult>> UnsubscribeBulkAsync(int serverHandle, IReadOnlyList<int> itemHandles, CancellationToken ct = default);
public Task WriteAsync(int serverHandle, int itemHandle, MxValue value, int userId, CancellationToken ct = default);
public IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken ct = default);
public Task CloseAsync(CancellationToken ct = default);
}
```
Generated protobuf types should remain available under a generated namespace.
Handwritten wrappers should not hide raw replies.
## Options
```csharp
public sealed class MxGatewayClientOptions
{
public required Uri Endpoint { get; init; }
public required string ApiKey { get; init; }
public bool UseTls { get; init; }
public string? CaCertificatePath { get; init; }
public string? ServerNameOverride { get; init; }
public TimeSpan ConnectTimeout { get; init; } = TimeSpan.FromSeconds(10);
public TimeSpan DefaultCallTimeout { get; init; } = TimeSpan.FromSeconds(30);
public MxGatewayClientRetryOptions Retry { get; init; } = new();
public ILoggerFactory? LoggerFactory { get; init; }
}
```
The .NET client applies a bounded Polly retry policy only to idempotent calls:
`CloseSession` and diagnostic `Invoke` commands such as `Ping`,
`GetSessionState`, and `GetWorkerInfo`. It does not retry `OpenSession`, event
streams, writes, secured writes, authentication, registration, item management,
or subscription changes because those calls can partially succeed in MXAccess.
API key may be loaded from `MXGATEWAY_API_KEY` by the CLI, not implicitly by the
library constructor unless a helper explicitly says it does that.
## Auth Interceptor
Use a gRPC call credentials/interceptor layer to attach:
```text
authorization: Bearer <api key>
```
The interceptor must redact the key in logs and exceptions.
## Streaming
Expose `StreamEventsAsync` as `IAsyncEnumerable<MxEvent>`. On cancellation,
cancel the gRPC stream and surface `OperationCanceledException` only when the
caller initiated cancellation.
Do not reorder events.
## Error Handling
Recommended exceptions:
```csharp
MxGatewayException
MxGatewayAuthenticationException
MxGatewayAuthorizationException
MxGatewaySessionException
MxGatewayWorkerException
MxGatewayCommandException
MxAccessException
```
For command replies that include MXAccess HRESULT/status, prefer returning the
reply and exposing helper methods:
```csharp
reply.EnsureProtocolSuccess();
reply.EnsureMxAccessSuccess();
```
## Test CLI
Project: `MxGateway.Client.Cli`.
Command examples:
```powershell
mxgw-dotnet version
mxgw-dotnet smoke --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --item TestChildObject.TestInt
mxgw-dotnet stream-events --session-id <id> --json
mxgw-dotnet write --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123
```
The CLI should use `System.CommandLine` or a similarly testable parser. JSON
output should be deterministic and redact API keys.
## Unit Tests
Use an in-process fake gRPC service with `Grpc.AspNetCore.Server` test host or
mock the generated client behind an internal interface.
Required tests:
- auth metadata is attached,
- API key is redacted,
- options build plaintext and TLS channels correctly,
- `RegisterAsync` builds the right command payload,
- `AddItem2Async` includes context,
- `WriteAsync` converts scalar and array values,
- command reply status helpers preserve MXAccess HRESULT,
- `StreamEventsAsync` yields ordered events,
- stream cancellation disposes the call,
- CLI parsing and JSON output.
## Integration Tests
Use xUnit traits or categories. Skip unless:
```text
MXGATEWAY_INTEGRATION=1
MXGATEWAY_ENDPOINT=<endpoint>
MXGATEWAY_API_KEY=<key>
MXGATEWAY_TEST_ITEM=<item>
```
Integration smoke should open, register, add, advise, stream for bounded time,
and close.
## Related Documentation
- [Client Libraries Detailed Design](../../docs/ClientLibrariesDesign.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Client Packaging](../../docs/ClientPackaging.md)
- [C# Style Guide](../../docs/style-guides/CSharpStyleGuide.md)
+15 -2
View File
@@ -164,6 +164,19 @@ foreach (GalaxyObject galaxyObject in objects)
}
```
Use `DiscoverHierarchyOptions` to request a server-side slice without pulling
the full Galaxy:
```csharp
IReadOnlyList<GalaxyObject> pumps = await repository.DiscoverHierarchyAsync(
new DiscoverHierarchyOptions
{
RootContainedPath = "Area1/Line3",
TagNameGlob = "Pump_*",
IncludeAttributes = false,
});
```
The CLI exposes the same operations:
```powershell
@@ -230,5 +243,5 @@ dotnet run --project clients/dotnet/MxGateway.Client.Cli -- smoke --endpoint $en
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [.NET Client Detailed Design](../../docs/clients-dotnet-csharp-design.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [.NET Client Detailed Design](./DotnetClientDesign.md)
+185
View File
@@ -0,0 +1,185 @@
# 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.
## 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)
+4 -4
View File
@@ -3,7 +3,7 @@
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/client-proto-generation.md` so gateway and client contracts stay in
`../../docs/ClientProtoGeneration.md` so gateway and client contracts stay in
sync.
## Layout
@@ -28,7 +28,7 @@ Run generation after the shared `.proto` files or the Go output path changes:
./generate-proto.ps1
```
The script uses the tool paths recorded in `../../docs/toolchain-links.md`.
The script uses the tool paths recorded in `../../docs/ToolchainLinks.md`.
## Build And Test
@@ -209,5 +209,5 @@ go run ./cmd/mxgw-go smoke -endpoint $env:MXGATEWAY_ENDPOINT -plaintext -api-key
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [Go Client Detailed Design](../../docs/clients-golang-design.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Go Client Detailed Design](./GoClientDesign.md)
+2 -2
View File
@@ -9,13 +9,13 @@ $protoc = 'C:\Users\dohertj2\AppData\Local\Microsoft\WinGet\Packages\Google.Prot
$goPluginPath = 'C:\Users\dohertj2\go\bin'
if (-not (Test-Path $protoc)) {
throw "protoc was not found at $protoc. See docs/toolchain-links.md."
throw "protoc was not found at $protoc. See docs/ToolchainLinks.md."
}
foreach ($pluginName in @('protoc-gen-go.exe', 'protoc-gen-go-grpc.exe')) {
$pluginPath = Join-Path $goPluginPath $pluginName
if (-not (Test-Path $pluginPath)) {
throw "$pluginName was not found at $pluginPath. See docs/toolchain-links.md."
throw "$pluginName was not found at $pluginPath. See docs/ToolchainLinks.md."
}
}
+219
View File
@@ -0,0 +1,219 @@
# Java Client Detailed Design
## Purpose
Provide a Java client library for MXAccess Gateway, plus a test CLI and unit
tests. The Java client should work for JVM services and operator tooling.
Follow the [Java Style Guide](../../docs/style-guides/JavaStyleGuide.md) for handwritten
code and the [Protobuf Style Guide](../../docs/style-guides/ProtobufStyleGuide.md) for
generated contract inputs.
## Build Layout
Recommended Gradle multi-project layout:
```text
clients/java/
settings.gradle
build.gradle
src/main/generated/
mxgateway-client/
build.gradle
src/main/java/com/dohertylan/mxgateway/client/
src/test/java/com/dohertylan/mxgateway/client/
mxgateway-cli/
build.gradle
src/main/java/com/dohertylan/mxgateway/cli/
```
Alternative Maven layout is acceptable if the repo standardizes on Maven.
Target Java:
- Java 21 recommended.
- The Gradle scaffold uses the Java 21 toolchain for compilation and tests.
Expected dependencies:
- `grpc-netty-shaded`
- `grpc-protobuf`
- `grpc-stub`
- `protobuf-java`
- `picocli`
- `junit-jupiter`
- `mockito` if needed
## Library API
Suggested API:
```java
public final class MxGatewayClient implements AutoCloseable {
public static MxGatewayClient connect(MxGatewayClientOptions options);
public MxGatewaySession openSession(OpenSessionOptions options);
public MxCommandReply invoke(MxCommandRequest request);
public CompletableFuture<MxCommandReply> invokeAsync(MxCommandRequest request);
public void close();
}
public final class MxGatewaySession implements AutoCloseable {
public String sessionId();
public int register(String clientName);
public void unregister(int serverHandle);
public int addItem(int serverHandle, String item);
public int addItem2(int serverHandle, String item, String context);
public void advise(int serverHandle, int itemHandle);
public List<SubscribeResult> addItemBulk(int serverHandle, List<String> tagAddresses);
public List<SubscribeResult> adviseItemBulk(int serverHandle, List<Integer> itemHandles);
public List<SubscribeResult> removeItemBulk(int serverHandle, List<Integer> itemHandles);
public List<SubscribeResult> unAdviseItemBulk(int serverHandle, List<Integer> itemHandles);
public List<SubscribeResult> subscribeBulk(int serverHandle, List<String> tagAddresses);
public List<SubscribeResult> unsubscribeBulk(int serverHandle, List<Integer> itemHandles);
public void write(int serverHandle, int itemHandle, MxValue value, int userId);
public Iterator<MxEvent> streamEvents();
public void streamEventsAsync(StreamObserver<MxEvent> observer);
public void close();
}
```
Expose generated protobuf classes for callers that need raw access.
## Options
```java
public final class MxGatewayClientOptions {
URI endpoint;
String apiKey;
boolean plaintext;
Path caCertificatePath;
String serverNameOverride;
Duration connectTimeout;
Duration callTimeout;
}
```
## Authentication
Use a gRPC `ClientInterceptor` to attach:
```text
authorization: Bearer <api key>
```
Redact API keys in `toString`, logs, and CLI output.
## TLS
Support:
- plaintext for local development,
- TLS with default JVM trust store,
- custom CA certificate file,
- server name override for test environments.
## Streaming
Support both:
- blocking iterator for simple CLIs,
- async `StreamObserver` for services.
Do not reorder events. Stream cancellation should call `ClientCall.cancel`.
## Error Handling
Recommended exceptions:
```java
MxGatewayException
MxGatewayAuthenticationException
MxGatewayAuthorizationException
MxGatewaySessionException
MxGatewayWorkerException
MxGatewayCommandException
MxAccessException
```
`MxGatewayCommandException` should carry the raw command reply when available.
## Test CLI
Binary wrapper name:
```text
mxgw-java
```
Use `picocli`.
Commands:
```text
mxgw-java version
mxgw-java smoke --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --item TestChildObject.TestInt
mxgw-java stream-events --session-id <id> --json
mxgw-java write --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123
```
JSON output can use Jackson or protobuf JSON formatting. Keep it deterministic.
## Unit Tests
Use JUnit 5.
Use `InProcessServerBuilder` and `InProcessChannelBuilder` for fake gRPC tests.
Required tests:
- auth interceptor attaches metadata,
- key redaction,
- plaintext and TLS channel setup,
- request construction helpers,
- value conversion,
- status/error mapping,
- blocking event stream iteration,
- async stream observer cancellation,
- CLI parsing,
- JSON output.
## Integration Tests
Skip unless:
```text
MXGATEWAY_INTEGRATION=1
```
Use JUnit assumptions. Integration flow should open, register, add, advise,
stream for bounded time, and close.
## Packaging
Publish library and CLI separately:
- `mxgateway-client` jar,
- `mxgateway-cli` runnable distribution.
Generated protobuf code should be produced during the build from shared proto
files and should not be hand-edited.
## Current Build
Run the Java scaffold checks from `clients/java`:
```powershell
gradle test
```
The `mxgateway-client` project generates the gateway and worker protobuf/gRPC
bindings into `src/main/generated`, compiles the generated contracts, and runs
JUnit 5 tests. The `mxgateway-cli` project builds a Picocli-based `mxgw-java`
entry point for later command implementation.
## Related Documentation
- [Client Libraries Detailed Design](../../docs/ClientLibrariesDesign.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Client Packaging](../../docs/ClientPackaging.md)
- [Java Style Guide](../../docs/style-guides/JavaStyleGuide.md)
+2 -2
View File
@@ -223,6 +223,6 @@ gradle :mxgateway-cli:run --args="smoke --endpoint $env:MXGATEWAY_ENDPOINT --pla
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [Java Client Detailed Design](../../docs/clients-java-design.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Java Client Detailed Design](./JavaClientDesign.md)
- [Java Style Guide](../../docs/style-guides/JavaStyleGuide.md)
+204
View File
@@ -0,0 +1,204 @@
# Python Client Detailed Design
## Purpose
Provide an async Python client package for MXAccess Gateway, plus a test CLI and
unit tests. The Python client should be useful for automation, diagnostics, and
test harnesses.
Follow the [Python Style Guide](../../docs/style-guides/PythonStyleGuide.md) for
handwritten code and the [Protobuf Style Guide](../../docs/style-guides/ProtobufStyleGuide.md)
for generated contract inputs.
## Package Layout
Recommended layout:
```text
clients/python/
pyproject.toml
src/mxgateway/
__init__.py
client.py
session.py
options.py
auth.py
values.py
errors.py
generated/
src/mxgateway_cli/
__main__.py
commands.py
tests/
```
Expected dependencies:
- `grpcio`
- `grpcio-tools`
- `protobuf`
- `click` or `typer`
- `pytest`
- `pytest-asyncio`
## Library API
Use async-first API. A sync wrapper can be added later if needed.
Suggested API:
```python
client = await GatewayClient.connect(
endpoint="localhost:5000",
api_key=api_key,
plaintext=True,
)
session = await client.open_session()
server = await session.register("python-client")
item = await session.add_item(server, "TestChildObject.TestInt")
await session.advise(server, item)
async for event in session.stream_events():
...
await session.close()
await client.close()
```
Classes:
```python
class GatewayClient:
@classmethod
async def connect(cls, options: ClientOptions) -> "GatewayClient": ...
async def open_session(self, options: OpenSessionOptions | None = None) -> "Session": ...
async def invoke(self, request: MxCommandRequest) -> MxCommandReply: ...
async def close(self) -> None: ...
class Session:
session_id: str
async def register(self, client_name: str) -> int: ...
async def add_item(self, server_handle: int, item: str) -> int: ...
async def add_item2(self, server_handle: int, item: str, context: str) -> int: ...
async def advise(self, server_handle: int, item_handle: int) -> None: ...
async def add_item_bulk(self, server_handle: int, tag_addresses: Sequence[str]) -> list[SubscribeResult]: ...
async def advise_item_bulk(self, server_handle: int, item_handles: Sequence[int]) -> list[SubscribeResult]: ...
async def remove_item_bulk(self, server_handle: int, item_handles: Sequence[int]) -> list[SubscribeResult]: ...
async def unadvise_item_bulk(self, server_handle: int, item_handles: Sequence[int]) -> list[SubscribeResult]: ...
async def subscribe_bulk(self, server_handle: int, tag_addresses: Sequence[str]) -> list[SubscribeResult]: ...
async def unsubscribe_bulk(self, server_handle: int, item_handles: Sequence[int]) -> list[SubscribeResult]: ...
async def write(self, server_handle: int, item_handle: int, value: MxValueInput, user_id: int = 0) -> None: ...
async def stream_events(self) -> AsyncIterator[MxEvent]: ...
async def close(self) -> None: ...
```
## Authentication
Use gRPC metadata:
```python
metadata = (("authorization", f"Bearer {api_key}"),)
```
Provide a metadata helper that all unary and streaming calls use. Redact API
keys in exceptions and CLI output.
## TLS
Support:
- insecure channel for local development,
- TLS channel with default roots,
- custom root certificate file.
## Streaming
Expose `stream_events` as an async iterator. Canceling the task should cancel
the gRPC stream.
Do not hide stream errors. Convert common auth/session errors into typed
exceptions.
## Error Handling
Define typed exceptions:
```python
MxGatewayError
MxGatewayTransportError
MxGatewayAuthenticationError
MxGatewayAuthorizationError
MxGatewaySessionError
MxGatewayWorkerError
MxGatewayCommandError
MxAccessError
```
`MxGatewayCommandError` should include the raw protobuf reply when available.
## Test CLI
Entry point:
```text
mxgw-py
```
Recommended commands:
```text
mxgw-py version
mxgw-py smoke --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --item TestChildObject.TestInt
mxgw-py stream-events --session-id <id> --json
mxgw-py write --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123
```
Use `click` or `typer`. JSON output should be stable for test automation.
## Unit Tests
Use `pytest` and `pytest-asyncio`.
Use fake generated stubs or an in-process test gRPC server where practical.
Required tests:
- API key metadata injection,
- API key redaction,
- insecure and TLS channel option construction,
- request construction for method helpers,
- value conversion from Python values,
- status/error mapping,
- async event iteration,
- stream cancellation,
- CLI parsing,
- JSON output.
## Integration Tests
Skip unless:
```text
MXGATEWAY_INTEGRATION=1
```
Use bounded smoke flow and always attempt `close_session` in `finally`.
## Packaging
Use `pyproject.toml`. Publishable package name should be stable, for example:
```text
mxaccess-gateway-client
```
Generated protobuf code should be regenerated through a documented command, not
edited by hand.
## Related Documentation
- [Client Libraries Detailed Design](../../docs/ClientLibrariesDesign.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Client Packaging](../../docs/ClientPackaging.md)
- [Python Style Guide](../../docs/style-guides/PythonStyleGuide.md)
+4 -4
View File
@@ -3,7 +3,7 @@
The Python client package contains generated MXAccess Gateway protobuf
bindings, the async `mxgateway` package, and the `mxgw-py` test CLI. The
package uses the shared proto inputs documented in
`../../docs/client-proto-generation.md` so gateway and client contracts stay in
`../../docs/ClientProtoGeneration.md` so gateway and client contracts stay in
sync.
## Layout
@@ -31,7 +31,7 @@ changes:
```
The script uses the Python tool path recorded in
`../../docs/toolchain-links.md`.
`../../docs/ToolchainLinks.md`.
## Build And Test
@@ -219,5 +219,5 @@ mxgw-py smoke --endpoint $env:MXGATEWAY_ENDPOINT --plaintext --api-key-env MXGAT
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [Python Client Detailed Design](../../docs/clients-python-design.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Python Client Detailed Design](./PythonClientDesign.md)
+1 -1
View File
@@ -7,7 +7,7 @@ $outputRoot = Join-Path $PSScriptRoot 'src\mxgateway\generated'
$python = 'C:\Users\dohertj2\AppData\Local\Programs\Python\Python312\python.exe'
if (-not (Test-Path $python)) {
throw "Python was not found at $python. See docs/toolchain-links.md."
throw "Python was not found at $python. See docs/ToolchainLinks.md."
}
New-Item -ItemType Directory -Path $outputRoot -Force | Out-Null
+4 -4
View File
@@ -4,7 +4,7 @@ The Rust client workspace contains the MXAccess Gateway client library, a
test CLI, and tests for generated contract wiring plus wrapper behavior. The
library uses
the shared protobuf inputs documented in
`../../docs/client-proto-generation.md` so the Rust bindings compile against
`../../docs/ClientProtoGeneration.md` so the Rust bindings compile against
the same public gateway and worker contracts as the server.
## Layout
@@ -36,7 +36,7 @@ cargo clippy --workspace --all-targets -- -D warnings
```
The build script uses `protoc` from `PATH` or the Windows path recorded in
`../../docs/toolchain-links.md`.
`../../docs/ToolchainLinks.md`.
## Packaging
@@ -184,6 +184,6 @@ cargo run -p mxgw-cli -- smoke --endpoint $env:MXGATEWAY_ENDPOINT --plaintext --
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [Rust Client Detailed Design](../../docs/clients-rust-design.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Rust Client Detailed Design](./RustClientDesign.md)
- [Rust Style Guide](../../docs/style-guides/RustStyleGuide.md)
+196
View File
@@ -0,0 +1,196 @@
# Rust Client Detailed Design
## Purpose
Provide an async Rust client crate for MXAccess Gateway, plus a test CLI and
unit tests. The Rust client should use `tonic` and `tokio`.
Follow the [Rust Style Guide](../../docs/style-guides/RustStyleGuide.md) for handwritten
code and the [Protobuf Style Guide](../../docs/style-guides/ProtobufStyleGuide.md) for
generated contract inputs.
## Crate Layout
Recommended layout:
```text
clients/rust/
Cargo.toml
build.rs
crates/
mxgateway-client/
src/lib.rs
src/client.rs
src/session.rs
src/options.rs
src/auth.rs
src/value.rs
src/error.rs
src/generated/
mxgw-cli/
src/main.rs
tests/
```
Expected dependencies:
- `tonic`
- `prost`
- `prost-types`
- `tokio`
- `tokio-stream`
- `thiserror`
- `clap`
- `serde`
- `serde_json`
- `tracing`
## Library API
Suggested API:
```rust
pub struct GatewayClient { /* tonic channel + generated client */ }
pub struct ClientOptions {
pub endpoint: String,
pub api_key: String,
pub plaintext: bool,
pub ca_file: Option<PathBuf>,
pub server_name_override: Option<String>,
pub connect_timeout: Duration,
pub call_timeout: Duration,
}
impl GatewayClient {
pub async fn connect(options: ClientOptions) -> Result<Self, Error>;
pub async fn open_session(&self, options: OpenSessionOptions) -> Result<Session, Error>;
pub async fn invoke(&self, request: MxCommandRequest) -> Result<MxCommandReply, Error>;
}
```
Session:
```rust
pub struct Session {
pub id: String,
}
impl Session {
pub async fn register(&self, client_name: &str) -> Result<i32, Error>;
pub async fn add_item(&self, server_handle: i32, item: &str) -> Result<i32, Error>;
pub async fn add_item2(&self, server_handle: i32, item: &str, context: &str) -> Result<i32, Error>;
pub async fn advise(&self, server_handle: i32, item_handle: i32) -> Result<(), Error>;
pub async fn add_item_bulk(&self, server_handle: i32, tag_addresses: Vec<String>) -> Result<Vec<SubscribeResult>, Error>;
pub async fn advise_item_bulk(&self, server_handle: i32, item_handles: Vec<i32>) -> Result<Vec<SubscribeResult>, Error>;
pub async fn remove_item_bulk(&self, server_handle: i32, item_handles: Vec<i32>) -> Result<Vec<SubscribeResult>, Error>;
pub async fn un_advise_item_bulk(&self, server_handle: i32, item_handles: Vec<i32>) -> Result<Vec<SubscribeResult>, Error>;
pub async fn subscribe_bulk(&self, server_handle: i32, tag_addresses: Vec<String>) -> Result<Vec<SubscribeResult>, Error>;
pub async fn unsubscribe_bulk(&self, server_handle: i32, item_handles: Vec<i32>) -> Result<Vec<SubscribeResult>, Error>;
pub async fn write(&self, server_handle: i32, item_handle: i32, value: MxValue, user_id: i32) -> Result<(), Error>;
pub async fn events(&self) -> Result<impl Stream<Item = Result<MxEvent, Error>>, Error>;
pub async fn close(&self) -> Result<(), Error>;
}
```
## Authentication
Use a `tonic` interceptor or request extension layer to add:
```text
authorization: Bearer <api key>
```
Use `SecretString` or equivalent if a dependency is acceptable. Always redact
API keys in `Debug` output.
## TLS
Support:
- plaintext channel for local development,
- native or rustls TLS depending on project preference,
- custom CA file,
- domain override.
## Streaming
Expose event streams as a `Stream<Item = Result<MxEvent, Error>>`. Dropping the
stream should cancel the underlying gRPC stream.
Do not buffer unboundedly in the client. If a helper channel is used, make it
bounded.
## Error Handling
Use `thiserror`:
```rust
pub enum Error {
Transport(tonic::transport::Error),
Status(tonic::Status),
Authentication(String),
Authorization(String),
Session(SessionError),
Worker(WorkerError),
Command(CommandError),
MxAccess(MxAccessError),
Timeout,
Cancelled,
}
```
Preserve raw command replies in `CommandError` where applicable.
## Test CLI
Binary: `mxgw`.
Use `clap` derive.
Commands:
```text
mxgw version
mxgw smoke --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --item TestChildObject.TestInt
mxgw stream-events --session-id <id> --json
mxgw write --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123
```
JSON output should use `serde_json`.
## Unit Tests
Use a fake `tonic` server started on a local ephemeral port, or abstract the
generated client behind a trait for unit tests.
Required tests:
- generated client compiles from proto,
- auth metadata injection,
- TLS/plaintext endpoint construction,
- value conversion,
- command request construction,
- error mapping from `tonic::Status`,
- event stream order,
- stream cancellation,
- CLI parsing,
- JSON redaction.
## Integration Tests
Skip unless:
```text
MXGATEWAY_INTEGRATION=1
```
Use `tokio::test`. Run bounded smoke flow and ensure `CloseSession` is attempted
with `drop` fallback docs, but do not rely on `Drop` for async close.
## Related Documentation
- [Client Libraries Detailed Design](../../docs/ClientLibrariesDesign.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Client Packaging](../../docs/ClientPackaging.md)
- [Rust Style Guide](../../docs/style-guides/RustStyleGuide.md)