Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add StringEnum<TEnum> #98

Merged
merged 2 commits into from
Jun 8, 2023
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

This file was deleted.

18 changes: 18 additions & 0 deletions src/Octokit.Webhooks/Converter/StringEnumConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Octokit.Webhooks.Converter;

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Octokit.Webhooks.Extensions;

public sealed class StringEnumConverter<TEnum> : JsonConverter<StringEnum<TEnum>>
where TEnum : struct
{
public override StringEnum<TEnum> Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) => new(reader.GetString()!);

public override void Write(Utf8JsonWriter writer, StringEnum<TEnum> value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, value.StringValue, options);
}
61 changes: 61 additions & 0 deletions src/Octokit.Webhooks/Converter/StringEnumEnumerableConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace Octokit.Webhooks.Converter;

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using Octokit.Webhooks.Extensions;

public sealed class StringEnumEnumerableConverter<TEnum> : JsonConverter<IEnumerable<StringEnum<TEnum>>>
where TEnum : struct
{
private static readonly JsonSerializerOptions Options = new()
{
Converters = { Activator.CreateInstance<StringEnumConverter<TEnum>>() },
};

public override IEnumerable<StringEnum<TEnum>> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
ReadInternal(ref reader);

public override void Write(Utf8JsonWriter writer, IEnumerable<StringEnum<TEnum>> value, JsonSerializerOptions options) =>
WriteInternal(writer, value);

private static List<StringEnum<TEnum>> ReadInternal(ref Utf8JsonReader reader)
{
if (reader.TokenType == JsonTokenType.Null)
{
throw new JsonException("Unexpected null value.");
}

var returnValue = new List<StringEnum<TEnum>>();

while (reader.TokenType != JsonTokenType.EndArray)
{
if (reader.TokenType != JsonTokenType.StartArray)
{
returnValue.Add((StringEnum<TEnum>)JsonSerializer.Deserialize(ref reader, typeof(StringEnum<TEnum>), Options)!);
}

reader.Read();
}

return returnValue!;
}

private static void WriteInternal(Utf8JsonWriter writer, IEnumerable<StringEnum<TEnum>> value)
{
if (value == null)
{
throw new JsonException("Unexpected null value.");
}

writer.WriteStartArray();

foreach (var data in value)
{
JsonSerializer.Serialize(writer, data, Options);
}

writer.WriteEndArray();
}
}
3 changes: 2 additions & 1 deletion src/Octokit.Webhooks/Events/CreateEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ public sealed record CreateEvent : WebhookEvent
public string Ref { get; init; } = null!;

[JsonPropertyName("ref_type")]
public RefType RefType { get; init; }
[JsonConverter(typeof(StringEnumConverter<RefType>))]
public StringEnum<RefType> RefType { get; init; } = null!;

[JsonPropertyName("master_branch")]
public string MasterBranch { get; init; } = null!;
Expand Down
3 changes: 2 additions & 1 deletion src/Octokit.Webhooks/Events/DeleteEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ public sealed record DeleteEvent : WebhookEvent
public string Ref { get; init; } = null!;

[JsonPropertyName("ref_type")]
public RefType RefType { get; init; }
[JsonConverter(typeof(StringEnumConverter<RefType>))]
public StringEnum<RefType> RefType { get; init; } = null!;

[JsonPropertyName("pusher_type")]
public string PusherType { get; init; } = null!;
Expand Down
3 changes: 2 additions & 1 deletion src/Octokit.Webhooks/Events/InstallationRepositoriesEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ public abstract record InstallationRepositoriesEvent : WebhookEvent
public new Models.Installation Installation { get; init; } = null!;

[JsonPropertyName("repository_selection")]
public InstallationRepositorySelection RepositorySelection { get; init; }
[JsonConverter(typeof(StringEnumConverter<InstallationRepositorySelection>))]
public StringEnum<InstallationRepositorySelection> RepositorySelection { get; init; } = null!;

[JsonPropertyName("repositories_added")]
public IEnumerable<Models.InstallationRepositoriesEvent.Repository> RepositoriesAdded { get; init; } = null!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ public sealed record InstallationTargetRenamedEvent : InstallationTargetEvent
public Changes Changes { get; init; } = null!;

[JsonPropertyName("target_type")]
public InstallationTargetType TargetType { get; init; }
[JsonConverter(typeof(StringEnumConverter<InstallationTargetType>))]
public StringEnum<InstallationTargetType> TargetType { get; init; } = null!;
}
3 changes: 2 additions & 1 deletion src/Octokit.Webhooks/Events/MembershipEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
public abstract record MembershipEvent : WebhookEvent
{
[JsonPropertyName("scope")]
public Scope Scope { get; init; }
[JsonConverter(typeof(StringEnumConverter<Scope>))]
public StringEnum<Scope> Scope { get; init; } = null!;

[JsonPropertyName("member")]
public User Member { get; init; } = null!;
Expand Down
3 changes: 2 additions & 1 deletion src/Octokit.Webhooks/Events/RepositoryImportEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
public sealed record RepositoryImportEvent : WebhookEvent
{
[JsonPropertyName("status")]
public Status Status { get; init; }
[JsonConverter(typeof(StringEnumConverter<Status>))]
public StringEnum<Status> Status { get; init; } = null!;
}
3 changes: 2 additions & 1 deletion src/Octokit.Webhooks/Events/StatusEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public sealed record StatusEvent : WebhookEvent
public string? Description { get; init; }

[JsonPropertyName("state")]
public StatusState State { get; init; }
[JsonConverter(typeof(StringEnumConverter<StatusState>))]
public StringEnum<StatusState> State { get; init; } = null!;

[JsonPropertyName("commit")]
public Commit Commit { get; init; } = null!;
Expand Down
104 changes: 104 additions & 0 deletions src/Octokit.Webhooks/Extensions/StringEnum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
namespace Octokit.Webhooks.Extensions;

using System;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using JetBrains.Annotations;

[PublicAPI]
public sealed record StringEnum<TEnum>
where TEnum : struct
{
private TEnum? parsedValue;

public StringEnum(string stringValue)
{
this.StringValue = stringValue;
this.parsedValue = null;
}

public StringEnum(TEnum parsedValue)
{
if (!Enum.IsDefined(typeof(TEnum), parsedValue))
{
throw GetArgumentException(parsedValue.ToString());
}

this.StringValue = ToEnumString(parsedValue);
this.parsedValue = parsedValue;
}

public string StringValue { get; }

public TEnum Value => this.parsedValue ??= this.ParseValue();

public static implicit operator StringEnum<TEnum>(string value) => new(value);

public static implicit operator StringEnum<TEnum>(TEnum parsedValue) => new(parsedValue);

public bool TryParse(out TEnum value)
{
if (this.parsedValue is not null)
{
value = this.parsedValue.Value;
return true;
}

try
{
value = ToEnum(this.StringValue);
this.parsedValue = value;
return true;
}
catch (ArgumentException)
{
value = default;
return false;
}
}

private static ArgumentException GetArgumentException(string? value) => new(string.Format(
CultureInfo.InvariantCulture,
"Value '{0}' is not a valid '{1}' enum value.",
value,
typeof(TEnum).Name));

private TEnum ParseValue()
{
if (this.TryParse(out var value))
{
return value;
}

throw GetArgumentException(this.StringValue);
}

private static string ToEnumString(TEnum type)
{
var enumType = typeof(TEnum);
var name = Enum.GetName(enumType, type);
if (name is not null)
{
var enumMemberAttribute = ((EnumMemberAttribute[])enumType.GetField(name)!.GetCustomAttributes(typeof(EnumMemberAttribute), true)).Single();
return enumMemberAttribute.Value!;
}

throw new ArgumentException(type.ToString());
}

private static TEnum ToEnum(string str)
{
var enumType = typeof(TEnum);
foreach (var name in Enum.GetNames(enumType))
{
var enumMemberAttribute = ((EnumMemberAttribute[])enumType.GetField(name)!.GetCustomAttributes(typeof(EnumMemberAttribute), true)).Single();
if (enumMemberAttribute.Value == str)
{
return (TEnum)Enum.Parse(enumType, name);
}
}

throw new ArgumentException(str);
}
}
2 changes: 0 additions & 2 deletions src/Octokit.Webhooks/Models/ActiveLockReason.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
namespace Octokit.Webhooks.Models;

[PublicAPI]
[JsonConverter(typeof(JsonStringEnumMemberConverterWithFallback))]
public enum ActiveLockReason
{
Unknown = -1,
[EnumMember(Value = "resolved")]
Resolved,
[EnumMember(Value = "off-topic")]
Expand Down
3 changes: 2 additions & 1 deletion src/Octokit.Webhooks/Models/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ public sealed record App
public AppPermissions? Permissions { get; init; }

[JsonPropertyName("events")]
public IEnumerable<AppEvent>? Events { get; init; }
[JsonConverter(typeof(StringEnumEnumerableConverter<AppEvent>))]
public IEnumerable<StringEnum<AppEvent>>? Events { get; init; }
}
2 changes: 0 additions & 2 deletions src/Octokit.Webhooks/Models/AppEvent.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
namespace Octokit.Webhooks.Models;

[PublicAPI]
[JsonConverter(typeof(JsonStringEnumMemberConverterWithFallback))]
public enum AppEvent
{
Unknown = -1,
[EnumMember(Value = "*")]
All,
[EnumMember(Value = "branch_protection_rule")]
Expand Down
Loading