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

Support filtering on Nullable<DateTime> #82

Merged
merged 2 commits into from
Feb 27, 2024
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
45 changes: 44 additions & 1 deletion src/BccCode.Linq/Server/OperandToExpressionResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,42 @@
if (valueType == type)
return value;

Type? nullableType = Nullable.GetUnderlyingType(type);
if (nullableType != null)
{
if (value is string strValue && strValue == "null")
return null;

var newValue = ConvertValue(nullableType, value);

return nullableType switch
{
Type when nullableType == typeof(bool) => (bool?)newValue,
Type when nullableType == typeof(sbyte) => (sbyte?)newValue,
Type when nullableType == typeof(byte) => (byte?)newValue,
Type when nullableType == typeof(char) => (char?)newValue,
Type when nullableType == typeof(short) => (short?)newValue,
Type when nullableType == typeof(ushort) => (ushort?)newValue,
Type when nullableType == typeof(int) => (int?)newValue,
Type when nullableType == typeof(uint) => (uint?)newValue,
Type when nullableType == typeof(long) => (long?)newValue,
Type when nullableType == typeof(ulong) => (ulong?)newValue,
Type when nullableType == typeof(nint) => (nint?)newValue,
Type when nullableType == typeof(nuint) => (nuint?)newValue,
Type when nullableType == typeof(float) => (float?)newValue,
Type when nullableType == typeof(double) => (double?)newValue,
Type when nullableType == typeof(decimal) => (decimal?)newValue,
Type when nullableType == typeof(Guid) => (Guid?)newValue,
Type when nullableType == typeof(DateTime) => (DateTime?)newValue,
Type when nullableType == typeof(TimeSpan) => (TimeSpan?)newValue,
#if NET6_0_OR_GREATER
Type when nullableType == typeof(DateOnly) => (DateOnly?)newValue,
Type when nullableType == typeof(TimeOnly) => (TimeOnly?)newValue,
#endif
_ => newValue,
};
}

// converts value to an array
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IList<>))
{
Expand Down Expand Up @@ -123,19 +159,26 @@
{
Type when type == typeof(bool) && value is string strVal => strVal == "1",
Type when type == typeof(int) && value is string strVal => (int)double.Parse(strVal, CultureInfo.InvariantCulture),
Type when type == typeof(float) && value is string strVal => float.Parse(strVal, CultureInfo.InvariantCulture),
Type when type == typeof(double) && value is string strVal => double.Parse(strVal, CultureInfo.InvariantCulture),
Type when type == typeof(decimal) && value is string strVal => decimal.Parse(strVal, CultureInfo.InvariantCulture),
Type when type == typeof(Guid) && value is string strVal && Guid.TryParse(strVal, out var uuid) => uuid,
Type when type == typeof(DateTime) && value is string strVal && DateTime.TryParse(strVal, out var dateTime) => dateTime,
Type when type == typeof(TimeSpan) && value is string strVal && TimeSpan.TryParse(strVal, out var dateTime) => dateTime,
#if NET6_0_OR_GREATER
Type when type == typeof(DateOnly) && value is string strVal && DateOnly.TryParse(strVal, out var dateTime) => dateTime,
Type when type == typeof(TimeOnly) && value is string strVal && TimeOnly.TryParse(strVal, out var dateTime) => dateTime,
#endif
Type when type == typeof(int) && value is ValueTuple<string, string> tuple => new ValueTuple<int, int>(
(int)ConvertValue(type, tuple.Item1), (int)ConvertValue(type, tuple.Item2)),

Check warning on line 173 in src/BccCode.Linq/Server/OperandToExpressionResolver.cs

View workflow job for this annotation

GitHub Actions / publish

Unboxing a possibly null value.

Check warning on line 173 in src/BccCode.Linq/Server/OperandToExpressionResolver.cs

View workflow job for this annotation

GitHub Actions / publish

Unboxing a possibly null value.

Check warning on line 173 in src/BccCode.Linq/Server/OperandToExpressionResolver.cs

View workflow job for this annotation

GitHub Actions / publish

Unboxing a possibly null value.

Check warning on line 173 in src/BccCode.Linq/Server/OperandToExpressionResolver.cs

View workflow job for this annotation

GitHub Actions / publish

Unboxing a possibly null value.
Type when type == typeof(double) && value is ValueTuple<string, string> tuple => new ValueTuple<double, double>(
(double)ConvertValue(type, tuple.Item1), (double)ConvertValue(type, tuple.Item2)),

Check warning on line 175 in src/BccCode.Linq/Server/OperandToExpressionResolver.cs

View workflow job for this annotation

GitHub Actions / publish

Unboxing a possibly null value.

Check warning on line 175 in src/BccCode.Linq/Server/OperandToExpressionResolver.cs

View workflow job for this annotation

GitHub Actions / publish

Unboxing a possibly null value.

Check warning on line 175 in src/BccCode.Linq/Server/OperandToExpressionResolver.cs

View workflow job for this annotation

GitHub Actions / publish

Unboxing a possibly null value.

Check warning on line 175 in src/BccCode.Linq/Server/OperandToExpressionResolver.cs

View workflow job for this annotation

GitHub Actions / publish

Unboxing a possibly null value.
Type when type == typeof(DateTime) && value is ValueTuple<string, string> tuple => new ValueTuple<DateTime, DateTime>(
(DateTime)ConvertValue(type, tuple.Item1), (DateTime)ConvertValue(type, tuple.Item2)),

Check warning on line 177 in src/BccCode.Linq/Server/OperandToExpressionResolver.cs

View workflow job for this annotation

GitHub Actions / publish

Unboxing a possibly null value.

Check warning on line 177 in src/BccCode.Linq/Server/OperandToExpressionResolver.cs

View workflow job for this annotation

GitHub Actions / publish

Unboxing a possibly null value.
Type when type == typeof(int) && value is not int => int.Parse(value.ToString()),
_ => value
};


value = Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
}
catch (Exception ex)
Expand Down
35 changes: 35 additions & 0 deletions tests/BccCode.Linq.Tests/FilterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,41 @@ public void should_not_cast_value_to_date()
var value = ((Filter<DateTime>)filter.Properties["AnyDate"]).Properties["_eq"];
});
}

[Fact]
public void should_cast_value_to_nullable_date()
{
var json = @"{ ""DateNullable"": { ""_eq"": ""2009-06-15T13:45:30"" } }";
var filter = new Filter<TestClass>(json);

var value = ((Filter<DateTime?>)filter.Properties["DateNullable"]).Properties["_eq"];

Assert.IsType<DateTime>(value);
}

#if NET6_0_OR_GREATER
[Fact]
public void should_cast_value_to_date_only()
{
var json = @"{ ""DateOnly"": { ""_eq"": ""2009-06-15"" } }";
var filter = new Filter<TestClass>(json);

var value = ((Filter<DateOnly>)filter.Properties["DateOnly"]).Properties["_eq"];

Assert.IsType<DateOnly>(value);
}

[Fact]
public void should_cast_value_to_time_only()
{
var json = @"{ ""TimeOnly"": { ""_eq"": ""13:45:30"" } }";
var filter = new Filter<TestClass>(json);

var value = ((Filter<TimeOnly>)filter.Properties["TimeOnly"]).Properties["_eq"];

Assert.IsType<TimeOnly>(value);
}
#endif

[Fact]
public void should_filter_on_nested_class()
Expand Down
1 change: 1 addition & 0 deletions tests/BccCode.Linq.Tests/Helpers/TestClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class TestClass
public Guid? UuidNullable { get; set; }
#if NET6_0_OR_GREATER
public DateOnly DateOnly { get; set; }
public TimeOnly TimeOnly { get; set; }
#endif

[DataMember(Name = "custom_name")]
Expand Down
28 changes: 24 additions & 4 deletions tests/BccCode.Linq.Tests/LinqTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -515,11 +515,11 @@ public void SingleTest()
}

[Fact]
public void SingleAsyncTest()
public async void SingleAsyncTest()
{
var api = new ApiClientMockup();

Assert.ThrowsAsync<InvalidOperationException>(async () =>
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
{
// ReSharper disable once UnusedVariable
var persons = await api.Persons.SingleAsync();
Expand Down Expand Up @@ -567,11 +567,11 @@ public void SingleOrDefaultEmptyTest()
}

[Fact]
public void SingleOrDefaultAsyncTest()
public async void SingleOrDefaultAsyncTest()
{
var api = new ApiClientMockup();

Assert.ThrowsAsync<InvalidOperationException>(async () =>
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
{
// ReSharper disable once UnusedVariable
var persons = await api.Persons.SingleOrDefaultAsync();
Expand Down Expand Up @@ -1363,6 +1363,26 @@ where p.DateNullable.ToString() == "2023-12-04T04:02:05Z"
Assert.Null(api.ClientQuery?.Limit);
Assert.Empty(persons);
}

[Fact]
public void WhereDateTimeNullableToStringGreaterThanToDateTimeTest()
{
var api = new ApiClientMockup();

var query =
from p in api.Empty
where p.DateNullable >= DateTime.Parse("2023-12-04T04:02:05Z").ToUniversalTime()
select p;

var persons = query.ToList();
Assert.Equal("empty", api.PageEndpoint);
Assert.Equal("{\"dateNullable\": {\"_gte\": \"2023-12-04T04:02:05.0000000Z\"}}", api.ClientQuery?.Filter);
Assert.Equal("*", api.ClientQuery?.Fields);
Assert.Null(api.ClientQuery?.Sort);
Assert.Null(api.ClientQuery?.Offset);
Assert.Null(api.ClientQuery?.Limit);
Assert.Empty(persons);
}

#if NET6_0_OR_GREATER
[Fact]
Expand Down
Loading