Skip to content

Commit

Permalink
Support embdedded resources that are not collections #22
Browse files Browse the repository at this point in the history
  • Loading branch information
johncmckim committed May 10, 2016
1 parent 42cc0a1 commit 0cd56b5
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public class FooController : ApiController {
.AddLinks(new Link[] {
new Link("other", "/api/foo/{fooId}/bar", replaceParameters: false)
})
.AddEmbeddedResource("baz", new { name = "bazza" }, new Link[] {
new Link("a", "test")
})
.AddEmbeddedCollection("bars", bars, new Link[] {
new Link("self", "/api/bar/{id}")
});
Expand Down
181 changes: 146 additions & 35 deletions src/Halcyon.Tests/HAL/HALResponseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@

namespace Halcyon.Tests.HAL {
public class HALResponseTests {
private const string TestHalProperties = "\"_links\":{\"a\":{\"href\":\"one\"}},\"_embedded\":{\"bars\":[{\"bar\":true}]}";
private const string TestHalPropertiesLinkArray = "\"_links\":{\"a\":[{\"href\":\"one\"},{\"href\":\"two\"}]},\"_embedded\":{\"bars\":[{\"bar\":true}]}";


[Theory]
[MemberData("GetCollectionModels")]
Expand All @@ -24,73 +21,187 @@ public class HALResponseTests {
}

[Theory]
[MemberData("GetTestModels")]
public void Has_Link(object model, Link linkAOne, Link linkATwo, Link linkB, string embeddedName, object[] embedded) {
[MemberData("GetLinkTestModels")]
public void Has_Link(object model, Link[] links, string expectedHal, string expectedPlain) {
var response = new HALResponse(model)
.AddLinks(linkAOne);
.AddLinks(links);

Assert.True(response.HasLink("a"));
Assert.True(response.HasLink("b"));
Assert.False(response.HasLink("c"));
}

[Fact]
public void JObject_To_JObject() {
string expected = GetExpectedJson("");

var personModel = PersonModel.GetTestModel();
var model = JObject.FromObject(personModel);

var response = new HALResponse(model);

var serializer = new JsonSerializer();
var jObject = response.ToJObject(serializer);

Assert.True(response.HasLink(linkAOne.Rel));
Assert.False(response.HasLink(linkB.Rel));
string actual = jObject.ToString(Formatting.None);
Assert.Equal(expected, actual);
}

[Theory]
[MemberData("GetTestModels")]
public void To_JObject(object model, Link linkAOne, Link linkATwo, Link linkB, string embeddedName, object[] embedded) {
string expected = GetExpectedJson(false);
[MemberData("GetLinkTestModels")]
public void Links_To_JObject(object model, Link[] links, string expectedHal, string expectedPlain) {
var serializer = new JsonSerializer();

var response = new HALResponse(model)
.AddLinks(links);

var jObject = response.ToJObject(serializer);

string actual = jObject.ToString(Formatting.None);
Assert.Equal(expectedHal, actual);
}

[Theory]
[MemberData("GetLinkTestModels")]
public void Links_To_ToPlainResponse(object model, Link[] links, string expectedHal, string expectedPlain) {
var serializer = new JsonSerializer();

var response = new HALResponse(model)
.AddLinks(linkAOne)
.AddEmbeddedCollection(embeddedName, embedded);
.AddLinks(links);

var jObject = response.ToPlainResponse(serializer);

string actual = jObject.ToString(Formatting.None);
Assert.Equal(expectedPlain, actual);
}

[Theory]
[MemberData("GetEmbeddedCollectionTestModels")]
public void Embedded_Resource_Collections_To_JObject(object model, Dictionary<string, IEnumerable<object>> embeddedCollections, string expectedHal, string expectedPlain) {
var serializer = new JsonSerializer();

var response = new HALResponse(model);
response.AddEmbeddedCollections(embeddedCollections);

var jObject = response.ToJObject(serializer);

string actual = jObject.ToString(Formatting.None);
Assert.Equal(expected, actual);
Assert.Equal(expectedHal, actual);
}

[Theory]
[MemberData("GetTestModels")]
public void To_JObject_Link_Array(object model, Link linkAOne, Link linkATwo, Link linkB, string embeddedName, object[] embedded) {
string expected = GetExpectedJson(true);
[MemberData("GetEmbeddedCollectionTestModels")]
public void Embedded_Resource_Collections_ToPlainResponse(object model, Dictionary<string, IEnumerable<object>> embeddedCollections, string expectedHal, string expectedPlain) {
var serializer = new JsonSerializer();

var response = new HALResponse(model);
response.AddEmbeddedCollections(embeddedCollections);

var jObject = response.ToPlainResponse(serializer);

string actual = jObject.ToString(Formatting.None);
Assert.Equal(expectedPlain, actual);
}

[Theory]
[MemberData("GetEmbeddedResourceTestModels")]
public void Embedded_Resource_To_JObject(object model, Dictionary<string, object> embeddedResources, string expectedHal, string expectedPlain) {
var serializer = new JsonSerializer();

var response = new HALResponse(model)
.AddLinks(linkAOne)
.AddLinks(linkATwo)
.AddEmbeddedCollection(embeddedName, embedded);
var response = new HALResponse(model);
response.AddEmbeddedResources(embeddedResources);

var jObject = response.ToJObject(serializer);

string actual = jObject.ToString(Formatting.None);
Assert.Equal(expected, actual);
Assert.Equal(expectedHal, actual);
}

private static string GetExpectedJson(bool linkArray) {
string testJson = linkArray ? TestHalPropertiesLinkArray : TestHalProperties;
var expectedPersonJson = "{" + PersonModel.TestModelJson + "," + testJson + "}";
return expectedPersonJson;
[Theory]
[MemberData("GetEmbeddedResourceTestModels")]
public void Embedded_Resource_To_ToPlainResponse(object model, Dictionary<string, object> embeddedResources, string expectedHal, string expectedPlain) {
var serializer = new JsonSerializer();

var response = new HALResponse(model);
response.AddEmbeddedResources(embeddedResources);

var jObject = response.ToPlainResponse(serializer);

string actual = jObject.ToString(Formatting.None);
Assert.Equal(expectedPlain, actual);
}

public static object[] GetTestModels() {
public static object[] GetLinkTestModels() {
var personModel = PersonModel.GetTestModel();

return new object[] {
new object[] {
personModel, new Link[] { new Link("a", "one"), new Link("b", "three") },
GetExpectedJson("\"_links\":{\"a\":{\"href\":\"one\"},\"b\":{\"href\":\"three\"}}"),
GetExpectedJson("")
},
new object[] {
personModel, new Link[] { new Link("a", "one"), new Link("a", "two"), new Link("b", "three") },
GetExpectedJson("\"_links\":{\"b\":{\"href\":\"three\"},\"a\":[{\"href\":\"one\"},{\"href\":\"two\"}]}"),
GetExpectedJson("")
}
};
}

var linkAOne = new Link("a", "one");
var linkATwo = new Link("a", "two");
var linkB = new Link("b", "two");
public static object[] GetEmbeddedCollectionTestModels() {
var personModel = PersonModel.GetTestModel();

string embeddedName = "bars";
var embedded = new[] {
new { bar = true }
return new object[] {
new object[] {
personModel, new Dictionary<string, IEnumerable<object>> {
{ "bars", new object[] { new { bar = true } } }
},
GetExpectedJson("\"_embedded\":{\"bars\":[{\"bar\":true}]}"),
GetExpectedJson("\"bars\":[{\"bar\":true}]")
},
new object[] {
personModel, new Dictionary<string, IEnumerable<object>> {
{
"bars", new object[] {
new { name = "one" },
new { name = "two" }
}
}
},
GetExpectedJson("\"_embedded\":{\"bars\":[{\"name\":\"one\"},{\"name\":\"two\"}]}"),
GetExpectedJson("\"bars\":[{\"name\":\"one\"},{\"name\":\"two\"}]")
},
};
}

public static object[] GetEmbeddedResourceTestModels() {
var personModel = PersonModel.GetTestModel();

return new object[] {
new object[] { personModel, linkAOne, linkATwo, linkB, embeddedName, embedded },
new object[] { JObject.FromObject(personModel), linkAOne, linkATwo, linkB, embeddedName, embedded },
new object[] {
personModel, new Dictionary<string, object> {
{ "bar", new { bar = true } }
},
GetExpectedJson("\"_embedded\":{\"bar\":{\"bar\":true}}"),
GetExpectedJson("\"bar\":{\"bar\":true}")
},
new object[] {
personModel, new Dictionary<string, object> {
{ "bar", new { name = "one" } },
{ "baz", new { name = "two" } }
},
GetExpectedJson("\"_embedded\":{\"bar\":{\"name\":\"one\"},\"baz\":{\"name\":\"two\"}}"),
GetExpectedJson("\"bar\":{\"name\":\"one\"},\"baz\":{\"name\":\"two\"}")
},
};
}

private static string GetExpectedJson(string halProperties) {
if(!String.IsNullOrWhiteSpace(halProperties)) halProperties = "," + halProperties;

var expectedPersonJson = "{" + PersonModel.TestModelJson + halProperties + "}";
return expectedPersonJson;
}

public static object[] GetCollectionModels() {
return new object[] {
Expand Down
40 changes: 26 additions & 14 deletions src/Halcyon/HAL/HALResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class HALResponse {

private readonly object model;
private readonly List<Link> links = new List<Link>();
private readonly Dictionary<string, IEnumerable<HALResponse>> embedded = new Dictionary<string, IEnumerable<HALResponse>>();
private readonly Dictionary<string, object> embedded = new Dictionary<string, object>();

public HALResponse(IHALModelConfig config) {
this.config = config ?? new HALModelConfig();
Expand Down Expand Up @@ -45,22 +45,22 @@ public HALResponse(object model, IHALModelConfig config = null)
return this;
}

public HALResponse AddEmbeddedResource(string name, HALResponse resource) {
embedded.Add(name, resource);
return this;
}

public HALResponse AddEmbeddedCollection(string name, IEnumerable<HALResponse> objects) {
embedded.Add(name, objects);
return this;
}

public object ToPlainResponse(JsonSerializer serializer, bool attachEmbedded = true) {
public JObject ToPlainResponse(JsonSerializer serializer, bool attachEmbedded = true) {
var output = GetBaseJObject(serializer);

if(this.embedded.Any()) {
var plainEmbedded = this.embedded.ToDictionary(
e => e.Key,
e => e.Value.Select(m => m.ToPlainResponse(serializer))
);

var embeddedObject = JObject.FromObject(plainEmbedded);
output.Merge(embeddedObject);
var embeddedOutput = EmbeddedToJObject((m) => m.ToPlainResponse(serializer));
output.Merge(embeddedOutput);
}

return output;
Expand Down Expand Up @@ -89,17 +89,29 @@ public HALResponse(object model, IHALModelConfig config = null)
}

if(this.embedded.Any()) {
var embeddedOutput = new JObject();
foreach(var embedPair in this.embedded) {
embeddedOutput.Add(embedPair.Key, new JArray(embedPair.Value.Select(m => m.ToJObject(serializer))));
}

var embeddedOutput = EmbeddedToJObject((m) => m.ToJObject(serializer));
output.Add(EmbeddedKey, embeddedOutput);
}

return output;
}

private JObject EmbeddedToJObject(Func<HALResponse, JObject> converter) {
var embeddedOutput = new JObject();
foreach(var embedPair in this.embedded) {

if(embedPair.Value is IEnumerable<HALResponse>) {
embeddedOutput.Add(embedPair.Key, JArray.FromObject(((IEnumerable<HALResponse>)embedPair.Value).Select(m => converter(m))));
} else if(embedPair.Value is HALResponse) {
embeddedOutput.Add(embedPair.Key, JObject.FromObject(converter((HALResponse)embedPair.Value)));
} else {
throw new NotImplementedException();
}
}

return embeddedOutput;
}

private JObject GetBaseJObject(JsonSerializer serializer) {
JObject output;

Expand Down
27 changes: 27 additions & 0 deletions src/Halcyon/HAL/HALResponseExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ public static class HALResponseExtensions {
return halModel.AddLinks(links);
}

public static HALResponse AddEmbeddedResource<T>(this HALResponse hyperMedia, string resourceName, T model, IEnumerable<Link> links = null) {
if(links == null) {
links = Enumerable.Empty<Link>();
}

var embedded = new HALResponse(model, hyperMedia.Config).AddLinks(links);
hyperMedia.AddEmbeddedResource(resourceName, embedded);

return hyperMedia;
}

public static HALResponse AddEmbeddedCollection<T>(this HALResponse hyperMedia, string collectionName, IEnumerable<T> model, IEnumerable<Link> links = null) {
if(links == null) {
links = Enumerable.Empty<Link>();
Expand All @@ -29,6 +40,22 @@ public static class HALResponseExtensions {
return hyperMedia;
}

public static HALResponse AddEmbeddedResources<T>(this HALResponse response, IEnumerable<KeyValuePair<string, T>> resources) {
foreach(var resource in resources) {
response.AddEmbeddedResource(resource.Key, resource.Value);
}

return response;
}

public static HALResponse AddEmbeddedCollections<T>(this HALResponse response, IEnumerable<KeyValuePair<string, IEnumerable<T>>> embeddedCollections) {
foreach(var embeddedCollection in embeddedCollections) {
response.AddEmbeddedCollection(embeddedCollection.Key, embeddedCollection.Value);
}

return response;
}

public static HALResponse AddEmbeddedCollections(this HALResponse response, IEnumerable<KeyValuePair<string, IEnumerable<HALResponse>>> embeddedCollections) {

foreach(var embeddedCollection in embeddedCollections) {
Expand Down

0 comments on commit 0cd56b5

Please sign in to comment.