using System.Text.Json; using System.Text.Json.Serialization; namespace StellaOps.Bench.ScannerAnalyzers; internal sealed record BenchmarkConfig { [JsonPropertyName("iterations")] public int? Iterations { get; init; } [JsonPropertyName("thresholdMs")] public double? ThresholdMs { get; init; } [JsonPropertyName("scenarios")] public List Scenarios { get; init; } = new(); public static async Task LoadAsync(string path) { if (string.IsNullOrWhiteSpace(path)) { throw new ArgumentException("Config path is required.", nameof(path)); } await using var stream = File.OpenRead(path); var config = await JsonSerializer.DeserializeAsync(stream, SerializerOptions).ConfigureAwait(false); if (config is null) { throw new InvalidOperationException($"Failed to parse benchmark config '{path}'."); } if (config.Scenarios.Count == 0) { throw new InvalidOperationException("config.scenarios must declare at least one scenario."); } foreach (var scenario in config.Scenarios) { scenario.Validate(); } return config; } private static JsonSerializerOptions SerializerOptions => new() { PropertyNameCaseInsensitive = true, ReadCommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true, }; } internal sealed record BenchmarkScenarioConfig { [JsonPropertyName("id")] public string? Id { get; init; } [JsonPropertyName("label")] public string? Label { get; init; } [JsonPropertyName("root")] public string? Root { get; init; } [JsonPropertyName("analyzers")] public List? Analyzers { get; init; } [JsonPropertyName("matcher")] public string? Matcher { get; init; } [JsonPropertyName("parser")] public string? Parser { get; init; } [JsonPropertyName("thresholdMs")] public double? ThresholdMs { get; init; } public bool HasAnalyzers => Analyzers is { Count: > 0 }; public void Validate() { if (string.IsNullOrWhiteSpace(Id)) { throw new InvalidOperationException("scenario.id is required."); } if (string.IsNullOrWhiteSpace(Root)) { throw new InvalidOperationException($"Scenario '{Id}' must specify a root path."); } if (HasAnalyzers) { return; } if (string.IsNullOrWhiteSpace(Parser)) { throw new InvalidOperationException($"Scenario '{Id}' must specify parser or analyzers."); } if (string.IsNullOrWhiteSpace(Matcher)) { throw new InvalidOperationException($"Scenario '{Id}' must specify matcher when parser is used."); } } }