Skip to content

Commit 2c7d07f

Browse files
authored
Merge pull request #70 from Research-Institute/develop
v1.2.0
2 parents 9fc2cf1 + 408113c commit 2c7d07f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1176
-301
lines changed

.vscode/launch.json

+1-31
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,11 @@
11
{
22
"version": "0.2.0",
33
"configurations": [
4-
{
5-
"name": ".NET Core Launch (web)",
6-
"type": "coreclr",
7-
"request": "launch",
8-
"preLaunchTask": "build",
9-
"program": "${workspaceRoot}/src/JsonApiDotNetCoreExample/bin/Debug/netcoreapp1.0/JsonApiDotNetCoreExample.dll",
10-
"args": [],
11-
"cwd": "${workspaceRoot}/src/JsonApiDotNetCoreExample",
12-
"stopAtEntry": false,
13-
"launchBrowser": {
14-
"enabled": false,
15-
"args": "${auto-detect-url}",
16-
"windows": {
17-
"command": "cmd.exe",
18-
"args": "/C start ${auto-detect-url}"
19-
},
20-
"osx": {
21-
"command": "open"
22-
},
23-
"linux": {
24-
"command": "xdg-open"
25-
}
26-
},
27-
"env": {
28-
"ASPNETCORE_ENVIRONMENT": "Development"
29-
},
30-
"sourceFileMap": {
31-
"/Views": "${workspaceRoot}/Views"
32-
}
33-
},
344
{
355
"name": ".NET Core Attach",
366
"type": "coreclr",
377
"request": "attach",
38-
"processId": "${command.pickProcess}"
8+
"processId": "${command:pickProcess}"
399
}
4010
]
4111
}

README.md

+60-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ JsonApiDotnetCore provides a framework for building [json:api](http://jsonapi.or
2727
- [Sorting](#sorting)
2828
- [Meta](#meta)
2929
- [Client Generated Ids](#client-generated-ids)
30+
- [Custom Errors](#custom-errors)
31+
- [Sparse Fieldsets](#sparse-fieldsets)
3032
- [Tests](#tests)
3133

3234
## Comprehensive Demo
@@ -44,14 +46,14 @@ Install-Package JsonApiDotnetCore
4446

4547
- project.json
4648
```json
47-
"JsonApiDotNetCore": "1.1.0"
49+
"JsonApiDotNetCore": "1.2.0"
4850
```
4951

5052
- *.csproj
5153
```xml
5254
<ItemGroup>
5355
<!-- ... -->
54-
<PackageReference Include="JsonApiDotNetCore" Version="1.1.0" />
56+
<PackageReference Include="JsonApiDotNetCore" Version="1.2.0" />
5557
</ItemGroup>
5658
```
5759

@@ -326,6 +328,10 @@ Resources can be sorted by an attribute:
326328

327329
### Meta
328330

331+
Meta objects can be assigned in two ways:
332+
- Resource meta
333+
- Request Meta
334+
329335
Resource meta can be defined by implementing `IHasMeta` on the model class:
330336

331337
```csharp
@@ -343,6 +349,9 @@ public class Person : Identifiable<int>, IHasMeta
343349
}
344350
```
345351

352+
Request Meta can be added by injecting a service that implements `IRequestMeta`.
353+
In the event of a key collision, the Request Meta will take precendence.
354+
346355
### Client Generated Ids
347356

348357
By default, the server will respond with a `403 Forbidden` HTTP Status Code if a `POST` request is
@@ -357,6 +366,55 @@ services.AddJsonApi<AppDbContext>(opt =>
357366
});
358367
```
359368

369+
### Custom Errors
370+
371+
By default, errors will only contain the properties defined by the internal [Error](https://github.com/Research-Institute/json-api-dotnet-core/blob/master/src/JsonApiDotNetCore/Internal/Error.cs) class. However, you can create your own by inheriting from `Error` and either throwing it in a `JsonApiException` or returning the error from your controller.
372+
373+
```csharp
374+
// custom error definition
375+
public class CustomError : Error {
376+
public CustomError(string status, string title, string detail, string myProp)
377+
: base(status, title, detail)
378+
{
379+
MyCustomProperty = myProp;
380+
}
381+
public string MyCustomProperty { get; set; }
382+
}
383+
384+
// throwing a custom error
385+
public void MyMethod() {
386+
var error = new CustomError("507", "title", "detail", "custom");
387+
throw new JsonApiException(error);
388+
}
389+
390+
// returning from controller
391+
[HttpPost]
392+
public override async Task<IActionResult> PostAsync([FromBody] MyEntity entity)
393+
{
394+
if(_db.IsFull)
395+
return new ObjectResult(new CustomError("507", "Database is full.", "Theres no more room.", "Sorry."));
396+
397+
// ...
398+
}
399+
```
400+
401+
### Sparse Fieldsets
402+
403+
We currently support top-level field selection.
404+
What this means is you can restrict which fields are returned by a query using the `fields` query parameter, but this does not yet apply to included relationships.
405+
406+
- Currently valid:
407+
```http
408+
GET /articles?fields[articles]=title,body HTTP/1.1
409+
Accept: application/vnd.api+json
410+
```
411+
412+
- Not yet supported:
413+
```http
414+
GET /articles?include=author&fields[articles]=title,body&fields[people]=name HTTP/1.1
415+
Accept: application/vnd.api+json
416+
```
417+
360418
## Tests
361419

362420
I am using DotNetCoreDocs to generate sample requests and documentation.

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

+58-36
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,37 @@
88

99
namespace JsonApiDotNetCore.Builders
1010
{
11-
public class DocumentBuilder
11+
public class DocumentBuilder : IDocumentBuilder
1212
{
1313
private IJsonApiContext _jsonApiContext;
1414
private IContextGraph _contextGraph;
15+
private readonly IRequestMeta _requestMeta;
1516

1617
public DocumentBuilder(IJsonApiContext jsonApiContext)
1718
{
1819
_jsonApiContext = jsonApiContext;
1920
_contextGraph = jsonApiContext.ContextGraph;
2021
}
2122

23+
public DocumentBuilder(IJsonApiContext jsonApiContext, IRequestMeta requestMeta)
24+
{
25+
_jsonApiContext = jsonApiContext;
26+
_contextGraph = jsonApiContext.ContextGraph;
27+
_requestMeta = requestMeta;
28+
}
29+
2230
public Document Build(IIdentifiable entity)
2331
{
2432
var contextEntity = _contextGraph.GetContextEntity(entity.GetType());
2533

2634
var document = new Document
2735
{
28-
Data = _getData(contextEntity, entity),
29-
Meta = _getMeta(entity),
36+
Data = GetData(contextEntity, entity),
37+
Meta = GetMeta(entity),
3038
Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext))
3139
};
3240

33-
document.Included = _appendIncludedObject(document.Included, contextEntity, entity);
41+
document.Included = AppendIncludedObject(document.Included, contextEntity, entity);
3442

3543
return document;
3644
}
@@ -46,39 +54,42 @@ public Documents Build(IEnumerable<IIdentifiable> entities)
4654
var documents = new Documents
4755
{
4856
Data = new List<DocumentData>(),
49-
Meta = _getMeta(entities.FirstOrDefault()),
57+
Meta = GetMeta(entities.FirstOrDefault()),
5058
Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext))
5159
};
5260

5361
foreach (var entity in entities)
5462
{
55-
documents.Data.Add(_getData(contextEntity, entity));
56-
documents.Included = _appendIncludedObject(documents.Included, contextEntity, entity);
63+
documents.Data.Add(GetData(contextEntity, entity));
64+
documents.Included = AppendIncludedObject(documents.Included, contextEntity, entity);
5765
}
5866

5967
return documents;
6068
}
6169

62-
private Dictionary<string, object> _getMeta(IIdentifiable entity)
70+
private Dictionary<string, object> GetMeta(IIdentifiable entity)
6371
{
6472
if (entity == null) return null;
65-
66-
var meta = new Dictionary<string, object>();
67-
var metaEntity = entity as IHasMeta;
6873

69-
if(metaEntity != null)
70-
meta = metaEntity.GetMeta(_jsonApiContext);
74+
var builder = _jsonApiContext.MetaBuilder;
75+
76+
if(entity is IHasMeta metaEntity)
77+
builder.Add(metaEntity.GetMeta(_jsonApiContext));
7178

7279
if(_jsonApiContext.Options.IncludeTotalRecordCount)
73-
meta["total-records"] = _jsonApiContext.PageManager.TotalRecords;
80+
builder.Add("total-records", _jsonApiContext.PageManager.TotalRecords);
7481

82+
if(_requestMeta != null)
83+
builder.Add(_requestMeta.GetMeta());
84+
85+
var meta = builder.Build();
7586
if(meta.Count > 0) return meta;
7687
return null;
7788
}
7889

79-
private List<DocumentData> _appendIncludedObject(List<DocumentData> includedObject, ContextEntity contextEntity, IIdentifiable entity)
90+
private List<DocumentData> AppendIncludedObject(List<DocumentData> includedObject, ContextEntity contextEntity, IIdentifiable entity)
8091
{
81-
var includedEntities = _getIncludedEntities(contextEntity, entity);
92+
var includedEntities = GetIncludedEntities(contextEntity, entity);
8293
if (includedEntities.Count > 0)
8394
{
8495
if (includedObject == null)
@@ -89,7 +100,7 @@ private List<DocumentData> _appendIncludedObject(List<DocumentData> includedObje
89100
return includedObject;
90101
}
91102

92-
private DocumentData _getData(ContextEntity contextEntity, IIdentifiable entity)
103+
private DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity)
93104
{
94105
var data = new DocumentData
95106
{
@@ -104,16 +115,24 @@ private DocumentData _getData(ContextEntity contextEntity, IIdentifiable entity)
104115

105116
contextEntity.Attributes.ForEach(attr =>
106117
{
107-
data.Attributes.Add(attr.PublicAttributeName, attr.GetValue(entity));
118+
if(ShouldIncludeAttribute(attr))
119+
data.Attributes.Add(attr.PublicAttributeName, attr.GetValue(entity));
108120
});
109121

110122
if (contextEntity.Relationships.Count > 0)
111-
_addRelationships(data, contextEntity, entity);
123+
AddRelationships(data, contextEntity, entity);
112124

113125
return data;
114126
}
115127

116-
private void _addRelationships(DocumentData data, ContextEntity contextEntity, IIdentifiable entity)
128+
private bool ShouldIncludeAttribute(AttrAttribute attr)
129+
{
130+
return (_jsonApiContext.QuerySet == null
131+
|| _jsonApiContext.QuerySet.Fields.Count == 0
132+
|| _jsonApiContext.QuerySet.Fields.Contains(attr.InternalAttributeName));
133+
}
134+
135+
private void AddRelationships(DocumentData data, ContextEntity contextEntity, IIdentifiable entity)
117136
{
118137
data.Relationships = new Dictionary<string, RelationshipData>();
119138
var linkBuilder = new LinkBuilder(_jsonApiContext);
@@ -129,54 +148,57 @@ private void _addRelationships(DocumentData data, ContextEntity contextEntity, I
129148
}
130149
};
131150

132-
if (_relationshipIsIncluded(r.InternalRelationshipName))
151+
if (RelationshipIsIncluded(r.InternalRelationshipName))
133152
{
134153
var navigationEntity = _jsonApiContext.ContextGraph
135154
.GetRelationship(entity, r.InternalRelationshipName);
136155

137156
if(navigationEntity == null)
138157
relationshipData.SingleData = null;
139158
else if (navigationEntity is IEnumerable)
140-
relationshipData.ManyData = _getRelationships((IEnumerable<object>)navigationEntity, r.InternalRelationshipName);
159+
relationshipData.ManyData = GetRelationships((IEnumerable<object>)navigationEntity, r.InternalRelationshipName);
141160
else
142-
relationshipData.SingleData = _getRelationship(navigationEntity, r.InternalRelationshipName);
161+
relationshipData.SingleData = GetRelationship(navigationEntity, r.InternalRelationshipName);
143162
}
144163

145164
data.Relationships.Add(r.InternalRelationshipName.Dasherize(), relationshipData);
146165
});
147166
}
148167

149-
private List<DocumentData> _getIncludedEntities(ContextEntity contextEntity, IIdentifiable entity)
168+
private List<DocumentData> GetIncludedEntities(ContextEntity contextEntity, IIdentifiable entity)
150169
{
151170
var included = new List<DocumentData>();
152171

153172
contextEntity.Relationships.ForEach(r =>
154173
{
155-
if (!_relationshipIsIncluded(r.InternalRelationshipName)) return;
174+
if (!RelationshipIsIncluded(r.InternalRelationshipName)) return;
156175

157176
var navigationEntity = _jsonApiContext.ContextGraph.GetRelationship(entity, r.InternalRelationshipName);
158177

159178
if (navigationEntity is IEnumerable)
160179
foreach (var includedEntity in (IEnumerable)navigationEntity)
161-
included.Add(_getIncludedEntity((IIdentifiable)includedEntity));
180+
AddIncludedEntity(included, (IIdentifiable)includedEntity);
162181
else
163-
included.Add(_getIncludedEntity((IIdentifiable)navigationEntity));
182+
AddIncludedEntity(included, (IIdentifiable)navigationEntity);
164183
});
165184

166185
return included;
167186
}
168187

169-
private DocumentData _getIncludedEntity(IIdentifiable entity)
188+
private void AddIncludedEntity(List<DocumentData> entities, IIdentifiable entity)
189+
{
190+
var includedEntity = GetIncludedEntity(entity);
191+
if(includedEntity != null)
192+
entities.Add(includedEntity);
193+
}
194+
195+
private DocumentData GetIncludedEntity(IIdentifiable entity)
170196
{
171197
if(entity == null) return null;
172198

173199
var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(entity.GetType());
174200

175-
var data = new DocumentData
176-
{
177-
Type = contextEntity.EntityName,
178-
Id = entity.StringId
179-
};
201+
var data = GetData(contextEntity, entity);
180202

181203
data.Attributes = new Dictionary<string, object>();
182204

@@ -188,13 +210,13 @@ private DocumentData _getIncludedEntity(IIdentifiable entity)
188210
return data;
189211
}
190212

191-
private bool _relationshipIsIncluded(string relationshipName)
213+
private bool RelationshipIsIncluded(string relationshipName)
192214
{
193215
return _jsonApiContext.IncludedRelationships != null &&
194216
_jsonApiContext.IncludedRelationships.Contains(relationshipName.ToProperCase());
195217
}
196218

197-
private List<Dictionary<string, string>> _getRelationships(IEnumerable<object> entities, string relationshipName)
219+
private List<Dictionary<string, string>> GetRelationships(IEnumerable<object> entities, string relationshipName)
198220
{
199221
var objType = entities.GetType().GenericTypeArguments[0];
200222

@@ -210,7 +232,7 @@ private List<Dictionary<string, string>> _getRelationships(IEnumerable<object> e
210232
}
211233
return relationships;
212234
}
213-
private Dictionary<string, string> _getRelationship(object entity, string relationshipName)
235+
private Dictionary<string, string> GetRelationship(object entity, string relationshipName)
214236
{
215237
var objType = entity.GetType();
216238

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Collections.Generic;
2+
using JsonApiDotNetCore.Models;
3+
4+
namespace JsonApiDotNetCore.Builders
5+
{
6+
public interface IDocumentBuilder
7+
{
8+
Document Build(IIdentifiable entity);
9+
Documents Build(IEnumerable<IIdentifiable> entities);
10+
}
11+
}

0 commit comments

Comments
 (0)