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 4 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 $"filter[{Type}]={string.Join(",", Fields)}";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should say fields instead of filter?

Copy link
Contributor Author

@PhyberApex PhyberApex Sep 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 79c9a86

}
}
}
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
16 changes: 16 additions & 0 deletions Tests/JsonApiSerializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,22 @@ public void ConditionallyAppliesFilters()

}

[Fact(DisplayName = "Uses query fieldset expressions if specified")]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a test where we ask for a field that's not on the model (should get nothing in that case).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the test in a0ee858. I think only the attributes should be empty as id and type field are still returned which I think would be correct behaviour at this point.

public void UsesQueryFieldsetExpressions()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like tabs and spaces got mixed in this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having some trouble detecting those on my machine I hope they got fixed with a0ee858

{
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 = "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