11using System . Collections . Immutable ;
2- using System . Text ;
32using JetBrains . Annotations ;
43using JsonApiDotNetCore . Configuration ;
54using JsonApiDotNetCore . Errors ;
@@ -19,15 +18,15 @@ public IncludeExpression Parse(string source, ResourceType resourceTypeInScope,
1918
2019 Tokenize ( source ) ;
2120
22- IncludeExpression expression = ParseInclude ( resourceTypeInScope , maximumDepth ) ;
21+ IncludeExpression expression = ParseInclude ( source , resourceTypeInScope , maximumDepth ) ;
2322
2423 AssertTokenStackIsEmpty ( ) ;
25- ValidateMaximumIncludeDepth ( maximumDepth , expression ) ;
24+ ValidateMaximumIncludeDepth ( maximumDepth , expression , 0 ) ;
2625
2726 return expression ;
2827 }
2928
30- protected IncludeExpression ParseInclude ( ResourceType resourceTypeInScope , int ? maximumDepth )
29+ protected IncludeExpression ParseInclude ( string source , ResourceType resourceTypeInScope , int ? maximumDepth )
3130 {
3231 var treeRoot = IncludeTreeNode . CreateRoot ( resourceTypeInScope ) ;
3332 bool isAtStart = true ;
@@ -43,13 +42,13 @@ protected IncludeExpression ParseInclude(ResourceType resourceTypeInScope, int?
4342 isAtStart = false ;
4443 }
4544
46- ParseRelationshipChain ( treeRoot ) ;
45+ ParseRelationshipChain ( source , treeRoot ) ;
4746 }
4847
4948 return treeRoot . ToExpression ( ) ;
5049 }
5150
52- private void ParseRelationshipChain ( IncludeTreeNode treeRoot )
51+ private void ParseRelationshipChain ( string source , IncludeTreeNode treeRoot )
5352 {
5453 // A relationship name usually matches a single relationship, even when overridden in derived types.
5554 // But in the following case, two relationships are matched on GET /shoppingBaskets?include=items:
@@ -77,27 +76,29 @@ private void ParseRelationshipChain(IncludeTreeNode treeRoot)
7776 // that there's currently no way to include Products without Articles. We could add such optional upcast syntax
7877 // in the future, if desired.
7978
80- ICollection < IncludeTreeNode > children = ParseRelationshipName ( treeRoot . AsList ( ) ) ;
79+ ICollection < IncludeTreeNode > children = ParseRelationshipName ( source , treeRoot . AsList ( ) ) ;
8180
8281 while ( TokenStack . TryPeek ( out Token ? nextToken ) && nextToken . Kind == TokenKind . Period )
8382 {
8483 EatSingleCharacterToken ( TokenKind . Period ) ;
8584
86- children = ParseRelationshipName ( children ) ;
85+ children = ParseRelationshipName ( source , children ) ;
8786 }
8887 }
8988
90- private ICollection < IncludeTreeNode > ParseRelationshipName ( ICollection < IncludeTreeNode > parents )
89+ private ICollection < IncludeTreeNode > ParseRelationshipName ( string source , ICollection < IncludeTreeNode > parents )
9190 {
91+ int position = GetNextTokenPositionOrEnd ( ) ;
92+
9293 if ( TokenStack . TryPop ( out Token ? token ) && token . Kind == TokenKind . Text )
9394 {
94- return LookupRelationshipName ( token . Value ! , parents ) ;
95+ return LookupRelationshipName ( token . Value ! , parents , source , position ) ;
9596 }
9697
97- throw new QueryParseException ( "Relationship name expected." ) ;
98+ throw new QueryParseException ( "Relationship name expected." , position ) ;
9899 }
99100
100- private ICollection < IncludeTreeNode > LookupRelationshipName ( string relationshipName , ICollection < IncludeTreeNode > parents )
101+ private ICollection < IncludeTreeNode > LookupRelationshipName ( string relationshipName , ICollection < IncludeTreeNode > parents , string source , int position )
101102 {
102103 List < IncludeTreeNode > children = new ( ) ;
103104 HashSet < RelationshipAttribute > relationshipsFound = new ( ) ;
@@ -118,116 +119,94 @@ private ICollection<IncludeTreeNode> LookupRelationshipName(string relationshipN
118119 }
119120 }
120121
121- AssertRelationshipsFound ( relationshipsFound , relationshipName , parents ) ;
122- AssertAtLeastOneCanBeIncluded ( relationshipsFound , relationshipName , parents ) ;
122+ AssertRelationshipsFound ( relationshipsFound , relationshipName , parents , position ) ;
123+ AssertAtLeastOneCanBeIncluded ( relationshipsFound , relationshipName , source , position ) ;
123124
124125 return children ;
125126 }
126127
127- private static void AssertRelationshipsFound ( ISet < RelationshipAttribute > relationshipsFound , string relationshipName , ICollection < IncludeTreeNode > parents )
128+ private static void AssertRelationshipsFound ( ISet < RelationshipAttribute > relationshipsFound , string relationshipName , ICollection < IncludeTreeNode > parents ,
129+ int position )
128130 {
129131 if ( relationshipsFound . Any ( ) )
130132 {
131133 return ;
132134 }
133135
134- string [ ] parentPaths = parents . Select ( parent => parent . Path ) . Distinct ( ) . Where ( path => path != string . Empty ) . ToArray ( ) ;
135- string path = parentPaths . Length > 0 ? $ "{ parentPaths [ 0 ] } .{ relationshipName } " : relationshipName ;
136-
137136 ResourceType [ ] parentResourceTypes = parents . Select ( parent => parent . Relationship . RightType ) . Distinct ( ) . ToArray ( ) ;
138137
139138 bool hasDerivedTypes = parents . Any ( parent => parent . Relationship . RightType . DirectlyDerivedTypes . Count > 0 ) ;
140139
141- string message = ErrorFormatter . GetForNoneFound ( ResourceFieldCategory . Relationship , relationshipName , path , parentResourceTypes , hasDerivedTypes ) ;
142- throw new QueryParseException ( message ) ;
140+ string message = ErrorFormatter . GetForNoneFound ( ResourceFieldCategory . Relationship , relationshipName , parentResourceTypes , hasDerivedTypes ) ;
141+ throw new QueryParseException ( message , position ) ;
143142 }
144143
145- private static void AssertAtLeastOneCanBeIncluded ( ISet < RelationshipAttribute > relationshipsFound , string relationshipName ,
146- ICollection < IncludeTreeNode > parents )
144+ private static void AssertAtLeastOneCanBeIncluded ( ISet < RelationshipAttribute > relationshipsFound , string relationshipName , string source , int position )
147145 {
148146 if ( relationshipsFound . All ( relationship => relationship . IsIncludeBlocked ( ) ) )
149147 {
150- string parentPath = parents . First ( ) . Path ;
151148 ResourceType resourceType = relationshipsFound . First ( ) . LeftType ;
149+ string message = $ "Including the relationship '{ relationshipName } ' on '{ resourceType } ' is not allowed.";
152150
153- string message = parentPath == string . Empty
154- ? $ "Including the relationship '{ relationshipName } ' on '{ resourceType } ' is not allowed."
155- : $ "Including the relationship '{ relationshipName } ' in '{ parentPath } .{ relationshipName } ' on '{ resourceType } ' is not allowed.";
151+ var exception = new QueryParseException ( message , position ) ;
152+ string specificMessage = exception . GetMessageWithPosition ( source ) ;
156153
157- throw new InvalidQueryStringParameterException ( "include" , "The specified include is invalid." , message ) ;
154+ throw new InvalidQueryStringParameterException ( "include" , "The specified include is invalid." , specificMessage ) ;
158155 }
159156 }
160157
161- private static void ValidateMaximumIncludeDepth ( int ? maximumDepth , IncludeExpression include )
158+ private static void ValidateMaximumIncludeDepth ( int ? maximumDepth , IncludeExpression include , int position )
162159 {
163160 if ( maximumDepth != null )
164161 {
165162 Stack < RelationshipAttribute > parentChain = new ( ) ;
166163
167164 foreach ( IncludeElementExpression element in include . Elements )
168165 {
169- ThrowIfMaximumDepthExceeded ( element , parentChain , maximumDepth . Value ) ;
166+ ThrowIfMaximumDepthExceeded ( element , parentChain , maximumDepth . Value , position ) ;
170167 }
171168 }
172169 }
173170
174- private static void ThrowIfMaximumDepthExceeded ( IncludeElementExpression includeElement , Stack < RelationshipAttribute > parentChain , int maximumDepth )
171+ private static void ThrowIfMaximumDepthExceeded ( IncludeElementExpression includeElement , Stack < RelationshipAttribute > parentChain , int maximumDepth ,
172+ int position )
175173 {
176174 parentChain . Push ( includeElement . Relationship ) ;
177175
178176 if ( parentChain . Count > maximumDepth )
179177 {
180178 string path = string . Join ( '.' , parentChain . Reverse ( ) . Select ( relationship => relationship . PublicName ) ) ;
181- throw new QueryParseException ( $ "Including '{ path } ' exceeds the maximum inclusion depth of { maximumDepth } .") ;
179+ throw new QueryParseException ( $ "Including '{ path } ' exceeds the maximum inclusion depth of { maximumDepth } .", position ) ;
182180 }
183181
184182 foreach ( IncludeElementExpression child in includeElement . Children )
185183 {
186- ThrowIfMaximumDepthExceeded ( child , parentChain , maximumDepth ) ;
184+ ThrowIfMaximumDepthExceeded ( child , parentChain , maximumDepth , position ) ;
187185 }
188186
189187 parentChain . Pop ( ) ;
190188 }
191189
192- protected override IImmutableList < ResourceFieldAttribute > OnResolveFieldChain ( string path , FieldChainRequirements chainRequirements )
190+ protected override IImmutableList < ResourceFieldAttribute > OnResolveFieldChain ( string path , int position , FieldChainRequirements chainRequirements )
193191 {
194192 throw new NotSupportedException ( ) ;
195193 }
196194
197195 private sealed class IncludeTreeNode
198196 {
199- private readonly IncludeTreeNode ? _parent ;
200197 private readonly IDictionary < RelationshipAttribute , IncludeTreeNode > _children = new Dictionary < RelationshipAttribute , IncludeTreeNode > ( ) ;
201198
202199 public RelationshipAttribute Relationship { get ; }
203200
204- public string Path
205- {
206- get
207- {
208- var pathBuilder = new StringBuilder ( ) ;
209- IncludeTreeNode ? parent = this ;
210-
211- while ( parent is { Relationship : not HiddenRootRelationshipAttribute } )
212- {
213- pathBuilder . Insert ( 0 , pathBuilder . Length > 0 ? $ "{ parent . Relationship . PublicName } ." : parent . Relationship . PublicName ) ;
214- parent = parent . _parent ;
215- }
216-
217- return pathBuilder . ToString ( ) ;
218- }
219- }
220-
221- private IncludeTreeNode ( RelationshipAttribute relationship , IncludeTreeNode ? parent )
201+ private IncludeTreeNode ( RelationshipAttribute relationship )
222202 {
223203 Relationship = relationship ;
224- _parent = parent ;
225204 }
226205
227206 public static IncludeTreeNode CreateRoot ( ResourceType resourceType )
228207 {
229208 var relationship = new HiddenRootRelationshipAttribute ( resourceType ) ;
230- return new IncludeTreeNode ( relationship , null ) ;
209+ return new IncludeTreeNode ( relationship ) ;
231210 }
232211
233212 public ICollection < IncludeTreeNode > EnsureChildren ( ICollection < RelationshipAttribute > relationships )
@@ -236,7 +215,7 @@ public ICollection<IncludeTreeNode> EnsureChildren(ICollection<RelationshipAttri
236215 {
237216 if ( ! _children . ContainsKey ( relationship ) )
238217 {
239- var newChild = new IncludeTreeNode ( relationship , this ) ;
218+ var newChild = new IncludeTreeNode ( relationship ) ;
240219 _children . Add ( relationship , newChild ) ;
241220 }
242221 }
0 commit comments