@@ -28,15 +28,16 @@ public FilterExpression Parse(string source, ResourceType resourceTypeInScope)
28
28
{
29
29
ArgumentGuard . NotNull ( resourceTypeInScope , nameof ( resourceTypeInScope ) ) ;
30
30
31
- _resourceTypeInScope = resourceTypeInScope ;
32
-
33
- Tokenize ( source ) ;
31
+ return InScopeOfResourceType ( resourceTypeInScope , ( ) =>
32
+ {
33
+ Tokenize ( source ) ;
34
34
35
- FilterExpression expression = ParseFilter ( ) ;
35
+ FilterExpression expression = ParseFilter ( ) ;
36
36
37
- AssertTokenStackIsEmpty ( ) ;
37
+ AssertTokenStackIsEmpty ( ) ;
38
38
39
- return expression ;
39
+ return expression ;
40
+ } ) ;
40
41
}
41
42
42
43
protected FilterExpression ParseFilter ( )
@@ -76,6 +77,10 @@ protected FilterExpression ParseFilter()
76
77
{
77
78
return ParseHas ( ) ;
78
79
}
80
+ case Keywords . IsType :
81
+ {
82
+ return ParseIsType ( ) ;
83
+ }
79
84
}
80
85
}
81
86
@@ -259,13 +264,88 @@ protected HasExpression ParseHas()
259
264
260
265
private FilterExpression ParseFilterInHas ( HasManyAttribute hasManyRelationship )
261
266
{
262
- ResourceType outerScopeBackup = _resourceTypeInScope ! ;
267
+ return InScopeOfResourceType ( hasManyRelationship . RightType , ParseFilter ) ;
268
+ }
269
+
270
+ private IsTypeExpression ParseIsType ( )
271
+ {
272
+ EatText ( Keywords . IsType ) ;
273
+ EatSingleCharacterToken ( TokenKind . OpenParen ) ;
274
+
275
+ string derivedTypeName = EatResourceTypeName ( ) ;
276
+
277
+ ResourceType baseType = _resourceTypeInScope ! ;
278
+ ResourceFieldChainExpression ? targetToOneRelationship = null ;
279
+
280
+ if ( TokenStack . TryPeek ( out Token ? nextToken ) && nextToken . Kind == TokenKind . Colon )
281
+ {
282
+ EatSingleCharacterToken ( TokenKind . Colon ) ;
283
+
284
+ targetToOneRelationship = ParseFieldChain ( FieldChainRequirements . EndsInToOne , "Relationship name expected." ) ;
285
+ baseType = ( ( RelationshipAttribute ) targetToOneRelationship . Fields [ ^ 1 ] ) . RightType ;
286
+ }
287
+
288
+ ResourceType derivedType = ResolveDerivedType ( baseType , derivedTypeName ) ;
289
+ FilterExpression ? child = TryParseFilterInIsType ( derivedType ) ;
290
+
291
+ EatSingleCharacterToken ( TokenKind . CloseParen ) ;
292
+
293
+ return new IsTypeExpression ( derivedType , targetToOneRelationship , child ) ;
294
+ }
295
+
296
+ private string EatResourceTypeName ( )
297
+ {
298
+ if ( TokenStack . TryPop ( out Token ? token ) && token . Kind == TokenKind . Text )
299
+ {
300
+ return token . Value ! ;
301
+ }
302
+
303
+ throw new QueryParseException ( "Resource type expected." ) ;
304
+ }
305
+
306
+ private ResourceType ResolveDerivedType ( ResourceType baseType , string derivedTypeName )
307
+ {
308
+ ResourceType ? derivedType = GetDerivedType ( baseType , derivedTypeName ) ;
263
309
264
- _resourceTypeInScope = hasManyRelationship . RightType ;
310
+ if ( derivedType == null )
311
+ {
312
+ throw new QueryParseException ( $ "Resource type '{ derivedTypeName } ' does not exist or does not derive from '{ baseType . PublicName } '.") ;
313
+ }
265
314
266
- FilterExpression filter = ParseFilter ( ) ;
315
+ return derivedType ;
316
+ }
317
+
318
+ private ResourceType ? GetDerivedType ( ResourceType baseType , string publicName )
319
+ {
320
+ foreach ( ResourceType derivedType in baseType . DirectlyDerivedTypes )
321
+ {
322
+ if ( derivedType . PublicName == publicName )
323
+ {
324
+ return derivedType ;
325
+ }
326
+
327
+ ResourceType ? nextType = GetDerivedType ( derivedType , publicName ) ;
328
+
329
+ if ( nextType != null )
330
+ {
331
+ return nextType ;
332
+ }
333
+ }
334
+
335
+ return null ;
336
+ }
337
+
338
+ private FilterExpression ? TryParseFilterInIsType ( ResourceType derivedType )
339
+ {
340
+ FilterExpression ? filter = null ;
341
+
342
+ if ( TokenStack . TryPeek ( out Token ? nextToken ) && nextToken . Kind == TokenKind . Comma )
343
+ {
344
+ EatSingleCharacterToken ( TokenKind . Comma ) ;
345
+
346
+ filter = InScopeOfResourceType ( derivedType , ParseFilter ) ;
347
+ }
267
348
268
- _resourceTypeInScope = outerScopeBackup ;
269
349
return filter ;
270
350
}
271
351
@@ -349,11 +429,31 @@ protected override IImmutableList<ResourceFieldAttribute> OnResolveFieldChain(st
349
429
return ChainResolver . ResolveToOneChainEndingInAttribute ( _resourceTypeInScope ! , path , _validateSingleFieldCallback ) ;
350
430
}
351
431
432
+ if ( chainRequirements == FieldChainRequirements . EndsInToOne )
433
+ {
434
+ return ChainResolver . ResolveToOneChain ( _resourceTypeInScope ! , path , _validateSingleFieldCallback ) ;
435
+ }
436
+
352
437
if ( chainRequirements . HasFlag ( FieldChainRequirements . EndsInAttribute ) && chainRequirements . HasFlag ( FieldChainRequirements . EndsInToOne ) )
353
438
{
354
439
return ChainResolver . ResolveToOneChainEndingInAttributeOrToOne ( _resourceTypeInScope ! , path , _validateSingleFieldCallback ) ;
355
440
}
356
441
357
442
throw new InvalidOperationException ( $ "Unexpected combination of chain requirement flags '{ chainRequirements } '.") ;
358
443
}
444
+
445
+ private TResult InScopeOfResourceType < TResult > ( ResourceType resourceType , Func < TResult > action )
446
+ {
447
+ ResourceType ? backupType = _resourceTypeInScope ;
448
+
449
+ try
450
+ {
451
+ _resourceTypeInScope = resourceType ;
452
+ return action ( ) ;
453
+ }
454
+ finally
455
+ {
456
+ _resourceTypeInScope = backupType ;
457
+ }
458
+ }
359
459
}
0 commit comments