281 lines
7.3 KiB
C#
281 lines
7.3 KiB
C#
// <copyright file="CodeScanningAlert.cs" company="StellaOps">
|
|
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
|
|
// </copyright>
|
|
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace StellaOps.Integrations.Plugin.GitHubApp.CodeScanning;
|
|
|
|
/// <summary>
|
|
/// Code scanning alert from GitHub.
|
|
/// Sprint: SPRINT_20260109_010_002 Task: Implement models
|
|
/// </summary>
|
|
public sealed record CodeScanningAlert
|
|
{
|
|
/// <summary>
|
|
/// Alert number.
|
|
/// </summary>
|
|
[JsonPropertyName("number")]
|
|
public required int Number { get; init; }
|
|
|
|
/// <summary>
|
|
/// Alert state (open, closed, dismissed, fixed).
|
|
/// </summary>
|
|
[JsonPropertyName("state")]
|
|
public required string State { get; init; }
|
|
|
|
/// <summary>
|
|
/// Rule ID that triggered the alert.
|
|
/// </summary>
|
|
public required string RuleId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Rule severity.
|
|
/// </summary>
|
|
public required string RuleSeverity { get; init; }
|
|
|
|
/// <summary>
|
|
/// Rule description.
|
|
/// </summary>
|
|
public required string RuleDescription { get; init; }
|
|
|
|
/// <summary>
|
|
/// Tool that produced the alert.
|
|
/// </summary>
|
|
public required string Tool { get; init; }
|
|
|
|
/// <summary>
|
|
/// HTML URL to the alert.
|
|
/// </summary>
|
|
[JsonPropertyName("html_url")]
|
|
public required string HtmlUrl { get; init; }
|
|
|
|
/// <summary>
|
|
/// When the alert was created.
|
|
/// </summary>
|
|
[JsonPropertyName("created_at")]
|
|
public required DateTimeOffset CreatedAt { get; init; }
|
|
|
|
/// <summary>
|
|
/// When the alert was dismissed (if applicable).
|
|
/// </summary>
|
|
[JsonPropertyName("dismissed_at")]
|
|
public DateTimeOffset? DismissedAt { get; init; }
|
|
|
|
/// <summary>
|
|
/// Reason for dismissal.
|
|
/// </summary>
|
|
[JsonPropertyName("dismissed_reason")]
|
|
public string? DismissedReason { get; init; }
|
|
|
|
/// <summary>
|
|
/// Who dismissed the alert.
|
|
/// </summary>
|
|
[JsonPropertyName("dismissed_by")]
|
|
public string? DismissedBy { get; init; }
|
|
|
|
/// <summary>
|
|
/// Most recent instance of the alert.
|
|
/// </summary>
|
|
[JsonPropertyName("most_recent_instance")]
|
|
public AlertInstance? MostRecentInstance { get; init; }
|
|
|
|
/// <summary>
|
|
/// Creates alert from GitHub API response.
|
|
/// </summary>
|
|
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
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Alert instance location.
|
|
/// </summary>
|
|
public sealed record AlertInstance
|
|
{
|
|
/// <summary>
|
|
/// Git ref where the alert was found.
|
|
/// </summary>
|
|
public required string Ref { get; init; }
|
|
|
|
/// <summary>
|
|
/// Analysis key.
|
|
/// </summary>
|
|
public string? AnalysisKey { get; init; }
|
|
|
|
/// <summary>
|
|
/// Environment (e.g., "production").
|
|
/// </summary>
|
|
public string? Environment { get; init; }
|
|
|
|
/// <summary>
|
|
/// Location in the code.
|
|
/// </summary>
|
|
public AlertLocation? Location { get; init; }
|
|
|
|
/// <summary>
|
|
/// Creates instance from API response.
|
|
/// </summary>
|
|
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
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Alert location in source code.
|
|
/// </summary>
|
|
public sealed record AlertLocation
|
|
{
|
|
/// <summary>
|
|
/// File path.
|
|
/// </summary>
|
|
public required string Path { get; init; }
|
|
|
|
/// <summary>
|
|
/// Start line.
|
|
/// </summary>
|
|
public int? StartLine { get; init; }
|
|
|
|
/// <summary>
|
|
/// End line.
|
|
/// </summary>
|
|
public int? EndLine { get; init; }
|
|
|
|
/// <summary>
|
|
/// Start column.
|
|
/// </summary>
|
|
public int? StartColumn { get; init; }
|
|
|
|
/// <summary>
|
|
/// End column.
|
|
/// </summary>
|
|
public int? EndColumn { get; init; }
|
|
}
|
|
|
|
#region GitHub API Response Models
|
|
|
|
/// <summary>
|
|
/// GitHub API alert response.
|
|
/// </summary>
|
|
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
|