Skip to content

Commit 14a756e

Browse files
committed
updated tests and readme
1 parent 246bdaa commit 14a756e

File tree

5 files changed

+165
-32
lines changed

5 files changed

+165
-32
lines changed

README.md

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,129 @@
1-
# GoatQuery .NET
1+
# GoatQuery
2+
3+
A .NET library for parsing OData-style query parameters into LINQ expressions. Enables database-level filtering, sorting, and pagination from HTTP query strings.
4+
5+
## Installation
6+
7+
```bash
8+
dotnet add package GoatQuery
9+
dotnet add package GoatQuery.AspNetCore # For ASP.NET Core integration
10+
```
11+
12+
## Quick Start
13+
14+
```csharp
15+
// Basic usage
16+
var users = dbContext.Users
17+
.Apply(new Query { Filter = "age gt 18 and isActive eq true" })
18+
.Value.Results;
19+
20+
// ASP.NET Core
21+
[HttpGet]
22+
[EnableQuery<UserDto>(maxTop: 100)]
23+
public IActionResult GetUsers() => Ok(dbContext.Users);
24+
```
25+
26+
## Supported Syntax
27+
28+
```
29+
GET /api/users?$filter=age gt 18 and isActive eq true
30+
GET /api/users?$orderby=lastName asc, firstName desc
31+
GET /api/users?$top=10&$skip=20&$count=true
32+
GET /api/users?$search=john
33+
```
34+
35+
## Filtering
36+
37+
### Operators
38+
- **Comparison**: `eq`, `ne`, `gt`, `ge`, `lt`, `le`
39+
- **Logical**: `and`, `or`
40+
- **String**: `contains`
41+
42+
### Data Types
43+
- String: `'value'`
44+
- Numbers: `42`, `3.14f`, `2.5m`, `1.0d`
45+
- Boolean: `true`, `false`
46+
- DateTime: `2023-12-25T10:30:00Z`
47+
- GUID: `123e4567-e89b-12d3-a456-426614174000`
48+
- Null: `null`
49+
50+
### Examples
51+
```csharp
52+
"age gt 18"
53+
"firstName eq 'John' and isActive ne false"
54+
"salary ge 50000 or department eq 'Engineering'"
55+
"name contains 'smith'"
56+
```
57+
58+
## Property Mapping
59+
60+
Supports `JsonPropertyName` attributes:
61+
62+
```csharp
63+
public class UserDto
64+
{
65+
[JsonPropertyName("first_name")]
66+
public string FirstName { get; set; }
67+
68+
public int Age { get; set; } // Maps to "age"
69+
}
70+
```
71+
72+
Query: `$filter=first_name eq 'John' and age gt 18`
73+
74+
## Search
75+
76+
Implement custom search logic:
77+
78+
```csharp
79+
public class UserSearchBinder : ISearchBinder<User>
80+
{
81+
public Expression<Func<User, bool>> Bind(string searchTerm) =>
82+
user => user.FirstName.Contains(searchTerm) ||
83+
user.LastName.Contains(searchTerm);
84+
}
85+
86+
var result = users.Apply(query, new UserSearchBinder());
87+
```
88+
89+
## ASP.NET Core Integration
90+
91+
### Action Filter
92+
```csharp
93+
[HttpGet]
94+
[EnableQuery<UserDto>(maxTop: 100)]
95+
public IActionResult GetUsers() => Ok(dbContext.Users);
96+
```
97+
98+
### Manual Processing
99+
```csharp
100+
[HttpGet]
101+
public IActionResult GetUsers([FromQuery] Query query)
102+
{
103+
var result = dbContext.Users.Apply(query);
104+
return result.IsFailed ? BadRequest(result.Errors) : Ok(result.Value);
105+
}
106+
```
107+
108+
## Error Handling
109+
110+
Uses FluentResults pattern:
111+
112+
```csharp
113+
var result = users.Apply(query);
114+
if (result.IsFailed)
115+
return BadRequest(result.Errors.Select(e => e.Message));
116+
117+
var data = result.Value.Results;
118+
var count = result.Value.Count; // If Count = true
119+
```
120+
121+
## Development
122+
123+
```bash
124+
dotnet test ./src/GoatQuery/tests
125+
dotnet build --configuration Release
126+
cd example && dotnet run
127+
```
128+
129+
**Targets**: .NET Standard 2.0/2.1, .NET 6.0+

example/Controllers/UserController.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using AutoMapper;
22
using AutoMapper.QueryableExtensions;
33
using Microsoft.AspNetCore.Mvc;
4+
using Microsoft.EntityFrameworkCore;
45

56
[ApiController]
67
[Route("controller/[controller]")]
@@ -22,6 +23,8 @@ public UsersController(ApplicationDbContext db, IMapper mapper)
2223
public ActionResult<IEnumerable<UserDto>> Get()
2324
{
2425
var users = _db.Users
26+
.Include(x => x.Manager)
27+
.ThenInclude(x => x.Manager)
2528
.Where(x => !x.IsDeleted)
2629
.ProjectTo<UserDto>(_mapper.ConfigurationProvider);
2730

example/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
app.MapGet("/minimal/users", (ApplicationDbContext db, [FromServices] IMapper mapper, [AsParameters] Query query) =>
6666
{
6767
var result = db.Users
68+
.Include(x => x.Manager)
69+
.ThenInclude(x => x.Manager)
6870
.Where(x => !x.IsDeleted)
6971
.ProjectTo<UserDto>(mapper.ConfigurationProvider)
7072
.Apply(query);

src/GoatQuery/tests/Filter/FilterTest.cs

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static IEnumerable<object[]> Parameters()
1616

1717
yield return new object[] {
1818
"Age eq 1",
19-
new[] { TestData.Users["Jane"], TestData.Users["Harry"] }
19+
new[] { TestData.Users["Apple"], TestData.Users["Harry"], TestData.Users["Doe"] }
2020
};
2121

2222
yield return new object[] {
@@ -30,23 +30,23 @@ public static IEnumerable<object[]> Parameters()
3030
};
3131

3232
yield return new object[] {
33-
"firstname eq 'John' or Age eq 3",
34-
new[] { TestData.Users["John"], TestData.Users["Doe"], TestData.Users["Egg"] }
33+
"firstname eq 'John' or Age eq 33",
34+
new[] { TestData.Users["John"], TestData.Users["Egg"] }
3535
};
3636

3737
yield return new object[] {
3838
"Age eq 1 and firstName eq 'Harry' or Age eq 2",
39-
new[] { TestData.Users["John"], TestData.Users["Apple"], TestData.Users["Harry"] }
39+
new[] { TestData.Users["John"], TestData.Users["Harry"] }
4040
};
4141

4242
yield return new object[] {
4343
"Age eq 1 or Age eq 2 or firstName eq 'Egg'",
44-
new[] { TestData.Users["John"], TestData.Users["Jane"], TestData.Users["Apple"], TestData.Users["Harry"], TestData.Users["Egg"] }
44+
new[] { TestData.Users["John"], TestData.Users["Apple"], TestData.Users["Harry"], TestData.Users["Doe"], TestData.Users["Egg"] }
4545
};
4646

4747
yield return new object[] {
48-
"Age ne 3",
49-
new[] { TestData.Users["John"], TestData.Users["Jane"], TestData.Users["Apple"], TestData.Users["Harry"], TestData.Users["NullUser"] }
48+
"Age ne 33",
49+
new[] { TestData.Users["John"], TestData.Users["Jane"], TestData.Users["Apple"], TestData.Users["Harry"], TestData.Users["Doe"], TestData.Users["NullUser"] }
5050
};
5151

5252
yield return new object[] {
@@ -56,36 +56,36 @@ public static IEnumerable<object[]> Parameters()
5656

5757
yield return new object[] {
5858
"Age ne 1 and firstName contains 'a'",
59-
new[] { TestData.Users["Apple"] }
59+
new[] { TestData.Users["Jane"] }
6060
};
6161

6262
yield return new object[] {
6363
"Age ne 1 and firstName contains 'a' or firstName eq 'Apple'",
64-
new[] { TestData.Users["Apple"] }
64+
new[] { TestData.Users["Jane"], TestData.Users["Apple"] }
6565
};
6666

6767
yield return new object[] {
68-
"Firstname eq 'John' and Age eq 2 or Age eq 3",
69-
new[] { TestData.Users["John"], TestData.Users["Doe"], TestData.Users["Egg"] }
68+
"Firstname eq 'John' and Age eq 2 or Age eq 33",
69+
new[] { TestData.Users["John"], TestData.Users["Egg"] }
7070
};
7171

7272
yield return new object[] {
73-
"(Firstname eq 'John' and Age eq 2) or Age eq 3",
74-
new[] { TestData.Users["John"], TestData.Users["Doe"], TestData.Users["Egg"] }
73+
"(Firstname eq 'John' and Age eq 2) or Age eq 33",
74+
new[] { TestData.Users["John"], TestData.Users["Egg"] }
7575
};
7676

7777
yield return new object[] {
78-
"Firstname eq 'John' and (Age eq 2 or Age eq 3)",
78+
"Firstname eq 'John' and (Age eq 2 or Age eq 33)",
7979
new[] { TestData.Users["John"] }
8080
};
8181

8282
yield return new object[] {
83-
"(Firstname eq 'John' and Age eq 2 or Age eq 3)",
84-
new[] { TestData.Users["John"], TestData.Users["Doe"], TestData.Users["Egg"] }
83+
"(Firstname eq 'John' and Age eq 2 or Age eq 33)",
84+
new[] { TestData.Users["John"], TestData.Users["Egg"] }
8585
};
8686

8787
yield return new object[] {
88-
"(Firstname eq 'John') or (Age eq 3 and Firstname eq 'Egg') or Age eq 1 and (Age eq 2)",
88+
"(Firstname eq 'John') or (Age eq 33 and Firstname eq 'Egg') or Age eq 1 and (Age eq 2)",
8989
new[] { TestData.Users["John"], TestData.Users["Egg"] }
9090
};
9191

@@ -96,7 +96,7 @@ public static IEnumerable<object[]> Parameters()
9696

9797
yield return new object[] {
9898
"age lt 3",
99-
new[] { TestData.Users["John"], TestData.Users["Jane"], TestData.Users["Apple"], TestData.Users["Harry"] }
99+
new[] { TestData.Users["John"], TestData.Users["Apple"], TestData.Users["Harry"], TestData.Users["Doe"] }
100100
};
101101

102102
yield return new object[] {
@@ -106,22 +106,22 @@ public static IEnumerable<object[]> Parameters()
106106

107107
yield return new object[] {
108108
"age lte 2",
109-
new[] { TestData.Users["John"], TestData.Users["Jane"], TestData.Users["Apple"], TestData.Users["Harry"] }
109+
new[] { TestData.Users["John"], TestData.Users["Apple"], TestData.Users["Harry"], TestData.Users["Doe"] }
110110
};
111111

112112
yield return new object[] {
113113
"age gt 1",
114-
new[] { TestData.Users["John"], TestData.Users["Apple"], TestData.Users["Doe"], TestData.Users["Egg"], TestData.Users["NullUser"] }
114+
new[] { TestData.Users["John"], TestData.Users["Jane"], TestData.Users["Egg"], TestData.Users["NullUser"] }
115115
};
116116

117117
yield return new object[] {
118118
"age gte 3",
119-
new[] { TestData.Users["Doe"], TestData.Users["Egg"], TestData.Users["NullUser"] }
119+
new[] { TestData.Users["Jane"], TestData.Users["Egg"], TestData.Users["NullUser"] }
120120
};
121121

122122
yield return new object[] {
123123
"age lt 3 and age gt 1",
124-
new[] { TestData.Users["John"], TestData.Users["Apple"] }
124+
new[] { TestData.Users["John"] }
125125
};
126126

127127
yield return new object[] {
@@ -241,7 +241,7 @@ public static IEnumerable<object[]> Parameters()
241241

242242
yield return new object[] {
243243
"balanceDecimal eq null and age gt 3",
244-
new[] { TestData.Users["NullUser"] }
244+
new[] { TestData.Users["Egg"], TestData.Users["NullUser"] }
245245
};
246246

247247
yield return new object[] {
@@ -276,12 +276,12 @@ public static IEnumerable<object[]> Parameters()
276276

277277
yield return new object[] {
278278
"age gt 2 and isEmailVerified eq true",
279-
new[] { TestData.Users["Doe"], TestData.Users["NullUser"] }
279+
new[] { TestData.Users["NullUser"] }
280280
};
281281

282282
yield return new object[] {
283283
"isEmailVerified eq false or age eq 2",
284-
new[] { TestData.Users["John"], TestData.Users["Jane"], TestData.Users["Apple"], TestData.Users["Harry"], TestData.Users["Egg"] }
284+
new[] { TestData.Users["John"], TestData.Users["Jane"], TestData.Users["Harry"], TestData.Users["Egg"] }
285285
};
286286

287287
yield return new object[] {
@@ -355,8 +355,8 @@ public static IEnumerable<object[]> Parameters()
355355
};
356356

357357
yield return new object[] {
358-
"(age eq 2 and manager/isEmailVerified eq true) or (age eq 3 and manager/manager ne null)",
359-
new[] { TestData.Users["Apple"], TestData.Users["Egg"] }
358+
"(age eq 2 and manager/isEmailVerified eq true) or (age eq 33 and manager/manager ne null)",
359+
new[] { TestData.Users["Egg"] }
360360
};
361361

362362
yield return new object[] {

src/GoatQuery/tests/TestData.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static class TestData
2222
},
2323
["Jane"] = new User
2424
{
25-
Age = 1,
25+
Age = 9,
2626
Firstname = "Jane",
2727
UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"),
2828
DateOfBirth = DateTime.Parse("2020-05-09 15:30:00"),
@@ -31,7 +31,7 @@ public static class TestData
3131
},
3232
["Apple"] = new User
3333
{
34-
Age = 2,
34+
Age = 1,
3535
Firstname = "Apple",
3636
UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"),
3737
DateOfBirth = DateTime.Parse("1980-12-31 00:00:01"),
@@ -58,7 +58,7 @@ public static class TestData
5858
},
5959
["Doe"] = new User
6060
{
61-
Age = 3,
61+
Age = 1,
6262
Firstname = "Doe",
6363
UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"),
6464
DateOfBirth = DateTime.Parse("2023-07-26 12:00:30"),
@@ -67,7 +67,7 @@ public static class TestData
6767
},
6868
["Egg"] = new User
6969
{
70-
Age = 3,
70+
Age = 33,
7171
Firstname = "Egg",
7272
UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"),
7373
DateOfBirth = DateTime.Parse("2000-01-01 00:00:00"),

0 commit comments

Comments
 (0)