using System.Diagnostics.CodeAnalysis; namespace StellaOps.Feedser.Source.Osv.Configuration; public sealed class OsvOptions { public const string HttpClientName = "source.osv"; public Uri BaseUri { get; set; } = new("https://osv-vulnerabilities.storage.googleapis.com/", UriKind.Absolute); public IReadOnlyList Ecosystems { get; set; } = new[] { "PyPI", "npm", "Maven", "Go", "crates" }; public TimeSpan InitialBackfill { get; set; } = TimeSpan.FromDays(14); public TimeSpan ModifiedTolerance { get; set; } = TimeSpan.FromMinutes(10); public int MaxAdvisoriesPerFetch { get; set; } = 250; public string ArchiveFileName { get; set; } = "all.zip"; public TimeSpan RequestDelay { get; set; } = TimeSpan.FromMilliseconds(250); public TimeSpan HttpTimeout { get; set; } = TimeSpan.FromMinutes(3); [MemberNotNull(nameof(BaseUri), nameof(Ecosystems), nameof(ArchiveFileName))] public void Validate() { if (BaseUri is null || !BaseUri.IsAbsoluteUri) { throw new InvalidOperationException("OSV base URI must be an absolute URI."); } if (string.IsNullOrWhiteSpace(ArchiveFileName)) { throw new InvalidOperationException("OSV archive file name must be provided."); } if (!ArchiveFileName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("OSV archive file name must be a .zip resource."); } if (Ecosystems is null || Ecosystems.Count == 0) { throw new InvalidOperationException("At least one OSV ecosystem must be configured."); } foreach (var ecosystem in Ecosystems) { if (string.IsNullOrWhiteSpace(ecosystem)) { throw new InvalidOperationException("Ecosystem names cannot be null or whitespace."); } } if (InitialBackfill <= TimeSpan.Zero) { throw new InvalidOperationException("Initial backfill window must be positive."); } if (ModifiedTolerance < TimeSpan.Zero) { throw new InvalidOperationException("Modified tolerance cannot be negative."); } if (MaxAdvisoriesPerFetch <= 0) { throw new InvalidOperationException("Max advisories per fetch must be greater than zero."); } if (RequestDelay < TimeSpan.Zero) { throw new InvalidOperationException("Request delay cannot be negative."); } if (HttpTimeout <= TimeSpan.Zero) { throw new InvalidOperationException("HTTP timeout must be positive."); } } }