Skip to content

Missing spec tests #41

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

Closed
wants to merge 17 commits into from
Closed
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
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ dotnet: 1.0.0-preview2-1-003177
branches:
only:
- master
- staging
script:
- ./build.sh
- ./build.sh
10 changes: 1 addition & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ Your models should inherit `Identifiable<TId>` where `TId` is the type of the pr

```csharp
public class Person : Identifiable<Guid>
{
public override Guid Id { get; set; }
}
{ }
```

#### Specifying Public Attributes
Expand All @@ -89,8 +87,6 @@ add the `AttrAttribute` and provide the outbound name.
```csharp
public class Person : Identifiable<int>
{
public override int Id { get; set; }

[Attr("first-name")]
public string FirstName { get; set; }
}
Expand All @@ -104,8 +100,6 @@ they should be labeled as virtual.
```csharp
public class Person : Identifiable<int>
{
public override int Id { get; set; }

[Attr("first-name")]
public string FirstName { get; set; }

Expand All @@ -119,8 +113,6 @@ For example, a `TodoItem` may have an `Owner` and so the Id attribute should be
```csharp
public class TodoItem : Identifiable<int>
{
public override int Id { get; set; }

[Attr("description")]
public string Description { get; set; }

Expand Down
3 changes: 2 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pull_requests:
branches:
only:
- master
- staging
nuget:
disable_publish_on_pr: true
build_script:
Expand All @@ -19,7 +20,7 @@ deploy:
secure: 6CeYcZ4Ze+57gxfeuHzqP6ldbUkPtF6pfpVM1Gw/K2jExFrAz763gNAQ++tiacq3
skip_symbols: true
on:
branch: master
branch: staging
- provider: NuGet
name: production
api_key:
Expand Down
14 changes: 10 additions & 4 deletions src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public Document Build(IIdentifiable entity)
var document = new Document
{
Data = _getData(contextEntity, entity),
Meta = _getMeta(entity)
Meta = _getMeta(entity),
Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext))
};

document.Included = _appendIncludedObject(document.Included, contextEntity, entity);
Expand All @@ -45,7 +46,8 @@ public Documents Build(IEnumerable<IIdentifiable> entities)
var documents = new Documents
{
Data = new List<DocumentData>(),
Meta = _getMeta(entities.FirstOrDefault())
Meta = _getMeta(entities.FirstOrDefault()),
Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext))
};

foreach (var entity in entities)
Expand All @@ -68,7 +70,7 @@ private Dictionary<string, object> _getMeta(IIdentifiable entity)
meta = metaEntity.GetMeta(_jsonApiContext);

if(_jsonApiContext.Options.IncludeTotalRecordCount)
meta["total-records"] = _jsonApiContext.TotalRecords;
meta["total-records"] = _jsonApiContext.PageManager.TotalRecords;

if(meta.Count > 0) return meta;
return null;
Expand Down Expand Up @@ -132,7 +134,9 @@ private void _addRelationships(DocumentData data, ContextEntity contextEntity, I
var navigationEntity = _jsonApiContext.ContextGraph
.GetRelationship(entity, r.RelationshipName);

if (navigationEntity is IEnumerable)
if(navigationEntity == null)
relationshipData.SingleData = null;
else if (navigationEntity is IEnumerable)
relationshipData.ManyData = _getRelationships((IEnumerable<object>)navigationEntity, r.RelationshipName);
else
relationshipData.SingleData = _getRelationship(navigationEntity, r.RelationshipName);
Expand Down Expand Up @@ -164,6 +168,8 @@ private List<DocumentData> _getIncludedEntities(ContextEntity contextEntity, IId

private DocumentData _getIncludedEntity(IIdentifiable entity)
{
if(entity == null) return null;

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

var data = new DocumentData
Expand Down
6 changes: 5 additions & 1 deletion src/JsonApiDotNetCore/Builders/LinkBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

using JsonApiDotNetCore.Extensions;
using JsonApiDotNetCore.Services;
using Microsoft.AspNetCore.Http;
Expand Down Expand Up @@ -45,5 +44,10 @@ public string GetRelatedRelationLink(string parent, string parentId, string chil
{
return $"{_context.BasePath}/{parent.Dasherize()}/{parentId}/{child.Dasherize()}";
}

public string GetPageLink(int pageOffset, int pageSize)
{
return $"{_context.BasePath}/{_context.RequestEntity.EntityName.Dasherize()}?page[size]={pageSize}&page[number]={pageOffset}";
}
}
}
16 changes: 6 additions & 10 deletions src/JsonApiDotNetCore/Controllers/JsonApiController.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
Expand Down Expand Up @@ -65,7 +66,7 @@ public virtual async Task<IActionResult> GetAsync()
entities = IncludeRelationships(entities, _jsonApiContext.QuerySet.IncludedRelationships);

if (_jsonApiContext.Options.IncludeTotalRecordCount)
_jsonApiContext.TotalRecords = await entities.CountAsync();
_jsonApiContext.PageManager.TotalRecords = await entities.CountAsync();

// pagination should be done last since it will execute the query
var pagedEntities = await ApplyPageQueryAsync(entities);
Expand Down Expand Up @@ -126,9 +127,6 @@ public virtual async Task<IActionResult> GetRelationshipAsync(TId id, string rel
var relationship = _jsonApiContext.ContextGraph
.GetRelationship<T>(entity, relationshipName);

if (relationship == null)
return NotFound();

return Ok(relationship);
}

Expand Down Expand Up @@ -190,17 +188,15 @@ private IQueryable<T> ApplySortAndFilterQuery(IQueryable<T> entities)

private async Task<IEnumerable<T>> ApplyPageQueryAsync(IQueryable<T> entities)
{
if(_jsonApiContext.Options.DefaultPageSize == 0 && (_jsonApiContext.QuerySet == null || _jsonApiContext.QuerySet.PageQuery.PageSize == 0))
var pageManager = _jsonApiContext.PageManager;
if(!pageManager.IsPaginated)
return entities;

var query = _jsonApiContext.QuerySet?.PageQuery ?? new PageQuery();

var pageNumber = query.PageOffset > 0 ? query.PageOffset : 1;
var pageSize = query.PageSize > 0 ? query.PageSize : _jsonApiContext.Options.DefaultPageSize;

_logger?.LogInformation($"Applying paging query. Fetching page {pageNumber} with {pageSize} entities");
_logger?.LogInformation($"Applying paging query. Fetching page {pageManager.CurrentPage} with {pageManager.PageSize} entities");

return await _entities.PageAsync(entities, pageSize, pageNumber);
return await _entities.PageAsync(entities, pageManager.PageSize, pageManager.CurrentPage);
}

private IQueryable<T> IncludeRelationships(IQueryable<T> entities, List<string> relationships)
Expand Down
37 changes: 25 additions & 12 deletions src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Text;
using System.Threading.Tasks;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Serialization;
using JsonApiDotNetCore.Services;
using Microsoft.AspNetCore.Mvc.Formatters;
Expand Down Expand Up @@ -65,23 +66,35 @@ private T GetService<T>(OutputFormatterWriteContext context)

private string GetResponseBody(object responseObject, IJsonApiContext jsonApiContext, ILogger logger)
{
if (responseObject == null)
return GetNullDataResponse();

if (responseObject.GetType() == typeof(Error) || jsonApiContext.RequestEntity == null)
return GetErrorJson(responseObject, logger);

return JsonApiSerializer.Serialize(responseObject, jsonApiContext);
}

private string GetNullDataResponse()
{
return JsonConvert.SerializeObject(new Document
{
if (responseObject.GetType() == typeof(Error))
{
var errors = new ErrorCollection();
errors.Add((Error)responseObject);
return errors.GetJson();
}
else
{
logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON.");
return JsonConvert.SerializeObject(responseObject);
}
Data = null
});
}

private string GetErrorJson(object responseObject, ILogger logger)
{
if (responseObject.GetType() == typeof(Error))
{
var errors = new ErrorCollection();
errors.Add((Error)responseObject);
return errors.GetJson();
}
else
{
return JsonApiSerializer.Serialize(responseObject, jsonApiContext);
logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON.");
return JsonConvert.SerializeObject(responseObject);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/Internal/ContextGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public object GetRelationship<TParent>(TParent entity, string relationshipName)
.FirstOrDefault(p => p.Name.ToLower() == relationshipName.ToLower());

if(navigationProperty == null)
return null;
throw new JsonApiException("400", $"{parentEntityType} does not contain a relationship named {relationshipName}");

return navigationProperty.GetValue(entity);
}
Expand Down
42 changes: 42 additions & 0 deletions src/JsonApiDotNetCore/Internal/PageManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using JsonApiDotNetCore.Builders;
using JsonApiDotNetCore.Models;

namespace JsonApiDotNetCore.Internal
{
public class PageManager
{
public int TotalRecords { get; set; }
public int PageSize { get; set; }
public int DefaultPageSize { get; set; }
public int CurrentPage { get; set; }
public bool IsPaginated { get { return PageSize > 0; } }
public int TotalPages {
get { return (TotalRecords == 0) ? -1: (int)Math.Ceiling(decimal.Divide(TotalRecords, PageSize)); }
}

public RootLinks GetPageLinks(LinkBuilder linkBuilder)
{
if(!IsPaginated || (CurrentPage == 1 && TotalPages <= 0))
return null;

var rootLinks = new RootLinks();

var includePageSize = DefaultPageSize != PageSize;

if(CurrentPage > 1)
rootLinks.First = linkBuilder.GetPageLink(1, PageSize);

if(CurrentPage > 1)
rootLinks.Prev = linkBuilder.GetPageLink(CurrentPage - 1, PageSize);

if(CurrentPage < TotalPages)
rootLinks.Next = linkBuilder.GetPageLink(CurrentPage + 1, PageSize);

if(TotalPages > 0)
rootLinks.Last = linkBuilder.GetPageLink(TotalPages, PageSize);

return rootLinks;
}
}
}
3 changes: 3 additions & 0 deletions src/JsonApiDotNetCore/Internal/Query/QuerySet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ private List<SortQuery> ParseSortParameters(string value)

private List<string> ParseIncludedRelationships(string value)
{
if(value.Contains("."))
throw new JsonApiException("400", "Deeply nested relationships are not supported");

return value
.Split(',')
.Select(s => s.ToProperCase())
Expand Down
23 changes: 22 additions & 1 deletion src/JsonApiDotNetCore/Models/DocumentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,30 @@
namespace JsonApiDotNetCore.Models
{
public class DocumentBase
{
{
[JsonProperty("links")]
public RootLinks Links { get; set; }

[JsonProperty("included")]
public List<DocumentData> Included { get; set; }

[JsonProperty("meta")]
public Dictionary<string, object> Meta { get; set; }

// http://www.newtonsoft.com/json/help/html/ConditionalProperties.htm
public bool ShouldSerializeIncluded()
{
return (Included != null);
}

public bool ShouldSerializeMeta()
{
return (Meta != null);
}

public bool ShouldSerializeLinks()
{
return (Links != null);
}
}
}
7 changes: 5 additions & 2 deletions src/JsonApiDotNetCore/Models/Identifiable.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
namespace JsonApiDotNetCore.Models
{
public abstract class Identifiable<T> : IIdentifiable<T>, IIdentifiable
public class Identifiable : Identifiable<int>
{}

public class Identifiable<T> : IIdentifiable<T>, IIdentifiable
{
public abstract T Id { get; set; }
public T Id { get; set; }

object IIdentifiable.Id
{
Expand Down
48 changes: 48 additions & 0 deletions src/JsonApiDotNetCore/Models/RootLinks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Newtonsoft.Json;

namespace JsonApiDotNetCore.Models
{
public class RootLinks
{
[JsonProperty("self")]
public string Self { get; set; }

[JsonProperty("next")]
public string Next { get; set; }

[JsonProperty("prev")]
public string Prev { get; set; }

[JsonProperty("first")]
public string First { get; set; }

[JsonProperty("last")]
public string Last { get; set; }

// http://www.newtonsoft.com/json/help/html/ConditionalProperties.htm
public bool ShouldSerializeSelf()
{
return (!string.IsNullOrEmpty(Self));
}

public bool ShouldSerializeFirst()
{
return (!string.IsNullOrEmpty(First));
}

public bool ShouldSerializeNext()
{
return (!string.IsNullOrEmpty(Next));
}

public bool ShouldSerializePrev()
{
return (!string.IsNullOrEmpty(Prev));
}

public bool ShouldSerializeLast()
{
return (!string.IsNullOrEmpty(Last));
}
}
}
Loading