From 5ee4d6404628e43e5251eb68c85c31878b7d5b78 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Sun, 25 Jun 2017 11:29:57 +0200 Subject: [PATCH] Add StringEnum to handle unknown enum values returned from API (#1595) * Added StringEnum * Added tests * Make sure the serializer can work with StringEnum * Use StringEnum for EventInfo.Event * Add convention test to assert that all Response models use StringEnum<> to wrap enum properties * Add Stringnum<> to all response types failing convention test * Handle StringEnum to Enum conversion when Issue response model populates IssueUpdate request model * Fix unit test * Refactor SimpleJsonSerializer to expose the DeserializeEnum strategy so it can be used in StringEnum class * Need to expose/use SerializeEnum functionality too, so we use the correct string representation of enum values that have custom properties (eg ReactionType Plus1 to "+1") * fix unit tests, since the string is now the "correct" upstream api value * Add a couple of tests for the Enum serialize/deserialize when underscores, hyphens and custom property attributes are present * Compare parsed values for equality * add convention test to ensure enum members all have Parameter property set * update test to cover implicit conversions too * this test should work but fails at the moment due to magic hyphen removal in deserializer causing a one way trip from utf-8 to EncodingType.Utf8 with no way to get back * (unsuccesfully) expand event info test to try to catch more cases of unknown event types * fix broken integration test while im here * Fixed build errors after .NET Core merge * Value -> StringValue, ParsedValue -> Value * Don't allow StringValue to be null * Ignore enums not used in request/response models * Added ParameterAttribute to almost all enum values * Ignore Language enum * Fix failing tests * Fix milestone sort parameter and tests * whitespace * fix milestone unit tests * Fix StringEnum.Equals ... This could've been embarrassing! * Change SimpleJsonSerializer Enum handling to only use `[Parameter()]` attributes (no more magic removal of hyphen/underscores from strings) * Tidy up this integration test while im here * Only test request/response enums in convention test * Keep skipping Language * Remove unused method * Remove excluded enum types * Removed unnecessary ParameterAttributes * Remove unused enum * Add StringEnum test for string-comparison of two invalid values * Bring back IssueCommentSort and use it in IssueCommentRequest This reverts commit 38a4a291d1476ef8c992fe0f76956974b6f32a49. * Use assembly instead of namespace for Octokit check * Add failing test to reproduce the issue where only the first enum paramter/value was added to the cache * Fix deserializer enum cache to include all enum members rather than only the first member encountered * Use a static SimpleJsonSerializer in StringEnum * Remove serializer instance in StringEnum * Add some documentation on StringEnum * Fix parameter value to resolve failing integration test --- .../EnumMissingPropertyAttributeException.cs | 22 +++ .../ModelNotUsingStringEnumException.cs | 22 +++ Octokit.Tests.Conventions/ModelTests.cs | 52 +++++- .../Clients/IssueTimelineClientTests.cs | 38 +++- .../Clients/IssuesLabelsClientTests.cs | 2 +- .../Clients/MilestonesClientTests.cs | 4 +- .../Clients/MiscellaneousClientTests.cs | 2 +- .../RepositoryInvitationsClientTests.cs | 8 +- Octokit.Tests/Models/StringEnumTests.cs | 162 ++++++++++++++++++ .../ObservableIssueCommentsClientTests.cs | 4 +- Octokit.Tests/SimpleJsonSerializerTests.cs | 55 ++++-- Octokit/Http/SimpleJsonSerializer.cs | 112 +++++++----- Octokit/Models/Request/IssueCommentRequest.cs | 4 +- Octokit/Models/Request/IssueRequest.cs | 13 ++ Octokit/Models/Request/MergePullRequest.cs | 4 + Octokit/Models/Request/MilestoneRequest.cs | 2 + Octokit/Models/Request/NewDeployment.cs | 1 + .../Models/Request/NewRepositoryWebHook.cs | 1 + Octokit/Models/Request/Permission.cs | 4 + Octokit/Models/Request/PullRequestRequest.cs | 6 + .../Request/RepositoryForksListRequest.cs | 4 + Octokit/Models/Request/RepositoryRequest.cs | 13 ++ .../Request/SearchRepositoriesRequest.cs | 5 + Octokit/Models/Request/SearchUsersRequest.cs | 14 ++ Octokit/Models/Request/StarredRequest.cs | 2 + Octokit/Models/Response/AccountType.cs | 7 +- Octokit/Models/Response/Blob.cs | 6 +- Octokit/Models/Response/BranchProtection.cs | 6 +- .../Models/Response/CombinedCommitStatus.cs | 2 +- Octokit/Models/Response/CommitContent.cs | 2 +- Octokit/Models/Response/CommitStatus.cs | 7 +- Octokit/Models/Response/ContentType.cs | 8 + Octokit/Models/Response/DeploymentStatus.cs | 12 +- Octokit/Models/Response/EventInfo.cs | 35 +++- Octokit/Models/Response/Issue.cs | 5 +- Octokit/Models/Response/IssueComment.cs | 3 + Octokit/Models/Response/IssueEvent.cs | 2 +- Octokit/Models/Response/Migration.cs | 7 +- Octokit/Models/Response/Milestone.cs | 2 +- Octokit/Models/Response/Page.cs | 13 +- Octokit/Models/Response/PagesBuild.cs | 2 +- Octokit/Models/Response/PullRequest.cs | 2 +- .../Response/PullRequestReviewComment.cs | 3 + Octokit/Models/Response/PunchCardPoint.cs | 2 +- Octokit/Models/Response/Reaction.cs | 11 +- .../Models/Response/RepositoryInvitation.cs | 8 +- .../Models/Response/RepositoryTrafficClone.cs | 4 + Octokit/Models/Response/StringEnum.cs | 156 +++++++++++++++++ Octokit/Models/Response/TagObject.cs | 10 +- Octokit/Models/Response/Team.cs | 2 +- Octokit/Models/Response/TimelineEventInfo.cs | 2 +- Octokit/Models/Response/TreeItem.cs | 8 +- Octokit/Models/Response/Verification.cs | 2 +- docs/working-with-enums.md | 124 ++++++++++++++ 54 files changed, 897 insertions(+), 112 deletions(-) create mode 100644 Octokit.Tests.Conventions/Exception/EnumMissingPropertyAttributeException.cs create mode 100644 Octokit.Tests.Conventions/Exception/ModelNotUsingStringEnumException.cs create mode 100644 Octokit.Tests/Models/StringEnumTests.cs create mode 100644 Octokit/Models/Response/StringEnum.cs create mode 100644 docs/working-with-enums.md diff --git a/Octokit.Tests.Conventions/Exception/EnumMissingPropertyAttributeException.cs b/Octokit.Tests.Conventions/Exception/EnumMissingPropertyAttributeException.cs new file mode 100644 index 00000000..f5aa1688 --- /dev/null +++ b/Octokit.Tests.Conventions/Exception/EnumMissingPropertyAttributeException.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Octokit.Tests.Conventions +{ + public class EnumMissingParameterAttributeException : Exception + { + public EnumMissingParameterAttributeException(Type enumType, IEnumerable enumMembers) + : base(CreateMessage(enumType, enumMembers)) + { } + + static string CreateMessage(Type enumType, IEnumerable enumMembers) + { + return string.Format("Enum type '{0}' contains the following members that are missing the Parameter attribute: {1}{2}", + enumType.FullName, + Environment.NewLine, + string.Join(Environment.NewLine, enumMembers.Select(x => x.Name))); + } + } +} \ No newline at end of file diff --git a/Octokit.Tests.Conventions/Exception/ModelNotUsingStringEnumException.cs b/Octokit.Tests.Conventions/Exception/ModelNotUsingStringEnumException.cs new file mode 100644 index 00000000..9df562cc --- /dev/null +++ b/Octokit.Tests.Conventions/Exception/ModelNotUsingStringEnumException.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Octokit.Tests.Conventions +{ + public class ModelNotUsingStringEnumException : Exception + { + public ModelNotUsingStringEnumException(Type modelType, IEnumerable enumProperties) + : base(CreateMessage(modelType, enumProperties)) + { } + + static string CreateMessage(Type modelType, IEnumerable enumProperties) + { + return string.Format("Model type '{0}' contains the following Enum properties which should be wrapped in a StringEnum instead: {1}{2}", + modelType.FullName, + Environment.NewLine, + string.Join(Environment.NewLine, enumProperties.Select(x => x.PropertyType.Name + " " + x.Name))); + } + } +} \ No newline at end of file diff --git a/Octokit.Tests.Conventions/ModelTests.cs b/Octokit.Tests.Conventions/ModelTests.cs index 6fee02e3..577fe55e 100644 --- a/Octokit.Tests.Conventions/ModelTests.cs +++ b/Octokit.Tests.Conventions/ModelTests.cs @@ -5,11 +5,14 @@ using System.Linq; using Xunit; using System.Collections.Generic; using System.Reflection; +using Octokit.Internal; namespace Octokit.Tests.Conventions { public class ModelTests { + private static readonly Assembly Octokit = typeof(AuthorizationUpdate).GetTypeInfo().Assembly; + [Theory] [MemberData("ModelTypes")] public void AllModelsHaveDebuggerDisplayAttribute(Type modelType) @@ -107,6 +110,19 @@ namespace Octokit.Tests.Conventions } } + [Theory] + [MemberData("ResponseModelTypes")] + public void ResponseModelsUseStringEnumWrapper(Type modelType) + { + var enumProperties = modelType.GetProperties() + .Where(x => x.PropertyType.GetTypeInfo().IsEnum); + + if (enumProperties.Any()) + { + throw new ModelNotUsingStringEnumException(modelType, enumProperties); + } + } + [Theory] [MemberData("ModelTypesWithUrlProperties")] public void ModelsHaveUrlPropertiesOfTypeString(Type modelType) @@ -123,15 +139,23 @@ namespace Octokit.Tests.Conventions } } - public static IEnumerable GetClientInterfaces() + [Theory] + [MemberData("EnumTypes")] + public void EnumMembersHaveParameterAttribute(Type enumType) { - return typeof(IGitHubClient) - .GetTypeInfo() - .Assembly - .ExportedTypes - .Where(TypeExtensions.IsClientInterface) - .Where(t => t != typeof(IStatisticsClient)) // This convention doesn't apply to this one type. - .Select(type => new[] { type }); + if (enumType == typeof(Language)) + { + return; // TODO: Annotate all Language entries with a ParameterAttribute. + } + + var membersWithoutProperty = enumType.GetRuntimeFields() + .Where(x => x.Name != "value__") + .Where(x => x.GetCustomAttribute(typeof(ParameterAttribute), false) == null); + + if (membersWithoutProperty.Any()) + { + throw new EnumMissingParameterAttributeException(enumType, membersWithoutProperty); + } } public static IEnumerable ModelTypes @@ -154,6 +178,18 @@ namespace Octokit.Tests.Conventions get { return GetModelTypes(includeRequestModels: false).Select(type => new[] { type }); } } + public static IEnumerable EnumTypes + { + get + { + return GetModelTypes(includeRequestModels: true) + .SelectMany(type => type.GetProperties()) + .SelectMany(property => UnwrapGenericArguments(property.PropertyType)) + .Where(type => type.GetTypeInfo().Assembly.Equals(Octokit) && type.GetTypeInfo().IsEnum) + .Select(type => new[] { type }); + } + } + private static IEnumerable GetModelTypes(bool includeRequestModels) { var allModelTypes = new HashSet(); diff --git a/Octokit.Tests.Integration/Clients/IssueTimelineClientTests.cs b/Octokit.Tests.Integration/Clients/IssueTimelineClientTests.cs index f7576fcf..cf28a45f 100644 --- a/Octokit.Tests.Integration/Clients/IssueTimelineClientTests.cs +++ b/Octokit.Tests.Integration/Clients/IssueTimelineClientTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Octokit.Tests.Integration.Helpers; using Xunit; @@ -62,7 +63,7 @@ namespace Octokit.Tests.Integration.Clients PerPage = 20, Page = 1 }; - search.Repos.Add("microsoft", "vscode"); + search.Repos.Add("dotnet", "roslyn"); // 20 most recent closed PRs search.Type = IssueTypeQualifier.PullRequest; @@ -71,18 +72,45 @@ namespace Octokit.Tests.Integration.Clients foreach (var pullRequest in pullRequestResults.Items) { var timelineEventInfos = await _issueTimelineClient.GetAllForIssue("microsoft", "vscode", pullRequest.Number); - Assert.NotEmpty(timelineEventInfos); + + // Ensure we dont have any errors parsing the Event enums + var enumValues = timelineEventInfos.Select(x => x.Event.Value).ToList(); } - // 20 most recent open Issues - search.Type = IssueTypeQualifier.Issue; + // 20 most recent open PRs + search.Type = IssueTypeQualifier.PullRequest; search.State = ItemState.Open; + var openPullRequestResults = await github.Search.SearchIssues(search); + foreach (var pullRequest in openPullRequestResults.Items) + { + var timelineEventInfos = await _issueTimelineClient.GetAllForIssue("microsoft", "vscode", pullRequest.Number); + + // Ensure we dont have any errors parsing the Event enums + var enumValues = timelineEventInfos.Select(x => x.Event.Value).ToList(); + } + + // 20 most recent closed Issues + search.Type = IssueTypeQualifier.Issue; + search.State = ItemState.Closed; var issueResults = await github.Search.SearchIssues(search); foreach (var issue in issueResults.Items) { var timelineEventInfos = await _issueTimelineClient.GetAllForIssue("microsoft", "vscode", issue.Number); - Assert.NotEmpty(timelineEventInfos); + // Ensure we dont have any errors parsing the Event enums + var enumValues = timelineEventInfos.Select(x => x.Event.Value).ToList(); + } + + // 20 most recent open Issues + search.Type = IssueTypeQualifier.Issue; + search.State = ItemState.Open; + var openIssueResults = await github.Search.SearchIssues(search); + foreach (var issue in issueResults.Items) + { + var timelineEventInfos = await _issueTimelineClient.GetAllForIssue("microsoft", "vscode", issue.Number); + + // Ensure we dont have any errors parsing the Event enums + var enumValues = timelineEventInfos.Select(x => x.Event.Value).ToList(); } } diff --git a/Octokit.Tests.Integration/Clients/IssuesLabelsClientTests.cs b/Octokit.Tests.Integration/Clients/IssuesLabelsClientTests.cs index dd43dc96..59c50038 100644 --- a/Octokit.Tests.Integration/Clients/IssuesLabelsClientTests.cs +++ b/Octokit.Tests.Integration/Clients/IssuesLabelsClientTests.cs @@ -538,7 +538,7 @@ public class IssuesLabelsClientTests : IDisposable } [IntegrationTest] - public async Task CanListLabelsForAnMilestoneWithRepositoryId() + public async Task CanListLabelsForAMilestoneWithRepositoryId() { var newIssue = new NewIssue("A test issue") { Body = "A new unassigned issue" }; var newLabel = new NewLabel("test label", "FFFFFF"); diff --git a/Octokit.Tests.Integration/Clients/MilestonesClientTests.cs b/Octokit.Tests.Integration/Clients/MilestonesClientTests.cs index 9624336d..2e0e76fb 100644 --- a/Octokit.Tests.Integration/Clients/MilestonesClientTests.cs +++ b/Octokit.Tests.Integration/Clients/MilestonesClientTests.cs @@ -161,7 +161,7 @@ public class MilestonesClientTests : IDisposable await _milestonesClient.Create(_context.RepositoryOwner, _context.RepositoryName, milestone3); var milestones = await _milestonesClient.GetAllForRepository(_context.RepositoryOwner, _context.RepositoryName, - new MilestoneRequest { SortDirection = SortDirection.Descending }); + new MilestoneRequest { SortProperty = MilestoneSort.DueDate, SortDirection = SortDirection.Descending }); Assert.Equal(2, milestones.Count); Assert.Equal("milestone 2", milestones[0].Title); @@ -179,7 +179,7 @@ public class MilestonesClientTests : IDisposable await _milestonesClient.Create(_context.Repository.Id, milestone3); var milestones = await _milestonesClient.GetAllForRepository(_context.Repository.Id, - new MilestoneRequest { SortDirection = SortDirection.Descending }); + new MilestoneRequest { SortProperty = MilestoneSort.DueDate, SortDirection = SortDirection.Descending }); Assert.Equal(2, milestones.Count); Assert.Equal("milestone 2", milestones[0].Title); diff --git a/Octokit.Tests.Integration/Clients/MiscellaneousClientTests.cs b/Octokit.Tests.Integration/Clients/MiscellaneousClientTests.cs index a6ec1944..fa8e5eec 100644 --- a/Octokit.Tests.Integration/Clients/MiscellaneousClientTests.cs +++ b/Octokit.Tests.Integration/Clients/MiscellaneousClientTests.cs @@ -26,7 +26,7 @@ public class MiscellaneousClientTests var result = await github.Miscellaneous.RenderRawMarkdown("This is\r\n a **test**"); - Assert.Equal("

This is\n a test

\n", result); + Assert.Equal("

This is\na test

\n", result); } } diff --git a/Octokit.Tests.Integration/Clients/RepositoryInvitationsClientTests.cs b/Octokit.Tests.Integration/Clients/RepositoryInvitationsClientTests.cs index 7b2ee015..17df3f20 100644 --- a/Octokit.Tests.Integration/Clients/RepositoryInvitationsClientTests.cs +++ b/Octokit.Tests.Integration/Clients/RepositoryInvitationsClientTests.cs @@ -7,14 +7,12 @@ using Xunit; public class RepositoryInvitationsClientTests { - const string owner = "octocat"; - const string name = "Hello-World"; - public class TheGetAllForRepositoryMethod { [IntegrationTest] public async Task CanGetAllInvitations() { + var collaborator = "octocat"; var github = Helper.GetAuthenticatedClient(); var repoName = Helper.MakeNameWithTimestamp("public-repo"); @@ -24,9 +22,9 @@ public class RepositoryInvitationsClientTests var permission = new CollaboratorRequest(Permission.Push); // invite a collaborator - var response = await fixture.Invite(context.RepositoryOwner, context.RepositoryName, owner, permission); + var response = await fixture.Invite(context.RepositoryOwner, context.RepositoryName, collaborator, permission); - Assert.Equal(owner, response.Invitee.Login); + Assert.Equal(collaborator, response.Invitee.Login); Assert.Equal(InvitationPermissionType.Write, response.Permissions); var invitations = await github.Repository.Invitation.GetAllForRepository(context.Repository.Id); diff --git a/Octokit.Tests/Models/StringEnumTests.cs b/Octokit.Tests/Models/StringEnumTests.cs new file mode 100644 index 00000000..4ae61bde --- /dev/null +++ b/Octokit.Tests/Models/StringEnumTests.cs @@ -0,0 +1,162 @@ +using System; +using Xunit; + +namespace Octokit.Tests.Models +{ + public class StringEnumTests + { + public class TheCtor + { + [Fact] + public void ShouldSetValue() + { + var stringEnum = new StringEnum("user"); + + Assert.Equal("user", stringEnum.StringValue); + } + + [Fact] + public void ShouldSetValueAndParsedValue() + { + var stringEnum = new StringEnum(AccountType.Bot); + + Assert.Equal("bot", stringEnum.StringValue); + Assert.Equal(AccountType.Bot, stringEnum.Value); + Assert.Equal("bot", stringEnum); + Assert.Equal(AccountType.Bot, stringEnum); + } + + [Fact] + public void ShouldRespectCustomPropertyAttributes() + { + StringEnum stringEnum = ReactionType.Plus1; + + Assert.Equal("+1", stringEnum.StringValue); + Assert.Equal(ReactionType.Plus1, stringEnum.Value); + } + + [Fact] + public void ShouldThrowForInvalidEnumValue() + { + Assert.Throws(() => new StringEnum((AccountType) 1337)); + } + } + + public class TheParsedValueProperty + { + [Theory] + [InlineData("")] + [InlineData(null)] + [InlineData("Cow")] + public void ShouldThrowForInvalidValue(string value) + { + var stringEnum = new StringEnum(value); + + Assert.Throws(() => stringEnum.Value); + } + + [Fact] + public void ShouldHandleUnderscores() + { + var stringEnum = new StringEnum("review_dismissed"); + + Assert.Equal("review_dismissed", stringEnum.StringValue); + Assert.Equal(EventInfoState.ReviewDismissed, stringEnum.Value); + } + + [Fact] + public void ShouldHandleHyphens() + { + var stringEnum = new StringEnum("utf-8"); + + Assert.Equal("utf-8", stringEnum.StringValue); + Assert.Equal(EncodingType.Utf8, stringEnum.Value); + } + + [Fact] + public void ShouldHandleCustomPropertyAttribute() + { + var stringEnum = new StringEnum("+1"); + + Assert.Equal("+1", stringEnum.StringValue); + Assert.Equal(ReactionType.Plus1, stringEnum.Value); + } + } + + public class TheTryParseMethod + { + [Fact] + public void ShouldReturnTrueForValidValue() + { + var stringEnum = new StringEnum("Bot"); + + AccountType type; + var result = stringEnum.TryParse(out type); + + Assert.True(result); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + [InlineData("Cow")] + public void ShouldReturnFalseForInvalidValue(string value) + { + var stringEnum = new StringEnum(value); + + AccountType type; + var result = stringEnum.TryParse(out type); + + Assert.False(result); + } + } + + public class TheImplicitConversionOperator + { + [Fact] + public void ShouldSetValue() + { + StringEnum stringEnum = "user"; + + Assert.Equal("user", stringEnum.StringValue); + } + + [Fact] + public void ShouldSetValueAndParsedValue() + { + StringEnum stringEnum = AccountType.Bot; + + Assert.Equal("bot", stringEnum.StringValue); + Assert.Equal(AccountType.Bot, stringEnum.Value); + } + + [Fact] + public void ShouldThrowForInvalidEnumValue() + { + StringEnum stringEnum; + Assert.Throws(() => stringEnum = (AccountType) 1337); + } + } + + public class TheEqualityOperator + { + [Fact] + public void IsCaseInsensitive() + { + var first = new StringEnum("bot"); + var second = new StringEnum("BoT"); + + Assert.True(first == second); + } + + [Fact] + public void FallsBackToStringComparison() + { + var first = new StringEnum("god"); + var second = new StringEnum("GoD"); + + Assert.True(first == second); + } + } + } +} diff --git a/Octokit.Tests/Reactive/ObservableIssueCommentsClientTests.cs b/Octokit.Tests/Reactive/ObservableIssueCommentsClientTests.cs index 821968b9..e420a4ff 100644 --- a/Octokit.Tests/Reactive/ObservableIssueCommentsClientTests.cs +++ b/Octokit.Tests/Reactive/ObservableIssueCommentsClientTests.cs @@ -86,7 +86,7 @@ namespace Octokit.Tests.Reactive { Direction = SortDirection.Descending, Since = new DateTimeOffset(2016, 11, 23, 11, 11, 11, 00, new TimeSpan()), - Sort = PullRequestReviewCommentSort.Updated + Sort = IssueCommentSort.Updated }; var options = new ApiOptions { @@ -116,7 +116,7 @@ namespace Octokit.Tests.Reactive { Direction = SortDirection.Descending, Since = new DateTimeOffset(2016, 11, 23, 11, 11, 11, 00, new TimeSpan()), - Sort = PullRequestReviewCommentSort.Updated + Sort = IssueCommentSort.Updated }; var options = new ApiOptions { diff --git a/Octokit.Tests/SimpleJsonSerializerTests.cs b/Octokit.Tests/SimpleJsonSerializerTests.cs index 9b1b3127..228dc082 100644 --- a/Octokit.Tests/SimpleJsonSerializerTests.cs +++ b/Octokit.Tests/SimpleJsonSerializerTests.cs @@ -298,33 +298,44 @@ namespace Octokit.Tests } [Fact] - public void DeserializesEnum() + public void DeserializesEnumWithParameterAttribute() { - const string json = @"{""some_enum"":""unicode""}"; + const string json1 = @"{""some_enum"":""+1""}"; + const string json2 = @"{""some_enum"":""utf-8""}"; + const string json3 = @"{""some_enum"":""something else""}"; + const string json4 = @"{""some_enum"":""another_example""}"; + const string json5 = @"{""some_enum"":""unicode""}"; - var sample = new SimpleJsonSerializer().Deserialize(json); + var sample1 = new SimpleJsonSerializer().Deserialize(json1); + var sample2 = new SimpleJsonSerializer().Deserialize(json2); + var sample3 = new SimpleJsonSerializer().Deserialize(json3); + var sample4 = new SimpleJsonSerializer().Deserialize(json4); + var sample5 = new SimpleJsonSerializer().Deserialize(json5); - Assert.Equal(SomeEnum.Unicode, sample.SomeEnum); + Assert.Equal(SomeEnum.PlusOne, sample1.SomeEnum); + Assert.Equal(SomeEnum.Utf8, sample2.SomeEnum); + Assert.Equal(SomeEnum.SomethingElse, sample3.SomeEnum); + Assert.Equal(SomeEnum.AnotherExample, sample4.SomeEnum); + Assert.Equal(SomeEnum.Unicode, sample5.SomeEnum); } [Fact] - public void RemovesDashFromEnums() + public void ShouldDeserializeMultipleEnumValues() { - const string json = @"{""some_enum"":""utf-8""}"; + var strings = new[] + { + "locked", + "unlocked", + "head_ref_deleted", + "head_ref_restored" + }; - var sample = new SimpleJsonSerializer().Deserialize(json); + foreach (var value in strings) + { + var enumValue = SimpleJsonSerializer.DeserializeEnum(value, typeof(EventInfoState)); - Assert.Equal(SomeEnum.Utf8, sample.SomeEnum); - } - - [Fact] - public void UnderstandsParameterAttribute() - { - const string json = @"{""some_enum"":""+1""}"; - - var sample = new SimpleJsonSerializer().Deserialize(json); - - Assert.Equal(SomeEnum.PlusOne, sample.SomeEnum); + // Test passes if no exception thrown + } } } @@ -361,9 +372,17 @@ namespace Octokit.Tests { [Parameter(Value = "+1")] PlusOne, + + [Parameter(Value = "utf-8")] Utf8, + [Parameter(Value = "something else")] SomethingElse, + + [Parameter(Value = "another_example")] + AnotherExample, + + [Parameter(Value = "unicode")] Unicode } } diff --git a/Octokit/Http/SimpleJsonSerializer.cs b/Octokit/Http/SimpleJsonSerializer.cs index 7921230d..429e9d40 100644 --- a/Octokit/Http/SimpleJsonSerializer.cs +++ b/Octokit/Http/SimpleJsonSerializer.cs @@ -9,7 +9,7 @@ namespace Octokit.Internal { public class SimpleJsonSerializer : IJsonSerializer { - readonly GitHubSerializerStrategy _serializationStrategy = new GitHubSerializerStrategy(); + static readonly GitHubSerializerStrategy _serializationStrategy = new GitHubSerializerStrategy(); public string Serialize(object item) { @@ -21,6 +21,16 @@ namespace Octokit.Internal return SimpleJson.DeserializeObject(json, _serializationStrategy); } + internal static string SerializeEnum(Enum value) + { + return _serializationStrategy.SerializeEnumHelper(value).ToString(); + } + + internal static object DeserializeEnum(string value, Type type) + { + return _serializationStrategy.DeserializeEnumHelper(value, type); + } + class GitHubSerializerStrategy : PocoJsonSerializerStrategy { readonly List _membersWhichShouldPublishNull = new List(); @@ -75,6 +85,11 @@ namespace Octokit.Internal return true; } + internal object SerializeEnumHelper(Enum p) + { + return SerializeEnum(p); + } + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "The API expects lowercase values")] protected override object SerializeEnum(Enum p) @@ -82,6 +97,43 @@ namespace Octokit.Internal return p.ToParameter(); } + internal object DeserializeEnumHelper(string value, Type type) + { + if (!_cachedEnums.ContainsKey(type)) + { + //First add type to Dictionary + _cachedEnums.Add(type, new Dictionary()); + + //then try to get all custom attributes, this happens only once per type + var fields = type.GetRuntimeFields(); + foreach (var field in fields) + { + if (field.Name == "value__") + continue; + var attribute = (ParameterAttribute)field.GetCustomAttribute(typeof(ParameterAttribute)); + if (attribute != null) + { + if (!_cachedEnums[type].ContainsKey(attribute.Value)) + { + var fieldValue = field.GetValue(null); + _cachedEnums[type].Add(attribute.Value, fieldValue); + } + } + } + } + if (_cachedEnums[type].ContainsKey(value)) + { + return _cachedEnums[type][value]; + } + else + { + //dictionary does not contain enum value and has no custom attribute. So add it for future loops and return value + var parsed = Enum.Parse(type, value, ignoreCase: true); + _cachedEnums[type].Add(value, parsed); + return parsed; + } + } + private string _type; // Overridden to handle enums. @@ -92,39 +144,11 @@ namespace Octokit.Internal if (stringValue != null) { - if (ReflectionUtils.GetTypeInfo(type).IsEnum) + var typeInfo = ReflectionUtils.GetTypeInfo(type); + + if (typeInfo.IsEnum) { - if (!_cachedEnums.ContainsKey(type)) - { - //First add type to Dictionary - _cachedEnums.Add(type, new Dictionary()); - //then try to get all custom attributes, this happens only once per type - var fields = type.GetRuntimeFields(); - foreach (var field in fields) - { - if (field.Name == "value__") - continue; - var attribute = (ParameterAttribute)field.GetCustomAttribute(typeof(ParameterAttribute)); - if (attribute != null) - { - if (attribute.Value.Equals(value)) - _cachedEnums[type].Add(attribute.Value, field.GetValue(null)); - } - } - } - if (_cachedEnums[type].ContainsKey(value)) - { - return _cachedEnums[type][value]; - } - else - { - //dictionary does not contain enum value and has no custom attribute. So add it for future loops and return value - // remove '-' from values coming in to be able to enum utf-8 - stringValue = RemoveHyphenAndUnderscore(stringValue); - var parsed = Enum.Parse(type, stringValue, ignoreCase: true); - _cachedEnums[type].Add(value, parsed); - return parsed; - } + return DeserializeEnumHelper(stringValue, type); } if (ReflectionUtils.IsNullableType(type)) @@ -132,8 +156,7 @@ namespace Octokit.Internal var underlyingType = Nullable.GetUnderlyingType(type); if (ReflectionUtils.GetTypeInfo(underlyingType).IsEnum) { - stringValue = RemoveHyphenAndUnderscore(stringValue); - return Enum.Parse(underlyingType, stringValue, ignoreCase: true); + return DeserializeEnumHelper(stringValue, underlyingType); } } @@ -147,6 +170,16 @@ namespace Octokit.Internal return stringValue.Split(','); } } + + if (typeInfo.IsGenericType) + { + var typeDefinition = typeInfo.GetGenericTypeDefinition(); + + if (typeof(StringEnum<>).IsAssignableFrom(typeDefinition)) + { + return Activator.CreateInstance(type, stringValue); + } + } } else if (jsonValue != null) { @@ -165,15 +198,6 @@ namespace Octokit.Internal return base.DeserializeObject(value, type); } - static string RemoveHyphenAndUnderscore(string stringValue) - { - // remove '-' from values coming in to be able to enum utf-8 - stringValue = stringValue.Replace("-", ""); - // remove '-' from values coming in to be able to enum EventInfoState names with underscores in them. Like "head_ref_deleted" - stringValue = stringValue.Replace("_", ""); - return stringValue; - } - internal override IDictionary> SetterValueFactory(Type type) { return type.GetPropertiesAndFields() diff --git a/Octokit/Models/Request/IssueCommentRequest.cs b/Octokit/Models/Request/IssueCommentRequest.cs index 21f0b12f..9a83f2c0 100644 --- a/Octokit/Models/Request/IssueCommentRequest.cs +++ b/Octokit/Models/Request/IssueCommentRequest.cs @@ -16,7 +16,7 @@ namespace Octokit public IssueCommentRequest() { // Default arguments - Sort = PullRequestReviewCommentSort.Created; + Sort = IssueCommentSort.Created; Direction = SortDirection.Ascending; Since = null; } @@ -24,7 +24,7 @@ namespace Octokit /// /// Can be either created or updated. Default: created. /// - public PullRequestReviewCommentSort Sort { get; set; } + public IssueCommentSort Sort { get; set; } /// /// Can be either asc or desc. Default: asc. diff --git a/Octokit/Models/Request/IssueRequest.cs b/Octokit/Models/Request/IssueRequest.cs index 32d5ea68..8c6edc9a 100644 --- a/Octokit/Models/Request/IssueRequest.cs +++ b/Octokit/Models/Request/IssueRequest.cs @@ -97,26 +97,31 @@ namespace Octokit /// /// Issues assigned to the authenticated user. (Default) /// + [Parameter(Value = "assigned")] Assigned, /// /// Issues created by the authenticated user. /// + [Parameter(Value = "created")] Created, /// /// Issues mentioning the authenticated user. /// + [Parameter(Value = "mentioned")] Mentioned, /// /// Issues the authenticated user is subscribed to for updates. /// + [Parameter(Value = "subscribed")] Subscribed, /// /// All issues the authenticated user can see, regardless of participation or creation. /// + [Parameter(Value = "all")] All } @@ -128,16 +133,19 @@ namespace Octokit /// /// Items that are open. /// + [Parameter(Value = "open")] Open, /// /// Items that are closed. /// + [Parameter(Value = "closed")] Closed, /// /// All the items. /// + [Parameter(Value = "all")] All } @@ -149,11 +157,13 @@ namespace Octokit /// /// Items that are open /// + [Parameter(Value = "open")] Open, /// /// Items that are closed /// + [Parameter(Value = "closed")] Closed } @@ -165,16 +175,19 @@ namespace Octokit /// /// Sort by create date (default) /// + [Parameter(Value = "created")] Created, /// /// Sort by the date of the last update /// + [Parameter(Value = "updated")] Updated, /// /// Sort by the number of comments /// + [Parameter(Value = "comments")] Comments } diff --git a/Octokit/Models/Request/MergePullRequest.cs b/Octokit/Models/Request/MergePullRequest.cs index 1a1dbe69..91bc8a34 100644 --- a/Octokit/Models/Request/MergePullRequest.cs +++ b/Octokit/Models/Request/MergePullRequest.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Globalization; +using Octokit.Internal; namespace Octokit { @@ -56,16 +57,19 @@ namespace Octokit /// /// Create a merge commit /// + [Parameter(Value = "merge")] Merge, /// /// Squash and merge /// + [Parameter(Value = "squash")] Squash, /// /// Rebase and merge /// + [Parameter(Value = "rebase")] Rebase } } diff --git a/Octokit/Models/Request/MilestoneRequest.cs b/Octokit/Models/Request/MilestoneRequest.cs index 6d8fa854..415bce33 100644 --- a/Octokit/Models/Request/MilestoneRequest.cs +++ b/Octokit/Models/Request/MilestoneRequest.cs @@ -41,6 +41,8 @@ namespace Octokit { [Parameter(Value = "due_date")] DueDate, + + [Parameter(Value = "completeness")] Completeness } } diff --git a/Octokit/Models/Request/NewDeployment.cs b/Octokit/Models/Request/NewDeployment.cs index 1df2e2e4..1e24f95c 100644 --- a/Octokit/Models/Request/NewDeployment.cs +++ b/Octokit/Models/Request/NewDeployment.cs @@ -103,6 +103,7 @@ namespace Octokit /// /// Deploy everything (default) /// + [Parameter(Value = "deploy")] Deploy, /// diff --git a/Octokit/Models/Request/NewRepositoryWebHook.cs b/Octokit/Models/Request/NewRepositoryWebHook.cs index 777608ed..ceae9c6d 100644 --- a/Octokit/Models/Request/NewRepositoryWebHook.cs +++ b/Octokit/Models/Request/NewRepositoryWebHook.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Octokit.Internal; namespace Octokit { diff --git a/Octokit/Models/Request/Permission.cs b/Octokit/Models/Request/Permission.cs index b2b3cea5..0f5ea6c8 100644 --- a/Octokit/Models/Request/Permission.cs +++ b/Octokit/Models/Request/Permission.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Octokit.Internal; namespace Octokit { @@ -11,16 +12,19 @@ namespace Octokit /// /// team members can pull, push and administer these repositories. /// + [Parameter(Value = "admin")] Admin, /// /// team members can pull and push, but not administer these repositories /// + [Parameter(Value = "push")] Push, /// /// team members can pull, but not push to or administer these repositories /// + [Parameter(Value = "pull")] Pull } } \ No newline at end of file diff --git a/Octokit/Models/Request/PullRequestRequest.cs b/Octokit/Models/Request/PullRequestRequest.cs index 78edad22..4767fa9a 100644 --- a/Octokit/Models/Request/PullRequestRequest.cs +++ b/Octokit/Models/Request/PullRequestRequest.cs @@ -58,15 +58,21 @@ namespace Octokit /// /// Sort by created date (default) /// + [Parameter(Value = "created")] Created, + /// /// Sort by last updated date /// + [Parameter(Value = "updated")] Updated, + /// /// Sort by popularity (comment count) /// + [Parameter(Value = "popularity")] Popularity, + /// /// Sort by age (filtering by pulls updated in the last month) /// diff --git a/Octokit/Models/Request/RepositoryForksListRequest.cs b/Octokit/Models/Request/RepositoryForksListRequest.cs index 398a591b..3963f91e 100644 --- a/Octokit/Models/Request/RepositoryForksListRequest.cs +++ b/Octokit/Models/Request/RepositoryForksListRequest.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using Octokit.Internal; namespace Octokit { @@ -42,16 +43,19 @@ namespace Octokit /// /// Sort by date and show the newest first. /// + [Parameter(Value = "newest")] Newest, /// /// Sort by date and show the oldest first. /// + [Parameter(Value = "oldest")] Oldest, /// /// Sort by the number of stargazers. /// + [Parameter(Value = "stargazers")] Stargazers } } diff --git a/Octokit/Models/Request/RepositoryRequest.cs b/Octokit/Models/Request/RepositoryRequest.cs index 813de9f5..fd886815 100644 --- a/Octokit/Models/Request/RepositoryRequest.cs +++ b/Octokit/Models/Request/RepositoryRequest.cs @@ -82,26 +82,31 @@ namespace Octokit /// /// Return all repositories. /// + [Parameter(Value = "all")] All, /// /// Return repositories that the current authenticated user owns. /// + [Parameter(Value = "owner")] Owner, /// /// Returns public repositories. /// + [Parameter(Value = "public")] Public, /// /// The privateReturn private repositories. /// + [Parameter(Value = "private")] Private, /// /// Return repositories for which the current authenticated user is a member of the org or team. /// + [Parameter(Value = "member")] Member } @@ -113,16 +118,19 @@ namespace Octokit /// /// Sort by the date the repository was created. /// + [Parameter(Value = "created")] Created, /// /// Sort by the date the repository was last updated. /// + [Parameter(Value = "updated")] Updated, /// /// Sort by the date the repository was last pushed. /// + [Parameter(Value = "pushed")] Pushed, /// @@ -140,16 +148,19 @@ namespace Octokit /// /// Returns only public repositories /// + [Parameter(Value = "public")] Public, /// /// Returns only private repositories /// + [Parameter(Value = "private")] Private, /// /// Return both public and private repositories /// + [Parameter(Value = "all")] All } @@ -161,11 +172,13 @@ namespace Octokit /// /// Repositories that are owned by the authenticated user /// + [Parameter(Value = "owner")] Owner, /// /// Repositories that the user has been added to as a collaborator. /// + [Parameter(Value = "collaborator")] Collaborator, /// diff --git a/Octokit/Models/Request/SearchRepositoriesRequest.cs b/Octokit/Models/Request/SearchRepositoriesRequest.cs index 931aaee2..1ccf0553 100644 --- a/Octokit/Models/Request/SearchRepositoriesRequest.cs +++ b/Octokit/Models/Request/SearchRepositoriesRequest.cs @@ -179,8 +179,13 @@ namespace Octokit /// public enum InQualifier { + [Parameter(Value = "name")] Name, + + [Parameter(Value = "description")] Description, + + [Parameter(Value = "readme")] Readme } diff --git a/Octokit/Models/Request/SearchUsersRequest.cs b/Octokit/Models/Request/SearchUsersRequest.cs index 60c53468..3d8d7836 100644 --- a/Octokit/Models/Request/SearchUsersRequest.cs +++ b/Octokit/Models/Request/SearchUsersRequest.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; +using Octokit.Internal; namespace Octokit { @@ -149,10 +150,13 @@ namespace Octokit /// /// User account /// + [Parameter(Value = "user")] User, + /// /// Organization account /// + [Parameter(Value = "org")] Org } @@ -165,15 +169,20 @@ namespace Octokit /// Search by the username /// [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Username")] + [Parameter(Value = "login")] Username, + /// /// Search by the user's email address /// + [Parameter(Value = "email")] Email, + /// /// Search by the user's full name /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Fullname")] + [Parameter(Value = "fullname")] Fullname } @@ -182,8 +191,13 @@ namespace Octokit /// public enum UsersSearchSort { + [Parameter(Value = "followers")] Followers, + + [Parameter(Value = "repositories")] Repositories, + + [Parameter(Value = "joined")] Joined } } diff --git a/Octokit/Models/Request/StarredRequest.cs b/Octokit/Models/Request/StarredRequest.cs index ef2ec776..040d707e 100644 --- a/Octokit/Models/Request/StarredRequest.cs +++ b/Octokit/Models/Request/StarredRequest.cs @@ -54,11 +54,13 @@ namespace Octokit /// /// Sort y the date the star was created. /// + [Parameter(Value = "created")] Created, /// /// Sort by the date the star was last updated. /// + [Parameter(Value = "updated")] Updated } } diff --git a/Octokit/Models/Response/AccountType.cs b/Octokit/Models/Response/AccountType.cs index dddc366d..5b04edec 100644 --- a/Octokit/Models/Response/AccountType.cs +++ b/Octokit/Models/Response/AccountType.cs @@ -1,20 +1,25 @@ -namespace Octokit +using Octokit.Internal; + +namespace Octokit { public enum AccountType { /// /// User account /// + [Parameter(Value = "user")] User, /// /// Organization account /// + [Parameter(Value = "organization")] Organization, /// /// Bot account /// + [Parameter(Value = "bot")] Bot } } diff --git a/Octokit/Models/Response/Blob.cs b/Octokit/Models/Response/Blob.cs index 011009a7..61af6028 100644 --- a/Octokit/Models/Response/Blob.cs +++ b/Octokit/Models/Response/Blob.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using Octokit.Internal; namespace Octokit { @@ -24,7 +25,7 @@ namespace Octokit /// /// The encoding of the blob. /// - public EncodingType Encoding { get; protected set; } + public StringEnum Encoding { get; protected set; } /// /// The SHA of the blob. @@ -47,7 +48,10 @@ namespace Octokit public enum EncodingType { + [Parameter(Value = "utf-8")] Utf8, + + [Parameter(Value = "base64")] Base64 } } \ No newline at end of file diff --git a/Octokit/Models/Response/BranchProtection.cs b/Octokit/Models/Response/BranchProtection.cs index dec5c4bf..2c4f5d7b 100644 --- a/Octokit/Models/Response/BranchProtection.cs +++ b/Octokit/Models/Response/BranchProtection.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.Linq; +using Octokit.Internal; namespace Octokit { @@ -59,7 +60,7 @@ namespace Octokit /// /// Who required status checks apply to /// - public EnforcementLevel EnforcementLevel { get; protected set; } + public StringEnum EnforcementLevel { get; protected set; } /// /// The list of status checks to require in order to merge into this @@ -84,16 +85,19 @@ namespace Octokit /// /// Turn off required status checks for this . /// + [Parameter(Value = "off")] Off, /// /// Required status checks will be enforced for non-admins. /// + [Parameter(Value = "non_admins")] NonAdmins, /// /// Required status checks will be enforced for everyone (including admins). /// + [Parameter(Value = "everyone")] Everyone } diff --git a/Octokit/Models/Response/CombinedCommitStatus.cs b/Octokit/Models/Response/CombinedCommitStatus.cs index a7c9c2fa..db2c2cae 100644 --- a/Octokit/Models/Response/CombinedCommitStatus.cs +++ b/Octokit/Models/Response/CombinedCommitStatus.cs @@ -21,7 +21,7 @@ namespace Octokit /// /// The combined state of the commits. /// - public CommitState State { get; protected set; } + public StringEnum State { get; protected set; } /// /// The SHA of the reference. diff --git a/Octokit/Models/Response/CommitContent.cs b/Octokit/Models/Response/CommitContent.cs index 5f1a68ee..34df4e83 100644 --- a/Octokit/Models/Response/CommitContent.cs +++ b/Octokit/Models/Response/CommitContent.cs @@ -49,7 +49,7 @@ namespace Octokit /// The type of this content. It might be a File, Directory, Submodule, or Symlink /// [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "Matches the property name used by the API")] - public ContentType Type { get; protected set; } + public StringEnum Type { get; protected set; } /// /// URL to the raw content diff --git a/Octokit/Models/Response/CommitStatus.cs b/Octokit/Models/Response/CommitStatus.cs index 2745021a..b8780b60 100644 --- a/Octokit/Models/Response/CommitStatus.cs +++ b/Octokit/Models/Response/CommitStatus.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Globalization; +using Octokit.Internal; namespace Octokit { @@ -35,7 +36,7 @@ namespace Octokit /// /// The state of the commit /// - public CommitState State { get; protected set; } + public StringEnum State { get; protected set; } /// /// URL associated with this status. GitHub.com displays this URL as a link to allow users to easily see the @@ -85,21 +86,25 @@ namespace Octokit /// /// The commit state is still being determined. A build server might set this when it starts a build. /// + [Parameter(Value = "pending")] Pending, /// /// The build was successful for the commit. /// + [Parameter(Value = "success")] Success, /// /// There was some error with the build. /// + [Parameter(Value = "error")] Error, /// /// The build completed and reports a failure. /// + [Parameter(Value = "failure")] Failure } } diff --git a/Octokit/Models/Response/ContentType.cs b/Octokit/Models/Response/ContentType.cs index 410181a4..5dc7c728 100644 --- a/Octokit/Models/Response/ContentType.cs +++ b/Octokit/Models/Response/ContentType.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Octokit.Internal; namespace Octokit { @@ -7,10 +8,17 @@ namespace Octokit /// public enum ContentType { + [Parameter(Value = "file")] File, + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Dir", Justification = "Matches the value returned by the API")] + [Parameter(Value = "dir")] Dir, + + [Parameter(Value = "symlink")] Symlink, + + [Parameter(Value = "submodule")] Submodule } } \ No newline at end of file diff --git a/Octokit/Models/Response/DeploymentStatus.cs b/Octokit/Models/Response/DeploymentStatus.cs index 70e75a8b..643ca810 100644 --- a/Octokit/Models/Response/DeploymentStatus.cs +++ b/Octokit/Models/Response/DeploymentStatus.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using Octokit.Internal; namespace Octokit { @@ -36,7 +37,7 @@ namespace Octokit /// /// The state of this deployment status. /// - public DeploymentState State { get; protected set; } + public StringEnum State { get; protected set; } /// /// The that created this deployment status. @@ -93,10 +94,19 @@ namespace Octokit public enum DeploymentState { + [Parameter(Value = "pending")] Pending, + + [Parameter(Value = "success")] Success, + + [Parameter(Value = "error")] Error, + + [Parameter(Value = "failure")] Failure, + + [Parameter(Value = "inactive")] Inactive } } \ No newline at end of file diff --git a/Octokit/Models/Response/EventInfo.cs b/Octokit/Models/Response/EventInfo.cs index 49468b90..a11d91e0 100644 --- a/Octokit/Models/Response/EventInfo.cs +++ b/Octokit/Models/Response/EventInfo.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using Octokit.Internal; namespace Octokit { @@ -50,7 +51,7 @@ namespace Octokit /// /// Identifies the actual type of Event that occurred. /// - public EventInfoState Event { get; protected set; } + public StringEnum Event { get; protected set; } /// /// The String SHA of a commit that referenced this Issue. @@ -77,145 +78,173 @@ namespace Octokit /// The issue was closed by the actor. When the commit_id is present, it identifies the commit that /// closed the issue using “closes / fixes #NN” syntax. /// + [Parameter(Value = "closed")] Closed, /// /// The issue was reopened by the actor. /// + [Parameter(Value = "reopened")] Reopened, /// /// The actor subscribed to receive notifications for an issue. /// + [Parameter(Value = "subscribed")] Subscribed, /// /// The issue was merged by the actor. The commit_id attribute is the SHA1 of the HEAD commit that was merged. /// + [Parameter(Value = "merged")] Merged, /// /// The issue was referenced from a commit message. The commit_id attribute is the commit SHA1 of where /// that happened. /// + [Parameter(Value = "referenced")] Referenced, /// /// The actor was @mentioned in an issue body. /// + [Parameter(Value = "mentioned")] Mentioned, /// /// The issue was assigned to the actor. /// + [Parameter(Value = "assigned")] Assigned, /// /// The issue was unassigned to the actor. /// + [Parameter(Value = "unassigned")] Unassigned, /// /// A label was added to the issue. /// + [Parameter(Value = "labeled")] Labeled, /// /// A label was removed from the issue. /// + [Parameter(Value = "unlabeled")] Unlabeled, /// /// The issue was added to a milestone. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Milestoned")] + [Parameter(Value = "milestoned")] Milestoned, /// /// The issue was removed from a milestone. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Demilestoned")] + [Parameter(Value = "demilestoned")] Demilestoned, /// /// The issue title was changed. /// + [Parameter(Value = "renamed")] Renamed, /// /// The issue was locked by the actor. /// + [Parameter(Value = "locked")] Locked, /// /// The issue was unlocked by the actor. /// + [Parameter(Value = "unlocked")] Unlocked, /// /// The pull request’s branch was deleted. /// + [Parameter(Value = "head_ref_deleted")] HeadRefDeleted, /// /// The pull request’s branch was restored. /// + [Parameter(Value = "head_ref_restored")] HeadRefRestored, /// /// The actor dismissed a review from the pull request. /// + [Parameter(Value = "review_dismissed")] ReviewDismissed, /// /// The actor requested review from the subject on this pull request. /// + [Parameter(Value = "review_requested")] ReviewRequested, /// /// The actor removed the review request for the subject on this pull request. /// + [Parameter(Value = "review_request_removed")] ReviewRequestRemoved, /// /// The issue was added to a project board. /// + [Parameter(Value = "added_to_project")] AddedToProject, /// /// The issue was moved between columns in a project board. /// + [Parameter(Value = "moved_columns_in_project")] MovedColumnsInProject, /// /// The issue was removed from a project board. /// + [Parameter(Value = "removed_from_project")] RemovedFromProject, /// /// The issue was created by converting a note in a project board to an issue. /// + [Parameter(Value = "converted_note_to_issue")] ConvertedNoteToIssue, /// /// The actor unsubscribed from notifications for an issue. /// + [Parameter(Value = "unsubscribed")] Unsubscribed, /// /// A comment was added to the issue. /// + [Parameter(Value = "commented")] Commented, /// /// A commit was added to the pull request's HEAD branch. /// Only provided for pull requests. /// + [Parameter(Value = "committed")] Committed, /// /// Base branch of the pull request was changed. /// + [Parameter(Value = "base_ref_changed")] BaseRefChanged, /// @@ -224,21 +253,25 @@ namespace Octokit /// url of the reference's source. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Crossreferenced")] + [Parameter(Value = "cross-referenced")] Crossreferenced, /// /// The issue was reveiewed. /// + [Parameter(Value = "reviewed")] Reviewed, /// /// A line comment was made. /// + [Parameter(Value = "line-commented")] LineCommented, /// /// A commit comment was made. /// + [Parameter(Value = "commit-commented")] CommitCommented } } diff --git a/Octokit/Models/Response/Issue.cs b/Octokit/Models/Response/Issue.cs index e6363663..bc3007bf 100644 --- a/Octokit/Models/Response/Issue.cs +++ b/Octokit/Models/Response/Issue.cs @@ -70,7 +70,7 @@ namespace Octokit /// /// Whether the issue is open or closed. /// - public ItemState State { get; protected set; } + public StringEnum State { get; protected set; } /// /// Title of the issue @@ -168,11 +168,12 @@ namespace Octokit ? null : Labels.Select(x => x.Name); + ItemState state; var issueUpdate = new IssueUpdate { Body = Body, Milestone = milestoneId, - State = State, + State = (State.TryParse(out state) ? (ItemState?)state : null), Title = Title }; diff --git a/Octokit/Models/Response/IssueComment.cs b/Octokit/Models/Response/IssueComment.cs index 0c46167d..d3ad0768 100644 --- a/Octokit/Models/Response/IssueComment.cs +++ b/Octokit/Models/Response/IssueComment.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Globalization; +using Octokit.Internal; namespace Octokit { @@ -68,11 +69,13 @@ namespace Octokit /// /// Sort by create date (default) /// + [Parameter(Value = "created")] Created, /// /// Sort by the date of the last update /// + [Parameter(Value = "updated")] Updated } } diff --git a/Octokit/Models/Response/IssueEvent.cs b/Octokit/Models/Response/IssueEvent.cs index 008f401a..780700f1 100644 --- a/Octokit/Models/Response/IssueEvent.cs +++ b/Octokit/Models/Response/IssueEvent.cs @@ -51,7 +51,7 @@ namespace Octokit /// /// Identifies the actual type of Event that occurred. /// - public EventInfoState Event { get; protected set; } + public StringEnum Event { get; protected set; } /// /// The String SHA of a commit that referenced this Issue. diff --git a/Octokit/Models/Response/Migration.cs b/Octokit/Models/Response/Migration.cs index 9246210e..84c960ba 100644 --- a/Octokit/Models/Response/Migration.cs +++ b/Octokit/Models/Response/Migration.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using Octokit.Internal; namespace Octokit { @@ -55,7 +56,7 @@ namespace Octokit /// /// The state of migration. Can be one of pending, exporting, exported and failed. /// - public MigrationState State { get; private set; } + public StringEnum State { get; private set; } /// /// Whether to lock repositories. @@ -106,21 +107,25 @@ namespace Octokit /// /// The migration hasn't started yet. /// + [Parameter(Value = "pending")] Pending, /// /// The migration is in progress. /// + [Parameter(Value = "exporting")] Exporting, /// /// The migration finished successfully. /// + [Parameter(Value = "exported")] Exported, /// /// The migration failed. /// + [Parameter(Value = "failed")] Failed } } diff --git a/Octokit/Models/Response/Milestone.cs b/Octokit/Models/Response/Milestone.cs index 0fd16255..727592b0 100644 --- a/Octokit/Models/Response/Milestone.cs +++ b/Octokit/Models/Response/Milestone.cs @@ -48,7 +48,7 @@ namespace Octokit /// /// Whether the milestone is open or closed. /// - public ItemState State { get; protected set; } + public StringEnum State { get; protected set; } /// /// Title of the milestone diff --git a/Octokit/Models/Response/Page.cs b/Octokit/Models/Response/Page.cs index 10a8672b..6993e915 100644 --- a/Octokit/Models/Response/Page.cs +++ b/Octokit/Models/Response/Page.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using Octokit.Internal; namespace Octokit { @@ -9,22 +10,32 @@ namespace Octokit /// /// The site has yet to be built /// + [Parameter(Value = "null")] Null, + /// + /// The build has been requested but not yet begun + /// + [Parameter(Value = "queued")] + Queued, + /// /// The build is in progress /// + [Parameter(Value = "building")] Building, /// /// The site has been built /// + [Parameter(Value = "built")] Built, /// /// An error occurred during the build /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Errored")] + [Parameter(Value = "errored")] Errored } @@ -58,7 +69,7 @@ namespace Octokit /// /// Build status of the pages site. /// - public PagesBuildStatus Status { get; protected set; } + public StringEnum Status { get; protected set; } /// /// CName of the pages site. Will be null if no CName was provided by the user. diff --git a/Octokit/Models/Response/PagesBuild.cs b/Octokit/Models/Response/PagesBuild.cs index dd452b7f..64c5c4ae 100644 --- a/Octokit/Models/Response/PagesBuild.cs +++ b/Octokit/Models/Response/PagesBuild.cs @@ -31,7 +31,7 @@ namespace Octokit /// /// The status of the build. /// - public PagesBuildStatus Status { get; protected set; } + public StringEnum Status { get; protected set; } /// /// Error details - if there was one. /// diff --git a/Octokit/Models/Response/PullRequest.cs b/Octokit/Models/Response/PullRequest.cs index bca78fdc..0b6ea1ad 100644 --- a/Octokit/Models/Response/PullRequest.cs +++ b/Octokit/Models/Response/PullRequest.cs @@ -93,7 +93,7 @@ namespace Octokit /// /// Whether the pull request is open or closed. The default is . /// - public ItemState State { get; protected set; } + public StringEnum State { get; protected set; } /// /// Title of the pull request. diff --git a/Octokit/Models/Response/PullRequestReviewComment.cs b/Octokit/Models/Response/PullRequestReviewComment.cs index c2814d67..a969e5b3 100644 --- a/Octokit/Models/Response/PullRequestReviewComment.cs +++ b/Octokit/Models/Response/PullRequestReviewComment.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Globalization; +using Octokit.Internal; namespace Octokit { @@ -115,11 +116,13 @@ namespace Octokit /// /// Sort by create date (default) /// + [Parameter(Value = "created")] Created, /// /// Sort by the date of the last update /// + [Parameter(Value = "updated")] Updated } } diff --git a/Octokit/Models/Response/PunchCardPoint.cs b/Octokit/Models/Response/PunchCardPoint.cs index 40b1b9e4..9492587e 100644 --- a/Octokit/Models/Response/PunchCardPoint.cs +++ b/Octokit/Models/Response/PunchCardPoint.cs @@ -29,7 +29,7 @@ namespace Octokit CommitCount = commitCount; } - public DayOfWeek DayOfWeek { get; private set; } + public StringEnum DayOfWeek { get; private set; } public int HourOfTheDay { get; private set; } public int CommitCount { get; private set; } diff --git a/Octokit/Models/Response/Reaction.cs b/Octokit/Models/Response/Reaction.cs index fc5ff8c0..0c10aa77 100644 --- a/Octokit/Models/Response/Reaction.cs +++ b/Octokit/Models/Response/Reaction.cs @@ -8,11 +8,20 @@ namespace Octokit { [Parameter(Value = "+1")] Plus1, + [Parameter(Value = "-1")] Minus1, + + [Parameter(Value = "laugh")] Laugh, + + [Parameter(Value = "confused")] Confused, + + [Parameter(Value = "heart")] Heart, + + [Parameter(Value = "hooray")] Hooray } @@ -42,7 +51,7 @@ namespace Octokit /// The reaction type for this commit comment. /// [Parameter(Key = "content")] - public ReactionType Content { get; protected set; } + public StringEnum Content { get; protected set; } internal string DebuggerDisplay { diff --git a/Octokit/Models/Response/RepositoryInvitation.cs b/Octokit/Models/Response/RepositoryInvitation.cs index 8ebb93c5..d28655a8 100644 --- a/Octokit/Models/Response/RepositoryInvitation.cs +++ b/Octokit/Models/Response/RepositoryInvitation.cs @@ -1,13 +1,19 @@ using System; using System.Diagnostics; using System.Globalization; +using Octokit.Internal; namespace Octokit { public enum InvitationPermissionType { + [Parameter(Value = "read")] Read, + + [Parameter(Value = "write")] Write, + + [Parameter(Value = "admin")] Admin } @@ -36,7 +42,7 @@ namespace Octokit public User Inviter { get; protected set; } - public InvitationPermissionType Permissions { get; protected set; } + public StringEnum Permissions { get; protected set; } public DateTimeOffset CreatedAt { get; protected set; } diff --git a/Octokit/Models/Response/RepositoryTrafficClone.cs b/Octokit/Models/Response/RepositoryTrafficClone.cs index f03aec0f..9d96eb28 100644 --- a/Octokit/Models/Response/RepositoryTrafficClone.cs +++ b/Octokit/Models/Response/RepositoryTrafficClone.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using Octokit.Internal; namespace Octokit { @@ -60,7 +61,10 @@ namespace Octokit public enum TrafficDayOrWeek { + [Parameter(Value = "day")] Day, + + [Parameter(Value = "week")] Week } } diff --git a/Octokit/Models/Response/StringEnum.cs b/Octokit/Models/Response/StringEnum.cs new file mode 100644 index 00000000..be9be44f --- /dev/null +++ b/Octokit/Models/Response/StringEnum.cs @@ -0,0 +1,156 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using Octokit.Internal; + +namespace Octokit +{ + [DebuggerDisplay("{DebuggerDisplay,nq}")] + [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] + public struct StringEnum : IEquatable> + where TEnum : struct + { + private readonly string _stringValue; + + private TEnum? _parsedValue; + + public StringEnum(TEnum parsedValue) + { + if (!Enum.IsDefined(typeof(TEnum), parsedValue)) + { + throw GetArgumentException(parsedValue.ToString()); + } + + // Use the SimpleJsonSerializer to serialize the TEnum into the correct string according to the GitHub Api strategy + _stringValue = SimpleJsonSerializer.SerializeEnum(parsedValue as Enum); + _parsedValue = parsedValue; + } + + public StringEnum(string stringValue) + { + _stringValue = stringValue ?? string.Empty; + _parsedValue = null; + } + + public string StringValue + { + get { return _stringValue; } + } + + public TEnum Value + { + get { return _parsedValue ?? (_parsedValue = ParseValue()).Value; } + } + + internal string DebuggerDisplay + { + get { return StringValue; } + } + + public bool TryParse(out TEnum value) + { + if (_parsedValue.HasValue) + { + // the value has been parsed already. + value = _parsedValue.Value; + return true; + } + + if (string.IsNullOrEmpty(StringValue)) + { + value = default(TEnum); + return false; + } + + try + { + // Use the SimpleJsonSerializer to parse the string to Enum according to the GitHub Api strategy + value = (TEnum)SimpleJsonSerializer.DeserializeEnum(StringValue, typeof(TEnum)); + + // cache the parsed value for subsequent calls. + _parsedValue = value; + return true; + } + catch (ArgumentException) + { + value = default(TEnum); + return false; + } + } + + public bool Equals(StringEnum other) + { + TEnum value; + TEnum otherValue; + if (TryParse(out value) && other.TryParse(out otherValue)) + { + // if we're able to parse both values, compare the parsed enum + return value.Equals(otherValue); + } + + // otherwise, we fall back to a case-insensitive comparison of the string values. + return string.Equals(StringValue, other.StringValue, StringComparison.OrdinalIgnoreCase); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + return obj is StringEnum && Equals((StringEnum) obj); + } + + public override int GetHashCode() + { + return StringComparer.OrdinalIgnoreCase.GetHashCode(StringValue); + } + + public static bool operator ==(StringEnum left, StringEnum right) + { + return left.Equals(right); + } + + public static bool operator !=(StringEnum left, StringEnum right) + { + return !left.Equals(right); + } + + public static implicit operator StringEnum(string value) + { + return new StringEnum(value); + } + + public static implicit operator StringEnum(TEnum parsedValue) + { + return new StringEnum(parsedValue); + } + + public override string ToString() + { + return StringValue; + } + + private TEnum ParseValue() + { + TEnum value; + if (TryParse(out value)) + { + return value; + } + + throw GetArgumentException(StringValue); + } + + private static ArgumentException GetArgumentException(string value) + { + return new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + "Value '{0}' is not a valid '{1}' enum value.", + value, + typeof(TEnum).Name)); + } + } +} \ No newline at end of file diff --git a/Octokit/Models/Response/TagObject.cs b/Octokit/Models/Response/TagObject.cs index a157d17b..26080b3b 100644 --- a/Octokit/Models/Response/TagObject.cs +++ b/Octokit/Models/Response/TagObject.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using Octokit.Internal; namespace Octokit { @@ -16,7 +17,7 @@ namespace Octokit [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "Name defined by web api and required for deserialization")] - public TaggedType Type { get; protected set; } + public StringEnum Type { get; protected set; } } /// @@ -24,9 +25,16 @@ namespace Octokit /// public enum TaggedType { + [Parameter(Value = "commit")] Commit, + + [Parameter(Value = "blob")] Blob, + + [Parameter(Value = "tree")] Tree, + + [Parameter(Value = "tag")] Tag } } \ No newline at end of file diff --git a/Octokit/Models/Response/Team.cs b/Octokit/Models/Response/Team.cs index a239a2b7..5a41d25b 100644 --- a/Octokit/Models/Response/Team.cs +++ b/Octokit/Models/Response/Team.cs @@ -42,7 +42,7 @@ namespace Octokit /// /// permission attached to this team /// - public Permission Permission { get; protected set; } + public StringEnum Permission { get; protected set; } /// /// how many members in this team diff --git a/Octokit/Models/Response/TimelineEventInfo.cs b/Octokit/Models/Response/TimelineEventInfo.cs index f67c6fc7..fcf7c107 100644 --- a/Octokit/Models/Response/TimelineEventInfo.cs +++ b/Octokit/Models/Response/TimelineEventInfo.cs @@ -28,7 +28,7 @@ namespace Octokit public string Url { get; protected set; } public User Actor { get; protected set; } public string CommitId { get; protected set; } - public EventInfoState Event { get; protected set; } + public StringEnum Event { get; protected set; } public DateTimeOffset CreatedAt { get; protected set; } public Label Label { get; protected set; } public User Assignee { get; protected set; } diff --git a/Octokit/Models/Response/TreeItem.cs b/Octokit/Models/Response/TreeItem.cs index c6105456..eb4a3262 100644 --- a/Octokit/Models/Response/TreeItem.cs +++ b/Octokit/Models/Response/TreeItem.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using Octokit.Internal; namespace Octokit { @@ -33,7 +34,7 @@ namespace Octokit /// The type of this Tree Item. /// [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] - public TreeType Type { get; protected set; } + public StringEnum Type { get; protected set; } /// /// The size of this Tree Item. @@ -58,8 +59,13 @@ namespace Octokit public enum TreeType { + [Parameter(Value = "blob")] Blob, + + [Parameter(Value = "tree")] Tree, + + [Parameter(Value = "commit")] Commit } diff --git a/Octokit/Models/Response/Verification.cs b/Octokit/Models/Response/Verification.cs index fdc703bf..6ac90f45 100644 --- a/Octokit/Models/Response/Verification.cs +++ b/Octokit/Models/Response/Verification.cs @@ -19,7 +19,7 @@ namespace Octokit /// The reason for verified value. /// [Parameter(Key = "reason")] - public VerificationReason Reason { get; protected set; } + public StringEnum Reason { get; protected set; } /// /// The signature that was extracted from the commit. diff --git a/docs/working-with-enums.md b/docs/working-with-enums.md new file mode 100644 index 00000000..c3a2a546 --- /dev/null +++ b/docs/working-with-enums.md @@ -0,0 +1,124 @@ +# Working with Enums + +In order to provide a consistent and familiar dotnet experience, Octokit maps appropriate GitHub API fields to c# `Enum`'s. + +For example an `Issue`'s status API values are `"open"` and `"closed"` which in Octokit are represented as `ItemState.Open` and `ItemState.Closed` + +## Introducing the `StringEnum` Wrapper + +Since the upstream GitHub API can move fast, we want to avoid throwing exceptions when deserializing responses that contain field values that are not yet present in our Octokit `Enum` values. + +Therefore Octokit now uses a wrapper class `StringEnum` to represent these values in all response models. + +### `StringEnum` Usage + +`StringEnum` is able to be implicitly converted to/from `Enum` and `string` values and provides the convenience of dealing with explicit enumeration members the majority of cases, with the added resillience to fall back to `string`s when it needs to. + +Whilst existing code will continue to function due to the implicit conversions, users should update their code to the "safe" usage patterns shown below in order to guard against future API additions causing deserialization failures. + +#### Implicit conversions: +``` csharp +public enum EncodingType +{ + Ascii, // api value "ascii" + Utf8 // api value "utf-8" +} + +// Implicit conversion from Enum +StringEnum encoding = EncodingType.Utf8; + +// Implicit conversion from API string +StringEnum encoding = "utf-8"; +``` + +#### When dealing with a known API value +Accessing the string value (safe): +``` csharp +StringEnum encoding = "utf-8"; +Console.WriteLine(encoding.StringValue); + +// "utf-8" +``` + +Accessing the Enum value (not safe, but works as the value is known): +``` csharp +StringEnum encoding = "utf-8"; +Console.WriteLine(encoding.Value); + +// EncodingType.Utf8 +``` + +#### When dealing with an unknown API value +Accessing the string value (safe): +``` csharp +StringEnum encoding = "new_hoopy_format"; +Console.WriteLine(encoding.StringValue); + +// "new_hoopy_format" +``` + +Accessing the Enum value (not safe, throws exceptionn as the value is not known) +``` csharp +StringEnum encoding = "new_hoopy_format"; +encoding.Value; + +// ArgumentException: "Value 'new_hoopy_format' is not a valid 'EncodingType' enum value. +``` + +Evaluating the value safely, using `TryParse()` +``` csharp +StringEnum encoding = "new_hoopy_format"; +if (encoding.TryParse(out EncodingType type)) +{ + Console.WriteLine("{0} was a known enum member!", type); +} +else +{ + Console.WriteLine("{0} was not a known enum member!", encoding.StringValue); +} + +// "new_hoopy_format was not a known enum member!" +``` + +## Activity/Timeline APIs and EventInfoState + +Of particular importance are the `Enum`'s used in activity/event stream APIs, such as `EventInfoState`. Since new functionality introduced on GitHub.com frequently leads to additional event types being received, Octokit was constantly playing catchup with these `Enum` values. Now, with `StringEnum` we have a solution to keep your code functioning, without needing to wait for a new Octokit release! + +### Safe Issue Timeline EventInfoState Example +``` csharp +// Get the event timeline for a PullRequest +var timelineEventInfos = await client.Issue.Timeline.GetAllForIssue("octokit", "octokit.net", 1595); + +foreach (var issueEvent in timelineEventInfos) +{ + // Safely check whether the EventInfoType was a known Enum value + if (issueEvent.Event.TryParse(out EventInfoState eventState)) + { + // Enum value known + switch (eventState) + { + case EventInfoState.Commented: + case EventInfoState.CommitCommented: + case EventInfoState.LineCommented: + { + Console.WriteLine("Comment activity found!"); + break; + } + case EventInfoState.ReviewDismissed: + case EventInfoState.Reviewed: + case EventInfoState.ReviewRequested: + case EventInfoState.ReviewRequestRemoved: + { + Console.WriteLine("Review activity found!"); + break; + } + } + } + else + { + // Enum value not known, use StringValue + Console.WriteLine("Unknown EventInfoState encountered: {0}", + issueEvent.Event.StringValue); + } +} +``` \ No newline at end of file