//
// 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