Skip to content

Commit b34bd3f

Browse files
committed
DateTime doesn't have an IS method.... so I don't know how we'll use it....
1 parent a3d9e6c commit b34bd3f

File tree

3 files changed

+62
-8
lines changed

3 files changed

+62
-8
lines changed

src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,24 +113,44 @@ public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> sourc
113113

114114
var concreteType = typeof(TSource);
115115
var property = concreteType.GetProperty(filterQuery.FilteredAttribute.InternalAttributeName);
116+
var op = filterQuery.FilterOperation;
116117

117118
if (property == null)
118119
throw new ArgumentException($"'{filterQuery.FilteredAttribute.InternalAttributeName}' is not a valid property of '{concreteType}'");
119120

120121
try
121122
{
122-
if (filterQuery.FilterOperation == FilterOperations.@in || filterQuery.FilterOperation == FilterOperations.nin)
123+
if (op == FilterOperations.@in || op == FilterOperations.nin)
123124
{
124125
string[] propertyValues = filterQuery.PropertyValue.Split(',');
125-
var lambdaIn = ArrayContainsPredicate<TSource>(propertyValues, property.Name, filterQuery.FilterOperation);
126+
var lambdaIn = ArrayContainsPredicate<TSource>(propertyValues, property.Name, op);
126127

127128
return source.Where(lambdaIn);
128129
}
130+
else if (op == FilterOperations.@is || op == FilterOperations.isnot) {
131+
var parameter = Expression.Parameter(concreteType, "model");
132+
// {model.Id}
133+
var left = Expression.PropertyOrField(parameter, property.Name);
134+
var right = Expression.Constant(filterQuery.PropertyValue, typeof(string));
135+
136+
var body = GetFilterExpressionLambda(left, right, op);
137+
var lambda = Expression.Lambda<Func<TSource, bool>>(body, parameter);
138+
139+
return source.Where(lambda);
140+
}
129141
else
130142
{
131143
var isNullabe = IsNullable(property.PropertyType);
132144
var propertyValue = filterQuery.PropertyValue;
133-
var value = isNullabe && propertyValue == "" ? null : propertyValue;
145+
var value = propertyValue;
146+
147+
if (op == FilterOperations.@isnot || op == FilterOperations.isnot)
148+
{
149+
if (isNullabe && propertyValue == "null")
150+
{
151+
value = null;
152+
}
153+
}
134154

135155
// convert the incoming value to the target value type
136156
// "1" -> 1
@@ -142,7 +162,7 @@ public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> sourc
142162
// {1}
143163
var right = Expression.Constant(convertedValue, property.PropertyType);
144164

145-
var body = GetFilterExpressionLambda(left, right, filterQuery.FilterOperation);
165+
var body = GetFilterExpressionLambda(left, right, op);
146166

147167
var lambda = Expression.Lambda<Func<TSource, bool>>(body, parameter);
148168

@@ -244,6 +264,14 @@ private static Expression GetFilterExpressionLambda(Expression left, Expression
244264
case FilterOperations.ne:
245265
body = Expression.NotEqual(left, right);
246266
break;
267+
case FilterOperations.isnot:
268+
// {model.Id != null}
269+
body = Expression.NotEqual(left, right);
270+
break;
271+
case FilterOperations.@is:
272+
// {model.Id == null}
273+
body = Expression.Equal(left, right);
274+
break;
247275
default:
248276
throw new JsonApiException(500, $"Unknown filter operation {operation}");
249277
}

src/JsonApiDotNetCore/Internal/Query/FilterOperations.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public enum FilterOperations
1111
like = 5,
1212
ne = 6,
1313
@in = 7, // prefix with @ to use keyword
14-
nin = 8
14+
nin = 8,
15+
@is = 9,
16+
isnot = 10
1517
}
1618
}

test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Linq;
34
using System.Net;
@@ -91,7 +92,30 @@ public async Task Can_Filter_TodoItems()
9192
}
9293

9394
[Fact]
94-
public async Task Can_Filter_TodoItems_Using_NotEqual_Operator()
95+
public async Task Can_Filter_TodoItems_Using_IsNot_Operator()
96+
{
97+
// Arrange
98+
var todoItem = _todoItemFaker.Generate();
99+
todoItem.UpdatedDate = new DateTime();
100+
_context.TodoItems.Add(todoItem);
101+
_context.SaveChanges();
102+
103+
var httpMethod = new HttpMethod("GET");
104+
var route = $"/api/v1/todo-items?filter[updated-date]=isnot:null";
105+
var request = new HttpRequestMessage(httpMethod, route);
106+
107+
// Act
108+
var response = await _fixture.Client.SendAsync(request);
109+
var body = await response.Content.ReadAsStringAsync();
110+
var deserializedBody = _fixture.GetService<IJsonApiDeSerializer>().DeserializeList<TodoItem>(body);
111+
112+
// Assert
113+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
114+
Assert.NotEmpty(deserializedBody);
115+
}
116+
117+
[Fact]
118+
public async Task Can_Filter_TodoItems_Using_Is_Operator()
95119
{
96120
// Arrange
97121
var todoItem = _todoItemFaker.Generate();
@@ -100,7 +124,7 @@ public async Task Can_Filter_TodoItems_Using_NotEqual_Operator()
100124
_context.SaveChanges();
101125

102126
var httpMethod = new HttpMethod("GET");
103-
var route = $"/api/v1/todo-items?filter[updated-date]=ne:";
127+
var route = $"/api/v1/todo-items?filter[updated-date]=is:null";
104128
var request = new HttpRequestMessage(httpMethod, route);
105129

106130
// Act
@@ -110,7 +134,7 @@ public async Task Can_Filter_TodoItems_Using_NotEqual_Operator()
110134

111135
// Assert
112136
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
113-
Assert.Empty(deserializedBody);
137+
Assert.NotEmpty(deserializedBody);
114138
}
115139

116140
[Fact]

0 commit comments

Comments
 (0)