diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index 30ddeca2ae..f93002dc53 100755 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -1,6 +1,6 @@  - 2.1.4 + 2.1.5 netstandard1.6 JsonApiDotNetCore JsonApiDotNetCore diff --git a/src/JsonApiDotNetCore/Services/IQueryAccessor.cs b/src/JsonApiDotNetCore/Services/IQueryAccessor.cs deleted file mode 100644 index 51b3ccbbbf..0000000000 --- a/src/JsonApiDotNetCore/Services/IQueryAccessor.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace JsonApiDotNetCore.Services -{ - public interface IQueryAccessor - { - bool TryGetValue(string key, out T value); - } -} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Services/QueryAccessor.cs b/src/JsonApiDotNetCore/Services/QueryAccessor.cs index 8134028a3e..0862e3fd3e 100644 --- a/src/JsonApiDotNetCore/Services/QueryAccessor.cs +++ b/src/JsonApiDotNetCore/Services/QueryAccessor.cs @@ -1,35 +1,54 @@ using System; -using System.Collections.Generic; using System.Linq; using JsonApiDotNetCore.Internal; using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Services { + public interface IQueryAccessor + { + bool TryGetValue(string key, out T value); + + /// + /// Gets the query value and throws a if it is not present. + /// If the exception is not caught, the middleware will return an HTTP 422 response. + /// + /// + T GetRequired(string key); + } + public class QueryAccessor : IQueryAccessor { private readonly IJsonApiContext _jsonApiContext; private readonly ILogger _logger; public QueryAccessor( - IJsonApiContext jsonApiContext, - ILoggerFactory loggerFactory) + IJsonApiContext jsonApiContext, + ILogger logger) { _jsonApiContext = jsonApiContext; - _logger = loggerFactory.CreateLogger(); + _logger = logger; + } + + public T GetRequired(string key) + { + if (TryGetValue(key, out T result) == false) + throw new JsonApiException(422, $"'{key}' is not a valid '{typeof(T).Name}' value for query parameter {key}"); + + return result; } public bool TryGetValue(string key, out T value) { value = default(T); - var stringValue = GetFilterValue(key); - if(stringValue == null) + var stringValue = GetFilterValue(key); + if (stringValue == null) { _logger.LogInformation($"'{key}' was not found in the query collection"); return false; } - + try { value = TypeHelper.ConvertType(stringValue); @@ -37,7 +56,7 @@ public bool TryGetValue(string key, out T value) } catch (FormatException) { - _logger.LogInformation($"'{value}' is not a valid guid value for query parameter {key}"); + _logger.LogInformation($"'{value}' is not a valid '{typeof(T).Name}' value for query parameter {key}"); return false; } } diff --git a/test/UnitTests/Services/QueryAccessorTests.cs b/test/UnitTests/Services/QueryAccessorTests.cs index df455b1b92..45538fde66 100644 --- a/test/UnitTests/Services/QueryAccessorTests.cs +++ b/test/UnitTests/Services/QueryAccessorTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; @@ -13,13 +14,13 @@ namespace UnitTests.Services public class QueryAccessorTests { private readonly Mock _contextMock; - private readonly Mock _loggerMock; + private readonly Mock> _loggerMock; private readonly Mock _queryMock; public QueryAccessorTests() { _contextMock = new Mock(); - _loggerMock = new Mock(); + _loggerMock = new Mock>(); _queryMock = new Mock(); } @@ -34,7 +35,7 @@ public void Can_Get_Guid_QueryValue() var query = new Dictionary { { filterQuery, value.ToString() } }; - + _queryMock.Setup(q => q.GetEnumerator()).Returns(query.GetEnumerator()); var querySet = new QuerySet(_contextMock.Object, _queryMock.Object); @@ -50,5 +51,59 @@ public void Can_Get_Guid_QueryValue() Assert.True(success); Assert.Equal(value, result); } + + [Fact] + public void GetRequired_Throws_If_Not_Present() + { + // arrange + const string key = "some-id"; + var filterQuery = $"filter[{key}]"; + var value = Guid.NewGuid(); + + var query = new Dictionary { + { filterQuery, value.ToString() } + }; + + _queryMock.Setup(q => q.GetEnumerator()).Returns(query.GetEnumerator()); + + var querySet = new QuerySet(_contextMock.Object, _queryMock.Object); + _contextMock.Setup(c => c.QuerySet) + .Returns(querySet); + + var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); + + // act + var exception = Assert.Throws(() => service.GetRequired("Invalid")); + + // assert + Assert.Equal(422, exception.GetStatusCode()); + } + + [Fact] + public void GetRequired_Does_Not_Throw_If_Present() + { + // arrange + const string key = "some-id"; + var filterQuery = $"filter[{key}]"; + var value = Guid.NewGuid(); + + var query = new Dictionary { + { filterQuery, value.ToString() } + }; + + _queryMock.Setup(q => q.GetEnumerator()).Returns(query.GetEnumerator()); + + var querySet = new QuerySet(_contextMock.Object, _queryMock.Object); + _contextMock.Setup(c => c.QuerySet) + .Returns(querySet); + + var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); + + // act + var result = service.GetRequired("SomeId"); + + // assert + Assert.Equal(value, result); + } } }