|
1 | | -using System.Collections.Immutable; |
2 | | -using System.Collections.ObjectModel; |
3 | | -using System.Text; |
4 | 1 | using JetBrains.Annotations; |
5 | 2 | using JsonApiDotNetCore.Configuration; |
6 | | -using JsonApiDotNetCore.Errors; |
7 | 3 | using JsonApiDotNetCore.Queries.Expressions; |
8 | 4 | using JsonApiDotNetCore.Resources.Annotations; |
9 | 5 |
|
@@ -61,158 +57,6 @@ protected virtual IncludeExpression ParseInclude(ResourceType resourceType) |
61 | 57 | return treeRoot.ToExpression(); |
62 | 58 | } |
63 | 59 |
|
64 | | - private void ParseRelationshipChain(IncludeTreeNode treeRoot) |
65 | | - { |
66 | | - // A relationship name usually matches a single relationship, even when overridden in derived types. |
67 | | - // But in the following case, two relationships are matched on GET /shoppingBaskets?include=items: |
68 | | - // |
69 | | - // public abstract class ShoppingBasket : Identifiable<long> |
70 | | - // { |
71 | | - // } |
72 | | - // |
73 | | - // public sealed class SilverShoppingBasket : ShoppingBasket |
74 | | - // { |
75 | | - // [HasMany] |
76 | | - // public ISet<Article> Items { get; get; } |
77 | | - // } |
78 | | - // |
79 | | - // public sealed class PlatinumShoppingBasket : ShoppingBasket |
80 | | - // { |
81 | | - // [HasMany] |
82 | | - // public ISet<Product> Items { get; get; } |
83 | | - // } |
84 | | - // |
85 | | - // Now if the include chain has subsequent relationships, we need to scan both Items relationships for matches, |
86 | | - // which is why ParseRelationshipName returns a collection. |
87 | | - // |
88 | | - // The advantage of this unfolding is we don't require callers to upcast in relationship chains. The downside is |
89 | | - // that there's currently no way to include Products without Articles. We could add such optional upcast syntax |
90 | | - // in the future, if desired. |
91 | | - |
92 | | - ReadOnlyCollection<IncludeTreeNode> children = ParseRelationshipName([treeRoot]); |
93 | | - |
94 | | - while (TokenStack.TryPeek(out Token? nextToken) && nextToken.Kind == TokenKind.Period) |
95 | | - { |
96 | | - EatSingleCharacterToken(TokenKind.Period); |
97 | | - |
98 | | - children = ParseRelationshipName(children); |
99 | | - } |
100 | | - } |
101 | | - |
102 | | - private ReadOnlyCollection<IncludeTreeNode> ParseRelationshipName(IReadOnlyCollection<IncludeTreeNode> parents) |
103 | | - { |
104 | | - int position = GetNextTokenPositionOrEnd(); |
105 | | - |
106 | | - if (TokenStack.TryPop(out Token? token) && token.Kind == TokenKind.Text) |
107 | | - { |
108 | | - return LookupRelationshipName(token.Value!, parents, position); |
109 | | - } |
110 | | - |
111 | | - throw new QueryParseException("Relationship name expected.", position); |
112 | | - } |
113 | | - |
114 | | - private ReadOnlyCollection<IncludeTreeNode> LookupRelationshipName(string relationshipName, IReadOnlyCollection<IncludeTreeNode> parents, int position) |
115 | | - { |
116 | | - List<IncludeTreeNode> children = []; |
117 | | - HashSet<RelationshipAttribute> relationshipsFound = []; |
118 | | - |
119 | | - foreach (IncludeTreeNode parent in parents) |
120 | | - { |
121 | | - // Depending on the left side of the include chain, we may match relationships anywhere in the resource type hierarchy. |
122 | | - // This is compensated for when rendering the response, which substitutes relationships on base types with the derived ones. |
123 | | - HashSet<RelationshipAttribute> relationships = GetRelationshipsInConcreteTypes(parent.Relationship.RightType, relationshipName); |
124 | | - |
125 | | - if (relationships.Count > 0) |
126 | | - { |
127 | | - relationshipsFound.UnionWith(relationships); |
128 | | - |
129 | | - RelationshipAttribute[] relationshipsToInclude = relationships.Where(relationship => !relationship.IsIncludeBlocked()).ToArray(); |
130 | | - ReadOnlyCollection<IncludeTreeNode> affectedChildren = parent.EnsureChildren(relationshipsToInclude); |
131 | | - children.AddRange(affectedChildren); |
132 | | - } |
133 | | - } |
134 | | - |
135 | | - AssertRelationshipsFound(relationshipsFound, relationshipName, parents, position); |
136 | | - AssertAtLeastOneCanBeIncluded(relationshipsFound, relationshipName, position); |
137 | | - |
138 | | - return children.AsReadOnly(); |
139 | | - } |
140 | | - |
141 | | - private static HashSet<RelationshipAttribute> GetRelationshipsInConcreteTypes(ResourceType resourceType, string relationshipName) |
142 | | - { |
143 | | - HashSet<RelationshipAttribute> relationshipsToInclude = []; |
144 | | - |
145 | | - foreach (RelationshipAttribute relationship in resourceType.GetRelationshipsInTypeOrDerived(relationshipName)) |
146 | | - { |
147 | | - if (!relationship.LeftType.ClrType.IsAbstract) |
148 | | - { |
149 | | - relationshipsToInclude.Add(relationship); |
150 | | - } |
151 | | - |
152 | | - IncludeRelationshipsFromConcreteDerivedTypes(relationship, relationshipsToInclude); |
153 | | - } |
154 | | - |
155 | | - return relationshipsToInclude; |
156 | | - } |
157 | | - |
158 | | - private static void IncludeRelationshipsFromConcreteDerivedTypes(RelationshipAttribute relationship, HashSet<RelationshipAttribute> relationshipsToInclude) |
159 | | - { |
160 | | - foreach (ResourceType derivedType in relationship.LeftType.GetAllConcreteDerivedTypes()) |
161 | | - { |
162 | | - RelationshipAttribute relationshipInDerived = derivedType.GetRelationshipByPublicName(relationship.PublicName); |
163 | | - relationshipsToInclude.Add(relationshipInDerived); |
164 | | - } |
165 | | - } |
166 | | - |
167 | | - private static void AssertRelationshipsFound(HashSet<RelationshipAttribute> relationshipsFound, string relationshipName, |
168 | | - IReadOnlyCollection<IncludeTreeNode> parents, int position) |
169 | | - { |
170 | | - if (relationshipsFound.Count > 0) |
171 | | - { |
172 | | - return; |
173 | | - } |
174 | | - |
175 | | - ResourceType[] parentResourceTypes = parents.Select(parent => parent.Relationship.RightType).Distinct().ToArray(); |
176 | | - |
177 | | - bool hasDerivedTypes = parents.Any(parent => parent.Relationship.RightType.DirectlyDerivedTypes.Count > 0); |
178 | | - |
179 | | - string message = GetErrorMessageForNoneFound(relationshipName, parentResourceTypes, hasDerivedTypes); |
180 | | - throw new QueryParseException(message, position); |
181 | | - } |
182 | | - |
183 | | - private static string GetErrorMessageForNoneFound(string relationshipName, ResourceType[] parentResourceTypes, bool hasDerivedTypes) |
184 | | - { |
185 | | - var builder = new StringBuilder($"Relationship '{relationshipName}'"); |
186 | | - |
187 | | - if (parentResourceTypes.Length == 1) |
188 | | - { |
189 | | - builder.Append($" does not exist on resource type '{parentResourceTypes.First().PublicName}'"); |
190 | | - } |
191 | | - else |
192 | | - { |
193 | | - string typeNames = string.Join(", ", parentResourceTypes.Select(type => $"'{type.PublicName}'")); |
194 | | - builder.Append($" does not exist on any of the resource types {typeNames}"); |
195 | | - } |
196 | | - |
197 | | - builder.Append(hasDerivedTypes ? " or any of its derived types." : "."); |
198 | | - |
199 | | - return builder.ToString(); |
200 | | - } |
201 | | - |
202 | | - private void AssertAtLeastOneCanBeIncluded(HashSet<RelationshipAttribute> relationshipsFound, string relationshipName, int position) |
203 | | - { |
204 | | - if (relationshipsFound.All(relationship => relationship.IsIncludeBlocked())) |
205 | | - { |
206 | | - ResourceType resourceType = relationshipsFound.First().LeftType; |
207 | | - string message = $"Including the relationship '{relationshipName}' on '{resourceType}' is not allowed."; |
208 | | - |
209 | | - var exception = new QueryParseException(message, position); |
210 | | - string specificMessage = exception.GetMessageWithPosition(Source); |
211 | | - |
212 | | - throw new InvalidQueryStringParameterException("include", "The specified include is invalid.", specificMessage); |
213 | | - } |
214 | | - } |
215 | | - |
216 | 60 | private void ValidateMaximumIncludeDepth(IncludeExpression include, int position) |
217 | 61 | { |
218 | 62 | if (_options.MaximumIncludeDepth != null) |
@@ -245,71 +89,4 @@ private static void ThrowIfMaximumDepthExceeded(IncludeElementExpression include |
245 | 89 |
|
246 | 90 | parentChain.Pop(); |
247 | 91 | } |
248 | | - |
249 | | - private sealed class IncludeTreeNode |
250 | | - { |
251 | | - private readonly Dictionary<RelationshipAttribute, IncludeTreeNode> _children = []; |
252 | | - |
253 | | - public RelationshipAttribute Relationship { get; } |
254 | | - |
255 | | - private IncludeTreeNode(RelationshipAttribute relationship) |
256 | | - { |
257 | | - Relationship = relationship; |
258 | | - } |
259 | | - |
260 | | - public static IncludeTreeNode CreateRoot(ResourceType resourceType) |
261 | | - { |
262 | | - var relationship = new HiddenRootRelationshipAttribute(resourceType); |
263 | | - return new IncludeTreeNode(relationship); |
264 | | - } |
265 | | - |
266 | | - public ReadOnlyCollection<IncludeTreeNode> EnsureChildren(RelationshipAttribute[] relationships) |
267 | | - { |
268 | | - foreach (RelationshipAttribute relationship in relationships) |
269 | | - { |
270 | | - if (!_children.ContainsKey(relationship)) |
271 | | - { |
272 | | - var newChild = new IncludeTreeNode(relationship); |
273 | | - _children.Add(relationship, newChild); |
274 | | - } |
275 | | - } |
276 | | - |
277 | | - return _children.Where(pair => relationships.Contains(pair.Key)).Select(pair => pair.Value).ToArray().AsReadOnly(); |
278 | | - } |
279 | | - |
280 | | - public IncludeExpression ToExpression() |
281 | | - { |
282 | | - IncludeElementExpression element = ToElementExpression(); |
283 | | - |
284 | | - if (element.Relationship is HiddenRootRelationshipAttribute) |
285 | | - { |
286 | | - return element.Children.Count > 0 ? new IncludeExpression(element.Children) : IncludeExpression.Empty; |
287 | | - } |
288 | | - |
289 | | - return new IncludeExpression(ImmutableHashSet.Create(element)); |
290 | | - } |
291 | | - |
292 | | - private IncludeElementExpression ToElementExpression() |
293 | | - { |
294 | | - IImmutableSet<IncludeElementExpression> elementChildren = _children.Values.Select(child => child.ToElementExpression()).ToImmutableHashSet(); |
295 | | - return new IncludeElementExpression(Relationship, elementChildren); |
296 | | - } |
297 | | - |
298 | | - public override string ToString() |
299 | | - { |
300 | | - IncludeExpression include = ToExpression(); |
301 | | - return include.ToFullString(); |
302 | | - } |
303 | | - |
304 | | - private sealed class HiddenRootRelationshipAttribute : RelationshipAttribute |
305 | | - { |
306 | | - public HiddenRootRelationshipAttribute(ResourceType rightType) |
307 | | - { |
308 | | - ArgumentNullException.ThrowIfNull(rightType); |
309 | | - |
310 | | - RightType = rightType; |
311 | | - PublicName = "<<root>>"; |
312 | | - } |
313 | | - } |
314 | | - } |
315 | 92 | } |
0 commit comments