Skip to content

Commit

Permalink
Improved the WebLink type so that it includes all the common features…
Browse files Browse the repository at this point in the history
…, and separates out the 'rel' which is a feature of its usage.
  • Loading branch information
mwadams committed Sep 21, 2019
1 parent 768810f commit 5787876
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 100 deletions.
28 changes: 15 additions & 13 deletions Solutions/Menes.Abstractions/Menes/Hal/HalDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,14 @@ public IEnumerable<HalDocument> EmbeddedResources
/// <summary>
/// Removes an embedded resource based on its href.
/// </summary>
/// <param name="rel">The relation type of the link.</param>
/// <param name="resourceLink">The link for the embedded resource from the links collection.</param>
/// <remarks>This finds the embdedded resources that match the rel type of the resource link, whose self link href matches the href of the resource link.</remarks>
public void RemoveEmbeddedResource(WebLink resourceLink)
public void RemoveEmbeddedResource(string rel, WebLink resourceLink)
{
this.GetEmbeddedResourcesForRelation(resourceLink.Rel)
this.GetEmbeddedResourcesForRelation(rel)
.Where(r => r.GetLinksForRelation("self").Any(link => link.Href == resourceLink.Href)).ToList()
.ForEach(r => this.RemoveEmbeddedResource(resourceLink.Rel, r));
.ForEach(r => this.RemoveEmbeddedResource(rel, r));
}

/// <summary>
Expand All @@ -108,11 +109,12 @@ public void SetProperties<T>(T properties)
/// <summary>
/// Determine if the document contains an embedded resource for the provided link.
/// </summary>
/// <param name="rel">The relation type of the link.</param>
/// <param name="resourceLink">The link with which to compare.</param>
/// <returns>True if there is a resource whose self link matches the resource link.</returns>
public bool HasEmbeddedResourceForLink(WebLink resourceLink)
public bool HasEmbeddedResourceForLink(string rel, WebLink resourceLink)
{
return this.GetEmbeddedResourcesForRelation(resourceLink.Rel)
return this.GetEmbeddedResourcesForRelation(rel)
.Any(r => r.GetLinksForRelation("self").Any(link => link.Href == resourceLink.Href));
}

Expand Down Expand Up @@ -146,20 +148,20 @@ public bool TryGetProperties<T>(out T result)
}

/// <inheritdoc/>
public void AddLink(WebLink link)
public void AddLink(string rel, WebLink link)
{
List<WebLink> linkList = this.EnsureListForLink(link);
List<WebLink> linkList = this.EnsureListForLink(rel);
linkList.Add(link);
}

/// <inheritdoc/>
public void RemoveLink(WebLink link)
public void RemoveLink(string rel, WebLink link)
{
List<WebLink> linkList = this.EnsureListForLink(link);
List<WebLink> linkList = this.EnsureListForLink(rel);
linkList.Remove(link);
if (linkList.Count == 0)
{
this.links.Remove(link.Rel);
this.links.Remove(rel);
}
}

Expand Down Expand Up @@ -262,14 +264,14 @@ public IEnumerable<HalDocument> GetEmbeddedResourcesForRelation(string rel)
yield break;
}

private List<WebLink> EnsureListForLink(WebLink link)
private List<WebLink> EnsureListForLink(string rel)
{
this.EnsureLinks();

if (!this.links.TryGetValue(link.Rel, out List<WebLink> linkList))
if (!this.links.TryGetValue(rel, out List<WebLink> linkList))
{
linkList = new List<WebLink>();
this.links.Add(link.Rel, linkList);
this.links.Add(rel, linkList);
}

return linkList;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
var jobject = JObject.Load(reader);
HalDocument halDocument = this.halDocumentFactory.CreateHalDocument();

DeserializeLinks(jobject, halDocument);

DeserializeLinks(serializer, jobject, halDocument);
DeserializeEmbeddedResources(serializer, jobject, halDocument);

jobject.Remove("_embedded");
Expand All @@ -66,7 +65,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
var halDocument = (HalDocument)value;
JObject result = (JObject)halDocument.Properties?.DeepClone() ?? new JObject();

SerializeLinks(halDocument, result);
SerializeLinks(halDocument, result, serializer);
SerializeEmbeddedResources(halDocument, result, serializer);

result.WriteTo(writer);
Expand All @@ -79,7 +78,7 @@ public override HalDocument Create(Type objectType)
return null;
}

private static void SerializeLinks(HalDocument halDocument, JObject result)
private static void SerializeLinks(HalDocument halDocument, JObject result, JsonSerializer serializer)
{
var linksObject = new JObject();

Expand All @@ -93,12 +92,12 @@ private static void SerializeLinks(HalDocument halDocument, JObject result)

if (links.Length == 1)
{
linksObject.Add(relation, GetJObjectFor(links[0]));
linksObject.Add(relation, JObject.FromObject(links[0], serializer));
}
else
{
var linksArray = new JArray();
links.ForEach(link => linksArray.Add(GetJObjectFor(link)));
links.ForEach(link => linksArray.Add(JObject.FromObject(links[0], serializer)));
linksObject.Add(relation, linksArray);
}
}
Expand Down Expand Up @@ -139,42 +138,22 @@ private static void SerializeEmbeddedResources(HalDocument halDocument, JObject
}
}

private static JObject GetJObjectFor(WebLink webLink)
{
var result = new JObject
{
{ "href", webLink.Href },
};

if (!string.IsNullOrEmpty(webLink.Name))
{
result.Add("name", webLink.Name);
}

if (webLink.IsTemplated.HasValue)
{
result.Add("isTemplated", webLink.IsTemplated.Value);
}

return result;
}

private static void DeserializeLinks(JObject jobject, HalDocument halDocument)
private static void DeserializeLinks(JsonSerializer serializer, JObject jobject, HalDocument halDocument)
{
foreach (KeyValuePair<string, JToken> link in (JObject)jobject["_links"])
{
if (link.Value is JArray array)
{
foreach (JToken linkItem in array)
{
var weblink = new WebLink(link.Key, (string)linkItem["href"], (string)linkItem["name"], (bool?)linkItem["isTemplated"]);
halDocument.AddLink(weblink);
WebLink weblink = linkItem.ToObject<WebLink>(serializer);
halDocument.AddLink(link.Key, weblink);
}
}
else
{
var weblink = new WebLink(link.Key, (string)link.Value["href"], (string)link.Value["name"], (bool?)link.Value["isTemplated"]);
halDocument.AddLink(weblink);
WebLink weblink = link.Value.ToObject<WebLink>(serializer);
halDocument.AddLink(link.Key, weblink);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public OpenApiWebLink Resolve(object owner, string relationType, params (string,
if (this.linkOperationMapper.TryGetOperationId(owner, relationType, out string operationId))
{
ResolvedOperationRequestInfo operation = this.templateProvider.GetResolvedOperationRequestInfo(operationId, parameters);
return new OpenApiWebLink(relationType, operationId, operation.Uri, operation.OperationType);
return new OpenApiWebLink(operationId, operation.Uri, operation.OperationType);
}

ContentFactory.TryGetContentType(owner, out string contentType);
Expand All @@ -48,7 +48,7 @@ public OpenApiWebLink Resolve(object owner, string relationType, string context,
if (this.linkOperationMapper.TryGetOperationId(owner, relationType, context, out string operationId))
{
ResolvedOperationRequestInfo operation = this.templateProvider.GetResolvedOperationRequestInfo(operationId, parameters);
return new OpenApiWebLink(relationType, operationId, operation.Uri, operation.OperationType);
return new OpenApiWebLink(operationId, operation.Uri, operation.OperationType);
}

ContentFactory.TryGetContentType(owner, out string contentType);
Expand All @@ -61,7 +61,7 @@ public OpenApiWebLink Resolve(object owner, string relationType, string context,
public OpenApiWebLink Resolve(string operationId, string relationType, params (string, object)[] parameters)
{
ResolvedOperationRequestInfo operation = this.templateProvider.GetResolvedOperationRequestInfo(operationId, parameters);
return new OpenApiWebLink(relationType, operationId, operation.Uri, operation.OperationType);
return new OpenApiWebLink(operationId, operation.Uri, operation.OperationType);
}
}
}
6 changes: 4 additions & 2 deletions Solutions/Menes.Abstractions/Menes/Links/ILinkCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ public interface ILinkCollection
/// <summary>
/// Adds a link to the collection.
/// </summary>
/// <param name="rel">The relation type of the link.</param>
/// <param name="link">The link to add.</param>
void AddLink(WebLink link);
void AddLink(string rel, WebLink link);

/// <summary>
/// Enumerate the relations in the link collection.
Expand All @@ -38,7 +39,8 @@ public interface ILinkCollection
/// <summary>
/// Removes a link from a <c>_links</c> collection.
/// </summary>
/// <param name="rel">The relation type of the link.</param>
/// <param name="link">The link to remove.</param>
void RemoveLink(WebLink link);
void RemoveLink(string rel, WebLink link);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static class LinkCollectionExtensions
public static OpenApiWebLink ResolveAndAdd(this ILinkCollection linkCollection, IOpenApiWebLinkResolver linkResolver, object owner, string rel, params (string, object)[] parameters)
{
OpenApiWebLink link = linkResolver.Resolve(owner, rel, parameters);
linkCollection.AddLink(link);
linkCollection.AddLink(rel, link);
return link;
}

Expand All @@ -37,8 +37,8 @@ public static OpenApiWebLink ResolveAndAdd(this ILinkCollection linkCollection,
/// <returns>A new <see cref="WebLink"/> which has been added to the given link collection.</returns>
public static OpenApiWebLink ResolveAndAdd(this ILinkCollection linkCollection, IOpenApiWebLinkResolver linkResolver, object owner, string rel, string context, params (string, object)[] parameters)
{
OpenApiWebLink link = linkResolver.Resolve(owner, rel, context, parameters);
linkCollection.AddLink(link);
OpenApiWebLink link = linkResolver.Resolve(owner, context, parameters);
linkCollection.AddLink(rel, link);
return link;
}

Expand All @@ -53,8 +53,8 @@ public static OpenApiWebLink ResolveAndAdd(this ILinkCollection linkCollection,
/// <returns>A new <see cref="WebLink"/> which has been added to the given link collection.</returns>
public static OpenApiWebLink ResolveAndAdd(this ILinkCollection linkCollection, IOpenApiWebLinkResolver linkResolver, string operationId, string rel, params (string, object)[] parameters)
{
OpenApiWebLink link = linkResolver.Resolve(rel, operationId, parameters);
linkCollection.AddLink(link);
OpenApiWebLink link = linkResolver.Resolve(operationId, rel, parameters);
linkCollection.AddLink(rel, link);
return link;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static async Task RemoveForbiddenLinksAsync(this IOpenApiAccessChecker th
// 1. The link itself.
// 2. The HalDocument(s) to which it belongs - it is possible that we may encounter the same link twice and we need to be able to deal with that
// 3. The link relation name.
var linkMap = new Dictionary<OpenApiWebLink, List<HalDocument>>();
var linkMap = new Dictionary<(string, OpenApiWebLink), List<HalDocument>>();

AddHalDocumentLinksToMap(
target,
Expand All @@ -48,7 +48,7 @@ public static async Task RemoveForbiddenLinksAsync(this IOpenApiAccessChecker th
// Build a second map of operation descriptors (needed to invoke the access policy check) to our OpenApiWebLinks.
var operationDescriptorMap = linkMap
.Keys
.Select(link => (Descriptor: new AccessCheckOperationDescriptor(link.Href, link.OperationId, link.OperationType.ToString().ToLowerInvariant()), Link: link))
.Select(link => (Descriptor: new AccessCheckOperationDescriptor(link.Item2.Href, link.Item2.OperationId, link.Item2.OperationType.ToString().ToLowerInvariant()), Link: link))
.GroupBy(x => x.Descriptor)
.ToDictionary(descriptor => descriptor.Key, descriptor => descriptor.Select(link => link.Link).ToArray());

Expand All @@ -60,35 +60,38 @@ public static async Task RemoveForbiddenLinksAsync(this IOpenApiAccessChecker th
// Now use the results of the access policy check to remove links/documents that don't belong because the access policy denies them.
foreach (KeyValuePair<AccessCheckOperationDescriptor, AccessControlPolicyResult> accessCheckResult in accessCheckResults.Where(result => !result.Value.Allow))
{
foreach (OpenApiWebLink link in operationDescriptorMap[accessCheckResult.Key])
foreach ((string, OpenApiWebLink) link in operationDescriptorMap[accessCheckResult.Key])
{
foreach (HalDocument document in linkMap[link])
{
document.RemoveLink(link);
document.RemoveLink(link.Item1, link.Item2);

// Also remove from embedded resources if present.
document.RemoveEmbeddedResource(link);
document.RemoveEmbeddedResource(link.Item1, link.Item2);
}
}
}
}

private static void AddHalDocumentLinksToMap(HalDocument target, Dictionary<OpenApiWebLink, List<HalDocument>> linkMap, bool recursive, bool unsafeChecking)
private static void AddHalDocumentLinksToMap(HalDocument target, Dictionary<(string, OpenApiWebLink), List<HalDocument>> linkMap, bool recursive, bool unsafeChecking)
{
foreach (OpenApiWebLink current in target.Links.OfType<OpenApiWebLink>())
foreach (string rel in target.GetLinkRelations())
{
if (unsafeChecking && ShouldSkipInUnsafeMode(current, target))
foreach (OpenApiWebLink current in target.GetLinksForRelation(rel))
{
continue;
}
if (unsafeChecking && ShouldSkipInUnsafeMode(rel, current, target))
{
continue;
}

if (!linkMap.TryGetValue(current, out List<HalDocument> documents))
{
documents = new List<HalDocument>();
linkMap.Add(current, documents);
}
if (!linkMap.TryGetValue((rel, current), out List<HalDocument> documents))
{
documents = new List<HalDocument>();
linkMap.Add((rel, current), documents);
}

documents.Add(target);
documents.Add(target);
}
}

if (recursive)
Expand All @@ -97,9 +100,9 @@ private static void AddHalDocumentLinksToMap(HalDocument target, Dictionary<Open
}
}

private static bool ShouldSkipInUnsafeMode(WebLink link, HalDocument target)
private static bool ShouldSkipInUnsafeMode(string rel, WebLink link, HalDocument target)
{
return (link.Rel == "self") || (target?.HasEmbeddedResourceForLink(link) ?? false);
return (rel == "self") || (target?.HasEmbeddedResourceForLink(rel, link) ?? false);
}
}
}
9 changes: 4 additions & 5 deletions Solutions/Menes.Abstractions/Menes/Links/OpenApiWebLink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ public class OpenApiWebLink : WebLink
/// <summary>
/// Initializes a new instance of the <see cref="OpenApiWebLink"/> struct.
/// </summary>
/// <param name="rel">The <see cref="WebLink.Rel"/>.</param>
/// <param name="operationId">The <see cref="OperationId"/>.</param>
/// <param name="href">The <see cref="WebLink.Href"/>.</param>
/// <param name="operationType">The <see cref="OperationType"/>.</param>
public OpenApiWebLink(string rel, string operationId, string href, OperationType operationType)
: base(rel, href)
public OpenApiWebLink(string operationId, string href, OperationType operationType)
: base(href)
{
this.OperationType = operationType;
this.OperationId = operationId;
Expand Down Expand Up @@ -59,7 +58,7 @@ public override bool Equals(object obj)
{
if (obj is OpenApiWebLink link)
{
return (this.Href, this.Rel, this.OperationId, this.OperationType) == (link.Href, link.Rel, link.OperationId, link.OperationType);
return (this.Href, this.OperationId, this.OperationType) == (link.Href, link.OperationId, link.OperationType);
}

return false;
Expand All @@ -68,7 +67,7 @@ public override bool Equals(object obj)
/// <inheritdoc/>
public override int GetHashCode()
{
return (this.Href, this.Rel, this.OperationId, this.OperationType).GetHashCode();
return (this.Href, this.OperationId, this.OperationType).GetHashCode();
}
}
}
Loading

0 comments on commit 5787876

Please sign in to comment.