Skip to content

Commit ccae41a

Browse files
author
Bart Koelman
authored
Merge pull request #759 from bart-degreed/todos
Resolved most ToDo's in code
2 parents 477b55d + 6406282 commit ccae41a

File tree

9 files changed

+48
-55
lines changed

9 files changed

+48
-55
lines changed

src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ protected virtual List<AttrAttribute> GetAttributes(Type entityType)
9292
{
9393
var attribute = (AttrAttribute)property.GetCustomAttribute(typeof(AttrAttribute));
9494

95-
// TODO: investigate why this is added in the exposed attributes list
96-
// because it is not really defined attribute considered from the json:api
97-
// spec point of view.
95+
// Although strictly not correct, 'id' is added to the list of attributes for convenience.
96+
// For example, it enables to filter on id, without the need to special-case existing logic.
97+
// And when using sparse fields, it silently adds 'id' to the set of attributes to retrieve.
9898
if (property.Name == nameof(Identifiable.Id) && attribute == null)
9999
{
100100
var idAttr = new AttrAttribute

src/JsonApiDotNetCore/Data/DefaultResourceRepository.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,6 @@ private void DetachRelationships(TResource entity)
200200
else
201201
{
202202
_context.Entry(value).State = EntityState.Detached;
203-
204-
// temporary work around for https://github.com/aspnet/EntityFrameworkCore/issues/18621
205-
// as soon as ef core 3.1 lands we can get rid of this again.
206-
_context.Entry(entity).State = EntityState.Detached;
207203
}
208204
}
209205
}

src/JsonApiDotNetCore/Models/Annotation/AttrAttribute.cs

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,12 @@ public object GetValue(object entity)
9595
throw new ArgumentNullException(nameof(entity));
9696
}
9797

98-
var property = GetResourceProperty(entity);
99-
if (property.GetMethod == null)
98+
if (PropertyInfo.GetMethod == null)
10099
{
101-
throw new InvalidOperationException($"Property '{property.DeclaringType?.Name}.{property.Name}' is write-only.");
100+
throw new InvalidOperationException($"Property '{PropertyInfo.DeclaringType?.Name}.{PropertyInfo.Name}' is write-only.");
102101
}
103102

104-
return property.GetValue(entity);
103+
return PropertyInfo.GetValue(entity);
105104
}
106105

107106
/// <summary>
@@ -114,40 +113,14 @@ public void SetValue(object entity, object newValue)
114113
throw new ArgumentNullException(nameof(entity));
115114
}
116115

117-
var property = GetResourceProperty(entity);
118-
if (property.SetMethod == null)
116+
if (PropertyInfo.SetMethod == null)
119117
{
120118
throw new InvalidOperationException(
121-
$"Property '{property.DeclaringType?.Name}.{property.Name}' is read-only.");
119+
$"Property '{PropertyInfo.DeclaringType?.Name}.{PropertyInfo.Name}' is read-only.");
122120
}
123121

124-
var convertedValue = TypeHelper.ConvertType(newValue, property.PropertyType);
125-
property.SetValue(entity, convertedValue);
126-
}
127-
128-
private PropertyInfo GetResourceProperty(object resource)
129-
{
130-
// There are some scenarios, especially ones where users are using a different
131-
// data model than view model, where they may use a repository implementation
132-
// that does not match the deserialized type. For now, we will continue to support
133-
// this use case.
134-
var targetType = resource.GetType();
135-
if (targetType != PropertyInfo.DeclaringType)
136-
{
137-
var propertyInfo = resource
138-
.GetType()
139-
.GetProperty(PropertyInfo.Name);
140-
141-
return propertyInfo;
142-
143-
// TODO: this should throw but will be a breaking change in some cases
144-
//if (propertyInfo == null)
145-
// throw new InvalidOperationException(
146-
// $"'{targetType}' does not contain a member named '{InternalAttributeName}'." +
147-
// $"There is also a mismatch in target types. Expected '{PropertyInfo.DeclaringType}' but instead received '{targetType}'.");
148-
}
149-
150-
return PropertyInfo;
122+
var convertedValue = TypeHelper.ConvertType(newValue, PropertyInfo.PropertyType);
123+
PropertyInfo.SetValue(entity, convertedValue);
151124
}
152125

153126
/// <summary>

src/JsonApiDotNetCore/QueryParameterServices/FilterService.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ private FilterQueryContext GetQueryContexts(FilterQuery query, string parameterN
8181
return queryContext;
8282
}
8383

84-
/// todo: this could be simplified a bunch
8584
private List<FilterQuery> GetFilterQueries(string parameterName, StringValues parameterValue)
8685
{
8786
// expected input = filter[id]=1
@@ -107,7 +106,6 @@ private List<FilterQuery> GetFilterQueries(string parameterName, StringValues pa
107106
return queries;
108107
}
109108

110-
/// todo: this could be simplified a bunch
111109
private (string operation, string value) ParseFilterOperation(string value)
112110
{
113111
if (value.Length < 3)
@@ -124,7 +122,6 @@ private List<FilterQuery> GetFilterQueries(string parameterName, StringValues pa
124122
return (operation, value);
125123
}
126124

127-
/// todo: this could be simplified a bunch
128125
private string GetFilterOperation(string value)
129126
{
130127
var values = value.Split(QueryConstants.COLON);

src/JsonApiDotNetCore/QueryParameterServices/IncludeService.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ namespace JsonApiDotNetCore.Query
1212
{
1313
public class IncludeService : QueryParameterService, IIncludeService
1414
{
15-
/// todo: use read-only lists.
1615
private readonly List<List<RelationshipAttribute>> _includedChains;
1716

1817
public IncludeService(IResourceGraph resourceGraph, ICurrentRequest currentRequest) : base(resourceGraph, currentRequest)

src/JsonApiDotNetCore/QueryParameterServices/PageService.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,6 @@ public virtual void Parse(string parameterName, StringValues parameterValue)
9090
{
9191
var number = ParsePageNumber(parameterValue, _options.MaximumPageNumber);
9292

93-
// TODO: It doesn't seem right that a negative paging value reverses the sort order.
94-
// A better way would be to allow ?sort=- to indicate reversing results.
95-
// Then a negative paging value, like -5, could mean: "5 pages back from the last page"
96-
9793
Backwards = number < 0;
9894
CurrentPage = Backwards ? -number : number;
9995
}

src/JsonApiDotNetCore/QueryParameterServices/SparseFieldsService.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,9 @@ private void RegisterRelatedResourceFields(IEnumerable<string> fields, Relations
130130
var attr = relationProperty.Attributes.SingleOrDefault(a => a.Is(field));
131131
if (attr == null)
132132
{
133-
// TODO: Add unit test for this error, once the nesting limitation is removed and this code becomes reachable again.
134-
135133
throw new InvalidQueryStringParameterException(parameterName,
136-
"Sparse field navigation path refers to an invalid related field.",
137-
$"Related resource '{relationship.RightType.Name}' does not contain an attribute named '{field}'.");
134+
"The specified field does not exist on the requested related resource.",
135+
$"The field '{field}' does not exist on related resource '{relationship.PublicRelationshipName}' of type '{relationProperty.ResourceName}'.");
138136
}
139137

140138
if (attr.PropertyInfo.SetMethod == null)

src/JsonApiDotNetCore/RequestServices/DefaultResourceChangeTracker.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ private IDictionary<string, string> CreateAttributeDictionary(TResource resource
8080
foreach (var attribute in attributes)
8181
{
8282
object value = attribute.GetValue(resource);
83-
// TODO: Remove explicit cast to JsonApiOptions after https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/687 has been fixed.
84-
var json = JsonConvert.SerializeObject(value, ((JsonApiOptions) _options).SerializerSettings);
83+
var json = JsonConvert.SerializeObject(value, _options.SerializerSettings);
8584
result.Add(attribute.PublicAttributeName, json);
8685
}
8786

test/UnitTests/QueryParameters/SparseFieldsServiceTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using JsonApiDotNetCoreExample.Models;
88
using Microsoft.Extensions.Primitives;
99
using Xunit;
10+
using Person = UnitTests.TestModels.Person;
1011

1112
namespace UnitTests.QueryParameters
1213
{
@@ -162,6 +163,40 @@ public void Parse_InvalidField_ThrowsJsonApiException()
162163
Assert.Equal("fields", exception.Error.Source.Parameter);
163164
}
164165

166+
[Fact]
167+
public void Parse_InvalidRelatedField_ThrowsJsonApiException()
168+
{
169+
// Arrange
170+
var idAttribute = new AttrAttribute("id") {PropertyInfo = typeof(Article).GetProperty(nameof(Article.Id))};
171+
172+
var query = new KeyValuePair<string, StringValues>("fields[author]", "invalid");
173+
174+
var resourceContext = new ResourceContext
175+
{
176+
ResourceName = "articles",
177+
Attributes = new List<AttrAttribute> {idAttribute},
178+
Relationships = new List<RelationshipAttribute>
179+
{
180+
new HasOneAttribute("author")
181+
{
182+
PropertyInfo = typeof(Article).GetProperty(nameof(Article.Author)),
183+
RightType = typeof(Person)
184+
}
185+
}
186+
};
187+
188+
var service = GetService(resourceContext);
189+
190+
// Act, assert
191+
var exception = Assert.Throws<InvalidQueryStringParameterException>(() => service.Parse(query.Key, query.Value));
192+
193+
Assert.Equal("fields[author]", exception.QueryParameterName);
194+
Assert.Equal(HttpStatusCode.BadRequest, exception.Error.StatusCode);
195+
Assert.Equal("The specified field does not exist on the requested related resource.", exception.Error.Title);
196+
Assert.Equal("The field 'invalid' does not exist on related resource 'author' of type 'people'.", exception.Error.Detail);
197+
Assert.Equal("fields[author]", exception.Error.Source.Parameter);
198+
}
199+
165200
[Fact]
166201
public void Parse_LegacyNotation_ThrowsJsonApiException()
167202
{

0 commit comments

Comments
 (0)