1
1
using System . Collections . Immutable ;
2
- using System . Text ;
3
2
using JetBrains . Annotations ;
4
3
using JsonApiDotNetCore . Configuration ;
5
4
using JsonApiDotNetCore . Errors ;
@@ -19,15 +18,15 @@ public IncludeExpression Parse(string source, ResourceType resourceTypeInScope,
19
18
20
19
Tokenize ( source ) ;
21
20
22
- IncludeExpression expression = ParseInclude ( resourceTypeInScope , maximumDepth ) ;
21
+ IncludeExpression expression = ParseInclude ( source , resourceTypeInScope , maximumDepth ) ;
23
22
24
23
AssertTokenStackIsEmpty ( ) ;
25
- ValidateMaximumIncludeDepth ( maximumDepth , expression ) ;
24
+ ValidateMaximumIncludeDepth ( maximumDepth , expression , 0 ) ;
26
25
27
26
return expression ;
28
27
}
29
28
30
- protected IncludeExpression ParseInclude ( ResourceType resourceTypeInScope , int ? maximumDepth )
29
+ protected IncludeExpression ParseInclude ( string source , ResourceType resourceTypeInScope , int ? maximumDepth )
31
30
{
32
31
var treeRoot = IncludeTreeNode . CreateRoot ( resourceTypeInScope ) ;
33
32
bool isAtStart = true ;
@@ -43,13 +42,13 @@ protected IncludeExpression ParseInclude(ResourceType resourceTypeInScope, int?
43
42
isAtStart = false ;
44
43
}
45
44
46
- ParseRelationshipChain ( treeRoot ) ;
45
+ ParseRelationshipChain ( source , treeRoot ) ;
47
46
}
48
47
49
48
return treeRoot . ToExpression ( ) ;
50
49
}
51
50
52
- private void ParseRelationshipChain ( IncludeTreeNode treeRoot )
51
+ private void ParseRelationshipChain ( string source , IncludeTreeNode treeRoot )
53
52
{
54
53
// A relationship name usually matches a single relationship, even when overridden in derived types.
55
54
// But in the following case, two relationships are matched on GET /shoppingBaskets?include=items:
@@ -77,27 +76,29 @@ private void ParseRelationshipChain(IncludeTreeNode treeRoot)
77
76
// that there's currently no way to include Products without Articles. We could add such optional upcast syntax
78
77
// in the future, if desired.
79
78
80
- ICollection < IncludeTreeNode > children = ParseRelationshipName ( treeRoot . AsList ( ) ) ;
79
+ ICollection < IncludeTreeNode > children = ParseRelationshipName ( source , treeRoot . AsList ( ) ) ;
81
80
82
81
while ( TokenStack . TryPeek ( out Token ? nextToken ) && nextToken . Kind == TokenKind . Period )
83
82
{
84
83
EatSingleCharacterToken ( TokenKind . Period ) ;
85
84
86
- children = ParseRelationshipName ( children ) ;
85
+ children = ParseRelationshipName ( source , children ) ;
87
86
}
88
87
}
89
88
90
- private ICollection < IncludeTreeNode > ParseRelationshipName ( ICollection < IncludeTreeNode > parents )
89
+ private ICollection < IncludeTreeNode > ParseRelationshipName ( string source , ICollection < IncludeTreeNode > parents )
91
90
{
91
+ int position = GetNextTokenPositionOrEnd ( ) ;
92
+
92
93
if ( TokenStack . TryPop ( out Token ? token ) && token . Kind == TokenKind . Text )
93
94
{
94
- return LookupRelationshipName ( token . Value ! , parents ) ;
95
+ return LookupRelationshipName ( token . Value ! , parents , source , position ) ;
95
96
}
96
97
97
- throw new QueryParseException ( "Relationship name expected." ) ;
98
+ throw new QueryParseException ( "Relationship name expected." , position ) ;
98
99
}
99
100
100
- private ICollection < IncludeTreeNode > LookupRelationshipName ( string relationshipName , ICollection < IncludeTreeNode > parents )
101
+ private ICollection < IncludeTreeNode > LookupRelationshipName ( string relationshipName , ICollection < IncludeTreeNode > parents , string source , int position )
101
102
{
102
103
List < IncludeTreeNode > children = new ( ) ;
103
104
HashSet < RelationshipAttribute > relationshipsFound = new ( ) ;
@@ -118,116 +119,94 @@ private ICollection<IncludeTreeNode> LookupRelationshipName(string relationshipN
118
119
}
119
120
}
120
121
121
- AssertRelationshipsFound ( relationshipsFound , relationshipName , parents ) ;
122
- AssertAtLeastOneCanBeIncluded ( relationshipsFound , relationshipName , parents ) ;
122
+ AssertRelationshipsFound ( relationshipsFound , relationshipName , parents , position ) ;
123
+ AssertAtLeastOneCanBeIncluded ( relationshipsFound , relationshipName , source , position ) ;
123
124
124
125
return children ;
125
126
}
126
127
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 )
128
130
{
129
131
if ( relationshipsFound . Any ( ) )
130
132
{
131
133
return ;
132
134
}
133
135
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
-
137
136
ResourceType [ ] parentResourceTypes = parents . Select ( parent => parent . Relationship . RightType ) . Distinct ( ) . ToArray ( ) ;
138
137
139
138
bool hasDerivedTypes = parents . Any ( parent => parent . Relationship . RightType . DirectlyDerivedTypes . Count > 0 ) ;
140
139
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 ) ;
143
142
}
144
143
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 )
147
145
{
148
146
if ( relationshipsFound . All ( relationship => relationship . IsIncludeBlocked ( ) ) )
149
147
{
150
- string parentPath = parents . First ( ) . Path ;
151
148
ResourceType resourceType = relationshipsFound . First ( ) . LeftType ;
149
+ string message = $ "Including the relationship '{ relationshipName } ' on '{ resourceType } ' is not allowed.";
152
150
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 ) ;
156
153
157
- throw new InvalidQueryStringParameterException ( "include" , "The specified include is invalid." , message ) ;
154
+ throw new InvalidQueryStringParameterException ( "include" , "The specified include is invalid." , specificMessage ) ;
158
155
}
159
156
}
160
157
161
- private static void ValidateMaximumIncludeDepth ( int ? maximumDepth , IncludeExpression include )
158
+ private static void ValidateMaximumIncludeDepth ( int ? maximumDepth , IncludeExpression include , int position )
162
159
{
163
160
if ( maximumDepth != null )
164
161
{
165
162
Stack < RelationshipAttribute > parentChain = new ( ) ;
166
163
167
164
foreach ( IncludeElementExpression element in include . Elements )
168
165
{
169
- ThrowIfMaximumDepthExceeded ( element , parentChain , maximumDepth . Value ) ;
166
+ ThrowIfMaximumDepthExceeded ( element , parentChain , maximumDepth . Value , position ) ;
170
167
}
171
168
}
172
169
}
173
170
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 )
175
173
{
176
174
parentChain . Push ( includeElement . Relationship ) ;
177
175
178
176
if ( parentChain . Count > maximumDepth )
179
177
{
180
178
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 ) ;
182
180
}
183
181
184
182
foreach ( IncludeElementExpression child in includeElement . Children )
185
183
{
186
- ThrowIfMaximumDepthExceeded ( child , parentChain , maximumDepth ) ;
184
+ ThrowIfMaximumDepthExceeded ( child , parentChain , maximumDepth , position ) ;
187
185
}
188
186
189
187
parentChain . Pop ( ) ;
190
188
}
191
189
192
- protected override IImmutableList < ResourceFieldAttribute > OnResolveFieldChain ( string path , FieldChainRequirements chainRequirements )
190
+ protected override IImmutableList < ResourceFieldAttribute > OnResolveFieldChain ( string path , int position , FieldChainRequirements chainRequirements )
193
191
{
194
192
throw new NotSupportedException ( ) ;
195
193
}
196
194
197
195
private sealed class IncludeTreeNode
198
196
{
199
- private readonly IncludeTreeNode ? _parent ;
200
197
private readonly IDictionary < RelationshipAttribute , IncludeTreeNode > _children = new Dictionary < RelationshipAttribute , IncludeTreeNode > ( ) ;
201
198
202
199
public RelationshipAttribute Relationship { get ; }
203
200
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 )
222
202
{
223
203
Relationship = relationship ;
224
- _parent = parent ;
225
204
}
226
205
227
206
public static IncludeTreeNode CreateRoot ( ResourceType resourceType )
228
207
{
229
208
var relationship = new HiddenRootRelationshipAttribute ( resourceType ) ;
230
- return new IncludeTreeNode ( relationship , null ) ;
209
+ return new IncludeTreeNode ( relationship ) ;
231
210
}
232
211
233
212
public ICollection < IncludeTreeNode > EnsureChildren ( ICollection < RelationshipAttribute > relationships )
@@ -236,7 +215,7 @@ public ICollection<IncludeTreeNode> EnsureChildren(ICollection<RelationshipAttri
236
215
{
237
216
if ( ! _children . ContainsKey ( relationship ) )
238
217
{
239
- var newChild = new IncludeTreeNode ( relationship , this ) ;
218
+ var newChild = new IncludeTreeNode ( relationship ) ;
240
219
_children . Add ( relationship , newChild ) ;
241
220
}
242
221
}
0 commit comments