Skip to content

Commit ff2ec5f

Browse files
sergey-litvinov-workSergey Litvinov
and
Sergey Litvinov
authored
Updated IApiResourceProvider to care about relationship\include cases (#244)
Co-authored-by: Sergey Litvinov <[email protected]>
1 parent c508f7b commit ff2ec5f

9 files changed

+157
-7
lines changed

Saule/Resources/DefaultApiResourceProvider.cs

+6
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,11 @@ public virtual ApiResource Resolve(object dataObject)
2424
{
2525
return ApiResource;
2626
}
27+
28+
/// <inheritdoc/>
29+
public virtual ApiResource ResolveRelationship(object dataObject, ApiResource relationship)
30+
{
31+
return relationship;
32+
}
2733
}
2834
}

Saule/Resources/IApiResourceProvider.cs

+8
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,13 @@ public interface IApiResourceProvider
1111
/// <param name="dataObject">Data object that is serialized. If it's an array, then it will be called for each item in the array</param>
1212
/// <returns>ApiResource that should be used for specific data object</returns>
1313
ApiResource Resolve(object dataObject);
14+
15+
/// <summary>
16+
/// Returns ApiResource based on the data object and specific relationship
17+
/// </summary>
18+
/// <param name="dataObject">Data object that is serialized. If it's an array, then it will be called for each item in the array</param>
19+
/// <param name="relationship">Relationship resource for which we are resolving api resource</param>
20+
/// <returns>ApiResource that should be used for specific data object and relationship</returns>
21+
ApiResource ResolveRelationship(object dataObject, ApiResource relationship);
1422
}
1523
}

Saule/Serialization/ResourceGraph.cs

+10-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public IEnumerable<ResourceGraphNode> IncludedNodes
5252

5353
private void Build(
5454
object obj,
55-
ApiResource resource,
55+
ApiResource relatedResource,
5656
ResourceGraphPathSet includePaths,
5757
int depth,
5858
string propertyName = null)
@@ -73,10 +73,18 @@ private void Build(
7373
return;
7474
}
7575

76-
if (resource == null)
76+
ApiResource resource;
77+
78+
// if we are serializing top object, then we just resolve it based on api resource of the endpoint
79+
// but if are processing relationship's includes, then we need to resolve it based on the relationship itself
80+
if (relatedResource == null)
7781
{
7882
resource = _apiResourceProvider.Resolve(obj);
7983
}
84+
else
85+
{
86+
resource = _apiResourceProvider.ResolveRelationship(obj, relatedResource);
87+
}
8088

8189
// keys (type & id pair) uniquely identifier each resource in a compount document
8290
var key = new ResourceGraphNodeKey(obj, resource);

Saule/Serialization/ResourceSerializer.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,8 @@ private JToken SerializeRelationshipData(ResourceGraphNode node, ResourceGraphRe
424424

425425
foreach (var sourceObject in (IEnumerable)relationship.SourceObject ?? new JArray())
426426
{
427-
content.Add(JObject.FromObject(new ResourceGraphNodeKey(sourceObject, relationship.Relationship.RelatedResource)));
427+
var resource = _apiResourceProvider.ResolveRelationship(sourceObject, relationship.Relationship.RelatedResource);
428+
content.Add(JObject.FromObject(new ResourceGraphNodeKey(sourceObject, resource)));
428429
}
429430

430431
return content;

Tests/Controllers/ShapeController.cs

+18-1
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
namespace Tests.Controllers
88
{
9-
[ReturnsResource(typeof(ShapeResource))]
109
[RoutePrefix("api")]
1110
public class ShapeController : ApiController
1211
{
1312
[HttpGet]
1413
[Route("shapes")]
1514
[AllowsQuery]
15+
[ReturnsResource(typeof(ShapeResource))]
1616
public IEnumerable<Shape> GetAllShapes()
1717
{
1818
return new Shape[]
@@ -28,9 +28,26 @@ public IEnumerable<Shape> GetAllShapes()
2828

2929
[HttpGet]
3030
[Route("shape/{id}")]
31+
[ReturnsResource(typeof(ShapeResource))]
3132
public Shape GetShape(string id)
3233
{
3334
return GetAllShapes().FirstOrDefault(s => s.Id == id);
3435
}
36+
37+
[HttpGet]
38+
[Route("groups")]
39+
[AllowsQuery]
40+
[DisableDefaultIncluded]
41+
[ReturnsResource(typeof(GroupResource))]
42+
public IEnumerable<Group> GetGroup()
43+
{
44+
return new[]
45+
{
46+
new Group(true, "1")
47+
{
48+
Shapes = GetAllShapes().ToList()
49+
}
50+
};
51+
}
3552
}
3653
}

Tests/Integration/JsonApiFormatterInheritenceTests.cs

+78-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System.Collections;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using System.Net;
45
using System.Net.Http;
@@ -170,6 +171,60 @@ public async Task GetSpecificRectangle()
170171
ValidateRectangle(rectangle, "2");
171172
}
172173
}
174+
175+
[Fact(DisplayName = "Get a group and validation relationship")]
176+
public async Task GetGroup()
177+
{
178+
using (var server = CreateServer())
179+
{
180+
var client = server.GetClient();
181+
var result = await client.GetJsonResponseAsync("api/groups");
182+
_output.WriteLine(result.ToString());
183+
184+
var items = result["data"] as JArray;
185+
Assert.Equal(1, items.Count);
186+
187+
var group = items[0];
188+
Assert.Equal("group", group["type"]);
189+
Assert.Equal("1", group["id"]);
190+
Assert.Equal("Group 1", group["attributes"]["name"]);
191+
192+
var relationships = group["relationships"]["shapes"]["data"] as JArray;
193+
Assert.Equal(3, relationships.Count);
194+
Assert.Equal("circle", relationships[0]["type"]);
195+
Assert.Equal("1", relationships[0]["id"]);
196+
197+
Assert.Equal("rectangle", relationships[1]["type"]);
198+
Assert.Equal("2", relationships[1]["id"]);
199+
200+
Assert.Equal("circle", relationships[2]["type"]);
201+
Assert.Equal("3", relationships[2]["id"]);
202+
}
203+
}
204+
205+
[Fact(DisplayName = "Get a group with included shapes and validate types and shapes itself")]
206+
public async Task GetGroupWithIncludes()
207+
{
208+
using (var server = CreateServer())
209+
{
210+
var client = server.GetClient();
211+
var result = await client.GetJsonResponseAsync("api/groups?include=shapes");
212+
_output.WriteLine(result.ToString());
213+
214+
var items = result["data"] as JArray;
215+
var included = result["included"] as JArray;
216+
Assert.Equal(1, items.Count);
217+
Assert.Equal(3, included.Count);
218+
219+
var circle1 = included[0];
220+
var rectangle2 = included[1];
221+
var circle3 = included[2];
222+
223+
ValidateCircle(circle1,"1");
224+
ValidateRectangle(rectangle2, "2");
225+
ValidateCircle(circle3,"3");
226+
}
227+
}
173228

174229

175230
private static void ValidateRectangle(JToken rectangle, string expectedId)
@@ -222,18 +277,38 @@ public class ShapeApiResourceProvider : IApiResourceProvider
222277
{
223278
public ApiResource Resolve(object dataObject)
224279
{
225-
if (dataObject is Circle)
280+
var type = dataObject.GetType();
281+
282+
if (type.IsEnumerable() || type.IsArray)
283+
type = type.GetGenericTypeParameterOfCollection();
284+
285+
if (type == typeof(Group))
286+
{
287+
return new GroupResource();
288+
}
289+
290+
if (type == typeof(Circle))
226291
{
227292
return new CircleResource();
228293
}
229294

230-
if (dataObject is Rectangle)
295+
if (type == typeof(Rectangle))
231296
{
232297
return new RectangleResource();
233298
}
234299

235300
return new ShapeResource();
236301
}
302+
303+
public ApiResource ResolveRelationship(object dataObject, ApiResource relationship)
304+
{
305+
if (relationship is ShapeResource)
306+
{
307+
return Resolve(dataObject);
308+
}
309+
310+
return new GroupResource();
311+
}
237312
}
238313
}
239314
}

Tests/Models/Inheritance/Group.cs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Collections.Generic;
2+
3+
namespace Tests.Models.Inheritance
4+
{
5+
public class Group
6+
{
7+
public Group(bool prefill = false, string id = "123")
8+
{
9+
Id = id;
10+
if (!prefill) return;
11+
12+
Name = $"Group {id}";
13+
}
14+
15+
public string Id { get; set; }
16+
public string Name { get; set; }
17+
public List<Shape> Shapes { get; set; }
18+
}
19+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Saule;
2+
3+
namespace Tests.Models.Inheritance
4+
{
5+
public class GroupResource : ApiResource
6+
{
7+
public GroupResource()
8+
{
9+
WithId(nameof(Group.Id));
10+
Attribute(nameof(Group.Name));
11+
HasMany<ShapeResource>(nameof(Group.Shapes));
12+
}
13+
}
14+
}

Tests/Tests.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@
141141
<Compile Include="Helpers\Paths.cs" />
142142
<Compile Include="Models\Inheritance\Circle.cs" />
143143
<Compile Include="Models\Inheritance\CircleResource.cs" />
144+
<Compile Include="Models\Inheritance\Group.cs" />
145+
<Compile Include="Models\Inheritance\GroupResource.cs" />
144146
<Compile Include="Models\Inheritance\Rectangle.cs" />
145147
<Compile Include="Models\Inheritance\RectangleResource.cs" />
146148
<Compile Include="Models\Inheritance\Shape.cs" />

0 commit comments

Comments
 (0)