Skip to content

Commit 9d64c7f

Browse files
author
Chris Santero
committed
support data member in to-many link relationship objects
1 parent 11af2d2 commit 9d64c7f

9 files changed

Lines changed: 195 additions & 13 deletions
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"posts": [
3+
{
4+
"id": "202",
5+
"links": {
6+
"tags": {
7+
"data": []
8+
}
9+
}
10+
}
11+
]
12+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"posts": [
3+
{
4+
"id": "202",
5+
"links": {
6+
"tags": {
7+
"data": [
8+
{
9+
"id": "301",
10+
"type": "tags"
11+
},
12+
{
13+
"id": "303",
14+
"type": "tags"
15+
}
16+
]
17+
}
18+
}
19+
}
20+
]
21+
}

JSONAPI.EntityFramework.Tests/Acceptance/Fixtures/Posts/Responses/PutWithMissingToManyIdsResponse.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"id": "{{SOME_GUID}}",
55
"status": "500",
66
"title": "JSONAPI.Json.JsonApiFormatter+BadRequestException",
7-
"detail": "Nothing was specified for the `ids` property.",
7+
"detail": "If `data` is not specified, then `ids` must be specified.",
88
"stackTrace": "{{STACK_TRACE}}",
99
"inner": null
1010
}

JSONAPI.EntityFramework.Tests/Acceptance/Fixtures/Posts/Responses/PutWithMissingToManyTypeResponse.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"id": "{{SOME_GUID}}",
55
"status": "500",
66
"title": "JSONAPI.Json.JsonApiFormatter+BadRequestException",
7-
"detail": "Nothing was specified for the `type` property.",
7+
"detail": "If `data` is not specified, then `type` must be specified.",
88
"stackTrace": "{{STACK_TRACE}}",
99
"inner": null
1010
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"posts": [
3+
{
4+
"id": "202",
5+
"title": "Post 2",
6+
"content": "Post 2 content",
7+
"created": "2015-02-05T08:10:00+00:00",
8+
"links": {
9+
"author": "401",
10+
"comments": [ "104" ],
11+
"tags": [ ]
12+
}
13+
}
14+
]
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"posts": [
3+
{
4+
"id": "202",
5+
"title": "Post 2",
6+
"content": "Post 2 content",
7+
"created": "2015-02-05T08:10:00+00:00",
8+
"links": {
9+
"author": "401",
10+
"comments": [ "104" ],
11+
"tags": [ "303", "301" ]
12+
}
13+
}
14+
]
15+
}

JSONAPI.EntityFramework.Tests/Acceptance/PostsTests.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,66 @@ public async Task PutWithToManyUpdate()
152152
}
153153
}
154154

155+
[TestMethod]
156+
[DeploymentItem(@"Acceptance\Data\Comment.csv", @"Acceptance\Data")]
157+
[DeploymentItem(@"Acceptance\Data\Post.csv", @"Acceptance\Data")]
158+
[DeploymentItem(@"Acceptance\Data\PostTagLink.csv", @"Acceptance\Data")]
159+
[DeploymentItem(@"Acceptance\Data\Tag.csv", @"Acceptance\Data")]
160+
[DeploymentItem(@"Acceptance\Data\User.csv", @"Acceptance\Data")]
161+
public async Task PutWithToManyHomogeneousDataUpdate()
162+
{
163+
using (var effortConnection = GetEffortConnection())
164+
{
165+
var response = await SubmitPut(effortConnection, "posts/202", @"Acceptance\Fixtures\Posts\Requests\PutWithToManyHomogeneousDataUpdateRequest.json");
166+
167+
response.StatusCode.Should().Be(HttpStatusCode.OK);
168+
await AssertResponseContent(response, @"Acceptance\Fixtures\Posts\Responses\PutWithToManyHomogeneousDataUpdateResponse.json");
169+
170+
using (var dbContext = new TestDbContext(effortConnection, false))
171+
{
172+
var allPosts = dbContext.Posts.Include(p => p.Tags).ToArray();
173+
allPosts.Length.Should().Be(4);
174+
var actualPost = allPosts.First(t => t.Id == "202");
175+
actualPost.Id.Should().Be("202");
176+
actualPost.Title.Should().Be("Post 2");
177+
actualPost.Content.Should().Be("Post 2 content");
178+
actualPost.Created.Should().Be(new DateTimeOffset(2015, 02, 05, 08, 10, 0, new TimeSpan(0)));
179+
actualPost.AuthorId.Should().Be("401");
180+
actualPost.Tags.Select(t => t.Id).Should().BeEquivalentTo("301", "303");
181+
}
182+
}
183+
}
184+
185+
[TestMethod]
186+
[DeploymentItem(@"Acceptance\Data\Comment.csv", @"Acceptance\Data")]
187+
[DeploymentItem(@"Acceptance\Data\Post.csv", @"Acceptance\Data")]
188+
[DeploymentItem(@"Acceptance\Data\PostTagLink.csv", @"Acceptance\Data")]
189+
[DeploymentItem(@"Acceptance\Data\Tag.csv", @"Acceptance\Data")]
190+
[DeploymentItem(@"Acceptance\Data\User.csv", @"Acceptance\Data")]
191+
public async Task PutWithToManyEmptyDataUpdate()
192+
{
193+
using (var effortConnection = GetEffortConnection())
194+
{
195+
var response = await SubmitPut(effortConnection, "posts/202", @"Acceptance\Fixtures\Posts\Requests\PutWithToManyEmptyDataUpdateRequest.json");
196+
197+
response.StatusCode.Should().Be(HttpStatusCode.OK);
198+
await AssertResponseContent(response, @"Acceptance\Fixtures\Posts\Responses\PutWithToManyEmptyDataUpdateResponse.json");
199+
200+
using (var dbContext = new TestDbContext(effortConnection, false))
201+
{
202+
var allPosts = dbContext.Posts.Include(p => p.Tags).ToArray();
203+
allPosts.Length.Should().Be(4);
204+
var actualPost = allPosts.First(t => t.Id == "202");
205+
actualPost.Id.Should().Be("202");
206+
actualPost.Title.Should().Be("Post 2");
207+
actualPost.Content.Should().Be("Post 2 content");
208+
actualPost.Created.Should().Be(new DateTimeOffset(2015, 02, 05, 08, 10, 0, new TimeSpan(0)));
209+
actualPost.AuthorId.Should().Be("401");
210+
actualPost.Tags.Should().BeEmpty();
211+
}
212+
}
213+
}
214+
155215
[TestMethod]
156216
[DeploymentItem(@"Acceptance\Data\Comment.csv", @"Acceptance\Data")]
157217
[DeploymentItem(@"Acceptance\Data\Post.csv", @"Acceptance\Data")]

JSONAPI.EntityFramework.Tests/JSONAPI.EntityFramework.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@
158158
<EmbeddedResource Include="Acceptance\Fixtures\Posts\Requests\PutWithArrayRelationshipValueRequest.json" />
159159
<EmbeddedResource Include="Acceptance\Fixtures\Posts\Responses\PutWithNullToOneUpdateResponse.json" />
160160
<EmbeddedResource Include="Acceptance\Fixtures\Posts\Requests\PutWithNullToOneUpdateRequest.json" />
161+
<EmbeddedResource Include="Acceptance\Fixtures\Posts\Requests\PutWithToManyHomogeneousDataUpdateRequest.json" />
162+
<EmbeddedResource Include="Acceptance\Fixtures\Posts\Responses\PutWithToManyHomogeneousDataUpdateResponse.json" />
163+
<EmbeddedResource Include="Acceptance\Fixtures\Posts\Responses\PutWithToManyEmptyDataUpdateResponse.json" />
164+
<EmbeddedResource Include="Acceptance\Fixtures\Posts\Requests\PutWithToManyEmptyDataUpdateRequest.json" />
161165
<None Include="App.Config">
162166
<SubType>Designer</SubType>
163167
</None>

JSONAPI/Json/JsonApiFormatter.cs

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,7 @@ private void DeserializeLinkedResources(object obj, Stream readStream, JsonReade
808808

809809
JArray ids = null;
810810
string resourceType = null;
811+
JArray relatedObjects = null;
811812

812813
while (reader.Read())
813814
{
@@ -823,22 +824,32 @@ private void DeserializeLinkedResources(object obj, Stream readStream, JsonReade
823824

824825
if (propName == "ids")
825826
{
827+
if (reader.TokenType != JsonToken.StartArray)
828+
throw new BadRequestException("The value of `ids` must be an array.");
829+
826830
ids = JArray.Load(reader);
827831
}
828832
else if (propName == "type")
829833
{
834+
if (reader.TokenType != JsonToken.String)
835+
throw new BadRequestException("Unexpected value for `type`: " + reader.TokenType);
836+
830837
resourceType = (string)reader.Value;
831838
}
839+
else if (propName == "data")
840+
{
841+
if (reader.TokenType != JsonToken.StartArray)
842+
throw new BadRequestException("Unexpected value for `data`: " + reader.TokenType);
843+
844+
relatedObjects = JArray.Load(reader);
845+
}
846+
else
847+
{
848+
throw new BadRequestException("Unexpected property name: " + propName);
849+
}
832850
}
833851

834-
// According to the spec, the ids must be specified
835-
if (ids == null)
836-
throw new BadRequestException("Nothing was specified for the `ids` property.");
837-
838-
// We aren't doing anything with this value for now, but it needs to be present in the request payload.
839-
// We will need to reference it to properly support polymorphism.
840-
if (resourceType == null)
841-
throw new BadRequestException("Nothing was specified for the `type` property.");
852+
var relatedStubs = new List<object>();
842853

843854
Type relType;
844855
if (prop.PropertyType.IsGenericType)
@@ -851,6 +862,51 @@ private void DeserializeLinkedResources(object obj, Stream readStream, JsonReade
851862
relType = prop.PropertyType.GetElementType();
852863
}
853864

865+
// According to the spec, either the type and ids or data must be specified
866+
if (relatedObjects != null)
867+
{
868+
if (ids != null)
869+
throw new BadRequestException("If `data` is specified, then `ids` may not be.");
870+
871+
if (resourceType != null)
872+
throw new BadRequestException("If `data` is specified, then `type` may not be.");
873+
874+
foreach (var relatedObject in relatedObjects)
875+
{
876+
if (!(relatedObject is JObject))
877+
throw new BadRequestException("Each element in the `data` array must be an object.");
878+
879+
var relatedObjectType = relatedObject["type"] as JValue;
880+
if (relatedObjectType == null || relatedObjectType.Type != JTokenType.String)
881+
throw new BadRequestException("Each element in the `data` array must have a string value for the key `type`.");
882+
883+
var relatedObjectId = relatedObject["id"] as JValue;
884+
if (relatedObjectId == null || relatedObjectId.Type != JTokenType.String)
885+
throw new BadRequestException("Each element in the `data` array must have a string value for the key `id`.");
886+
887+
var relatedObjectIdValue = relatedObjectId.Value<string>();
888+
if (string.IsNullOrWhiteSpace(relatedObjectIdValue))
889+
throw new BadRequestException("The value for `id` must be specified.");
890+
891+
var stub = GetById(relType, relatedObjectIdValue);
892+
relatedStubs.Add(stub);
893+
}
894+
}
895+
else if (ids == null)
896+
{
897+
throw new BadRequestException("If `data` is not specified, then `ids` must be specified.");
898+
}
899+
else if (resourceType == null)
900+
{
901+
// We aren't doing anything with this value for now, but it needs to be present in the request payload.
902+
// We will need to reference it to properly support polymorphism.
903+
throw new BadRequestException("If `data` is not specified, then `type` must be specified.");
904+
}
905+
else
906+
{
907+
relatedStubs.AddRange(ids.Select(token => GetById(relType, token.ToObject<string>())));
908+
}
909+
854910
IEnumerable<Object> hmrel = (IEnumerable<Object>)prop.GetValue(obj, null);
855911
if (hmrel == null)
856912
{
@@ -893,10 +949,9 @@ private void DeserializeLinkedResources(object obj, Stream readStream, JsonReade
893949
Type hmtype = hmrel.GetType();
894950
MethodInfo add = hmtype.GetMethod("Add");
895951

896-
foreach (JToken token in ids)
952+
foreach (var stub in relatedStubs)
897953
{
898-
//((ICollection<object>)prop.GetValue(obj, null)).Add(Activator.CreateInstance(relType));
899-
add.Invoke(hmrel, new object[] { this.GetById(relType, token.ToObject<string>()) });
954+
add.Invoke(hmrel, new [] { stub });
900955
}
901956

902957
prop.SetValue(obj, hmrel);

0 commit comments

Comments
 (0)