Skip to content
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

Added fields as query parameter #199

Merged
merged 6 commits into from
Sep 10, 2018
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
1 change: 1 addition & 0 deletions Saule/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public static class QueryNames
public const string Sorting = "sort";
public const string Filtering = "filter";
public const string Including = "include";
public const string Fieldset = "fields";
}

public static class QueryValues
Expand Down
2 changes: 2 additions & 0 deletions Saule/Http/AllowsQueryAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Saule.Queries.Fieldset;
using Saule.Queries.Filtering;
using Saule.Queries.Including;
using Saule.Queries.Sorting;
Expand All @@ -28,6 +29,7 @@ public override void OnActionExecuting(HttpActionContext actionContext)

queryContext.Sort = new SortContext(queryParams);
queryContext.Filter = new FilterContext(queryParams);
queryContext.Fieldset = new FieldsetContext(queryParams);

if (queryContext.Include == null)
{
Expand Down
3 changes: 2 additions & 1 deletion Saule/JsonApiSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ public PreprocessResult PreprocessContent(object @object, ApiResource resource,
propertyNameConverter: config.PropertyNameConverter,
urlBuilder: UrlPathBuilder,
paginationContext: QueryContext?.Pagination,
includeContext: QueryContext?.Include);
includeContext: QueryContext?.Include,
fieldsetContext: QueryContext?.Fieldset);
}
catch (Exception ex)
{
Expand Down
2 changes: 2 additions & 0 deletions Saule/JsonApiSerializerOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Newtonsoft.Json.Linq;
using Saule.Http;
using Saule.Queries;
using Saule.Queries.Fieldset;
using Saule.Queries.Filtering;
using Saule.Queries.Including;
using Saule.Queries.Pagination;
Expand Down Expand Up @@ -130,6 +131,7 @@ private QueryContext GetQueryContext(IEnumerable<KeyValuePair<string, string>> f
context.Sort = new SortContext(keyValuePairs);
context.Filter = new FilterContext(keyValuePairs) { QueryFilters = QueryFilterExpressions };
context.Include = new IncludeContext(keyValuePairs);
context.Fieldset = new FieldsetContext(keyValuePairs);
}

return context;
Expand Down
36 changes: 36 additions & 0 deletions Saule/Queries/Fieldset/FieldsetContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Linq;

namespace Saule.Queries.Fieldset
{
/// <summary>
/// Context for fieldset operations
/// </summary>
public class FieldsetContext
{
/// <summary>
/// Initializes a new instance of the <see cref="FieldsetContext"/> class.
/// </summary>
/// <param name="queryParams">query string that might contain Fieldset keyword</param>
public FieldsetContext(IEnumerable<KeyValuePair<string, string>> queryParams)
{
Properties =
from query in queryParams
where query.Key.StartsWith(Constants.QueryNames.Fieldset)
let type = query.Key.Substring(Constants.QueryNames.Fieldset.Length + 1)
let fields = query.Value.Split(',')
select new FieldsetProperty(type, fields);
}

/// <summary>
/// Gets including properties
/// </summary>
public IEnumerable<FieldsetProperty> Properties { get; internal set; }

/// <inheritdoc/>
public override string ToString()
{
return Properties != null && Properties.Any() ? string.Join("&", Properties.Select(p => p.ToString())) : string.Empty;
}
}
}
35 changes: 35 additions & 0 deletions Saule/Queries/Fieldset/FieldsetProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace Saule.Queries.Fieldset
{
/// <summary>
/// Property for fieldset
/// </summary>
public class FieldsetProperty
{
/// <summary>
/// Initializes a new instance of the <see cref="FieldsetProperty"/> class.
/// </summary>
/// <param name="type">type for field filter</param>
/// <param name="fields">fields to serialize filter</param>
public FieldsetProperty(string type, string[] fields)
{
Type = type;
Fields = fields;
}

/// <summary>
/// Gets property type
/// </summary>
public string Type { get; }

/// <summary>
/// Gets property fields
/// </summary>
public string[] Fields { get; }

/// <inheritdoc/>
public override string ToString()
{
return $"fields[{Type}]={string.Join(",", Fields)}";
}
}
}
9 changes: 8 additions & 1 deletion Saule/Queries/QueryContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Saule.Queries.Fieldset;
using Saule.Queries.Filtering;
using Saule.Queries.Including;
using Saule.Queries.Pagination;
Expand Down Expand Up @@ -33,6 +34,11 @@ public class QueryContext
/// </summary>
public IncludeContext Include { get; internal set; }

/// <summary>
/// Gets include context
/// </summary>
public FieldsetContext Fieldset { get; internal set; }

/// <summary>
/// Gets or sets a value indicating whether that query parameters
/// will be handled by action itself or Saule should handle them
Expand All @@ -47,7 +53,8 @@ public override string ToString()
Pagination?.ToString(),
Sort?.ToString(),
Filter?.ToString(),
Include?.ToString()
Include?.ToString(),
Fieldset?.ToString()
};

return string.Join("&", result.Where(c => !string.IsNullOrEmpty(c)));
Expand Down
2 changes: 2 additions & 0 deletions Saule/Saule.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
<Compile Include="Http\JsonApiQueryValueProvider.cs" />
<Compile Include="Http\JsonApiQueryValueProviderFactory.cs" />
<Compile Include="JsonApiSerializerOfT.cs" />
<Compile Include="Queries\Fieldset\FieldsetContext.cs" />
<Compile Include="Queries\Fieldset\FieldsetProperty.cs" />
<Compile Include="Serialization\CamelCasePropertyNameConverter.cs" />
<Compile Include="Serialization\DefaultPropertyNameConverter.cs" />
<Compile Include="Serialization\IPropertyNameConverter.cs" />
Expand Down
34 changes: 33 additions & 1 deletion Saule/Serialization/ResourceSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Saule.Queries.Fieldset;
using Saule.Queries.Including;
using Saule.Queries.Pagination;

Expand All @@ -14,6 +15,7 @@ internal class ResourceSerializer
private readonly Uri _baseUrl;
private readonly PaginationContext _paginationContext;
private readonly IncludeContext _includeContext;
private readonly FieldsetContext _fieldsetContext;
private readonly ApiResource _resource;
private readonly object _value;
private readonly IPropertyNameConverter _propertyNameConverter;
Expand All @@ -28,6 +30,7 @@ public ResourceSerializer(
IUrlPathBuilder urlBuilder,
PaginationContext paginationContext,
IncludeContext includeContext,
FieldsetContext fieldsetContext,
IPropertyNameConverter propertyNameConverter = null)
{
_propertyNameConverter = propertyNameConverter ?? new DefaultPropertyNameConverter();
Expand All @@ -37,6 +40,7 @@ public ResourceSerializer(
_baseUrl = baseUrl;
_paginationContext = paginationContext;
_includeContext = includeContext;
_fieldsetContext = fieldsetContext;
_includedGraphPaths = IncludedGraphPathsFromContext(includeContext);
}

Expand Down Expand Up @@ -248,7 +252,17 @@ private JObject SerializeNode(ResourceGraphNode node, bool isCollection)
}
}

var attributes = SerializeAttributes(node);
JObject attributes = null;
if (_fieldsetContext != null && _fieldsetContext.Properties.Count(property => property.Type == node.Key.Type) > 0)
{
FieldsetProperty fieldset = _fieldsetContext.Properties.Where(property => property.Type == node.Key.Type).First();
attributes = SerializeAttributes(node, fieldset);
}
else
{
attributes = SerializeAttributes(node);
}

if (attributes != null)
{
response["attributes"] = attributes;
Expand Down Expand Up @@ -281,6 +295,24 @@ private JObject SerializeAttributes(ResourceGraphNode node)
return JObject.FromObject(attributeHash, _serializer);
}

private JObject SerializeAttributes(ResourceGraphNode node, FieldsetProperty fieldset)
{
var attributeHash = node.Resource.Attributes
.Where(a =>
node.SourceObject.IncludesProperty(_propertyNameConverter.ToModelPropertyName(a.InternalName)) && fieldset.Fields.Contains(_propertyNameConverter.ToModelPropertyName(a.InternalName)))
.Select(a =>
new
{
Key = _propertyNameConverter.ToJsonPropertyName(a.InternalName),
Value = node.SourceObject.GetValueOfProperty(_propertyNameConverter.ToModelPropertyName(a.InternalName))
})
.ToDictionary(
kvp => kvp.Key,
kvp => kvp.Value);

return JObject.FromObject(attributeHash, _serializer);
}

private JObject SerializeRelationships(ResourceGraphNode node)
{
if (node.Relationships.Count == 0)
Expand Down
30 changes: 30 additions & 0 deletions Tests/JsonApiSerializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,36 @@ public void ConditionallyAppliesFilters()

}

[Fact(DisplayName = "Uses query fieldset expressions if specified")]
public void UsesQueryFieldsetExpressions()
{
var target = new JsonApiSerializer<CompanyResource>
{
AllowQuery = true
};
var companies = Get.Companies(1).ToList();
var result = target.Serialize(companies, new Uri(DefaultUrl, "?fields[corporation]=Name,Location"));
_output.WriteLine(result.ToString());

Assert.NotNull(result["data"][0]["attributes"]["name"]);
Assert.NotNull(result["data"][0]["attributes"]["location"]);
Assert.Null(result["data"][0]["attributes"]["number-of-employees"]);
}

[Fact(DisplayName = "Returns no fields if requested field is not part of that model")]
public void ReturnEmptyModelForUnknownFieldsetExpressions()
{
var target = new JsonApiSerializer<CompanyResource>
{
AllowQuery = true
};
var companies = Get.Companies(1).ToList();
var result = target.Serialize(companies, new Uri(DefaultUrl, "?fields[corporation]=Notafield"));
_output.WriteLine(result.ToString());

Assert.False(((JToken)result["data"][0]["attributes"]).HasValues);
}

[Fact(DisplayName = "Does not allow null Uri")]
public void HasAContract()
{
Expand Down
14 changes: 7 additions & 7 deletions Tests/Serialization/MetadataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public void SerializesObjectsCorrectly()
new Uri("http://localhost/people/123"),
new DefaultUrlPathBuilder(),
null,
null);
null, null);

var result = target.Serialize();
_output.WriteLine(result.ToString());
Expand Down Expand Up @@ -67,7 +67,7 @@ public void SerializesDictionariesCorrectly()
new Uri("http://localhost/people/123"),
new DefaultUrlPathBuilder(),
null,
null);
null, null);

var result = target.Serialize();
_output.WriteLine(result.ToString());
Expand Down Expand Up @@ -95,7 +95,7 @@ public void PassesThroughJTokens()
new Uri("http://localhost/people/123"),
new DefaultUrlPathBuilder(),
null,
null);
null, null);

var result = target.Serialize();
_output.WriteLine(result.ToString());
Expand All @@ -121,7 +121,7 @@ public void SerializesCollectionsCorrectly()
new Uri("http://localhost/people/123"),
new DefaultUrlPathBuilder(),
null,
null);
null, null);

var result = target.Serialize();
_output.WriteLine(result.ToString());
Expand All @@ -146,7 +146,7 @@ public void CallsGetMetadataForCorrectly()
new Uri("http://localhost/people/123"),
new DefaultUrlPathBuilder(),
null,
null);
null, null);

target.Serialize();

Expand All @@ -160,7 +160,7 @@ public void CallsGetMetadataForCorrectly()
new Uri("http://localhost/people/123"),
new DefaultUrlPathBuilder(),
null,
null);
null, null);

target.Serialize();

Expand All @@ -178,7 +178,7 @@ public void EmptyForNull()
new Uri("http://localhost/people/123"),
new DefaultUrlPathBuilder(),
null,
null);
null, null);

var result = target.Serialize();
_output.WriteLine(result.ToString());
Expand Down
6 changes: 3 additions & 3 deletions Tests/Serialization/ResourceDeserializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ public ResourceDeserializerTests()
_people = Get.People(5).ToArray();
var singleSerializer = new ResourceSerializer(
_person, new PersonResource(), new Uri("http://example.com/people/1"),
new DefaultUrlPathBuilder(), null, null);
new DefaultUrlPathBuilder(), null, null, null);
var multiSerializer = new ResourceSerializer(
_people, new PersonResource(), new Uri("http://example.com/people/"),
new DefaultUrlPathBuilder(), null, null);
new DefaultUrlPathBuilder(), null, null, null);

_singleJson = JToken.Parse(singleSerializer.Serialize().ToString());
_collectionJson = JToken.Parse(multiSerializer.Serialize().ToString());
Expand All @@ -53,7 +53,7 @@ public void DeserializesCamelCase()

var singleSerializer = new ResourceSerializer(
_person, new PersonResource(), new Uri("http://example.com/people/1"),
new DefaultUrlPathBuilder(), null, null, camelCasePropertyNameConverter);
new DefaultUrlPathBuilder(), null, null, null, camelCasePropertyNameConverter);

var singleJson = JToken.Parse(singleSerializer.Serialize().ToString());

Expand Down
Loading