Skip to content

Commit 66492a2

Browse files
committed
fix(GetOpProcessor): #340.1 not properly handling HasMany relationships
1 parent 6d2ccf5 commit 66492a2

File tree

3 files changed

+70
-8
lines changed

3 files changed

+70
-8
lines changed

Diff for: src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ public DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity, I
138138

139139
return data;
140140
}
141-
142141
private bool ShouldIncludeAttribute(AttrAttribute attr, object attributeValue)
143142
{
144143
return OmitNullValuedAttribute(attr, attributeValue) == false

Diff for: src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs

+34-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections;
12
using System.Collections.Generic;
23
using System.Linq;
34
using System.Threading.Tasks;
@@ -83,10 +84,10 @@ public async Task<Operation> ProcessAsync(Operation operation)
8384
};
8485

8586
operationResult.Data = string.IsNullOrWhiteSpace(operation.Ref.Id)
86-
? await GetAllAsync(operation)
87-
: string.IsNullOrWhiteSpace(operation.Ref.Relationship)
88-
? await GetByIdAsync(operation)
89-
: await GetRelationshipAsync(operation);
87+
? await GetAllAsync(operation)
88+
: string.IsNullOrWhiteSpace(operation.Ref.Relationship)
89+
? await GetByIdAsync(operation)
90+
: await GetRelationshipAsync(operation);
9091

9192
return operationResult;
9293
}
@@ -135,11 +136,38 @@ private async Task<object> GetRelationshipAsync(Operation operation)
135136
// when no generic parameter is available
136137
var relationshipType = _contextGraph.GetContextEntity(operation.GetResourceTypeName())
137138
.Relationships.Single(r => r.Is(operation.Ref.Relationship)).Type;
139+
138140
var relatedContextEntity = _jsonApiContext.ContextGraph.GetContextEntity(relationshipType);
139141

140-
var doc = _documentBuilder.GetData(relatedContextEntity, result as IIdentifiable); // TODO: if this is safe, then it should be cast in the GetRelationshipAsync call
142+
if (result == null)
143+
return null;
144+
145+
if (result is IIdentifiable singleResource)
146+
return GetData(relatedContextEntity, singleResource);
141147

142-
return doc;
148+
if (result is IEnumerable multipleResults)
149+
return GetData(relatedContextEntity, multipleResults);
150+
151+
throw new JsonApiException(500,
152+
$"An unexpected type was returned from '{_getRelationship.GetType()}.{nameof(IGetRelationshipService<T, TId>.GetRelationshipAsync)}'.",
153+
detail: $"Type '{result.GetType()} does not implement {nameof(IIdentifiable)} nor {nameof(IEnumerable<IIdentifiable>)}'");
154+
}
155+
156+
private DocumentData GetData(ContextEntity contextEntity, IIdentifiable singleResource)
157+
{
158+
return _documentBuilder.GetData(contextEntity, singleResource);
159+
}
160+
161+
private List<DocumentData> GetData(ContextEntity contextEntity, IEnumerable multipleResults)
162+
{
163+
var resources = new List<DocumentData>();
164+
foreach (var singleResult in multipleResults)
165+
{
166+
if (singleResult is IIdentifiable resource)
167+
resources.Add(_documentBuilder.GetData(contextEntity, resource));
168+
}
169+
170+
return resources;
143171
}
144172

145173
private TId GetReferenceId(Operation operation) => TypeHelper.ConvertType<TId>(operation.Ref.Id);

Diff for: test/OperationsExampleTests/Get/GetRelationshipTests.cs

+36-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class GetRelationshipTests : Fixture, IDisposable
1616
private readonly Faker _faker = new Faker();
1717

1818
[Fact]
19-
public async Task Can_Get_Article_Author()
19+
public async Task Can_Get_HasOne_Relationship()
2020
{
2121
// arrange
2222
var context = GetService<AppDbContext>();
@@ -48,5 +48,40 @@ public async Task Can_Get_Article_Author()
4848
Assert.Equal(author.Id.ToString(), resourceObject.Id);
4949
Assert.Equal("authors", resourceObject.Type);
5050
}
51+
52+
[Fact]
53+
public async Task Can_Get_HasMany_Relationship()
54+
{
55+
// arrange
56+
var context = GetService<AppDbContext>();
57+
var author = AuthorFactory.Get();
58+
var article = ArticleFactory.Get();
59+
article.Author = author;
60+
context.Articles.Add(article);
61+
context.SaveChanges();
62+
63+
var content = new
64+
{
65+
operations = new[] {
66+
new Dictionary<string, object> {
67+
{ "op", "get"},
68+
{ "ref", new { type = "authors", id = author.StringId, relationship = "articles" } }
69+
}
70+
}
71+
};
72+
73+
// act
74+
var (response, data) = await PatchAsync<OperationsDocument>("api/bulk", content);
75+
76+
// assert
77+
Assert.NotNull(response);
78+
Assert.NotNull(data);
79+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
80+
Assert.Single(data.Operations);
81+
82+
var resourceObject = data.Operations.Single().DataList.Single();
83+
Assert.Equal(article.Id.ToString(), resourceObject.Id);
84+
Assert.Equal("articles", resourceObject.Type);
85+
}
5186
}
5287
}

0 commit comments

Comments
 (0)