// // Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later. // using System.Text.Json.Serialization; namespace StellaOps.Integrations.Plugin.GitHubApp.CodeScanning; /// /// Code scanning alert from GitHub. /// Sprint: SPRINT_20260109_010_002 Task: Implement models /// public sealed record CodeScanningAlert { /// /// Alert number. /// [JsonPropertyName("number")] public required int Number { get; init; } /// /// Alert state (open, closed, dismissed, fixed). /// [JsonPropertyName("state")] public required string State { get; init; } /// /// Rule ID that triggered the alert. /// public required string RuleId { get; init; } /// /// Rule severity. /// public required string RuleSeverity { get; init; } /// /// Rule description. /// public required string RuleDescription { get; init; } /// /// Tool that produced the alert. /// public required string Tool { get; init; } /// /// HTML URL to the alert. /// [JsonPropertyName("html_url")] public required string HtmlUrl { get; init; } /// /// When the alert was created. /// [JsonPropertyName("created_at")] public required DateTimeOffset CreatedAt { get; init; } /// /// When the alert was dismissed (if applicable). /// [JsonPropertyName("dismissed_at")] public DateTimeOffset? DismissedAt { get; init; } /// /// Reason for dismissal. /// [JsonPropertyName("dismissed_reason")] public string? DismissedReason { get; init; } /// /// Who dismissed the alert. /// [JsonPropertyName("dismissed_by")] public string? DismissedBy { get; init; } /// /// Most recent instance of the alert. /// [JsonPropertyName("most_recent_instance")] public AlertInstance? MostRecentInstance { get; init; } /// /// Creates alert from GitHub API response. /// public static CodeScanningAlert FromApiResponse(GitHubAlertResponse response) => new() { Number = response.Number, State = response.State ?? "unknown", RuleId = response.Rule?.Id ?? "unknown", RuleSeverity = response.Rule?.Severity ?? "unknown", RuleDescription = response.Rule?.Description ?? "", Tool = response.Tool?.Name ?? "unknown", HtmlUrl = response.HtmlUrl ?? "", CreatedAt = response.CreatedAt, DismissedAt = response.DismissedAt, DismissedReason = response.DismissedReason, DismissedBy = response.DismissedBy?.Login, MostRecentInstance = response.MostRecentInstance is not null ? AlertInstance.FromApiResponse(response.MostRecentInstance) : null }; } /// /// Alert instance location. /// public sealed record AlertInstance { /// /// Git ref where the alert was found. /// public required string Ref { get; init; } /// /// Analysis key. /// public string? AnalysisKey { get; init; } /// /// Environment (e.g., "production"). /// public string? Environment { get; init; } /// /// Location in the code. /// public AlertLocation? Location { get; init; } /// /// Creates instance from API response. /// public static AlertInstance FromApiResponse(GitHubAlertInstanceResponse response) => new() { Ref = response.Ref ?? "unknown", AnalysisKey = response.AnalysisKey, Environment = response.Environment, Location = response.Location is not null ? new AlertLocation { Path = response.Location.Path ?? "", StartLine = response.Location.StartLine, EndLine = response.Location.EndLine, StartColumn = response.Location.StartColumn, EndColumn = response.Location.EndColumn } : null }; } /// /// Alert location in source code. /// public sealed record AlertLocation { /// /// File path. /// public required string Path { get; init; } /// /// Start line. /// public int? StartLine { get; init; } /// /// End line. /// public int? EndLine { get; init; } /// /// Start column. /// public int? StartColumn { get; init; } /// /// End column. /// public int? EndColumn { get; init; } } #region GitHub API Response Models /// /// GitHub API alert response. /// public sealed record GitHubAlertResponse { [JsonPropertyName("number")] public int Number { get; init; } [JsonPropertyName("state")] public string? State { get; init; } [JsonPropertyName("rule")] public GitHubRuleResponse? Rule { get; init; } [JsonPropertyName("tool")] public GitHubToolResponse? Tool { get; init; } [JsonPropertyName("html_url")] public string? HtmlUrl { get; init; } [JsonPropertyName("created_at")] public DateTimeOffset CreatedAt { get; init; } [JsonPropertyName("dismissed_at")] public DateTimeOffset? DismissedAt { get; init; } [JsonPropertyName("dismissed_reason")] public string? DismissedReason { get; init; } [JsonPropertyName("dismissed_by")] public GitHubUserResponse? DismissedBy { get; init; } [JsonPropertyName("most_recent_instance")] public GitHubAlertInstanceResponse? MostRecentInstance { get; init; } } public sealed record GitHubRuleResponse { [JsonPropertyName("id")] public string? Id { get; init; } [JsonPropertyName("severity")] public string? Severity { get; init; } [JsonPropertyName("description")] public string? Description { get; init; } } public sealed record GitHubToolResponse { [JsonPropertyName("name")] public string? Name { get; init; } [JsonPropertyName("version")] public string? Version { get; init; } } public sealed record GitHubUserResponse { [JsonPropertyName("login")] public string? Login { get; init; } } public sealed record GitHubAlertInstanceResponse { [JsonPropertyName("ref")] public string? Ref { get; init; } [JsonPropertyName("analysis_key")] public string? AnalysisKey { get; init; } [JsonPropertyName("environment")] public string? Environment { get; init; } [JsonPropertyName("location")] public GitHubLocationResponse? Location { get; init; } } public sealed record GitHubLocationResponse { [JsonPropertyName("path")] public string? Path { get; init; } [JsonPropertyName("start_line")] public int? StartLine { get; init; } [JsonPropertyName("end_line")] public int? EndLine { get; init; } [JsonPropertyName("start_column")] public int? StartColumn { get; init; } [JsonPropertyName("end_column")] public int? EndColumn { get; init; } } #endregion