1
1
using System . Collections . Immutable ;
2
- using System . Reflection ;
3
2
using Humanizer ;
4
3
using JetBrains . Annotations ;
5
4
using JsonApiDotNetCore . Configuration ;
6
5
using JsonApiDotNetCore . Queries . Expressions ;
7
6
using JsonApiDotNetCore . Resources ;
8
7
using JsonApiDotNetCore . Resources . Annotations ;
8
+ using JsonApiDotNetCore . Resources . Internal ;
9
9
10
10
namespace JsonApiDotNetCore . Queries . Internal . Parsing ;
11
11
@@ -141,28 +141,32 @@ protected ComparisonExpression ParseComparison(string operatorName)
141
141
: FieldChainRequirements . EndsInAttribute ;
142
142
143
143
QueryExpression leftTerm = ParseCountOrField ( leftChainRequirements ) ;
144
+ Converter < string , object > rightConstantValueConverter ;
145
+
146
+ if ( leftTerm is CountExpression )
147
+ {
148
+ rightConstantValueConverter = GetConstantValueConverterForCount ( ) ;
149
+ }
150
+ else if ( leftTerm is ResourceFieldChainExpression fieldChain && fieldChain . Fields [ ^ 1 ] is AttrAttribute attribute )
151
+ {
152
+ rightConstantValueConverter = GetConstantValueConverterForAttribute ( attribute ) ;
153
+ }
154
+ else
155
+ {
156
+ // This temporary value never survives; it gets discarded during the second pass below.
157
+ rightConstantValueConverter = _ => 0 ;
158
+ }
144
159
145
160
EatSingleCharacterToken ( TokenKind . Comma ) ;
146
161
147
- QueryExpression rightTerm = ParseCountOrConstantOrNullOrField ( FieldChainRequirements . EndsInAttribute ) ;
162
+ QueryExpression rightTerm = ParseCountOrConstantOrNullOrField ( FieldChainRequirements . EndsInAttribute , rightConstantValueConverter ) ;
148
163
149
164
EatSingleCharacterToken ( TokenKind . CloseParen ) ;
150
165
151
- if ( leftTerm is ResourceFieldChainExpression leftChain )
166
+ if ( leftTerm is ResourceFieldChainExpression leftChain && leftChain . Fields [ ^ 1 ] is RelationshipAttribute && rightTerm is not NullConstantExpression )
152
167
{
153
- if ( leftChainRequirements . HasFlag ( FieldChainRequirements . EndsInToOne ) && rightTerm is not NullConstantExpression )
154
- {
155
- // Run another pass over left chain to have it fail when chain ends in relationship.
156
- OnResolveFieldChain ( leftChain . ToString ( ) , FieldChainRequirements . EndsInAttribute ) ;
157
- }
158
-
159
- PropertyInfo leftProperty = leftChain . Fields [ ^ 1 ] . Property ;
160
-
161
- if ( leftProperty . Name == nameof ( Identifiable < object > . Id ) && rightTerm is LiteralConstantExpression rightConstant )
162
- {
163
- string id = DeObfuscateStringId ( leftProperty . ReflectedType ! , rightConstant . Value ) ;
164
- rightTerm = new LiteralConstantExpression ( id ) ;
165
- }
168
+ // Run another pass over left chain to produce an error.
169
+ OnResolveFieldChain ( leftChain . ToString ( ) , FieldChainRequirements . EndsInAttribute ) ;
166
170
}
167
171
168
172
return new ComparisonExpression ( comparisonOperator , leftTerm , rightTerm ) ;
@@ -173,16 +177,23 @@ protected MatchTextExpression ParseTextMatch(string matchFunctionName)
173
177
EatText ( matchFunctionName ) ;
174
178
EatSingleCharacterToken ( TokenKind . OpenParen ) ;
175
179
176
- ResourceFieldChainExpression targetAttribute = ParseFieldChain ( FieldChainRequirements . EndsInAttribute , null ) ;
180
+ ResourceFieldChainExpression targetAttributeChain = ParseFieldChain ( FieldChainRequirements . EndsInAttribute , null ) ;
181
+ Type targetAttributeType = ( ( AttrAttribute ) targetAttributeChain . Fields [ ^ 1 ] ) . Property . PropertyType ;
182
+
183
+ if ( targetAttributeType != typeof ( string ) )
184
+ {
185
+ throw new QueryParseException ( "Attribute of type 'String' expected." ) ;
186
+ }
177
187
178
188
EatSingleCharacterToken ( TokenKind . Comma ) ;
179
189
180
- LiteralConstantExpression constant = ParseConstant ( ) ;
190
+ Converter < string , object > constantValueConverter = stringValue => stringValue ;
191
+ LiteralConstantExpression constant = ParseConstant ( constantValueConverter ) ;
181
192
182
193
EatSingleCharacterToken ( TokenKind . CloseParen ) ;
183
194
184
195
var matchKind = Enum . Parse < TextMatchKind > ( matchFunctionName . Pascalize ( ) ) ;
185
- return new MatchTextExpression ( targetAttribute , constant , matchKind ) ;
196
+ return new MatchTextExpression ( targetAttributeChain , constant , matchKind ) ;
186
197
}
187
198
188
199
protected AnyExpression ParseAny ( )
@@ -191,52 +202,30 @@ protected AnyExpression ParseAny()
191
202
EatSingleCharacterToken ( TokenKind . OpenParen ) ;
192
203
193
204
ResourceFieldChainExpression targetAttribute = ParseFieldChain ( FieldChainRequirements . EndsInAttribute , null ) ;
205
+ Converter < string , object > constantValueConverter = GetConstantValueConverterForAttribute ( ( AttrAttribute ) targetAttribute . Fields [ ^ 1 ] ) ;
194
206
195
207
EatSingleCharacterToken ( TokenKind . Comma ) ;
196
208
197
209
ImmutableHashSet < LiteralConstantExpression > . Builder constantsBuilder = ImmutableHashSet . CreateBuilder < LiteralConstantExpression > ( ) ;
198
210
199
- LiteralConstantExpression constant = ParseConstant ( ) ;
211
+ LiteralConstantExpression constant = ParseConstant ( constantValueConverter ) ;
200
212
constantsBuilder . Add ( constant ) ;
201
213
202
214
while ( TokenStack . TryPeek ( out Token ? nextToken ) && nextToken . Kind == TokenKind . Comma )
203
215
{
204
216
EatSingleCharacterToken ( TokenKind . Comma ) ;
205
217
206
- constant = ParseConstant ( ) ;
218
+ constant = ParseConstant ( constantValueConverter ) ;
207
219
constantsBuilder . Add ( constant ) ;
208
220
}
209
221
210
222
EatSingleCharacterToken ( TokenKind . CloseParen ) ;
211
223
212
224
IImmutableSet < LiteralConstantExpression > constantSet = constantsBuilder . ToImmutable ( ) ;
213
225
214
- PropertyInfo targetAttributeProperty = targetAttribute . Fields [ ^ 1 ] . Property ;
215
-
216
- if ( targetAttributeProperty . Name == nameof ( Identifiable < object > . Id ) )
217
- {
218
- constantSet = DeObfuscateIdConstants ( constantSet , targetAttributeProperty ) ;
219
- }
220
-
221
226
return new AnyExpression ( targetAttribute , constantSet ) ;
222
227
}
223
228
224
- private IImmutableSet < LiteralConstantExpression > DeObfuscateIdConstants ( IImmutableSet < LiteralConstantExpression > constantSet ,
225
- PropertyInfo targetAttributeProperty )
226
- {
227
- ImmutableHashSet < LiteralConstantExpression > . Builder idConstantsBuilder = ImmutableHashSet . CreateBuilder < LiteralConstantExpression > ( ) ;
228
-
229
- foreach ( LiteralConstantExpression idConstant in constantSet )
230
- {
231
- string stringId = idConstant . Value ;
232
- string id = DeObfuscateStringId ( targetAttributeProperty . ReflectedType ! , stringId ) ;
233
-
234
- idConstantsBuilder . Add ( new LiteralConstantExpression ( id ) ) ;
235
- }
236
-
237
- return idConstantsBuilder . ToImmutable ( ) ;
238
- }
239
-
240
229
protected HasExpression ParseHas ( )
241
230
{
242
231
EatText ( Keywords . Has ) ;
@@ -360,7 +349,7 @@ protected QueryExpression ParseCountOrField(FieldChainRequirements chainRequirem
360
349
return ParseFieldChain ( chainRequirements , "Count function or field name expected." ) ;
361
350
}
362
351
363
- protected QueryExpression ParseCountOrConstantOrNullOrField ( FieldChainRequirements chainRequirements )
352
+ protected QueryExpression ParseCountOrConstantOrNullOrField ( FieldChainRequirements chainRequirements , Converter < string , object > constantValueConverter )
364
353
{
365
354
CountExpression ? count = TryParseCount ( ) ;
366
355
@@ -369,7 +358,7 @@ protected QueryExpression ParseCountOrConstantOrNullOrField(FieldChainRequiremen
369
358
return count ;
370
359
}
371
360
372
- IdentifierExpression ? constantOrNull = TryParseConstantOrNull ( ) ;
361
+ IdentifierExpression ? constantOrNull = TryParseConstantOrNull ( constantValueConverter ) ;
373
362
374
363
if ( constantOrNull != null )
375
364
{
@@ -379,7 +368,7 @@ protected QueryExpression ParseCountOrConstantOrNullOrField(FieldChainRequiremen
379
368
return ParseFieldChain ( chainRequirements , "Count function, value between quotes, null or field name expected." ) ;
380
369
}
381
370
382
- protected IdentifierExpression ? TryParseConstantOrNull ( )
371
+ protected IdentifierExpression ? TryParseConstantOrNull ( Converter < string , object > constantValueConverter )
383
372
{
384
373
if ( TokenStack . TryPeek ( out Token ? nextToken ) )
385
374
{
@@ -392,28 +381,55 @@ protected QueryExpression ParseCountOrConstantOrNullOrField(FieldChainRequiremen
392
381
if ( nextToken . Kind == TokenKind . QuotedText )
393
382
{
394
383
TokenStack . Pop ( ) ;
395
- return new LiteralConstantExpression ( nextToken . Value ! ) ;
384
+
385
+ object constantValue = constantValueConverter ( nextToken . Value ! ) ;
386
+ return new LiteralConstantExpression ( constantValue , nextToken . Value ! ) ;
396
387
}
397
388
}
398
389
399
390
return null ;
400
391
}
401
392
402
- protected LiteralConstantExpression ParseConstant ( )
393
+ protected LiteralConstantExpression ParseConstant ( Converter < string , object > constantValueConverter )
403
394
{
404
395
if ( TokenStack . TryPop ( out Token ? token ) && token . Kind == TokenKind . QuotedText )
405
396
{
406
- return new LiteralConstantExpression ( token . Value ! ) ;
397
+ object constantValue = constantValueConverter ( token . Value ! ) ;
398
+ return new LiteralConstantExpression ( constantValue , token . Value ! ) ;
407
399
}
408
400
409
401
throw new QueryParseException ( "Value between quotes expected." ) ;
410
402
}
411
403
412
- private string DeObfuscateStringId ( Type resourceClrType , string stringId )
404
+ private Converter < string , object > GetConstantValueConverterForCount ( )
405
+ {
406
+ return stringValue => ConvertStringToType ( stringValue , typeof ( int ) ) ;
407
+ }
408
+
409
+ private object ConvertStringToType ( string value , Type type )
410
+ {
411
+ try
412
+ {
413
+ return RuntimeTypeConverter . ConvertType ( value , type ) ! ;
414
+ }
415
+ catch ( FormatException )
416
+ {
417
+ throw new QueryParseException ( $ "Failed to convert '{ value } ' of type 'String' to type '{ type . Name } '.") ;
418
+ }
419
+ }
420
+
421
+ private Converter < string , object > GetConstantValueConverterForAttribute ( AttrAttribute attribute )
422
+ {
423
+ return stringValue => attribute . Property . Name == nameof ( IIdentifiable < object > . Id )
424
+ ? DeObfuscateStringId ( attribute . Type . ClrType , stringValue )
425
+ : ConvertStringToType ( stringValue , attribute . Property . PropertyType ) ;
426
+ }
427
+
428
+ private object DeObfuscateStringId ( Type resourceClrType , string stringId )
413
429
{
414
430
IIdentifiable tempResource = _resourceFactory . CreateInstance ( resourceClrType ) ;
415
431
tempResource . StringId = stringId ;
416
- return tempResource . GetTypedId ( ) . ToString ( ) ! ;
432
+ return tempResource . GetTypedId ( ) ;
417
433
}
418
434
419
435
protected override IImmutableList < ResourceFieldAttribute > OnResolveFieldChain ( string path , FieldChainRequirements chainRequirements )
0 commit comments