Skip to content

Fixed invalid top-level self link #672

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -15,9 +15,10 @@ public class RequestSerializer : BaseDocumentBuilder, IRequestSerializer
{
private Type _currentTargetedResource;
private readonly IResourceGraph _resourceGraph;

public RequestSerializer(IResourceGraph resourceGraph,
IResourceObjectBuilder resourceObjectBuilder)
: base(resourceObjectBuilder, resourceGraph)
: base(resourceObjectBuilder)
{
_resourceGraph = resourceGraph;
}
Expand Down Expand Up @@ -96,4 +97,4 @@ private List<RelationshipAttribute> GetRelationshipsToSerialize(IIdentifiable en
return RelationshipsToSerialize.ToList();
}
}
}
}
5 changes: 2 additions & 3 deletions src/JsonApiDotNetCore/Serialization/Common/DocumentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ namespace JsonApiDotNetCore.Serialization
/// </summary>
public abstract class BaseDocumentBuilder
{
protected readonly IResourceContextProvider _provider;
protected readonly IResourceObjectBuilder _resourceObjectBuilder;
protected BaseDocumentBuilder(IResourceObjectBuilder resourceObjectBuilder, IResourceContextProvider provider)

protected BaseDocumentBuilder(IResourceObjectBuilder resourceObjectBuilder)
{
_resourceObjectBuilder = resourceObjectBuilder;
_provider = provider;
}

/// <summary>
Expand Down
58 changes: 40 additions & 18 deletions src/JsonApiDotNetCore/Serialization/Server/Builders/LinkBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Contracts;
Expand Down Expand Up @@ -27,17 +28,19 @@ public LinkBuilder(ILinksConfiguration options,
}

/// <inheritdoc/>
public TopLevelLinks GetTopLevelLinks(ResourceContext primaryResource)
public TopLevelLinks GetTopLevelLinks()
{
ResourceContext resourceContext = _currentRequest.GetRequestResource();

TopLevelLinks topLevelLinks = null;
if (ShouldAddTopLevelLink(primaryResource, Link.Self))
if (ShouldAddTopLevelLink(resourceContext, Link.Self))
{
topLevelLinks = new TopLevelLinks { Self = GetSelfTopLevelLink(primaryResource.ResourceName) };
topLevelLinks = new TopLevelLinks { Self = GetSelfTopLevelLink(resourceContext) };
}

if (ShouldAddTopLevelLink(primaryResource, Link.Paging) && _pageService.CanPaginate)
if (ShouldAddTopLevelLink(resourceContext, Link.Paging) && _pageService.CanPaginate)
{
SetPageLinks(primaryResource, topLevelLinks ??= new TopLevelLinks());
SetPageLinks(resourceContext, topLevelLinks ??= new TopLevelLinks());
}

return topLevelLinks;
Expand All @@ -48,50 +51,69 @@ public TopLevelLinks GetTopLevelLinks(ResourceContext primaryResource)
/// configuration on the <see cref="ResourceContext"/>, and if not configured, by checking with the
/// global configuration in <see cref="ILinksConfiguration"/>.
/// </summary>
/// <param name="resourceContext"></param>
/// <param name="link"></param>
private bool ShouldAddTopLevelLink(ResourceContext primaryResource, Link link)
private bool ShouldAddTopLevelLink(ResourceContext resourceContext, Link link)
{
if (primaryResource.TopLevelLinks != Link.NotConfigured)
if (resourceContext.TopLevelLinks != Link.NotConfigured)
{
return primaryResource.TopLevelLinks.HasFlag(link);
return resourceContext.TopLevelLinks.HasFlag(link);
}

return _options.TopLevelLinks.HasFlag(link);
}

private void SetPageLinks(ResourceContext primaryResource, TopLevelLinks links)
private void SetPageLinks(ResourceContext resourceContext, TopLevelLinks links)
{
if (_pageService.CurrentPage > 1)
{
links.Prev = GetPageLink(primaryResource, _pageService.CurrentPage - 1, _pageService.CurrentPageSize);
links.Prev = GetPageLink(resourceContext, _pageService.CurrentPage - 1, _pageService.CurrentPageSize);
}

if (_pageService.CurrentPage < _pageService.TotalPages)
{
links.Next = GetPageLink(primaryResource, _pageService.CurrentPage + 1, _pageService.CurrentPageSize);
links.Next = GetPageLink(resourceContext, _pageService.CurrentPage + 1, _pageService.CurrentPageSize);
}

if (_pageService.TotalPages > 0)
{
links.Self = GetPageLink(primaryResource, _pageService.CurrentPage, _pageService.CurrentPageSize);
links.First = GetPageLink(primaryResource, 1, _pageService.CurrentPageSize);
links.Last = GetPageLink(primaryResource, _pageService.TotalPages, _pageService.CurrentPageSize);
links.Self = GetPageLink(resourceContext, _pageService.CurrentPage, _pageService.CurrentPageSize);
links.First = GetPageLink(resourceContext, 1, _pageService.CurrentPageSize);
links.Last = GetPageLink(resourceContext, _pageService.TotalPages, _pageService.CurrentPageSize);
}
}

private string GetSelfTopLevelLink(string resourceName)
private string GetSelfTopLevelLink(ResourceContext resourceContext)
{
return $"{GetBasePath()}/{resourceName}";
var builder = new StringBuilder();
builder.Append(GetBasePath());
builder.Append("/");
builder.Append(resourceContext.ResourceName);

string resourceId = _currentRequest.BaseId;
if (resourceId != null)
{
builder.Append("/");
builder.Append(resourceId);
}

if (_currentRequest.RequestRelationship != null)
{
builder.Append("/");
builder.Append(_currentRequest.RequestRelationship.PublicRelationshipName);
}

return builder.ToString();
}

private string GetPageLink(ResourceContext primaryResource, int pageOffset, int pageSize)
private string GetPageLink(ResourceContext resourceContext, int pageOffset, int pageSize)
{
if (_pageService.Backwards)
{
pageOffset = -pageOffset;
}

return $"{GetBasePath()}/{primaryResource.ResourceName}?page[size]={pageSize}&page[number]={pageOffset}";
return $"{GetBasePath()}/{resourceContext.ResourceName}?page[size]={pageSize}&page[number]={pageOffset}";
}


Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Models.Links;

Expand All @@ -12,8 +11,7 @@ public interface ILinkBuilder
/// <summary>
/// Builds the links object that is included in the top-level of the document.
/// </summary>
/// <param name="primaryResource">The primary resource of the response body</param>
TopLevelLinks GetTopLevelLinks(ResourceContext primaryResource);
TopLevelLinks GetTopLevelLinks();
/// <summary>
/// Builds the links object for resources in the primary data.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@ public ResponseSerializer(IMetaBuilder<TResource> metaBuilder,
ILinkBuilder linkBuilder,
IIncludedResourceObjectBuilder includedBuilder,
IFieldsToSerialize fieldsToSerialize,
IResourceObjectBuilder resourceObjectBuilder,
IResourceContextProvider provider) :
base(resourceObjectBuilder, provider)
IResourceObjectBuilder resourceObjectBuilder) :
base(resourceObjectBuilder)
{
_fieldsToSerialize = fieldsToSerialize;
_linkBuilder = linkBuilder;
Expand Down Expand Up @@ -158,7 +157,7 @@ private List<RelationshipAttribute> GetRelationshipsToSerialize(Type resourceTyp
/// </summary>
private void AddTopLevelObjects(Document document)
{
document.Links = _linkBuilder.GetTopLevelLinks(_provider.GetResourceContext<TResource>());
document.Links = _linkBuilder.GetTopLevelLinks();
document.Meta = _metaBuilder.GetMeta();
document.Included = _includedBuilder.Build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Net;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Bogus;
Expand Down Expand Up @@ -43,7 +43,7 @@ public async Task Request_UnsetRelationship_Returns_Null_DataObject()
var server = new TestServer(builder);
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);
var expectedBody = "{\"meta\":{\"copyright\":\"Copyright 2015 Example Corp.\",\"authors\":[\"Jared Nance\",\"Maurits Moeys\",\"Harro van der Kroft\"]},\"links\":{\"self\":\"http://localhost/api/v1/people\"},\"data\":null}";
var expectedBody = "{\"meta\":{\"copyright\":\"Copyright 2015 Example Corp.\",\"authors\":[\"Jared Nance\",\"Maurits Moeys\",\"Harro van der Kroft\"]},\"links\":{\"self\":\"http://localhost" + route + "\"},\"data\":null}";

// Act
var response = await client.SendAsync(request);
Expand Down
36 changes: 27 additions & 9 deletions test/UnitTests/Builders/LinkBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Contracts;
Expand All @@ -18,6 +19,8 @@ public class LinkBuilderTests
private readonly IPageService _pageService;
private readonly Mock<IResourceGraph> _provider = new Mock<IResourceGraph>();
private const string _host = "http://www.example.com";
private const int _baseId = 123;
private const string _relationshipName = "author";
private const string _topSelf = "http://www.example.com/articles";
private const string _resourceSelf = "http://www.example.com/articles/123";
private const string _relSelf = "http://www.example.com/articles/123/relationships/author";
Expand Down Expand Up @@ -47,7 +50,7 @@ public void BuildResourceLinks_GlobalAndResourceConfiguration_ExpectedResult(Lin
var builder = new LinkBuilder(config, GetRequestManager(), null, _provider.Object);

// Act
var links = builder.GetResourceLinks("articles", "123");
var links = builder.GetResourceLinks("articles", _baseId.ToString());

// Assert
if (expectedResult == null)
Expand Down Expand Up @@ -96,7 +99,7 @@ public void BuildRelationshipLinks_GlobalResourceAndAttrConfiguration_ExpectedLi
var attr = new HasOneAttribute(links: relationship) { RightType = typeof(Author), PublicRelationshipName = "author" };

// Act
var links = builder.GetRelationshipLinks(attr, new Article { Id = 123 });
var links = builder.GetRelationshipLinks(attr, new Article { Id = _baseId });

// Assert
if (expectedSelfLink == null && expectedRelatedLink == null)
Expand Down Expand Up @@ -131,20 +134,32 @@ public void BuildRelationshipLinks_GlobalResourceAndAttrConfiguration_ExpectedLi
[InlineData(Link.None, Link.Self, _topSelf, false)]
[InlineData(Link.None, Link.Paging, null, true)]
[InlineData(Link.None, Link.None, null, false)]
[InlineData(Link.All, Link.Self, _resourceSelf, false)]
[InlineData(Link.Self, Link.Self, _resourceSelf, false)]
[InlineData(Link.Paging, Link.Self, _resourceSelf, false)]
[InlineData(Link.None, Link.Self, _resourceSelf, false)]
[InlineData(Link.All, Link.Self, _relRelated, false)]
[InlineData(Link.Self, Link.Self, _relRelated, false)]
[InlineData(Link.Paging, Link.Self, _relRelated, false)]
[InlineData(Link.None, Link.Self, _relRelated, false)]
public void BuildTopLevelLinks_GlobalAndResourceConfiguration_ExpectedLinks(Link global,
Link resource,
object expectedSelfLink,
string expectedSelfLink,
bool pages)
{
// Arrange
var config = GetConfiguration(topLevelLinks: global);
var primaryResource = GetResourceContext<Article>(topLevelLinks: resource);
_provider.Setup(m => m.GetResourceContext<Article>()).Returns(primaryResource);

var builder = new LinkBuilder(config, GetRequestManager(), _pageService, _provider.Object);
bool useBaseId = expectedSelfLink != _topSelf;
string relationshipName = expectedSelfLink == _relRelated ? _relationshipName : null;
ICurrentRequest currentRequest = GetRequestManager(primaryResource, useBaseId, relationshipName);

var builder = new LinkBuilder(config, currentRequest, _pageService, _provider.Object);

// Act
var links = builder.GetTopLevelLinks(primaryResource);
var links = builder.GetTopLevelLinks();

// Assert
if (!pages && expectedSelfLink == null)
Expand All @@ -153,11 +168,11 @@ public void BuildTopLevelLinks_GlobalAndResourceConfiguration_ExpectedLinks(Link
}
else
{
Assert.True(CheckPages(links, pages));
Assert.True(CheckLinks(links, pages, expectedSelfLink));
}
}

private bool CheckPages(TopLevelLinks links, bool pages)
private bool CheckLinks(TopLevelLinks links, bool pages, string expectedSelfLink)
{
if (pages)
{
Expand All @@ -167,13 +182,16 @@ private bool CheckPages(TopLevelLinks links, bool pages)
&& links.Next == $"{_host}/articles?page[size]=10&page[number]=3"
&& links.Last == $"{_host}/articles?page[size]=10&page[number]=3";
}
return links.First == null && links.Prev == null && links.Next == null && links.Last == null;

return links.Self == expectedSelfLink && links.First == null && links.Prev == null && links.Next == null && links.Last == null;
}

private ICurrentRequest GetRequestManager(ResourceContext resourceContext = null)
private ICurrentRequest GetRequestManager(ResourceContext resourceContext = null, bool useBaseId = false, string relationshipName = null)
{
var mock = new Mock<ICurrentRequest>();
mock.Setup(m => m.BasePath).Returns(_host);
mock.Setup(m => m.BaseId).Returns(useBaseId ? _baseId.ToString() : null);
mock.Setup(m => m.RequestRelationship).Returns(relationshipName != null ? new HasOneAttribute(relationshipName) : null);
mock.Setup(m => m.GetRequestResource()).Returns(resourceContext);
return mock.Object;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public BaseDocumentBuilderTests()
{
var mock = new Mock<IResourceObjectBuilder>();
mock.Setup(m => m.Build(It.IsAny<IIdentifiable>(), It.IsAny<IEnumerable<AttrAttribute>>(), It.IsAny<IEnumerable<RelationshipAttribute>>())).Returns(new ResourceObject());
_builder = new TestDocumentBuilder(mock.Object, _resourceGraph);
_builder = new TestDocumentBuilder(mock.Object);
}


Expand Down
17 changes: 5 additions & 12 deletions test/UnitTests/Serialization/SerializerTestsSetup.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Managers.Contracts;
using JsonApiDotNetCore.Models;
Expand Down Expand Up @@ -49,9 +48,8 @@ protected ResponseSerializer<T> GetResponseSerializer<T>(List<List<RelationshipA
var included = GetIncludedRelationships(inclusionChains);
var includedBuilder = GetIncludedBuilder();
var fieldsToSerialize = GetSerializableFields();
var provider = GetResourceContextProvider();
ResponseResourceObjectBuilder resourceObjectBuilder = new ResponseResourceObjectBuilder(link, includedBuilder, included, _resourceGraph, GetSerializerSettingsProvider());
return new ResponseSerializer<T>(meta, link, includedBuilder, fieldsToSerialize, resourceObjectBuilder, provider);
return new ResponseSerializer<T>(meta, link, includedBuilder, fieldsToSerialize, resourceObjectBuilder);
}

protected ResponseResourceObjectBuilder GetResponseResourceObjectBuilder(List<List<RelationshipAttribute>> inclusionChains = null, ResourceLinks resourceLinks = null, RelationshipLinks relationshipLinks = null)
Expand All @@ -74,11 +72,6 @@ protected IResourceObjectBuilderSettingsProvider GetSerializerSettingsProvider()
return mock.Object;
}

private IResourceGraph GetResourceContextProvider()
{
return _resourceGraph;
}

protected IMetaBuilder<T> GetMetaBuilder<T>(Dictionary<string, object> meta = null) where T : class, IIdentifiable
{
var mock = new Mock<IMetaBuilder<T>>();
Expand All @@ -96,7 +89,7 @@ protected ICurrentRequest GetRequestManager<T>() where T : class, IIdentifiable
protected ILinkBuilder GetLinkBuilder(TopLevelLinks top = null, ResourceLinks resource = null, RelationshipLinks relationship = null)
{
var mock = new Mock<ILinkBuilder>();
mock.Setup(m => m.GetTopLevelLinks(It.IsAny<ResourceContext>())).Returns(top);
mock.Setup(m => m.GetTopLevelLinks()).Returns(top);
mock.Setup(m => m.GetResourceLinks(It.IsAny<string>(), It.IsAny<string>())).Returns(resource);
mock.Setup(m => m.GetRelationshipLinks(It.IsAny<RelationshipAttribute>(), It.IsAny<IIdentifiable>())).Returns(relationship);
return mock.Object;
Expand Down Expand Up @@ -131,7 +124,7 @@ protected IIncludeService GetIncludedRelationships(List<List<RelationshipAttribu
/// </summary>
protected class TestDocumentBuilder : BaseDocumentBuilder
{
public TestDocumentBuilder(IResourceObjectBuilder resourceObjectBuilder, IResourceContextProvider provider) : base(resourceObjectBuilder, provider) { }
public TestDocumentBuilder(IResourceObjectBuilder resourceObjectBuilder) : base(resourceObjectBuilder) { }

public new Document Build(IIdentifiable entity, List<AttrAttribute> attributes = null, List<RelationshipAttribute> relationships = null)
{
Expand All @@ -144,4 +137,4 @@ public TestDocumentBuilder(IResourceObjectBuilder resourceObjectBuilder, IResour
}
}
}
}
}