45 lines
2.5 KiB
C#
45 lines
2.5 KiB
C#
using System.Text.Json;
|
|
using Kbx.Shared.Telemetry.Generated;
|
|
using Kbx.Shared.Experiments.Generated;
|
|
|
|
namespace Kbx.Shared.Telemetry;
|
|
|
|
public sealed record KbxUxEventRequest(
|
|
string EventName,
|
|
string? ScreenId,
|
|
string? ScreenVersion,
|
|
Guid SessionId,
|
|
Guid? TaskSessionId,
|
|
string? AppVersion,
|
|
string? ExperimentId,
|
|
string? ExperimentVariant,
|
|
DateTimeOffset OccurredAt,
|
|
int? DurationMs,
|
|
int? Count,
|
|
IReadOnlyDictionary<string,string>? Attributes);
|
|
|
|
public sealed record KbxUxEventBatchRequest(IReadOnlyList<KbxUxEventRequest> Events);
|
|
public sealed record KbxUxMetricRow(string MetricKey,string Label,double? Value,string Unit,long SampleCount,double? P50=null,double? P95=null);
|
|
public sealed record KbxUxMetricsResponse(DateOnly From,DateOnly To,string? ScreenId,IReadOnlyList<KbxUxMetricRow> Metrics);
|
|
|
|
public static class KbxUxTelemetryGuard
|
|
{
|
|
private static readonly string[] ForbiddenFragments = ["phone","address","name","orderno","customer","itemcode","barcode","keyword","query","email","businessnumber","entityid"];
|
|
public static void Validate(KbxUxEventRequest item)
|
|
{
|
|
if (!KbxTelemetryCatalog.Events.TryGetValue(item.EventName,out var definition)) throw new ArgumentException($"Unknown telemetry event: {item.EventName}");
|
|
if (definition.RequiresDuration && item.DurationMs is null) throw new ArgumentException($"{item.EventName} requires durationMs.");
|
|
if (item.DurationMs is < 0 or > 86_400_000) throw new ArgumentOutOfRangeException(nameof(item.DurationMs));
|
|
if (item.ExperimentId is { Length: > 160 } || item.ExperimentVariant is { Length: > 80 }) throw new ArgumentException("Experiment context is too long.");
|
|
if ((item.ExperimentId is null) != (item.ExperimentVariant is null)) throw new ArgumentException("Experiment id and variant must be supplied together.");
|
|
if (item.Count is < 1 or > 1_000_000) throw new ArgumentOutOfRangeException(nameof(item.Count));
|
|
var allowed=definition.AllowedAttributes.ToHashSet(StringComparer.Ordinal);
|
|
foreach(var pair in item.Attributes ?? new Dictionary<string,string>())
|
|
{
|
|
if(!allowed.Contains(pair.Key)) throw new ArgumentException($"Attribute '{pair.Key}' is not allowed for {item.EventName}.");
|
|
if(ForbiddenFragments.Any(x=>pair.Key.Contains(x,StringComparison.OrdinalIgnoreCase))) throw new ArgumentException($"Attribute '{pair.Key}' is forbidden for telemetry.");
|
|
if(pair.Value.Length>80) throw new ArgumentException($"Attribute value is too long: {pair.Key}");
|
|
}
|
|
}
|
|
}
|