Skip to content

Commit 59fc512

Browse files
committed
feat(#129): optionally disable links
1 parent d804628 commit 59fc512

File tree

11 files changed

+288
-18
lines changed

11 files changed

+288
-18
lines changed

src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ public class ContextGraphBuilder : IContextGraphBuilder
1212
{
1313
private List<ContextEntity> _entities;
1414
private bool _usesDbContext;
15+
public Link DocumentLinks { get; set; } = Link.All;
16+
1517
public ContextGraphBuilder()
1618
{
1719
_entities = new List<ContextEntity>();
18-
}
20+
}
1921

2022
public IContextGraph Build()
2123
{
@@ -35,10 +37,20 @@ public void AddResource<TResource>(string pluralizedTypeName) where TResource :
3537
EntityName = pluralizedTypeName,
3638
EntityType = entityType,
3739
Attributes = GetAttributes(entityType),
38-
Relationships = GetRelationships(entityType)
40+
Relationships = GetRelationships(entityType),
41+
Links = GetLinkFlags(entityType)
3942
});
4043
}
4144

45+
private Link GetLinkFlags(Type entityType)
46+
{
47+
var attribute = (LinksAttribute)entityType.GetTypeInfo().GetCustomAttribute(typeof(LinksAttribute));
48+
if (attribute != null)
49+
return attribute.Links;
50+
51+
return DocumentLinks;
52+
}
53+
4254
protected virtual List<AttrAttribute> GetAttributes(Type entityType)
4355
{
4456
var attributes = new List<AttrAttribute>();

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ public Document Build(IIdentifiable entity)
3333
var document = new Document
3434
{
3535
Data = GetData(contextEntity, entity),
36-
Meta = GetMeta(entity),
37-
Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext))
36+
Meta = GetMeta(entity)
3837
};
3938

39+
if(ShouldIncludePageLinks(contextEntity))
40+
document.Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext));
41+
4042
document.Included = AppendIncludedObject(document.Included, contextEntity, entity);
4143

4244
return document;
@@ -54,10 +56,12 @@ public Documents Build(IEnumerable<IIdentifiable> entities)
5456
var documents = new Documents
5557
{
5658
Data = new List<DocumentData>(),
57-
Meta = GetMeta(enumeratedEntities.FirstOrDefault()),
58-
Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext))
59+
Meta = GetMeta(enumeratedEntities.FirstOrDefault())
5960
};
6061

62+
if(ShouldIncludePageLinks(contextEntity))
63+
documents.Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext));
64+
6165
foreach (var entity in enumeratedEntities)
6266
{
6367
documents.Data.Add(GetData(contextEntity, entity));
@@ -87,6 +91,8 @@ private Dictionary<string, object> GetMeta(IIdentifiable entity)
8791
return null;
8892
}
8993

94+
private bool ShouldIncludePageLinks(ContextEntity entity) => entity.Links.HasFlag(Link.Paging);
95+
9096
private List<DocumentData> AppendIncludedObject(List<DocumentData> includedObject, ContextEntity contextEntity, IIdentifiable entity)
9197
{
9298
var includedEntities = GetIncludedEntities(contextEntity, entity);
@@ -139,14 +145,17 @@ private void AddRelationships(DocumentData data, ContextEntity contextEntity, II
139145

140146
contextEntity.Relationships.ForEach(r =>
141147
{
142-
var relationshipData = new RelationshipData
148+
var relationshipData = new RelationshipData();
149+
150+
if(r.DocumentLinks.HasFlag(Link.None) == false)
143151
{
144-
Links = new Links
145-
{
146-
Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, r.PublicRelationshipName),
147-
Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, r.PublicRelationshipName)
148-
}
149-
};
152+
relationshipData.Links = new Links();
153+
if(r.DocumentLinks.HasFlag(Link.Self))
154+
relationshipData.Links.Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, r.PublicRelationshipName);
155+
156+
if(r.DocumentLinks.HasFlag(Link.Related))
157+
relationshipData.Links.Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, r.PublicRelationshipName);
158+
}
150159

151160
if (RelationshipIsIncluded(r.PublicRelationshipName))
152161
{

src/JsonApiDotNetCore/Builders/IContextGraphBuilder.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using JsonApiDotNetCore.Internal;
2+
using JsonApiDotNetCore.Models;
23
using Microsoft.EntityFrameworkCore;
34

45
namespace JsonApiDotNetCore.Builders
56
{
67
public interface IContextGraphBuilder
78
{
9+
Link DocumentLinks { get; set; }
810
IContextGraph Build();
911
void AddResource<TResource>(string pluralizedTypeName) where TResource : class;
1012
void AddDbContext<T>() where T : DbContext;

src/JsonApiDotNetCore/Internal/ContextEntity.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ public class ContextEntity
1010
public Type EntityType { get; set; }
1111
public List<AttrAttribute> Attributes { get; set; }
1212
public List<RelationshipAttribute> Relationships { get; set; }
13+
public Link Links { get; set; }
1314
}
1415
}

src/JsonApiDotNetCore/Models/HasManyAttribute.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ namespace JsonApiDotNetCore.Models
44
{
55
public class HasManyAttribute : RelationshipAttribute
66
{
7-
public HasManyAttribute(string publicName)
8-
: base(publicName)
7+
public HasManyAttribute(string publicName, Link documentLinks = Link.All)
8+
: base(publicName, documentLinks)
99
{
1010
PublicRelationshipName = publicName;
1111
}

src/JsonApiDotNetCore/Models/HasOneAttribute.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ namespace JsonApiDotNetCore.Models
44
{
55
public class HasOneAttribute : RelationshipAttribute
66
{
7-
public HasOneAttribute(string publicName)
8-
: base(publicName)
7+
public HasOneAttribute(string publicName, Link documentLinks = Link.All)
8+
: base(publicName, documentLinks)
99
{
1010
PublicRelationshipName = publicName;
1111
}

src/JsonApiDotNetCore/Models/Link.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
3+
namespace JsonApiDotNetCore.Models
4+
{
5+
[Flags]
6+
public enum Link
7+
{
8+
Self = 1 << 0,
9+
Paging = 1 << 1,
10+
Related = 1 << 2,
11+
All = ~(-1 << 3),
12+
None = 1 << 4,
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
3+
namespace JsonApiDotNetCore.Models
4+
{
5+
public class LinksAttribute : Attribute
6+
{
7+
public LinksAttribute(Link links)
8+
{
9+
Links = links;
10+
}
11+
12+
public Link Links { get; set; }
13+
}
14+
}

src/JsonApiDotNetCore/Models/RelationshipAttribute.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ namespace JsonApiDotNetCore.Models
44
{
55
public abstract class RelationshipAttribute : Attribute
66
{
7-
protected RelationshipAttribute(string publicName)
7+
protected RelationshipAttribute(string publicName, Link documentLinks)
88
{
99
PublicRelationshipName = publicName;
10+
DocumentLinks = documentLinks;
1011
}
1112

1213
public string PublicRelationshipName { get; set; }
1314
public string InternalRelationshipName { get; set; }
1415
public Type Type { get; set; }
1516
public bool IsHasMany => GetType() == typeof(HasManyAttribute);
1617
public bool IsHasOne => GetType() == typeof(HasOneAttribute);
18+
public Link DocumentLinks { get; set; } = Link.All;
1719

1820
public abstract void SetValue(object entity, object newValue);
1921

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using JsonApiDotNetCore.Builders;
4+
using JsonApiDotNetCore.Configuration;
5+
using JsonApiDotNetCore.Internal;
6+
using JsonApiDotNetCore.Models;
7+
using JsonApiDotNetCore.Services;
8+
using Moq;
9+
using Xunit;
10+
11+
namespace UnitTests
12+
{
13+
public class DocumentBuilder_Tests
14+
{
15+
private readonly Mock<IJsonApiContext> _jsonApiContextMock;
16+
private readonly PageManager _pageManager;
17+
private readonly JsonApiOptions _options;
18+
19+
public DocumentBuilder_Tests()
20+
{
21+
_jsonApiContextMock = new Mock<IJsonApiContext>();
22+
23+
_options = new JsonApiOptions();
24+
25+
_options.BuildContextGraph(builder =>
26+
{
27+
builder.AddResource<Model>("models");
28+
});
29+
30+
_jsonApiContextMock
31+
.Setup(m => m.Options)
32+
.Returns(_options);
33+
34+
_jsonApiContextMock
35+
.Setup(m => m.ContextGraph)
36+
.Returns(_options.ContextGraph);
37+
38+
_jsonApiContextMock
39+
.Setup(m => m.MetaBuilder)
40+
.Returns(new MetaBuilder());
41+
42+
_pageManager = new PageManager();
43+
_jsonApiContextMock
44+
.Setup(m => m.PageManager)
45+
.Returns(_pageManager);
46+
47+
_jsonApiContextMock
48+
.Setup(m => m.BasePath)
49+
.Returns("localhost");
50+
51+
_jsonApiContextMock
52+
.Setup(m => m.RequestEntity)
53+
.Returns(_options.ContextGraph.GetContextEntity(typeof(Model)));
54+
}
55+
56+
[Fact]
57+
public void Includes_Paging_Links_By_Default()
58+
{
59+
// arrange
60+
_pageManager.PageSize = 1;
61+
_pageManager.TotalRecords = 1;
62+
_pageManager.CurrentPage = 1;
63+
64+
var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object);
65+
var entity = new Model();
66+
67+
// act
68+
var document = documentBuilder.Build(entity);
69+
70+
// assert
71+
Assert.NotNull(document.Links);
72+
Assert.NotNull(document.Links.Last);
73+
}
74+
75+
[Fact]
76+
public void Page_Links_Can_Be_Disabled_Globally()
77+
{
78+
// arrange
79+
_pageManager.PageSize = 1;
80+
_pageManager.TotalRecords = 1;
81+
_pageManager.CurrentPage = 1;
82+
83+
_options.BuildContextGraph(builder =>
84+
{
85+
builder.DocumentLinks = Link.None;
86+
builder.AddResource<Model>("models");
87+
});
88+
89+
_jsonApiContextMock
90+
.Setup(m => m.ContextGraph)
91+
.Returns(_options.ContextGraph);
92+
93+
var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object);
94+
var entity = new Model();
95+
96+
// act
97+
var document = documentBuilder.Build(entity);
98+
99+
// assert
100+
Assert.Null(document.Links);
101+
}
102+
103+
[Fact]
104+
public void Related_Links_Can_Be_Disabled()
105+
{
106+
// arrange
107+
_pageManager.PageSize = 1;
108+
_pageManager.TotalRecords = 1;
109+
_pageManager.CurrentPage = 1;
110+
111+
_jsonApiContextMock
112+
.Setup(m => m.ContextGraph)
113+
.Returns(_options.ContextGraph);
114+
115+
var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object);
116+
var entity = new Model();
117+
118+
// act
119+
var document = documentBuilder.Build(entity);
120+
121+
// assert
122+
Assert.Null(document.Data.Relationships["related-model"].Links);
123+
}
124+
125+
private class Model : Identifiable
126+
{
127+
[HasOne("related-model", Link.None)]
128+
public RelatedModel RelatedModel { get; set; }
129+
public int RelatedModelId { get; set; }
130+
}
131+
132+
private class RelatedModel : Identifiable
133+
{
134+
[HasMany("models")]
135+
public List<Model> Models { get; set; }
136+
}
137+
}
138+
}

0 commit comments

Comments
 (0)