1
+ using System ;
1
2
using System . Collections . Generic ;
2
3
using System . Collections . Immutable ;
3
4
using System . Linq ;
5
+ using System . Net ;
4
6
using JetBrains . Annotations ;
5
7
using JsonApiDotNetCore . Configuration ;
8
+ using JsonApiDotNetCore . Errors ;
6
9
using JsonApiDotNetCore . Queries . Expressions ;
7
10
using JsonApiDotNetCore . Queries . Internal ;
8
11
using JsonApiDotNetCore . Resources ;
9
12
using JsonApiDotNetCore . Resources . Annotations ;
13
+ using JsonApiDotNetCore . Serialization . Objects ;
10
14
11
15
#pragma warning disable AV1551 // Method overload should call another overload
12
16
#pragma warning disable AV2310 // Code block should not contain inline comment
@@ -52,48 +56,23 @@ public NoSqlQueryLayerComposer(IEnumerable<IQueryConstraintProvider> constraintP
52
56
/// <inheritdoc />
53
57
public FilterExpression ? GetPrimaryFilterFromConstraintsForNoSql ( ResourceType primaryResourceType )
54
58
{
55
- return GetPrimaryFilterFromConstraints ( primaryResourceType ) ;
59
+ return AssertFilterExpressionIsSimple ( GetPrimaryFilterFromConstraints ( primaryResourceType ) ) ;
56
60
}
57
61
58
62
/// <inheritdoc />
59
63
public ( QueryLayer QueryLayer , IncludeExpression Include ) ComposeFromConstraintsForNoSql ( ResourceType requestResourceType )
60
64
{
61
65
QueryLayer queryLayer = ComposeFromConstraints ( requestResourceType ) ;
62
- IncludeExpression include = queryLayer . Include ?? IncludeExpression . Empty ;
63
66
64
- queryLayer . Include = IncludeExpression . Empty ;
65
- queryLayer . Projection = null ;
66
-
67
- return ( queryLayer , include ) ;
68
- }
69
-
70
- /// <inheritdoc />
71
- public ( QueryLayer QueryLayer , IncludeExpression Include ) ComposeForGetByIdWithConstraintsForNoSql < TId > ( TId id , ResourceType primaryResourceType ,
72
- TopFieldSelection fieldSelection )
73
- where TId : notnull
74
- {
75
- QueryLayer queryLayer = ComposeForGetById ( id , primaryResourceType , fieldSelection ) ;
76
- IncludeExpression include = queryLayer . Include ?? IncludeExpression . Empty ;
67
+ IncludeExpression include = AssertIncludeExpressionIsSimple ( queryLayer . Include ) ;
77
68
69
+ queryLayer . Filter = AssertFilterExpressionIsSimple ( queryLayer . Filter ) ;
78
70
queryLayer . Include = IncludeExpression . Empty ;
79
71
queryLayer . Projection = null ;
80
72
81
73
return ( queryLayer , include ) ;
82
74
}
83
75
84
- /// <inheritdoc />
85
- public QueryLayer ComposeForGetByIdForNoSql < TId > ( TId id , ResourceType primaryResourceType )
86
- where TId : notnull
87
- {
88
- return new QueryLayer ( primaryResourceType )
89
- {
90
- Filter = new ComparisonExpression ( ComparisonOperator . Equals ,
91
- new ResourceFieldChainExpression ( primaryResourceType . Fields . Single ( field => field . Property . Name == nameof ( IIdentifiable < TId > . Id ) ) ) ,
92
- new LiteralConstantExpression ( id . ToString ( ) ! ) ) ,
93
- Include = IncludeExpression . Empty
94
- } ;
95
- }
96
-
97
76
/// <inheritdoc />
98
77
public ( QueryLayer QueryLayer , IncludeExpression Include ) ComposeFromConstraintsForNoSql ( ResourceType requestResourceType , string propertyName ,
99
78
string propertyValue , bool isIncluded )
@@ -104,27 +83,44 @@ public QueryLayer ComposeForGetByIdForNoSql<TId>(TId id, ResourceType primaryRes
104
83
ComposeSecondaryResourceFilter ( requestResourceType , propertyName , propertyValue )
105
84
} ;
106
85
86
+ // @formatter:off
87
+
107
88
// Get the query expressions from the request.
108
- ExpressionInScope [ ] constraints = _constraintProviders . SelectMany ( provider => provider . GetConstraints ( ) ) . ToArray ( ) ;
89
+ ExpressionInScope [ ] constraints = _constraintProviders
90
+ . SelectMany ( provider => provider . GetConstraints ( ) )
91
+ . ToArray ( ) ;
109
92
110
93
bool IsQueryLayerConstraint ( ExpressionInScope constraint )
111
94
{
112
- return constraint . Expression is not IncludeExpression && ( ! isIncluded || ( constraint . Scope is not null &&
113
- constraint . Scope . Fields . Any ( field => field . PublicName == requestResourceType . PublicName ) ) ) ;
95
+ return constraint . Expression is not IncludeExpression && ( ! isIncluded || IsResourceScoped ( constraint ) ) ;
96
+ }
97
+
98
+ bool IsResourceScoped ( ExpressionInScope constraint )
99
+ {
100
+ return constraint . Scope is not null &&
101
+ constraint . Scope . Fields . Any ( field => field . PublicName == requestResourceType . PublicName ) ;
114
102
}
115
103
116
- IEnumerable < QueryExpression > requestQueryExpressions = constraints . Where ( IsQueryLayerConstraint ) . Select ( constraint => constraint . Expression ) ;
104
+ QueryExpression [ ] requestQueryExpressions = constraints
105
+ . Where ( IsQueryLayerConstraint )
106
+ . Select ( constraint => constraint . Expression )
107
+ . ToArray ( ) ;
117
108
118
- // Combine the secondary resource filter and request query expressions and
119
- // create the query layer from the combined query expressions.
120
- QueryExpression [ ] queryExpressions = secondaryResourceFilterExpressions . Concat ( requestQueryExpressions ) . ToArray ( ) ;
109
+ FilterExpression [ ] requestFilterExpressions = requestQueryExpressions
110
+ . OfType < FilterExpression > ( )
111
+ . Select ( filterExpression => AssertFilterExpressionIsSimple ( filterExpression ) ! )
112
+ . ToArray ( ) ;
113
+
114
+ FilterExpression [ ] combinedFilterExpressions = secondaryResourceFilterExpressions
115
+ . Concat ( requestFilterExpressions )
116
+ . ToArray ( ) ;
121
117
122
118
var queryLayer = new QueryLayer ( requestResourceType )
123
119
{
124
120
Include = IncludeExpression . Empty ,
125
- Filter = GetFilter ( queryExpressions , requestResourceType ) ,
126
- Sort = GetSort ( queryExpressions , requestResourceType ) ,
127
- Pagination = GetPagination ( queryExpressions , requestResourceType )
121
+ Filter = GetFilter ( combinedFilterExpressions , requestResourceType ) ,
122
+ Sort = GetSort ( requestQueryExpressions , requestResourceType ) ,
123
+ Pagination = GetPagination ( requestQueryExpressions , requestResourceType )
128
124
} ;
129
125
130
126
// Retrieve the IncludeExpression from the constraints collection.
@@ -133,7 +129,13 @@ bool IsQueryLayerConstraint(ExpressionInScope constraint)
133
129
// into a single expression.
134
130
IncludeExpression include = isIncluded
135
131
? IncludeExpression . Empty
136
- : constraints . Select ( constraint => constraint . Expression ) . OfType < IncludeExpression > ( ) . DefaultIfEmpty ( IncludeExpression . Empty ) . Single ( ) ;
132
+ : AssertIncludeExpressionIsSimple ( constraints
133
+ . Select ( constraint => constraint . Expression )
134
+ . OfType < IncludeExpression > ( )
135
+ . DefaultIfEmpty ( IncludeExpression . Empty )
136
+ . Single ( ) ) ;
137
+
138
+ // @formatter:on
137
139
138
140
return ( queryLayer , include ) ;
139
141
}
@@ -145,6 +147,35 @@ private static FilterExpression ComposeSecondaryResourceFilter(ResourceType reso
145
147
new LiteralConstantExpression ( properyValue ) ) ;
146
148
}
147
149
150
+ /// <inheritdoc />
151
+ public ( QueryLayer QueryLayer , IncludeExpression Include ) ComposeForGetByIdWithConstraintsForNoSql < TId > ( TId id , ResourceType primaryResourceType ,
152
+ TopFieldSelection fieldSelection )
153
+ where TId : notnull
154
+ {
155
+ QueryLayer queryLayer = ComposeForGetById ( id , primaryResourceType , fieldSelection ) ;
156
+
157
+ IncludeExpression include = AssertIncludeExpressionIsSimple ( queryLayer . Include ) ;
158
+
159
+ queryLayer . Filter = AssertFilterExpressionIsSimple ( queryLayer . Filter ) ;
160
+ queryLayer . Include = IncludeExpression . Empty ;
161
+ queryLayer . Projection = null ;
162
+
163
+ return ( queryLayer , include ) ;
164
+ }
165
+
166
+ /// <inheritdoc />
167
+ public QueryLayer ComposeForGetByIdForNoSql < TId > ( TId id , ResourceType primaryResourceType )
168
+ where TId : notnull
169
+ {
170
+ return new QueryLayer ( primaryResourceType )
171
+ {
172
+ Filter = new ComparisonExpression ( ComparisonOperator . Equals ,
173
+ new ResourceFieldChainExpression ( primaryResourceType . Fields . Single ( field => field . Property . Name == nameof ( IIdentifiable < TId > . Id ) ) ) ,
174
+ new LiteralConstantExpression ( id . ToString ( ) ! ) ) ,
175
+ Include = IncludeExpression . Empty
176
+ } ;
177
+ }
178
+
148
179
public ( QueryLayer QueryLayer , IncludeExpression Include ) ComposeForUpdateForNoSql < TId > ( TId id , ResourceType primaryResourceType )
149
180
where TId : notnull
150
181
{
@@ -171,5 +202,127 @@ private static AttrAttribute GetIdAttribute(ResourceType resourceType)
171
202
{
172
203
return resourceType . GetAttributeByPropertyName ( nameof ( Identifiable < object > . Id ) ) ;
173
204
}
205
+
206
+ private static FilterExpression ? AssertFilterExpressionIsSimple ( FilterExpression ? filterExpression )
207
+ {
208
+ if ( filterExpression is null )
209
+ {
210
+ return filterExpression ;
211
+ }
212
+
213
+ var visitor = new FilterExpressionVisitor ( ) ;
214
+
215
+ return visitor . Visit ( filterExpression , null )
216
+ ? filterExpression
217
+ : throw new JsonApiException ( new ErrorObject ( HttpStatusCode . BadRequest )
218
+ {
219
+ Title = "Unsupported filter expression" ,
220
+ Detail = "Navigation of to-one or to-many relationships is not supported."
221
+ } ) ;
222
+ }
223
+
224
+ private static IncludeExpression AssertIncludeExpressionIsSimple ( IncludeExpression ? includeExpression )
225
+ {
226
+ if ( includeExpression is null )
227
+ {
228
+ return IncludeExpression . Empty ;
229
+ }
230
+
231
+ return includeExpression . Elements . Any ( element => element . Children . Any ( ) )
232
+ ? throw new JsonApiException ( new ErrorObject ( HttpStatusCode . BadRequest )
233
+ {
234
+ Title = "Unsupported include expression" ,
235
+ Detail = "Multi-level include expressions are not supported."
236
+ } )
237
+ : includeExpression ;
238
+ }
239
+
240
+ private sealed class FilterExpressionVisitor : QueryExpressionVisitor < object ? , bool >
241
+ {
242
+ private bool _isSimpleFilterExpression = true ;
243
+
244
+ /// <inheritdoc />
245
+ public override bool DefaultVisit ( QueryExpression expression , object ? argument )
246
+ {
247
+ return _isSimpleFilterExpression ;
248
+ }
249
+
250
+ /// <inheritdoc />
251
+ public override bool VisitComparison ( ComparisonExpression expression , object ? argument )
252
+ {
253
+ return expression . Left . Accept ( this , argument ) && expression . Right . Accept ( this , argument ) ;
254
+ }
255
+
256
+ /// <inheritdoc />
257
+ public override bool VisitResourceFieldChain ( ResourceFieldChainExpression expression , object ? argument )
258
+ {
259
+ _isSimpleFilterExpression &= expression . Fields . All ( IsFieldSupported ) ;
260
+
261
+ return _isSimpleFilterExpression ;
262
+ }
263
+
264
+ private static bool IsFieldSupported ( ResourceFieldAttribute field )
265
+ {
266
+ return field switch
267
+ {
268
+ AttrAttribute => true ,
269
+ HasManyAttribute hasMany when HasOwnsManyAttribute ( hasMany ) => true ,
270
+ _ => false
271
+ } ;
272
+ }
273
+
274
+ private static bool HasOwnsManyAttribute ( ResourceFieldAttribute field )
275
+ {
276
+ return Attribute . GetCustomAttribute ( field . Property , typeof ( NoSqlOwnsManyAttribute ) ) is not null ;
277
+ }
278
+
279
+ /// <inheritdoc />
280
+ public override bool VisitLogical ( LogicalExpression expression , object ? argument )
281
+ {
282
+ return expression . Terms . All ( term => term . Accept ( this , argument ) ) ;
283
+ }
284
+
285
+ /// <inheritdoc />
286
+ public override bool VisitNot ( NotExpression expression , object ? argument )
287
+ {
288
+ return expression . Child . Accept ( this , argument ) ;
289
+ }
290
+
291
+ /// <inheritdoc />
292
+ public override bool VisitHas ( HasExpression expression , object ? argument )
293
+ {
294
+ return expression . TargetCollection . Accept ( this , argument ) && ( expression . Filter is null || expression . Filter . Accept ( this , argument ) ) ;
295
+ }
296
+
297
+ /// <inheritdoc />
298
+ public override bool VisitSortElement ( SortElementExpression expression , object ? argument )
299
+ {
300
+ return expression . TargetAttribute is null || expression . TargetAttribute . Accept ( this , argument ) ;
301
+ }
302
+
303
+ /// <inheritdoc />
304
+ public override bool VisitSort ( SortExpression expression , object ? argument )
305
+ {
306
+ return expression . Elements . All ( element => element . Accept ( this , argument ) ) ;
307
+ }
308
+
309
+ /// <inheritdoc />
310
+ public override bool VisitCount ( CountExpression expression , object ? argument )
311
+ {
312
+ return expression . TargetCollection . Accept ( this , argument ) ;
313
+ }
314
+
315
+ /// <inheritdoc />
316
+ public override bool VisitMatchText ( MatchTextExpression expression , object ? argument )
317
+ {
318
+ return expression . TargetAttribute . Accept ( this , argument ) ;
319
+ }
320
+
321
+ /// <inheritdoc />
322
+ public override bool VisitAny ( AnyExpression expression , object ? argument )
323
+ {
324
+ return expression . TargetAttribute . Accept ( this , argument ) ;
325
+ }
326
+ }
174
327
}
175
328
}
0 commit comments