|
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