feat(comm): stream List attribute values as canonical JSON

Replace ValueFormatter.FormatDisplayValue with AttributeValueCodec.Encode
in StreamRelayActor so List<T> attribute values cross the gRPC wire as a
JSON array (e.g. ["a","b"]) rather than a comma-joined display string.
Scalars and null values are unaffected. Tests cover List→JSON, scalar
string pass-through, and null→empty-string.
This commit is contained in:
Joseph Doherty
2026-06-16 15:37:33 -04:00
parent 492b41f0fd
commit ba414cbb68
2 changed files with 34 additions and 1 deletions
@@ -45,7 +45,7 @@ public class StreamRelayActor : ReceiveActor
InstanceUniqueName = msg.InstanceUniqueName,
AttributePath = msg.AttributePath,
AttributeName = msg.AttributeName,
Value = ValueFormatter.FormatDisplayValue(msg.Value),
Value = AttributeValueCodec.Encode(msg.Value) ?? string.Empty,
Quality = MapQuality(msg.Quality),
Timestamp = Timestamp.FromDateTimeOffset(msg.Timestamp)
}
@@ -204,4 +204,37 @@ public class StreamRelayActorTests : TestKit
Assert.True(channel.Reader.TryRead(out var evt));
Assert.Equal("", evt.AttributeChanged.Value);
}
[Fact]
public void ListValue_EncodesAsJsonArray()
{
// List attributes must cross the wire as JSON, not as a comma-joined display string.
var channel = Channel.CreateUnbounded<SiteStreamEvent>();
var actor = Sys.ActorOf(Props.Create(() =>
new StreamRelayActor("corr-list", channel.Writer)));
var ts = DateTimeOffset.UtcNow;
actor.Tell(new AttributeValueChanged("Inst", "Path", "Tags",
new List<string> { "a", "b" }, "Good", ts));
Thread.Sleep(500);
Assert.True(channel.Reader.TryRead(out var evt));
Assert.Equal("[\"a\",\"b\"]", evt.AttributeChanged.Value);
}
[Fact]
public void ScalarStringValue_PassesThroughUnchanged()
{
// Scalar strings must not be double-encoded.
var channel = Channel.CreateUnbounded<SiteStreamEvent>();
var actor = Sys.ActorOf(Props.Create(() =>
new StreamRelayActor("corr-scalar", channel.Writer)));
var ts = DateTimeOffset.UtcNow;
actor.Tell(new AttributeValueChanged("Inst", "Path", "Name", "x", "Good", ts));
Thread.Sleep(500);
Assert.True(channel.Reader.TryRead(out var evt));
Assert.Equal("x", evt.AttributeChanged.Value);
}
}