Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Deserializer should handle nullable StringEnum<T> #1760

Merged
merged 12 commits into from
Feb 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions Octokit.Tests/Models/StringEnumTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ public class StringEnumTests
{
public class TheCtor
{
[Fact]
public void ShouldThrowForNullOrEmptyStringValues()
{
Assert.Throws<ArgumentNullException>(() => new StringEnum<AccountType>(null));

Assert.Throws<ArgumentException>(() => new StringEnum<AccountType>(""));
}

[Fact]
public void ShouldSetValue()
{
Expand Down Expand Up @@ -42,16 +50,12 @@ public void ShouldThrowForInvalidEnumValue()
}
}

public class TheParsedValueProperty
public class TheValueProperty
{
[Theory]
[InlineData("")]
[InlineData(null)]
[InlineData("Cow")]
public void ShouldThrowForInvalidValue(string value)
[Fact]
public void ShouldThrowForInvalidValue()
{
var stringEnum = new StringEnum<AccountType>(value);

var stringEnum = new StringEnum<AccountType>("Cow");
Assert.Throws<ArgumentException>(() => stringEnum.Value);
}

Expand Down Expand Up @@ -96,13 +100,10 @@ public void ShouldReturnTrueForValidValue()
Assert.True(result);
}

[Theory]
[InlineData("")]
[InlineData(null)]
[InlineData("Cow")]
public void ShouldReturnFalseForInvalidValue(string value)
[Fact]
public void ShouldReturnFalseForInvalidValue()
{
var stringEnum = new StringEnum<AccountType>(value);
var stringEnum = new StringEnum<AccountType>("Cow");

AccountType type;
var result = stringEnum.TryParse(out type);
Expand Down
102 changes: 99 additions & 3 deletions Octokit.Tests/SimpleJsonSerializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,30 @@ public void HandlesEnum()
var item = new ObjectWithEnumProperty
{
Name = "Ferris Bueller",
SomeEnum = SomeEnum.PlusOne
SomeEnum = SomeEnum.Unicode,
SomeEnumNullable = SomeEnum.Unicode,
StringEnum = SomeEnum.SomethingElse,
StringEnumNullable = SomeEnum.SomethingElse
};

var json = new SimpleJsonSerializer().Serialize(item);

Assert.Equal("{\"name\":\"Ferris Bueller\",\"some_enum\":\"+1\"}", json);
Assert.Equal("{\"name\":\"Ferris Bueller\",\"some_enum\":\"unicode\",\"some_enum_nullable\":\"unicode\",\"string_enum\":\"something else\",\"string_enum_nullable\":\"something else\"}", json);
}

[Fact]
public void HandlesEnumDefaults()
{
var item = new ObjectWithEnumProperty
{
Name = "Ferris Bueller",
SomeEnum = SomeEnum.PlusOne,
StringEnum = SomeEnum.PlusOne
};

var json = new SimpleJsonSerializer().Serialize(item);

Assert.Equal("{\"name\":\"Ferris Bueller\",\"some_enum\":\"+1\",\"string_enum\":\"+1\"}", json);
}
}

Expand Down Expand Up @@ -301,7 +319,7 @@ public void DefaultsMissingParameters()
}

[Fact]
public void DeserializesEnumWithParameterAttribute()
public void DeserializesEnums()
{
const string json1 = @"{""some_enum"":""+1""}";
const string json2 = @"{""some_enum"":""utf-8""}";
Expand All @@ -322,6 +340,78 @@ public void DeserializesEnumWithParameterAttribute()
Assert.Equal(SomeEnum.Unicode, sample5.SomeEnum);
}

[Fact]
public void DeserializesNullableEnums()
{
const string json1 = @"{""some_enum_nullable"":""+1""}";
const string json2 = @"{""some_enum_nullable"":""utf-8""}";
const string json3 = @"{""some_enum_nullable"":""something else""}";
const string json4 = @"{""some_enum_nullable"":""another_example""}";
const string json5 = @"{""some_enum_nullable"":""unicode""}";
const string json6 = @"{""some_enum_nullable"":null}";

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);
var sample6 = new SimpleJsonSerializer().Deserialize<ObjectWithEnumProperty>(json6);

Assert.Equal(SomeEnum.PlusOne, sample1.SomeEnumNullable);
Assert.Equal(SomeEnum.Utf8, sample2.SomeEnumNullable);
Assert.Equal(SomeEnum.SomethingElse, sample3.SomeEnumNullable);
Assert.Equal(SomeEnum.AnotherExample, sample4.SomeEnumNullable);
Assert.Equal(SomeEnum.Unicode, sample5.SomeEnumNullable);
Assert.False(sample6.SomeEnumNullable.HasValue);
}

[Fact]
public void DeserializesStringEnums()
{
const string json1 = @"{""string_enum"":""+1""}";
const string json2 = @"{""string_enum"":""utf-8""}";
const string json3 = @"{""string_enum"":""something else""}";
const string json4 = @"{""string_enum"":""another_example""}";
const string json5 = @"{""string_enum"":""unicode""}";

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.PlusOne, sample1.StringEnum);
Assert.Equal(SomeEnum.Utf8, sample2.StringEnum);
Assert.Equal(SomeEnum.SomethingElse, sample3.StringEnum);
Assert.Equal(SomeEnum.AnotherExample, sample4.StringEnum);
Assert.Equal(SomeEnum.Unicode, sample5.StringEnum);
}

[Fact]
public void DeserializesNullableStringEnums()
{
const string json1 = @"{""string_enum_nullable"":""+1""}";
const string json2 = @"{""string_enum_nullable"":""utf-8""}";
const string json3 = @"{""string_enum_nullable"":""something else""}";
const string json4 = @"{""string_enum_nullable"":""another_example""}";
const string json5 = @"{""string_enum_nullable"":""unicode""}";
const string json6 = @"{""string_enum_nullable"":null}";

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);
var sample6 = new SimpleJsonSerializer().Deserialize<ObjectWithEnumProperty>(json6);

Assert.Equal(SomeEnum.PlusOne, sample1.StringEnumNullable);
Assert.Equal(SomeEnum.Utf8, sample2.StringEnumNullable);
Assert.Equal(SomeEnum.SomethingElse, sample3.StringEnumNullable);
Assert.Equal(SomeEnum.AnotherExample, sample4.StringEnumNullable);
Assert.Equal(SomeEnum.Unicode, sample5.StringEnumNullable);
Assert.False(sample6.StringEnumNullable.HasValue);
}

[Fact]
public void ShouldDeserializeMultipleEnumValues()
{
Expand Down Expand Up @@ -369,6 +459,12 @@ public class ObjectWithEnumProperty
public string Name { get; set; }

public SomeEnum SomeEnum { get; set; }

public SomeEnum? SomeEnumNullable { get; set; }

public StringEnum<SomeEnum> StringEnum { get; set; }

public StringEnum<SomeEnum>? StringEnumNullable { get; set; }
}

public enum SomeEnum
Expand Down
39 changes: 22 additions & 17 deletions Octokit/Http/SimpleJsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,21 @@ protected override bool TrySerializeUnknownTypes(object input, out object output
Ensure.ArgumentNotNull(input, "input");

var type = input.GetType();
var jsonObject = new JsonObject();
var getters = GetCache[type];

if (ReflectionUtils.IsStringEnumWrapper(type))
{
// Handle StringEnum<T> by getting the underlying enum value, then using the enum serializer
// Note this will throw if the StringEnum<T> was initialised using a string that is not a valid enum member
var inputEnum = (getters["value"](input) as Enum);
if (inputEnum != null)
{
output = SerializeEnum(inputEnum);
return true;
}
}

var jsonObject = new JsonObject();
foreach (var getter in getters)
{
if (getter.Value != null)
Expand Down Expand Up @@ -144,22 +157,19 @@ public override object DeserializeObject(object value, Type type)

if (stringValue != null)
{
// If it's a nullable type, use the underlying type
if (ReflectionUtils.IsNullableType(type))
{
type = Nullable.GetUnderlyingType(type);
}

var typeInfo = ReflectionUtils.GetTypeInfo(type);

if (typeInfo.IsEnum)
{
return DeserializeEnumHelper(stringValue, type);
}

if (ReflectionUtils.IsNullableType(type))
{
var underlyingType = Nullable.GetUnderlyingType(type);
if (ReflectionUtils.GetTypeInfo(underlyingType).IsEnum)
{
return DeserializeEnumHelper(stringValue, underlyingType);
}
}

if (ReflectionUtils.IsTypeGenericeCollectionInterface(type))
{
// OAuth tokens might be a string of comma-separated values
Expand All @@ -171,14 +181,9 @@ public override object DeserializeObject(object value, Type type)
}
}

if (typeInfo.IsGenericType)
if (ReflectionUtils.IsStringEnumWrapper(type))
{
var typeDefinition = typeInfo.GetGenericTypeDefinition();

if (typeof(StringEnum<>).IsAssignableFrom(typeDefinition))
{
return Activator.CreateInstance(type, stringValue);
}
return Activator.CreateInstance(type, stringValue);
}
}
else if (jsonValue != null)
Expand Down
10 changes: 3 additions & 7 deletions Octokit/Models/Response/StringEnum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ public StringEnum(TEnum parsedValue)

public StringEnum(string stringValue)
{
_stringValue = stringValue ?? string.Empty;
Ensure.ArgumentNotNullOrEmptyString(stringValue, nameof(stringValue));

_stringValue = stringValue;
_parsedValue = null;
}

Expand Down Expand Up @@ -66,12 +68,6 @@ public bool TryParse(out TEnum 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
Expand Down
12 changes: 12 additions & 0 deletions Octokit/SimpleJson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1772,6 +1772,18 @@ public static bool IsAssignableFrom(Type type1, Type type2)
return GetTypeInfo(type1).IsAssignableFrom(GetTypeInfo(type2));
}

public static bool IsStringEnumWrapper(Type type)
{
var typeInfo = ReflectionUtils.GetTypeInfo(type);
if (typeInfo.IsGenericType)
{
var typeDefinition = typeInfo.GetGenericTypeDefinition();

return typeof(StringEnum<>).IsAssignableFrom(typeDefinition);
}
return false;
}

public static IEnumerable<Type> GetInterfaces(Type type)
{
#if SIMPLE_JSON_TYPEINFO
Expand Down