From 33a03a51c6ee7ffb0b0e9ca402d83b762c0794af Mon Sep 17 00:00:00 2001 From: Janis Walliser Date: Sat, 8 Sep 2018 22:38:49 +0200 Subject: [PATCH 1/6] Added fields as query parameter --- Saule/Constants.cs | 1 + Saule/Http/AllowsQueryAttribute.cs | 2 + Saule/JsonApiSerializer.cs | 3 +- Saule/JsonApiSerializerOfT.cs | 2 + Saule/Queries/Fieldset/FieldsetContext.cs | 37 ++++++++++ Saule/Queries/Fieldset/FieldsetProperty.cs | 34 +++++++++ Saule/Queries/QueryContext.cs | 9 ++- Saule/Saule.csproj | 2 + Saule/Serialization/ResourceSerializer.cs | 34 ++++++++- Tests/JsonApiSerializerTests.cs | 16 +++++ Tests/Serialization/MetadataTests.cs | 14 ++-- .../ResourceDeserializerTests.cs | 6 +- .../Serialization/ResourceSerializerTests.cs | 72 +++++++++---------- Tests/Serialization/UrlConstructionTests.cs | 36 +++++----- 14 files changed, 201 insertions(+), 67 deletions(-) create mode 100644 Saule/Queries/Fieldset/FieldsetContext.cs create mode 100644 Saule/Queries/Fieldset/FieldsetProperty.cs diff --git a/Saule/Constants.cs b/Saule/Constants.cs index 1a3add6..dc2982c 100644 --- a/Saule/Constants.cs +++ b/Saule/Constants.cs @@ -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 diff --git a/Saule/Http/AllowsQueryAttribute.cs b/Saule/Http/AllowsQueryAttribute.cs index 6354b01..e222bb1 100644 --- a/Saule/Http/AllowsQueryAttribute.cs +++ b/Saule/Http/AllowsQueryAttribute.cs @@ -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; @@ -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) { diff --git a/Saule/JsonApiSerializer.cs b/Saule/JsonApiSerializer.cs index ce6d4a2..cb98538 100644 --- a/Saule/JsonApiSerializer.cs +++ b/Saule/JsonApiSerializer.cs @@ -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) { diff --git a/Saule/JsonApiSerializerOfT.cs b/Saule/JsonApiSerializerOfT.cs index a25f2fe..0a7a01a 100644 --- a/Saule/JsonApiSerializerOfT.cs +++ b/Saule/JsonApiSerializerOfT.cs @@ -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; @@ -130,6 +131,7 @@ private QueryContext GetQueryContext(IEnumerable> 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; diff --git a/Saule/Queries/Fieldset/FieldsetContext.cs b/Saule/Queries/Fieldset/FieldsetContext.cs new file mode 100644 index 0000000..d17aff9 --- /dev/null +++ b/Saule/Queries/Fieldset/FieldsetContext.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Saule.Queries.Fieldset +{ + /// + /// Context for fieldset operations + /// + public class FieldsetContext + { + /// + /// Initializes a new instance of the class. + /// + /// query string that might contain Fieldset keyword + public FieldsetContext(IEnumerable> 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); + } + + /// + /// Gets including properties + /// + public IEnumerable Properties { get; internal set; } + + /// + public override string ToString() + { + return string.Empty; + //return Includes != null && Includes.Any() ? "include=" + string.Join(",", Includes.Select(p => p.ToString())) : string.Empty; + } + } +} diff --git a/Saule/Queries/Fieldset/FieldsetProperty.cs b/Saule/Queries/Fieldset/FieldsetProperty.cs new file mode 100644 index 0000000..eba157c --- /dev/null +++ b/Saule/Queries/Fieldset/FieldsetProperty.cs @@ -0,0 +1,34 @@ +namespace Saule.Queries.Fieldset +{ + /// + /// Property for fieldset + /// + public class FieldsetProperty + { + /// + /// Initializes a new instance of the class. + /// + /// property name + public FieldsetProperty(string type, string[] fields) + { + Type = type; + Fields = fields; + } + + /// + /// Gets property type + /// + public string Type { get; } + + /// + /// Gets property fields + /// + public string[] Fields { get; } + + /// + public override string ToString() + { + return $"filter[{Type}]={string.Join(",", Fields)}"; + } + } +} \ No newline at end of file diff --git a/Saule/Queries/QueryContext.cs b/Saule/Queries/QueryContext.cs index b7056ee..ba4f216 100644 --- a/Saule/Queries/QueryContext.cs +++ b/Saule/Queries/QueryContext.cs @@ -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; @@ -33,6 +34,11 @@ public class QueryContext /// public IncludeContext Include { get; internal set; } + /// + /// Gets include context + /// + public FieldsetContext Fieldset { get; internal set; } + /// /// Gets or sets a value indicating whether that query parameters /// will be handled by action itself or Saule should handle them @@ -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))); diff --git a/Saule/Saule.csproj b/Saule/Saule.csproj index 111a7b0..67c68b1 100644 --- a/Saule/Saule.csproj +++ b/Saule/Saule.csproj @@ -70,6 +70,8 @@ + + diff --git a/Saule/Serialization/ResourceSerializer.cs b/Saule/Serialization/ResourceSerializer.cs index f73cbca..51086ad 100644 --- a/Saule/Serialization/ResourceSerializer.cs +++ b/Saule/Serialization/ResourceSerializer.cs @@ -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; @@ -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; @@ -28,6 +30,7 @@ public ResourceSerializer( IUrlPathBuilder urlBuilder, PaginationContext paginationContext, IncludeContext includeContext, + FieldsetContext fieldsetContext, IPropertyNameConverter propertyNameConverter = null) { _propertyNameConverter = propertyNameConverter ?? new DefaultPropertyNameConverter(); @@ -37,6 +40,7 @@ public ResourceSerializer( _baseUrl = baseUrl; _paginationContext = paginationContext; _includeContext = includeContext; + _fieldsetContext = fieldsetContext; _includedGraphPaths = IncludedGraphPathsFromContext(includeContext); } @@ -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; @@ -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) diff --git a/Tests/JsonApiSerializerTests.cs b/Tests/JsonApiSerializerTests.cs index 26e5be8..77d35e9 100644 --- a/Tests/JsonApiSerializerTests.cs +++ b/Tests/JsonApiSerializerTests.cs @@ -68,6 +68,22 @@ public void ConditionallyAppliesFilters() } + [Fact(DisplayName = "Uses query fieldset expressions if specified")] + public void UsesQueryFieldsetExpressions() + { + var target = new JsonApiSerializer + { + 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() { diff --git a/Tests/Serialization/MetadataTests.cs b/Tests/Serialization/MetadataTests.cs index 4bf3344..8395476 100644 --- a/Tests/Serialization/MetadataTests.cs +++ b/Tests/Serialization/MetadataTests.cs @@ -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()); @@ -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()); @@ -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()); @@ -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()); @@ -146,7 +146,7 @@ public void CallsGetMetadataForCorrectly() new Uri("http://localhost/people/123"), new DefaultUrlPathBuilder(), null, - null); + null, null); target.Serialize(); @@ -160,7 +160,7 @@ public void CallsGetMetadataForCorrectly() new Uri("http://localhost/people/123"), new DefaultUrlPathBuilder(), null, - null); + null, null); target.Serialize(); @@ -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()); diff --git a/Tests/Serialization/ResourceDeserializerTests.cs b/Tests/Serialization/ResourceDeserializerTests.cs index 50bbd05..a8987bb 100644 --- a/Tests/Serialization/ResourceDeserializerTests.cs +++ b/Tests/Serialization/ResourceDeserializerTests.cs @@ -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()); @@ -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()); diff --git a/Tests/Serialization/ResourceSerializerTests.cs b/Tests/Serialization/ResourceSerializerTests.cs index a015134..ff79e4f 100644 --- a/Tests/Serialization/ResourceSerializerTests.cs +++ b/Tests/Serialization/ResourceSerializerTests.cs @@ -43,7 +43,7 @@ public void HandlesRecursiveProperties() fourthModel.Parent = thirdModel; var target = new ResourceSerializer(firstModel, new Recursion.FirstModelResource(), - GetUri(id: firstModel.Id), DefaultPathBuilder, null, null); + GetUri(id: firstModel.Id), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -77,7 +77,7 @@ public void UsesDefaultPropertyId() { var data = new PersonWithNoJob(); var target = new ResourceSerializer(data, new PersonWithDefaultIdResource(), - GetUri(id: "123"), DefaultPathBuilder, null, null); + GetUri(id: "123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -91,7 +91,7 @@ public void UsesDefaultPropertyId() public void UsesSpecifiedPropertyId() { var target = new ResourceSerializer(DefaultObject, DefaultResource, - GetUri(id: "abc"), DefaultPathBuilder, null, null); + GetUri(id: "abc"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -107,7 +107,7 @@ public void UsesCustomIdInUrls() var person = Get.Person(id: "abc"); person.Friends = Get.People(1); var target = new ResourceSerializer(person, DefaultResource, - GetUri(id: "abc"), DefaultPathBuilder, null, null); + GetUri(id: "abc"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -144,7 +144,7 @@ public void UsesCustomIdInCollections() }; var target = new ResourceSerializer(person, DefaultResource, - GetUri(id: "abc"), DefaultPathBuilder, null, null); + GetUri(id: "abc"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -161,7 +161,7 @@ public void UsesCustomIdInRelationships() var person = new PersonWithDifferentId(id: "abc", prefill: true); var resource = new PersonWithDifferentIdResource(); var target = new ResourceSerializer(person, resource, - GetUri(id: "abc"), DefaultPathBuilder, null, null); + GetUri(id: "abc"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -175,7 +175,7 @@ public void UsesCustomIdInRelationships() public void AttributesComplete() { var target = new ResourceSerializer(DefaultObject, DefaultResource, - GetUri(id: "123"), DefaultPathBuilder, null, null); + GetUri(id: "123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -191,7 +191,7 @@ public void AttributesComplete() public void AttributesSufficient() { var target = new ResourceSerializer(DefaultObject, DefaultResource, - GetUri(id: "123"), DefaultPathBuilder, null, null); + GetUri(id: "123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -206,7 +206,7 @@ public void UsesTitle() var company = Get.Company(); var target = new ResourceSerializer(company, new CompanyResource(), GetUri("/corporations", "456"), - DefaultPathBuilder, null, null); + DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -218,7 +218,7 @@ public void ThrowsRightException() { var person = new PersonWithNoId(); var target = new ResourceSerializer(person, DefaultResource, - GetUri(id: "123"), DefaultPathBuilder, null, null); + GetUri(id: "123"), DefaultPathBuilder, null, null, null); Assert.Throws(() => { @@ -231,7 +231,7 @@ public void SerializesRelationshipData() { var person = new PersonWithNoJob(); var target = new ResourceSerializer(person, new PersonWithDefaultIdResource(), - GetUri(id: "123"), DefaultPathBuilder, null, null); + GetUri(id: "123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -250,7 +250,7 @@ public void SerializesRelationshipDataAsNull() var person = Get.Person(id: "123"); person.Job = null; var target = new ResourceSerializer(person, DefaultResource, - GetUri(id: "123"), DefaultPathBuilder, null, null); + GetUri(id: "123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -267,7 +267,7 @@ public void SerializesRelationshipDataAsNull() public void IncludesRelationshipData() { var target = new ResourceSerializer(DefaultObject, DefaultResource, - GetUri(id: "123"), DefaultPathBuilder, null, null); + GetUri(id: "123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -286,7 +286,7 @@ public void NoIncludedRelationshipData() var includes = new IncludeContext(); includes.DisableDefaultIncluded = true; var target = new ResourceSerializer(DefaultObject, DefaultResource, - GetUri(id: "123"), DefaultPathBuilder, null, includes); + GetUri(id: "123"), DefaultPathBuilder, null, includes, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -304,7 +304,7 @@ public void OnlyIncludedRelationshipData() var includeParam = new KeyValuePair("include", "job"); includes.SetIncludes(new List>() { includeParam }); var target = new ResourceSerializer(DefaultObject, DefaultResource, - GetUri(id: "123"), DefaultPathBuilder, null, includes); + GetUri(id: "123"), DefaultPathBuilder, null, includes, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -320,7 +320,7 @@ public void IncludedRelationshipIdentifierObjects() var includes = new IncludeContext(); includes.DisableDefaultIncluded = true; var target = new ResourceSerializer(DefaultObject, DefaultResource, - GetUri(id: "123"), DefaultPathBuilder, null, includes); + GetUri(id: "123"), DefaultPathBuilder, null, includes, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -337,7 +337,7 @@ public void OmitRelationshipIdentifierObjectsWithoutProperty() var includes = new IncludeContext(); includes.DisableDefaultIncluded = true; var target = new ResourceSerializer(DefaultObject, DefaultResource, - GetUri(id: "123"), DefaultPathBuilder, null, includes); + GetUri(id: "123"), DefaultPathBuilder, null, includes, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -357,7 +357,7 @@ public void IncludedResourceRelationshipURLsAreCorrect() }; var target = new ResourceSerializer(person, new PersonWithCompanyWithCustomersResource(), - GetUri(id: "123"), DefaultPathBuilder, null, null); + GetUri(id: "123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -385,7 +385,7 @@ public void IncludedResourceOnlyOnce() var include = new IncludeContext(GetQuery("include", "friends.job")); var target = new ResourceSerializer(person, DefaultResource, - GetUri(id: "123"), DefaultPathBuilder, null, include); + GetUri(id: "123"), DefaultPathBuilder, null, include, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -399,7 +399,7 @@ public void HandlesNullValues() { var person = new Person(id: "45"); var target = new ResourceSerializer(person, DefaultResource, - GetUri(id: "45"), DefaultPathBuilder, null, new IncludeContext { DisableDefaultIncluded = true }); + GetUri(id: "45"), DefaultPathBuilder, null, new IncludeContext { DisableDefaultIncluded = true }, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -420,7 +420,7 @@ public void SerializesEnumerables() { var people = Get.People(5); var target = new ResourceSerializer(people, DefaultResource, - GetUri(), DefaultPathBuilder, null, null); + GetUri(), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -436,7 +436,7 @@ public void DocumentMustContainAtLeastOneDataOrErrorOrMeta() { var people = new Person[] { }; var target = new ResourceSerializer(people, DefaultResource, - GetUri(), DefaultPathBuilder, null, null); + GetUri(), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -448,7 +448,7 @@ public void DocumentMustNotContainIncludedForEmptySet() { var people = new Person[0]; var target = new ResourceSerializer(people, DefaultResource, - GetUri(), DefaultPathBuilder, null, null); + GetUri(), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -459,7 +459,7 @@ public void DocumentMustNotContainIncludedForEmptySet() public void HandlesNullResources() { var target = new ResourceSerializer(null, DefaultResource, - GetUri(), DefaultPathBuilder, null, null); + GetUri(), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -471,7 +471,7 @@ public void SupportsGuidIds() { var guid = new GuidAsId(); var serializer = new ResourceSerializer(guid, new PersonWithDefaultIdResource(), - GetUri(id: "123"), DefaultPathBuilder, null, null); + GetUri(id: "123"), DefaultPathBuilder, null, null, null); var guidResult = serializer.Serialize(); _output.WriteLine(guidResult.ToString()); @@ -485,7 +485,7 @@ public void SupportsGuidIdsInCollections() { var guids = new [] { new GuidAsId(), new GuidAsId() }; var serializer = new ResourceSerializer(guids, new PersonWithDefaultIdResource(), - GetUri(id: "123"), DefaultPathBuilder, null, null); + GetUri(id: "123"), DefaultPathBuilder, null, null, null); var guidsResult = serializer.Serialize(); _output.WriteLine(guidsResult.ToString()); @@ -498,7 +498,7 @@ public void SupportsGuidsAsRelations() { var relatedToGuidId = new GuidAsRelation(); var serializer = new ResourceSerializer( relatedToGuidId, new PersonWithGuidAsRelationsResource(), - GetUri(id: "123"), DefaultPathBuilder, null, null); + GetUri(id: "123"), DefaultPathBuilder, null, null, null); var result = serializer.Serialize(); _output.WriteLine(result.ToString()); @@ -512,7 +512,7 @@ public void SerializeOnlyWhatYouHave() { var company = new GuidAsId(); var serializer = new ResourceSerializer(company, new CompanyResource(), - GetUri(id: "123"), DefaultPathBuilder, null, null); + GetUri(id: "123"), DefaultPathBuilder, null, null, null); var result = serializer.Serialize(); _output.WriteLine(result.ToString()); @@ -527,7 +527,7 @@ public void SerializeWithCamelCase() { var person = new Person(false, "1"); var serializer = new ResourceSerializer(person, new PersonResource(), - GetUri(id: "123/camelCase"), DefaultPathBuilder, null, null, new CamelCasePropertyNameConverter()); + GetUri(id: "123/camelCase"), DefaultPathBuilder, null, null, null, new CamelCasePropertyNameConverter()); var result = serializer.Serialize(); _output.WriteLine(result.ToString()); @@ -547,7 +547,7 @@ public void ResourceObjectsAreNotDuplicated() var people = new Person[] { personA, personB }; var target = new ResourceSerializer(people, DefaultResource, - GetUri(), DefaultPathBuilder, null, null); + GetUri(), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); var data = result["data"] as JArray; @@ -573,7 +573,7 @@ public void SerializesCollectionsAsSuch() var people = new[] { new Person(true, "123") }; var target = new ResourceSerializer(people, DefaultResource, - GetUri(), DefaultPathBuilder, null, null); + GetUri(), DefaultPathBuilder, null, null, null); var result = target.Serialize(); Assert.True(result["data"] is JArray); @@ -593,7 +593,7 @@ public void IncludedResourcesHaveCorrectRelationshipLinkage() var somePeople = new Person[] { personA, personB }; var target = new ResourceSerializer(somePeople, DefaultResource, - GetUri(), DefaultPathBuilder, null, null); + GetUri(), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -620,7 +620,7 @@ public void AllIdsAreSerializedAsStrings() }; var target = new ResourceSerializer(w, new WidgetResource(), - GetUri(), DefaultPathBuilder, null, null); + GetUri(), DefaultPathBuilder, null, null, null); var result = target.Serialize(); Assert.True(result["data"]?["id"]?.Type == JTokenType.String); @@ -639,7 +639,7 @@ public void SerializesDictionaries() }; var target = new ResourceSerializer(person, DefaultResource, - GetUri(id: "1"), DefaultPathBuilder, null, null); + GetUri(id: "1"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -660,7 +660,7 @@ public void SerializesDynamics() person.NumberOfLegs = 4; var target = new ResourceSerializer(person, DefaultResource, - GetUri(id: "1"), DefaultPathBuilder, null, null); + GetUri(id: "1"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -677,7 +677,7 @@ public void OnlySerializesAttributesInTheResource() personMock.SetupGet(p => p.Identifier).Returns("123"); var target = new ResourceSerializer(personMock.Object, DefaultResource, - GetUri(id: "1"), DefaultPathBuilder, null, null); + GetUri(id: "1"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); diff --git a/Tests/Serialization/UrlConstructionTests.cs b/Tests/Serialization/UrlConstructionTests.cs index be0a6ef..87f4ce8 100644 --- a/Tests/Serialization/UrlConstructionTests.cs +++ b/Tests/Serialization/UrlConstructionTests.cs @@ -27,7 +27,7 @@ public UrlConstructionTests(ITestOutputHelper output) public void HandlesQueryParams() { var target = new ResourceSerializer(Get.Person(), new PersonResource(), - GetUri("123", "a=b&c=d"), DefaultPathBuilder, null, null); + GetUri("123", "a=b&c=d"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -47,7 +47,7 @@ public void SelfLinksInCollection() { var people = Get.People(5); var target = new ResourceSerializer(people, new PersonResource(), - GetUri(), DefaultPathBuilder, null, null); + GetUri(), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -63,7 +63,7 @@ public void SelfLinksInCollection() public void NoSelfLinksInObject() { var target = new ResourceSerializer(Get.Person(), new PersonResource(), - GetUri("123"), DefaultPathBuilder, null, null); + GetUri("123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -76,7 +76,7 @@ public void NoSelfLinksInObject() public void SelfLink() { var target = new ResourceSerializer(Get.Person(), new PersonResource(), - GetUri("123"), DefaultPathBuilder, null, null); + GetUri("123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -89,7 +89,7 @@ public void SelfLink() public void NoTopLevelLinks() { var target = new ResourceSerializer(Get.Person(), new PersonNoLinksResource(), - GetUri("123"), DefaultPathBuilder, null, null); + GetUri("123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -102,7 +102,7 @@ public void NextLink() var people = Get.People(5); var target = new ResourceSerializer(people, new PersonResource(), GetUri(), DefaultPathBuilder, - new PaginationContext(GetQuery(Constants.QueryNames.PageNumber, "2"), pageSizeDefault: 10), null); + new PaginationContext(GetQuery(Constants.QueryNames.PageNumber, "2"), pageSizeDefault: 10), null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -110,7 +110,7 @@ public void NextLink() target = new ResourceSerializer(people, new PersonResource(), GetUri(), DefaultPathBuilder, - new PaginationContext(GetQuery(Constants.QueryNames.PageNumber, "2"), pageSizeDefault: 4), null); + new PaginationContext(GetQuery(Constants.QueryNames.PageNumber, "2"), pageSizeDefault: 4), null, null); result = target.Serialize(); var nextLink = Uri.UnescapeDataString(result["links"].Value("next").Query); @@ -123,7 +123,7 @@ public void PreviousLink() var people = Get.People(5); var target = new ResourceSerializer(people, new PersonResource(), GetUri(), DefaultPathBuilder, - new PaginationContext(GetQuery(Constants.QueryNames.PageNumber, "0"), pageSizeDefault: 10), null); + new PaginationContext(GetQuery(Constants.QueryNames.PageNumber, "0"), pageSizeDefault: 10), null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -131,7 +131,7 @@ public void PreviousLink() target = new ResourceSerializer(people, new PersonResource(), GetUri(), DefaultPathBuilder, - new PaginationContext(GetQuery(Constants.QueryNames.PageNumber, "1"), pageSizeDefault: 10), null); + new PaginationContext(GetQuery(Constants.QueryNames.PageNumber, "1"), pageSizeDefault: 10), null, null); result = target.Serialize(); var nextLink = Uri.UnescapeDataString(result["links"].Value("prev").Query); @@ -144,7 +144,7 @@ public void PaginationQueryParams() var people = Get.People(5); var target = new ResourceSerializer(people, new PersonResource(), GetUri(query: "q=a"), DefaultPathBuilder, - new PaginationContext(GetQuery("q", "a"), pageSizeDefault: 4), null); + new PaginationContext(GetQuery("q", "a"), pageSizeDefault: 4), null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -159,7 +159,7 @@ public void FirstLink() var people = Get.People(5); var target = new ResourceSerializer(people, new PersonResource(), GetUri(), DefaultPathBuilder, - new PaginationContext(Enumerable.Empty>(), pageSizeDefault: 4), null); + new PaginationContext(Enumerable.Empty>(), pageSizeDefault: 4), null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -172,7 +172,7 @@ public void FirstLink() public void SerializesRelationshipLinks() { var target = new ResourceSerializer(Get.Person(), new PersonResource(), - GetUri("123"), DefaultPathBuilder, null, null); + GetUri("123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -194,7 +194,7 @@ public void SerializeDifferentBuilder() var person = Get.Person(); person.Friends = Get.People(1); var target = new ResourceSerializer(person, new PersonResource(), - GetUri("123"), new CanonicalUrlPathBuilder(), null, null); + GetUri("123"), new CanonicalUrlPathBuilder(), null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -213,7 +213,7 @@ public void SerializeDifferentBuilder() public void BuildsRightLinks() { var target = new ResourceSerializer(Get.Person(), new PersonResource(), - GetUri("123"), DefaultPathBuilder, null, null); + GetUri("123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -229,7 +229,7 @@ public void BuildsRightLinks() public void NoRelLinks() { var target = new ResourceSerializer(Get.Person(), new PersonNoJobLinksResource(), - GetUri("123"), DefaultPathBuilder, null, null); + GetUri("123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -242,7 +242,7 @@ public void NoRelLinks() public void NoRelRelLinks() { var target = new ResourceSerializer(Get.Person(), new PersonJobOnlySelfLinksResource(), - GetUri("123"), DefaultPathBuilder, null, null); + GetUri("123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -257,7 +257,7 @@ public void NoRelRelLinks() public void NoRelSelfLinks() { var target = new ResourceSerializer(Get.Person(), new PersonJobOnlyRelatedLinksResource(), - GetUri("123"), DefaultPathBuilder, null, null); + GetUri("123"), DefaultPathBuilder, null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); @@ -272,7 +272,7 @@ public void NoRelSelfLinks() public void UrlBuilder() { var target = new ResourceSerializer(Get.Person(), new PersonResource(), - GetUri("123"), new EmptyUrlBuilder(), null, null); + GetUri("123"), new EmptyUrlBuilder(), null, null, null); var result = target.Serialize(); _output.WriteLine(result.ToString()); From 461cebc8fde8d5f844f9854a580de194b0fde524 Mon Sep 17 00:00:00 2001 From: Janis Walliser Date: Sat, 8 Sep 2018 22:46:29 +0200 Subject: [PATCH 2/6] Added xml comments --- Saule/Queries/Fieldset/FieldsetContext.cs | 2 +- Saule/Queries/Fieldset/FieldsetProperty.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Saule/Queries/Fieldset/FieldsetContext.cs b/Saule/Queries/Fieldset/FieldsetContext.cs index d17aff9..cc24d5e 100644 --- a/Saule/Queries/Fieldset/FieldsetContext.cs +++ b/Saule/Queries/Fieldset/FieldsetContext.cs @@ -11,7 +11,7 @@ public class FieldsetContext /// /// Initializes a new instance of the class. /// - /// query string that might contain Fieldset keyword + /// query string that might contain Fieldset keyword public FieldsetContext(IEnumerable> queryParams) { Properties = diff --git a/Saule/Queries/Fieldset/FieldsetProperty.cs b/Saule/Queries/Fieldset/FieldsetProperty.cs index eba157c..278cca2 100644 --- a/Saule/Queries/Fieldset/FieldsetProperty.cs +++ b/Saule/Queries/Fieldset/FieldsetProperty.cs @@ -8,7 +8,8 @@ public class FieldsetProperty /// /// Initializes a new instance of the class. /// - /// property name + /// type for field filter + /// fields to serialize filter public FieldsetProperty(string type, string[] fields) { Type = type; From 08cec56752944f6b8f4e6b279af8af615db33247 Mon Sep 17 00:00:00 2001 From: Janis Walliser Date: Sat, 8 Sep 2018 22:48:22 +0200 Subject: [PATCH 3/6] Also fixed tabs --- Saule/JsonApiSerializerOfT.cs | 2 +- Saule/Queries/Fieldset/FieldsetContext.cs | 8 ++++---- Saule/Queries/Fieldset/FieldsetProperty.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Saule/JsonApiSerializerOfT.cs b/Saule/JsonApiSerializerOfT.cs index 0a7a01a..cf86ebe 100644 --- a/Saule/JsonApiSerializerOfT.cs +++ b/Saule/JsonApiSerializerOfT.cs @@ -131,7 +131,7 @@ private QueryContext GetQueryContext(IEnumerable> f context.Sort = new SortContext(keyValuePairs); context.Filter = new FilterContext(keyValuePairs) { QueryFilters = QueryFilterExpressions }; context.Include = new IncludeContext(keyValuePairs); - context.Fieldset = new FieldsetContext(keyValuePairs); + context.Fieldset = new FieldsetContext(keyValuePairs); } return context; diff --git a/Saule/Queries/Fieldset/FieldsetContext.cs b/Saule/Queries/Fieldset/FieldsetContext.cs index cc24d5e..86b6649 100644 --- a/Saule/Queries/Fieldset/FieldsetContext.cs +++ b/Saule/Queries/Fieldset/FieldsetContext.cs @@ -14,10 +14,10 @@ public class FieldsetContext /// query string that might contain Fieldset keyword public FieldsetContext(IEnumerable> queryParams) { - Properties = - from query in queryParams - where query.Key.StartsWith(Constants.QueryNames.Fieldset) - let type = query.Key.Substring(Constants.QueryNames.Fieldset.Length + 1) + 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); } diff --git a/Saule/Queries/Fieldset/FieldsetProperty.cs b/Saule/Queries/Fieldset/FieldsetProperty.cs index 278cca2..f4e39f0 100644 --- a/Saule/Queries/Fieldset/FieldsetProperty.cs +++ b/Saule/Queries/Fieldset/FieldsetProperty.cs @@ -9,7 +9,7 @@ public class FieldsetProperty /// Initializes a new instance of the class. /// /// type for field filter - /// fields to serialize filter + /// fields to serialize filter public FieldsetProperty(string type, string[] fields) { Type = type; From ed4de8ed36a73ed0c653907c6298d8f4c2728e2b Mon Sep 17 00:00:00 2001 From: Janis Walliser Date: Sat, 8 Sep 2018 23:01:58 +0200 Subject: [PATCH 4/6] Added a correct ToString method --- Saule/Queries/Fieldset/FieldsetContext.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Saule/Queries/Fieldset/FieldsetContext.cs b/Saule/Queries/Fieldset/FieldsetContext.cs index 86b6649..fb5a7ce 100644 --- a/Saule/Queries/Fieldset/FieldsetContext.cs +++ b/Saule/Queries/Fieldset/FieldsetContext.cs @@ -30,8 +30,7 @@ where query.Key.StartsWith(Constants.QueryNames.Fieldset) /// public override string ToString() { - return string.Empty; - //return Includes != null && Includes.Any() ? "include=" + string.Join(",", Includes.Select(p => p.ToString())) : string.Empty; + return Properties != null && Properties.Any() ? string.Join("&", Properties.Select(p => p.ToString())) : string.Empty; } } } From 79c9a8600409e9175152bc2c35873abd96cfa54b Mon Sep 17 00:00:00 2001 From: Janis Walliser Date: Mon, 10 Sep 2018 08:36:49 +0200 Subject: [PATCH 5/6] Fixed to string method of FieldsetProperty --- Saule/Queries/Fieldset/FieldsetProperty.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Saule/Queries/Fieldset/FieldsetProperty.cs b/Saule/Queries/Fieldset/FieldsetProperty.cs index f4e39f0..e3f523b 100644 --- a/Saule/Queries/Fieldset/FieldsetProperty.cs +++ b/Saule/Queries/Fieldset/FieldsetProperty.cs @@ -29,7 +29,7 @@ public FieldsetProperty(string type, string[] fields) /// public override string ToString() { - return $"filter[{Type}]={string.Join(",", Fields)}"; + return $"fields[{Type}]={string.Join(",", Fields)}"; } } } \ No newline at end of file From a0ee8584e64fe2562d16f05a175a6225962dee00 Mon Sep 17 00:00:00 2001 From: Janis Walliser Date: Mon, 10 Sep 2018 08:52:34 +0200 Subject: [PATCH 6/6] Added test for fields not known to type --- Tests/JsonApiSerializerTests.cs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Tests/JsonApiSerializerTests.cs b/Tests/JsonApiSerializerTests.cs index 77d35e9..1edb5d0 100644 --- a/Tests/JsonApiSerializerTests.cs +++ b/Tests/JsonApiSerializerTests.cs @@ -68,7 +68,7 @@ public void ConditionallyAppliesFilters() } - [Fact(DisplayName = "Uses query fieldset expressions if specified")] + [Fact(DisplayName = "Uses query fieldset expressions if specified")] public void UsesQueryFieldsetExpressions() { var target = new JsonApiSerializer @@ -79,10 +79,24 @@ public void UsesQueryFieldsetExpressions() 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"]); - } + 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 + { + 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()