Add StringEnum to handle unknown enum values returned from API (#1595)

* Added StringEnum<TEnum>

* 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<TEnum>

* Fix parameter value to resolve failing integration test
This commit is contained in:
Kristian Hellang
2017-06-25 11:29:57 +02:00
committed by Ryan Gribble
parent 8877eea1cb
commit 5ee4d64046
54 changed files with 897 additions and 112 deletions

View File

@@ -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<FieldInfo> enumMembers)
: base(CreateMessage(enumType, enumMembers))
{ }
static string CreateMessage(Type enumType, IEnumerable<FieldInfo> 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)));
}
}
}

View File

@@ -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<PropertyInfo> enumProperties)
: base(CreateMessage(modelType, enumProperties))
{ }
static string CreateMessage(Type modelType, IEnumerable<PropertyInfo> enumProperties)
{
return string.Format("Model type '{0}' contains the following Enum properties which should be wrapped in a StringEnum<TEnum> instead: {1}{2}",
modelType.FullName,
Environment.NewLine,
string.Join(Environment.NewLine, enumProperties.Select(x => x.PropertyType.Name + " " + x.Name)));
}
}
}

View File

@@ -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<object[]> 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<object[]> ModelTypes
@@ -154,6 +178,18 @@ namespace Octokit.Tests.Conventions
get { return GetModelTypes(includeRequestModels: false).Select(type => new[] { type }); }
}
public static IEnumerable<object[]> 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<Type> GetModelTypes(bool includeRequestModels)
{
var allModelTypes = new HashSet<Type>();

View File

@@ -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();
}
}

View File

@@ -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");

View File

@@ -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);

View File

@@ -26,7 +26,7 @@ public class MiscellaneousClientTests
var result = await github.Miscellaneous.RenderRawMarkdown("This is\r\n a **test**");
Assert.Equal("<p>This is\n a <strong>test</strong></p>\n", result);
Assert.Equal("<p>This is\na <strong>test</strong></p>\n", result);
}
}

View File

@@ -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);

View File

@@ -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<AccountType>("user");
Assert.Equal("user", stringEnum.StringValue);
}
[Fact]
public void ShouldSetValueAndParsedValue()
{
var stringEnum = new StringEnum<AccountType>(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<ReactionType> stringEnum = ReactionType.Plus1;
Assert.Equal("+1", stringEnum.StringValue);
Assert.Equal(ReactionType.Plus1, stringEnum.Value);
}
[Fact]
public void ShouldThrowForInvalidEnumValue()
{
Assert.Throws<ArgumentException>(() => new StringEnum<AccountType>((AccountType) 1337));
}
}
public class TheParsedValueProperty
{
[Theory]
[InlineData("")]
[InlineData(null)]
[InlineData("Cow")]
public void ShouldThrowForInvalidValue(string value)
{
var stringEnum = new StringEnum<AccountType>(value);
Assert.Throws<ArgumentException>(() => stringEnum.Value);
}
[Fact]
public void ShouldHandleUnderscores()
{
var stringEnum = new StringEnum<EventInfoState>("review_dismissed");
Assert.Equal("review_dismissed", stringEnum.StringValue);
Assert.Equal(EventInfoState.ReviewDismissed, stringEnum.Value);
}
[Fact]
public void ShouldHandleHyphens()
{
var stringEnum = new StringEnum<EncodingType>("utf-8");
Assert.Equal("utf-8", stringEnum.StringValue);
Assert.Equal(EncodingType.Utf8, stringEnum.Value);
}
[Fact]
public void ShouldHandleCustomPropertyAttribute()
{
var stringEnum = new StringEnum<ReactionType>("+1");
Assert.Equal("+1", stringEnum.StringValue);
Assert.Equal(ReactionType.Plus1, stringEnum.Value);
}
}
public class TheTryParseMethod
{
[Fact]
public void ShouldReturnTrueForValidValue()
{
var stringEnum = new StringEnum<AccountType>("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<AccountType>(value);
AccountType type;
var result = stringEnum.TryParse(out type);
Assert.False(result);
}
}
public class TheImplicitConversionOperator
{
[Fact]
public void ShouldSetValue()
{
StringEnum<AccountType> stringEnum = "user";
Assert.Equal("user", stringEnum.StringValue);
}
[Fact]
public void ShouldSetValueAndParsedValue()
{
StringEnum<AccountType> stringEnum = AccountType.Bot;
Assert.Equal("bot", stringEnum.StringValue);
Assert.Equal(AccountType.Bot, stringEnum.Value);
}
[Fact]
public void ShouldThrowForInvalidEnumValue()
{
StringEnum<AccountType> stringEnum;
Assert.Throws<ArgumentException>(() => stringEnum = (AccountType) 1337);
}
}
public class TheEqualityOperator
{
[Fact]
public void IsCaseInsensitive()
{
var first = new StringEnum<AccountType>("bot");
var second = new StringEnum<AccountType>("BoT");
Assert.True(first == second);
}
[Fact]
public void FallsBackToStringComparison()
{
var first = new StringEnum<AccountType>("god");
var second = new StringEnum<AccountType>("GoD");
Assert.True(first == second);
}
}
}
}

View File

@@ -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
{

View File

@@ -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<ObjectWithEnumProperty>(json);
var sample1 = new SimpleJsonSerializer().Deserialize<ObjectWithEnumProperty>(json1);
var sample2 = new SimpleJsonSerializer().Deserialize<ObjectWithEnumProperty>(json2);
var sample3 = new SimpleJsonSerializer().Deserialize<ObjectWithEnumProperty>(json3);
var sample4 = new SimpleJsonSerializer().Deserialize<ObjectWithEnumProperty>(json4);
var sample5 = new SimpleJsonSerializer().Deserialize<ObjectWithEnumProperty>(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<ObjectWithEnumProperty>(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<ObjectWithEnumProperty>(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
}
}

View File

@@ -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<T>(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<string> _membersWhichShouldPublishNull = new List<string>();
@@ -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<object, object>());
//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<object, object>());
//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<string, KeyValuePair<Type, ReflectionUtils.SetDelegate>> SetterValueFactory(Type type)
{
return type.GetPropertiesAndFields()

View File

@@ -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
/// <summary>
/// Can be either created or updated. Default: created.
/// </summary>
public PullRequestReviewCommentSort Sort { get; set; }
public IssueCommentSort Sort { get; set; }
/// <summary>
/// Can be either asc or desc. Default: asc.

View File

@@ -97,26 +97,31 @@ namespace Octokit
/// <summary>
/// Issues assigned to the authenticated user. (Default)
/// </summary>
[Parameter(Value = "assigned")]
Assigned,
/// <summary>
/// Issues created by the authenticated user.
/// </summary>
[Parameter(Value = "created")]
Created,
/// <summary>
/// Issues mentioning the authenticated user.
/// </summary>
[Parameter(Value = "mentioned")]
Mentioned,
/// <summary>
/// Issues the authenticated user is subscribed to for updates.
/// </summary>
[Parameter(Value = "subscribed")]
Subscribed,
/// <summary>
/// All issues the authenticated user can see, regardless of participation or creation.
/// </summary>
[Parameter(Value = "all")]
All
}
@@ -128,16 +133,19 @@ namespace Octokit
/// <summary>
/// Items that are open.
/// </summary>
[Parameter(Value = "open")]
Open,
/// <summary>
/// Items that are closed.
/// </summary>
[Parameter(Value = "closed")]
Closed,
/// <summary>
/// All the items.
/// </summary>
[Parameter(Value = "all")]
All
}
@@ -149,11 +157,13 @@ namespace Octokit
/// <summary>
/// Items that are open
/// </summary>
[Parameter(Value = "open")]
Open,
/// <summary>
/// Items that are closed
/// </summary>
[Parameter(Value = "closed")]
Closed
}
@@ -165,16 +175,19 @@ namespace Octokit
/// <summary>
/// Sort by create date (default)
/// </summary>
[Parameter(Value = "created")]
Created,
/// <summary>
/// Sort by the date of the last update
/// </summary>
[Parameter(Value = "updated")]
Updated,
/// <summary>
/// Sort by the number of comments
/// </summary>
[Parameter(Value = "comments")]
Comments
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Globalization;
using Octokit.Internal;
namespace Octokit
{
@@ -56,16 +57,19 @@ namespace Octokit
/// <summary>
/// Create a merge commit
/// </summary>
[Parameter(Value = "merge")]
Merge,
/// <summary>
/// Squash and merge
/// </summary>
[Parameter(Value = "squash")]
Squash,
/// <summary>
/// Rebase and merge
/// </summary>
[Parameter(Value = "rebase")]
Rebase
}
}

View File

@@ -41,6 +41,8 @@ namespace Octokit
{
[Parameter(Value = "due_date")]
DueDate,
[Parameter(Value = "completeness")]
Completeness
}
}

View File

@@ -103,6 +103,7 @@ namespace Octokit
/// <summary>
/// Deploy everything (default)
/// </summary>
[Parameter(Value = "deploy")]
Deploy,
/// <summary>

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Octokit.Internal;
namespace Octokit
{

View File

@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Octokit.Internal;
namespace Octokit
{
@@ -11,16 +12,19 @@ namespace Octokit
/// <summary>
/// team members can pull, push and administer these repositories.
/// </summary>
[Parameter(Value = "admin")]
Admin,
/// <summary>
/// team members can pull and push, but not administer these repositories
/// </summary>
[Parameter(Value = "push")]
Push,
/// <summary>
/// team members can pull, but not push to or administer these repositories
/// </summary>
[Parameter(Value = "pull")]
Pull
}
}

View File

@@ -58,15 +58,21 @@ namespace Octokit
/// <summary>
/// Sort by created date (default)
/// </summary>
[Parameter(Value = "created")]
Created,
/// <summary>
/// Sort by last updated date
/// </summary>
[Parameter(Value = "updated")]
Updated,
/// <summary>
/// Sort by popularity (comment count)
/// </summary>
[Parameter(Value = "popularity")]
Popularity,
/// <summary>
/// Sort by age (filtering by pulls updated in the last month)
/// </summary>

View File

@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Globalization;
using Octokit.Internal;
namespace Octokit
{
@@ -42,16 +43,19 @@ namespace Octokit
/// <summary>
/// Sort by date and show the newest first.
/// </summary>
[Parameter(Value = "newest")]
Newest,
/// <summary>
/// Sort by date and show the oldest first.
/// </summary>
[Parameter(Value = "oldest")]
Oldest,
/// <summary>
/// Sort by the number of stargazers.
/// </summary>
[Parameter(Value = "stargazers")]
Stargazers
}
}

View File

@@ -82,26 +82,31 @@ namespace Octokit
/// <summary>
/// Return all repositories.
/// </summary>
[Parameter(Value = "all")]
All,
/// <summary>
/// Return repositories that the current authenticated user owns.
/// </summary>
[Parameter(Value = "owner")]
Owner,
/// <summary>
/// Returns public repositories.
/// </summary>
[Parameter(Value = "public")]
Public,
/// <summary>
/// The privateReturn private repositories.
/// </summary>
[Parameter(Value = "private")]
Private,
/// <summary>
/// Return repositories for which the current authenticated user is a member of the org or team.
/// </summary>
[Parameter(Value = "member")]
Member
}
@@ -113,16 +118,19 @@ namespace Octokit
/// <summary>
/// Sort by the date the repository was created.
/// </summary>
[Parameter(Value = "created")]
Created,
/// <summary>
/// Sort by the date the repository was last updated.
/// </summary>
[Parameter(Value = "updated")]
Updated,
/// <summary>
/// Sort by the date the repository was last pushed.
/// </summary>
[Parameter(Value = "pushed")]
Pushed,
/// <summary>
@@ -140,16 +148,19 @@ namespace Octokit
/// <summary>
/// Returns only public repositories
/// </summary>
[Parameter(Value = "public")]
Public,
/// <summary>
/// Returns only private repositories
/// </summary>
[Parameter(Value = "private")]
Private,
/// <summary>
/// Return both public and private repositories
/// </summary>
[Parameter(Value = "all")]
All
}
@@ -161,11 +172,13 @@ namespace Octokit
/// <summary>
/// Repositories that are owned by the authenticated user
/// </summary>
[Parameter(Value = "owner")]
Owner,
/// <summary>
/// Repositories that the user has been added to as a collaborator.
/// </summary>
[Parameter(Value = "collaborator")]
Collaborator,
/// <summary>

View File

@@ -179,8 +179,13 @@ namespace Octokit
/// </summary>
public enum InQualifier
{
[Parameter(Value = "name")]
Name,
[Parameter(Value = "description")]
Description,
[Parameter(Value = "readme")]
Readme
}

View File

@@ -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
/// <summary>
/// User account
/// </summary>
[Parameter(Value = "user")]
User,
/// <summary>
/// Organization account
/// </summary>
[Parameter(Value = "org")]
Org
}
@@ -165,15 +169,20 @@ namespace Octokit
/// Search by the username
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Username")]
[Parameter(Value = "login")]
Username,
/// <summary>
/// Search by the user's email address
/// </summary>
[Parameter(Value = "email")]
Email,
/// <summary>
/// Search by the user's full name
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Fullname")]
[Parameter(Value = "fullname")]
Fullname
}
@@ -182,8 +191,13 @@ namespace Octokit
/// </summary>
public enum UsersSearchSort
{
[Parameter(Value = "followers")]
Followers,
[Parameter(Value = "repositories")]
Repositories,
[Parameter(Value = "joined")]
Joined
}
}

View File

@@ -54,11 +54,13 @@ namespace Octokit
/// <summary>
/// Sort y the date the star was created.
/// </summary>
[Parameter(Value = "created")]
Created,
/// <summary>
/// Sort by the date the star was last updated.
/// </summary>
[Parameter(Value = "updated")]
Updated
}
}

View File

@@ -1,20 +1,25 @@
namespace Octokit
using Octokit.Internal;
namespace Octokit
{
public enum AccountType
{
/// <summary>
/// User account
/// </summary>
[Parameter(Value = "user")]
User,
/// <summary>
/// Organization account
/// </summary>
[Parameter(Value = "organization")]
Organization,
/// <summary>
/// Bot account
/// </summary>
[Parameter(Value = "bot")]
Bot
}
}

View File

@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Globalization;
using Octokit.Internal;
namespace Octokit
{
@@ -24,7 +25,7 @@ namespace Octokit
/// <summary>
/// The encoding of the blob.
/// </summary>
public EncodingType Encoding { get; protected set; }
public StringEnum<EncodingType> Encoding { get; protected set; }
/// <summary>
/// The SHA of the blob.
@@ -47,7 +48,10 @@ namespace Octokit
public enum EncodingType
{
[Parameter(Value = "utf-8")]
Utf8,
[Parameter(Value = "base64")]
Base64
}
}

View File

@@ -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
/// <summary>
/// Who required status checks apply to
/// </summary>
public EnforcementLevel EnforcementLevel { get; protected set; }
public StringEnum<EnforcementLevel> EnforcementLevel { get; protected set; }
/// <summary>
/// The list of status checks to require in order to merge into this <see cref="Branch"/>
@@ -84,16 +85,19 @@ namespace Octokit
/// <summary>
/// Turn off required status checks for this <see cref="Branch"/>.
/// </summary>
[Parameter(Value = "off")]
Off,
/// <summary>
/// Required status checks will be enforced for non-admins.
/// </summary>
[Parameter(Value = "non_admins")]
NonAdmins,
/// <summary>
/// Required status checks will be enforced for everyone (including admins).
/// </summary>
[Parameter(Value = "everyone")]
Everyone
}

View File

@@ -21,7 +21,7 @@ namespace Octokit
/// <summary>
/// The combined state of the commits.
/// </summary>
public CommitState State { get; protected set; }
public StringEnum<CommitState> State { get; protected set; }
/// <summary>
/// The SHA of the reference.

View File

@@ -49,7 +49,7 @@ namespace Octokit
/// The type of this content. It might be a File, Directory, Submodule, or Symlink
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "Matches the property name used by the API")]
public ContentType Type { get; protected set; }
public StringEnum<ContentType> Type { get; protected set; }
/// <summary>
/// URL to the raw content

View File

@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Globalization;
using Octokit.Internal;
namespace Octokit
{
@@ -35,7 +36,7 @@ namespace Octokit
/// <summary>
/// The state of the commit
/// </summary>
public CommitState State { get; protected set; }
public StringEnum<CommitState> State { get; protected set; }
/// <summary>
/// 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
/// <summary>
/// The commit state is still being determined. A build server might set this when it starts a build.
/// </summary>
[Parameter(Value = "pending")]
Pending,
/// <summary>
/// The build was successful for the commit.
/// </summary>
[Parameter(Value = "success")]
Success,
/// <summary>
/// There was some error with the build.
/// </summary>
[Parameter(Value = "error")]
Error,
/// <summary>
/// The build completed and reports a failure.
/// </summary>
[Parameter(Value = "failure")]
Failure
}
}

View File

@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Octokit.Internal;
namespace Octokit
{
@@ -7,10 +8,17 @@ namespace Octokit
/// </summary>
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
}
}

View File

@@ -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
/// <summary>
/// The state of this deployment status.
/// </summary>
public DeploymentState State { get; protected set; }
public StringEnum<DeploymentState> State { get; protected set; }
/// <summary>
/// The <seealso cref="User"/> 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
}
}

View File

@@ -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
/// <summary>
/// Identifies the actual type of Event that occurred.
/// </summary>
public EventInfoState Event { get; protected set; }
public StringEnum<EventInfoState> Event { get; protected set; }
/// <summary>
/// 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.
/// </summary>
[Parameter(Value = "closed")]
Closed,
/// <summary>
/// The issue was reopened by the actor.
/// </summary>
[Parameter(Value = "reopened")]
Reopened,
/// <summary>
/// The actor subscribed to receive notifications for an issue.
/// </summary>
[Parameter(Value = "subscribed")]
Subscribed,
/// <summary>
/// The issue was merged by the actor. The commit_id attribute is the SHA1 of the HEAD commit that was merged.
/// </summary>
[Parameter(Value = "merged")]
Merged,
/// <summary>
/// The issue was referenced from a commit message. The commit_id attribute is the commit SHA1 of where
/// that happened.
/// </summary>
[Parameter(Value = "referenced")]
Referenced,
/// <summary>
/// The actor was @mentioned in an issue body.
/// </summary>
[Parameter(Value = "mentioned")]
Mentioned,
/// <summary>
/// The issue was assigned to the actor.
/// </summary>
[Parameter(Value = "assigned")]
Assigned,
/// <summary>
/// The issue was unassigned to the actor.
/// </summary>
[Parameter(Value = "unassigned")]
Unassigned,
/// <summary>
/// A label was added to the issue.
/// </summary>
[Parameter(Value = "labeled")]
Labeled,
/// <summary>
/// A label was removed from the issue.
/// </summary>
[Parameter(Value = "unlabeled")]
Unlabeled,
/// <summary>
/// The issue was added to a milestone.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Milestoned")]
[Parameter(Value = "milestoned")]
Milestoned,
/// <summary>
/// The issue was removed from a milestone.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Demilestoned")]
[Parameter(Value = "demilestoned")]
Demilestoned,
/// <summary>
/// The issue title was changed.
/// </summary>
[Parameter(Value = "renamed")]
Renamed,
/// <summary>
/// The issue was locked by the actor.
/// </summary>
[Parameter(Value = "locked")]
Locked,
/// <summary>
/// The issue was unlocked by the actor.
/// </summary>
[Parameter(Value = "unlocked")]
Unlocked,
/// <summary>
/// The pull requests branch was deleted.
/// </summary>
[Parameter(Value = "head_ref_deleted")]
HeadRefDeleted,
/// <summary>
/// The pull requests branch was restored.
/// </summary>
[Parameter(Value = "head_ref_restored")]
HeadRefRestored,
/// <summary>
/// The actor dismissed a review from the pull request.
/// </summary>
[Parameter(Value = "review_dismissed")]
ReviewDismissed,
/// <summary>
/// The actor requested review from the subject on this pull request.
/// </summary>
[Parameter(Value = "review_requested")]
ReviewRequested,
/// <summary>
/// The actor removed the review request for the subject on this pull request.
/// </summary>
[Parameter(Value = "review_request_removed")]
ReviewRequestRemoved,
/// <summary>
/// The issue was added to a project board.
/// </summary>
[Parameter(Value = "added_to_project")]
AddedToProject,
/// <summary>
/// The issue was moved between columns in a project board.
/// </summary>
[Parameter(Value = "moved_columns_in_project")]
MovedColumnsInProject,
/// <summary>
/// The issue was removed from a project board.
/// </summary>
[Parameter(Value = "removed_from_project")]
RemovedFromProject,
/// <summary>
/// The issue was created by converting a note in a project board to an issue.
/// </summary>
[Parameter(Value = "converted_note_to_issue")]
ConvertedNoteToIssue,
/// <summary>
/// The actor unsubscribed from notifications for an issue.
/// </summary>
[Parameter(Value = "unsubscribed")]
Unsubscribed,
/// <summary>
/// A comment was added to the issue.
/// </summary>
[Parameter(Value = "commented")]
Commented,
/// <summary>
/// A commit was added to the pull request's HEAD branch.
/// Only provided for pull requests.
/// </summary>
[Parameter(Value = "committed")]
Committed,
/// <summary>
/// Base branch of the pull request was changed.
/// </summary>
[Parameter(Value = "base_ref_changed")]
BaseRefChanged,
/// <summary>
@@ -224,21 +253,25 @@ namespace Octokit
/// url of the reference's source.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Crossreferenced")]
[Parameter(Value = "cross-referenced")]
Crossreferenced,
/// <summary>
/// The issue was reveiewed.
/// </summary>
[Parameter(Value = "reviewed")]
Reviewed,
/// <summary>
/// A line comment was made.
/// </summary>
[Parameter(Value = "line-commented")]
LineCommented,
/// <summary>
/// A commit comment was made.
/// </summary>
[Parameter(Value = "commit-commented")]
CommitCommented
}
}

View File

@@ -70,7 +70,7 @@ namespace Octokit
/// <summary>
/// Whether the issue is open or closed.
/// </summary>
public ItemState State { get; protected set; }
public StringEnum<ItemState> State { get; protected set; }
/// <summary>
/// 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
};

View File

@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Globalization;
using Octokit.Internal;
namespace Octokit
{
@@ -68,11 +69,13 @@ namespace Octokit
/// <summary>
/// Sort by create date (default)
/// </summary>
[Parameter(Value = "created")]
Created,
/// <summary>
/// Sort by the date of the last update
/// </summary>
[Parameter(Value = "updated")]
Updated
}
}

View File

@@ -51,7 +51,7 @@ namespace Octokit
/// <summary>
/// Identifies the actual type of Event that occurred.
/// </summary>
public EventInfoState Event { get; protected set; }
public StringEnum<EventInfoState> Event { get; protected set; }
/// <summary>
/// The String SHA of a commit that referenced this Issue.

View File

@@ -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
/// <summary>
/// The state of migration. Can be one of pending, exporting, exported and failed.
/// </summary>
public MigrationState State { get; private set; }
public StringEnum<MigrationState> State { get; private set; }
/// <summary>
/// Whether to lock repositories.
@@ -106,21 +107,25 @@ namespace Octokit
/// <summary>
/// The migration hasn't started yet.
/// </summary>
[Parameter(Value = "pending")]
Pending,
/// <summary>
/// The migration is in progress.
/// </summary>
[Parameter(Value = "exporting")]
Exporting,
/// <summary>
/// The migration finished successfully.
/// </summary>
[Parameter(Value = "exported")]
Exported,
/// <summary>
/// The migration failed.
/// </summary>
[Parameter(Value = "failed")]
Failed
}
}

View File

@@ -48,7 +48,7 @@ namespace Octokit
/// <summary>
/// Whether the milestone is open or closed.
/// </summary>
public ItemState State { get; protected set; }
public StringEnum<ItemState> State { get; protected set; }
/// <summary>
/// Title of the milestone

View File

@@ -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
/// <summary>
/// The site has yet to be built
/// </summary>
[Parameter(Value = "null")]
Null,
/// <summary>
/// The build has been requested but not yet begun
/// </summary>
[Parameter(Value = "queued")]
Queued,
/// <summary>
/// The build is in progress
/// </summary>
[Parameter(Value = "building")]
Building,
/// <summary>
/// The site has been built
/// </summary>
[Parameter(Value = "built")]
Built,
/// <summary>
/// An error occurred during the build
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Errored")]
[Parameter(Value = "errored")]
Errored
}
@@ -58,7 +69,7 @@ namespace Octokit
/// <summary>
/// Build status of the pages site.
/// </summary>
public PagesBuildStatus Status { get; protected set; }
public StringEnum<PagesBuildStatus> Status { get; protected set; }
/// <summary>
/// CName of the pages site. Will be null if no CName was provided by the user.

View File

@@ -31,7 +31,7 @@ namespace Octokit
/// <summary>
/// The status of the build.
/// </summary>
public PagesBuildStatus Status { get; protected set; }
public StringEnum<PagesBuildStatus> Status { get; protected set; }
/// <summary>
/// Error details - if there was one.
/// </summary>

View File

@@ -93,7 +93,7 @@ namespace Octokit
/// <summary>
/// Whether the pull request is open or closed. The default is <see cref="ItemState.Open"/>.
/// </summary>
public ItemState State { get; protected set; }
public StringEnum<ItemState> State { get; protected set; }
/// <summary>
/// Title of the pull request.

View File

@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Globalization;
using Octokit.Internal;
namespace Octokit
{
@@ -115,11 +116,13 @@ namespace Octokit
/// <summary>
/// Sort by create date (default)
/// </summary>
[Parameter(Value = "created")]
Created,
/// <summary>
/// Sort by the date of the last update
/// </summary>
[Parameter(Value = "updated")]
Updated
}
}

View File

@@ -29,7 +29,7 @@ namespace Octokit
CommitCount = commitCount;
}
public DayOfWeek DayOfWeek { get; private set; }
public StringEnum<DayOfWeek> DayOfWeek { get; private set; }
public int HourOfTheDay { get; private set; }
public int CommitCount { get; private set; }

View File

@@ -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.
/// </summary>
[Parameter(Key = "content")]
public ReactionType Content { get; protected set; }
public StringEnum<ReactionType> Content { get; protected set; }
internal string DebuggerDisplay
{

View File

@@ -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<InvitationPermissionType> Permissions { get; protected set; }
public DateTimeOffset CreatedAt { get; protected set; }

View File

@@ -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
}
}

View File

@@ -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<TEnum> : IEquatable<StringEnum<TEnum>>
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<TEnum> 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<TEnum> && Equals((StringEnum<TEnum>) obj);
}
public override int GetHashCode()
{
return StringComparer.OrdinalIgnoreCase.GetHashCode(StringValue);
}
public static bool operator ==(StringEnum<TEnum> left, StringEnum<TEnum> right)
{
return left.Equals(right);
}
public static bool operator !=(StringEnum<TEnum> left, StringEnum<TEnum> right)
{
return !left.Equals(right);
}
public static implicit operator StringEnum<TEnum>(string value)
{
return new StringEnum<TEnum>(value);
}
public static implicit operator StringEnum<TEnum>(TEnum parsedValue)
{
return new StringEnum<TEnum>(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));
}
}
}

View File

@@ -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<TaggedType> Type { get; protected set; }
}
/// <summary>
@@ -24,9 +25,16 @@ namespace Octokit
/// </summary>
public enum TaggedType
{
[Parameter(Value = "commit")]
Commit,
[Parameter(Value = "blob")]
Blob,
[Parameter(Value = "tree")]
Tree,
[Parameter(Value = "tag")]
Tag
}
}

View File

@@ -42,7 +42,7 @@ namespace Octokit
/// <summary>
/// permission attached to this team
/// </summary>
public Permission Permission { get; protected set; }
public StringEnum<Permission> Permission { get; protected set; }
/// <summary>
/// how many members in this team

View File

@@ -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<EventInfoState> Event { get; protected set; }
public DateTimeOffset CreatedAt { get; protected set; }
public Label Label { get; protected set; }
public User Assignee { get; protected set; }

View File

@@ -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.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")]
public TreeType Type { get; protected set; }
public StringEnum<TreeType> Type { get; protected set; }
/// <summary>
/// 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
}

View File

@@ -19,7 +19,7 @@ namespace Octokit
/// The reason for verified value.
/// </summary>
[Parameter(Key = "reason")]
public VerificationReason Reason { get; protected set; }
public StringEnum<VerificationReason> Reason { get; protected set; }
/// <summary>
/// The signature that was extracted from the commit.

124
docs/working-with-enums.md Normal file
View File

@@ -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<TEnum>` 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<TEnum>` to represent these values in all response models.
### `StringEnum<TEnum>` 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<EncodingType> encoding = EncodingType.Utf8;
// Implicit conversion from API string
StringEnum<EncodingType> encoding = "utf-8";
```
#### When dealing with a known API value
Accessing the string value (safe):
``` csharp
StringEnum<EncodingType> encoding = "utf-8";
Console.WriteLine(encoding.StringValue);
// "utf-8"
```
Accessing the Enum value (not safe, but works as the value is known):
``` csharp
StringEnum<EncodingType> encoding = "utf-8";
Console.WriteLine(encoding.Value);
// EncodingType.Utf8
```
#### When dealing with an unknown API value
Accessing the string value (safe):
``` csharp
StringEnum<EncodingType> 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<EncodingType> 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<EncodingType> 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<TEnum>` 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);
}
}
```