Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,85 @@ namespace ChilliCream.Nitro.CommandLine.Helpers;

internal static class SourceMetadataParser
{
private const string TypePropertyName = "type";
private const string GitHubType = "github";
private const string AzureDevOpsType = "azure-devops";

public static SourceMetadata? Parse(string? json)
{
if (string.IsNullOrWhiteSpace(json))
{
return null;
}

GitHubSourceMetadataDto dto;

try
{
dto = JsonSerializer.Deserialize(json, SourceMetadataJsonContext.Default.GitHubSourceMetadataDto)
?? throw new ExitException("Failed to parse --source-metadata: deserialized value was null.");

return new SourceMetadata(
new SourceGitHubMetadata(
dto.Actor,
dto.CommitHash,
dto.WorkflowName,
dto.RunNumber,
dto.RunId,
dto.JobId,
new Uri(dto.RepositoryUrl)));
using var document = JsonDocument.Parse(json);

if (document.RootElement.ValueKind != JsonValueKind.Object)
{
throw new InvalidOperationException("Expected a JSON object.");
}

// When no 'type' marker is present we assume GitHub for backwards compatibility.
var type = GitHubType;
if (document.RootElement.TryGetProperty(TypePropertyName, out var typeElement))
{
if (typeElement.ValueKind != JsonValueKind.String)
{
throw new InvalidOperationException(
$"'{TypePropertyName}' must be a string. Expected '{GitHubType}' or '{AzureDevOpsType}'.");
}

type = typeElement.GetString()!;
}

return type switch
{
GitHubType => new SourceMetadata(GitHub: ParseGitHub(document.RootElement)),
AzureDevOpsType => new SourceMetadata(AzureDevOps: ParseAzureDevOps(document.RootElement)),
_ => throw new InvalidOperationException(
$"Unsupported '{TypePropertyName}' value '{type}'. "
+ $"Expected '{GitHubType}' or '{AzureDevOpsType}'.")
};
Comment thread
tobias-tengler marked this conversation as resolved.
}
catch (Exception ex)
{
throw new ExitException($"Failed to parse --source-metadata: {ex.Message}");
throw new ExitException($"Failed to parse --source-metadata: {ex.Message.EscapeMarkup()}");
}
Comment thread
tobias-tengler marked this conversation as resolved.
}

private static SourceGitHubMetadata ParseGitHub(JsonElement element)
{
var dto = element.Deserialize(SourceMetadataJsonContext.Default.GitHubSourceMetadataDto)
?? throw new InvalidOperationException("Could not deserialize GitHub source metadata.");

return new SourceGitHubMetadata(
dto.Actor,
dto.CommitHash,
dto.WorkflowName,
dto.RunNumber,
dto.RunId,
dto.JobId,
new Uri(dto.RepositoryUrl));
}

private static SourceAzureDevOpsMetadata ParseAzureDevOps(JsonElement element)
{
var dto = element.Deserialize(SourceMetadataJsonContext.Default.AzureDevOpsSourceMetadataDto)
?? throw new InvalidOperationException("Could not deserialize Azure DevOps source metadata.");

return new SourceAzureDevOpsMetadata(
new AzureDevOpsActor(dto.Actor.Name, dto.Actor.Email),
dto.PipelineName,
dto.RunNumber,
dto.RunId,
new Uri(dto.ProjectUrl),
dto.CommitHash,
dto.JobId,
dto.TaskId,
dto.RepositoryUrl is null ? null : new Uri(dto.RepositoryUrl));
}
}

internal sealed record GitHubSourceMetadataDto(
Expand All @@ -46,6 +96,22 @@ internal sealed record GitHubSourceMetadataDto(
string? JobId,
[property: JsonRequired] string RepositoryUrl);

internal sealed record AzureDevOpsSourceMetadataDto(
[property: JsonRequired] AzureDevOpsActorDto Actor,
[property: JsonRequired] string PipelineName,
[property: JsonRequired] string RunNumber,
[property: JsonRequired] string RunId,
[property: JsonRequired] string ProjectUrl,
string? CommitHash,
string? JobId,
string? TaskId,
string? RepositoryUrl);

internal sealed record AzureDevOpsActorDto(
[property: JsonRequired] string Name,
string? Email);

[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(GitHubSourceMetadataDto))]
[JsonSerializable(typeof(AzureDevOpsSourceMetadataDto))]
internal partial class SourceMetadataJsonContext : JsonSerializerContext;
Loading
Loading