Skip to content

Commit

Permalink
feat: add StringEnum<TEnum> (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieMagee committed Jun 8, 2023
1 parent 709974c commit ba8a3da
Show file tree
Hide file tree
Showing 128 changed files with 647 additions and 321 deletions.

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

0 comments on commit ba8a3da

Please sign in to comment.