From 1f8a3f8fb9dc4e07bd7d4e17a3fc8e01ddc50111 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Fri, 3 May 2019 17:31:39 +0200 Subject: [PATCH 01/26] feat: started work on decoupling --- JsonApiDotnetCore.sln | 29 ++++++--- .../Properties/launchSettings.json | 27 ++++++++ .../Services/CustomArticleService.cs | 32 ++++++++++ .../Builders/IDocumentBuilder.cs | 2 +- .../Configuration/IJsonApiOptions.cs | 19 ++++++ .../Services/EntityResourceService.cs | 22 ++++--- .../Services/IJsonApiContext.cs | 2 +- test/UnitTests/JsonApiContext/BasicTest.cs | 61 +++++++++++++++++++ 8 files changed, 174 insertions(+), 20 deletions(-) create mode 100644 src/Examples/GettingStarted/Properties/launchSettings.json create mode 100644 src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs create mode 100644 src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs create mode 100644 test/UnitTests/JsonApiContext/BasicTest.cs diff --git a/JsonApiDotnetCore.sln b/JsonApiDotnetCore.sln index bb07f87439..06d0d25651 100644 --- a/JsonApiDotnetCore.sln +++ b/JsonApiDotnetCore.sln @@ -1,6 +1,7 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2010 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28606.126 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore", "src\JsonApiDotNetCore\JsonApiDotNetCore.csproj", "{C0EC9E70-EB2E-436F-9D94-FA16FA774123}" EndProject @@ -41,13 +42,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OperationsExample", "src\Ex EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OperationsExampleTests", "test\OperationsExampleTests\OperationsExampleTests.csproj", "{9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceEntitySeparationExample", "src\Examples\ResourceEntitySeparationExample\ResourceEntitySeparationExample.csproj", "{F4097194-9415-418A-AB4E-315C5D5466AF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourceEntitySeparationExample", "src\Examples\ResourceEntitySeparationExample\ResourceEntitySeparationExample.csproj", "{F4097194-9415-418A-AB4E-315C5D5466AF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceEntitySeparationExampleTests", "test\ResourceEntitySeparationExampleTests\ResourceEntitySeparationExampleTests.csproj", "{6DFA30D7-1679-4333-9779-6FB678E48EF5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourceEntitySeparationExampleTests", "test\ResourceEntitySeparationExampleTests\ResourceEntitySeparationExampleTests.csproj", "{6DFA30D7-1679-4333-9779-6FB678E48EF5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{DF9BFD82-D937-4907-B0B4-64670417115F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{DF9BFD82-D937-4907-B0B4-64670417115F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscoveryTests", "test\DiscoveryTests\DiscoveryTests.csproj", "{09C0C8D8-B721-4955-8889-55CB149C3B5C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscoveryTests", "test\DiscoveryTests\DiscoveryTests.csproj", "{09C0C8D8-B721-4955-8889-55CB149C3B5C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -191,6 +192,18 @@ Global {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x64.Build.0 = Release|Any CPU {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x86.ActiveCfg = Release|Any CPU {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x86.Build.0 = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x64.Build.0 = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x86.ActiveCfg = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x86.Build.0 = Debug|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|Any CPU.Build.0 = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x64.ActiveCfg = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x64.Build.0 = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x86.ActiveCfg = Release|Any CPU + {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x86.Build.0 = Release|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -203,8 +216,6 @@ Global {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x64.Build.0 = Release|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x86.ActiveCfg = Release|Any CPU {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x86.Build.0 = Release|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Examples/GettingStarted/Properties/launchSettings.json b/src/Examples/GettingStarted/Properties/launchSettings.json new file mode 100644 index 0000000000..a6cbc3bd6b --- /dev/null +++ b/src/Examples/GettingStarted/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:49299/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "GettingStarted": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:49300/" + } + } +} \ No newline at end of file diff --git a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs new file mode 100644 index 0000000000..3b0c581709 --- /dev/null +++ b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs @@ -0,0 +1,32 @@ + +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Data; +using JsonApiDotNetCore.Services; +using JsonApiDotNetCoreExample.Models; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace JsonApiDotNetCoreExample.Services +{ + public class CustomArticleService : EntityResourceService
+ { + public CustomArticleService( + IJsonApiContext jsonApiContext, + IEntityRepository
repository, + IJsonApiOptions jsonApiOptions, + ILoggerFactory loggerFactory + ) : base(jsonApiContext, repository, jsonApiOptions, loggerFactory) + { } + + public override async Task
GetAsync(int id) + { + var newEntity = await base.GetAsync(id); + newEntity.Name = "None for you Glen Coco"; + return newEntity; + } + } + +} diff --git a/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs index 45ba096447..db20954730 100644 --- a/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs @@ -8,7 +8,7 @@ namespace JsonApiDotNetCore.Builders public interface IDocumentBuilder { /// - /// Builds a json:api document from the provided resource instance. + /// Builds a Json:Api document from the provided resource instance. /// /// The resource to convert. Document Build(IIdentifiable entity); diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs new file mode 100644 index 0000000000..1807afe67d --- /dev/null +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Configuration +{ + public interface IJsonApiOptions + { + /// + /// Whether or not the total-record count should be included in all document + /// level meta objects. + /// Defaults to false. + /// + /// + /// options.IncludeTotalRecordCount = true; + /// + bool IncludeTotalRecordCount { get; set; } + } +} diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index 815c34157b..ca12ae3a3a 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; @@ -16,6 +17,7 @@ public class EntityResourceService : EntityResourceService entityRepository, + IJsonApiOptions optionsFetcher, ILoggerFactory loggerFactory = null) : base(jsonApiContext, entityRepository, loggerFactory) { } @@ -28,8 +30,9 @@ public class EntityResourceService : EntityResourceService entityRepository, - ILoggerFactory loggerFactory = null) : - base(jsonApiContext, entityRepository, loggerFactory) + IJsonApiOptions apiOptions, + ILoggerFactory loggerFactory = null) + : base(jsonApiContext, entityRepository, apiOptions, loggerFactory) { } } @@ -39,6 +42,7 @@ public class EntityResourceService : where TEntity : class, IIdentifiable { private readonly IJsonApiContext _jsonApiContext; + private readonly IJsonApiOptions _options; private readonly IEntityRepository _entities; private readonly ILogger _logger; private readonly IResourceMapper _mapper; @@ -46,26 +50,25 @@ public class EntityResourceService : public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, - ILoggerFactory loggerFactory = null) + IJsonApiOptions apiOptions, + ILoggerFactory loggerFactory = null) : this(jsonApiContext,entityRepository,apiOptions,loggerFactory, null) { // no mapper provided, TResource & TEntity must be the same type if (typeof(TResource) != typeof(TEntity)) { throw new InvalidOperationException("Resource and Entity types are NOT the same. Please provide a mapper."); } - - _jsonApiContext = jsonApiContext; - _entities = entityRepository; - _logger = loggerFactory?.CreateLogger>(); } public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, + IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceMapper mapper) { _jsonApiContext = jsonApiContext; + _options = options; _entities = entityRepository; _logger = loggerFactory.CreateLogger>(); _mapper = mapper; @@ -82,7 +85,7 @@ public virtual async Task CreateAsync(TResource resource) // https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/343 if (ShouldIncludeRelationships()) { - if(_entities is IEntityFrameworkRepository efRepository) + if (_entities is IEntityFrameworkRepository efRepository) efRepository.DetachRelationshipPointers(entity); return await GetWithRelationshipsAsync(entity.Id); @@ -105,7 +108,8 @@ public virtual async Task> GetAsync() if (ShouldIncludeRelationships()) entities = IncludeRelationships(entities, _jsonApiContext.QuerySet.IncludedRelationships); - if (_jsonApiContext.Options.IncludeTotalRecordCount) + + if (_options.IncludeTotalRecordCount) _jsonApiContext.PageManager.TotalRecords = await _entities.CountAsync(entities); entities = _entities.Select(entities, _jsonApiContext.QuerySet?.Fields); diff --git a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs index 2509d7124f..5806fae78b 100644 --- a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs @@ -58,7 +58,7 @@ public interface IJsonApiRequest : IJsonApiApplication, IUpdateRequest, IQueryRe /// relationship pointers to persist the relationship. /// /// The expected use case is POST-ing or PATCH-ing an entity with HasMany - /// relaitonships: + /// relationships: /// /// { /// "data": { diff --git a/test/UnitTests/JsonApiContext/BasicTest.cs b/test/UnitTests/JsonApiContext/BasicTest.cs new file mode 100644 index 0000000000..0d4848e4ad --- /dev/null +++ b/test/UnitTests/JsonApiContext/BasicTest.cs @@ -0,0 +1,61 @@ +using JsonApiDotNetCore.Services; +using JsonApiDotNetCoreExample.Controllers; +using JsonApiDotNetCoreExample.Models; +using Moq; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using Microsoft.AspNetCore.Mvc; +using JsonApiDotNetCoreExample.Services; +using JsonApiDotNetCore.Data; +using Microsoft.Extensions.Logging; +using JsonApiDotNetCore.Configuration; + +namespace UnitTests.JsonApiContext +{ + public class BasicTest + { + [Fact] + public async Task CanTestController() + { + // Arrange + var jsonApiContext = new Mock(); + var serviceMock = new Mock>(); + var controller = new ArticlesController(jsonApiContext.Object,serviceMock.Object); + + // Act + var result = await controller.GetAsync(); + + // Assert + var okResult = Assert.IsType(result); + var value = okResult.Value as IEnumerable
; + + Assert.NotNull(value); + } + + [Fact] + public async Task CanTestService() + { + // Arrange + var jacMock = FetchContextMock(); + var loggerMock = new Mock(); + var jsonApiOptionsMock = new Mock(); + var repositoryMock = new Mock>(); + + var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptionsMock.Object, loggerMock.Object); + // Act + var result = await service.GetAsync(); + + // Assert + Assert.NotNull(result); + } + + public Mock FetchContextMock() + { + return new Mock(); + } + + } +} From 88235e8528bdad926097dd803310c236e6ff5a78 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Fri, 3 May 2019 17:33:53 +0200 Subject: [PATCH 02/26] feat: add total record count --- src/JsonApiDotNetCore/Services/EntityResourceService.cs | 7 +++++-- test/UnitTests/JsonApiContext/BasicTest.cs | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index ca12ae3a3a..8aa684e306 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -51,7 +51,7 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions apiOptions, - ILoggerFactory loggerFactory = null) : this(jsonApiContext,entityRepository,apiOptions,loggerFactory, null) + ILoggerFactory loggerFactory = null) : this(jsonApiContext, entityRepository, apiOptions, loggerFactory, null) { // no mapper provided, TResource & TEntity must be the same type if (typeof(TResource) != typeof(TEntity)) @@ -106,11 +106,14 @@ public virtual async Task> GetAsync() entities = ApplySortAndFilterQuery(entities); if (ShouldIncludeRelationships()) + { entities = IncludeRelationships(entities, _jsonApiContext.QuerySet.IncludedRelationships); - + } if (_options.IncludeTotalRecordCount) + { _jsonApiContext.PageManager.TotalRecords = await _entities.CountAsync(entities); + } entities = _entities.Select(entities, _jsonApiContext.QuerySet?.Fields); diff --git a/test/UnitTests/JsonApiContext/BasicTest.cs b/test/UnitTests/JsonApiContext/BasicTest.cs index 0d4848e4ad..cbe2b15759 100644 --- a/test/UnitTests/JsonApiContext/BasicTest.cs +++ b/test/UnitTests/JsonApiContext/BasicTest.cs @@ -41,7 +41,10 @@ public async Task CanTestService() // Arrange var jacMock = FetchContextMock(); var loggerMock = new Mock(); - var jsonApiOptionsMock = new Mock(); + var jsonApiOptionsMock = new JsonApiOptions + { + IncludeTotalRecordCount = false + }; var repositoryMock = new Mock>(); var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptionsMock.Object, loggerMock.Object); From 1a94a4987fcfa779b8640706977802605801b541 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Mon, 13 May 2019 11:33:02 +0200 Subject: [PATCH 03/26] feat: green tests, new managers --- README.md | 7 +- .../Controllers/TodoCollectionsController.cs | 16 +- .../Services/CustomArticleService.cs | 5 +- .../JsonApiDotNetCoreExample/Startup.cs | 11 +- .../Configuration/IJsonApiOptions.cs | 13 ++ .../Configuration/JsonApiOptions.cs | 2 +- .../Data/DefaultEntityRepository.cs | 3 + .../IServiceCollectionExtensions.cs | 10 +- .../Graph/ServiceDiscoveryFacade.cs | 19 ++- src/JsonApiDotNetCore/Graph/TypeLocator.cs | 31 ++-- .../Internal/JsonApiException.cs | 6 + src/JsonApiDotNetCore/Internal/PageManager.cs | 3 +- .../Managers/Contracts/IPageManager.cs | 11 ++ .../Managers/Contracts/IQueryManager.cs | 20 +++ .../Managers/QueryManager.cs | 28 ++++ .../Services/EntityResourceService.cs | 150 ++++++++++++------ .../Services/IJsonApiContext.cs | 2 +- .../Services/JsonApiContext.cs | 4 +- src/JsonApiDotNetCore/Services/QueryParser.cs | 4 +- .../ServiceDiscoveryFacadeTests.cs | 9 +- .../NullValuedAttributeHandlingTests.cs | 2 +- .../HttpReadOnlyTests.cs | 10 +- .../Acceptance/Spec/CreatingDataTests.cs | 3 +- test/UnitTests/JsonApiContext/BasicTest.cs | 72 ++++++--- .../Services/EntityResourceService_Tests.cs | 2 +- 25 files changed, 322 insertions(+), 121 deletions(-) create mode 100644 src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs create mode 100644 src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs create mode 100644 src/JsonApiDotNetCore/Managers/QueryManager.cs diff --git a/README.md b/README.md index 5f7a929bb1..84c10f928c 100644 --- a/README.md +++ b/README.md @@ -91,12 +91,7 @@ Running tests locally requires access to a postgresql database. If you have docker installed, this can be propped up via: ```bash -docker run --rm --name jsonapi-dotnet-core-testing \ - -e POSTGRES_DB=JsonApiDotNetCoreExample \ - -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ - -p 5432:5432 \ - postgres +docker run --rm --name jsonapi-dotnet-core-testing -e POSTGRES_DB=JsonApiDotNetCoreExample -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres ``` And then to run the tests: diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs index 6bac2ff6a0..a6063c6cc3 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs @@ -13,18 +13,16 @@ namespace JsonApiDotNetCoreExample.Controllers { public class TodoCollectionsController : JsonApiController { - readonly IDbContextResolver _dbResolver; - public TodoCollectionsController( - IDbContextResolver contextResolver, - IJsonApiContext jsonApiContext, - IResourceService resourceService, - ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + public TodoCollectionsController( + IDbContextResolver contextResolver, + IJsonApiContext jsonApiContext, + IResourceService resourceService, + ILoggerFactory loggerFactory) + : base(jsonApiContext, resourceService, loggerFactory) { _dbResolver = contextResolver; - } [HttpPatch("{id}")] @@ -40,4 +38,4 @@ public override async Task PatchAsync(Guid id, [FromBody] TodoIte } } -} \ No newline at end of file +} diff --git a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs index 3b0c581709..5244f3d46d 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs @@ -1,6 +1,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Data; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -17,8 +18,10 @@ public CustomArticleService( IJsonApiContext jsonApiContext, IEntityRepository
repository, IJsonApiOptions jsonApiOptions, + IQueryManager queryManager, + IPageManager pageManager, ILoggerFactory loggerFactory - ) : base(jsonApiContext, repository, jsonApiOptions, loggerFactory) + ) : base(jsonApiContext, repository, jsonApiOptions, queryManager, pageManager, loggerFactory) { } public override async Task
GetAsync(int id) diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs index 68ea93a7fc..960cc2f0b1 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs @@ -7,6 +7,9 @@ using Microsoft.EntityFrameworkCore; using JsonApiDotNetCore.Extensions; using System; +using System.ComponentModel.Design; +using JsonApiDotNetCoreExample.Models; +using JsonApiDotNetCore.Services; namespace JsonApiDotNetCoreExample { @@ -43,7 +46,10 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) mvcBuilder, discovery => discovery.AddCurrentAssembly()); - return services.BuildServiceProvider(); + var serviceProvider = services.BuildServiceProvider(); + + + return serviceProvider; } public virtual void Configure( @@ -53,9 +59,8 @@ public virtual void Configure( AppDbContext context) { context.Database.EnsureCreated(); - loggerFactory.AddConsole(Config.GetSection("Logging")); - + var serviceProvider = app.ApplicationServices; app.UseJsonApi(); } diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 1807afe67d..bb338b2d19 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Text; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Models; +using Newtonsoft.Json; namespace JsonApiDotNetCore.Configuration { @@ -15,5 +18,15 @@ public interface IJsonApiOptions /// options.IncludeTotalRecordCount = true; /// bool IncludeTotalRecordCount { get; set; } + int DefaultPageSize { get; } + bool ValidateModelState { get; } + bool AllowClientGeneratedIds { get; } + JsonSerializerSettings SerializerSettings { get; } + bool EnableOperations { get; set; } + Link DefaultRelationshipLinks { get; set; } + NullAttributeResponseBehavior NullAttributeResponseBehavior { get; set; } + bool RelativeLinks { get; set; } + IResourceGraph ResourceGraph { get; set; } + bool AllowCustomQueryParameters { get; set; } } } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index e8e7d83be8..738b499210 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -14,7 +14,7 @@ namespace JsonApiDotNetCore.Configuration /// /// Global options /// - public class JsonApiOptions + public class JsonApiOptions : IJsonApiOptions { /// /// Provides an interface for formatting resource names by convention diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 9922c7c84f..59813d7c3d 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -160,8 +160,11 @@ public virtual async Task CreateAsync(TEntity entity) AttachRelationships(entity); _dbSet.Add(entity); + + await _context.SaveChangesAsync(); + return entity; } diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index d1c1de352c..3268278d0e 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -10,6 +10,8 @@ using JsonApiDotNetCore.Graph; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Generics; +using JsonApiDotNetCore.Managers; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; @@ -64,7 +66,7 @@ public static IServiceCollection AddJsonApi( { var config = new JsonApiOptions(); configureOptions(config); - + if(autoDiscover != null) { var facade = new ServiceDiscoveryFacade(services, config.ResourceGraphBuilder); @@ -110,7 +112,9 @@ public static void AddJsonApiInternals( } if (jsonApiOptions.EnableOperations) + { AddOperationServices(services); + } services.AddScoped(typeof(IEntityRepository<>), typeof(DefaultEntityRepository<>)); services.AddScoped(typeof(IEntityRepository<,>), typeof(DefaultEntityRepository<,>)); @@ -136,7 +140,8 @@ public static void AddJsonApiInternals( services.AddScoped(typeof(IResourceService<>), typeof(EntityResourceService<>)); services.AddScoped(typeof(IResourceService<,>), typeof(EntityResourceService<,>)); - services.AddSingleton(jsonApiOptions); + services.AddSingleton(jsonApiOptions); + services.AddTransient(); services.AddSingleton(jsonApiOptions.ResourceGraph); services.AddScoped(); services.AddSingleton(); @@ -156,6 +161,7 @@ public static void AddJsonApiInternals( services.AddScoped(); // services.AddScoped(); + services.AddScoped(); } private static void AddOperationServices(IServiceCollection services) diff --git a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs index 99c3845108..f2b1e1aebf 100644 --- a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs +++ b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs @@ -152,8 +152,11 @@ public ServiceDiscoveryFacade AddServices(Assembly assembly) private void AddServices(Assembly assembly, ResourceDescriptor resourceDescriptor) { + foreach(var serviceInterface in ServiceInterfaces) RegisterServiceImplementations(assembly, serviceInterface, resourceDescriptor); + + } /// @@ -174,16 +177,30 @@ private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescr foreach(var serviceInterface in RepositoryInterfaces) RegisterServiceImplementations(assembly, serviceInterface, resourceDescriptor); } - + public int i = 0; private void RegisterServiceImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor) { + + + + if (resourceDescriptor.IdType == typeof(Guid) && interfaceType.GetTypeInfo().GenericTypeParameters.Length == 1) + { + return ; + } var genericArguments = interfaceType.GetTypeInfo().GenericTypeParameters.Length == 2 ? new [] { resourceDescriptor.ResourceType, resourceDescriptor.IdType } : new [] { resourceDescriptor.ResourceType }; var service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); + if(service.implementation?.Name == "CustomArticleService" && genericArguments[0].Name != "Article") + { + + service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); + } if (service.implementation != null) + { _services.AddScoped(service.registrationInterface, service.implementation); + } } } } diff --git a/src/JsonApiDotNetCore/Graph/TypeLocator.cs b/src/JsonApiDotNetCore/Graph/TypeLocator.cs index f96e17ffe0..b211ad7a5d 100644 --- a/src/JsonApiDotNetCore/Graph/TypeLocator.cs +++ b/src/JsonApiDotNetCore/Graph/TypeLocator.cs @@ -14,7 +14,7 @@ internal static class TypeLocator private static Dictionary _typeCache = new Dictionary(); private static Dictionary> _identifiableTypeCache = new Dictionary>(); - + /// /// Determine whether or not this is a json:api resource by checking if it implements . /// Returns the status and the resultant id type, either `(true, Type)` OR `(false, null)` @@ -48,10 +48,10 @@ private static Type[] GetAssemblyTypes(Assembly assembly) /// /// Get all implementations of in the assembly /// - public static IEnumerable GetIdentifableTypes(Assembly assembly) + public static IEnumerable GetIdentifableTypes(Assembly assembly) => (_identifiableTypeCache.TryGetValue(assembly, out var descriptors) == false) ? FindIdentifableTypes(assembly) - : _identifiableTypeCache[assembly]; + : _identifiableTypeCache[assembly]; private static IEnumerable FindIdentifableTypes(Assembly assembly) { @@ -60,7 +60,7 @@ private static IEnumerable FindIdentifableTypes(Assembly ass foreach (var type in assembly.GetTypes()) { - if (TryGetResourceDescriptor(type, out var descriptor)) + if (TryGetResourceDescriptor(type, out var descriptor)) { descriptors.Add(descriptor); yield return descriptor; @@ -77,15 +77,15 @@ private static IEnumerable FindIdentifableTypes(Assembly ass internal static bool TryGetResourceDescriptor(Type type, out ResourceDescriptor descriptor) { var possible = GetIdType(type); - if (possible.isJsonApiResource) { + if (possible.isJsonApiResource) + { descriptor = new ResourceDescriptor(type, possible.idType); return true; - } - + } + descriptor = ResourceDescriptor.Empty; return false; } - /// /// Get all implementations of the generic interface /// @@ -99,11 +99,11 @@ internal static bool TryGetResourceDescriptor(Type type, out ResourceDescriptor /// public static (Type implementation, Type registrationInterface) GetGenericInterfaceImplementation(Assembly assembly, Type openGenericInterfaceType, params Type[] genericInterfaceArguments) { - if(assembly == null) throw new ArgumentNullException(nameof(assembly)); - if(openGenericInterfaceType == null) throw new ArgumentNullException(nameof(openGenericInterfaceType)); - if(genericInterfaceArguments == null) throw new ArgumentNullException(nameof(genericInterfaceArguments)); - if(genericInterfaceArguments.Length == 0) throw new ArgumentException("No arguments supplied for the generic interface.", nameof(genericInterfaceArguments)); - if(openGenericInterfaceType.IsGenericType == false) throw new ArgumentException("Requested type is not a generic type.", nameof(openGenericInterfaceType)); + if (assembly == null) throw new ArgumentNullException(nameof(assembly)); + if (openGenericInterfaceType == null) throw new ArgumentNullException(nameof(openGenericInterfaceType)); + if (genericInterfaceArguments == null) throw new ArgumentNullException(nameof(genericInterfaceArguments)); + if (genericInterfaceArguments.Length == 0) throw new ArgumentException("No arguments supplied for the generic interface.", nameof(genericInterfaceArguments)); + if (openGenericInterfaceType.IsGenericType == false) throw new ArgumentException("Requested type is not a generic type.", nameof(openGenericInterfaceType)); foreach (var type in assembly.GetTypes()) { @@ -113,7 +113,8 @@ public static (Type implementation, Type registrationInterface) GetGenericInterf if (interfaceType.IsGenericType) { var genericTypeDefinition = interfaceType.GetGenericTypeDefinition(); - if(genericTypeDefinition == openGenericInterfaceType.GetGenericTypeDefinition()) { + if (interfaceType.GetGenericArguments().First() == genericInterfaceArguments.First() &&genericTypeDefinition == openGenericInterfaceType.GetGenericTypeDefinition()) + { return ( type, genericTypeDefinition.MakeGenericType(genericInterfaceArguments) @@ -157,7 +158,7 @@ public static IEnumerable GetDerivedTypes(Assembly assembly, Type inherite { foreach (var type in assembly.GetTypes()) { - if(inheritedType.IsAssignableFrom(type)) + if (inheritedType.IsAssignableFrom(type)) yield return type; } } diff --git a/src/JsonApiDotNetCore/Internal/JsonApiException.cs b/src/JsonApiDotNetCore/Internal/JsonApiException.cs index 0852ac1e04..6c3690df3f 100644 --- a/src/JsonApiDotNetCore/Internal/JsonApiException.cs +++ b/src/JsonApiDotNetCore/Internal/JsonApiException.cs @@ -25,6 +25,12 @@ public JsonApiException(string statusCode, string message, string detail, string : base(message) => _errors.Add(new Error(statusCode, message, detail, GetMeta(), source)); + /// + /// + /// + /// the integer status code to throw + /// + /// public JsonApiException(int statusCode, string message, string source = null) : base(message) => _errors.Add(new Error(statusCode, message, null, GetMeta(), source)); diff --git a/src/JsonApiDotNetCore/Internal/PageManager.cs b/src/JsonApiDotNetCore/Internal/PageManager.cs index d27fc158fd..e486731052 100644 --- a/src/JsonApiDotNetCore/Internal/PageManager.cs +++ b/src/JsonApiDotNetCore/Internal/PageManager.cs @@ -1,10 +1,11 @@ using System; using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; namespace JsonApiDotNetCore.Internal { - public class PageManager + public class PageManager : IPageManager { public int? TotalRecords { get; set; } public int PageSize { get; set; } diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs new file mode 100644 index 0000000000..f7c7c9012c --- /dev/null +++ b/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs @@ -0,0 +1,11 @@ +using JsonApiDotNetCore.Internal; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Managers.Contracts +{ + public interface IPageManager + { + } +} diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs new file mode 100644 index 0000000000..d148127dcd --- /dev/null +++ b/src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Managers.Contracts +{ + public interface IQueryManager + { + /// + /// Gets the relationships as set in the query parameters + /// + /// + List GetRelationships(); + /// + /// Gets the sparse fields + /// + /// + List GetFields(); + } +} diff --git a/src/JsonApiDotNetCore/Managers/QueryManager.cs b/src/JsonApiDotNetCore/Managers/QueryManager.cs new file mode 100644 index 0000000000..3e4dbdc66a --- /dev/null +++ b/src/JsonApiDotNetCore/Managers/QueryManager.cs @@ -0,0 +1,28 @@ +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Services; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Managers +{ + class QueryManager : IQueryManager + { + private IJsonApiContext _jsonApiContext; + + public QueryManager(IJsonApiContext jsonApiContext) + { + _jsonApiContext = jsonApiContext; + } + + public List GetFields() + { + return _jsonApiContext.QuerySet?.Fields; + } + + public List GetRelationships() + { + return _jsonApiContext.QuerySet?.IncludedRelationships; + } + } +} diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index 8aa684e306..19a12370c9 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -1,7 +1,9 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -17,9 +19,11 @@ public class EntityResourceService : EntityResourceService entityRepository, - IJsonApiOptions optionsFetcher, + IJsonApiOptions options, + IQueryManager queryManager, + IPageManager pageManager, ILoggerFactory loggerFactory = null) : - base(jsonApiContext, entityRepository, loggerFactory) + base(jsonApiContext, entityRepository, options, queryManager, pageManager, loggerFactory) { } } @@ -31,8 +35,10 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions apiOptions, + IQueryManager queryManager, + IPageManager pageManager, ILoggerFactory loggerFactory = null) - : base(jsonApiContext, entityRepository, apiOptions, loggerFactory) + : base(jsonApiContext, entityRepository, apiOptions, queryManager, pageManager, loggerFactory) { } } @@ -41,9 +47,11 @@ public class EntityResourceService : where TResource : class, IIdentifiable where TEntity : class, IIdentifiable { + private readonly IPageManager _pageManager; + private readonly IQueryManager _queryManager; private readonly IJsonApiContext _jsonApiContext; private readonly IJsonApiOptions _options; - private readonly IEntityRepository _entities; + private readonly IEntityRepository _repository; private readonly ILogger _logger; private readonly IResourceMapper _mapper; @@ -51,7 +59,9 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions apiOptions, - ILoggerFactory loggerFactory = null) : this(jsonApiContext, entityRepository, apiOptions, loggerFactory, null) + IQueryManager queryManager, + IPageManager pageManager, + ILoggerFactory loggerFactory = null) : this(jsonApiContext, entityRepository, apiOptions, null, queryManager, pageManager, loggerFactory ) { // no mapper provided, TResource & TEntity must be the same type if (typeof(TResource) != typeof(TEntity)) @@ -64,13 +74,20 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceMapper mapper) + IResourceMapper mapper, + IQueryManager queryManager, + IPageManager pageManager, + ILoggerFactory loggerFactory) { + _pageManager = pageManager; + _queryManager = queryManager; _jsonApiContext = jsonApiContext; _options = options; - _entities = entityRepository; - _logger = loggerFactory.CreateLogger>(); + _repository = entityRepository; + if(loggerFactory != null) + { + _logger = loggerFactory.CreateLogger>(); + } _mapper = mapper; } @@ -78,14 +95,22 @@ public virtual async Task CreateAsync(TResource resource) { var entity = MapIn(resource); - entity = await _entities.CreateAsync(entity); + try + { + entity = await _repository.CreateAsync(entity); + + } + catch(DbUpdateException ex) + { + throw new JsonApiException(500, "Database update exception", ex); + } // this ensures relationships get reloaded from the database if they have // been requested // https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/343 - if (ShouldIncludeRelationships()) + if (AreRelationshipsIncluded()) { - if (_entities is IEntityFrameworkRepository efRepository) + if (_repository is IEntityFrameworkRepository efRepository) efRepository.DetachRelationshipPointers(entity); return await GetWithRelationshipsAsync(entity.Id); @@ -96,26 +121,25 @@ public virtual async Task CreateAsync(TResource resource) public virtual async Task DeleteAsync(TId id) { - return await _entities.DeleteAsync(id); + return await _repository.DeleteAsync(id); } - public virtual async Task> GetAsync() { - var entities = _entities.GetQueryable(); + var entities = _repository.GetQueryable(); entities = ApplySortAndFilterQuery(entities); - if (ShouldIncludeRelationships()) + if (AreRelationshipsIncluded()) { entities = IncludeRelationships(entities, _jsonApiContext.QuerySet.IncludedRelationships); } if (_options.IncludeTotalRecordCount) { - _jsonApiContext.PageManager.TotalRecords = await _entities.CountAsync(entities); + _jsonApiContext.PageManager.TotalRecords = await _repository.CountAsync(entities); } - entities = _entities.Select(entities, _jsonApiContext.QuerySet?.Fields); + entities = _repository.Select(entities, _jsonApiContext.QuerySet?.Fields); // pagination should be done last since it will execute the query var pagedEntities = await ApplyPageQueryAsync(entities); @@ -124,12 +148,21 @@ public virtual async Task> GetAsync() public virtual async Task GetAsync(TId id) { - if (ShouldIncludeRelationships()) - return await GetWithRelationshipsAsync(id); - - TEntity entity = await _entities.GetAsync(id); - - return MapOut(entity); + + TResource resource; + if (AreRelationshipsIncluded()) + { + resource = await GetWithRelationshipsAsync(id); + } + else + { + resource = MapOut(await _repository.GetAsync(id)); + } + if(resource == null) + { + throw new JsonApiException(404, $"That entity ({_jsonApiContext.RequestEntity.EntityName}) with id ({id}) was not found in the database"); + } + return resource; } public virtual async Task GetRelationshipsAsync(TId id, string relationshipName) @@ -137,7 +170,7 @@ public virtual async Task GetRelationshipsAsync(TId id, string relations public virtual async Task GetRelationshipAsync(TId id, string relationshipName) { - var entity = await _entities.GetAndIncludeAsync(id, relationshipName); + var entity = await _repository.GetAndIncludeAsync(id, relationshipName); // TODO: it would be better if we could distinguish whether or not the relationship was not found, // vs the relationship not being set on the instance of T @@ -161,14 +194,14 @@ public virtual async Task UpdateAsync(TId id, TResource resource) { var entity = MapIn(resource); - entity = await _entities.UpdateAsync(id, entity); + entity = await _repository.UpdateAsync(id, entity); return MapOut(entity); } public virtual async Task UpdateRelationshipsAsync(TId id, string relationshipName, List relationships) { - var entity = await _entities.GetAndIncludeAsync(id, relationshipName); + var entity = await _repository.GetAndIncludeAsync(id, relationshipName); if (entity == null) { throw new JsonApiException(404, $"Entity with id {id} could not be found."); @@ -195,7 +228,7 @@ public virtual async Task UpdateRelationshipsAsync(TId id, string relationshipNa var relationshipIds = relationships.Select(r => r?.Id?.ToString()); - await _entities.UpdateRelationshipsAsync(entity, relationship, relationshipIds); + await _repository.UpdateRelationshipsAsync(entity, relationship, relationshipIds); relationship.Type = relationshipType; } @@ -205,7 +238,7 @@ protected virtual async Task> ApplyPageQueryAsync(IQuerya var pageManager = _jsonApiContext.PageManager; if (!pageManager.IsPaginated) { - var allEntities = await _entities.ToListAsync(entities); + var allEntities = await _repository.ToListAsync(entities); return (typeof(TResource) == typeof(TEntity)) ? allEntities as IEnumerable : _mapper.Map>(allEntities); } @@ -216,7 +249,7 @@ protected virtual async Task> ApplyPageQueryAsync(IQuerya $"with {pageManager.PageSize} entities"); } - var pagedEntities = await _entities.PageAsync(entities, pageManager.PageSize, pageManager.CurrentPage); + var pagedEntities = await _repository.PageAsync(entities, pageManager.PageSize, pageManager.CurrentPage); return MapOut(pagedEntities); } @@ -230,50 +263,77 @@ protected virtual IQueryable ApplySortAndFilterQuery(IQueryable 0) foreach (var filter in query.Filters) - entities = _entities.Filter(entities, filter); + entities = _repository.Filter(entities, filter); - entities = _entities.Sort(entities, query.SortParameters); + entities = _repository.Sort(entities, query.SortParameters); return entities; } + /// + /// actually include the relationships + /// + /// + /// + /// protected virtual IQueryable IncludeRelationships(IQueryable entities, List relationships) { _jsonApiContext.IncludedRelationships = relationships; foreach (var r in relationships) - entities = _entities.Include(entities, r); + { + entities = _repository.Include(entities, r); + } return entities; } + /// + /// Get the specified id with relationships (async) + /// + /// + /// private async Task GetWithRelationshipsAsync(TId id) { - var query = _entities.Select(_entities.GetQueryable(), _jsonApiContext.QuerySet?.Fields).Where(e => e.Id.Equals(id)); + var fields = _queryManager.GetFields(); + var query = _repository.Select(_repository.GetQueryable(), fields).Where(e => e.Id.Equals(id)); - _jsonApiContext.QuerySet.IncludedRelationships.ForEach(r => + _queryManager.GetRelationships().ForEach(r => { - query = _entities.Include(query, r); + query = _repository.Include(query, r); }); TEntity value; // https://github.com/aspnet/EntityFrameworkCore/issues/6573 - if (_jsonApiContext.QuerySet?.Fields?.Count > 0) + if(_queryManager.GetFields()?.Count() > 0) + { value = query.FirstOrDefault(); + } else - value = await _entities.FirstOrDefaultAsync(query); - + { + value = await _repository.FirstOrDefaultAsync(query); + } return MapOut(value); } - private bool ShouldIncludeRelationships() - => (_jsonApiContext.QuerySet?.IncludedRelationships != null && - _jsonApiContext.QuerySet.IncludedRelationships.Count > 0); + /// + /// Should the relationships be included? + /// + /// + private bool AreRelationshipsIncluded() + { + return _queryManager.GetRelationships()?.Count() > 0; + } + /// + /// Casts the entity given to `TResource` or maps it to its equal + /// + /// + /// private TResource MapOut(TEntity entity) - => (typeof(TResource) == typeof(TEntity)) - ? entity as TResource : - _mapper.Map(entity); + { + return (typeof(TResource) == typeof(TEntity)) ? entity as TResource : _mapper.Map(entity); + } private IEnumerable MapOut(IEnumerable entities) => (typeof(TResource) == typeof(TEntity)) diff --git a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs index 5806fae78b..94e13cb243 100644 --- a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs @@ -12,7 +12,7 @@ namespace JsonApiDotNetCore.Services { public interface IJsonApiApplication { - JsonApiOptions Options { get; set; } + IJsonApiOptions Options { get; set; } IResourceGraph ResourceGraph { get; set; } } diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs index 2553cbe451..33e3be2093 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiContext.cs @@ -20,7 +20,7 @@ public class JsonApiContext : IJsonApiContext public JsonApiContext( IResourceGraph resourceGraph, IHttpContextAccessor httpContextAccessor, - JsonApiOptions options, + IJsonApiOptions options, IMetaBuilder metaBuilder, IGenericProcessorFactory genericProcessorFactory, IQueryParser queryParser, @@ -35,7 +35,7 @@ public JsonApiContext( _controllerContext = controllerContext; } - public JsonApiOptions Options { get; set; } + public IJsonApiOptions Options { get; set; } public IResourceGraph ResourceGraph { get; set; } [Obsolete("Use the proxied member IControllerContext.RequestEntity instead.")] public ContextEntity RequestEntity { get => _controllerContext.RequestEntity; set => _controllerContext.RequestEntity = value; } diff --git a/src/JsonApiDotNetCore/Services/QueryParser.cs b/src/JsonApiDotNetCore/Services/QueryParser.cs index cd839ffa73..21a66e651b 100644 --- a/src/JsonApiDotNetCore/Services/QueryParser.cs +++ b/src/JsonApiDotNetCore/Services/QueryParser.cs @@ -18,11 +18,11 @@ public interface IQueryParser public class QueryParser : IQueryParser { private readonly IControllerContext _controllerContext; - private readonly JsonApiOptions _options; + private readonly IJsonApiOptions _options; public QueryParser( IControllerContext controllerContext, - JsonApiOptions options) + IJsonApiOptions options) { _controllerContext = controllerContext; _options = options; diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index 6953b5f49c..dc51ef806e 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -1,8 +1,10 @@ using GettingStarted.Models; using GettingStarted.ResourceDefinitionExample; using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Graph; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.Extensions.DependencyInjection; @@ -73,8 +75,11 @@ public class TestModel : Identifiable { } public class TestModelService : EntityResourceService { private static IEntityRepository _repo = new Mock>().Object; - private static IJsonApiContext _jsonApiContext = new Mock().Object; - public TestModelService() : base(_jsonApiContext, _repo) { } + private static IJsonApiContext _jsonApiContext = new Mock().Object; + private static IJsonApiOptions _jsonApiOptions = new Mock().Object; + private static IQueryManager _queryManager = new Mock().Object; + private static IPageManager _pageManager = new Mock().Object; + public TestModelService() : base(_jsonApiContext, _repo, _jsonApiOptions, _queryManager, _pageManager) { } } public class TestModelRepository : DefaultEntityRepository diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/NullValuedAttributeHandlingTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/NullValuedAttributeHandlingTests.cs index 2030694918..40c74b4500 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/NullValuedAttributeHandlingTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/NullValuedAttributeHandlingTests.cs @@ -82,7 +82,7 @@ public async Task CheckNullBehaviorCombination(bool? omitNullValuedAttributes, b { nullAttributeResponseBehavior = new NullAttributeResponseBehavior(); } - var jsonApiOptions = _fixture.GetService(); + var jsonApiOptions = _fixture.GetService(); jsonApiOptions.NullAttributeResponseBehavior = nullAttributeResponseBehavior; jsonApiOptions.AllowCustomQueryParameters = true; diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/HttpReadOnlyTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/HttpReadOnlyTests.cs index 90496b3690..88906ba7ab 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/HttpReadOnlyTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/HttpMethodRestrictions/HttpReadOnlyTests.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using JsonApiDotNetCoreExample; @@ -14,14 +14,14 @@ public class HttpReadOnlyTests [Fact] public async Task Allows_GET_Requests() { - // arrange + // Arrange const string route = "readonly"; const string method = "GET"; - // act + // Act var statusCode = await MakeRequestAsync(route, method); - // assert + // Assert Assert.Equal(HttpStatusCode.OK, statusCode); } @@ -79,4 +79,4 @@ private async Task MakeRequestAsync(string route, string method) return response.StatusCode; } } -} \ No newline at end of file +} diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs index a34312ca2f..cd52df5e9d 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -78,6 +78,7 @@ public async Task Can_Create_Guid_Identifiable_Entity() // act var response = await client.SendAsync(request); + var sdfsd = await response.Content.ReadAsStringAsync(); // assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); diff --git a/test/UnitTests/JsonApiContext/BasicTest.cs b/test/UnitTests/JsonApiContext/BasicTest.cs index cbe2b15759..08c41bd4da 100644 --- a/test/UnitTests/JsonApiContext/BasicTest.cs +++ b/test/UnitTests/JsonApiContext/BasicTest.cs @@ -12,47 +12,75 @@ using JsonApiDotNetCore.Data; using Microsoft.Extensions.Logging; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal; +using System.Net; +using JsonApiDotNetCore.Managers.Contracts; -namespace UnitTests.JsonApiContext +namespace UnitTests.Services { - public class BasicTest + public class EntityResourceServiceMore { [Fact] - public async Task CanTestController() + public async Task TestCanGetAll() { - // Arrange - var jsonApiContext = new Mock(); - var serviceMock = new Mock>(); - var controller = new ArticlesController(jsonApiContext.Object,serviceMock.Object); - // Act - var result = await controller.GetAsync(); + } - // Assert - var okResult = Assert.IsType(result); - var value = okResult.Value as IEnumerable
; + /// + /// we expect the service layer to give use a 404 if there is no entity returned + /// + /// + [Fact] + public async Task GetAsync_Throw404OnNoEntityFound() + { + // Arrange + var jacMock = FetchContextMock(); + var loggerMock = new Mock(); + var jsonApiOptions = new JsonApiOptions + { + IncludeTotalRecordCount = false + } as IJsonApiOptions; + var repositoryMock = new Mock>(); + var queryManagerMock = new Mock(); + var pageManagerMock = new Mock(); + var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptions, queryManagerMock.Object, pageManagerMock.Object, loggerMock.Object); - Assert.NotNull(value); + // Act / Assert + var toExecute = new Func(() => + { + return service.GetAsync(4); + }); + var exception = await Assert.ThrowsAsync(toExecute); + Assert.Equal(404, exception.GetStatusCode()); } + /// + /// we expect the service layer to give use a 404 if there is no entity returned + /// + /// [Fact] - public async Task CanTestService() + public async Task GetAsync_ShouldThrow404OnNoEntityFoundWithRelationships() { // Arrange var jacMock = FetchContextMock(); var loggerMock = new Mock(); - var jsonApiOptionsMock = new JsonApiOptions + var jsonApiOptions = new JsonApiOptions { IncludeTotalRecordCount = false - }; + } as IJsonApiOptions; var repositoryMock = new Mock>(); + var queryManagerMock = new Mock(); + var pageManagerMock = new Mock(); + queryManagerMock.Setup(qm => qm.GetRelationships()).Returns(new List() { "cookies" }); + var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptions, queryManagerMock.Object, pageManagerMock.Object, loggerMock.Object); - var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptionsMock.Object, loggerMock.Object); - // Act - var result = await service.GetAsync(); - - // Assert - Assert.NotNull(result); + // Act / Assert + var toExecute = new Func(() => + { + return service.GetAsync(4); + }); + var exception = await Assert.ThrowsAsync(toExecute); + Assert.Equal(404, exception.GetStatusCode()); } public Mock FetchContextMock() diff --git a/test/UnitTests/Services/EntityResourceService_Tests.cs b/test/UnitTests/Services/EntityResourceService_Tests.cs index 4380a6622b..7052641583 100644 --- a/test/UnitTests/Services/EntityResourceService_Tests.cs +++ b/test/UnitTests/Services/EntityResourceService_Tests.cs @@ -73,6 +73,6 @@ public async Task GetRelationshipAsync_Returns_Relationship_Value() } private EntityResourceService GetService() => - new EntityResourceService(_jsonApiContextMock.Object, _repositoryMock.Object, _loggerFactory); + new EntityResourceService(_jsonApiContextMock.Object, _repositoryMock.Object, null, null,null, _loggerFactory); } } From ab78ad222d98824052a29e1b57467e72b72bd300 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Fri, 17 May 2019 10:51:03 +0200 Subject: [PATCH 04/26] feat: changed startup --- src/Examples/JsonApiDotNetCoreExample/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs index 960cc2f0b1..ff77dd068c 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs @@ -42,7 +42,7 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) options.Namespace = "api/v1"; options.DefaultPageSize = 5; options.IncludeTotalRecordCount = true; - }, + }, mvcBuilder, discovery => discovery.AddCurrentAssembly()); From 5da1a867a8bdeee1d217d5d68b6d9190fab72a58 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Fri, 17 May 2019 16:28:45 +0200 Subject: [PATCH 05/26] feat: most annoying commit of my life, rewriting dozens of controllers --- .../Controllers/ArticlesController.cs | 9 ++-- .../Controllers/PeopleController.cs | 10 ++-- .../ModelsController.cs | 10 ++-- .../Controllers/ArticlesController.cs | 6 ++- .../Controllers/CamelCasedModelsController.cs | 4 +- .../Controllers/PeopleController.cs | 4 +- .../Controllers/PersonRolesController.cs | 4 +- .../Controllers/TodoCollectionsController.cs | 6 ++- .../Controllers/TodoItemsController.cs | 4 +- .../Controllers/TodoItemsTestController.cs | 7 ++- .../Controllers/UsersController.cs | 4 +- .../JsonApiDotNetCoreExample/Startup.cs | 6 --- .../Controllers/CustomTodoItemsController.cs | 6 ++- .../Controllers/ReportsController.cs | 10 ++-- .../Controllers/CoursesController.cs | 4 +- .../Controllers/DepartmentsController.cs | 4 +- .../Controllers/StudentsController.cs | 4 +- .../Controllers/BaseJsonApiController.cs | 46 ++++++++++++------- .../Controllers/JsonApiCmdController.cs | 11 +++-- .../Controllers/JsonApiController.cs | 37 ++++++++++----- .../Controllers/JsonApiQueryController.cs | 7 ++- .../Data/DefaultEntityRepository.cs | 5 -- .../Extensions/ModelStateExtensions.cs | 2 +- .../BaseJsonApiController_Tests.cs | 44 +++++++++--------- 24 files changed, 154 insertions(+), 100 deletions(-) diff --git a/src/Examples/GettingStarted/Controllers/ArticlesController.cs b/src/Examples/GettingStarted/Controllers/ArticlesController.cs index 53517540b1..93d370897b 100644 --- a/src/Examples/GettingStarted/Controllers/ArticlesController.cs +++ b/src/Examples/GettingStarted/Controllers/ArticlesController.cs @@ -1,4 +1,5 @@ using GettingStarted.Models; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; @@ -7,9 +8,9 @@ namespace GettingStarted public class ArticlesController : JsonApiController
{ public ArticlesController( - IJsonApiContext jsonApiContext, - IResourceService
resourceService) - : base(jsonApiContext, resourceService) + IJsonApiOptions jsonApiOptions, + IJsonApiContext jsonApiContext, + IResourceService
resourceService) : base(jsonApiOptions, jsonApiContext, resourceService) { } } -} \ No newline at end of file +} diff --git a/src/Examples/GettingStarted/Controllers/PeopleController.cs b/src/Examples/GettingStarted/Controllers/PeopleController.cs index f3c0c4b868..b033ae172a 100644 --- a/src/Examples/GettingStarted/Controllers/PeopleController.cs +++ b/src/Examples/GettingStarted/Controllers/PeopleController.cs @@ -1,4 +1,5 @@ using GettingStarted.Models; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; @@ -7,9 +8,10 @@ namespace GettingStarted public class PeopleController : JsonApiController { public PeopleController( - IJsonApiContext jsonApiContext, - IResourceService resourceService) - : base(jsonApiContext, resourceService) + IJsonApiOptions jsonApiOptions, + IJsonApiContext jsonApiContext, + IResourceService resourceService) + : base(jsonApiOptions,jsonApiContext, resourceService) { } } -} \ No newline at end of file +} diff --git a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs index a14394e830..55138e7d71 100644 --- a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs +++ b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs @@ -1,4 +1,5 @@ using GettingStarted.Models; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; @@ -7,9 +8,10 @@ namespace GettingStarted.ResourceDefinitionExample public class ModelsController : JsonApiController { public ModelsController( - IJsonApiContext jsonApiContext, - IResourceService resourceService) - : base(jsonApiContext, resourceService) + IJsonApiOptions jsonApiOptions, + IJsonApiContext jsonApiContext, + IResourceService resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } } -} \ No newline at end of file +} diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs index 95aa7d69f9..865f8454a2 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -7,9 +8,10 @@ namespace JsonApiDotNetCoreExample.Controllers public class ArticlesController : JsonApiController
{ public ArticlesController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService
resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } } -} \ No newline at end of file +} diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs index e46b3f8efd..cf9c8bdf2e 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -11,10 +12,11 @@ namespace JsonApiDotNetCoreExample.Controllers public class CamelCasedModelsController : JsonApiController { public CamelCasedModelsController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs index e249e2af53..236c946f89 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -8,10 +9,11 @@ namespace JsonApiDotNetCoreExample.Controllers public class PeopleController : JsonApiController { public PeopleController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs index dbc3b482f5..e00c5b906e 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -8,10 +9,11 @@ namespace JsonApiDotNetCoreExample.Controllers public class PersonRolesController : JsonApiController { public PersonRolesController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs index a6063c6cc3..04fe912e57 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Services; @@ -16,11 +17,12 @@ public class TodoCollectionsController : JsonApiController resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { _dbResolver = contextResolver; } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs index 768dd1c37c..040ad38e20 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -8,10 +9,11 @@ namespace JsonApiDotNetCoreExample.Controllers public class TodoItemsController : JsonApiController { public TodoItemsController( + IJsonApiOptions jsonApiOPtions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOPtions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs index 9bab3cf544..caab1c1c85 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; @@ -11,10 +12,11 @@ public abstract class AbstractTodoItemsController : JsonApiController where T : class, IIdentifiable { protected AbstractTodoItemsController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService service, ILoggerFactory loggerFactory) - : base(jsonApiContext, service, loggerFactory) + : base(jsonApiOptions, jsonApiContext, service, loggerFactory) { } } @@ -22,10 +24,11 @@ protected AbstractTodoItemsController( public class TodoItemsTestController : AbstractTodoItemsController { public TodoItemsTestController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService service, ILoggerFactory loggerFactory) - : base(jsonApiContext, service, loggerFactory) + : base(jsonApiOptions, jsonApiContext, service, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs index dbd144caa4..931db12325 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -8,10 +9,11 @@ namespace JsonApiDotNetCoreExample.Controllers public class UsersController : JsonApiController { public UsersController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs index ff77dd068c..fd26e0274a 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs @@ -32,9 +32,7 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) { var loggerFactory = new LoggerFactory(); loggerFactory.AddConsole(LogLevel.Warning); - var mvcBuilder = services.AddMvcCore(); - services .AddSingleton(loggerFactory) .AddDbContext(options => options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient) @@ -45,10 +43,7 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) }, mvcBuilder, discovery => discovery.AddCurrentAssembly()); - var serviceProvider = services.BuildServiceProvider(); - - return serviceProvider; } @@ -60,7 +55,6 @@ public virtual void Configure( { context.Database.EnsureCreated(); loggerFactory.AddConsole(Config.GetSection("Logging")); - var serviceProvider = app.ApplicationServices; app.UseJsonApi(); } diff --git a/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs b/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs index a6ded9749f..4027582a9c 100644 --- a/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs +++ b/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs @@ -1,4 +1,5 @@ -using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -8,10 +9,11 @@ namespace NoEntityFrameworkExample.Controllers public class CustomTodoItemsController : JsonApiController { public CustomTodoItemsController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/ReportsExample/Controllers/ReportsController.cs b/src/Examples/ReportsExample/Controllers/ReportsController.cs index 6f431d9291..95add7975c 100644 --- a/src/Examples/ReportsExample/Controllers/ReportsController.cs +++ b/src/Examples/ReportsExample/Controllers/ReportsController.cs @@ -1,17 +1,19 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; - +using JsonApiDotNetCore.Configuration; + namespace ReportsExample.Controllers { [Route("api/[controller]")] public class ReportsController : BaseJsonApiController { - public ReportsController( + public ReportsController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IGetAllService getAll) - : base(jsonApiContext, getAll: getAll) + : base(jsonApiOptions, jsonApiContext, getAll: getAll) { } [HttpGet] diff --git a/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs b/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs index 6809ace0bb..908776ebc5 100644 --- a/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs +++ b/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models.Resources; @@ -8,10 +9,11 @@ namespace ResourceEntitySeparationExample.Controllers public class CoursesController : JsonApiController { public CoursesController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs b/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs index 08f3ab33ad..9edef6fa7c 100644 --- a/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs +++ b/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs @@ -1,4 +1,5 @@ using System; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models.Resources; @@ -9,10 +10,11 @@ namespace ResourceEntitySeparationExample.Controllers public class DepartmentsController : JsonApiController { public DepartmentsController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs b/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs index 34d5d33031..7ba8fde1af 100644 --- a/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs +++ b/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models.Resources; @@ -8,10 +9,11 @@ namespace ResourceEntitySeparationExample.Controllers public class StudentsController : JsonApiController { public StudentsController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } } } diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index 93fd4826e2..f2206667ec 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; @@ -13,17 +14,20 @@ public class BaseJsonApiController where T : class, IIdentifiable { public BaseJsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService - ) : base(jsonApiContext, resourceService) { } + ) : base(jsonApiOptions, jsonApiContext, resourceService) { } public BaseJsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceQueryService queryService = null, IResourceCmdService cmdService = null - ) : base(jsonApiContext, queryService, cmdService) { } + ) : base(jsonApiOptions, jsonApiContext, queryService, cmdService) { } public BaseJsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, @@ -33,7 +37,7 @@ public BaseJsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } } public class BaseJsonApiController @@ -48,12 +52,15 @@ public class BaseJsonApiController private readonly IUpdateService _update; private readonly IUpdateRelationshipService _updateRelationships; private readonly IDeleteService _delete; + private readonly IJsonApiOptions _jsonApiOptions; private readonly IJsonApiContext _jsonApiContext; public BaseJsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) { + _jsonApiOptions = jsonApiOptions; _jsonApiContext = jsonApiContext.ApplyContext(this); _getAll = resourceService; _getById = resourceService; @@ -66,10 +73,12 @@ public BaseJsonApiController( } public BaseJsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceQueryService queryService = null, IResourceCmdService cmdService = null) { + _jsonApiOptions = jsonApiOptions; _jsonApiContext = jsonApiContext.ApplyContext(this); _getAll = queryService; _getById = queryService; @@ -82,6 +91,7 @@ public BaseJsonApiController( } public BaseJsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, @@ -92,6 +102,7 @@ public BaseJsonApiController( IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null) { + _jsonApiOptions = jsonApiOptions; _jsonApiContext = jsonApiContext.ApplyContext(this); _getAll = getAll; _getById = getById; @@ -106,28 +117,27 @@ public BaseJsonApiController( public virtual async Task GetAsync() { if (_getAll == null) throw Exceptions.UnSupportedRequestMethod; - var entities = await _getAll.GetAsync(); - return Ok(entities); } public virtual async Task GetAsync(TId id) { if (_getById == null) throw Exceptions.UnSupportedRequestMethod; - var entity = await _getById.GetAsync(id); - if (entity == null) + { return NotFound(); - + } return Ok(entity); } public virtual async Task GetRelationshipsAsync(TId id, string relationshipName) { - if (_getRelationships == null) throw Exceptions.UnSupportedRequestMethod; - + if (_getRelationships == null) + { + throw Exceptions.UnSupportedRequestMethod; + } var relationship = await _getRelationships.GetRelationshipsAsync(id, relationshipName); if (relationship == null) return NotFound(); @@ -138,9 +148,7 @@ public virtual async Task GetRelationshipsAsync(TId id, string re public virtual async Task GetRelationshipAsync(TId id, string relationshipName) { if (_getRelationship == null) throw Exceptions.UnSupportedRequestMethod; - var relationship = await _getRelationship.GetRelationshipAsync(id, relationshipName); - return Ok(relationship); } @@ -152,11 +160,13 @@ public virtual async Task PostAsync([FromBody] T entity) if (entity == null) return UnprocessableEntity(); - if (!_jsonApiContext.Options.AllowClientGeneratedIds && !string.IsNullOrEmpty(entity.StringId)) + if (!_jsonApiOptions.AllowClientGeneratedIds && !string.IsNullOrEmpty(entity.StringId)) return Forbidden(); - if (_jsonApiContext.Options.ValidateModelState && !ModelState.IsValid) - return UnprocessableEntity(ModelState.ConvertToErrorCollection(_jsonApiContext.ResourceGraph)); + if (_jsonApiOptions.ValidateModelState && !ModelState.IsValid) + { + return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _jsonApiContext.ResourceGraph)); + } entity = await _create.CreateAsync(entity); @@ -170,8 +180,10 @@ public virtual async Task PatchAsync(TId id, [FromBody] T entity) if (entity == null) return UnprocessableEntity(); - if (_jsonApiContext.Options.ValidateModelState && !ModelState.IsValid) - return UnprocessableEntity(ModelState.ConvertToErrorCollection(_jsonApiContext.ResourceGraph)); + if (_jsonApiOptions.ValidateModelState && !ModelState.IsValid) + { + return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _jsonApiContext.ResourceGraph)); + } var updatedEntity = await _update.UpdateAsync(id, entity); diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs index 16ab4aa74a..b3d69060a0 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs @@ -1,18 +1,20 @@ using System.Collections.Generic; using System.Threading.Tasks; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; namespace JsonApiDotNetCore.Controllers { - public class JsonApiCmdController - : JsonApiCmdController where T : class, IIdentifiable + public class JsonApiCmdController : JsonApiCmdController + where T : class, IIdentifiable { public JsonApiCmdController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } } @@ -20,9 +22,10 @@ public class JsonApiCmdController : BaseJsonApiController where T : class, IIdentifiable { public JsonApiCmdController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } [HttpPost] diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index a77c03da06..4569048d6d 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; @@ -7,23 +8,30 @@ namespace JsonApiDotNetCore.Controllers { - public class JsonApiController - : JsonApiController where T : class, IIdentifiable + public class JsonApiController : JsonApiController where T : class, IIdentifiable { + private IJsonApiOptions jsonApiOptions; + private IJsonApiContext jsonApiContext; + private IResourceService resourceService; + private ILoggerFactory loggerFactory; + public JsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { } public JsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } public JsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, @@ -33,27 +41,34 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + + public JsonApiController(IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + { + } } - public class JsonApiController - : BaseJsonApiController where T : class, IIdentifiable + public class JsonApiController : BaseJsonApiController where T : class, IIdentifiable { public JsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } public JsonApiController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } public JsonApiController( - IJsonApiContext jsonApiContext, + IJsonApiOptions jsonApiOptions, + IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -62,7 +77,7 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } [HttpGet] public override async Task GetAsync() => await base.GetAsync(); diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs index 5211e5fa3b..ad0978709b 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; @@ -9,9 +10,10 @@ public class JsonApiQueryController : JsonApiQueryController where T : class, IIdentifiable { public JsonApiQueryController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } } @@ -19,9 +21,10 @@ public class JsonApiQueryController : BaseJsonApiController where T : class, IIdentifiable { public JsonApiQueryController( + IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiContext, resourceService) + : base(jsonApiOptions, jsonApiContext, resourceService) { } [HttpGet] diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 59813d7c3d..9a1c94e4fc 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -159,12 +159,7 @@ public virtual async Task CreateAsync(TEntity entity) { AttachRelationships(entity); _dbSet.Add(entity); - - - await _context.SaveChangesAsync(); - - return entity; } diff --git a/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs b/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs index 4b31a7f021..05ce8e6238 100644 --- a/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs @@ -27,7 +27,7 @@ public static ErrorCollection ConvertToErrorCollection(this ModelStateDictionary return collection; } - public static ErrorCollection ConvertToErrorCollection(this ModelStateDictionary modelState, IResourceGraph resourceGraph) + public static ErrorCollection ConvertToErrorCollection(ModelStateDictionary modelState, IResourceGraph resourceGraph) { ErrorCollection collection = new ErrorCollection(); foreach (var entry in modelState) diff --git a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs index 3397aa4eda..f0d7184cb1 100644 --- a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs +++ b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs @@ -25,7 +25,7 @@ public async Task GetAsync_Calls_Service() { // arrange var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getAll: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getAll: serviceMock.Object); // act await controller.GetAsync(); @@ -40,7 +40,7 @@ public async Task GetAsync_Throws_405_If_No_Service() { // arrange var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, null); // act var exception = await Assert.ThrowsAsync(() => controller.GetAsync()); @@ -55,7 +55,7 @@ public async Task GetAsyncById_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getById: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getById: serviceMock.Object); // act await controller.GetAsync(id); @@ -71,7 +71,7 @@ public async Task GetAsyncById_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getById: null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getById: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetAsync(id)); @@ -86,7 +86,7 @@ public async Task GetRelationshipsAsync_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getRelationships: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationships: serviceMock.Object); // act await controller.GetRelationshipsAsync(id, string.Empty); @@ -102,7 +102,7 @@ public async Task GetRelationshipsAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getRelationships: null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationships: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetRelationshipsAsync(id, string.Empty)); @@ -117,7 +117,7 @@ public async Task GetRelationshipAsync_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getRelationship: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationship: serviceMock.Object); // act await controller.GetRelationshipAsync(id, string.Empty); @@ -133,7 +133,7 @@ public async Task GetRelationshipAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, getRelationship: null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationship: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetRelationshipAsync(id, string.Empty)); @@ -151,7 +151,7 @@ public async Task PatchAsync_Calls_Service() var serviceMock = new Mock>(); _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions()); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, update: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: serviceMock.Object); // act await controller.PatchAsync(id, resource); @@ -170,7 +170,7 @@ public async Task PatchAsync_ModelStateInvalid_ValidateModelStateDisbled() var serviceMock = new Mock>(); _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = false }); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, update: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: serviceMock.Object); // act var response = await controller.PatchAsync(id, resource); @@ -192,7 +192,7 @@ public async Task PatchAsync_ModelStateInvalid_ValidateModelStateEnabled() _resourceGraphMock.Setup(a => a.GetPublicAttributeName("TestAttribute")).Returns("test-attribute"); _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions{ValidateModelState = true}); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, update: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: serviceMock.Object); controller.ModelState.AddModelError("TestAttribute", "Failed Validation"); // act @@ -210,7 +210,7 @@ public async Task PatchAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, update: null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: null); // act var exception = await Assert.ThrowsAsync(() => controller.PatchAsync(id, It.IsAny())); @@ -227,7 +227,7 @@ public async Task PostAsync_Calls_Service() var serviceMock = new Mock>(); _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions()); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, create: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, create: serviceMock.Object); serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext {HttpContext = new DefaultHttpContext()}; @@ -247,7 +247,7 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateDisabled() var serviceMock = new Mock>(); _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = false }); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, create: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, create: serviceMock.Object); serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext { HttpContext = new DefaultHttpContext() }; @@ -270,7 +270,7 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateEnabled() _resourceGraphMock.Setup(a => a.GetPublicAttributeName("TestAttribute")).Returns("test-attribute"); _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = true }); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, create: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, create: serviceMock.Object); controller.ModelState.AddModelError("TestAttribute", "Failed Validation"); // act @@ -289,7 +289,7 @@ public async Task PatchRelationshipsAsync_Calls_Service() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, updateRelationships: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, updateRelationships: serviceMock.Object); // act await controller.PatchRelationshipsAsync(id, string.Empty, null); @@ -305,7 +305,7 @@ public async Task PatchRelationshipsAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, updateRelationships: null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, updateRelationships: null); // act var exception = await Assert.ThrowsAsync(() => controller.PatchRelationshipsAsync(id, string.Empty, null)); @@ -317,16 +317,16 @@ public async Task PatchRelationshipsAsync_Throws_405_If_No_Service() [Fact] public async Task DeleteAsync_Calls_Service() { - // arrange + // Arrange const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, delete: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, delete: serviceMock.Object); - // act + // Act await controller.DeleteAsync(id); - // assert + // Assert serviceMock.Verify(m => m.DeleteAsync(id), Times.Once); VerifyApplyContext(); } @@ -337,7 +337,7 @@ public async Task DeleteAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(_jsonApiContextMock.Object, delete: null); + var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, delete: null); // act var exception = await Assert.ThrowsAsync(() => controller.DeleteAsync(id)); From 494f6eb56f459640f0153a8fce9e9fe67ef39812 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Thu, 23 May 2019 13:21:33 +0200 Subject: [PATCH 06/26] feat: rename: QueryManager -> RequestManager, deeper seperation of concerns, 12 tests running... --- ...uilder_ GetNamespaceFromPath_Benchmarks.cs | 40 +++++++- benchmarks/Program.cs | 4 +- .../JsonApiSerializer_Benchmarks.cs | 98 +++++++++---------- .../Controllers/ArticlesController.cs | 10 +- .../Services/CustomArticleService.cs | 2 +- .../Builders/ContextGraphBuilder.cs | 1 + .../Builders/DocumentBuilder.cs | 70 +++++++------ .../Builders/ILinkBuilder.cs | 11 +++ src/JsonApiDotNetCore/Builders/LinkBuilder.cs | 58 +++-------- .../Configuration/IJsonApiOptions.cs | 1 + .../Configuration/JsonApiOptions.cs | 1 + .../Controllers/BaseJsonApiController.cs | 22 ++--- .../Controllers/JsonApiCmdController.cs | 3 +- .../Controllers/JsonApiController.cs | 17 ++-- .../Data/DefaultEntityRepository.cs | 8 +- .../IApplicationBuilderExtensions.cs | 1 + .../IServiceCollectionExtensions.cs | 3 +- .../Extensions/ModelStateExtensions.cs | 1 + .../Internal/ContextGraph.cs | 1 + .../Internal/Contracts/IResourceGraph.cs | 87 ++++++++++++++++ src/JsonApiDotNetCore/Internal/PageManager.cs | 16 ++- .../Internal/ResourceGraph.cs | 81 +++------------ .../Managers/Contracts/IPageManager.cs | 24 +++++ .../Managers/Contracts/IQueryManager.cs | 20 ---- .../Managers/Contracts/IRequestManager.cs | 41 ++++++++ .../Contracts/IResourceGraphManager.cs | 11 +++ .../Managers/QueryManager.cs | 28 ------ .../Managers/RequestManager.cs | 41 ++++++++ .../Middleware/RequestMiddleware.cs | 63 +++++++++++- .../Models/ResourceDefinition.cs | 1 + .../Serialization/JsonApiSerializer.cs | 6 +- .../Services/EntityResourceService.cs | 31 +++--- .../Services/IJsonApiContext.cs | 17 ++-- .../Services/JsonApiContext.cs | 45 +++++---- .../Processors/CreateOpProcessor.cs | 1 + .../Operations/Processors/GetOpProcessor.cs | 1 + .../Processors/RemoveOpProcessor.cs | 1 + .../Processors/UpdateOpProcessor.cs | 1 + .../Services/QueryAccessor.cs | 4 +- .../Services/QueryComposer.cs | 9 +- .../ServiceDiscoveryFacadeTests.cs | 2 +- .../CamelCasedModelsControllerTests.cs | 6 +- .../Extensibility/CustomErrorTests.cs | 2 +- .../Builders/ContextGraphBuilder_Tests.cs | 1 + .../Builders/DocumentBuilder_Tests.cs | 57 +++++++---- test/UnitTests/Builders/LinkBuilder_Tests.cs | 35 +++---- .../BaseJsonApiController_Tests.cs | 5 +- .../IServiceCollectionExtensionsTests.cs | 1 + test/UnitTests/JsonApiContext/BasicTest.cs | 4 +- .../Models/ResourceDefinitionTests.cs | 1 + .../Serialization/JsonApiSerializerTests.cs | 37 ++++--- test/UnitTests/Services/QueryAccessorTests.cs | 94 +++++++++--------- test/UnitTests/Services/QueryComposerTests.cs | 21 ++-- 53 files changed, 702 insertions(+), 445 deletions(-) create mode 100644 src/JsonApiDotNetCore/Builders/ILinkBuilder.cs create mode 100644 src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs delete mode 100644 src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs create mode 100644 src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs create mode 100644 src/JsonApiDotNetCore/Managers/Contracts/IResourceGraphManager.cs delete mode 100644 src/JsonApiDotNetCore/Managers/QueryManager.cs create mode 100644 src/JsonApiDotNetCore/Managers/RequestManager.cs diff --git a/benchmarks/LinkBuilder/LinkBuilder_ GetNamespaceFromPath_Benchmarks.cs b/benchmarks/LinkBuilder/LinkBuilder_ GetNamespaceFromPath_Benchmarks.cs index 05728321c3..1432afecd8 100644 --- a/benchmarks/LinkBuilder/LinkBuilder_ GetNamespaceFromPath_Benchmarks.cs +++ b/benchmarks/LinkBuilder/LinkBuilder_ GetNamespaceFromPath_Benchmarks.cs @@ -1,10 +1,11 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes.Exporters; using BenchmarkDotNet.Attributes.Jobs; +using System; namespace Benchmarks.LinkBuilder { - [MarkdownExporter, SimpleJob(launchCount : 3, warmupCount : 10, targetCount : 20), MemoryDiagnoser] + [MarkdownExporter, SimpleJob(launchCount: 3, warmupCount: 10, targetCount: 20), MemoryDiagnoser] public class LinkBuilder_GetNamespaceFromPath_Benchmarks { private const string PATH = "/api/some-really-long-namespace-path/resources/current/articles"; @@ -14,7 +15,7 @@ public class LinkBuilder_GetNamespaceFromPath_Benchmarks public void UsingSplit() => GetNamespaceFromPath_BySplitting(PATH, ENTITY_NAME); [Benchmark] - public void Current() => GetNameSpaceFromPath_Current(PATH, ENTITY_NAME); + public void Current() => GetNameSpaceFromPathCurrent(PATH, ENTITY_NAME); public static string GetNamespaceFromPath_BySplitting(string path, string entityName) { @@ -32,7 +33,38 @@ public static string GetNamespaceFromPath_BySplitting(string path, string entity return nSpace; } - public static string GetNameSpaceFromPath_Current(string path, string entityName) - => JsonApiDotNetCore.Builders.LinkBuilder.GetNamespaceFromPath(path, entityName); + public static string GetNameSpaceFromPathCurrent(string path, string entityName) + { + + var entityNameSpan = entityName.AsSpan(); + var pathSpan = path.AsSpan(); + const char delimiter = '/'; + for (var i = 0; i < pathSpan.Length; i++) + { + if (pathSpan[i].Equals(delimiter)) + { + var nextPosition = i + 1; + if (pathSpan.Length > i + entityNameSpan.Length) + { + var possiblePathSegment = pathSpan.Slice(nextPosition, entityNameSpan.Length); + if (entityNameSpan.SequenceEqual(possiblePathSegment)) + { + // check to see if it's the last position in the string + // or if the next character is a / + var lastCharacterPosition = nextPosition + entityNameSpan.Length; + + if (lastCharacterPosition == pathSpan.Length || pathSpan.Length >= lastCharacterPosition + 2 && pathSpan[lastCharacterPosition].Equals(delimiter)) + { + return pathSpan.Slice(0, i).ToString(); + } + } + } + } + } + + return string.Empty; + + + } } } diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs index 9a2c45dffb..0ec4c80e14 100644 --- a/benchmarks/Program.cs +++ b/benchmarks/Program.cs @@ -1,4 +1,4 @@ -using BenchmarkDotNet.Running; +using BenchmarkDotNet.Running; using Benchmarks.JsonApiContext; using Benchmarks.LinkBuilder; using Benchmarks.Query; @@ -10,7 +10,7 @@ class Program { static void Main(string[] args) { var switcher = new BenchmarkSwitcher(new[] { typeof(JsonApiDeserializer_Benchmarks), - typeof(JsonApiSerializer_Benchmarks), + //typeof(JsonApiSerializer_Benchmarks), typeof(QueryParser_Benchmarks), typeof(LinkBuilder_GetNamespaceFromPath_Benchmarks), typeof(ContainsMediaTypeParameters_Benchmarks), diff --git a/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs index d80540434b..1238cc082f 100644 --- a/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs +++ b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs @@ -1,49 +1,49 @@ -using System.Collections.Generic; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Attributes.Exporters; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Internal.Generics; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Services; -using Moq; -using Newtonsoft.Json.Serialization; - -namespace Benchmarks.Serialization { - [MarkdownExporter] - public class JsonApiSerializer_Benchmarks { - private const string TYPE_NAME = "simple-types"; - private static readonly SimpleType Content = new SimpleType(); - - private readonly JsonApiSerializer _jsonApiSerializer; - - public JsonApiSerializer_Benchmarks() { - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource(TYPE_NAME); - var resourceGraph = resourceGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); - - var jsonApiOptions = new JsonApiOptions(); - jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var genericProcessorFactoryMock = new Mock(); - - var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object); - _jsonApiSerializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); - } - - [Benchmark] - public object SerializeSimpleObject() => _jsonApiSerializer.Serialize(Content); - - private class SimpleType : Identifiable { - [Attr("name")] - public string Name { get; set; } - } - } -} +//using System.Collections.Generic; +//using BenchmarkDotNet.Attributes; +//using BenchmarkDotNet.Attributes.Exporters; +//using JsonApiDotNetCore.Builders; +//using JsonApiDotNetCore.Configuration; +//using JsonApiDotNetCore.Internal.Generics; +//using JsonApiDotNetCore.Models; +//using JsonApiDotNetCore.Serialization; +//using JsonApiDotNetCore.Services; +//using Moq; +//using Newtonsoft.Json.Serialization; + +//namespace Benchmarks.Serialization { +// [MarkdownExporter] +// public class JsonApiSerializer_Benchmarks { +// private const string TYPE_NAME = "simple-types"; +// private static readonly SimpleType Content = new SimpleType(); + +// private readonly JsonApiSerializer _jsonApiSerializer; + +// public JsonApiSerializer_Benchmarks() { +// var resourceGraphBuilder = new ResourceGraphBuilder(); +// resourceGraphBuilder.AddResource(TYPE_NAME); +// var resourceGraph = resourceGraphBuilder.Build(); + +// var jsonApiContextMock = new Mock(); +// jsonApiContextMock.SetupAllProperties(); +// jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); +// jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); + +// var jsonApiOptions = new JsonApiOptions(); +// jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); +// jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + +// var genericProcessorFactoryMock = new Mock(); + +// var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object); +// _jsonApiSerializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); +// } + +// [Benchmark] +// public object SerializeSimpleObject() => _jsonApiSerializer.Serialize(Content); + +// private class SimpleType : Identifiable { +// [Attr("name")] +// public string Name { get; set; } +// } +// } +//} diff --git a/src/Examples/GettingStarted/Controllers/ArticlesController.cs b/src/Examples/GettingStarted/Controllers/ArticlesController.cs index 93d370897b..8077983e92 100644 --- a/src/Examples/GettingStarted/Controllers/ArticlesController.cs +++ b/src/Examples/GettingStarted/Controllers/ArticlesController.cs @@ -10,7 +10,15 @@ public class ArticlesController : JsonApiController
public ArticlesController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, - IResourceService
resourceService) : base(jsonApiOptions, jsonApiContext, resourceService) + IResourceService
resourceService) + + + + + + + + : base(jsonApiOptions, jsonApiContext, resourceService) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs index 5244f3d46d..92c5a11447 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs @@ -18,7 +18,7 @@ public CustomArticleService( IJsonApiContext jsonApiContext, IEntityRepository
repository, IJsonApiOptions jsonApiOptions, - IQueryManager queryManager, + IRequestManager queryManager, IPageManager pageManager, ILoggerFactory loggerFactory ) : base(jsonApiContext, repository, jsonApiOptions, queryManager, pageManager, loggerFactory) diff --git a/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs index f17d20f4a2..038796c17a 100644 --- a/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs @@ -7,6 +7,7 @@ using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Graph; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index 48464923d2..04194d68c2 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -4,6 +4,8 @@ using System.Linq; using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; @@ -12,6 +14,8 @@ namespace JsonApiDotNetCore.Builders /// public class DocumentBuilder : IDocumentBuilder { + private readonly IRequestManager _requestManager; + private readonly IPageManager _pageManager; private readonly IJsonApiContext _jsonApiContext; private readonly IResourceGraph _resourceGraph; private readonly IRequestMeta _requestMeta; @@ -20,10 +24,14 @@ public class DocumentBuilder : IDocumentBuilder public DocumentBuilder( IJsonApiContext jsonApiContext, + IPageManager pageManager, + IRequestManager requestManager, IRequestMeta requestMeta = null, IDocumentBuilderOptionsProvider documentBuilderOptionsProvider = null, IScopedServiceProvider scopedServiceProvider = null) { + _requestManager = requestManager; + _pageManager = pageManager; _jsonApiContext = jsonApiContext; _resourceGraph = jsonApiContext.ResourceGraph; _requestMeta = requestMeta; @@ -44,7 +52,9 @@ public Document Build(IIdentifiable entity) }; if (ShouldIncludePageLinks(contextEntity)) - document.Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext)); + { + document.Links = _pageManager.GetPageLinks(); + } document.Included = AppendIncludedObject(document.Included, contextEntity, entity); @@ -66,7 +76,9 @@ public Documents Build(IEnumerable entities) }; if (ShouldIncludePageLinks(contextEntity)) - documents.Links = _jsonApiContext.PageManager.GetPageLinks(new LinkBuilder(_jsonApiContext)); + { + documents.Links = _pageManager.GetPageLinks(); + } foreach (var entity in enumeratedEntities) { @@ -80,7 +92,7 @@ public Documents Build(IEnumerable entities) private Dictionary GetMeta(IIdentifiable entity) { var builder = _jsonApiContext.MetaBuilder; - if (_jsonApiContext.Options.IncludeTotalRecordCount && _jsonApiContext.PageManager.TotalRecords != null) + if (_jsonApiContext.Options.IncludeTotalRecordCount && _pageManager.TotalRecords != null) builder.Add("total-records", _jsonApiContext.PageManager.TotalRecords); if (_requestMeta != null) @@ -146,9 +158,9 @@ private bool ShouldIncludeAttribute(AttrAttribute attr, object attributeValue, R { return OmitNullValuedAttribute(attr, attributeValue) == false && attr.InternalAttributeName != nameof(Identifiable.Id) - && ((_jsonApiContext.QuerySet == null - || _jsonApiContext.QuerySet.Fields.Count == 0) - || _jsonApiContext.QuerySet.Fields.Contains(relationship != null ? + && ((_requestManager.QuerySet == null + || _requestManager.QuerySet.Fields.Count == 0) + || _requestManager.QuerySet.Fields.Contains(relationship != null ? $"{relationship.InternalRelationshipName}.{attr.InternalAttributeName}" : attr.InternalAttributeName)); } @@ -171,39 +183,39 @@ private void AddRelationships(ResourceObject data, ContextEntity contextEntity, private RelationshipData GetRelationshipData(RelationshipAttribute attr, ContextEntity contextEntity, IIdentifiable entity) { - var linkBuilder = new LinkBuilder(_jsonApiContext); + //var linkBuilder = new LinkBuilder(_documentBuilderOptions,_requestManager); var relationshipData = new RelationshipData(); - if (_jsonApiContext.Options.DefaultRelationshipLinks.HasFlag(Link.None) == false && attr.DocumentLinks.HasFlag(Link.None) == false) - { - relationshipData.Links = new Links(); - if (attr.DocumentLinks.HasFlag(Link.Self)) - relationshipData.Links.Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); - - if (attr.DocumentLinks.HasFlag(Link.Related)) - relationshipData.Links.Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); - } - - // this only includes the navigation property, we need to actually check the navigation property Id - var navigationEntity = _jsonApiContext.ResourceGraph.GetRelationshipValue(entity, attr); - if (navigationEntity == null) - relationshipData.SingleData = attr.IsHasOne - ? GetIndependentRelationshipIdentifier((HasOneAttribute)attr, entity) - : null; - else if (navigationEntity is IEnumerable) - relationshipData.ManyData = GetRelationships((IEnumerable)navigationEntity); - else - relationshipData.SingleData = GetRelationship(navigationEntity); + //if (_jsonApiContext.Options.DefaultRelationshipLinks.HasFlag(Link.None) == false && attr.DocumentLinks.HasFlag(Link.None) == false) + //{ + // relationshipData.Links = new Links(); + // if (attr.DocumentLinks.HasFlag(Link.Self)) + // relationshipData.Links.Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); + + // if (attr.DocumentLinks.HasFlag(Link.Related)) + // relationshipData.Links.Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); + //} + + //// this only includes the navigation property, we need to actually check the navigation property Id + //var navigationEntity = _jsonApiContext.ResourceGraph.GetRelationshipValue(entity, attr); + //if (navigationEntity == null) + // relationshipData.SingleData = attr.IsHasOne + // ? GetIndependentRelationshipIdentifier((HasOneAttribute)attr, entity) + // : null; + //else if (navigationEntity is IEnumerable) + // relationshipData.ManyData = GetRelationships((IEnumerable)navigationEntity); + //else + // relationshipData.SingleData = GetRelationship(navigationEntity); return relationshipData; } private List GetIncludedEntities(List included, ContextEntity rootContextEntity, IIdentifiable rootResource) { - if (_jsonApiContext.IncludedRelationships != null) + if (_jsonApiContext.RequestManager.IncludedRelationships != null) { - foreach (var relationshipName in _jsonApiContext.IncludedRelationships) + foreach (var relationshipName in _jsonApiContext.RequestManager.IncludedRelationships) { var relationshipChain = relationshipName.Split('.'); diff --git a/src/JsonApiDotNetCore/Builders/ILinkBuilder.cs b/src/JsonApiDotNetCore/Builders/ILinkBuilder.cs new file mode 100644 index 0000000000..c8af9e7dac --- /dev/null +++ b/src/JsonApiDotNetCore/Builders/ILinkBuilder.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Http; + +namespace JsonApiDotNetCore.Builders +{ + public interface ILinkBuilder + { + string GetPageLink(int pageOffset, int pageSize); + string GetRelatedRelationLink(string parent, string parentId, string child); + string GetSelfRelationLink(string parent, string parentId, string child); + } +} diff --git a/src/JsonApiDotNetCore/Builders/LinkBuilder.cs b/src/JsonApiDotNetCore/Builders/LinkBuilder.cs index 3de45558c4..f06134d397 100644 --- a/src/JsonApiDotNetCore/Builders/LinkBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/LinkBuilder.cs @@ -1,72 +1,38 @@ using System; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; namespace JsonApiDotNetCore.Builders { - public class LinkBuilder + public class LinkBuilder : ILinkBuilder { - private readonly IJsonApiContext _context; + private IRequestManager _requestManager; + private IJsonApiOptions _options; - public LinkBuilder(IJsonApiContext context) + public LinkBuilder(IJsonApiOptions options, IRequestManager requestManager) { - _context = context; + _requestManager = requestManager; + _options = options; } - public string GetBasePath(HttpContext context, string entityName) - { - var r = context.Request; - return (_context.Options.RelativeLinks) - ? GetNamespaceFromPath(r.Path, entityName) - : $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}"; - } - - internal static string GetNamespaceFromPath(string path, string entityName) - { - var entityNameSpan = entityName.AsSpan(); - var pathSpan = path.AsSpan(); - const char delimiter = '/'; - for (var i = 0; i < pathSpan.Length; i++) - { - if(pathSpan[i].Equals(delimiter)) - { - var nextPosition = i + 1; - if(pathSpan.Length > i + entityNameSpan.Length) - { - var possiblePathSegment = pathSpan.Slice(nextPosition, entityNameSpan.Length); - if (entityNameSpan.SequenceEqual(possiblePathSegment)) - { - // check to see if it's the last position in the string - // or if the next character is a / - var lastCharacterPosition = nextPosition + entityNameSpan.Length; - - if(lastCharacterPosition == pathSpan.Length || pathSpan.Length >= lastCharacterPosition + 2 && pathSpan[lastCharacterPosition].Equals(delimiter)) - { - return pathSpan.Slice(0, i).ToString(); - } - } - } - } - } - - return string.Empty; - } public string GetSelfRelationLink(string parent, string parentId, string child) { - return $"{_context.BasePath}/{parent}/{parentId}/relationships/{child}"; + return $"{_requestManager.BasePath}/{parent}/{parentId}/relationships/{child}"; } public string GetRelatedRelationLink(string parent, string parentId, string child) { - return $"{_context.BasePath}/{parent}/{parentId}/{child}"; + return $"{_requestManager.BasePath}/{parent}/{parentId}/{child}"; } public string GetPageLink(int pageOffset, int pageSize) { var filterQueryComposer = new QueryComposer(); - var filters = filterQueryComposer.Compose(_context); - return $"{_context.BasePath}/{_context.RequestEntity.EntityName}?page[size]={pageSize}&page[number]={pageOffset}{filters}"; + var filters = filterQueryComposer.Compose(_requestManager); + return $"{_requestManager.BasePath}/{_requestManager.GetContextEntity().EntityName}?page[size]={pageSize}&page[number]={pageOffset}{filters}"; } } } diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index bb338b2d19..191ea1b937 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using Newtonsoft.Json; diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index 738b499210..f81c034663 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -3,6 +3,7 @@ using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Graph; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; using Microsoft.EntityFrameworkCore; diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index f2206667ec..2c44a5f335 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -3,6 +3,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; @@ -17,7 +18,7 @@ public BaseJsonApiController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService - ) : base(jsonApiOptions, jsonApiContext, resourceService) { } + ) : base(jsonApiOptions, jsonApiContext, resourceService, resourceService) { } public BaseJsonApiController( IJsonApiOptions jsonApiOptions, @@ -26,6 +27,7 @@ public BaseJsonApiController( IResourceCmdService cmdService = null ) : base(jsonApiOptions, jsonApiContext, queryService, cmdService) { } + public BaseJsonApiController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, @@ -53,15 +55,15 @@ public class BaseJsonApiController private readonly IUpdateRelationshipService _updateRelationships; private readonly IDeleteService _delete; private readonly IJsonApiOptions _jsonApiOptions; - private readonly IJsonApiContext _jsonApiContext; + private readonly IResourceGraph _resourceGraph; public BaseJsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraphManager, IResourceService resourceService) { _jsonApiOptions = jsonApiOptions; - _jsonApiContext = jsonApiContext.ApplyContext(this); + _resourceGraph = resourceGraphManager; _getAll = resourceService; _getById = resourceService; _getRelationship = resourceService; @@ -79,7 +81,6 @@ public BaseJsonApiController( IResourceCmdService cmdService = null) { _jsonApiOptions = jsonApiOptions; - _jsonApiContext = jsonApiContext.ApplyContext(this); _getAll = queryService; _getById = queryService; _getRelationship = queryService; @@ -103,7 +104,6 @@ public BaseJsonApiController( IDeleteService delete = null) { _jsonApiOptions = jsonApiOptions; - _jsonApiContext = jsonApiContext.ApplyContext(this); _getAll = getAll; _getById = getById; _getRelationship = getRelationship; @@ -165,7 +165,7 @@ public virtual async Task PostAsync([FromBody] T entity) if (_jsonApiOptions.ValidateModelState && !ModelState.IsValid) { - return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _jsonApiContext.ResourceGraph)); + return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _resourceGraph)); } entity = await _create.CreateAsync(entity); @@ -176,13 +176,12 @@ public virtual async Task PostAsync([FromBody] T entity) public virtual async Task PatchAsync(TId id, [FromBody] T entity) { if (_update == null) throw Exceptions.UnSupportedRequestMethod; - if (entity == null) return UnprocessableEntity(); if (_jsonApiOptions.ValidateModelState && !ModelState.IsValid) { - return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _jsonApiContext.ResourceGraph)); + return UnprocessableEntity(ModelStateExtensions.ConvertToErrorCollection(ModelState, _resourceGraph)); } var updatedEntity = await _update.UpdateAsync(id, entity); @@ -196,21 +195,16 @@ public virtual async Task PatchAsync(TId id, [FromBody] T entity) public virtual async Task PatchRelationshipsAsync(TId id, string relationshipName, [FromBody] List relationships) { if (_updateRelationships == null) throw Exceptions.UnSupportedRequestMethod; - await _updateRelationships.UpdateRelationshipsAsync(id, relationshipName, relationships); - return Ok(); } public virtual async Task DeleteAsync(TId id) { if (_delete == null) throw Exceptions.UnSupportedRequestMethod; - var wasDeleted = await _delete.DeleteAsync(id); - if (!wasDeleted) return NotFound(); - return NoContent(); } } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs index b3d69060a0..eed4f9bd1a 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs @@ -14,7 +14,8 @@ public JsonApiCmdController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) + : base(jsonApiOptions, + jsonApiContext, resourceService) { } } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index 4569048d6d..0427f5a678 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -43,20 +43,23 @@ public JsonApiController( IDeleteService delete = null ) : base(jsonApiOptions, jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } - public JsonApiController(IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, ILoggerFactory loggerFactory) + public JsonApiController(IJsonApiOptions jsonApiOptions, + IJsonApiContext jsonApiContext, + IResourceService resourceService, + ILoggerFactory loggerFactory) : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) { - } + } } - public class JsonApiController : BaseJsonApiController where T : class, IIdentifiable + public class JsonApiController : BaseJsonApiController where T : class, IIdentifiable { public JsonApiController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService, - ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService) + ILoggerFactory loggerFactory) + : base(jsonApiOptions, jsonApiContext, resourceService, resourceService) { } public JsonApiController( @@ -99,7 +102,9 @@ public override async Task PostAsync([FromBody] T entity) [HttpPatch("{id}")] public override async Task PatchAsync(TId id, [FromBody] T entity) - => await base.PatchAsync(id, entity); + { + return await base.PatchAsync(id, entity); + } [HttpPatch("{id}/relationships/{relationshipName}")] public override async Task PatchRelationshipsAsync( diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 9a1c94e4fc..2a60acff21 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -82,8 +82,8 @@ public DefaultEntityRepository( public virtual IQueryable Get() { - if (_jsonApiContext.QuerySet?.Fields != null && _jsonApiContext.QuerySet.Fields.Count > 0) - return _dbSet.Select(_jsonApiContext.QuerySet?.Fields); + if (_jsonApiContext.RequestManager.QuerySet?.Fields != null && _jsonApiContext.RequestManager.QuerySet.Fields.Count > 0) + return _dbSet.Select(_jsonApiContext.RequestManager.QuerySet?.Fields); return _dbSet; } @@ -140,7 +140,7 @@ public virtual IQueryable Sort(IQueryable entities, List public virtual async Task GetAsync(TId id) { - return await Select(GetQueryable(), _jsonApiContext.QuerySet?.Fields).SingleOrDefaultAsync(e => e.Id.Equals(id)); + return await Select(GetQueryable(), _jsonApiContext.RequestManager.QuerySet?.Fields).SingleOrDefaultAsync(e => e.Id.Equals(id)); } /// @@ -148,7 +148,7 @@ public virtual async Task GetAndIncludeAsync(TId id, string relationshi { _logger?.LogDebug($"[JADN] GetAndIncludeAsync({id}, {relationshipName})"); - var includedSet = Include(Select(GetQueryable(), _jsonApiContext.QuerySet?.Fields), relationshipName); + var includedSet = Include(Select(GetQueryable(), _jsonApiContext.RequestManager.QuerySet?.Fields), relationshipName); var result = await includedSet.SingleOrDefaultAsync(e => e.Id.Equals(id)); return result; diff --git a/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs b/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs index 20cee086d6..6fef3b44d1 100644 --- a/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs @@ -1,6 +1,7 @@ using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Middleware; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 3268278d0e..57674cf0ca 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -161,7 +161,8 @@ public static void AddJsonApiInternals( services.AddScoped(); // services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } private static void AddOperationServices(IServiceCollection services) diff --git a/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs b/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs index 05ce8e6238..c756570552 100644 --- a/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs @@ -1,5 +1,6 @@ using System; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.EntityFrameworkCore.Internal; diff --git a/src/JsonApiDotNetCore/Internal/ContextGraph.cs b/src/JsonApiDotNetCore/Internal/ContextGraph.cs index 18aab6646e..e3656943b4 100644 --- a/src/JsonApiDotNetCore/Internal/ContextGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ContextGraph.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; namespace JsonApiDotNetCore.Internal diff --git a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs new file mode 100644 index 0000000000..54febfa45b --- /dev/null +++ b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs @@ -0,0 +1,87 @@ +using JsonApiDotNetCore.Models; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Internal.Contracts +{ + /// + /// A cache for the models in entity core + /// + public interface IResourceGraph + { + /// + /// Gets the value of the navigation property, defined by the relationshipName, + /// on the provided instance. + /// + /// The resource instance + /// The navigation property name. + /// + /// + /// _graph.GetRelationship(todoItem, nameof(TodoItem.Owner)); + /// + /// + /// + /// In the case of a `HasManyThrough` relationship, it will not traverse the relationship + /// and will instead return the value of the shadow property (e.g. Articles.Tags). + /// If you want to traverse the relationship, you should use . + /// + object GetRelationship(TParent resource, string propertyName); + + /// + /// Get the entity type based on a string + /// + /// + /// The context entity from the resource graph + ContextEntity GetEntityType(string entityName); + + /// + /// Gets the value of the navigation property (defined by the ) + /// on the provided instance. + /// In the case of `HasManyThrough` relationships, it will traverse the through entity and return the + /// value of the relationship on the other side of a join entity (e.g. Articles.ArticleTags.Tag). + /// + /// The resource instance + /// The attribute used to define the relationship. + /// + /// + /// _graph.GetRelationshipValue(todoItem, nameof(TodoItem.Owner)); + /// + /// + object GetRelationshipValue(TParent resource, RelationshipAttribute relationship) where TParent : IIdentifiable; + + /// + /// Get the internal navigation property name for the specified public + /// relationship name. + /// + /// The public relationship name specified by a or + /// + /// + /// _graph.GetRelationshipName<TodoItem>("achieved-date"); + /// // returns "AchievedDate" + /// + /// + string GetRelationshipName(string relationshipName); + + /// + /// Get the resource metadata by the DbSet property name + /// + ContextEntity GetContextEntity(string dbSetName); + + /// + /// Get the resource metadata by the resource type + /// + ContextEntity GetContextEntity(Type entityType); + + /// + /// Get the public attribute name for a type based on the internal attribute name. + /// + /// The internal attribute name for a . + string GetPublicAttributeName(string internalAttributeName); + + /// + /// Was built against an EntityFrameworkCore DbContext ? + /// + bool UsesDbContext { get; } + } +} diff --git a/src/JsonApiDotNetCore/Internal/PageManager.cs b/src/JsonApiDotNetCore/Internal/PageManager.cs index e486731052..28c13fd885 100644 --- a/src/JsonApiDotNetCore/Internal/PageManager.cs +++ b/src/JsonApiDotNetCore/Internal/PageManager.cs @@ -7,6 +7,12 @@ namespace JsonApiDotNetCore.Internal { public class PageManager : IPageManager { + private ILinkBuilder _linkBuilder; + + public PageManager(ILinkBuilder linkBuilder) + { + _linkBuilder = linkBuilder; + } public int? TotalRecords { get; set; } public int PageSize { get; set; } public int DefaultPageSize { get; set; } @@ -14,7 +20,7 @@ public class PageManager : IPageManager public bool IsPaginated => PageSize > 0; public int TotalPages => (TotalRecords == null) ? -1 : (int)Math.Ceiling(decimal.Divide(TotalRecords.Value, PageSize)); - public RootLinks GetPageLinks(LinkBuilder linkBuilder) + public RootLinks GetPageLinks() { if (ShouldIncludeLinksObject()) return null; @@ -22,16 +28,16 @@ public RootLinks GetPageLinks(LinkBuilder linkBuilder) var rootLinks = new RootLinks(); if (CurrentPage > 1) - rootLinks.First = linkBuilder.GetPageLink(1, PageSize); + rootLinks.First = _linkBuilder.GetPageLink(1, PageSize); if (CurrentPage > 1) - rootLinks.Prev = linkBuilder.GetPageLink(CurrentPage - 1, PageSize); + rootLinks.Prev = _linkBuilder.GetPageLink(CurrentPage - 1, PageSize); if (CurrentPage < TotalPages) - rootLinks.Next = linkBuilder.GetPageLink(CurrentPage + 1, PageSize); + rootLinks.Next = _linkBuilder.GetPageLink(CurrentPage + 1, PageSize); if (TotalPages > 0) - rootLinks.Last = linkBuilder.GetPageLink(TotalPages, PageSize); + rootLinks.Last = _linkBuilder.GetPageLink(TotalPages, PageSize); return rootLinks; } diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index a7afe98398..e2db1b667b 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -2,80 +2,14 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; namespace JsonApiDotNetCore.Internal { - public interface IResourceGraph - { - /// - /// Gets the value of the navigation property, defined by the relationshipName, - /// on the provided instance. - /// - /// The resource instance - /// The navigation property name. - /// - /// - /// _graph.GetRelationship(todoItem, nameof(TodoItem.Owner)); - /// - /// - /// - /// In the case of a `HasManyThrough` relationship, it will not traverse the relationship - /// and will instead return the value of the shadow property (e.g. Articles.Tags). - /// If you want to traverse the relationship, you should use . - /// - object GetRelationship(TParent resource, string propertyName); - - /// - /// Gets the value of the navigation property (defined by the ) - /// on the provided instance. - /// In the case of `HasManyThrough` relationships, it will traverse the through entity and return the - /// value of the relationship on the other side of a join entity (e.g. Articles.ArticleTags.Tag). - /// - /// The resource instance - /// The attribute used to define the relationship. - /// - /// - /// _graph.GetRelationshipValue(todoItem, nameof(TodoItem.Owner)); - /// - /// - object GetRelationshipValue(TParent resource, RelationshipAttribute relationship) where TParent : IIdentifiable; - - /// - /// Get the internal navigation property name for the specified public - /// relationship name. - /// - /// The public relationship name specified by a or - /// - /// - /// _graph.GetRelationshipName<TodoItem>("achieved-date"); - /// // returns "AchievedDate" - /// - /// - string GetRelationshipName(string relationshipName); - - /// - /// Get the resource metadata by the DbSet property name - /// - ContextEntity GetContextEntity(string dbSetName); - - /// - /// Get the resource metadata by the resource type - /// - ContextEntity GetContextEntity(Type entityType); - - /// - /// Get the public attribute name for a type based on the internal attribute name. - /// - /// The internal attribute name for a . - string GetPublicAttributeName(string internalAttributeName); - - /// - /// Was built against an EntityFrameworkCore DbContext ? - /// - bool UsesDbContext { get; } - } - + /// + /// keeps track of all the models/resources defined in JADNC + /// public class ResourceGraph : IResourceGraph { internal List Entities { get; } @@ -91,6 +25,11 @@ public ResourceGraph(List entities, bool usesDbContext) Instance = this; } + public ContextEntity GetEntityType(string entityName) + { + return Entities.Where(e => e.EntityName == entityName).FirstOrDefault(); + } + // eventually, this is the planned public constructor // to avoid breaking changes, we will be leaving the original constructor in place // until the context graph validation process is completed @@ -177,5 +116,7 @@ public string GetPublicAttributeName(string internalAttributeName) .SingleOrDefault(a => a.InternalAttributeName == internalAttributeName)? .PublicAttributeName; } + + } } diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs index f7c7c9012c..814cdc35a5 100644 --- a/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs +++ b/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs @@ -1,4 +1,6 @@ +using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Models; using System; using System.Collections.Generic; using System.Text; @@ -7,5 +9,27 @@ namespace JsonApiDotNetCore.Managers.Contracts { public interface IPageManager { + /// + /// What the total records are for this output + /// + int? TotalRecords { get; set; } + /// + /// How many records per page should be shown + /// + int PageSize { get; set; } + /// + /// What is the default page size + /// + int DefaultPageSize { get; set; } + /// + /// What page are we currently on + /// + int CurrentPage { get; set; } + /// + /// Are we even paginating + /// + bool IsPaginated { get; } + + RootLinks GetPageLinks(); } } diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs deleted file mode 100644 index d148127dcd..0000000000 --- a/src/JsonApiDotNetCore/Managers/Contracts/IQueryManager.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace JsonApiDotNetCore.Managers.Contracts -{ - public interface IQueryManager - { - /// - /// Gets the relationships as set in the query parameters - /// - /// - List GetRelationships(); - /// - /// Gets the sparse fields - /// - /// - List GetFields(); - } -} diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs new file mode 100644 index 0000000000..3f8f0b8e52 --- /dev/null +++ b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Text; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Services; + +namespace JsonApiDotNetCore.Managers.Contracts +{ + public interface IRequestManager : IQueryRequest + { + /// + /// The request namespace. This may be an absolute or relative path + /// depending upon the configuration. + /// + /// + /// Absolute: https://example.com/api/v1 + /// + /// Relative: /api/v1 + /// + string BasePath { get; set; } + QuerySet QuerySet { get; set; } + /// + /// Gets the relationships as set in the query parameters + /// + /// + List GetRelationships(); + /// + /// Gets the sparse fields + /// + /// + List GetFields(); + /// + /// Sets the current context entity for this entire request + /// + /// + void SetContextEntity(ContextEntity contextEntityCurrent); + + ContextEntity GetContextEntity(); + } +} diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IResourceGraphManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IResourceGraphManager.cs new file mode 100644 index 0000000000..3a58f3e2b2 --- /dev/null +++ b/src/JsonApiDotNetCore/Managers/Contracts/IResourceGraphManager.cs @@ -0,0 +1,11 @@ +using JsonApiDotNetCore.Internal; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Managers.Contracts +{ + public interface IResourceGraphManager + { + } +} diff --git a/src/JsonApiDotNetCore/Managers/QueryManager.cs b/src/JsonApiDotNetCore/Managers/QueryManager.cs deleted file mode 100644 index 3e4dbdc66a..0000000000 --- a/src/JsonApiDotNetCore/Managers/QueryManager.cs +++ /dev/null @@ -1,28 +0,0 @@ -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Services; -using System; -using System.Collections.Generic; -using System.Text; - -namespace JsonApiDotNetCore.Managers -{ - class QueryManager : IQueryManager - { - private IJsonApiContext _jsonApiContext; - - public QueryManager(IJsonApiContext jsonApiContext) - { - _jsonApiContext = jsonApiContext; - } - - public List GetFields() - { - return _jsonApiContext.QuerySet?.Fields; - } - - public List GetRelationships() - { - return _jsonApiContext.QuerySet?.IncludedRelationships; - } - } -} diff --git a/src/JsonApiDotNetCore/Managers/RequestManager.cs b/src/JsonApiDotNetCore/Managers/RequestManager.cs new file mode 100644 index 0000000000..857b97c64f --- /dev/null +++ b/src/JsonApiDotNetCore/Managers/RequestManager.cs @@ -0,0 +1,41 @@ +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Services; +using System; +using System.Collections.Generic; +using System.Text; + +namespace JsonApiDotNetCore.Managers +{ + class RequestManager : IRequestManager + { + + private ContextEntity _contextEntity; + + public string BasePath { get; set; } + public List IncludedRelationships { get; set; } + public QuerySet QuerySet { get; set; } + public PageManager PageManager { get; set; } + + + public List GetFields() + { + return QuerySet?.Fields; + } + + public List GetRelationships() + { + return QuerySet?.IncludedRelationships; + } + public ContextEntity GetContextEntity() + { + return _contextEntity; + } + + public void SetContextEntity(ContextEntity contextEntityCurrent) + { + _contextEntity = contextEntityCurrent; + } + } +} diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index ab472a8dba..b148692e41 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -1,6 +1,10 @@ using System; using System.Threading.Tasks; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; @@ -16,7 +20,7 @@ public RequestMiddleware(RequestDelegate next) _next = next; } - public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext) + public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext, IResourceGraph resourceGraph, IRequestManager requestManager, IJsonApiOptions options ) { if (IsValid(context)) { @@ -25,10 +29,67 @@ public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext) // since the JsonApiContext is using field initializers // Need to work on finding a better solution. jsonApiContext.BeginOperation(); + ContextEntity contextEntityCurrent = GetCurrentEntity(context.Request.Path, resourceGraph); + requestManager.SetContextEntity(contextEntityCurrent); + requestManager.BasePath = GetBasePath(context, options, contextEntityCurrent?.EntityName); await _next(context); } } + private string GetBasePath(HttpContext context, IJsonApiOptions options, string entityName) + { + var r = context.Request; + if (options.RelativeLinks) + { + return GetNamespaceFromPath(r.Path, entityName); + } + else + { + return $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}"; + } + } + internal static string GetNamespaceFromPath(string path, string entityName) + { + var entityNameSpan = entityName.AsSpan(); + var pathSpan = path.AsSpan(); + const char delimiter = '/'; + for (var i = 0; i < pathSpan.Length; i++) + { + if (pathSpan[i].Equals(delimiter)) + { + var nextPosition = i + 1; + if (pathSpan.Length > i + entityNameSpan.Length) + { + var possiblePathSegment = pathSpan.Slice(nextPosition, entityNameSpan.Length); + if (entityNameSpan.SequenceEqual(possiblePathSegment)) + { + // check to see if it's the last position in the string + // or if the next character is a / + var lastCharacterPosition = nextPosition + entityNameSpan.Length; + + if (lastCharacterPosition == pathSpan.Length || pathSpan.Length >= lastCharacterPosition + 2 && pathSpan[lastCharacterPosition].Equals(delimiter)) + { + return pathSpan.Slice(0, i).ToString(); + } + } + } + } + } + + return string.Empty; + } + /// + /// Gets the current entity that we need for serialization and deserialization. + /// + /// + /// + /// + private ContextEntity GetCurrentEntity(PathString path, IResourceGraph resourceGraph) + { + var typeString = path.ToString().Split('/')[1]; + return resourceGraph.GetEntityType(typeString); + } + private static bool IsValid(HttpContext context) { return IsValidContentTypeHeader(context) && IsValidAcceptHeader(context); diff --git a/src/JsonApiDotNetCore/Models/ResourceDefinition.cs b/src/JsonApiDotNetCore/Models/ResourceDefinition.cs index 77461aba42..15a417376f 100644 --- a/src/JsonApiDotNetCore/Models/ResourceDefinition.cs +++ b/src/JsonApiDotNetCore/Models/ResourceDefinition.cs @@ -1,4 +1,5 @@ using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Internal.Query; using System; using System.Collections.Generic; diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs index a784554f58..2729e1ec94 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.Extensions.Logging; @@ -12,6 +13,7 @@ public class JsonApiSerializer : IJsonApiSerializer { private readonly IDocumentBuilder _documentBuilder; private readonly ILogger _logger; + private readonly IRequestManager _requestManager; private readonly IJsonApiContext _jsonApiContext; public JsonApiSerializer( @@ -24,9 +26,11 @@ public JsonApiSerializer( public JsonApiSerializer( IJsonApiContext jsonApiContext, + IRequestManager requestManager, IDocumentBuilder documentBuilder, ILoggerFactory loggerFactory) { + _requestManager = requestManager; _jsonApiContext = jsonApiContext; _documentBuilder = documentBuilder; _logger = loggerFactory?.CreateLogger(); @@ -37,7 +41,7 @@ public string Serialize(object entity) if (entity == null) return GetNullDataResponse(); - if (entity.GetType() == typeof(ErrorCollection) || (_jsonApiContext.RequestEntity == null && _jsonApiContext.IsBulkOperationRequest == false)) + if (entity.GetType() == typeof(ErrorCollection) || (_requestManager.GetContextEntity()== null && _jsonApiContext.IsBulkOperationRequest == false)) return GetErrorJson(entity, _logger); if (_jsonApiContext.IsBulkOperationRequest) diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index 19a12370c9..78f1539520 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -20,7 +20,7 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions options, - IQueryManager queryManager, + IRequestManager queryManager, IPageManager pageManager, ILoggerFactory loggerFactory = null) : base(jsonApiContext, entityRepository, options, queryManager, pageManager, loggerFactory) @@ -35,7 +35,7 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions apiOptions, - IQueryManager queryManager, + IRequestManager queryManager, IPageManager pageManager, ILoggerFactory loggerFactory = null) : base(jsonApiContext, entityRepository, apiOptions, queryManager, pageManager, loggerFactory) @@ -48,7 +48,7 @@ public class EntityResourceService : where TEntity : class, IIdentifiable { private readonly IPageManager _pageManager; - private readonly IQueryManager _queryManager; + private readonly IRequestManager _queryManager; private readonly IJsonApiContext _jsonApiContext; private readonly IJsonApiOptions _options; private readonly IEntityRepository _repository; @@ -59,7 +59,7 @@ public EntityResourceService( IJsonApiContext jsonApiContext, IEntityRepository entityRepository, IJsonApiOptions apiOptions, - IQueryManager queryManager, + IRequestManager queryManager, IPageManager pageManager, ILoggerFactory loggerFactory = null) : this(jsonApiContext, entityRepository, apiOptions, null, queryManager, pageManager, loggerFactory ) { @@ -75,7 +75,7 @@ public EntityResourceService( IEntityRepository entityRepository, IJsonApiOptions options, IResourceMapper mapper, - IQueryManager queryManager, + IRequestManager queryManager, IPageManager pageManager, ILoggerFactory loggerFactory) { @@ -131,15 +131,15 @@ public virtual async Task> GetAsync() if (AreRelationshipsIncluded()) { - entities = IncludeRelationships(entities, _jsonApiContext.QuerySet.IncludedRelationships); + entities = IncludeRelationships(entities, _jsonApiContext.RequestManager.QuerySet.IncludedRelationships); } if (_options.IncludeTotalRecordCount) { - _jsonApiContext.PageManager.TotalRecords = await _repository.CountAsync(entities); + _pageManager.TotalRecords = await _repository.CountAsync(entities); } - entities = _repository.Select(entities, _jsonApiContext.QuerySet?.Fields); + entities = _repository.Select(entities, _jsonApiContext.RequestManager.QuerySet?.Fields); // pagination should be done last since it will execute the query var pagedEntities = await ApplyPageQueryAsync(entities); @@ -235,8 +235,7 @@ public virtual async Task UpdateRelationshipsAsync(TId id, string relationshipNa protected virtual async Task> ApplyPageQueryAsync(IQueryable entities) { - var pageManager = _jsonApiContext.PageManager; - if (!pageManager.IsPaginated) + if (!_pageManager.IsPaginated) { var allEntities = await _repository.ToListAsync(entities); return (typeof(TResource) == typeof(TEntity)) ? allEntities as IEnumerable : @@ -245,20 +244,20 @@ protected virtual async Task> ApplyPageQueryAsync(IQuerya if (_logger?.IsEnabled(LogLevel.Information) == true) { - _logger?.LogInformation($"Applying paging query. Fetching page {pageManager.CurrentPage} " + - $"with {pageManager.PageSize} entities"); + _logger?.LogInformation($"Applying paging query. Fetching page {_pageManager.CurrentPage} " + + $"with {_pageManager.PageSize} entities"); } - var pagedEntities = await _repository.PageAsync(entities, pageManager.PageSize, pageManager.CurrentPage); + var pagedEntities = await _repository.PageAsync(entities, _pageManager.PageSize, _pageManager.CurrentPage); return MapOut(pagedEntities); } protected virtual IQueryable ApplySortAndFilterQuery(IQueryable entities) { - var query = _jsonApiContext.QuerySet; + var query = _jsonApiContext.RequestManager.QuerySet; - if (_jsonApiContext.QuerySet == null) + if (_jsonApiContext.RequestManager.QuerySet == null) return entities; if (query.Filters.Count > 0) @@ -278,7 +277,7 @@ protected virtual IQueryable ApplySortAndFilterQuery(IQueryable protected virtual IQueryable IncludeRelationships(IQueryable entities, List relationships) { - _jsonApiContext.IncludedRelationships = relationships; + _jsonApiContext.RequestManager.IncludedRelationships = relationships; foreach (var r in relationships) { diff --git a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs index 94e13cb243..7d1304e9b3 100644 --- a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs @@ -3,8 +3,10 @@ using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Internal.Generics; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Request; @@ -38,18 +40,9 @@ public interface IUpdateRequest Dictionary RelationshipsToUpdate { get; set; } } - public interface IJsonApiRequest : IJsonApiApplication, IUpdateRequest, IQueryRequest + public interface IJsonApiRequest : IJsonApiApplication, IUpdateRequest { - /// - /// The request namespace. This may be an absolute or relative path - /// depending upon the configuration. - /// - /// - /// Absolute: https://example.com/api/v1 - /// - /// Relative: /api/v1 - /// - string BasePath { get; set; } + /// /// Stores information to set relationships for the request resource. @@ -146,6 +139,8 @@ public interface IJsonApiRequest : IJsonApiApplication, IUpdateRequest, IQueryRe public interface IJsonApiContext : IJsonApiRequest { + IRequestManager RequestManager { get; set; } + IPageManager PageManager { get; set; } IJsonApiContext ApplyContext(object controller); IMetaBuilder MetaBuilder { get; set; } IGenericProcessorFactory GenericProcessorFactory { get; set; } diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs index 33e3be2093..faf65f9397 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiContext.cs @@ -3,8 +3,10 @@ using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Internal.Generics; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Request; using Microsoft.AspNetCore.Http; @@ -24,8 +26,12 @@ public JsonApiContext( IMetaBuilder metaBuilder, IGenericProcessorFactory genericProcessorFactory, IQueryParser queryParser, + IPageManager pageManager, + IRequestManager requestManager, IControllerContext controllerContext) { + RequestManager = requestManager; + PageManager = pageManager; ResourceGraph = resourceGraph; _httpContextAccessor = httpContextAccessor; Options = options; @@ -39,12 +45,12 @@ public JsonApiContext( public IResourceGraph ResourceGraph { get; set; } [Obsolete("Use the proxied member IControllerContext.RequestEntity instead.")] public ContextEntity RequestEntity { get => _controllerContext.RequestEntity; set => _controllerContext.RequestEntity = value; } - public string BasePath { get; set; } + public QuerySet QuerySet { get; set; } public bool IsRelationshipData { get; set; } public bool IsRelationshipPath { get; private set; } public List IncludedRelationships { get; set; } - public PageManager PageManager { get; set; } + public IPageManager PageManager { get; set; } public IMetaBuilder MetaBuilder { get; set; } public IGenericProcessorFactory GenericProcessorFactory { get; set; } public Type ControllerType { get; set; } @@ -55,6 +61,8 @@ public JsonApiContext( public Dictionary RelationshipsToUpdate { get; set; } = new Dictionary(); public HasManyRelationshipPointers HasManyRelationshipPointers { get; private set; } = new HasManyRelationshipPointers(); public HasOneRelationshipPointers HasOneRelationshipPointers { get; private set; } = new HasOneRelationshipPointers(); + public IRequestManager RequestManager { get; set; } + IPageManager IJsonApiContext.PageManager { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public IJsonApiContext ApplyContext(object controller) { @@ -74,8 +82,7 @@ public IJsonApiContext ApplyContext(object controller) IncludedRelationships = QuerySet.IncludedRelationships; } - BasePath = new LinkBuilder(this).GetBasePath(context, _controllerContext.RequestEntity.EntityName); - PageManager = GetPageManager(); + //PageManager = GetPageManager(); IsRelationshipPath = PathIsRelationship(context.Request.Path.Value); return this; @@ -115,20 +122,22 @@ internal static bool PathIsRelationship(string requestPath) return false; } - private PageManager GetPageManager() - { - if (Options.DefaultPageSize == 0 && (QuerySet == null || QuerySet.PageQuery.PageSize == 0)) - return new PageManager(); - - var query = QuerySet?.PageQuery ?? new PageQuery(); - - return new PageManager - { - DefaultPageSize = Options.DefaultPageSize, - CurrentPage = query.PageOffset, - PageSize = query.PageSize > 0 ? query.PageSize : Options.DefaultPageSize - }; - } + //private PageManager GetPageManager() + //{ + // if (Options.DefaultPageSize == 0 && (QuerySet == null || QuerySet.PageQuery.PageSize == 0)) + // { + // return new PageManager(); + // } + + // var query = QuerySet?.PageQuery ?? new PageQuery(); + + // return new PageManager + // { + // DefaultPageSize = Options.DefaultPageSize, + // CurrentPage = query.PageOffset, + // PageSize = query.PageSize > 0 ? query.PageSize : Options.DefaultPageSize + // }; + //} [Obsolete("Use the proxied method IControllerContext.GetControllerAttribute instead.")] public TAttribute GetControllerAttribute() where TAttribute : Attribute diff --git a/src/JsonApiDotNetCore/Services/Operations/Processors/CreateOpProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/Processors/CreateOpProcessor.cs index ebc600a5fb..bd4e89eb84 100644 --- a/src/JsonApiDotNetCore/Services/Operations/Processors/CreateOpProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/Processors/CreateOpProcessor.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Serialization; diff --git a/src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs index 0551b0a3cd..66f4306fa7 100644 --- a/src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Serialization; diff --git a/src/JsonApiDotNetCore/Services/Operations/Processors/RemoveOpProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/Processors/RemoveOpProcessor.cs index f3efe7939c..70ddae2aea 100644 --- a/src/JsonApiDotNetCore/Services/Operations/Processors/RemoveOpProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/Processors/RemoveOpProcessor.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Serialization; diff --git a/src/JsonApiDotNetCore/Services/Operations/Processors/UpdateOpProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/Processors/UpdateOpProcessor.cs index 9b136b30c0..7969b6f3ed 100644 --- a/src/JsonApiDotNetCore/Services/Operations/Processors/UpdateOpProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/Processors/UpdateOpProcessor.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Serialization; diff --git a/src/JsonApiDotNetCore/Services/QueryAccessor.cs b/src/JsonApiDotNetCore/Services/QueryAccessor.cs index dc9ff7ef0a..260c379968 100644 --- a/src/JsonApiDotNetCore/Services/QueryAccessor.cs +++ b/src/JsonApiDotNetCore/Services/QueryAccessor.cs @@ -71,13 +71,13 @@ public bool TryGetValue(string key, out T value) } private string GetFilterValue(string key) { - var publicValue = _jsonApiContext.QuerySet.Filters + var publicValue = _jsonApiContext.RequestManager.QuerySet.Filters .FirstOrDefault(f => string.Equals(f.Attribute, key, StringComparison.OrdinalIgnoreCase))?.Value; if(publicValue != null) return publicValue; - var internalValue = _jsonApiContext.QuerySet.Filters + var internalValue = _jsonApiContext.RequestManager.QuerySet.Filters .FirstOrDefault(f => string.Equals(f.Attribute, key, StringComparison.OrdinalIgnoreCase))?.Value; if(internalValue != null) { diff --git a/src/JsonApiDotNetCore/Services/QueryComposer.cs b/src/JsonApiDotNetCore/Services/QueryComposer.cs index e365811704..b96b83718a 100644 --- a/src/JsonApiDotNetCore/Services/QueryComposer.cs +++ b/src/JsonApiDotNetCore/Services/QueryComposer.cs @@ -1,21 +1,22 @@ using System.Collections.Generic; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; namespace JsonApiDotNetCore.Services { public interface IQueryComposer { - string Compose(IJsonApiContext jsonApiContext); + string Compose(IRequestManager jsonApiContext); } public class QueryComposer : IQueryComposer { - public string Compose(IJsonApiContext jsonApiContext) + public string Compose(IRequestManager requestManager) { string result = ""; - if (jsonApiContext != null && jsonApiContext.QuerySet != null) + if (requestManager != null && requestManager.QuerySet != null) { - List filterQueries = jsonApiContext.QuerySet.Filters; + List filterQueries = requestManager.QuerySet.Filters; if (filterQueries.Count > 0) { foreach (FilterQuery filter in filterQueries) diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index dc51ef806e..1f8acbbdb3 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -77,7 +77,7 @@ public class TestModelService : EntityResourceService private static IEntityRepository _repo = new Mock>().Object; private static IJsonApiContext _jsonApiContext = new Mock().Object; private static IJsonApiOptions _jsonApiOptions = new Mock().Object; - private static IQueryManager _queryManager = new Mock().Object; + private static IRequestManager _queryManager = new Mock().Object; private static IPageManager _pageManager = new Mock().Object; public TestModelService() : base(_jsonApiContext, _repo, _jsonApiOptions, _queryManager, _pageManager) { } } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs index 533c59839a..1bf2bfa204 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs @@ -52,8 +52,7 @@ public async Task Can_Get_CamelCasedModels() // Act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService() - .DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -151,8 +150,7 @@ public async Task Can_Patch_CamelCasedModels() }; var httpMethod = new HttpMethod("PATCH"); var route = $"/camelCasedModels/{model.Id}"; - var builder = new WebHostBuilder() - .UseStartup(); + var builder = new WebHostBuilder().UseStartup(); var server = new TestServer(builder); var client = server.CreateClient(); var request = new HttpRequestMessage(httpMethod, route); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs index fcc6e5ffde..39fc11178d 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs @@ -27,7 +27,7 @@ public void Can_Return_Custom_Error_Types() }); // act - var result = new JsonApiSerializer(null, null, null) + var result = new JsonApiSerializer(null, null, null,null) .Serialize(errorCollection); // assert diff --git a/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs b/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs index a88ecceb67..d0055a9530 100644 --- a/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs +++ b/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs @@ -8,6 +8,7 @@ using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Graph; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; diff --git a/test/UnitTests/Builders/DocumentBuilder_Tests.cs b/test/UnitTests/Builders/DocumentBuilder_Tests.cs index 459a8a758d..ddff141b8a 100644 --- a/test/UnitTests/Builders/DocumentBuilder_Tests.cs +++ b/test/UnitTests/Builders/DocumentBuilder_Tests.cs @@ -1,8 +1,10 @@ +using System; using System.Collections; using System.Collections.Generic; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.Extensions.DependencyInjection; @@ -14,7 +16,7 @@ namespace UnitTests public class DocumentBuilder_Tests { private readonly Mock _jsonApiContextMock; - private readonly PageManager _pageManager; + private readonly IPageManager _pageManager; private readonly JsonApiOptions _options; private readonly Mock _requestMetaMock; @@ -43,14 +45,12 @@ public DocumentBuilder_Tests() .Setup(m => m.MetaBuilder) .Returns(new MetaBuilder()); - _pageManager = new PageManager(); + _pageManager = new Mock().Object; _jsonApiContextMock .Setup(m => m.PageManager) .Returns(_pageManager); - _jsonApiContextMock - .Setup(m => m.BasePath) - .Returns("localhost"); + _jsonApiContextMock .Setup(m => m.RequestEntity) @@ -65,7 +65,8 @@ public void Includes_Paging_Links_By_Default() _pageManager.TotalRecords = 1; _pageManager.CurrentPage = 1; - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + + var documentBuilder = GetDocumentBuilder(); var entity = new Model(); // act @@ -76,6 +77,8 @@ public void Includes_Paging_Links_By_Default() Assert.NotNull(document.Links.Last); } + + [Fact] public void Page_Links_Can_Be_Disabled_Globally() { @@ -90,7 +93,7 @@ public void Page_Links_Can_Be_Disabled_Globally() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var entity = new Model(); // act @@ -112,7 +115,7 @@ public void Related_Links_Can_Be_Disabled() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var entity = new Model(); // act @@ -136,7 +139,7 @@ public void Related_Links_Can_Be_Disabled_Globally() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var entity = new RelatedModel(); // act @@ -157,7 +160,7 @@ public void Related_Data_Included_In_Relationships_By_Default() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var entity = new Model { RelatedModel = new RelatedModel @@ -189,7 +192,7 @@ public void IndependentIdentifier_Included_In_HasOne_Relationships_By_Default() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var entity = new Model { RelatedModelId = relatedId @@ -211,7 +214,7 @@ public void IndependentIdentifier_Included_In_HasOne_Relationships_By_Default() public void Build_Can_Build_Arrays() { var entities = new[] { new Model() }; - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var documents = documentBuilder.Build(entities); @@ -222,7 +225,7 @@ public void Build_Can_Build_Arrays() public void Build_Can_Build_CustomIEnumerables() { var entities = new Models(new[] { new Model() }); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); var documents = documentBuilder.Build(entities); @@ -247,7 +250,8 @@ public void DocumentBuilderOptions( documentBuilderBehaviourMock.Setup(m => m.GetDocumentBuilderOptions()) .Returns(new DocumentBuilderOptions(omitNullValuedAttributes.Value)); } - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, null, omitNullValuedAttributes.HasValue ? documentBuilderBehaviourMock.Object : null); + var pageManagerMock = new Mock(); + var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, null, documentBuilderOptionsProvider: omitNullValuedAttributes.HasValue ? documentBuilderBehaviourMock.Object : null); var document = documentBuilder.Build(new Model() { StringProperty = attributeValue }); Assert.Equal(resultContainsAttribute, document.Data.Attributes.ContainsKey("StringProperty")); @@ -302,7 +306,7 @@ public void Build_Will_Use_Resource_If_Defined_For_Multiple_Documents() .AddScoped, UserResource>() .BuildServiceProvider()); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(scopedServiceProvider: scopedServiceProvider); var documents = documentBuilder.Build(entities); @@ -325,7 +329,7 @@ public void Build_Will_Use_Resource_If_Defined_For_Single_Document() .AddScoped, UserResource>() .BuildServiceProvider()); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock, scopedServiceProvider: scopedServiceProvider); var documents = documentBuilder.Build(entity); @@ -347,7 +351,7 @@ public void Build_Will_Use_Instance_Specific_Resource_If_Defined_For_Multiple_Do .AddScoped, InstanceSpecificUserResource>() .BuildServiceProvider()); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock, scopedServiceProvider: scopedServiceProvider); var documents = documentBuilder.Build(entities); @@ -370,10 +374,10 @@ public void Build_Will_Use_Instance_Specific_Resource_If_Defined_For_Single_Docu .AddScoped, InstanceSpecificUserResource>() .BuildServiceProvider()); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(_jsonApiContextMock, scopedServiceProvider: scopedServiceProvider); var documents = documentBuilder.Build(entity); - + Assert.False(documents.Data.Attributes.ContainsKey("password")); Assert.True(documents.Data.Attributes.ContainsKey("username")); } @@ -395,5 +399,20 @@ public class UserResource : ResourceDefinition protected override List OutputAttrs() => Remove(user => user.Password); } + private DocumentBuilder GetDocumentBuilder(Mock jaContextMock = null, TestScopedServiceProvider scopedServiceProvider = null) + { + var pageManagerMock = new Mock(); + var rmMock = new Mock(); + rmMock.SetupGet(rm => rm.BasePath).Returns("Localhost"); + if (jaContextMock != null) + { + return new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); + + } + else + { + return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); + } + } } } diff --git a/test/UnitTests/Builders/LinkBuilder_Tests.cs b/test/UnitTests/Builders/LinkBuilder_Tests.cs index 69b135de03..1e9fdddda3 100644 --- a/test/UnitTests/Builders/LinkBuilder_Tests.cs +++ b/test/UnitTests/Builders/LinkBuilder_Tests.cs @@ -21,28 +21,29 @@ public void GetBasePath_Returns_Path_Before_Resource(string scheme, string host, string path, bool isRelative, string expectedPath) { // arrange - const string resource = "articles"; - var jsonApiContextMock = new Mock(); - jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions - { - RelativeLinks = isRelative - }); + //const string resource = "articles"; + //var jsonApiContextMock = new Mock(); + //jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions + //{ + // RelativeLinks = isRelative + //}); - var requestMock = new Mock(); - requestMock.Setup(m => m.Scheme).Returns(scheme); - requestMock.Setup(m => m.Host).Returns(new HostString(host)); - requestMock.Setup(m => m.Path).Returns(new PathString(path)); + //var requestMock = new Mock(); + //requestMock.Setup(m => m.Scheme).Returns(scheme); + //requestMock.Setup(m => m.Host).Returns(new HostString(host)); + //requestMock.Setup(m => m.Path).Returns(new PathString(path)); - var contextMock = new Mock(); - contextMock.Setup(m => m.Request).Returns(requestMock.Object); + //var contextMock = new Mock(); + //contextMock.Setup(m => m.Request).Returns(requestMock.Object); - var linkBuilder = new LinkBuilder(jsonApiContextMock.Object); + //var linkBuilder = new LinkBuilder(jsonApiContextMock.Object); - // act - var basePath = linkBuilder.GetBasePath(contextMock.Object, resource); + //// act + //var basePath = linkBuilder.GetBasePath(contextMock.Object, resource); - // assert - Assert.Equal(expectedPath, basePath); + //// assert + //Assert.Equal(expectedPath, basePath); + Assert.False(true); } } } diff --git a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs index f0d7184cb1..6fe8638eca 100644 --- a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs +++ b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs @@ -8,6 +8,7 @@ using JsonApiDotNetCore.Internal; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using JsonApiDotNetCore.Internal.Contracts; namespace UnitTests { @@ -337,7 +338,9 @@ public async Task DeleteAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, delete: null); + var controller = new BaseJsonApiController(new Mock().Object, + + _jsonApiContextMock.Object, delete: null); // act var exception = await Assert.ThrowsAsync(() => controller.DeleteAsync(id)); diff --git a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs index 3b49dda9bf..41d546faf6 100644 --- a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs +++ b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using JsonApiDotNetCore.Internal.Contracts; namespace UnitTests.Extensions { diff --git a/test/UnitTests/JsonApiContext/BasicTest.cs b/test/UnitTests/JsonApiContext/BasicTest.cs index 08c41bd4da..3d587a3065 100644 --- a/test/UnitTests/JsonApiContext/BasicTest.cs +++ b/test/UnitTests/JsonApiContext/BasicTest.cs @@ -41,7 +41,7 @@ public async Task GetAsync_Throw404OnNoEntityFound() IncludeTotalRecordCount = false } as IJsonApiOptions; var repositoryMock = new Mock>(); - var queryManagerMock = new Mock(); + var queryManagerMock = new Mock(); var pageManagerMock = new Mock(); var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptions, queryManagerMock.Object, pageManagerMock.Object, loggerMock.Object); @@ -69,7 +69,7 @@ public async Task GetAsync_ShouldThrow404OnNoEntityFoundWithRelationships() IncludeTotalRecordCount = false } as IJsonApiOptions; var repositoryMock = new Mock>(); - var queryManagerMock = new Mock(); + var queryManagerMock = new Mock(); var pageManagerMock = new Mock(); queryManagerMock.Setup(qm => qm.GetRelationships()).Returns(new List() { "cookies" }); var service = new CustomArticleService(jacMock.Object, repositoryMock.Object, jsonApiOptions, queryManagerMock.Object, pageManagerMock.Object, loggerMock.Object); diff --git a/test/UnitTests/Models/ResourceDefinitionTests.cs b/test/UnitTests/Models/ResourceDefinitionTests.cs index f058dbc946..1ae632777f 100644 --- a/test/UnitTests/Models/ResourceDefinitionTests.cs +++ b/test/UnitTests/Models/ResourceDefinitionTests.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Models; using System.Collections.Generic; diff --git a/test/UnitTests/Serialization/JsonApiSerializerTests.cs b/test/UnitTests/Serialization/JsonApiSerializerTests.cs index c781617d03..479a59e652 100644 --- a/test/UnitTests/Serialization/JsonApiSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiSerializerTests.cs @@ -1,10 +1,11 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Request; using JsonApiDotNetCore.Serialization; @@ -23,7 +24,7 @@ public void Can_Serialize_Complex_Types() // arrange var resourceGraphBuilder = new ResourceGraphBuilder(); resourceGraphBuilder.AddResource("test-resource"); - + var serializer = GetSerializer(resourceGraphBuilder); var resource = new TestResource @@ -40,7 +41,7 @@ public void Can_Serialize_Complex_Types() // assert Assert.NotNull(result); - var expectedFormatted = + var expectedFormatted = @"{ ""data"": { ""attributes"": { @@ -61,7 +62,7 @@ public void Can_Serialize_Complex_Types() } }"; var expected = Regex.Replace(expectedFormatted, @"\s+", ""); - + Assert.Equal(expected, result); } @@ -73,7 +74,7 @@ public void Can_Serialize_Deeply_Nested_Relationships() resourceGraphBuilder.AddResource("test-resource"); resourceGraphBuilder.AddResource("children"); resourceGraphBuilder.AddResource("infections"); - + var serializer = GetSerializer( resourceGraphBuilder, included: new List { "children.infections" } @@ -101,8 +102,8 @@ public void Can_Serialize_Deeply_Nested_Relationships() // assert Assert.NotNull(result); - - var expectedFormatted = + + var expectedFormatted = @"{ ""data"": { ""attributes"": { @@ -206,7 +207,7 @@ public void Can_Serialize_Deeply_Nested_Relationships() } private JsonApiSerializer GetSerializer( - ResourceGraphBuilder resourceGraphBuilder, + ResourceGraphBuilder resourceGraphBuilder, List included = null) { var resourceGraph = resourceGraphBuilder.Build(); @@ -221,10 +222,11 @@ private JsonApiSerializer GetSerializer( // jsonApiContextMock.Setup(m => m.HasManyRelationshipPointers).Returns(new HasManyRelationshipPointers()); // jsonApiContextMock.Setup(m => m.HasOneRelationshipPointers).Returns(new HasOneRelationshipPointers()); jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); - jsonApiContextMock.Setup(m => m.PageManager).Returns(new PageManager()); + var pmMock = new Mock(); + jsonApiContextMock.Setup(m => m.PageManager).Returns(pmMock.Object); + - if (included != null) - jsonApiContextMock.Setup(m => m.IncludedRelationships).Returns(included); + Assert.True(false); var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); @@ -239,7 +241,7 @@ private JsonApiSerializer GetSerializer( var provider = services.BuildServiceProvider(); var scoped = new TestScopedServiceProvider(provider); - var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object, scopedServiceProvider: scoped); + var documentBuilder = GetDocumentBuilder(jsonApiContextMock, scopedServiceProvider: scoped); var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); return serializer; @@ -260,7 +262,7 @@ private class ComplexType private class ChildResource : Identifiable { - [HasMany("infections")] public List Infections { get; set;} + [HasMany("infections")] public List Infections { get; set; } [HasOne("parent")] public TestResource Parent { get; set; } } @@ -269,5 +271,14 @@ private class InfectionResource : Identifiable { [HasOne("infected")] public ChildResource Infected { get; set; } } + + private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, TestScopedServiceProvider scopedServiceProvider = null) + { + var pageManagerMock = new Mock(); + var rmMock = new Mock(); + + return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); + + } } } diff --git a/test/UnitTests/Services/QueryAccessorTests.cs b/test/UnitTests/Services/QueryAccessorTests.cs index aa8bc6ae7e..855efa2d16 100644 --- a/test/UnitTests/Services/QueryAccessorTests.cs +++ b/test/UnitTests/Services/QueryAccessorTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; @@ -26,76 +26,80 @@ public QueryAccessorTests() [Fact] public void Can_Get_Guid_QueryValue() { - // arrange - const string key = "SomeId"; - var value = Guid.NewGuid(); - var querySet = new QuerySet - { - Filters = new List { - new FilterQuery(key, value.ToString(), "eq") - } - }; + //// arrange + //const string key = "SomeId"; + //var value = Guid.NewGuid(); + //var querySet = new QuerySet + //{ + // Filters = new List { + // new FilterQuery(key, value.ToString(), "eq") + // } + //}; + - _contextMock.Setup(c => c.QuerySet).Returns(querySet); + //_contextMock.Setup(c => c.QuerySet).Returns(querySet); - var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); + //var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); - // act - var success = service.TryGetValue("SomeId", out Guid result); + //// act + //var success = service.TryGetValue("SomeId", out Guid result); - // assert - Assert.True(success); - Assert.Equal(value, result); + //// assert + //Assert.True(success); + //Assert.Equal(value, result); + Assert.True(false); } [Fact] public void GetRequired_Throws_If_Not_Present() { // arrange - const string key = "SomeId"; - var value = Guid.NewGuid(); + //const string key = "SomeId"; + //var value = Guid.NewGuid(); - var querySet = new QuerySet - { - Filters = new List { - new FilterQuery(key, value.ToString(), "eq") - } - }; + //var querySet = new QuerySet + //{ + // Filters = new List { + // new FilterQuery(key, value.ToString(), "eq") + // } + //}; - _contextMock.Setup(c => c.QuerySet).Returns(querySet); + //_contextMock.Setup(c => c.QuerySet).Returns(querySet); - var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); + //var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); - // act - var exception = Assert.Throws(() => service.GetRequired("Invalid")); + //// act + //var exception = Assert.Throws(() => service.GetRequired("Invalid")); - // assert - Assert.Equal(422, exception.GetStatusCode()); + //// assert + //Assert.Equal(422, exception.GetStatusCode()); + Assert.True(false); } [Fact] public void GetRequired_Does_Not_Throw_If_Present() { // arrange - const string key = "SomeId"; - var value = Guid.NewGuid(); + //const string key = "SomeId"; + //var value = Guid.NewGuid(); - var querySet = new QuerySet - { - Filters = new List { - new FilterQuery(key, value.ToString(), "eq") - } - }; + //var querySet = new QuerySet + //{ + // Filters = new List { + // new FilterQuery(key, value.ToString(), "eq") + // } + //}; - _contextMock.Setup(c => c.QuerySet).Returns(querySet); + //_contextMock.Setup(c => c.QuerySet).Returns(querySet); - var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); + //var service = new QueryAccessor(_contextMock.Object, _loggerMock.Object); - // act - var result = service.GetRequired("SomeId"); + //// act + //var result = service.GetRequired("SomeId"); - // assert - Assert.Equal(value, result); + //// assert + //Assert.Equal(value, result); + Assert.True(false); } } } diff --git a/test/UnitTests/Services/QueryComposerTests.cs b/test/UnitTests/Services/QueryComposerTests.cs index efa600f2f3..607c321b6c 100644 --- a/test/UnitTests/Services/QueryComposerTests.cs +++ b/test/UnitTests/Services/QueryComposerTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; using Moq; using Xunit; @@ -25,13 +26,14 @@ public void Can_ComposeEqual_FilterStringForUrl() filters.Add(filter); querySet.Filters = filters; - _jsonApiContext + var rmMock = new Mock(); + rmMock .Setup(m => m.QuerySet) .Returns(querySet); var queryComposer = new QueryComposer(); // act - var filterString = queryComposer.Compose(_jsonApiContext.Object); + var filterString = queryComposer.Compose(rmMock.Object); // assert Assert.Equal("&filter[attribute]=eq:value", filterString); } @@ -47,14 +49,15 @@ public void Can_ComposeLessThan_FilterStringForUrl() filters.Add(filter); filters.Add(filter2); querySet.Filters = filters; - - _jsonApiContext + var rmMock = new Mock(); + rmMock .Setup(m => m.QuerySet) .Returns(querySet); + var queryComposer = new QueryComposer(); // act - var filterString = queryComposer.Compose(_jsonApiContext.Object); + var filterString = queryComposer.Compose(rmMock.Object); // assert Assert.Equal("&filter[attribute]=le:value&filter[attribute2]=value2", filterString); } @@ -65,13 +68,15 @@ public void NoFilter_Compose_EmptyStringReturned() // arrange var querySet = new QuerySet(); - _jsonApiContext + var rmMock = new Mock(); + rmMock .Setup(m => m.QuerySet) .Returns(querySet); var queryComposer = new QueryComposer(); - // act - var filterString = queryComposer.Compose(_jsonApiContext.Object); + // Act + + var filterString = queryComposer.Compose(rmMock.Object); // assert Assert.Equal("", filterString); } From 77a2ae9e0caf33eb258c6ceeb80204e0f3eda10b Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Fri, 24 May 2019 11:28:26 +0200 Subject: [PATCH 07/26] feat: removed JsonApiContext dependency from controller, fixed namespaced tests --- .../Controllers/ArticlesController.cs | 5 +- .../Controllers/CamelCasedModelsController.cs | 5 +- .../Controllers/PeopleController.cs | 5 +- .../Controllers/PersonRolesController.cs | 5 +- .../Controllers/TodoCollectionsController.cs | 5 +- .../Controllers/TodoItemsController.cs | 5 +- .../Controllers/TodoItemsTestController.cs | 9 ++-- .../Controllers/UsersController.cs | 5 +- .../Controllers/CustomTodoItemsController.cs | 5 +- .../Builders/DocumentBuilder.cs | 46 ++++++++++--------- .../Configuration/IJsonApiOptions.cs | 1 + .../Controllers/BaseJsonApiController.cs | 22 ++++++--- .../Controllers/JsonApiCmdController.cs | 2 +- .../Controllers/JsonApiController.cs | 42 +++++------------ .../Controllers/JsonApiQueryController.cs | 2 +- .../{RequestManager.cs => QueryManager.cs} | 0 .../Middleware/RequestMiddleware.cs | 10 ++-- .../Serialization/JsonApiDeSerializer.cs | 20 +++++--- 18 files changed, 102 insertions(+), 92 deletions(-) rename src/JsonApiDotNetCore/Managers/{RequestManager.cs => QueryManager.cs} (100%) diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs index 865f8454a2..abf6ce50ea 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/ArticlesController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -9,9 +10,9 @@ public class ArticlesController : JsonApiController
{ public ArticlesController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService
resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) + : base(jsonApiOptions, resourceGraph, resourceService) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs index cf9c8bdf2e..c46a9aa094 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/CamelCasedModelsController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.AspNetCore.Mvc; @@ -13,10 +14,10 @@ public class CamelCasedModelsController : JsonApiController { public CamelCasedModelsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs index 236c946f89..d29cafe508 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/PeopleController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class PeopleController : JsonApiController { public PeopleController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs index e00c5b906e..0234eb6899 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/PersonRolesController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class PersonRolesController : JsonApiController { public PersonRolesController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs index 04fe912e57..f2da06c9c2 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoCollectionsController.cs @@ -4,6 +4,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Data; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.AspNetCore.Mvc; @@ -18,11 +19,11 @@ public class TodoCollectionsController : JsonApiController resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { _dbResolver = contextResolver; } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs index 040ad38e20..c7f3a6244e 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class TodoItemsController : JsonApiController { public TodoItemsController( IJsonApiOptions jsonApiOPtions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOPtions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOPtions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs index caab1c1c85..18e566dc07 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsTestController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; @@ -13,10 +14,10 @@ public abstract class AbstractTodoItemsController { protected AbstractTodoItemsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService service, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, service, loggerFactory) + : base(jsonApiOptions, resourceGraph, service, loggerFactory) { } } @@ -25,10 +26,10 @@ public class TodoItemsTestController : AbstractTodoItemsController { public TodoItemsTestController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService service, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, service, loggerFactory) + : base(jsonApiOptions, resourceGraph, service, loggerFactory) { } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs index 931db12325..475b93b300 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/UsersController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class UsersController : JsonApiController { public UsersController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs b/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs index 4027582a9c..4e93d26bea 100644 --- a/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs +++ b/src/Examples/NoEntityFrameworkExample/Controllers/CustomTodoItemsController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class CustomTodoItemsController : JsonApiController { public CustomTodoItemsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index 04194d68c2..5f97567339 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -183,30 +183,34 @@ private void AddRelationships(ResourceObject data, ContextEntity contextEntity, private RelationshipData GetRelationshipData(RelationshipAttribute attr, ContextEntity contextEntity, IIdentifiable entity) { - //var linkBuilder = new LinkBuilder(_documentBuilderOptions,_requestManager); + var linkBuilder = new LinkBuilder(_jsonApiContext.Options,_requestManager); var relationshipData = new RelationshipData(); - //if (_jsonApiContext.Options.DefaultRelationshipLinks.HasFlag(Link.None) == false && attr.DocumentLinks.HasFlag(Link.None) == false) - //{ - // relationshipData.Links = new Links(); - // if (attr.DocumentLinks.HasFlag(Link.Self)) - // relationshipData.Links.Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); - - // if (attr.DocumentLinks.HasFlag(Link.Related)) - // relationshipData.Links.Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); - //} - - //// this only includes the navigation property, we need to actually check the navigation property Id - //var navigationEntity = _jsonApiContext.ResourceGraph.GetRelationshipValue(entity, attr); - //if (navigationEntity == null) - // relationshipData.SingleData = attr.IsHasOne - // ? GetIndependentRelationshipIdentifier((HasOneAttribute)attr, entity) - // : null; - //else if (navigationEntity is IEnumerable) - // relationshipData.ManyData = GetRelationships((IEnumerable)navigationEntity); - //else - // relationshipData.SingleData = GetRelationship(navigationEntity); + if (_jsonApiContext.Options.DefaultRelationshipLinks.HasFlag(Link.None) == false && attr.DocumentLinks.HasFlag(Link.None) == false) + { + relationshipData.Links = new Links(); + if (attr.DocumentLinks.HasFlag(Link.Self)) + { + relationshipData.Links.Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); + } + + if (attr.DocumentLinks.HasFlag(Link.Related)) + { + relationshipData.Links.Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); + } + } + + // this only includes the navigation property, we need to actually check the navigation property Id + var navigationEntity = _jsonApiContext.ResourceGraph.GetRelationshipValue(entity, attr); + if (navigationEntity == null) + relationshipData.SingleData = attr.IsHasOne + ? GetIndependentRelationshipIdentifier((HasOneAttribute)attr, entity) + : null; + else if (navigationEntity is IEnumerable) + relationshipData.ManyData = GetRelationships((IEnumerable)navigationEntity); + else + relationshipData.SingleData = GetRelationship(navigationEntity); return relationshipData; } diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 191ea1b937..952ff26582 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -29,5 +29,6 @@ public interface IJsonApiOptions bool RelativeLinks { get; set; } IResourceGraph ResourceGraph { get; set; } bool AllowCustomQueryParameters { get; set; } + string Namespace { get; set; } } } diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index 2c44a5f335..a04d2b1c94 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -7,6 +7,7 @@ using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Controllers { @@ -18,14 +19,13 @@ public BaseJsonApiController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService - ) : base(jsonApiOptions, jsonApiContext, resourceService, resourceService) { } + ) : base(jsonApiOptions, resourceService, resourceService) { } public BaseJsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IResourceQueryService queryService = null, IResourceCmdService cmdService = null - ) : base(jsonApiOptions, jsonApiContext, queryService, cmdService) { } + ) : base(jsonApiOptions, queryService, cmdService) { } public BaseJsonApiController( @@ -39,7 +39,7 @@ public BaseJsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } } public class BaseJsonApiController @@ -54,14 +54,24 @@ public class BaseJsonApiController private readonly IUpdateService _update; private readonly IUpdateRelationshipService _updateRelationships; private readonly IDeleteService _delete; + private readonly ILogger> _logger; private readonly IJsonApiOptions _jsonApiOptions; private readonly IResourceGraph _resourceGraph; public BaseJsonApiController( IJsonApiOptions jsonApiOptions, IResourceGraph resourceGraphManager, - IResourceService resourceService) + IResourceService resourceService, + ILoggerFactory loggerFactory) { + if(loggerFactory != null) + { + _logger = loggerFactory.CreateLogger>(); + } + else + { + _logger = new Logger>(new LoggerFactory()); + } _jsonApiOptions = jsonApiOptions; _resourceGraph = resourceGraphManager; _getAll = resourceService; @@ -76,7 +86,6 @@ public BaseJsonApiController( public BaseJsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IResourceQueryService queryService = null, IResourceCmdService cmdService = null) { @@ -93,7 +102,6 @@ public BaseJsonApiController( public BaseJsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs index eed4f9bd1a..3ee26db3c6 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs @@ -26,7 +26,7 @@ public JsonApiCmdController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) + : base(jsonApiOptions, resourceService) { } [HttpPost] diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index 0427f5a678..0d69034dd4 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Mvc; @@ -17,22 +18,15 @@ public class JsonApiController : JsonApiController where T : class, I public JsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, - ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + ILoggerFactory loggerFactory = null + ) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } public JsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, - IResourceService resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) - { } - - public JsonApiController( - IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -41,37 +35,23 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + - public JsonApiController(IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, - IResourceService resourceService, - ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) - { - } } public class JsonApiController : BaseJsonApiController where T : class, IIdentifiable { public JsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, - ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, resourceService) - { } - - public JsonApiController( - IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, - IResourceService resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) + ILoggerFactory loggerFactory = null) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory = null) { } public JsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -80,7 +60,7 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, jsonApiContext, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } [HttpGet] public override async Task GetAsync() => await base.GetAsync(); diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs index ad0978709b..21ca22a578 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs @@ -24,7 +24,7 @@ public JsonApiQueryController( IJsonApiOptions jsonApiOptions, IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) + : base(jsonApiOptions, resourceService) { } [HttpGet] diff --git a/src/JsonApiDotNetCore/Managers/RequestManager.cs b/src/JsonApiDotNetCore/Managers/QueryManager.cs similarity index 100% rename from src/JsonApiDotNetCore/Managers/RequestManager.cs rename to src/JsonApiDotNetCore/Managers/QueryManager.cs diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index b148692e41..68ba0de48c 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -20,7 +20,7 @@ public RequestMiddleware(RequestDelegate next) _next = next; } - public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext, IResourceGraph resourceGraph, IRequestManager requestManager, IJsonApiOptions options ) + public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext, IResourceGraph resourceGraph, IRequestManager requestManager, IJsonApiOptions options) { if (IsValid(context)) { @@ -29,7 +29,7 @@ public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext, IR // since the JsonApiContext is using field initializers // Need to work on finding a better solution. jsonApiContext.BeginOperation(); - ContextEntity contextEntityCurrent = GetCurrentEntity(context.Request.Path, resourceGraph); + ContextEntity contextEntityCurrent = GetCurrentEntity(context.Request.Path, resourceGraph, options); requestManager.SetContextEntity(contextEntityCurrent); requestManager.BasePath = GetBasePath(context, options, contextEntityCurrent?.EntityName); await _next(context); @@ -84,9 +84,11 @@ internal static string GetNamespaceFromPath(string path, string entityName) /// /// /// - private ContextEntity GetCurrentEntity(PathString path, IResourceGraph resourceGraph) + private ContextEntity GetCurrentEntity(PathString path, IResourceGraph resourceGraph, IJsonApiOptions options) { - var typeString = path.ToString().Split('/')[1]; + var pathSplit = path.ToString().Replace($"{options.Namespace}/", "").Split('/'); + + var typeString = pathSplit[1]; return resourceGraph.GetEntityType(typeString); } diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index ac4b4f2438..defdfd3e92 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -184,9 +184,15 @@ private object SetRelationships( foreach (var attr in contextEntity.Relationships) { - entity = attr.IsHasOne - ? SetHasOneRelationship(entity, entityProperties, (HasOneAttribute)attr, contextEntity, relationships, included) - : SetHasManyRelationship(entity, entityProperties, (HasManyAttribute)attr, contextEntity, relationships, included); + if (attr.IsHasOne) + { + SetHasOneRelationship(entity, entityProperties, (HasOneAttribute)attr, contextEntity, relationships, included); + } + else + { + SetHasManyRelationship(entity, entityProperties, (HasManyAttribute)attr, contextEntity, relationships, included); + } + } return entity; @@ -215,14 +221,14 @@ private object SetHasOneRelationship(object entity, SetHasOneNavigationPropertyValue(entity, attr, rio, included); // recursive call ... - if(included != null) + if (included != null) { var navigationPropertyValue = attr.GetValue(entity); var resourceGraphEntity = _jsonApiContext.ResourceGraph.GetContextEntity(attr.Type); - if(navigationPropertyValue != null && resourceGraphEntity != null) + if (navigationPropertyValue != null && resourceGraphEntity != null) { var includedResource = included.SingleOrDefault(r => r.Type == rio.Type && r.Id == rio.Id); - if(includedResource != null) + if (includedResource != null) SetRelationships(navigationPropertyValue, resourceGraphEntity, includedResource.Relationships, included); } } @@ -283,7 +289,7 @@ private object SetHasManyRelationship(object entity, if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) { - if(relationshipData.IsHasMany == false || relationshipData.ManyData == null) + if (relationshipData.IsHasMany == false || relationshipData.ManyData == null) return entity; var relatedResources = relationshipData.ManyData.Select(r => From 94615f85df27522a0ea8b67e4e7b067dace2804b Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Fri, 24 May 2019 17:29:29 +0200 Subject: [PATCH 08/26] feat: decoupled controllers --- .../Controllers/ArticlesController.cs | 12 +-- .../Controllers/PeopleController.cs | 7 +- .../ModelsController.cs | 6 +- .../Controllers/ReportsController.cs | 5 +- .../Controllers/CoursesController.cs | 5 +- .../Controllers/DepartmentsController.cs | 5 +- .../Controllers/StudentsController.cs | 5 +- .../Builders/DocumentBuilder.cs | 2 +- .../Controllers/BaseJsonApiController.cs | 13 ++- .../Controllers/JsonApiController.cs | 6 +- .../Internal/ResourceGraph.cs | 8 ++ .../Managers/Contracts/IRequestManager.cs | 3 + .../{QueryManager.cs => RequestManager.cs} | 1 + .../Middleware/RequestMiddleware.cs | 30 ++++++- .../Services/EntityResourceService.cs | 22 ++--- .../Services/JsonApiContext.cs | 5 ++ .../BaseJsonApiController_Tests.cs | 83 +++++++++---------- 17 files changed, 132 insertions(+), 86 deletions(-) rename src/JsonApiDotNetCore/Managers/{QueryManager.cs => RequestManager.cs} (96%) diff --git a/src/Examples/GettingStarted/Controllers/ArticlesController.cs b/src/Examples/GettingStarted/Controllers/ArticlesController.cs index 8077983e92..2d1567659b 100644 --- a/src/Examples/GettingStarted/Controllers/ArticlesController.cs +++ b/src/Examples/GettingStarted/Controllers/ArticlesController.cs @@ -1,6 +1,7 @@ using GettingStarted.Models; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; namespace GettingStarted @@ -9,16 +10,9 @@ public class ArticlesController : JsonApiController
{ public ArticlesController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService
resourceService) - - - - - - - - : base(jsonApiOptions, jsonApiContext, resourceService) + : base(jsonApiOptions, resourceGraph, resourceService) { } } } diff --git a/src/Examples/GettingStarted/Controllers/PeopleController.cs b/src/Examples/GettingStarted/Controllers/PeopleController.cs index b033ae172a..8d8ee58a2a 100644 --- a/src/Examples/GettingStarted/Controllers/PeopleController.cs +++ b/src/Examples/GettingStarted/Controllers/PeopleController.cs @@ -1,6 +1,7 @@ using GettingStarted.Models; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; namespace GettingStarted @@ -8,10 +9,10 @@ namespace GettingStarted public class PeopleController : JsonApiController { public PeopleController( - IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IJsonApiOptions jsonApiOptions, + IResourceGraph resourceGraph, IResourceService resourceService) - : base(jsonApiOptions,jsonApiContext, resourceService) + : base(jsonApiOptions, resourceGraph, resourceService) { } } } diff --git a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs index 55138e7d71..1c066c28c9 100644 --- a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs +++ b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelsController.cs @@ -1,6 +1,6 @@ -using GettingStarted.Models; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; namespace GettingStarted.ResourceDefinitionExample @@ -9,9 +9,9 @@ public class ModelsController : JsonApiController { public ModelsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) + : base(jsonApiOptions, resourceGraph, resourceService) { } } } diff --git a/src/Examples/ReportsExample/Controllers/ReportsController.cs b/src/Examples/ReportsExample/Controllers/ReportsController.cs index 95add7975c..e421477c20 100644 --- a/src/Examples/ReportsExample/Controllers/ReportsController.cs +++ b/src/Examples/ReportsExample/Controllers/ReportsController.cs @@ -3,6 +3,7 @@ using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal.Contracts; namespace ReportsExample.Controllers { @@ -11,9 +12,9 @@ public class ReportsController : BaseJsonApiController { public ReportsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IGetAllService getAll) - : base(jsonApiOptions, jsonApiContext, getAll: getAll) + : base(jsonApiOptions, resourceGraph, getAll: getAll) { } [HttpGet] diff --git a/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs b/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs index 908776ebc5..48d280f5cb 100644 --- a/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs +++ b/src/Examples/ResourceEntitySeparationExample/Controllers/CoursesController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models.Resources; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class CoursesController : JsonApiController { public CoursesController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs b/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs index 9edef6fa7c..63310743ac 100644 --- a/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs +++ b/src/Examples/ResourceEntitySeparationExample/Controllers/DepartmentsController.cs @@ -1,6 +1,7 @@ using System; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models.Resources; using Microsoft.Extensions.Logging; @@ -11,10 +12,10 @@ public class DepartmentsController : JsonApiController { public DepartmentsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs b/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs index 7ba8fde1af..5f3551849a 100644 --- a/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs +++ b/src/Examples/ResourceEntitySeparationExample/Controllers/StudentsController.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models.Resources; using Microsoft.Extensions.Logging; @@ -10,10 +11,10 @@ public class StudentsController : JsonApiController { public StudentsController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IResourceService resourceService, ILoggerFactory loggerFactory) - : base(jsonApiOptions, jsonApiContext, resourceService, loggerFactory) + : base(jsonApiOptions, resourceGraph, resourceService, loggerFactory) { } } } diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index 5f97567339..7814915134 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -217,7 +217,7 @@ private RelationshipData GetRelationshipData(RelationshipAttribute attr, Context private List GetIncludedEntities(List included, ContextEntity rootContextEntity, IIdentifiable rootResource) { - if (_jsonApiContext.RequestManager.IncludedRelationships != null) + if (_requestManager.IncludedRelationships != null) { foreach (var relationshipName in _jsonApiContext.RequestManager.IncludedRelationships) { diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index a04d2b1c94..50b06e7b31 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; @@ -30,7 +31,7 @@ public BaseJsonApiController( public BaseJsonApiController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -39,7 +40,7 @@ public BaseJsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, resourceGraph, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } } public class BaseJsonApiController @@ -82,6 +83,12 @@ public BaseJsonApiController( _update = resourceService; _updateRelationships = resourceService; _delete = resourceService; + ParseQueryParams(); + } + + private void ParseQueryParams() + { + } public BaseJsonApiController( @@ -102,6 +109,7 @@ public BaseJsonApiController( public BaseJsonApiController( IJsonApiOptions jsonApiOptions, + IResourceGraph resourceGraph, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -111,6 +119,7 @@ public BaseJsonApiController( IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null) { + _resourceGraph = resourceGraph; _jsonApiOptions = jsonApiOptions; _getAll = getAll; _getById = getById; diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index 0d69034dd4..9c9155bb8c 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -27,6 +27,7 @@ public JsonApiController( public JsonApiController( IJsonApiOptions jsonApiOptions, + IResourceGraph resourceGraph, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -35,7 +36,7 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, resourceGraph, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } } @@ -52,6 +53,7 @@ public JsonApiController( public JsonApiController( IJsonApiOptions jsonApiOptions, + IResourceGraph resourceGraph, IGetAllService getAll = null, IGetByIdService getById = null, IGetRelationshipService getRelationship = null, @@ -60,7 +62,7 @@ public JsonApiController( IUpdateService update = null, IUpdateRelationshipService updateRelationships = null, IDeleteService delete = null - ) : base(jsonApiOptions, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } + ) : base(jsonApiOptions, resourceGraph, getAll, getById, getRelationship, getRelationships, create, update, updateRelationships, delete) { } [HttpGet] public override async Task GetAsync() => await base.GetAsync(); diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index e2db1b667b..a08c18eaca 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -7,6 +7,10 @@ namespace JsonApiDotNetCore.Internal { + internal class ControllerMapping + { + + } /// /// keeps track of all the models/resources defined in JADNC /// @@ -14,6 +18,10 @@ public class ResourceGraph : IResourceGraph { internal List Entities { get; } internal List ValidationResults { get; } + + + + [Obsolete("please instantiate properly")] internal static IResourceGraph Instance { get; set; } public ResourceGraph() { } diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs index 3f8f0b8e52..41eef8f1b7 100644 --- a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs @@ -4,6 +4,7 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Http; namespace JsonApiDotNetCore.Managers.Contracts { @@ -20,6 +21,8 @@ public interface IRequestManager : IQueryRequest /// string BasePath { get; set; } QuerySet QuerySet { get; set; } + IQueryCollection FullQuerySet { get; set; } + /// /// Gets the relationships as set in the query parameters /// diff --git a/src/JsonApiDotNetCore/Managers/QueryManager.cs b/src/JsonApiDotNetCore/Managers/RequestManager.cs similarity index 96% rename from src/JsonApiDotNetCore/Managers/QueryManager.cs rename to src/JsonApiDotNetCore/Managers/RequestManager.cs index 857b97c64f..da0e3f105e 100644 --- a/src/JsonApiDotNetCore/Managers/QueryManager.cs +++ b/src/JsonApiDotNetCore/Managers/RequestManager.cs @@ -12,6 +12,7 @@ class RequestManager : IRequestManager { private ContextEntity _contextEntity; + private IQueryParser _queryParser; public string BasePath { get; set; } public List IncludedRelationships { get; set; } diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 68ba0de48c..4173863f9a 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -11,6 +11,11 @@ namespace JsonApiDotNetCore.Middleware { + /// + /// Can be overwritten to help you out during testing + /// + /// This sets all necessary paaramters relating to the HttpContext for JADNC + /// public class RequestMiddleware { private readonly RequestDelegate _next; @@ -20,8 +25,14 @@ public RequestMiddleware(RequestDelegate next) _next = next; } - public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext, IResourceGraph resourceGraph, IRequestManager requestManager, IJsonApiOptions options) + public async Task Invoke(HttpContext context, + IJsonApiContext jsonApiContext, + IResourceGraph resourceGraph, + IRequestManager requestManager, + IQueryParser queryParser, + IJsonApiOptions options) { + if (IsValid(context)) { // HACK: this currently results in allocation of @@ -32,9 +43,26 @@ public async Task Invoke(HttpContext context, IJsonApiContext jsonApiContext, IR ContextEntity contextEntityCurrent = GetCurrentEntity(context.Request.Path, resourceGraph, options); requestManager.SetContextEntity(contextEntityCurrent); requestManager.BasePath = GetBasePath(context, options, contextEntityCurrent?.EntityName); + //Handle all querySet + HandleUriParameters(context, queryParser, requestManager); + await _next(context); } } + /// + /// Parses the uri, and helps you out + /// + /// + /// + protected void HandleUriParameters(HttpContext context, IQueryParser queryParser, IRequestManager requestManager) + { + if (context.Request.Query.Count > 0) + { + //requestManager.FullQuerySet = context.Request.Query; + requestManager.QuerySet = queryParser.Parse(context.Request.Query); + requestManager.IncludedRelationships = requestManager.QuerySet.IncludedRelationships; + } + } private string GetBasePath(HttpContext context, IJsonApiOptions options, string entityName) { diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index 78f1539520..6aad0e92fb 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -48,7 +48,7 @@ public class EntityResourceService : where TEntity : class, IIdentifiable { private readonly IPageManager _pageManager; - private readonly IRequestManager _queryManager; + private readonly IRequestManager _requestManager; private readonly IJsonApiContext _jsonApiContext; private readonly IJsonApiOptions _options; private readonly IEntityRepository _repository; @@ -80,7 +80,7 @@ public EntityResourceService( ILoggerFactory loggerFactory) { _pageManager = pageManager; - _queryManager = queryManager; + _requestManager = queryManager; _jsonApiContext = jsonApiContext; _options = options; _repository = entityRepository; @@ -108,7 +108,7 @@ public virtual async Task CreateAsync(TResource resource) // this ensures relationships get reloaded from the database if they have // been requested // https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/343 - if (AreRelationshipsIncluded()) + if (ShouldRelationshipsBeIncluded()) { if (_repository is IEntityFrameworkRepository efRepository) efRepository.DetachRelationshipPointers(entity); @@ -129,7 +129,7 @@ public virtual async Task> GetAsync() entities = ApplySortAndFilterQuery(entities); - if (AreRelationshipsIncluded()) + if (ShouldRelationshipsBeIncluded()) { entities = IncludeRelationships(entities, _jsonApiContext.RequestManager.QuerySet.IncludedRelationships); } @@ -150,7 +150,7 @@ public virtual async Task GetAsync(TId id) { TResource resource; - if (AreRelationshipsIncluded()) + if (ShouldRelationshipsBeIncluded()) { resource = await GetWithRelationshipsAsync(id); } @@ -270,7 +270,7 @@ protected virtual IQueryable ApplySortAndFilterQuery(IQueryable - /// actually include the relationships + /// Actually include the relationships /// /// /// @@ -294,17 +294,17 @@ protected virtual IQueryable IncludeRelationships(IQueryable e /// private async Task GetWithRelationshipsAsync(TId id) { - var fields = _queryManager.GetFields(); + var fields = _requestManager.GetFields(); var query = _repository.Select(_repository.GetQueryable(), fields).Where(e => e.Id.Equals(id)); - _queryManager.GetRelationships().ForEach(r => + _requestManager.GetRelationships().ForEach(r => { query = _repository.Include(query, r); }); TEntity value; // https://github.com/aspnet/EntityFrameworkCore/issues/6573 - if(_queryManager.GetFields()?.Count() > 0) + if(_requestManager.GetFields()?.Count() > 0) { value = query.FirstOrDefault(); } @@ -319,9 +319,9 @@ private async Task GetWithRelationshipsAsync(TId id) /// Should the relationships be included? /// /// - private bool AreRelationshipsIncluded() + private bool ShouldRelationshipsBeIncluded() { - return _queryManager.GetRelationships()?.Count() > 0; + return _requestManager.GetRelationships()?.Count() > 0; } /// diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs index faf65f9397..637080e98f 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiContext.cs @@ -42,10 +42,12 @@ public JsonApiContext( } public IJsonApiOptions Options { get; set; } + [Obsolete("Please use the standalone `IResourceGraph`")] public IResourceGraph ResourceGraph { get; set; } [Obsolete("Use the proxied member IControllerContext.RequestEntity instead.")] public ContextEntity RequestEntity { get => _controllerContext.RequestEntity; set => _controllerContext.RequestEntity = value; } + [Obsolete("Please us the IRequestManager")] public QuerySet QuerySet { get; set; } public bool IsRelationshipData { get; set; } public bool IsRelationshipPath { get; private set; } @@ -61,9 +63,12 @@ public JsonApiContext( public Dictionary RelationshipsToUpdate { get; set; } = new Dictionary(); public HasManyRelationshipPointers HasManyRelationshipPointers { get; private set; } = new HasManyRelationshipPointers(); public HasOneRelationshipPointers HasOneRelationshipPointers { get; private set; } = new HasOneRelationshipPointers(); + [Obsolete("Please use the standalone Requestmanager")] public IRequestManager RequestManager { get; set; } IPageManager IJsonApiContext.PageManager { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + [Obsolete("This is no longer necessary")] public IJsonApiContext ApplyContext(object controller) { if (controller == null) diff --git a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs index 6fe8638eca..d6c5e951b8 100644 --- a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs +++ b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs @@ -18,7 +18,7 @@ public class Resource : Identifiable { [Attr("test-attribute")] public string TestAttribute { get; set; } } - private Mock _jsonApiContextMock = new Mock(); + private Mock _resourceGraph = new Mock(); private Mock _resourceGraphMock = new Mock(); [Fact] @@ -26,14 +26,14 @@ public async Task GetAsync_Calls_Service() { // arrange var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getAll: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getAll: serviceMock.Object); // act await controller.GetAsync(); // assert serviceMock.Verify(m => m.GetAsync(), Times.Once); - VerifyApplyContext(); + } [Fact] @@ -41,7 +41,7 @@ public async Task GetAsync_Throws_405_If_No_Service() { // arrange var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, null); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, null); // act var exception = await Assert.ThrowsAsync(() => controller.GetAsync()); @@ -56,14 +56,14 @@ public async Task GetAsyncById_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getById: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getById: serviceMock.Object); // act await controller.GetAsync(id); // assert serviceMock.Verify(m => m.GetAsync(id), Times.Once); - VerifyApplyContext(); + } [Fact] @@ -72,7 +72,7 @@ public async Task GetAsyncById_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getById: null); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getById: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetAsync(id)); @@ -87,14 +87,13 @@ public async Task GetRelationshipsAsync_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationships: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getRelationships: serviceMock.Object); // act await controller.GetRelationshipsAsync(id, string.Empty); // assert serviceMock.Verify(m => m.GetRelationshipsAsync(id, string.Empty), Times.Once); - VerifyApplyContext(); } [Fact] @@ -103,7 +102,7 @@ public async Task GetRelationshipsAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationships: null); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getRelationships: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetRelationshipsAsync(id, string.Empty)); @@ -118,14 +117,13 @@ public async Task GetRelationshipAsync_Calls_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationship: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getRelationship: serviceMock.Object); // act await controller.GetRelationshipAsync(id, string.Empty); // assert serviceMock.Verify(m => m.GetRelationshipAsync(id, string.Empty), Times.Once); - VerifyApplyContext(); } [Fact] @@ -134,7 +132,7 @@ public async Task GetRelationshipAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, getRelationship: null); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, getRelationship: null); // act var exception = await Assert.ThrowsAsync(() => controller.GetRelationshipAsync(id, string.Empty)); @@ -150,16 +148,16 @@ public async Task PatchAsync_Calls_Service() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); - _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions()); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: serviceMock.Object); + //_resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); + + + var controller = new BaseJsonApiController(new JsonApiOptions(), _resourceGraph.Object, update: serviceMock.Object); // act await controller.PatchAsync(id, resource); // assert serviceMock.Verify(m => m.UpdateAsync(id, It.IsAny()), Times.Once); - VerifyApplyContext(); } [Fact] @@ -169,16 +167,15 @@ public async Task PatchAsync_ModelStateInvalid_ValidateModelStateDisbled() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); - _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = false }); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: serviceMock.Object); + //_resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); + + var controller = new BaseJsonApiController(new JsonApiOptions(), _resourceGraph.Object, update: serviceMock.Object); // act var response = await controller.PatchAsync(id, resource); // assert serviceMock.Verify(m => m.UpdateAsync(id, It.IsAny()), Times.Once); - VerifyApplyContext(); Assert.IsNotType(response); } @@ -189,11 +186,10 @@ public async Task PatchAsync_ModelStateInvalid_ValidateModelStateEnabled() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - _jsonApiContextMock.SetupGet(a => a.ResourceGraph).Returns(_resourceGraphMock.Object); _resourceGraphMock.Setup(a => a.GetPublicAttributeName("TestAttribute")).Returns("test-attribute"); - _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); - _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions{ValidateModelState = true}); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: serviceMock.Object); +// _resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); + + var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = true }, _resourceGraph.Object, update: serviceMock.Object); controller.ModelState.AddModelError("TestAttribute", "Failed Validation"); // act @@ -211,7 +207,7 @@ public async Task PatchAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, update: null); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, update: null); // act var exception = await Assert.ThrowsAsync(() => controller.PatchAsync(id, It.IsAny())); @@ -226,9 +222,9 @@ public async Task PostAsync_Calls_Service() // arrange var resource = new Resource(); var serviceMock = new Mock>(); - _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); - _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions()); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, create: serviceMock.Object); +// _resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); + + var controller = new BaseJsonApiController(new JsonApiOptions(), _resourceGraph.Object, create: serviceMock.Object); serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext {HttpContext = new DefaultHttpContext()}; @@ -237,7 +233,6 @@ public async Task PostAsync_Calls_Service() // assert serviceMock.Verify(m => m.CreateAsync(It.IsAny()), Times.Once); - VerifyApplyContext(); } [Fact] @@ -246,9 +241,9 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateDisabled() // arrange var resource = new Resource(); var serviceMock = new Mock>(); - _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); - _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = false }); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, create: serviceMock.Object); + //_resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); + //_resourceGraph.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = false }); + var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = false }, _resourceGraph.Object, create: serviceMock.Object); serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext { HttpContext = new DefaultHttpContext() }; @@ -257,7 +252,6 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateDisabled() // assert serviceMock.Verify(m => m.CreateAsync(It.IsAny()), Times.Once); - VerifyApplyContext(); Assert.IsNotType(response); } @@ -267,11 +261,11 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateEnabled() // arrange var resource = new Resource(); var serviceMock = new Mock>(); - _jsonApiContextMock.SetupGet(a => a.ResourceGraph).Returns(_resourceGraphMock.Object); + //_resourceGraph.SetupGet(a => a.ResourceGraph).Returns(_resourceGraphMock.Object); _resourceGraphMock.Setup(a => a.GetPublicAttributeName("TestAttribute")).Returns("test-attribute"); - _jsonApiContextMock.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_jsonApiContextMock.Object); - _jsonApiContextMock.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = true }); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, create: serviceMock.Object); + //_resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); + //_resourceGraph.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = true }); + var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = false }, _resourceGraph.Object, create: serviceMock.Object); controller.ModelState.AddModelError("TestAttribute", "Failed Validation"); // act @@ -290,14 +284,13 @@ public async Task PatchRelationshipsAsync_Calls_Service() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, updateRelationships: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, updateRelationships: serviceMock.Object); // act await controller.PatchRelationshipsAsync(id, string.Empty, null); // assert serviceMock.Verify(m => m.UpdateRelationshipsAsync(id, string.Empty, null), Times.Once); - VerifyApplyContext(); } [Fact] @@ -306,7 +299,7 @@ public async Task PatchRelationshipsAsync_Throws_405_If_No_Service() // arrange const int id = 0; var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, updateRelationships: null); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, updateRelationships: null); // act var exception = await Assert.ThrowsAsync(() => controller.PatchRelationshipsAsync(id, string.Empty, null)); @@ -322,14 +315,13 @@ public async Task DeleteAsync_Calls_Service() const int id = 0; var resource = new Resource(); var serviceMock = new Mock>(); - var controller = new BaseJsonApiController(new Mock().Object, _jsonApiContextMock.Object, delete: serviceMock.Object); + var controller = new BaseJsonApiController(new Mock().Object, _resourceGraph.Object, delete: serviceMock.Object); // Act await controller.DeleteAsync(id); // Assert serviceMock.Verify(m => m.DeleteAsync(id), Times.Once); - VerifyApplyContext(); } [Fact] @@ -340,7 +332,7 @@ public async Task DeleteAsync_Throws_405_If_No_Service() var serviceMock = new Mock>(); var controller = new BaseJsonApiController(new Mock().Object, - _jsonApiContextMock.Object, delete: null); + _resourceGraph.Object, delete: null); // act var exception = await Assert.ThrowsAsync(() => controller.DeleteAsync(id)); @@ -349,7 +341,6 @@ public async Task DeleteAsync_Throws_405_If_No_Service() Assert.Equal(405, exception.GetStatusCode()); } - private void VerifyApplyContext() - => _jsonApiContextMock.Verify(m => m.ApplyContext(It.IsAny>()), Times.Once); + } } From 4a53c0056bba74c8967547ec365cb10ebe2cc4b4 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Mon, 27 May 2019 16:14:50 +0200 Subject: [PATCH 09/26] chore: renamed resourcegraphbuilder, took out some extensions, made benchmarks work again --- benchmarks/Query/QueryParser_Benchmarks.cs | 11 +- .../Controllers/TodoItemsCustomController.cs | 3 +- .../Builders/IResourceGraphBuilder.cs | 81 ++++++++++++ ...raphBuilder.cs => ResourceGraphBuilder.cs} | 118 +++++++----------- .../Configuration/JsonApiOptions.cs | 1 + .../Data/DefaultEntityRepository.cs | 31 ++++- .../Extensions/IQueryableExtensions.cs | 10 -- .../IServiceCollectionExtensions.cs | 27 ++-- .../Formatters/JsonApiReader.cs | 16 ++- .../Graph/ServiceDiscoveryFacade.cs | 94 ++++++++++---- .../Internal/Contracts/IResourceGraph.cs | 3 + src/JsonApiDotNetCore/Internal/PageManager.cs | 15 ++- .../Internal/Query/AttrSortQuery.cs | 6 +- .../Internal/Query/BaseAttrQuery.cs | 36 +++--- .../Internal/Query/BaseFilterQuery.cs | 2 +- .../Internal/Query/RelatedAttrSortQuery.cs | 2 +- .../Internal/ResourceGraph.cs | 41 +++++- .../Managers/Contracts/IRequestManager.cs | 7 ++ .../Managers/RequestManager.cs | 5 + .../Middleware/RequestMiddleware.cs | 48 ++++++- .../Services/EntityResourceService.cs | 4 +- .../Services/IJsonApiContext.cs | 3 + .../Services/JsonApiContext.cs | 2 - src/JsonApiDotNetCore/Services/QueryParser.cs | 25 ++-- .../Extensibility/CustomControllerTests.cs | 7 +- .../Acceptance/TodoItemsControllerTests.cs | 23 ++-- test/UnitTests/Services/QueryParser_Tests.cs | 85 ++++++------- 27 files changed, 470 insertions(+), 236 deletions(-) create mode 100644 src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs rename src/JsonApiDotNetCore/Builders/{ContextGraphBuilder.cs => ResourceGraphBuilder.cs} (78%) diff --git a/benchmarks/Query/QueryParser_Benchmarks.cs b/benchmarks/Query/QueryParser_Benchmarks.cs index de82baa60f..19819c3609 100644 --- a/benchmarks/Query/QueryParser_Benchmarks.cs +++ b/benchmarks/Query/QueryParser_Benchmarks.cs @@ -5,6 +5,7 @@ using BenchmarkDotNet.Attributes.Jobs; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http.Internal; @@ -21,14 +22,14 @@ public class QueryParser_Benchmarks { private const string DESCENDING_SORT = "-" + ATTRIBUTE; public QueryParser_Benchmarks() { - var controllerContextMock = new Mock(); - controllerContextMock.Setup(m => m.RequestEntity).Returns(new ContextEntity { + var requestMock = new Mock(); + requestMock.Setup(m => m.GetContextEntity()).Returns(new ContextEntity { Attributes = new List { new AttrAttribute(ATTRIBUTE, ATTRIBUTE) } }); var options = new JsonApiOptions(); - _queryParser = new BenchmarkFacade(controllerContextMock.Object, options); + _queryParser = new BenchmarkFacade(requestMock.Object, options); } [Benchmark] @@ -58,8 +59,8 @@ private void Run(int iterations, Action action) { // this facade allows us to expose and micro-benchmark protected methods private class BenchmarkFacade : QueryParser { public BenchmarkFacade( - IControllerContext controllerContext, - JsonApiOptions options) : base(controllerContext, options) { } + IRequestManager requestManager, + JsonApiOptions options) : base(requestManager, options) { } public void _ParseSortParameters(string value) => base.ParseSortParameters(value); } diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs index ca2e860fa9..17b11215c4 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/TodoItemsCustomController.cs @@ -9,8 +9,7 @@ namespace JsonApiDotNetCoreExample.Controllers { - [DisableRoutingConvention] - [Route("custom/route/todo-items")] + [DisableRoutingConvention, Route("custom/route/todo-items")] public class TodoItemsCustomController : CustomJsonApiController { public TodoItemsCustomController( diff --git a/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs new file mode 100644 index 0000000000..7bcf6aa7ad --- /dev/null +++ b/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Graph; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCore.Builders +{ + public interface IResourceGraphBuilder + { + /// + /// Construct the + /// + IResourceGraph Build(); + + /// + /// Add a json:api resource + /// + /// The resource model type + /// + /// The pluralized name that should be exposed by the API. + /// If nothing is specified, the configured name formatter will be used. + /// See . + /// + IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable; + + IResourceGraphBuilder AddControllerPairing(Type controller, Type model = null); + + /// + /// Add a json:api resource + /// + /// The resource model type + /// The resource model identifier type + /// + /// The pluralized name that should be exposed by the API. + /// If nothing is specified, the configured name formatter will be used. + /// See . + /// + IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable; + + /// + /// Add a Json:Api resource + /// + /// The resource model type + /// The resource model identifier type + /// + /// The pluralized name that should be exposed by the API. + /// If nothing is specified, the configured name formatter will be used. + /// See . + /// + IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null); + + /// + /// Add all the models that are part of the provided + /// that also implement + /// + /// The implementation type. + IResourceGraphBuilder AddDbContext() where T : DbContext; + + /// + /// Specify the used to format resource names. + /// + /// Formatter used to define exposed resource names by convention. + IResourceGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNameFormatter); + + /// + /// Which links to include. Defaults to . + /// + Link DocumentLinks { get; set; } + + + } +} diff --git a/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs similarity index 78% rename from src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs rename to src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs index 038796c17a..696d2fe176 100644 --- a/src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs @@ -9,76 +9,18 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Builders { - public interface IResourceGraphBuilder - { - /// - /// Construct the - /// - IResourceGraph Build(); - - /// - /// Add a json:api resource - /// - /// The resource model type - /// - /// The pluralized name that should be exposed by the API. - /// If nothing is specified, the configured name formatter will be used. - /// See . - /// - IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable; - - /// - /// Add a json:api resource - /// - /// The resource model type - /// The resource model identifier type - /// - /// The pluralized name that should be exposed by the API. - /// If nothing is specified, the configured name formatter will be used. - /// See . - /// - IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable; - - /// - /// Add a json:api resource - /// - /// The resource model type - /// The resource model identifier type - /// - /// The pluralized name that should be exposed by the API. - /// If nothing is specified, the configured name formatter will be used. - /// See . - /// - IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null); - - /// - /// Add all the models that are part of the provided - /// that also implement - /// - /// The implementation type. - IResourceGraphBuilder AddDbContext() where T : DbContext; - - /// - /// Specify the used to format resource names. - /// - /// Formatter used to define exposed resource names by convention. - IResourceGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNameFormatter); - - /// - /// Which links to include. Defaults to . - /// - Link DocumentLinks { get; set; } - } - public class ResourceGraphBuilder : IResourceGraphBuilder { private List _entities = new List(); private List _validationResults = new List(); + private Dictionary> _controllerMapper = new Dictionary>() { }; + private List _undefinedMapper = new List() { }; private bool _usesDbContext; private IResourceNameFormatter _resourceNameFormatter = JsonApiOptions.ResourceNameFormatter; @@ -91,7 +33,23 @@ public IResourceGraph Build() // this must be done at build so that call order doesn't matter _entities.ForEach(e => e.Links = GetLinkFlags(e.EntityType)); - var graph = new ResourceGraph(_entities, _usesDbContext, _validationResults); + List controllerContexts = new List() { }; + foreach(var cm in _controllerMapper) + { + var model = cm.Key; + foreach(var controller in cm.Value) + { + var routeAttribute = controller.GetCustomAttribute(); + + controllerContexts.Add(new ControllerModelMap + { + Model = model, + Controller = controller, + Path = routeAttribute?.Template + }); + } + } + var graph = new ResourceGraph(_entities, _usesDbContext, _validationResults, controllerContexts); return graph; } @@ -183,16 +141,17 @@ protected virtual List GetRelationships(Type entityType) attribute.Type = GetRelationshipType(attribute, prop); attributes.Add(attribute); - if (attribute is HasManyThroughAttribute hasManyThroughAttribute) { + if (attribute is HasManyThroughAttribute hasManyThroughAttribute) + { var throughProperty = properties.SingleOrDefault(p => p.Name == hasManyThroughAttribute.InternalThroughName); - if(throughProperty == null) + if (throughProperty == null) throw new JsonApiSetupException($"Invalid '{nameof(HasManyThroughAttribute)}' on type '{entityType}'. Type does not contain a property named '{hasManyThroughAttribute.InternalThroughName}'."); - - if(throughProperty.PropertyType.Implements() == false) + + if (throughProperty.PropertyType.Implements() == false) throw new JsonApiSetupException($"Invalid '{nameof(HasManyThroughAttribute)}' on type '{entityType}.{throughProperty.Name}'. Property type does not implement IList."); - + // assumption: the property should be a generic collection, e.g. List - if(throughProperty.PropertyType.IsGenericType == false) + if (throughProperty.PropertyType.IsGenericType == false) throw new JsonApiSetupException($"Invalid '{nameof(HasManyThroughAttribute)}' on type '{entityType}'. Expected through entity to be a generic type, such as List<{prop.PropertyType}>."); // Article → List @@ -202,7 +161,7 @@ protected virtual List GetRelationships(Type entityType) hasManyThroughAttribute.ThroughType = throughProperty.PropertyType.GetGenericArguments()[0]; var throughProperties = hasManyThroughAttribute.ThroughType.GetProperties(); - + // ArticleTag.Article hasManyThroughAttribute.LeftProperty = throughProperties.SingleOrDefault(x => x.PropertyType == entityType) ?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a navigation property to type {entityType}"); @@ -215,7 +174,7 @@ protected virtual List GetRelationships(Type entityType) // Article → ArticleTag.Tag hasManyThroughAttribute.RightProperty = throughProperties.SingleOrDefault(x => x.PropertyType == hasManyThroughAttribute.Type) ?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a navigation property to type {hasManyThroughAttribute.Type}"); - + // ArticleTag.TagId var rightIdPropertyName = JsonApiOptions.RelatedIdMapper.GetRelatedIdPropertyName(hasManyThroughAttribute.RightProperty.Name); hasManyThroughAttribute.RightIdProperty = throughProperties.SingleOrDefault(x => x.Name == rightIdPropertyName) @@ -305,5 +264,24 @@ public IResourceGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNam _resourceNameFormatter = resourceNameFormatter; return this; } + + public IResourceGraphBuilder AddControllerPairing(Type controller, Type model = null) + { + if (model == null) + { + _undefinedMapper.Add(controller); + return this; + + } + if (_controllerMapper.Keys.Contains(model)) + { + _controllerMapper[model].Add(controller); + } + else + { + _controllerMapper.Add(model, new List() { controller }); + } + return this; + } } } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index f81c034663..c0499d472a 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -77,6 +77,7 @@ public class JsonApiOptions : IJsonApiOptions /// /// The graph of all resources exposed by this application. /// + [Obsolete("Use the standalone resourcegraph")] public IResourceGraph ResourceGraph { get; set; } /// diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 2a60acff21..4254935d78 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -7,6 +7,7 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Generics; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.EntityFrameworkCore; @@ -45,6 +46,7 @@ public class DefaultEntityRepository IEntityFrameworkRepository where TEntity : class, IIdentifiable { + private readonly IRequestManager _requestManager; private readonly DbContext _context; private readonly DbSet _dbSet; private readonly ILogger _logger; @@ -57,6 +59,7 @@ public DefaultEntityRepository( IDbContextResolver contextResolver, ResourceDefinition resourceDefinition = null) { + _requestManager = jsonApiContext.RequestManager; _context = contextResolver.GetContext(); _dbSet = contextResolver.GetDbSet(); _jsonApiContext = jsonApiContext; @@ -70,6 +73,8 @@ public DefaultEntityRepository( IDbContextResolver contextResolver, ResourceDefinition resourceDefinition = null) { + _requestManager = jsonApiContext.RequestManager; + _context = contextResolver.GetContext(); _dbSet = contextResolver.GetDbSet(); _jsonApiContext = jsonApiContext; @@ -79,7 +84,7 @@ public DefaultEntityRepository( } - + public virtual IQueryable Get() { if (_jsonApiContext.RequestManager.QuerySet?.Fields != null && _jsonApiContext.RequestManager.QuerySet.Fields.Count > 0) @@ -89,7 +94,7 @@ public virtual IQueryable Get() } /// - public virtual IQueryable GetQueryable() + public virtual IQueryable GetQueryable() => _dbSet; public virtual IQueryable Select(IQueryable entities, List fields) @@ -112,7 +117,23 @@ public virtual IQueryable Filter(IQueryable entities, FilterQu } } - return entities.Filter(_jsonApiContext, filterQuery); + return FilterEntities(entities, filterQuery); + } + + public IQueryable FilterEntities(IQueryable entities, FilterQuery filterQuery) + { + if (filterQuery == null) + { + return entities; + } + + // Relationship.Attribute + if (filterQuery.IsAttributeOfRelationship) + { + return entities.Filter(new RelatedAttrFilterQuery(_jsonApiContext, filterQuery)); + } + + return entities.Filter(new AttrFilterQuery(_jsonApiContext, filterQuery)); } /// @@ -245,7 +266,7 @@ private void AttachHasMany(TEntity entity, HasManyAttribute relationship, IList foreach (var pointer in pointers) { if (_context.EntityIsTracked(pointer as IIdentifiable) == false) - _context.Entry(pointer).State = EntityState.Unchanged; + _context.Entry(pointer).State = EntityState.Unchanged; } } } @@ -407,7 +428,7 @@ public virtual IQueryable Include(IQueryable entities, string // variables mutated in recursive loop // TODO: make recursive method string internalRelationshipPath = null; - var entity = _jsonApiContext.RequestEntity; + var entity = _requestManager.GetContextEntity(); for (var i = 0; i < relationshipChain.Length; i++) { var requestedRelationship = relationshipChain[i]; diff --git a/src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs b/src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs index af33e883a6..4dfa83733c 100644 --- a/src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs @@ -122,17 +122,7 @@ private static IOrderedQueryable CallGenericOrderMethod(IQuery return (IOrderedQueryable)result; } - public static IQueryable Filter(this IQueryable source, IJsonApiContext jsonApiContext, FilterQuery filterQuery) - { - if (filterQuery == null) - return source; - // Relationship.Attribute - if (filterQuery.IsAttributeOfRelationship) - return source.Filter(new RelatedAttrFilterQuery(jsonApiContext, filterQuery)); - - return source.Filter(new AttrFilterQuery(jsonApiContext, filterQuery)); - } public static IQueryable Filter(this IQueryable source, BaseFilterQuery filterQuery) { diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 57674cf0ca..066b8a65af 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -66,8 +66,8 @@ public static IServiceCollection AddJsonApi( { var config = new JsonApiOptions(); configureOptions(config); - - if(autoDiscover != null) + + if (autoDiscover != null) { var facade = new ServiceDiscoveryFacade(services, config.ResourceGraphBuilder); autoDiscover(facade); @@ -103,7 +103,9 @@ public static void AddJsonApiInternals( JsonApiOptions jsonApiOptions) { if (jsonApiOptions.ResourceGraph == null) + { jsonApiOptions.ResourceGraph = jsonApiOptions.ResourceGraphBuilder.Build(); + } if (jsonApiOptions.ResourceGraph.UsesDbContext == false) { @@ -141,7 +143,7 @@ public static void AddJsonApiInternals( services.AddScoped(typeof(IResourceService<,>), typeof(EntityResourceService<,>)); services.AddSingleton(jsonApiOptions); - services.AddTransient(); + services.AddScoped(); services.AddSingleton(jsonApiOptions.ResourceGraph); services.AddScoped(); services.AddSingleton(); @@ -197,7 +199,7 @@ public static void SerializeAsJsonApi(this MvcOptions options, JsonApiOptions js /// Adds all required registrations for the service to the container /// /// - public static IServiceCollection AddResourceService(this IServiceCollection services) + public static IServiceCollection AddResourceService(this IServiceCollection services) { var typeImplementsAnExpectedInterface = false; @@ -206,28 +208,29 @@ public static IServiceCollection AddResourceService(this IServiceCollection s // it is _possible_ that a single concrete type could be used for multiple resources... var resourceDescriptors = GetResourceTypesFromServiceImplementation(serviceImplementationType); - foreach(var resourceDescriptor in resourceDescriptors) + foreach (var resourceDescriptor in resourceDescriptors) { - foreach(var openGenericType in ServiceDiscoveryFacade.ServiceInterfaces) + foreach (var openGenericType in ServiceDiscoveryFacade.ServiceInterfaces) { // A shorthand interface is one where the id type is ommitted // e.g. IResourceService is the shorthand for IResourceService var isShorthandInterface = (openGenericType.GetTypeInfo().GenericTypeParameters.Length == 1); - if(isShorthandInterface && resourceDescriptor.IdType != typeof(int)) + if (isShorthandInterface && resourceDescriptor.IdType != typeof(int)) continue; // we can't create a shorthand for id types other than int var concreteGenericType = isShorthandInterface ? openGenericType.MakeGenericType(resourceDescriptor.ResourceType) : openGenericType.MakeGenericType(resourceDescriptor.ResourceType, resourceDescriptor.IdType); - if(concreteGenericType.IsAssignableFrom(serviceImplementationType)) { + if (concreteGenericType.IsAssignableFrom(serviceImplementationType)) + { services.AddScoped(concreteGenericType, serviceImplementationType); typeImplementsAnExpectedInterface = true; } } } - if(typeImplementsAnExpectedInterface == false) + if (typeImplementsAnExpectedInterface == false) throw new JsonApiSetupException($"{serviceImplementationType} does not implement any of the expected JsonApiDotNetCore interfaces."); return services; @@ -237,12 +240,12 @@ private static HashSet GetResourceTypesFromServiceImplementa { var resourceDecriptors = new HashSet(); var interfaces = type.GetInterfaces(); - foreach(var i in interfaces) + foreach (var i in interfaces) { - if(i.IsGenericType) + if (i.IsGenericType) { var firstGenericArgument = i.GenericTypeArguments.FirstOrDefault(); - if(TypeLocator.TryGetResourceDescriptor(firstGenericArgument, out var resourceDescriptor) == true) + if (TypeLocator.TryGetResourceDescriptor(firstGenericArgument, out var resourceDescriptor) == true) { resourceDecriptors.Add(resourceDescriptor); } diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs index 3acba206fa..7c4ec4e68c 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs @@ -12,14 +12,14 @@ namespace JsonApiDotNetCore.Formatters { public class JsonApiReader : IJsonApiReader { - private readonly IJsonApiDeSerializer _deSerializer; + private readonly IJsonApiDeSerializer _deserializer; private readonly IJsonApiContext _jsonApiContext; private readonly ILogger _logger; public JsonApiReader(IJsonApiDeSerializer deSerializer, IJsonApiContext jsonApiContext, ILoggerFactory loggerFactory) { - _deSerializer = deSerializer; + _deserializer = deSerializer; _jsonApiContext = jsonApiContext; _logger = loggerFactory.CreateLogger(); } @@ -37,9 +37,15 @@ public Task ReadAsync(InputFormatterContext context) { var body = GetRequestBody(context.HttpContext.Request.Body); - var model = _jsonApiContext.IsRelationshipPath ? - _deSerializer.DeserializeRelationship(body) : - _deSerializer.Deserialize(body); + object model; + if (_jsonApiContext.RequestManager.IsRelationshipPath) + { + model = _deserializer.DeserializeRelationship(body); + } + else + { + model = _deserializer.Deserialize(body); + } if (model == null) _logger?.LogError("An error occurred while de-serializing the payload"); diff --git a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs index f2b1e1aebf..d75cf9818e 100644 --- a/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs +++ b/src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs @@ -1,9 +1,12 @@ using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; @@ -17,7 +20,7 @@ public class ServiceDiscoveryFacade { internal static HashSet ServiceInterfaces = new HashSet { typeof(IResourceService<>), - typeof(IResourceService<,>), + typeof(IResourceService<,>), typeof(IResourceCmdService<>), typeof(IResourceCmdService<,>), typeof(IResourceQueryService<>), @@ -46,13 +49,12 @@ public class ServiceDiscoveryFacade typeof(IEntityReadRepository<>), typeof(IEntityReadRepository<,>) }; - private readonly IServiceCollection _services; private readonly IResourceGraphBuilder _graphBuilder; private readonly List _identifiables = new List(); public ServiceDiscoveryFacade( - IServiceCollection services, + IServiceCollection services, IResourceGraphBuilder graphBuilder) { _services = services; @@ -80,13 +82,56 @@ public ServiceDiscoveryFacade AddAssembly(Assembly assembly) AddRepositories(assembly, resourceDescriptor); } + ScanControllers(assembly); + return this; } + private void ScanControllers(Assembly assembly) + { + var baseTypes = new List() { typeof(ControllerBase), typeof(JsonApiControllerMixin), typeof(JsonApiController<>), typeof(BaseJsonApiController<>) }; + List baseTypesSeen = new List() { }; + var types = assembly.GetTypes().ToList(); + //types.ForEach(t => baseTypesSeen.Add(t.BaseType.Name)); + + var controllerMapper = new Dictionary>() { }; + var undefinedMapper = new List() { }; + var sdf = assembly.GetTypes() + .Where(type => typeof(ControllerBase).IsAssignableFrom(type) & !type.IsGenericType).ToList(); + foreach (var controllerType in sdf) + { + // get generic parameter + var genericParameters = controllerType.BaseType.GetGenericArguments(); + if (genericParameters.Count() > 0) + { + + _graphBuilder.AddControllerPairing(controllerType, genericParameters[0]); + } + else + { + _graphBuilder.AddControllerPairing(controllerType); + } + } + } + + public IEnumerable FindDerivedTypes(Type baseType) + { + return baseType.Assembly.GetTypes().Where(t => + { + if (t.BaseType != null) + { + return baseType.IsSubclassOf(t); + } + return false; + + }); + } + + private void AddDbContextResolvers(Assembly assembly) { var dbContextTypes = TypeLocator.GetDerivedTypes(assembly, typeof(DbContext)); - foreach(var dbContextType in dbContextTypes) + foreach (var dbContextType in dbContextTypes) { var resolverType = typeof(DbContextResolver<>).MakeGenericType(dbContextType); _services.AddScoped(typeof(IDbContextResolver), resolverType); @@ -125,7 +170,7 @@ private void RegisterResourceDefinition(Assembly assembly, ResourceDescriptor id catch (InvalidOperationException e) { throw new JsonApiSetupException($"Cannot define multiple ResourceDefinition<> implementations for '{identifiable.ResourceType}'", e); - } + } } private void AddResourceToGraph(ResourceDescriptor identifiable) @@ -134,7 +179,7 @@ private void AddResourceToGraph(ResourceDescriptor identifiable) _graphBuilder.AddResource(identifiable.ResourceType, identifiable.IdType, resourceName); } - private string FormatResourceName(Type resourceType) + private string FormatResourceName(Type resourceType) => JsonApiOptions.ResourceNameFormatter.FormatResourceName(resourceType); /// @@ -145,58 +190,55 @@ public ServiceDiscoveryFacade AddServices(Assembly assembly) { var resourceDescriptors = TypeLocator.GetIdentifableTypes(assembly); foreach (var resourceDescriptor in resourceDescriptors) + { AddServices(assembly, resourceDescriptor); - + } return this; } private void AddServices(Assembly assembly, ResourceDescriptor resourceDescriptor) { - - foreach(var serviceInterface in ServiceInterfaces) + foreach (var serviceInterface in ServiceInterfaces) + { RegisterServiceImplementations(assembly, serviceInterface, resourceDescriptor); - - + } } /// /// Add implementations to container. /// /// The assembly to search for resources in. - public ServiceDiscoveryFacade AddRepositories(Assembly assembly) + public ServiceDiscoveryFacade AddRepositories(Assembly assembly) { var resourceDescriptors = TypeLocator.GetIdentifableTypes(assembly); foreach (var resourceDescriptor in resourceDescriptors) + { AddRepositories(assembly, resourceDescriptor); + } return this; } private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescriptor) { - foreach(var serviceInterface in RepositoryInterfaces) + foreach (var serviceInterface in RepositoryInterfaces) + { RegisterServiceImplementations(assembly, serviceInterface, resourceDescriptor); + } } public int i = 0; private void RegisterServiceImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor) { - - - if (resourceDescriptor.IdType == typeof(Guid) && interfaceType.GetTypeInfo().GenericTypeParameters.Length == 1) { - return ; + return; } - var genericArguments = interfaceType.GetTypeInfo().GenericTypeParameters.Length == 2 - ? new [] { resourceDescriptor.ResourceType, resourceDescriptor.IdType } - : new [] { resourceDescriptor.ResourceType }; - + var genericArguments = interfaceType.GetTypeInfo().GenericTypeParameters.Length == 2 ? new[] { resourceDescriptor.ResourceType, resourceDescriptor.IdType } : new[] { resourceDescriptor.ResourceType }; var service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); - if(service.implementation?.Name == "CustomArticleService" && genericArguments[0].Name != "Article") - { - - service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); - } + //if(service.implementation?.Name == "CustomArticleService" && genericArguments[0].Name != "Article") + //{ + // service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments); + //} if (service.implementation != null) { _services.AddScoped(service.registrationInterface, service.implementation); diff --git a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs index 54febfa45b..2bbb638d5a 100644 --- a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs @@ -83,5 +83,8 @@ public interface IResourceGraph /// Was built against an EntityFrameworkCore DbContext ? /// bool UsesDbContext { get; } + + + ContextEntity GetEntityBasedOnPath(string pathParsed); } } diff --git a/src/JsonApiDotNetCore/Internal/PageManager.cs b/src/JsonApiDotNetCore/Internal/PageManager.cs index 28c13fd885..6e9d3e6a49 100644 --- a/src/JsonApiDotNetCore/Internal/PageManager.cs +++ b/src/JsonApiDotNetCore/Internal/PageManager.cs @@ -1,5 +1,6 @@ using System; using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; @@ -8,10 +9,22 @@ namespace JsonApiDotNetCore.Internal public class PageManager : IPageManager { private ILinkBuilder _linkBuilder; + private IJsonApiOptions _options; - public PageManager(ILinkBuilder linkBuilder) + public PageManager(ILinkBuilder linkBuilder, IJsonApiOptions options, IRequestManager requestManager) { _linkBuilder = linkBuilder; + _options = options; + if (requestManager.QuerySet != null) + { + PageSize = requestManager.QuerySet?.PageQuery.PageSize != null ? requestManager.QuerySet.PageQuery.PageSize : _options.DefaultPageSize; + } + else + { + PageSize = _options.DefaultPageSize; + } + + DefaultPageSize = _options.DefaultPageSize; } public int? TotalRecords { get; set; } public int PageSize { get; set; } diff --git a/src/JsonApiDotNetCore/Internal/Query/AttrSortQuery.cs b/src/JsonApiDotNetCore/Internal/Query/AttrSortQuery.cs index 341b7e15c0..9f78bd2d5d 100644 --- a/src/JsonApiDotNetCore/Internal/Query/AttrSortQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/AttrSortQuery.cs @@ -4,10 +4,8 @@ namespace JsonApiDotNetCore.Internal.Query { public class AttrSortQuery : BaseAttrQuery { - public AttrSortQuery( - IJsonApiContext jsonApiContext, - SortQuery sortQuery) - :base(jsonApiContext, sortQuery) + public AttrSortQuery(IJsonApiContext jsonApiContext,SortQuery sortQuery) + :base(jsonApiContext.RequestManager,jsonApiContext.ResourceGraph, sortQuery) { if (Attribute == null) throw new JsonApiException(400, $"'{sortQuery.Attribute}' is not a valid attribute."); diff --git a/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs b/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs index d0cde0495f..fcde60cd27 100644 --- a/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs @@ -1,3 +1,5 @@ +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using System; @@ -12,24 +14,19 @@ namespace JsonApiDotNetCore.Internal.Query /// public abstract class BaseAttrQuery { - private readonly IJsonApiContext _jsonApiContext; + private readonly IRequestManager _requestManager; + private readonly IResourceGraph _resourceGraph; - public BaseAttrQuery(IJsonApiContext jsonApiContext, BaseQuery baseQuery) + public BaseAttrQuery(IRequestManager requestManager, IResourceGraph resourceGraph, BaseQuery baseQuery) { - _jsonApiContext = jsonApiContext ?? throw new ArgumentNullException(nameof(jsonApiContext)); - - if(_jsonApiContext.RequestEntity == null) - throw new ArgumentException($"{nameof(IJsonApiContext)}.{nameof(_jsonApiContext.RequestEntity)} cannot be null. " - + "This property contains the ResourceGraph node for the requested entity. " - + "If this is a unit test, you need to mock this member. " - + "See this issue to check the current status of improved test guidelines: " - + "https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/251", nameof(jsonApiContext)); + _requestManager = requestManager ?? throw new ArgumentNullException(nameof(requestManager)); + _resourceGraph = resourceGraph ?? throw new ArgumentNullException(nameof(resourceGraph)); - if(_jsonApiContext.ResourceGraph == null) - throw new ArgumentException($"{nameof(IJsonApiContext)}.{nameof(_jsonApiContext.ResourceGraph)} cannot be null. " + if(_resourceGraph == null) + throw new ArgumentException($"{nameof(IJsonApiContext)}.{nameof(_resourceGraph)} cannot be null. " + "If this is a unit test, you need to construct a graph containing the resources being tested. " + "See this issue to check the current status of improved test guidelines: " - + "https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/251", nameof(jsonApiContext)); + + "https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/251", nameof(requestManager)); if (baseQuery.IsAttributeOfRelationship) { @@ -56,16 +53,19 @@ public string GetPropertyPath() } private AttrAttribute GetAttribute(string attribute) - => _jsonApiContext.RequestEntity.Attributes.FirstOrDefault(attr => attr.Is(attribute)); + { + return _requestManager.GetContextEntity().Attributes.FirstOrDefault(attr => attr.Is(attribute)); + } private RelationshipAttribute GetRelationship(string propertyName) - => _jsonApiContext.RequestEntity.Relationships.FirstOrDefault(r => r.Is(propertyName)); + { + return _requestManager.GetContextEntity().Relationships.FirstOrDefault(r => r.Is(propertyName)); + } private AttrAttribute GetAttribute(RelationshipAttribute relationship, string attribute) { - var relatedContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.Type); - return relatedContextEntity.Attributes - .FirstOrDefault(a => a.Is(attribute)); + var relatedContextEntity = _resourceGraph.GetContextEntity(relationship.Type); + return relatedContextEntity.Attributes.FirstOrDefault(a => a.Is(attribute)); } } } diff --git a/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs b/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs index f7e308369e..fbf6301bc7 100644 --- a/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs @@ -8,7 +8,7 @@ public class BaseFilterQuery : BaseAttrQuery public BaseFilterQuery( IJsonApiContext jsonApiContext, FilterQuery filterQuery) - : base(jsonApiContext, filterQuery) + : base(jsonApiContext.RequestManager, jsonApiContext.ResourceGraph, filterQuery) { PropertyValue = filterQuery.Value; FilterOperation = GetFilterOperation(filterQuery.Operation); diff --git a/src/JsonApiDotNetCore/Internal/Query/RelatedAttrSortQuery.cs b/src/JsonApiDotNetCore/Internal/Query/RelatedAttrSortQuery.cs index 4215382c80..8c54581693 100644 --- a/src/JsonApiDotNetCore/Internal/Query/RelatedAttrSortQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/RelatedAttrSortQuery.cs @@ -7,7 +7,7 @@ public class RelatedAttrSortQuery : BaseAttrQuery public RelatedAttrSortQuery( IJsonApiContext jsonApiContext, SortQuery sortQuery) - :base(jsonApiContext, sortQuery) + :base(jsonApiContext.RequestManager, jsonApiContext.ResourceGraph, sortQuery) { if (Relationship == null) throw new JsonApiException(400, $"{sortQuery.Relationship} is not a valid relationship on {jsonApiContext.RequestEntity.EntityName}."); diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index a08c18eaca..f15138dc46 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -7,8 +7,13 @@ namespace JsonApiDotNetCore.Internal { - internal class ControllerMapping + public class ControllerModelMap { + public Type Controller; + public Type Model; + public string Path { get; set; } + + } /// @@ -19,12 +24,13 @@ public class ResourceGraph : IResourceGraph internal List Entities { get; } internal List ValidationResults { get; } - + internal List ControllerModelMap { get; set; } [Obsolete("please instantiate properly")] internal static IResourceGraph Instance { get; set; } public ResourceGraph() { } + [Obsolete("Use new one")] public ResourceGraph(List entities, bool usesDbContext) { Entities = entities; @@ -38,12 +44,15 @@ public ContextEntity GetEntityType(string entityName) return Entities.Where(e => e.EntityName == entityName).FirstOrDefault(); } + + // eventually, this is the planned public constructor // to avoid breaking changes, we will be leaving the original constructor in place // until the context graph validation process is completed // you can track progress on this issue here: https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/170 - internal ResourceGraph(List entities, bool usesDbContext, List validationResults) + internal ResourceGraph(List entities, bool usesDbContext, List validationResults, List controllerContexts) { + ControllerModelMap = controllerContexts; Entities = entities; UsesDbContext = usesDbContext; ValidationResults = validationResults; @@ -125,6 +134,32 @@ public string GetPublicAttributeName(string internalAttributeName) .PublicAttributeName; } + public ControllerModelMap GetControllerMap(string path) + { + var limitedContexts = ControllerModelMap.Where(cc => cc.Path != null); + foreach(var cc in limitedContexts) + { + if (path.Contains(cc.Path)) + { + return cc; + } + } + return null; + } + + public ContextEntity GetEntityBasedOnPath(string pathParsed) + { + // Check if there is a custom controller registered + var controllerHelper = GetControllerMap(pathParsed); + if (controllerHelper != null) + { + return GetContextEntity(controllerHelper.Model); + } + + var pathSplit = pathParsed.Split('/').ToList(); + + return GetContextEntity(pathSplit[0]); + } } } diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs index 41eef8f1b7..0bf235488c 100644 --- a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Services; @@ -23,6 +24,10 @@ public interface IRequestManager : IQueryRequest QuerySet QuerySet { get; set; } IQueryCollection FullQuerySet { get; set; } + /// + /// If the request is on the `{id}/relationships/{relationshipName}` route + /// + bool IsRelationshipPath { get; set; } /// /// Gets the relationships as set in the query parameters /// @@ -40,5 +45,7 @@ public interface IRequestManager : IQueryRequest void SetContextEntity(ContextEntity contextEntityCurrent); ContextEntity GetContextEntity(); + QueryParams DisabledQueryParams { get; set; } + } } diff --git a/src/JsonApiDotNetCore/Managers/RequestManager.cs b/src/JsonApiDotNetCore/Managers/RequestManager.cs index da0e3f105e..f51b22a480 100644 --- a/src/JsonApiDotNetCore/Managers/RequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/RequestManager.cs @@ -1,7 +1,9 @@ +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.Text; @@ -18,6 +20,9 @@ class RequestManager : IRequestManager public List IncludedRelationships { get; set; } public QuerySet QuerySet { get; set; } public PageManager PageManager { get; set; } + public IQueryCollection FullQuerySet { get; set; } + public QueryParams DisabledQueryParams { get; set; } + public bool IsRelationshipPath { get; set; } public List GetFields() diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 4173863f9a..6d535b8106 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; @@ -32,7 +33,6 @@ public async Task Invoke(HttpContext context, IQueryParser queryParser, IJsonApiOptions options) { - if (IsValid(context)) { // HACK: this currently results in allocation of @@ -45,7 +45,10 @@ public async Task Invoke(HttpContext context, requestManager.BasePath = GetBasePath(context, options, contextEntityCurrent?.EntityName); //Handle all querySet HandleUriParameters(context, queryParser, requestManager); - + requestManager.IsRelationshipPath = PathIsRelationship(context.Request.Path.Value); + // BACKWARD COMPATIBILITY for v4 will be removed in v5 + jsonApiContext.RequestManager = requestManager; + jsonApiContext.PageManager = new PageManager(new LinkBuilder(options, requestManager), options, requestManager); await _next(context); } } @@ -63,7 +66,39 @@ protected void HandleUriParameters(HttpContext context, IQueryParser queryParser requestManager.IncludedRelationships = requestManager.QuerySet.IncludedRelationships; } } + internal static bool PathIsRelationship(string requestPath) + { + // while(!Debugger.IsAttached) { Thread.Sleep(1000); } + const string relationships = "relationships"; + const char pathSegmentDelimiter = '/'; + + var span = requestPath.AsSpan(); + + // we need to iterate over the string, from the end, + // checking whether or not the 2nd to last path segment + // is "relationships" + // -2 is chosen in case the path ends with '/' + for (var i = requestPath.Length - 2; i >= 0; i--) + { + // if there are not enough characters left in the path to + // contain "relationships" + if (i < relationships.Length) + return false; + // we have found the first instance of '/' + if (span[i] == pathSegmentDelimiter) + { + // in the case of a "relationships" route, the next + // path segment will be "relationships" + return ( + span.Slice(i - relationships.Length, relationships.Length) + .SequenceEqual(relationships.AsSpan()) + ); + } + } + + return false; + } private string GetBasePath(HttpContext context, IJsonApiOptions options, string entityName) { var r = context.Request; @@ -114,10 +149,13 @@ internal static string GetNamespaceFromPath(string path, string entityName) /// private ContextEntity GetCurrentEntity(PathString path, IResourceGraph resourceGraph, IJsonApiOptions options) { - var pathSplit = path.ToString().Replace($"{options.Namespace}/", "").Split('/'); + var pathParsed = path.ToString().Replace($"{options.Namespace}/", ""); + if(pathParsed[0] == '/') + { + pathParsed = pathParsed.Substring(1); + } + return resourceGraph.GetEntityBasedOnPath(pathParsed); - var typeString = pathSplit[1]; - return resourceGraph.GetEntityType(typeString); } private static bool IsValid(HttpContext context) diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index 6aad0e92fb..624d53fdc6 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -75,12 +75,12 @@ public EntityResourceService( IEntityRepository entityRepository, IJsonApiOptions options, IResourceMapper mapper, - IRequestManager queryManager, + IRequestManager requestManager, IPageManager pageManager, ILoggerFactory loggerFactory) { _pageManager = pageManager; - _requestManager = queryManager; + _requestManager = requestManager; _jsonApiContext = jsonApiContext; _options = options; _repository = entityRepository; diff --git a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs index 7d1304e9b3..c39bce76f3 100644 --- a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs @@ -15,6 +15,7 @@ namespace JsonApiDotNetCore.Services public interface IJsonApiApplication { IJsonApiOptions Options { get; set; } + [Obsolete("Use standalone resourcegraph")] IResourceGraph ResourceGraph { get; set; } } @@ -139,7 +140,9 @@ public interface IJsonApiRequest : IJsonApiApplication, IUpdateRequest public interface IJsonApiContext : IJsonApiRequest { + [Obsolete("Use standalone IRequestManager")] IRequestManager RequestManager { get; set; } + [Obsolete("Use standalone IPageManager")] IPageManager PageManager { get; set; } IJsonApiContext ApplyContext(object controller); IMetaBuilder MetaBuilder { get; set; } diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs index 637080e98f..165a2ee154 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiContext.cs @@ -65,7 +65,6 @@ public JsonApiContext( public HasOneRelationshipPointers HasOneRelationshipPointers { get; private set; } = new HasOneRelationshipPointers(); [Obsolete("Please use the standalone Requestmanager")] public IRequestManager RequestManager { get; set; } - IPageManager IJsonApiContext.PageManager { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } [Obsolete("This is no longer necessary")] @@ -87,7 +86,6 @@ public IJsonApiContext ApplyContext(object controller) IncludedRelationships = QuerySet.IncludedRelationships; } - //PageManager = GetPageManager(); IsRelationshipPath = PathIsRelationship(context.Request.Path.Value); return this; diff --git a/src/JsonApiDotNetCore/Services/QueryParser.cs b/src/JsonApiDotNetCore/Services/QueryParser.cs index 21a66e651b..e067d6d5d9 100644 --- a/src/JsonApiDotNetCore/Services/QueryParser.cs +++ b/src/JsonApiDotNetCore/Services/QueryParser.cs @@ -5,6 +5,7 @@ using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using Microsoft.AspNetCore.Http; @@ -17,22 +18,23 @@ public interface IQueryParser public class QueryParser : IQueryParser { - private readonly IControllerContext _controllerContext; + private readonly IRequestManager _requestManager; private readonly IJsonApiOptions _options; public QueryParser( - IControllerContext controllerContext, + IRequestManager requestManager, IJsonApiOptions options) { - _controllerContext = controllerContext; + _requestManager = requestManager; _options = options; } public virtual QuerySet Parse(IQueryCollection query) { var querySet = new QuerySet(); - var disabledQueries = _controllerContext.GetControllerAttribute()?.QueryParams ?? QueryParams.None; + // var disabledQueries = _controllerContext.GetControllerAttribute()?.QueryParams ?? QueryParams.None; + var disabledQueries = QueryParams.None; foreach (var pair in query) { if (pair.Key.StartsWith(QueryConstants.FILTER)) @@ -133,9 +135,11 @@ protected virtual PageQuery ParsePageQuery(PageQuery pageQuery, string key, stri const string NUMBER = "number"; if (propertyName == SIZE) + { pageQuery.PageSize = int.TryParse(value, out var pageSize) ? pageSize : throw new JsonApiException(400, $"Invalid page size '{value}'"); + } else if (propertyName == NUMBER) pageQuery.PageOffset = int.TryParse(value, out var pageOffset) ? @@ -184,8 +188,8 @@ protected virtual List ParseFieldsQuery(string key, string value) var typeName = key.Split(QueryConstants.OPEN_BRACKET, QueryConstants.CLOSE_BRACKET)[1]; var includedFields = new List { nameof(Identifiable.Id) }; - var relationship = _controllerContext.RequestEntity.Relationships.SingleOrDefault(a => a.Is(typeName)); - if (relationship == default && string.Equals(typeName, _controllerContext.RequestEntity.EntityName, StringComparison.OrdinalIgnoreCase) == false) + var relationship = _requestManager.GetContextEntity().Relationships.SingleOrDefault(a => a.Is(typeName)); + if (relationship == default && string.Equals(typeName, _requestManager.GetContextEntity().EntityName, StringComparison.OrdinalIgnoreCase) == false) return includedFields; var fields = value.Split(QueryConstants.COMMA); @@ -203,9 +207,9 @@ protected virtual List ParseFieldsQuery(string key, string value) } else { - var attr = _controllerContext.RequestEntity.Attributes.SingleOrDefault(a => a.Is(field)); + var attr = _requestManager.GetContextEntity().Attributes.SingleOrDefault(a => a.Is(field)); if (attr == null) - throw new JsonApiException(400, $"'{_controllerContext.RequestEntity.EntityName}' does not contain '{field}'."); + throw new JsonApiException(400, $"'{_requestManager.GetContextEntity().EntityName}' does not contain '{field}'."); // e.g. "Name" includedFields.Add(attr.InternalAttributeName); @@ -219,14 +223,13 @@ protected virtual AttrAttribute GetAttribute(string propertyName) { try { - return _controllerContext - .RequestEntity + return _requestManager.GetContextEntity() .Attributes .Single(attr => attr.Is(propertyName)); } catch (InvalidOperationException e) { - throw new JsonApiException(400, $"Attribute '{propertyName}' does not exist on resource '{_controllerContext.RequestEntity.EntityName}'", e); + throw new JsonApiException(400, $"Attribute '{propertyName}' does not exist on resource '{_requestManager.GetContextEntity().EntityName}'", e); } } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs index 478f40f14f..3b9fd699c7 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomControllerTests.cs @@ -110,8 +110,7 @@ public async Task CustomRouteControllers_Creates_Proper_Relationship_Links() context.TodoItems.Add(todoItem); await context.SaveChangesAsync(); - var builder = new WebHostBuilder() - .UseStartup(); + var builder = new WebHostBuilder().UseStartup(); var httpMethod = new HttpMethod("GET"); var route = $"/custom/route/todo-items/{todoItem.Id}"; @@ -119,8 +118,10 @@ public async Task CustomRouteControllers_Creates_Proper_Relationship_Links() var client = server.CreateClient(); var request = new HttpRequestMessage(httpMethod, route); - // act & assert + // Act var response = await client.SendAsync(request); + + // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs index 69bcccb2a2..555617ab32 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs @@ -44,15 +44,22 @@ public TodoItemControllerTests(TestFixture fixture) } [Fact] - public async Task Can_Get_TodoItems() + public async Task Can_Get_TodoItems_Paginate_Check() { // Arrange - const int expectedEntitiesPerPage = 5; - var person = new Person(); - var todoItem = _todoItemFaker.Generate(); - todoItem.Owner = person; - _context.TodoItems.Add(todoItem); + _context.TodoItems.RemoveRange(_context.TodoItems.ToList()); _context.SaveChanges(); + int expectedEntitiesPerPage = _jsonApiContext.Options.DefaultPageSize; + var person = new Person(); + var todoItems = _todoItemFaker.Generate(expectedEntitiesPerPage +1); + + foreach (var todoItem in todoItems) + { + todoItem.Owner = person; + _context.TodoItems.Add(todoItem); + _context.SaveChanges(); + + } var httpMethod = new HttpMethod("GET"); var route = "/api/v1/todo-items"; @@ -66,7 +73,7 @@ public async Task Can_Get_TodoItems() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.NotEmpty(deserializedBody); - Assert.True(deserializedBody.Count <= expectedEntitiesPerPage); + Assert.True(deserializedBody.Count <= expectedEntitiesPerPage, $"There are more items on the page than the default page size. {deserializedBody.Count} > {expectedEntitiesPerPage}"); } [Fact] @@ -96,7 +103,7 @@ public async Task Can_Filter_By_Resource_Id() public async Task Can_Filter_By_Relationship_Id() { // Arrange - var person = new Person(); + var person = new Person(); var todoItem = _todoItemFaker.Generate(); todoItem.Owner = person; _context.TodoItems.Add(todoItem); diff --git a/test/UnitTests/Services/QueryParser_Tests.cs b/test/UnitTests/Services/QueryParser_Tests.cs index c0e5752dad..276cc16d93 100644 --- a/test/UnitTests/Services/QueryParser_Tests.cs +++ b/test/UnitTests/Services/QueryParser_Tests.cs @@ -3,6 +3,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; @@ -14,12 +15,12 @@ namespace UnitTests.Services { public class QueryParser_Tests { - private readonly Mock _controllerContextMock; + private readonly Mock _requestMock; private readonly Mock _queryCollectionMock; public QueryParser_Tests() { - _controllerContextMock = new Mock(); + _requestMock = new Mock(); _queryCollectionMock = new Mock(); } @@ -35,11 +36,11 @@ public void Can_Build_Filters() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.None)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.None); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -61,11 +62,11 @@ public void Filters_Properly_Parses_DateTime_With_Operation() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.None)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.None); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -88,11 +89,11 @@ public void Filters_Properly_Parses_DateTime_Without_Operation() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.None)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.None); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -114,11 +115,11 @@ public void Can_Disable_Filters() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.Filter)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.Filter); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -139,11 +140,11 @@ public void Can_Disable_Sort() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.Sort)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.Sort); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -164,11 +165,11 @@ public void Can_Disable_Include() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.Include)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.Include); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -189,11 +190,11 @@ public void Can_Disable_Page() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.Page)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.Page); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -214,11 +215,11 @@ public void Can_Disable_Fields() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.GetControllerAttribute()) - .Returns(new DisableQueryAttribute(QueryParams.Fields)); + _requestMock + .Setup(m => m.DisabledQueryParams) + .Returns(QueryParams.Fields); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -241,8 +242,8 @@ public void Can_Parse_Fields_Query() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.RequestEntity) + _requestMock + .Setup(m => m.GetContextEntity()) .Returns(new ContextEntity { EntityName = type, @@ -256,7 +257,7 @@ public void Can_Parse_Fields_Query() Relationships = new List() }); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -281,8 +282,8 @@ public void Throws_JsonApiException_If_Field_DoesNotExist() .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - _controllerContextMock - .Setup(m => m.RequestEntity) + _requestMock + .Setup(m => m.GetContextEntity()) .Returns(new ContextEntity { EntityName = type, @@ -290,7 +291,7 @@ public void Throws_JsonApiException_If_Field_DoesNotExist() Relationships = new List() }); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act , assert var ex = Assert.Throws(() => queryParser.Parse(_queryCollectionMock.Object)); @@ -312,7 +313,7 @@ public void Can_Parse_Page_Size_Query(string value, int expectedValue, bool shou .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act if (shouldThrow) @@ -342,7 +343,7 @@ public void Can_Parse_Page_Number_Query(string value, int expectedValue, bool sh .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // act if (shouldThrow) From afb02f2eb34d951430bf91b7fe76a80df7ef0c70 Mon Sep 17 00:00:00 2001 From: Harro van der Kroft Date: Tue, 2 Jul 2019 16:06:49 +0200 Subject: [PATCH 10/26] feat: json api context decoupling mroe and more --- .../Data/DefaultEntityRepository.cs | 55 +++---- .../Hooks/ResourceHookExecutor.cs | 25 +--- .../Hooks/Traversal/ChildNode.cs | 14 +- .../Hooks/Traversal/IEntityNode.cs | 7 +- .../Hooks/Traversal/ITraversalHelper.cs | 26 +++- .../Hooks/Traversal/RootNode.cs | 4 +- .../Hooks/Traversal/TraversalHelper.cs | 33 ++--- .../Managers/Contracts/IRequestManager.cs | 3 + .../Managers/RequestManager.cs | 32 ++++- .../Services/IJsonApiContext.cs | 17 +-- .../Delete/AfterDeleteTests.cs | 8 +- .../ResourceHooks/ResourceHooksTestsSetup.cs | 135 +++++++++--------- 12 files changed, 201 insertions(+), 158 deletions(-) diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 3e5a7b0a89..00169edd0a 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -14,27 +14,7 @@ using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Data { - /// - public class DefaultEntityRepository - : DefaultEntityRepository, - IEntityRepository - where TEntity : class, IIdentifiable - { - public DefaultEntityRepository( - IJsonApiContext jsonApiContext, - IDbContextResolver contextResolver, - ResourceDefinition resourceDefinition = null) - : base(jsonApiContext, contextResolver, resourceDefinition) - { } - public DefaultEntityRepository( - ILoggerFactory loggerFactory, - IJsonApiContext jsonApiContext, - IDbContextResolver contextResolver, - ResourceDefinition resourceDefinition = null) - : base(loggerFactory, jsonApiContext, contextResolver, resourceDefinition) - { } - } /// /// Provides a default repository implementation and is responsible for @@ -52,6 +32,11 @@ public class DefaultEntityRepository private readonly IJsonApiContext _jsonApiContext; private readonly IGenericProcessorFactory _genericProcessorFactory; private readonly ResourceDefinition _resourceDefinition; + + + + + [Obsolete("Dont use jsonapicontext instantiation anymore")] public DefaultEntityRepository( IJsonApiContext jsonApiContext, IDbContextResolver contextResolver, @@ -65,6 +50,7 @@ public DefaultEntityRepository( _resourceDefinition = resourceDefinition; } + [Obsolete("Dont use jsonapicontext instantiation anymore")] public DefaultEntityRepository( ILoggerFactory loggerFactory, IJsonApiContext jsonApiContext, @@ -146,7 +132,7 @@ public virtual async Task GetAndIncludeAsync(TId id, string relationshi /// public virtual async Task CreateAsync(TEntity entity) { - foreach (var relationshipAttr in _jsonApiContext.RelationshipsToUpdate?.Keys) + foreach (var relationshipAttr in _requestManager.GetUpdatedRelationships()?.Keys) { var trackedRelationshipValue = GetTrackedRelationshipValue(relationshipAttr, entity, out bool wasAlreadyTracked); LoadInverseRelationships(trackedRelationshipValue, relationshipAttr); @@ -228,7 +214,7 @@ private bool IsHasOneRelationship(string internalRelationshipName, Type type) public void DetachRelationshipPointers(TEntity entity) { - foreach (var relationshipAttr in _jsonApiContext.RelationshipsToUpdate.Keys) + foreach (var relationshipAttr in _requestManager.GetUpdatedRelationships().Keys) { if (relationshipAttr is HasOneAttribute hasOneAttr) { @@ -271,10 +257,10 @@ public virtual async Task UpdateAsync(TEntity updatedEntity) if (databaseEntity == null) return null; - foreach (var attr in _jsonApiContext.AttributesToUpdate.Keys) + foreach (var attr in _requestManager.GetUpdatedAttributes().Keys) attr.SetValue(databaseEntity, attr.GetValue(updatedEntity)); - foreach (var relationshipAttr in _jsonApiContext.RelationshipsToUpdate?.Keys) + foreach (var relationshipAttr in _requestManager.GetUpdatedRelationships()?.Keys) { /// loads databasePerson.todoItems LoadCurrentRelationships(databaseEntity, relationshipAttr); @@ -584,4 +570,25 @@ private IIdentifiable AttachOrGetTracked(IIdentifiable relationshipValue) return null; } } + /// + public class DefaultEntityRepository + : DefaultEntityRepository, + IEntityRepository + where TEntity : class, IIdentifiable + { + public DefaultEntityRepository( + IJsonApiContext jsonApiContext, + IDbContextResolver contextResolver, + ResourceDefinition resourceDefinition = null) + : base(jsonApiContext, contextResolver, resourceDefinition) + { } + + public DefaultEntityRepository( + ILoggerFactory loggerFactory, + IJsonApiContext jsonApiContext, + IDbContextResolver contextResolver, + ResourceDefinition resourceDefinition = null) + : base(loggerFactory, jsonApiContext, contextResolver, resourceDefinition) + { } + } } diff --git a/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs index 9d7fa4e4be..808ed3bc90 100644 --- a/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs @@ -15,7 +15,7 @@ namespace JsonApiDotNetCore.Hooks { /// - internal class ResourceHookExecutor : IResourceHookExecutor + internal class ResourceHookExecutor : IResourceHookExecutor { public static readonly IdentifiableComparer Comparer = new IdentifiableComparer(); internal readonly ITraversalHelper _traversalHelper; @@ -32,17 +32,6 @@ public ResourceHookExecutor(IHookExecutorHelper helper, ITraversalHelper travers _traversalHelper = traversalHelper; } - - [Obsolete("Dont use, use consturctor without jsonApiContext")] - public ResourceHookExecutor(IHookExecutorHelper helper, ITraversalHelper traversalHelper, IJsonApiContext context, IRequestManager requestManager) : this(helper, traversalHelper, context.ResourceGraph, context.RequestManager) - { - - } - - - - - /// public virtual void BeforeRead(ResourcePipeline pipeline, string stringId = null) where TEntity : class, IIdentifiable { @@ -201,12 +190,12 @@ bool GetHook(ResourceHook target, IEnumerable entities, } /// - /// Traverses the nodes in a . + /// Traverses the nodes in a . /// - void Traverse(EntityChildLayer currentLayer, ResourceHook target, Action action) + void Traverse(NodeLayer currentLayer, ResourceHook target, Action action) { if (!currentLayer.AnyEntities()) return; - foreach (IEntityNode node in currentLayer) + foreach (INode node in currentLayer) { var entityType = node.EntityType; var hookContainer = _executorHelper.GetResourceHookContainer(entityType, target); @@ -259,9 +248,9 @@ void RecursiveBeforeRead(ContextEntity contextEntity, List relationshipC /// First the BeforeUpdateRelationship should be for owner1, then the /// BeforeImplicitUpdateRelationship hook should be fired for /// owner2, and lastely the BeforeImplicitUpdateRelationship for article2. - void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, EntityChildLayer layer) + void FireNestedBeforeUpdateHooks(ResourcePipeline pipeline, NodeLayer layer) { - foreach (IEntityNode node in layer) + foreach (INode node in layer) { var nestedHookcontainer = _executorHelper.GetResourceHookContainer(node.EntityType, ResourceHook.BeforeUpdateRelationship); IEnumerable uniqueEntities = node.UniqueEntities; @@ -461,7 +450,7 @@ IEnumerable LoadDbValues(Type entityType, IEnumerable uniqueEntities, ResourceHo /// /// Fires the AfterUpdateRelationship hook /// - void FireAfterUpdateRelationship(IResourceHookContainer container, IEntityNode node, ResourcePipeline pipeline) + void FireAfterUpdateRelationship(IResourceHookContainer container, INode node, ResourcePipeline pipeline) { Dictionary currenEntitiesGrouped = node.RelationshipsFromPreviousLayer.GetDependentEntities(); diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/ChildNode.cs b/src/JsonApiDotNetCore/Hooks/Traversal/ChildNode.cs index 443ee7daf1..2fa1ae2aa2 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/ChildNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/ChildNode.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Linq; using JsonApiDotNetCore.Extensions; @@ -7,8 +7,11 @@ namespace JsonApiDotNetCore.Hooks { - /// - internal class ChildNode : IEntityNode where TEntity : class, IIdentifiable + /// + /// Child node in the tree + /// + /// + internal class ChildNode : INode where TEntity : class, IIdentifiable { /// public DependentType EntityType { get; private set; } @@ -51,7 +54,10 @@ public void UpdateUnique(IEnumerable updated) } } - /// + /// + /// Reassignment is done according to provided relationships + /// + /// public void Reassign(IEnumerable updated = null) { var unique = (HashSet)UniqueEntities; diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/IEntityNode.cs b/src/JsonApiDotNetCore/Hooks/Traversal/IEntityNode.cs index 159b373ef5..00e8a0b8f3 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/IEntityNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/IEntityNode.cs @@ -1,9 +1,12 @@ -using System.Collections; +using System.Collections; using DependentType = System.Type; namespace JsonApiDotNetCore.Hooks { - internal interface IEntityNode + /// + /// This is the interface that nodes need to inherit from + /// + internal interface INode { /// /// Each node representes the entities of a given type throughout a particular layer. diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/ITraversalHelper.cs b/src/JsonApiDotNetCore/Hooks/Traversal/ITraversalHelper.cs index 7d20fdf26f..f0449b9756 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/ITraversalHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/ITraversalHelper.cs @@ -1,12 +1,30 @@ -using System.Collections.Generic; +using System.Collections.Generic; using JsonApiDotNetCore.Models; namespace JsonApiDotNetCore.Hooks { internal interface ITraversalHelper { - EntityChildLayer CreateNextLayer(IEntityNode rootNode); - EntityChildLayer CreateNextLayer(IEnumerable nodes); + /// + /// Crates the next layer + /// + /// + /// + NodeLayer CreateNextLayer(INode node); + /// + /// Creates the next layer based on the nodes provided + /// + /// + /// + NodeLayer CreateNextLayer(IEnumerable nodes); + /// + /// Creates a root node for breadth-first-traversal (BFS). Note that typically, in + /// JADNC, the root layer will be homogeneous. Also, because it is the first layer, + /// there can be no relationships to previous layers, only to next layers. + /// + /// The root node. + /// Root entities. + /// The 1st type parameter. RootNode CreateRootNode(IEnumerable rootEntities) where TEntity : class, IIdentifiable; } -} \ No newline at end of file +} diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/RootNode.cs b/src/JsonApiDotNetCore/Hooks/Traversal/RootNode.cs index 7783d041e1..1aa3c0eb8b 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/RootNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/RootNode.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -11,7 +11,7 @@ namespace JsonApiDotNetCore.Hooks /// The root node class of the breadth-first-traversal of entity data structures /// as performed by the /// - internal class RootNode : IEntityNode where TEntity : class, IIdentifiable + internal class RootNode : INode where TEntity : class, IIdentifiable { private readonly RelationshipProxy[] _allRelationshipsToNextLayer; private HashSet _uniqueEntities; diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs b/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs index a7d6fdb8fb..00c1c4dca8 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs @@ -6,6 +6,7 @@ using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using DependentType = System.Type; @@ -25,7 +26,8 @@ namespace JsonApiDotNetCore.Hooks internal class TraversalHelper : ITraversalHelper { private readonly IResourceGraph _graph; - private readonly IJsonApiContext _context; + private readonly IRequestManager _requestManager; + /// /// Keeps track of which entities has already been traversed through, to prevent /// infinite loops in eg cyclic data structures. @@ -36,13 +38,11 @@ internal class TraversalHelper : ITraversalHelper /// See the latter for more details. /// private readonly Dictionary RelationshipProxies = new Dictionary(); - - public TraversalHelper( IResourceGraph graph, - IJsonApiContext context) + IRequestManager requestManager) { - _context = context; + _requestManager = requestManager; _graph = graph; } @@ -69,9 +69,9 @@ public RootNode CreateRootNode(IEnumerable rootEntiti /// /// The next layer. /// Root node. - public EntityChildLayer CreateNextLayer(IEntityNode rootNode) + public NodeLayer CreateNextLayer(INode rootNode) { - return CreateNextLayer(new IEntityNode[] { rootNode }); + return CreateNextLayer(new INode[] { rootNode }); } /// @@ -79,7 +79,7 @@ public EntityChildLayer CreateNextLayer(IEntityNode rootNode) /// /// The next layer. /// Nodes. - public EntityChildLayer CreateNextLayer(IEnumerable nodes) + public NodeLayer CreateNextLayer(IEnumerable nodes) { /// first extract entities by parsing populated relationships in the entities /// of previous layer @@ -108,7 +108,7 @@ public EntityChildLayer CreateNextLayer(IEnumerable nodes) }).ToList(); /// wrap the child nodes in a EntityChildLayer - return new EntityChildLayer(nextNodes); + return new NodeLayer(nextNodes); } /// @@ -124,7 +124,7 @@ Dictionary - (Dictionary>, Dictionary>) ExtractEntities(IEnumerable principalNodes) + (Dictionary>, Dictionary>) ExtractEntities(IEnumerable principalNodes) { var principalsEntitiesGrouped = new Dictionary>(); // RelationshipAttr_prevlayer->currentlayer => prevLayerEntities var dependentsEntitiesGrouped = new Dictionary>(); // RelationshipAttr_prevlayer->currentlayer => currentLayerEntities @@ -211,6 +211,7 @@ void RegisterRelationshipProxies(DependentType type) DependentType dependentType = GetDependentTypeFromRelationship(attr); bool isContextRelation = false; if (_context.RelationshipsToUpdate != null) isContextRelation = _context.RelationshipsToUpdate.ContainsKey(attr); + if (_context.RelationshipsToUpdate != null) isContextRelation = _context.RelationshipsToUpdate.ContainsKey(attr); var proxy = new RelationshipProxy(attr, dependentType, isContextRelation); RelationshipProxies[attr] = proxy; } @@ -287,10 +288,10 @@ void AddToRelationshipGroup(Dictionary> t /// /// Reflective helper method to create an instance of ; /// - IEntityNode CreateNodeInstance(DependentType nodeType, RelationshipProxy[] relationshipsToNext, IEnumerable relationshipsFromPrev) + INode CreateNodeInstance(DependentType nodeType, RelationshipProxy[] relationshipsToNext, IEnumerable relationshipsFromPrev) { IRelationshipsFromPreviousLayer prev = CreateRelationshipsFromInstance(nodeType, relationshipsFromPrev); - return (IEntityNode)TypeHelper.CreateInstanceOfOpenType(typeof(ChildNode<>), nodeType, new object[] { relationshipsToNext, prev }); + return (INode)TypeHelper.CreateInstanceOfOpenType(typeof(ChildNode<>), nodeType, new object[] { relationshipsToNext, prev }); } /// @@ -318,21 +319,21 @@ IRelationshipGroup CreateRelationshipGroupInstance(Type thisLayerType, Relations /// A helper class that represents all entities in the current layer that /// are being traversed for which hooks will be executed (see IResourceHookExecutor) /// - internal class EntityChildLayer : IEnumerable + internal class NodeLayer : IEnumerable { - readonly List _collection; + readonly List _collection; public bool AnyEntities() { return _collection.Any(n => n.UniqueEntities.Cast().Any()); } - public EntityChildLayer(List nodes) + public NodeLayer(List nodes) { _collection = nodes; } - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { return _collection.GetEnumerator(); } diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs index ac9f05c0c5..6a24b652a6 100644 --- a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs @@ -4,6 +4,7 @@ using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; +using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; @@ -11,6 +12,8 @@ namespace JsonApiDotNetCore.Managers.Contracts { public interface IRequestManager : IQueryRequest { + Dictionary GetUpdatedAttributes(); + Dictionary GetUpdatedRelationships(); /// /// The request namespace. This may be an absolute or relative path /// depending upon the configuration. diff --git a/src/JsonApiDotNetCore/Managers/RequestManager.cs b/src/JsonApiDotNetCore/Managers/RequestManager.cs index f51b22a480..592a55e724 100644 --- a/src/JsonApiDotNetCore/Managers/RequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/RequestManager.cs @@ -2,6 +2,7 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; using System; @@ -10,9 +11,24 @@ namespace JsonApiDotNetCore.Managers { - class RequestManager : IRequestManager + public class UpdatesContainer { + /// + /// The attributes that were included in a PATCH request. + /// Only the attributes in this dictionary should be updated. + /// + public Dictionary Attributes { get; set; } + + /// + /// Any relationships that were included in a PATCH request. + /// Only the relationships in this dictionary should be updated. + /// + public Dictionary Relationships { get; } + } + + class RequestManager : IRequestManager + { private ContextEntity _contextEntity; private IQueryParser _queryParser; @@ -23,8 +39,22 @@ class RequestManager : IRequestManager public IQueryCollection FullQuerySet { get; set; } public QueryParams DisabledQueryParams { get; set; } public bool IsRelationshipPath { get; set; } + public Dictionary AttributesToUpdate { get; set; } + /// + /// Contains all the information you want about any update occuring + /// + private UpdatesContainer _updatesContainer { get; set; } + public Dictionary RelationshipsToUpdate { get; set; } + public Dictionary GetUpdatedAttributes() + { + return _updatesContainer.Attributes; + } + public Dictionary GetUpdatedRelationships() + { + return _updatesContainer.Relationships; + } public List GetFields() { return QuerySet?.Fields; diff --git a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs index 5120163095..d1a7878c3f 100644 --- a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs @@ -26,25 +26,10 @@ public interface IQueryRequest PageManager PageManager { get; set; } } - public interface IUpdateRequest - { - /// - /// The attributes that were included in a PATCH request. - /// Only the attributes in this dictionary should be updated. - /// - Dictionary AttributesToUpdate { get; set; } - /// - /// Any relationships that were included in a PATCH request. - /// Only the relationships in this dictionary should be updated. - /// - Dictionary RelationshipsToUpdate { get; } - } - public interface IJsonApiRequest : IJsonApiApplication, IUpdateRequest, IQueryRequest + public interface IJsonApiRequest : IJsonApiApplication, IQueryRequest { - - /// /// Stores information to set relationships for the request resource. /// These relationships must already exist and should not be re-created. diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/AfterDeleteTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/AfterDeleteTests.cs index 051fea3bba..edc0f6e4ae 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/AfterDeleteTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/AfterDeleteTests.cs @@ -13,15 +13,15 @@ public class AfterDeleteTests : HooksTestsSetup [Fact] public void AfterDelete() { - // arrange + // Arrange var discovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var resourceDefinitionMock) = CreateTestObjects(discovery); + var (contextMock, hookExecutor, resourceDefinitionMock) = CreateTestObjects(discovery); var todoList = CreateTodoWithOwner(); - // act + // Act hookExecutor.AfterDelete(todoList, ResourcePipeline.Delete, It.IsAny()); - // assert + // Assert resourceDefinitionMock.Verify(rd => rd.AfterDelete(It.IsAny>(), ResourcePipeline.Delete, It.IsAny()), Times.Once()); VerifyNoOtherCalls(resourceDefinitionMock); } diff --git a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs index d898d1e93b..846ecd659e 100644 --- a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs +++ b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs @@ -15,6 +15,8 @@ using System.Collections.Generic; using System.Linq; using Person = JsonApiDotNetCoreExample.Models.Person; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; namespace UnitTests.ResourceHooks { @@ -134,21 +136,30 @@ protected List CreateTodoWithOwner() public class HooksTestsSetup : HooksDummyData { - protected (Mock, IResourceHookExecutor, Mock>) - CreateTestObjects(IHooksDiscovery discovery = null) - where TMain : class, IIdentifiable + (Mock, Mock, Mock, Mock) CreateMocks() + { + var pfMock = new Mock(); + var rgMock = new Mock(); + var rqMock = new Mock(); + var optionsMock = new Mock(new JsonApiOptions { LoadDatabaseValues = false }); + return (rgMock, rqMock, pfMock, optionsMock); + } + + internal (ResourceHookExecutor, Mock>) CreateTestObjects(IHooksDiscovery discovery = null) + where TMain : class, IIdentifiable { // creates the resource definition mock and corresponding ImplementedHooks discovery instance var mainResource = CreateResourceDefinition(discovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - (var context, var processorFactory) = CreateContextAndProcessorMocks(); - var traversalHelper = new TraversalHelper(ResourceGraph.Instance, context.Object); + var (rgMock, rqMock, gpfMock, optionsMock) = CreateMocks(); - var meta = new HookExecutorHelper(context.Object.GenericProcessorFactory, ResourceGraph.Instance, context.Object.Options); - var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, ResourceGraph.Instance, context.Object.RequestManager); + var traversalHelper = new TraversalHelper(rgMock.Object, rqMock.Object); + + var meta = new HookExecutorHelper(gpfMock.Object, rgMock.Object, optionsMock.Object); + var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, rgMock.Object, rqMock.Object); - return (context, hookExecutor, mainResource); + return (hookExecutor, mainResource); } protected (Mock context, IResourceHookExecutor, Mock>, Mock>) @@ -157,20 +168,20 @@ public class HooksTestsSetup : HooksDummyData IHooksDiscovery nestedDiscovery = null, DbContextOptions repoDbContextOptions = null ) - where TMain : class, IIdentifiable - where TNested : class, IIdentifiable + where TMain : class, IIdentifiable + where TNested : class, IIdentifiable { // creates the resource definition mock and corresponding for a given set of discoverable hooks var mainResource = CreateResourceDefinition(mainDiscovery); var nestedResource = CreateResourceDefinition(nestedDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - (var context, var processorFactory) = CreateContextAndProcessorMocks(); + var (rgMock, rqMock, gpfMock, optionsMock) = CreateMocks(); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; - var traversalHelper = new TraversalHelper(ResourceGraph.Instance, context.Object); + var traversalHelper = new TraversalHelper(rgMock.Object, rqMock.Object); - SetupProcessorFactoryForResourceDefinition(processorFactory, mainResource.Object, mainDiscovery, context.Object, dbContext); + SetupProcessorFactoryForResourceDefinition(gpfMock.Object, mainResource.Object, mainDiscovery, context.Object, dbContext); var meta = new HookExecutorHelper(context.Object.GenericProcessorFactory, ResourceGraph.Instance, context.Object.Options); var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, ResourceGraph.Instance, context.Object.RequestManager); @@ -181,10 +192,10 @@ public class HooksTestsSetup : HooksDummyData protected (Mock context, IResourceHookExecutor, Mock>, Mock>, Mock>) CreateTestObjects( - IHooksDiscovery mainDiscovery = null, - IHooksDiscovery firstNestedDiscovery = null, - IHooksDiscovery secondNestedDiscovery = null, - DbContextOptions repoDbContextOptions = null + IHooksDiscovery mainDiscovery = null, + IHooksDiscovery firstNestedDiscovery = null, + IHooksDiscovery secondNestedDiscovery = null, + DbContextOptions repoDbContextOptions = null ) where TMain : class, IIdentifiable where TFirstNested : class, IIdentifiable @@ -196,14 +207,14 @@ public class HooksTestsSetup : HooksDummyData var secondNestedResource = CreateResourceDefinition(secondNestedDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - (var context, var processorFactory) = CreateContextAndProcessorMocks(); + (var context, var processorFactory) = CreateMocks(); var traversalHelper = new TraversalHelper(ResourceGraph.Instance, context.Object); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; SetupProcessorFactoryForResourceDefinition(processorFactory, mainResource.Object, mainDiscovery, context.Object, dbContext); var hookExecutorHelper = new HookExecutorHelper(context.Object.GenericProcessorFactory, ResourceGraph.Instance, context.Object.Options); - var hookExecutor = new ResourceHookExecutor(hookExecutorHelper, traversalHelper, context.Object, context.Object.RequestManager); + var hookExecutor = new ResourceHookExecutor(hookExecutorHelper, traversalHelper, ResourceGraph.Instance, context.Object.RequestManager); SetupProcessorFactoryForResourceDefinition(processorFactory, firstNestedResource.Object, firstNestedDiscovery, context.Object, dbContext); SetupProcessorFactoryForResourceDefinition(processorFactory, secondNestedResource.Object, secondNestedDiscovery, context.Object, dbContext); @@ -240,8 +251,8 @@ protected void VerifyNoOtherCalls(params dynamic[] resourceMocks) protected DbContextOptions InitInMemoryDb(Action seeder) { var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase(databaseName: "repository_mock") - .Options; + .UseInMemoryDatabase(databaseName: "repository_mock") + .Options; using (var context = new AppDbContext(options)) { @@ -254,70 +265,59 @@ protected DbContextOptions InitInMemoryDb(Action seeder void MockHooks(Mock> resourceDefinition) where TModel : class, IIdentifiable { resourceDefinition - .Setup(rd => rd.BeforeCreate(It.IsAny>(), It.IsAny())) - .Returns, ResourcePipeline>((entities, context) => entities) - .Verifiable(); + .Setup(rd => rd.BeforeCreate(It.IsAny>(), It.IsAny())) + .Returns, ResourcePipeline>((entities, context) => entities) + .Verifiable(); resourceDefinition - .Setup(rd => rd.BeforeRead(It.IsAny(), It.IsAny(), It.IsAny())) - .Verifiable(); + .Setup(rd => rd.BeforeRead(It.IsAny(), It.IsAny(), It.IsAny())) + .Verifiable(); resourceDefinition - .Setup(rd => rd.BeforeUpdate(It.IsAny>(), It.IsAny())) - .Returns, ResourcePipeline>((entityDiff, context) => entityDiff.Entities) - .Verifiable(); + .Setup(rd => rd.BeforeUpdate(It.IsAny>(), It.IsAny())) + .Returns, ResourcePipeline>((entityDiff, context) => entityDiff.Entities) + .Verifiable(); resourceDefinition - .Setup(rd => rd.BeforeDelete(It.IsAny>(), It.IsAny())) - .Returns, ResourcePipeline>((entities, context) => entities) - .Verifiable(); + .Setup(rd => rd.BeforeDelete(It.IsAny>(), It.IsAny())) + .Returns, ResourcePipeline>((entities, context) => entities) + .Verifiable(); resourceDefinition - .Setup(rd => rd.BeforeUpdateRelationship(It.IsAny>(), It.IsAny>(), It.IsAny())) - .Returns, IRelationshipsDictionary, ResourcePipeline>((ids, context, helper) => ids) - .Verifiable(); + .Setup(rd => rd.BeforeUpdateRelationship(It.IsAny>(), It.IsAny>(), It.IsAny())) + .Returns, IRelationshipsDictionary, ResourcePipeline>((ids, context, helper) => ids) + .Verifiable(); resourceDefinition - .Setup(rd => rd.BeforeImplicitUpdateRelationship(It.IsAny>(), It.IsAny())) - .Verifiable(); + .Setup(rd => rd.BeforeImplicitUpdateRelationship(It.IsAny>(), It.IsAny())) + .Verifiable(); resourceDefinition - .Setup(rd => rd.OnReturn(It.IsAny>(), It.IsAny())) - .Returns, ResourcePipeline>((entities, context) => entities) - .Verifiable(); + .Setup(rd => rd.OnReturn(It.IsAny>(), It.IsAny())) + .Returns, ResourcePipeline>((entities, context) => entities) + .Verifiable(); resourceDefinition - .Setup(rd => rd.AfterCreate(It.IsAny>(), It.IsAny())) - .Verifiable(); + .Setup(rd => rd.AfterCreate(It.IsAny>(), It.IsAny())) + .Verifiable(); resourceDefinition - .Setup(rd => rd.AfterRead(It.IsAny>(), It.IsAny(), It.IsAny())) - .Verifiable(); + .Setup(rd => rd.AfterRead(It.IsAny>(), It.IsAny(), It.IsAny())) + .Verifiable(); resourceDefinition - .Setup(rd => rd.AfterUpdate(It.IsAny>(), It.IsAny())) - .Verifiable(); + .Setup(rd => rd.AfterUpdate(It.IsAny>(), It.IsAny())) + .Verifiable(); resourceDefinition - .Setup(rd => rd.AfterDelete(It.IsAny>(), It.IsAny(), It.IsAny())) - .Verifiable(); + .Setup(rd => rd.AfterDelete(It.IsAny>(), It.IsAny(), It.IsAny())) + .Verifiable(); } - (Mock, Mock) CreateContextAndProcessorMocks() - { - var processorFactory = new Mock(); - var context = new Mock(); - context.Setup(c => c.GenericProcessorFactory).Returns(processorFactory.Object); - context.Setup(c => c.Options).Returns(new JsonApiOptions { LoadDatabaseValues = false }); - context.Setup(c => c.ResourceGraph).Returns(ResourceGraph.Instance); - return (context, processorFactory); - } void SetupProcessorFactoryForResourceDefinition( - Mock processorFactory, - IResourceHookContainer modelResource, - IHooksDiscovery discovery, - IJsonApiContext apiContext, - AppDbContext dbContext = null + Mock processorFactory, + IResourceHookContainer modelResource, + IHooksDiscovery discovery, + IJsonApiContext apiContext, + AppDbContext dbContext = null ) where TModel : class, IIdentifiable { - processorFactory.Setup(c => c.GetProcessor(typeof(ResourceDefinition<>), typeof(TModel))) - .Returns(modelResource); + processorFactory.Setup(c => c.GetProcessor(typeof(ResourceDefinition<>), typeof(TModel))).Returns(modelResource); - processorFactory.Setup(c => c.GetProcessor(typeof(IHooksDiscovery<>), typeof(TModel))) - .Returns(discovery); + processorFactory.Setup(c => c.GetProcessor(typeof(IHooksDiscovery<>), typeof(TModel))).Returns(discovery); if (dbContext != null) { @@ -326,7 +326,8 @@ void SetupProcessorFactoryForResourceDefinition( { IEntityReadRepository repo = CreateTestRepository(dbContext, apiContext); processorFactory.Setup(c => c.GetProcessor>(typeof(IEntityReadRepository<,>), typeof(TModel), typeof(int))).Returns(repo); - } else + } + else { throw new TypeLoadException("Test not set up properly"); } From 2be340be1edbeeb9e1e92cae5f2964500ea9abff Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 15 Aug 2019 10:50:32 +0200 Subject: [PATCH 11/26] chore: readded solutions --- JsonApiDotnetCore.sln | 376 +++++++++++++++++++++--------------------- 1 file changed, 188 insertions(+), 188 deletions(-) diff --git a/JsonApiDotnetCore.sln b/JsonApiDotnetCore.sln index a0330ce005..d28c19a7f4 100644 --- a/JsonApiDotnetCore.sln +++ b/JsonApiDotnetCore.sln @@ -2,16 +2,10 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28606.126 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore", "src\JsonApiDotNetCore\JsonApiDotNetCore.csproj", "{C0EC9E70-EB2E-436F-9D94-FA16FA774123}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCoreExample", "src\Examples\JsonApiDotNetCoreExample\JsonApiDotNetCoreExample.csproj", "{97EE048B-16C0-43F6-BDA9-4E762B2F579F}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCoreExampleTests", "test\JsonApiDotNetCoreExampleTests\JsonApiDotNetCoreExampleTests.csproj", "{0B959765-40D2-43B5-87EE-FE2FEF9DBED5}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C5B4D998-CECB-454D-9F32-085A897577BE}" ProjectSection(SolutionItems) = preProject .gitignore = .gitignore @@ -23,31 +17,37 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NoEntityFrameworkExample", "src\Examples\NoEntityFrameworkExample\NoEntityFrameworkExample.csproj", "{570165EC-62B5-4684-A139-8D2A30DD4475}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{026FBC6C-AF76-4568-9B87-EC73457899FD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{076E1AE4-FD25-4684-B826-CAAE37FEA0AA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NoEntityFrameworkTests", "test\NoEntityFrameworkTests\NoEntityFrameworkTests.csproj", "{73DA578D-A63F-4956-83ED-6D7102E09140}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCore", "src\JsonApiDotNetCore\JsonApiDotNetCore.csproj", "{9D36BE59-7C14-448B-984D-93A0E7816314}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "test\UnitTests\UnitTests.csproj", "{6D4BD85A-A262-44C6-8572-FE3A30410BF3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCoreExample", "src\Examples\JsonApiDotNetCoreExample\JsonApiDotNetCoreExample.csproj", "{27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{026FBC6C-AF76-4568-9B87-EC73457899FD}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoEntityFrameworkExample", "src\Examples\NoEntityFrameworkExample\NoEntityFrameworkExample.csproj", "{99BAF03C-362B-41FA-9FFF-67F697EFC28C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReportsExample", "src\Examples\ReportsExample\ReportsExample.csproj", "{FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReportsExample", "src\Examples\ReportsExample\ReportsExample.csproj", "{1CC0831C-ED1D-442E-8421-331D50BD41F1}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{076E1AE4-FD25-4684-B826-CAAE37FEA0AA}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OperationsExample", "src\Examples\OperationsExample\OperationsExample.csproj", "{3AB43764-C57A-4B75-8C03-C671D3925BF3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceEntitySeparationExample", "src\Examples\ResourceEntitySeparationExample\ResourceEntitySeparationExample.csproj", "{623792C0-5B7D-4D7D-A276-73F908FD4C34}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonApiDotNetCoreExampleTests", "test\JsonApiDotNetCoreExampleTests\JsonApiDotNetCoreExampleTests.csproj", "{CAF331F8-9255-4D72-A1A8-A54141E99F1E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "benchmarks\Benchmarks.csproj", "{1F604666-BB0F-413E-922D-9D37C6073285}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoEntityFrameworkTests", "test\NoEntityFrameworkTests\NoEntityFrameworkTests.csproj", "{4F15A8F8-5BC6-45A1-BC51-03F921B726A4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OperationsExample", "src\Examples\OperationsExample\OperationsExample.csproj", "{CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "test\UnitTests\UnitTests.csproj", "{8788FF65-C2B6-40B2-A3A0-1E3D91C02664}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OperationsExampleTests", "test\OperationsExampleTests\OperationsExampleTests.csproj", "{9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OperationsExampleTests", "test\OperationsExampleTests\OperationsExampleTests.csproj", "{65BF5960-3D9B-4230-99F4-A12CAA130792}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourceEntitySeparationExample", "src\Examples\ResourceEntitySeparationExample\ResourceEntitySeparationExample.csproj", "{F4097194-9415-418A-AB4E-315C5D5466AF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceEntitySeparationExampleTests", "test\ResourceEntitySeparationExampleTests\ResourceEntitySeparationExampleTests.csproj", "{778C4EB9-BD65-4C0F-9230-B5CB1D72186A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourceEntitySeparationExampleTests", "test\ResourceEntitySeparationExampleTests\ResourceEntitySeparationExampleTests.csproj", "{6DFA30D7-1679-4333-9779-6FB678E48EF5}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscoveryTests", "test\DiscoveryTests\DiscoveryTests.csproj", "{03032A2F-664D-4DD8-A82F-AD8A482EDD85}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{DF9BFD82-D937-4907-B0B4-64670417115F}" +Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "Benchmarks", "benchmarks\Benchmarks.csproj", "{7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscoveryTests", "test\DiscoveryTests\DiscoveryTests.csproj", "{09C0C8D8-B721-4955-8889-55CB149C3B5C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{92BFF50F-BF96-43AD-AB86-A8B861C32412}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -59,181 +59,181 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Debug|x64.ActiveCfg = Debug|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Debug|x86.ActiveCfg = Debug|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Release|Any CPU.Build.0 = Release|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Release|x64.ActiveCfg = Release|Any CPU - {C0EC9E70-EB2E-436F-9D94-FA16FA774123}.Release|x86.ActiveCfg = Release|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Debug|x64.ActiveCfg = Debug|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Debug|x86.ActiveCfg = Debug|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Release|Any CPU.Build.0 = Release|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Release|x64.ActiveCfg = Release|Any CPU - {97EE048B-16C0-43F6-BDA9-4E762B2F579F}.Release|x86.ActiveCfg = Release|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Debug|x64.ActiveCfg = Debug|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Debug|x86.ActiveCfg = Debug|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Release|Any CPU.Build.0 = Release|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Release|x64.ActiveCfg = Release|Any CPU - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5}.Release|x86.ActiveCfg = Release|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Debug|Any CPU.Build.0 = Debug|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Debug|x64.ActiveCfg = Debug|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Debug|x64.Build.0 = Debug|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Debug|x86.ActiveCfg = Debug|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Debug|x86.Build.0 = Debug|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Release|Any CPU.ActiveCfg = Release|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Release|Any CPU.Build.0 = Release|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Release|x64.ActiveCfg = Release|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Release|x64.Build.0 = Release|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Release|x86.ActiveCfg = Release|Any CPU - {570165EC-62B5-4684-A139-8D2A30DD4475}.Release|x86.Build.0 = Release|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Debug|Any CPU.Build.0 = Debug|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Debug|x64.ActiveCfg = Debug|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Debug|x64.Build.0 = Debug|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Debug|x86.ActiveCfg = Debug|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Debug|x86.Build.0 = Debug|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Release|Any CPU.ActiveCfg = Release|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Release|Any CPU.Build.0 = Release|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Release|x64.ActiveCfg = Release|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Release|x64.Build.0 = Release|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Release|x86.ActiveCfg = Release|Any CPU - {73DA578D-A63F-4956-83ED-6D7102E09140}.Release|x86.Build.0 = Release|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|x64.ActiveCfg = Debug|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|x64.Build.0 = Debug|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|x86.ActiveCfg = Debug|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|x86.Build.0 = Debug|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|Any CPU.Build.0 = Release|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|x64.ActiveCfg = Release|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|x64.Build.0 = Release|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|x86.ActiveCfg = Release|Any CPU - {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|x86.Build.0 = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Debug|x64.ActiveCfg = Debug|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Debug|x64.Build.0 = Debug|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Debug|x86.ActiveCfg = Debug|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Debug|x86.Build.0 = Debug|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|Any CPU.Build.0 = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x64.ActiveCfg = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x64.Build.0 = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x86.ActiveCfg = Release|Any CPU - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D}.Release|x86.Build.0 = Release|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x64.ActiveCfg = Debug|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x64.Build.0 = Debug|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x86.ActiveCfg = Debug|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Debug|x86.Build.0 = Debug|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Release|Any CPU.Build.0 = Release|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x64.ActiveCfg = Release|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x64.Build.0 = Release|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x86.ActiveCfg = Release|Any CPU - {1F604666-BB0F-413E-922D-9D37C6073285}.Release|x86.Build.0 = Release|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|x64.ActiveCfg = Debug|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|x64.Build.0 = Debug|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|x86.ActiveCfg = Debug|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Debug|x86.Build.0 = Debug|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|Any CPU.Build.0 = Release|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|x64.ActiveCfg = Release|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|x64.Build.0 = Release|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|x86.ActiveCfg = Release|Any CPU - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D}.Release|x86.Build.0 = Release|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|x64.ActiveCfg = Debug|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|x64.Build.0 = Debug|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|x86.ActiveCfg = Debug|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Debug|x86.Build.0 = Debug|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|Any CPU.Build.0 = Release|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|x64.ActiveCfg = Release|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|x64.Build.0 = Release|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|x86.ActiveCfg = Release|Any CPU - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1}.Release|x86.Build.0 = Release|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Debug|x64.ActiveCfg = Debug|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Debug|x64.Build.0 = Debug|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Debug|x86.ActiveCfg = Debug|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Debug|x86.Build.0 = Debug|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Release|Any CPU.Build.0 = Release|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Release|x64.ActiveCfg = Release|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Release|x64.Build.0 = Release|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Release|x86.ActiveCfg = Release|Any CPU - {F4097194-9415-418A-AB4E-315C5D5466AF}.Release|x86.Build.0 = Release|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Debug|x64.ActiveCfg = Debug|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Debug|x64.Build.0 = Debug|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Debug|x86.ActiveCfg = Debug|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Debug|x86.Build.0 = Debug|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|Any CPU.Build.0 = Release|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x64.ActiveCfg = Release|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x64.Build.0 = Release|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x86.ActiveCfg = Release|Any CPU - {6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x86.Build.0 = Release|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x64.ActiveCfg = Debug|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x64.Build.0 = Debug|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x86.ActiveCfg = Debug|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|x86.Build.0 = Debug|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|Any CPU.Build.0 = Release|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x64.ActiveCfg = Release|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x64.Build.0 = Release|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x86.ActiveCfg = Release|Any CPU - {DF9BFD82-D937-4907-B0B4-64670417115F}.Release|x86.Build.0 = Release|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x64.ActiveCfg = Debug|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x64.Build.0 = Debug|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x86.ActiveCfg = Debug|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x86.Build.0 = Debug|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|Any CPU.Build.0 = Release|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x64.ActiveCfg = Release|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x64.Build.0 = Release|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x86.ActiveCfg = Release|Any CPU - {09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x86.Build.0 = Release|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Debug|x64.ActiveCfg = Debug|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Debug|x86.ActiveCfg = Debug|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Release|Any CPU.Build.0 = Release|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Release|x64.ActiveCfg = Release|Any CPU + {9D36BE59-7C14-448B-984D-93A0E7816314}.Release|x86.ActiveCfg = Release|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Debug|x64.ActiveCfg = Debug|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Debug|x86.ActiveCfg = Debug|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Release|Any CPU.Build.0 = Release|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Release|x64.ActiveCfg = Release|Any CPU + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5}.Release|x86.ActiveCfg = Release|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Debug|x64.ActiveCfg = Debug|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Debug|x64.Build.0 = Debug|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Debug|x86.ActiveCfg = Debug|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Debug|x86.Build.0 = Debug|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Release|Any CPU.Build.0 = Release|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Release|x64.ActiveCfg = Release|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Release|x64.Build.0 = Release|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Release|x86.ActiveCfg = Release|Any CPU + {99BAF03C-362B-41FA-9FFF-67F697EFC28C}.Release|x86.Build.0 = Release|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Debug|x64.ActiveCfg = Debug|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Debug|x64.Build.0 = Debug|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Debug|x86.ActiveCfg = Debug|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Debug|x86.Build.0 = Debug|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Release|Any CPU.Build.0 = Release|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Release|x64.ActiveCfg = Release|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Release|x64.Build.0 = Release|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Release|x86.ActiveCfg = Release|Any CPU + {1CC0831C-ED1D-442E-8421-331D50BD41F1}.Release|x86.Build.0 = Release|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Debug|x64.ActiveCfg = Debug|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Debug|x64.Build.0 = Debug|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Debug|x86.ActiveCfg = Debug|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Debug|x86.Build.0 = Debug|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Release|Any CPU.Build.0 = Release|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Release|x64.ActiveCfg = Release|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Release|x64.Build.0 = Release|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Release|x86.ActiveCfg = Release|Any CPU + {3AB43764-C57A-4B75-8C03-C671D3925BF3}.Release|x86.Build.0 = Release|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Debug|x64.ActiveCfg = Debug|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Debug|x64.Build.0 = Debug|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Debug|x86.ActiveCfg = Debug|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Debug|x86.Build.0 = Debug|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Release|Any CPU.Build.0 = Release|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Release|x64.ActiveCfg = Release|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Release|x64.Build.0 = Release|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Release|x86.ActiveCfg = Release|Any CPU + {623792C0-5B7D-4D7D-A276-73F908FD4C34}.Release|x86.Build.0 = Release|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|x64.ActiveCfg = Debug|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Debug|x86.ActiveCfg = Debug|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Release|Any CPU.Build.0 = Release|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Release|x64.ActiveCfg = Release|Any CPU + {CAF331F8-9255-4D72-A1A8-A54141E99F1E}.Release|x86.ActiveCfg = Release|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Debug|x64.ActiveCfg = Debug|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Debug|x64.Build.0 = Debug|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Debug|x86.ActiveCfg = Debug|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Debug|x86.Build.0 = Debug|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Release|Any CPU.Build.0 = Release|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Release|x64.ActiveCfg = Release|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Release|x64.Build.0 = Release|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Release|x86.ActiveCfg = Release|Any CPU + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4}.Release|x86.Build.0 = Release|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Debug|x64.ActiveCfg = Debug|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Debug|x64.Build.0 = Debug|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Debug|x86.ActiveCfg = Debug|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Debug|x86.Build.0 = Debug|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Release|Any CPU.Build.0 = Release|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Release|x64.ActiveCfg = Release|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Release|x64.Build.0 = Release|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Release|x86.ActiveCfg = Release|Any CPU + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664}.Release|x86.Build.0 = Release|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Debug|x64.ActiveCfg = Debug|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Debug|x64.Build.0 = Debug|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Debug|x86.ActiveCfg = Debug|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Debug|x86.Build.0 = Debug|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Release|Any CPU.Build.0 = Release|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Release|x64.ActiveCfg = Release|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Release|x64.Build.0 = Release|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Release|x86.ActiveCfg = Release|Any CPU + {65BF5960-3D9B-4230-99F4-A12CAA130792}.Release|x86.Build.0 = Release|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Debug|x64.ActiveCfg = Debug|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Debug|x64.Build.0 = Debug|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Debug|x86.ActiveCfg = Debug|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Debug|x86.Build.0 = Debug|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Release|Any CPU.Build.0 = Release|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Release|x64.ActiveCfg = Release|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Release|x64.Build.0 = Release|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Release|x86.ActiveCfg = Release|Any CPU + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A}.Release|x86.Build.0 = Release|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Debug|x64.ActiveCfg = Debug|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Debug|x64.Build.0 = Debug|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Debug|x86.ActiveCfg = Debug|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Debug|x86.Build.0 = Debug|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|Any CPU.Build.0 = Release|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x64.ActiveCfg = Release|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x64.Build.0 = Release|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x86.ActiveCfg = Release|Any CPU + {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x86.Build.0 = Release|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x64.ActiveCfg = Debug|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x64.Build.0 = Debug|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x86.ActiveCfg = Debug|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x86.Build.0 = Debug|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|Any CPU.Build.0 = Release|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x64.ActiveCfg = Release|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x64.Build.0 = Release|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x86.ActiveCfg = Release|Any CPU + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x86.Build.0 = Release|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|x64.ActiveCfg = Debug|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|x64.Build.0 = Debug|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|x86.ActiveCfg = Debug|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|x86.Build.0 = Debug|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|Any CPU.Build.0 = Release|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x64.ActiveCfg = Release|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x64.Build.0 = Release|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x86.ActiveCfg = Release|Any CPU + {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {C0EC9E70-EB2E-436F-9D94-FA16FA774123} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} - {97EE048B-16C0-43F6-BDA9-4E762B2F579F} = {026FBC6C-AF76-4568-9B87-EC73457899FD} - {0B959765-40D2-43B5-87EE-FE2FEF9DBED5} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} - {570165EC-62B5-4684-A139-8D2A30DD4475} = {026FBC6C-AF76-4568-9B87-EC73457899FD} - {73DA578D-A63F-4956-83ED-6D7102E09140} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} - {6D4BD85A-A262-44C6-8572-FE3A30410BF3} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {026FBC6C-AF76-4568-9B87-EC73457899FD} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} - {FBFB0B0B-EA86-4B41-AB2A-E0249F70C86D} = {026FBC6C-AF76-4568-9B87-EC73457899FD} - {1F604666-BB0F-413E-922D-9D37C6073285} = {076E1AE4-FD25-4684-B826-CAAE37FEA0AA} - {CF2C1EB6-8449-4B35-B8C7-F43D6D90632D} = {026FBC6C-AF76-4568-9B87-EC73457899FD} - {9CD2C116-D133-4FE4-97DA-A9FEAFF045F1} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} - {F4097194-9415-418A-AB4E-315C5D5466AF} = {026FBC6C-AF76-4568-9B87-EC73457899FD} - {6DFA30D7-1679-4333-9779-6FB678E48EF5} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} - {09C0C8D8-B721-4955-8889-55CB149C3B5C} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {9D36BE59-7C14-448B-984D-93A0E7816314} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} + {27FA7CD3-8DCD-4104-9AB4-B2D927F421B5} = {026FBC6C-AF76-4568-9B87-EC73457899FD} + {99BAF03C-362B-41FA-9FFF-67F697EFC28C} = {026FBC6C-AF76-4568-9B87-EC73457899FD} + {1CC0831C-ED1D-442E-8421-331D50BD41F1} = {026FBC6C-AF76-4568-9B87-EC73457899FD} + {3AB43764-C57A-4B75-8C03-C671D3925BF3} = {026FBC6C-AF76-4568-9B87-EC73457899FD} + {623792C0-5B7D-4D7D-A276-73F908FD4C34} = {026FBC6C-AF76-4568-9B87-EC73457899FD} + {CAF331F8-9255-4D72-A1A8-A54141E99F1E} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {4F15A8F8-5BC6-45A1-BC51-03F921B726A4} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {8788FF65-C2B6-40B2-A3A0-1E3D91C02664} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {65BF5960-3D9B-4230-99F4-A12CAA130792} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {778C4EB9-BD65-4C0F-9230-B5CB1D72186A} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {03032A2F-664D-4DD8-A82F-AD8A482EDD85} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4} = {076E1AE4-FD25-4684-B826-CAAE37FEA0AA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A2421882-8F0A-4905-928F-B550B192F9A4} From 6796295ab9c669d037fc87f79eba2853b47523d4 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Mon, 2 Sep 2019 11:58:44 +0200 Subject: [PATCH 12/26] feat: upgrading to 2.2, setting contextentity in middleware --- Directory.Build.props | 12 +-- JsonApiDotnetCore.sln | 30 +++---- .../JsonApiDeserializer_Benchmarks.cs | 6 +- .../GettingStarted/GettingStarted.csproj | 6 +- .../ModelDefinition.cs | 4 - .../JsonApiDotNetCoreExample.csproj | 4 +- .../Builders/DocumentBuilder.cs | 4 +- src/JsonApiDotNetCore/Builders/LinkBuilder.cs | 24 ++++-- .../Builders/ResourceGraphBuilder.cs | 17 ++-- .../IApplicationBuilderExtensions.cs | 6 ++ .../IServiceCollectionExtensions.cs | 3 + .../Internal/Contracts/IResourceGraph.cs | 2 +- .../Internal/ResourceGraph.cs | 23 +++-- .../Internal/RouteMatcher.cs | 10 +++ .../JsonApiDotNetCore.csproj | 5 +- .../Managers/RequestManager.cs | 6 +- .../Middleware/RequestMiddleware.cs | 85 ++++++++++++------- .../Serialization/JsonApiSerializer.cs | 1 + .../Services/EntityResourceService.cs | 24 +----- .../Services/JsonApiContext.cs | 6 +- .../ServiceDiscoveryFacadeTests.cs | 12 ++- .../JsonApiDotNetCoreExampleTests.csproj | 1 + .../Builders/DocumentBuilder_Tests.cs | 47 +++++----- test/UnitTests/Builders/LinkBuilderTests.cs | 49 +++++++++++ test/UnitTests/Builders/LinkBuilder_Tests.cs | 49 ----------- .../BaseJsonApiController_Tests.cs | 14 +-- .../IServiceCollectionExtensionsTests.cs | 3 +- .../ResourceHooks/ResourceHooksTestsSetup.cs | 61 +++++++------ .../Serialization/JsonApiDeSerializerTests.cs | 7 +- .../Serialization/JsonApiSerializerTests.cs | 20 ++--- test/UnitTests/Services/QueryParserTests.cs | 2 +- test/UnitTests/UnitTests.csproj | 3 +- 32 files changed, 295 insertions(+), 251 deletions(-) create mode 100644 src/JsonApiDotNetCore/Internal/RouteMatcher.cs create mode 100644 test/UnitTests/Builders/LinkBuilderTests.cs delete mode 100644 test/UnitTests/Builders/LinkBuilder_Tests.cs diff --git a/Directory.Build.props b/Directory.Build.props index 0d034e0c5d..fcd668b5ba 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,14 +4,14 @@ netcoreapp2.0 netstandard2.0 - 2.1.0 + 2.* - 2.1.0 - 2.1.0 - 2.1.0 + 2.* + 2.* + 2.* - 2.1.0 - 2.1.0 + 2.* + 2.* 4.0.0 2.1.0 diff --git a/JsonApiDotnetCore.sln b/JsonApiDotnetCore.sln index d28c19a7f4..a0be085cb2 100644 --- a/JsonApiDotnetCore.sln +++ b/JsonApiDotnetCore.sln @@ -45,10 +45,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceEntitySeparationExa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscoveryTests", "test\DiscoveryTests\DiscoveryTests.csproj", "{03032A2F-664D-4DD8-A82F-AD8A482EDD85}" EndProject -Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "Benchmarks", "benchmarks\Benchmarks.csproj", "{7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{92BFF50F-BF96-43AD-AB86-A8B861C32412}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "benchmarks\Benchmarks.csproj", "{DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -191,18 +191,6 @@ Global {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x64.Build.0 = Release|Any CPU {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x86.ActiveCfg = Release|Any CPU {03032A2F-664D-4DD8-A82F-AD8A482EDD85}.Release|x86.Build.0 = Release|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x64.ActiveCfg = Debug|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x64.Build.0 = Debug|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x86.ActiveCfg = Debug|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Debug|x86.Build.0 = Debug|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|Any CPU.Build.0 = Release|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x64.ActiveCfg = Release|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x64.Build.0 = Release|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x86.ActiveCfg = Release|Any CPU - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4}.Release|x86.Build.0 = Release|Any CPU {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|Any CPU.Build.0 = Debug|Any CPU {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -215,6 +203,18 @@ Global {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x64.Build.0 = Release|Any CPU {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x86.ActiveCfg = Release|Any CPU {92BFF50F-BF96-43AD-AB86-A8B861C32412}.Release|x86.Build.0 = Release|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Debug|x64.Build.0 = Debug|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Debug|x86.ActiveCfg = Debug|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Debug|x86.Build.0 = Debug|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Release|Any CPU.Build.0 = Release|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Release|x64.ActiveCfg = Release|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Release|x64.Build.0 = Release|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Release|x86.ActiveCfg = Release|Any CPU + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -233,7 +233,7 @@ Global {65BF5960-3D9B-4230-99F4-A12CAA130792} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {778C4EB9-BD65-4C0F-9230-B5CB1D72186A} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {03032A2F-664D-4DD8-A82F-AD8A482EDD85} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} - {7919C4AB-0B3F-4B3E-B1B3-00E5FB4879D4} = {076E1AE4-FD25-4684-B826-CAAE37FEA0AA} + {DF0FCFB2-CB12-44BA-BBB5-1BE0BCFCD14C} = {076E1AE4-FD25-4684-B826-CAAE37FEA0AA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A2421882-8F0A-4905-928F-B550B192F9A4} diff --git a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs index 983bc07f90..03b4878aa6 100644 --- a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs +++ b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs @@ -4,7 +4,6 @@ using BenchmarkDotNet.Attributes.Exporters; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Internal.Generics; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Services; @@ -12,7 +11,8 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -namespace Benchmarks.Serialization { +namespace Benchmarks.Serialization +{ [MarkdownExporter] public class JsonApiDeserializer_Benchmarks { private const string TYPE_NAME = "simple-types"; @@ -39,7 +39,7 @@ public JsonApiDeserializer_Benchmarks() { var jsonApiContextMock = new Mock(); jsonApiContextMock.SetupAllProperties(); jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); + jsonApiContextMock.Setup(m => m.RequestManager.GetUpdatedAttributes()).Returns(new Dictionary()); var jsonApiOptions = new JsonApiOptions(); jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); diff --git a/src/Examples/GettingStarted/GettingStarted.csproj b/src/Examples/GettingStarted/GettingStarted.csproj index ece976d5e5..e29e94ce6a 100644 --- a/src/Examples/GettingStarted/GettingStarted.csproj +++ b/src/Examples/GettingStarted/GettingStarted.csproj @@ -14,9 +14,9 @@ - - - + + + diff --git a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelDefinition.cs b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelDefinition.cs index b700022eba..d31458250c 100644 --- a/src/Examples/GettingStarted/ResourceDefinitionExample/ModelDefinition.cs +++ b/src/Examples/GettingStarted/ResourceDefinitionExample/ModelDefinition.cs @@ -1,9 +1,5 @@ using System.Collections.Generic; -using JsonApiDotNetCore.Internal; -<<<<<<< HEAD using JsonApiDotNetCore.Internal.Contracts; -======= ->>>>>>> master using JsonApiDotNetCore.Models; namespace GettingStarted.ResourceDefinitionExample diff --git a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj index d56e91f21e..92f1bf4fa0 100644 --- a/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj +++ b/src/Examples/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index d8c7118ae8..426b61c185 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -30,9 +30,9 @@ public DocumentBuilder( IDocumentBuilderOptionsProvider documentBuilderOptionsProvider = null, IScopedServiceProvider scopedServiceProvider = null) { - _requestManager = requestManager; _pageManager = pageManager; _jsonApiContext = jsonApiContext; + _requestManager = requestManager ?? jsonApiContext.RequestManager; _resourceGraph = jsonApiContext.ResourceGraph; _requestMeta = requestMeta; _documentBuilderOptions = documentBuilderOptionsProvider?.GetDocumentBuilderOptions() ?? new DocumentBuilderOptions(); @@ -219,7 +219,7 @@ private List GetIncludedEntities(List included, { if (_requestManager.IncludedRelationships != null) { - foreach (var relationshipName in _jsonApiContext.RequestManager.IncludedRelationships) + foreach (var relationshipName in _requestManager.IncludedRelationships) { var relationshipChain = relationshipName.Split('.'); diff --git a/src/JsonApiDotNetCore/Builders/LinkBuilder.cs b/src/JsonApiDotNetCore/Builders/LinkBuilder.cs index f06134d397..9738065ec3 100644 --- a/src/JsonApiDotNetCore/Builders/LinkBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/LinkBuilder.cs @@ -1,38 +1,44 @@ -using System; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; -using Microsoft.AspNetCore.Http; namespace JsonApiDotNetCore.Builders { public class LinkBuilder : ILinkBuilder { - private IRequestManager _requestManager; - private IJsonApiOptions _options; + private readonly IRequestManager _requestManager; + private readonly IJsonApiOptions _options; public LinkBuilder(IJsonApiOptions options, IRequestManager requestManager) { - _requestManager = requestManager; _options = options; + _requestManager = requestManager; } - + /// public string GetSelfRelationLink(string parent, string parentId, string child) { - return $"{_requestManager.BasePath}/{parent}/{parentId}/relationships/{child}"; + return $"{GetBasePath()}/{parent}/{parentId}/relationships/{child}"; } + /// public string GetRelatedRelationLink(string parent, string parentId, string child) { - return $"{_requestManager.BasePath}/{parent}/{parentId}/{child}"; + return $"{GetBasePath()}/{parent}/{parentId}/{child}"; } + /// public string GetPageLink(int pageOffset, int pageSize) { var filterQueryComposer = new QueryComposer(); var filters = filterQueryComposer.Compose(_requestManager); - return $"{_requestManager.BasePath}/{_requestManager.GetContextEntity().EntityName}?page[size]={pageSize}&page[number]={pageOffset}{filters}"; + return $"{GetBasePath()}/{_requestManager.GetContextEntity().EntityName}?page[size]={pageSize}&page[number]={pageOffset}{filters}"; + } + + private string GetBasePath() + { + if (_options.RelativeLinks) return string.Empty; + return _requestManager.BasePath; } } } diff --git a/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs index 500446a7c5..2c9d2677ea 100644 --- a/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs @@ -9,7 +9,6 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; -using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -33,20 +32,20 @@ public IResourceGraph Build() // this must be done at build so that call order doesn't matter _entities.ForEach(e => e.Links = GetLinkFlags(e.EntityType)); - List controllerContexts = new List() { }; + List controllerContexts = new List() { }; foreach(var cm in _controllerMapper) { var model = cm.Key; - foreach(var controller in cm.Value) + foreach (var controller in cm.Value) { - var routeAttribute = controller.GetCustomAttribute(); - - controllerContexts.Add(new ControllerModelMap + var controllerName = controller.Name.Replace("Controller", ""); + + controllerContexts.Add(new ControllerResourceMap { - Model = model, - Controller = controller, - Path = routeAttribute?.Template + Resource = model, + ControllerName = controllerName, }); + } } var graph = new ResourceGraph(_entities, _usesDbContext, _validationResults, controllerContexts); diff --git a/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs b/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs index 91f7baf180..44c741043c 100644 --- a/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IApplicationBuilderExtensions.cs @@ -5,8 +5,10 @@ using JsonApiDotNetCore.Middleware; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Routing; namespace JsonApiDotNetCore.Extensions { @@ -18,10 +20,14 @@ public static IApplicationBuilder UseJsonApi(this IApplicationBuilder app, bool DisableDetailedErrorsIfProduction(app); LogResourceGraphValidations(app); + app.UseEndpointRouting(); + app.UseMiddleware(); if (useMvc) + { app.UseMvc(); + } using (var scope = app.ApplicationServices.CreateScope()) { diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 0d20148e82..61b5b329b3 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -22,6 +22,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace JsonApiDotNetCore.Extensions { @@ -79,6 +80,7 @@ private static void AddMvcOptions(MvcOptions options, JsonApiOptions config) options.Filters.Add(typeof(JsonApiExceptionFilter)); options.Filters.Add(typeof(TypeMatchFilter)); options.SerializeAsJsonApi(config); + } public static void AddJsonApiInternals( @@ -170,6 +172,7 @@ public static void AddJsonApiInternals( services.AddTransient(typeof(IResourceHookExecutor), typeof(ResourceHookExecutor)); services.AddTransient(); } + services.AddTransient(); services.AddScoped(); } diff --git a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs index 71819d2666..39710ce616 100644 --- a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs @@ -88,6 +88,6 @@ public interface IResourceGraph bool UsesDbContext { get; } List IncludedRelationships { get; set; } - ContextEntity GetEntityBasedOnPath(string pathParsed); + ContextEntity GetEntityFromControllerName(string pathParsed); } } diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index 5659687994..770661d3e4 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -7,11 +7,10 @@ namespace JsonApiDotNetCore.Internal { - public class ControllerModelMap + public class ControllerResourceMap { - public Type Controller { get; set; } - public Type Model { get; set; } - public string Path { get; set; } + public string ControllerName { get; set; } + public Type Resource { get; set; } } /// @@ -22,7 +21,7 @@ public class ResourceGraph : IResourceGraph internal List Entities { get; } internal List ValidationResults { get; } - public List ControllerModelMap { get; internal set; } + public List ControllerResourceMap { get; internal set; } [Obsolete("please instantiate properly, dont use the static constructor")] internal static IResourceGraph Instance { get; set; } @@ -46,9 +45,9 @@ public ContextEntity GetEntityType(string entityName) // to avoid breaking changes, we will be leaving the original constructor in place // until the context graph validation process is completed // you can track progress on this issue here: https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/170 - internal ResourceGraph(List entities, bool usesDbContext, List validationResults, List controllerContexts) + internal ResourceGraph(List entities, bool usesDbContext, List validationResults, List controllerContexts) { - ControllerModelMap = controllerContexts; + ControllerResourceMap = controllerContexts; Entities = entities; UsesDbContext = usesDbContext; ValidationResults = validationResults; @@ -137,13 +136,11 @@ public RelationshipAttribute GetInverseRelationship(RelationshipAttribute relati return GetContextEntity(relationship.DependentType).Relationships.SingleOrDefault(r => r.InternalRelationshipName == relationship.InverseNavigation); } - public ContextEntity GetEntityBasedOnPath(string pathParsed) + public ContextEntity GetEntityFromControllerName(string controllerName) { - var controllerMatches = ControllerModelMap.Where(cm => cm.Controller.Name.ToLower().Contains(pathParsed.ToLower())); - - var model = controllerMatches.First().Model; - - return Entities.Where(e => e.EntityType == model).First(); + var resource = ControllerResourceMap.FirstOrDefault(cm => cm.ControllerName == controllerName)?.Resource; + if (resource == null) return null; + return Entities.First(e => e.EntityType == resource); } } } diff --git a/src/JsonApiDotNetCore/Internal/RouteMatcher.cs b/src/JsonApiDotNetCore/Internal/RouteMatcher.cs new file mode 100644 index 0000000000..4c5771ade1 --- /dev/null +++ b/src/JsonApiDotNetCore/Internal/RouteMatcher.cs @@ -0,0 +1,10 @@ +using System; +namespace JsonApiDotNetCore.Internal +{ + public class RouteMatcher + { + public RouteMatcher() + { + } + } +} diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index dcfe030039..1ae5427196 100644 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -27,7 +27,7 @@ - + @@ -44,7 +44,4 @@ - - - diff --git a/src/JsonApiDotNetCore/Managers/RequestManager.cs b/src/JsonApiDotNetCore/Managers/RequestManager.cs index 592a55e724..8aad3794de 100644 --- a/src/JsonApiDotNetCore/Managers/RequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/RequestManager.cs @@ -17,13 +17,13 @@ public class UpdatesContainer /// The attributes that were included in a PATCH request. /// Only the attributes in this dictionary should be updated. /// - public Dictionary Attributes { get; set; } + public Dictionary Attributes { get; set; } = new Dictionary(); /// /// Any relationships that were included in a PATCH request. /// Only the relationships in this dictionary should be updated. /// - public Dictionary Relationships { get; } + public Dictionary Relationships { get; } = new Dictionary(); } @@ -43,7 +43,7 @@ class RequestManager : IRequestManager /// /// Contains all the information you want about any update occuring /// - private UpdatesContainer _updatesContainer { get; set; } + private UpdatesContainer _updatesContainer { get; set; } = new UpdatesContainer(); public Dictionary RelationshipsToUpdate { get; set; } diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 9d42f7da0b..25147cb22c 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -8,6 +8,7 @@ using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Primitives; namespace JsonApiDotNetCore.Middleware @@ -20,36 +21,56 @@ namespace JsonApiDotNetCore.Middleware public class RequestMiddleware { private readonly RequestDelegate _next; + private IResourceGraph _resourceGraph; + private HttpContext _httpContext; + private IJsonApiOptions _options; + private IJsonApiContext _jsonApiContext; + private IRequestManager _requestManager; + private IQueryParser _queryParser; public RequestMiddleware(RequestDelegate next) { _next = next; } - public async Task Invoke(HttpContext context, + public async Task Invoke(HttpContext httpContext, IJsonApiContext jsonApiContext, IResourceGraph resourceGraph, IRequestManager requestManager, IQueryParser queryParser, IJsonApiOptions options) { - if (IsValid(context)) + _httpContext = httpContext; + _jsonApiContext = jsonApiContext; + _resourceGraph = resourceGraph; + _requestManager = requestManager; + _queryParser = queryParser; + _options = options; + + if (IsValid()) { + // HACK: this currently results in allocation of // objects that may or may not be used and even double allocation // since the JsonApiContext is using field initializers // Need to work on finding a better solution. jsonApiContext.BeginOperation(); - ContextEntity contextEntityCurrent = GetCurrentEntity(context.Request.Path, resourceGraph, options); - requestManager.SetContextEntity(contextEntityCurrent); - requestManager.BasePath = GetBasePath(context, options, contextEntityCurrent?.EntityName); - //Handle all querySet - HandleUriParameters(context, queryParser, requestManager); - requestManager.IsRelationshipPath = PathIsRelationship(context.Request.Path.Value); - // BACKWARD COMPATIBILITY for v4 will be removed in v5 - jsonApiContext.RequestManager = requestManager; - jsonApiContext.PageManager = new PageManager(new LinkBuilder(options, requestManager), options, requestManager); - await _next(context); + ContextEntity contextEntityCurrent = GetCurrentEntity(); + // the contextEntity is null eg when we're using a non-JsonApiDotNetCore route. + if (contextEntityCurrent != null) + { + requestManager.SetContextEntity(contextEntityCurrent); + // TODO: this does not need to be reset every request: we shouldn't need to rely on an external request to figure out the basepath of current application + requestManager.BasePath = GetBasePath(contextEntityCurrent.EntityName); + //Handle all querySet + HandleUriParameters(); + requestManager.IsRelationshipPath = PathIsRelationship(); + // BACKWARD COMPATIBILITY for v4 will be removed in v5 + jsonApiContext.RequestManager = requestManager; + jsonApiContext.PageManager = new PageManager(new LinkBuilder(options, requestManager), options, requestManager); + } + + await _next(httpContext); } } /// @@ -57,17 +78,19 @@ public async Task Invoke(HttpContext context, /// /// /// - protected void HandleUriParameters(HttpContext context, IQueryParser queryParser, IRequestManager requestManager) + protected void HandleUriParameters() { - if (context.Request.Query.Count > 0) + if (_httpContext.Request.Query.Count > 0) { //requestManager.FullQuerySet = context.Request.Query; - requestManager.QuerySet = queryParser.Parse(context.Request.Query); - requestManager.IncludedRelationships = requestManager.QuerySet.IncludedRelationships; + _requestManager.QuerySet = _queryParser.Parse(_httpContext.Request.Query); + _requestManager.IncludedRelationships = _requestManager.QuerySet.IncludedRelationships; } } - internal static bool PathIsRelationship(string requestPath) + + protected bool PathIsRelationship() { + string requestPath = _httpContext.Request.Path.Value; // while(!Debugger.IsAttached) { Thread.Sleep(1000); } const string relationships = "relationships"; const char pathSegmentDelimiter = '/'; @@ -99,10 +122,10 @@ internal static bool PathIsRelationship(string requestPath) return false; } - private string GetBasePath(HttpContext context, IJsonApiOptions options, string entityName) + private string GetBasePath(string entityName) { - var r = context.Request; - if (options.RelativeLinks) + var r = _httpContext.Request; + if (_options.RelativeLinks) { return GetNamespaceFromPath(r.Path, entityName); } @@ -147,22 +170,18 @@ internal static string GetNamespaceFromPath(string path, string entityName) /// /// /// - private ContextEntity GetCurrentEntity(PathString path, IResourceGraph resourceGraph, IJsonApiOptions options) + private ContextEntity GetCurrentEntity() { - var pathParsed = path.ToString().Replace($"{options.Namespace}/", ""); - if(pathParsed[0] == '/') - { - pathParsed = pathParsed.Substring(1); - } - return resourceGraph.GetEntityBasedOnPath(pathParsed); + var controllerName = (string)_httpContext.GetRouteData().Values["controller"]; + return _resourceGraph.GetEntityFromControllerName(controllerName); } - private static bool IsValid(HttpContext context) + private bool IsValid() { - return IsValidContentTypeHeader(context) && IsValidAcceptHeader(context); + return IsValidContentTypeHeader(_httpContext) && IsValidAcceptHeader(_httpContext); } - private static bool IsValidContentTypeHeader(HttpContext context) + private bool IsValidContentTypeHeader(HttpContext context) { var contentType = context.Request.ContentType; if (contentType != null && ContainsMediaTypeParameters(contentType)) @@ -173,7 +192,7 @@ private static bool IsValidContentTypeHeader(HttpContext context) return true; } - private static bool IsValidAcceptHeader(HttpContext context) + private bool IsValidAcceptHeader(HttpContext context) { if (context.Request.Headers.TryGetValue(Constants.AcceptHeader, out StringValues acceptHeaders) == false) return true; @@ -189,7 +208,7 @@ private static bool IsValidAcceptHeader(HttpContext context) return true; } - internal static bool ContainsMediaTypeParameters(string mediaType) + internal bool ContainsMediaTypeParameters(string mediaType) { var incomingMediaTypeSpan = mediaType.AsSpan(); @@ -208,7 +227,7 @@ internal static bool ContainsMediaTypeParameters(string mediaType) ); } - private static void FlushResponse(HttpContext context, int statusCode) + private void FlushResponse(HttpContext context, int statusCode) { context.Response.StatusCode = statusCode; context.Response.Body.Flush(); diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs index 2729e1ec94..f3db8e866b 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs @@ -21,6 +21,7 @@ public JsonApiSerializer( IDocumentBuilder documentBuilder) { _jsonApiContext = jsonApiContext; + _requestManager = jsonApiContext.RequestManager; _documentBuilder = documentBuilder; } diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index b73ba16ec8..d783d2df47 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -33,16 +33,6 @@ public class EntityResourceService : private readonly IResourceMapper _mapper; private readonly IResourceHookExecutor _hookExecutor; - /// - /// Base constructor where we assign the variables - /// - /// - /// - /// - /// - /// - /// - /// public EntityResourceService( IEntityRepository repository, IJsonApiOptions options, @@ -402,14 +392,8 @@ public class EntityResourceService : EntityResourceService { /// - /// Constructor for no mapping with integer as dfeault + /// Constructor for no mapping with integer as default /// - /// - /// - /// - /// - /// - /// public EntityResourceService( IEntityRepository repository, IJsonApiOptions options, @@ -421,8 +405,8 @@ public EntityResourceService( base(repository: repository, apiOptions: options, requestManager, resourceGraph, pageManager, loggerFactory, hookExecutor) { } - [Obsolete("Dont use this constructor, use the one without JsonApiContext instead")] - public EntityResourceService( - IJsonApiContext context, IEntityRepository repository) : this(repository, context.Options, context.RequestManager, context.PageManager, context.ResourceGraph) { } + //[Obsolete("Dont use this constructor, use the one without JsonApiContext instead")] + //public EntityResourceService( + // IJsonApiContext context, IEntityRepository repository) : this(repository, context.Options, context.RequestManager, context.PageManager, context.ResourceGraph) { } } } diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs index 6d7778c3b2..b235ffc197 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiContext.cs @@ -48,10 +48,13 @@ public JsonApiContext( [Obsolete("Use the proxied member IControllerContext.RequestEntity instead.")] public ContextEntity RequestEntity { get => _controllerContext.RequestEntity; set => _controllerContext.RequestEntity = value; } - [Obsolete("Please us the IRequestManager")] + [Obsolete("Use IRequestManager")] public QuerySet QuerySet { get; set; } + [Obsolete("Use IRequestManager")] public bool IsRelationshipData { get; set; } + [Obsolete("Use IRequestManager")] public bool IsRelationshipPath { get; private set; } + [Obsolete("Use IRequestManager")] public List IncludedRelationships { get; set; } public IPageManager PageManager { get; set; } public IMetaBuilder MetaBuilder { get; set; } @@ -136,6 +139,7 @@ internal static bool PathIsRelationship(string requestPath) public void BeginOperation() { + RequestManager.IncludedRelationships = new List(); IncludedRelationships = new List(); AttributesToUpdate = new Dictionary(); HasManyRelationshipPointers = new HasManyRelationshipPointers(); diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index 440bf9bd83..872b9f693b 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -1,12 +1,17 @@ using GettingStarted.Models; using GettingStarted.ResourceDefinitionExample; using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Graph; +using JsonApiDotNetCore.Hooks; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Moq; using Xunit; @@ -83,8 +88,11 @@ public class TestModel : Identifiable { } public class TestModelService : EntityResourceService { private static IEntityRepository _repo = new Mock>().Object; - private static IJsonApiContext _jsonApiContext = new Mock().Object; - public TestModelService() : base(_jsonApiContext, _repo) { } + private static IJsonApiContext _jsonApiContext = new Mock().Object; + + public TestModelService(IEntityRepository repository, IJsonApiOptions options, IRequestManager requestManager, IPageManager pageManager, IResourceGraph resourceGraph, ILoggerFactory loggerFactory = null, IResourceHookExecutor hookExecutor = null) : base(repository, options, requestManager, pageManager, resourceGraph, loggerFactory, hookExecutor) + { + } } public class TestModelRepository : DefaultEntityRepository diff --git a/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj b/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj index b4fcaf7ae0..fb3fac898d 100644 --- a/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj +++ b/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj @@ -24,6 +24,7 @@ + diff --git a/test/UnitTests/Builders/DocumentBuilder_Tests.cs b/test/UnitTests/Builders/DocumentBuilder_Tests.cs index 00b3b57b16..19018e1a62 100644 --- a/test/UnitTests/Builders/DocumentBuilder_Tests.cs +++ b/test/UnitTests/Builders/DocumentBuilder_Tests.cs @@ -62,12 +62,17 @@ public DocumentBuilder_Tests() public void Includes_Paging_Links_By_Default() { // arrange - _pageManager.PageSize = 1; - _pageManager.TotalRecords = 1; - _pageManager.CurrentPage = 1; - var documentBuilder = GetDocumentBuilder(); + var rmMock = new Mock(); + rmMock.Setup(m => m.GetContextEntity()).Returns(new ContextEntity { EntityName = "resources" }); + var rm = rmMock.Object; + var options = new JsonApiOptions { RelativeLinks = false }; + var pg = new PageManager(new LinkBuilder(options, rm), options, rm); + pg.PageSize = 1; + pg.TotalRecords = 1; + pg.CurrentPage = 1; + var documentBuilder = GetDocumentBuilder(pageManager: pg); var entity = new Model(); // act @@ -94,7 +99,7 @@ public void Page_Links_Can_Be_Disabled_Globally() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var entity = new Model(); // act @@ -116,7 +121,7 @@ public void Related_Links_Can_Be_Disabled() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var entity = new Model(); // act @@ -140,7 +145,7 @@ public void Related_Links_Can_Be_Disabled_Globally() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var entity = new RelatedModel(); // act @@ -161,7 +166,7 @@ public void Related_Data_Included_In_Relationships_By_Default() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var entity = new Model { RelatedModel = new RelatedModel @@ -193,7 +198,7 @@ public void IndependentIdentifier_Included_In_HasOne_Relationships_By_Default() .Setup(m => m.ResourceGraph) .Returns(_options.ResourceGraph); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var entity = new Model { RelatedModelId = relatedId @@ -215,7 +220,7 @@ public void IndependentIdentifier_Included_In_HasOne_Relationships_By_Default() public void Build_Can_Build_Arrays() { var entities = new[] { new Model() }; - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var documents = documentBuilder.Build(entities); @@ -226,7 +231,7 @@ public void Build_Can_Build_Arrays() public void Build_Can_Build_CustomIEnumerables() { var entities = new Models(new[] { new Model() }); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock); + var documentBuilder = GetDocumentBuilder(); var documents = documentBuilder.Build(entities); @@ -252,7 +257,8 @@ public void DocumentBuilderOptions( .Returns(new DocumentBuilderOptions(omitNullValuedAttributes.Value)); } var pageManagerMock = new Mock(); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, null, documentBuilderOptionsProvider: omitNullValuedAttributes.HasValue ? documentBuilderBehaviourMock.Object : null); + var requestManagerMock = new Mock(); + var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, requestManagerMock.Object, documentBuilderOptionsProvider: omitNullValuedAttributes.HasValue ? documentBuilderBehaviourMock.Object : null); var document = documentBuilder.Build(new Model() { StringProperty = attributeValue }); Assert.Equal(resultContainsAttribute, document.Data.Attributes.ContainsKey("StringProperty")); @@ -332,7 +338,7 @@ public void Build_Will_Use_Resource_If_Defined_For_Single_Document() .AddSingleton(resourceGraph) .BuildServiceProvider()); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(scopedServiceProvider); var documents = documentBuilder.Build(entity); @@ -355,7 +361,7 @@ public void Build_Will_Use_Instance_Specific_Resource_If_Defined_For_Multiple_Do .AddSingleton(resourceGraph) .BuildServiceProvider()); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(scopedServiceProvider); var documents = documentBuilder.Build(entities); @@ -379,7 +385,7 @@ public void Build_Will_Use_Instance_Specific_Resource_If_Defined_For_Single_Docu .AddSingleton(resourceGraph) .BuildServiceProvider()); - var documentBuilder = GetDocumentBuilder(_jsonApiContextMock, scopedServiceProvider: scopedServiceProvider); + var documentBuilder = GetDocumentBuilder(scopedServiceProvider); var documents = documentBuilder.Build(entity); @@ -412,20 +418,17 @@ public UserResource(IResourceGraph graph) : base(graph) protected override List OutputAttrs() => Remove(user => user.Password); } - private DocumentBuilder GetDocumentBuilder(Mock jaContextMock = null, TestScopedServiceProvider scopedServiceProvider = null) + private DocumentBuilder GetDocumentBuilder(TestScopedServiceProvider scopedServiceProvider = null, IPageManager pageManager = null) { var pageManagerMock = new Mock(); var rmMock = new Mock(); rmMock.SetupGet(rm => rm.BasePath).Returns("Localhost"); - if (jaContextMock != null) - { - return new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); - } - else + if (pageManager != null) { - return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); + return new DocumentBuilder(_jsonApiContextMock.Object, pageManager, rmMock.Object, scopedServiceProvider: scopedServiceProvider); } + return new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); } } } diff --git a/test/UnitTests/Builders/LinkBuilderTests.cs b/test/UnitTests/Builders/LinkBuilderTests.cs new file mode 100644 index 0000000000..ae8b3ef68e --- /dev/null +++ b/test/UnitTests/Builders/LinkBuilderTests.cs @@ -0,0 +1,49 @@ +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; +using Moq; +using Xunit; + +namespace UnitTests +{ + public class LinkBuilderTests + { + private readonly Mock _requestManagerMock = new Mock(); + private const string _host = "http://www.example.com"; + + + public LinkBuilderTests() + { + _requestManagerMock.Setup(m => m.BasePath).Returns(_host); + _requestManagerMock.Setup(m => m.GetContextEntity()).Returns(new ContextEntity { EntityName = "articles" }); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void GetPageLink_GivenRelativeConfiguration_ReturnsExpectedPath(bool isRelative) + { + //arrange + var options = new JsonApiOptions { RelativeLinks = isRelative }; + var linkBuilder = new LinkBuilder(options, _requestManagerMock.Object); + var pageSize = 10; + var pageOffset = 20; + var expectedLink = $"/articles?page[size]={pageSize}&page[number]={pageOffset}"; + + // act + var link = linkBuilder.GetPageLink(pageOffset, pageSize); + + // assert + if (isRelative) + { + Assert.Equal(expectedLink, link); + } else + { + Assert.Equal(_host + expectedLink, link); + } + } + + /// todo: write tests for remaining linkBuilder methods + } +} diff --git a/test/UnitTests/Builders/LinkBuilder_Tests.cs b/test/UnitTests/Builders/LinkBuilder_Tests.cs deleted file mode 100644 index 1e9fdddda3..0000000000 --- a/test/UnitTests/Builders/LinkBuilder_Tests.cs +++ /dev/null @@ -1,49 +0,0 @@ -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Services; -using Microsoft.AspNetCore.Http; -using Moq; -using Xunit; - -namespace UnitTests -{ - public class LinkBuilder_Tests - { - [Theory] - [InlineData("http", "localhost", "/api/v1/articles", false, "http://localhost/api/v1")] - [InlineData("https", "localhost", "/api/v1/articles", false, "https://localhost/api/v1")] - [InlineData("http", "example.com", "/api/v1/articles", false, "http://example.com/api/v1")] - [InlineData("https", "example.com", "/api/v1/articles", false, "https://example.com/api/v1")] - [InlineData("https", "example.com", "/articles", false, "https://example.com")] - [InlineData("https", "example.com", "/articles", true, "")] - [InlineData("https", "example.com", "/api/v1/articles", true, "/api/v1")] - public void GetBasePath_Returns_Path_Before_Resource(string scheme, - string host, string path, bool isRelative, string expectedPath) - { - // arrange - //const string resource = "articles"; - //var jsonApiContextMock = new Mock(); - //jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions - //{ - // RelativeLinks = isRelative - //}); - - //var requestMock = new Mock(); - //requestMock.Setup(m => m.Scheme).Returns(scheme); - //requestMock.Setup(m => m.Host).Returns(new HostString(host)); - //requestMock.Setup(m => m.Path).Returns(new PathString(path)); - - //var contextMock = new Mock(); - //contextMock.Setup(m => m.Request).Returns(requestMock.Object); - - //var linkBuilder = new LinkBuilder(jsonApiContextMock.Object); - - //// act - //var basePath = linkBuilder.GetBasePath(contextMock.Object, resource); - - //// assert - //Assert.Equal(expectedPath, basePath); - Assert.False(true); - } - } -} diff --git a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs index d6c5e951b8..14bf06e3d5 100644 --- a/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs +++ b/test/UnitTests/Controllers/BaseJsonApiController_Tests.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using JsonApiDotNetCore.Internal.Contracts; +using System.IO; namespace UnitTests { @@ -241,11 +242,10 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateDisabled() // arrange var resource = new Resource(); var serviceMock = new Mock>(); - //_resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); - //_resourceGraph.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = false }); var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = false }, _resourceGraph.Object, create: serviceMock.Object); - serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext { HttpContext = new DefaultHttpContext() }; + serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); + // act var response = await controller.PostAsync(resource); @@ -261,12 +261,12 @@ public async Task PostAsync_ModelStateInvalid_ValidateModelStateEnabled() // arrange var resource = new Resource(); var serviceMock = new Mock>(); - //_resourceGraph.SetupGet(a => a.ResourceGraph).Returns(_resourceGraphMock.Object); _resourceGraphMock.Setup(a => a.GetPublicAttributeName("TestAttribute")).Returns("test-attribute"); - //_resourceGraph.Setup(a => a.ApplyContext(It.IsAny>())).Returns(_resourceGraph.Object); - //_resourceGraph.SetupGet(a => a.Options).Returns(new JsonApiOptions { ValidateModelState = true }); - var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = false }, _resourceGraph.Object, create: serviceMock.Object); + var controller = new BaseJsonApiController(new JsonApiOptions { ValidateModelState = true }, _resourceGraph.Object, create: serviceMock.Object); + controller.ControllerContext = new Microsoft.AspNetCore.Mvc.ControllerContext { HttpContext = new DefaultHttpContext() }; controller.ModelState.AddModelError("TestAttribute", "Failed Validation"); + serviceMock.Setup(m => m.CreateAsync(It.IsAny())).ReturnsAsync(resource); + // act var response = await controller.PostAsync(resource); diff --git a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs index 41d546faf6..3da8339437 100644 --- a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs +++ b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs @@ -19,6 +19,7 @@ using System.Threading.Tasks; using JsonApiDotNetCore.Internal.Contracts; + namespace UnitTests.Extensions { public class IServiceCollectionExtensionsTests @@ -42,7 +43,7 @@ public void AddJsonApiInternals_Adds_All_Required_Services() // assert Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService(typeof(IEntityRepository))); - Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); diff --git a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs index 5eae120747..8b2f61b5a7 100644 --- a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs +++ b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs @@ -22,6 +22,7 @@ namespace UnitTests.ResourceHooks { public class HooksDummyData { + protected IResourceGraph _graph; protected ResourceHook[] NoHooks = new ResourceHook[0]; protected ResourceHook[] EnableDbValues = { ResourceHook.BeforeUpdate, ResourceHook.BeforeUpdateRelationship }; protected ResourceHook[] DisableDbValues = new ResourceHook[0]; @@ -34,15 +35,15 @@ public class HooksDummyData protected readonly Faker _passportFaker; public HooksDummyData() { - new ResourceGraphBuilder() - .AddResource() - .AddResource() - .AddResource() - .AddResource
() - .AddResource() - .AddResource() - .AddResource() - .Build(); + _graph = new ResourceGraphBuilder() + .AddResource() + .AddResource() + .AddResource() + .AddResource
() + .AddResource() + .AddResource() + .AddResource() + .Build(); _todoFaker = new Faker().Rules((f, i) => i.Id = f.UniqueIndex + 1); _personFaker = new Faker().Rules((f, i) => i.Id = f.UniqueIndex + 1); @@ -136,28 +137,32 @@ protected List CreateTodoWithOwner() public class HooksTestsSetup : HooksDummyData { - (Mock, Mock, Mock, Mock) CreateMocks() + (IResourceGraph, Mock, Mock, IJsonApiOptions) CreateMocks() { var pfMock = new Mock(); - var rgMock = new Mock(); + var graph = _graph; var rqMock = new Mock(); - var optionsMock = new Mock(new JsonApiOptions { LoadDatabaseValues = false }); - return (rgMock, rqMock, pfMock, optionsMock); + var optionsMock = new JsonApiOptions { LoadDatabaseValues = false }; + return (graph, rqMock, pfMock, optionsMock); } - internal (Mock requestManagerMock, ResourceHookExecutor, Mock>) CreateTestObjects(IHooksDiscovery discovery = null) + internal (Mock requestManagerMock, ResourceHookExecutor, Mock>) CreateTestObjects(IHooksDiscovery mainDiscovery = null) where TMain : class, IIdentifiable { // creates the resource definition mock and corresponding ImplementedHooks discovery instance - var mainResource = CreateResourceDefinition(discovery); + var mainResource = CreateResourceDefinition(mainDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - var (rgMock, rqMock, gpfMock, optionsMock) = CreateMocks(); + var (graph, rqMock, gpfMock, options) = CreateMocks(); + + + var traversalHelper = new TraversalHelper(graph, rqMock.Object); + + SetupProcessorFactoryForResourceDefinition(gpfMock, mainResource.Object, mainDiscovery, null); - var traversalHelper = new TraversalHelper(rgMock.Object, rqMock.Object); - var meta = new HookExecutorHelper(gpfMock.Object, rgMock.Object, optionsMock.Object); - var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, rgMock.Object, rqMock.Object); + var meta = new HookExecutorHelper(gpfMock.Object, graph, options); + var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, graph, rqMock.Object); return (rqMock, hookExecutor, mainResource); } @@ -176,15 +181,15 @@ public class HooksTestsSetup : HooksDummyData var nestedResource = CreateResourceDefinition(nestedDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - var (rgMock, rqMock, gpfMock, optionsMock) = CreateMocks(); + var (graph, rqMock, gpfMock, options) = CreateMocks(); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; - var traversalHelper = new TraversalHelper(rgMock.Object, rqMock.Object); + var traversalHelper = new TraversalHelper(graph, rqMock.Object); SetupProcessorFactoryForResourceDefinition(gpfMock, mainResource.Object, mainDiscovery, dbContext); SetupProcessorFactoryForResourceDefinition(gpfMock, nestedResource.Object, nestedDiscovery, dbContext); - var meta = new HookExecutorHelper(gpfMock.Object, ResourceGraph.Instance, optionsMock.Object); - var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, ResourceGraph.Instance, rqMock.Object); + var meta = new HookExecutorHelper(gpfMock.Object, graph, options); + var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, graph, rqMock.Object); return (rqMock, hookExecutor, mainResource, nestedResource); } @@ -206,8 +211,8 @@ public class HooksTestsSetup : HooksDummyData var secondNestedResource = CreateResourceDefinition(secondNestedDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - var (_, rqMock, gpfMock, optionsMock) = CreateMocks(); - var traversalHelper = new TraversalHelper(ResourceGraph.Instance, rqMock.Object); + var (graph, rqMock, gpfMock, options) = CreateMocks(); + var traversalHelper = new TraversalHelper(graph, rqMock.Object); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; @@ -215,8 +220,8 @@ public class HooksTestsSetup : HooksDummyData SetupProcessorFactoryForResourceDefinition(gpfMock, firstNestedResource.Object, firstNestedDiscovery, dbContext); SetupProcessorFactoryForResourceDefinition(gpfMock, secondNestedResource.Object, secondNestedDiscovery, dbContext); - var hookExecutorHelper = new HookExecutorHelper(gpfMock.Object, ResourceGraph.Instance, optionsMock.Object); - var hookExecutor = new ResourceHookExecutor(hookExecutorHelper, traversalHelper, ResourceGraph.Instance, rqMock.Object); + var hookExecutorHelper = new HookExecutorHelper(gpfMock.Object, graph, options); + var hookExecutor = new ResourceHookExecutor(hookExecutorHelper, traversalHelper, graph, rqMock.Object); return (rqMock, hookExecutor, mainResource, firstNestedResource, secondNestedResource); } @@ -333,7 +338,7 @@ void SetupProcessorFactoryForResourceDefinition( var idType = TypeHelper.GetIdentifierType(); if (idType == typeof(int)) { - IEntityReadRepository repo = CreateTestRepository(dbContext); + IEntityReadRepository repo = CreateTestRepository(dbContext, new Mock().Object); processorFactory.Setup(c => c.GetProcessor>(typeof(IEntityReadRepository<,>), typeof(TModel), typeof(int))).Returns(repo); } else diff --git a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs index 5f746508af..57e0d98497 100644 --- a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs @@ -29,6 +29,7 @@ public JsonApiDeSerializerTests() _jsonApiContextMock.Setup(m => m.RequestManager).Returns(_requestManagerMock.Object); var resourceGraphBuilder = new ResourceGraphBuilder(); resourceGraphBuilder.AddResource("test-resource"); + resourceGraphBuilder.AddResource("test-resource-with-list"); resourceGraphBuilder.AddResource("independents"); resourceGraphBuilder.AddResource("dependents"); var resourceGraph = resourceGraphBuilder.Build(); @@ -79,7 +80,7 @@ public void Can_Deserialize_Complex_List_Types() { Data = new ResourceObject { - Type = "test-resource", + Type = "test-resource-with-list", Id = "1", Attributes = new Dictionary { @@ -103,6 +104,8 @@ public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs() // arrange var jsonApiOptions = new JsonApiOptions(); jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); // <-- + _jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object); var content = new Document @@ -133,7 +136,7 @@ public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() { // arrange var attributesToUpdate = new Dictionary(); - _requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(new Dictionary()); + _requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(attributesToUpdate); var jsonApiOptions = new JsonApiOptions(); jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); _jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); diff --git a/test/UnitTests/Serialization/JsonApiSerializerTests.cs b/test/UnitTests/Serialization/JsonApiSerializerTests.cs index fc3b716bf3..8a1afdebe4 100644 --- a/test/UnitTests/Serialization/JsonApiSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiSerializerTests.cs @@ -77,7 +77,7 @@ public void Can_Serialize_Deeply_Nested_Relationships() var serializer = GetSerializer( resourceGraphBuilder, - included: new List { "children.infections" } + new List { "children.infections" } ); var resource = new TestResource @@ -211,16 +211,17 @@ private JsonApiSerializer GetSerializer( List included = null) { var resourceGraph = resourceGraphBuilder.Build(); - + var requestManagerMock = new Mock(); + requestManagerMock.Setup(m => m.GetContextEntity()).Returns(resourceGraph.GetContextEntity("test-resource")); + requestManagerMock.Setup(m => m.IncludedRelationships).Returns(included); var jsonApiContextMock = new Mock(); jsonApiContextMock.SetupAllProperties(); jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); jsonApiContextMock.Setup(m => m.RequestEntity).Returns(resourceGraph.GetContextEntity("test-resource")); - // jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); - // jsonApiContextMock.Setup(m => m.RelationshipsToUpdate).Returns(new Dictionary()); - // jsonApiContextMock.Setup(m => m.HasManyRelationshipPointers).Returns(new HasManyRelationshipPointers()); - // jsonApiContextMock.Setup(m => m.HasOneRelationshipPointers).Returns(new HasOneRelationshipPointers()); + jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); + + jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); var pmMock = new Mock(); jsonApiContextMock.Setup(m => m.PageManager).Returns(pmMock.Object); @@ -240,7 +241,7 @@ private JsonApiSerializer GetSerializer( var provider = services.BuildServiceProvider(); var scoped = new TestScopedServiceProvider(provider); - var documentBuilder = GetDocumentBuilder(jsonApiContextMock, scopedServiceProvider: scoped); + var documentBuilder = GetDocumentBuilder(jsonApiContextMock, requestManagerMock.Object, scopedServiceProvider: scoped); var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); return serializer; @@ -271,12 +272,11 @@ private class InfectionResource : Identifiable [HasOne("infected")] public ChildResource Infected { get; set; } } - private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, TestScopedServiceProvider scopedServiceProvider = null) + private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, IRequestManager requestManager, TestScopedServiceProvider scopedServiceProvider = null) { var pageManagerMock = new Mock(); - var rmMock = new Mock(); - return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); + return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, requestManager, scopedServiceProvider: scopedServiceProvider); } } diff --git a/test/UnitTests/Services/QueryParserTests.cs b/test/UnitTests/Services/QueryParserTests.cs index ba45c0719d..f8f091f6ae 100644 --- a/test/UnitTests/Services/QueryParserTests.cs +++ b/test/UnitTests/Services/QueryParserTests.cs @@ -142,7 +142,7 @@ public void Parse_EmptySortSegment_ReceivesJsonApiException(string stringSortQue .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); // Act / Assert var exception = Assert.Throws(() => diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index 8494f476a9..120249c9ea 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -8,12 +8,13 @@ + - + From 0d212549354dc0e4596ec90b0bf6832bad42cf66 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Tue, 3 Sep 2019 12:19:15 +0200 Subject: [PATCH 13/26] fix: removed outdated authorization test --- .../Controllers/BaseJsonApiController.cs | 2 +- .../Middleware/RequestMiddleware.cs | 35 +----- .../Extensibility/RepositoryOverrideTests.cs | 102 ------------------ .../Helpers/Startups/AuthorizedStartup.cs | 56 ---------- .../JsonApiDotNetCoreExampleTests.csproj | 7 ++ 5 files changed, 11 insertions(+), 191 deletions(-) delete mode 100644 test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/Helpers/Startups/AuthorizedStartup.cs diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index 3a076430d7..1194bca652 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -1,4 +1,4 @@ -using System; + using System; using System.Collections.Generic; using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 25147cb22c..3cd3f52094 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -90,37 +90,8 @@ protected void HandleUriParameters() protected bool PathIsRelationship() { - string requestPath = _httpContext.Request.Path.Value; - // while(!Debugger.IsAttached) { Thread.Sleep(1000); } - const string relationships = "relationships"; - const char pathSegmentDelimiter = '/'; - - var span = requestPath.AsSpan(); - - // we need to iterate over the string, from the end, - // checking whether or not the 2nd to last path segment - // is "relationships" - // -2 is chosen in case the path ends with '/' - for (var i = requestPath.Length - 2; i >= 0; i--) - { - // if there are not enough characters left in the path to - // contain "relationships" - if (i < relationships.Length) - return false; - - // we have found the first instance of '/' - if (span[i] == pathSegmentDelimiter) - { - // in the case of a "relationships" route, the next - // path segment will be "relationships" - return ( - span.Slice(i - relationships.Length, relationships.Length) - .SequenceEqual(relationships.AsSpan()) - ); - } - } - - return false; + var actionName = (string)_httpContext.GetRouteData().Values["action"]; + return actionName.ToLower().Contains("relationships"); } private string GetBasePath(string entityName) { @@ -208,7 +179,7 @@ private bool IsValidAcceptHeader(HttpContext context) return true; } - internal bool ContainsMediaTypeParameters(string mediaType) + internal static bool ContainsMediaTypeParameters(string mediaType) { var incomingMediaTypeSpan = mediaType.AsSpan(); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs deleted file mode 100644 index d63575e263..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RepositoryOverrideTests.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCoreExample.Data; -using JsonApiDotNetCoreExample.Models; -using JsonApiDotNetCoreExampleTests.Services; -using JsonApiDotNetCoreExampleTests.Startups; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Xunit; - -namespace JsonApiDotNetCoreExampleTests.Acceptance.Extensibility -{ - [Collection("WebHostCollection")] - public class RepositoryOverrideTests - { - private TestFixture _fixture; - - public RepositoryOverrideTests(TestFixture fixture) - { - _fixture = fixture; - } - - [Fact] - public async Task Total_Record_Count_Included() - { - // arrange - var builder = new WebHostBuilder() - .UseStartup(); - var server = new TestServer(builder); - var client = server.CreateClient(); - var context = (AppDbContext)server.Host.Services.GetService(typeof(AppDbContext)); - var jsonApiContext = (IJsonApiContext)server.Host.Services.GetService(typeof(IJsonApiContext)); - - var person = new Person(); - context.People.Add(person); - var ownedTodoItem = new TodoItem(); - var unOwnedTodoItem = new TodoItem(); - ownedTodoItem.Owner = person; - context.TodoItems.Add(ownedTodoItem); - context.TodoItems.Add(unOwnedTodoItem); - context.SaveChanges(); - - var authService = (IAuthorizationService)server.Host.Services.GetService(typeof(IAuthorizationService)); - authService.CurrentUserId = person.Id; - - var httpMethod = new HttpMethod("GET"); - var route = $"/api/v1/todo-items?include=owner"; - - var request = new HttpRequestMessage(httpMethod, route); - - // act - var response = await client.SendAsync(request); - var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(responseBody); - - // assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - foreach(var item in deserializedBody) - Assert.Equal(person.Id, item.Owner.Id); - } - - [Fact] - public async Task Sparse_Fields_Works_With_Get_Override() - { - // arrange - var builder = new WebHostBuilder() - .UseStartup(); - var server = new TestServer(builder); - var client = server.CreateClient(); - var context = (AppDbContext)server.Host.Services.GetService(typeof(AppDbContext)); - var jsonApiContext = (IJsonApiContext)server.Host.Services.GetService(typeof(IJsonApiContext)); - - var person = new Person(); - context.People.Add(person); - var todoItem = new TodoItem(); - todoItem.Owner = person; - context.TodoItems.Add(todoItem); - context.SaveChanges(); - - var authService = (IAuthorizationService)server.Host.Services.GetService(typeof(IAuthorizationService)); - authService.CurrentUserId = person.Id; - - var httpMethod = new HttpMethod("GET"); - var route = $"/api/v1/todo-items/{todoItem.Id}?fields[todo-items]=description"; - - var request = new HttpRequestMessage(httpMethod, route); - - // act - var response = await client.SendAsync(request); - var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().Deserialize(responseBody); - - // assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(todoItem.Description, deserializedBody.Description); - - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/AuthorizedStartup.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/AuthorizedStartup.cs deleted file mode 100644 index 12a207fea8..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/AuthorizedStartup.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using JsonApiDotNetCoreExample.Data; -using Microsoft.EntityFrameworkCore; -using JsonApiDotNetCore.Extensions; -using System; -using JsonApiDotNetCoreExample; -using Moq; -using JsonApiDotNetCoreExampleTests.Services; -using JsonApiDotNetCore.Data; -using JsonApiDotNetCoreExample.Models; -using JsonApiDotNetCoreExampleTests.Repositories; -using UnitTests; -using JsonApiDotNetCore.Services; - -namespace JsonApiDotNetCoreExampleTests.Startups -{ - public class AuthorizedStartup : Startup - { - public AuthorizedStartup(IHostingEnvironment env) - : base(env) - { } - - public override IServiceProvider ConfigureServices(IServiceCollection services) - { - var loggerFactory = new LoggerFactory(); - - loggerFactory.AddConsole(); - - services.AddSingleton(loggerFactory); - - services.AddDbContext(options => - { - options.UseNpgsql(GetDbConnectionString()); - }, ServiceLifetime.Transient); - - services.AddJsonApi(opt => - { - opt.Namespace = "api/v1"; - opt.DefaultPageSize = 5; - opt.IncludeTotalRecordCount = true; - }); - - // custom authorization implementation - var authServicMock = new Mock(); - authServicMock.SetupAllProperties(); - services.AddSingleton(authServicMock.Object); - services.AddScoped, AuthorizedTodoItemsRepository>(); - - services.AddScoped(); - - return services.BuildServiceProvider(); - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj b/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj index fb3fac898d..91471ee7c0 100644 --- a/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj +++ b/test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj @@ -30,4 +30,11 @@ + + + + + + + From ab027662954c3e3b0e4e717d423e10fe34e9ae56 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Tue, 3 Sep 2019 12:26:26 +0200 Subject: [PATCH 14/26] fix: requestmeta tests --- .../Extensibility/RequestMetaTests.cs | 3 ++- .../Helpers/Startups/MetaStartup.cs | 21 ++----------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs index 4f9198619a..d69280e7e7 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/RequestMetaTests.cs @@ -40,7 +40,8 @@ public async Task Injecting_IRequestMeta_Adds_Meta_Data() // act var response = await client.SendAsync(request); - var documents = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + var body = await response.Content.ReadAsStringAsync(); + var documents = JsonConvert.DeserializeObject(body); // assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/MetaStartup.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/MetaStartup.cs index 6bc5a08016..1aa7614a7e 100644 --- a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/MetaStartup.cs +++ b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/MetaStartup.cs @@ -1,9 +1,5 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using JsonApiDotNetCoreExample.Data; -using Microsoft.EntityFrameworkCore; -using JsonApiDotNetCore.Extensions; using System; using JsonApiDotNetCoreExample; using JsonApiDotNetCore.Services; @@ -19,21 +15,8 @@ public MetaStartup(IHostingEnvironment env) public override IServiceProvider ConfigureServices(IServiceCollection services) { - var loggerFactory = new LoggerFactory(); - loggerFactory.AddConsole(LogLevel.Warning); - - services - .AddSingleton(loggerFactory) - .AddDbContext(options => - options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient) - .AddJsonApi(options => { - options.Namespace = "api/v1"; - options.DefaultPageSize = 5; - options.IncludeTotalRecordCount = true; - }) - .AddScoped(); - - return services.BuildServiceProvider(); + services.AddScoped(); + return base.ConfigureServices(services); } } } From 7246ca992928fa6eb5a04252266a7f2efba6ff5f Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 5 Sep 2019 10:55:27 +0200 Subject: [PATCH 15/26] fix: some acceptance tests --- .../Data/DefaultEntityRepository.cs | 4 +-- .../Internal/Contracts/IResourceGraph.cs | 1 - .../Internal/ResourceGraph.cs | 1 - .../Serialization/JsonApiDeSerializer.cs | 13 +++++-- .../Services/EntityResourceService.cs | 1 - .../Acceptance/Spec/AttributeFilterTests.cs | 2 ++ .../Acceptance/Spec/CreatingDataTests.cs | 8 +++-- .../Startups/ClientGeneratedIdsStartup.cs | 34 +++++++++---------- 8 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 54f9749629..112db1c352 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -75,7 +75,7 @@ public virtual IQueryable Select(IQueryable entities, List public virtual IQueryable Filter(IQueryable entities, FilterQuery filterQuery) { @@ -87,7 +87,7 @@ public virtual IQueryable Filter(IQueryable entities, FilterQu return defaultQueryFilter(entities, filterQuery); } } - return entities; + return entities.Filter(new AttrFilterQuery(_requestManager, _jsonApiContext.ResourceGraph, filterQuery)); } /// diff --git a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs index 39710ce616..5d2780d607 100644 --- a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs @@ -86,7 +86,6 @@ public interface IResourceGraph /// Was built against an EntityFrameworkCore DbContext ? ///
bool UsesDbContext { get; } - List IncludedRelationships { get; set; } ContextEntity GetEntityFromControllerName(string pathParsed); } diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index 770661d3e4..8e7499db39 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -56,7 +56,6 @@ internal ResourceGraph(List entities, bool usesDbContext, List public bool UsesDbContext { get; } - public List IncludedRelationships { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } /// public ContextEntity GetContextEntity(string entityName) diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index 7dbc59dc04..ba0f33f768 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -259,7 +259,12 @@ private void SetHasOneForeignKeyValue(object entity, HasOneAttribute hasOneAttr, /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - if (convertedValue == null) _jsonApiContext.HasOneRelationshipPointers.Add(hasOneAttr, null); + if (convertedValue == null) + { + _jsonApiContext.RequestManager.GetUpdatedRelationships()[hasOneAttr] = null; + //_jsonApiContext.HasOneRelationshipPointers.Add(hasOneAttr, null); + } + } } @@ -284,7 +289,8 @@ private void SetHasOneNavigationPropertyValue(object entity, HasOneAttribute has /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - _jsonApiContext.HasOneRelationshipPointers.Add(hasOneAttr, null); + _jsonApiContext.RequestManager.GetUpdatedRelationships()[hasOneAttr] = null; + } } @@ -316,7 +322,8 @@ private object SetHasManyRelationship(object entity, /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - _jsonApiContext.HasManyRelationshipPointers.Add(attr, null); + _jsonApiContext.RequestManager.GetUpdatedRelationships()[attr] = null; + } return entity; diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index d783d2df47..e2d1f8c856 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -269,7 +269,6 @@ protected virtual IQueryable ApplySortAndFilterQuery(IQueryable protected virtual IQueryable IncludeRelationships(IQueryable entities, List relationships) { - _resourceGraph.IncludedRelationships = relationships; foreach (var r in relationships) { diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs index 591dfa4c7f..370960ee29 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs @@ -215,6 +215,8 @@ public async Task Can_Filter_On_Not_In_Array_Values() { // arrange var context = _fixture.GetService(); + context.TodoItems.RemoveRange(context.TodoItems); + context.SaveChanges(); var todoItems = _todoItemFaker.Generate(5); var guids = new List(); var notInGuids = new List(); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs index 559a8562d3..0811699b9c 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs @@ -235,9 +235,11 @@ public async Task Can_Create_And_Set_HasMany_Relationships() var context = _fixture.GetService(); - var owner = new JsonApiDotNetCoreExample.Models.Person(); - var todoItem = new TodoItem(); - todoItem.Owner = owner; + var owner = new Person(); + var todoItem = new TodoItem + { + Owner = owner + }; context.People.Add(owner); context.TodoItems.Add(todoItem); await context.SaveChangesAsync(); diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs index 32d8186802..1f765e1b1e 100644 --- a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs +++ b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs @@ -6,6 +6,7 @@ using JsonApiDotNetCore.Extensions; using System; using JsonApiDotNetCoreExample; +using System.Reflection; namespace JsonApiDotNetCoreExampleTests.Startups { @@ -18,25 +19,24 @@ public ClientGeneratedIdsStartup(IHostingEnvironment env) public override IServiceProvider ConfigureServices(IServiceCollection services) { var loggerFactory = new LoggerFactory(); - - loggerFactory.AddConsole(); - - services.AddSingleton(loggerFactory); - - services.AddDbContext(options => - { - options.UseNpgsql(GetDbConnectionString()); - }, ServiceLifetime.Transient); - - services.AddJsonApi(opt => - { - opt.Namespace = "api/v1"; - opt.DefaultPageSize = 5; - opt.IncludeTotalRecordCount = true; - opt.AllowClientGeneratedIds = true; - }); + loggerFactory.AddConsole(LogLevel.Warning); + var mvcBuilder = services.AddMvcCore(); + services + .AddSingleton(loggerFactory) + .AddDbContext(options => options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient) + .AddJsonApi(options => { + options.Namespace = "api/v1"; + options.DefaultPageSize = 5; + options.IncludeTotalRecordCount = true; + options.EnableResourceHooks = true; + options.LoadDatabaseValues = true; + options.AllowClientGeneratedIds = true; + }, + mvcBuilder, + discovery => discovery.AddAssembly(Assembly.Load(nameof(JsonApiDotNetCoreExample)))); return services.BuildServiceProvider(); + } } } From c177659aca07837cfcc3cda2947bed56975d7407 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 5 Sep 2019 11:58:32 +0200 Subject: [PATCH 16/26] fix: pagination --- src/JsonApiDotNetCore/Internal/PageManager.cs | 12 ++---------- src/JsonApiDotNetCore/Internal/Query/PageQuery.cs | 4 ++-- .../Middleware/RequestMiddleware.cs | 12 +++++++++--- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/JsonApiDotNetCore/Internal/PageManager.cs b/src/JsonApiDotNetCore/Internal/PageManager.cs index 6e9d3e6a49..9efd2fdd6c 100644 --- a/src/JsonApiDotNetCore/Internal/PageManager.cs +++ b/src/JsonApiDotNetCore/Internal/PageManager.cs @@ -15,20 +15,12 @@ public PageManager(ILinkBuilder linkBuilder, IJsonApiOptions options, IRequestMa { _linkBuilder = linkBuilder; _options = options; - if (requestManager.QuerySet != null) - { - PageSize = requestManager.QuerySet?.PageQuery.PageSize != null ? requestManager.QuerySet.PageQuery.PageSize : _options.DefaultPageSize; - } - else - { - PageSize = _options.DefaultPageSize; - } - DefaultPageSize = _options.DefaultPageSize; + PageSize = _options.DefaultPageSize; } public int? TotalRecords { get; set; } public int PageSize { get; set; } - public int DefaultPageSize { get; set; } + public int DefaultPageSize { get; set; } // I think we shouldnt expose this public int CurrentPage { get; set; } public bool IsPaginated => PageSize > 0; public int TotalPages => (TotalRecords == null) ? -1 : (int)Math.Ceiling(decimal.Divide(TotalRecords.Value, PageSize)); diff --git a/src/JsonApiDotNetCore/Internal/Query/PageQuery.cs b/src/JsonApiDotNetCore/Internal/Query/PageQuery.cs index 7c09d4c386..eb44cae170 100644 --- a/src/JsonApiDotNetCore/Internal/Query/PageQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/PageQuery.cs @@ -2,7 +2,7 @@ namespace JsonApiDotNetCore.Internal.Query { public class PageQuery { - public int PageSize { get; set; } - public int PageOffset { get; set; } = 1; + public int? PageSize { get; set; } + public int? PageOffset { get; set; } = 1; } } \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 3cd3f52094..7b30540d3a 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -23,10 +23,11 @@ public class RequestMiddleware private readonly RequestDelegate _next; private IResourceGraph _resourceGraph; private HttpContext _httpContext; - private IJsonApiOptions _options; private IJsonApiContext _jsonApiContext; private IRequestManager _requestManager; + private IPageManager _pageManager; private IQueryParser _queryParser; + private IJsonApiOptions _options; public RequestMiddleware(RequestDelegate next) { @@ -37,6 +38,7 @@ public async Task Invoke(HttpContext httpContext, IJsonApiContext jsonApiContext, IResourceGraph resourceGraph, IRequestManager requestManager, + IPageManager pageManager, IQueryParser queryParser, IJsonApiOptions options) { @@ -44,6 +46,7 @@ public async Task Invoke(HttpContext httpContext, _jsonApiContext = jsonApiContext; _resourceGraph = resourceGraph; _requestManager = requestManager; + _pageManager = pageManager; _queryParser = queryParser; _options = options; @@ -74,7 +77,7 @@ public async Task Invoke(HttpContext httpContext, } } /// - /// Parses the uri, and helps you out + /// Parses the uri /// /// /// @@ -83,7 +86,10 @@ protected void HandleUriParameters() if (_httpContext.Request.Query.Count > 0) { //requestManager.FullQuerySet = context.Request.Query; - _requestManager.QuerySet = _queryParser.Parse(_httpContext.Request.Query); + var querySet = _queryParser.Parse(_httpContext.Request.Query); + _requestManager.QuerySet = querySet; //this shouldn't be exposed + _pageManager.PageSize = querySet.PageQuery.PageSize ?? _pageManager.PageSize; + _pageManager.CurrentPage = querySet.PageQuery.PageOffset ?? _pageManager.CurrentPage; _requestManager.IncludedRelationships = _requestManager.QuerySet.IncludedRelationships; } } From 857b8a9db6675430b33b72419abe29ab95d0ffab Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Fri, 6 Sep 2019 11:50:21 +0200 Subject: [PATCH 17/26] fix: total records in meta --- src/JsonApiDotNetCore/Builders/DocumentBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index 426b61c185..b2e9475db8 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -93,7 +93,7 @@ private Dictionary GetMeta(IIdentifiable entity) { var builder = _jsonApiContext.MetaBuilder; if (_jsonApiContext.Options.IncludeTotalRecordCount && _pageManager.TotalRecords != null) - builder.Add("total-records", _jsonApiContext.PageManager.TotalRecords); + builder.Add("total-records", _pageManager.TotalRecords); if (_requestMeta != null) builder.Add(_requestMeta.GetMeta()); From 4eec35a383331928c326fb25f72d130ca7d01fb3 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Fri, 6 Sep 2019 14:43:40 +0200 Subject: [PATCH 18/26] feat: introduced JsonApiActionFilter --- .../IServiceCollectionExtensions.cs | 1 + .../Middleware/JsonApiActionFilter.cs | 137 ++++++++++++++++++ .../Middleware/RequestMiddleware.cs | 98 +------------ .../Acceptance/Spec/QueryParameters.cs | 7 +- 4 files changed, 146 insertions(+), 97 deletions(-) create mode 100644 src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 61b5b329b3..4181216f2e 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -79,6 +79,7 @@ private static void AddMvcOptions(MvcOptions options, JsonApiOptions config) { options.Filters.Add(typeof(JsonApiExceptionFilter)); options.Filters.Add(typeof(TypeMatchFilter)); + options.Filters.Add(typeof(JsonApiActionFilter)); options.SerializeAsJsonApi(config); } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs new file mode 100644 index 0000000000..2c42a8f82b --- /dev/null +++ b/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs @@ -0,0 +1,137 @@ +using System; +using System.Linq; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; + +namespace JsonApiDotNetCore.Middleware +{ + public class JsonApiActionFilter : IActionFilter + { + private readonly IJsonApiContext _jsonApiContext; + private readonly IResourceGraph _resourceGraph; + private readonly IRequestManager _requestManager; + private readonly IPageManager _pageManager; + private readonly IQueryParser _queryParser; + private readonly IJsonApiOptions _options; + private HttpContext _httpContext; + public JsonApiActionFilter(IResourceGraph resourceGraph, + IRequestManager requestManager, + IPageManager pageManager, + IQueryParser queryParser, + IJsonApiOptions options) + { + _resourceGraph = resourceGraph; + _requestManager = requestManager; + _pageManager = pageManager; + _queryParser = queryParser; + _options = options; + } + + /// + /// + public void OnActionExecuting(ActionExecutingContext context) + { + _httpContext = context.HttpContext; + ContextEntity contextEntityCurrent = GetCurrentEntity(); + + // the contextEntity is null eg when we're using a non-JsonApiDotNetCore route. + if (contextEntityCurrent != null) + { + _requestManager.SetContextEntity(contextEntityCurrent); + _requestManager.BasePath = GetBasePath(contextEntityCurrent.EntityName); + HandleUriParameters(); + _requestManager.IsRelationshipPath = PathIsRelationship(); + } + + } + + + /// + /// Parses the uri + /// + protected void HandleUriParameters() + { + if (_httpContext.Request.Query.Count > 0) + { + var querySet = _queryParser.Parse(_httpContext.Request.Query); + _requestManager.QuerySet = querySet; //this shouldn't be exposed? + _pageManager.PageSize = querySet.PageQuery.PageSize ?? _pageManager.PageSize; + _pageManager.CurrentPage = querySet.PageQuery.PageOffset ?? _pageManager.CurrentPage; + _requestManager.IncludedRelationships = _requestManager.QuerySet.IncludedRelationships; + } + } + + protected bool PathIsRelationship() + { + var actionName = (string)_httpContext.GetRouteData().Values["action"]; + return actionName.ToLower().Contains("relationships"); + } + private string GetBasePath(string entityName) + { + var r = _httpContext.Request; + if (_options.RelativeLinks) + { + return GetNamespaceFromPath(r.Path, entityName); + } + else + { + return $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}"; + } + } + internal static string GetNamespaceFromPath(string path, string entityName) + { + var entityNameSpan = entityName.AsSpan(); + var pathSpan = path.AsSpan(); + const char delimiter = '/'; + for (var i = 0; i < pathSpan.Length; i++) + { + if (pathSpan[i].Equals(delimiter)) + { + var nextPosition = i + 1; + if (pathSpan.Length > i + entityNameSpan.Length) + { + var possiblePathSegment = pathSpan.Slice(nextPosition, entityNameSpan.Length); + if (entityNameSpan.SequenceEqual(possiblePathSegment)) + { + // check to see if it's the last position in the string + // or if the next character is a / + var lastCharacterPosition = nextPosition + entityNameSpan.Length; + + if (lastCharacterPosition == pathSpan.Length || pathSpan.Length >= lastCharacterPosition + 2 && pathSpan[lastCharacterPosition].Equals(delimiter)) + { + return pathSpan.Slice(0, i).ToString(); + } + } + } + } + } + + return string.Empty; + } + /// + /// Gets the current entity that we need for serialization and deserialization. + /// + /// + /// + /// + private ContextEntity GetCurrentEntity() + { + var controllerName = (string)_httpContext.GetRouteData().Values["controller"]; + return _resourceGraph.GetEntityFromControllerName(controllerName); + } + + + private bool IsJsonApiRequest(HttpRequest request) + { + return (request.ContentType?.Equals(Constants.ContentType, StringComparison.OrdinalIgnoreCase) == true); + } + + public void OnActionExecuted(ActionExecutedContext context) { /* noop */ } + } +} diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index 7b30540d3a..ecdd62d7d7 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -5,6 +5,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; @@ -16,14 +17,13 @@ namespace JsonApiDotNetCore.Middleware /// /// Can be overwritten to help you out during testing /// - /// This sets all necessary paaramters relating to the HttpContext for JADNC + /// This sets all necessary parameters relating to the HttpContext for JADNC /// public class RequestMiddleware { private readonly RequestDelegate _next; private IResourceGraph _resourceGraph; private HttpContext _httpContext; - private IJsonApiContext _jsonApiContext; private IRequestManager _requestManager; private IPageManager _pageManager; private IQueryParser _queryParser; @@ -40,10 +40,10 @@ public async Task Invoke(HttpContext httpContext, IRequestManager requestManager, IPageManager pageManager, IQueryParser queryParser, - IJsonApiOptions options) + IJsonApiOptions options + ) { _httpContext = httpContext; - _jsonApiContext = jsonApiContext; _resourceGraph = resourceGraph; _requestManager = requestManager; _pageManager = pageManager; @@ -58,100 +58,10 @@ public async Task Invoke(HttpContext httpContext, // since the JsonApiContext is using field initializers // Need to work on finding a better solution. jsonApiContext.BeginOperation(); - ContextEntity contextEntityCurrent = GetCurrentEntity(); - // the contextEntity is null eg when we're using a non-JsonApiDotNetCore route. - if (contextEntityCurrent != null) - { - requestManager.SetContextEntity(contextEntityCurrent); - // TODO: this does not need to be reset every request: we shouldn't need to rely on an external request to figure out the basepath of current application - requestManager.BasePath = GetBasePath(contextEntityCurrent.EntityName); - //Handle all querySet - HandleUriParameters(); - requestManager.IsRelationshipPath = PathIsRelationship(); - // BACKWARD COMPATIBILITY for v4 will be removed in v5 - jsonApiContext.RequestManager = requestManager; - jsonApiContext.PageManager = new PageManager(new LinkBuilder(options, requestManager), options, requestManager); - } await _next(httpContext); } } - /// - /// Parses the uri - /// - /// - /// - protected void HandleUriParameters() - { - if (_httpContext.Request.Query.Count > 0) - { - //requestManager.FullQuerySet = context.Request.Query; - var querySet = _queryParser.Parse(_httpContext.Request.Query); - _requestManager.QuerySet = querySet; //this shouldn't be exposed - _pageManager.PageSize = querySet.PageQuery.PageSize ?? _pageManager.PageSize; - _pageManager.CurrentPage = querySet.PageQuery.PageOffset ?? _pageManager.CurrentPage; - _requestManager.IncludedRelationships = _requestManager.QuerySet.IncludedRelationships; - } - } - - protected bool PathIsRelationship() - { - var actionName = (string)_httpContext.GetRouteData().Values["action"]; - return actionName.ToLower().Contains("relationships"); - } - private string GetBasePath(string entityName) - { - var r = _httpContext.Request; - if (_options.RelativeLinks) - { - return GetNamespaceFromPath(r.Path, entityName); - } - else - { - return $"{r.Scheme}://{r.Host}{GetNamespaceFromPath(r.Path, entityName)}"; - } - } - internal static string GetNamespaceFromPath(string path, string entityName) - { - var entityNameSpan = entityName.AsSpan(); - var pathSpan = path.AsSpan(); - const char delimiter = '/'; - for (var i = 0; i < pathSpan.Length; i++) - { - if (pathSpan[i].Equals(delimiter)) - { - var nextPosition = i + 1; - if (pathSpan.Length > i + entityNameSpan.Length) - { - var possiblePathSegment = pathSpan.Slice(nextPosition, entityNameSpan.Length); - if (entityNameSpan.SequenceEqual(possiblePathSegment)) - { - // check to see if it's the last position in the string - // or if the next character is a / - var lastCharacterPosition = nextPosition + entityNameSpan.Length; - - if (lastCharacterPosition == pathSpan.Length || pathSpan.Length >= lastCharacterPosition + 2 && pathSpan[lastCharacterPosition].Equals(delimiter)) - { - return pathSpan.Slice(0, i).ToString(); - } - } - } - } - } - - return string.Empty; - } - /// - /// Gets the current entity that we need for serialization and deserialization. - /// - /// - /// - /// - private ContextEntity GetCurrentEntity() - { - var controllerName = (string)_httpContext.GetRouteData().Values["controller"]; - return _resourceGraph.GetEntityFromControllerName(controllerName); - } private bool IsValid() { diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/QueryParameters.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/QueryParameters.cs index 58b2323da5..d530f70d40 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/QueryParameters.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/QueryParameters.cs @@ -35,12 +35,13 @@ public async Task Server_Returns_400_ForUnknownQueryParam() // act var response = await client.SendAsync(request); - var body = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + var body = await response.Content.ReadAsStringAsync(); + var errorCollection = JsonConvert.DeserializeObject(body); // assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - Assert.Single(body.Errors); - Assert.Equal($"[{queryKey}, {queryValue}] is not a valid query.", body.Errors[0].Title); + Assert.Single(errorCollection.Errors); + Assert.Equal($"[{queryKey}, {queryValue}] is not a valid query.", errorCollection.Errors[0].Title); } } } From d672e8e431f909500ef6a2475a9539cbcb2b62dd Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Mon, 9 Sep 2019 11:36:56 +0200 Subject: [PATCH 19/26] fix: more tests --- .../JsonApiDeserializer_Benchmarks.cs | 6 +- src/Examples/GettingStarted/Startup.cs | 5 +- .../Data/AppDbContext.cs | 4 + .../Resources/ArticleResource.cs | 2 - .../Services/CustomArticleService.cs | 4 +- .../JsonApiDotNetCoreExample/Startup.cs | 5 - src/Examples/ReportsExample/Startup.cs | 3 +- .../Startup.cs | 7 +- .../Configuration/JsonApiOptions.cs | 4 +- .../IServiceCollectionExtensions.cs | 108 +++++++++++++----- .../Formatters/JsonApiReader.cs | 9 +- .../Hooks/ResourceHookExecutor.cs | 5 +- .../Internal/ResourceGraph.cs | 16 ++- .../Middleware/JsonApiActionFilter.cs | 7 +- .../Middleware/RequestMiddleware.cs | 8 +- .../Middleware/TypeMatchFilter.cs | 11 +- .../Serialization/JsonApiDeSerializer.cs | 15 ++- .../Services/EntityResourceService.cs | 4 - .../Operations/OperationsProcessor.cs | 21 +++- .../ServiceDiscoveryFacadeTests.cs | 23 +++- .../ResourceDefinitionTests.cs | 4 - .../Startups/ClientGeneratedIdsStartup.cs | 4 +- .../ResourceHooks/ResourceHooksTestsSetup.cs | 11 +- .../Serialization/JsonApiDeSerializerTests.cs | 26 +++-- .../Operations/OperationsProcessorTests.cs | 10 +- test/UnitTests/Services/QueryParserTests.cs | 2 +- 26 files changed, 207 insertions(+), 117 deletions(-) diff --git a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs index 03b4878aa6..15478a5c52 100644 --- a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs +++ b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs @@ -4,6 +4,7 @@ using BenchmarkDotNet.Attributes.Exporters; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Services; @@ -35,6 +36,9 @@ public JsonApiDeserializer_Benchmarks() { var resourceGraphBuilder = new ResourceGraphBuilder(); resourceGraphBuilder.AddResource(TYPE_NAME); var resourceGraph = resourceGraphBuilder.Build(); + var requestManagerMock = new Mock(); + + requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(new Dictionary()); var jsonApiContextMock = new Mock(); jsonApiContextMock.SetupAllProperties(); @@ -46,7 +50,7 @@ public JsonApiDeserializer_Benchmarks() { jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - _jsonApiDeSerializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + _jsonApiDeSerializer = new JsonApiDeSerializer(jsonApiContextMock.Object, requestManagerMock.Object); } [Benchmark] diff --git a/src/Examples/GettingStarted/Startup.cs b/src/Examples/GettingStarted/Startup.cs index 5d0fa8dc91..d5c805282b 100644 --- a/src/Examples/GettingStarted/Startup.cs +++ b/src/Examples/GettingStarted/Startup.cs @@ -20,11 +20,10 @@ public void ConfigureServices(IServiceCollection services) options.UseSqlite("Data Source=sample.db"); }); - var mvcCoreBuilder = services.AddMvcCore(); + var mvcBuilder = services.AddMvcCore(); services.AddJsonApi( options => options.Namespace = "api", - mvcCoreBuilder, - discover => discover.AddCurrentAssembly()); + discover => discover.AddCurrentAssembly(), mvcBuilder); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, SampleDbContext context) diff --git a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs index d7147123f6..6a6f7994fb 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs @@ -92,5 +92,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) public DbSet ArticleTags { get; set; } public DbSet IdentifiableArticleTags { get; set; } public DbSet Tags { get; set; } + + } + + } diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs index 17dfca12f2..1e3230c759 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs @@ -5,8 +5,6 @@ using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Hooks; using JsonApiDotNetCoreExample.Models; -using Microsoft.Extensions.Logging; -using System.Security.Principal; using JsonApiDotNetCore.Internal.Contracts; namespace JsonApiDotNetCoreExample.Resources diff --git a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs index 4d486c58e5..eb05a54888 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs @@ -1,5 +1,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Data; +using JsonApiDotNetCore.Hooks; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Managers.Contracts; @@ -18,8 +19,9 @@ public CustomArticleService( IRequestManager queryManager, IPageManager pageManager, IResourceGraph resourceGraph, + IResourceHookExecutor resourceHookExecutor = null, ILoggerFactory loggerFactory = null - ) : base(repository: repository, jsonApiOptions, queryManager, pageManager, resourceGraph:resourceGraph, loggerFactory) + ) : base(repository: repository, jsonApiOptions, queryManager, pageManager, resourceGraph:resourceGraph, loggerFactory, resourceHookExecutor) { } public override async Task
GetAsync(int id) diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs index 1b3af9a1db..047161e324 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs @@ -7,9 +7,6 @@ using Microsoft.EntityFrameworkCore; using JsonApiDotNetCore.Extensions; using System; -using System.ComponentModel.Design; -using JsonApiDotNetCoreExample.Models; -using JsonApiDotNetCore.Services; namespace JsonApiDotNetCoreExample { @@ -32,7 +29,6 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) { var loggerFactory = new LoggerFactory(); loggerFactory.AddConsole(LogLevel.Warning); - var mvcBuilder = services.AddMvcCore(); services .AddSingleton(loggerFactory) .AddDbContext(options => options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient) @@ -43,7 +39,6 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) options.EnableResourceHooks = true; options.LoadDatabaseValues = true; }, - mvcBuilder, discovery => discovery.AddCurrentAssembly()); return services.BuildServiceProvider(); diff --git a/src/Examples/ReportsExample/Startup.cs b/src/Examples/ReportsExample/Startup.cs index b71b7fa74a..4f49e87db6 100644 --- a/src/Examples/ReportsExample/Startup.cs +++ b/src/Examples/ReportsExample/Startup.cs @@ -27,8 +27,7 @@ public virtual void ConfigureServices(IServiceCollection services) var mvcBuilder = services.AddMvcCore(); services.AddJsonApi( opt => opt.Namespace = "api", - mvcBuilder, - discovery => discovery.AddCurrentAssembly()); + discovery => discovery.AddCurrentAssembly(), mvcBuilder); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) diff --git a/src/Examples/ResourceEntitySeparationExample/Startup.cs b/src/Examples/ResourceEntitySeparationExample/Startup.cs index a99febfee8..f87dea6935 100644 --- a/src/Examples/ResourceEntitySeparationExample/Startup.cs +++ b/src/Examples/ResourceEntitySeparationExample/Startup.cs @@ -43,18 +43,19 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) ServiceLifetime.Transient); services.AddScoped>(); - var mvcBuilder = services.AddMvcCore(); services.AddJsonApi(options => { options.Namespace = "api/v1"; options.DefaultPageSize = 10; options.IncludeTotalRecordCount = true; - options.BuildResourceGraph((builder) => { + options.EnableResourceHooks = false; // not supported with ResourceEntitySeparation + options.BuildResourceGraph((builder) => + { builder.AddResource("courses"); builder.AddResource("departments"); builder.AddResource("students"); }); - }, mvcBuilder); + }); services.AddAutoMapper(); services.AddScoped(); diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index 456ff26263..57a4f98396 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -2,13 +2,11 @@ using System.Collections.Generic; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Graph; -using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; namespace JsonApiDotNetCore.Configuration { @@ -32,7 +30,7 @@ public class JsonApiOptions : IJsonApiOptions /// Whether or not stack traces should be serialized in Error objects ///
public static bool DisableErrorStackTraces { get; set; } - + /// /// Whether or not source URLs should be serialized in Error objects /// diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 4181216f2e..57e5788b2c 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -29,52 +29,99 @@ namespace JsonApiDotNetCore.Extensions // ReSharper disable once InconsistentNaming public static class IServiceCollectionExtensions { - public static IServiceCollection AddJsonApi(this IServiceCollection services) + static private readonly Action _noopConfig = opt => { }; + static private JsonApiOptions _options { get { return new JsonApiOptions(); } } + public static IServiceCollection AddJsonApi(this IServiceCollection services, + IMvcCoreBuilder mvcBuilder = null) where TContext : DbContext { - var mvcBuilder = services.AddMvcCore(); - return AddJsonApi(services, opt => { }, mvcBuilder); + return AddJsonApi(services, _noopConfig, mvcBuilder); } - public static IServiceCollection AddJsonApi(this IServiceCollection services, Action options) + /// + /// Enabling JsonApiDotNetCore using the EF Core DbContext to build the ResourceGraph. + /// + /// + /// + /// + /// + public static IServiceCollection AddJsonApi(this IServiceCollection services, + Action configureAction, + IMvcCoreBuilder mvcBuilder = null) where TContext : DbContext { - var mvcBuilder = services.AddMvcCore(); - return AddJsonApi(services, options, mvcBuilder); + var options = _options; + // add basic Mvc functionality + mvcBuilder = mvcBuilder ?? services.AddMvcCore(); + // set standard options + configureAction(options); + + // ResourceGraphBuilder should not be exposed on JsonApiOptions. + // Instead, ResourceGraphBuilder should consume JsonApiOptions + + // build the resource graph using ef core DbContext + options.BuildResourceGraph(builder => builder.AddDbContext()); + + // add JsonApi fitlers and serializer + mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, options)); + + // register services + AddJsonApiInternals(services, options); + return services; } - public static IServiceCollection AddJsonApi( - this IServiceCollection services, - Action options, - IMvcCoreBuilder mvcBuilder) where TContext : DbContext + + /// + /// Enabling JsonApiDotNetCore using manual declaration to build the ResourceGraph. + /// + /// + /// + /// + public static IServiceCollection AddJsonApi(this IServiceCollection services, + Action configureOptions, + IMvcCoreBuilder mvcBuilder = null) { - var config = new JsonApiOptions(); - options(config); - config.BuildResourceGraph(builder => builder.AddDbContext()); - mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, config)); - AddJsonApiInternals(services, config); + var options = _options; + mvcBuilder = mvcBuilder ?? services.AddMvcCore(); + configureOptions(options); + + // add JsonApi fitlers and serializer + mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, options)); + + // register services + AddJsonApiInternals(services, options); return services; } - public static IServiceCollection AddJsonApi( - this IServiceCollection services, - Action configureOptions, - IMvcCoreBuilder mvcBuilder, - Action autoDiscover = null) + /// + /// Enabling JsonApiDotNetCore using the EF Core DbContext to build the ResourceGraph. + /// + /// + /// + /// + /// + public static IServiceCollection AddJsonApi(this IServiceCollection services, + Action configureOptions, + Action autoDiscover, + IMvcCoreBuilder mvcBuilder = null) { - var config = new JsonApiOptions(); - configureOptions(config); + var options = _options; + mvcBuilder = mvcBuilder ?? services.AddMvcCore(); + configureOptions(options); - if (autoDiscover != null) - { - var facade = new ServiceDiscoveryFacade(services, config.ResourceGraphBuilder); - autoDiscover(facade); - } - mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, config)); - AddJsonApiInternals(services, config); + // build the resource graph using auto discovery. + var facade = new ServiceDiscoveryFacade(services, options.ResourceGraphBuilder); + autoDiscover(facade); + + // add JsonApi fitlers and serializer + mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, options)); + + // register services + AddJsonApiInternals(services, options); return services; } + private static void AddMvcOptions(MvcOptions options, JsonApiOptions config) { options.Filters.Add(typeof(JsonApiExceptionFilter)); @@ -144,7 +191,6 @@ public static void AddJsonApiInternals( services.AddScoped(typeof(IResourceService<,>), typeof(EntityResourceService<,>)); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddSingleton(jsonApiOptions); services.AddScoped(); @@ -173,7 +219,7 @@ public static void AddJsonApiInternals( services.AddTransient(typeof(IResourceHookExecutor), typeof(ResourceHookExecutor)); services.AddTransient(); } - services.AddTransient(); + //services.AddTransient(); services.AddScoped(); } diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs index 20d646d939..a8cf56a789 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading.Tasks; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Services; @@ -16,13 +17,13 @@ namespace JsonApiDotNetCore.Formatters public class JsonApiReader : IJsonApiReader { private readonly IJsonApiDeSerializer _deserializer; - private readonly IJsonApiContext _jsonApiContext; + private readonly IRequestManager _requestManager; private readonly ILogger _logger; - public JsonApiReader(IJsonApiDeSerializer deSerializer, IJsonApiContext jsonApiContext, ILoggerFactory loggerFactory) + public JsonApiReader(IJsonApiDeSerializer deSerializer, IRequestManager requestManager, ILoggerFactory loggerFactory) { _deserializer = deSerializer; - _jsonApiContext = jsonApiContext; + _requestManager = requestManager; _logger = loggerFactory.CreateLogger(); } @@ -40,7 +41,7 @@ public Task ReadAsync(InputFormatterContext context) var body = GetRequestBody(context.HttpContext.Request.Body); object model = null; - if (_jsonApiContext.IsRelationshipPath) + if (_requestManager.IsRelationshipPath) { model = _deserializer.DeserializeRelationship(body); } diff --git a/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs index 9207cc66a1..3b85e071ae 100644 --- a/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs @@ -18,22 +18,21 @@ namespace JsonApiDotNetCore.Hooks internal class ResourceHookExecutor : IResourceHookExecutor { public static readonly IdentifiableComparer Comparer = new IdentifiableComparer(); - internal readonly ITraversalHelper _traversalHelper; private readonly IRequestManager _requestManager; internal readonly IHookExecutorHelper _executorHelper; protected readonly IJsonApiContext _context; private readonly IResourceGraph _graph; + private readonly TraversalHelper _traversalHelper; public ResourceHookExecutor( IHookExecutorHelper helper, - ITraversalHelper traversalHelper, IResourceGraph resourceGraph, IRequestManager requestManager) { _requestManager = requestManager; _executorHelper = helper; _graph = resourceGraph; - _traversalHelper = traversalHelper; + _traversalHelper = new TraversalHelper(resourceGraph, requestManager); } /// diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index 8e7499db39..8cb22f1030 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -137,9 +137,19 @@ public RelationshipAttribute GetInverseRelationship(RelationshipAttribute relati public ContextEntity GetEntityFromControllerName(string controllerName) { - var resource = ControllerResourceMap.FirstOrDefault(cm => cm.ControllerName == controllerName)?.Resource; - if (resource == null) return null; - return Entities.First(e => e.EntityType == resource); + + if (ControllerResourceMap.Any()) + { + // Autodiscovery was used, so there is a well defined mapping between exposed resources and their associated controllers + var resourceType = ControllerResourceMap.FirstOrDefault(cm => cm.ControllerName == controllerName)?.Resource; + if (resourceType == null) return null; + return Entities.First(e => e.EntityType == resourceType); + + } else + { + // No autodiscovery: try to guess contextentity from controller name. + return Entities.FirstOrDefault(e => e.EntityName.ToLower().Replace("-", "") == controllerName.ToLower()); + } } } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs index 2c42a8f82b..9e6becbcde 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs @@ -46,7 +46,6 @@ public void OnActionExecuting(ActionExecutingContext context) _requestManager.SetContextEntity(contextEntityCurrent); _requestManager.BasePath = GetBasePath(contextEntityCurrent.EntityName); HandleUriParameters(); - _requestManager.IsRelationshipPath = PathIsRelationship(); } } @@ -67,11 +66,7 @@ protected void HandleUriParameters() } } - protected bool PathIsRelationship() - { - var actionName = (string)_httpContext.GetRouteData().Values["action"]; - return actionName.ToLower().Contains("relationships"); - } + private string GetBasePath(string entityName) { var r = _httpContext.Request; diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index ecdd62d7d7..a540811dfa 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -58,12 +58,18 @@ IJsonApiOptions options // since the JsonApiContext is using field initializers // Need to work on finding a better solution. jsonApiContext.BeginOperation(); + _requestManager.IsRelationshipPath = PathIsRelationship(); await _next(httpContext); } } - private bool IsValid() + protected bool PathIsRelationship() + { + var actionName = (string)_httpContext.GetRouteData().Values["action"]; + return actionName.ToLower().Contains("relationships"); + } + private bool IsValid() { return IsValidContentTypeHeader(_httpContext) && IsValidAcceptHeader(_httpContext); } diff --git a/src/JsonApiDotNetCore/Middleware/TypeMatchFilter.cs b/src/JsonApiDotNetCore/Middleware/TypeMatchFilter.cs index 64624dfa70..e3e474740e 100644 --- a/src/JsonApiDotNetCore/Middleware/TypeMatchFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/TypeMatchFilter.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; @@ -9,11 +10,11 @@ namespace JsonApiDotNetCore.Middleware { public class TypeMatchFilter : IActionFilter { - private readonly IJsonApiContext _jsonApiContext; + private readonly IResourceGraph _resourceGraph; - public TypeMatchFilter(IJsonApiContext jsonApiContext) + public TypeMatchFilter(IResourceGraph resourceGraph) { - _jsonApiContext = jsonApiContext; + _resourceGraph = resourceGraph; } /// @@ -29,10 +30,10 @@ public void OnActionExecuting(ActionExecutingContext context) if (deserializedType != null && targetType != null && deserializedType != targetType) { - var expectedJsonApiResource = _jsonApiContext.ResourceGraph.GetContextEntity(targetType); + var expectedJsonApiResource = _resourceGraph.GetContextEntity(targetType); throw new JsonApiException(409, - $"Cannot '{context.HttpContext.Request.Method}' type '{_jsonApiContext.RequestEntity.EntityName}' " + $"Cannot '{context.HttpContext.Request.Method}' type '{deserializedType.Name}' " + $"to '{expectedJsonApiResource?.EntityName}' endpoint.", detail: "Check that the request payload type matches the type expected by this endpoint."); } diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index ba0f33f768..86bd3f3016 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -6,6 +6,7 @@ using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Generics; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Services; @@ -17,10 +18,12 @@ namespace JsonApiDotNetCore.Serialization public class JsonApiDeSerializer : IJsonApiDeSerializer { private readonly IJsonApiContext _jsonApiContext; + private readonly IRequestManager _requestManager; - public JsonApiDeSerializer(IJsonApiContext jsonApiContext) + public JsonApiDeSerializer(IJsonApiContext jsonApiContext, IRequestManager requestManager) { _jsonApiContext = jsonApiContext; + _requestManager = requestManager; } public object Deserialize(string requestBody) @@ -112,7 +115,7 @@ public object DocumentToObject(ResourceObject data, List include throw new JsonApiException(422, "Failed to deserialize document as json:api."); var contextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(data.Type?.ToString()); - _jsonApiContext.RequestEntity = contextEntity ?? throw new JsonApiException(400, + if(contextEntity == null) throw new JsonApiException(400, message: $"This API does not contain a json:api resource named '{data.Type}'.", detail: "This resource is not registered on the ResourceGraph. " + "If you are using Entity Framework, make sure the DbSet matches the expected resource name. " @@ -150,7 +153,7 @@ private object SetEntityAttributes( /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - _jsonApiContext.RequestManager.GetUpdatedAttributes()[attr] = null; + _requestManager.GetUpdatedAttributes()[attr] = null; } } @@ -261,7 +264,7 @@ private void SetHasOneForeignKeyValue(object entity, HasOneAttribute hasOneAttr, /// see #512 if (convertedValue == null) { - _jsonApiContext.RequestManager.GetUpdatedRelationships()[hasOneAttr] = null; + _requestManager.GetUpdatedRelationships()[hasOneAttr] = null; //_jsonApiContext.HasOneRelationshipPointers.Add(hasOneAttr, null); } @@ -289,7 +292,7 @@ private void SetHasOneNavigationPropertyValue(object entity, HasOneAttribute has /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - _jsonApiContext.RequestManager.GetUpdatedRelationships()[hasOneAttr] = null; + _requestManager.GetUpdatedRelationships()[hasOneAttr] = null; } } @@ -322,7 +325,7 @@ private object SetHasManyRelationship(object entity, /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - _jsonApiContext.RequestManager.GetUpdatedRelationships()[attr] = null; + _requestManager.GetUpdatedRelationships()[attr] = null; } diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index e2d1f8c856..aa64ba6b24 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -403,9 +403,5 @@ public EntityResourceService( IResourceHookExecutor hookExecutor = null) : base(repository: repository, apiOptions: options, requestManager, resourceGraph, pageManager, loggerFactory, hookExecutor) { } - - //[Obsolete("Dont use this constructor, use the one without JsonApiContext instead")] - //public EntityResourceService( - // IJsonApiContext context, IEntityRepository repository) : this(repository, context.Options, context.RequestManager, context.PageManager, context.ResourceGraph) { } } } diff --git a/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs index 275b4b85b0..048959f9eb 100644 --- a/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs @@ -4,6 +4,8 @@ using System.Threading.Tasks; using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using Microsoft.EntityFrameworkCore; @@ -20,15 +22,21 @@ public class OperationsProcessor : IOperationsProcessor private readonly IOperationProcessorResolver _processorResolver; private readonly DbContext _dbContext; private readonly IJsonApiContext _jsonApiContext; + private readonly IRequestManager _requestManager; + private readonly IResourceGraph _resourceGraph; public OperationsProcessor( IOperationProcessorResolver processorResolver, IDbContextResolver dbContextResolver, - IJsonApiContext jsonApiContext) + IJsonApiContext jsonApiContext, + IRequestManager requestManager, + IResourceGraph resourceGraph) { _processorResolver = processorResolver; _dbContext = dbContextResolver.GetContext(); _jsonApiContext = jsonApiContext; + _requestManager = requestManager; + _resourceGraph = resourceGraph; } public async Task> ProcessAsync(List inputOps) @@ -44,6 +52,7 @@ public async Task> ProcessAsync(List inputOps) foreach (var op in inputOps) { _jsonApiContext.BeginOperation(); + lastAttemptedOperation = op.Op; await ProcessOperation(op, outputOps); opIndex++; @@ -70,6 +79,16 @@ private async Task ProcessOperation(Operation op, List outputOps) ReplaceLocalIdsInResourceObject(op.DataObject, outputOps); ReplaceLocalIdsInRef(op.Ref, outputOps); + string type = null; + if (op.Op == OperationCode.add || op.Op == OperationCode.update) + { + type = op.DataObject.Type; + } else if (op.Op == OperationCode.get || op.Op == OperationCode.remove) + { + type = op.Ref.Type; + } + _requestManager.SetContextEntity(_resourceGraph.GetEntityFromControllerName(type)); + var processor = GetOperationsProcessor(op); var resultOp = await processor.ProcessAsync(op); diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index 872b9f693b..3a64dc3326 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -5,6 +5,7 @@ using JsonApiDotNetCore.Data; using JsonApiDotNetCore.Graph; using JsonApiDotNetCore.Hooks; +using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; @@ -63,13 +64,20 @@ public void AddCurrentAssembly_Adds_Resources_To_Graph() [Fact] public void AddCurrentAssembly_Adds_Services_To_Container() - { - // arrange, act + { + // arrange, act + _services.AddSingleton(new JsonApiOptions()); + + _services.AddScoped( (_) => new Mock().Object); + _services.AddScoped( (_) => new Mock().Object); + _services.AddScoped( (_) => new Mock().Object); + _services.AddScoped( (_) => new Mock().Object); _facade.AddCurrentAssembly(); // assert var services = _services.BuildServiceProvider(); - Assert.IsType(services.GetService>()); + var service = services.GetService>(); + Assert.IsType(service); } [Fact] @@ -90,7 +98,14 @@ public class TestModelService : EntityResourceService private static IEntityRepository _repo = new Mock>().Object; private static IJsonApiContext _jsonApiContext = new Mock().Object; - public TestModelService(IEntityRepository repository, IJsonApiOptions options, IRequestManager requestManager, IPageManager pageManager, IResourceGraph resourceGraph, ILoggerFactory loggerFactory = null, IResourceHookExecutor hookExecutor = null) : base(repository, options, requestManager, pageManager, resourceGraph, loggerFactory, hookExecutor) + public TestModelService( + IEntityRepository repository, + IJsonApiOptions options, + IRequestManager requestManager, + IPageManager pageManager, + IResourceGraph resourceGraph, + ILoggerFactory loggerFactory = null, + IResourceHookExecutor hookExecutor = null) : base(repository, options, requestManager, pageManager, resourceGraph, loggerFactory, hookExecutor) { } } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs index 88b98f982a..616a1cf9da 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs @@ -206,10 +206,6 @@ public async Task Unauthorized_Article() var route = $"/api/v1/articles/{article.Id}"; - var httpMethod = new HttpMethod("GET"); - var request = new HttpRequestMessage(httpMethod, route); - - // Act var response = await _fixture.Client.GetAsync(route); diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs index 1f765e1b1e..c3c3d5a3ec 100644 --- a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs +++ b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs @@ -32,8 +32,8 @@ public override IServiceProvider ConfigureServices(IServiceCollection services) options.LoadDatabaseValues = true; options.AllowClientGeneratedIds = true; }, - mvcBuilder, - discovery => discovery.AddAssembly(Assembly.Load(nameof(JsonApiDotNetCoreExample)))); + discovery => discovery.AddAssembly(Assembly.Load(nameof(JsonApiDotNetCoreExample))), + mvcBuilder); return services.BuildServiceProvider(); diff --git a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs index 8b2f61b5a7..fbe909938f 100644 --- a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs +++ b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs @@ -155,14 +155,10 @@ public class HooksTestsSetup : HooksDummyData // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. var (graph, rqMock, gpfMock, options) = CreateMocks(); - - var traversalHelper = new TraversalHelper(graph, rqMock.Object); - SetupProcessorFactoryForResourceDefinition(gpfMock, mainResource.Object, mainDiscovery, null); - var meta = new HookExecutorHelper(gpfMock.Object, graph, options); - var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, graph, rqMock.Object); + var hookExecutor = new ResourceHookExecutor(meta, graph, rqMock.Object); return (rqMock, hookExecutor, mainResource); } @@ -189,7 +185,7 @@ public class HooksTestsSetup : HooksDummyData SetupProcessorFactoryForResourceDefinition(gpfMock, mainResource.Object, mainDiscovery, dbContext); SetupProcessorFactoryForResourceDefinition(gpfMock, nestedResource.Object, nestedDiscovery, dbContext); var meta = new HookExecutorHelper(gpfMock.Object, graph, options); - var hookExecutor = new ResourceHookExecutor(meta, traversalHelper, graph, rqMock.Object); + var hookExecutor = new ResourceHookExecutor(meta, graph, rqMock.Object); return (rqMock, hookExecutor, mainResource, nestedResource); } @@ -212,7 +208,6 @@ public class HooksTestsSetup : HooksDummyData // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. var (graph, rqMock, gpfMock, options) = CreateMocks(); - var traversalHelper = new TraversalHelper(graph, rqMock.Object); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; @@ -221,7 +216,7 @@ public class HooksTestsSetup : HooksDummyData SetupProcessorFactoryForResourceDefinition(gpfMock, secondNestedResource.Object, secondNestedDiscovery, dbContext); var hookExecutorHelper = new HookExecutorHelper(gpfMock.Object, graph, options); - var hookExecutor = new ResourceHookExecutor(hookExecutorHelper, traversalHelper, graph, rqMock.Object); + var hookExecutor = new ResourceHookExecutor(hookExecutorHelper, graph, rqMock.Object); return (rqMock, hookExecutor, mainResource, firstNestedResource, secondNestedResource); } diff --git a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs index 57e0d98497..b2a7ad8e57 100644 --- a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs @@ -47,7 +47,7 @@ private void CreateMocks() public void Can_Deserialize_Complex_Types() { // arrange - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var content = new Document { @@ -74,7 +74,7 @@ public void Can_Deserialize_Complex_Types() public void Can_Deserialize_Complex_List_Types() { // arrange - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var content = new Document { @@ -106,7 +106,7 @@ public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs() jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); // <-- _jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var content = new Document { @@ -141,7 +141,8 @@ public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); _jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); + var content = new Document { @@ -176,7 +177,7 @@ public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship() { // arrange - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var property = Guid.NewGuid().ToString(); var content = new Document @@ -219,7 +220,8 @@ public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship_With_Str var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); + var property = Guid.NewGuid().ToString(); var content = new Document @@ -266,7 +268,7 @@ public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship_With_Rel var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var property = Guid.NewGuid().ToString(); var content = new Document @@ -320,7 +322,7 @@ public void Sets_The_DocumentMeta_Property_In_JsonApiContext() jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var property = Guid.NewGuid().ToString(); @@ -412,7 +414,7 @@ public void Can_Deserialize_Object_With_HasManyRelationship() var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var contentString = @"{ @@ -468,7 +470,7 @@ public void Sets_Attribute_Values_On_Included_HasMany_Relationships() var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var expectedName = "John Doe"; var contentString = @@ -536,7 +538,7 @@ public void Sets_Attribute_Values_On_Included_HasOne_Relationships() var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); var expectedName = "John Doe"; var contentString = @@ -729,7 +731,7 @@ private JsonApiDeSerializer GetDeserializer(ResourceGraphBuilder resourceGraphBu var jsonApiOptions = new JsonApiOptions(); jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object); + var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); return deserializer; } diff --git a/test/UnitTests/Services/Operations/OperationsProcessorTests.cs b/test/UnitTests/Services/Operations/OperationsProcessorTests.cs index 08211c5a67..0bdcfa92e9 100644 --- a/test/UnitTests/Services/Operations/OperationsProcessorTests.cs +++ b/test/UnitTests/Services/Operations/OperationsProcessorTests.cs @@ -2,6 +2,8 @@ using System.Threading; using System.Threading.Tasks; using JsonApiDotNetCore.Data; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Services; using JsonApiDotNetCore.Services.Operations; @@ -93,7 +95,9 @@ public async Task ProcessAsync_Performs_LocalId_ReplacementAsync_In_Relationship .Returns(opProcessorMock.Object); _dbContextResolverMock.Setup(m => m.GetContext()).Returns(_dbContextMock.Object); - var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object, _jsonApiContextMock.Object); + var requestManagerMock = new Mock(); + var resourceGraphMock = new Mock(); + var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object, _jsonApiContextMock.Object, requestManagerMock.Object, resourceGraphMock.Object); // act var results = await operationsProcessor.ProcessAsync(operations); @@ -176,7 +180,9 @@ public async Task ProcessAsync_Performs_LocalId_ReplacementAsync_In_References() .Returns(updateOpProcessorMock.Object); _dbContextResolverMock.Setup(m => m.GetContext()).Returns(_dbContextMock.Object); - var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object, _jsonApiContextMock.Object); + var requestManagerMock = new Mock(); + var resourceGraphMock = new Mock(); + var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object, _jsonApiContextMock.Object, requestManagerMock.Object, resourceGraphMock.Object); // act var results = await operationsProcessor.ProcessAsync(operations); diff --git a/test/UnitTests/Services/QueryParserTests.cs b/test/UnitTests/Services/QueryParserTests.cs index f8f091f6ae..63e04004d0 100644 --- a/test/UnitTests/Services/QueryParserTests.cs +++ b/test/UnitTests/Services/QueryParserTests.cs @@ -223,7 +223,7 @@ public void Can_Disable_Page() var querySet = queryParser.Parse(_queryCollectionMock.Object); // assert - Assert.Equal(0, querySet.PageQuery.PageSize); + Assert.Equal(null, querySet.PageQuery.PageSize); } [Fact] From 0704cc6a1d48138f3ca0770ac9291886b3d413a8 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 26 Sep 2019 17:45:05 +0200 Subject: [PATCH 20/26] feat: decoupled deserializer and serializer --- .../IDocumentBuilderOptionsProvider.cs | 7 - .../Builders/ILinkBuilder.cs | 11 - src/JsonApiDotNetCore/Builders/LinkBuilder.cs | 44 - src/JsonApiDotNetCore/Builders/MetaBuilder.cs | 31 - ...BuilderOptions.cs => SerializerOptions.cs} | 0 .../IGlobalLinksConfiguration.cs | 12 + .../Internal/CamelizedRoutingConvention.cs | 40 + .../Contracts/IContextEntityProvider.cs | 6 + src/JsonApiDotNetCore/Internal/PageManager.cs | 52 -- .../Managers/IUpdatedFieldManager.cs | 18 + src/JsonApiDotNetCore/Models/DocumentBase.cs | 17 - src/JsonApiDotNetCore/Models/Documents.cs | 11 - .../Models/IResourceField.cs | 6 + .../Models/ISerializableFields.cs | 16 + .../Models/{ => JsonApi}/Document.cs | 1 + .../Models/JsonApi/ExposableData.cs | 77 ++ .../Models/{ => JsonApi}/IIdentifiable.cs | 0 .../Models/{ => JsonApi}/Identifiable.cs | 0 src/JsonApiDotNetCore/Models/JsonApi/Link.cs | 15 + .../Models/{ => JsonApi}/RelationshipData.cs | 21 +- .../Models/JsonApi/RelationshipLinks.cs | 19 + .../{ => JsonApi}/ResourceIdentifierObject.cs | 3 +- .../Models/JsonApi/ResourceLinks.cs | 13 + .../Models/{ => JsonApi}/ResourceObject.cs | 8 +- .../TopLevelLinks.cs} | 7 +- src/JsonApiDotNetCore/Models/Link.cs | 14 - src/JsonApiDotNetCore/Models/Links.cs | 13 - .../Models/LinksAttribute.cs | 37 +- .../Models/SerializableFields.cs | 58 ++ .../Contract/IFieldQueryService.cs | 10 + .../Contract/IIncludedQueryService.cs | 11 + .../Contract/IInternalFIeldQueryService.cs | 9 + .../Contract/IInternalIncludedQueryService.cs | 10 + .../Contracts/IFieldQueryService.cs | 10 + .../Contracts/IIncludedQueryService.cs | 11 + .../Contracts/IPageQueryService.cs} | 5 +- .../QueryServices/FieldQueryService.cs | 40 + .../QueryServices/IncludedQueryService.cs | 26 + .../QueryServices/PageQueryService.cs | 29 + .../Request/HasManyRelationshipPointers.cs | 49 -- .../Request/HasOneRelationshipPointers.cs | 45 -- .../Contracts/IClientDeserializer.cs | 10 + .../Contracts/IJsonApiDeserializer.cs | 7 + .../Serialization/Contracts/ILinkBuilder.cs | 24 + .../Contracts}/IMetaBuilder.cs | 6 +- .../Serialization/DasherizedResolver.cs | 19 - .../Deserializer/ClientDeserializer.cs | 101 +++ .../Deserializer/DeserializedResponse.cs | 27 + .../Deserializer/DocumentParser.cs | 215 +++++ .../OperationsDeserializer.cs} | 145 +--- .../Deserializer/ServerDeserializer.cs | 36 + .../Serialization/IJsonApiDeSerializer.cs | 14 - .../Serialization/IJsonApiSerializer.cs | 7 - .../IJsonApiSerializerSettings.cs | 9 + .../Serialization/JsonApiSerializer.cs | 101 --- .../JsonApiSerializerSettings.cs | 18 + .../Serializer/ClientSerializer.cs | 131 +++ .../Serializer/DocumentBuilder.cs | 39 + .../IIncludedRelationshipsBuilder.cs | 11 + .../Serializer/IJsonApiSerializer.cs | 10 + .../Serializer/IServerSerializerFactory.cs | 7 + .../IncludedRelationshipsBuilder.cs | 112 +++ .../Serialization/Serializer/LinkBuilder.cs | 152 ++++ .../Serialization/Serializer/MetaBuilder.cs | 61 ++ .../Serializer/ResourceObjectBuilder.cs | 118 +++ .../Serializer/ResourceObjectComparer.cs | 18 + .../Serializer/ServerSerializer.cs | 190 +++++ .../Services/ResourceFieldExplorer.cs | 10 + test/UnitTests/Builders/LinkTests.cs | 10 + .../Deserialization/BaseDeserializerTests.cs | 370 +++++++++ .../ClientDeserializerTests.cs | 333 ++++++++ .../DasherizedResolverTests.cs | 4 +- .../Deserialization/DeserializerTestsSetup.cs | 172 ++++ .../JsonApiSerializerTests.cs | 4 +- .../SerializationTestsSetupBase.cs | 121 +++ .../ServerDeserializerTests.cs | 107 +++ .../Serializ/BaseDeserializerTests.cs | 370 +++++++++ .../Serializ/ClientDeserializerTests.cs | 333 ++++++++ .../Serializ/DasherizedResolverTests.cs | 30 + .../Serializ/DeserializerTestsSetup.cs | 68 ++ .../Serializ/JsonApiSerializerTests.cs | 285 +++++++ .../Serializ/SerializationTestsSetupBase.cs | 121 +++ .../Serializ/ServerDeserializerTests.cs | 107 +++ .../BaseDeserializerTests.cs | 370 +++++++++ .../ClientDeserializerTests.cs | 333 ++++++++ .../DasherizedResolverTests.cs | 30 + .../DeserializerTestsSetup.cs | 172 ++++ .../JsonApiSerializerTests.cs | 285 +++++++ .../ServerDeserializerTests.cs | 107 +++ .../Deserializer/ClientDeserializerTests.cs | 333 ++++++++ .../Deserializer/DeserializerTestsSetup.cs | 68 ++ .../Deserializer/DocumentParserTests.cs | 367 +++++++++ .../Deserializer/ServerDeserializerTests.cs | 107 +++ .../Serialization/JsonApiDeSerializerTests.cs | 765 ------------------ .../SerializationTestsSetupBase.cs | 121 +++ .../Serializer/ClientSerializerTests.cs | 138 ++++ .../Serializer/DocumentBuilderTests.cs | 241 ++++++ .../IncludedRelationshipsBuilderTests.cs | 216 +++++ .../Serializer/SerializerTestsSetup.cs | 6 + .../Serializer/ServerSerializerTests.cs | 259 ++++++ .../Serialization/SerializerBaseTests.cs | 284 +++++++ .../Serialization/SerializerTestsSetup.cs | 6 + .../Serializer/JsonApiSerializerTests.cs | 284 +++++++ .../Serializer/SerializerBaseTests.cs | 23 + .../Serializer/SerializerTestsSetup.cs | 6 + .../OperationsProcessorResolverTests.cs | 102 --- .../Operations/OperationsProcessorTests.cs | 203 ----- .../Processors/CreateOpProcessorTests.cs | 80 -- 108 files changed, 8039 insertions(+), 1712 deletions(-) delete mode 100644 src/JsonApiDotNetCore/Builders/IDocumentBuilderOptionsProvider.cs delete mode 100644 src/JsonApiDotNetCore/Builders/ILinkBuilder.cs delete mode 100644 src/JsonApiDotNetCore/Builders/LinkBuilder.cs delete mode 100644 src/JsonApiDotNetCore/Builders/MetaBuilder.cs rename src/JsonApiDotNetCore/Builders/{DocumentBuilderOptions.cs => SerializerOptions.cs} (100%) create mode 100644 src/JsonApiDotNetCore/Configuration/IGlobalLinksConfiguration.cs create mode 100644 src/JsonApiDotNetCore/Internal/CamelizedRoutingConvention.cs create mode 100644 src/JsonApiDotNetCore/Internal/Contracts/IContextEntityProvider.cs delete mode 100644 src/JsonApiDotNetCore/Internal/PageManager.cs create mode 100644 src/JsonApiDotNetCore/Managers/IUpdatedFieldManager.cs delete mode 100644 src/JsonApiDotNetCore/Models/DocumentBase.cs delete mode 100644 src/JsonApiDotNetCore/Models/Documents.cs create mode 100644 src/JsonApiDotNetCore/Models/IResourceField.cs create mode 100644 src/JsonApiDotNetCore/Models/ISerializableFields.cs rename src/JsonApiDotNetCore/Models/{ => JsonApi}/Document.cs (83%) create mode 100644 src/JsonApiDotNetCore/Models/JsonApi/ExposableData.cs rename src/JsonApiDotNetCore/Models/{ => JsonApi}/IIdentifiable.cs (100%) rename src/JsonApiDotNetCore/Models/{ => JsonApi}/Identifiable.cs (100%) create mode 100644 src/JsonApiDotNetCore/Models/JsonApi/Link.cs rename src/JsonApiDotNetCore/Models/{ => JsonApi}/RelationshipData.cs (69%) create mode 100644 src/JsonApiDotNetCore/Models/JsonApi/RelationshipLinks.cs rename src/JsonApiDotNetCore/Models/{ => JsonApi}/ResourceIdentifierObject.cs (90%) create mode 100644 src/JsonApiDotNetCore/Models/JsonApi/ResourceLinks.cs rename src/JsonApiDotNetCore/Models/{ => JsonApi}/ResourceObject.cs (61%) rename src/JsonApiDotNetCore/Models/{RootLinks.cs => JsonApi/TopLevelLinks.cs} (85%) delete mode 100644 src/JsonApiDotNetCore/Models/Link.cs delete mode 100644 src/JsonApiDotNetCore/Models/Links.cs create mode 100644 src/JsonApiDotNetCore/Models/SerializableFields.cs create mode 100644 src/JsonApiDotNetCore/QueryServices/Contract/IFieldQueryService.cs create mode 100644 src/JsonApiDotNetCore/QueryServices/Contract/IIncludedQueryService.cs create mode 100644 src/JsonApiDotNetCore/QueryServices/Contract/IInternalFIeldQueryService.cs create mode 100644 src/JsonApiDotNetCore/QueryServices/Contract/IInternalIncludedQueryService.cs create mode 100644 src/JsonApiDotNetCore/QueryServices/Contracts/IFieldQueryService.cs create mode 100644 src/JsonApiDotNetCore/QueryServices/Contracts/IIncludedQueryService.cs rename src/JsonApiDotNetCore/{Managers/Contracts/IPageManager.cs => QueryServices/Contracts/IPageQueryService.cs} (89%) create mode 100644 src/JsonApiDotNetCore/QueryServices/FieldQueryService.cs create mode 100644 src/JsonApiDotNetCore/QueryServices/IncludedQueryService.cs create mode 100644 src/JsonApiDotNetCore/QueryServices/PageQueryService.cs delete mode 100644 src/JsonApiDotNetCore/Request/HasManyRelationshipPointers.cs delete mode 100644 src/JsonApiDotNetCore/Request/HasOneRelationshipPointers.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Contracts/IClientDeserializer.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Contracts/IJsonApiDeserializer.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Contracts/ILinkBuilder.cs rename src/JsonApiDotNetCore/{Builders => Serialization/Contracts}/IMetaBuilder.cs (53%) delete mode 100644 src/JsonApiDotNetCore/Serialization/DasherizedResolver.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Deserializer/ClientDeserializer.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Deserializer/DeserializedResponse.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Deserializer/DocumentParser.cs rename src/JsonApiDotNetCore/Serialization/{JsonApiDeSerializer.cs => Deserializer/OperationsDeserializer.cs} (68%) create mode 100644 src/JsonApiDotNetCore/Serialization/Deserializer/ServerDeserializer.cs delete mode 100644 src/JsonApiDotNetCore/Serialization/IJsonApiDeSerializer.cs delete mode 100644 src/JsonApiDotNetCore/Serialization/IJsonApiSerializer.cs create mode 100644 src/JsonApiDotNetCore/Serialization/IJsonApiSerializerSettings.cs delete mode 100644 src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs create mode 100644 src/JsonApiDotNetCore/Serialization/JsonApiSerializerSettings.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Serializer/ClientSerializer.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Serializer/DocumentBuilder.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Serializer/IIncludedRelationshipsBuilder.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Serializer/IJsonApiSerializer.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Serializer/IServerSerializerFactory.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Serializer/IncludedRelationshipsBuilder.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Serializer/LinkBuilder.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Serializer/MetaBuilder.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Serializer/ResourceObjectBuilder.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Serializer/ResourceObjectComparer.cs create mode 100644 src/JsonApiDotNetCore/Serialization/Serializer/ServerSerializer.cs create mode 100644 src/JsonApiDotNetCore/Services/ResourceFieldExplorer.cs create mode 100644 test/UnitTests/Builders/LinkTests.cs create mode 100644 test/UnitTests/Deserialization/BaseDeserializerTests.cs create mode 100644 test/UnitTests/Deserialization/ClientDeserializerTests.cs rename test/UnitTests/{Serialization => Deserialization}/DasherizedResolverTests.cs (88%) create mode 100644 test/UnitTests/Deserialization/DeserializerTestsSetup.cs rename test/UnitTests/{Serialization => Deserialization}/JsonApiSerializerTests.cs (99%) create mode 100644 test/UnitTests/Deserialization/SerializationTestsSetupBase.cs create mode 100644 test/UnitTests/Deserialization/ServerDeserializerTests.cs create mode 100644 test/UnitTests/Serializ/BaseDeserializerTests.cs create mode 100644 test/UnitTests/Serializ/ClientDeserializerTests.cs create mode 100644 test/UnitTests/Serializ/DasherizedResolverTests.cs create mode 100644 test/UnitTests/Serializ/DeserializerTestsSetup.cs create mode 100644 test/UnitTests/Serializ/JsonApiSerializerTests.cs create mode 100644 test/UnitTests/Serializ/SerializationTestsSetupBase.cs create mode 100644 test/UnitTests/Serializ/ServerDeserializerTests.cs create mode 100644 test/UnitTests/Serialization (copy)/BaseDeserializerTests.cs create mode 100644 test/UnitTests/Serialization (copy)/ClientDeserializerTests.cs create mode 100644 test/UnitTests/Serialization (copy)/DasherizedResolverTests.cs create mode 100644 test/UnitTests/Serialization (copy)/DeserializerTestsSetup.cs create mode 100644 test/UnitTests/Serialization (copy)/JsonApiSerializerTests.cs create mode 100644 test/UnitTests/Serialization (copy)/ServerDeserializerTests.cs create mode 100644 test/UnitTests/Serialization/Deserializer/ClientDeserializerTests.cs create mode 100644 test/UnitTests/Serialization/Deserializer/DeserializerTestsSetup.cs create mode 100644 test/UnitTests/Serialization/Deserializer/DocumentParserTests.cs create mode 100644 test/UnitTests/Serialization/Deserializer/ServerDeserializerTests.cs delete mode 100644 test/UnitTests/Serialization/JsonApiDeSerializerTests.cs create mode 100644 test/UnitTests/Serialization/SerializationTestsSetupBase.cs create mode 100644 test/UnitTests/Serialization/Serializer/ClientSerializerTests.cs create mode 100644 test/UnitTests/Serialization/Serializer/DocumentBuilderTests.cs create mode 100644 test/UnitTests/Serialization/Serializer/IncludedRelationshipsBuilderTests.cs create mode 100644 test/UnitTests/Serialization/Serializer/SerializerTestsSetup.cs create mode 100644 test/UnitTests/Serialization/Serializer/ServerSerializerTests.cs create mode 100644 test/UnitTests/Serialization/SerializerBaseTests.cs create mode 100644 test/UnitTests/Serialization/SerializerTestsSetup.cs create mode 100644 test/UnitTests/Serializer/JsonApiSerializerTests.cs create mode 100644 test/UnitTests/Serializer/SerializerBaseTests.cs create mode 100644 test/UnitTests/Serializer/SerializerTestsSetup.cs delete mode 100644 test/UnitTests/Services/Operations/OperationsProcessorResolverTests.cs delete mode 100644 test/UnitTests/Services/Operations/OperationsProcessorTests.cs delete mode 100644 test/UnitTests/Services/Operations/Processors/CreateOpProcessorTests.cs diff --git a/src/JsonApiDotNetCore/Builders/IDocumentBuilderOptionsProvider.cs b/src/JsonApiDotNetCore/Builders/IDocumentBuilderOptionsProvider.cs deleted file mode 100644 index fe014bced5..0000000000 --- a/src/JsonApiDotNetCore/Builders/IDocumentBuilderOptionsProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace JsonApiDotNetCore.Builders -{ - public interface IDocumentBuilderOptionsProvider - { - DocumentBuilderOptions GetDocumentBuilderOptions(); - } -} diff --git a/src/JsonApiDotNetCore/Builders/ILinkBuilder.cs b/src/JsonApiDotNetCore/Builders/ILinkBuilder.cs deleted file mode 100644 index c8af9e7dac..0000000000 --- a/src/JsonApiDotNetCore/Builders/ILinkBuilder.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace JsonApiDotNetCore.Builders -{ - public interface ILinkBuilder - { - string GetPageLink(int pageOffset, int pageSize); - string GetRelatedRelationLink(string parent, string parentId, string child); - string GetSelfRelationLink(string parent, string parentId, string child); - } -} diff --git a/src/JsonApiDotNetCore/Builders/LinkBuilder.cs b/src/JsonApiDotNetCore/Builders/LinkBuilder.cs deleted file mode 100644 index 9738065ec3..0000000000 --- a/src/JsonApiDotNetCore/Builders/LinkBuilder.cs +++ /dev/null @@ -1,44 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Services; - -namespace JsonApiDotNetCore.Builders -{ - public class LinkBuilder : ILinkBuilder - { - private readonly IRequestManager _requestManager; - private readonly IJsonApiOptions _options; - - public LinkBuilder(IJsonApiOptions options, IRequestManager requestManager) - { - _options = options; - _requestManager = requestManager; - } - - /// - public string GetSelfRelationLink(string parent, string parentId, string child) - { - return $"{GetBasePath()}/{parent}/{parentId}/relationships/{child}"; - } - - /// - public string GetRelatedRelationLink(string parent, string parentId, string child) - { - return $"{GetBasePath()}/{parent}/{parentId}/{child}"; - } - - /// - public string GetPageLink(int pageOffset, int pageSize) - { - var filterQueryComposer = new QueryComposer(); - var filters = filterQueryComposer.Compose(_requestManager); - return $"{GetBasePath()}/{_requestManager.GetContextEntity().EntityName}?page[size]={pageSize}&page[number]={pageOffset}{filters}"; - } - - private string GetBasePath() - { - if (_options.RelativeLinks) return string.Empty; - return _requestManager.BasePath; - } - } -} diff --git a/src/JsonApiDotNetCore/Builders/MetaBuilder.cs b/src/JsonApiDotNetCore/Builders/MetaBuilder.cs deleted file mode 100644 index 14b80321f6..0000000000 --- a/src/JsonApiDotNetCore/Builders/MetaBuilder.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace JsonApiDotNetCore.Builders -{ - public class MetaBuilder : IMetaBuilder - { - private Dictionary _meta = new Dictionary(); - - public void Add(string key, object value) - { - _meta[key] = value; - } - - /// - /// Joins the new dictionary with the current one. In the event of a key collision, - /// the new value will override the old. - /// - public void Add(Dictionary values) - { - _meta = values.Keys.Union(_meta.Keys) - .ToDictionary(key => key, - key => values.ContainsKey(key) ? values[key] : _meta[key]); - } - - public Dictionary Build() - { - return _meta; - } - } -} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilderOptions.cs b/src/JsonApiDotNetCore/Builders/SerializerOptions.cs similarity index 100% rename from src/JsonApiDotNetCore/Builders/DocumentBuilderOptions.cs rename to src/JsonApiDotNetCore/Builders/SerializerOptions.cs diff --git a/src/JsonApiDotNetCore/Configuration/IGlobalLinksConfiguration.cs b/src/JsonApiDotNetCore/Configuration/IGlobalLinksConfiguration.cs new file mode 100644 index 0000000000..599eee24ca --- /dev/null +++ b/src/JsonApiDotNetCore/Configuration/IGlobalLinksConfiguration.cs @@ -0,0 +1,12 @@ +using JsonApiDotNetCore.Models.Links; + +namespace JsonApiDotNetCore.Configuration +{ + public interface IGlobalLinksConfiguration + { + bool RelativeLinks { get; set; } + Link RelationshipLinks { get; set; } + Link TopLevelLinks { get; set; } + Link ResourceLinks { get; set; } + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Internal/CamelizedRoutingConvention.cs b/src/JsonApiDotNetCore/Internal/CamelizedRoutingConvention.cs new file mode 100644 index 0000000000..edb7e2444a --- /dev/null +++ b/src/JsonApiDotNetCore/Internal/CamelizedRoutingConvention.cs @@ -0,0 +1,40 @@ +// REF: https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.CustomRoutingConvention/NameSpaceRoutingConvention.cs +// REF: https://github.com/aspnet/Mvc/issues/5691 +using System.Reflection; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Extensions; +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +namespace JsonApiDotNetCore.Internal +{ + public class DasherizedRoutingConvention : IApplicationModelConvention + { + private readonly string _namespace; + public DasherizedRoutingConvention(string nspace) + { + _namespace = nspace; + } + + public void Apply(ApplicationModel application) + { + foreach (var controller in application.Controllers) + { + if (IsDasherizedJsonApiController(controller) == false) + continue; + + var template = $"{_namespace}/{controller.ControllerName.Dasherize()}"; + controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel + { + Template = template + }; + } + } + + private bool IsDasherizedJsonApiController(ControllerModel controller) + { + var type = controller.ControllerType; + var notDisabled = type.GetCustomAttribute() == null; + return notDisabled && type.IsSubclassOf(typeof(JsonApiControllerMixin)); + } + } +} diff --git a/src/JsonApiDotNetCore/Internal/Contracts/IContextEntityProvider.cs b/src/JsonApiDotNetCore/Internal/Contracts/IContextEntityProvider.cs new file mode 100644 index 0000000000..cc174ec94f --- /dev/null +++ b/src/JsonApiDotNetCore/Internal/Contracts/IContextEntityProvider.cs @@ -0,0 +1,6 @@ +namespace JsonApiDotNetCore.Internal.Contracts +{ + public interface IContextEntityProvider + { + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Internal/PageManager.cs b/src/JsonApiDotNetCore/Internal/PageManager.cs deleted file mode 100644 index 9efd2fdd6c..0000000000 --- a/src/JsonApiDotNetCore/Internal/PageManager.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Models; - -namespace JsonApiDotNetCore.Internal -{ - public class PageManager : IPageManager - { - private ILinkBuilder _linkBuilder; - private IJsonApiOptions _options; - - public PageManager(ILinkBuilder linkBuilder, IJsonApiOptions options, IRequestManager requestManager) - { - _linkBuilder = linkBuilder; - _options = options; - DefaultPageSize = _options.DefaultPageSize; - PageSize = _options.DefaultPageSize; - } - public int? TotalRecords { get; set; } - public int PageSize { get; set; } - public int DefaultPageSize { get; set; } // I think we shouldnt expose this - public int CurrentPage { get; set; } - public bool IsPaginated => PageSize > 0; - public int TotalPages => (TotalRecords == null) ? -1 : (int)Math.Ceiling(decimal.Divide(TotalRecords.Value, PageSize)); - - public RootLinks GetPageLinks() - { - if (ShouldIncludeLinksObject()) - return null; - - var rootLinks = new RootLinks(); - - if (CurrentPage > 1) - rootLinks.First = _linkBuilder.GetPageLink(1, PageSize); - - if (CurrentPage > 1) - rootLinks.Prev = _linkBuilder.GetPageLink(CurrentPage - 1, PageSize); - - if (CurrentPage < TotalPages) - rootLinks.Next = _linkBuilder.GetPageLink(CurrentPage + 1, PageSize); - - if (TotalPages > 0) - rootLinks.Last = _linkBuilder.GetPageLink(TotalPages, PageSize); - - return rootLinks; - } - - private bool ShouldIncludeLinksObject() => (!IsPaginated || ((CurrentPage == 1 || CurrentPage == 0) && TotalPages <= 0)); - } -} diff --git a/src/JsonApiDotNetCore/Managers/IUpdatedFieldManager.cs b/src/JsonApiDotNetCore/Managers/IUpdatedFieldManager.cs new file mode 100644 index 0000000000..c760838975 --- /dev/null +++ b/src/JsonApiDotNetCore/Managers/IUpdatedFieldManager.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Serialization +{ + + public interface IUpdatedFieldsManager + { + List AttributesToUpdate { get; set; } + List RelationshipsToUpdate { get; set; } + } + + public interface IUpdatedFieldManager_ProposalWithDictionaries + { + Dictionary> AttributesToUpdate { get; set; } + Dictionary> RelationshipsToUpdate { get; set; } + } +} diff --git a/src/JsonApiDotNetCore/Models/DocumentBase.cs b/src/JsonApiDotNetCore/Models/DocumentBase.cs deleted file mode 100644 index 8812d301e5..0000000000 --- a/src/JsonApiDotNetCore/Models/DocumentBase.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace JsonApiDotNetCore.Models -{ - public class DocumentBase - { - [JsonProperty("links", NullValueHandling = NullValueHandling.Ignore)] - public RootLinks Links { get; set; } - - [JsonProperty("included", NullValueHandling = NullValueHandling.Ignore)] - public List Included { get; set; } - - [JsonProperty("meta", NullValueHandling = NullValueHandling.Ignore)] - public Dictionary Meta { get; set; } - } -} diff --git a/src/JsonApiDotNetCore/Models/Documents.cs b/src/JsonApiDotNetCore/Models/Documents.cs deleted file mode 100644 index 8e1dcbb36e..0000000000 --- a/src/JsonApiDotNetCore/Models/Documents.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace JsonApiDotNetCore.Models -{ - public class Documents : DocumentBase - { - [JsonProperty("data")] - public List Data { get; set; } - } -} diff --git a/src/JsonApiDotNetCore/Models/IResourceField.cs b/src/JsonApiDotNetCore/Models/IResourceField.cs new file mode 100644 index 0000000000..d4ffa5c4a0 --- /dev/null +++ b/src/JsonApiDotNetCore/Models/IResourceField.cs @@ -0,0 +1,6 @@ +namespace JsonApiDotNetCore.Models +{ + internal interface IResourceField + { + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Models/ISerializableFields.cs b/src/JsonApiDotNetCore/Models/ISerializableFields.cs new file mode 100644 index 0000000000..3a8a6a771f --- /dev/null +++ b/src/JsonApiDotNetCore/Models/ISerializableFields.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace JsonApiDotNetCore.Models +{ + /// TODO: GetOutputAttrs is used in SERIALIATION LAYER to remove fields from + /// list of attrs that will be displayed, (i.e. touches the DOCUMENT structure) + /// whereas hooks is stuff to do on the MODEL in the SERVICELAYER. + /// Consider (not sure yet) to move to different class because of this. + /// edit: using different interfaces for this is maybe good enough to separate + public interface ISerializableFields + { + List GetAllowedAttributes(Type type); + List GetAllowedRelationships(Type type); + } +} diff --git a/src/JsonApiDotNetCore/Models/Document.cs b/src/JsonApiDotNetCore/Models/JsonApi/Document.cs similarity index 83% rename from src/JsonApiDotNetCore/Models/Document.cs rename to src/JsonApiDotNetCore/Models/JsonApi/Document.cs index 5d0d10d188..c8aa3601e2 100644 --- a/src/JsonApiDotNetCore/Models/Document.cs +++ b/src/JsonApiDotNetCore/Models/JsonApi/Document.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Models.Links; using Newtonsoft.Json; namespace JsonApiDotNetCore.Models diff --git a/src/JsonApiDotNetCore/Models/JsonApi/ExposableData.cs b/src/JsonApiDotNetCore/Models/JsonApi/ExposableData.cs new file mode 100644 index 0000000000..9f4c269c17 --- /dev/null +++ b/src/JsonApiDotNetCore/Models/JsonApi/ExposableData.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace JsonApiDotNetCore.Models +{ + public class ExposableData + { + /// + /// see "primary data" in https://jsonapi.org/format/#document-top-level. + /// + [JsonProperty("data")] + public object Data { get { return GetPrimaryData(); } set { SetPrimaryData(value); } } + + /// + /// see https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm + /// + public bool ShouldSerializData() + { + return IsPopulated; + } + + /// + /// Internally used for "single" primary data. + /// + internal T SingleData { get; private set; } + + /// + /// Internally used for "many" primary data. + /// + internal List ManyData { get; private set; } + + /// + /// Internally used to indicate if the document's primary data is + /// "single" or "many". + /// + internal bool IsManyData { get; private set; } = false; + + /// + /// Internally used to indicate if the document's primary data is + /// should still be serialized when it's value is null. This is used when + /// a single resource is requested but not present (eg /articles/1/author). + /// + internal bool IsPopulated { get; private set; } = false; + + /// + /// Gets the "single" or "many" data depending on which one was + /// assigned in this document. + /// + protected object GetPrimaryData() + { + if (IsManyData) + return ManyData; + return SingleData; + } + + /// + /// Sets the primary data depending on if it is "single" or "many" data. + /// + protected void SetPrimaryData(object value) + { + IsPopulated = true; + if (value is JObject jObject) + SingleData = jObject.ToObject(); + else if (value is T ro) + SingleData = ro; + else if (value != null) + { + IsManyData = true; + if (value is JArray jArray) + ManyData = jArray.ToObject>(); + else + ManyData = (List)value; + } + } + } +} diff --git a/src/JsonApiDotNetCore/Models/IIdentifiable.cs b/src/JsonApiDotNetCore/Models/JsonApi/IIdentifiable.cs similarity index 100% rename from src/JsonApiDotNetCore/Models/IIdentifiable.cs rename to src/JsonApiDotNetCore/Models/JsonApi/IIdentifiable.cs diff --git a/src/JsonApiDotNetCore/Models/Identifiable.cs b/src/JsonApiDotNetCore/Models/JsonApi/Identifiable.cs similarity index 100% rename from src/JsonApiDotNetCore/Models/Identifiable.cs rename to src/JsonApiDotNetCore/Models/JsonApi/Identifiable.cs diff --git a/src/JsonApiDotNetCore/Models/JsonApi/Link.cs b/src/JsonApiDotNetCore/Models/JsonApi/Link.cs new file mode 100644 index 0000000000..5bba6273a0 --- /dev/null +++ b/src/JsonApiDotNetCore/Models/JsonApi/Link.cs @@ -0,0 +1,15 @@ +using System; + +namespace JsonApiDotNetCore.Models.Links +{ + [Flags] + public enum Link + { + Self = 1 << 0, + Related = 1 << 1, + Paging = 1 << 2, + NotConfigured = 1 << 3, + None = 1 << 4, + All = Self | Related | Paging + } +} diff --git a/src/JsonApiDotNetCore/Models/RelationshipData.cs b/src/JsonApiDotNetCore/Models/JsonApi/RelationshipData.cs similarity index 69% rename from src/JsonApiDotNetCore/Models/RelationshipData.cs rename to src/JsonApiDotNetCore/Models/JsonApi/RelationshipData.cs index 1cfe47c5c7..2630750c87 100644 --- a/src/JsonApiDotNetCore/Models/RelationshipData.cs +++ b/src/JsonApiDotNetCore/Models/JsonApi/RelationshipData.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Models.Links; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -6,8 +8,8 @@ namespace JsonApiDotNetCore.Models { public class RelationshipData { - [JsonProperty("links")] - public Links Links { get; set; } + [JsonProperty("links", NullValueHandling = NullValueHandling.Ignore)] + public RelationshipLinks Links { get; set; } [JsonProperty("data")] public object ExposedData @@ -23,12 +25,18 @@ public object ExposedData if (value is JObject jObject) SingleData = jObject.ToObject(); else if (value is ResourceIdentifierObject dict) - SingleData = (ResourceIdentifierObject)value; + SingleData = dict; else SetManyData(value); } } + /// TODO check if behaviour is OK. + public bool ShouldSerializeExposedData() + { + return IsPopulated; + } + private void SetManyData(object value) { IsHasMany = true; @@ -46,5 +54,12 @@ private void SetManyData(object value) [JsonIgnore] public bool IsHasMany { get; private set; } + + internal bool IsPopulated { get; set; } = false; + + internal bool Any() + { + return ((IsHasMany && ManyData.Any()) || SingleData != null); + } } } diff --git a/src/JsonApiDotNetCore/Models/JsonApi/RelationshipLinks.cs b/src/JsonApiDotNetCore/Models/JsonApi/RelationshipLinks.cs new file mode 100644 index 0000000000..c728df6777 --- /dev/null +++ b/src/JsonApiDotNetCore/Models/JsonApi/RelationshipLinks.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace JsonApiDotNetCore.Models.Links +{ + public class RelationshipLinks + { + /// + /// see "links" bulletin at https://jsonapi.org/format/#document-resource-object-relationships + /// + [JsonProperty("self", NullValueHandling = NullValueHandling.Ignore)] + public string Self { get; set; } + + /// + /// https://jsonapi.org/format/#document-resource-object-related-resource-links + /// + [JsonProperty("related", NullValueHandling = NullValueHandling.Ignore)] + public string Related { get; set; } + } +} diff --git a/src/JsonApiDotNetCore/Models/ResourceIdentifierObject.cs b/src/JsonApiDotNetCore/Models/JsonApi/ResourceIdentifierObject.cs similarity index 90% rename from src/JsonApiDotNetCore/Models/ResourceIdentifierObject.cs rename to src/JsonApiDotNetCore/Models/JsonApi/ResourceIdentifierObject.cs index 2cf4ef401e..a3b8abdaf1 100644 --- a/src/JsonApiDotNetCore/Models/ResourceIdentifierObject.cs +++ b/src/JsonApiDotNetCore/Models/JsonApi/ResourceIdentifierObject.cs @@ -17,7 +17,8 @@ public ResourceIdentifierObject(string type, string id) [JsonProperty("id")] public string Id { get; set; } - [JsonProperty("lid")] + [JsonIgnore] + //[JsonProperty("lid")] public string LocalId { get; set; } } } diff --git a/src/JsonApiDotNetCore/Models/JsonApi/ResourceLinks.cs b/src/JsonApiDotNetCore/Models/JsonApi/ResourceLinks.cs new file mode 100644 index 0000000000..ea701f7681 --- /dev/null +++ b/src/JsonApiDotNetCore/Models/JsonApi/ResourceLinks.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace JsonApiDotNetCore.Models.Links +{ + public class ResourceLinks + { + /// + /// https://jsonapi.org/format/#document-resource-object-links + /// + [JsonProperty("self", NullValueHandling = NullValueHandling.Ignore)] + public string Self { get; set; } + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Models/ResourceObject.cs b/src/JsonApiDotNetCore/Models/JsonApi/ResourceObject.cs similarity index 61% rename from src/JsonApiDotNetCore/Models/ResourceObject.cs rename to src/JsonApiDotNetCore/Models/JsonApi/ResourceObject.cs index 1a28631407..961de7255c 100644 --- a/src/JsonApiDotNetCore/Models/ResourceObject.cs +++ b/src/JsonApiDotNetCore/Models/JsonApi/ResourceObject.cs @@ -1,14 +1,18 @@ using System.Collections.Generic; +using JsonApiDotNetCore.Models.Links; using Newtonsoft.Json; namespace JsonApiDotNetCore.Models -{ +{ public class ResourceObject : ResourceIdentifierObject { - [JsonProperty("attributes")] + [JsonProperty("attributes", NullValueHandling = NullValueHandling.Ignore)] public Dictionary Attributes { get; set; } [JsonProperty("relationships", NullValueHandling = NullValueHandling.Ignore)] public Dictionary Relationships { get; set; } + + [JsonProperty("links", NullValueHandling = NullValueHandling.Ignore)] + public ResourceLinks Links { get; set; } } } diff --git a/src/JsonApiDotNetCore/Models/RootLinks.cs b/src/JsonApiDotNetCore/Models/JsonApi/TopLevelLinks.cs similarity index 85% rename from src/JsonApiDotNetCore/Models/RootLinks.cs rename to src/JsonApiDotNetCore/Models/JsonApi/TopLevelLinks.cs index 42b0a7863f..22c8d12f16 100644 --- a/src/JsonApiDotNetCore/Models/RootLinks.cs +++ b/src/JsonApiDotNetCore/Models/JsonApi/TopLevelLinks.cs @@ -1,8 +1,11 @@ using Newtonsoft.Json; -namespace JsonApiDotNetCore.Models +namespace JsonApiDotNetCore.Models.Links { - public class RootLinks + /// + /// see links section in https://jsonapi.org/format/#document-top-level + /// + public class TopLevelLinks { [JsonProperty("self")] public string Self { get; set; } diff --git a/src/JsonApiDotNetCore/Models/Link.cs b/src/JsonApiDotNetCore/Models/Link.cs deleted file mode 100644 index 2d99fa7197..0000000000 --- a/src/JsonApiDotNetCore/Models/Link.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace JsonApiDotNetCore.Models -{ - [Flags] - public enum Link - { - Self = 1 << 0, - Paging = 1 << 1, - Related = 1 << 2, - All = ~(-1 << 3), - None = 1 << 4, - } -} diff --git a/src/JsonApiDotNetCore/Models/Links.cs b/src/JsonApiDotNetCore/Models/Links.cs deleted file mode 100644 index 993ca209d0..0000000000 --- a/src/JsonApiDotNetCore/Models/Links.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Newtonsoft.Json; - -namespace JsonApiDotNetCore.Models -{ - public class Links - { - [JsonProperty("self")] - public string Self { get; set; } - - [JsonProperty("related")] - public string Related { get; set; } - } -} diff --git a/src/JsonApiDotNetCore/Models/LinksAttribute.cs b/src/JsonApiDotNetCore/Models/LinksAttribute.cs index 85e2693111..1c071723f1 100644 --- a/src/JsonApiDotNetCore/Models/LinksAttribute.cs +++ b/src/JsonApiDotNetCore/Models/LinksAttribute.cs @@ -1,14 +1,43 @@ using System; +using JsonApiDotNetCore.Internal; -namespace JsonApiDotNetCore.Models +namespace JsonApiDotNetCore.Models.Links { + [AttributeUsage(AttributeTargets.Class, Inherited = false)] public class LinksAttribute : Attribute { - public LinksAttribute(Link links) + public LinksAttribute(Link topLevelLinks = Link.NotConfigured, Link resourceLinks = Link.NotConfigured, Link relationshipLinks = Link.NotConfigured) { - Links = links; + if (topLevelLinks == Link.Related) + throw new JsonApiSetupException($"{Link.Related.ToString("g")} not allowed for argument {nameof(topLevelLinks)}"); + + if (resourceLinks == Link.Paging) + throw new JsonApiSetupException($"{Link.Paging.ToString("g")} not allowed for argument {nameof(resourceLinks)}"); + + if (relationshipLinks == Link.Paging) + throw new JsonApiSetupException($"{Link.Paging.ToString("g")} not allowed for argument {nameof(relationshipLinks)}"); + + TopLevelLinks = topLevelLinks; + ResourceLinks = resourceLinks; + RelationshipLinks = relationshipLinks; } - public Link Links { get; set; } + /// + /// Configures which links to show in the + /// object for this resource. + /// + public Link TopLevelLinks { get; private set; } + + /// + /// Configures which links to show in the + /// object for this resource. + /// + public Link ResourceLinks { get; private set; } + + /// + /// Configures which links to show in the + /// for all relationships of the resource for which this attribute was instantiated. + /// + public Link RelationshipLinks { get; private set; } } } diff --git a/src/JsonApiDotNetCore/Models/SerializableFields.cs b/src/JsonApiDotNetCore/Models/SerializableFields.cs new file mode 100644 index 0000000000..0bd487f93b --- /dev/null +++ b/src/JsonApiDotNetCore/Models/SerializableFields.cs @@ -0,0 +1,58 @@ +using JsonApiDotNetCore.Internal.Contracts; +using System; +using System.Collections.Generic; +using JsonApiDotNetCore.Services; + +namespace JsonApiDotNetCore.Models +{ + public class SerializableFields : ISerializableFields + { + private readonly IResourceGraph _resourceGraph; + private readonly IServiceProvider _provider; + private readonly Dictionary _resourceDefinitionCache = new Dictionary(); + private readonly IExposedFieldExplorer _fieldExplorer; + + public SerializableFields(IExposedFieldExplorer fieldExplorer, + IResourceGraph resourceGraph, + IServiceProvider provider) + { + _fieldExplorer = fieldExplorer; + _resourceGraph = resourceGraph; + _provider = provider; + } + + public List GetAllowedAttributes(Type type) + { + var resourceDefinition = GetResourceDefinition(type); + if (resourceDefinition != null) + // The set of allowed attribrutes to be exposed was defined on the resource definition + return resourceDefinition.GetAllowedAttributes(); + + // The set of allowed attribrutes to be exposed was NOT defined on the resource definition: return all + return _fieldExplorer.GetAttributes(type); + } + + public List GetAllowedRelationships(Type type) + { + var resourceDefinition = GetResourceDefinition(type); + if (resourceDefinition != null) + // The set of allowed attribrutes to be exposed was defined on the resource definition + return resourceDefinition.GetAllowedRelationships(); + + // The set of allowed attribrutes to be exposed was NOT defined on the resource definition: return all + return _fieldExplorer.GetRelationships(type); + } + + private IResourceDefinition GetResourceDefinition(Type resourceType) + { + + var resourceDefinitionType = _resourceGraph.GetContextEntity(resourceType).ResourceType; + if (!_resourceDefinitionCache.TryGetValue(resourceDefinitionType, out IResourceDefinition resourceDefinition)) + { + resourceDefinition = _provider.GetService(resourceDefinitionType) as IResourceDefinition; + _resourceDefinitionCache.Add(resourceDefinitionType, resourceDefinition); + } + return resourceDefinition; + } + } +} diff --git a/src/JsonApiDotNetCore/QueryServices/Contract/IFieldQueryService.cs b/src/JsonApiDotNetCore/QueryServices/Contract/IFieldQueryService.cs new file mode 100644 index 0000000000..a82d4bc6a1 --- /dev/null +++ b/src/JsonApiDotNetCore/QueryServices/Contract/IFieldQueryService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Services +{ + public interface IFieldsQueryService + { + List Get(RelationshipAttribute relationship = null); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/QueryServices/Contract/IIncludedQueryService.cs b/src/JsonApiDotNetCore/QueryServices/Contract/IIncludedQueryService.cs new file mode 100644 index 0000000000..0e7dc6f146 --- /dev/null +++ b/src/JsonApiDotNetCore/QueryServices/Contract/IIncludedQueryService.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Services +{ + public interface IIncludedQueryService + { + List> Get(); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/QueryServices/Contract/IInternalFIeldQueryService.cs b/src/JsonApiDotNetCore/QueryServices/Contract/IInternalFIeldQueryService.cs new file mode 100644 index 0000000000..1e02edae47 --- /dev/null +++ b/src/JsonApiDotNetCore/QueryServices/Contract/IInternalFIeldQueryService.cs @@ -0,0 +1,9 @@ +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Services +{ + public interface IInternalFieldsQueryService + { + void Register(AttrAttribute selected, RelationshipAttribute relationship = null); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/QueryServices/Contract/IInternalIncludedQueryService.cs b/src/JsonApiDotNetCore/QueryServices/Contract/IInternalIncludedQueryService.cs new file mode 100644 index 0000000000..84234f6259 --- /dev/null +++ b/src/JsonApiDotNetCore/QueryServices/Contract/IInternalIncludedQueryService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Services +{ + public interface IInternalIncludedQueryService + { + void Register(List inclusionChain); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/QueryServices/Contracts/IFieldQueryService.cs b/src/JsonApiDotNetCore/QueryServices/Contracts/IFieldQueryService.cs new file mode 100644 index 0000000000..a82d4bc6a1 --- /dev/null +++ b/src/JsonApiDotNetCore/QueryServices/Contracts/IFieldQueryService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Services +{ + public interface IFieldsQueryService + { + List Get(RelationshipAttribute relationship = null); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/QueryServices/Contracts/IIncludedQueryService.cs b/src/JsonApiDotNetCore/QueryServices/Contracts/IIncludedQueryService.cs new file mode 100644 index 0000000000..0e7dc6f146 --- /dev/null +++ b/src/JsonApiDotNetCore/QueryServices/Contracts/IIncludedQueryService.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Services +{ + public interface IIncludedQueryService + { + List> Get(); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs b/src/JsonApiDotNetCore/QueryServices/Contracts/IPageQueryService.cs similarity index 89% rename from src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs rename to src/JsonApiDotNetCore/QueryServices/Contracts/IPageQueryService.cs index 814cdc35a5..b9e6e727d8 100644 --- a/src/JsonApiDotNetCore/Managers/Contracts/IPageManager.cs +++ b/src/JsonApiDotNetCore/QueryServices/Contracts/IPageQueryService.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.Managers.Contracts { - public interface IPageManager + public interface IPageQueryService { /// /// What the total records are for this output @@ -29,7 +29,8 @@ public interface IPageManager /// Are we even paginating /// bool IsPaginated { get; } + int TotalPages { get; } - RootLinks GetPageLinks(); + bool ShouldPaginate(); } } diff --git a/src/JsonApiDotNetCore/QueryServices/FieldQueryService.cs b/src/JsonApiDotNetCore/QueryServices/FieldQueryService.cs new file mode 100644 index 0000000000..1b5f4ed0e5 --- /dev/null +++ b/src/JsonApiDotNetCore/QueryServices/FieldQueryService.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Services +{ + public class FieldsQueryService : IFieldsQueryService, IInternalFieldsQueryService + { + private List _selectedFields; + private readonly Dictionary> _selectedRelationshipFields; + + public FieldsQueryService() + { + _selectedFields = new List(); + _selectedRelationshipFields = new Dictionary>(); + } + + public List Get(RelationshipAttribute relationship = null) + { + if (relationship == null) + return _selectedFields; + + _selectedRelationshipFields.TryGetValue(relationship, out var fields); + return fields; + } + + public void Register(AttrAttribute selected, RelationshipAttribute relationship = null) + { + if (relationship == null) + { + _selectedFields = _selectedFields ?? new List(); + _selectedFields.Add(selected); + } + + if (!_selectedRelationshipFields.TryGetValue(relationship, out var fields)) + _selectedRelationshipFields.Add(relationship, fields = new List()); + + fields.Add(selected); + } + } +} diff --git a/src/JsonApiDotNetCore/QueryServices/IncludedQueryService.cs b/src/JsonApiDotNetCore/QueryServices/IncludedQueryService.cs new file mode 100644 index 0000000000..0b036f6303 --- /dev/null +++ b/src/JsonApiDotNetCore/QueryServices/IncludedQueryService.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Services +{ + + public class IncludedQueryService : IIncludedQueryService, IInternalIncludedQueryService + { + private readonly List> _includedChains; + + public IncludedQueryService() + { + _includedChains = new List>(); + } + + public List> Get() + { + return _includedChains; + } + + public void Register(List chain) + { + _includedChains.Add(chain); + } + } +} diff --git a/src/JsonApiDotNetCore/QueryServices/PageQueryService.cs b/src/JsonApiDotNetCore/QueryServices/PageQueryService.cs new file mode 100644 index 0000000000..effe5d8564 --- /dev/null +++ b/src/JsonApiDotNetCore/QueryServices/PageQueryService.cs @@ -0,0 +1,29 @@ +using System; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Managers.Contracts; + +namespace JsonApiDotNetCore.Internal +{ + public class PageQueryService : IPageQueryService + { + private IJsonApiOptions _options; + + public PageQueryService(IJsonApiOptions options) + { + _options = options; + DefaultPageSize = _options.DefaultPageSize; + PageSize = _options.DefaultPageSize; + } + public int? TotalRecords { get; set; } + public int PageSize { get; set; } + public int DefaultPageSize { get; set; } // I think we shouldnt expose this + public int CurrentPage { get; set; } + public bool IsPaginated => PageSize > 0; + public int TotalPages => (TotalRecords == null) ? -1 : (int)Math.Ceiling(decimal.Divide(TotalRecords.Value, PageSize)); + + public bool ShouldPaginate() + { + return !IsPaginated || ((CurrentPage == 1 || CurrentPage == 0) && TotalPages <= 0); + } + } +} diff --git a/src/JsonApiDotNetCore/Request/HasManyRelationshipPointers.cs b/src/JsonApiDotNetCore/Request/HasManyRelationshipPointers.cs deleted file mode 100644 index 3fda5dc44e..0000000000 --- a/src/JsonApiDotNetCore/Request/HasManyRelationshipPointers.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using JsonApiDotNetCore.Models; - -namespace JsonApiDotNetCore.Request -{ - /// - /// Stores information to set relationships for the request resource. - /// These relationships must already exist and should not be re-created. - /// - /// The expected use case is POST-ing or PATCH-ing - /// an entity with HasMany relaitonships: - /// - /// { - /// "data": { - /// "type": "photos", - /// "attributes": { - /// "title": "Ember Hamster", - /// "src": "http://example.com/images/productivity.png" - /// }, - /// "relationships": { - /// "tags": { - /// "data": [ - /// { "type": "tags", "id": "2" }, - /// { "type": "tags", "id": "3" } - /// ] - /// } - /// } - /// } - /// } - /// - /// - public class HasManyRelationshipPointers - { - private readonly Dictionary _hasManyRelationships = new Dictionary(); - - /// - /// Add the relationship to the list of relationships that should be - /// set in the repository layer. - /// - public void Add(RelationshipAttribute relationship, IList entities) - => _hasManyRelationships[relationship] = entities; - - /// - /// Get all the models that should be associated - /// - public Dictionary Get() => _hasManyRelationships; - } -} diff --git a/src/JsonApiDotNetCore/Request/HasOneRelationshipPointers.cs b/src/JsonApiDotNetCore/Request/HasOneRelationshipPointers.cs deleted file mode 100644 index 19046b9eaa..0000000000 --- a/src/JsonApiDotNetCore/Request/HasOneRelationshipPointers.cs +++ /dev/null @@ -1,45 +0,0 @@ -using JsonApiDotNetCore.Models; -using System.Collections.Generic; - -namespace JsonApiDotNetCore.Request -{ - /// - /// Stores information to set relationships for the request resource. - /// These relationships must already exist and should not be re-created. - /// - /// The expected use case is POST-ing or PATCH-ing - /// an entity with HasOne relationships: - /// - /// { - /// "data": { - /// "type": "photos", - /// "attributes": { - /// "title": "Ember Hamster", - /// "src": "http://example.com/images/productivity.png" - /// }, - /// "relationships": { - /// "photographer": { - /// "data": { "type": "people", "id": "2" } - /// } - /// } - /// } - /// } - /// - /// - public class HasOneRelationshipPointers - { - private readonly Dictionary _hasOneRelationships = new Dictionary(); - - /// - /// Add the relationship to the list of relationships that should be - /// set in the repository layer. - /// - public void Add(HasOneAttribute relationship, IIdentifiable entity) - => _hasOneRelationships[relationship] = entity; - - /// - /// Get all the models that should be associated - /// - public Dictionary Get() => _hasOneRelationships; - } -} diff --git a/src/JsonApiDotNetCore/Serialization/Contracts/IClientDeserializer.cs b/src/JsonApiDotNetCore/Serialization/Contracts/IClientDeserializer.cs new file mode 100644 index 0000000000..465afc218d --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Contracts/IClientDeserializer.cs @@ -0,0 +1,10 @@ +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Serialization.Contracts +{ + public interface IClientDeserializer + { + DeserializedSingleResponse DeserializeSingle(string body) where TResource : class, IIdentifiable; + DeserializedListResponse DeserializeList(string body) where TResource : class, IIdentifiable; + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/Contracts/IJsonApiDeserializer.cs b/src/JsonApiDotNetCore/Serialization/Contracts/IJsonApiDeserializer.cs new file mode 100644 index 0000000000..4246034a43 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Contracts/IJsonApiDeserializer.cs @@ -0,0 +1,7 @@ +namespace JsonApiDotNetCore.Serialization.Contracts +{ + public interface IJsonApiDeserializer + { + object Deserialize(string body); + } +} diff --git a/src/JsonApiDotNetCore/Serialization/Contracts/ILinkBuilder.cs b/src/JsonApiDotNetCore/Serialization/Contracts/ILinkBuilder.cs new file mode 100644 index 0000000000..572389867c --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Contracts/ILinkBuilder.cs @@ -0,0 +1,24 @@ +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Models.Links; + +namespace JsonApiDotNetCore.Builders +{ + public interface ILinkBuilder + { + /// + /// Builds the links object that is included in the top-level of the document. + /// + TopLevelLinks GetTopLevelLinks(); + /// + /// Builds the links object for resources in the primary data. + /// + /// + ResourceLinks GetResourceLinks(string resourceName, string id); + /// + /// Builds the links object that is included in the values of the . + /// + /// + /// + RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable parent); + } +} diff --git a/src/JsonApiDotNetCore/Builders/IMetaBuilder.cs b/src/JsonApiDotNetCore/Serialization/Contracts/IMetaBuilder.cs similarity index 53% rename from src/JsonApiDotNetCore/Builders/IMetaBuilder.cs rename to src/JsonApiDotNetCore/Serialization/Contracts/IMetaBuilder.cs index bf35b9d210..d563329b30 100644 --- a/src/JsonApiDotNetCore/Builders/IMetaBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Contracts/IMetaBuilder.cs @@ -1,11 +1,13 @@ +using System; using System.Collections.Generic; +using JsonApiDotNetCore.Models; namespace JsonApiDotNetCore.Builders { - public interface IMetaBuilder + public interface IMetaBuilder where T : class, IIdentifiable { void Add(string key, object value); void Add(Dictionary values); - Dictionary Build(); + Dictionary GetMeta(); } } \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/DasherizedResolver.cs b/src/JsonApiDotNetCore/Serialization/DasherizedResolver.cs deleted file mode 100644 index 1b4a3aae6c..0000000000 --- a/src/JsonApiDotNetCore/Serialization/DasherizedResolver.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection; -using JsonApiDotNetCore.Extensions; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -namespace JsonApiDotNetCore.Serialization -{ - public class DasherizedResolver : DefaultContractResolver - { - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - JsonProperty property = base.CreateProperty(member, memberSerialization); - - property.PropertyName = property.PropertyName.Dasherize(); - - return property; - } - } -} diff --git a/src/JsonApiDotNetCore/Serialization/Deserializer/ClientDeserializer.cs b/src/JsonApiDotNetCore/Serialization/Deserializer/ClientDeserializer.cs new file mode 100644 index 0000000000..f1571a27e2 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Deserializer/ClientDeserializer.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization.Contracts; + +namespace JsonApiDotNetCore.Serialization +{ + public class ClientDeserializer : DocumentParser, IClientDeserializer + { + public ClientDeserializer(IContextEntityProvider provider) : base(provider) { } + + public DeserializedSingleResponse DeserializeSingle(string body) where TResource : class, IIdentifiable + { + var entity = base.Deserialize(body); + return new DeserializedSingleResponse() + { + Links = _document.Links, + Meta = _document.Meta, + Data = entity == null ? null : (TResource)entity, + JsonApi = null, + Errors = null + }; + } + + public DeserializedListResponse DeserializeList(string body) where TResource : class, IIdentifiable + { + var entities = base.Deserialize(body); + return new DeserializedListResponse() + { + Links = _document.Links, + Meta = _document.Meta, + Data = entities == null ? null : ((List)entities).Cast().ToList(), + JsonApi = null, + Errors = null + }; + } + + protected override void AfterProcessField(IIdentifiable entity, IResourceField field, RelationshipData data = null) + { + if (field is AttrAttribute) + return; + + // can't provide any more data other than the rios since it is not contained in the included section + if (_document.Included == null || _document.Included.Count == 0) + return; + + if (field is HasOneAttribute hasOneAttr) + { + var rio = data.SingleData; + if (rio == null) + hasOneAttr.SetValue(entity, null); + else + hasOneAttr.SetValue(entity, GetIncludedRelationship(hasOneAttr, rio)); + } + else if (field is HasManyAttribute hasManyAttr) + { + var values = TypeHelper.CreateListFor(hasManyAttr.DependentType); + foreach (var rio in data.ManyData) + values.Add(GetIncludedRelationship(hasManyAttr, rio)); + + hasManyAttr.SetValue(entity, values); + } + } + + private IIdentifiable GetIncludedRelationship(RelationshipAttribute relationshipAttr, ResourceIdentifierObject relatedResourceIdentifier) + { + var relatedInstance = relationshipAttr.DependentType.New(); + relatedInstance.StringId = relatedResourceIdentifier.Id; + + var includedResource = GetLinkedResource(relatedResourceIdentifier); + if (includedResource == null) + return relatedInstance; + + var contextEntity = _provider.GetContextEntity(relatedResourceIdentifier.Type); + if (contextEntity == null) + throw new InvalidOperationException($"Included type '{relationshipAttr.DependentType}' is not a registered json:api resource."); + + SetAttributes(relatedInstance, includedResource.Attributes, contextEntity.Attributes); + SetRelationships(relatedInstance, includedResource.Relationships, contextEntity.Relationships); + return relatedInstance; + } + + + private ResourceObject GetLinkedResource(ResourceIdentifierObject relatedResourceIdentifier) + { + try + { + return _document.Included.SingleOrDefault(r => r.Type == relatedResourceIdentifier.Type && r.Id == relatedResourceIdentifier.Id); + } + catch (InvalidOperationException e) + { + throw new InvalidOperationException($"A compound document MUST NOT include more than one resource object for each type and id pair." + + $"The duplicate pair was '{relatedResourceIdentifier.Type}, {relatedResourceIdentifier.Id}'", e); + } + } + } +} diff --git a/src/JsonApiDotNetCore/Serialization/Deserializer/DeserializedResponse.cs b/src/JsonApiDotNetCore/Serialization/Deserializer/DeserializedResponse.cs new file mode 100644 index 0000000000..0c3ac99401 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Deserializer/DeserializedResponse.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Models.Links; + +namespace JsonApiDotNetCore.Serialization +{ + /// TODO: Currently and + /// information is ignored by the serializer. This is considered not mission critical for now, and therefore out of scope. + public class DeserializedResponseBase + { + public TopLevelLinks Links { get; internal set; } + public Dictionary Meta { get; internal set; } + public object Errors { get; internal set; } + public object JsonApi { get; internal set; } + } + + public class DeserializedSingleResponse : DeserializedResponseBase where TResource : class, IIdentifiable + { + public TResource Data { get; internal set; } + } + + public class DeserializedListResponse : DeserializedResponseBase where TResource : class, IIdentifiable + { + public List Data { get; internal set; } + } +} diff --git a/src/JsonApiDotNetCore/Serialization/Deserializer/DocumentParser.cs b/src/JsonApiDotNetCore/Serialization/Deserializer/DocumentParser.cs new file mode 100644 index 0000000000..e06263edb0 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Deserializer/DocumentParser.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace JsonApiDotNetCore.Serialization +{ + /// + /// Base class for deserialization. + /// + public abstract class DocumentParser + { + protected Document _document; + protected readonly IContextEntityProvider _provider; + + protected DocumentParser(IContextEntityProvider provider) + { + _provider = provider; + } + + protected abstract void AfterProcessField(IIdentifiable entity, IResourceField field, RelationshipData data = null); + + protected object Deserialize(string body) + { + var bodyJToken = LoadJToken(body); + _document = bodyJToken.ToObject(); + if (_document.IsManyData) + { + if (_document.ManyData.Count == 0) return new List(); + return _document.ManyData.Select(DocumentToObject).ToList(); + } + else + { + if (_document.SingleData == null) return null; + return DocumentToObject(_document.SingleData); + } + } + + protected IIdentifiable SetAttributes(IIdentifiable entity, Dictionary attributeValues, List attributes) + { + if (attributeValues == null || attributeValues.Count == 0) + return entity; + + foreach (var attr in attributes) + { + if (attributeValues.TryGetValue(attr.PublicAttributeName, out object newValue)) + { + var convertedValue = ConvertAttrValue(newValue, attr.PropertyInfo.PropertyType); + attr.SetValue(entity, convertedValue); + AfterProcessField(entity, attr); + } + } + + return entity; + } + + protected IIdentifiable SetRelationships(IIdentifiable entity, Dictionary relationships, List relationshipAttributes) + { + if (relationships == null || relationships.Count == 0) + return entity; + + var entityProperties = entity.GetType().GetProperties(); + foreach (var attr in relationshipAttributes) + { + if (attr is HasOneAttribute hasOne) + SetHasOneRelationship(entity, entityProperties, (HasOneAttribute)attr, relationships); + else + SetHasManyRelationship(entity, (HasManyAttribute)attr, relationships); + + } + return entity; + } + + private JToken LoadJToken(string body) + { + JToken jToken; + using (JsonReader jsonReader = new JsonTextReader(new StringReader(body))) + { + jToken = JToken.Load(jsonReader); + } + return jToken; + } + + private IIdentifiable DocumentToObject(ResourceObject data) + { + var contextEntity = _provider.GetContextEntity(data.Type); + if (contextEntity == null) + { + throw new JsonApiException(400, + message: $"This API does not contain a json:api resource named '{data.Type}'.", + detail: "This resource is not registered on the ResourceGraph. " + + "If you are using Entity Framework, make sure the DbSet matches the expected resource name. " + + "If you have manually registered the resource, check that the call to AddResource correctly sets the public name."); + } + + var entity = (IIdentifiable)Activator.CreateInstance(contextEntity.EntityType); + + entity = SetAttributes(entity, data.Attributes, contextEntity.Attributes); + entity = SetRelationships(entity, data.Relationships, contextEntity.Relationships); + + if (data.Id != null) + entity.StringId = data.Id?.ToString(); + + return entity; + } + + + private object ConvertAttrValue(object newValue, Type targetType) + { + if (newValue is JContainer jObject) + return DeserializeComplexType(jObject, targetType); + + var convertedValue = TypeHelper.ConvertType(newValue, targetType); + return convertedValue; + } + + private object DeserializeComplexType(JContainer obj, Type targetType) + { + return obj.ToObject(targetType); + //return obj.ToObject(targetType, _jsonSerializer); + } + + private object SetHasOneRelationship(IIdentifiable entity, + PropertyInfo[] entityProperties, + HasOneAttribute attr, + Dictionary relationships) + { + if (relationships.TryGetValue(attr.PublicRelationshipName, out RelationshipData relationshipData) == false) + return entity; + + var rio = (ResourceIdentifierObject)relationshipData.Data; + var relatedId = rio?.Id ?? null; + + // this does not make sense in the following case: if we're setting the dependent of a one-to-one relationship, IdentifiablePropertyName should be null. + var foreignKeyProperty = entityProperties.FirstOrDefault(p => p.Name == attr.IdentifiablePropertyName); + + if (foreignKeyProperty == null) + { + /// there is no FK from the current entity pointing to the related object, + /// i.e. means we're populating the relationship from the principal side. + SetPrincipalSide(entity, attr, relatedId); + } + else + { + /// there is a FK from the current entity pointing to the related object, + /// i.e. we're populating the relationship from the dependent side. + SetDependentSide(entity, foreignKeyProperty, attr, relatedId); + } + + AfterProcessField(entity, attr, relationshipData); + + return entity; + } + + private void SetDependentSide(IIdentifiable entity, PropertyInfo foreignKey, HasOneAttribute attr, string id) + { + bool foreignKeyPropertyIsNullableType = Nullable.GetUnderlyingType(foreignKey.PropertyType) != null + || foreignKey.PropertyType == typeof(string); + if (id == null && !foreignKeyPropertyIsNullableType) + { + // this happens when a non-optional relationship is deliberatedly set to null. + // For a server deserializer, it should be mapped to a BadRequest HTTP error code. + throw new FormatException($"Cannot set required relationship identifier '{attr.IdentifiablePropertyName}' to null because it is a non-nullable type."); + } + var convertedId = TypeHelper.ConvertType(id, foreignKey.PropertyType); + foreignKey.SetValue(entity, convertedId); + } + + private void SetPrincipalSide(IIdentifiable entity, HasOneAttribute attr, string relatedId) + { + if (relatedId == null) + { + attr.SetValue(entity, null); + } + else + { + var relatedInstance = attr.DependentType.New(); + relatedInstance.StringId = relatedId; + attr.SetValue(entity, relatedInstance); + } + } + + + private object SetHasManyRelationship(IIdentifiable entity, + HasManyAttribute attr, + Dictionary relationships) + { + if (relationships.TryGetValue(attr.PublicRelationshipName, out RelationshipData relationshipData)) + { + if (!relationshipData.IsManyData) + return entity; + + var relatedResources = relationshipData.ManyData.Select(rio => + { + var relatedInstance = attr.DependentType.New(); + relatedInstance.StringId = rio.Id; + return relatedInstance; + }); + + var convertedCollection = TypeHelper.ConvertCollection(relatedResources, attr.DependentType); + attr.SetValue(entity, convertedCollection); + AfterProcessField(entity, attr, relationshipData); + } + + return entity; + } + } +} diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/Deserializer/OperationsDeserializer.cs similarity index 68% rename from src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs rename to src/JsonApiDotNetCore/Serialization/Deserializer/OperationsDeserializer.cs index 86bd3f3016..372afbf6fe 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Deserializer/OperationsDeserializer.cs @@ -5,25 +5,28 @@ using System.Reflection; using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Internal.Generics; -using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; -using JsonApiDotNetCore.Services; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace JsonApiDotNetCore.Serialization { - public class JsonApiDeSerializer : IJsonApiDeSerializer + + public class OperationsDeserializer : IOperationsDeserializer { - private readonly IJsonApiContext _jsonApiContext; - private readonly IRequestManager _requestManager; + private readonly IUpdatedFields _updatedFieldsManager; + private readonly IResourceGraph _resourceGraph; + private readonly JsonSerializer _jsonSerializer; - public JsonApiDeSerializer(IJsonApiContext jsonApiContext, IRequestManager requestManager) + public OperationsDeserializer(IUpdatedFields updatedFieldsManager, + IResourceGraph resourceGraph, + IJsonApiSerializerSettings serializerSettings) { - _jsonApiContext = jsonApiContext; - _requestManager = requestManager; + _updatedFieldsManager = updatedFieldsManager; + _resourceGraph = resourceGraph; + _jsonSerializer = JsonSerializer.Create(serializerSettings.GetSettings()); } public object Deserialize(string requestBody) @@ -36,24 +39,11 @@ public object Deserialize(string requestBody) jsonReader.DateParseHandling = DateParseHandling.None; bodyJToken = JToken.Load(jsonReader); } - if (RequestIsOperation(bodyJToken)) - { - _jsonApiContext.IsBulkOperationRequest = true; + var operations = JsonConvert.DeserializeObject(requestBody); + if (operations == null) + throw new JsonApiException(400, "Failed to deserialize operations request."); - // TODO: determine whether or not the token should be re-used rather than performing full - // deserialization again from the string - var operations = JsonConvert.DeserializeObject(requestBody); - if (operations == null) - throw new JsonApiException(400, "Failed to deserialize operations request."); - - return operations; - } - - var document = bodyJToken.ToObject(); - - _jsonApiContext.DocumentMeta = document.Meta; - var entity = DocumentToObject(document.Data, document.Included); - return entity; + return operations; } catch (JsonApiException) { @@ -65,61 +55,21 @@ public object Deserialize(string requestBody) } } - private bool RequestIsOperation(JToken bodyJToken) - => _jsonApiContext.Options.EnableOperations - && (bodyJToken.SelectToken("operations") != null); - - public TEntity Deserialize(string requestBody) => (TEntity)Deserialize(requestBody); - - public object DeserializeRelationship(string requestBody) - { - try - { - var data = JToken.Parse(requestBody)["data"]; - - if (data is JArray) - return data.ToObject>(); - - return new List { data.ToObject() }; - } - catch (Exception e) - { - throw new JsonApiException(400, "Failed to deserialize request body", e); - } - } - - public List DeserializeList(string requestBody) - { - try - { - var documents = JsonConvert.DeserializeObject(requestBody); - - var deserializedList = new List(); - foreach (var data in documents.Data) - { - var entity = (TEntity)DocumentToObject(data, documents.Included); - deserializedList.Add(entity); - } - - return deserializedList; - } - catch (Exception e) - { - throw new JsonApiException(400, "Failed to deserialize request body", e); - } - } - public object DocumentToObject(ResourceObject data, List included = null) { if (data == null) throw new JsonApiException(422, "Failed to deserialize document as json:api."); - var contextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(data.Type?.ToString()); - if(contextEntity == null) throw new JsonApiException(400, + var contextEntity = _resourceGraph.GetContextEntity(data.Type?.ToString()); + if (contextEntity == null) + { + throw new JsonApiException(400, message: $"This API does not contain a json:api resource named '{data.Type}'.", detail: "This resource is not registered on the ResourceGraph. " + "If you are using Entity Framework, make sure the DbSet matches the expected resource name. " + "If you have manually registered the resource, check that the call to AddResource correctly sets the public name."); + } + var entity = Activator.CreateInstance(contextEntity.EntityType); @@ -148,12 +98,7 @@ private object SetEntityAttributes( continue; var convertedValue = ConvertAttrValue(newValue, attr.PropertyInfo.PropertyType); attr.SetValue(entity, convertedValue); - /// todo: as a part of the process of decoupling JADNC (specifically - /// through the decoupling IJsonApiContext), we now no longer need to - /// store the updated relationship values in this property. For now - /// just assigning null as value, will remove this property later as a whole. - /// see #512 - _requestManager.GetUpdatedAttributes()[attr] = null; + _updatedFieldsManager.AttributesToUpdate.Add(attr); } } @@ -171,7 +116,7 @@ private object ConvertAttrValue(object newValue, Type targetType) private object DeserializeComplexType(JContainer obj, Type targetType) { - return obj.ToObject(targetType, JsonSerializer.Create(_jsonApiContext.Options.SerializerSettings)); + return obj.ToObject(targetType, _jsonSerializer); } private object SetRelationships( @@ -187,15 +132,9 @@ private object SetRelationships( foreach (var attr in contextEntity.Relationships) { - if (attr.IsHasOne) - { - SetHasOneRelationship(entity, entityProperties, (HasOneAttribute)attr, contextEntity, relationships, included); - } - else - { - SetHasManyRelationship(entity, entityProperties, (HasManyAttribute)attr, contextEntity, relationships, included); - } - + entity = attr.IsHasOne + ? SetHasOneRelationship(entity, entityProperties, (HasOneAttribute)attr, contextEntity, relationships, included) + : SetHasManyRelationship(entity, entityProperties, (HasManyAttribute)attr, contextEntity, relationships, included); } return entity; @@ -213,7 +152,7 @@ private object SetHasOneRelationship(object entity, if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData) == false) return entity; - var rio = (ResourceIdentifierObject)relationshipData.ExposedData; + var rio = (ResourceIdentifierObject)relationshipData.Data; var foreignKey = attr.IdentifiablePropertyName; var foreignKeyProperty = entityProperties.FirstOrDefault(p => p.Name == foreignKey); @@ -228,8 +167,8 @@ private object SetHasOneRelationship(object entity, { var navigationPropertyValue = attr.GetValue(entity); - var resourceGraphEntity = _jsonApiContext.ResourceGraph.GetContextEntity(attr.DependentType); - if(navigationPropertyValue != null && resourceGraphEntity != null) + var resourceGraphEntity = _resourceGraph.GetContextEntity(attr.DependentType); + if (navigationPropertyValue != null && resourceGraphEntity != null) { var includedResource = included.SingleOrDefault(r => r.Type == rio.Type && r.Id == rio.Id); @@ -262,12 +201,7 @@ private void SetHasOneForeignKeyValue(object entity, HasOneAttribute hasOneAttr, /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - if (convertedValue == null) - { - _requestManager.GetUpdatedRelationships()[hasOneAttr] = null; - //_jsonApiContext.HasOneRelationshipPointers.Add(hasOneAttr, null); - } - + if (convertedValue == null) _updatedFieldsManager.RelationshipsToUpdate.Add(hasOneAttr); } } @@ -292,8 +226,7 @@ private void SetHasOneNavigationPropertyValue(object entity, HasOneAttribute has /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - _requestManager.GetUpdatedRelationships()[hasOneAttr] = null; - + _updatedFieldsManager.RelationshipsToUpdate.Add(hasOneAttr); } } @@ -308,7 +241,7 @@ private object SetHasManyRelationship(object entity, if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) { - if (relationshipData.IsHasMany == false || relationshipData.ManyData == null) + if (relationshipData.IsManyData == false) return entity; var relatedResources = relationshipData.ManyData.Select(r => @@ -320,13 +253,7 @@ private object SetHasManyRelationship(object entity, var convertedCollection = TypeHelper.ConvertCollection(relatedResources, attr.DependentType); attr.SetValue(entity, convertedCollection); - /// todo: as a part of the process of decoupling JADNC (specifically - /// through the decoupling IJsonApiContext), we now no longer need to - /// store the updated relationship values in this property. For now - /// just assigning null as value, will remove this property later as a whole. - /// see #512 - _requestManager.GetUpdatedRelationships()[attr] = null; - + _updatedFieldsManager.RelationshipsToUpdate.Add(attr); } return entity; @@ -346,7 +273,7 @@ private IIdentifiable GetIncludedRelationship(ResourceIdentifierObject relatedRe if (includedResource == null) return relatedInstance; - var contextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationshipAttr.DependentType); + var contextEntity = _resourceGraph.GetContextEntity(relationshipAttr.DependentType); if (contextEntity == null) throw new JsonApiException(400, $"Included type '{relationshipAttr.DependentType}' is not a registered json:api resource."); @@ -368,4 +295,4 @@ private ResourceObject GetLinkedResource(ResourceIdentifierObject relatedResourc } } } -} +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/Deserializer/ServerDeserializer.cs b/src/JsonApiDotNetCore/Serialization/Deserializer/ServerDeserializer.cs new file mode 100644 index 0000000000..29d1d7e271 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Deserializer/ServerDeserializer.cs @@ -0,0 +1,36 @@ +using System; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization.Contracts; + +namespace JsonApiDotNetCore.Serialization +{ + public class ServerDeserializer : DocumentParser, IJsonApiDeserializer + { + protected readonly IUpdatedFields _updatedFields; + + public ServerDeserializer(IResourceGraph resourceGraph, + IUpdatedFields updatedFields) : base(resourceGraph) + { + _updatedFields = updatedFields; + } + + public new object Deserialize(string body) + { + return base.Deserialize(body); + } + + protected override void AfterProcessField(IIdentifiable entity, IResourceField field, RelationshipData data = null) + { + if (field is AttrAttribute attr) + { + if (!attr.IsImmutable) + _updatedFields.AttributesToUpdate.Add(attr); + else + throw new InvalidOperationException($"Attribute {attr.PublicAttributeName} is immutable and therefore cannot be updated."); + } + else if (field is RelationshipAttribute relationship) + _updatedFields.RelationshipsToUpdate.Add(relationship); + } + } +} diff --git a/src/JsonApiDotNetCore/Serialization/IJsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/IJsonApiDeSerializer.cs deleted file mode 100644 index 6b6f41fbf7..0000000000 --- a/src/JsonApiDotNetCore/Serialization/IJsonApiDeSerializer.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using JsonApiDotNetCore.Models; - -namespace JsonApiDotNetCore.Serialization -{ - public interface IJsonApiDeSerializer - { - object Deserialize(string requestBody); - TEntity Deserialize(string requestBody); - object DeserializeRelationship(string requestBody); - List DeserializeList(string requestBody); - object DocumentToObject(ResourceObject data, List included = null); - } -} diff --git a/src/JsonApiDotNetCore/Serialization/IJsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/IJsonApiSerializer.cs deleted file mode 100644 index 21eae09980..0000000000 --- a/src/JsonApiDotNetCore/Serialization/IJsonApiSerializer.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace JsonApiDotNetCore.Serialization -{ - public interface IJsonApiSerializer - { - string Serialize(object entity); - } -} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/IJsonApiSerializerSettings.cs b/src/JsonApiDotNetCore/Serialization/IJsonApiSerializerSettings.cs new file mode 100644 index 0000000000..364eddb3df --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/IJsonApiSerializerSettings.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace JsonApiDotNetCore.Serialization +{ + public interface IJsonApiSerializerSettings + { + JsonSerializerSettings GetSettings(); + } +} diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs deleted file mode 100644 index f3db8e866b..0000000000 --- a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Collections.Generic; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; - -namespace JsonApiDotNetCore.Serialization -{ - public class JsonApiSerializer : IJsonApiSerializer - { - private readonly IDocumentBuilder _documentBuilder; - private readonly ILogger _logger; - private readonly IRequestManager _requestManager; - private readonly IJsonApiContext _jsonApiContext; - - public JsonApiSerializer( - IJsonApiContext jsonApiContext, - IDocumentBuilder documentBuilder) - { - _jsonApiContext = jsonApiContext; - _requestManager = jsonApiContext.RequestManager; - _documentBuilder = documentBuilder; - } - - public JsonApiSerializer( - IJsonApiContext jsonApiContext, - IRequestManager requestManager, - IDocumentBuilder documentBuilder, - ILoggerFactory loggerFactory) - { - _requestManager = requestManager; - _jsonApiContext = jsonApiContext; - _documentBuilder = documentBuilder; - _logger = loggerFactory?.CreateLogger(); - } - - public string Serialize(object entity) - { - if (entity == null) - return GetNullDataResponse(); - - if (entity.GetType() == typeof(ErrorCollection) || (_requestManager.GetContextEntity()== null && _jsonApiContext.IsBulkOperationRequest == false)) - return GetErrorJson(entity, _logger); - - if (_jsonApiContext.IsBulkOperationRequest) - return _serialize(entity); - - if (entity is IEnumerable) - return SerializeDocuments(entity); - - return SerializeDocument(entity); - } - - private string GetNullDataResponse() - { - return JsonConvert.SerializeObject(new Document - { - Data = null - }); - } - - private string GetErrorJson(object responseObject, ILogger logger) - { - if (responseObject is ErrorCollection errorCollection) - { - return errorCollection.GetJson(); - } - else - { - if (logger?.IsEnabled(LogLevel.Information) == true) - { - logger.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON."); - } - - return JsonConvert.SerializeObject(responseObject); - } - } - - private string SerializeDocuments(object entity) - { - var entities = entity as IEnumerable; - var documents = _documentBuilder.Build(entities); - return _serialize(documents); - } - - private string SerializeDocument(object entity) - { - var identifiableEntity = entity as IIdentifiable; - var document = _documentBuilder.Build(identifiableEntity); - return _serialize(document); - } - - private string _serialize(object obj) - { - return JsonConvert.SerializeObject(obj, _jsonApiContext.Options.SerializerSettings); - } - } -} diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializerSettings.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializerSettings.cs new file mode 100644 index 0000000000..feda1920c3 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/JsonApiSerializerSettings.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace JsonApiDotNetCore.Serialization +{ + public class JsonApiSerializerSettings : IJsonApiSerializerSettings + { + public JsonSerializerSettings GetSettings() + { + return new JsonSerializerSettings() + { + NullValueHandling = NullValueHandling.Ignore, + ContractResolver = new DasherizedResolver(), + DateParseHandling = DateParseHandling.None + }; + } + + } +} diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/ClientSerializer.cs b/src/JsonApiDotNetCore/Serialization/Serializer/ClientSerializer.cs new file mode 100644 index 0000000000..4203976755 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Serializer/ClientSerializer.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq.Expressions; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Services; + +namespace JsonApiDotNetCore.Builders +{ + public class ClientSerializer : DocumentBuilder + { + private readonly Dictionary> _attributesToSerializeCache = new Dictionary>(); + private readonly Dictionary> _relationshipsToSerializeCache = new Dictionary>(); + private Type _currentTargetedResource; + private readonly IExposedFieldExplorer _fieldExplorer; + public ClientSerializer(IExposedFieldExplorer fieldExplorer, + IContextEntityProvider provider, + IResourceGraph resourceGraph) : base(resourceGraph, provider) + { + _fieldExplorer = fieldExplorer; + } + + /// + /// Creates and serializes a document for a single intance of a resource. + /// + /// Entity to serialize + /// The serialized content + public string Serialize(IIdentifiable entity) + { + if (entity == null) + return GetStringOutput(base.Build(entity)); + + _currentTargetedResource = entity?.GetType(); + var attributes = GetAttributesToSerialize(entity); + var relationships = GetRelationshipsToSerialize(entity); + var document = base.Build(entity, attributes, relationships); + _currentTargetedResource = null; + return GetStringOutput(document); + + } + + /// + /// Creates and serializes a document for for a list of entities of one resource. + /// + /// Entities to serialize + /// The serialized content + public string Serialize(IEnumerable entities) + { + IIdentifiable entity = null; + foreach (IIdentifiable item in entities) + { + entity = item; + break; + } + if (entity == null) + return GetStringOutput(base.Build(entities)); + + _currentTargetedResource = entity?.GetType(); + var attributes = GetAttributesToSerialize(entity); + var relationships = GetRelationshipsToSerialize(entity); + var document = base.Build(entities, attributes, relationships); + _currentTargetedResource = null; + return GetStringOutput(document); + } + + /// + /// Sets the s to serialize for resources of type . + /// If no s are specified, by default all attributes are included in the serialization result. + /// + /// + /// + public void SetAttributesToSerialize(Expression> filter) where T : class, IIdentifiable + { + var allowedAttributes = _fieldExplorer.GetAttributes(filter); + _attributesToSerializeCache[typeof(T)] = allowedAttributes; + } + + /// + /// Sets the s to serialize for resources of type . + /// If no s are specified, by default no relationships are included in the serialization result. + /// + /// + /// + public void SetRelationshipsToSerialize(Expression> filter) where T : class, IIdentifiable + { + var allowedRelationships = _fieldExplorer.GetRelationships(filter); + _relationshipsToSerializeCache[typeof(T)] = allowedRelationships; + } + + /// + /// By default, the client serializer includes all attributes in the result, + /// unless a list of allowed attributes was supplied using the + /// method. For any related resources, attributes are never exposed. + /// + /// Entity to be serialized + /// List of allowed attributes in the serialized result. + private List GetAttributesToSerialize(IIdentifiable entity) + { + var resourceType = entity.GetType(); + if (_currentTargetedResource != resourceType) + // We're dealing with a relationship that is being serialized, for which + // we never want to include any attributes in the payload. + return new List(); + + if (!_attributesToSerializeCache.TryGetValue(resourceType, out var attributes)) + return _fieldExplorer.GetAttributes(resourceType); + + return attributes; + } + + /// + /// By default, the client serializer does not include any relationships + /// for entities in the primary data unless explicitly included using + /// . + /// + /// Entity to be serialized + /// List of allowed relationships in the serialized result. + private List GetRelationshipsToSerialize(IIdentifiable entity) + { + var currentResourceType = entity.GetType(); + /// only allow relationship attributes to be serialized if they were set using + /// + /// and the current is a main entry in the primary data. + if (!_relationshipsToSerializeCache.TryGetValue(currentResourceType, out var relationships)) + return new List(); + + return relationships; + } + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/DocumentBuilder.cs b/src/JsonApiDotNetCore/Serialization/Serializer/DocumentBuilder.cs new file mode 100644 index 0000000000..6aad0c246e --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Serializer/DocumentBuilder.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; +using Newtonsoft.Json; + +namespace JsonApiDotNetCore.Builders +{ + public abstract class DocumentBuilder : ResourceObjectBuilder + { + protected DocumentBuilder(IResourceGraph resourceGraph, IContextEntityProvider provider) : base(resourceGraph, provider) { } + protected Document Build(IIdentifiable entity, List attributes = null, List relationships = null) + { + if (entity == null) + return new Document(); + + return new Document { Data = BuildResourceObject(entity, attributes, relationships) }; + } + + protected Document Build(IEnumerable entities, List attributes = null, List relationships = null) + { + var data = new List(); + foreach (IIdentifiable entity in entities) + data.Add(BuildResourceObject(entity, attributes, relationships)); + + return new Document { Data = data }; + } + + protected string GetStringOutput(Document document) + { + //var settings = new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore }; + return JsonConvert.SerializeObject(document); + } + } +} diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/IIncludedRelationshipsBuilder.cs b/src/JsonApiDotNetCore/Serialization/Serializer/IIncludedRelationshipsBuilder.cs new file mode 100644 index 0000000000..c5889b0bd2 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Serializer/IIncludedRelationshipsBuilder.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Builders +{ + public interface IIncludedRelationshipsBuilder + { + List Build(); + void IncludeRelationshipChain(List inclusionChain, IIdentifiable rootEntity); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/IJsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/Serializer/IJsonApiSerializer.cs new file mode 100644 index 0000000000..b58c24f5fb --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Serializer/IJsonApiSerializer.cs @@ -0,0 +1,10 @@ +using System.Collections; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Builders +{ + public interface IJsonApiSerializer + { + string Serialize(object content); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/IServerSerializerFactory.cs b/src/JsonApiDotNetCore/Serialization/Serializer/IServerSerializerFactory.cs new file mode 100644 index 0000000000..d0aec078ee --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Serializer/IServerSerializerFactory.cs @@ -0,0 +1,7 @@ +namespace JsonApiDotNetCore.Builders +{ + public interface IJsonApiSerializerFactory + { + IJsonApiSerializer GetSerializer(); + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/IncludedRelationshipsBuilder.cs b/src/JsonApiDotNetCore/Serialization/Serializer/IncludedRelationshipsBuilder.cs new file mode 100644 index 0000000000..32907b620c --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Serializer/IncludedRelationshipsBuilder.cs @@ -0,0 +1,112 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Builders +{ + + public class IncludedRelationshipsBuilder : ResourceObjectBuilder, IIncludedRelationshipsBuilder + { + private readonly HashSet _included; + private readonly ISerializableFields _serializableFields; + private readonly ILinkBuilder _linkBuilder; + + public IncludedRelationshipsBuilder(ISerializableFields serializableFields, + ILinkBuilder linkBuilder, + IResourceGraph resourceGraph, + IContextEntityProvider provider) : base(resourceGraph, provider) + { + _included = new HashSet(new ResourceObjectComparer()); + _serializableFields = serializableFields; + _linkBuilder = linkBuilder; + } + + public List Build() + { + if (_included.Any()) + { + foreach (var resourceObject in _included) + { + if (resourceObject.Relationships != null) + { + var pruned = resourceObject.Relationships.Where(p => p.Value.IsPopulated || p.Value.Links != null).ToDictionary(p => p.Key, p => p.Value); + if (!pruned.Any()) + pruned = null; + resourceObject.Relationships = pruned; + } + + resourceObject.Links = _linkBuilder.GetResourceLinks(resourceObject.Type, resourceObject.Id); + } + + return _included.ToList(); + } + return null; + } + + + public void IncludeRelationshipChain(List inclusionChain, IIdentifiable rootEntity) + { + /// we dont have to build a resource object for the root entity because this one is + /// in the documents primary data. + var relationship = inclusionChain.First(); + var chainRemainder = ShiftChain(inclusionChain); + var related = _resourceGraph.GetRelationshipValue(rootEntity, relationship); + + ProcessChain(relationship, related, chainRemainder); + } + + private void ProcessChain(RelationshipAttribute originRelationship, object related, List inclusionChain ) + { + if (related is IEnumerable children) + foreach (IIdentifiable child in children) + ProcessRelationship(originRelationship, child, inclusionChain); + else + ProcessRelationship(originRelationship, (IIdentifiable)related, inclusionChain); + } + + private void ProcessRelationship(RelationshipAttribute originRelationship, IIdentifiable parent, List inclusionChain) + { + var resourceObject = GetOrBuildResourceObject(parent, originRelationship); + if (!inclusionChain.Any()) + return; + + var nextRelationship = inclusionChain.First(); + var chainRemainder = inclusionChain.ToList(); + chainRemainder.RemoveAt(0); + var relationshipData = base.GetRelationshipData(nextRelationship, parent); + resourceObject.Relationships[nextRelationship.PublicRelationshipName] = relationshipData; + if (relationshipData.HasData) + { + var related = _resourceGraph.GetRelationshipValue(parent, nextRelationship); + ProcessChain(nextRelationship, related, chainRemainder); + } + } + + private List ShiftChain(List chain) + { + var chainRemainder = chain.ToList(); + chainRemainder.RemoveAt(0); + return chainRemainder; + } + + protected override RelationshipData GetRelationshipData(RelationshipAttribute relationship, IIdentifiable entity) + { + return new RelationshipData { Links = _linkBuilder.GetRelationshipLinks(relationship, entity) }; + } + + private ResourceObject GetOrBuildResourceObject(IIdentifiable parent, RelationshipAttribute attr) + { + var type = parent.GetType(); + var resourceName = _provider.GetContextEntity(type).EntityName; + var entry = _included.SingleOrDefault(ro => ro.Type == resourceName && ro.Id == parent.StringId); + if (entry == null) + { + entry = BuildResourceObject(parent, _serializableFields.GetAllowedAttributes(type), _serializableFields.GetAllowedRelationships(type)); + _included.Add(entry); + } + return entry; + } + } +} diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/LinkBuilder.cs b/src/JsonApiDotNetCore/Serialization/Serializer/LinkBuilder.cs new file mode 100644 index 0000000000..4994bf36a9 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Serializer/LinkBuilder.cs @@ -0,0 +1,152 @@ +using System; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Models.Links; +using JsonApiDotNetCore.Services; + +namespace JsonApiDotNetCore.Builders +{ + public class LinkBuilder : ILinkBuilder + { + private readonly IRequestManager _requestManager; + private readonly IGlobalLinksConfiguration _options; + private readonly IPageQueryService _pageManager; + private readonly ContextEntity _requestResourceContext; + private readonly IContextEntityProvider _provider; + + public LinkBuilder(IGlobalLinksConfiguration options, + IRequestManager requestManager, + IPageQueryService pageManager, + IContextEntityProvider provider) + { + _options = options; + _requestManager = requestManager; + _pageManager = pageManager; + _provider = provider; + _requestResourceContext = _requestManager.GetRequestResource(); + } + + /// + public TopLevelLinks GetTopLevelLinks() + { + TopLevelLinks topLevelLinks = null; + if (ShouldAddTopLevelLink(Link.Self)) + topLevelLinks = new TopLevelLinks { Self = GetSelfTopLevelLink(_requestResourceContext.EntityName) }; + + if (ShouldAddTopLevelLink(Link.Paging)) + SetPageLinks(ref topLevelLinks); + + return topLevelLinks; + } + + /// + public ResourceLinks GetResourceLinks(string resourceName, string id) + { + var resourceContext = _provider.GetContextEntity(resourceName); + if (ShouldAddResourceLink(resourceContext, Link.Self)) + return new ResourceLinks { Self = GetSelfResourceLink(resourceName, id) }; + + return null; + } + + /// + public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable parent) + { + var parentResourceContext = _provider.GetContextEntity(parent.GetType()); + var childNavigation = relationship.PublicRelationshipName; + RelationshipLinks links = null; + if (ShouldAddRelationshipLink(parentResourceContext, relationship, Link.Related)) + links = new RelationshipLinks { Related = GetRelatedRelationshipLink(parentResourceContext.EntityName, parent.StringId, childNavigation) }; + + if (ShouldAddRelationshipLink(parentResourceContext, relationship, Link.Self)) + { + links = links ?? new RelationshipLinks(); + links.Self = GetSelfRelationshipLink(parentResourceContext.EntityName, parent.StringId, childNavigation); + } + + return links; + } + + private void SetPageLinks(ref TopLevelLinks links) + { + if (!_pageManager.ShouldPaginate()) + return; + + links = links ?? new TopLevelLinks(); + + if (_pageManager.CurrentPage > 1) + { + links.First = GetPageLink(1, _pageManager.PageSize); + links.Prev = GetPageLink(_pageManager.CurrentPage - 1, _pageManager.PageSize); + } + + + if (_pageManager.CurrentPage < _pageManager.TotalPages) + links.Next = GetPageLink(_pageManager.CurrentPage + 1, _pageManager.PageSize); + + + if (_pageManager.TotalPages > 0) + links.Last = GetPageLink(_pageManager.TotalPages, _pageManager.PageSize); + } + + private string GetSelfTopLevelLink(string resourceName) + { + return $"{GetBasePath()}/{resourceName}"; + } + + private string GetSelfRelationshipLink(string parent, string parentId, string navigation) + { + return $"{GetBasePath()}/{parent}/{parentId}/relationships/{navigation}"; + } + + private string GetSelfResourceLink(string resource, string resourceId) + { + return $"{GetBasePath()}/{resource}/{resourceId}"; + } + + private string GetRelatedRelationshipLink(string parent, string parentId, string navigation) + { + return $"{GetBasePath()}/{parent}/{parentId}/{navigation}"; + } + + private string GetPageLink(int pageOffset, int pageSize) + { + var filterQueryComposer = new QueryComposer(); + var filters = filterQueryComposer.Compose(_requestManager); + return $"{GetBasePath()}/{_requestResourceContext.EntityName}?page[size]={pageSize}&page[number]={pageOffset}{filters}"; + } + + private bool ShouldAddTopLevelLink(Link link) + { + if (_requestResourceContext.TopLevelLinks != Link.NotConfigured) + return _requestResourceContext.TopLevelLinks.HasFlag(link); + return _options.TopLevelLinks.HasFlag(link); + } + + private bool ShouldAddResourceLink(ContextEntity resourceContext, Link link) + { + if (resourceContext.ResourceLinks != Link.NotConfigured) + return resourceContext.ResourceLinks.HasFlag(link); + return _options.ResourceLinks.HasFlag(link); + } + + private bool ShouldAddRelationshipLink(ContextEntity resourceContext, RelationshipAttribute relationship, Link link) + { + if (relationship.RelationshipLinks != Link.NotConfigured) + return relationship.RelationshipLinks.HasFlag(link); + if (resourceContext.RelationshipLinks != Link.NotConfigured) + return resourceContext.RelationshipLinks.HasFlag(link); + return _options.RelationshipLinks.HasFlag(link); + } + + private string GetBasePath() + { + if (_options.RelativeLinks) + return string.Empty; + return _requestManager.BasePath; + } + } +} diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/MetaBuilder.cs b/src/JsonApiDotNetCore/Serialization/Serializer/MetaBuilder.cs new file mode 100644 index 0000000000..643244269d --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Serializer/MetaBuilder.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Services; + +namespace JsonApiDotNetCore.Builders +{ + public class MetaBuilder : IMetaBuilder where T : class, IIdentifiable + { + private Dictionary _meta = new Dictionary(); + private readonly IPageQueryService _pageManager; + private readonly IJsonApiOptions _options; + private readonly IRequestMeta _requestMeta; + private readonly IHasMeta _resourceMeta; + + public MetaBuilder(IPageQueryService pageManager, + IJsonApiOptions options, + IRequestMeta requestMeta = null, + ResourceDefinition resourceDefinition = null) + { + _pageManager = pageManager; + _options = options; + _requestMeta = requestMeta; + _resourceMeta = resourceDefinition as IHasMeta; + } + + public void Add(string key, object value) + { + _meta[key] = value; + } + + /// + /// Joins the new dictionary with the current one. In the event of a key collision, + /// the new value will override the old. + /// + public void Add(Dictionary values) + { + _meta = values.Keys.Union(_meta.Keys) + .ToDictionary(key => key, + key => values.ContainsKey(key) ? values[key] : _meta[key]); + } + + public Dictionary GetMeta() + { + if (_options.IncludeTotalRecordCount && _pageManager.TotalRecords != null) + _meta.Add("total-records", _pageManager.TotalRecords); + + if (_requestMeta != null) + Add(_requestMeta.GetMeta()); + + if (_resourceMeta != null) + Add(_resourceMeta.GetMeta()); + + if (_meta.Any()) return _meta; + return null; + } + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/ResourceObjectBuilder.cs b/src/JsonApiDotNetCore/Serialization/Serializer/ResourceObjectBuilder.cs new file mode 100644 index 0000000000..adfa84009a --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Serializer/ResourceObjectBuilder.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Builders +{ + public abstract class ResourceObjectBuilder + { + protected readonly IResourceGraph _resourceGraph; + protected readonly IContextEntityProvider _provider; + private const string _identifiablePropertyName = nameof(Identifiable.Id); + + protected ResourceObjectBuilder(IResourceGraph resourceGraph, IContextEntityProvider provider) + { + _resourceGraph = resourceGraph; + _provider = provider; + } + + protected ResourceObject BuildResourceObject(IIdentifiable entity, IEnumerable attrs = null, IEnumerable rels = null) + { + var resourceContext = _provider.GetContextEntity(entity.GetType()); + + // populating the top-level "type" and "id" members. + var ro = new ResourceObject { Type = resourceContext.EntityName, Id = entity.StringId.NullIfEmpty() }; + + // populating the top-level "attribute" member, if any + if (attrs != null) + { + // never include "id" as an attribute + attrs = attrs.Where(attr => attr.InternalAttributeName != _identifiablePropertyName); + if (attrs.Any()) + { + ro.Attributes = new Dictionary(); + foreach (var attr in attrs) + ro.Attributes.Add(attr.PublicAttributeName, attr.GetValue(entity)); + } + } + + // populating top-level "relationship" member, if any + if (rels != null && rels.Any()) + { + foreach (var rel in rels) + { + var relData = GetRelationshipData(rel, entity); + if (relData != null) + (ro.Relationships = ro.Relationships ?? new Dictionary()).Add(rel.PublicRelationshipName, relData); + } + } + + return ro; + } + + protected ResourceIdentifierObject GetRelatedResourceLinkage(HasOneAttribute attr, IIdentifiable entity) + { + var relatedEntity = (IIdentifiable)_resourceGraph.GetRelationshipValue(entity, attr); + if (relatedEntity == null && IsRequiredToOneRelationship(attr, entity)) + throw new NotSupportedException("Cannot serialize a required to one relationship that is not populated but was included in the set of relationships to be serialized."); + + if (relatedEntity != null) + return CreateResourceIdentifier(relatedEntity); + + return null; + } + + protected List GetRelatedResourceLinkage(HasManyAttribute attr, IIdentifiable entity) + { + var relatedEntities = (IEnumerable)_resourceGraph.GetRelationshipValue(entity, attr); + var manyData = new List(); + if (relatedEntities != null) + foreach (IIdentifiable relatedEntity in relatedEntities) + manyData.Add(CreateResourceIdentifier(relatedEntity)); + + return manyData; + } + + /// + /// Builds the entries of the "relationships" + /// objects. The default behaviour is to just construct a resource linkage + /// with the "data" field populated with "single" or "many" data. + /// + protected virtual RelationshipData GetRelationshipData(RelationshipAttribute relationship, IIdentifiable entity) + { + if (relationship is HasOneAttribute hasOne) + return new RelationshipData { Data = GetRelatedResourceLinkage(hasOne, entity) }; + + return new RelationshipData { Data = GetRelatedResourceLinkage((HasManyAttribute)relationship, entity) }; + } + + /// + /// Creates a from . + /// + private ResourceIdentifierObject CreateResourceIdentifier(IIdentifiable entity) + { + var resourceName = _provider.GetContextEntity(entity.GetType()).EntityName; + return new ResourceIdentifierObject + { + Type = resourceName, + Id = entity.StringId + }; + } + + /// + /// Checks if the to-one relationship is required by checking if the foreign key is nullable. + /// + private bool IsRequiredToOneRelationship(HasOneAttribute attr, IIdentifiable entity) + { + var foreignKey = entity.GetType().GetProperty(attr.IdentifiablePropertyName); + if (foreignKey != null && Nullable.GetUnderlyingType(foreignKey.PropertyType) == null) + return true; + + return false; + } + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/ResourceObjectComparer.cs b/src/JsonApiDotNetCore/Serialization/Serializer/ResourceObjectComparer.cs new file mode 100644 index 0000000000..e7da59bb98 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Serializer/ResourceObjectComparer.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Builders +{ + class ResourceObjectComparer : IEqualityComparer + { + public bool Equals(ResourceObject x, ResourceObject y) + { + return x.Id.Equals(y.Id) && x.Type.Equals(y.Type); + } + + public int GetHashCode(ResourceObject ro) + { + return ro.GetHashCode(); + } + } +} diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/ServerSerializer.cs b/src/JsonApiDotNetCore/Serialization/Serializer/ServerSerializer.cs new file mode 100644 index 0000000000..7f6f8729f2 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Serializer/ServerSerializer.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace JsonApiDotNetCore.Builders +{ + + public class ServerSerializerFactory : IJsonApiSerializerFactory + { + private readonly IRequestManager _requestManager; + private readonly IServiceProvider _provider; + + public ServerSerializerFactory(IRequestManager requestManager, IServiceProvider provider) + { + _requestManager = requestManager; + _provider = provider; + } + public IJsonApiSerializer GetSerializer() + { + var serializerType = typeof(ServerSerializer<>).MakeGenericType(_requestManager.GetRequestResource().EntityType); + return (IJsonApiSerializer)_provider.GetRequiredService(serializerType); + } + } + + public class ServerSerializer : DocumentBuilder, IJsonApiSerializer where T : class, IIdentifiable + { + private readonly Dictionary> _attributesToSerializeCache = new Dictionary>(); + private readonly Dictionary> _relationshipsToSerializeCache = new Dictionary>(); + private readonly IIncludedQueryService _includedQuery; + private readonly IFieldsQueryService _fieldQuery; + private readonly ISerializableFields _serializableFields; + private readonly IMetaBuilder _metaBuilder; + private readonly Type _requestResourceType; + private readonly ILinkBuilder _linkBuilder; + private readonly IIncludedRelationshipsBuilder _includedBuilder; + + public ServerSerializer( + IMetaBuilder metaBuilder, + ILinkBuilder linkBuilder, + IIncludedRelationshipsBuilder includedBuilder, + ISerializableFields serializableFields, + IIncludedQueryService includedQuery, + IFieldsQueryService fieldQuery, + IResourceGraph resourceGraph, IContextEntityProvider provider) : base(resourceGraph, provider) + { + _includedQuery = includedQuery; + _fieldQuery = fieldQuery; + _serializableFields = serializableFields; + _linkBuilder = linkBuilder; + _metaBuilder = metaBuilder; + _includedBuilder = includedBuilder; + _requestResourceType = typeof(T); + } + + public string Serialize(object content) + { + if (content is IEnumerable entities) + return SerializeMany(entities); + return SerializeSingle((IIdentifiable)content); + } + + internal string SerializeSingle(IIdentifiable entity) + { + var attributes = GetAttributesToSerialize(_requestResourceType); + var relationships = GetRelationshipsToSerialize(_requestResourceType); + var document = Build(entity, attributes, relationships); + var resourceObject = document.SingleData; + if (resourceObject != null) resourceObject.Links = _linkBuilder.GetResourceLinks(resourceObject.Type, resourceObject.Id); + AddTopLevelObjects(document); + return GetStringOutput(document); + } + + internal string SerializeMany(IEnumerable entities) + { + var attributes = GetAttributesToSerialize(_requestResourceType); + var relationships = GetRelationshipsToSerialize(_requestResourceType); + var document = Build(entities, attributes, relationships); + foreach (ResourceObject resourceObject in (IEnumerable)document.Data) + { + var links = _linkBuilder.GetResourceLinks(resourceObject.Type, resourceObject.Id); + if (links == null) + break; + + resourceObject.Links = links; + } + AddTopLevelObjects(document); + return GetStringOutput(document); + } + + /// + /// Gets the list of attributes to serialize for the given . + /// Depending on if instance-dependent attribute hiding was implemented in the corresponding + /// , the server serializer caches the output list of attributes + /// or recalculates it for every instance. Note that the choice omitting null-values + /// is not handled here, but in . + /// + /// Type of entity to be serialized + /// List of allowed attributes in the serialized result + private List GetAttributesToSerialize(Type resourceType) + { + /// Check the attributes cache to see if the allowed attrs for this resource type were determined before. + if (_attributesToSerializeCache.TryGetValue(resourceType, out List allowedAttributes)) + return allowedAttributes; + + // Get the list of attributes to be exposed for this type + allowedAttributes = _serializableFields.GetAllowedAttributes(resourceType); + var fields = _fieldQuery.Get(); + if (fields != null) + // from the allowed attributes, select the ones flagged by sparse field selection. + allowedAttributes = allowedAttributes.Where(attr => !fields.Contains(attr)).ToList(); + + // add to cache so we we don't have to look this up next time. + _attributesToSerializeCache.Add(resourceType, allowedAttributes); + return allowedAttributes; + } + + /// + /// By default, the server serializer exposes all defined relationships, unless + /// in the a subset to hide was defined explicitly. + /// + /// Type of entity to be serialized + /// List of allowed relationships in the serialized result + private List GetRelationshipsToSerialize(Type resourceType) + { + /// Check the relationships cache to see if the allowed attrs for this resource type were determined before. + if (_relationshipsToSerializeCache.TryGetValue(resourceType, out List allowedRelations)) + return allowedRelations; + + // Get the list of relationships to be exposed for this type + allowedRelations = _serializableFields.GetAllowedRelationships(resourceType); + _relationshipsToSerializeCache.Add(resourceType, allowedRelations); + return allowedRelations; + + } + + /// + /// Builds the values of the relationships object on a resource object. + /// The server serializer only populates the "data" member when the relationship is included, + /// and adds links unless these are turned off. This means that if a relationship is not included + /// and links are turned off, the entry would be completely empty, ie { }, which is not conform + /// json:api spec. In that case we return null which will omit the entry from the output. + /// + /// + /// + /// + protected override RelationshipData GetRelationshipData(RelationshipAttribute relationship, IIdentifiable entity) + { + RelationshipData relationshipData = null; + /// if the relationship is included, populate the "data" field. + if (ShouldInclude(relationship, out var relationshipChain)) + { + relationshipData = base.GetRelationshipData(relationship, entity); + if (relationshipData.HasData) + _includedBuilder.IncludeRelationshipChain(relationshipChain, entity); + } + + var links = _linkBuilder.GetRelationshipLinks(relationship, entity); + /// if links relationshiplinks should be built for this entry, populate the "links" field. + if (links != null) + { + relationshipData = relationshipData ?? new RelationshipData(); + relationshipData.Links = links; + } + + /// if neither "links" nor "data" was popupated, return null, which will omit this entry from the output. + return relationshipData; + } + + private void AddTopLevelObjects(Document document) + { + document.Links = _linkBuilder.GetTopLevelLinks(); + document.Meta = _metaBuilder.GetMeta(); + document.Included = _includedBuilder.Build(); + } + + private bool ShouldInclude(RelationshipAttribute relationship, out List inclusionChain) + { + inclusionChain = _includedQuery.Get()?.SingleOrDefault(l => l.First().Equals(relationship)); + if (inclusionChain == null) + return false; + return true; + } + } +} diff --git a/src/JsonApiDotNetCore/Services/ResourceFieldExplorer.cs b/src/JsonApiDotNetCore/Services/ResourceFieldExplorer.cs new file mode 100644 index 0000000000..9f67b1c45e --- /dev/null +++ b/src/JsonApiDotNetCore/Services/ResourceFieldExplorer.cs @@ -0,0 +1,10 @@ +using System; +namespace JsonApiDotNetCore.Services +{ + public class ResourceFieldExplorer + { + public ResourceFieldExplorer() + { + } + } +} diff --git a/test/UnitTests/Builders/LinkTests.cs b/test/UnitTests/Builders/LinkTests.cs new file mode 100644 index 0000000000..8adf2649da --- /dev/null +++ b/test/UnitTests/Builders/LinkTests.cs @@ -0,0 +1,10 @@ +using System; +namespace UnitTests.Builders +{ + public class LinkTests + { + public LinkTests() + { + } + } +} diff --git a/test/UnitTests/Deserialization/BaseDeserializerTests.cs b/test/UnitTests/Deserialization/BaseDeserializerTests.cs new file mode 100644 index 0000000000..7cc858f793 --- /dev/null +++ b/test/UnitTests/Deserialization/BaseDeserializerTests.cs @@ -0,0 +1,370 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class BaseDeserializerTests : DeserializerTestsSetup + { + private readonly DeserializerBase _deserializer; + public BaseDeserializerTests() + { + _deserializer = new DeserializerBase(_resourceGraph, _defaultSettings); + } + + [Fact] + public void DeserializeResourceIdentifiers_SingleData_CanDeserialize() + { + // arange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (TestResource)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + + } + + [Fact] + public void DeserializeResourceIdentifiers_EmptySingleData_CanDeserialize() + { + // arange + var content = new Document { }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.Deserialize(body); + + // arrange + Assert.Null(result); + } + + [Fact] + public void DeserializeResourceIdentifiers_ArrayData_CanDeserialize() + { + // arange + var content = new Documents + { + Data = new List + { + new ResourceObject + { + Type = "test-resource", + Id = "1", + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (List)_deserializer.Deserialize(body); + + // assert + Assert.Equal("1", result.First().StringId); + } + + [Fact] + public void DeserializeResourceIdentifiers_EmptyArrayData_CanDeserialize() + { + var content = new Documents { Data = new List { } }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (IList)_deserializer.Deserialize(body); + + // assert + Assert.Empty(result); + } + + [Theory] + [InlineData("string-field", "some string")] + [InlineData("string-field", null)] + [InlineData("int-field", null, true)] + [InlineData("int-field", 1)] + [InlineData("int-field", "1")] + [InlineData("nullable-int-field", null)] + [InlineData("nullable-int-field", "1")] + [InlineData("guid-field", "bad format", true)] + [InlineData("guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781")] + [InlineData("date-time-field", "9/11/2019 11:41:40 AM")] + [InlineData("date-time-field", null, true)] + [InlineData("nullable-date-time-field", null)] + public void DeserializeAttributes_VariousDataTypes_CanDeserialize(string member, object value, bool expectError = false) + { + // arrange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { member, value } + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act, assert + if (expectError) + { + Assert.ThrowsAny(() => _deserializer.Deserialize(body)); + return; + } + + // act + var entity = (TestResource)_deserializer.Deserialize(body); + + // assert + var pi = _resourceGraph.GetContextEntity("test-resource").Attributes.Single(attr => attr.PublicAttributeName == member).PropertyInfo; + var deserializedValue = pi.GetValue(entity); + + if (member == "int-field") + { + Assert.Equal(deserializedValue, 1); + } + else if (member == "nullable-int-field" && value == null) + { + Assert.Equal(deserializedValue, null); + } + else if (member == "nullable-int-field" && (string)value == "1") + { + Assert.Equal(deserializedValue, 1); + } + else if (member == "guid-field") + { + Assert.Equal(deserializedValue, Guid.Parse("1a68be43-cc84-4924-a421-7f4d614b7781")); + } + else if (member == "date-time-field") + { + Assert.Equal(deserializedValue, DateTime.Parse("9/11/2019 11:41:40 AM")); + } else + { + Assert.Equal(value, deserializedValue); + } + } + + [Fact] + public void DeserializeAttributes_ComplexType_CanDeserialize() + { + // arrange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { "complex-field", new Dictionary { {"compound-name", "testName" } } } + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (TestResource)_deserializer.Deserialize(body); + + // assert + Assert.NotNull(result.ComplexField); + Assert.Equal("testName", result.ComplexField.CompoundName); + } + + [Fact] + public void DeserializeAttributes_ComplexListType_CanDeserialize() + { + // arrange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource-with-list", + Id = "1", + Attributes = new Dictionary + { + { "complex-fields", new [] { new Dictionary { {"compound-name", "testName" } } } } + } + } + }; + var body = JsonConvert.SerializeObject(content); + + + // act + var result = (TestResourceWithList)_deserializer.Deserialize(body); + + // assert + Assert.NotNull(result.ComplexFields); + Assert.NotEmpty(result.ComplexFields); + Assert.Equal("testName", result.ComplexFields[0].CompoundName); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToOneDependent_NavigationPropertyIsNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOnePrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Dependent); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToOneDependent_NavigationPropertyIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent", "one-to-one-dependents"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOnePrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Equal(10, result.Dependent.Id); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToOnePrincipal_NavigationPropertyAndForeignKeyAreNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOneDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Null(result.PrincipalId); + } + + [Fact] + public void DeserializeRelationships_EmptyRequiredOneToOnePrincipal_ThrowsFormatException() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-required-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act, assert + Assert.ThrowsAny(() => _deserializer.Deserialize(body)); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToOnePrincipal_NavigationPropertyIsNullAndForeignKeyIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal", "one-to-one-principals"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOneDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Equal(10, result.PrincipalId); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToManyPrincipal_NavigationAndForeignKeyAreNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Null(result.PrincipalId); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToManyRequiredPrincipal_ThrowsFormatException() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-required-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act, assert + Assert.ThrowsAny(() => _deserializer.Deserialize(body)); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToManyPrincipal_NavigationIsNullAndForeignKeyIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal", "one-to-many-principals"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Equal(10, result.PrincipalId); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToManyDependent_NavigationIsNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyPrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Dependents); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToManyDependent_NavigationIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents", "one-to-many-dependents", isToManyData: true); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyPrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Equal(1, result.Dependents.Count); + Assert.Equal(10, result.Dependents.First().Id); + Assert.Null(result.AttributeMember); + } + } +} diff --git a/test/UnitTests/Deserialization/ClientDeserializerTests.cs b/test/UnitTests/Deserialization/ClientDeserializerTests.cs new file mode 100644 index 0000000000..315b932b11 --- /dev/null +++ b/test/UnitTests/Deserialization/ClientDeserializerTests.cs @@ -0,0 +1,333 @@ +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class ClientDeserializerTests : DeserializerTestsSetup + { + private readonly Dictionary _linkValues = new Dictionary(); + private readonly ClientDeserializer _deserializer; + + public ClientDeserializerTests() + { + _deserializer = new ClientDeserializer(_resourceGraph, _defaultSettings); + _linkValues.Add("self", "http://example.com/articles"); + _linkValues.Add("next", "http://example.com/articles?page[offset]=2"); + _linkValues.Add("last", "http://example.com/articles?page[offset]=10"); + } + + [Fact] + public void DeserializeSingle_EmptyResponseWithMeta_CanDeserialize() + { + // arrange + var content = new Document + { + Meta = new Dictionary { { "foo", "bar" } } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + + // assert + Assert.Null(result.Data); + Assert.NotNull(result.Meta); + Assert.Equal("bar", result.Meta["foo"]); + } + + [Fact] + public void DeserializeSingle_EmptyResponseWithTopLevelLinks_CanDeserialize() + { + // arrange + var content = new Document + { + Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + + // assert + Assert.Null(result.Data); + Assert.NotNull(result.Links); + Assert.Equal(_linkValues["self"], result.Links.Self); + Assert.Equal(_linkValues["next"], result.Links.Next); + Assert.Equal(_linkValues["last"], result.Links.Last); + } + + [Fact] + public void DeserializeList_EmptyResponseWithTopLevelLinks_CanDeserialize() + { + // arrange + var content = new Documents + { + Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] }, + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeList(body); + + // assert + Assert.Empty(result.Data); + Assert.NotNull(result.Links); + Assert.Equal(_linkValues["self"], result.Links.Self); + Assert.Equal(_linkValues["next"], result.Links.Next); + Assert.Equal(_linkValues["last"], result.Links.Last); + } + + [Fact] + public void DeserializeSingle_ResourceWithAttributes_CanDeserialize() + { + // arrange + var content = CreateTestResourceDocument(); + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Null(result.Links); + Assert.Null(result.Meta); + Assert.Equal(1, entity.Id); + Assert.Equal(content.Data.Attributes["string-field"], entity.StringField); + } + + [Fact] + public void DeserializeSingle_MultipleDependentRelationshipsWithIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); + content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); + var toOneAttributeValue = "populated-to-one member content"; + var toManyAttributeValue = "populated-to-manies member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "one-to-one-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } + }, + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + Assert.NotNull(entity.PopulatedToOne); + Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); + Assert.Equal(toManyAttributeValue, entity.PopulatedToManies.First().AttributeMember); + Assert.NotNull(entity.PopulatedToManies); + Assert.NotNull(entity.EmptyToManies); + Assert.Empty(entity.EmptyToManies); + Assert.Null(entity.EmptyToOne); + } + + [Fact] + public void DeserializeSingle_MultiplePrincipalRelationshipsWithIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-dependents"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); + content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); + var toOneAttributeValue = "populated-to-one member content"; + var toManyAttributeValue = "populated-to-manies member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "one-to-one-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + Assert.NotNull(entity.PopulatedToOne); + Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); + Assert.Equal(toManyAttributeValue, entity.PopulatedToMany.AttributeMember); + Assert.NotNull(entity.PopulatedToMany); + Assert.Null(entity.EmptyToMany); + Assert.Null(entity.EmptyToOne); + } + + [Fact] + public void DeserializeSingle_NestedIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + var toManyAttributeValue = "populated-to-manies member content"; + var nestedIncludeAttributeValue = "nested include member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } }, + Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", nestedIncludeAttributeValue } } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + Assert.Null(entity.PopulatedToOne); + Assert.Null(entity.EmptyToManies); + Assert.Null(entity.EmptyToOne); + Assert.NotNull(entity.PopulatedToManies); + var includedEntity = entity.PopulatedToManies.First(); + Assert.Equal(toManyAttributeValue, includedEntity.AttributeMember); + var nestedIncludedEntity = includedEntity.Principal; + Assert.Equal(nestedIncludeAttributeValue, nestedIncludedEntity.AttributeMember); + } + + + [Fact] + public void DeserializeSingle_DeeplyNestedIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("multi", CreateRelationshipData("multi-principals")); + var includedAttributeValue = "multi member content"; + var nestedIncludedAttributeValue = "nested include member content"; + var deeplyNestedIncludedAttributeValue = "deeply nested member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "multi-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, + Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } + }, + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, + Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } + }, + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + var included = entity.Multi; + Assert.Equal(10, included.Id); + Assert.Equal(includedAttributeValue, included.AttributeMember); + var nestedIncluded = included.PopulatedToManies.First(); + Assert.Equal(10, nestedIncluded.Id); + Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); + var deeplyNestedIncluded = nestedIncluded.Principal; + Assert.Equal(10, deeplyNestedIncluded.Id); + Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); + } + + + [Fact] + public void DeserializeList_DeeplyNestedIncluded_CanDeserialize() + { + // arrange + var content = new Documents { Data = new List { CreateDocumentWithRelationships("multi-principals").Data } }; + content.Data[0].Relationships.Add("multi", CreateRelationshipData("multi-principals")); + var includedAttributeValue = "multi member content"; + var nestedIncludedAttributeValue = "nested include member content"; + var deeplyNestedIncludedAttributeValue = "deeply nested member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "multi-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, + Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } + }, + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, + Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } + }, + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeList(body); + var entity = result.Data.First(); + + // assert + Assert.Equal(1, entity.Id); + var included = entity.Multi; + Assert.Equal(10, included.Id); + Assert.Equal(includedAttributeValue, included.AttributeMember); + var nestedIncluded = included.PopulatedToManies.First(); + Assert.Equal(10, nestedIncluded.Id); + Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); + var deeplyNestedIncluded = nestedIncluded.Principal; + Assert.Equal(10, deeplyNestedIncluded.Id); + Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); + } + } +} diff --git a/test/UnitTests/Serialization/DasherizedResolverTests.cs b/test/UnitTests/Deserialization/DasherizedResolverTests.cs similarity index 88% rename from test/UnitTests/Serialization/DasherizedResolverTests.cs rename to test/UnitTests/Deserialization/DasherizedResolverTests.cs index 5c0c4d08f3..ca746bfb91 100644 --- a/test/UnitTests/Serialization/DasherizedResolverTests.cs +++ b/test/UnitTests/Deserialization/DasherizedResolverTests.cs @@ -1,8 +1,10 @@ using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using Newtonsoft.Json; using Xunit; -namespace UnitTests.Serialization +namespace UnitTests.Deserialization { public class DasherizedResolverTests { diff --git a/test/UnitTests/Deserialization/DeserializerTestsSetup.cs b/test/UnitTests/Deserialization/DeserializerTestsSetup.cs new file mode 100644 index 0000000000..3c37ad52b3 --- /dev/null +++ b/test/UnitTests/Deserialization/DeserializerTestsSetup.cs @@ -0,0 +1,172 @@ +using JsonApiDotNetCore.Models; +using System.Collections.Generic; +using System; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Serialization; + +namespace UnitTests.Deserialization +{ + public class DeserializerTestsSetup + { + protected readonly IResourceGraph _resourceGraph; + protected readonly JsonApiSerializerSettings _defaultSettings = new JsonApiSerializerSettings(); + + public DeserializerTestsSetup() + { + var resourceGraphBuilder = new ResourceGraphBuilder(); + resourceGraphBuilder.AddResource("test-resource"); + resourceGraphBuilder.AddResource("test-resource-with-list"); + // one to one relationships + resourceGraphBuilder.AddResource("one-to-one-principals"); + resourceGraphBuilder.AddResource("one-to-one-dependents"); + resourceGraphBuilder.AddResource("one-to-one-required-dependents"); + // one to many relationships + resourceGraphBuilder.AddResource("one-to-many-principals"); + resourceGraphBuilder.AddResource("one-to-many-dependents"); + resourceGraphBuilder.AddResource("one-to-many-required-dependents"); + // collective relationships + resourceGraphBuilder.AddResource("multi-principals"); + resourceGraphBuilder.AddResource("multi-dependents"); + _resourceGraph = resourceGraphBuilder.Build(); + } + + protected Document CreateDocumentWithRelationships(string mainType, string relationshipMemberName, string relatedType = null, bool isToManyData = false) + { + var content = CreateDocumentWithRelationships(mainType); + content.Data.Relationships.Add(relationshipMemberName, CreateRelationshipData(relatedType, isToManyData)); + return content; + } + + protected Document CreateDocumentWithRelationships(string mainType) + { + return new Document + { + Data = new ResourceObject + { + Id = "1", + Type = mainType, + Relationships = new Dictionary { } + } + }; + } + + protected RelationshipData CreateRelationshipData(string relatedType = null, bool isToManyData = false) + { + var data = new RelationshipData(); + var rio = relatedType == null ? null : new ResourceIdentifierObject { Id = "10", Type = relatedType }; + + if (isToManyData) + { + data.ExposedData = new List(); + if (relatedType != null) ((List)data.ExposedData).Add(rio); + } else + { + data.ExposedData = rio; + } + return data; + } + + protected Document CreateTestResourceDocument() + { + return new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { "string-field", "some string" }, + { "int-field", 1 }, + { "nullable-int-field", null }, + { "guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781" }, + { "date-time-field", "9/11/2019 11:41:40 AM" } + } + } + }; + } + + protected class TestResource : Identifiable + { + [Attr] public string StringField { get; set; } + [Attr] public DateTime DateTimeField { get; set; } + [Attr] public DateTime? NullableDateTimeField { get; set; } + [Attr] public int IntField { get; set; } + [Attr] public int? NullableIntField { get; set; } + [Attr] public Guid GuidField { get; set; } + [Attr] public ComplexType ComplexField { get; set; } + [Attr(isImmutable: true)] public string Immutable { get; set; } + } + + protected class TestResourceWithList : Identifiable + { + [Attr] public List ComplexFields { get; set; } + } + + protected class ComplexType + { + public string CompoundName { get; set; } + } + + protected class OneToOnePrincipal : IdentifiableWithAttribute + { + [HasOne] public OneToOneDependent Dependent { get; set; } + } + + protected class OneToOneDependent : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal Principal { get; set; } + public int? PrincipalId { get; set; } + } + + protected class OneToOneRequiredDependent : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal Principal { get; set; } + public int PrincipalId { get; set; } + } + + protected class OneToManyDependent : IdentifiableWithAttribute + { + [HasOne] public OneToManyPrincipal Principal { get; set; } + public int? PrincipalId { get; set; } + } + + protected class OneToManyRequiredDependent : IdentifiableWithAttribute + { + [HasOne] public OneToManyPrincipal Principal { get; set; } + public int PrincipalId { get; set; } + } + + protected class OneToManyPrincipal : IdentifiableWithAttribute + { + [HasMany] public List Dependents { get; set; } + } + + protected class IdentifiableWithAttribute : Identifiable + { + [Attr] public string AttributeMember { get; set; } + } + + protected class MultipleRelationshipsPrincipalPart : IdentifiableWithAttribute + { + [HasOne] public OneToOneDependent PopulatedToOne { get; set; } + [HasOne] public OneToOneDependent EmptyToOne { get; set; } + [HasMany] public List PopulatedToManies { get; set; } + [HasMany] public List EmptyToManies { get; set; } + [HasOne] public MultipleRelationshipsPrincipalPart Multi { get; set; } + } + + protected class MultipleRelationshipsDependentPart : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal PopulatedToOne { get; set; } + public int PopulatedToOneId { get; set; } + [HasOne] public OneToOnePrincipal EmptyToOne { get; set; } + public int? EmptyToOneId { get; set; } + [HasOne] public OneToManyPrincipal PopulatedToMany { get; set; } + public int PopulatedToManyId { get; set; } + [HasOne] public OneToManyPrincipal EmptyToMany { get; set; } + public int? EmptyToManyId { get; set; } + } + } +} diff --git a/test/UnitTests/Serialization/JsonApiSerializerTests.cs b/test/UnitTests/Deserialization/JsonApiSerializerTests.cs similarity index 99% rename from test/UnitTests/Serialization/JsonApiSerializerTests.cs rename to test/UnitTests/Deserialization/JsonApiSerializerTests.cs index 8a1afdebe4..6a1dcb602e 100644 --- a/test/UnitTests/Serialization/JsonApiSerializerTests.cs +++ b/test/UnitTests/Deserialization/JsonApiSerializerTests.cs @@ -9,12 +9,14 @@ using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Request; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCore.Services; using Microsoft.Extensions.DependencyInjection; using Moq; using Xunit; -namespace UnitTests.Serialization +namespace UnitTests.Deserialization { public class JsonApiSerializerTests { diff --git a/test/UnitTests/Deserialization/SerializationTestsSetupBase.cs b/test/UnitTests/Deserialization/SerializationTestsSetupBase.cs new file mode 100644 index 0000000000..5135a2e642 --- /dev/null +++ b/test/UnitTests/Deserialization/SerializationTestsSetupBase.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; + +namespace UnitTests.Deserialization +{ + public class SerializationTestsSetupBase + { + protected readonly IResourceGraph _resourceGraph; + protected readonly JsonApiSerializerSettings _defaultSettings = new JsonApiSerializerSettings(); + + public SerializationTestsSetupBase() + { + _resourceGraph = BuildGraph(); + } + + protected IResourceGraph BuildGraph() + { + var resourceGraphBuilder = new ResourceGraphBuilder(); + resourceGraphBuilder.AddResource("test-resource"); + resourceGraphBuilder.AddResource("test-resource-with-list"); + // one to one relationships + resourceGraphBuilder.AddResource("one-to-one-principals"); + resourceGraphBuilder.AddResource("one-to-one-dependents"); + resourceGraphBuilder.AddResource("one-to-one-required-dependents"); + // one to many relationships + resourceGraphBuilder.AddResource("one-to-many-principals"); + resourceGraphBuilder.AddResource("one-to-many-dependents"); + resourceGraphBuilder.AddResource("one-to-many-required-dependents"); + // collective relationships + resourceGraphBuilder.AddResource("multi-principals"); + resourceGraphBuilder.AddResource("multi-dependents"); + return resourceGraphBuilder.Build(); + } + + protected class TestResource : Identifiable + { + [Attr] public string StringField { get; set; } + [Attr] public DateTime DateTimeField { get; set; } + [Attr] public DateTime? NullableDateTimeField { get; set; } + [Attr] public int IntField { get; set; } + [Attr] public int? NullableIntField { get; set; } + [Attr] public Guid GuidField { get; set; } + [Attr] public ComplexType ComplexField { get; set; } + [Attr(isImmutable: true)] public string Immutable { get; set; } + } + + protected class TestResourceWithList : Identifiable + { + [Attr] public List ComplexFields { get; set; } + } + + protected class ComplexType + { + public string CompoundName { get; set; } + } + + protected class OneToOnePrincipal : IdentifiableWithAttribute + { + [HasOne] public OneToOneDependent Dependent { get; set; } + } + + protected class OneToOneDependent : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal Principal { get; set; } + public int? PrincipalId { get; set; } + } + + protected class OneToOneRequiredDependent : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal Principal { get; set; } + public int PrincipalId { get; set; } + } + + protected class OneToManyDependent : IdentifiableWithAttribute + { + [HasOne] public OneToManyPrincipal Principal { get; set; } + public int? PrincipalId { get; set; } + } + + protected class OneToManyRequiredDependent : IdentifiableWithAttribute + { + [HasOne] public OneToManyPrincipal Principal { get; set; } + public int PrincipalId { get; set; } + } + + protected class OneToManyPrincipal : IdentifiableWithAttribute + { + [HasMany] public List Dependents { get; set; } + } + + protected class IdentifiableWithAttribute : Identifiable + { + [Attr] public string AttributeMember { get; set; } + } + + protected class MultipleRelationshipsPrincipalPart : IdentifiableWithAttribute + { + [HasOne] public OneToOneDependent PopulatedToOne { get; set; } + [HasOne] public OneToOneDependent EmptyToOne { get; set; } + [HasMany] public List PopulatedToManies { get; set; } + [HasMany] public List EmptyToManies { get; set; } + [HasOne] public MultipleRelationshipsPrincipalPart Multi { get; set; } + } + + protected class MultipleRelationshipsDependentPart : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal PopulatedToOne { get; set; } + public int PopulatedToOneId { get; set; } + [HasOne] public OneToOnePrincipal EmptyToOne { get; set; } + public int? EmptyToOneId { get; set; } + [HasOne] public OneToManyPrincipal PopulatedToMany { get; set; } + public int PopulatedToManyId { get; set; } + [HasOne] public OneToManyPrincipal EmptyToMany { get; set; } + public int? EmptyToManyId { get; set; } + } + } +} \ No newline at end of file diff --git a/test/UnitTests/Deserialization/ServerDeserializerTests.cs b/test/UnitTests/Deserialization/ServerDeserializerTests.cs new file mode 100644 index 0000000000..9d97bdf3e3 --- /dev/null +++ b/test/UnitTests/Deserialization/ServerDeserializerTests.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using Moq; +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class ServerDeserializerTests : DeserializerTestsSetup + { + private readonly ServerDeserializer _deserializer; + private readonly Mock _fieldsManagerMock = new Mock(); + public ServerDeserializerTests() : base() + { + _deserializer = new ServerDeserializer(_resourceGraph, _defaultSettings, _fieldsManagerMock.Object); + } + + [Fact] + public void DeserializeAttributes_VariousUpdatedMembers_RegistersUpdatedFields() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + Document content = CreateTestResourceDocument(); + var body = JsonConvert.SerializeObject(content); + + // act + _deserializer.Deserialize(body); + + // assert + Assert.Equal(5, attributesToUpdate.Count); + Assert.Empty(relationshipsToUpdate); + } + + [Fact] + public void DeserializeAttributes_UpdatedImmutableMember_ThrowsInvalidOperationException() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { "immutable", "some string" }, + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act, assert + Assert.Throws(() => _deserializer.Deserialize(body)); + } + + [Fact] + public void DeserializeRelationships_MultipleDependentRelationships_RegistersUpdatedRelationships() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); + var body = JsonConvert.SerializeObject(content); + + // act + _deserializer.Deserialize(body); + + // assert + Assert.Equal(4, relationshipsToUpdate.Count); + Assert.Empty(attributesToUpdate); + } + + [Fact] + public void DeserializeRelationships_MultiplePrincipalRelationships_RegistersUpdatedRelationships() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + var content = CreateDocumentWithRelationships("multi-dependents"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); + content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); + var body = JsonConvert.SerializeObject(content); + + // act + _deserializer.Deserialize(body); + + // assert + Assert.Equal(4, relationshipsToUpdate.Count); + Assert.Empty(attributesToUpdate); + } + + private void SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate) + { + attributesToUpdate = new List(); + relationshipsToUpdate = new List(); + _fieldsManagerMock.Setup(m => m.AttributesToUpdate).Returns(attributesToUpdate); + _fieldsManagerMock.Setup(m => m.RelationshipsToUpdate).Returns(relationshipsToUpdate); + } + } +} diff --git a/test/UnitTests/Serializ/BaseDeserializerTests.cs b/test/UnitTests/Serializ/BaseDeserializerTests.cs new file mode 100644 index 0000000000..7cc858f793 --- /dev/null +++ b/test/UnitTests/Serializ/BaseDeserializerTests.cs @@ -0,0 +1,370 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class BaseDeserializerTests : DeserializerTestsSetup + { + private readonly DeserializerBase _deserializer; + public BaseDeserializerTests() + { + _deserializer = new DeserializerBase(_resourceGraph, _defaultSettings); + } + + [Fact] + public void DeserializeResourceIdentifiers_SingleData_CanDeserialize() + { + // arange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (TestResource)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + + } + + [Fact] + public void DeserializeResourceIdentifiers_EmptySingleData_CanDeserialize() + { + // arange + var content = new Document { }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.Deserialize(body); + + // arrange + Assert.Null(result); + } + + [Fact] + public void DeserializeResourceIdentifiers_ArrayData_CanDeserialize() + { + // arange + var content = new Documents + { + Data = new List + { + new ResourceObject + { + Type = "test-resource", + Id = "1", + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (List)_deserializer.Deserialize(body); + + // assert + Assert.Equal("1", result.First().StringId); + } + + [Fact] + public void DeserializeResourceIdentifiers_EmptyArrayData_CanDeserialize() + { + var content = new Documents { Data = new List { } }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (IList)_deserializer.Deserialize(body); + + // assert + Assert.Empty(result); + } + + [Theory] + [InlineData("string-field", "some string")] + [InlineData("string-field", null)] + [InlineData("int-field", null, true)] + [InlineData("int-field", 1)] + [InlineData("int-field", "1")] + [InlineData("nullable-int-field", null)] + [InlineData("nullable-int-field", "1")] + [InlineData("guid-field", "bad format", true)] + [InlineData("guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781")] + [InlineData("date-time-field", "9/11/2019 11:41:40 AM")] + [InlineData("date-time-field", null, true)] + [InlineData("nullable-date-time-field", null)] + public void DeserializeAttributes_VariousDataTypes_CanDeserialize(string member, object value, bool expectError = false) + { + // arrange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { member, value } + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act, assert + if (expectError) + { + Assert.ThrowsAny(() => _deserializer.Deserialize(body)); + return; + } + + // act + var entity = (TestResource)_deserializer.Deserialize(body); + + // assert + var pi = _resourceGraph.GetContextEntity("test-resource").Attributes.Single(attr => attr.PublicAttributeName == member).PropertyInfo; + var deserializedValue = pi.GetValue(entity); + + if (member == "int-field") + { + Assert.Equal(deserializedValue, 1); + } + else if (member == "nullable-int-field" && value == null) + { + Assert.Equal(deserializedValue, null); + } + else if (member == "nullable-int-field" && (string)value == "1") + { + Assert.Equal(deserializedValue, 1); + } + else if (member == "guid-field") + { + Assert.Equal(deserializedValue, Guid.Parse("1a68be43-cc84-4924-a421-7f4d614b7781")); + } + else if (member == "date-time-field") + { + Assert.Equal(deserializedValue, DateTime.Parse("9/11/2019 11:41:40 AM")); + } else + { + Assert.Equal(value, deserializedValue); + } + } + + [Fact] + public void DeserializeAttributes_ComplexType_CanDeserialize() + { + // arrange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { "complex-field", new Dictionary { {"compound-name", "testName" } } } + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (TestResource)_deserializer.Deserialize(body); + + // assert + Assert.NotNull(result.ComplexField); + Assert.Equal("testName", result.ComplexField.CompoundName); + } + + [Fact] + public void DeserializeAttributes_ComplexListType_CanDeserialize() + { + // arrange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource-with-list", + Id = "1", + Attributes = new Dictionary + { + { "complex-fields", new [] { new Dictionary { {"compound-name", "testName" } } } } + } + } + }; + var body = JsonConvert.SerializeObject(content); + + + // act + var result = (TestResourceWithList)_deserializer.Deserialize(body); + + // assert + Assert.NotNull(result.ComplexFields); + Assert.NotEmpty(result.ComplexFields); + Assert.Equal("testName", result.ComplexFields[0].CompoundName); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToOneDependent_NavigationPropertyIsNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOnePrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Dependent); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToOneDependent_NavigationPropertyIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent", "one-to-one-dependents"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOnePrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Equal(10, result.Dependent.Id); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToOnePrincipal_NavigationPropertyAndForeignKeyAreNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOneDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Null(result.PrincipalId); + } + + [Fact] + public void DeserializeRelationships_EmptyRequiredOneToOnePrincipal_ThrowsFormatException() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-required-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act, assert + Assert.ThrowsAny(() => _deserializer.Deserialize(body)); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToOnePrincipal_NavigationPropertyIsNullAndForeignKeyIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal", "one-to-one-principals"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOneDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Equal(10, result.PrincipalId); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToManyPrincipal_NavigationAndForeignKeyAreNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Null(result.PrincipalId); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToManyRequiredPrincipal_ThrowsFormatException() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-required-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act, assert + Assert.ThrowsAny(() => _deserializer.Deserialize(body)); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToManyPrincipal_NavigationIsNullAndForeignKeyIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal", "one-to-many-principals"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Equal(10, result.PrincipalId); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToManyDependent_NavigationIsNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyPrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Dependents); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToManyDependent_NavigationIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents", "one-to-many-dependents", isToManyData: true); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyPrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Equal(1, result.Dependents.Count); + Assert.Equal(10, result.Dependents.First().Id); + Assert.Null(result.AttributeMember); + } + } +} diff --git a/test/UnitTests/Serializ/ClientDeserializerTests.cs b/test/UnitTests/Serializ/ClientDeserializerTests.cs new file mode 100644 index 0000000000..315b932b11 --- /dev/null +++ b/test/UnitTests/Serializ/ClientDeserializerTests.cs @@ -0,0 +1,333 @@ +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class ClientDeserializerTests : DeserializerTestsSetup + { + private readonly Dictionary _linkValues = new Dictionary(); + private readonly ClientDeserializer _deserializer; + + public ClientDeserializerTests() + { + _deserializer = new ClientDeserializer(_resourceGraph, _defaultSettings); + _linkValues.Add("self", "http://example.com/articles"); + _linkValues.Add("next", "http://example.com/articles?page[offset]=2"); + _linkValues.Add("last", "http://example.com/articles?page[offset]=10"); + } + + [Fact] + public void DeserializeSingle_EmptyResponseWithMeta_CanDeserialize() + { + // arrange + var content = new Document + { + Meta = new Dictionary { { "foo", "bar" } } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + + // assert + Assert.Null(result.Data); + Assert.NotNull(result.Meta); + Assert.Equal("bar", result.Meta["foo"]); + } + + [Fact] + public void DeserializeSingle_EmptyResponseWithTopLevelLinks_CanDeserialize() + { + // arrange + var content = new Document + { + Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + + // assert + Assert.Null(result.Data); + Assert.NotNull(result.Links); + Assert.Equal(_linkValues["self"], result.Links.Self); + Assert.Equal(_linkValues["next"], result.Links.Next); + Assert.Equal(_linkValues["last"], result.Links.Last); + } + + [Fact] + public void DeserializeList_EmptyResponseWithTopLevelLinks_CanDeserialize() + { + // arrange + var content = new Documents + { + Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] }, + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeList(body); + + // assert + Assert.Empty(result.Data); + Assert.NotNull(result.Links); + Assert.Equal(_linkValues["self"], result.Links.Self); + Assert.Equal(_linkValues["next"], result.Links.Next); + Assert.Equal(_linkValues["last"], result.Links.Last); + } + + [Fact] + public void DeserializeSingle_ResourceWithAttributes_CanDeserialize() + { + // arrange + var content = CreateTestResourceDocument(); + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Null(result.Links); + Assert.Null(result.Meta); + Assert.Equal(1, entity.Id); + Assert.Equal(content.Data.Attributes["string-field"], entity.StringField); + } + + [Fact] + public void DeserializeSingle_MultipleDependentRelationshipsWithIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); + content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); + var toOneAttributeValue = "populated-to-one member content"; + var toManyAttributeValue = "populated-to-manies member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "one-to-one-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } + }, + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + Assert.NotNull(entity.PopulatedToOne); + Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); + Assert.Equal(toManyAttributeValue, entity.PopulatedToManies.First().AttributeMember); + Assert.NotNull(entity.PopulatedToManies); + Assert.NotNull(entity.EmptyToManies); + Assert.Empty(entity.EmptyToManies); + Assert.Null(entity.EmptyToOne); + } + + [Fact] + public void DeserializeSingle_MultiplePrincipalRelationshipsWithIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-dependents"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); + content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); + var toOneAttributeValue = "populated-to-one member content"; + var toManyAttributeValue = "populated-to-manies member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "one-to-one-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + Assert.NotNull(entity.PopulatedToOne); + Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); + Assert.Equal(toManyAttributeValue, entity.PopulatedToMany.AttributeMember); + Assert.NotNull(entity.PopulatedToMany); + Assert.Null(entity.EmptyToMany); + Assert.Null(entity.EmptyToOne); + } + + [Fact] + public void DeserializeSingle_NestedIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + var toManyAttributeValue = "populated-to-manies member content"; + var nestedIncludeAttributeValue = "nested include member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } }, + Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", nestedIncludeAttributeValue } } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + Assert.Null(entity.PopulatedToOne); + Assert.Null(entity.EmptyToManies); + Assert.Null(entity.EmptyToOne); + Assert.NotNull(entity.PopulatedToManies); + var includedEntity = entity.PopulatedToManies.First(); + Assert.Equal(toManyAttributeValue, includedEntity.AttributeMember); + var nestedIncludedEntity = includedEntity.Principal; + Assert.Equal(nestedIncludeAttributeValue, nestedIncludedEntity.AttributeMember); + } + + + [Fact] + public void DeserializeSingle_DeeplyNestedIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("multi", CreateRelationshipData("multi-principals")); + var includedAttributeValue = "multi member content"; + var nestedIncludedAttributeValue = "nested include member content"; + var deeplyNestedIncludedAttributeValue = "deeply nested member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "multi-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, + Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } + }, + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, + Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } + }, + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + var included = entity.Multi; + Assert.Equal(10, included.Id); + Assert.Equal(includedAttributeValue, included.AttributeMember); + var nestedIncluded = included.PopulatedToManies.First(); + Assert.Equal(10, nestedIncluded.Id); + Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); + var deeplyNestedIncluded = nestedIncluded.Principal; + Assert.Equal(10, deeplyNestedIncluded.Id); + Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); + } + + + [Fact] + public void DeserializeList_DeeplyNestedIncluded_CanDeserialize() + { + // arrange + var content = new Documents { Data = new List { CreateDocumentWithRelationships("multi-principals").Data } }; + content.Data[0].Relationships.Add("multi", CreateRelationshipData("multi-principals")); + var includedAttributeValue = "multi member content"; + var nestedIncludedAttributeValue = "nested include member content"; + var deeplyNestedIncludedAttributeValue = "deeply nested member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "multi-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, + Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } + }, + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, + Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } + }, + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeList(body); + var entity = result.Data.First(); + + // assert + Assert.Equal(1, entity.Id); + var included = entity.Multi; + Assert.Equal(10, included.Id); + Assert.Equal(includedAttributeValue, included.AttributeMember); + var nestedIncluded = included.PopulatedToManies.First(); + Assert.Equal(10, nestedIncluded.Id); + Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); + var deeplyNestedIncluded = nestedIncluded.Principal; + Assert.Equal(10, deeplyNestedIncluded.Id); + Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); + } + } +} diff --git a/test/UnitTests/Serializ/DasherizedResolverTests.cs b/test/UnitTests/Serializ/DasherizedResolverTests.cs new file mode 100644 index 0000000000..ca746bfb91 --- /dev/null +++ b/test/UnitTests/Serializ/DasherizedResolverTests.cs @@ -0,0 +1,30 @@ +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class DasherizedResolverTests + { + [Fact] + public void Resolver_Dasherizes_Property_Names() + { + // arrange + var obj = new + { + myProp = "val" + }; + + // act + var result = JsonConvert.SerializeObject(obj, + Formatting.None, + new JsonSerializerSettings { ContractResolver = new DasherizedResolver() } + ); + + // assert + Assert.Equal("{\"my-prop\":\"val\"}", result); + } + } +} diff --git a/test/UnitTests/Serializ/DeserializerTestsSetup.cs b/test/UnitTests/Serializ/DeserializerTestsSetup.cs new file mode 100644 index 0000000000..da10de7450 --- /dev/null +++ b/test/UnitTests/Serializ/DeserializerTestsSetup.cs @@ -0,0 +1,68 @@ +using JsonApiDotNetCore.Models; +using System.Collections.Generic; +using System; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Serialization; + +namespace UnitTests.Deserialization +{ + public class DeserializerTestsSetup : SerializationTestBase + { + protected Document CreateDocumentWithRelationships(string mainType, string relationshipMemberName, string relatedType = null, bool isToManyData = false) + { + var content = CreateDocumentWithRelationships(mainType); + content.Data.Relationships.Add(relationshipMemberName, CreateRelationshipData(relatedType, isToManyData)); + return content; + } + + protected Document CreateDocumentWithRelationships(string mainType) + { + return new Document + { + Data = new ResourceObject + { + Id = "1", + Type = mainType, + Relationships = new Dictionary { } + } + }; + } + + protected RelationshipData CreateRelationshipData(string relatedType = null, bool isToManyData = false) + { + var data = new RelationshipData(); + var rio = relatedType == null ? null : new ResourceIdentifierObject { Id = "10", Type = relatedType }; + + if (isToManyData) + { + data.ExposedData = new List(); + if (relatedType != null) ((List)data.ExposedData).Add(rio); + } else + { + data.ExposedData = rio; + } + return data; + } + + protected Document CreateTestResourceDocument() + { + return new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { "string-field", "some string" }, + { "int-field", 1 }, + { "nullable-int-field", null }, + { "guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781" }, + { "date-time-field", "9/11/2019 11:41:40 AM" } + } + } + }; + } + } +} diff --git a/test/UnitTests/Serializ/JsonApiSerializerTests.cs b/test/UnitTests/Serializ/JsonApiSerializerTests.cs new file mode 100644 index 0000000000..22a7a0ea93 --- /dev/null +++ b/test/UnitTests/Serializ/JsonApiSerializerTests.cs @@ -0,0 +1,285 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Request; +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class JsonApiSerializerTests + { + [Fact] + public void Can_Serialize_Complex_Types() + { + // arrange + var resourceGraphBuilder = new ResourceGraphBuilder(); + resourceGraphBuilder.AddResource("test-resource"); + + var serializer = GetSerializer(resourceGraphBuilder); + + var resource = new TestResource + { + ComplexMember = new ComplexType + { + CompoundName = "testname" + } + }; + + // act + var result = serializer.Serialize(resource); + + // assert + Assert.NotNull(result); + + var expectedFormatted = + @"{ + ""data"": { + ""attributes"": { + ""complex-member"": { + ""compound-name"": ""testname"" + } + }, + ""relationships"": { + ""children"": { + ""links"": { + ""self"": ""/test-resource//relationships/children"", + ""related"": ""/test-resource//children"" + } + } + }, + ""type"": ""test-resource"", + ""id"": """" + } + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + + Assert.Equal(expected, result); + } + + [Fact] + public void Can_Serialize_Deeply_Nested_Relationships() + { + // arrange + var resourceGraphBuilder = new ResourceGraphBuilder(); + resourceGraphBuilder.AddResource("test-resource"); + resourceGraphBuilder.AddResource("children"); + resourceGraphBuilder.AddResource("infections"); + + var serializer = GetSerializer( + resourceGraphBuilder, + new List { "children.infections" } + ); + + var resource = new TestResource + { + Id = 1, + Children = new List { + new ChildResource { + Id = 2, + Infections = new List { + new InfectionResource { Id = 4 }, + new InfectionResource { Id = 5 }, + } + }, + new ChildResource { + Id = 3 + } + } + }; + + // act + var result = serializer.Serialize(resource); + + // assert + Assert.NotNull(result); + + var expectedFormatted = + @"{ + ""data"": { + ""attributes"": { + ""complex-member"": null + }, + ""relationships"": { + ""children"": { + ""links"": { + ""self"": ""/test-resource/1/relationships/children"", + ""related"": ""/test-resource/1/children"" + }, + ""data"": [{ + ""type"": ""children"", + ""id"": ""2"" + }, { + ""type"": ""children"", + ""id"": ""3"" + }] + } + }, + ""type"": ""test-resource"", + ""id"": ""1"" + }, + ""included"": [ + { + ""attributes"": {}, + ""relationships"": { + ""infections"": { + ""links"": { + ""self"": ""/children/2/relationships/infections"", + ""related"": ""/children/2/infections"" + }, + ""data"": [{ + ""type"": ""infections"", + ""id"": ""4"" + }, { + ""type"": ""infections"", + ""id"": ""5"" + }] + }, + ""parent"": { + ""links"": { + ""self"": ""/children/2/relationships/parent"", + ""related"": ""/children/2/parent"" + } + } + }, + ""type"": ""children"", + ""id"": ""2"" + }, + { + ""attributes"": {}, + ""relationships"": { + ""infected"": { + ""links"": { + ""self"": ""/infections/4/relationships/infected"", + ""related"": ""/infections/4/infected"" + } + } + }, + ""type"": ""infections"", + ""id"": ""4"" + }, + { + ""attributes"": {}, + ""relationships"": { + ""infected"": { + ""links"": { + ""self"": ""/infections/5/relationships/infected"", + ""related"": ""/infections/5/infected"" + } + } + }, + ""type"": ""infections"", + ""id"": ""5"" + }, + { + ""attributes"": {}, + ""relationships"": { + ""infections"": { + ""links"": { + ""self"": ""/children/3/relationships/infections"", + ""related"": ""/children/3/infections"" + } + }, + ""parent"": { + ""links"": { + ""self"": ""/children/3/relationships/parent"", + ""related"": ""/children/3/parent"" + } + } + }, + ""type"": ""children"", + ""id"": ""3"" + } + ] + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + + Assert.Equal(expected, result); + } + + private JsonApiSerializer GetSerializer( + ResourceGraphBuilder resourceGraphBuilder, + List included = null) + { + var resourceGraph = resourceGraphBuilder.Build(); + var requestManagerMock = new Mock(); + requestManagerMock.Setup(m => m.GetRequestResource()).Returns(resourceGraph.GetContextEntity("test-resource")); + requestManagerMock.Setup(m => m.IncludedRelationships).Returns(included); + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); + jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); + jsonApiContextMock.Setup(m => m.RequestEntity).Returns(resourceGraph.GetContextEntity("test-resource")); + jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); + + + jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); + var pmMock = new Mock(); + jsonApiContextMock.Setup(m => m.PageManager).Returns(pmMock.Object); + + + + var jsonApiOptions = new JsonApiOptions(); + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var services = new ServiceCollection(); + + var mvcBuilder = services.AddMvcCore(); + + services + .AddJsonApiInternals(jsonApiOptions); + + var provider = services.BuildServiceProvider(); + var scoped = new TestScopedServiceProvider(provider); + + var documentBuilder = GetDocumentBuilder(jsonApiContextMock, requestManagerMock.Object, scopedServiceProvider: scoped); + var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); + + return serializer; + } + + private class TestResource : Identifiable + { + [Attr("complex-member")] + public ComplexType ComplexMember { get; set; } + + [HasMany("children")] public List Children { get; set; } + } + + private class ComplexType + { + public string CompoundName { get; set; } + } + + private class ChildResource : Identifiable + { + [HasMany("infections")] public List Infections { get; set; } + + [HasOne("parent")] public TestResource Parent { get; set; } + } + + private class InfectionResource : Identifiable + { + [HasOne("infected")] public ChildResource Infected { get; set; } + } + + private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, IRequestManager requestManager, TestScopedServiceProvider scopedServiceProvider = null) + { + var pageManagerMock = new Mock(); + + return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, requestManager, scopedServiceProvider: scopedServiceProvider); + + } + } +} diff --git a/test/UnitTests/Serializ/SerializationTestsSetupBase.cs b/test/UnitTests/Serializ/SerializationTestsSetupBase.cs new file mode 100644 index 0000000000..5135a2e642 --- /dev/null +++ b/test/UnitTests/Serializ/SerializationTestsSetupBase.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; + +namespace UnitTests.Deserialization +{ + public class SerializationTestsSetupBase + { + protected readonly IResourceGraph _resourceGraph; + protected readonly JsonApiSerializerSettings _defaultSettings = new JsonApiSerializerSettings(); + + public SerializationTestsSetupBase() + { + _resourceGraph = BuildGraph(); + } + + protected IResourceGraph BuildGraph() + { + var resourceGraphBuilder = new ResourceGraphBuilder(); + resourceGraphBuilder.AddResource("test-resource"); + resourceGraphBuilder.AddResource("test-resource-with-list"); + // one to one relationships + resourceGraphBuilder.AddResource("one-to-one-principals"); + resourceGraphBuilder.AddResource("one-to-one-dependents"); + resourceGraphBuilder.AddResource("one-to-one-required-dependents"); + // one to many relationships + resourceGraphBuilder.AddResource("one-to-many-principals"); + resourceGraphBuilder.AddResource("one-to-many-dependents"); + resourceGraphBuilder.AddResource("one-to-many-required-dependents"); + // collective relationships + resourceGraphBuilder.AddResource("multi-principals"); + resourceGraphBuilder.AddResource("multi-dependents"); + return resourceGraphBuilder.Build(); + } + + protected class TestResource : Identifiable + { + [Attr] public string StringField { get; set; } + [Attr] public DateTime DateTimeField { get; set; } + [Attr] public DateTime? NullableDateTimeField { get; set; } + [Attr] public int IntField { get; set; } + [Attr] public int? NullableIntField { get; set; } + [Attr] public Guid GuidField { get; set; } + [Attr] public ComplexType ComplexField { get; set; } + [Attr(isImmutable: true)] public string Immutable { get; set; } + } + + protected class TestResourceWithList : Identifiable + { + [Attr] public List ComplexFields { get; set; } + } + + protected class ComplexType + { + public string CompoundName { get; set; } + } + + protected class OneToOnePrincipal : IdentifiableWithAttribute + { + [HasOne] public OneToOneDependent Dependent { get; set; } + } + + protected class OneToOneDependent : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal Principal { get; set; } + public int? PrincipalId { get; set; } + } + + protected class OneToOneRequiredDependent : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal Principal { get; set; } + public int PrincipalId { get; set; } + } + + protected class OneToManyDependent : IdentifiableWithAttribute + { + [HasOne] public OneToManyPrincipal Principal { get; set; } + public int? PrincipalId { get; set; } + } + + protected class OneToManyRequiredDependent : IdentifiableWithAttribute + { + [HasOne] public OneToManyPrincipal Principal { get; set; } + public int PrincipalId { get; set; } + } + + protected class OneToManyPrincipal : IdentifiableWithAttribute + { + [HasMany] public List Dependents { get; set; } + } + + protected class IdentifiableWithAttribute : Identifiable + { + [Attr] public string AttributeMember { get; set; } + } + + protected class MultipleRelationshipsPrincipalPart : IdentifiableWithAttribute + { + [HasOne] public OneToOneDependent PopulatedToOne { get; set; } + [HasOne] public OneToOneDependent EmptyToOne { get; set; } + [HasMany] public List PopulatedToManies { get; set; } + [HasMany] public List EmptyToManies { get; set; } + [HasOne] public MultipleRelationshipsPrincipalPart Multi { get; set; } + } + + protected class MultipleRelationshipsDependentPart : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal PopulatedToOne { get; set; } + public int PopulatedToOneId { get; set; } + [HasOne] public OneToOnePrincipal EmptyToOne { get; set; } + public int? EmptyToOneId { get; set; } + [HasOne] public OneToManyPrincipal PopulatedToMany { get; set; } + public int PopulatedToManyId { get; set; } + [HasOne] public OneToManyPrincipal EmptyToMany { get; set; } + public int? EmptyToManyId { get; set; } + } + } +} \ No newline at end of file diff --git a/test/UnitTests/Serializ/ServerDeserializerTests.cs b/test/UnitTests/Serializ/ServerDeserializerTests.cs new file mode 100644 index 0000000000..9d97bdf3e3 --- /dev/null +++ b/test/UnitTests/Serializ/ServerDeserializerTests.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using Moq; +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class ServerDeserializerTests : DeserializerTestsSetup + { + private readonly ServerDeserializer _deserializer; + private readonly Mock _fieldsManagerMock = new Mock(); + public ServerDeserializerTests() : base() + { + _deserializer = new ServerDeserializer(_resourceGraph, _defaultSettings, _fieldsManagerMock.Object); + } + + [Fact] + public void DeserializeAttributes_VariousUpdatedMembers_RegistersUpdatedFields() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + Document content = CreateTestResourceDocument(); + var body = JsonConvert.SerializeObject(content); + + // act + _deserializer.Deserialize(body); + + // assert + Assert.Equal(5, attributesToUpdate.Count); + Assert.Empty(relationshipsToUpdate); + } + + [Fact] + public void DeserializeAttributes_UpdatedImmutableMember_ThrowsInvalidOperationException() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { "immutable", "some string" }, + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act, assert + Assert.Throws(() => _deserializer.Deserialize(body)); + } + + [Fact] + public void DeserializeRelationships_MultipleDependentRelationships_RegistersUpdatedRelationships() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); + var body = JsonConvert.SerializeObject(content); + + // act + _deserializer.Deserialize(body); + + // assert + Assert.Equal(4, relationshipsToUpdate.Count); + Assert.Empty(attributesToUpdate); + } + + [Fact] + public void DeserializeRelationships_MultiplePrincipalRelationships_RegistersUpdatedRelationships() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + var content = CreateDocumentWithRelationships("multi-dependents"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); + content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); + var body = JsonConvert.SerializeObject(content); + + // act + _deserializer.Deserialize(body); + + // assert + Assert.Equal(4, relationshipsToUpdate.Count); + Assert.Empty(attributesToUpdate); + } + + private void SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate) + { + attributesToUpdate = new List(); + relationshipsToUpdate = new List(); + _fieldsManagerMock.Setup(m => m.AttributesToUpdate).Returns(attributesToUpdate); + _fieldsManagerMock.Setup(m => m.RelationshipsToUpdate).Returns(relationshipsToUpdate); + } + } +} diff --git a/test/UnitTests/Serialization (copy)/BaseDeserializerTests.cs b/test/UnitTests/Serialization (copy)/BaseDeserializerTests.cs new file mode 100644 index 0000000000..7cc858f793 --- /dev/null +++ b/test/UnitTests/Serialization (copy)/BaseDeserializerTests.cs @@ -0,0 +1,370 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class BaseDeserializerTests : DeserializerTestsSetup + { + private readonly DeserializerBase _deserializer; + public BaseDeserializerTests() + { + _deserializer = new DeserializerBase(_resourceGraph, _defaultSettings); + } + + [Fact] + public void DeserializeResourceIdentifiers_SingleData_CanDeserialize() + { + // arange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (TestResource)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + + } + + [Fact] + public void DeserializeResourceIdentifiers_EmptySingleData_CanDeserialize() + { + // arange + var content = new Document { }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.Deserialize(body); + + // arrange + Assert.Null(result); + } + + [Fact] + public void DeserializeResourceIdentifiers_ArrayData_CanDeserialize() + { + // arange + var content = new Documents + { + Data = new List + { + new ResourceObject + { + Type = "test-resource", + Id = "1", + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (List)_deserializer.Deserialize(body); + + // assert + Assert.Equal("1", result.First().StringId); + } + + [Fact] + public void DeserializeResourceIdentifiers_EmptyArrayData_CanDeserialize() + { + var content = new Documents { Data = new List { } }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (IList)_deserializer.Deserialize(body); + + // assert + Assert.Empty(result); + } + + [Theory] + [InlineData("string-field", "some string")] + [InlineData("string-field", null)] + [InlineData("int-field", null, true)] + [InlineData("int-field", 1)] + [InlineData("int-field", "1")] + [InlineData("nullable-int-field", null)] + [InlineData("nullable-int-field", "1")] + [InlineData("guid-field", "bad format", true)] + [InlineData("guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781")] + [InlineData("date-time-field", "9/11/2019 11:41:40 AM")] + [InlineData("date-time-field", null, true)] + [InlineData("nullable-date-time-field", null)] + public void DeserializeAttributes_VariousDataTypes_CanDeserialize(string member, object value, bool expectError = false) + { + // arrange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { member, value } + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act, assert + if (expectError) + { + Assert.ThrowsAny(() => _deserializer.Deserialize(body)); + return; + } + + // act + var entity = (TestResource)_deserializer.Deserialize(body); + + // assert + var pi = _resourceGraph.GetContextEntity("test-resource").Attributes.Single(attr => attr.PublicAttributeName == member).PropertyInfo; + var deserializedValue = pi.GetValue(entity); + + if (member == "int-field") + { + Assert.Equal(deserializedValue, 1); + } + else if (member == "nullable-int-field" && value == null) + { + Assert.Equal(deserializedValue, null); + } + else if (member == "nullable-int-field" && (string)value == "1") + { + Assert.Equal(deserializedValue, 1); + } + else if (member == "guid-field") + { + Assert.Equal(deserializedValue, Guid.Parse("1a68be43-cc84-4924-a421-7f4d614b7781")); + } + else if (member == "date-time-field") + { + Assert.Equal(deserializedValue, DateTime.Parse("9/11/2019 11:41:40 AM")); + } else + { + Assert.Equal(value, deserializedValue); + } + } + + [Fact] + public void DeserializeAttributes_ComplexType_CanDeserialize() + { + // arrange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { "complex-field", new Dictionary { {"compound-name", "testName" } } } + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (TestResource)_deserializer.Deserialize(body); + + // assert + Assert.NotNull(result.ComplexField); + Assert.Equal("testName", result.ComplexField.CompoundName); + } + + [Fact] + public void DeserializeAttributes_ComplexListType_CanDeserialize() + { + // arrange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource-with-list", + Id = "1", + Attributes = new Dictionary + { + { "complex-fields", new [] { new Dictionary { {"compound-name", "testName" } } } } + } + } + }; + var body = JsonConvert.SerializeObject(content); + + + // act + var result = (TestResourceWithList)_deserializer.Deserialize(body); + + // assert + Assert.NotNull(result.ComplexFields); + Assert.NotEmpty(result.ComplexFields); + Assert.Equal("testName", result.ComplexFields[0].CompoundName); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToOneDependent_NavigationPropertyIsNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOnePrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Dependent); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToOneDependent_NavigationPropertyIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent", "one-to-one-dependents"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOnePrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Equal(10, result.Dependent.Id); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToOnePrincipal_NavigationPropertyAndForeignKeyAreNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOneDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Null(result.PrincipalId); + } + + [Fact] + public void DeserializeRelationships_EmptyRequiredOneToOnePrincipal_ThrowsFormatException() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-required-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act, assert + Assert.ThrowsAny(() => _deserializer.Deserialize(body)); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToOnePrincipal_NavigationPropertyIsNullAndForeignKeyIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal", "one-to-one-principals"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOneDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Equal(10, result.PrincipalId); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToManyPrincipal_NavigationAndForeignKeyAreNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Null(result.PrincipalId); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToManyRequiredPrincipal_ThrowsFormatException() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-required-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act, assert + Assert.ThrowsAny(() => _deserializer.Deserialize(body)); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToManyPrincipal_NavigationIsNullAndForeignKeyIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal", "one-to-many-principals"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Equal(10, result.PrincipalId); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToManyDependent_NavigationIsNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyPrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Dependents); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToManyDependent_NavigationIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents", "one-to-many-dependents", isToManyData: true); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyPrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Equal(1, result.Dependents.Count); + Assert.Equal(10, result.Dependents.First().Id); + Assert.Null(result.AttributeMember); + } + } +} diff --git a/test/UnitTests/Serialization (copy)/ClientDeserializerTests.cs b/test/UnitTests/Serialization (copy)/ClientDeserializerTests.cs new file mode 100644 index 0000000000..315b932b11 --- /dev/null +++ b/test/UnitTests/Serialization (copy)/ClientDeserializerTests.cs @@ -0,0 +1,333 @@ +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class ClientDeserializerTests : DeserializerTestsSetup + { + private readonly Dictionary _linkValues = new Dictionary(); + private readonly ClientDeserializer _deserializer; + + public ClientDeserializerTests() + { + _deserializer = new ClientDeserializer(_resourceGraph, _defaultSettings); + _linkValues.Add("self", "http://example.com/articles"); + _linkValues.Add("next", "http://example.com/articles?page[offset]=2"); + _linkValues.Add("last", "http://example.com/articles?page[offset]=10"); + } + + [Fact] + public void DeserializeSingle_EmptyResponseWithMeta_CanDeserialize() + { + // arrange + var content = new Document + { + Meta = new Dictionary { { "foo", "bar" } } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + + // assert + Assert.Null(result.Data); + Assert.NotNull(result.Meta); + Assert.Equal("bar", result.Meta["foo"]); + } + + [Fact] + public void DeserializeSingle_EmptyResponseWithTopLevelLinks_CanDeserialize() + { + // arrange + var content = new Document + { + Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + + // assert + Assert.Null(result.Data); + Assert.NotNull(result.Links); + Assert.Equal(_linkValues["self"], result.Links.Self); + Assert.Equal(_linkValues["next"], result.Links.Next); + Assert.Equal(_linkValues["last"], result.Links.Last); + } + + [Fact] + public void DeserializeList_EmptyResponseWithTopLevelLinks_CanDeserialize() + { + // arrange + var content = new Documents + { + Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] }, + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeList(body); + + // assert + Assert.Empty(result.Data); + Assert.NotNull(result.Links); + Assert.Equal(_linkValues["self"], result.Links.Self); + Assert.Equal(_linkValues["next"], result.Links.Next); + Assert.Equal(_linkValues["last"], result.Links.Last); + } + + [Fact] + public void DeserializeSingle_ResourceWithAttributes_CanDeserialize() + { + // arrange + var content = CreateTestResourceDocument(); + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Null(result.Links); + Assert.Null(result.Meta); + Assert.Equal(1, entity.Id); + Assert.Equal(content.Data.Attributes["string-field"], entity.StringField); + } + + [Fact] + public void DeserializeSingle_MultipleDependentRelationshipsWithIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); + content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); + var toOneAttributeValue = "populated-to-one member content"; + var toManyAttributeValue = "populated-to-manies member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "one-to-one-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } + }, + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + Assert.NotNull(entity.PopulatedToOne); + Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); + Assert.Equal(toManyAttributeValue, entity.PopulatedToManies.First().AttributeMember); + Assert.NotNull(entity.PopulatedToManies); + Assert.NotNull(entity.EmptyToManies); + Assert.Empty(entity.EmptyToManies); + Assert.Null(entity.EmptyToOne); + } + + [Fact] + public void DeserializeSingle_MultiplePrincipalRelationshipsWithIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-dependents"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); + content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); + var toOneAttributeValue = "populated-to-one member content"; + var toManyAttributeValue = "populated-to-manies member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "one-to-one-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + Assert.NotNull(entity.PopulatedToOne); + Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); + Assert.Equal(toManyAttributeValue, entity.PopulatedToMany.AttributeMember); + Assert.NotNull(entity.PopulatedToMany); + Assert.Null(entity.EmptyToMany); + Assert.Null(entity.EmptyToOne); + } + + [Fact] + public void DeserializeSingle_NestedIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + var toManyAttributeValue = "populated-to-manies member content"; + var nestedIncludeAttributeValue = "nested include member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } }, + Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", nestedIncludeAttributeValue } } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + Assert.Null(entity.PopulatedToOne); + Assert.Null(entity.EmptyToManies); + Assert.Null(entity.EmptyToOne); + Assert.NotNull(entity.PopulatedToManies); + var includedEntity = entity.PopulatedToManies.First(); + Assert.Equal(toManyAttributeValue, includedEntity.AttributeMember); + var nestedIncludedEntity = includedEntity.Principal; + Assert.Equal(nestedIncludeAttributeValue, nestedIncludedEntity.AttributeMember); + } + + + [Fact] + public void DeserializeSingle_DeeplyNestedIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("multi", CreateRelationshipData("multi-principals")); + var includedAttributeValue = "multi member content"; + var nestedIncludedAttributeValue = "nested include member content"; + var deeplyNestedIncludedAttributeValue = "deeply nested member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "multi-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, + Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } + }, + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, + Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } + }, + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + var included = entity.Multi; + Assert.Equal(10, included.Id); + Assert.Equal(includedAttributeValue, included.AttributeMember); + var nestedIncluded = included.PopulatedToManies.First(); + Assert.Equal(10, nestedIncluded.Id); + Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); + var deeplyNestedIncluded = nestedIncluded.Principal; + Assert.Equal(10, deeplyNestedIncluded.Id); + Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); + } + + + [Fact] + public void DeserializeList_DeeplyNestedIncluded_CanDeserialize() + { + // arrange + var content = new Documents { Data = new List { CreateDocumentWithRelationships("multi-principals").Data } }; + content.Data[0].Relationships.Add("multi", CreateRelationshipData("multi-principals")); + var includedAttributeValue = "multi member content"; + var nestedIncludedAttributeValue = "nested include member content"; + var deeplyNestedIncludedAttributeValue = "deeply nested member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "multi-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, + Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } + }, + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, + Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } + }, + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeList(body); + var entity = result.Data.First(); + + // assert + Assert.Equal(1, entity.Id); + var included = entity.Multi; + Assert.Equal(10, included.Id); + Assert.Equal(includedAttributeValue, included.AttributeMember); + var nestedIncluded = included.PopulatedToManies.First(); + Assert.Equal(10, nestedIncluded.Id); + Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); + var deeplyNestedIncluded = nestedIncluded.Principal; + Assert.Equal(10, deeplyNestedIncluded.Id); + Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); + } + } +} diff --git a/test/UnitTests/Serialization (copy)/DasherizedResolverTests.cs b/test/UnitTests/Serialization (copy)/DasherizedResolverTests.cs new file mode 100644 index 0000000000..ca746bfb91 --- /dev/null +++ b/test/UnitTests/Serialization (copy)/DasherizedResolverTests.cs @@ -0,0 +1,30 @@ +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class DasherizedResolverTests + { + [Fact] + public void Resolver_Dasherizes_Property_Names() + { + // arrange + var obj = new + { + myProp = "val" + }; + + // act + var result = JsonConvert.SerializeObject(obj, + Formatting.None, + new JsonSerializerSettings { ContractResolver = new DasherizedResolver() } + ); + + // assert + Assert.Equal("{\"my-prop\":\"val\"}", result); + } + } +} diff --git a/test/UnitTests/Serialization (copy)/DeserializerTestsSetup.cs b/test/UnitTests/Serialization (copy)/DeserializerTestsSetup.cs new file mode 100644 index 0000000000..3c37ad52b3 --- /dev/null +++ b/test/UnitTests/Serialization (copy)/DeserializerTestsSetup.cs @@ -0,0 +1,172 @@ +using JsonApiDotNetCore.Models; +using System.Collections.Generic; +using System; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Serialization; + +namespace UnitTests.Deserialization +{ + public class DeserializerTestsSetup + { + protected readonly IResourceGraph _resourceGraph; + protected readonly JsonApiSerializerSettings _defaultSettings = new JsonApiSerializerSettings(); + + public DeserializerTestsSetup() + { + var resourceGraphBuilder = new ResourceGraphBuilder(); + resourceGraphBuilder.AddResource("test-resource"); + resourceGraphBuilder.AddResource("test-resource-with-list"); + // one to one relationships + resourceGraphBuilder.AddResource("one-to-one-principals"); + resourceGraphBuilder.AddResource("one-to-one-dependents"); + resourceGraphBuilder.AddResource("one-to-one-required-dependents"); + // one to many relationships + resourceGraphBuilder.AddResource("one-to-many-principals"); + resourceGraphBuilder.AddResource("one-to-many-dependents"); + resourceGraphBuilder.AddResource("one-to-many-required-dependents"); + // collective relationships + resourceGraphBuilder.AddResource("multi-principals"); + resourceGraphBuilder.AddResource("multi-dependents"); + _resourceGraph = resourceGraphBuilder.Build(); + } + + protected Document CreateDocumentWithRelationships(string mainType, string relationshipMemberName, string relatedType = null, bool isToManyData = false) + { + var content = CreateDocumentWithRelationships(mainType); + content.Data.Relationships.Add(relationshipMemberName, CreateRelationshipData(relatedType, isToManyData)); + return content; + } + + protected Document CreateDocumentWithRelationships(string mainType) + { + return new Document + { + Data = new ResourceObject + { + Id = "1", + Type = mainType, + Relationships = new Dictionary { } + } + }; + } + + protected RelationshipData CreateRelationshipData(string relatedType = null, bool isToManyData = false) + { + var data = new RelationshipData(); + var rio = relatedType == null ? null : new ResourceIdentifierObject { Id = "10", Type = relatedType }; + + if (isToManyData) + { + data.ExposedData = new List(); + if (relatedType != null) ((List)data.ExposedData).Add(rio); + } else + { + data.ExposedData = rio; + } + return data; + } + + protected Document CreateTestResourceDocument() + { + return new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { "string-field", "some string" }, + { "int-field", 1 }, + { "nullable-int-field", null }, + { "guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781" }, + { "date-time-field", "9/11/2019 11:41:40 AM" } + } + } + }; + } + + protected class TestResource : Identifiable + { + [Attr] public string StringField { get; set; } + [Attr] public DateTime DateTimeField { get; set; } + [Attr] public DateTime? NullableDateTimeField { get; set; } + [Attr] public int IntField { get; set; } + [Attr] public int? NullableIntField { get; set; } + [Attr] public Guid GuidField { get; set; } + [Attr] public ComplexType ComplexField { get; set; } + [Attr(isImmutable: true)] public string Immutable { get; set; } + } + + protected class TestResourceWithList : Identifiable + { + [Attr] public List ComplexFields { get; set; } + } + + protected class ComplexType + { + public string CompoundName { get; set; } + } + + protected class OneToOnePrincipal : IdentifiableWithAttribute + { + [HasOne] public OneToOneDependent Dependent { get; set; } + } + + protected class OneToOneDependent : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal Principal { get; set; } + public int? PrincipalId { get; set; } + } + + protected class OneToOneRequiredDependent : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal Principal { get; set; } + public int PrincipalId { get; set; } + } + + protected class OneToManyDependent : IdentifiableWithAttribute + { + [HasOne] public OneToManyPrincipal Principal { get; set; } + public int? PrincipalId { get; set; } + } + + protected class OneToManyRequiredDependent : IdentifiableWithAttribute + { + [HasOne] public OneToManyPrincipal Principal { get; set; } + public int PrincipalId { get; set; } + } + + protected class OneToManyPrincipal : IdentifiableWithAttribute + { + [HasMany] public List Dependents { get; set; } + } + + protected class IdentifiableWithAttribute : Identifiable + { + [Attr] public string AttributeMember { get; set; } + } + + protected class MultipleRelationshipsPrincipalPart : IdentifiableWithAttribute + { + [HasOne] public OneToOneDependent PopulatedToOne { get; set; } + [HasOne] public OneToOneDependent EmptyToOne { get; set; } + [HasMany] public List PopulatedToManies { get; set; } + [HasMany] public List EmptyToManies { get; set; } + [HasOne] public MultipleRelationshipsPrincipalPart Multi { get; set; } + } + + protected class MultipleRelationshipsDependentPart : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal PopulatedToOne { get; set; } + public int PopulatedToOneId { get; set; } + [HasOne] public OneToOnePrincipal EmptyToOne { get; set; } + public int? EmptyToOneId { get; set; } + [HasOne] public OneToManyPrincipal PopulatedToMany { get; set; } + public int PopulatedToManyId { get; set; } + [HasOne] public OneToManyPrincipal EmptyToMany { get; set; } + public int? EmptyToManyId { get; set; } + } + } +} diff --git a/test/UnitTests/Serialization (copy)/JsonApiSerializerTests.cs b/test/UnitTests/Serialization (copy)/JsonApiSerializerTests.cs new file mode 100644 index 0000000000..6a1dcb602e --- /dev/null +++ b/test/UnitTests/Serialization (copy)/JsonApiSerializerTests.cs @@ -0,0 +1,285 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Request; +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class JsonApiSerializerTests + { + [Fact] + public void Can_Serialize_Complex_Types() + { + // arrange + var resourceGraphBuilder = new ResourceGraphBuilder(); + resourceGraphBuilder.AddResource("test-resource"); + + var serializer = GetSerializer(resourceGraphBuilder); + + var resource = new TestResource + { + ComplexMember = new ComplexType + { + CompoundName = "testname" + } + }; + + // act + var result = serializer.Serialize(resource); + + // assert + Assert.NotNull(result); + + var expectedFormatted = + @"{ + ""data"": { + ""attributes"": { + ""complex-member"": { + ""compound-name"": ""testname"" + } + }, + ""relationships"": { + ""children"": { + ""links"": { + ""self"": ""/test-resource//relationships/children"", + ""related"": ""/test-resource//children"" + } + } + }, + ""type"": ""test-resource"", + ""id"": """" + } + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + + Assert.Equal(expected, result); + } + + [Fact] + public void Can_Serialize_Deeply_Nested_Relationships() + { + // arrange + var resourceGraphBuilder = new ResourceGraphBuilder(); + resourceGraphBuilder.AddResource("test-resource"); + resourceGraphBuilder.AddResource("children"); + resourceGraphBuilder.AddResource("infections"); + + var serializer = GetSerializer( + resourceGraphBuilder, + new List { "children.infections" } + ); + + var resource = new TestResource + { + Id = 1, + Children = new List { + new ChildResource { + Id = 2, + Infections = new List { + new InfectionResource { Id = 4 }, + new InfectionResource { Id = 5 }, + } + }, + new ChildResource { + Id = 3 + } + } + }; + + // act + var result = serializer.Serialize(resource); + + // assert + Assert.NotNull(result); + + var expectedFormatted = + @"{ + ""data"": { + ""attributes"": { + ""complex-member"": null + }, + ""relationships"": { + ""children"": { + ""links"": { + ""self"": ""/test-resource/1/relationships/children"", + ""related"": ""/test-resource/1/children"" + }, + ""data"": [{ + ""type"": ""children"", + ""id"": ""2"" + }, { + ""type"": ""children"", + ""id"": ""3"" + }] + } + }, + ""type"": ""test-resource"", + ""id"": ""1"" + }, + ""included"": [ + { + ""attributes"": {}, + ""relationships"": { + ""infections"": { + ""links"": { + ""self"": ""/children/2/relationships/infections"", + ""related"": ""/children/2/infections"" + }, + ""data"": [{ + ""type"": ""infections"", + ""id"": ""4"" + }, { + ""type"": ""infections"", + ""id"": ""5"" + }] + }, + ""parent"": { + ""links"": { + ""self"": ""/children/2/relationships/parent"", + ""related"": ""/children/2/parent"" + } + } + }, + ""type"": ""children"", + ""id"": ""2"" + }, + { + ""attributes"": {}, + ""relationships"": { + ""infected"": { + ""links"": { + ""self"": ""/infections/4/relationships/infected"", + ""related"": ""/infections/4/infected"" + } + } + }, + ""type"": ""infections"", + ""id"": ""4"" + }, + { + ""attributes"": {}, + ""relationships"": { + ""infected"": { + ""links"": { + ""self"": ""/infections/5/relationships/infected"", + ""related"": ""/infections/5/infected"" + } + } + }, + ""type"": ""infections"", + ""id"": ""5"" + }, + { + ""attributes"": {}, + ""relationships"": { + ""infections"": { + ""links"": { + ""self"": ""/children/3/relationships/infections"", + ""related"": ""/children/3/infections"" + } + }, + ""parent"": { + ""links"": { + ""self"": ""/children/3/relationships/parent"", + ""related"": ""/children/3/parent"" + } + } + }, + ""type"": ""children"", + ""id"": ""3"" + } + ] + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + + Assert.Equal(expected, result); + } + + private JsonApiSerializer GetSerializer( + ResourceGraphBuilder resourceGraphBuilder, + List included = null) + { + var resourceGraph = resourceGraphBuilder.Build(); + var requestManagerMock = new Mock(); + requestManagerMock.Setup(m => m.GetContextEntity()).Returns(resourceGraph.GetContextEntity("test-resource")); + requestManagerMock.Setup(m => m.IncludedRelationships).Returns(included); + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); + jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); + jsonApiContextMock.Setup(m => m.RequestEntity).Returns(resourceGraph.GetContextEntity("test-resource")); + jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); + + + jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); + var pmMock = new Mock(); + jsonApiContextMock.Setup(m => m.PageManager).Returns(pmMock.Object); + + + + var jsonApiOptions = new JsonApiOptions(); + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var services = new ServiceCollection(); + + var mvcBuilder = services.AddMvcCore(); + + services + .AddJsonApiInternals(jsonApiOptions); + + var provider = services.BuildServiceProvider(); + var scoped = new TestScopedServiceProvider(provider); + + var documentBuilder = GetDocumentBuilder(jsonApiContextMock, requestManagerMock.Object, scopedServiceProvider: scoped); + var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); + + return serializer; + } + + private class TestResource : Identifiable + { + [Attr("complex-member")] + public ComplexType ComplexMember { get; set; } + + [HasMany("children")] public List Children { get; set; } + } + + private class ComplexType + { + public string CompoundName { get; set; } + } + + private class ChildResource : Identifiable + { + [HasMany("infections")] public List Infections { get; set; } + + [HasOne("parent")] public TestResource Parent { get; set; } + } + + private class InfectionResource : Identifiable + { + [HasOne("infected")] public ChildResource Infected { get; set; } + } + + private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, IRequestManager requestManager, TestScopedServiceProvider scopedServiceProvider = null) + { + var pageManagerMock = new Mock(); + + return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, requestManager, scopedServiceProvider: scopedServiceProvider); + + } + } +} diff --git a/test/UnitTests/Serialization (copy)/ServerDeserializerTests.cs b/test/UnitTests/Serialization (copy)/ServerDeserializerTests.cs new file mode 100644 index 0000000000..9d97bdf3e3 --- /dev/null +++ b/test/UnitTests/Serialization (copy)/ServerDeserializerTests.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using Moq; +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class ServerDeserializerTests : DeserializerTestsSetup + { + private readonly ServerDeserializer _deserializer; + private readonly Mock _fieldsManagerMock = new Mock(); + public ServerDeserializerTests() : base() + { + _deserializer = new ServerDeserializer(_resourceGraph, _defaultSettings, _fieldsManagerMock.Object); + } + + [Fact] + public void DeserializeAttributes_VariousUpdatedMembers_RegistersUpdatedFields() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + Document content = CreateTestResourceDocument(); + var body = JsonConvert.SerializeObject(content); + + // act + _deserializer.Deserialize(body); + + // assert + Assert.Equal(5, attributesToUpdate.Count); + Assert.Empty(relationshipsToUpdate); + } + + [Fact] + public void DeserializeAttributes_UpdatedImmutableMember_ThrowsInvalidOperationException() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { "immutable", "some string" }, + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act, assert + Assert.Throws(() => _deserializer.Deserialize(body)); + } + + [Fact] + public void DeserializeRelationships_MultipleDependentRelationships_RegistersUpdatedRelationships() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); + var body = JsonConvert.SerializeObject(content); + + // act + _deserializer.Deserialize(body); + + // assert + Assert.Equal(4, relationshipsToUpdate.Count); + Assert.Empty(attributesToUpdate); + } + + [Fact] + public void DeserializeRelationships_MultiplePrincipalRelationships_RegistersUpdatedRelationships() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + var content = CreateDocumentWithRelationships("multi-dependents"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); + content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); + var body = JsonConvert.SerializeObject(content); + + // act + _deserializer.Deserialize(body); + + // assert + Assert.Equal(4, relationshipsToUpdate.Count); + Assert.Empty(attributesToUpdate); + } + + private void SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate) + { + attributesToUpdate = new List(); + relationshipsToUpdate = new List(); + _fieldsManagerMock.Setup(m => m.AttributesToUpdate).Returns(attributesToUpdate); + _fieldsManagerMock.Setup(m => m.RelationshipsToUpdate).Returns(relationshipsToUpdate); + } + } +} diff --git a/test/UnitTests/Serialization/Deserializer/ClientDeserializerTests.cs b/test/UnitTests/Serialization/Deserializer/ClientDeserializerTests.cs new file mode 100644 index 0000000000..315b932b11 --- /dev/null +++ b/test/UnitTests/Serialization/Deserializer/ClientDeserializerTests.cs @@ -0,0 +1,333 @@ +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class ClientDeserializerTests : DeserializerTestsSetup + { + private readonly Dictionary _linkValues = new Dictionary(); + private readonly ClientDeserializer _deserializer; + + public ClientDeserializerTests() + { + _deserializer = new ClientDeserializer(_resourceGraph, _defaultSettings); + _linkValues.Add("self", "http://example.com/articles"); + _linkValues.Add("next", "http://example.com/articles?page[offset]=2"); + _linkValues.Add("last", "http://example.com/articles?page[offset]=10"); + } + + [Fact] + public void DeserializeSingle_EmptyResponseWithMeta_CanDeserialize() + { + // arrange + var content = new Document + { + Meta = new Dictionary { { "foo", "bar" } } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + + // assert + Assert.Null(result.Data); + Assert.NotNull(result.Meta); + Assert.Equal("bar", result.Meta["foo"]); + } + + [Fact] + public void DeserializeSingle_EmptyResponseWithTopLevelLinks_CanDeserialize() + { + // arrange + var content = new Document + { + Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + + // assert + Assert.Null(result.Data); + Assert.NotNull(result.Links); + Assert.Equal(_linkValues["self"], result.Links.Self); + Assert.Equal(_linkValues["next"], result.Links.Next); + Assert.Equal(_linkValues["last"], result.Links.Last); + } + + [Fact] + public void DeserializeList_EmptyResponseWithTopLevelLinks_CanDeserialize() + { + // arrange + var content = new Documents + { + Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] }, + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeList(body); + + // assert + Assert.Empty(result.Data); + Assert.NotNull(result.Links); + Assert.Equal(_linkValues["self"], result.Links.Self); + Assert.Equal(_linkValues["next"], result.Links.Next); + Assert.Equal(_linkValues["last"], result.Links.Last); + } + + [Fact] + public void DeserializeSingle_ResourceWithAttributes_CanDeserialize() + { + // arrange + var content = CreateTestResourceDocument(); + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Null(result.Links); + Assert.Null(result.Meta); + Assert.Equal(1, entity.Id); + Assert.Equal(content.Data.Attributes["string-field"], entity.StringField); + } + + [Fact] + public void DeserializeSingle_MultipleDependentRelationshipsWithIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); + content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); + var toOneAttributeValue = "populated-to-one member content"; + var toManyAttributeValue = "populated-to-manies member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "one-to-one-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } + }, + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + Assert.NotNull(entity.PopulatedToOne); + Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); + Assert.Equal(toManyAttributeValue, entity.PopulatedToManies.First().AttributeMember); + Assert.NotNull(entity.PopulatedToManies); + Assert.NotNull(entity.EmptyToManies); + Assert.Empty(entity.EmptyToManies); + Assert.Null(entity.EmptyToOne); + } + + [Fact] + public void DeserializeSingle_MultiplePrincipalRelationshipsWithIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-dependents"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); + content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); + var toOneAttributeValue = "populated-to-one member content"; + var toManyAttributeValue = "populated-to-manies member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "one-to-one-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + Assert.NotNull(entity.PopulatedToOne); + Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); + Assert.Equal(toManyAttributeValue, entity.PopulatedToMany.AttributeMember); + Assert.NotNull(entity.PopulatedToMany); + Assert.Null(entity.EmptyToMany); + Assert.Null(entity.EmptyToOne); + } + + [Fact] + public void DeserializeSingle_NestedIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + var toManyAttributeValue = "populated-to-manies member content"; + var nestedIncludeAttributeValue = "nested include member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } }, + Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", nestedIncludeAttributeValue } } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + Assert.Null(entity.PopulatedToOne); + Assert.Null(entity.EmptyToManies); + Assert.Null(entity.EmptyToOne); + Assert.NotNull(entity.PopulatedToManies); + var includedEntity = entity.PopulatedToManies.First(); + Assert.Equal(toManyAttributeValue, includedEntity.AttributeMember); + var nestedIncludedEntity = includedEntity.Principal; + Assert.Equal(nestedIncludeAttributeValue, nestedIncludedEntity.AttributeMember); + } + + + [Fact] + public void DeserializeSingle_DeeplyNestedIncluded_CanDeserialize() + { + // arrange + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("multi", CreateRelationshipData("multi-principals")); + var includedAttributeValue = "multi member content"; + var nestedIncludedAttributeValue = "nested include member content"; + var deeplyNestedIncludedAttributeValue = "deeply nested member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "multi-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, + Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } + }, + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, + Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } + }, + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeSingle(body); + var entity = result.Data; + + // assert + Assert.Equal(1, entity.Id); + var included = entity.Multi; + Assert.Equal(10, included.Id); + Assert.Equal(includedAttributeValue, included.AttributeMember); + var nestedIncluded = included.PopulatedToManies.First(); + Assert.Equal(10, nestedIncluded.Id); + Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); + var deeplyNestedIncluded = nestedIncluded.Principal; + Assert.Equal(10, deeplyNestedIncluded.Id); + Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); + } + + + [Fact] + public void DeserializeList_DeeplyNestedIncluded_CanDeserialize() + { + // arrange + var content = new Documents { Data = new List { CreateDocumentWithRelationships("multi-principals").Data } }; + content.Data[0].Relationships.Add("multi", CreateRelationshipData("multi-principals")); + var includedAttributeValue = "multi member content"; + var nestedIncludedAttributeValue = "nested include member content"; + var deeplyNestedIncludedAttributeValue = "deeply nested member content"; + content.Included = new List() + { + new ResourceObject() + { + Type = "multi-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, + Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } + }, + new ResourceObject() + { + Type = "one-to-many-dependents", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, + Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } + }, + new ResourceObject() + { + Type = "one-to-many-principals", + Id = "10", + Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } + }, + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.DeserializeList(body); + var entity = result.Data.First(); + + // assert + Assert.Equal(1, entity.Id); + var included = entity.Multi; + Assert.Equal(10, included.Id); + Assert.Equal(includedAttributeValue, included.AttributeMember); + var nestedIncluded = included.PopulatedToManies.First(); + Assert.Equal(10, nestedIncluded.Id); + Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); + var deeplyNestedIncluded = nestedIncluded.Principal; + Assert.Equal(10, deeplyNestedIncluded.Id); + Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); + } + } +} diff --git a/test/UnitTests/Serialization/Deserializer/DeserializerTestsSetup.cs b/test/UnitTests/Serialization/Deserializer/DeserializerTestsSetup.cs new file mode 100644 index 0000000000..9e9f8f1ca2 --- /dev/null +++ b/test/UnitTests/Serialization/Deserializer/DeserializerTestsSetup.cs @@ -0,0 +1,68 @@ +using JsonApiDotNetCore.Models; +using System.Collections.Generic; +using System; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Serialization; + +namespace UnitTests.Deserialization +{ + public class DeserializerTestsSetup : SerializationTestsSetupBase + { + protected Document CreateDocumentWithRelationships(string mainType, string relationshipMemberName, string relatedType = null, bool isToManyData = false) + { + var content = CreateDocumentWithRelationships(mainType); + content.Data.Relationships.Add(relationshipMemberName, CreateRelationshipData(relatedType, isToManyData)); + return content; + } + + protected Document CreateDocumentWithRelationships(string mainType) + { + return new Document + { + Data = new ResourceObject + { + Id = "1", + Type = mainType, + Relationships = new Dictionary { } + } + }; + } + + protected RelationshipData CreateRelationshipData(string relatedType = null, bool isToManyData = false) + { + var data = new RelationshipData(); + var rio = relatedType == null ? null : new ResourceIdentifierObject { Id = "10", Type = relatedType }; + + if (isToManyData) + { + data.ExposedData = new List(); + if (relatedType != null) ((List)data.ExposedData).Add(rio); + } else + { + data.ExposedData = rio; + } + return data; + } + + protected Document CreateTestResourceDocument() + { + return new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { "string-field", "some string" }, + { "int-field", 1 }, + { "nullable-int-field", null }, + { "guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781" }, + { "date-time-field", "9/11/2019 11:41:40 AM" } + } + } + }; + } + } +} diff --git a/test/UnitTests/Serialization/Deserializer/DocumentParserTests.cs b/test/UnitTests/Serialization/Deserializer/DocumentParserTests.cs new file mode 100644 index 0000000000..23c2c9c072 --- /dev/null +++ b/test/UnitTests/Serialization/Deserializer/DocumentParserTests.cs @@ -0,0 +1,367 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Serialization.Deserializer +{ + public class DocumentParserTests : DeserializerTestsSetup + { + private readonly TestDocumentParser _deserializer; + + [Fact] + public void DeserializeResourceIdentifiers_SingleData_CanDeserialize() + { + // arange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (TestResource)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + + } + + [Fact] + public void DeserializeResourceIdentifiers_EmptySingleData_CanDeserialize() + { + // arange + var content = new Document { }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = _deserializer.Deserialize(body); + + // arrange + Assert.Null(result); + } + + [Fact] + public void DeserializeResourceIdentifiers_ArrayData_CanDeserialize() + { + // arange + var content = new Document + { + Data = new List + { + new ResourceObject + { + Type = "test-resource", + Id = "1", + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (List)_deserializer.Deserialize(body); + + // assert + Assert.Equal("1", result.First().StringId); + } + + [Fact] + public void DeserializeResourceIdentifiers_EmptyArrayData_CanDeserialize() + { + var content = new Document { Data = new List { } }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (IList)_deserializer.Deserialize(body); + + // assert + Assert.Empty(result); + } + + [Theory] + [InlineData("string-field", "some string")] + [InlineData("string-field", null)] + [InlineData("int-field", null, true)] + [InlineData("int-field", 1)] + [InlineData("int-field", "1")] + [InlineData("nullable-int-field", null)] + [InlineData("nullable-int-field", "1")] + [InlineData("guid-field", "bad format", true)] + [InlineData("guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781")] + [InlineData("date-time-field", "9/11/2019 11:41:40 AM")] + [InlineData("date-time-field", null, true)] + [InlineData("nullable-date-time-field", null)] + public void DeserializeAttributes_VariousDataTypes_CanDeserialize(string member, object value, bool expectError = false) + { + // arrange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { member, value } + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act, assert + if (expectError) + { + Assert.ThrowsAny(() => _deserializer.Deserialize(body)); + return; + } + + // act + var entity = (TestResource)_deserializer.Deserialize(body); + + // assert + var pi = _resourceGraph.GetContextEntity("test-resource").Attributes.Single(attr => attr.PublicAttributeName == member).PropertyInfo; + var deserializedValue = pi.GetValue(entity); + + if (member == "int-field") + { + Assert.Equal(deserializedValue, 1); + } + else if (member == "nullable-int-field" && value == null) + { + Assert.Equal(deserializedValue, null); + } + else if (member == "nullable-int-field" && (string)value == "1") + { + Assert.Equal(deserializedValue, 1); + } + else if (member == "guid-field") + { + Assert.Equal(deserializedValue, Guid.Parse("1a68be43-cc84-4924-a421-7f4d614b7781")); + } + else if (member == "date-time-field") + { + Assert.Equal(deserializedValue, DateTime.Parse("9/11/2019 11:41:40 AM")); + } + else + { + Assert.Equal(value, deserializedValue); + } + } + + [Fact] + public void DeserializeAttributes_ComplexType_CanDeserialize() + { + // arrange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { "complex-field", new Dictionary { {"compoundName", "testName" } } } // this is not right + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act + var result = (TestResource)_deserializer.Deserialize(body); + + // assert + Assert.NotNull(result.ComplexField); + Assert.Equal("testName", result.ComplexField.CompoundName); + } + + [Fact] + public void DeserializeAttributes_ComplexListType_CanDeserialize() + { + // arrange + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource-with-list", + Id = "1", + Attributes = new Dictionary + { + { "complex-fields", new [] { new Dictionary { {"compoundName", "testName" } } } } + } + } + }; + var body = JsonConvert.SerializeObject(content); + + + // act + var result = (TestResourceWithList)_deserializer.Deserialize(body); + + // assert + Assert.NotNull(result.ComplexFields); + Assert.NotEmpty(result.ComplexFields); + Assert.Equal("testName", result.ComplexFields[0].CompoundName); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToOneDependent_NavigationPropertyIsNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOnePrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Dependent); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToOneDependent_NavigationPropertyIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent", "one-to-one-dependents"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOnePrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Equal(10, result.Dependent.Id); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToOnePrincipal_NavigationPropertyAndForeignKeyAreNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOneDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Null(result.PrincipalId); + } + + [Fact] + public void DeserializeRelationships_EmptyRequiredOneToOnePrincipal_ThrowsFormatException() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-required-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act, assert + Assert.ThrowsAny(() => _deserializer.Deserialize(body)); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToOnePrincipal_NavigationPropertyIsNullAndForeignKeyIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal", "one-to-one-principals"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToOneDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Equal(10, result.PrincipalId); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToManyPrincipal_NavigationAndForeignKeyAreNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Null(result.PrincipalId); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToManyRequiredPrincipal_ThrowsFormatException() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-required-dependents", "principal"); + var body = JsonConvert.SerializeObject(content); + + // act, assert + Assert.ThrowsAny(() => _deserializer.Deserialize(body)); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToManyPrincipal_NavigationIsNullAndForeignKeyIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal", "one-to-many-principals"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyDependent)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Principal); + Assert.Equal(10, result.PrincipalId); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_EmptyOneToManyDependent_NavigationIsNull() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents"); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyPrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Null(result.Dependents); + Assert.Null(result.AttributeMember); + } + + [Fact] + public void DeserializeRelationships_PopulatedOneToManyDependent_NavigationIsPopulated() + { + // arrange + var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents", "one-to-many-dependents", isToManyData: true); + var body = JsonConvert.SerializeObject(content); + + // act + var result = (OneToManyPrincipal)_deserializer.Deserialize(body); + + // assert + Assert.Equal(1, result.Id); + Assert.Equal(1, result.Dependents.Count); + Assert.Equal(10, result.Dependents.First().Id); + Assert.Null(result.AttributeMember); + } + } +} diff --git a/test/UnitTests/Serialization/Deserializer/ServerDeserializerTests.cs b/test/UnitTests/Serialization/Deserializer/ServerDeserializerTests.cs new file mode 100644 index 0000000000..9d97bdf3e3 --- /dev/null +++ b/test/UnitTests/Serialization/Deserializer/ServerDeserializerTests.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using Moq; +using Newtonsoft.Json; +using Xunit; + +namespace UnitTests.Deserialization +{ + public class ServerDeserializerTests : DeserializerTestsSetup + { + private readonly ServerDeserializer _deserializer; + private readonly Mock _fieldsManagerMock = new Mock(); + public ServerDeserializerTests() : base() + { + _deserializer = new ServerDeserializer(_resourceGraph, _defaultSettings, _fieldsManagerMock.Object); + } + + [Fact] + public void DeserializeAttributes_VariousUpdatedMembers_RegistersUpdatedFields() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + Document content = CreateTestResourceDocument(); + var body = JsonConvert.SerializeObject(content); + + // act + _deserializer.Deserialize(body); + + // assert + Assert.Equal(5, attributesToUpdate.Count); + Assert.Empty(relationshipsToUpdate); + } + + [Fact] + public void DeserializeAttributes_UpdatedImmutableMember_ThrowsInvalidOperationException() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + var content = new Document + { + Data = new ResourceObject + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary + { + { "immutable", "some string" }, + } + } + }; + var body = JsonConvert.SerializeObject(content); + + // act, assert + Assert.Throws(() => _deserializer.Deserialize(body)); + } + + [Fact] + public void DeserializeRelationships_MultipleDependentRelationships_RegistersUpdatedRelationships() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + var content = CreateDocumentWithRelationships("multi-principals"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); + var body = JsonConvert.SerializeObject(content); + + // act + _deserializer.Deserialize(body); + + // assert + Assert.Equal(4, relationshipsToUpdate.Count); + Assert.Empty(attributesToUpdate); + } + + [Fact] + public void DeserializeRelationships_MultiplePrincipalRelationships_RegistersUpdatedRelationships() + { + // arrange + SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); + var content = CreateDocumentWithRelationships("multi-dependents"); + content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); + content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); + content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); + var body = JsonConvert.SerializeObject(content); + + // act + _deserializer.Deserialize(body); + + // assert + Assert.Equal(4, relationshipsToUpdate.Count); + Assert.Empty(attributesToUpdate); + } + + private void SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate) + { + attributesToUpdate = new List(); + relationshipsToUpdate = new List(); + _fieldsManagerMock.Setup(m => m.AttributesToUpdate).Returns(attributesToUpdate); + _fieldsManagerMock.Setup(m => m.RelationshipsToUpdate).Returns(relationshipsToUpdate); + } + } +} diff --git a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs deleted file mode 100644 index b2a7ad8e57..0000000000 --- a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs +++ /dev/null @@ -1,765 +0,0 @@ -using System; -using System.Collections.Generic; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Request; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Services; -using Moq; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using Xunit; - -namespace UnitTests.Serialization -{ - public class JsonApiDeSerializerTests - { - private readonly Mock _requestManagerMock = new Mock(); - private readonly Mock _jsonApiContextMock = new Mock(); - - public JsonApiDeSerializerTests() - { - _jsonApiContextMock.SetupAllProperties(); - _requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(new Dictionary()); - var jsonApiOptions = new JsonApiOptions(); - jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - _jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - _jsonApiContextMock.Setup(m => m.RequestManager).Returns(_requestManagerMock.Object); - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - resourceGraphBuilder.AddResource("test-resource-with-list"); - resourceGraphBuilder.AddResource("independents"); - resourceGraphBuilder.AddResource("dependents"); - var resourceGraph = resourceGraphBuilder.Build(); - _jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - - - } - - private void CreateMocks() - { - - } - - [Fact] - public void Can_Deserialize_Complex_Types() - { - // arrange - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); - - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { "complex-member", new { compoundName = "testName" } } - } - } - }; - - // act - var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); - - // assert - Assert.NotNull(result.ComplexMember); - Assert.Equal("testName", result.ComplexMember.CompoundName); - } - - [Fact] - public void Can_Deserialize_Complex_List_Types() - { - // arrange - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); - - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource-with-list", - Id = "1", - Attributes = new Dictionary - { - { "complex-members", new [] { new { compoundName = "testName" } } } - } - } - }; - - // act - var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); - - // assert - Assert.NotNull(result.ComplexMembers); - Assert.NotEmpty(result.ComplexMembers); - Assert.Equal("testName", result.ComplexMembers[0].CompoundName); - } - - [Fact] - public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs() - { - // arrange - var jsonApiOptions = new JsonApiOptions(); - jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); // <-- - _jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); - - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { - "complex-member", new Dictionary { { "compound-name", "testName" } } - } - } - } - }; - - // act - var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); - - // assert - Assert.NotNull(result.ComplexMember); - Assert.Equal("testName", result.ComplexMember.CompoundName); - } - - [Fact] - public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() - { - // arrange - var attributesToUpdate = new Dictionary(); - _requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(attributesToUpdate); - var jsonApiOptions = new JsonApiOptions(); - jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); - _jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); - - - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { - "complex-member", new Dictionary { { "compound-name", "testName" } } - }, - { "immutable", "value" } - } - } - }; - - var contentString = JsonConvert.SerializeObject(content); - - // act - var result = deserializer.Deserialize(contentString); - - // assert - Assert.NotNull(result.ComplexMember); - Assert.Single(attributesToUpdate); - - foreach (var attr in attributesToUpdate) - Assert.False(attr.Key.IsImmutable); - } - - [Fact] - public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship() - { - // arrange - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); - - var property = Guid.NewGuid().ToString(); - var content = new Document - { - Data = new ResourceObject - { - Type = "independents", - Id = "1", - Attributes = new Dictionary { { "property", property } } - } - }; - - var contentString = JsonConvert.SerializeObject(content); - - // act - var result = deserializer.Deserialize(contentString); - - // assert - Assert.NotNull(result); - Assert.Equal(property, result.Property); - } - - [Fact] - public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship_With_String_Keys() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("independents"); - resourceGraphBuilder.AddResource("dependents"); - var resourceGraph = resourceGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - var requestManagerMock = new Mock(); - requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(new Dictionary()); - jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); - jsonApiContextMock.Setup(m => m.HasOneRelationshipPointers).Returns(new HasOneRelationshipPointers()); - - var jsonApiOptions = new JsonApiOptions(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); - - - var property = Guid.NewGuid().ToString(); - var content = new Document - { - Data = new ResourceObject - { - Type = "independents", - Id = "natural-key", - Attributes = new Dictionary { { "property", property } }, - Relationships = new Dictionary - { - { "dependent" , new RelationshipData { } } - } - } - }; - - var contentString = JsonConvert.SerializeObject(content); - - // act - var result = deserializer.Deserialize(contentString); - - // assert - Assert.NotNull(result); - Assert.Equal(property, result.Property); - } - - [Fact] - public void Can_Deserialize_Independent_Side_Of_One_To_One_Relationship_With_Relationship_Body() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("independents"); - resourceGraphBuilder.AddResource("dependents"); - var resourceGraph = resourceGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - var requestManagerMock = new Mock(); - requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(new Dictionary()); - jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); - jsonApiContextMock.Setup(m => m.HasOneRelationshipPointers).Returns(new HasOneRelationshipPointers()); - - var jsonApiOptions = new JsonApiOptions(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); - - var property = Guid.NewGuid().ToString(); - var content = new Document - { - Data = new ResourceObject - { - Type = "independents", - Id = "1", - Attributes = new Dictionary { { "property", property } }, - // a common case for this is deserialization in unit tests - Relationships = new Dictionary { - { - "dependent", new RelationshipData - { - SingleData = new ResourceIdentifierObject("dependents", "1") - } - } - } - } - }; - - var contentString = JsonConvert.SerializeObject(content); - - // act - var result = deserializer.Deserialize(contentString); - - // assert - Assert.NotNull(result); - Assert.Equal(property, result.Property); - Assert.NotNull(result.Dependent); - Assert.Equal(1, result.Dependent.Id); - } - - [Fact] - public void Sets_The_DocumentMeta_Property_In_JsonApiContext() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("independents"); - resourceGraphBuilder.AddResource("dependents"); - var resourceGraph = resourceGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - var requestManagerMock = new Mock(); - requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(new Dictionary()); - jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); - - var jsonApiOptions = new JsonApiOptions(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); - - var property = Guid.NewGuid().ToString(); - - var content = new Document - { - Meta = new Dictionary() { { "foo", "bar" } }, - Data = new ResourceObject - { - Type = "independents", - Id = "1", - Attributes = new Dictionary { { "property", property } }, - // a common case for this is deserialization in unit tests - Relationships = new Dictionary { { "dependent", new RelationshipData { } } } - } - }; - - var contentString = JsonConvert.SerializeObject(content); - - // act - var result = deserializer.Deserialize(contentString); - - // assert - jsonApiContextMock.VerifySet(mock => mock.DocumentMeta = content.Meta); - } - - private class TestResource : Identifiable - { - [Attr("complex-member")] - public ComplexType ComplexMember { get; set; } - - [Attr("immutable", isImmutable: true)] - public string Immutable { get; set; } - } - - private class TestResourceWithList : Identifiable - { - [Attr("complex-members")] - public List ComplexMembers { get; set; } - } - - private class ComplexType - { - public string CompoundName { get; set; } - } - - private class Independent : Identifiable - { - [Attr("property")] public string Property { get; set; } - [HasOne("dependent")] public Dependent Dependent { get; set; } - } - - private class Dependent : Identifiable - { - [HasOne("independent")] public Independent Independent { get; set; } - public int IndependentId { get; set; } - } - - private class IndependentWithStringKey : Identifiable - { - [Attr("property")] public string Property { get; set; } - [HasOne("dependent")] public Dependent Dependent { get; set; } - public string DependentId { get; set; } - } - - private class DependentWithStringKey : Identifiable - { - [HasOne("independent")] public Independent Independent { get; set; } - public string IndependentId { get; set; } - } - - [Fact] - public void Can_Deserialize_Object_With_HasManyRelationship() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("independents"); - resourceGraphBuilder.AddResource("dependents"); - var resourceGraph = resourceGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - var requestManagerMock = new Mock(); - requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(new Dictionary()); - requestManagerMock.Setup(m => m.GetUpdatedRelationships()).Returns(new Dictionary()); - jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); - jsonApiContextMock.Setup(m => m.HasManyRelationshipPointers).Returns(new HasManyRelationshipPointers()); - - var jsonApiOptions = new JsonApiOptions(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); - - var contentString = - @"{ - ""data"": { - ""type"": ""independents"", - ""id"": ""1"", - ""attributes"": { }, - ""relationships"": { - ""dependents"": { - ""data"": [ - { - ""type"": ""dependents"", - ""id"": ""2"" - } - ] - } - } - } - }"; - - // act - var result = deserializer.Deserialize(contentString); - - // assert - Assert.NotNull(result); - Assert.Equal(1, result.Id); - Assert.NotNull(result.Dependents); - Assert.NotEmpty(result.Dependents); - Assert.Single(result.Dependents); - - var dependent = result.Dependents[0]; - Assert.Equal(2, dependent.Id); - } - - [Fact] - public void Sets_Attribute_Values_On_Included_HasMany_Relationships() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("independents"); - resourceGraphBuilder.AddResource("dependents"); - var resourceGraph = resourceGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - var requestManagerMock = new Mock(); - requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(new Dictionary()); - requestManagerMock.Setup(m => m.GetUpdatedRelationships()).Returns(new Dictionary()); - jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); - jsonApiContextMock.Setup(m => m.HasManyRelationshipPointers).Returns(new HasManyRelationshipPointers()); - - var jsonApiOptions = new JsonApiOptions(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); - - var expectedName = "John Doe"; - var contentString = - @"{ - ""data"": { - ""type"": ""independents"", - ""id"": ""1"", - ""attributes"": { }, - ""relationships"": { - ""dependents"": { - ""data"": [ - { - ""type"": ""dependents"", - ""id"": ""2"" - } - ] - } - } - }, - ""included"": [ - { - ""type"": ""dependents"", - ""id"": ""2"", - ""attributes"": { - ""name"": """ + expectedName + @""" - } - } - ] - }"; - - // act - var result = deserializer.Deserialize(contentString); - - // assert - Assert.NotNull(result); - Assert.Equal(1, result.Id); - Assert.NotNull(result.Dependents); - Assert.NotEmpty(result.Dependents); - Assert.Single(result.Dependents); - - var dependent = result.Dependents[0]; - Assert.Equal(2, dependent.Id); - Assert.Equal(expectedName, dependent.Name); - } - - [Fact] - public void Sets_Attribute_Values_On_Included_HasOne_Relationships() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("independents"); - resourceGraphBuilder.AddResource("dependents"); - var resourceGraph = resourceGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - var requestManagerMock = new Mock(); - requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(new Dictionary()); - requestManagerMock.Setup(m => m.GetUpdatedRelationships()).Returns(new Dictionary()); - jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); - jsonApiContextMock.Setup(m => m.HasManyRelationshipPointers).Returns(new HasManyRelationshipPointers()); - jsonApiContextMock.Setup(m => m.HasOneRelationshipPointers).Returns(new HasOneRelationshipPointers()); - - var jsonApiOptions = new JsonApiOptions(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); - - var expectedName = "John Doe"; - var contentString = - @"{ - ""data"": { - ""type"": ""dependents"", - ""id"": ""1"", - ""attributes"": { }, - ""relationships"": { - ""independent"": { - ""data"": { - ""type"": ""independents"", - ""id"": ""2"" - } - } - } - }, - ""included"": [ - { - ""type"": ""independents"", - ""id"": ""2"", - ""attributes"": { - ""name"": """ + expectedName + @""" - } - } - ] - }"; - - // act - var result = deserializer.Deserialize(contentString); - - // assert - Assert.NotNull(result); - Assert.Equal(1, result.Id); - Assert.NotNull(result.Independent); - Assert.Equal(2, result.Independent.Id); - Assert.Equal(expectedName, result.Independent.Name); - } - - - [Fact] - public void Can_Deserialize_Nested_Included_HasMany_Relationships() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("independents"); - resourceGraphBuilder.AddResource("dependents"); - resourceGraphBuilder.AddResource("many-to-manys"); - - var deserializer = GetDeserializer(resourceGraphBuilder); - - var contentString = - @"{ - ""data"": { - ""type"": ""independents"", - ""id"": ""1"", - ""attributes"": { }, - ""relationships"": { - ""many-to-manys"": { - ""data"": [{ - ""type"": ""many-to-manys"", - ""id"": ""2"" - }, { - ""type"": ""many-to-manys"", - ""id"": ""3"" - }] - } - } - }, - ""included"": [ - { - ""type"": ""many-to-manys"", - ""id"": ""2"", - ""attributes"": {}, - ""relationships"": { - ""dependent"": { - ""data"": { - ""type"": ""dependents"", - ""id"": ""4"" - } - }, - ""independent"": { - ""data"": { - ""type"": ""independents"", - ""id"": ""5"" - } - } - } - }, - { - ""type"": ""many-to-manys"", - ""id"": ""3"", - ""attributes"": {}, - ""relationships"": { - ""dependent"": { - ""data"": { - ""type"": ""dependents"", - ""id"": ""4"" - } - }, - ""independent"": { - ""data"": { - ""type"": ""independents"", - ""id"": ""6"" - } - } - } - }, - { - ""type"": ""dependents"", - ""id"": ""4"", - ""attributes"": {}, - ""relationships"": { - ""many-to-manys"": { - ""data"": [{ - ""type"": ""many-to-manys"", - ""id"": ""2"" - }, { - ""type"": ""many-to-manys"", - ""id"": ""3"" - }] - } - } - } - , - { - ""type"": ""independents"", - ""id"": ""5"", - ""attributes"": {}, - ""relationships"": { - ""many-to-manys"": { - ""data"": [{ - ""type"": ""many-to-manys"", - ""id"": ""2"" - }] - } - } - } - , - { - ""type"": ""independents"", - ""id"": ""6"", - ""attributes"": {}, - ""relationships"": { - ""many-to-manys"": { - ""data"": [{ - ""type"": ""many-to-manys"", - ""id"": ""3"" - }] - } - } - } - ] - }"; - - // act - var result = deserializer.Deserialize(contentString); - - // assert - Assert.NotNull(result); - Assert.Equal(1, result.Id); - Assert.NotNull(result.ManyToManys); - Assert.Equal(2, result.ManyToManys.Count); - - // TODO: not sure if this should be a thing that works? - // could this cause cycles in the graph? - // Assert.NotNull(result.ManyToManys[0].Dependent); - // Assert.NotNull(result.ManyToManys[0].Independent); - // Assert.NotNull(result.ManyToManys[1].Dependent); - // Assert.NotNull(result.ManyToManys[1].Independent); - - // Assert.Equal(result.ManyToManys[0].Dependent, result.ManyToManys[1].Dependent); - // Assert.NotEqual(result.ManyToManys[0].Independent, result.ManyToManys[1].Independent); - } - - private JsonApiDeSerializer GetDeserializer(ResourceGraphBuilder resourceGraphBuilder) - { - var resourceGraph = resourceGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - var requestManagerMock = new Mock(); - requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(new Dictionary()); - requestManagerMock.Setup(m => m.GetUpdatedRelationships()).Returns(new Dictionary()); - jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); - jsonApiContextMock.Setup(m => m.HasManyRelationshipPointers).Returns(new HasManyRelationshipPointers()); - jsonApiContextMock.Setup(m => m.HasOneRelationshipPointers).Returns(new HasOneRelationshipPointers()); - - var jsonApiOptions = new JsonApiOptions(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var deserializer = new JsonApiDeSerializer(_jsonApiContextMock.Object, _requestManagerMock.Object); - - return deserializer; - } - - private class ManyToManyNested : Identifiable - { - [Attr("name")] public string Name { get; set; } - [HasOne("dependent")] public OneToManyDependent Dependent { get; set; } - public int DependentId { get; set; } - [HasOne("independent")] public OneToManyIndependent Independent { get; set; } - public int InependentId { get; set; } - } - - private class OneToManyDependent : Identifiable - { - [Attr("name")] public string Name { get; set; } - [HasOne("independent")] public OneToManyIndependent Independent { get; set; } - public int IndependentId { get; set; } - - [HasMany("many-to-manys")] public List ManyToManys { get; set; } - } - - private class OneToManyIndependent : Identifiable - { - [Attr("name")] public string Name { get; set; } - [HasMany("dependents")] public List Dependents { get; set; } - - [HasMany("many-to-manys")] public List ManyToManys { get; set; } - } - } -} diff --git a/test/UnitTests/Serialization/SerializationTestsSetupBase.cs b/test/UnitTests/Serialization/SerializationTestsSetupBase.cs new file mode 100644 index 0000000000..5135a2e642 --- /dev/null +++ b/test/UnitTests/Serialization/SerializationTestsSetupBase.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; + +namespace UnitTests.Deserialization +{ + public class SerializationTestsSetupBase + { + protected readonly IResourceGraph _resourceGraph; + protected readonly JsonApiSerializerSettings _defaultSettings = new JsonApiSerializerSettings(); + + public SerializationTestsSetupBase() + { + _resourceGraph = BuildGraph(); + } + + protected IResourceGraph BuildGraph() + { + var resourceGraphBuilder = new ResourceGraphBuilder(); + resourceGraphBuilder.AddResource("test-resource"); + resourceGraphBuilder.AddResource("test-resource-with-list"); + // one to one relationships + resourceGraphBuilder.AddResource("one-to-one-principals"); + resourceGraphBuilder.AddResource("one-to-one-dependents"); + resourceGraphBuilder.AddResource("one-to-one-required-dependents"); + // one to many relationships + resourceGraphBuilder.AddResource("one-to-many-principals"); + resourceGraphBuilder.AddResource("one-to-many-dependents"); + resourceGraphBuilder.AddResource("one-to-many-required-dependents"); + // collective relationships + resourceGraphBuilder.AddResource("multi-principals"); + resourceGraphBuilder.AddResource("multi-dependents"); + return resourceGraphBuilder.Build(); + } + + protected class TestResource : Identifiable + { + [Attr] public string StringField { get; set; } + [Attr] public DateTime DateTimeField { get; set; } + [Attr] public DateTime? NullableDateTimeField { get; set; } + [Attr] public int IntField { get; set; } + [Attr] public int? NullableIntField { get; set; } + [Attr] public Guid GuidField { get; set; } + [Attr] public ComplexType ComplexField { get; set; } + [Attr(isImmutable: true)] public string Immutable { get; set; } + } + + protected class TestResourceWithList : Identifiable + { + [Attr] public List ComplexFields { get; set; } + } + + protected class ComplexType + { + public string CompoundName { get; set; } + } + + protected class OneToOnePrincipal : IdentifiableWithAttribute + { + [HasOne] public OneToOneDependent Dependent { get; set; } + } + + protected class OneToOneDependent : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal Principal { get; set; } + public int? PrincipalId { get; set; } + } + + protected class OneToOneRequiredDependent : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal Principal { get; set; } + public int PrincipalId { get; set; } + } + + protected class OneToManyDependent : IdentifiableWithAttribute + { + [HasOne] public OneToManyPrincipal Principal { get; set; } + public int? PrincipalId { get; set; } + } + + protected class OneToManyRequiredDependent : IdentifiableWithAttribute + { + [HasOne] public OneToManyPrincipal Principal { get; set; } + public int PrincipalId { get; set; } + } + + protected class OneToManyPrincipal : IdentifiableWithAttribute + { + [HasMany] public List Dependents { get; set; } + } + + protected class IdentifiableWithAttribute : Identifiable + { + [Attr] public string AttributeMember { get; set; } + } + + protected class MultipleRelationshipsPrincipalPart : IdentifiableWithAttribute + { + [HasOne] public OneToOneDependent PopulatedToOne { get; set; } + [HasOne] public OneToOneDependent EmptyToOne { get; set; } + [HasMany] public List PopulatedToManies { get; set; } + [HasMany] public List EmptyToManies { get; set; } + [HasOne] public MultipleRelationshipsPrincipalPart Multi { get; set; } + } + + protected class MultipleRelationshipsDependentPart : IdentifiableWithAttribute + { + [HasOne] public OneToOnePrincipal PopulatedToOne { get; set; } + public int PopulatedToOneId { get; set; } + [HasOne] public OneToOnePrincipal EmptyToOne { get; set; } + public int? EmptyToOneId { get; set; } + [HasOne] public OneToManyPrincipal PopulatedToMany { get; set; } + public int PopulatedToManyId { get; set; } + [HasOne] public OneToManyPrincipal EmptyToMany { get; set; } + public int? EmptyToManyId { get; set; } + } + } +} \ No newline at end of file diff --git a/test/UnitTests/Serialization/Serializer/ClientSerializerTests.cs b/test/UnitTests/Serialization/Serializer/ClientSerializerTests.cs new file mode 100644 index 0000000000..1d011bf8aa --- /dev/null +++ b/test/UnitTests/Serialization/Serializer/ClientSerializerTests.cs @@ -0,0 +1,138 @@ +using System.Collections.Generic; +using System.Text.RegularExpressions; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; + +namespace UnitTests.Serialization.Serializer +{ + public class ClientSerializerTests : SerializerTestsSetup + { + + public ClientSerializerTests() + { + + } + + + + [Fact] + public void Serialize_TestResource_CanSerialize() + { + // arrange + var complexFieldValue = "complex type field"; + var stringFieldValue = "string field"; + var entity = new TestResource() + { + Id = 1, + ComplexField = new ComplexType() { CompoundName = complexFieldValue }, + StringField = stringFieldValue + }; + var serializer = GetClientSerializer(); + + // act + var document = serializer.Build(entity); + + // assert + Assert.Equal(8, document.Data.Attributes.Keys.Count); + var complexType = (ComplexType)document.Data.Attributes["complex-field"]; + Assert.Equal(complexFieldValue, complexType.CompoundName); + Assert.Equal(stringFieldValue, document.Data.Attributes["string-field"]); + Assert.Null(document.Data.Relationships); + Assert.Equal("1", document.Data.Id); + Assert.Equal("test-resource", document.Data.Type); + } + + [Fact] + public void Serialize_TestResourceList_CanSerialize() + { + // arrange + var entities = new List + { + new TestResource { Id = 1 }, + new TestResource { Id = 2 }, + new TestResource { Id = 3 } + }; + var serializer = GetClientSerializer(); + + // act + var documents = serializer.Build(entities); + + // assert + Assert.Equal(3, documents.Data.Count); + foreach (var resourceObject in documents.Data) + { + Assert.Equal(8, resourceObject.Attributes.Keys.Count); + Assert.Null(resourceObject.Relationships); + } + } + + + [Fact] + public void Serialize_TestResourceListWithSubsetOfAttributes_CanSerialize() + { + // arrange + var entities = new List + { + new TestResource { Id = 1 }, + new TestResource { Id = 2 }, + new TestResource { Id = 3 } + }; + var serializer = GetClientSerializer(); + serializer.AttributesToInclude(tr => new { tr.StringField, tr.NullableDateTimeField }); + serializer.SetResourceForTests(); + + // act + var documents = serializer.Build(entities); + + // assert + Assert.Equal(3, documents.Data.Count); + foreach (var resourceObject in documents.Data) + { + Assert.Equal(2, resourceObject.Attributes.Keys.Count); + Assert.Null(resourceObject.Relationships); + } + } + + [Fact] + public void Serialize_ResourceWithNoAttributes_CanSerialize() + { + // arrange + var serializer = GetClientSerializer(); + serializer.AttributesToInclude(tr => new { }); + serializer.SetResourceForTests(); + + // act + var document = serializer.Build(new TestResource { Id = 1 }); + + // assert + Assert.Null(document.Data.Attributes); + Assert.Null(document.Data.Relationships); + } + + [Fact] + public void Serialize_ResourceWithRelationships_CanSerialize() + { + // arrange + var serializer = GetClientSerializer(); + //serializer.AttributesToInclude(tr => new { }); + var entity = new MultipleRelationshipsPrincipalPart(); + + + // act + var document = serializer.Build(entity); + + // assert + Assert.Equal(1, document.Data.Attributes.Keys.Count); + Assert.Null(document.Data.Relationships); + } + + } +} diff --git a/test/UnitTests/Serialization/Serializer/DocumentBuilderTests.cs b/test/UnitTests/Serialization/Serializer/DocumentBuilderTests.cs new file mode 100644 index 0000000000..f57d04d237 --- /dev/null +++ b/test/UnitTests/Serialization/Serializer/DocumentBuilderTests.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Models; +using Xunit; + +namespace UnitTests.Serialization.Serializer +{ + public class DocumentBuilderTests : SerializerTestsSetup + { + private readonly TestSerializer _serializer; + + public DocumentBuilderTests() + { + _serializer = new TestSerializer(_resourceGraph, _resourceGraph); + + } + + [Fact] + public void ResourceToDocument_EmptyResource_CanBuild() + { + // arrange + var entity = new TestResource(); + + // act + var document = _serializer.Build(entity); + var data = (ResourceObject)document.Data; + + // assert + Assert.Null(data.Attributes); + Assert.Null(data.Relationships); + Assert.Null(data.Id); + Assert.Equal("test-resource", data.Type); + } + + [Fact] + public void ResourceToDocument_ResourceWithId_CanBuild() + { + // arrange + var entity = new TestResource() { Id = 1 }; + + // act + var document = _serializer.Build(entity); + var data = (ResourceObject)document.Data; + + // assert + Assert.Equal("1", data.Id); + Assert.Null(data.Attributes); + Assert.Null(data.Relationships); + Assert.Equal("test-resource", data.Type); + } + + [Theory] + [InlineData(null, null)] + [InlineData("string field", 1)] + public void ResourceToDocument_ResourceWithIncludedAttrs_CanBuild(string stringFieldValue, int? intFieldValue) + { + // arrange + var entity = new TestResource() { StringField = stringFieldValue, NullableIntField = intFieldValue }; + var attrs = _fieldExplorer.GetAttributes(tr => new { tr.StringField, tr.NullableIntField }); + // act + var document = _serializer.Build(entity, attrs); + var data = (ResourceObject)document.Data; + + // assert + Assert.NotNull(data.Attributes); + Assert.Equal(2, data.Attributes.Keys.Count); + Assert.Equal(stringFieldValue, data.Attributes["string-field"]); + Assert.Equal(intFieldValue, data.Attributes["nullable-int-field"]); + } + + + + [Theory] + [InlineData(null, null)] + [InlineData("string field", 1)] + public void ResourceListToDocument_ResourcesWithIncludedAttrs_CanBuild(string stringFieldValue, int? intFieldValue) + { + // arrange + var entities = new List() + { + new TestResource() { Id = 1, StringField = stringFieldValue, NullableIntField = intFieldValue }, + new TestResource() { Id = 2, StringField = stringFieldValue, NullableIntField = intFieldValue } + }; + var attrs = _fieldExplorer.GetAttributes(tr => new { tr.StringField, tr.NullableIntField }); + // act + var document = _serializer.Build(entities, attrs); + var data = (List)document.Data; + + // assert + + Assert.Equal(2, data.Count); + foreach (var ro in data) + { + Assert.Equal(2, ro.Attributes.Keys.Count); + Assert.Equal(stringFieldValue, ro.Attributes["string-field"]); + Assert.Equal(intFieldValue, ro.Attributes["nullable-int-field"]); + } + } + + [Fact] + public void ResourceWithRelationshipsToDocument_EmptyResource_CanBuild() + { + // arrange + var entity = new MultipleRelationshipsPrincipalPart(); + + // act + var document = _serializer.Build(entity); + var data = (ResourceObject)document.Data; + + // assert + Assert.Null(data.Attributes); + Assert.Null(data.Relationships); + Assert.Null(data.Id); + Assert.Equal("multi-principals", data.Type); + } + + [Fact] + public void ResourceWithRelationshipsToDocument_ResourceWithId_CanBuild() + { + // arrange + var entity = new MultipleRelationshipsPrincipalPart + { + PopulatedToOne = new OneToOneDependent { Id = 10 }, + }; + + // act + var document = _serializer.Build(entity); + var data = (ResourceObject)document.Data; + + // assert + Assert.Null(data.Attributes); + Assert.Null(data.Relationships); + Assert.Null(data.Id); + Assert.Equal("multi-principals", data.Type); + } + + [Fact] + public void ResourceWithRelationshipsToDocument_WithIncludedRelationshipsAttributes_CanBuild() + { + // arrange + var entity = new MultipleRelationshipsPrincipalPart + { + PopulatedToOne = new OneToOneDependent { Id = 10 }, + PopulatedToManies = new List { new OneToManyDependent { Id = 20 } } + }; + var relationships = _fieldExplorer.GetRelationships(tr => new { tr.PopulatedToManies, tr.PopulatedToOne, tr.EmptyToOne, tr.EmptyToManies }); + + // act + var document = _serializer.Build(entity, relationships: relationships); + var data = (ResourceObject)document.Data; + + // assert + Assert.Equal(4, data.Relationships.Count); + Assert.Null(data.Relationships["empty-to-one"].Data); + Assert.Empty((IList)data.Relationships["empty-to-manies"].Data); + var populatedToOneData = (ResourceIdentifierObject)data.Relationships["populated-to-one"].Data; + Assert.NotNull(populatedToOneData); + Assert.Equal("10", populatedToOneData.Id); + Assert.Equal("one-to-one-dependents", populatedToOneData.Type); + var populatedToManiesData = (List)data.Relationships["populated-to-manies"].Data; + Assert.Equal(1, populatedToManiesData.Count); + Assert.Equal("20", populatedToManiesData.First().Id); + Assert.Equal("one-to-many-dependents", populatedToManiesData.First().Type); + } + + [Fact] + public void ResourceWithRelationshipsToDocument_DeviatingForeignKeyWhileRelationshipIncluded_IgnoresForeignKeyDuringBuild() + { + // arrange + var entity = new OneToOneDependent { Principal = new OneToOnePrincipal { Id = 10 }, PrincipalId = 123 }; + var relationships = _fieldExplorer.GetRelationships(tr => tr.Principal); + + // act + var document = _serializer.Build(entity, relationships: relationships); + var data = (ResourceObject)document.Data; + + // assert + Assert.Equal(1, data.Relationships.Count); + Assert.NotNull(data.Relationships["principal"].Data); + var ro = (ResourceIdentifierObject)data.Relationships["principal"].Data; + Assert.Equal("10", ro.Id); + } + + [Fact] + public void ResourceWithRelationshipsToDocument_DeviatingForeignKeyAndNoNavigationWhileRelationshipIncluded_IgnoresForeignKeyDuringBuild() + { + // arrange + var entity = new OneToOneDependent { Principal = null, PrincipalId = 123 }; + var relationships = _fieldExplorer.GetRelationships(tr => tr.Principal); + + // act + var document = _serializer.Build(entity, relationships: relationships); + var data = (ResourceObject)document.Data; + + // assert + Assert.Null(data.Relationships["principal"].Data); + } + + [Fact] + public void ResourceWithRequiredRelationshipsToDocument_DeviatingForeignKeyWhileRelationshipIncluded_IgnoresForeignKeyDuringBuild() + { + // arrange + var entity = new OneToOneRequiredDependent { Principal = new OneToOnePrincipal { Id = 10 }, PrincipalId = 123 }; + var relationships = _fieldExplorer.GetRelationships(tr => tr.Principal); + + // act + var document = _serializer.Build(entity, relationships: relationships); + var data = (ResourceObject)document.Data; + + // assert + Assert.Equal(1, data.Relationships.Count); + Assert.NotNull(data.Relationships["principal"].Data); + var ro = (ResourceIdentifierObject)data.Relationships["principal"].Data; + Assert.Equal("10", ro.Id); + } + + [Fact] + public void ResourceWithRequiredRelationshipsToDocument_DeviatingForeignKeyAndNoNavigationWhileRelationshipIncluded_ThrowsNotSupportedException() + { + // arrange + var entity = new OneToOneRequiredDependent { Principal = null, PrincipalId = 123 }; + var relationships = _fieldExplorer.GetRelationships(tr => tr.Principal); + + // act & assert + Assert.ThrowsAny(() => _serializer.Build(entity, relationships: relationships)); + } + + [Fact] + public void ResourceWithRequiredRelationshipsToDocument_EmptyResourceWhileRelationshipIncluded_ThrowsNotSupportedException() + { + // arrange + var entity = new OneToOneRequiredDependent(); + var relationships = _fieldExplorer.GetRelationships(tr => tr.Principal); + + // act & assert + Assert.ThrowsAny(() => _serializer.Build(entity, relationships: relationships)); + } + } +} diff --git a/test/UnitTests/Serialization/Serializer/IncludedRelationshipsBuilderTests.cs b/test/UnitTests/Serialization/Serializer/IncludedRelationshipsBuilderTests.cs new file mode 100644 index 0000000000..c456ea2241 --- /dev/null +++ b/test/UnitTests/Serialization/Serializer/IncludedRelationshipsBuilderTests.cs @@ -0,0 +1,216 @@ +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Models.Links; +using JsonApiDotNetCoreExample.Models; +using Moq; +using Xunit; +using System; + +namespace UnitTests +{ + public class LinkBuilderTests + { + private readonly IPageManager _pageManager; + private readonly Mock _provider = new Mock(); + private const string _host = "http://www.example.com"; + private const string _topSelf = "http://www.example.com/articles"; + private const string _resourceSelf = "http://www.example.com/articles/123"; + private const string _relSelf = "http://www.example.com/articles/123/relationships/author"; + private const string _relRelated = "http://www.example.com/articles/123/author"; + + public LinkBuilderTests() + { + _pageManager = GetPageManager(); + } + + [Theory] + [InlineData(Link.All, Link.NotConfigured, _resourceSelf)] + [InlineData(Link.Self, Link.NotConfigured, _resourceSelf)] + [InlineData(Link.None, Link.NotConfigured, null)] + [InlineData(Link.All, Link.Self, _resourceSelf)] + [InlineData(Link.Self, Link.Self, _resourceSelf)] + [InlineData(Link.None, Link.Self, _resourceSelf)] + [InlineData(Link.All, Link.None, null)] + [InlineData(Link.Self, Link.None, null)] + [InlineData(Link.None, Link.None, null)] + public void BuildResourceLinks_GlobalAndResourceConfiguration_ExpectedResult(Link global, Link resource, object expectedResult) + { + // arrange + var config = GetConfiguration(resourceLinks: global); + _provider.Setup(m => m.GetContextEntity("articles")).Returns(GetContextEntity
(resourceLinks: resource)); + var builder = new LinkBuilder(config, GetRequestManager(), _pageManager, _provider.Object); + + // act + var links = builder.GetResourceLinks("articles", "123"); + + // assert + if (expectedResult == null) + Assert.Null(links); + else + Assert.Equal(_resourceSelf, links.Self); + } + + + + [Theory] + [InlineData(Link.All, Link.NotConfigured, Link.NotConfigured, _relSelf, _relRelated)] + [InlineData(Link.All, Link.NotConfigured, Link.All, _relSelf, _relRelated)] + [InlineData(Link.All, Link.NotConfigured, Link.Self, _relSelf, null)] + [InlineData(Link.All, Link.NotConfigured, Link.Related, null, _relRelated)] + [InlineData(Link.All, Link.NotConfigured, Link.None, null, null)] + [InlineData(Link.All, Link.All, Link.NotConfigured, _relSelf, _relRelated)] + [InlineData(Link.All, Link.All, Link.All, _relSelf, _relRelated)] + [InlineData(Link.All, Link.All, Link.Self, _relSelf, null)] + [InlineData(Link.All, Link.All, Link.Related, null, _relRelated)] + [InlineData(Link.All, Link.All, Link.None, null, null)] + [InlineData(Link.All, Link.Self, Link.NotConfigured, _relSelf, null)] + [InlineData(Link.All, Link.Self, Link.All, _relSelf, _relRelated)] + [InlineData(Link.All, Link.Self, Link.Self, _relSelf, null)] + [InlineData(Link.All, Link.Self, Link.Related, null, _relRelated)] + [InlineData(Link.All, Link.Self, Link.None, null, null)] + [InlineData(Link.All, Link.Related, Link.NotConfigured, null, _relRelated)] + [InlineData(Link.All, Link.Related, Link.All, _relSelf, _relRelated)] + [InlineData(Link.All, Link.Related, Link.Self, _relSelf, null)] + [InlineData(Link.All, Link.Related, Link.Related, null, _relRelated)] + [InlineData(Link.All, Link.Related, Link.None, null, null)] + [InlineData(Link.All, Link.None, Link.NotConfigured, null, null)] + [InlineData(Link.All, Link.None, Link.All, _relSelf, _relRelated)] + [InlineData(Link.All, Link.None, Link.Self, _relSelf, null)] + [InlineData(Link.All, Link.None, Link.Related, null, _relRelated)] + [InlineData(Link.All, Link.None, Link.None, null, null)] + public void BuildRelationshipLinks_GlobalResourceAndAttrConfiguration_ExpectedLinks(Link global, + Link resource, + Link relationship, + object expectedSelfLink, + object expectedRelatedLink) + { + // arrange + var config = GetConfiguration(relationshipLinks: global); + _provider.Setup(m => m.GetContextEntity(typeof(Article))).Returns(GetContextEntity
(relationshipLinks: resource)); + var builder = new LinkBuilder(config, GetRequestManager(), _pageManager, _provider.Object); + var attr = new HasOneAttribute(links: relationship) { DependentType = typeof(Author), PublicRelationshipName = "author" }; + + // act + var links = builder.GetRelationshipLinks(attr, new Article { Id = 123 }); + + // assert + if (expectedSelfLink == null && expectedRelatedLink == null) + { + Assert.Null(links); + } + else + { + Assert.Equal(expectedSelfLink, links.Self); + Assert.Equal(expectedRelatedLink, links.Related); + } + } + + [Theory] + [InlineData(Link.All, Link.NotConfigured, _topSelf, true)] + [InlineData(Link.All, Link.All, _topSelf, true)] + [InlineData(Link.All, Link.Self, _topSelf, false)] + [InlineData(Link.All, Link.Paging, null, true)] + [InlineData(Link.All, Link.None, null, null)] + [InlineData(Link.Self, Link.NotConfigured, _topSelf, false)] + [InlineData(Link.Self, Link.All, _topSelf, true)] + [InlineData(Link.Self, Link.Self, _topSelf, false)] + [InlineData(Link.Self, Link.Paging, null, true)] + [InlineData(Link.Self, Link.None, null, null)] + [InlineData(Link.Paging, Link.NotConfigured, null, true)] + [InlineData(Link.Paging, Link.All, _topSelf, true)] + [InlineData(Link.Paging, Link.Self, _topSelf, false)] + [InlineData(Link.Paging, Link.Paging, null, true)] + [InlineData(Link.Paging, Link.None, null, null)] + [InlineData(Link.None, Link.NotConfigured, null, false)] + [InlineData(Link.None, Link.All, _topSelf, true)] + [InlineData(Link.None, Link.Self, _topSelf, false)] + [InlineData(Link.None, Link.Paging, null, true)] + [InlineData(Link.None, Link.None, null, null)] + public void BuildTopLevelLinks_GlobalAndResourceConfiguration_ExpectedLinks(Link global, + Link resource, + object expectedSelfLink, + bool pages) + { + // arrange + var config = GetConfiguration(topLevelLinks: global); + var resourceContext = GetContextEntity
(topLevelLinks: resource); + var builder = new LinkBuilder(config, GetRequestManager(resourceContext), _pageManager, null); + + // act + var links = builder.GetTopLevelLinks(); + + // assert + if (!pages && expectedSelfLink == null) + { + Assert.Null(links); + } + else + { + Assert.Equal(expectedSelfLink, links.Self); + Assert.True(CheckPages(links, pages)); + } + } + + private bool CheckPages(TopLevelLinks links, bool pages) + { + if (pages) + { + return links.First == $"{_host}/articles?page[size]=10&page[number]=1" + && links.Prev == $"{_host}/articles?page[size]=10&page[number]=1" + && links.Next == $"{_host}/articles?page[size]=10&page[number]=3" + && links.Last == $"{_host}/articles?page[size]=10&page[number]=3"; + } + return links.First == null && links.Prev == null && links.Next == null && links.Last == null; + } + + private IRequestManager GetRequestManager(ContextEntity resourceContext = null) + { + var mock = new Mock(); + mock.Setup(m => m.BasePath).Returns(_host); + mock.Setup(m => m.GetRequestResource()).Returns(resourceContext); + return mock.Object; + } + + private IGlobalLinksConfiguration GetConfiguration(Link resourceLinks = Link.All, + Link topLevelLinks = Link.All, + Link relationshipLinks = Link.All) + { + var config = new Mock(); + config.Setup(m => m.TopLevelLinks).Returns(topLevelLinks); + config.Setup(m => m.ResourceLinks).Returns(resourceLinks); + config.Setup(m => m.RelationshipLinks).Returns(relationshipLinks); + return config.Object; + } + + private IPageManager GetPageManager() + { + var mock = new Mock(); + mock.Setup(m => m.ShouldPaginate()).Returns(true); + mock.Setup(m => m.CurrentPage).Returns(2); + mock.Setup(m => m.TotalPages).Returns(3); + mock.Setup(m => m.PageSize).Returns(10); + return mock.Object; + + } + + + + private ContextEntity GetContextEntity(Link resourceLinks = Link.NotConfigured, + Link topLevelLinks = Link.NotConfigured, + Link relationshipLinks = Link.NotConfigured) where TResource : class, IIdentifiable + { + return new ContextEntity + { + ResourceLinks = resourceLinks, + TopLevelLinks = topLevelLinks, + RelationshipLinks = relationshipLinks, + EntityName = typeof(TResource).Name.Dasherize() + "s" + }; + } + } +} diff --git a/test/UnitTests/Serialization/Serializer/SerializerTestsSetup.cs b/test/UnitTests/Serialization/Serializer/SerializerTestsSetup.cs new file mode 100644 index 0000000000..b1d3a9a5e9 --- /dev/null +++ b/test/UnitTests/Serialization/Serializer/SerializerTestsSetup.cs @@ -0,0 +1,6 @@ +namespace UnitTests.Serialization +{ + public class SerializerTestsSetup + { + } +} \ No newline at end of file diff --git a/test/UnitTests/Serialization/Serializer/ServerSerializerTests.cs b/test/UnitTests/Serialization/Serializer/ServerSerializerTests.cs new file mode 100644 index 0000000000..64b530e8b6 --- /dev/null +++ b/test/UnitTests/Serialization/Serializer/ServerSerializerTests.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Models; +using Xunit; + +namespace UnitTests.Serialization.Serializer +{ + public class ServerSerializerTests : SerializerTestsSetup + { + [Fact] + public void SerializeSingle_ResourceWithDefaultTargetFields_CanBuild() + { + // arrange + var entity = new TestResource() { Id = 1, StringField = "value", NullableIntField = 123 }; + ClientSerializer serializer = GetServerSerializer(); + + // act + string serialized = serializer.Serialize(entity); + + // assert + var expectedFormatted = + @"{ + ""data"":{ + ""attributes"":{ + ""string-field"":""value"", + ""date-time-field"":""0001-01-01T00:00:00"", + ""nullable-date-time-field"":null, + ""int-field"":0, + ""nullable-int-field"":123, + ""guid-field"":""00000000-0000-0000-0000-000000000000"", + ""complex-field"":null, + ""immutable"":null + }, + ""type"":""test-resource"", + ""id"":""1"" + } + }"; + + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + + Assert.Equal(expected, serialized); + } + + [Fact] + public void SerializeSingle_ResourceWithTargetedSetAttributes_CanBuild() + { + // arrange + var entity = new TestResource() { Id = 1, StringField = "value", NullableIntField = 123 }; + ClientSerializer serializer = GetServerSerializer(); + serializer.SetAttributesToSerialize(tr => tr.StringField); + + // act + string serialized = serializer.Serialize(entity); + + // assert + var expectedFormatted = + @"{ + ""data"":{ + ""attributes"":{ + ""string-field"":""value"" + }, + ""type"":""test-resource"", + ""id"":""1"" + } + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); + } + + [Fact] + public void SerializeSingle_NoIdWithTargetedSetAttributes_CanBuild() + { + // arrange + var entityNoId = new TestResource() { Id = 0, StringField = "value", NullableIntField = 123 }; + ClientSerializer serializer = GetServerSerializer(); + serializer.SetAttributesToSerialize(tr => tr.StringField); + + // act + string serialized = serializer.Serialize(entityNoId); + + // assert + var expectedFormatted = + @"{ + ""data"":{ + ""attributes"":{ + ""string-field"":""value"" + }, + ""type"":""test-resource"" + } + }"; + + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); + } + + [Fact] + public void SerializeSingle_ResourceWithoutTargetedAttributes_CanBuild() + { + // arrange + var entity = new TestResource() { Id = 1, StringField = "value", NullableIntField = 123 }; + ClientSerializer serializer = GetServerSerializer(); + serializer.SetAttributesToSerialize(tr => new { }); + + // act + string serialized = serializer.Serialize(entity); + + // assert + var expectedFormatted = + @"{ + ""data"":{ + ""type"":""test-resource"", + ""id"":""1"" + } + }"; + + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); + } + + [Fact] + public void SerializeSingle_ResourceWithTargetedRelationships_CanBuild() + { + // arrange + var entityWithRelationships = new MultipleRelationshipsPrincipalPart + { + PopulatedToOne = new OneToOneDependent { Id = 10 }, + PopulatedToManies = new List { new OneToManyDependent { Id = 20 } } + }; + ClientSerializer serializer = GetServerSerializer(); + serializer.SetRelationshipsToSerialize(tr => new { tr.EmptyToOne, tr.EmptyToManies, tr.PopulatedToOne, tr.PopulatedToManies }); + + // act + string serialized = serializer.Serialize(entityWithRelationships); + Console.WriteLine(serialized); + // assert + var expectedFormatted = + @"{ + ""data"":{ + ""attributes"":{ + ""attribute-member"":null + }, + ""relationships"":{ + ""empty-to-one"":{ + ""data"":null + }, + ""empty-to-manies"":{ + ""data"":[ + + ] + }, + ""populated-to-one"":{ + ""data"":{ + ""type"":""one-to-one-dependents"", + ""id"":""10"" + } + }, + ""populated-to-manies"":{ + ""data"":[ + { + ""type"":""one-to-many-dependents"", + ""id"":""20"" + } + ] + } + }, + ""type"":""multi-principals"" + } + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); + } + + [Fact] + public void SerializeMany_ResourcesWithTargetedAttributes_CanBuild() + { + // arrange + var entities = new List + { + new TestResource() { Id = 1, StringField = "value1", NullableIntField = 123 }, + new TestResource() { Id = 2, StringField = "value2", NullableIntField = 123 } + }; + ClientSerializer serializer = GetServerSerializer(); + serializer.SetAttributesToSerialize(tr => tr.StringField); + + // act + string serialized = serializer.Serialize(entities); + + // assert + var expectedFormatted = + @"{ + ""data"":[ + { + ""attributes"":{ + ""string-field"":""value1"" + }, + ""type"":""test-resource"", + ""id"":""1"" + }, + { + ""attributes"":{ + ""string-field"":""value2"" + }, + ""type"":""test-resource"", + ""id"":""2"" + } + ] + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); + } + + [Fact] + public void SerializeSingle_Null_CanBuild() + { + // arrange + ClientSerializer serializer = GetServerSerializer(); + serializer.SetAttributesToSerialize(tr => tr.StringField); + + // act + IIdentifiable obj = null; ; + string serialized = serializer.Serialize(obj); + + // assert + var expectedFormatted = + @"{ + ""data"":null + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); + } + + [Fact] + public void SerializeMany_EmptyList_CanBuild() + { + // arrange + var entities = new List { }; + ClientSerializer serializer = GetServerSerializer(); + serializer.SetAttributesToSerialize(tr => tr.StringField); + + // act + string serialized = serializer.Serialize(entities); + + // assert + var expectedFormatted = + @"{ + ""data"":[] + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); + } + + private ServerSerializer GetServerSerializer() + { + return new ServerSerializer(_fieldExplorer, _resourceGraph, _defaultSettings); + } + } +} diff --git a/test/UnitTests/Serialization/SerializerBaseTests.cs b/test/UnitTests/Serialization/SerializerBaseTests.cs new file mode 100644 index 0000000000..d301caf1fa --- /dev/null +++ b/test/UnitTests/Serialization/SerializerBaseTests.cs @@ -0,0 +1,284 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Request; +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; + +namespace UnitTests.Serialization +{ + public class JsonApiSerializerTests + { + [Fact] + public void Can_Serialize_Complex_Types() + { + // arrange + var resourceGraphBuilder = new ResourceGraphBuilder(); + resourceGraphBuilder.AddResource("test-resource"); + + var serializer = GetSerializer(resourceGraphBuilder); + + var resource = new TestResource + { + ComplexMember = new ComplexType + { + CompoundName = "testname" + } + }; + + // act + var result = serializer.Serialize(resource); + + // assert + Assert.NotNull(result); + + var expectedFormatted = + @"{ + ""data"": { + ""attributes"": { + ""complex-member"": { + ""compound-name"": ""testname"" + } + }, + ""relationships"": { + ""children"": { + ""links"": { + ""self"": ""/test-resource//relationships/children"", + ""related"": ""/test-resource//children"" + } + } + }, + ""type"": ""test-resource"", + ""id"": """" + } + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + + Assert.Equal(expected, result); + } + + [Fact] + public void Can_Serialize_Deeply_Nested_Relationships() + { + // arrange + var resourceGraphBuilder = new ResourceGraphBuilder(); + resourceGraphBuilder.AddResource("test-resource"); + resourceGraphBuilder.AddResource("children"); + resourceGraphBuilder.AddResource("infections"); + + var serializer = GetSerializer( + resourceGraphBuilder, + new List { "children.infections" } + ); + + var resource = new TestResource + { + Id = 1, + Children = new List { + new ChildResource { + Id = 2, + Infections = new List { + new InfectionResource { Id = 4 }, + new InfectionResource { Id = 5 }, + } + }, + new ChildResource { + Id = 3 + } + } + }; + + // act + var result = serializer.Serialize(resource); + + // assert + Assert.NotNull(result); + + var expectedFormatted = + @"{ + ""data"": { + ""attributes"": { + ""complex-member"": null + }, + ""relationships"": { + ""children"": { + ""links"": { + ""self"": ""/test-resource/1/relationships/children"", + ""related"": ""/test-resource/1/children"" + }, + ""data"": [{ + ""type"": ""children"", + ""id"": ""2"" + }, { + ""type"": ""children"", + ""id"": ""3"" + }] + } + }, + ""type"": ""test-resource"", + ""id"": ""1"" + }, + ""included"": [ + { + ""attributes"": {}, + ""relationships"": { + ""infections"": { + ""links"": { + ""self"": ""/children/2/relationships/infections"", + ""related"": ""/children/2/infections"" + }, + ""data"": [{ + ""type"": ""infections"", + ""id"": ""4"" + }, { + ""type"": ""infections"", + ""id"": ""5"" + }] + }, + ""parent"": { + ""links"": { + ""self"": ""/children/2/relationships/parent"", + ""related"": ""/children/2/parent"" + } + } + }, + ""type"": ""children"", + ""id"": ""2"" + }, + { + ""attributes"": {}, + ""relationships"": { + ""infected"": { + ""links"": { + ""self"": ""/infections/4/relationships/infected"", + ""related"": ""/infections/4/infected"" + } + } + }, + ""type"": ""infections"", + ""id"": ""4"" + }, + { + ""attributes"": {}, + ""relationships"": { + ""infected"": { + ""links"": { + ""self"": ""/infections/5/relationships/infected"", + ""related"": ""/infections/5/infected"" + } + } + }, + ""type"": ""infections"", + ""id"": ""5"" + }, + { + ""attributes"": {}, + ""relationships"": { + ""infections"": { + ""links"": { + ""self"": ""/children/3/relationships/infections"", + ""related"": ""/children/3/infections"" + } + }, + ""parent"": { + ""links"": { + ""self"": ""/children/3/relationships/parent"", + ""related"": ""/children/3/parent"" + } + } + }, + ""type"": ""children"", + ""id"": ""3"" + } + ] + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + + Assert.Equal(expected, result); + } + + private JsonApiSerializer GetSerializer( + ResourceGraphBuilder resourceGraphBuilder, + List included = null) + { + var resourceGraph = resourceGraphBuilder.Build(); + var requestManagerMock = new Mock(); + requestManagerMock.Setup(m => m.GetRequestResource()).Returns(resourceGraph.GetContextEntity("test-resource")); + requestManagerMock.Setup(m => m.IncludedRelationships).Returns(included); + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); + jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); + jsonApiContextMock.Setup(m => m.RequestEntity).Returns(resourceGraph.GetContextEntity("test-resource")); + jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); + + + jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); + var pmMock = new Mock(); + jsonApiContextMock.Setup(m => m.PageManager).Returns(pmMock.Object); + + + + var jsonApiOptions = new JsonApiOptions(); + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var services = new ServiceCollection(); + + var mvcBuilder = services.AddMvcCore(); + + services + .AddJsonApiInternals(jsonApiOptions); + + var provider = services.BuildServiceProvider(); + var scoped = new TestScopedServiceProvider(provider); + + var documentBuilder = GetDocumentBuilder(jsonApiContextMock, requestManagerMock.Object, scopedServiceProvider: scoped); + var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); + + return serializer; + } + + private class TestResource : Identifiable + { + [Attr("complex-member")] + public ComplexType ComplexMember { get; set; } + + [HasMany("children")] public List Children { get; set; } + } + + private class ComplexType + { + public string CompoundName { get; set; } + } + + private class ChildResource : Identifiable + { + [HasMany("infections")] public List Infections { get; set; } + + [HasOne("parent")] public TestResource Parent { get; set; } + } + + private class InfectionResource : Identifiable + { + [HasOne("infected")] public ChildResource Infected { get; set; } + } + + private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, IRequestManager requestManager, TestScopedServiceProvider scopedServiceProvider = null) + { + var pageManagerMock = new Mock(); + + return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, requestManager, scopedServiceProvider: scopedServiceProvider); + + } + } +} diff --git a/test/UnitTests/Serialization/SerializerTestsSetup.cs b/test/UnitTests/Serialization/SerializerTestsSetup.cs new file mode 100644 index 0000000000..b1d3a9a5e9 --- /dev/null +++ b/test/UnitTests/Serialization/SerializerTestsSetup.cs @@ -0,0 +1,6 @@ +namespace UnitTests.Serialization +{ + public class SerializerTestsSetup + { + } +} \ No newline at end of file diff --git a/test/UnitTests/Serializer/JsonApiSerializerTests.cs b/test/UnitTests/Serializer/JsonApiSerializerTests.cs new file mode 100644 index 0000000000..d301caf1fa --- /dev/null +++ b/test/UnitTests/Serializer/JsonApiSerializerTests.cs @@ -0,0 +1,284 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Request; +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; + +namespace UnitTests.Serialization +{ + public class JsonApiSerializerTests + { + [Fact] + public void Can_Serialize_Complex_Types() + { + // arrange + var resourceGraphBuilder = new ResourceGraphBuilder(); + resourceGraphBuilder.AddResource("test-resource"); + + var serializer = GetSerializer(resourceGraphBuilder); + + var resource = new TestResource + { + ComplexMember = new ComplexType + { + CompoundName = "testname" + } + }; + + // act + var result = serializer.Serialize(resource); + + // assert + Assert.NotNull(result); + + var expectedFormatted = + @"{ + ""data"": { + ""attributes"": { + ""complex-member"": { + ""compound-name"": ""testname"" + } + }, + ""relationships"": { + ""children"": { + ""links"": { + ""self"": ""/test-resource//relationships/children"", + ""related"": ""/test-resource//children"" + } + } + }, + ""type"": ""test-resource"", + ""id"": """" + } + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + + Assert.Equal(expected, result); + } + + [Fact] + public void Can_Serialize_Deeply_Nested_Relationships() + { + // arrange + var resourceGraphBuilder = new ResourceGraphBuilder(); + resourceGraphBuilder.AddResource("test-resource"); + resourceGraphBuilder.AddResource("children"); + resourceGraphBuilder.AddResource("infections"); + + var serializer = GetSerializer( + resourceGraphBuilder, + new List { "children.infections" } + ); + + var resource = new TestResource + { + Id = 1, + Children = new List { + new ChildResource { + Id = 2, + Infections = new List { + new InfectionResource { Id = 4 }, + new InfectionResource { Id = 5 }, + } + }, + new ChildResource { + Id = 3 + } + } + }; + + // act + var result = serializer.Serialize(resource); + + // assert + Assert.NotNull(result); + + var expectedFormatted = + @"{ + ""data"": { + ""attributes"": { + ""complex-member"": null + }, + ""relationships"": { + ""children"": { + ""links"": { + ""self"": ""/test-resource/1/relationships/children"", + ""related"": ""/test-resource/1/children"" + }, + ""data"": [{ + ""type"": ""children"", + ""id"": ""2"" + }, { + ""type"": ""children"", + ""id"": ""3"" + }] + } + }, + ""type"": ""test-resource"", + ""id"": ""1"" + }, + ""included"": [ + { + ""attributes"": {}, + ""relationships"": { + ""infections"": { + ""links"": { + ""self"": ""/children/2/relationships/infections"", + ""related"": ""/children/2/infections"" + }, + ""data"": [{ + ""type"": ""infections"", + ""id"": ""4"" + }, { + ""type"": ""infections"", + ""id"": ""5"" + }] + }, + ""parent"": { + ""links"": { + ""self"": ""/children/2/relationships/parent"", + ""related"": ""/children/2/parent"" + } + } + }, + ""type"": ""children"", + ""id"": ""2"" + }, + { + ""attributes"": {}, + ""relationships"": { + ""infected"": { + ""links"": { + ""self"": ""/infections/4/relationships/infected"", + ""related"": ""/infections/4/infected"" + } + } + }, + ""type"": ""infections"", + ""id"": ""4"" + }, + { + ""attributes"": {}, + ""relationships"": { + ""infected"": { + ""links"": { + ""self"": ""/infections/5/relationships/infected"", + ""related"": ""/infections/5/infected"" + } + } + }, + ""type"": ""infections"", + ""id"": ""5"" + }, + { + ""attributes"": {}, + ""relationships"": { + ""infections"": { + ""links"": { + ""self"": ""/children/3/relationships/infections"", + ""related"": ""/children/3/infections"" + } + }, + ""parent"": { + ""links"": { + ""self"": ""/children/3/relationships/parent"", + ""related"": ""/children/3/parent"" + } + } + }, + ""type"": ""children"", + ""id"": ""3"" + } + ] + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + + Assert.Equal(expected, result); + } + + private JsonApiSerializer GetSerializer( + ResourceGraphBuilder resourceGraphBuilder, + List included = null) + { + var resourceGraph = resourceGraphBuilder.Build(); + var requestManagerMock = new Mock(); + requestManagerMock.Setup(m => m.GetRequestResource()).Returns(resourceGraph.GetContextEntity("test-resource")); + requestManagerMock.Setup(m => m.IncludedRelationships).Returns(included); + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); + jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); + jsonApiContextMock.Setup(m => m.RequestEntity).Returns(resourceGraph.GetContextEntity("test-resource")); + jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); + + + jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); + var pmMock = new Mock(); + jsonApiContextMock.Setup(m => m.PageManager).Returns(pmMock.Object); + + + + var jsonApiOptions = new JsonApiOptions(); + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var services = new ServiceCollection(); + + var mvcBuilder = services.AddMvcCore(); + + services + .AddJsonApiInternals(jsonApiOptions); + + var provider = services.BuildServiceProvider(); + var scoped = new TestScopedServiceProvider(provider); + + var documentBuilder = GetDocumentBuilder(jsonApiContextMock, requestManagerMock.Object, scopedServiceProvider: scoped); + var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); + + return serializer; + } + + private class TestResource : Identifiable + { + [Attr("complex-member")] + public ComplexType ComplexMember { get; set; } + + [HasMany("children")] public List Children { get; set; } + } + + private class ComplexType + { + public string CompoundName { get; set; } + } + + private class ChildResource : Identifiable + { + [HasMany("infections")] public List Infections { get; set; } + + [HasOne("parent")] public TestResource Parent { get; set; } + } + + private class InfectionResource : Identifiable + { + [HasOne("infected")] public ChildResource Infected { get; set; } + } + + private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, IRequestManager requestManager, TestScopedServiceProvider scopedServiceProvider = null) + { + var pageManagerMock = new Mock(); + + return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, requestManager, scopedServiceProvider: scopedServiceProvider); + + } + } +} diff --git a/test/UnitTests/Serializer/SerializerBaseTests.cs b/test/UnitTests/Serializer/SerializerBaseTests.cs new file mode 100644 index 0000000000..be3602344e --- /dev/null +++ b/test/UnitTests/Serializer/SerializerBaseTests.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Text.RegularExpressions; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using UnitTests.Deserialization; +using Xunit; + +namespace UnitTests.Serialization +{ + public class SerializerBaseTests : SerializationTestModels + { + + + + } +} diff --git a/test/UnitTests/Serializer/SerializerTestsSetup.cs b/test/UnitTests/Serializer/SerializerTestsSetup.cs new file mode 100644 index 0000000000..b1d3a9a5e9 --- /dev/null +++ b/test/UnitTests/Serializer/SerializerTestsSetup.cs @@ -0,0 +1,6 @@ +namespace UnitTests.Serialization +{ + public class SerializerTestsSetup + { + } +} \ No newline at end of file diff --git a/test/UnitTests/Services/Operations/OperationsProcessorResolverTests.cs b/test/UnitTests/Services/Operations/OperationsProcessorResolverTests.cs deleted file mode 100644 index 6ebfc0bda5..0000000000 --- a/test/UnitTests/Services/Operations/OperationsProcessorResolverTests.cs +++ /dev/null @@ -1,102 +0,0 @@ -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Internal.Generics; -using JsonApiDotNetCore.Models.Operations; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCore.Services.Operations; -using Moq; -using Xunit; - -namespace UnitTests.Services -{ - public class OperationProcessorResolverTests - { - private readonly Mock _processorFactoryMock; - public readonly Mock _jsonApiContextMock; - - public OperationProcessorResolverTests() - { - _processorFactoryMock = new Mock(); - _jsonApiContextMock = new Mock(); - } - - [Fact] - public void LocateCreateService_Throws_400_For_Entity_Not_Registered() - { - // arrange - _jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(new ResourceGraphBuilder().Build()); - var service = GetService(); - var op = new Operation - { - Ref = new ResourceReference - { - Type = "non-existent-type" - } - }; - - // act, assert - var e = Assert.Throws(() => service.LocateCreateService(op)); - Assert.Equal(400, e.GetStatusCode()); - } - - [Fact] - public void LocateGetService_Throws_400_For_Entity_Not_Registered() - { - // arrange - _jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(new ResourceGraphBuilder().Build()); - var service = GetService(); - var op = new Operation - { - Ref = new ResourceReference - { - Type = "non-existent-type" - } - }; - - // act, assert - var e = Assert.Throws(() => service.LocateGetService(op)); - Assert.Equal(400, e.GetStatusCode()); - } - - [Fact] - public void LocateRemoveService_Throws_400_For_Entity_Not_Registered() - { - // arrange - _jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(new ResourceGraphBuilder().Build()); - var service = GetService(); - var op = new Operation - { - Ref = new ResourceReference - { - Type = "non-existent-type" - } - }; - - // act, assert - var e = Assert.Throws(() => service.LocateRemoveService(op)); - Assert.Equal(400, e.GetStatusCode()); - } - - [Fact] - public void LocateUpdateService_Throws_400_For_Entity_Not_Registered() - { - // arrange - _jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(new ResourceGraphBuilder().Build()); - var service = GetService(); - var op = new Operation - { - Ref = new ResourceReference - { - Type = "non-existent-type" - } - }; - - // act, assert - var e = Assert.Throws(() => service.LocateUpdateService(op)); - Assert.Equal(400, e.GetStatusCode()); - } - - private OperationProcessorResolver GetService() - => new OperationProcessorResolver(_processorFactoryMock.Object, _jsonApiContextMock.Object); - } -} diff --git a/test/UnitTests/Services/Operations/OperationsProcessorTests.cs b/test/UnitTests/Services/Operations/OperationsProcessorTests.cs deleted file mode 100644 index 0bdcfa92e9..0000000000 --- a/test/UnitTests/Services/Operations/OperationsProcessorTests.cs +++ /dev/null @@ -1,203 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using JsonApiDotNetCore.Data; -using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Models.Operations; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCore.Services.Operations; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; -using Moq; -using Newtonsoft.Json; -using Xunit; - -namespace UnitTests.Services -{ - public class OperationsProcessorTests - { - private readonly Mock _resolverMock; - public readonly Mock _dbContextMock; - public readonly Mock _dbContextResolverMock; - public readonly Mock _jsonApiContextMock; - - public OperationsProcessorTests() - { - _resolverMock = new Mock(); - _dbContextMock = new Mock(); - _dbContextResolverMock = new Mock(); - _jsonApiContextMock = new Mock(); - } - - [Fact] - public async Task ProcessAsync_Performs_LocalId_ReplacementAsync_In_Relationships() - { - // arrange - var request = @"[ - { - ""op"": ""add"", - ""data"": { - ""type"": ""authors"", - ""lid"": ""a"", - ""attributes"": { - ""name"": ""dgeb"" - } - } - }, { - ""op"": ""add"", - ""data"": { - ""type"": ""articles"", - ""attributes"": { - ""title"": ""JSON API paints my bikeshed!"" - }, - ""relationships"": { - ""author"": { - ""data"": { - ""type"": ""authors"", - ""lid"": ""a"" - } - } - } - } - } - ]"; - - var op1Result = @"{ - ""links"": { - ""self"": ""http://example.com/authors/9"" - }, - ""data"": { - ""type"": ""authors"", - ""id"": ""9"", - ""lid"": ""a"", - ""attributes"": { - ""name"": ""dgeb"" - } - } - }"; - - var operations = JsonConvert.DeserializeObject>(request); - var addOperationResult = JsonConvert.DeserializeObject(op1Result); - - var databaseMock = new Mock(_dbContextMock.Object); - var transactionMock = new Mock(); - databaseMock.Setup(m => m.BeginTransactionAsync(It.IsAny())) - .ReturnsAsync(transactionMock.Object); - _dbContextMock.Setup(m => m.Database).Returns(databaseMock.Object); - - var opProcessorMock = new Mock(); - opProcessorMock.Setup(m => m.ProcessAsync(It.Is(op => op.DataObject.Type.ToString() == "authors"))) - .ReturnsAsync(addOperationResult); - - _resolverMock.Setup(m => m.LocateCreateService(It.IsAny())) - .Returns(opProcessorMock.Object); - - _dbContextResolverMock.Setup(m => m.GetContext()).Returns(_dbContextMock.Object); - var requestManagerMock = new Mock(); - var resourceGraphMock = new Mock(); - var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object, _jsonApiContextMock.Object, requestManagerMock.Object, resourceGraphMock.Object); - - // act - var results = await operationsProcessor.ProcessAsync(operations); - - // assert - opProcessorMock.Verify( - m => m.ProcessAsync( - It.Is(o => - o.DataObject.Type.ToString() == "articles" - && o.DataObject.Relationships["author"].SingleData.Id == "9" - ) - ) - ); - } - - [Fact] - public async Task ProcessAsync_Performs_LocalId_ReplacementAsync_In_References() - { - // arrange - var request = @"[ - { - ""op"": ""add"", - ""data"": { - ""type"": ""authors"", - ""lid"": ""a"", - ""attributes"": { - ""name"": ""jaredcnance"" - } - } - }, { - ""op"": ""update"", - ""ref"": { - ""type"": ""authors"", - ""lid"": ""a"" - }, - ""data"": { - ""type"": ""authors"", - ""lid"": ""a"", - ""attributes"": { - ""name"": ""jnance"" - } - } - } - ]"; - - var op1Result = @"{ - ""data"": { - ""type"": ""authors"", - ""id"": ""9"", - ""lid"": ""a"", - ""attributes"": { - ""name"": ""jaredcnance"" - } - } - }"; - - var operations = JsonConvert.DeserializeObject>(request); - var addOperationResult = JsonConvert.DeserializeObject(op1Result); - - var databaseMock = new Mock(_dbContextMock.Object); - var transactionMock = new Mock(); - - databaseMock.Setup(m => m.BeginTransactionAsync(It.IsAny())) - .ReturnsAsync(transactionMock.Object); - - _dbContextMock.Setup(m => m.Database).Returns(databaseMock.Object); - - // setup add - var addOpProcessorMock = new Mock(); - addOpProcessorMock.Setup(m => m.ProcessAsync(It.Is(op => op.DataObject.Type.ToString() == "authors"))) - .ReturnsAsync(addOperationResult); - _resolverMock.Setup(m => m.LocateCreateService(It.IsAny())) - .Returns(addOpProcessorMock.Object); - - // setup update - var updateOpProcessorMock = new Mock(); - updateOpProcessorMock.Setup(m => m.ProcessAsync(It.Is(op => op.DataObject.Type.ToString() == "authors"))) - .ReturnsAsync((Operation)null); - _resolverMock.Setup(m => m.LocateUpdateService(It.IsAny())) - .Returns(updateOpProcessorMock.Object); - - _dbContextResolverMock.Setup(m => m.GetContext()).Returns(_dbContextMock.Object); - var requestManagerMock = new Mock(); - var resourceGraphMock = new Mock(); - var operationsProcessor = new OperationsProcessor(_resolverMock.Object, _dbContextResolverMock.Object, _jsonApiContextMock.Object, requestManagerMock.Object, resourceGraphMock.Object); - - // act - var results = await operationsProcessor.ProcessAsync(operations); - - // assert - updateOpProcessorMock.Verify( - m => m.ProcessAsync( - It.Is(o => - o.DataObject.Type.ToString() == "authors" - // && o.DataObject.Id == "9" // currently, we will not replace the data.id member - && o.DataObject.Id == null - && o.Ref.Id == "9" - ) - ) - ); - } - } -} diff --git a/test/UnitTests/Services/Operations/Processors/CreateOpProcessorTests.cs b/test/UnitTests/Services/Operations/Processors/CreateOpProcessorTests.cs deleted file mode 100644 index 73f9c272b9..0000000000 --- a/test/UnitTests/Services/Operations/Processors/CreateOpProcessorTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Models.Operations; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCore.Services.Operations.Processors; -using Moq; -using Xunit; - -namespace UnitTests.Services -{ - public class CreateOpProcessorTests - { - private readonly Mock> _createServiceMock; - private readonly Mock _deserializerMock; - private readonly Mock _documentBuilderMock; - - public CreateOpProcessorTests() - { - _createServiceMock = new Mock>(); - _deserializerMock = new Mock(); - _documentBuilderMock = new Mock(); - } - - [Fact] - public async Task ProcessAsync_Deserializes_And_Creates() - { - // arrange - var testResource = new TestResource { - Name = "some-name" - }; - - var data = new ResourceObject { - Type = "test-resources", - Attributes = new Dictionary { - { "name", testResource.Name } - } - }; - - var operation = new Operation { - Data = data, - }; - - var resourceGraph = new ResourceGraphBuilder() - .AddResource("test-resources") - .Build(); - - _deserializerMock.Setup(m => m.DocumentToObject(It.IsAny(), It.IsAny>())) - .Returns(testResource); - - var opProcessor = new CreateOpProcessor( - _createServiceMock.Object, - _deserializerMock.Object, - _documentBuilderMock.Object, - resourceGraph - ); - - _documentBuilderMock.Setup(m => m.GetData(It.IsAny(), It.IsAny())) - .Returns(data); - - // act - var result = await opProcessor.ProcessAsync(operation); - - // assert - Assert.Equal(OperationCode.add, result.Op); - Assert.NotNull(result.Data); - Assert.Equal(testResource.Name, result.DataObject.Attributes["name"]); - _createServiceMock.Verify(m => m.CreateAsync(It.IsAny())); - } - - public class TestResource : Identifiable - { - [Attr("name")] - public string Name { get; set; } - } - } -} From 5745c447972c2e6fbc07ed3a107fffb680e7b5dd Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 26 Sep 2019 17:46:13 +0200 Subject: [PATCH 21/26] chore: remove / cleanup old serializers --- .../Builders/DocumentBuilder.cs | 742 +++++++++--------- .../DocumentBuilderOptionsProvider.cs | 17 +- .../Builders/IDocumentBuilder.cs | 4 +- .../Builders/IResourceGraphBuilder.cs | 5 - .../Builders/ResourceGraphBuilder.cs | 41 +- .../Builders/SerializerOptions.cs | 22 +- 6 files changed, 426 insertions(+), 405 deletions(-) diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index b2e9475db8..55f9357476 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -1,370 +1,372 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using JsonApiDotNetCore.Extensions; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Services; - -namespace JsonApiDotNetCore.Builders -{ - /// - public class DocumentBuilder : IDocumentBuilder - { - private readonly IRequestManager _requestManager; - private readonly IPageManager _pageManager; - private readonly IJsonApiContext _jsonApiContext; - private readonly IResourceGraph _resourceGraph; - private readonly IRequestMeta _requestMeta; - private readonly DocumentBuilderOptions _documentBuilderOptions; - private readonly IScopedServiceProvider _scopedServiceProvider; - - public DocumentBuilder( - IJsonApiContext jsonApiContext, - IPageManager pageManager, - IRequestManager requestManager, - IRequestMeta requestMeta = null, - IDocumentBuilderOptionsProvider documentBuilderOptionsProvider = null, - IScopedServiceProvider scopedServiceProvider = null) - { - _pageManager = pageManager; - _jsonApiContext = jsonApiContext; - _requestManager = requestManager ?? jsonApiContext.RequestManager; - _resourceGraph = jsonApiContext.ResourceGraph; - _requestMeta = requestMeta; - _documentBuilderOptions = documentBuilderOptionsProvider?.GetDocumentBuilderOptions() ?? new DocumentBuilderOptions(); - _scopedServiceProvider = scopedServiceProvider; - } - - /// - public Document Build(IIdentifiable entity) - { - var contextEntity = _resourceGraph.GetContextEntity(entity.GetType()); - - var resourceDefinition = _scopedServiceProvider?.GetService(contextEntity.ResourceType) as IResourceDefinition; - var document = new Document - { - Data = GetData(contextEntity, entity, resourceDefinition), - Meta = GetMeta(entity) - }; - - if (ShouldIncludePageLinks(contextEntity)) - { - document.Links = _pageManager.GetPageLinks(); - } - - document.Included = AppendIncludedObject(document.Included, contextEntity, entity); - - return document; - } - - /// - public Documents Build(IEnumerable entities) - { - var entityType = entities.GetElementType(); - var contextEntity = _resourceGraph.GetContextEntity(entityType); - var resourceDefinition = _scopedServiceProvider?.GetService(contextEntity.ResourceType) as IResourceDefinition; - - var enumeratedEntities = entities as IList ?? entities.ToList(); - var documents = new Documents - { - Data = new List(), - Meta = GetMeta(enumeratedEntities.FirstOrDefault()) - }; - - if (ShouldIncludePageLinks(contextEntity)) - { - documents.Links = _pageManager.GetPageLinks(); - } - - foreach (var entity in enumeratedEntities) - { - documents.Data.Add(GetData(contextEntity, entity, resourceDefinition)); - documents.Included = AppendIncludedObject(documents.Included, contextEntity, entity); - } - - return documents; - } - - private Dictionary GetMeta(IIdentifiable entity) - { - var builder = _jsonApiContext.MetaBuilder; - if (_jsonApiContext.Options.IncludeTotalRecordCount && _pageManager.TotalRecords != null) - builder.Add("total-records", _pageManager.TotalRecords); - - if (_requestMeta != null) - builder.Add(_requestMeta.GetMeta()); - - if (entity != null && entity is IHasMeta metaEntity) - builder.Add(metaEntity.GetMeta(_jsonApiContext)); - - var meta = builder.Build(); - if (meta.Count > 0) - return meta; - - return null; - } - - private bool ShouldIncludePageLinks(ContextEntity entity) => entity.Links.HasFlag(Link.Paging); - - private List AppendIncludedObject(List includedObject, ContextEntity contextEntity, IIdentifiable entity) - { - var includedEntities = GetIncludedEntities(includedObject, contextEntity, entity); - if (includedEntities?.Count > 0) - { - includedObject = includedEntities; - } - - return includedObject; - } - - [Obsolete("You should specify an IResourceDefinition implementation using the GetData/3 overload.")] - public ResourceObject GetData(ContextEntity contextEntity, IIdentifiable entity) - => GetData(contextEntity, entity, resourceDefinition: null); - - /// - public ResourceObject GetData(ContextEntity contextEntity, IIdentifiable entity, IResourceDefinition resourceDefinition = null) - { - var data = new ResourceObject - { - Type = contextEntity.EntityName, - Id = entity.StringId - }; - - if (_jsonApiContext.IsRelationshipPath) - return data; - - data.Attributes = new Dictionary(); - - var resourceAttributes = resourceDefinition?.GetOutputAttrs(entity) ?? contextEntity.Attributes; - resourceAttributes.ForEach(attr => - { - var attributeValue = attr.GetValue(entity); - if (ShouldIncludeAttribute(attr, attributeValue)) - { - data.Attributes.Add(attr.PublicAttributeName, attributeValue); - } - }); - - if (contextEntity.Relationships.Count > 0) - AddRelationships(data, contextEntity, entity); - - return data; - } - private bool ShouldIncludeAttribute(AttrAttribute attr, object attributeValue, RelationshipAttribute relationship = null) - { - return OmitNullValuedAttribute(attr, attributeValue) == false - && attr.InternalAttributeName != nameof(Identifiable.Id) - && ((_requestManager.QuerySet == null - || _requestManager.QuerySet.Fields.Count == 0) - || _requestManager.QuerySet.Fields.Contains(relationship != null ? - $"{relationship.InternalRelationshipName}.{attr.InternalAttributeName}" : - attr.InternalAttributeName)); - } - - private bool OmitNullValuedAttribute(AttrAttribute attr, object attributeValue) - { - return attributeValue == null && _documentBuilderOptions.OmitNullValuedAttributes; - } - - private void AddRelationships(ResourceObject data, ContextEntity contextEntity, IIdentifiable entity) - { - data.Relationships = new Dictionary(); - contextEntity.Relationships.ForEach(r => - data.Relationships.Add( - r.PublicRelationshipName, - GetRelationshipData(r, contextEntity, entity) - ) - ); - } - - private RelationshipData GetRelationshipData(RelationshipAttribute attr, ContextEntity contextEntity, IIdentifiable entity) - { - var linkBuilder = new LinkBuilder(_jsonApiContext.Options,_requestManager); - - var relationshipData = new RelationshipData(); - - if (_jsonApiContext.Options.DefaultRelationshipLinks.HasFlag(Link.None) == false && attr.DocumentLinks.HasFlag(Link.None) == false) - { - relationshipData.Links = new Links(); - if (attr.DocumentLinks.HasFlag(Link.Self)) - { - relationshipData.Links.Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); - } - - if (attr.DocumentLinks.HasFlag(Link.Related)) - { - relationshipData.Links.Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); - } - } - - // this only includes the navigation property, we need to actually check the navigation property Id - var navigationEntity = _jsonApiContext.ResourceGraph.GetRelationshipValue(entity, attr); - if (navigationEntity == null) - relationshipData.SingleData = attr.IsHasOne - ? GetIndependentRelationshipIdentifier((HasOneAttribute)attr, entity) - : null; - else if (navigationEntity is IEnumerable) - relationshipData.ManyData = GetRelationships((IEnumerable)navigationEntity); - else - relationshipData.SingleData = GetRelationship(navigationEntity); - - return relationshipData; - } - - private List GetIncludedEntities(List included, ContextEntity rootContextEntity, IIdentifiable rootResource) - { - if (_requestManager.IncludedRelationships != null) - { - foreach (var relationshipName in _requestManager.IncludedRelationships) - { - var relationshipChain = relationshipName.Split('.'); - - var contextEntity = rootContextEntity; - var entity = rootResource; - included = IncludeRelationshipChain(included, rootContextEntity, rootResource, relationshipChain, 0); - } - } - - return included; - } - - private List IncludeRelationshipChain( - List included, ContextEntity parentEntity, IIdentifiable parentResource, string[] relationshipChain, int relationshipChainIndex) - { - var requestedRelationship = relationshipChain[relationshipChainIndex]; - var relationship = parentEntity.Relationships.FirstOrDefault(r => r.PublicRelationshipName == requestedRelationship); - if(relationship == null) - throw new JsonApiException(400, $"{parentEntity.EntityName} does not contain relationship {requestedRelationship}"); - - var navigationEntity = _jsonApiContext.ResourceGraph.GetRelationshipValue(parentResource, relationship); - if(navigationEntity == null) - return included; - if (navigationEntity is IEnumerable hasManyNavigationEntity) - { - foreach (IIdentifiable includedEntity in hasManyNavigationEntity) - { - included = AddIncludedEntity(included, includedEntity, relationship); - included = IncludeSingleResourceRelationships(included, includedEntity, relationship, relationshipChain, relationshipChainIndex); - } - } - else - { - included = AddIncludedEntity(included, (IIdentifiable)navigationEntity, relationship); - included = IncludeSingleResourceRelationships(included, (IIdentifiable)navigationEntity, relationship, relationshipChain, relationshipChainIndex); - } - - return included; - } - - private List IncludeSingleResourceRelationships( - List included, IIdentifiable navigationEntity, RelationshipAttribute relationship, string[] relationshipChain, int relationshipChainIndex) - { - if (relationshipChainIndex < relationshipChain.Length) - { - var nextContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.DependentType); - var resource = (IIdentifiable)navigationEntity; - // recursive call - if (relationshipChainIndex < relationshipChain.Length - 1) - included = IncludeRelationshipChain(included, nextContextEntity, resource, relationshipChain, relationshipChainIndex + 1); - } - - return included; - } - - - private List AddIncludedEntity(List entities, IIdentifiable entity, RelationshipAttribute relationship) - { - var includedEntity = GetIncludedEntity(entity, relationship); - - if (entities == null) - entities = new List(); - - if (includedEntity != null && entities.Any(doc => - string.Equals(doc.Id, includedEntity.Id) && string.Equals(doc.Type, includedEntity.Type)) == false) - { - entities.Add(includedEntity); - } - - return entities; - } - - private ResourceObject GetIncludedEntity(IIdentifiable entity, RelationshipAttribute relationship) - { - if (entity == null) return null; - - var contextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(entity.GetType()); - var resourceDefinition = _scopedServiceProvider.GetService(contextEntity.ResourceType) as IResourceDefinition; - - var data = GetData(contextEntity, entity, resourceDefinition); - - data.Attributes = new Dictionary(); - - contextEntity.Attributes.ForEach(attr => - { - var attributeValue = attr.GetValue(entity); - if (ShouldIncludeAttribute(attr, attributeValue, relationship)) - { - data.Attributes.Add(attr.PublicAttributeName, attributeValue); - } - }); - - return data; - } - - private List GetRelationships(IEnumerable entities) - { - string typeName = null; - var relationships = new List(); - foreach (var entity in entities) - { - // this method makes the assumption that entities is a homogenous collection - // so, we just lookup the type of the first entity on the graph - // this is better than trying to get it from the generic parameter since it could - // be less specific than what is registered on the graph (e.g. IEnumerable) - typeName = typeName ?? _jsonApiContext.ResourceGraph.GetContextEntity(entity.GetType()).EntityName; - relationships.Add(new ResourceIdentifierObject - { - Type = typeName, - Id = ((IIdentifiable)entity).StringId - }); - } - return relationships; - } - - private ResourceIdentifierObject GetRelationship(object entity) - { - var objType = entity.GetType(); - var contextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(objType); - - if (entity is IIdentifiable identifiableEntity) - return new ResourceIdentifierObject - { - Type = contextEntity.EntityName, - Id = identifiableEntity.StringId - }; - - return null; - } - - private ResourceIdentifierObject GetIndependentRelationshipIdentifier(HasOneAttribute hasOne, IIdentifiable entity) - { - var independentRelationshipIdentifier = hasOne.GetIdentifiablePropertyValue(entity); - if (independentRelationshipIdentifier == null) - return null; - - var relatedContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(hasOne.DependentType); - if (relatedContextEntity == null) // TODO: this should probably be a debug log at minimum - return null; - - return new ResourceIdentifierObject - { - Type = relatedContextEntity.EntityName, - Id = independentRelationshipIdentifier.ToString() - }; - } - } -} +//using System; +//using System.Collections; +//using System.Collections.Generic; +//using System.Linq; +//using JsonApiDotNetCore.Extensions; +//using JsonApiDotNetCore.Internal; +//using JsonApiDotNetCore.Internal.Contracts; +//using JsonApiDotNetCore.Managers.Contracts; +//using JsonApiDotNetCore.Models; +//using JsonApiDotNetCore.Services; + +//namespace JsonApiDotNetCore.Builders +//{ +// /// +// public class DocumentBuilder : IDocumentBuilder +// { +// private readonly IRequestManager _requestManager; +// private readonly IPageManager _pageManager; +// private readonly IJsonApiContext _jsonApiContext; +// private readonly IResourceGraph _resourceGraph; +// private readonly IRequestMeta _requestMeta; +// private readonly DocumentBuilderOptions _documentBuilderOptions; +// private readonly IScopedServiceProvider _scopedServiceProvider; + +// public DocumentBuilder( +// IJsonApiContext jsonApiContext, +// IPageManager pageManager, +// IRequestManager requestManager, +// IRequestMeta requestMeta = null, +// IDocumentBuilderOptionsProvider documentBuilderOptionsProvider = null, +// IScopedServiceProvider scopedServiceProvider = null) +// { +// _pageManager = pageManager; +// _jsonApiContext = jsonApiContext; +// _requestManager = requestManager ?? jsonApiContext.RequestManager; +// _resourceGraph = jsonApiContext.ResourceGraph; +// _requestMeta = requestMeta; +// _documentBuilderOptions = documentBuilderOptionsProvider?.GetDocumentBuilderOptions() ?? new DocumentBuilderOptions(); +// _scopedServiceProvider = scopedServiceProvider; +// } + +// /// +// public Document Build(IIdentifiable entity) +// { +// var contextEntity = _resourceGraph.GetContextEntity(entity.GetType()); + +// var resourceDefinition = _scopedServiceProvider?.GetService(contextEntity.ResourceType) as IResourceDefinition; +// var document = new Document +// { +// Data = GetData(contextEntity, entity, resourceDefinition), +// Meta = GetMeta(entity) +// }; + +// if (ShouldIncludePageLinks(contextEntity)) +// { +// document.Links = _pageManager.GetPageLinks(); +// } + +// document.Included = AppendIncludedObject(document.Included, contextEntity, entity); + +// return document; +// } + +// /// +// public Documents Build(IEnumerable entities) +// { +// var entityType = entities.GetElementType(); +// var contextEntity = _resourceGraph.GetContextEntity(entityType); +// var resourceDefinition = _scopedServiceProvider?.GetService(contextEntity.ResourceType) as IResourceDefinition; + +// var enumeratedEntities = entities as IList ?? entities.ToList(); +// var documents = new Documents +// { +// Data = new List(), +// Meta = GetMeta(enumeratedEntities.FirstOrDefault()) +// }; + +// if (ShouldIncludePageLinks(contextEntity)) +// { +// documents.Links = _pageManager.GetPageLinks(); +// } + +// foreach (var entity in enumeratedEntities) +// { +// documents.Data.Add(GetData(contextEntity, entity, resourceDefinition)); +// documents.Included = AppendIncludedObject(documents.Included, contextEntity, entity); +// } + +// return documents; +// } + + +// private bool ShouldIncludePageLinks(ContextEntity entity) => entity.Links.HasFlag(Link.Paging); + +// private Dictionary GetMeta(IIdentifiable entity) +// { +// var builder = _jsonApiContext.MetaBuilder; +// if (_jsonApiContext.Options.IncludeTotalRecordCount && _pageManager.TotalRecords != null) +// builder.Add("total-records", _pageManager.TotalRecords); + +// if (_requestMeta != null) +// builder.Add(_requestMeta.GetMeta()); + +// if (entity != null && entity is IHasMeta metaEntity) +// builder.Add(metaEntity.GetMeta()); + +// var meta = builder.Build(); +// if (meta.Count > 0) +// return meta; + +// return null; +// } + + +// private List AppendIncludedObject(List includedObject, ContextEntity contextEntity, IIdentifiable entity) +// { +// var includedEntities = GetIncludedEntities(includedObject, contextEntity, entity); +// if (includedEntities?.Count > 0) +// { +// includedObject = includedEntities; +// } + +// return includedObject; +// } + +// [Obsolete("You should specify an IResourceDefinition implementation using the GetData/3 overload.")] +// public ResourceObject GetData(ContextEntity contextEntity, IIdentifiable entity) +// => GetData(contextEntity, entity, resourceDefinition: null); + +// /// +// public ResourceObject GetData(ContextEntity contextEntity, IIdentifiable entity, IResourceDefinition resourceDefinition = null) +// { +// var data = new ResourceObject +// { +// Type = contextEntity.EntityName, +// Id = entity.StringId +// }; + +// if (_jsonApiContext.IsRelationshipPath) +// return data; + +// data.Attributes = new Dictionary(); + +// var resourceAttributes = resourceDefinition?.GetOutputAttrs(entity) ?? contextEntity.Attributes; +// resourceAttributes.ForEach(attr => +// { +// var attributeValue = attr.GetValue(entity); +// if (ShouldIncludeAttribute(attr, attributeValue)) +// { +// data.Attributes.Add(attr.PublicAttributeName, attributeValue); +// } +// }); + +// if (contextEntity.Relationships.Count > 0) +// AddRelationships(data, contextEntity, entity); + +// return data; +// } +// private bool ShouldIncludeAttribute(AttrAttribute attr, object attributeValue, RelationshipAttribute relationship = null) +// { +// return OmitNullValuedAttribute(attr, attributeValue) == false +// && attr.InternalAttributeName != nameof(Identifiable.Id) +// && ((_requestManager.QuerySet == null +// || _requestManager.QuerySet.Fields.Count == 0) +// || _requestManager.QuerySet.Fields.Contains(relationship != null ? +// $"{relationship.InternalRelationshipName}.{attr.InternalAttributeName}" : +// attr.InternalAttributeName)); +// } + +// private bool OmitNullValuedAttribute(AttrAttribute attr, object attributeValue) +// { +// return attributeValue == null && _documentBuilderOptions.OmitNullValuedAttributes; +// } + +// private void AddRelationships(ResourceObject data, ContextEntity contextEntity, IIdentifiable entity) +// { +// data.Relationships = new Dictionary(); +// contextEntity.Relationships.ForEach(r => +// data.Relationships.Add( +// r.PublicRelationshipName, +// GetRelationshipData(r, contextEntity, entity) +// ) +// ); +// } + +// private RelationshipData GetRelationshipData(RelationshipAttribute attr, ContextEntity contextEntity, IIdentifiable entity) +// { +// var linkBuilder = new LinkBuilder(_jsonApiContext.Options, _requestManager); + +// var relationshipData = new RelationshipData(); + +// if (_jsonApiContext.Options.DefaultRelationshipLinks.HasFlag(Link.None) == false && attr.RelationshipLinks.HasFlag(Link.None) == false) +// { +// relationshipData.Links = new RelationshipLinks(); +// if (attr.RelationshipLinks.HasFlag(Link.Self)) +// { +// relationshipData.Links.Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); +// } + +// if (attr.RelationshipLinks.HasFlag(Link.Related)) +// { +// relationshipData.Links.Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.StringId, attr.PublicRelationshipName); +// } +// } + +// // this only includes the navigation property, we need to actually check the navigation property Id +// var navigationEntity = _jsonApiContext.ResourceGraph.GetRelationshipValue(entity, attr); +// if (navigationEntity == null) +// relationshipData.SingleData = attr.IsHasOne +// ? GetIndependentRelationshipIdentifier((HasOneAttribute)attr, entity) +// : null; +// else if (navigationEntity is IEnumerable) +// relationshipData.ManyData = GetRelationships((IEnumerable)navigationEntity); +// else +// relationshipData.SingleData = GetRelationship(navigationEntity); + +// return relationshipData; +// } + +// private List GetIncludedEntities(List included, ContextEntity rootContextEntity, IIdentifiable rootResource) +// { +// if (_requestManager.IncludedRelationships != null) +// { +// foreach (var relationshipName in _requestManager.IncludedRelationships) +// { +// var relationshipChain = relationshipName.Split('.'); + +// var contextEntity = rootContextEntity; +// var entity = rootResource; +// included = IncludeRelationshipChain(included, rootContextEntity, rootResource, relationshipChain, 0); +// } +// } + +// return included; +// } + +// private List IncludeRelationshipChain( +// List included, ContextEntity parentEntity, IIdentifiable parentResource, string[] relationshipChain, int relationshipChainIndex) +// { +// var requestedRelationship = relationshipChain[relationshipChainIndex]; +// var relationship = parentEntity.Relationships.FirstOrDefault(r => r.PublicRelationshipName == requestedRelationship); +// if (relationship == null) +// throw new JsonApiException(400, $"{parentEntity.EntityName} does not contain relationship {requestedRelationship}"); + +// var navigationEntity = _jsonApiContext.ResourceGraph.GetRelationshipValue(parentResource, relationship); +// if (navigationEntity == null) +// return included; +// if (navigationEntity is IEnumerable hasManyNavigationEntity) +// { +// foreach (IIdentifiable includedEntity in hasManyNavigationEntity) +// { +// included = AddIncludedEntity(included, includedEntity, relationship); +// included = IncludeSingleResourceRelationships(included, includedEntity, relationship, relationshipChain, relationshipChainIndex); +// } +// } +// else +// { +// included = AddIncludedEntity(included, (IIdentifiable)navigationEntity, relationship); +// included = IncludeSingleResourceRelationships(included, (IIdentifiable)navigationEntity, relationship, relationshipChain, relationshipChainIndex); +// } + +// return included; +// } + +// private List IncludeSingleResourceRelationships( +// List included, IIdentifiable navigationEntity, RelationshipAttribute relationship, string[] relationshipChain, int relationshipChainIndex) +// { +// if (relationshipChainIndex < relationshipChain.Length) +// { +// var nextContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.DependentType); +// var resource = (IIdentifiable)navigationEntity; +// // recursive call +// if (relationshipChainIndex < relationshipChain.Length - 1) +// included = IncludeRelationshipChain(included, nextContextEntity, resource, relationshipChain, relationshipChainIndex + 1); +// } + +// return included; +// } + + +// private List AddIncludedEntity(List entities, IIdentifiable entity, RelationshipAttribute relationship) +// { +// var includedEntity = GetIncludedEntity(entity, relationship); + +// if (entities == null) +// entities = new List(); + +// if (includedEntity != null && entities.Any(doc => +// string.Equals(doc.Id, includedEntity.Id) && string.Equals(doc.Type, includedEntity.Type)) == false) +// { +// entities.Add(includedEntity); +// } + +// return entities; +// } + +// private ResourceObject GetIncludedEntity(IIdentifiable entity, RelationshipAttribute relationship) +// { +// if (entity == null) return null; + +// var contextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(entity.GetType()); +// var resourceDefinition = _scopedServiceProvider.GetService(contextEntity.ResourceType) as IResourceDefinition; + +// var data = GetData(contextEntity, entity, resourceDefinition); + +// data.Attributes = new Dictionary(); + +// contextEntity.Attributes.ForEach(attr => +// { +// var attributeValue = attr.GetValue(entity); +// if (ShouldIncludeAttribute(attr, attributeValue, relationship)) +// { +// data.Attributes.Add(attr.PublicAttributeName, attributeValue); +// } +// }); + +// return data; +// } + +// private List GetRelationships(IEnumerable entities) +// { +// string typeName = null; +// var relationships = new List(); +// foreach (var entity in entities) +// { +// // this method makes the assumption that entities is a homogenous collection +// // so, we just lookup the type of the first entity on the graph +// // this is better than trying to get it from the generic parameter since it could +// // be less specific than what is registered on the graph (e.g. IEnumerable) +// typeName = typeName ?? _jsonApiContext.ResourceGraph.GetContextEntity(entity.GetType()).EntityName; +// relationships.Add(new ResourceIdentifierObject +// { +// Type = typeName, +// Id = ((IIdentifiable)entity).StringId +// }); +// } +// return relationships; +// } + +// private ResourceIdentifierObject GetRelationship(object entity) +// { +// var objType = entity.GetType(); +// var contextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(objType); + +// if (entity is IIdentifiable identifiableEntity) +// return new ResourceIdentifierObject +// { +// Type = contextEntity.EntityName, +// Id = identifiableEntity.StringId +// }; + +// return null; +// } + +// private ResourceIdentifierObject GetIndependentRelationshipIdentifier(HasOneAttribute hasOne, IIdentifiable entity) +// { +// var independentRelationshipIdentifier = hasOne.GetIdentifiablePropertyValue(entity); +// if (independentRelationshipIdentifier == null) +// return null; + +// var relatedContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(hasOne.DependentType); +// if (relatedContextEntity == null) // TODO: this should probably be a debug log at minimum +// return null; + +// return new ResourceIdentifierObject +// { +// Type = relatedContextEntity.EntityName, +// Id = independentRelationshipIdentifier.ToString() +// }; +// } +// } +//} diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilderOptionsProvider.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilderOptionsProvider.cs index 37c5d51273..22d2c339d7 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilderOptionsProvider.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilderOptionsProvider.cs @@ -1,3 +1,4 @@ +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; @@ -8,23 +9,29 @@ public class DocumentBuilderOptionsProvider : IDocumentBuilderOptionsProvider private readonly IJsonApiContext _jsonApiContext; private readonly IHttpContextAccessor _httpContextAccessor; - public DocumentBuilderOptionsProvider(IJsonApiContext jsonApiContext, IHttpContextAccessor httpContextAccessor) + public DocumentBuilderOptionsProvider(IJsonApiOptions options, IHttpContextAccessor httpContextAccessor) { - _jsonApiContext = jsonApiContext; _httpContextAccessor = httpContextAccessor; } - public DocumentBuilderOptions GetDocumentBuilderOptions() + public SerializerBehaviour GetDocumentBuilderOptions() { var nullAttributeResponseBehaviorConfig = this._jsonApiContext.Options.NullAttributeResponseBehavior; if (nullAttributeResponseBehaviorConfig.AllowClientOverride && _httpContextAccessor.HttpContext.Request.Query.TryGetValue("omitNullValuedAttributes", out var omitNullValuedAttributesQs)) { if (bool.TryParse(omitNullValuedAttributesQs, out var omitNullValuedAttributes)) { - return new DocumentBuilderOptions(omitNullValuedAttributes); + //return new SerializerBehaviour(omitNullValuedAttributes); + return null; } } - return new DocumentBuilderOptions(this._jsonApiContext.Options.NullAttributeResponseBehavior.OmitNullValuedAttributes); + //return new SerializerBehaviour(this._jsonApiContext.Options.NullAttributeResponseBehavior.OmitNullValuedAttributes); + + return null; } } + + public interface IDocumentBuilderOptionsProvider + { + } } diff --git a/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs index db20954730..1850d36604 100644 --- a/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/IDocumentBuilder.cs @@ -17,7 +17,7 @@ public interface IDocumentBuilder /// Builds a json:api document from the provided resource instances. /// /// The collection of resources to convert. - Documents Build(IEnumerable entities); + //Documents Build(IEnumerable entities); [Obsolete("You should specify an IResourceDefinition implementation using the GetData/3 overload.")] ResourceObject GetData(ContextEntity contextEntity, IIdentifiable entity); @@ -32,6 +32,6 @@ public interface IDocumentBuilder /// that should not be exposed to the client. For example, you might want to limit /// the exposed attributes based on the authenticated user's role. /// - ResourceObject GetData(ContextEntity contextEntity, IIdentifiable entity, IResourceDefinition resourceDefinition = null); + ResourceObject GetData(ContextEntity contextEntity, IIdentifiable entity, object resourceDefinition = null); } } diff --git a/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs index 7bcf6aa7ad..434875564a 100644 --- a/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs @@ -71,11 +71,6 @@ public interface IResourceGraphBuilder /// Formatter used to define exposed resource names by convention. IResourceGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNameFormatter); - /// - /// Which links to include. Defaults to . - /// - Link DocumentLinks { get; set; } - } } diff --git a/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs index 2c9d2677ea..5422cea8a1 100644 --- a/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs @@ -9,6 +9,7 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Models.Links; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -21,16 +22,17 @@ public class ResourceGraphBuilder : IResourceGraphBuilder private Dictionary> _controllerMapper = new Dictionary>() { }; private List _undefinedMapper = new List() { }; private bool _usesDbContext; - private IResourceNameFormatter _resourceNameFormatter = JsonApiOptions.ResourceNameFormatter; + private IResourceNameFormatter _resourceNameFormatter; - /// - public Link DocumentLinks { get; set; } = Link.All; + public ResourceGraphBuilder(IResourceNameFormatter formatter = null) + { + _resourceNameFormatter = formatter ?? new DefaultResourceNameFormatter(); + } /// public IResourceGraph Build() { - // this must be done at build so that call order doesn't matter - _entities.ForEach(e => e.Links = GetLinkFlags(e.EntityType)); + _entities.ForEach(SetResourceLinksOptions); List controllerContexts = new List() { }; foreach(var cm in _controllerMapper) @@ -52,6 +54,17 @@ public IResourceGraph Build() return graph; } + private void SetResourceLinksOptions(ContextEntity resourceContext) + { + var attribute = (LinksAttribute)resourceContext.EntityType.GetCustomAttribute(typeof(LinksAttribute)); + if (attribute != null) + { + resourceContext.RelationshipLinks = attribute.RelationshipLinks; + resourceContext.ResourceLinks = attribute.ResourceLinks; + resourceContext.TopLevelLinks = attribute.TopLevelLinks; + } + } + /// public IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable => AddResource(pluralizedTypeName); @@ -82,14 +95,6 @@ public IResourceGraphBuilder AddResource(Type entityType, Type idType, string pl ResourceType = GetResourceDefinitionType(entityType) }; - private Link GetLinkFlags(Type entityType) - { - var attribute = (LinksAttribute)entityType.GetTypeInfo().GetCustomAttribute(typeof(LinksAttribute)); - if (attribute != null) - return attribute.Links; - - return DocumentLinks; - } protected virtual List GetAttributes(Type entityType) { @@ -99,11 +104,14 @@ protected virtual List GetAttributes(Type entityType) foreach (var prop in properties) { + /// todo: investigate why this is added in the exposed attributes list + /// because it is not really defined attribute considered from the json:api + /// spec point of view. if (prop.Name == nameof(Identifiable.Id)) { var idAttr = new AttrAttribute() { - PublicAttributeName = JsonApiOptions.ResourceNameFormatter.FormatPropertyName(prop), + PublicAttributeName = _resourceNameFormatter.FormatPropertyName(prop), PropertyInfo = prop, InternalAttributeName = prop.Name }; @@ -115,7 +123,7 @@ protected virtual List GetAttributes(Type entityType) if (attribute == null) continue; - attribute.PublicAttributeName = attribute.PublicAttributeName ?? JsonApiOptions.ResourceNameFormatter.FormatPropertyName(prop); + attribute.PublicAttributeName = attribute.PublicAttributeName ?? _resourceNameFormatter.FormatPropertyName(prop); attribute.InternalAttributeName = prop.Name; attribute.PropertyInfo = prop; @@ -133,7 +141,7 @@ protected virtual List GetRelationships(Type entityType) var attribute = (RelationshipAttribute)prop.GetCustomAttribute(typeof(RelationshipAttribute)); if (attribute == null) continue; - attribute.PublicRelationshipName = attribute.PublicRelationshipName ?? JsonApiOptions.ResourceNameFormatter.FormatPropertyName(prop); + attribute.PublicRelationshipName = attribute.PublicRelationshipName ?? _resourceNameFormatter.FormatPropertyName(prop); attribute.InternalRelationshipName = prop.Name; attribute.DependentType = GetRelationshipType(attribute, prop); attribute.PrincipalType = entityType; @@ -269,7 +277,6 @@ public IResourceGraphBuilder AddControllerPairing(Type controller, Type model = { _undefinedMapper.Add(controller); return this; - } if (_controllerMapper.Keys.Contains(model)) { diff --git a/src/JsonApiDotNetCore/Builders/SerializerOptions.cs b/src/JsonApiDotNetCore/Builders/SerializerOptions.cs index a5b16bdd37..3d9e3f71fc 100644 --- a/src/JsonApiDotNetCore/Builders/SerializerOptions.cs +++ b/src/JsonApiDotNetCore/Builders/SerializerOptions.cs @@ -1,17 +1,21 @@ +using JsonApiDotNetCore.Configuration; + namespace JsonApiDotNetCore.Builders { /// /// Options used to configure how a model gets serialized into /// a json:api document. /// - public struct DocumentBuilderOptions + public class SerializerBehaviour { - /// - /// Do not serialize attributes with null values. - /// - public DocumentBuilderOptions(bool omitNullValuedAttributes = false) + /// Omit null values from attributes + public SerializerBehaviour(ISerializerOptions options) { - this.OmitNullValuedAttributes = omitNullValuedAttributes; + OmitNullValuedAttributes = options.NullAttributeResponseBehavior.OmitNullValuedAttributes; + if (options.NullAttributeResponseBehavior.AllowClientOverride) + { + + } } /// @@ -26,4 +30,10 @@ public DocumentBuilderOptions(bool omitNullValuedAttributes = false) /// public bool OmitNullValuedAttributes { get; private set; } } + + public interface ISerializerOptions + { + NullAttributeResponseBehavior NullAttributeResponseBehavior { get;set;} + } } + From b679f0713b2ed9b8f54069954c92a4021db9d83c Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 26 Sep 2019 17:46:59 +0200 Subject: [PATCH 22/26] chore: remove operation services --- .../Services/EntityResourceService.cs | 8 +- .../Services/IJsonApiContext.cs | 71 +---------- .../Services/JsonApiContext.cs | 33 +---- .../Operations/OperationsProcessor.cs | 9 +- .../Processors/CreateOpProcessor.cs | 19 ++- .../Operations/Processors/GetOpProcessor.cs | 11 +- .../Processors/RemoveOpProcessor.cs | 13 +- .../Processors/UpdateOpProcessor.cs | 16 ++- src/JsonApiDotNetCore/Services/QueryParser.cs | 70 +++++++--- .../Services/ResourceFieldExplorer.cs | 120 +++++++++++++++++- 10 files changed, 224 insertions(+), 146 deletions(-) diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index aa64ba6b24..ec6f055507 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -24,7 +24,7 @@ public class EntityResourceService : where TResource : class, IIdentifiable where TEntity : class, IIdentifiable { - private readonly IPageManager _pageManager; + private readonly IPageQueryService _pageManager; private readonly IRequestManager _requestManager; private readonly IJsonApiOptions _options; private readonly IResourceGraph _resourceGraph; @@ -37,7 +37,7 @@ public EntityResourceService( IEntityRepository repository, IJsonApiOptions options, IRequestManager requestManager, - IPageManager pageManager, + IPageQueryService pageManager, IResourceGraph resourceGraph, IResourceHookExecutor hookExecutor = null, IResourceMapper mapper = null, @@ -369,7 +369,7 @@ public EntityResourceService( IJsonApiOptions apiOptions, IRequestManager requestManager, IResourceGraph resourceGraph, - IPageManager pageManager, + IPageQueryService pageManager, ILoggerFactory loggerFactory = null, IResourceHookExecutor hookExecutor = null) : base(repository: repository, @@ -397,7 +397,7 @@ public EntityResourceService( IEntityRepository repository, IJsonApiOptions options, IRequestManager requestManager, - IPageManager pageManager, + IPageQueryService pageManager, IResourceGraph resourceGraph, ILoggerFactory loggerFactory = null, IResourceHookExecutor hookExecutor = null) : diff --git a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs index 338eabb0fa..4bc7fe3550 100644 --- a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs @@ -8,7 +8,6 @@ using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Request; namespace JsonApiDotNetCore.Services { @@ -23,70 +22,15 @@ public interface IQueryRequest { List IncludedRelationships { get; set; } QuerySet QuerySet { get; set; } - PageManager PageManager { get; set; } + PageQueryService PageManager { get; set; } } public interface IJsonApiRequest : IJsonApiApplication, IQueryRequest { - /// - /// Stores information to set relationships for the request resource. - /// These relationships must already exist and should not be re-created. - /// By default, it is the responsibility of the repository to use the - /// relationship pointers to persist the relationship. - /// - /// The expected use case is POST-ing or PATCH-ing an entity with HasMany - /// relationships: - /// - /// { - /// "data": { - /// "type": "photos", - /// "attributes": { - /// "title": "Ember Hamster", - /// "src": "http://example.com/images/productivity.png" - /// }, - /// "relationships": { - /// "tags": { - /// "data": [ - /// { "type": "tags", "id": "2" }, - /// { "type": "tags", "id": "3" } - /// ] - /// } - /// } - /// } - /// } - /// - /// - HasManyRelationshipPointers HasManyRelationshipPointers { get; } - - /// - /// Stores information to set relationships for the request resource. - /// These relationships must already exist and should not be re-created. - /// - /// The expected use case is POST-ing or PATCH-ing - /// an entity with HasOne relationships: - /// - /// { - /// "data": { - /// "type": "photos", - /// "attributes": { - /// "title": "Ember Hamster", - /// "src": "http://example.com/images/productivity.png" - /// }, - /// "relationships": { - /// "photographer": { - /// "data": { "type": "people", "id": "2" } - /// } - /// } - /// } - /// } - /// - /// - HasOneRelationshipPointers HasOneRelationshipPointers { get; } - /// /// If the request is a bulk json:api v1.1 operations request. /// This is determined by the ` - /// ` class. + /// ` class. /// /// See [json-api/1254](https://github.com/json-api/json-api/pull/1254) for details. /// @@ -123,16 +67,9 @@ public interface IJsonApiContext : IJsonApiRequest [Obsolete("Use standalone IRequestManager")] IRequestManager RequestManager { get; set; } [Obsolete("Use standalone IPageManager")] - IPageManager PageManager { get; set; } + IPageQueryService PageManager { get; set; } IJsonApiContext ApplyContext(object controller); - IMetaBuilder MetaBuilder { get; set; } + //IMetaBuilder MetaBuilder { get; set; } IGenericProcessorFactory GenericProcessorFactory { get; set; } - - /// - /// **_Experimental_**: do not use. It is likely to change in the future. - /// - /// Resets operational state information. - /// - void BeginOperation(); } } diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs index b235ffc197..4fd38b4c1a 100644 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/JsonApiContext.cs @@ -9,7 +9,6 @@ using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Request; using Microsoft.AspNetCore.Http; namespace JsonApiDotNetCore.Services @@ -24,10 +23,10 @@ public JsonApiContext( IResourceGraph resourceGraph, IHttpContextAccessor httpContextAccessor, IJsonApiOptions options, - IMetaBuilder metaBuilder, + IGenericProcessorFactory genericProcessorFactory, IQueryParser queryParser, - IPageManager pageManager, + IPageQueryService pageManager, IRequestManager requestManager, IControllerContext controllerContext) { @@ -36,7 +35,6 @@ public JsonApiContext( ResourceGraph = resourceGraph; _httpContextAccessor = httpContextAccessor; Options = options; - MetaBuilder = metaBuilder; GenericProcessorFactory = genericProcessorFactory; _queryParser = queryParser; _controllerContext = controllerContext; @@ -56,28 +54,16 @@ public JsonApiContext( public bool IsRelationshipPath { get; private set; } [Obsolete("Use IRequestManager")] public List IncludedRelationships { get; set; } - public IPageManager PageManager { get; set; } - public IMetaBuilder MetaBuilder { get; set; } + public IPageQueryService PageManager { get; set; } + //public IMetaBuilder MetaBuilder { get; set; } public IGenericProcessorFactory GenericProcessorFactory { get; set; } public Type ControllerType { get; set; } public Dictionary DocumentMeta { get; set; } public bool IsBulkOperationRequest { get; set; } - public Dictionary AttributesToUpdate { get; set; } = new Dictionary(); - public Dictionary RelationshipsToUpdate { get => GetRelationshipsToUpdate(); } - - private Dictionary GetRelationshipsToUpdate() - { - var hasOneEntries = HasOneRelationshipPointers.Get().ToDictionary(kvp => (RelationshipAttribute)kvp.Key, kvp => (object)kvp.Value); - var hasManyEntries = HasManyRelationshipPointers.Get().ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value); - return hasOneEntries.Union(hasManyEntries).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - } - - public HasManyRelationshipPointers HasManyRelationshipPointers { get; private set; } = new HasManyRelationshipPointers(); - public HasOneRelationshipPointers HasOneRelationshipPointers { get; private set; } = new HasOneRelationshipPointers(); [Obsolete("Please use the standalone Requestmanager")] public IRequestManager RequestManager { get; set; } - PageManager IQueryRequest.PageManager { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + PageQueryService IQueryRequest.PageManager { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } [Obsolete("This is no longer necessary")] public IJsonApiContext ApplyContext(object controller) @@ -136,14 +122,5 @@ internal static bool PathIsRelationship(string requestPath) return false; } - - public void BeginOperation() - { - RequestManager.IncludedRelationships = new List(); - IncludedRelationships = new List(); - AttributesToUpdate = new Dictionary(); - HasManyRelationshipPointers = new HasManyRelationshipPointers(); - HasOneRelationshipPointers = new HasOneRelationshipPointers(); - } } } diff --git a/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs index 048959f9eb..f7c4b85ca0 100644 --- a/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs @@ -51,7 +51,7 @@ public async Task> ProcessAsync(List inputOps) { foreach (var op in inputOps) { - _jsonApiContext.BeginOperation(); + //_jsonApiContext.BeginOperation(); lastAttemptedOperation = op.Op; await ProcessOperation(op, outputOps); @@ -83,11 +83,12 @@ private async Task ProcessOperation(Operation op, List outputOps) if (op.Op == OperationCode.add || op.Op == OperationCode.update) { type = op.DataObject.Type; - } else if (op.Op == OperationCode.get || op.Op == OperationCode.remove) + } + else if (op.Op == OperationCode.get || op.Op == OperationCode.remove) { type = op.Ref.Type; } - _requestManager.SetContextEntity(_resourceGraph.GetEntityFromControllerName(type)); + _requestManager.SetRequestResource(_resourceGraph.GetEntityFromControllerName(type)); var processor = GetOperationsProcessor(op); var resultOp = await processor.ProcessAsync(op); @@ -115,7 +116,7 @@ private void ReplaceLocalIdsInResourceObject(ResourceObject resourceObject, List { foreach (var relationshipDictionary in resourceObject.Relationships) { - if (relationshipDictionary.Value.IsHasMany) + if (relationshipDictionary.Value.IsManyData) { foreach (var relationship in relationshipDictionary.Value.ManyData) if (HasLocalId(relationship)) diff --git a/src/JsonApiDotNetCore/Services/Operations/Processors/CreateOpProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/Processors/CreateOpProcessor.cs index bd4e89eb84..cc3a70faed 100644 --- a/src/JsonApiDotNetCore/Services/Operations/Processors/CreateOpProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/Processors/CreateOpProcessor.cs @@ -5,6 +5,9 @@ using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + +using JsonApiDotNetCore.Serialization.Contracts; namespace JsonApiDotNetCore.Services.Operations.Processors { @@ -22,10 +25,10 @@ public class CreateOpProcessor { public CreateOpProcessor( ICreateService service, - IJsonApiDeSerializer deSerializer, + IOperationsDeserializer deserializer, IDocumentBuilder documentBuilder, IResourceGraph resourceGraph - ) : base(service, deSerializer, documentBuilder, resourceGraph) + ) : base(service, deserializer, documentBuilder, resourceGraph) { } } @@ -33,25 +36,26 @@ public class CreateOpProcessor : ICreateOpProcessor where T : class, IIdentifiable { private readonly ICreateService _service; - private readonly IJsonApiDeSerializer _deSerializer; + private readonly IOperationsDeserializer _deserializer; private readonly IDocumentBuilder _documentBuilder; private readonly IResourceGraph _resourceGraph; public CreateOpProcessor( ICreateService service, - IJsonApiDeSerializer deSerializer, + IOperationsDeserializer deserializer, IDocumentBuilder documentBuilder, IResourceGraph resourceGraph) { _service = service; - _deSerializer = deSerializer; + _deserializer = deserializer; _documentBuilder = documentBuilder; _resourceGraph = resourceGraph; } public async Task ProcessAsync(Operation operation) { - var model = (T)_deSerializer.DocumentToObject(operation.DataObject); + + var model = (T)_deserializer.DocumentToObject(operation.DataObject); var result = await _service.CreateAsync(model); var operationResult = new Operation @@ -67,7 +71,8 @@ public async Task ProcessAsync(Operation operation) // can locate the result of this operation by its localId operationResult.DataObject.LocalId = operation.DataObject.LocalId; - return operationResult; + return null; + //return operationResult; } } } diff --git a/src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs index 6535bf21b0..e5b2d21918 100644 --- a/src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs @@ -8,6 +8,7 @@ using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; namespace JsonApiDotNetCore.Services.Operations.Processors { @@ -37,11 +38,11 @@ public GetOpProcessor( IGetAllService getAll, IGetByIdService getById, IGetRelationshipService getRelationship, - IJsonApiDeSerializer deSerializer, + IOperationsDeserializer deserializer, IDocumentBuilder documentBuilder, IResourceGraph resourceGraph, IJsonApiContext jsonApiContext - ) : base(getAll, getById, getRelationship, deSerializer, documentBuilder, resourceGraph, jsonApiContext) + ) : base(getAll, getById, getRelationship, deserializer, documentBuilder, resourceGraph, jsonApiContext) { } } @@ -52,7 +53,7 @@ public class GetOpProcessor : IGetOpProcessor private readonly IGetAllService _getAll; private readonly IGetByIdService _getById; private readonly IGetRelationshipService _getRelationship; - private readonly IJsonApiDeSerializer _deSerializer; + private readonly IOperationsDeserializer _deserializer; private readonly IDocumentBuilder _documentBuilder; private readonly IResourceGraph _resourceGraph; private readonly IJsonApiContext _jsonApiContext; @@ -62,7 +63,7 @@ public GetOpProcessor( IGetAllService getAll, IGetByIdService getById, IGetRelationshipService getRelationship, - IJsonApiDeSerializer deSerializer, + IOperationsDeserializer deserializer, IDocumentBuilder documentBuilder, IResourceGraph resourceGraph, IJsonApiContext jsonApiContext) @@ -70,7 +71,7 @@ public GetOpProcessor( _getAll = getAll; _getById = getById; _getRelationship = getRelationship; - _deSerializer = deSerializer; + _deserializer = deserializer; _documentBuilder = documentBuilder; _resourceGraph = resourceGraph; _jsonApiContext = jsonApiContext.ApplyContext(this); diff --git a/src/JsonApiDotNetCore/Services/Operations/Processors/RemoveOpProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/Processors/RemoveOpProcessor.cs index 70ddae2aea..d84031c5e0 100644 --- a/src/JsonApiDotNetCore/Services/Operations/Processors/RemoveOpProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/Processors/RemoveOpProcessor.cs @@ -5,6 +5,9 @@ using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + +using JsonApiDotNetCore.Serialization.Contracts; namespace JsonApiDotNetCore.Services.Operations.Processors { @@ -21,10 +24,10 @@ public class RemoveOpProcessor : RemoveOpProcessor, IRemoveOpProcesso { public RemoveOpProcessor( IDeleteService service, - IJsonApiDeSerializer deSerializer, + IOperationsDeserializer deserializer, IDocumentBuilder documentBuilder, IResourceGraph resourceGraph - ) : base(service, deSerializer, documentBuilder, resourceGraph) + ) : base(service, deserializer, documentBuilder, resourceGraph) { } } @@ -32,18 +35,18 @@ public class RemoveOpProcessor : IRemoveOpProcessor where T : class, IIdentifiable { private readonly IDeleteService _service; - private readonly IJsonApiDeSerializer _deSerializer; + private readonly IOperationsDeserializer _deserializer; private readonly IDocumentBuilder _documentBuilder; private readonly IResourceGraph _resourceGraph; public RemoveOpProcessor( IDeleteService service, - IJsonApiDeSerializer deSerializer, + IOperationsDeserializer deserializer, IDocumentBuilder documentBuilder, IResourceGraph resourceGraph) { _service = service; - _deSerializer = deSerializer; + _deserializer = deserializer; _documentBuilder = documentBuilder; _resourceGraph = resourceGraph; } diff --git a/src/JsonApiDotNetCore/Services/Operations/Processors/UpdateOpProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/Processors/UpdateOpProcessor.cs index 7969b6f3ed..ffe104000f 100644 --- a/src/JsonApiDotNetCore/Services/Operations/Processors/UpdateOpProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/Processors/UpdateOpProcessor.cs @@ -5,6 +5,9 @@ using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Models.Operations; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + +using JsonApiDotNetCore.Serialization.Contracts; namespace JsonApiDotNetCore.Services.Operations.Processors { @@ -21,10 +24,10 @@ public class UpdateOpProcessor : UpdateOpProcessor, IUpdateOpProcesso { public UpdateOpProcessor( IUpdateService service, - IJsonApiDeSerializer deSerializer, + IOperationsDeserializer deserializer, IDocumentBuilder documentBuilder, IResourceGraph resourceGraph - ) : base(service, deSerializer, documentBuilder, resourceGraph) + ) : base(service, deserializer, documentBuilder, resourceGraph) { } } @@ -32,18 +35,18 @@ public class UpdateOpProcessor : IUpdateOpProcessor where T : class, IIdentifiable { private readonly IUpdateService _service; - private readonly IJsonApiDeSerializer _deSerializer; + private readonly IOperationsDeserializer _deserializer; private readonly IDocumentBuilder _documentBuilder; private readonly IResourceGraph _resourceGraph; public UpdateOpProcessor( IUpdateService service, - IJsonApiDeSerializer deSerializer, + IOperationsDeserializer deserializer, IDocumentBuilder documentBuilder, IResourceGraph resourceGraph) { _service = service; - _deSerializer = deSerializer; + _deserializer = deserializer; _documentBuilder = documentBuilder; _resourceGraph = resourceGraph; } @@ -53,7 +56,8 @@ public async Task ProcessAsync(Operation operation) if (string.IsNullOrWhiteSpace(operation?.DataObject?.Id?.ToString())) throw new JsonApiException(400, "The data.id parameter is required for replace operations"); - var model = (T)_deSerializer.DocumentToObject(operation.DataObject); + //var model = (T)_deserializer.DocumentToObject(operation.DataObject); + T model = null; // TODO var result = await _service.UpdateAsync(model.Id, model); if (result == null) diff --git a/src/JsonApiDotNetCore/Services/QueryParser.cs b/src/JsonApiDotNetCore/Services/QueryParser.cs index 6c4ec7a988..4d78b2a8ab 100644 --- a/src/JsonApiDotNetCore/Services/QueryParser.cs +++ b/src/JsonApiDotNetCore/Services/QueryParser.cs @@ -4,6 +4,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; @@ -11,6 +12,7 @@ namespace JsonApiDotNetCore.Services { + public interface IQueryParser { QuerySet Parse(IQueryCollection query); @@ -18,52 +20,62 @@ public interface IQueryParser public class QueryParser : IQueryParser { + private readonly IInternalIncludedQueryService _includedQuery; + private readonly IInternalFieldsQueryService _fieldQuery; private readonly IRequestManager _requestManager; private readonly IJsonApiOptions _options; + private readonly ContextEntity _requestResource; + private readonly IContextEntityProvider _provider; - public QueryParser( + public QueryParser(IInternalIncludedQueryService includedRelationships, + IInternalFieldsQueryService fieldQuery, IRequestManager requestManager, + IContextEntityProvider provider, IJsonApiOptions options) { + _includedQuery = includedRelationships; + _fieldQuery = fieldQuery; _requestManager = requestManager; + _provider = provider; + _requestResource = requestManager.GetRequestResource(); _options = options; } public virtual QuerySet Parse(IQueryCollection query) { - var querySet = new QuerySet(); + var querySet = new QuerySet(); var disabledQueries = _requestManager.DisabledQueryParams; foreach (var pair in query) { - if (pair.Key.StartsWith(QueryConstants.FILTER)) + if (pair.Key.StartsWith(QueryConstants.FILTER, StringComparison.Ordinal)) { if (disabledQueries.HasFlag(QueryParams.Filters) == false) querySet.Filters.AddRange(ParseFilterQuery(pair.Key, pair.Value)); continue; } - if (pair.Key.StartsWith(QueryConstants.SORT)) + if (pair.Key.StartsWith(QueryConstants.SORT, StringComparison.Ordinal)) { if (disabledQueries.HasFlag(QueryParams.Sort) == false) querySet.SortParameters = ParseSortParameters(pair.Value); continue; } - if (pair.Key.StartsWith(QueryConstants.INCLUDE)) + if (pair.Key.StartsWith(QueryConstants.INCLUDE, StringComparison.Ordinal)) { if (disabledQueries.HasFlag(QueryParams.Include) == false) querySet.IncludedRelationships = ParseIncludedRelationships(pair.Value); continue; } - if (pair.Key.StartsWith(QueryConstants.PAGE)) + if (pair.Key.StartsWith(QueryConstants.PAGE, StringComparison.Ordinal)) { if (disabledQueries.HasFlag(QueryParams.Page) == false) querySet.PageQuery = ParsePageQuery(querySet.PageQuery, pair.Key, pair.Value); continue; } - if (pair.Key.StartsWith(QueryConstants.FIELDS)) + if (pair.Key.StartsWith(QueryConstants.FIELDS, StringComparison.Ordinal)) { if (disabledQueries.HasFlag(QueryParams.Fields) == false) querySet.Fields = ParseFieldsQuery(pair.Key, pair.Value); @@ -155,7 +167,7 @@ protected virtual List ParseSortParameters(string value) const char DESCENDING_SORT_OPERATOR = '-'; var sortSegments = value.Split(QueryConstants.COMMA); - if(sortSegments.Where(s => s == string.Empty).Count() >0) + if (sortSegments.Where(s => s == string.Empty).Count() > 0) { throw new JsonApiException(400, "The sort URI segment contained a null value."); } @@ -178,9 +190,30 @@ protected virtual List ParseSortParameters(string value) protected virtual List ParseIncludedRelationships(string value) { - return value - .Split(QueryConstants.COMMA) - .ToList(); + var inclusions = value.Split(QueryConstants.COMMA).ToList(); + foreach (var chain in inclusions) + { + var parsedChain = new List(); + var resourceContext = _requestManager.GetRequestResource(); + var splittedPath = chain.Split(QueryConstants.DOT); + foreach (var requestedRelationship in splittedPath) + { + var relationship = resourceContext.Relationships.Single(r => r.PublicRelationshipName == requestedRelationship); + if (relationship == null) + throw new JsonApiException(400, $"Invalid relationship {requestedRelationship} on {resourceContext.EntityName}", + $"{resourceContext.EntityName} does not have a relationship named {requestedRelationship}"); + + if (relationship.CanInclude == false) + throw new JsonApiException(400, $"Including the relationship {requestedRelationship} on {resourceContext.EntityName} is not allowed"); + + parsedChain.Add(relationship); + resourceContext = _provider.GetContextEntity(relationship.PrincipalType); + } + _includedQuery.Register(parsedChain); + } + + + return inclusions; } protected virtual List ParseFieldsQuery(string key, string value) @@ -189,8 +222,8 @@ protected virtual List ParseFieldsQuery(string key, string value) var typeName = key.Split(QueryConstants.OPEN_BRACKET, QueryConstants.CLOSE_BRACKET)[1]; var includedFields = new List { nameof(Identifiable.Id) }; - var relationship = _requestManager.GetContextEntity().Relationships.SingleOrDefault(a => a.Is(typeName)); - if (relationship == default && string.Equals(typeName, _requestManager.GetContextEntity().EntityName, StringComparison.OrdinalIgnoreCase) == false) + var relationship = _requestResource.Relationships.SingleOrDefault(a => a.Is(typeName)); + if (relationship == default && string.Equals(typeName, _requestResource.EntityName, StringComparison.OrdinalIgnoreCase) == false) return includedFields; var fields = value.Split(QueryConstants.COMMA); @@ -200,17 +233,18 @@ protected virtual List ParseFieldsQuery(string key, string value) { var relationProperty = _options.ResourceGraph.GetContextEntity(relationship.DependentType); var attr = relationProperty.Attributes.SingleOrDefault(a => a.Is(field)); - if(attr == null) + if (attr == null) throw new JsonApiException(400, $"'{relationship.DependentType.Name}' does not contain '{field}'."); // e.g. "Owner.Name" includedFields.Add(relationship.InternalRelationshipName + "." + attr.InternalAttributeName); + } else { - var attr = _requestManager.GetContextEntity().Attributes.SingleOrDefault(a => a.Is(field)); + var attr = _requestResource.Attributes.SingleOrDefault(a => a.Is(field)); if (attr == null) - throw new JsonApiException(400, $"'{_requestManager.GetContextEntity().EntityName}' does not contain '{field}'."); + throw new JsonApiException(400, $"'{_requestResource.EntityName}' does not contain '{field}'."); // e.g. "Name" includedFields.Add(attr.InternalAttributeName); @@ -224,13 +258,13 @@ protected virtual AttrAttribute GetAttribute(string propertyName) { try { - return _requestManager.GetContextEntity() + return _requestResource .Attributes .Single(attr => attr.Is(propertyName)); } catch (InvalidOperationException e) { - throw new JsonApiException(400, $"Attribute '{propertyName}' does not exist on resource '{_requestManager.GetContextEntity().EntityName}'", e); + throw new JsonApiException(400, $"Attribute '{propertyName}' does not exist on resource '{_requestResource.EntityName}'", e); } } diff --git a/src/JsonApiDotNetCore/Services/ResourceFieldExplorer.cs b/src/JsonApiDotNetCore/Services/ResourceFieldExplorer.cs index 9f67b1c45e..869d3067d7 100644 --- a/src/JsonApiDotNetCore/Services/ResourceFieldExplorer.cs +++ b/src/JsonApiDotNetCore/Services/ResourceFieldExplorer.cs @@ -1,10 +1,126 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; + namespace JsonApiDotNetCore.Services { - public class ResourceFieldExplorer + + public class ExposedFieldExplorer : IExposedFieldExplorer { - public ResourceFieldExplorer() + private readonly IContextEntityProvider _provider; + + public ExposedFieldExplorer(IContextEntityProvider provider) { + _provider = provider; } + + public List GetFields(Expression> selector = null) where T : IIdentifiable + { + return Getter(selector).ToList(); + } + + public List GetAttributes(Expression> selector = null) where T : IIdentifiable + { + return Getter(selector, FieldFilterType.Attribute).Cast().ToList(); + } + + public List GetRelationships(Expression> selector = null) where T : IIdentifiable + { + return Getter(selector, FieldFilterType.Relationship).Cast().ToList(); + } + + public List GetFields(Type type) + { + return _provider.GetContextEntity(type).Fields.ToList(); + } + + public List GetAttributes(Type type) + { + return _provider.GetContextEntity(type).Attributes.ToList(); + } + + public List GetRelationships(Type type) + { + return _provider.GetContextEntity(type).Relationships.ToList(); + } + + private IEnumerable Getter(Expression> selector = null, FieldFilterType type = FieldFilterType.None) where T : IIdentifiable + { + IEnumerable available; + if (type == FieldFilterType.Attribute) + available = _provider.GetContextEntity(typeof(T)).Attributes.Cast(); + else if (type == FieldFilterType.Relationship) + available = _provider.GetContextEntity(typeof(T)).Relationships.Cast(); + else + available = _provider.GetContextEntity(typeof(T)).Fields; + + if (selector == null) + return available; + + var targeted = new List(); + // model => model.Field1 + if (selector.Body is MemberExpression memberExpression) + { + try + { + targeted.Add(available.Single(f => f.ExposedInternalMemberName == memberExpression.Member.Name)); + return targeted; + } + catch (Exception ex) + { + ThrowNotExposedError(memberExpression.Member.Name, type); + } + } + + // model => new { model.Field1, model.Field2 } + if (selector.Body is NewExpression newExpression) + { + string memberName = null; + try + { + if (newExpression.Members == null) + return targeted; + + foreach (var member in newExpression.Members) + { + memberName = member.Name; + targeted.Add(available.Single(f => f.ExposedInternalMemberName == memberName)); + } + return targeted; + } + catch (Exception ex) + { + ThrowNotExposedError(memberName, type); + } + } + + throw new ArgumentException($"The expression returned by '{selector}' for '{GetType()}' is of type {selector.Body.GetType()}" + + " and cannot be used to select resource attributes. The type must be a NewExpression.Example: article => new { article.Author };"); + + } + + private void ThrowNotExposedError(string memberName, FieldFilterType type) + { + throw new ArgumentException($"{memberName} is not an json:api exposed {type.ToString("g")}."); + } + private enum FieldFilterType + { + None, + Attribute, + Relationship + } + } + + public interface IExposedFieldExplorer + { + List GetFields(Expression> selector = null) where T : IIdentifiable; + List GetAttributes(Expression> selector = null) where T : IIdentifiable; + List GetRelationships(Expression> selector = null) where T : IIdentifiable; + List GetFields(Type type); + List GetAttributes(Type type); + List GetRelationships(Type type); } } From 12d43ed6d7e76daa83782bfa9cbb9a87c9ed6a37 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 26 Sep 2019 17:48:23 +0200 Subject: [PATCH 23/26] tests: unit tests client/server (de)serializers --- .../Deserializer/ClientDeserializerTests.cs | 42 +- .../Deserializer/DeserializerTestsSetup.cs | 33 +- .../Deserializer/DocumentParserTests.cs | 5 + .../Deserializer/ServerDeserializerTests.cs | 22 +- .../SerializationTestsSetupBase.cs | 94 ++++- .../Serializer/ClientSerializerTests.cs | 254 ++++++++---- .../Serializer/DocumentBuilderTests.cs | 1 - .../IncludedRelationshipsBuilderTests.cs | 302 ++++++-------- .../Serializer/SerializerTestsSetup.cs | 129 +++++- .../Serializer/ServerSerializerTests.cs | 380 ++++++++++++------ 10 files changed, 846 insertions(+), 416 deletions(-) diff --git a/test/UnitTests/Serialization/Deserializer/ClientDeserializerTests.cs b/test/UnitTests/Serialization/Deserializer/ClientDeserializerTests.cs index 315b932b11..c76500d8d4 100644 --- a/test/UnitTests/Serialization/Deserializer/ClientDeserializerTests.cs +++ b/test/UnitTests/Serialization/Deserializer/ClientDeserializerTests.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; using System.Linq; using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Models.Links; using JsonApiDotNetCore.Serialization; using Newtonsoft.Json; using Xunit; -namespace UnitTests.Deserialization +namespace UnitTests.Serialization.Deserializer { public class ClientDeserializerTests : DeserializerTestsSetup { @@ -14,7 +15,7 @@ public class ClientDeserializerTests : DeserializerTestsSetup public ClientDeserializerTests() { - _deserializer = new ClientDeserializer(_resourceGraph, _defaultSettings); + _deserializer = new ClientDeserializer(_resourceGraph); _linkValues.Add("self", "http://example.com/articles"); _linkValues.Add("next", "http://example.com/articles?page[offset]=2"); _linkValues.Add("last", "http://example.com/articles?page[offset]=10"); @@ -45,7 +46,7 @@ public void DeserializeSingle_EmptyResponseWithTopLevelLinks_CanDeserialize() // arrange var content = new Document { - Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] } + Links = new TopLevelLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] } }; var body = JsonConvert.SerializeObject(content); @@ -64,9 +65,10 @@ public void DeserializeSingle_EmptyResponseWithTopLevelLinks_CanDeserialize() public void DeserializeList_EmptyResponseWithTopLevelLinks_CanDeserialize() { // arrange - var content = new Documents + var content = new Document { - Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] }, + Links = new TopLevelLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] }, + Data = new List() }; var body = JsonConvert.SerializeObject(content); @@ -95,8 +97,8 @@ public void DeserializeSingle_ResourceWithAttributes_CanDeserialize() // assert Assert.Null(result.Links); Assert.Null(result.Meta); - Assert.Equal(1, entity.Id); - Assert.Equal(content.Data.Attributes["string-field"], entity.StringField); + Assert.Equal(1, entity.Id); + Assert.Equal(content.SingleData.Attributes["string-field"], entity.StringField); } [Fact] @@ -104,10 +106,10 @@ public void DeserializeSingle_MultipleDependentRelationshipsWithIncluded_CanDese { // arrange var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); - content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); + content.SingleData.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); + content.SingleData.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + content.SingleData.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.SingleData.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); var toOneAttributeValue = "populated-to-one member content"; var toManyAttributeValue = "populated-to-manies member content"; content.Included = new List() @@ -147,10 +149,10 @@ public void DeserializeSingle_MultiplePrincipalRelationshipsWithIncluded_CanDese { // arrange var content = CreateDocumentWithRelationships("multi-dependents"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); - content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); + content.SingleData.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); + content.SingleData.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); + content.SingleData.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.SingleData.Relationships.Add("empty-to-many", CreateRelationshipData()); var toOneAttributeValue = "populated-to-one member content"; var toManyAttributeValue = "populated-to-manies member content"; content.Included = new List() @@ -189,7 +191,7 @@ public void DeserializeSingle_NestedIncluded_CanDeserialize() { // arrange var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + content.SingleData.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); var toManyAttributeValue = "populated-to-manies member content"; var nestedIncludeAttributeValue = "nested include member content"; content.Included = new List() @@ -232,7 +234,7 @@ public void DeserializeSingle_DeeplyNestedIncluded_CanDeserialize() { // arrange var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("multi", CreateRelationshipData("multi-principals")); + content.SingleData.Relationships.Add("multi", CreateRelationshipData("multi-principals")); var includedAttributeValue = "multi member content"; var nestedIncludedAttributeValue = "nested include member content"; var deeplyNestedIncludedAttributeValue = "deeply nested member content"; @@ -270,7 +272,7 @@ public void DeserializeSingle_DeeplyNestedIncluded_CanDeserialize() var included = entity.Multi; Assert.Equal(10, included.Id); Assert.Equal(includedAttributeValue, included.AttributeMember); - var nestedIncluded = included.PopulatedToManies.First(); + var nestedIncluded = included.PopulatedToManies.First(); Assert.Equal(10, nestedIncluded.Id); Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); var deeplyNestedIncluded = nestedIncluded.Principal; @@ -283,8 +285,8 @@ public void DeserializeSingle_DeeplyNestedIncluded_CanDeserialize() public void DeserializeList_DeeplyNestedIncluded_CanDeserialize() { // arrange - var content = new Documents { Data = new List { CreateDocumentWithRelationships("multi-principals").Data } }; - content.Data[0].Relationships.Add("multi", CreateRelationshipData("multi-principals")); + var content = new Document { Data = new List { CreateDocumentWithRelationships("multi-principals").SingleData } }; + content.ManyData[0].Relationships.Add("multi", CreateRelationshipData("multi-principals")); var includedAttributeValue = "multi member content"; var nestedIncludedAttributeValue = "nested include member content"; var deeplyNestedIncludedAttributeValue = "deeply nested member content"; diff --git a/test/UnitTests/Serialization/Deserializer/DeserializerTestsSetup.cs b/test/UnitTests/Serialization/Deserializer/DeserializerTestsSetup.cs index 9e9f8f1ca2..35ece88f97 100644 --- a/test/UnitTests/Serialization/Deserializer/DeserializerTestsSetup.cs +++ b/test/UnitTests/Serialization/Deserializer/DeserializerTestsSetup.cs @@ -1,18 +1,28 @@ -using JsonApiDotNetCore.Models; -using System.Collections.Generic; -using System; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; +using System.Collections.Generic; -namespace UnitTests.Deserialization +namespace UnitTests.Serialization.Deserializer { public class DeserializerTestsSetup : SerializationTestsSetupBase { + protected class TestDocumentParser : DocumentParser + { + public TestDocumentParser(IResourceGraph resourceGraph) : base(resourceGraph) { } + + public new object Deserialize(string body) + { + return base.Deserialize(body); + } + + protected override void AfterProcessField(IIdentifiable entity, IResourceField field, RelationshipData data = null) { } + } + protected Document CreateDocumentWithRelationships(string mainType, string relationshipMemberName, string relatedType = null, bool isToManyData = false) { var content = CreateDocumentWithRelationships(mainType); - content.Data.Relationships.Add(relationshipMemberName, CreateRelationshipData(relatedType, isToManyData)); + content.SingleData.Relationships.Add(relationshipMemberName, CreateRelationshipData(relatedType, isToManyData)); return content; } @@ -36,11 +46,12 @@ protected RelationshipData CreateRelationshipData(string relatedType = null, boo if (isToManyData) { - data.ExposedData = new List(); - if (relatedType != null) ((List)data.ExposedData).Add(rio); - } else + data.Data = new List(); + if (relatedType != null) ((List)data.Data).Add(rio); + } + else { - data.ExposedData = rio; + data.Data = rio; } return data; } diff --git a/test/UnitTests/Serialization/Deserializer/DocumentParserTests.cs b/test/UnitTests/Serialization/Deserializer/DocumentParserTests.cs index 23c2c9c072..80ed40183b 100644 --- a/test/UnitTests/Serialization/Deserializer/DocumentParserTests.cs +++ b/test/UnitTests/Serialization/Deserializer/DocumentParserTests.cs @@ -13,6 +13,11 @@ public class DocumentParserTests : DeserializerTestsSetup { private readonly TestDocumentParser _deserializer; + public DocumentParserTests() + { + _deserializer = new TestDocumentParser(_resourceGraph); + } + [Fact] public void DeserializeResourceIdentifiers_SingleData_CanDeserialize() { diff --git a/test/UnitTests/Serialization/Deserializer/ServerDeserializerTests.cs b/test/UnitTests/Serialization/Deserializer/ServerDeserializerTests.cs index 9d97bdf3e3..6b098c4ddb 100644 --- a/test/UnitTests/Serialization/Deserializer/ServerDeserializerTests.cs +++ b/test/UnitTests/Serialization/Deserializer/ServerDeserializerTests.cs @@ -6,15 +6,15 @@ using Newtonsoft.Json; using Xunit; -namespace UnitTests.Deserialization +namespace UnitTests.Serialization.Deserializer { public class ServerDeserializerTests : DeserializerTestsSetup { private readonly ServerDeserializer _deserializer; - private readonly Mock _fieldsManagerMock = new Mock(); + private readonly Mock _fieldsManagerMock = new Mock(); public ServerDeserializerTests() : base() { - _deserializer = new ServerDeserializer(_resourceGraph, _defaultSettings, _fieldsManagerMock.Object); + _deserializer = new ServerDeserializer(_resourceGraph, _fieldsManagerMock.Object); } [Fact] @@ -62,10 +62,10 @@ public void DeserializeRelationships_MultipleDependentRelationships_RegistersUpd // arrange SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); - content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); + content.SingleData.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); + content.SingleData.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.SingleData.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); + content.SingleData.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); var body = JsonConvert.SerializeObject(content); // act @@ -82,10 +82,10 @@ public void DeserializeRelationships_MultiplePrincipalRelationships_RegistersUpd // arrange SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); var content = CreateDocumentWithRelationships("multi-dependents"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); - content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); + content.SingleData.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); + content.SingleData.Relationships.Add("empty-to-one", CreateRelationshipData()); + content.SingleData.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); + content.SingleData.Relationships.Add("empty-to-many", CreateRelationshipData()); var body = JsonConvert.SerializeObject(content); // act diff --git a/test/UnitTests/Serialization/SerializationTestsSetupBase.cs b/test/UnitTests/Serialization/SerializationTestsSetupBase.cs index 5135a2e642..3728f2b4f2 100644 --- a/test/UnitTests/Serialization/SerializationTestsSetupBase.cs +++ b/test/UnitTests/Serialization/SerializationTestsSetupBase.cs @@ -1,20 +1,42 @@ -using System; +using System; using System.Collections.Generic; +using Bogus; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; -namespace UnitTests.Deserialization +namespace UnitTests.Serialization { public class SerializationTestsSetupBase { - protected readonly IResourceGraph _resourceGraph; + protected IResourceGraph _resourceGraph; + protected IContextEntityProvider _provider; + protected readonly Faker _foodFaker; + protected readonly Faker _songFaker; + protected readonly Faker
_articleFaker; + protected readonly Faker _blogFaker; + protected readonly Faker _personFaker; protected readonly JsonApiSerializerSettings _defaultSettings = new JsonApiSerializerSettings(); public SerializationTestsSetupBase() { _resourceGraph = BuildGraph(); + _articleFaker = new Faker
() + .RuleFor(f => f.Title, f => f.Hacker.Phrase()) + .RuleFor(f => f.Id, f => f.UniqueIndex + 1); + _personFaker = new Faker() + .RuleFor(f => f.Name, f => f.Person.FullName) + .RuleFor(f => f.Id, f => f.UniqueIndex + 1); + _blogFaker = new Faker() + .RuleFor(f => f.Title, f => f.Hacker.Phrase()) + .RuleFor(f => f.Id, f => f.UniqueIndex + 1); + _songFaker = new Faker() + .RuleFor(f => f.Title, f => f.Lorem.Sentence()) + .RuleFor(f => f.Id, f => f.UniqueIndex + 1); + _foodFaker = new Faker() + .RuleFor(f => f.Dish, f => f.Lorem.Sentence()) + .RuleFor(f => f.Id, f => f.UniqueIndex + 1); } protected IResourceGraph BuildGraph() @@ -33,10 +55,17 @@ protected IResourceGraph BuildGraph() // collective relationships resourceGraphBuilder.AddResource("multi-principals"); resourceGraphBuilder.AddResource("multi-dependents"); - return resourceGraphBuilder.Build(); + + resourceGraphBuilder.AddResource
(); + resourceGraphBuilder.AddResource(); + resourceGraphBuilder.AddResource(); + resourceGraphBuilder.AddResource(); + resourceGraphBuilder.AddResource(); + + return resourceGraphBuilder.Build(); } - protected class TestResource : Identifiable + public class TestResource : Identifiable { [Attr] public string StringField { get; set; } [Attr] public DateTime DateTimeField { get; set; } @@ -48,56 +77,56 @@ protected class TestResource : Identifiable [Attr(isImmutable: true)] public string Immutable { get; set; } } - protected class TestResourceWithList : Identifiable + public class TestResourceWithList : Identifiable { [Attr] public List ComplexFields { get; set; } } - protected class ComplexType + public class ComplexType { public string CompoundName { get; set; } } - protected class OneToOnePrincipal : IdentifiableWithAttribute + public class OneToOnePrincipal : IdentifiableWithAttribute { [HasOne] public OneToOneDependent Dependent { get; set; } } - protected class OneToOneDependent : IdentifiableWithAttribute + public class OneToOneDependent : IdentifiableWithAttribute { [HasOne] public OneToOnePrincipal Principal { get; set; } public int? PrincipalId { get; set; } } - protected class OneToOneRequiredDependent : IdentifiableWithAttribute + public class OneToOneRequiredDependent : IdentifiableWithAttribute { [HasOne] public OneToOnePrincipal Principal { get; set; } public int PrincipalId { get; set; } } - protected class OneToManyDependent : IdentifiableWithAttribute + public class OneToManyDependent : IdentifiableWithAttribute { [HasOne] public OneToManyPrincipal Principal { get; set; } public int? PrincipalId { get; set; } } - protected class OneToManyRequiredDependent : IdentifiableWithAttribute + public class OneToManyRequiredDependent : IdentifiableWithAttribute { [HasOne] public OneToManyPrincipal Principal { get; set; } public int PrincipalId { get; set; } } - protected class OneToManyPrincipal : IdentifiableWithAttribute + public class OneToManyPrincipal : IdentifiableWithAttribute { [HasMany] public List Dependents { get; set; } } - protected class IdentifiableWithAttribute : Identifiable + public class IdentifiableWithAttribute : Identifiable { [Attr] public string AttributeMember { get; set; } } - protected class MultipleRelationshipsPrincipalPart : IdentifiableWithAttribute + public class MultipleRelationshipsPrincipalPart : IdentifiableWithAttribute { [HasOne] public OneToOneDependent PopulatedToOne { get; set; } [HasOne] public OneToOneDependent EmptyToOne { get; set; } @@ -106,7 +135,7 @@ protected class MultipleRelationshipsPrincipalPart : IdentifiableWithAttribute [HasOne] public MultipleRelationshipsPrincipalPart Multi { get; set; } } - protected class MultipleRelationshipsDependentPart : IdentifiableWithAttribute + public class MultipleRelationshipsDependentPart : IdentifiableWithAttribute { [HasOne] public OneToOnePrincipal PopulatedToOne { get; set; } public int PopulatedToOneId { get; set; } @@ -117,5 +146,38 @@ protected class MultipleRelationshipsDependentPart : IdentifiableWithAttribute [HasOne] public OneToManyPrincipal EmptyToMany { get; set; } public int? EmptyToManyId { get; set; } } + + + public class Article : Identifiable + { + [Attr] public string Title { get; set; } + [HasOne] public Person Reviewer { get; set; } + [HasOne] public Person Author { get; set; } + } + + public class Person : Identifiable + { + [Attr] public string Name { get; set; } + [HasMany] public List Blogs { get; set; } + [HasOne] public Food FavoriteFood { get; set; } + [HasOne] public Song FavoriteSong { get; set; } + } + + public class Blog : Identifiable + { + [Attr] public string Title { get; set; } + [HasOne] public Person Reviewer { get; set; } + [HasOne] public Person Author { get; set; } + } + + public class Food : Identifiable + { + [Attr] public string Dish { get; set; } + } + + public class Song : Identifiable + { + [Attr] public string Title { get; set; } + } } } \ No newline at end of file diff --git a/test/UnitTests/Serialization/Serializer/ClientSerializerTests.cs b/test/UnitTests/Serialization/Serializer/ClientSerializerTests.cs index 1d011bf8aa..c457cd7723 100644 --- a/test/UnitTests/Serialization/Serializer/ClientSerializerTests.cs +++ b/test/UnitTests/Serialization/Serializer/ClientSerializerTests.cs @@ -1,138 +1,250 @@ +using System; using System.Collections.Generic; using System.Text.RegularExpressions; using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Extensions; -using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.DependencyInjection; -using Moq; using Xunit; namespace UnitTests.Serialization.Serializer { public class ClientSerializerTests : SerializerTestsSetup { + private readonly ClientSerializer _serializer; public ClientSerializerTests() { - + _serializer = new ClientSerializer(_fieldExplorer, _resourceGraph, _resourceGraph); } + [Fact] + public void SerializeSingle_ResourceWithDefaultTargetFields_CanBuild() + { + // arrange + var entity = new TestResource() { Id = 1, StringField = "value", NullableIntField = 123 }; + + // act + string serialized = _serializer.Serialize(entity); + + // assert + var expectedFormatted = + @"{ + ""data"":{ + ""type"":""test-resource"", + ""id"":""1"", + ""attributes"":{ + ""string-field"":""value"", + ""date-time-field"":""0001-01-01T00:00:00"", + ""nullable-date-time-field"":null, + ""int-field"":0, + ""nullable-int-field"":123, + ""guid-field"":""00000000-0000-0000-0000-000000000000"", + ""complex-field"":null, + ""immutable"":null + } + } + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); + } [Fact] - public void Serialize_TestResource_CanSerialize() + public void SerializeSingle_ResourceWithTargetedSetAttributes_CanBuild() { // arrange - var complexFieldValue = "complex type field"; - var stringFieldValue = "string field"; - var entity = new TestResource() - { - Id = 1, - ComplexField = new ComplexType() { CompoundName = complexFieldValue }, - StringField = stringFieldValue - }; - var serializer = GetClientSerializer(); + var entity = new TestResource() { Id = 1, StringField = "value", NullableIntField = 123 }; + _serializer.SetAttributesToSerialize(tr => tr.StringField); // act - var document = serializer.Build(entity); + string serialized = _serializer.Serialize(entity); // assert - Assert.Equal(8, document.Data.Attributes.Keys.Count); - var complexType = (ComplexType)document.Data.Attributes["complex-field"]; - Assert.Equal(complexFieldValue, complexType.CompoundName); - Assert.Equal(stringFieldValue, document.Data.Attributes["string-field"]); - Assert.Null(document.Data.Relationships); - Assert.Equal("1", document.Data.Id); - Assert.Equal("test-resource", document.Data.Type); + var expectedFormatted = + @"{ + ""data"":{ + ""type"":""test-resource"", + ""id"":""1"", + ""attributes"":{ + ""string-field"":""value"" + } + } + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); } [Fact] - public void Serialize_TestResourceList_CanSerialize() + public void SerializeSingle_NoIdWithTargetedSetAttributes_CanBuild() { // arrange - var entities = new List - { - new TestResource { Id = 1 }, - new TestResource { Id = 2 }, - new TestResource { Id = 3 } - }; - var serializer = GetClientSerializer(); + var entityNoId = new TestResource() { Id = 0, StringField = "value", NullableIntField = 123 }; + _serializer.SetAttributesToSerialize(tr => tr.StringField); // act - var documents = serializer.Build(entities); + string serialized = _serializer.Serialize(entityNoId); // assert - Assert.Equal(3, documents.Data.Count); - foreach (var resourceObject in documents.Data) - { - Assert.Equal(8, resourceObject.Attributes.Keys.Count); - Assert.Null(resourceObject.Relationships); - } + var expectedFormatted = + @"{ + ""data"":{ + ""type"":""test-resource"", + ""attributes"":{ + ""string-field"":""value"" + } + } + }"; + + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); } + [Fact] + public void SerializeSingle_ResourceWithoutTargetedAttributes_CanBuild() + { + // arrange + var entity = new TestResource() { Id = 1, StringField = "value", NullableIntField = 123 }; + _serializer.SetAttributesToSerialize(tr => new { }); + + // act + string serialized = _serializer.Serialize(entity); + + // assert + var expectedFormatted = + @"{ + ""data"":{ + ""type"":""test-resource"", + ""id"":""1"" + } + }"; + + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); + } [Fact] - public void Serialize_TestResourceListWithSubsetOfAttributes_CanSerialize() + public void SerializeSingle_ResourceWithTargetedRelationships_CanBuild() + { + // arrange + var entityWithRelationships = new MultipleRelationshipsPrincipalPart + { + PopulatedToOne = new OneToOneDependent { Id = 10 }, + PopulatedToManies = new List { new OneToManyDependent { Id = 20 } } + }; + _serializer.SetRelationshipsToSerialize(tr => new { tr.EmptyToOne, tr.EmptyToManies, tr.PopulatedToOne, tr.PopulatedToManies }); + + // act + string serialized = _serializer.Serialize(entityWithRelationships); + Console.WriteLine(serialized); + // assert + var expectedFormatted = + @"{ + ""data"":{ + ""type"":""multi-principals"", + ""attributes"":{ + ""attribute-member"":null + }, + ""relationships"":{ + ""empty-to-one"":{ + ""data"":null + }, + ""empty-to-manies"":{ + ""data"":[ ] + }, + ""populated-to-one"":{ + ""data"":{ + ""type"":""one-to-one-dependents"", + ""id"":""10"" + } + }, + ""populated-to-manies"":{ + ""data"":[ + { + ""type"":""one-to-many-dependents"", + ""id"":""20"" + } + ] + } + } + } + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); + } + + [Fact] + public void SerializeMany_ResourcesWithTargetedAttributes_CanBuild() { // arrange var entities = new List { - new TestResource { Id = 1 }, - new TestResource { Id = 2 }, - new TestResource { Id = 3 } + new TestResource() { Id = 1, StringField = "value1", NullableIntField = 123 }, + new TestResource() { Id = 2, StringField = "value2", NullableIntField = 123 } }; - var serializer = GetClientSerializer(); - serializer.AttributesToInclude(tr => new { tr.StringField, tr.NullableDateTimeField }); - serializer.SetResourceForTests(); + _serializer.SetAttributesToSerialize(tr => tr.StringField); // act - var documents = serializer.Build(entities); + string serialized = _serializer.Serialize(entities); // assert - Assert.Equal(3, documents.Data.Count); - foreach (var resourceObject in documents.Data) - { - Assert.Equal(2, resourceObject.Attributes.Keys.Count); - Assert.Null(resourceObject.Relationships); - } + var expectedFormatted = + @"{ + ""data"":[ + { + ""type"":""test-resource"", + ""id"":""1"", + ""attributes"":{ + ""string-field"":""value1"" + } + }, + { + ""type"":""test-resource"", + ""id"":""2"", + ""attributes"":{ + ""string-field"":""value2"" + } + } + ] + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); } [Fact] - public void Serialize_ResourceWithNoAttributes_CanSerialize() + public void SerializeSingle_Null_CanBuild() { // arrange - var serializer = GetClientSerializer(); - serializer.AttributesToInclude(tr => new { }); - serializer.SetResourceForTests(); + _serializer.SetAttributesToSerialize(tr => tr.StringField); // act - var document = serializer.Build(new TestResource { Id = 1 }); + IIdentifiable obj = null; + string serialized = _serializer.Serialize(obj); // assert - Assert.Null(document.Data.Attributes); - Assert.Null(document.Data.Relationships); + var expectedFormatted = + @"{ + ""data"":null + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); } [Fact] - public void Serialize_ResourceWithRelationships_CanSerialize() + public void SerializeMany_EmptyList_CanBuild() { // arrange - var serializer = GetClientSerializer(); - //serializer.AttributesToInclude(tr => new { }); - var entity = new MultipleRelationshipsPrincipalPart(); - + var entities = new List { }; + _serializer.SetAttributesToSerialize(tr => tr.StringField); // act - var document = serializer.Build(entity); + string serialized = _serializer.Serialize(entities); // assert - Assert.Equal(1, document.Data.Attributes.Keys.Count); - Assert.Null(document.Data.Relationships); + var expectedFormatted = + @"{ + ""data"":[] + }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); } - } } diff --git a/test/UnitTests/Serialization/Serializer/DocumentBuilderTests.cs b/test/UnitTests/Serialization/Serializer/DocumentBuilderTests.cs index f57d04d237..a2095cc82a 100644 --- a/test/UnitTests/Serialization/Serializer/DocumentBuilderTests.cs +++ b/test/UnitTests/Serialization/Serializer/DocumentBuilderTests.cs @@ -14,7 +14,6 @@ public class DocumentBuilderTests : SerializerTestsSetup public DocumentBuilderTests() { _serializer = new TestSerializer(_resourceGraph, _resourceGraph); - } [Fact] diff --git a/test/UnitTests/Serialization/Serializer/IncludedRelationshipsBuilderTests.cs b/test/UnitTests/Serialization/Serializer/IncludedRelationshipsBuilderTests.cs index c456ea2241..c3b4e439f8 100644 --- a/test/UnitTests/Serialization/Serializer/IncludedRelationshipsBuilderTests.cs +++ b/test/UnitTests/Serialization/Serializer/IncludedRelationshipsBuilderTests.cs @@ -1,216 +1,178 @@ using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Extensions; -using JsonApiDotNetCore.Models.Links; -using JsonApiDotNetCoreExample.Models; -using Moq; using Xunit; -using System; +using UnitTests.Serialization.Serializer; +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Internal.Query; -namespace UnitTests +namespace UnitTests.Serialization.IncludedRelationshipBuilder { - public class LinkBuilderTests + public class IncludedRelationshipBuilderTests : SerializerTestsSetup { - private readonly IPageManager _pageManager; - private readonly Mock _provider = new Mock(); - private const string _host = "http://www.example.com"; - private const string _topSelf = "http://www.example.com/articles"; - private const string _resourceSelf = "http://www.example.com/articles/123"; - private const string _relSelf = "http://www.example.com/articles/123/relationships/author"; - private const string _relRelated = "http://www.example.com/articles/123/author"; - - public LinkBuilderTests() + [Fact] + public void BuildIncluded_DeeplyNestedCircularChainOfSingleData_CanBuild() { - _pageManager = GetPageManager(); - } - - [Theory] - [InlineData(Link.All, Link.NotConfigured, _resourceSelf)] - [InlineData(Link.Self, Link.NotConfigured, _resourceSelf)] - [InlineData(Link.None, Link.NotConfigured, null)] - [InlineData(Link.All, Link.Self, _resourceSelf)] - [InlineData(Link.Self, Link.Self, _resourceSelf)] - [InlineData(Link.None, Link.Self, _resourceSelf)] - [InlineData(Link.All, Link.None, null)] - [InlineData(Link.Self, Link.None, null)] - [InlineData(Link.None, Link.None, null)] - public void BuildResourceLinks_GlobalAndResourceConfiguration_ExpectedResult(Link global, Link resource, object expectedResult) - { - // arrange - var config = GetConfiguration(resourceLinks: global); - _provider.Setup(m => m.GetContextEntity("articles")).Returns(GetContextEntity
(resourceLinks: resource)); - var builder = new LinkBuilder(config, GetRequestManager(), _pageManager, _provider.Object); + // arrange + var (article, author, authorFood, reviewer, reviewerFood) = GetAuthorChainInstances(); + var authorChain = GetIncludedRelationshipsChain("author.blogs.reviewer.favorite-food"); + var builder = GetBuilder(); // act - var links = builder.GetResourceLinks("articles", "123"); + builder.IncludeRelationshipChain(authorChain, article); + var result = builder.Build(); // assert - if (expectedResult == null) - Assert.Null(links); - else - Assert.Equal(_resourceSelf, links.Self); - } + Assert.Equal(6, result.Count); + var authorResourceObject = result.Single((ro) => ro.Type == "people" && ro.Id == author.StringId); + var authorFoodRelation = authorResourceObject.Relationships["favorite-food"].SingleData; + Assert.Equal(author.FavoriteFood.StringId, authorFoodRelation.Id); + var reviewerResourceObject = result.Single((ro) => ro.Type == "people" && ro.Id == reviewer.StringId); + var reviewerFoodRelation = reviewerResourceObject.Relationships["favorite-food"].SingleData; + Assert.Equal(reviewer.FavoriteFood.StringId, reviewerFoodRelation.Id); + } - [Theory] - [InlineData(Link.All, Link.NotConfigured, Link.NotConfigured, _relSelf, _relRelated)] - [InlineData(Link.All, Link.NotConfigured, Link.All, _relSelf, _relRelated)] - [InlineData(Link.All, Link.NotConfigured, Link.Self, _relSelf, null)] - [InlineData(Link.All, Link.NotConfigured, Link.Related, null, _relRelated)] - [InlineData(Link.All, Link.NotConfigured, Link.None, null, null)] - [InlineData(Link.All, Link.All, Link.NotConfigured, _relSelf, _relRelated)] - [InlineData(Link.All, Link.All, Link.All, _relSelf, _relRelated)] - [InlineData(Link.All, Link.All, Link.Self, _relSelf, null)] - [InlineData(Link.All, Link.All, Link.Related, null, _relRelated)] - [InlineData(Link.All, Link.All, Link.None, null, null)] - [InlineData(Link.All, Link.Self, Link.NotConfigured, _relSelf, null)] - [InlineData(Link.All, Link.Self, Link.All, _relSelf, _relRelated)] - [InlineData(Link.All, Link.Self, Link.Self, _relSelf, null)] - [InlineData(Link.All, Link.Self, Link.Related, null, _relRelated)] - [InlineData(Link.All, Link.Self, Link.None, null, null)] - [InlineData(Link.All, Link.Related, Link.NotConfigured, null, _relRelated)] - [InlineData(Link.All, Link.Related, Link.All, _relSelf, _relRelated)] - [InlineData(Link.All, Link.Related, Link.Self, _relSelf, null)] - [InlineData(Link.All, Link.Related, Link.Related, null, _relRelated)] - [InlineData(Link.All, Link.Related, Link.None, null, null)] - [InlineData(Link.All, Link.None, Link.NotConfigured, null, null)] - [InlineData(Link.All, Link.None, Link.All, _relSelf, _relRelated)] - [InlineData(Link.All, Link.None, Link.Self, _relSelf, null)] - [InlineData(Link.All, Link.None, Link.Related, null, _relRelated)] - [InlineData(Link.All, Link.None, Link.None, null, null)] - public void BuildRelationshipLinks_GlobalResourceAndAttrConfiguration_ExpectedLinks(Link global, - Link resource, - Link relationship, - object expectedSelfLink, - object expectedRelatedLink) + [Fact] + public void BuildIncluded_DeeplyNestedCircularChainOfManyData_BuildsWithoutDuplicates() { // arrange - var config = GetConfiguration(relationshipLinks: global); - _provider.Setup(m => m.GetContextEntity(typeof(Article))).Returns(GetContextEntity
(relationshipLinks: resource)); - var builder = new LinkBuilder(config, GetRequestManager(), _pageManager, _provider.Object); - var attr = new HasOneAttribute(links: relationship) { DependentType = typeof(Author), PublicRelationshipName = "author" }; + var (article, author, _, _, _) = GetAuthorChainInstances(); + var secondArticle = _articleFaker.Generate(); + secondArticle.Author = author; + var builder = GetBuilder(); // act - var links = builder.GetRelationshipLinks(attr, new Article { Id = 123 }); + var authorChain = GetIncludedRelationshipsChain("author.blogs.reviewer.favorite-food"); + builder.IncludeRelationshipChain(authorChain, article); + builder.IncludeRelationshipChain(authorChain, secondArticle); // assert - if (expectedSelfLink == null && expectedRelatedLink == null) - { - Assert.Null(links); - } - else - { - Assert.Equal(expectedSelfLink, links.Self); - Assert.Equal(expectedRelatedLink, links.Related); - } + var result = builder.Build(); + Assert.Equal(6, result.Count); } - [Theory] - [InlineData(Link.All, Link.NotConfigured, _topSelf, true)] - [InlineData(Link.All, Link.All, _topSelf, true)] - [InlineData(Link.All, Link.Self, _topSelf, false)] - [InlineData(Link.All, Link.Paging, null, true)] - [InlineData(Link.All, Link.None, null, null)] - [InlineData(Link.Self, Link.NotConfigured, _topSelf, false)] - [InlineData(Link.Self, Link.All, _topSelf, true)] - [InlineData(Link.Self, Link.Self, _topSelf, false)] - [InlineData(Link.Self, Link.Paging, null, true)] - [InlineData(Link.Self, Link.None, null, null)] - [InlineData(Link.Paging, Link.NotConfigured, null, true)] - [InlineData(Link.Paging, Link.All, _topSelf, true)] - [InlineData(Link.Paging, Link.Self, _topSelf, false)] - [InlineData(Link.Paging, Link.Paging, null, true)] - [InlineData(Link.Paging, Link.None, null, null)] - [InlineData(Link.None, Link.NotConfigured, null, false)] - [InlineData(Link.None, Link.All, _topSelf, true)] - [InlineData(Link.None, Link.Self, _topSelf, false)] - [InlineData(Link.None, Link.Paging, null, true)] - [InlineData(Link.None, Link.None, null, null)] - public void BuildTopLevelLinks_GlobalAndResourceConfiguration_ExpectedLinks(Link global, - Link resource, - object expectedSelfLink, - bool pages) + [Fact] + public void BuildIncluded_OverlappingDeeplyNestedCirculairChains_CanBuild() { // arrange - var config = GetConfiguration(topLevelLinks: global); - var resourceContext = GetContextEntity
(topLevelLinks: resource); - var builder = new LinkBuilder(config, GetRequestManager(resourceContext), _pageManager, null); + var authorChain = GetIncludedRelationshipsChain("author.blogs.reviewer.favorite-food"); + var (article, author, authorFood, reviewer, reviewerFood) = GetAuthorChainInstances(); + var sharedBlog = author.Blogs.First(); + var sharedBlogAuthor = reviewer; + var (_reviewer, _reviewerSong, _author, _authorSong) = GetReviewerChainInstances(article, sharedBlog, sharedBlogAuthor); + var reviewerChain = GetIncludedRelationshipsChain("reviewer.blogs.author.favorite-song"); + var builder = GetBuilder(); // act - var links = builder.GetTopLevelLinks(); + builder.IncludeRelationshipChain(authorChain, article); + builder.IncludeRelationshipChain(reviewerChain, article); + var result = builder.Build(); // assert - if (!pages && expectedSelfLink == null) - { - Assert.Null(links); - } - else - { - Assert.Equal(expectedSelfLink, links.Self); - Assert.True(CheckPages(links, pages)); - } - } + Assert.Equal(10, result.Count); + var overlappingBlogResourcObject = result.Single((ro) => ro.Type == "blogs" && ro.Id == sharedBlog.StringId); - private bool CheckPages(TopLevelLinks links, bool pages) - { - if (pages) - { - return links.First == $"{_host}/articles?page[size]=10&page[number]=1" - && links.Prev == $"{_host}/articles?page[size]=10&page[number]=1" - && links.Next == $"{_host}/articles?page[size]=10&page[number]=3" - && links.Last == $"{_host}/articles?page[size]=10&page[number]=3"; - } - return links.First == null && links.Prev == null && links.Next == null && links.Last == null; - } + Assert.Equal(2, overlappingBlogResourcObject.Relationships.Keys.ToList().Count); + var nonOverlappingBlogs = result.Where((ro) => ro.Type == "blogs" && ro.Id != sharedBlog.StringId).ToList(); - private IRequestManager GetRequestManager(ContextEntity resourceContext = null) - { - var mock = new Mock(); - mock.Setup(m => m.BasePath).Returns(_host); - mock.Setup(m => m.GetRequestResource()).Returns(resourceContext); - return mock.Object; + foreach (var blog in nonOverlappingBlogs) + Assert.Equal(1, blog.Relationships.Keys.ToList().Count); + + var sharedAuthorResourceObject = result.Single((ro) => ro.Type == "people" && ro.Id == sharedBlogAuthor.StringId); + var sharedAuthorSongRelation = sharedAuthorResourceObject.Relationships["favorite-song"].SingleData; + Assert.Equal(_authorSong.StringId, sharedBlogAuthor.FavoriteSong.StringId); + var sharedAuthorFoodRelation = sharedAuthorResourceObject.Relationships["favorite-food"].SingleData; + Assert.Equal(reviewerFood.StringId, sharedBlogAuthor.FavoriteFood.StringId); } - private IGlobalLinksConfiguration GetConfiguration(Link resourceLinks = Link.All, - Link topLevelLinks = Link.All, - Link relationshipLinks = Link.All) + private (Person, Song, Person, Song) GetReviewerChainInstances(Article article, Blog sharedBlog, Person sharedBlogAuthor) { - var config = new Mock(); - config.Setup(m => m.TopLevelLinks).Returns(topLevelLinks); - config.Setup(m => m.ResourceLinks).Returns(resourceLinks); - config.Setup(m => m.RelationshipLinks).Returns(relationshipLinks); - return config.Object; + var reviewer = _personFaker.Generate(); + article.Reviewer = reviewer; + + var blogs = _blogFaker.Generate(1).ToList(); + blogs.Add(sharedBlog); + reviewer.Blogs = blogs; + + blogs[0].Author = reviewer; + var author = _personFaker.Generate(); + blogs[1].Author = sharedBlogAuthor; + + var authorSong = _songFaker.Generate(); + author.FavoriteSong = authorSong; + sharedBlogAuthor.FavoriteSong = authorSong; + + var reviewerSong = _songFaker.Generate(); + reviewer.FavoriteSong = reviewerSong; + + return (reviewer, reviewerSong, author, authorSong); } - private IPageManager GetPageManager() + private (Article, Person, Food, Person, Food) GetAuthorChainInstances() { - var mock = new Mock(); - mock.Setup(m => m.ShouldPaginate()).Returns(true); - mock.Setup(m => m.CurrentPage).Returns(2); - mock.Setup(m => m.TotalPages).Returns(3); - mock.Setup(m => m.PageSize).Returns(10); - return mock.Object; + var article = _articleFaker.Generate(); + var author = _personFaker.Generate(); + article.Author = author; + + var blogs = _blogFaker.Generate(2).ToList(); + author.Blogs = blogs; + + blogs[0].Reviewer = author; + var reviewer = _personFaker.Generate(); + blogs[1].Reviewer = reviewer; + + var authorFood = _foodFaker.Generate(); + author.FavoriteFood = authorFood; + var reviewerFood = _foodFaker.Generate(); + reviewer.FavoriteFood = reviewerFood; + return (article, author, authorFood, reviewer, reviewerFood); } + [Fact] + public void BuildIncluded_DuplicateChildrenMultipleChains_OnceInOutput() + { + var person = _personFaker.Generate(); + var articles = _articleFaker.Generate(5).ToList(); + articles.ForEach(a => a.Author = person); + articles.ForEach(a => a.Reviewer = person); + var builder = GetBuilder(); + var authorChain = GetIncludedRelationshipsChain("author"); + var reviewerChain = GetIncludedRelationshipsChain("reviewer"); + foreach (var article in articles) + { + builder.IncludeRelationshipChain(authorChain, article); + builder.IncludeRelationshipChain(reviewerChain, article); + } + var result = builder.Build(); + Assert.Equal(1, result.Count); + Assert.Equal(person.Name, result[0].Attributes["name"]); + Assert.Equal(person.Id.ToString(), result[0].Id); + } - private ContextEntity GetContextEntity(Link resourceLinks = Link.NotConfigured, - Link topLevelLinks = Link.NotConfigured, - Link relationshipLinks = Link.NotConfigured) where TResource : class, IIdentifiable + private List GetIncludedRelationshipsChain(string chain) { - return new ContextEntity + var parsedChain = new List(); + var resourceContext = _resourceGraph.GetContextEntity
(); + var splittedPath = chain.Split(QueryConstants.DOT); + foreach (var requestedRelationship in splittedPath) { - ResourceLinks = resourceLinks, - TopLevelLinks = topLevelLinks, - RelationshipLinks = relationshipLinks, - EntityName = typeof(TResource).Name.Dasherize() + "s" - }; + var relationship = resourceContext.Relationships.Single(r => r.PublicRelationshipName == requestedRelationship); + parsedChain.Add(relationship); + resourceContext = _resourceGraph.GetContextEntity(relationship.DependentType); + } + return parsedChain; } + + private IncludedRelationshipsBuilder GetBuilder() + { + var fields = GetSerializableFields(); + var links = GetLinkBuilder(); + return new IncludedRelationshipsBuilder(fields, links, _resourceGraph, _resourceGraph); + } + } } diff --git a/test/UnitTests/Serialization/Serializer/SerializerTestsSetup.cs b/test/UnitTests/Serialization/Serializer/SerializerTestsSetup.cs index b1d3a9a5e9..ab318a940f 100644 --- a/test/UnitTests/Serialization/Serializer/SerializerTestsSetup.cs +++ b/test/UnitTests/Serialization/Serializer/SerializerTestsSetup.cs @@ -1,6 +1,131 @@ -namespace UnitTests.Serialization +using System; +using System.Collections; +using System.Collections.Generic; +using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Models.Links; +using JsonApiDotNetCore.Services; +using Moq; + +namespace UnitTests.Serialization.Serializer { - public class SerializerTestsSetup + public class SerializerTestsSetup : SerializationTestsSetupBase { + protected readonly IExposedFieldExplorer _fieldExplorer; + protected readonly TopLevelLinks _dummyToplevelLinks; + protected readonly ResourceLinks _dummyResourceLinks; + protected readonly RelationshipLinks _dummyRelationshipLinks; + public SerializerTestsSetup() + { + _fieldExplorer = new ExposedFieldExplorer(_resourceGraph); + _dummyToplevelLinks = new TopLevelLinks + { + Self = "http://www.dummy.com/dummy-self-link", + Next = "http://www.dummy.com/dummy-next-link", + Prev = "http://www.dummy.com/dummy-prev-link", + First = "http://www.dummy.com/dummy-first-link", + Last = "http://www.dummy.com/dummy-last-link" + }; + _dummyResourceLinks = new ResourceLinks + { + Self = "http://www.dummy.com/dummy-resource-self-link" + }; + _dummyRelationshipLinks = new RelationshipLinks + { + Related = "http://www.dummy.com/dummy-relationship-related-link", + Self = "http://www.dummy.com/dummy-relationship-self-link" + }; + } + + protected ServerSerializer GetServerSerializer(List> inclusionChains = null, Dictionary metaDict = null, TopLevelLinks topLinks = null, ResourceLinks resourceLinks = null, RelationshipLinks relationshipLinks = null) where T : class, IIdentifiable + { + var meta = GetMetaBuilder(metaDict); + var link = GetLinkBuilder(topLinks, resourceLinks, relationshipLinks); + var serializableFields = GetSerializableFields(); + var sparseFields = GetFieldsQuery(); + var included = GetIncludedRelationships(inclusionChains); + var provider = GetContextEntityProvider(); + var includedBuilder = GetIncludedBuilder(); + + return new ServerSerializer(meta, link, includedBuilder, serializableFields, included, sparseFields, _resourceGraph, provider); + } + + private IIncludedRelationshipsBuilder GetIncludedBuilder() + { + return new IncludedRelationshipsBuilder(GetSerializableFields(), GetLinkBuilder(), _resourceGraph, _resourceGraph); + } + + private IContextEntityProvider GetContextEntityProvider() + { + return _resourceGraph; + } + + protected IMetaBuilder GetMetaBuilder(Dictionary meta = null) where T : class, IIdentifiable + { + var mock = new Mock>(); + mock.Setup(m => m.GetMeta()).Returns(meta); + return mock.Object; + } + + protected IRequestManager GetRequestManager() where T : class, IIdentifiable + { + var mock = new Mock(); + mock.Setup(m => m.GetRequestResource()).Returns(_resourceGraph.GetContextEntity()); + return mock.Object; + } + + protected ILinkBuilder GetLinkBuilder(TopLevelLinks top = null, ResourceLinks resource = null, RelationshipLinks relationship = null) + { + var mock = new Mock(); + mock.Setup(m => m.GetTopLevelLinks()).Returns(top); + mock.Setup(m => m.GetResourceLinks(It.IsAny(), It.IsAny())).Returns(resource); + mock.Setup(m => m.GetRelationshipLinks(It.IsAny(), It.IsAny())).Returns(relationship); + return mock.Object; + } + + protected IFieldsQueryService GetFieldsQuery() + { + var mock = new Mock(); + return mock.Object; + } + + protected ISerializableFields GetSerializableFields() + { + var mock = new Mock(); + mock.Setup(m => m.GetAllowedAttributes(It.IsAny())).Returns(t => _resourceGraph.GetContextEntity(t).Attributes); + mock.Setup(m => m.GetAllowedRelationships(It.IsAny())).Returns(t => _resourceGraph.GetContextEntity(t).Relationships); + return mock.Object; + } + + protected IIncludedQueryService GetIncludedRelationships(List> inclusionChains = null) + { + var mock = new Mock(); + if (inclusionChains != null) + mock.Setup(m => m.Get()).Returns(inclusionChains); + + return mock.Object; + } + + + /// + /// Minimal implementation of abstract JsonApiSerializer base class, with + /// the purpose of testing the business logic for building the document structure. + /// + protected class TestSerializer : DocumentBuilder + { + public TestSerializer(IResourceGraph resourceGraph, IContextEntityProvider provider) : base(resourceGraph, provider) { } + + public new Document Build(IIdentifiable entity, List attributes = null, List relationships = null) + { + return base.Build(entity, attributes, relationships); + } + + public new Document Build(IEnumerable entities, List attributes = null, List relationships = null) + { + return base.Build(entities, attributes, relationships); + } + } } } \ No newline at end of file diff --git a/test/UnitTests/Serialization/Serializer/ServerSerializerTests.cs b/test/UnitTests/Serialization/Serializer/ServerSerializerTests.cs index 64b530e8b6..ac397086b1 100644 --- a/test/UnitTests/Serialization/Serializer/ServerSerializerTests.cs +++ b/test/UnitTests/Serialization/Serializer/ServerSerializerTests.cs @@ -1,28 +1,32 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; -using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Models.Links; using Xunit; namespace UnitTests.Serialization.Serializer { public class ServerSerializerTests : SerializerTestsSetup { + [Fact] public void SerializeSingle_ResourceWithDefaultTargetFields_CanBuild() { // arrange var entity = new TestResource() { Id = 1, StringField = "value", NullableIntField = 123 }; - ClientSerializer serializer = GetServerSerializer(); + var serializer = GetServerSerializer(); // act - string serialized = serializer.Serialize(entity); + string serialized = serializer.SerializeSingle(entity); // assert var expectedFormatted = @"{ ""data"":{ + ""type"":""test-resource"", + ""id"":""1"", ""attributes"":{ ""string-field"":""value"", ""date-time-field"":""0001-01-01T00:00:00"", @@ -32,9 +36,7 @@ public void SerializeSingle_ResourceWithDefaultTargetFields_CanBuild() ""guid-field"":""00000000-0000-0000-0000-000000000000"", ""complex-field"":null, ""immutable"":null - }, - ""type"":""test-resource"", - ""id"":""1"" + } } }"; @@ -44,51 +46,105 @@ public void SerializeSingle_ResourceWithDefaultTargetFields_CanBuild() } [Fact] - public void SerializeSingle_ResourceWithTargetedSetAttributes_CanBuild() + public void SerializeMany_ResourceWithDefaultTargetFields_CanBuild() { // arrange var entity = new TestResource() { Id = 1, StringField = "value", NullableIntField = 123 }; - ClientSerializer serializer = GetServerSerializer(); - serializer.SetAttributesToSerialize(tr => tr.StringField); + var serializer = GetServerSerializer(); // act - string serialized = serializer.Serialize(entity); + string serialized = serializer.SerializeMany(new List { entity }); // assert var expectedFormatted = @"{ - ""data"":{ - ""attributes"":{ - ""string-field"":""value"" - }, + ""data"":[{ ""type"":""test-resource"", - ""id"":""1"" - } + ""id"":""1"", + ""attributes"":{ + ""string-field"":""value"", + ""date-time-field"":""0001-01-01T00:00:00"", + ""nullable-date-time-field"":null, + ""int-field"":0, + ""nullable-int-field"":123, + ""guid-field"":""00000000-0000-0000-0000-000000000000"", + ""complex-field"":null, + ""immutable"":null + } + }] }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); Assert.Equal(expected, serialized); } [Fact] - public void SerializeSingle_NoIdWithTargetedSetAttributes_CanBuild() + public void SerializeSingle_ResourceWithIncludedRelationships_CanSerialize() { // arrange - var entityNoId = new TestResource() { Id = 0, StringField = "value", NullableIntField = 123 }; - ClientSerializer serializer = GetServerSerializer(); - serializer.SetAttributesToSerialize(tr => tr.StringField); + var entity = new MultipleRelationshipsPrincipalPart + { + Id = 1, + PopulatedToOne = new OneToOneDependent { Id = 10 }, + PopulatedToManies = new List { new OneToManyDependent { Id = 20 } } + }; + var chain = _fieldExplorer.GetRelationships().Select(r => new List { r }).ToList(); + var serializer = GetServerSerializer(inclusionChains: chain); // act - string serialized = serializer.Serialize(entityNoId); + string serialized = serializer.SerializeSingle(entity); // assert var expectedFormatted = @"{ ""data"":{ + ""type"":""multi-principals"", + ""id"":""1"", ""attributes"":{ - ""string-field"":""value"" + ""attribute-member"":null }, - ""type"":""test-resource"" - } + ""relationships"":{ + ""populated-to-one"":{ + ""data"":{ + ""type"":""one-to-one-dependents"", + ""id"":""10"" + } + }, + ""empty-to-one"":{ + ""data"":null + }, + ""populated-to-manies"":{ + ""data"":[ + { + ""type"":""one-to-many-dependents"", + ""id"":""20"" + } + ] + }, + ""empty-to-manies"":{ + ""data"":[ ] + }, + ""multi"":{ + ""data"":null + } + } + }, + ""included"":[ + { + ""type"":""one-to-one-dependents"", + ""id"":""10"", + ""attributes"":{ + ""attribute-member"":null + } + }, + { + ""type"":""one-to-many-dependents"", + ""id"":""20"", + ""attributes"":{ + ""attribute-member"":null + } + } + ] }"; var expected = Regex.Replace(expectedFormatted, @"\s+", ""); @@ -96,23 +152,88 @@ public void SerializeSingle_NoIdWithTargetedSetAttributes_CanBuild() } [Fact] - public void SerializeSingle_ResourceWithoutTargetedAttributes_CanBuild() + public void SerializeSingle_ResourceWithDeeplyIncludedRelationships_CanSerialize() { // arrange - var entity = new TestResource() { Id = 1, StringField = "value", NullableIntField = 123 }; - ClientSerializer serializer = GetServerSerializer(); - serializer.SetAttributesToSerialize(tr => new { }); + var deeplyIncludedEntity = new OneToManyPrincipal { Id = 30, AttributeMember = "deep" }; + var includedEntity = new OneToManyDependent { Id = 20, Principal = deeplyIncludedEntity }; + var entity = new MultipleRelationshipsPrincipalPart + { + Id = 10, + PopulatedToManies = new List { includedEntity } + }; + + var chains = _fieldExplorer.GetRelationships() + .Select(r => + { + var chain = new List { r }; + if (r.PublicRelationshipName != "populated-to-manies") + return new List { r }; + chain.AddRange(_fieldExplorer.GetRelationships()); + return chain; + }).ToList(); + + var serializer = GetServerSerializer(inclusionChains: chains); // act - string serialized = serializer.Serialize(entity); + string serialized = serializer.SerializeSingle(entity); // assert var expectedFormatted = @"{ - ""data"":{ - ""type"":""test-resource"", - ""id"":""1"" - } + ""data"":{ + ""type"":""multi-principals"", + ""id"":""10"", + ""attributes"":{ + ""attribute-member"":null + }, + ""relationships"":{ + ""populated-to-one"":{ + ""data"":null + }, + ""empty-to-one"":{ + ""data"":null + }, + ""populated-to-manies"":{ + ""data"":[ + { + ""type"":""one-to-many-dependents"", + ""id"":""20"" + } + ] + }, + ""empty-to-manies"":{ + ""data"":[] + }, + ""multi"":{ + ""data"":null + } + } + }, + ""included"":[ + { + ""type"":""one-to-many-dependents"", + ""id"":""20"", + ""attributes"":{ + ""attribute-member"":null + }, + ""relationships"":{ + ""principal"":{ + ""data"":{ + ""type"":""one-to-many-principals"", + ""id"":""30"" + } + } + } + }, + { + ""type"":""one-to-many-principals"", + ""id"":""30"", + ""attributes"":{ + ""attribute-member"":""deep"" + } + } + ] }"; var expected = Regex.Replace(expectedFormatted, @"\s+", ""); @@ -120,140 +241,171 @@ public void SerializeSingle_ResourceWithoutTargetedAttributes_CanBuild() } [Fact] - public void SerializeSingle_ResourceWithTargetedRelationships_CanBuild() + public void SerializeSingle_Null_CanSerialize() { // arrange - var entityWithRelationships = new MultipleRelationshipsPrincipalPart - { - PopulatedToOne = new OneToOneDependent { Id = 10 }, - PopulatedToManies = new List { new OneToManyDependent { Id = 20 } } - }; - ClientSerializer serializer = GetServerSerializer(); - serializer.SetRelationshipsToSerialize(tr => new { tr.EmptyToOne, tr.EmptyToManies, tr.PopulatedToOne, tr.PopulatedToManies }); - + var serializer = GetServerSerializer(); + TestResource entity = null; // act - string serialized = serializer.Serialize(entityWithRelationships); - Console.WriteLine(serialized); + string serialized = serializer.SerializeSingle(entity); + // assert var expectedFormatted = - @"{ - ""data"":{ - ""attributes"":{ - ""attribute-member"":null - }, - ""relationships"":{ - ""empty-to-one"":{ - ""data"":null - }, - ""empty-to-manies"":{ - ""data"":[ + @"{ + ""data"": null + }"; - ] - }, - ""populated-to-one"":{ - ""data"":{ - ""type"":""one-to-one-dependents"", - ""id"":""10"" - } - }, - ""populated-to-manies"":{ - ""data"":[ - { - ""type"":""one-to-many-dependents"", - ""id"":""20"" - } - ] - } - }, - ""type"":""multi-principals"" - } + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); + } + + [Fact] + public void SerializeList_EmptyList_CanSerialize() + { + // arrange + var serializer = GetServerSerializer(); + // act + string serialized = serializer.SerializeMany(new List()); + + // assert + var expectedFormatted = + @"{ + ""data"": [] }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); Assert.Equal(expected, serialized); } [Fact] - public void SerializeMany_ResourcesWithTargetedAttributes_CanBuild() + public void SerializeSingle_ResourceWithLinksEnabled_CanSerialize() { // arrange - var entities = new List - { - new TestResource() { Id = 1, StringField = "value1", NullableIntField = 123 }, - new TestResource() { Id = 2, StringField = "value2", NullableIntField = 123 } - }; - ClientSerializer serializer = GetServerSerializer(); - serializer.SetAttributesToSerialize(tr => tr.StringField); + var entity = new OneToManyPrincipal { Id = 10 }; + var includeRelationshipsOn = new List { typeof(OneToManyPrincipal) }; + var serializer = GetServerSerializer(topLinks: _dummyToplevelLinks, relationshipLinks: _dummyRelationshipLinks, resourceLinks: _dummyResourceLinks); // act - string serialized = serializer.Serialize(entities); + string serialized = serializer.SerializeSingle(entity); + Console.WriteLine(serialized); // assert var expectedFormatted = @"{ - ""data"":[ - { - ""attributes"":{ - ""string-field"":""value1"" - }, - ""type"":""test-resource"", - ""id"":""1"" - }, - { - ""attributes"":{ - ""string-field"":""value2"" - }, - ""type"":""test-resource"", - ""id"":""2"" - } - ] + ""links"":{ + ""self"":""http://www.dummy.com/dummy-self-link"", + ""next"":""http://www.dummy.com/dummy-next-link"", + ""prev"":""http://www.dummy.com/dummy-prev-link"", + ""first"":""http://www.dummy.com/dummy-first-link"", + ""last"":""http://www.dummy.com/dummy-last-link"" + }, + ""data"":{ + ""type"":""one-to-many-principals"", + ""id"":""10"", + ""attributes"":{ + ""attribute-member"":null + }, + ""relationships"":{ + ""dependents"":{ + ""links"":{ + ""self"":""http://www.dummy.com/dummy-relationship-self-link"", + ""related"":""http://www.dummy.com/dummy-relationship-related-link"" + } + } + }, + ""links"":{ + ""self"":""http://www.dummy.com/dummy-resource-self-link"" + } + } }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); Assert.Equal(expected, serialized); } [Fact] - public void SerializeSingle_Null_CanBuild() + public void SerializeSingle_ResourceNoLinksNoRelationships_DoesNotSerializeRelationshipMember() { // arrange - ClientSerializer serializer = GetServerSerializer(); - serializer.SetAttributesToSerialize(tr => tr.StringField); + var entity = new OneToManyPrincipal { Id = 10 }; + var serializer = GetServerSerializer(); // act - IIdentifiable obj = null; ; - string serialized = serializer.Serialize(obj); + string serialized = serializer.SerializeSingle(entity); + Console.WriteLine(serialized); // assert var expectedFormatted = @"{ - ""data"":null + ""data"":{ + ""type"":""one-to-many-principals"", + ""id"":""10"", + ""attributes"":{ + ""attribute-member"":null + } + } }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); Assert.Equal(expected, serialized); } [Fact] - public void SerializeMany_EmptyList_CanBuild() + public void SerializeSingle_ResourceWithMeta_IncludesMetaInResult() { // arrange - var entities = new List { }; - ClientSerializer serializer = GetServerSerializer(); - serializer.SetAttributesToSerialize(tr => tr.StringField); - + var meta = new Dictionary { { "test", "meta" } }; + var entity = new OneToManyPrincipal { Id = 10 }; + var serializer = GetServerSerializer(metaDict: meta); // act - string serialized = serializer.Serialize(entities); + string serialized = serializer.SerializeSingle(entity); + Console.WriteLine(serialized); // assert var expectedFormatted = @"{ - ""data"":[] + ""meta"":{ ""test"": ""meta"" }, + ""data"":{ + ""type"":""one-to-many-principals"", + ""id"":""10"", + ""attributes"":{ + ""attribute-member"":null + } + } }"; + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); Assert.Equal(expected, serialized); } - private ServerSerializer GetServerSerializer() + + [Fact] + public void SerializeSingle_NullWithLinksAndMeta_StillShowsLinksAndMeta() { - return new ServerSerializer(_fieldExplorer, _resourceGraph, _defaultSettings); + // arrange + var meta = new Dictionary { { "test", "meta" } }; + OneToManyPrincipal entity = null; + var serializer = GetServerSerializer(metaDict: meta, topLinks: _dummyToplevelLinks, relationshipLinks: _dummyRelationshipLinks, resourceLinks: _dummyResourceLinks); + // act + string serialized = serializer.SerializeSingle(entity); + + Console.WriteLine(serialized); + // assert + var expectedFormatted = + @"{ + ""meta"":{ ""test"": ""meta"" }, + ""links"":{ + ""self"":""http://www.dummy.com/dummy-self-link"", + ""next"":""http://www.dummy.com/dummy-next-link"", + ""prev"":""http://www.dummy.com/dummy-prev-link"", + ""first"":""http://www.dummy.com/dummy-first-link"", + ""last"":""http://www.dummy.com/dummy-last-link"" + }, + ""data"": null + }"; + + var expected = Regex.Replace(expectedFormatted, @"\s+", ""); + Assert.Equal(expected, serialized); } } } From 03f4b7a427f478ddfb5f785d85c8d7b2064b66f0 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 26 Sep 2019 17:49:24 +0200 Subject: [PATCH 24/26] chore: various edits to run tests --- benchmarks/Program.cs | 2 +- benchmarks/Query/QueryParser_Benchmarks.cs | 2 +- .../JsonApiDeserializer_Benchmarks.cs | 12 +- .../JsonApiSerializer_Benchmarks.cs | 2 + src/Examples/GettingStarted/Models/Article.cs | 1 - .../JsonApiDotNetCoreExample/Models/Person.cs | 13 +- .../Resources/LockableResource.cs | 1 + .../Resources/PersonResource.cs | 12 +- .../Resources/UserResource.cs | 8 +- .../Services/CustomArticleService.cs | 2 +- .../JsonApiDotNetCoreExample/Startup.cs | 2 +- .../IGlobalLinksConfiguration.cs | 8 +- .../Configuration/IJsonApiOptions.cs | 18 +- .../Configuration/JsonApiOptions.cs | 114 ++- .../Data/DefaultEntityRepository.cs | 41 +- .../IServiceCollectionExtensions.cs | 43 +- .../Extensions/StringExtensions.cs | 14 +- .../Formatters/JsonApiReader.cs | 26 +- .../Formatters/JsonApiWriter.cs | 5 +- .../Hooks/Discovery/HooksDiscovery.cs | 2 +- .../Discovery/LoadDatabaseValuesAttribute.cs | 4 +- .../Hooks/Execution/DiffableEntityHashSet.cs | 7 +- .../Hooks/Execution/HookExecutorHelper.cs | 5 +- .../Hooks/ResourceHookExecutor.cs | 54 +- .../Hooks/Traversal/ChildNode.cs | 8 +- .../Hooks/Traversal/RootNode.cs | 4 +- .../Hooks/Traversal/TraversalHelper.cs | 23 +- .../Internal/ContextEntity.cs | 36 +- .../Contracts/IContextEntityProvider.cs | 19 +- .../Internal/Contracts/IResourceGraph.cs | 21 +- .../Internal/DasherizedRoutingConvention.cs | 10 +- .../Internal/IdentifiableComparer.cs | 2 +- .../Internal/Query/BaseAttrQuery.cs | 4 +- .../Internal/Query/RelatedAttrFilterQuery.cs | 2 +- .../Internal/ResourceGraph.cs | 24 +- src/JsonApiDotNetCore/Internal/TypeHelper.cs | 21 +- .../JsonApiDotNetCore.csproj | 8 + .../Managers/Contracts/IRequestManager.cs | 10 +- .../Managers/IUpdatedFieldManager.cs | 10 +- .../Managers/RequestManager.cs | 43 +- .../Middleware/JsonApiActionFilter.cs | 7 +- .../Middleware/RequestMiddleware.cs | 34 +- src/JsonApiDotNetCore/Models/AttrAttribute.cs | 5 +- .../Models/HasManyAttribute.cs | 7 +- .../Models/HasManyThroughAttribute.cs | 8 +- .../Models/HasOneAttribute.cs | 18 +- src/JsonApiDotNetCore/Models/IHasMeta.cs | 2 +- .../Models/IResourceField.cs | 3 +- .../Models/JsonApi/Document.cs | 25 +- .../Models/JsonApi/ExposableData.cs | 16 +- .../Models/JsonApi/Identifiable.cs | 7 +- .../Models/JsonApi/RelationshipData.cs | 53 +- .../JsonApi/ResourceIdentifierObject.cs | 10 +- .../Models/JsonApi/ResourceObject.cs | 2 +- .../Models/Operations/Operation.cs | 12 +- .../Models/RelationshipAttribute.cs | 23 +- .../Models/ResourceDefinition.cs | 143 +-- .../Models/SerializableFields.cs | 8 +- .../Contract/IFieldQueryService.cs | 10 - .../Contract/IIncludedQueryService.cs | 11 - .../Contract/IInternalFIeldQueryService.cs | 9 - .../Contract/IInternalIncludedQueryService.cs | 10 - .../Contracts/IFieldQueryService.cs | 5 + .../Contracts/IIncludedQueryService.cs | 5 + .../ServiceDiscoveryFacadeTests.cs | 10 +- .../CamelCasedModelsControllerTests.cs | 14 +- .../Extensibility/CustomErrorTests.cs | 2 + .../Acceptance/ManyToManyTests.cs | 14 +- .../ResourceDefinitions/QueryFiltersTests.cs | 6 +- .../ResourceDefinitionTests.cs | 6 +- .../Acceptance/Spec/AttributeFilterTests.cs | 10 +- .../Acceptance/Spec/CreatingDataTests.cs | 22 +- .../Spec/DeeplyNestedInclusionTests.cs | 2 +- .../Acceptance/Spec/DocumentTests/Included.cs | 8 +- .../Acceptance/Spec/FetchingDataTests.cs | 4 +- .../Acceptance/Spec/PagingTests.cs | 8 +- .../Acceptance/Spec/SparseFieldSetTests.cs | 4 +- .../Acceptance/TestFixture.cs | 6 +- .../Acceptance/TodoItemsControllerTests.cs | 36 +- .../Startups/ClientGeneratedIdsStartup.cs | 2 +- .../Extensibility/NoEntityFrameworkTests.cs | 8 +- .../Acceptance/GetTests.cs | 12 +- .../Acceptance/RelationshipGetTests.cs | 14 +- .../TestFixture.cs | 4 +- .../Builders/ContextGraphBuilder_Tests.cs | 6 +- .../DocumentBuilderBehaviour_Tests.cs | 124 +-- .../Builders/DocumentBuilder_Tests.cs | 868 +++++++++--------- test/UnitTests/Builders/LinkBuilderTests.cs | 207 ++++- test/UnitTests/Builders/LinkTests.cs | 31 +- test/UnitTests/Builders/MetaBuilderTests.cs | 122 +-- .../Data/DefaultEntityRepository_Tests.cs | 364 ++++---- .../Deserialization/BaseDeserializerTests.cs | 370 -------- .../ClientDeserializerTests.cs | 333 ------- .../DasherizedResolverTests.cs | 30 - .../Deserialization/DeserializerTestsSetup.cs | 172 ---- .../Deserialization/JsonApiSerializerTests.cs | 285 ------ .../SerializationTestsSetupBase.cs | 121 --- .../ServerDeserializerTests.cs | 107 --- .../IServiceCollectionExtensionsTests.cs | 21 +- test/UnitTests/JsonApiContext/BasicTest.cs | 4 +- test/UnitTests/Models/LinkTests.cs | 1 + .../UnitTests/Models/RelationshipDataTests.cs | 24 +- .../Models/ResourceDefinitionTests.cs | 292 +++--- .../UnitTests/ResourceHooks/DiscoveryTests.cs | 4 +- .../Create/AfterCreateTests.cs | 12 +- .../Create/BeforeCreateTests.cs | 12 +- .../Create/BeforeCreate_WithDbValues_Tests.cs | 18 +- .../Delete/AfterDeleteTests.cs | 4 +- .../Delete/BeforeDeleteTests.cs | 4 +- .../Delete/BeforeDelete_WithDbValue_Tests.cs | 13 +- .../IdentifiableManyToMany_OnReturnTests.cs | 35 +- .../ManyToMany_OnReturnTests.cs | 20 +- .../Read/BeforeReadTests.cs | 35 +- .../IdentifiableManyToMany_AfterReadTests.cs | 30 +- .../Read/ManyToMany_AfterReadTests.cs | 20 +- .../ResourceHookExecutor/ScenarioTests.cs | 9 +- .../Update/AfterUpdateTests.cs | 12 +- .../Update/BeforeUpdateTests.cs | 12 +- .../Update/BeforeUpdate_WithDbValues_Tests.cs | 27 +- .../ResourceHooks/ResourceHooksTestsSetup.cs | 94 +- .../Serializ/BaseDeserializerTests.cs | 370 -------- .../Serializ/ClientDeserializerTests.cs | 333 ------- .../Serializ/DasherizedResolverTests.cs | 30 - .../Serializ/DeserializerTestsSetup.cs | 68 -- .../Serializ/JsonApiSerializerTests.cs | 285 ------ .../Serializ/SerializationTestsSetupBase.cs | 121 --- .../Serializ/ServerDeserializerTests.cs | 107 --- .../BaseDeserializerTests.cs | 370 -------- .../ClientDeserializerTests.cs | 333 ------- .../DasherizedResolverTests.cs | 30 - .../DeserializerTestsSetup.cs | 172 ---- .../JsonApiSerializerTests.cs | 285 ------ .../ServerDeserializerTests.cs | 107 --- .../Serialization/SerializerBaseTests.cs | 284 ------ .../Serialization/SerializerTestsSetup.cs | 6 - .../Serializer/JsonApiSerializerTests.cs | 284 ------ .../Serializer/SerializerBaseTests.cs | 23 - .../Serializer/SerializerTestsSetup.cs | 6 - test/UnitTests/Services/QueryParserTests.cs | 34 +- test/UnitTests/UnitTests.csproj | 4 + 140 files changed, 1953 insertions(+), 6410 deletions(-) delete mode 100644 src/JsonApiDotNetCore/QueryServices/Contract/IFieldQueryService.cs delete mode 100644 src/JsonApiDotNetCore/QueryServices/Contract/IIncludedQueryService.cs delete mode 100644 src/JsonApiDotNetCore/QueryServices/Contract/IInternalFIeldQueryService.cs delete mode 100644 src/JsonApiDotNetCore/QueryServices/Contract/IInternalIncludedQueryService.cs delete mode 100644 test/UnitTests/Deserialization/BaseDeserializerTests.cs delete mode 100644 test/UnitTests/Deserialization/ClientDeserializerTests.cs delete mode 100644 test/UnitTests/Deserialization/DasherizedResolverTests.cs delete mode 100644 test/UnitTests/Deserialization/DeserializerTestsSetup.cs delete mode 100644 test/UnitTests/Deserialization/JsonApiSerializerTests.cs delete mode 100644 test/UnitTests/Deserialization/SerializationTestsSetupBase.cs delete mode 100644 test/UnitTests/Deserialization/ServerDeserializerTests.cs delete mode 100644 test/UnitTests/Serializ/BaseDeserializerTests.cs delete mode 100644 test/UnitTests/Serializ/ClientDeserializerTests.cs delete mode 100644 test/UnitTests/Serializ/DasherizedResolverTests.cs delete mode 100644 test/UnitTests/Serializ/DeserializerTestsSetup.cs delete mode 100644 test/UnitTests/Serializ/JsonApiSerializerTests.cs delete mode 100644 test/UnitTests/Serializ/SerializationTestsSetupBase.cs delete mode 100644 test/UnitTests/Serializ/ServerDeserializerTests.cs delete mode 100644 test/UnitTests/Serialization (copy)/BaseDeserializerTests.cs delete mode 100644 test/UnitTests/Serialization (copy)/ClientDeserializerTests.cs delete mode 100644 test/UnitTests/Serialization (copy)/DasherizedResolverTests.cs delete mode 100644 test/UnitTests/Serialization (copy)/DeserializerTestsSetup.cs delete mode 100644 test/UnitTests/Serialization (copy)/JsonApiSerializerTests.cs delete mode 100644 test/UnitTests/Serialization (copy)/ServerDeserializerTests.cs delete mode 100644 test/UnitTests/Serialization/SerializerBaseTests.cs delete mode 100644 test/UnitTests/Serialization/SerializerTestsSetup.cs delete mode 100644 test/UnitTests/Serializer/JsonApiSerializerTests.cs delete mode 100644 test/UnitTests/Serializer/SerializerBaseTests.cs delete mode 100644 test/UnitTests/Serializer/SerializerTestsSetup.cs diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs index 0ec4c80e14..bd504c670f 100644 --- a/benchmarks/Program.cs +++ b/benchmarks/Program.cs @@ -9,7 +9,7 @@ namespace Benchmarks { class Program { static void Main(string[] args) { var switcher = new BenchmarkSwitcher(new[] { - typeof(JsonApiDeserializer_Benchmarks), + typeof(JsonApideserializer_Benchmarks), //typeof(JsonApiSerializer_Benchmarks), typeof(QueryParser_Benchmarks), typeof(LinkBuilder_GetNamespaceFromPath_Benchmarks), diff --git a/benchmarks/Query/QueryParser_Benchmarks.cs b/benchmarks/Query/QueryParser_Benchmarks.cs index 19819c3609..7550a27818 100644 --- a/benchmarks/Query/QueryParser_Benchmarks.cs +++ b/benchmarks/Query/QueryParser_Benchmarks.cs @@ -23,7 +23,7 @@ public class QueryParser_Benchmarks { public QueryParser_Benchmarks() { var requestMock = new Mock(); - requestMock.Setup(m => m.GetContextEntity()).Returns(new ContextEntity { + requestMock.Setup(m => m.GetRequestResource()).Returns(new ContextEntity { Attributes = new List { new AttrAttribute(ATTRIBUTE, ATTRIBUTE) } diff --git a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs index 15478a5c52..dbaf3eb826 100644 --- a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs +++ b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs @@ -7,6 +7,8 @@ using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCore.Services; using Moq; using Newtonsoft.Json; @@ -15,7 +17,7 @@ namespace Benchmarks.Serialization { [MarkdownExporter] - public class JsonApiDeserializer_Benchmarks { + public class JsonApideserializer_Benchmarks { private const string TYPE_NAME = "simple-types"; private static readonly string Content = JsonConvert.SerializeObject(new Document { Data = new ResourceObject { @@ -30,9 +32,9 @@ public class JsonApiDeserializer_Benchmarks { } }); - private readonly JsonApiDeSerializer _jsonApiDeSerializer; + private readonly JsonApideserializer _jsonApideserializer; - public JsonApiDeserializer_Benchmarks() { + public JsonApideserializer_Benchmarks() { var resourceGraphBuilder = new ResourceGraphBuilder(); resourceGraphBuilder.AddResource(TYPE_NAME); var resourceGraph = resourceGraphBuilder.Build(); @@ -50,11 +52,11 @@ public JsonApiDeserializer_Benchmarks() { jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - _jsonApiDeSerializer = new JsonApiDeSerializer(jsonApiContextMock.Object, requestManagerMock.Object); + _jsonApideserializer = new JsonApideserializer(jsonApiContextMock.Object, requestManagerMock.Object); } [Benchmark] - public object DeserializeSimpleObject() => _jsonApiDeSerializer.Deserialize(Content); + public object DeserializeSimpleObject() => _jsonApideserializer.Deserialize(Content); private class SimpleType : Identifiable { [Attr("name")] diff --git a/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs index 1238cc082f..4ec1ef99c4 100644 --- a/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs +++ b/benchmarks/Serialization/JsonApiSerializer_Benchmarks.cs @@ -6,6 +6,8 @@ //using JsonApiDotNetCore.Internal.Generics; //using JsonApiDotNetCore.Models; //using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + //using JsonApiDotNetCore.Services; //using Moq; //using Newtonsoft.Json.Serialization; diff --git a/src/Examples/GettingStarted/Models/Article.cs b/src/Examples/GettingStarted/Models/Article.cs index 68cecf060d..f10c3b175f 100644 --- a/src/Examples/GettingStarted/Models/Article.cs +++ b/src/Examples/GettingStarted/Models/Article.cs @@ -6,7 +6,6 @@ public class Article : Identifiable { [Attr] public string Title { get; set; } - [HasOne] public Person Author { get; set; } public int AuthorId { get; set; } diff --git a/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs b/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs index 3b3d44a8e2..9c88c3beeb 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Models/Person.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Models.Links; using JsonApiDotNetCore.Services; namespace JsonApiDotNetCoreExample.Models @@ -11,7 +12,7 @@ public class PersonRole : Identifiable public Person Person { get; set; } } - public class Person : Identifiable, IHasMeta, IIsLockable + public class Person : Identifiable, IIsLockable { public bool IsLocked { get; set; } @@ -45,7 +46,7 @@ public class Person : Identifiable, IHasMeta, IIsLockable public virtual TodoItem StakeHolderTodo { get; set; } public virtual int? StakeHolderTodoId { get; set; } - [HasOne("unincludeable-item", documentLinks: Link.All, canInclude: false)] + [HasOne("unincludeable-item", links: Link.All, canInclude: false)] public virtual TodoItem UnIncludeableItem { get; set; } public int? PassportId { get; set; } @@ -53,13 +54,5 @@ public class Person : Identifiable, IHasMeta, IIsLockable [HasOne("passport")] public virtual Passport Passport { get; set; } - public Dictionary GetMeta(IJsonApiContext context) - { - return new Dictionary { - { "copyright", "Copyright 2015 Example Corp." }, - { "authors", new string[] { "Jared Nance" } } - }; - } - } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/LockableResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/LockableResource.cs index 59af8be86b..96585a9458 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Resources/LockableResource.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/LockableResource.cs @@ -4,6 +4,7 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Models; namespace JsonApiDotNetCoreExample.Resources diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/PersonResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/PersonResource.cs index d07e750681..82f6dac17f 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Resources/PersonResource.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/PersonResource.cs @@ -3,10 +3,11 @@ using JsonApiDotNetCore.Hooks; using JsonApiDotNetCoreExample.Models; using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Models; namespace JsonApiDotNetCoreExample.Resources { - public class PersonResource : LockableResource + public class PersonResource : LockableResource, IHasMeta { public PersonResource(IResourceGraph graph) : base(graph) { } @@ -20,5 +21,14 @@ public override void BeforeImplicitUpdateRelationship(IRelationshipsDictionary

().ToList().ForEach(kvp => DisallowLocked(kvp.Value)); } + + + public Dictionary GetMeta() + { + return new Dictionary { + { "copyright", "Copyright 2015 Example Corp." }, + { "authors", new string[] { "Jared Nance" } } + }; + } } } diff --git a/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs b/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs index 1e32282c47..f6810f086b 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Resources/UserResource.cs @@ -4,16 +4,16 @@ using JsonApiDotNetCoreExample.Models; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Internal.Contracts; - +using JsonApiDotNetCore.Services; namespace JsonApiDotNetCoreExample.Resources { public class UserResource : ResourceDefinition { - public UserResource(IResourceGraph graph) : base(graph) { } + public UserResource(IResourceGraph graph, IExposedFieldExplorer fieldExplorer) : base(fieldExplorer, graph) { } - protected override List OutputAttrs() - => Remove(user => user.Password); + //protected override List OutputAttrs() + // => Remove(user => user.Password); public override QueryFilters GetQueryFilters() { diff --git a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs index eb05a54888..e5beaecc09 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs @@ -17,7 +17,7 @@ public CustomArticleService( IEntityRepository

repository, IJsonApiOptions jsonApiOptions, IRequestManager queryManager, - IPageManager pageManager, + IPageQueryService pageManager, IResourceGraph resourceGraph, IResourceHookExecutor resourceHookExecutor = null, ILoggerFactory loggerFactory = null diff --git a/src/Examples/JsonApiDotNetCoreExample/Startup.cs b/src/Examples/JsonApiDotNetCoreExample/Startup.cs index 047161e324..a784de13f6 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Startup.cs @@ -37,7 +37,7 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services) options.DefaultPageSize = 5; options.IncludeTotalRecordCount = true; options.EnableResourceHooks = true; - options.LoadDatabaseValues = true; + options.LoaDatabaseValues = true; }, discovery => discovery.AddCurrentAssembly()); diff --git a/src/JsonApiDotNetCore/Configuration/IGlobalLinksConfiguration.cs b/src/JsonApiDotNetCore/Configuration/IGlobalLinksConfiguration.cs index 599eee24ca..57e47d7494 100644 --- a/src/JsonApiDotNetCore/Configuration/IGlobalLinksConfiguration.cs +++ b/src/JsonApiDotNetCore/Configuration/IGlobalLinksConfiguration.cs @@ -4,9 +4,9 @@ namespace JsonApiDotNetCore.Configuration { public interface IGlobalLinksConfiguration { - bool RelativeLinks { get; set; } - Link RelationshipLinks { get; set; } - Link TopLevelLinks { get; set; } - Link ResourceLinks { get; set; } + bool RelativeLinks { get; } + Link RelationshipLinks { get; } + Link TopLevelLinks { get; } + Link ResourceLinks { get; } } } \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 2ec70cc331..cdc6a8a2e9 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Text; -using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Models; using Newtonsoft.Json; namespace JsonApiDotNetCore.Configuration { - public interface IJsonApiOptions + public interface IJsonApiOptions : IGlobalLinksConfiguration, ISerializerOptions { /// /// Whether or not database values should be included by default @@ -16,7 +11,7 @@ public interface IJsonApiOptions /// /// Defaults to . /// - bool LoadDatabaseValues { get; set; } + bool LoaDatabaseValues { get; set; } /// /// Whether or not the total-record count should be included in all document /// level meta objects. @@ -29,13 +24,14 @@ public interface IJsonApiOptions int DefaultPageSize { get; } bool ValidateModelState { get; } bool AllowClientGeneratedIds { get; } - JsonSerializerSettings SerializerSettings { get; } bool EnableOperations { get; set; } - Link DefaultRelationshipLinks { get; set; } - NullAttributeResponseBehavior NullAttributeResponseBehavior { get; set; } - bool RelativeLinks { get; set; } IResourceGraph ResourceGraph { get; set; } bool AllowCustomQueryParameters { get; set; } string Namespace { get; set; } } + + public interface ISerializerOptions + { + NullAttributeResponseBehavior NullAttributeResponseBehavior { get; set; } + } } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index 57a4f98396..05feb6fa69 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -4,17 +4,83 @@ using JsonApiDotNetCore.Graph; using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Models.Links; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; namespace JsonApiDotNetCore.Configuration { + /// /// Global options /// public class JsonApiOptions : IJsonApiOptions { + /// + /// Use relative links for all resources. + /// + /// + /// + /// options.RelativeLinks = true; + /// + /// + /// { + /// "type": "articles", + /// "id": "4309", + /// "relationships": { + /// "author": { + /// "links": { + /// "self": "/api/v1/articles/4309/relationships/author", + /// "related": "/api/v1/articles/4309/author" + /// } + /// } + /// } + /// } + /// + /// + public bool RelativeLinks { get; set; } = false; + + /// + /// Configures globally which links to show in the + /// object for a requested resource. Setting can be overriden per resource by + /// setting the option or on the + /// RelationshipAttribute in the class definition of your model. + /// + /// + /// + /// options.DefaultRelationshipLinks = Link.None; + /// + /// + /// { + /// "type": "articles", + /// "id": "4309", + /// "relationships": { + /// "author": { "data": { "type": "people", "id": "1234" } + /// } + /// } + /// } + /// + /// + public Link RelationshipLinks { get; set; } = Link.All; + + + /// + /// Configures globally which links to show in the + /// object for a requested resource. Setting can be overriden per resource by + /// setting the option. + /// + public Link TopLevelLinks { get; set; } = Link.All; + + + /// + /// Configures globally which links to show in the + /// object for a requested resource. Setting can be overriden per resource by + /// setting the option. + /// + public Link ResourceLinks { get; set; } = Link.All; /// /// Provides an interface for formatting resource names by convention @@ -49,7 +115,7 @@ public class JsonApiOptions : IJsonApiOptions /// /// Defaults to . /// - public bool LoadDatabaseValues { get; set; } = false; + public bool LoaDatabaseValues { get; set; } = false; /// /// The base URL Namespace @@ -94,50 +160,6 @@ public class JsonApiOptions : IJsonApiOptions [Obsolete("Use the standalone resourcegraph")] public IResourceGraph ResourceGraph { get; set; } - /// - /// Use relative links for all resources. - /// - /// - /// - /// options.RelativeLinks = true; - /// - /// - /// { - /// "type": "articles", - /// "id": "4309", - /// "relationships": { - /// "author": { - /// "links": { - /// "self": "/api/v1/articles/4309/relationships/author", - /// "related": "/api/v1/articles/4309/author" - /// } - /// } - /// } - /// } - /// - /// - public bool RelativeLinks { get; set; } - - /// - /// Which links to include in relationships. Defaults to . - /// - /// - /// - /// options.DefaultRelationshipLinks = Link.None; - /// - /// - /// { - /// "type": "articles", - /// "id": "4309", - /// "relationships": { - /// "author": {} - /// } - /// } - /// } - /// - /// - public Link DefaultRelationshipLinks { get; set; } = Link.All; - /// /// Whether or not to allow all custom query parameters. /// @@ -163,7 +185,7 @@ public class JsonApiOptions : IJsonApiOptions /// /// Whether or not to allow json:api v1.1 operation requests. /// This is a beta feature and there may be breaking changes - /// in subsequent releases. For now, it should be considered + /// in subsequent releases. For now, ijt should be considered /// experimental. /// /// diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 112db1c352..35e7719a5d 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -9,6 +9,7 @@ using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -26,6 +27,7 @@ public class DefaultEntityRepository where TEntity : class, IIdentifiable { private readonly IRequestManager _requestManager; + private readonly IUpdatedFields _updatedFields; private readonly DbContext _context; private readonly DbSet _dbSet; private readonly ILogger _logger; @@ -35,10 +37,12 @@ public class DefaultEntityRepository [Obsolete("Dont use jsonapicontext instantiation anymore")] public DefaultEntityRepository( + IUpdatedFields updatedFields, IJsonApiContext jsonApiContext, IDbContextResolver contextResolver, ResourceDefinition resourceDefinition = null) { + _updatedFields = updatedFields; _requestManager = jsonApiContext.RequestManager; _context = contextResolver.GetContext(); _dbSet = _context.Set(); @@ -49,13 +53,14 @@ public DefaultEntityRepository( [Obsolete("Dont use jsonapicontext instantiation anymore")] public DefaultEntityRepository( + IUpdatedFields updatedFields, ILoggerFactory loggerFactory, IJsonApiContext jsonApiContext, IDbContextResolver contextResolver, ResourceDefinition resourceDefinition = null) { + _updatedFields = updatedFields; _requestManager = jsonApiContext.RequestManager; - _context = contextResolver.GetContext(); _dbSet = _context.Set(); _jsonApiContext = jsonApiContext; @@ -75,7 +80,7 @@ public virtual IQueryable Select(IQueryable entities, List public virtual IQueryable Filter(IQueryable entities, FilterQuery filterQuery) { @@ -129,7 +134,7 @@ public virtual async Task GetAndIncludeAsync(TId id, string relationshi /// public virtual async Task CreateAsync(TEntity entity) { - foreach (var relationshipAttr in _requestManager.GetUpdatedRelationships()?.Keys) + foreach (var relationshipAttr in _updatedFields.RelationshipsToUpdate) { var trackedRelationshipValue = GetTrackedRelationshipValue(relationshipAttr, entity, out bool wasAlreadyTracked); LoadInverseRelationships(trackedRelationshipValue, relationshipAttr); @@ -192,11 +197,12 @@ private void LoadInverseRelationships(object trackedRelationshipValue, Relations private bool IsHasOneRelationship(string internalRelationshipName, Type type) { var relationshipAttr = _jsonApiContext.ResourceGraph.GetContextEntity(type).Relationships.SingleOrDefault(r => r.InternalRelationshipName == internalRelationshipName); - if(relationshipAttr != null) + if (relationshipAttr != null) { if (relationshipAttr is HasOneAttribute) return true; return false; - } else + } + else { // relationshipAttr is null when we don't put a [RelationshipAttribute] on the inverse navigation property. // In this case we use relfection to figure out what kind of relationship is pointing back. @@ -209,14 +215,13 @@ private bool IsHasOneRelationship(string internalRelationshipName, Type type) public void DetachRelationshipPointers(TEntity entity) { - foreach (var relationshipAttr in _requestManager.GetUpdatedRelationships().Keys) + foreach (var relationshipAttr in _updatedFields.RelationshipsToUpdate) { if (relationshipAttr is HasOneAttribute hasOneAttr) { var relationshipValue = GetEntityResourceSeparationValue(entity, hasOneAttr) ?? (IIdentifiable)hasOneAttr.GetValue(entity); if (relationshipValue == null) continue; _context.Entry(relationshipValue).State = EntityState.Detached; - } else { @@ -252,10 +257,10 @@ public virtual async Task UpdateAsync(TEntity updatedEntity) if (databaseEntity == null) return null; - foreach (var attr in _requestManager.GetUpdatedAttributes().Keys) + foreach (var attr in _updatedFields.AttributesToUpdate) attr.SetValue(databaseEntity, attr.GetValue(updatedEntity)); - foreach (var relationshipAttr in _requestManager.GetUpdatedRelationships()?.Keys) + foreach (var relationshipAttr in _updatedFields.RelationshipsToUpdate) { /// loads databasePerson.todoItems LoadCurrentRelationships(databaseEntity, relationshipAttr); @@ -374,21 +379,11 @@ public virtual IQueryable Include(IQueryable entities, string // variables mutated in recursive loop // TODO: make recursive method string internalRelationshipPath = null; - var entity = _requestManager.GetContextEntity(); + var entity = _requestManager.GetRequestResource(); for (var i = 0; i < relationshipChain.Length; i++) { var requestedRelationship = relationshipChain[i]; var relationship = entity.Relationships.FirstOrDefault(r => r.PublicRelationshipName == requestedRelationship); - if (relationship == null) - { - throw new JsonApiException(400, $"Invalid relationship {requestedRelationship} on {entity.EntityName}", - $"{entity.EntityName} does not have a relationship named {requestedRelationship}"); - } - - if (relationship.CanInclude == false) - { - throw new JsonApiException(400, $"Including the relationship {requestedRelationship} on {entity.EntityName} is not allowed"); - } internalRelationshipPath = (internalRelationshipPath == null) ? relationship.RelationshipPath @@ -572,18 +567,20 @@ public class DefaultEntityRepository where TEntity : class, IIdentifiable { public DefaultEntityRepository( + IUpdatedFields updatedFields, IJsonApiContext jsonApiContext, IDbContextResolver contextResolver, ResourceDefinition resourceDefinition = null) - : base(jsonApiContext, contextResolver, resourceDefinition) + : base(updatedFields, jsonApiContext, contextResolver, resourceDefinition) { } public DefaultEntityRepository( + IUpdatedFields updatedFields, ILoggerFactory loggerFactory, IJsonApiContext jsonApiContext, IDbContextResolver contextResolver, ResourceDefinition resourceDefinition = null) - : base(loggerFactory, jsonApiContext, contextResolver, resourceDefinition) + : base(updatedFields, loggerFactory, jsonApiContext, contextResolver, resourceDefinition) { } } } diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 57e5788b2c..0a6fc11d07 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -14,6 +14,8 @@ using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCore.Hooks; using JsonApiDotNetCore.Services; using JsonApiDotNetCore.Services.Operations; @@ -23,6 +25,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Mvc.Infrastructure; +using JsonApiDotNetCore.Serialization.Contracts; +using JsonApiDotNetCore.Internal.Contracts; namespace JsonApiDotNetCore.Extensions { @@ -147,10 +151,8 @@ public static void AddJsonApiInternals( this IServiceCollection services, JsonApiOptions jsonApiOptions) { - if (jsonApiOptions.ResourceGraph == null) - { - jsonApiOptions.ResourceGraph = jsonApiOptions.ResourceGraphBuilder.Build(); - } + + var graph = jsonApiOptions.ResourceGraph ?? jsonApiOptions.ResourceGraphBuilder.Build(); if (jsonApiOptions.ResourceGraph.UsesDbContext == false) { @@ -191,26 +193,40 @@ public static void AddJsonApiInternals( services.AddScoped(typeof(IResourceService<,>), typeof(EntityResourceService<,>)); services.AddScoped(); - services.AddScoped(); + services.AddScoped(typeof(IMetaBuilder<>), typeof(MetaBuilder<>)); + services.AddSingleton(jsonApiOptions); - services.AddScoped(); - services.AddSingleton(jsonApiOptions.ResourceGraph); - services.AddScoped(); + services.AddSingleton(jsonApiOptions); + services.AddSingleton(graph); services.AddSingleton(); + services.AddSingleton(graph); + + services.AddScoped(typeof(ServerSerializer<>)); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(typeof(GenericProcessor<>)); services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); if (jsonApiOptions.EnableResourceHooks) { @@ -219,7 +235,6 @@ public static void AddJsonApiInternals( services.AddTransient(typeof(IResourceHookExecutor), typeof(ResourceHookExecutor)); services.AddTransient(); } - //services.AddTransient(); services.AddScoped(); } diff --git a/src/JsonApiDotNetCore/Extensions/StringExtensions.cs b/src/JsonApiDotNetCore/Extensions/StringExtensions.cs index 24d5bc8d58..2f8839cc74 100644 --- a/src/JsonApiDotNetCore/Extensions/StringExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/StringExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Text; namespace JsonApiDotNetCore.Extensions @@ -15,7 +16,7 @@ public static string ToProperCase(this string str) { if ((chars[i]) == '-') { - i = i + 1; + i++; builder.Append(char.ToUpper(chars[i])); } else @@ -50,5 +51,16 @@ public static string Dasherize(this string str) } return str; } + + public static string Camelize(this string str) + { + return char.ToLowerInvariant(str[0]) + str.Substring(1); + } + + public static string NullIfEmpty(this string value) + { + if (value == "") return null; + return value; + } } } diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs index a8cf56a789..c585196056 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs @@ -6,23 +6,27 @@ using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Services; +using JsonApiDotNetCore.Serialization.Contracts; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; namespace JsonApiDotNetCore.Formatters { /// public class JsonApiReader : IJsonApiReader { - private readonly IJsonApiDeSerializer _deserializer; + private readonly IOperationsDeserializer _operationsDeserializer; + private readonly IJsonApiDeserializer _deserializer; private readonly IRequestManager _requestManager; private readonly ILogger _logger; - public JsonApiReader(IJsonApiDeSerializer deSerializer, IRequestManager requestManager, ILoggerFactory loggerFactory) + public JsonApiReader(IJsonApiDeserializer deserializer, + IOperationsDeserializer operationsDeserializer, + IRequestManager requestManager, + ILoggerFactory loggerFactory) { - _deserializer = deSerializer; + _deserializer = deserializer; + _operationsDeserializer = operationsDeserializer; _requestManager = requestManager; _logger = loggerFactory.CreateLogger(); } @@ -40,16 +44,14 @@ public Task ReadAsync(InputFormatterContext context) { var body = GetRequestBody(context.HttpContext.Request.Body); - object model = null; - if (_requestManager.IsRelationshipPath) + if( _requestManager.IsBulkRequest) { - model = _deserializer.DeserializeRelationship(body); - } - else - { - model = _deserializer.Deserialize(body); + var operations = _operationsDeserializer.Deserialize(body); + return InputFormatterResult.SuccessAsync(operations); } + object model = _deserializer.Deserialize(body); + if (model == null) { _logger?.LogError("An error occurred while de-serializing the payload"); diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs b/src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs index fcf0ac7850..fd55a2f0a1 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs @@ -1,6 +1,7 @@ using System; using System.Text; using System.Threading.Tasks; +using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Serialization; using Microsoft.AspNetCore.Mvc.Formatters; @@ -14,10 +15,10 @@ public class JsonApiWriter : IJsonApiWriter private readonly IJsonApiSerializer _serializer; public JsonApiWriter( - IJsonApiSerializer serializer, + IJsonApiSerializerFactory factory, ILoggerFactory loggerFactory) { - _serializer = serializer; + _serializer = factory.GetSerializer(); _logger = loggerFactory.CreateLogger(); } diff --git a/src/JsonApiDotNetCore/Hooks/Discovery/HooksDiscovery.cs b/src/JsonApiDotNetCore/Hooks/Discovery/HooksDiscovery.cs index 958a3e3ab2..519276dad9 100644 --- a/src/JsonApiDotNetCore/Hooks/Discovery/HooksDiscovery.cs +++ b/src/JsonApiDotNetCore/Hooks/Discovery/HooksDiscovery.cs @@ -66,7 +66,7 @@ void DiscoverImplementedHooksForModel() if (method.DeclaringType != parameterizedResourceDefinition) { implementedHooks.Add(hook); - var attr = method.GetCustomAttributes(true).OfType().SingleOrDefault(); + var attr = method.GetCustomAttributes(true).OfType().SingleOrDefault(); if (attr != null) { if (!_databaseValuesAttributeAllowed.Contains(hook)) diff --git a/src/JsonApiDotNetCore/Hooks/Discovery/LoadDatabaseValuesAttribute.cs b/src/JsonApiDotNetCore/Hooks/Discovery/LoadDatabaseValuesAttribute.cs index 6a47e9d2a0..1477cc0ec1 100644 --- a/src/JsonApiDotNetCore/Hooks/Discovery/LoadDatabaseValuesAttribute.cs +++ b/src/JsonApiDotNetCore/Hooks/Discovery/LoadDatabaseValuesAttribute.cs @@ -1,10 +1,10 @@ using System; namespace JsonApiDotNetCore.Hooks { - public class LoadDatabaseValues : Attribute + public class LoaDatabaseValues : Attribute { public readonly bool value; - public LoadDatabaseValues(bool mode = true) + public LoaDatabaseValues(bool mode = true) { value = mode; } diff --git a/src/JsonApiDotNetCore/Hooks/Execution/DiffableEntityHashSet.cs b/src/JsonApiDotNetCore/Hooks/Execution/DiffableEntityHashSet.cs index 24d9d52756..7ee4d37226 100644 --- a/src/JsonApiDotNetCore/Hooks/Execution/DiffableEntityHashSet.cs +++ b/src/JsonApiDotNetCore/Hooks/Execution/DiffableEntityHashSet.cs @@ -8,6 +8,7 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Services; namespace JsonApiDotNetCore.Hooks @@ -53,9 +54,9 @@ public DiffableEntityHashSet(HashSet requestEntities, internal DiffableEntityHashSet(IEnumerable requestEntities, IEnumerable databaseEntities, Dictionary relationships, - IRequestManager requestManager) + IUpdatedFields updatedFields) : this((HashSet)requestEntities, (HashSet)databaseEntities, TypeHelper.ConvertRelationshipDictionary(relationships), - TypeHelper.ConvertAttributeDictionary(requestManager.GetUpdatedAttributes(), (HashSet)requestEntities)) + TypeHelper.ConvertAttributeDictionary(updatedFields.AttributesToUpdate, (HashSet)requestEntities)) { } @@ -91,7 +92,7 @@ public IEnumerable> GetDiffs() private HashSet ThrowNoDbValuesError() { - throw new MemberAccessException("Cannot iterate over the diffs if the LoadDatabaseValues option is set to false"); + throw new MemberAccessException("Cannot iterate over the diffs if the LoaDatabaseValues option is set to false"); } } diff --git a/src/JsonApiDotNetCore/Hooks/Execution/HookExecutorHelper.cs b/src/JsonApiDotNetCore/Hooks/Execution/HookExecutorHelper.cs index 566b69cb15..0867c62a19 100644 --- a/src/JsonApiDotNetCore/Hooks/Execution/HookExecutorHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Execution/HookExecutorHelper.cs @@ -19,6 +19,7 @@ namespace JsonApiDotNetCore.Hooks /// internal class HookExecutorHelper : IHookExecutorHelper { + private readonly IdentifiableComparer _comparer = new IdentifiableComparer(); private readonly IJsonApiOptions _options; protected readonly IGenericProcessorFactory _genericProcessorFactory; protected readonly IResourceGraph _graph; @@ -117,7 +118,7 @@ public bool ShouldLoadDbValues(Type entityType, ResourceHook hook) } else { - return _options.LoadDatabaseValues; + return _options.LoaDatabaseValues; } } @@ -188,7 +189,7 @@ public Dictionary LoadImplicitlyAffected( dbDependentEntityList = (IList)relationshipValue; } var dbDependentEntityListCasted = dbDependentEntityList.Cast().ToList(); - if (existingDependentEntities != null) dbDependentEntityListCasted = dbDependentEntityListCasted.Except(existingDependentEntities.Cast(), ResourceHookExecutor.Comparer).ToList(); + if (existingDependentEntities != null) dbDependentEntityListCasted = dbDependentEntityListCasted.Except(existingDependentEntities.Cast(), _comparer).ToList(); if (dbDependentEntityListCasted.Any()) { diff --git a/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs b/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs index 3b85e071ae..1ab795b415 100644 --- a/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs +++ b/src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs @@ -10,29 +10,30 @@ using JsonApiDotNetCore.Services; using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Serialization; namespace JsonApiDotNetCore.Hooks { /// - internal class ResourceHookExecutor : IResourceHookExecutor + internal class ResourceHookExecutor : IResourceHookExecutor { - public static readonly IdentifiableComparer Comparer = new IdentifiableComparer(); - private readonly IRequestManager _requestManager; internal readonly IHookExecutorHelper _executorHelper; - protected readonly IJsonApiContext _context; + private readonly ITraversalHelper _traversalHelper; + private readonly IIncludedQueryService _includedQuery; + private readonly IUpdatedFields _updatedFields; private readonly IResourceGraph _graph; - private readonly TraversalHelper _traversalHelper; - public ResourceHookExecutor( - IHookExecutorHelper helper, - IResourceGraph resourceGraph, - IRequestManager requestManager) + IHookExecutorHelper executorHelper, + ITraversalHelper traversalHelper, + IUpdatedFields updatedFields, + IIncludedQueryService includedRelationships, + IResourceGraph resourceGraph) { - _requestManager = requestManager; - _executorHelper = helper; + _executorHelper = executorHelper; + _traversalHelper = traversalHelper; + _updatedFields = updatedFields; + _includedQuery = includedRelationships; _graph = resourceGraph; - _traversalHelper = new TraversalHelper(resourceGraph, requestManager); } /// @@ -40,12 +41,9 @@ public virtual void BeforeRead(ResourcePipeline pipeline, string string { var hookContainer = _executorHelper.GetResourceHookContainer(ResourceHook.BeforeRead); hookContainer?.BeforeRead(pipeline, false, stringId); - var contextEntity = _graph.GetContextEntity(typeof(TEntity)); var calledContainers = new List() { typeof(TEntity) }; - foreach (var relationshipPath in _requestManager.IncludedRelationships) - { - RecursiveBeforeRead(contextEntity, relationshipPath.Split('.').ToList(), pipeline, calledContainers); - } + foreach (var chain in _includedQuery.Get()) + RecursiveBeforeRead(chain, pipeline, calledContainers); } /// @@ -55,7 +53,7 @@ public virtual IEnumerable BeforeUpdate(IEnumerable e { var relationships = node.RelationshipsToNextLayer.Select(p => p.Attribute).ToArray(); var dbValues = LoadDbValues(typeof(TEntity), (IEnumerable)node.UniqueEntities, ResourceHook.BeforeUpdate, relationships); - var diff = new DiffableEntityHashSet(node.UniqueEntities, dbValues, node.PrincipalsToNextLayer(), _requestManager); + var diff = new DiffableEntityHashSet(node.UniqueEntities, dbValues, node.PrincipalsToNextLayer(), _updatedFields); IEnumerable updated = container.BeforeUpdate(diff, pipeline); node.UpdateUnique(updated); node.Reassign(entities); @@ -214,31 +212,19 @@ void Traverse(NodeLayer currentLayer, ResourceHook target, Action - void RecursiveBeforeRead(ContextEntity contextEntity, List relationshipChain, ResourcePipeline pipeline, List calledContainers) + void RecursiveBeforeRead(List relationshipChain, ResourcePipeline pipeline, List calledContainers) { - var target = relationshipChain.First(); - var relationship = contextEntity.Relationships.FirstOrDefault(r => r.PublicRelationshipName == target); - if (relationship == null) - { - throw new JsonApiException(400, $"Invalid relationship {target} on {contextEntity.EntityName}", - $"{contextEntity.EntityName} does not have a relationship named {target}"); - } - + var relationship = relationshipChain.First(); if (!calledContainers.Contains(relationship.DependentType)) { calledContainers.Add(relationship.DependentType); var container = _executorHelper.GetResourceHookContainer(relationship.DependentType, ResourceHook.BeforeRead); if (container != null) - { CallHook(container, ResourceHook.BeforeRead, new object[] { pipeline, true, null }); - } } relationshipChain.RemoveAt(0); if (relationshipChain.Any()) - { - - RecursiveBeforeRead(_graph.GetContextEntity(relationship.DependentType), relationshipChain, pipeline, calledContainers); - } + RecursiveBeforeRead(relationshipChain, pipeline, calledContainers); } /// diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/ChildNode.cs b/src/JsonApiDotNetCore/Hooks/Traversal/ChildNode.cs index 8b5a97b8c9..8a29d6c539 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/ChildNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/ChildNode.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; using DependentType = System.Type; @@ -13,6 +14,7 @@ namespace JsonApiDotNetCore.Hooks /// internal class ChildNode : INode where TEntity : class, IIdentifiable { + private readonly IdentifiableComparer _comparer = new IdentifiableComparer(); /// public DependentType EntityType { get; private set; } /// @@ -50,7 +52,7 @@ public void UpdateUnique(IEnumerable updated) List casted = updated.Cast().ToList(); foreach (var rpfl in _relationshipsFromPreviousLayer) { - rpfl.DependentEntities = new HashSet(rpfl.DependentEntities.Intersect(casted, ResourceHookExecutor.Comparer).Cast()); + rpfl.DependentEntities = new HashSet(rpfl.DependentEntities.Intersect(casted, _comparer).Cast()); } } @@ -72,12 +74,12 @@ public void Reassign(IEnumerable updated = null) if (currentValue is IEnumerable relationshipCollection) { - var newValue = relationshipCollection.Intersect(unique, ResourceHookExecutor.Comparer).Cast(proxy.DependentType); + var newValue = relationshipCollection.Intersect(unique, _comparer).Cast(proxy.DependentType); proxy.SetValue(principal, newValue); } else if (currentValue is IIdentifiable relationshipSingle) { - if (!unique.Intersect(new HashSet() { relationshipSingle }, ResourceHookExecutor.Comparer).Any()) + if (!unique.Intersect(new HashSet() { relationshipSingle }, _comparer).Any()) { proxy.SetValue(principal, null); } diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/RootNode.cs b/src/JsonApiDotNetCore/Hooks/Traversal/RootNode.cs index 1aa3c0eb8b..1c4d4d6c1a 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/RootNode.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/RootNode.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; namespace JsonApiDotNetCore.Hooks @@ -13,6 +14,7 @@ namespace JsonApiDotNetCore.Hooks /// internal class RootNode : INode where TEntity : class, IIdentifiable { + private readonly IdentifiableComparer _comparer = new IdentifiableComparer(); private readonly RelationshipProxy[] _allRelationshipsToNextLayer; private HashSet _uniqueEntities; public Type EntityType { get; internal set; } @@ -54,7 +56,7 @@ public RootNode(IEnumerable uniqueEntities, RelationshipProxy[] poplate public void UpdateUnique(IEnumerable updated) { var casted = updated.Cast().ToList(); - var intersected = _uniqueEntities.Intersect(casted, ResourceHookExecutor.Comparer).Cast(); + var intersected = _uniqueEntities.Intersect(casted, _comparer).Cast(); _uniqueEntities = new HashSet(intersected); } diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs b/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs index f8849f5a16..8feb959288 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs @@ -8,6 +8,7 @@ using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Serialization; using DependentType = System.Type; using PrincipalType = System.Type; @@ -20,12 +21,12 @@ namespace JsonApiDotNetCore.Hooks /// It creates nodes for each layer. /// Typically, the first layer is homogeneous (all entities have the same type), /// and further nodes can be mixed. - /// /// internal class TraversalHelper : ITraversalHelper { - private readonly IResourceGraph _graph; - private readonly IRequestManager _requestManager; + private readonly IdentifiableComparer _comparer = new IdentifiableComparer(); + private readonly IContextEntityProvider _provider; + private readonly IUpdatedFields _updatedFields; /// /// Keeps track of which entities has already been traversed through, to prevent /// infinite loops in eg cyclic data structures. @@ -37,11 +38,11 @@ internal class TraversalHelper : ITraversalHelper /// private readonly Dictionary RelationshipProxies = new Dictionary(); public TraversalHelper( - IResourceGraph graph, - IRequestManager requestManager) + IContextEntityProvider provider, + IUpdatedFields updatedFields) { - _requestManager = requestManager; - _graph = graph; + _updatedFields = updatedFields; + _provider = provider; } /// @@ -200,7 +201,7 @@ HashSet ProcessEntities(IEnumerable incomingEntities) /// The type to parse void RegisterRelationshipProxies(DependentType type) { - var contextEntity = _graph.GetContextEntity(type); + var contextEntity = _provider.GetContextEntity(type); foreach (RelationshipAttribute attr in contextEntity.Relationships) { if (!attr.CanInclude) continue; @@ -208,8 +209,8 @@ void RegisterRelationshipProxies(DependentType type) { DependentType dependentType = GetDependentTypeFromRelationship(attr); bool isContextRelation = false; - var relationshipsToUpdate = _requestManager.GetUpdatedRelationships(); - if (relationshipsToUpdate != null) isContextRelation = relationshipsToUpdate.ContainsKey(attr); + var relationshipsToUpdate = _updatedFields.RelationshipsToUpdate; + if (relationshipsToUpdate != null) isContextRelation = relationshipsToUpdate.Contains(attr); var proxy = new RelationshipProxy(attr, dependentType, isContextRelation); RelationshipProxies[attr] = proxy; } @@ -252,7 +253,7 @@ HashSet GetProcessedEntities(Type entityType) /// Entity type. HashSet UniqueInTree(IEnumerable entities, Type entityType) where TEntity : class, IIdentifiable { - var newEntities = entities.Except(GetProcessedEntities(entityType), ResourceHookExecutor.Comparer).Cast(); + var newEntities = entities.Except(GetProcessedEntities(entityType), _comparer).Cast(); return new HashSet(newEntities); } diff --git a/src/JsonApiDotNetCore/Internal/ContextEntity.cs b/src/JsonApiDotNetCore/Internal/ContextEntity.cs index 867a04350c..d873da893a 100644 --- a/src/JsonApiDotNetCore/Internal/ContextEntity.cs +++ b/src/JsonApiDotNetCore/Internal/ContextEntity.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Models.Links; namespace JsonApiDotNetCore.Internal { @@ -30,18 +32,44 @@ public string EntityName { public Type ResourceType { get; set; } /// - /// Exposed resource attributes + /// Exposed resource attributes. + /// See https://jsonapi.org/format/#document-resource-object-attributes. /// public List Attributes { get; set; } /// - /// Exposed resource relationships + /// Exposed resource relationships. + /// See https://jsonapi.org/format/#document-resource-object-relationships /// public List Relationships { get; set; } + private List _fields; + public List Fields { get { _fields = _fields ?? Attributes.Cast().Concat(Relationships).ToList(); return _fields; } } + + /// + /// Configures which links to show in the + /// object for this resource. If set to , + /// the configuration will be read from . + /// Defaults to . + /// + public Link TopLevelLinks { get; internal set; } = Link.NotConfigured; + /// - /// Links to include in resource responses + /// Configures which links to show in the + /// object for this resource. If set to , + /// the configuration will be read from . + /// Defaults to . /// - public Link Links { get; set; } = Link.All; + public Link ResourceLinks { get; internal set; } = Link.NotConfigured; + + /// + /// Configures which links to show in the + /// for all relationships of the resource for which this attribute was instantiated. + /// If set to , the configuration will + /// be read from or + /// . Defaults to . + /// + public Link RelationshipLinks { get; internal set; } = Link.NotConfigured; + } } diff --git a/src/JsonApiDotNetCore/Internal/Contracts/IContextEntityProvider.cs b/src/JsonApiDotNetCore/Internal/Contracts/IContextEntityProvider.cs index cc174ec94f..cf438f9667 100644 --- a/src/JsonApiDotNetCore/Internal/Contracts/IContextEntityProvider.cs +++ b/src/JsonApiDotNetCore/Internal/Contracts/IContextEntityProvider.cs @@ -1,6 +1,23 @@ -namespace JsonApiDotNetCore.Internal.Contracts +using System; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Internal.Contracts { public interface IContextEntityProvider { + /// + /// Get the resource metadata by the DbSet property name + /// + ContextEntity GetContextEntity(string exposedResourceName); + + /// + /// Get the resource metadata by the resource type + /// + ContextEntity GetContextEntity(Type resourceType); + + /// + /// Get the resource metadata by the resource type + /// + ContextEntity GetContextEntity() where TResource : class, IIdentifiable; } } \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs index 5d2780d607..ded97b615a 100644 --- a/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/Contracts/IResourceGraph.cs @@ -8,10 +8,8 @@ namespace JsonApiDotNetCore.Internal.Contracts /// /// A cache for the models in entity core /// - public interface IResourceGraph + public interface IResourceGraph : IContextEntityProvider { - - RelationshipAttribute GetInverseRelationship(RelationshipAttribute relationship); /// /// Gets the value of the navigation property, defined by the relationshipName, @@ -31,13 +29,6 @@ public interface IResourceGraph /// object GetRelationship(TParent resource, string propertyName); - /// - /// Get the entity type based on a string - /// - /// - /// The context entity from the resource graph - ContextEntity GetEntityType(string entityName); - /// /// Gets the value of the navigation property (defined by the ) /// on the provided instance. @@ -66,16 +57,6 @@ public interface IResourceGraph /// string GetRelationshipName(string relationshipName); - /// - /// Get the resource metadata by the DbSet property name - /// - ContextEntity GetContextEntity(string dbSetName); - - /// - /// Get the resource metadata by the resource type - /// - ContextEntity GetContextEntity(Type entityType); - /// /// Get the public attribute name for a type based on the internal attribute name. /// diff --git a/src/JsonApiDotNetCore/Internal/DasherizedRoutingConvention.cs b/src/JsonApiDotNetCore/Internal/DasherizedRoutingConvention.cs index edb7e2444a..21033838d4 100644 --- a/src/JsonApiDotNetCore/Internal/DasherizedRoutingConvention.cs +++ b/src/JsonApiDotNetCore/Internal/DasherizedRoutingConvention.cs @@ -7,10 +7,10 @@ namespace JsonApiDotNetCore.Internal { - public class DasherizedRoutingConvention : IApplicationModelConvention + public class CamelizedRoutingConvention : IApplicationModelConvention { private readonly string _namespace; - public DasherizedRoutingConvention(string nspace) + public CamelizedRoutingConvention(string nspace) { _namespace = nspace; } @@ -19,10 +19,10 @@ public void Apply(ApplicationModel application) { foreach (var controller in application.Controllers) { - if (IsDasherizedJsonApiController(controller) == false) + if (IsCamelizedJsonApiController(controller) == false) continue; - var template = $"{_namespace}/{controller.ControllerName.Dasherize()}"; + var template = $"{_namespace}/{controller.ControllerName.Camelize()}"; controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel { Template = template @@ -30,7 +30,7 @@ public void Apply(ApplicationModel application) } } - private bool IsDasherizedJsonApiController(ControllerModel controller) + private bool IsCamelizedJsonApiController(ControllerModel controller) { var type = controller.ControllerType; var notDisabled = type.GetCustomAttribute() == null; diff --git a/src/JsonApiDotNetCore/Internal/IdentifiableComparer.cs b/src/JsonApiDotNetCore/Internal/IdentifiableComparer.cs index a830e2aec5..273f5f5d51 100644 --- a/src/JsonApiDotNetCore/Internal/IdentifiableComparer.cs +++ b/src/JsonApiDotNetCore/Internal/IdentifiableComparer.cs @@ -8,9 +8,9 @@ namespace JsonApiDotNetCore.Internal /// /// Compares `IIdentifiable` with each other based on ID /// - /// The type to compare public class IdentifiableComparer : IEqualityComparer { + internal static readonly IdentifiableComparer Instance = new IdentifiableComparer(); public bool Equals(IIdentifiable x, IIdentifiable y) { return x.StringId == y.StringId; diff --git a/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs b/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs index 44d39ba002..259ca84ee9 100644 --- a/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs @@ -54,12 +54,12 @@ public string GetPropertyPath() private AttrAttribute GetAttribute(string attribute) { - return _requestManager.GetContextEntity().Attributes.FirstOrDefault(attr => attr.Is(attribute)); + return _requestManager.GetRequestResource().Attributes.FirstOrDefault(attr => attr.Is(attribute)); } private RelationshipAttribute GetRelationship(string propertyName) { - return _requestManager.GetContextEntity().Relationships.FirstOrDefault(r => r.Is(propertyName)); + return _requestManager.GetRequestResource().Relationships.FirstOrDefault(r => r.Is(propertyName)); } private AttrAttribute GetAttribute(RelationshipAttribute relationship, string attribute) diff --git a/src/JsonApiDotNetCore/Internal/Query/RelatedAttrFilterQuery.cs b/src/JsonApiDotNetCore/Internal/Query/RelatedAttrFilterQuery.cs index d27a03d349..af744135e7 100644 --- a/src/JsonApiDotNetCore/Internal/Query/RelatedAttrFilterQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/RelatedAttrFilterQuery.cs @@ -17,7 +17,7 @@ public RelatedAttrFilterQuery( filterQuery: filterQuery) { if (Relationship == null) - throw new JsonApiException(400, $"{filterQuery.Relationship} is not a valid relationship on {requestManager.GetContextEntity().EntityName}."); + throw new JsonApiException(400, $"{filterQuery.Relationship} is not a valid relationship on {requestManager.GetRequestResource().EntityName}."); if (Attribute == null) throw new JsonApiException(400, $"'{filterQuery.Attribute}' is not a valid attribute."); diff --git a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs index 8cb22f1030..b1b408b3bd 100644 --- a/src/JsonApiDotNetCore/Internal/ResourceGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ResourceGraph.cs @@ -36,11 +36,6 @@ public ResourceGraph(List entities, bool usesDbContext) Instance = this; } - public ContextEntity GetEntityType(string entityName) - { - return Entities.Where(e => e.EntityName == entityName).FirstOrDefault(); - } - // eventually, this is the planned public constructor // to avoid breaking changes, we will be leaving the original constructor in place // until the context graph validation process is completed @@ -57,14 +52,6 @@ internal ResourceGraph(List entities, bool usesDbContext, List public bool UsesDbContext { get; } - /// - public ContextEntity GetContextEntity(string entityName) - => Entities.SingleOrDefault(e => string.Equals(e.EntityName, entityName, StringComparison.OrdinalIgnoreCase)); - - /// - public ContextEntity GetContextEntity(Type entityType) - => Entities.SingleOrDefault(e => e.EntityType == entityType); - /// public object GetRelationship(TParent entity, string relationshipName) { @@ -151,5 +138,16 @@ public ContextEntity GetEntityFromControllerName(string controllerName) return Entities.FirstOrDefault(e => e.EntityName.ToLower().Replace("-", "") == controllerName.ToLower()); } } + + /// + public ContextEntity GetContextEntity(string entityName) + => Entities.SingleOrDefault(e => string.Equals(e.EntityName, entityName, StringComparison.OrdinalIgnoreCase)); + + /// + public ContextEntity GetContextEntity(Type entityType) + => Entities.SingleOrDefault(e => e.EntityType == entityType); + /// + public ContextEntity GetContextEntity() where TResource : class, IIdentifiable + => GetContextEntity(typeof(TResource)); } } diff --git a/src/JsonApiDotNetCore/Internal/TypeHelper.cs b/src/JsonApiDotNetCore/Internal/TypeHelper.cs index f4e5c9dff0..47024c63c0 100644 --- a/src/JsonApiDotNetCore/Internal/TypeHelper.cs +++ b/src/JsonApiDotNetCore/Internal/TypeHelper.cs @@ -17,22 +17,28 @@ public static IList ConvertCollection(IEnumerable collection, Type targe list.Add(ConvertType(item, targetType)); return list; } - + public static bool IsNullable(Type type) + { + return (!type.IsValueType || Nullable.GetUnderlyingType(type) != null); + } public static object ConvertType(object value, Type type) { + if (value == null && !IsNullable(type)) + throw new FormatException($"Cannot convert null to a non-nullable type"); + if (value == null) return null; - var valueType = value.GetType(); + Type typeOfValue = value.GetType(); try { - if (valueType == type || type.IsAssignableFrom(valueType)) + if (typeOfValue == type || type.IsAssignableFrom(typeOfValue)) return value; type = Nullable.GetUnderlyingType(type) ?? type; - var stringValue = value.ToString(); + var stringValue = value?.ToString(); if (string.IsNullOrEmpty(stringValue)) return GetDefaultType(type); @@ -43,7 +49,6 @@ public static object ConvertType(object value, Type type) if (type == typeof(DateTimeOffset)) return DateTimeOffset.Parse(stringValue); - if (type == typeof(TimeSpan)) return TimeSpan.Parse(stringValue); @@ -54,7 +59,7 @@ public static object ConvertType(object value, Type type) } catch (Exception e) { - throw new FormatException($"{ valueType } cannot be converted to { type }", e); + throw new FormatException($"{ typeOfValue } cannot be converted to { type }", e); } } @@ -157,9 +162,9 @@ public static Dictionary> ConvertRelat /// /// /// - public static Dictionary> ConvertAttributeDictionary(Dictionary attributes, HashSet entities) + public static Dictionary> ConvertAttributeDictionary(List attributes, HashSet entities) { - return attributes?.ToDictionary(p => p.Key.PropertyInfo, p => entities); + return attributes?.ToDictionary(attr => attr.PropertyInfo, attr => entities); } /// diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index 1ae5427196..fe22326bbd 100644 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -44,4 +44,12 @@ + + + + + + + + diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs index 6a24b652a6..634a5fe646 100644 --- a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs @@ -1,10 +1,7 @@ -using System; using System.Collections.Generic; -using System.Text; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Query; -using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; @@ -12,8 +9,6 @@ namespace JsonApiDotNetCore.Managers.Contracts { public interface IRequestManager : IQueryRequest { - Dictionary GetUpdatedAttributes(); - Dictionary GetUpdatedRelationships(); /// /// The request namespace. This may be an absolute or relative path /// depending upon the configuration. @@ -45,13 +40,14 @@ public interface IRequestManager : IQueryRequest /// Sets the current context entity for this entire request /// /// - void SetContextEntity(ContextEntity contextEntityCurrent); + void SetRequestResource(ContextEntity contextEntityCurrent); - ContextEntity GetContextEntity(); + ContextEntity GetRequestResource(); /// /// Which query params are filtered /// QueryParams DisabledQueryParams { get; set; } + bool IsBulkRequest { get; set; } } } diff --git a/src/JsonApiDotNetCore/Managers/IUpdatedFieldManager.cs b/src/JsonApiDotNetCore/Managers/IUpdatedFieldManager.cs index c760838975..4dbad4b97d 100644 --- a/src/JsonApiDotNetCore/Managers/IUpdatedFieldManager.cs +++ b/src/JsonApiDotNetCore/Managers/IUpdatedFieldManager.cs @@ -3,16 +3,16 @@ namespace JsonApiDotNetCore.Serialization { - - public interface IUpdatedFieldsManager + public interface IUpdatedFields { List AttributesToUpdate { get; set; } List RelationshipsToUpdate { get; set; } } - public interface IUpdatedFieldManager_ProposalWithDictionaries + public class UpdatedFields: IUpdatedFields { - Dictionary> AttributesToUpdate { get; set; } - Dictionary> RelationshipsToUpdate { get; set; } + public List AttributesToUpdate { get; set; } = new List(); + public List RelationshipsToUpdate { get; set; } = new List(); } + } diff --git a/src/JsonApiDotNetCore/Managers/RequestManager.cs b/src/JsonApiDotNetCore/Managers/RequestManager.cs index 8aad3794de..997d7d208f 100644 --- a/src/JsonApiDotNetCore/Managers/RequestManager.cs +++ b/src/JsonApiDotNetCore/Managers/RequestManager.cs @@ -11,22 +11,6 @@ namespace JsonApiDotNetCore.Managers { - public class UpdatesContainer - { - /// - /// The attributes that were included in a PATCH request. - /// Only the attributes in this dictionary should be updated. - /// - public Dictionary Attributes { get; set; } = new Dictionary(); - - /// - /// Any relationships that were included in a PATCH request. - /// Only the relationships in this dictionary should be updated. - /// - public Dictionary Relationships { get; } = new Dictionary(); - - } - class RequestManager : IRequestManager { private ContextEntity _contextEntity; @@ -35,26 +19,16 @@ class RequestManager : IRequestManager public string BasePath { get; set; } public List IncludedRelationships { get; set; } public QuerySet QuerySet { get; set; } - public PageManager PageManager { get; set; } + public PageQueryService PageManager { get; set; } public IQueryCollection FullQuerySet { get; set; } public QueryParams DisabledQueryParams { get; set; } public bool IsRelationshipPath { get; set; } public Dictionary AttributesToUpdate { get; set; } - /// - /// Contains all the information you want about any update occuring - /// - private UpdatesContainer _updatesContainer { get; set; } = new UpdatesContainer(); + public Dictionary RelationshipsToUpdate { get; set; } + public bool IsBulkRequest { get; set; } = false; - public Dictionary GetUpdatedAttributes() - { - return _updatesContainer.Attributes; - } - public Dictionary GetUpdatedRelationships() - { - return _updatesContainer.Relationships; - } public List GetFields() { return QuerySet?.Fields; @@ -64,14 +38,19 @@ public List GetRelationships() { return QuerySet?.IncludedRelationships; } - public ContextEntity GetContextEntity() + + /// s + /// The main resource of the request. + /// + /// + public ContextEntity GetRequestResource() { return _contextEntity; } - public void SetContextEntity(ContextEntity contextEntityCurrent) + public void SetRequestResource(ContextEntity requestResource) { - _contextEntity = contextEntityCurrent; + _contextEntity = requestResource; } } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs index 9e6becbcde..ea3e54623e 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs @@ -13,16 +13,15 @@ namespace JsonApiDotNetCore.Middleware { public class JsonApiActionFilter : IActionFilter { - private readonly IJsonApiContext _jsonApiContext; private readonly IResourceGraph _resourceGraph; private readonly IRequestManager _requestManager; - private readonly IPageManager _pageManager; + private readonly IPageQueryService _pageManager; private readonly IQueryParser _queryParser; private readonly IJsonApiOptions _options; private HttpContext _httpContext; public JsonApiActionFilter(IResourceGraph resourceGraph, IRequestManager requestManager, - IPageManager pageManager, + IPageQueryService pageManager, IQueryParser queryParser, IJsonApiOptions options) { @@ -43,7 +42,7 @@ public void OnActionExecuting(ActionExecutingContext context) // the contextEntity is null eg when we're using a non-JsonApiDotNetCore route. if (contextEntityCurrent != null) { - _requestManager.SetContextEntity(contextEntityCurrent); + _requestManager.SetRequestResource(contextEntityCurrent); _requestManager.BasePath = GetBasePath(contextEntityCurrent.EntityName); HandleUriParameters(); } diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index a540811dfa..ac1766579a 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -1,11 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Services; using Microsoft.AspNetCore.Http; @@ -22,12 +18,8 @@ namespace JsonApiDotNetCore.Middleware public class RequestMiddleware { private readonly RequestDelegate _next; - private IResourceGraph _resourceGraph; private HttpContext _httpContext; private IRequestManager _requestManager; - private IPageManager _pageManager; - private IQueryParser _queryParser; - private IJsonApiOptions _options; public RequestMiddleware(RequestDelegate next) { @@ -35,35 +27,25 @@ public RequestMiddleware(RequestDelegate next) } public async Task Invoke(HttpContext httpContext, - IJsonApiContext jsonApiContext, - IResourceGraph resourceGraph, - IRequestManager requestManager, - IPageManager pageManager, - IQueryParser queryParser, - IJsonApiOptions options - ) + IRequestManager requestManager) { _httpContext = httpContext; - _resourceGraph = resourceGraph; _requestManager = requestManager; - _pageManager = pageManager; - _queryParser = queryParser; - _options = options; if (IsValid()) { - - // HACK: this currently results in allocation of - // objects that may or may not be used and even double allocation - // since the JsonApiContext is using field initializers - // Need to work on finding a better solution. - jsonApiContext.BeginOperation(); _requestManager.IsRelationshipPath = PathIsRelationship(); - + _requestManager.IsBulkRequest = PathIsBulk(); await _next(httpContext); } } + private bool PathIsBulk() + { + var actionName = (string)_httpContext.GetRouteData().Values["action"]; + return actionName.ToLower().Contains("bulk"); + } + protected bool PathIsRelationship() { var actionName = (string)_httpContext.GetRouteData().Values["action"]; diff --git a/src/JsonApiDotNetCore/Models/AttrAttribute.cs b/src/JsonApiDotNetCore/Models/AttrAttribute.cs index 6d48098192..da3a9d4631 100644 --- a/src/JsonApiDotNetCore/Models/AttrAttribute.cs +++ b/src/JsonApiDotNetCore/Models/AttrAttribute.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.Models { - public class AttrAttribute : Attribute + public class AttrAttribute : Attribute, IResourceField { /// /// Defines a public attribute exposed by the API @@ -34,6 +34,9 @@ public AttrAttribute(string publicName = null, bool isImmutable = false, bool is IsSortable = isSortable; } + public string ExposedInternalMemberName => InternalAttributeName; + + /// /// Do not use this overload in your applications. /// Provides a method for instantiating instances of `AttrAttribute` and specifying diff --git a/src/JsonApiDotNetCore/Models/HasManyAttribute.cs b/src/JsonApiDotNetCore/Models/HasManyAttribute.cs index 37fe42c9af..a69cb82c56 100644 --- a/src/JsonApiDotNetCore/Models/HasManyAttribute.cs +++ b/src/JsonApiDotNetCore/Models/HasManyAttribute.cs @@ -1,4 +1,5 @@ using System; +using JsonApiDotNetCore.Models.Links; namespace JsonApiDotNetCore.Models { @@ -9,7 +10,7 @@ public class HasManyAttribute : RelationshipAttribute /// /// /// The relationship name as exposed by the API - /// Which links are available. Defaults to + /// Which links are available. Defaults to /// Whether or not this relationship can be included using the ?include=public-name query string /// The name of the entity mapped property, defaults to null /// @@ -24,8 +25,8 @@ public class HasManyAttribute : RelationshipAttribute /// /// /// - public HasManyAttribute(string publicName = null, Link documentLinks = Link.All, bool canInclude = true, string mappedBy = null, string inverseNavigationProperty = null) - : base(publicName, documentLinks, canInclude, mappedBy) + public HasManyAttribute(string publicName = null, Link relationshipLinks = Link.All, bool canInclude = true, string mappedBy = null, string inverseNavigationProperty = null) + : base(publicName, relationshipLinks, canInclude, mappedBy) { InverseNavigation = inverseNavigationProperty; } diff --git a/src/JsonApiDotNetCore/Models/HasManyThroughAttribute.cs b/src/JsonApiDotNetCore/Models/HasManyThroughAttribute.cs index c6f5bc47db..235607c6d3 100644 --- a/src/JsonApiDotNetCore/Models/HasManyThroughAttribute.cs +++ b/src/JsonApiDotNetCore/Models/HasManyThroughAttribute.cs @@ -1,6 +1,6 @@ using System; using System.Reflection; -using System.Security; +using JsonApiDotNetCore.Models.Links; namespace JsonApiDotNetCore.Models { @@ -29,7 +29,7 @@ public class HasManyThroughAttribute : HasManyAttribute /// /// /// The name of the navigation property that will be used to get the HasMany relationship - /// Which links are available. Defaults to + /// Which links are available. Defaults to /// Whether or not this relationship can be included using the ?include=public-name query string /// The name of the entity mapped property, defaults to null /// @@ -38,8 +38,8 @@ public class HasManyThroughAttribute : HasManyAttribute /// [HasManyThrough(nameof(ArticleTags), documentLinks: Link.All, canInclude: true)] /// /// - public HasManyThroughAttribute(string internalThroughName, Link documentLinks = Link.All, bool canInclude = true, string mappedBy = null) - : base(null, documentLinks, canInclude, mappedBy) + public HasManyThroughAttribute(string internalThroughName, Link relationshipLinks = Link.All, bool canInclude = true, string mappedBy = null) + : base(null, relationshipLinks, canInclude, mappedBy) { InternalThroughName = internalThroughName; } diff --git a/src/JsonApiDotNetCore/Models/HasOneAttribute.cs b/src/JsonApiDotNetCore/Models/HasOneAttribute.cs index 54a024e703..e9d0883180 100644 --- a/src/JsonApiDotNetCore/Models/HasOneAttribute.cs +++ b/src/JsonApiDotNetCore/Models/HasOneAttribute.cs @@ -1,5 +1,6 @@ using System; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Models.Links; namespace JsonApiDotNetCore.Models { @@ -10,7 +11,8 @@ public class HasOneAttribute : RelationshipAttribute /// /// /// The relationship name as exposed by the API - /// Which links are available. Defaults to + /// Enum to set which links should be outputted for this relationship. Defaults to which means that the configuration in + /// or is used. /// Whether or not this relationship can be included using the ?include=public-name query string /// The foreign key property name. Defaults to "{RelationshipName}Id" /// The name of the entity mapped property, defaults to null @@ -26,11 +28,10 @@ public class HasOneAttribute : RelationshipAttribute /// public int AuthorKey { get; set; } /// } /// - /// /// - public HasOneAttribute(string publicName = null, Link documentLinks = Link.All, bool canInclude = true, string withForeignKey = null, string mappedBy = null, string inverseNavigationProperty = null) + public HasOneAttribute(string publicName = null, Link links = Link.NotConfigured, bool canInclude = true, string withForeignKey = null, string mappedBy = null, string inverseNavigationProperty = null) - : base(publicName, documentLinks, canInclude, mappedBy) + : base(publicName, links, canInclude, mappedBy) { _explicitIdentifiablePropertyName = withForeignKey; InverseNavigation = inverseNavigationProperty; @@ -57,8 +58,13 @@ public override void SetValue(object resource, object newValue) // we set the foreignKey to null. We could also set the actual property to null, // but then we would first need to load the current relationship, which requires an extra query. if (newValue == null) propertyName = IdentifiablePropertyName; - - var propertyInfo = resource.GetType().GetProperty(propertyName); + var resourceType = resource.GetType(); + var propertyInfo = resourceType.GetProperty(propertyName); + if (propertyInfo == null) + { + // we can't set the FK to null because there isn't any. + propertyInfo = resourceType.GetProperty(RelationshipPath); + } propertyInfo.SetValue(resource, newValue); } diff --git a/src/JsonApiDotNetCore/Models/IHasMeta.cs b/src/JsonApiDotNetCore/Models/IHasMeta.cs index 50d86e6034..34efffdabd 100644 --- a/src/JsonApiDotNetCore/Models/IHasMeta.cs +++ b/src/JsonApiDotNetCore/Models/IHasMeta.cs @@ -5,6 +5,6 @@ namespace JsonApiDotNetCore.Models { public interface IHasMeta { - Dictionary GetMeta(IJsonApiContext context); + Dictionary GetMeta(); } } diff --git a/src/JsonApiDotNetCore/Models/IResourceField.cs b/src/JsonApiDotNetCore/Models/IResourceField.cs index d4ffa5c4a0..90ec306c93 100644 --- a/src/JsonApiDotNetCore/Models/IResourceField.cs +++ b/src/JsonApiDotNetCore/Models/IResourceField.cs @@ -1,6 +1,7 @@ namespace JsonApiDotNetCore.Models { - internal interface IResourceField + public interface IResourceField { + string ExposedInternalMemberName { get; } } } \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Models/JsonApi/Document.cs b/src/JsonApiDotNetCore/Models/JsonApi/Document.cs index c8aa3601e2..0b74574ee9 100644 --- a/src/JsonApiDotNetCore/Models/JsonApi/Document.cs +++ b/src/JsonApiDotNetCore/Models/JsonApi/Document.cs @@ -1,11 +1,30 @@ +using System.Collections.Generic; using JsonApiDotNetCore.Models.Links; using Newtonsoft.Json; namespace JsonApiDotNetCore.Models { - public class Document : DocumentBase + /// + /// https://jsonapi.org/format/#document-structure + /// + public class Document : ExposableData { - [JsonProperty("data")] - public ResourceObject Data { get; set; } + /// + /// see "meta" in https://jsonapi.org/format/#document-top-level + /// + [JsonProperty("meta", NullValueHandling = NullValueHandling.Ignore)] + public Dictionary Meta { get; set; } + + /// + /// see "links" in https://jsonapi.org/format/#document-top-level + /// + [JsonProperty("links", NullValueHandling = NullValueHandling.Ignore)] + public TopLevelLinks Links { get; set; } + + /// + /// see "included" in https://jsonapi.org/format/#document-top-level + /// + [JsonProperty("included", NullValueHandling = NullValueHandling.Ignore, Order = 1)] + public List Included { get; set; } } } diff --git a/src/JsonApiDotNetCore/Models/JsonApi/ExposableData.cs b/src/JsonApiDotNetCore/Models/JsonApi/ExposableData.cs index 9f4c269c17..7fc8d8cdae 100644 --- a/src/JsonApiDotNetCore/Models/JsonApi/ExposableData.cs +++ b/src/JsonApiDotNetCore/Models/JsonApi/ExposableData.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace JsonApiDotNetCore.Models { - public class ExposableData + public class ExposableData where T : class { /// /// see "primary data" in https://jsonapi.org/format/#document-top-level. @@ -15,9 +16,16 @@ public class ExposableData /// /// see https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm /// - public bool ShouldSerializData() + /// + /// Moving this method to the derived class where it is needed only in the + /// case of would make more sense, but + /// Newtonsoft does not support this. + /// + public bool ShouldSerializeData() { - return IsPopulated; + if (GetType() == typeof(RelationshipData)) + return IsPopulated; + return true; } /// @@ -43,6 +51,8 @@ public bool ShouldSerializData() /// internal bool IsPopulated { get; private set; } = false; + internal bool HasData { get { return IsPopulated && ((IsManyData && ManyData.Any()) || SingleData != null); } } + /// /// Gets the "single" or "many" data depending on which one was /// assigned in this document. diff --git a/src/JsonApiDotNetCore/Models/JsonApi/Identifiable.cs b/src/JsonApiDotNetCore/Models/JsonApi/Identifiable.cs index b62f31fe89..b85128444e 100644 --- a/src/JsonApiDotNetCore/Models/JsonApi/Identifiable.cs +++ b/src/JsonApiDotNetCore/Models/JsonApi/Identifiable.cs @@ -38,7 +38,7 @@ public string StringId protected virtual string GetStringId(object value) { if(value == null) - return string.Empty; + return string.Empty; // todo; investigate why not using null, because null would make more sense in serialization var type = typeof(T); var stringValue = value.ToString(); @@ -59,8 +59,9 @@ protected virtual string GetStringId(object value) /// protected virtual T GetTypedId(string value) { - var convertedValue = TypeHelper.ConvertType(value, typeof(T)); - return convertedValue == null ? default : (T)convertedValue; + if (value == null) + return default; + return (T)TypeHelper.ConvertType(value, typeof(T)); } } } diff --git a/src/JsonApiDotNetCore/Models/JsonApi/RelationshipData.cs b/src/JsonApiDotNetCore/Models/JsonApi/RelationshipData.cs index 2630750c87..65aa8d8f8c 100644 --- a/src/JsonApiDotNetCore/Models/JsonApi/RelationshipData.cs +++ b/src/JsonApiDotNetCore/Models/JsonApi/RelationshipData.cs @@ -6,60 +6,9 @@ namespace JsonApiDotNetCore.Models { - public class RelationshipData + public class RelationshipData : ExposableData { [JsonProperty("links", NullValueHandling = NullValueHandling.Ignore)] public RelationshipLinks Links { get; set; } - - [JsonProperty("data")] - public object ExposedData - { - get - { - if (ManyData != null) - return ManyData; - return SingleData; - } - set - { - if (value is JObject jObject) - SingleData = jObject.ToObject(); - else if (value is ResourceIdentifierObject dict) - SingleData = dict; - else - SetManyData(value); - } - } - - /// TODO check if behaviour is OK. - public bool ShouldSerializeExposedData() - { - return IsPopulated; - } - - private void SetManyData(object value) - { - IsHasMany = true; - if (value is JArray jArray) - ManyData = jArray.ToObject>(); - else - ManyData = (List)value; - } - - [JsonIgnore] - public List ManyData { get; set; } - - [JsonIgnore] - public ResourceIdentifierObject SingleData { get; set; } - - [JsonIgnore] - public bool IsHasMany { get; private set; } - - internal bool IsPopulated { get; set; } = false; - - internal bool Any() - { - return ((IsHasMany && ManyData.Any()) || SingleData != null); - } } } diff --git a/src/JsonApiDotNetCore/Models/JsonApi/ResourceIdentifierObject.cs b/src/JsonApiDotNetCore/Models/JsonApi/ResourceIdentifierObject.cs index a3b8abdaf1..aac5af98be 100644 --- a/src/JsonApiDotNetCore/Models/JsonApi/ResourceIdentifierObject.cs +++ b/src/JsonApiDotNetCore/Models/JsonApi/ResourceIdentifierObject.cs @@ -11,14 +11,20 @@ public ResourceIdentifierObject(string type, string id) Id = id; } - [JsonProperty("type")] + [JsonProperty("type", Order = -3)] public string Type { get; set; } - [JsonProperty("id")] + [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore, Order = -2)] public string Id { get; set; } [JsonIgnore] //[JsonProperty("lid")] public string LocalId { get; set; } + + + public override string ToString() + { + return $"(type: {Type}, id: {Id})"; + } } } diff --git a/src/JsonApiDotNetCore/Models/JsonApi/ResourceObject.cs b/src/JsonApiDotNetCore/Models/JsonApi/ResourceObject.cs index 961de7255c..89ff2e9e9c 100644 --- a/src/JsonApiDotNetCore/Models/JsonApi/ResourceObject.cs +++ b/src/JsonApiDotNetCore/Models/JsonApi/ResourceObject.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace JsonApiDotNetCore.Models -{ +{ public class ResourceObject : ResourceIdentifierObject { [JsonProperty("attributes", NullValueHandling = NullValueHandling.Ignore)] diff --git a/src/JsonApiDotNetCore/Models/Operations/Operation.cs b/src/JsonApiDotNetCore/Models/Operations/Operation.cs index 604643d231..be6310c0da 100644 --- a/src/JsonApiDotNetCore/Models/Operations/Operation.cs +++ b/src/JsonApiDotNetCore/Models/Operations/Operation.cs @@ -1,12 +1,22 @@ using System.Collections.Generic; +using JsonApiDotNetCore.Models.Links; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; namespace JsonApiDotNetCore.Models.Operations { - public class Operation : DocumentBase + public class Operation { + [JsonProperty("links", NullValueHandling = NullValueHandling.Ignore)] + public TopLevelLinks Links { get; set; } + + [JsonProperty("included", NullValueHandling = NullValueHandling.Ignore)] + public List Included { get; set; } + + [JsonProperty("meta", NullValueHandling = NullValueHandling.Ignore)] + public Dictionary Meta { get; set; } + [JsonProperty("op"), JsonConverter(typeof(StringEnumConverter))] public OperationCode Op { get; set; } diff --git a/src/JsonApiDotNetCore/Models/RelationshipAttribute.cs b/src/JsonApiDotNetCore/Models/RelationshipAttribute.cs index 3bd44f5030..83d8b4043c 100644 --- a/src/JsonApiDotNetCore/Models/RelationshipAttribute.cs +++ b/src/JsonApiDotNetCore/Models/RelationshipAttribute.cs @@ -1,19 +1,24 @@ using System; -using System.Runtime.CompilerServices; using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Models.Links; namespace JsonApiDotNetCore.Models { - public abstract class RelationshipAttribute : Attribute + public abstract class RelationshipAttribute : Attribute, IResourceField { - protected RelationshipAttribute(string publicName, Link documentLinks, bool canInclude, string mappedBy) + protected RelationshipAttribute(string publicName, Link relationshipLinks, bool canInclude, string mappedBy) { + if (relationshipLinks == Link.Paging) + throw new JsonApiSetupException($"{Link.Paging.ToString("g")} not allowed for argument {nameof(relationshipLinks)}"); + PublicRelationshipName = publicName; - DocumentLinks = documentLinks; + RelationshipLinks = relationshipLinks; CanInclude = canInclude; EntityPropertyName = mappedBy; } + public string ExposedInternalMemberName => InternalRelationshipName; public string PublicRelationshipName { get; internal set; } public string InternalRelationshipName { get; internal set; } public string InverseNavigation { get; internal set; } @@ -25,7 +30,7 @@ protected RelationshipAttribute(string publicName, Link documentLinks, bool canI /// /// /// - /// public List<Tag> Tags { get; set; } // Type => Tag + /// public List<Tag> Tags { get; sit; } // Type => Tag /// /// [Obsolete("Use property DependentType")] @@ -52,7 +57,12 @@ protected RelationshipAttribute(string publicName, Link documentLinks, bool canI public bool IsHasMany => GetType() == typeof(HasManyAttribute) || GetType().Inherits(typeof(HasManyAttribute)); public bool IsHasOne => GetType() == typeof(HasOneAttribute); - public Link DocumentLinks { get; } = Link.All; + + /// + /// Configures which links to show in the + /// object for this relationship. + /// + public Link RelationshipLinks { get; } public bool CanInclude { get; } public string EntityPropertyName { get; } @@ -119,5 +129,6 @@ public virtual bool Is(string publicRelationshipName) /// In all cases except the HasManyThrough relationships, this will just be the . /// public virtual string RelationshipPath => InternalRelationshipName; + } } diff --git a/src/JsonApiDotNetCore/Models/ResourceDefinition.cs b/src/JsonApiDotNetCore/Models/ResourceDefinition.cs index fb97cad55e..9961bb6d92 100644 --- a/src/JsonApiDotNetCore/Models/ResourceDefinition.cs +++ b/src/JsonApiDotNetCore/Models/ResourceDefinition.cs @@ -6,86 +6,46 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Reflection; +using JsonApiDotNetCore.Services; namespace JsonApiDotNetCore.Models { - public interface IResourceDefinition { - List GetOutputAttrs(object instance); + List GetAllowedAttributes(); + List GetAllowedRelationships(); } - /// /// exposes developer friendly hooks into how their resources are exposed. /// It is intended to improve the experience and reduce boilerplate for commonly required features. /// The goal of this class is to reduce the frequency with which developers have to override the /// service and repository layers. /// - /// The resource type - public class ResourceDefinition : IResourceDefinition, IResourceHookContainer where T : class, IIdentifiable + /// The resource type + public class ResourceDefinition : IResourceDefinition, IResourceHookContainer where TResource : class, IIdentifiable { private readonly ContextEntity _contextEntity; - internal readonly bool _instanceAttrsAreSpecified; - - private bool _requestCachedAttrsHaveBeenLoaded = false; - private List _requestCachedAttrs; - - public ResourceDefinition(IResourceGraph graph) + private readonly IExposedFieldExplorer _fieldExplorer; + private List _allowedAttributes; + private List _allowedRelationships; + public ResourceDefinition(IExposedFieldExplorer fieldExplorer, IResourceGraph graph) { - _contextEntity = graph.GetContextEntity(typeof(T)); - _instanceAttrsAreSpecified = InstanceOutputAttrsAreSpecified(); + _contextEntity = graph.GetContextEntity(typeof(TResource)); + _allowedAttributes = _contextEntity.Attributes; + _allowedRelationships = _contextEntity.Relationships; + _fieldExplorer = fieldExplorer; } - private bool InstanceOutputAttrsAreSpecified() + public ResourceDefinition(IResourceGraph graph) { - var derivedType = GetType(); - var methods = derivedType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance); - var instanceMethod = methods - .Where(m => - m.Name == nameof(OutputAttrs) - && m.GetParameters() - .FirstOrDefault() - ?.ParameterType == typeof(T)) - .FirstOrDefault(); - var declaringType = instanceMethod?.DeclaringType; - return declaringType == derivedType; + _allowedAttributes = _contextEntity.Attributes; + _allowedRelationships = _contextEntity.Relationships; + _contextEntity = graph.GetContextEntity(typeof(TResource)); } - /// - /// Remove an attribute - /// - /// the filter to execute - /// @TODO - /// - protected List Remove(Expression> filter, List from = null) - { - //@TODO: need to investigate options for caching these - from = from ?? _contextEntity.Attributes; - - // model => model.Attribute - if (filter.Body is MemberExpression memberExpression) - return _contextEntity.Attributes - .Where(a => a.InternalAttributeName != memberExpression.Member.Name) - .ToList(); - - // model => new { model.Attribute1, model.Attribute2 } - if (filter.Body is NewExpression newExpression) - { - var attributes = new List(); - foreach (var attr in _contextEntity.Attributes) - if (newExpression.Members.Any(m => m.Name == attr.InternalAttributeName) == false) - attributes.Add(attr); - - return attributes; - } - - throw new JsonApiException(500, - message: $"The expression returned by '{filter}' for '{GetType()}' is of type {filter.Body.GetType()}" - + " and cannot be used to select resource attributes. ", - detail: "The type must be a NewExpression. Example: article => new { article.Author }; "); - } + public List GetAllowedRelationships() => _allowedRelationships; + public List GetAllowedAttributes() => _allowedAttributes; /// /// Allows POST / PATCH requests to set the value of an @@ -96,38 +56,18 @@ protected List Remove(Expression> filter, List - protected virtual List OutputAttrs() => _contextEntity.Attributes; - - /// - /// Allows POST / PATCH requests to set the value of an - /// attribute, but exclude the attribute in the response - /// this might be used if the incoming value gets hashed or - /// encrypted prior to being persisted and this value should - /// never be sent back to the client. - /// - /// Called for every instance of a resource. - /// - protected virtual List OutputAttrs(T instance) => _contextEntity.Attributes; - - public List GetOutputAttrs(object instance) - => _instanceAttrsAreSpecified == false - ? GetOutputAttrs() - : OutputAttrs(instance as T); - - private List GetOutputAttrs() + public void HideAttributes(Expression> selector) { - if (_requestCachedAttrsHaveBeenLoaded == false) - { - _requestCachedAttrs = OutputAttrs(); - // the reason we don't just check for null is because we - // guarantee that OutputAttrs will be called once per - // request and null is a valid return value - _requestCachedAttrsHaveBeenLoaded = true; - } - - return _requestCachedAttrs; + var attributesToHide = _fieldExplorer.GetAttributes(selector); + _allowedAttributes = _allowedAttributes.Except(attributesToHide).ToList(); + } + public void HideRelationships(Expression> selector) + { + var relationshipsToHide = _fieldExplorer.GetRelationships(selector); + _allowedRelationships = _allowedRelationships.Except(relationshipsToHide).ToList(); } + /// /// Define a set of custom query expressions that can be applied /// instead of the default query behavior. A common use-case for this @@ -166,29 +106,29 @@ private List GetOutputAttrs() public virtual QueryFilters GetQueryFilters() => null; /// - public virtual void AfterCreate(HashSet entities, ResourcePipeline pipeline) { } + public virtual void AfterCreate(HashSet entities, ResourcePipeline pipeline) { } /// - public virtual void AfterRead(HashSet entities, ResourcePipeline pipeline, bool isIncluded = false) { } + public virtual void AfterRead(HashSet entities, ResourcePipeline pipeline, bool isIncluded = false) { } /// - public virtual void AfterUpdate(HashSet entities, ResourcePipeline pipeline) { } + public virtual void AfterUpdate(HashSet entities, ResourcePipeline pipeline) { } /// - public virtual void AfterDelete(HashSet entities, ResourcePipeline pipeline, bool succeeded) { } + public virtual void AfterDelete(HashSet entities, ResourcePipeline pipeline, bool succeeded) { } /// - public virtual void AfterUpdateRelationship(IRelationshipsDictionary entitiesByRelationship, ResourcePipeline pipeline) { } + public virtual void AfterUpdateRelationship(IRelationshipsDictionary entitiesByRelationship, ResourcePipeline pipeline) { } /// - public virtual IEnumerable BeforeCreate(IEntityHashSet entities, ResourcePipeline pipeline) { return entities; } + public virtual IEnumerable BeforeCreate(IEntityHashSet entities, ResourcePipeline pipeline) { return entities; } /// public virtual void BeforeRead(ResourcePipeline pipeline, bool isIncluded = false, string stringId = null) { } /// - public virtual IEnumerable BeforeUpdate(IDiffableEntityHashSet entities, ResourcePipeline pipeline) { return entities; } + public virtual IEnumerable BeforeUpdate(IDiffableEntityHashSet entities, ResourcePipeline pipeline) { return entities; } /// - public virtual IEnumerable BeforeDelete(IEntityHashSet entities, ResourcePipeline pipeline) { return entities; } + public virtual IEnumerable BeforeDelete(IEntityHashSet entities, ResourcePipeline pipeline) { return entities; } /// - public virtual IEnumerable BeforeUpdateRelationship(HashSet ids, IRelationshipsDictionary entitiesByRelationship, ResourcePipeline pipeline) { return ids; } + public virtual IEnumerable BeforeUpdateRelationship(HashSet ids, IRelationshipsDictionary entitiesByRelationship, ResourcePipeline pipeline) { return ids; } /// - public virtual void BeforeImplicitUpdateRelationship(IRelationshipsDictionary entitiesByRelationship, ResourcePipeline pipeline) { } + public virtual void BeforeImplicitUpdateRelationship(IRelationshipsDictionary entitiesByRelationship, ResourcePipeline pipeline) { } /// - public virtual IEnumerable OnReturn(HashSet entities, ResourcePipeline pipeline) { return entities; } + public virtual IEnumerable OnReturn(HashSet entities, ResourcePipeline pipeline) { return entities; } /// @@ -196,7 +136,7 @@ public virtual void BeforeImplicitUpdateRelationship(IRelationshipsDictionary /// method signature. /// See for usage details. /// - public class QueryFilters : Dictionary, FilterQuery, IQueryable>> { } + public class QueryFilters : Dictionary, FilterQuery, IQueryable>> { } /// /// Define a the default sort order if no sort key is provided. @@ -237,11 +177,12 @@ public class QueryFilters : Dictionary, FilterQuery, return null; } + /// /// This is an alias type intended to simplify the implementation's /// method signature. /// See for usage details. /// - public class PropertySortOrder : List<(Expression>, SortDirection)> { } + public class PropertySortOrder : List<(Expression>, SortDirection)> { } } } diff --git a/src/JsonApiDotNetCore/Models/SerializableFields.cs b/src/JsonApiDotNetCore/Models/SerializableFields.cs index 0bd487f93b..2888840f5e 100644 --- a/src/JsonApiDotNetCore/Models/SerializableFields.cs +++ b/src/JsonApiDotNetCore/Models/SerializableFields.cs @@ -7,17 +7,17 @@ namespace JsonApiDotNetCore.Models { public class SerializableFields : ISerializableFields { - private readonly IResourceGraph _resourceGraph; + private readonly IContextEntityProvider _resourceContextProvider; private readonly IServiceProvider _provider; private readonly Dictionary _resourceDefinitionCache = new Dictionary(); private readonly IExposedFieldExplorer _fieldExplorer; public SerializableFields(IExposedFieldExplorer fieldExplorer, - IResourceGraph resourceGraph, + IContextEntityProvider resourceContextProvider, IServiceProvider provider) { _fieldExplorer = fieldExplorer; - _resourceGraph = resourceGraph; + _resourceContextProvider = resourceContextProvider; _provider = provider; } @@ -46,7 +46,7 @@ public List GetAllowedRelationships(Type type) private IResourceDefinition GetResourceDefinition(Type resourceType) { - var resourceDefinitionType = _resourceGraph.GetContextEntity(resourceType).ResourceType; + var resourceDefinitionType = _resourceContextProvider.GetContextEntity(resourceType).ResourceType; if (!_resourceDefinitionCache.TryGetValue(resourceDefinitionType, out IResourceDefinition resourceDefinition)) { resourceDefinition = _provider.GetService(resourceDefinitionType) as IResourceDefinition; diff --git a/src/JsonApiDotNetCore/QueryServices/Contract/IFieldQueryService.cs b/src/JsonApiDotNetCore/QueryServices/Contract/IFieldQueryService.cs deleted file mode 100644 index a82d4bc6a1..0000000000 --- a/src/JsonApiDotNetCore/QueryServices/Contract/IFieldQueryService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using JsonApiDotNetCore.Models; - -namespace JsonApiDotNetCore.Services -{ - public interface IFieldsQueryService - { - List Get(RelationshipAttribute relationship = null); - } -} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/QueryServices/Contract/IIncludedQueryService.cs b/src/JsonApiDotNetCore/QueryServices/Contract/IIncludedQueryService.cs deleted file mode 100644 index 0e7dc6f146..0000000000 --- a/src/JsonApiDotNetCore/QueryServices/Contract/IIncludedQueryService.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using JsonApiDotNetCore.Models; - -namespace JsonApiDotNetCore.Services -{ - public interface IIncludedQueryService - { - List> Get(); - } -} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/QueryServices/Contract/IInternalFIeldQueryService.cs b/src/JsonApiDotNetCore/QueryServices/Contract/IInternalFIeldQueryService.cs deleted file mode 100644 index 1e02edae47..0000000000 --- a/src/JsonApiDotNetCore/QueryServices/Contract/IInternalFIeldQueryService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using JsonApiDotNetCore.Models; - -namespace JsonApiDotNetCore.Services -{ - public interface IInternalFieldsQueryService - { - void Register(AttrAttribute selected, RelationshipAttribute relationship = null); - } -} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/QueryServices/Contract/IInternalIncludedQueryService.cs b/src/JsonApiDotNetCore/QueryServices/Contract/IInternalIncludedQueryService.cs deleted file mode 100644 index 84234f6259..0000000000 --- a/src/JsonApiDotNetCore/QueryServices/Contract/IInternalIncludedQueryService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using JsonApiDotNetCore.Models; - -namespace JsonApiDotNetCore.Services -{ - public interface IInternalIncludedQueryService - { - void Register(List inclusionChain); - } -} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/QueryServices/Contracts/IFieldQueryService.cs b/src/JsonApiDotNetCore/QueryServices/Contracts/IFieldQueryService.cs index a82d4bc6a1..b9328c297f 100644 --- a/src/JsonApiDotNetCore/QueryServices/Contracts/IFieldQueryService.cs +++ b/src/JsonApiDotNetCore/QueryServices/Contracts/IFieldQueryService.cs @@ -7,4 +7,9 @@ public interface IFieldsQueryService { List Get(RelationshipAttribute relationship = null); } + + public interface IInternalFieldsQueryService + { + void Register(AttrAttribute selected, RelationshipAttribute relationship = null); + } } \ No newline at end of file diff --git a/src/JsonApiDotNetCore/QueryServices/Contracts/IIncludedQueryService.cs b/src/JsonApiDotNetCore/QueryServices/Contracts/IIncludedQueryService.cs index 0e7dc6f146..8160c98739 100644 --- a/src/JsonApiDotNetCore/QueryServices/Contracts/IIncludedQueryService.cs +++ b/src/JsonApiDotNetCore/QueryServices/Contracts/IIncludedQueryService.cs @@ -8,4 +8,9 @@ public interface IIncludedQueryService { List> Get(); } + + public interface IInternalIncludedQueryService + { + void Register(List inclusionChain); + } } \ No newline at end of file diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index 3a64dc3326..e8b09d7409 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -68,10 +68,10 @@ public void AddCurrentAssembly_Adds_Services_To_Container() // arrange, act _services.AddSingleton(new JsonApiOptions()); - _services.AddScoped( (_) => new Mock().Object); - _services.AddScoped( (_) => new Mock().Object); - _services.AddScoped( (_) => new Mock().Object); - _services.AddScoped( (_) => new Mock().Object); + _services.AddScoped((_) => new Mock().Object); + _services.AddScoped((_) => new Mock().Object); + _services.AddScoped((_) => new Mock().Object); + _services.AddScoped((_) => new Mock().Object); _facade.AddCurrentAssembly(); // assert @@ -102,7 +102,7 @@ public TestModelService( IEntityRepository repository, IJsonApiOptions options, IRequestManager requestManager, - IPageManager pageManager, + IPageQueryService pageManager, IResourceGraph resourceGraph, ILoggerFactory loggerFactory = null, IResourceHookExecutor hookExecutor = null) : base(repository, options, requestManager, pageManager, resourceGraph, loggerFactory, hookExecutor) diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs index 9649bbb2bd..07c9be984e 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/CamelCasedModelsControllerTests.cs @@ -5,6 +5,12 @@ using System.Threading.Tasks; using Bogus; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + +using JsonApiDotNetCore.Serialization.Contracts; + +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample; using JsonApiDotNetCoreExample.Data; @@ -52,7 +58,7 @@ public async Task Can_Get_CamelCasedModels() // Act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -79,7 +85,7 @@ public async Task Can_Get_CamelCasedModels_ById() // Act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (CamelCasedModel)_fixture.GetService() + var deserializedBody = (CamelCasedModel)_fixture.GetService() .Deserialize(body); // Assert @@ -123,7 +129,7 @@ public async Task Can_Post_CamelCasedModels() Assert.NotNull(body); Assert.NotEmpty(body); - var deserializedBody = (CamelCasedModel)_fixture.GetService() + var deserializedBody = (CamelCasedModel)_fixture.GetService() .Deserialize(body); Assert.Equal(model.CompoundAttr, deserializedBody.CompoundAttr); } @@ -167,7 +173,7 @@ public async Task Can_Patch_CamelCasedModels() Assert.NotNull(body); Assert.NotEmpty(body); - var deserializedBody = (CamelCasedModel)_fixture.GetService() + var deserializedBody = (CamelCasedModel)_fixture.GetService() .Deserialize(body); Assert.Equal(newModel.CompoundAttr, deserializedBody.CompoundAttr); } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs index 39fc11178d..651d096225 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Extensibility/CustomErrorTests.cs @@ -1,6 +1,8 @@ using Newtonsoft.Json; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using Xunit; namespace JsonApiDotNetCoreExampleTests.Acceptance.Extensibility diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs index 5fc6ce902c..ab60a03c3b 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/ManyToManyTests.cs @@ -8,6 +8,8 @@ using Bogus; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; using Microsoft.EntityFrameworkCore; @@ -62,7 +64,7 @@ public async Task Can_Fetch_Many_To_Many_Through_All() var document = JsonConvert.DeserializeObject(body); Assert.NotEmpty(document.Included); - var articleResponseList = _fixture.GetService().DeserializeList
(body); + var articleResponseList = _fixture.GetService().DeserializeList
(body); Assert.NotNull(articleResponseList); var articleResponse = articleResponseList.FirstOrDefault(a => a.Id == article.Id); @@ -101,7 +103,7 @@ public async Task Can_Fetch_Many_To_Many_Through_GetById() var document = JsonConvert.DeserializeObject(body); Assert.NotEmpty(document.Included); - var articleResponse = _fixture.GetService().Deserialize
(body); + var articleResponse = _fixture.GetService().Deserialize
(body); Assert.NotNull(articleResponse); Assert.Equal(article.Id, articleResponse.Id); @@ -190,7 +192,7 @@ public async Task Can_Create_Many_To_Many() var body = await response.Content.ReadAsStringAsync(); Assert.True(HttpStatusCode.Created == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}"); - var articleResponse = _fixture.GetService().Deserialize
(body); + var articleResponse = _fixture.GetService().Deserialize
(body); Assert.NotNull(articleResponse); var persistedArticle = await _fixture.Context.Articles @@ -243,7 +245,7 @@ public async Task Can_Update_Many_To_Many() var body = await response.Content.ReadAsStringAsync(); Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}"); - var articleResponse = _fixture.GetService().Deserialize
(body); + var articleResponse = _fixture.GetService().Deserialize
(body); Assert.NotNull(articleResponse); _fixture.ReloadDbContext(); @@ -303,7 +305,7 @@ public async Task Can_Update_Many_To_Many_With_Complete_Replacement() var body = await response.Content.ReadAsStringAsync(); Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}"); - var articleResponse = _fixture.GetService().Deserialize
(body); + var articleResponse = _fixture.GetService().Deserialize
(body); Assert.NotNull(articleResponse); _fixture.ReloadDbContext(); @@ -366,7 +368,7 @@ public async Task Can_Update_Many_To_Many_With_Complete_Replacement_With_Overlap var body = await response.Content.ReadAsStringAsync(); Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}"); - var articleResponse = _fixture.GetService().Deserialize
(body); + var articleResponse = _fixture.GetService().Deserialize
(body); Assert.NotNull(articleResponse); _fixture.ReloadDbContext(); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/QueryFiltersTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/QueryFiltersTests.cs index 92589dd0b5..3c09e4061b 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/QueryFiltersTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/QueryFiltersTests.cs @@ -7,6 +7,8 @@ using Bogus; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; using Microsoft.EntityFrameworkCore; @@ -50,7 +52,7 @@ public async Task FiltersWithCustomQueryFiltersEquals() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); var usersWithFirstCharacter = _context.Users.Where(u => u.Username[0] == firstUsernameCharacter); Assert.True(deserializedBody.All(u => u.Username[0] == firstUsernameCharacter)); } @@ -78,7 +80,7 @@ public async Task FiltersWithCustomQueryFiltersLessThan() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); Assert.True(deserializedBody.All(u => u.Username[0] < median)); } } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs index 616a1cf9da..f56b4edc84 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/ResourceDefinitions/ResourceDefinitionTests.cs @@ -8,6 +8,8 @@ using Bogus; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; using Microsoft.EntityFrameworkCore; @@ -101,7 +103,7 @@ public async Task Can_Create_User_With_Password() // response assertions var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (User)_fixture.GetService().Deserialize(body); + var deserializedBody = (User)_fixture.GetService().Deserialize(body); var document = JsonConvert.DeserializeObject(body); Assert.False(document.Data.Attributes.ContainsKey("password")); Assert.Equal(user.Username, document.Data.Attributes["username"]); @@ -150,7 +152,7 @@ public async Task Can_Update_User_Password() // response assertions var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (User)_fixture.GetService().Deserialize(body); + var deserializedBody = (User)_fixture.GetService().Deserialize(body); var document = JsonConvert.DeserializeObject(body); Assert.False(document.Data.Attributes.ContainsKey("password")); Assert.Equal(user.Username, document.Data.Attributes["username"]); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs index 370960ee29..48eec4adb5 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeFilterTests.cs @@ -7,6 +7,8 @@ using Bogus; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; using Newtonsoft.Json; @@ -52,7 +54,7 @@ public async Task Can_Filter_On_Guid_Properties() var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); var deserializedBody = _fixture - .GetService() + .GetService() .DeserializeList(body); var todoItemResponse = deserializedBody.Single(); @@ -125,7 +127,7 @@ public async Task Can_Filter_On_Not_Equal_Values() var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); var deserializedTodoItems = _fixture - .GetService() + .GetService() .DeserializeList(body); // assert @@ -161,7 +163,7 @@ public async Task Can_Filter_On_In_Array_Values() var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); var deserializedTodoItems = _fixture - .GetService() + .GetService() .DeserializeList(body); // assert @@ -240,7 +242,7 @@ public async Task Can_Filter_On_Not_In_Array_Values() var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); var deserializedTodoItems = _fixture - .GetService() + .GetService() .DeserializeList(body); // assert diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs index 0811699b9c..b76f6ca3e5 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs @@ -7,6 +7,8 @@ using System.Threading.Tasks; using Bogus; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample; using JsonApiDotNetCoreExample.Data; @@ -164,7 +166,7 @@ public async Task Can_Create_Entity_With_Client_Defined_Id_If_Configured() // act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); // assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); @@ -216,7 +218,7 @@ public async Task Can_Create_Guid_Identifiable_Entity_With_Client_Defined_Id_If_ // act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItemCollection)_fixture.GetService().Deserialize(body); + var deserializedBody = (TodoItemCollection)_fixture.GetService().Deserialize(body); // assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); @@ -279,7 +281,7 @@ public async Task Can_Create_And_Set_HasMany_Relationships() // act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItemCollection)_fixture.GetService().Deserialize(body); + var deserializedBody = (TodoItemCollection)_fixture.GetService().Deserialize(body); var newId = deserializedBody.Id; context = _fixture.GetService(); @@ -352,7 +354,7 @@ public async Task Can_Create_With_HasMany_Relationship_And_Include_Result() // assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); - var collectionResult = _fixture.GetService().Deserialize(body); + var collectionResult = _fixture.GetService().Deserialize(body); Assert.NotNull(collectionResult); Assert.NotEmpty(collectionResult.TodoItems); @@ -405,7 +407,7 @@ public async Task Can_Create_And_Set_HasOne_Relationships() // assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); - var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); var newId = deserializedBody.Id; context = _fixture.GetService(); @@ -466,7 +468,7 @@ public async Task Can_Create_With_HasOne_Relationship_And_Include_Result() // assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); - var todoItemResult = (TodoItem)_fixture.GetService().Deserialize(body); + var todoItemResult = (TodoItem)_fixture.GetService().Deserialize(body); Assert.NotNull(todoItemResult); Assert.NotNull(todoItemResult.Owner); Assert.Equal(owner.FirstName, todoItemResult.Owner.FirstName); @@ -519,7 +521,7 @@ public async Task Can_Create_And_Set_HasOne_Relationships_From_Independent_Side( // assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); - var deserializedBody = (PersonRole)_fixture.GetService().Deserialize(body); + var deserializedBody = (PersonRole)_fixture.GetService().Deserialize(body); Assert.Equal(person.Id, deserializedBody.Person.Id); } @@ -554,7 +556,7 @@ public async Task ShouldReceiveLocationHeader_InResponse() // act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); // assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); @@ -635,7 +637,7 @@ public async Task Create_With_ToOne_Relationship_With_Implicit_Remove() // Act var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var personResult = _fixture.GetService().Deserialize(body); + var personResult = _fixture.GetService().Deserialize(body); // Assert @@ -694,7 +696,7 @@ public async Task Create_With_ToMany_Relationship_With_Implicit_Remove() // Act var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var personResult = _fixture.GetService().Deserialize(body); + var personResult = _fixture.GetService().Deserialize(body); // Assert Assert.True(HttpStatusCode.Created == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}"); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DeeplyNestedInclusionTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DeeplyNestedInclusionTests.cs index 5e4754c7c5..5421d72391 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DeeplyNestedInclusionTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DeeplyNestedInclusionTests.cs @@ -57,7 +57,7 @@ public async Task Can_Include_Nested_Relationships() Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); - var todoItems = _fixture.DeSerializer.DeserializeList(body); + var todoItems = _fixture.deserializer.DeserializeList(body); var responseTodoItem = Assert.Single(todoItems); Assert.NotNull(responseTodoItem); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs index e277c8d0af..cf61c10805 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs @@ -43,7 +43,7 @@ public Included(TestFixture fixture) } [Fact] - public async Task GET_Included_Contains_SideloadedData_ForManyToOne() + public async Task GET_Included_Contains_SideloadeData_ForManyToOne() { // arrange var person = _personFaker.Generate(); @@ -77,7 +77,7 @@ public async Task GET_Included_Contains_SideloadedData_ForManyToOne() } [Fact] - public async Task GET_ById_Included_Contains_SideloadedData_ForManyToOne() + public async Task GET_ById_Included_Contains_SideloadeData_ForManyToOne() { // arrange var person = _personFaker.Generate(); @@ -111,7 +111,7 @@ public async Task GET_ById_Included_Contains_SideloadedData_ForManyToOne() } [Fact] - public async Task GET_Included_Contains_SideloadedData_OneToMany() + public async Task GET_Included_Contains_SideloadeData_OneToMany() { // arrange _context.People.RemoveRange(_context.People); // ensure all people have todo-items @@ -214,7 +214,7 @@ public async Task GET_Included_DoesNot_Duplicate_Records_If_HasOne_Exists_Twice( } [Fact] - public async Task GET_ById_Included_Contains_SideloadedData_ForOneToMany() + public async Task GET_ById_Included_Contains_SideloadeData_ForOneToMany() { // arrange const int numberOfTodoItems = 5; diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs index 9c8d5f8214..db9775ba4d 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/FetchingDataTests.cs @@ -5,6 +5,8 @@ using Bogus; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample; using JsonApiDotNetCoreExample.Data; @@ -62,7 +64,7 @@ public async Task Request_ForEmptyCollection_Returns_EmptyDataCollection() // act var response = await client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); // assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/PagingTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/PagingTests.cs index 0667b51756..67be776f1a 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/PagingTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/PagingTests.cs @@ -5,6 +5,8 @@ using Bogus; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCoreExample.Models; using Xunit; using Person = JsonApiDotNetCoreExample.Models.Person; @@ -42,7 +44,7 @@ public async Task Can_Paginate_TodoItems() Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = GetService().DeserializeList(body); + var deserializedBody = GetService().DeserializeList(body); Assert.NotEmpty(deserializedBody); Assert.Equal(expectedEntitiesPerPage, deserializedBody.Count); @@ -73,7 +75,7 @@ public async Task Can_Paginate_TodoItems_From_Start() Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = GetService().DeserializeList(body); + var deserializedBody = GetService().DeserializeList(body); var expectedTodoItems = new[] { todoItems[0], todoItems[1] }; Assert.Equal(expectedTodoItems, deserializedBody, new IdComparer()); @@ -104,7 +106,7 @@ public async Task Can_Paginate_TodoItems_From_End() Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = GetService().DeserializeList(body); + var deserializedBody = GetService().DeserializeList(body); var expectedTodoItems = new[] { todoItems[totalCount - 2], todoItems[totalCount - 1] }; Assert.Equal(expectedTodoItems, deserializedBody, new IdComparer()); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/SparseFieldSetTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/SparseFieldSetTests.cs index 73f1ab524a..69018e8fb5 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/SparseFieldSetTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/SparseFieldSetTests.cs @@ -19,6 +19,8 @@ using Person = JsonApiDotNetCoreExample.Models.Person; using System.Net; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec { @@ -146,7 +148,7 @@ public async Task Fields_Query_Selects_All_Fieldset_With_HasOne() Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); var deserializedTodoItems = _fixture - .GetService() + .GetService() .DeserializeList(body); foreach(var item in deserializedTodoItems.Where(i => i.Owner != null)) diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/TestFixture.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/TestFixture.cs index 6d6de345a0..a1555f3fe0 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/TestFixture.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/TestFixture.cs @@ -1,6 +1,8 @@ using System; using System.Net.Http; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCoreExample.Data; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; @@ -25,13 +27,13 @@ public TestFixture() Client = _server.CreateClient(); Context = GetService().GetContext() as AppDbContext; - DeSerializer = GetService(); + deserializer = GetService(); JsonApiContext = GetService(); } public HttpClient Client { get; set; } public AppDbContext Context { get; private set; } - public IJsonApiDeSerializer DeSerializer { get; private set; } + public IJsonApiDeserializer deserializer { get; private set; } public IJsonApiContext JsonApiContext { get; private set; } public T GetService() => (T)_services.GetService(typeof(T)); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs index dcbb8119ed..05268e2cb0 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/TodoItemsControllerTests.cs @@ -8,6 +8,8 @@ using Bogus; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; @@ -68,7 +70,7 @@ public async Task Can_Get_TodoItems_Paginate_Check() // Act var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -91,7 +93,7 @@ public async Task Can_Filter_By_Resource_Id() // Act var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -116,7 +118,7 @@ public async Task Can_Filter_By_Relationship_Id() // Act var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -142,7 +144,7 @@ public async Task Can_Filter_TodoItems() // Act var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -175,7 +177,7 @@ public async Task Can_Filter_TodoItems_Using_IsNotNull_Operator() Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); - var todoItems = _fixture.GetService().DeserializeList(body); + var todoItems = _fixture.GetService().DeserializeList(body); // Assert Assert.NotEmpty(todoItems); @@ -205,7 +207,7 @@ public async Task Can_Filter_TodoItems_Using_IsNull_Operator() Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); - var todoItems = _fixture.GetService().DeserializeList(body); + var todoItems = _fixture.GetService().DeserializeList(body); // Assert Assert.NotEmpty(todoItems); @@ -229,7 +231,7 @@ public async Task Can_Filter_TodoItems_Using_Like_Operator() // Act var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -264,7 +266,7 @@ public async Task Can_Sort_TodoItems_By_Ordinal_Ascending() // Act var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -305,7 +307,7 @@ public async Task Can_Sort_TodoItems_By_Nested_Attribute_Ascending() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); Assert.NotEmpty(deserializedBody); long lastAge = 0; @@ -343,7 +345,7 @@ public async Task Can_Sort_TodoItems_By_Nested_Attribute_Descending() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); Assert.NotEmpty(deserializedBody); int maxAge = deserializedBody.Max(i => i.Owner.Age) + 1; @@ -379,7 +381,7 @@ public async Task Can_Sort_TodoItems_By_Ordinal_Descending() // Act var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.GetService().DeserializeList(body); + var deserializedBody = _fixture.GetService().DeserializeList(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -410,7 +412,7 @@ public async Task Can_Get_TodoItem_ById() // Act var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -441,7 +443,7 @@ public async Task Can_Get_TodoItem_WithOwner() // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); Assert.Equal(person.Id, deserializedBody.Owner.Id); Assert.Equal(todoItem.Id, deserializedBody.Id); @@ -500,7 +502,7 @@ public async Task Can_Post_TodoItem() // Assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); Assert.Equal(HttpStatusCode.Created, response.StatusCode); Assert.Equal(todoItem.Description, deserializedBody.Description); Assert.Equal(todoItem.CreatedDate.ToString("G"), deserializedBody.CreatedDate.ToString("G")); @@ -616,7 +618,7 @@ public async Task Can_Patch_TodoItem() // Act var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -669,7 +671,7 @@ public async Task Can_Patch_TodoItemWithNullable() // Act var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -721,7 +723,7 @@ public async Task Can_Patch_TodoItemWithNullValue() // Act var response = await _fixture.Client.SendAsync(request); var body = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); + var deserializedBody = (TodoItem)_fixture.GetService().Deserialize(body); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs index c3c3d5a3ec..8048738417 100644 --- a/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs +++ b/test/JsonApiDotNetCoreExampleTests/Helpers/Startups/ClientGeneratedIdsStartup.cs @@ -29,7 +29,7 @@ public override IServiceProvider ConfigureServices(IServiceCollection services) options.DefaultPageSize = 5; options.IncludeTotalRecordCount = true; options.EnableResourceHooks = true; - options.LoadDatabaseValues = true; + options.LoaDatabaseValues = true; options.AllowClientGeneratedIds = true; }, discovery => discovery.AddAssembly(Assembly.Load(nameof(JsonApiDotNetCoreExample))), diff --git a/test/NoEntityFrameworkTests/Acceptance/Extensibility/NoEntityFrameworkTests.cs b/test/NoEntityFrameworkTests/Acceptance/Extensibility/NoEntityFrameworkTests.cs index 95e2f32142..190a8a748c 100644 --- a/test/NoEntityFrameworkTests/Acceptance/Extensibility/NoEntityFrameworkTests.cs +++ b/test/NoEntityFrameworkTests/Acceptance/Extensibility/NoEntityFrameworkTests.cs @@ -4,6 +4,8 @@ using System.Net.Http.Headers; using System.Threading.Tasks; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCoreExample.Models; using JsonApiDotNetCoreExampleTests.Helpers.Extensions; using Newtonsoft.Json; @@ -37,7 +39,7 @@ public async Task Can_Get_TodoItems() // act var response = await client.SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.Server.GetService() + var deserializedBody = _fixture.Server.GetService() .DeserializeList(responseBody); // assert @@ -64,7 +66,7 @@ public async Task Can_Get_TodoItems_By_Id() // act var response = await client.SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)_fixture.Server.GetService() + var deserializedBody = (TodoItem)_fixture.Server.GetService() .Deserialize(responseBody); // assert @@ -101,7 +103,7 @@ public async Task Can_Create_TodoItems() // act var response = await client.SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = (TodoItem)_fixture.Server.GetService() + var deserializedBody = (TodoItem)_fixture.Server.GetService() .Deserialize(responseBody); // assert diff --git a/test/ResourceEntitySeparationExampleTests/Acceptance/GetTests.cs b/test/ResourceEntitySeparationExampleTests/Acceptance/GetTests.cs index 371a07ab9b..ae7da5ad47 100644 --- a/test/ResourceEntitySeparationExampleTests/Acceptance/GetTests.cs +++ b/test/ResourceEntitySeparationExampleTests/Acceptance/GetTests.cs @@ -1,4 +1,6 @@ using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCoreExample.Models.Entities; using JsonApiDotNetCoreExample.Models.Resources; using JsonApiDotNetCoreExampleTests.Helpers.Extensions; @@ -32,7 +34,7 @@ public async Task Can_Get_Courses() // act var response = await _fixture.SendAsync("GET", route, null); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.Server.GetService() + var deserializedBody = _fixture.Server.GetService() .DeserializeList(responseBody); // assert @@ -116,7 +118,7 @@ public async Task Can_Get_Departments() // act var response = await _fixture.SendAsync("GET", route, null); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.Server.GetService() + var deserializedBody = _fixture.Server.GetService() .DeserializeList(responseBody); // assert @@ -190,7 +192,7 @@ public async Task Can_Get_Students() // act var response = await _fixture.SendAsync("GET", route, null); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.Server.GetService() + var deserializedBody = _fixture.Server.GetService() .DeserializeList(responseBody); // assert @@ -215,7 +217,7 @@ public async Task Can_Get_Student_By_Id() // act var response = await _fixture.Server.CreateClient().SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = (StudentResource)_fixture.Server.GetService() + var deserializedBody = (StudentResource)_fixture.Server.GetService() .Deserialize(responseBody); // assert @@ -249,7 +251,7 @@ public async Task Can_Get_Student_With_Relationships() // act var response = await _fixture.Server.CreateClient().SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = (StudentResource)_fixture.Server.GetService() + var deserializedBody = (StudentResource)_fixture.Server.GetService() .Deserialize(responseBody); // assert diff --git a/test/ResourceEntitySeparationExampleTests/Acceptance/RelationshipGetTests.cs b/test/ResourceEntitySeparationExampleTests/Acceptance/RelationshipGetTests.cs index 0beb6c3b6b..acd6ae08c9 100644 --- a/test/ResourceEntitySeparationExampleTests/Acceptance/RelationshipGetTests.cs +++ b/test/ResourceEntitySeparationExampleTests/Acceptance/RelationshipGetTests.cs @@ -1,4 +1,6 @@ using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCoreExample.Models.Entities; using JsonApiDotNetCoreExample.Models.Resources; using JsonApiDotNetCoreExampleTests.Helpers.Extensions; @@ -33,7 +35,7 @@ public async Task Can_Get_Courses_For_Department() // act var response = await _fixture.SendAsync("GET", route, null); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.Server.GetService() + var deserializedBody = _fixture.Server.GetService() .DeserializeList(responseBody); // assert @@ -57,7 +59,7 @@ public async Task Can_Get_Course_Relationships_For_Department() // act var response = await _fixture.SendAsync("GET", route, null); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.Server.GetService() + var deserializedBody = _fixture.Server.GetService() .DeserializeList(responseBody); // assert @@ -85,7 +87,7 @@ public async Task Can_Get_Courses_For_Student() // act var response = await _fixture.SendAsync("GET", route, null); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.Server.GetService() + var deserializedBody = _fixture.Server.GetService() .DeserializeList(responseBody); // assert @@ -113,7 +115,7 @@ public async Task Can_Get_Course_Relationships_For_Student() // act var response = await _fixture.SendAsync("GET", route, null); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.Server.GetService() + var deserializedBody = _fixture.Server.GetService() .DeserializeList(responseBody); // assert @@ -184,7 +186,7 @@ public async Task Can_Get_Students_For_Course() // act var response = await _fixture.SendAsync("GET", route, null); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.Server.GetService() + var deserializedBody = _fixture.Server.GetService() .DeserializeList(responseBody); // assert @@ -212,7 +214,7 @@ public async Task Can_Get_Student_Relationships_For_Course() // act var response = await _fixture.SendAsync("GET", route, null); var responseBody = await response.Content.ReadAsStringAsync(); - var deserializedBody = _fixture.Server.GetService() + var deserializedBody = _fixture.Server.GetService() .DeserializeList(responseBody); // assert diff --git a/test/ResourceEntitySeparationExampleTests/TestFixture.cs b/test/ResourceEntitySeparationExampleTests/TestFixture.cs index d54fe43688..2e3617f2b6 100644 --- a/test/ResourceEntitySeparationExampleTests/TestFixture.cs +++ b/test/ResourceEntitySeparationExampleTests/TestFixture.cs @@ -1,5 +1,7 @@ using Bogus; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models.Entities; using JsonApiDotNetCoreExampleTests.Helpers.Extensions; @@ -85,7 +87,7 @@ public async Task SendAsync(string method, string route, ob { var response = await SendAsync(method, route, data); var json = await response.Content.ReadAsStringAsync(); - var obj = (T)Server.GetService().Deserialize(json); + var obj = (T)Server.GetService().Deserialize(json); return (response, obj); } } diff --git a/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs b/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs index d0055a9530..186530197a 100644 --- a/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs +++ b/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs @@ -71,8 +71,7 @@ public void Resources_Without_Names_Specified_Will_Use_Default_Formatter() public void Resources_Without_Names_Specified_Will_Use_Configured_Formatter() { // arrange - JsonApiOptions.ResourceNameFormatter = new CamelCaseNameFormatter(); - var builder = new ResourceGraphBuilder(); + var builder = new ResourceGraphBuilder(new CamelCaseNameFormatter()); builder.AddResource(); // act @@ -102,8 +101,7 @@ public void Attrs_Without_Names_Specified_Will_Use_Default_Formatter() public void Attrs_Without_Names_Specified_Will_Use_Configured_Formatter() { // arrange - JsonApiOptions.ResourceNameFormatter = new CamelCaseNameFormatter(); - var builder = new ResourceGraphBuilder(); + var builder = new ResourceGraphBuilder(new CamelCaseNameFormatter()); builder.AddResource(); // act diff --git a/test/UnitTests/Builders/DocumentBuilderBehaviour_Tests.cs b/test/UnitTests/Builders/DocumentBuilderBehaviour_Tests.cs index 3c5e2e5147..36b5a96dc0 100644 --- a/test/UnitTests/Builders/DocumentBuilderBehaviour_Tests.cs +++ b/test/UnitTests/Builders/DocumentBuilderBehaviour_Tests.cs @@ -1,70 +1,70 @@ -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Services; -using Microsoft.AspNetCore.Http; -using Moq; -using Xunit; +//using JsonApiDotNetCore.Builders; +//using JsonApiDotNetCore.Configuration; +//using JsonApiDotNetCore.Services; +//using Microsoft.AspNetCore.Http; +//using Moq; +//using Xunit; -namespace UnitTests.Builders -{ - public class DocumentBuilderBehaviour_Tests - { +//namespace UnitTests.Builders +//{ +// public class DocumentBuilderBehaviour_Tests +// { - [Theory] - [InlineData(null, null, null, false)] - [InlineData(false, null, null, false)] - [InlineData(true, null, null, true)] - [InlineData(false, false, "true", false)] - [InlineData(false, true, "true", true)] - [InlineData(true, true, "false", false)] - [InlineData(true, false, "false", true)] - [InlineData(null, false, "false", false)] - [InlineData(null, false, "true", false)] - [InlineData(null, true, "true", true)] - [InlineData(null, true, "false", false)] - [InlineData(null, true, "foo", false)] - [InlineData(null, false, "foo", false)] - [InlineData(true, true, "foo", true)] - [InlineData(true, false, "foo", true)] - [InlineData(null, true, null, false)] - [InlineData(null, false, null, false)] - public void CheckNullBehaviorCombination(bool? omitNullValuedAttributes, bool? allowClientOverride, string clientOverride, bool omitsNulls) - { +// [Theory] +// [InlineData(null, null, null, false)] +// [InlineData(false, null, null, false)] +// [InlineData(true, null, null, true)] +// [InlineData(false, false, "true", false)] +// [InlineData(false, true, "true", true)] +// [InlineData(true, true, "false", false)] +// [InlineData(true, false, "false", true)] +// [InlineData(null, false, "false", false)] +// [InlineData(null, false, "true", false)] +// [InlineData(null, true, "true", true)] +// [InlineData(null, true, "false", false)] +// [InlineData(null, true, "foo", false)] +// [InlineData(null, false, "foo", false)] +// [InlineData(true, true, "foo", true)] +// [InlineData(true, false, "foo", true)] +// [InlineData(null, true, null, false)] +// [InlineData(null, false, null, false)] +// public void CheckNullBehaviorCombination(bool? omitNullValuedAttributes, bool? allowClientOverride, string clientOverride, bool omitsNulls) +// { - NullAttributeResponseBehavior nullAttributeResponseBehavior; - if (omitNullValuedAttributes.HasValue && allowClientOverride.HasValue) - { - nullAttributeResponseBehavior = new NullAttributeResponseBehavior(omitNullValuedAttributes.Value, allowClientOverride.Value); - }else if (omitNullValuedAttributes.HasValue) - { - nullAttributeResponseBehavior = new NullAttributeResponseBehavior(omitNullValuedAttributes.Value); - }else if - (allowClientOverride.HasValue) - { - nullAttributeResponseBehavior = new NullAttributeResponseBehavior(allowClientOverride: allowClientOverride.Value); - } - else - { - nullAttributeResponseBehavior = new NullAttributeResponseBehavior(); - } +// NullAttributeResponseBehavior nullAttributeResponseBehavior; +// if (omitNullValuedAttributes.HasValue && allowClientOverride.HasValue) +// { +// nullAttributeResponseBehavior = new NullAttributeResponseBehavior(omitNullValuedAttributes.Value, allowClientOverride.Value); +// }else if (omitNullValuedAttributes.HasValue) +// { +// nullAttributeResponseBehavior = new NullAttributeResponseBehavior(omitNullValuedAttributes.Value); +// }else if +// (allowClientOverride.HasValue) +// { +// nullAttributeResponseBehavior = new NullAttributeResponseBehavior(allowClientOverride: allowClientOverride.Value); +// } +// else +// { +// nullAttributeResponseBehavior = new NullAttributeResponseBehavior(); +// } - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupGet(m => m.Options) - .Returns(new JsonApiOptions() {NullAttributeResponseBehavior = nullAttributeResponseBehavior}); +// var jsonApiContextMock = new Mock(); +// jsonApiContextMock.SetupGet(m => m.Options) +// .Returns(new JsonApiOptions() {NullAttributeResponseBehavior = nullAttributeResponseBehavior}); - var httpContext = new DefaultHttpContext(); - if (clientOverride != null) - { - httpContext.Request.QueryString = new QueryString($"?omitNullValuedAttributes={clientOverride}"); - } - var httpContextAccessorMock = new Mock(); - httpContextAccessorMock.SetupGet(m => m.HttpContext).Returns(httpContext); +// var httpContext = new DefaultHttpContext(); +// if (clientOverride != null) +// { +// httpContext.Request.QueryString = new QueryString($"?omitNullValuedAttributes={clientOverride}"); +// } +// var httpContextAccessorMock = new Mock(); +// httpContextAccessorMock.SetupGet(m => m.HttpContext).Returns(httpContext); - var sut = new DocumentBuilderOptionsProvider(jsonApiContextMock.Object, httpContextAccessorMock.Object); - var documentBuilderOptions = sut.GetDocumentBuilderOptions(); +// var sut = new DocumentBuilderOptionsProvider(jsonApiContextMock.Object, httpContextAccessorMock.Object); +// var documentBuilderOptions = sut.GetDocumentBuilderOptions(); - Assert.Equal(omitsNulls, documentBuilderOptions.OmitNullValuedAttributes); - } +// Assert.Equal(omitsNulls, documentBuilderOptions.OmitNullValuedAttributes); +// } - } -} +// } +//} diff --git a/test/UnitTests/Builders/DocumentBuilder_Tests.cs b/test/UnitTests/Builders/DocumentBuilder_Tests.cs index 19018e1a62..0aafaa31e5 100644 --- a/test/UnitTests/Builders/DocumentBuilder_Tests.cs +++ b/test/UnitTests/Builders/DocumentBuilder_Tests.cs @@ -1,434 +1,434 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Xunit; - -namespace UnitTests -{ - public class DocumentBuilder_Tests - { - private readonly Mock _jsonApiContextMock; - private readonly IPageManager _pageManager; - private readonly JsonApiOptions _options; - private readonly Mock _requestMetaMock; - - public DocumentBuilder_Tests() - { - _jsonApiContextMock = new Mock(); - _requestMetaMock = new Mock(); - - _options = new JsonApiOptions(); - - _options.BuildResourceGraph(builder => - { - builder.AddResource("models"); - builder.AddResource("related-models"); - }); - - _jsonApiContextMock - .Setup(m => m.Options) - .Returns(_options); - - _jsonApiContextMock - .Setup(m => m.ResourceGraph) - .Returns(_options.ResourceGraph); - - _jsonApiContextMock - .Setup(m => m.MetaBuilder) - .Returns(new MetaBuilder()); - - _pageManager = new Mock().Object; - _jsonApiContextMock - .Setup(m => m.PageManager) - .Returns(_pageManager); - - - - _jsonApiContextMock - .Setup(m => m.RequestEntity) - .Returns(_options.ResourceGraph.GetContextEntity(typeof(Model))); - } - - [Fact] - public void Includes_Paging_Links_By_Default() - { - // arrange - - - var rmMock = new Mock(); - rmMock.Setup(m => m.GetContextEntity()).Returns(new ContextEntity { EntityName = "resources" }); - var rm = rmMock.Object; - var options = new JsonApiOptions { RelativeLinks = false }; - var pg = new PageManager(new LinkBuilder(options, rm), options, rm); - pg.PageSize = 1; - pg.TotalRecords = 1; - pg.CurrentPage = 1; - var documentBuilder = GetDocumentBuilder(pageManager: pg); - var entity = new Model(); - - // act - var document = documentBuilder.Build(entity); - - // assert - Assert.NotNull(document.Links); - Assert.NotNull(document.Links.Last); - } - - - - [Fact] - public void Page_Links_Can_Be_Disabled_Globally() - { - // arrange - _pageManager.PageSize = 1; - _pageManager.TotalRecords = 1; - _pageManager.CurrentPage = 1; - - _options.BuildResourceGraph(builder => builder.DocumentLinks = Link.None); - - _jsonApiContextMock - .Setup(m => m.ResourceGraph) - .Returns(_options.ResourceGraph); - - var documentBuilder = GetDocumentBuilder(); - var entity = new Model(); - - // act - var document = documentBuilder.Build(entity); - - // assert - Assert.Null(document.Links); - } - - [Fact] - public void Related_Links_Can_Be_Disabled() - { - // arrange - _pageManager.PageSize = 1; - _pageManager.TotalRecords = 1; - _pageManager.CurrentPage = 1; - - _jsonApiContextMock - .Setup(m => m.ResourceGraph) - .Returns(_options.ResourceGraph); - - var documentBuilder = GetDocumentBuilder(); - var entity = new Model(); - - // act - var document = documentBuilder.Build(entity); - - // assert - Assert.Null(document.Data.Relationships["related-model"].Links); - } - - [Fact] - public void Related_Links_Can_Be_Disabled_Globally() - { - // arrange - _pageManager.PageSize = 1; - _pageManager.TotalRecords = 1; - _pageManager.CurrentPage = 1; - - _options.DefaultRelationshipLinks = Link.None; - - _jsonApiContextMock - .Setup(m => m.ResourceGraph) - .Returns(_options.ResourceGraph); - - var documentBuilder = GetDocumentBuilder(); - var entity = new RelatedModel(); - - // act - var document = documentBuilder.Build(entity); - - // assert - Assert.Null(document.Data.Relationships["models"].Links); - } - - [Fact] - public void Related_Data_Included_In_Relationships_By_Default() - { - // arrange - const string relatedTypeName = "related-models"; - const string relationshipName = "related-model"; - const int relatedId = 1; - _jsonApiContextMock - .Setup(m => m.ResourceGraph) - .Returns(_options.ResourceGraph); - - var documentBuilder = GetDocumentBuilder(); - var entity = new Model - { - RelatedModel = new RelatedModel - { - Id = relatedId - } - }; - - // act - var document = documentBuilder.Build(entity); - - // assert - var relationshipData = document.Data.Relationships[relationshipName]; - Assert.NotNull(relationshipData); - Assert.NotNull(relationshipData.SingleData); - Assert.NotNull(relationshipData.SingleData); - Assert.Equal(relatedId.ToString(), relationshipData.SingleData.Id); - Assert.Equal(relatedTypeName, relationshipData.SingleData.Type); - } - - [Fact] - public void IndependentIdentifier_Included_In_HasOne_Relationships_By_Default() - { - // arrange - const string relatedTypeName = "related-models"; - const string relationshipName = "related-model"; - const int relatedId = 1; - _jsonApiContextMock - .Setup(m => m.ResourceGraph) - .Returns(_options.ResourceGraph); - - var documentBuilder = GetDocumentBuilder(); - var entity = new Model - { - RelatedModelId = relatedId - }; - - // act - var document = documentBuilder.Build(entity); - - // assert - var relationshipData = document.Data.Relationships[relationshipName]; - Assert.NotNull(relationshipData); - Assert.NotNull(relationshipData.SingleData); - Assert.NotNull(relationshipData.SingleData); - Assert.Equal(relatedId.ToString(), relationshipData.SingleData.Id); - Assert.Equal(relatedTypeName, relationshipData.SingleData.Type); - } - - [Fact] - public void Build_Can_Build_Arrays() - { - var entities = new[] { new Model() }; - var documentBuilder = GetDocumentBuilder(); - - var documents = documentBuilder.Build(entities); - - Assert.Single(documents.Data); - } - - [Fact] - public void Build_Can_Build_CustomIEnumerables() - { - var entities = new Models(new[] { new Model() }); - var documentBuilder = GetDocumentBuilder(); - - var documents = documentBuilder.Build(entities); - - Assert.Single(documents.Data); - } - - [Theory] - [InlineData(null, null, true)] - [InlineData(false, null, true)] - [InlineData(true, null, false)] - [InlineData(null, "foo", true)] - [InlineData(false, "foo", true)] - [InlineData(true, "foo", true)] - public void DocumentBuilderOptions( - bool? omitNullValuedAttributes, - string attributeValue, - bool resultContainsAttribute) - { - var documentBuilderBehaviourMock = new Mock(); - if (omitNullValuedAttributes.HasValue) - { - documentBuilderBehaviourMock.Setup(m => m.GetDocumentBuilderOptions()) - .Returns(new DocumentBuilderOptions(omitNullValuedAttributes.Value)); - } - var pageManagerMock = new Mock(); - var requestManagerMock = new Mock(); - var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, requestManagerMock.Object, documentBuilderOptionsProvider: omitNullValuedAttributes.HasValue ? documentBuilderBehaviourMock.Object : null); - var document = documentBuilder.Build(new Model() { StringProperty = attributeValue }); - - Assert.Equal(resultContainsAttribute, document.Data.Attributes.ContainsKey("StringProperty")); - } - - private class Model : Identifiable - { - [Attr("StringProperty")] public string StringProperty { get; set; } - - [HasOne("related-model", documentLinks: Link.None)] - public RelatedModel RelatedModel { get; set; } - public int RelatedModelId { get; set; } - } - - private class RelatedModel : Identifiable - { - [HasMany("models")] - public List Models { get; set; } - } - - private class Models : IEnumerable - { - private readonly IEnumerable models; - - public Models(IEnumerable models) - { - this.models = models; - } - - public IEnumerator GetEnumerator() - { - return models.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return models.GetEnumerator(); - } - } - - [Fact] - public void Build_Will_Use_Resource_If_Defined_For_Multiple_Documents() - { - var entities = new[] { new User() }; - var resourceGraph = new ResourceGraphBuilder() - .AddResource("user") - .Build(); - _jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - - var scopedServiceProvider = new TestScopedServiceProvider( - new ServiceCollection() - .AddScoped, UserResource>() - .AddSingleton(resourceGraph) - .BuildServiceProvider()); - - var documentBuilder = GetDocumentBuilder(scopedServiceProvider: scopedServiceProvider); - - var documents = documentBuilder.Build(entities); - - Assert.Single(documents.Data); - Assert.False(documents.Data[0].Attributes.ContainsKey("password")); - Assert.True(documents.Data[0].Attributes.ContainsKey("username")); - } - - [Fact] - public void Build_Will_Use_Resource_If_Defined_For_Single_Document() - { - var entity = new User(); - var resourceGraph = new ResourceGraphBuilder() - .AddResource("user") - .Build(); - _jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - - var scopedServiceProvider = new TestScopedServiceProvider( - new ServiceCollection() - .AddScoped, UserResource>() - .AddSingleton(resourceGraph) - .BuildServiceProvider()); - - var documentBuilder = GetDocumentBuilder(scopedServiceProvider); - - var documents = documentBuilder.Build(entity); - - Assert.False(documents.Data.Attributes.ContainsKey("password")); - Assert.True(documents.Data.Attributes.ContainsKey("username")); - } - - [Fact] - public void Build_Will_Use_Instance_Specific_Resource_If_Defined_For_Multiple_Documents() - { - var entities = new[] { new User() }; - var resourceGraph = new ResourceGraphBuilder() - .AddResource("user") - .Build(); - _jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - - var scopedServiceProvider = new TestScopedServiceProvider( - new ServiceCollection() - .AddScoped, InstanceSpecificUserResource>() - .AddSingleton(resourceGraph) - .BuildServiceProvider()); - - var documentBuilder = GetDocumentBuilder(scopedServiceProvider); - - var documents = documentBuilder.Build(entities); - - Assert.Single(documents.Data); - Assert.False(documents.Data[0].Attributes.ContainsKey("password")); - Assert.True(documents.Data[0].Attributes.ContainsKey("username")); - } - - [Fact] - public void Build_Will_Use_Instance_Specific_Resource_If_Defined_For_Single_Document() - { - var entity = new User(); - var resourceGraph = new ResourceGraphBuilder() - .AddResource("user") - .Build(); - _jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - - var scopedServiceProvider = new TestScopedServiceProvider( - new ServiceCollection() - .AddScoped, InstanceSpecificUserResource>() - .AddSingleton(resourceGraph) - .BuildServiceProvider()); - - var documentBuilder = GetDocumentBuilder(scopedServiceProvider); - - var documents = documentBuilder.Build(entity); - - Assert.False(documents.Data.Attributes.ContainsKey("password")); - Assert.True(documents.Data.Attributes.ContainsKey("username")); - } - - public class User : Identifiable - { - [Attr("username")] public string Username { get; set; } - [Attr("password")] public string Password { get; set; } - } - - public class InstanceSpecificUserResource : ResourceDefinition - { - public InstanceSpecificUserResource(IResourceGraph graph) : base(graph) - { - } - - protected override List OutputAttrs(User instance) - => Remove(user => user.Password); - } - - public class UserResource : ResourceDefinition - { - public UserResource(IResourceGraph graph) : base(graph) - { - } - - protected override List OutputAttrs() - => Remove(user => user.Password); - } - private DocumentBuilder GetDocumentBuilder(TestScopedServiceProvider scopedServiceProvider = null, IPageManager pageManager = null) - { - var pageManagerMock = new Mock(); - var rmMock = new Mock(); - rmMock.SetupGet(rm => rm.BasePath).Returns("Localhost"); - - if (pageManager != null) - { - return new DocumentBuilder(_jsonApiContextMock.Object, pageManager, rmMock.Object, scopedServiceProvider: scopedServiceProvider); - } - return new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); - } - } -} +//using System; +//using System.Collections; +//using System.Collections.Generic; +//using JsonApiDotNetCore.Builders; +//using JsonApiDotNetCore.Configuration; +//using JsonApiDotNetCore.Internal; +//using JsonApiDotNetCore.Internal.Contracts; +//using JsonApiDotNetCore.Managers.Contracts; +//using JsonApiDotNetCore.Models; +//using JsonApiDotNetCore.Services; +//using Microsoft.Extensions.DependencyInjection; +//using Moq; +//using Xunit; + +//namespace UnitTests +//{ +// public class DocumentBuilder_Tests +// { +// private readonly Mock _jsonApiContextMock; +// private readonly IPageManager _pageManager; +// private readonly JsonApiOptions _options; +// private readonly Mock _requestMetaMock; + +// public DocumentBuilder_Tests() +// { +// _jsonApiContextMock = new Mock(); +// _requestMetaMock = new Mock(); + +// _options = new JsonApiOptions(); + +// _options.BuildResourceGraph(builder => +// { +// builder.AddResource("models"); +// builder.AddResource("related-models"); +// }); + +// _jsonApiContextMock +// .Setup(m => m.Options) +// .Returns(_options); + +// _jsonApiContextMock +// .Setup(m => m.ResourceGraph) +// .Returns(_options.ResourceGraph); + +// _jsonApiContextMock +// .Setup(m => m.MetaBuilder) +// .Returns(new MetaBuilder()); + +// _pageManager = new Mock().Object; +// _jsonApiContextMock +// .Setup(m => m.PageManager) +// .Returns(_pageManager); + + + +// _jsonApiContextMock +// .Setup(m => m.RequestEntity) +// .Returns(_options.ResourceGraph.GetContextEntity(typeof(Model))); +// } + +// [Fact] +// public void Includes_Paging_Links_By_Default() +// { +// // arrange + + +// var rmMock = new Mock(); +// rmMock.Setup(m => m.GetRequestResource()).Returns(new ContextEntity { EntityName = "resources" }); +// var rm = rmMock.Object; +// var options = new JsonApiOptions { RelativeLinks = false }; +// var pg = new PageManager(new LinkBuilder(options, rm), options, rm); +// pg.PageSize = 1; +// pg.TotalRecords = 1; +// pg.CurrentPage = 1; +// var documentBuilder = GetDocumentBuilder(pageManager: pg); +// var entity = new Model(); + +// // act +// var document = documentBuilder.Build(entity); + +// // assert +// Assert.NotNull(document.Links); +// Assert.NotNull(document.Links.Last); +// } + + + +// [Fact] +// public void Page_Links_Can_Be_Disabled_Globally() +// { +// // arrange +// _pageManager.PageSize = 1; +// _pageManager.TotalRecords = 1; +// _pageManager.CurrentPage = 1; + +// _options.BuildResourceGraph(builder => builder.DocumentLinks = Link.None); + +// _jsonApiContextMock +// .Setup(m => m.ResourceGraph) +// .Returns(_options.ResourceGraph); + +// var documentBuilder = GetDocumentBuilder(); +// var entity = new Model(); + +// // act +// var document = documentBuilder.Build(entity); + +// // assert +// Assert.Null(document.Links); +// } + +// [Fact] +// public void Related_Links_Can_Be_Disabled() +// { +// // arrange +// _pageManager.PageSize = 1; +// _pageManager.TotalRecords = 1; +// _pageManager.CurrentPage = 1; + +// _jsonApiContextMock +// .Setup(m => m.ResourceGraph) +// .Returns(_options.ResourceGraph); + +// var documentBuilder = GetDocumentBuilder(); +// var entity = new Model(); + +// // act +// var document = documentBuilder.Build(entity); + +// // assert +// Assert.Null(document.Data.Relationships["related-model"].Links); +// } + +// [Fact] +// public void Related_Links_Can_Be_Disabled_Globally() +// { +// // arrange +// _pageManager.PageSize = 1; +// _pageManager.TotalRecords = 1; +// _pageManager.CurrentPage = 1; + +// _options.DefaultRelationshipLinks = Link.None; + +// _jsonApiContextMock +// .Setup(m => m.ResourceGraph) +// .Returns(_options.ResourceGraph); + +// var documentBuilder = GetDocumentBuilder(); +// var entity = new RelatedModel(); + +// // act +// var document = documentBuilder.Build(entity); + +// // assert +// Assert.Null(document.Data.Relationships["models"].Links); +// } + +// [Fact] +// public void Related_Data_Included_In_Relationships_By_Default() +// { +// // arrange +// const string relatedTypeName = "related-models"; +// const string relationshipName = "related-model"; +// const int relatedId = 1; +// _jsonApiContextMock +// .Setup(m => m.ResourceGraph) +// .Returns(_options.ResourceGraph); + +// var documentBuilder = GetDocumentBuilder(); +// var entity = new Model +// { +// RelatedModel = new RelatedModel +// { +// Id = relatedId +// } +// }; + +// // act +// var document = documentBuilder.Build(entity); + +// // assert +// var relationshipData = document.Data.Relationships[relationshipName]; +// Assert.NotNull(relationshipData); +// Assert.NotNull(relationshipData.SingleData); +// Assert.NotNull(relationshipData.SingleData); +// Assert.Equal(relatedId.ToString(), relationshipData.SingleData.Id); +// Assert.Equal(relatedTypeName, relationshipData.SingleData.Type); +// } + +// [Fact] +// public void IndependentIdentifier_Included_In_HasOne_Relationships_By_Default() +// { +// // arrange +// const string relatedTypeName = "related-models"; +// const string relationshipName = "related-model"; +// const int relatedId = 1; +// _jsonApiContextMock +// .Setup(m => m.ResourceGraph) +// .Returns(_options.ResourceGraph); + +// var documentBuilder = GetDocumentBuilder(); +// var entity = new Model +// { +// RelatedModelId = relatedId +// }; + +// // act +// var document = documentBuilder.Build(entity); + +// // assert +// var relationshipData = document.Data.Relationships[relationshipName]; +// Assert.NotNull(relationshipData); +// Assert.NotNull(relationshipData.SingleData); +// Assert.NotNull(relationshipData.SingleData); +// Assert.Equal(relatedId.ToString(), relationshipData.SingleData.Id); +// Assert.Equal(relatedTypeName, relationshipData.SingleData.Type); +// } + +// [Fact] +// public void Build_Can_Build_Arrays() +// { +// var entities = new[] { new Model() }; +// var documentBuilder = GetDocumentBuilder(); + +// var documents = documentBuilder.Build(entities); + +// Assert.Single(documents.Data); +// } + +// [Fact] +// public void Build_Can_Build_CustomIEnumerables() +// { +// var entities = new Models(new[] { new Model() }); +// var documentBuilder = GetDocumentBuilder(); + +// var documents = documentBuilder.Build(entities); + +// Assert.Single(documents.Data); +// } + +// [Theory] +// [InlineData(null, null, true)] +// [InlineData(false, null, true)] +// [InlineData(true, null, false)] +// [InlineData(null, "foo", true)] +// [InlineData(false, "foo", true)] +// [InlineData(true, "foo", true)] +// public void DocumentBuilderOptions( +// bool? omitNullValuedAttributes, +// string attributeValue, +// bool resultContainsAttribute) +// { +// var documentBuilderBehaviourMock = new Mock(); +// if (omitNullValuedAttributes.HasValue) +// { +// documentBuilderBehaviourMock.Setup(m => m.GetDocumentBuilderOptions()) +// .Returns(new DocumentBuilderOptions(omitNullValuedAttributes.Value)); +// } +// var pageManagerMock = new Mock(); +// var requestManagerMock = new Mock(); +// var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, requestManagerMock.Object, documentBuilderOptionsProvider: omitNullValuedAttributes.HasValue ? documentBuilderBehaviourMock.Object : null); +// var document = documentBuilder.Build(new Model() { StringProperty = attributeValue }); + +// Assert.Equal(resultContainsAttribute, document.Data.Attributes.ContainsKey("StringProperty")); +// } + +// private class Model : Identifiable +// { +// [Attr("StringProperty")] public string StringProperty { get; set; } + +// [HasOne("related-model", documentLinks: Link.None)] +// public RelatedModel RelatedModel { get; set; } +// public int RelatedModelId { get; set; } +// } + +// private class RelatedModel : Identifiable +// { +// [HasMany("models")] +// public List Models { get; set; } +// } + +// private class Models : IEnumerable +// { +// private readonly IEnumerable models; + +// public Models(IEnumerable models) +// { +// this.models = models; +// } + +// public IEnumerator GetEnumerator() +// { +// return models.GetEnumerator(); +// } + +// IEnumerator IEnumerable.GetEnumerator() +// { +// return models.GetEnumerator(); +// } +// } + +// [Fact] +// public void Build_Will_Use_Resource_If_Defined_For_Multiple_Documents() +// { +// var entities = new[] { new User() }; +// var resourceGraph = new ResourceGraphBuilder() +// .AddResource("user") +// .Build(); +// _jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); + +// var scopedServiceProvider = new TestScopedServiceProvider( +// new ServiceCollection() +// .AddScoped, UserResource>() +// .AddSingleton(resourceGraph) +// .BuildServiceProvider()); + +// var documentBuilder = GetDocumentBuilder(scopedServiceProvider: scopedServiceProvider); + +// var documents = documentBuilder.Build(entities); + +// Assert.Single(documents.Data); +// Assert.False(documents.Data[0].Attributes.ContainsKey("password")); +// Assert.True(documents.Data[0].Attributes.ContainsKey("username")); +// } + +// [Fact] +// public void Build_Will_Use_Resource_If_Defined_For_Single_Document() +// { +// var entity = new User(); +// var resourceGraph = new ResourceGraphBuilder() +// .AddResource("user") +// .Build(); +// _jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); + +// var scopedServiceProvider = new TestScopedServiceProvider( +// new ServiceCollection() +// .AddScoped, UserResource>() +// .AddSingleton(resourceGraph) +// .BuildServiceProvider()); + +// var documentBuilder = GetDocumentBuilder(scopedServiceProvider); + +// var documents = documentBuilder.Build(entity); + +// Assert.False(documents.Data.Attributes.ContainsKey("password")); +// Assert.True(documents.Data.Attributes.ContainsKey("username")); +// } + +// [Fact] +// public void Build_Will_Use_Instance_Specific_Resource_If_Defined_For_Multiple_Documents() +// { +// var entities = new[] { new User() }; +// var resourceGraph = new ResourceGraphBuilder() +// .AddResource("user") +// .Build(); +// _jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); + +// var scopedServiceProvider = new TestScopedServiceProvider( +// new ServiceCollection() +// .AddScoped, InstanceSpecificUserResource>() +// .AddSingleton(resourceGraph) +// .BuildServiceProvider()); + +// var documentBuilder = GetDocumentBuilder(scopedServiceProvider); + +// var documents = documentBuilder.Build(entities); + +// Assert.Single(documents.Data); +// Assert.False(documents.Data[0].Attributes.ContainsKey("password")); +// Assert.True(documents.Data[0].Attributes.ContainsKey("username")); +// } + +// [Fact] +// public void Build_Will_Use_Instance_Specific_Resource_If_Defined_For_Single_Document() +// { +// var entity = new User(); +// var resourceGraph = new ResourceGraphBuilder() +// .AddResource("user") +// .Build(); +// _jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); + +// var scopedServiceProvider = new TestScopedServiceProvider( +// new ServiceCollection() +// .AddScoped, InstanceSpecificUserResource>() +// .AddSingleton(resourceGraph) +// .BuildServiceProvider()); + +// var documentBuilder = GetDocumentBuilder(scopedServiceProvider); + +// var documents = documentBuilder.Build(entity); + +// Assert.False(documents.Data.Attributes.ContainsKey("password")); +// Assert.True(documents.Data.Attributes.ContainsKey("username")); +// } + +// public class User : Identifiable +// { +// [Attr("username")] public string Username { get; set; } +// [Attr("password")] public string Password { get; set; } +// } + +// public class InstanceSpecificUserResource : ResourceDefinition +// { +// public InstanceSpecificUserResource(IResourceGraph graph) : base(graph) +// { +// } + +// protected override List OutputAttrs(User instance) +// => Remove(user => user.Password); +// } + +// public class UserResource : ResourceDefinition +// { +// public UserResource(IResourceGraph graph) : base(graph) +// { +// } + +// protected override List OutputAttrs() +// => Remove(user => user.Password); +// } +// private DocumentBuilder GetDocumentBuilder(TestScopedServiceProvider scopedServiceProvider = null, IPageManager pageManager = null) +// { +// var pageManagerMock = new Mock(); +// var rmMock = new Mock(); +// rmMock.SetupGet(rm => rm.BasePath).Returns("Localhost"); + +// if (pageManager != null) +// { +// return new DocumentBuilder(_jsonApiContextMock.Object, pageManager, rmMock.Object, scopedServiceProvider: scopedServiceProvider); +// } +// return new DocumentBuilder(_jsonApiContextMock.Object, pageManagerMock.Object, rmMock.Object, scopedServiceProvider: scopedServiceProvider); +// } +// } +//} diff --git a/test/UnitTests/Builders/LinkBuilderTests.cs b/test/UnitTests/Builders/LinkBuilderTests.cs index ae8b3ef68e..d42e245c77 100644 --- a/test/UnitTests/Builders/LinkBuilderTests.cs +++ b/test/UnitTests/Builders/LinkBuilderTests.cs @@ -1,49 +1,216 @@ -using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Extensions; +using JsonApiDotNetCore.Models.Links; +using JsonApiDotNetCoreExample.Models; using Moq; using Xunit; +using System; namespace UnitTests { public class LinkBuilderTests { - private readonly Mock _requestManagerMock = new Mock(); + private readonly IPageQueryService _pageManager; + private readonly Mock _provider = new Mock(); private const string _host = "http://www.example.com"; - + private const string _topSelf = "http://www.example.com/articles"; + private const string _resourceSelf = "http://www.example.com/articles/123"; + private const string _relSelf = "http://www.example.com/articles/123/relationships/author"; + private const string _relRelated = "http://www.example.com/articles/123/author"; public LinkBuilderTests() { - _requestManagerMock.Setup(m => m.BasePath).Returns(_host); - _requestManagerMock.Setup(m => m.GetContextEntity()).Returns(new ContextEntity { EntityName = "articles" }); + _pageManager = GetPageManager(); + } + + [Theory] + [InlineData(Link.All, Link.NotConfigured, _resourceSelf)] + [InlineData(Link.Self, Link.NotConfigured, _resourceSelf)] + [InlineData(Link.None, Link.NotConfigured, null)] + [InlineData(Link.All, Link.Self, _resourceSelf)] + [InlineData(Link.Self, Link.Self, _resourceSelf)] + [InlineData(Link.None, Link.Self, _resourceSelf)] + [InlineData(Link.All, Link.None, null)] + [InlineData(Link.Self, Link.None, null)] + [InlineData(Link.None, Link.None, null)] + public void BuildResourceLinks_GlobalAndResourceConfiguration_ExpectedResult(Link global, Link resource, object expectedResult) + { + // arrange + var config = GetConfiguration(resourceLinks: global); + _provider.Setup(m => m.GetContextEntity("articles")).Returns(GetContextEntity
(resourceLinks: resource)); + var builder = new LinkBuilder(config, GetRequestManager(), _pageManager, _provider.Object); + + // act + var links = builder.GetResourceLinks("articles", "123"); + + // assert + if (expectedResult == null) + Assert.Null(links); + else + Assert.Equal(_resourceSelf, links.Self); + } + + + + [Theory] + [InlineData(Link.All, Link.NotConfigured, Link.NotConfigured, _relSelf, _relRelated)] + [InlineData(Link.All, Link.NotConfigured, Link.All, _relSelf, _relRelated)] + [InlineData(Link.All, Link.NotConfigured, Link.Self, _relSelf, null)] + [InlineData(Link.All, Link.NotConfigured, Link.Related, null, _relRelated)] + [InlineData(Link.All, Link.NotConfigured, Link.None, null, null)] + [InlineData(Link.All, Link.All, Link.NotConfigured, _relSelf, _relRelated)] + [InlineData(Link.All, Link.All, Link.All, _relSelf, _relRelated)] + [InlineData(Link.All, Link.All, Link.Self, _relSelf, null)] + [InlineData(Link.All, Link.All, Link.Related, null, _relRelated)] + [InlineData(Link.All, Link.All, Link.None, null, null)] + [InlineData(Link.All, Link.Self, Link.NotConfigured, _relSelf, null)] + [InlineData(Link.All, Link.Self, Link.All, _relSelf, _relRelated)] + [InlineData(Link.All, Link.Self, Link.Self, _relSelf, null)] + [InlineData(Link.All, Link.Self, Link.Related, null, _relRelated)] + [InlineData(Link.All, Link.Self, Link.None, null, null)] + [InlineData(Link.All, Link.Related, Link.NotConfigured, null, _relRelated)] + [InlineData(Link.All, Link.Related, Link.All, _relSelf, _relRelated)] + [InlineData(Link.All, Link.Related, Link.Self, _relSelf, null)] + [InlineData(Link.All, Link.Related, Link.Related, null, _relRelated)] + [InlineData(Link.All, Link.Related, Link.None, null, null)] + [InlineData(Link.All, Link.None, Link.NotConfigured, null, null)] + [InlineData(Link.All, Link.None, Link.All, _relSelf, _relRelated)] + [InlineData(Link.All, Link.None, Link.Self, _relSelf, null)] + [InlineData(Link.All, Link.None, Link.Related, null, _relRelated)] + [InlineData(Link.All, Link.None, Link.None, null, null)] + public void BuildRelationshipLinks_GlobalResourceAndAttrConfiguration_ExpectedLinks(Link global, + Link resource, + Link relationship, + object expectedSelfLink, + object expectedRelatedLink) + { + // arrange + var config = GetConfiguration(relationshipLinks: global); + _provider.Setup(m => m.GetContextEntity(typeof(Article))).Returns(GetContextEntity
(relationshipLinks: resource)); + var builder = new LinkBuilder(config, GetRequestManager(), _pageManager, _provider.Object); + var attr = new HasOneAttribute(links: relationship) { DependentType = typeof(Author), PublicRelationshipName = "author" }; + + // act + var links = builder.GetRelationshipLinks(attr, new Article { Id = 123 }); + + // assert + if (expectedSelfLink == null && expectedRelatedLink == null) + { + Assert.Null(links); + } + else + { + Assert.Equal(expectedSelfLink, links.Self); + Assert.Equal(expectedRelatedLink, links.Related); + } } [Theory] - [InlineData(true)] - [InlineData(false)] - public void GetPageLink_GivenRelativeConfiguration_ReturnsExpectedPath(bool isRelative) + [InlineData(Link.All, Link.NotConfigured, _topSelf, true)] + [InlineData(Link.All, Link.All, _topSelf, true)] + [InlineData(Link.All, Link.Self, _topSelf, false)] + [InlineData(Link.All, Link.Paging, null, true)] + [InlineData(Link.All, Link.None, null, null)] + [InlineData(Link.Self, Link.NotConfigured, _topSelf, false)] + [InlineData(Link.Self, Link.All, _topSelf, true)] + [InlineData(Link.Self, Link.Self, _topSelf, false)] + [InlineData(Link.Self, Link.Paging, null, true)] + [InlineData(Link.Self, Link.None, null, null)] + [InlineData(Link.Paging, Link.NotConfigured, null, true)] + [InlineData(Link.Paging, Link.All, _topSelf, true)] + [InlineData(Link.Paging, Link.Self, _topSelf, false)] + [InlineData(Link.Paging, Link.Paging, null, true)] + [InlineData(Link.Paging, Link.None, null, null)] + [InlineData(Link.None, Link.NotConfigured, null, false)] + [InlineData(Link.None, Link.All, _topSelf, true)] + [InlineData(Link.None, Link.Self, _topSelf, false)] + [InlineData(Link.None, Link.Paging, null, true)] + [InlineData(Link.None, Link.None, null, null)] + public void BuildTopLevelLinks_GlobalAndResourceConfiguration_ExpectedLinks(Link global, + Link resource, + object expectedSelfLink, + bool pages) { - //arrange - var options = new JsonApiOptions { RelativeLinks = isRelative }; - var linkBuilder = new LinkBuilder(options, _requestManagerMock.Object); - var pageSize = 10; - var pageOffset = 20; - var expectedLink = $"/articles?page[size]={pageSize}&page[number]={pageOffset}"; + // arrange + var config = GetConfiguration(topLevelLinks: global); + var resourceContext = GetContextEntity
(topLevelLinks: resource); + var builder = new LinkBuilder(config, GetRequestManager(resourceContext), _pageManager, null); // act - var link = linkBuilder.GetPageLink(pageOffset, pageSize); + var links = builder.GetTopLevelLinks(); // assert - if (isRelative) + if (!pages && expectedSelfLink == null) { - Assert.Equal(expectedLink, link); - } else + Assert.Null(links); + } + else { - Assert.Equal(_host + expectedLink, link); + Assert.Equal(expectedSelfLink, links.Self); + Assert.True(CheckPages(links, pages)); } } - /// todo: write tests for remaining linkBuilder methods + private bool CheckPages(TopLevelLinks links, bool pages) + { + if (pages) + { + return links.First == $"{_host}/articles?page[size]=10&page[number]=1" + && links.Prev == $"{_host}/articles?page[size]=10&page[number]=1" + && links.Next == $"{_host}/articles?page[size]=10&page[number]=3" + && links.Last == $"{_host}/articles?page[size]=10&page[number]=3"; + } + return links.First == null && links.Prev == null && links.Next == null && links.Last == null; + } + + private IRequestManager GetRequestManager(ContextEntity resourceContext = null) + { + var mock = new Mock(); + mock.Setup(m => m.BasePath).Returns(_host); + mock.Setup(m => m.GetRequestResource()).Returns(resourceContext); + return mock.Object; + } + + private IGlobalLinksConfiguration GetConfiguration(Link resourceLinks = Link.All, + Link topLevelLinks = Link.All, + Link relationshipLinks = Link.All) + { + var config = new Mock(); + config.Setup(m => m.TopLevelLinks).Returns(topLevelLinks); + config.Setup(m => m.ResourceLinks).Returns(resourceLinks); + config.Setup(m => m.RelationshipLinks).Returns(relationshipLinks); + return config.Object; + } + + private IPageQueryService GetPageManager() + { + var mock = new Mock(); + mock.Setup(m => m.ShouldPaginate()).Returns(true); + mock.Setup(m => m.CurrentPage).Returns(2); + mock.Setup(m => m.TotalPages).Returns(3); + mock.Setup(m => m.PageSize).Returns(10); + return mock.Object; + + } + + + + private ContextEntity GetContextEntity(Link resourceLinks = Link.NotConfigured, + Link topLevelLinks = Link.NotConfigured, + Link relationshipLinks = Link.NotConfigured) where TResource : class, IIdentifiable + { + return new ContextEntity + { + ResourceLinks = resourceLinks, + TopLevelLinks = topLevelLinks, + RelationshipLinks = relationshipLinks, + EntityName = typeof(TResource).Name.Dasherize() + "s" + }; + } } } diff --git a/test/UnitTests/Builders/LinkTests.cs b/test/UnitTests/Builders/LinkTests.cs index 8adf2649da..4dc3dba47f 100644 --- a/test/UnitTests/Builders/LinkTests.cs +++ b/test/UnitTests/Builders/LinkTests.cs @@ -1,10 +1,39 @@ using System; +using JsonApiDotNetCore.Models.Links; +using Xunit; + namespace UnitTests.Builders { public class LinkTests { - public LinkTests() + [Theory] + [InlineData(Link.All, Link.Self, true)] + [InlineData(Link.All, Link.Related, true)] + [InlineData(Link.All, Link.Paging, true)] + [InlineData(Link.None, Link.Self, false)] + [InlineData(Link.None, Link.Related, false)] + [InlineData(Link.None, Link.Paging, false)] + [InlineData(Link.NotConfigured, Link.Self, false)] + [InlineData(Link.NotConfigured, Link.Related, false)] + [InlineData(Link.NotConfigured, Link.Paging, false)] + [InlineData(Link.Self, Link.Self, true)] + [InlineData(Link.Self, Link.Related, false)] + [InlineData(Link.Self, Link.Paging, false)] + [InlineData(Link.Self, Link.None, false)] + [InlineData(Link.Self, Link.NotConfigured, false)] + [InlineData(Link.Related, Link.Self, false)] + [InlineData(Link.Related, Link.Related, true)] + [InlineData(Link.Related, Link.Paging, false)] + [InlineData(Link.Related, Link.None, false)] + [InlineData(Link.Related, Link.NotConfigured, false)] + [InlineData(Link.Paging, Link.Self, false)] + [InlineData(Link.Paging, Link.Related, false)] + [InlineData(Link.Paging, Link.Paging, true)] + [InlineData(Link.Paging, Link.None, false)] + [InlineData(Link.Paging, Link.NotConfigured, false)] + public void LinkHasFlag_BaseLinkAndCheckLink_ExpectedResult(Link baseLink, Link checkLink, bool equal) { + Assert.Equal(equal, baseLink.HasFlag(checkLink)); } } } diff --git a/test/UnitTests/Builders/MetaBuilderTests.cs b/test/UnitTests/Builders/MetaBuilderTests.cs index 0b784ef5b7..c0cf81d4d3 100644 --- a/test/UnitTests/Builders/MetaBuilderTests.cs +++ b/test/UnitTests/Builders/MetaBuilderTests.cs @@ -1,73 +1,73 @@ -using System.Collections.Generic; -using JsonApiDotNetCore.Builders; -using Xunit; +//using System.Collections.Generic; +//using JsonApiDotNetCore.Builders; +//using Xunit; -namespace UnitTests.Builders -{ - public class MetaBuilderTests - { - [Fact] - public void Can_Add_Key_Value() - { - // arrange - var builder = new MetaBuilder(); - var key = "test"; - var value = "testValue"; +//namespace UnitTests.Builders +//{ +// public class MetaBuilderTests +// { +// [Fact] +// public void Can_Add_Key_Value() +// { +// // arrange +// var builder = new MetaBuilder(); +// var key = "test"; +// var value = "testValue"; - // act - builder.Add(key, value); - var result = builder.Build(); +// // act +// builder.Add(key, value); +// var result = builder.Build(); - // assert - Assert.NotEmpty(result); - Assert.Equal(value, result[key]); - } +// // assert +// Assert.NotEmpty(result); +// Assert.Equal(value, result[key]); +// } - [Fact] - public void Can_Add_Multiple_Values() - { - // arrange - var builder = new MetaBuilder(); - var input = new Dictionary { - { "key1", "value1" }, - { "key2", "value2" } - }; +// [Fact] +// public void Can_Add_Multiple_Values() +// { +// // arrange +// var builder = new MetaBuilder(); +// var input = new Dictionary { +// { "key1", "value1" }, +// { "key2", "value2" } +// }; - // act - builder.Add(input); - var result = builder.Build(); +// // act +// builder.Add(input); +// var result = builder.Build(); - // assert - Assert.NotEmpty(result); - foreach (var entry in input) - Assert.Equal(input[entry.Key], result[entry.Key]); - } +// // assert +// Assert.NotEmpty(result); +// foreach (var entry in input) +// Assert.Equal(input[entry.Key], result[entry.Key]); +// } - [Fact] - public void When_Adding_Duplicate_Values_Keep_Newest() - { - // arrange - var builder = new MetaBuilder(); +// [Fact] +// public void When_Adding_Duplicate_Values_Keep_Newest() +// { +// // arrange +// var builder = new MetaBuilder(); - var key = "key"; - var oldValue = "oldValue"; - var newValue = "newValue"; +// var key = "key"; +// var oldValue = "oldValue"; +// var newValue = "newValue"; - builder.Add(key, oldValue); +// builder.Add(key, oldValue); - var input = new Dictionary { - { key, newValue }, - { "key2", "value2" } - }; +// var input = new Dictionary { +// { key, newValue }, +// { "key2", "value2" } +// }; - // act - builder.Add(input); - var result = builder.Build(); +// // act +// builder.Add(input); +// var result = builder.Build(); - // assert - Assert.NotEmpty(result); - Assert.Equal(input.Count, result.Count); - Assert.Equal(input[key], result[key]); - } - } -} +// // assert +// Assert.NotEmpty(result); +// Assert.Equal(input.Count, result.Count); +// Assert.Equal(input[key], result[key]); +// } +// } +//} diff --git a/test/UnitTests/Data/DefaultEntityRepository_Tests.cs b/test/UnitTests/Data/DefaultEntityRepository_Tests.cs index 0b3f3469cf..9e14d173d9 100644 --- a/test/UnitTests/Data/DefaultEntityRepository_Tests.cs +++ b/test/UnitTests/Data/DefaultEntityRepository_Tests.cs @@ -1,187 +1,179 @@ -using System; -using System.Collections.Generic; -using JsonApiDotNetCore.Controllers; -using Xunit; -using Moq; -using Microsoft.EntityFrameworkCore; -using JsonApiDotNetCoreExample.Models; -using JsonApiDotNetCore.Extensions; -using JsonApiDotNetCore.Data; -using JsonApiDotNetCore.Models; -using Microsoft.Extensions.Logging; -using JsonApiDotNetCore.Services; -using System.Threading.Tasks; -using System.Linq; -using JsonApiDotNetCore.Request; - -namespace UnitTests.Data -{ - public class DefaultEntityRepository_Tests : JsonApiControllerMixin - { - private readonly Mock _jsonApiContextMock; - private readonly Mock _loggFactoryMock; - private readonly Mock> _dbSetMock; - private readonly Mock _contextMock; - private readonly Mock _contextResolverMock; - private readonly TodoItem _todoItem; - private Dictionary _attrsToUpdate = new Dictionary(); - private Dictionary _relationshipsToUpdate = new Dictionary(); - - public DefaultEntityRepository_Tests() - { - _todoItem = new TodoItem - { - Id = 1, - Description = Guid.NewGuid().ToString(), - Ordinal = 10 - }; - _jsonApiContextMock = new Mock(); - _loggFactoryMock = new Mock(); - _dbSetMock = DbSetMock.Create(new[] { _todoItem }); - _contextMock = new Mock(); - _contextResolverMock = new Mock(); - } - - [Fact] - public async Task UpdateAsync_Updates_Attributes_In_AttributesToUpdate() - { - // arrange - var todoItemUpdates = new TodoItem - { - Id = _todoItem.Id, - Description = Guid.NewGuid().ToString() - }; - - var descAttr = new AttrAttribute("description", "Description"); - descAttr.PropertyInfo = typeof(TodoItem).GetProperty(nameof(TodoItem.Description)); - - _attrsToUpdate = new Dictionary - { - { - descAttr, - null //todoItemUpdates.Description - } - }; - - var repository = GetRepository(); - - // act - var updatedItem = await repository.UpdateAsync(todoItemUpdates); - - // assert - Assert.NotNull(updatedItem); - Assert.Equal(_todoItem.Ordinal, updatedItem.Ordinal); - Assert.Equal(todoItemUpdates.Description, updatedItem.Description); - } - - private DefaultEntityRepository GetRepository() - { - - _contextMock - .Setup(m => m.Set()) - .Returns(_dbSetMock.Object); - - _contextResolverMock - .Setup(m => m.GetContext()) - .Returns(_contextMock.Object); - - _jsonApiContextMock - .Setup(m => m.RequestManager.GetUpdatedAttributes()) - .Returns(_attrsToUpdate); - - _jsonApiContextMock - .Setup(m => m.RequestManager.GetUpdatedRelationships()) - .Returns(_relationshipsToUpdate); - - _jsonApiContextMock - .Setup(m => m.HasManyRelationshipPointers) - .Returns(new HasManyRelationshipPointers()); - - _jsonApiContextMock - .Setup(m => m.HasOneRelationshipPointers) - .Returns(new HasOneRelationshipPointers()); - - return new DefaultEntityRepository( - _loggFactoryMock.Object, - _jsonApiContextMock.Object, - _contextResolverMock.Object); - } - - [Theory] - [InlineData(0)] - [InlineData(-1)] - [InlineData(-10)] - public async Task Page_When_PageSize_Is_NonPositive_Does_Nothing(int pageSize) - { - var todoItems = DbSetMock.Create(TodoItems(2, 3, 1)).Object; - var repository = GetRepository(); - - var result = await repository.PageAsync(todoItems, pageSize, 3); - - Assert.Equal(TodoItems(2, 3, 1), result, new IdComparer()); - } - - [Fact] - public async Task Page_When_PageNumber_Is_Zero_Pretends_PageNumber_Is_One() - { - var todoItems = DbSetMock.Create(TodoItems(2, 3, 1)).Object; - var repository = GetRepository(); - - var result = await repository.PageAsync(todoItems, 1, 0); - - Assert.Equal(TodoItems(2), result, new IdComparer()); - } - - [Fact] - public async Task Page_When_PageNumber_Of_PageSize_Does_Not_Exist_Return_Empty_Queryable() - { - var todoItems = DbSetMock.Create(TodoItems(2, 3, 1)).Object; - var repository = GetRepository(); - - var result = await repository.PageAsync(todoItems, 2, 3); - - Assert.Empty(result); - } - - [Theory] - [InlineData(3, 2, new[] { 4, 5, 6 })] - [InlineData(8, 2, new[] { 9 })] - [InlineData(20, 1, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 })] - public async Task Page_When_PageNumber_Is_Positive_Returns_PageNumberTh_Page_Of_Size_PageSize(int pageSize, int pageNumber, int[] expectedResult) - { - var todoItems = DbSetMock.Create(TodoItems(1, 2, 3, 4, 5, 6, 7, 8, 9)).Object; - var repository = GetRepository(); - - var result = await repository.PageAsync(todoItems, pageSize, pageNumber); - - Assert.Equal(TodoItems(expectedResult), result, new IdComparer()); - } - - [Theory] - [InlineData(6, -1, new[] { 4, 5, 6, 7, 8, 9 })] - [InlineData(6, -2, new[] { 1, 2, 3 })] - [InlineData(20, -1, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 })] - public async Task Page_When_PageNumber_Is_Negative_Returns_PageNumberTh_Page_From_End(int pageSize, int pageNumber, int[] expectedIds) - { - var todoItems = DbSetMock.Create(TodoItems(1, 2, 3, 4, 5, 6, 7, 8, 9)).Object; - var repository = GetRepository(); - - var result = await repository.PageAsync(todoItems, pageSize, pageNumber); +//using System; +//using System.Collections.Generic; +//using JsonApiDotNetCore.Controllers; +//using Xunit; +//using Moq; +//using Microsoft.EntityFrameworkCore; +//using JsonApiDotNetCoreExample.Models; +//using JsonApiDotNetCore.Extensions; +//using JsonApiDotNetCore.Data; +//using JsonApiDotNetCore.Models; +//using Microsoft.Extensions.Logging; +//using JsonApiDotNetCore.Services; +//using System.Threading.Tasks; +//using System.Linq; + +//namespace UnitTests.Data +//{ +// public class DefaultEntityRepository_Tests : JsonApiControllerMixin +// { +// private readonly Mock _jsonApiContextMock; +// private readonly Mock _loggFactoryMock; +// private readonly Mock> _dbSetMock; +// private readonly Mock _contextMock; +// private readonly Mock _contextResolverMock; +// private readonly TodoItem _todoItem; +// private Dictionary _attrsToUpdate = new Dictionary(); +// private Dictionary _relationshipsToUpdate = new Dictionary(); + +// public DefaultEntityRepository_Tests() +// { +// _todoItem = new TodoItem +// { +// Id = 1, +// Description = Guid.NewGuid().ToString(), +// Ordinal = 10 +// }; +// _jsonApiContextMock = new Mock(); +// _loggFactoryMock = new Mock(); +// _dbSetMock = DbSetMock.Create(new[] { _todoItem }); +// _contextMock = new Mock(); +// _contextResolverMock = new Mock(); +// } + +// [Fact] +// public async Task UpdateAsync_Updates_Attributes_In_AttributesToUpdate() +// { +// // arrange +// var todoItemUpdates = new TodoItem +// { +// Id = _todoItem.Id, +// Description = Guid.NewGuid().ToString() +// }; + +// var descAttr = new AttrAttribute("description", "Description"); +// descAttr.PropertyInfo = typeof(TodoItem).GetProperty(nameof(TodoItem.Description)); + +// _attrsToUpdate = new Dictionary +// { +// { +// descAttr, +// null //todoItemUpdates.Description +// } +// }; + +// var repository = GetRepository(); + +// // act +// var updatedItem = await repository.UpdateAsync(todoItemUpdates); + +// // assert +// Assert.NotNull(updatedItem); +// Assert.Equal(_todoItem.Ordinal, updatedItem.Ordinal); +// Assert.Equal(todoItemUpdates.Description, updatedItem.Description); +// } + +// private DefaultEntityRepository GetRepository() +// { + +// _contextMock +// .Setup(m => m.Set()) +// .Returns(_dbSetMock.Object); + +// _contextResolverMock +// .Setup(m => m.GetContext()) +// .Returns(_contextMock.Object); + +// _jsonApiContextMock +// .Setup(m => m.RequestManager.GetUpdatedAttributes()) +// .Returns(_attrsToUpdate); + +// _jsonApiContextMock +// .Setup(m => m.RequestManager.GetUpdatedRelationships()) +// .Returns(_relationshipsToUpdate); + + +// return new DefaultEntityRepository( +// _loggFactoryMock.Object, +// _jsonApiContextMock.Object, +// _contextResolverMock.Object); +// } + +// [Theory] +// [InlineData(0)] +// [InlineData(-1)] +// [InlineData(-10)] +// public async Task Page_When_PageSize_Is_NonPositive_Does_Nothing(int pageSize) +// { +// var todoItems = DbSetMock.Create(TodoItems(2, 3, 1)).Object; +// var repository = GetRepository(); + +// var result = await repository.PageAsync(todoItems, pageSize, 3); + +// Assert.Equal(TodoItems(2, 3, 1), result, new IdComparer()); +// } + +// [Fact] +// public async Task Page_When_PageNumber_Is_Zero_Pretends_PageNumber_Is_One() +// { +// var todoItems = DbSetMock.Create(TodoItems(2, 3, 1)).Object; +// var repository = GetRepository(); + +// var result = await repository.PageAsync(todoItems, 1, 0); + +// Assert.Equal(TodoItems(2), result, new IdComparer()); +// } + +// [Fact] +// public async Task Page_When_PageNumber_Of_PageSize_Does_Not_Exist_Return_Empty_Queryable() +// { +// var todoItems = DbSetMock.Create(TodoItems(2, 3, 1)).Object; +// var repository = GetRepository(); + +// var result = await repository.PageAsync(todoItems, 2, 3); + +// Assert.Empty(result); +// } + +// [Theory] +// [InlineData(3, 2, new[] { 4, 5, 6 })] +// [InlineData(8, 2, new[] { 9 })] +// [InlineData(20, 1, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 })] +// public async Task Page_When_PageNumber_Is_Positive_Returns_PageNumberTh_Page_Of_Size_PageSize(int pageSize, int pageNumber, int[] expectedResult) +// { +// var todoItems = DbSetMock.Create(TodoItems(1, 2, 3, 4, 5, 6, 7, 8, 9)).Object; +// var repository = GetRepository(); + +// var result = await repository.PageAsync(todoItems, pageSize, pageNumber); + +// Assert.Equal(TodoItems(expectedResult), result, new IdComparer()); +// } + +// [Theory] +// [InlineData(6, -1, new[] { 4, 5, 6, 7, 8, 9 })] +// [InlineData(6, -2, new[] { 1, 2, 3 })] +// [InlineData(20, -1, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 })] +// public async Task Page_When_PageNumber_Is_Negative_Returns_PageNumberTh_Page_From_End(int pageSize, int pageNumber, int[] expectedIds) +// { +// var todoItems = DbSetMock.Create(TodoItems(1, 2, 3, 4, 5, 6, 7, 8, 9)).Object; +// var repository = GetRepository(); + +// var result = await repository.PageAsync(todoItems, pageSize, pageNumber); - Assert.Equal(TodoItems(expectedIds), result, new IdComparer()); - } - - private static TodoItem[] TodoItems(params int[] ids) - { - return ids.Select(id => new TodoItem { Id = id }).ToArray(); - } - - private class IdComparer : IEqualityComparer - where T : IIdentifiable - { - public bool Equals(T x, T y) => x?.StringId == y?.StringId; - - public int GetHashCode(T obj) => obj?.StringId?.GetHashCode() ?? 0; - } - } -} +// Assert.Equal(TodoItems(expectedIds), result, new IdComparer()); +// } + +// private static TodoItem[] TodoItems(params int[] ids) +// { +// return ids.Select(id => new TodoItem { Id = id }).ToArray(); +// } + +// private class IdComparer : IEqualityComparer +// where T : IIdentifiable +// { +// public bool Equals(T x, T y) => x?.StringId == y?.StringId; + +// public int GetHashCode(T obj) => obj?.StringId?.GetHashCode() ?? 0; +// } +// } +//} diff --git a/test/UnitTests/Deserialization/BaseDeserializerTests.cs b/test/UnitTests/Deserialization/BaseDeserializerTests.cs deleted file mode 100644 index 7cc858f793..0000000000 --- a/test/UnitTests/Deserialization/BaseDeserializerTests.cs +++ /dev/null @@ -1,370 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using Newtonsoft.Json; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class BaseDeserializerTests : DeserializerTestsSetup - { - private readonly DeserializerBase _deserializer; - public BaseDeserializerTests() - { - _deserializer = new DeserializerBase(_resourceGraph, _defaultSettings); - } - - [Fact] - public void DeserializeResourceIdentifiers_SingleData_CanDeserialize() - { - // arange - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = (TestResource)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - - } - - [Fact] - public void DeserializeResourceIdentifiers_EmptySingleData_CanDeserialize() - { - // arange - var content = new Document { }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.Deserialize(body); - - // arrange - Assert.Null(result); - } - - [Fact] - public void DeserializeResourceIdentifiers_ArrayData_CanDeserialize() - { - // arange - var content = new Documents - { - Data = new List - { - new ResourceObject - { - Type = "test-resource", - Id = "1", - } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = (List)_deserializer.Deserialize(body); - - // assert - Assert.Equal("1", result.First().StringId); - } - - [Fact] - public void DeserializeResourceIdentifiers_EmptyArrayData_CanDeserialize() - { - var content = new Documents { Data = new List { } }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = (IList)_deserializer.Deserialize(body); - - // assert - Assert.Empty(result); - } - - [Theory] - [InlineData("string-field", "some string")] - [InlineData("string-field", null)] - [InlineData("int-field", null, true)] - [InlineData("int-field", 1)] - [InlineData("int-field", "1")] - [InlineData("nullable-int-field", null)] - [InlineData("nullable-int-field", "1")] - [InlineData("guid-field", "bad format", true)] - [InlineData("guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781")] - [InlineData("date-time-field", "9/11/2019 11:41:40 AM")] - [InlineData("date-time-field", null, true)] - [InlineData("nullable-date-time-field", null)] - public void DeserializeAttributes_VariousDataTypes_CanDeserialize(string member, object value, bool expectError = false) - { - // arrange - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { member, value } - } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act, assert - if (expectError) - { - Assert.ThrowsAny(() => _deserializer.Deserialize(body)); - return; - } - - // act - var entity = (TestResource)_deserializer.Deserialize(body); - - // assert - var pi = _resourceGraph.GetContextEntity("test-resource").Attributes.Single(attr => attr.PublicAttributeName == member).PropertyInfo; - var deserializedValue = pi.GetValue(entity); - - if (member == "int-field") - { - Assert.Equal(deserializedValue, 1); - } - else if (member == "nullable-int-field" && value == null) - { - Assert.Equal(deserializedValue, null); - } - else if (member == "nullable-int-field" && (string)value == "1") - { - Assert.Equal(deserializedValue, 1); - } - else if (member == "guid-field") - { - Assert.Equal(deserializedValue, Guid.Parse("1a68be43-cc84-4924-a421-7f4d614b7781")); - } - else if (member == "date-time-field") - { - Assert.Equal(deserializedValue, DateTime.Parse("9/11/2019 11:41:40 AM")); - } else - { - Assert.Equal(value, deserializedValue); - } - } - - [Fact] - public void DeserializeAttributes_ComplexType_CanDeserialize() - { - // arrange - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { "complex-field", new Dictionary { {"compound-name", "testName" } } } - } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = (TestResource)_deserializer.Deserialize(body); - - // assert - Assert.NotNull(result.ComplexField); - Assert.Equal("testName", result.ComplexField.CompoundName); - } - - [Fact] - public void DeserializeAttributes_ComplexListType_CanDeserialize() - { - // arrange - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource-with-list", - Id = "1", - Attributes = new Dictionary - { - { "complex-fields", new [] { new Dictionary { {"compound-name", "testName" } } } } - } - } - }; - var body = JsonConvert.SerializeObject(content); - - - // act - var result = (TestResourceWithList)_deserializer.Deserialize(body); - - // assert - Assert.NotNull(result.ComplexFields); - Assert.NotEmpty(result.ComplexFields); - Assert.Equal("testName", result.ComplexFields[0].CompoundName); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToOneDependent_NavigationPropertyIsNull() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToOnePrincipal)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Dependent); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_PopulatedOneToOneDependent_NavigationPropertyIsPopulated() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent", "one-to-one-dependents"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToOnePrincipal)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Equal(10, result.Dependent.Id); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToOnePrincipal_NavigationPropertyAndForeignKeyAreNull() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToOneDependent)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Principal); - Assert.Null(result.PrincipalId); - } - - [Fact] - public void DeserializeRelationships_EmptyRequiredOneToOnePrincipal_ThrowsFormatException() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-required-dependents", "principal"); - var body = JsonConvert.SerializeObject(content); - - // act, assert - Assert.ThrowsAny(() => _deserializer.Deserialize(body)); - } - - [Fact] - public void DeserializeRelationships_PopulatedOneToOnePrincipal_NavigationPropertyIsNullAndForeignKeyIsPopulated() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal", "one-to-one-principals"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToOneDependent)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Principal); - Assert.Equal(10, result.PrincipalId); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToManyPrincipal_NavigationAndForeignKeyAreNull() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToManyDependent)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Principal); - Assert.Null(result.PrincipalId); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToManyRequiredPrincipal_ThrowsFormatException() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-required-dependents", "principal"); - var body = JsonConvert.SerializeObject(content); - - // act, assert - Assert.ThrowsAny(() => _deserializer.Deserialize(body)); - } - - [Fact] - public void DeserializeRelationships_PopulatedOneToManyPrincipal_NavigationIsNullAndForeignKeyIsPopulated() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal", "one-to-many-principals"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToManyDependent)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Principal); - Assert.Equal(10, result.PrincipalId); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToManyDependent_NavigationIsNull() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToManyPrincipal)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Dependents); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_PopulatedOneToManyDependent_NavigationIsPopulated() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents", "one-to-many-dependents", isToManyData: true); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToManyPrincipal)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Equal(1, result.Dependents.Count); - Assert.Equal(10, result.Dependents.First().Id); - Assert.Null(result.AttributeMember); - } - } -} diff --git a/test/UnitTests/Deserialization/ClientDeserializerTests.cs b/test/UnitTests/Deserialization/ClientDeserializerTests.cs deleted file mode 100644 index 315b932b11..0000000000 --- a/test/UnitTests/Deserialization/ClientDeserializerTests.cs +++ /dev/null @@ -1,333 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using Newtonsoft.Json; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class ClientDeserializerTests : DeserializerTestsSetup - { - private readonly Dictionary _linkValues = new Dictionary(); - private readonly ClientDeserializer _deserializer; - - public ClientDeserializerTests() - { - _deserializer = new ClientDeserializer(_resourceGraph, _defaultSettings); - _linkValues.Add("self", "http://example.com/articles"); - _linkValues.Add("next", "http://example.com/articles?page[offset]=2"); - _linkValues.Add("last", "http://example.com/articles?page[offset]=10"); - } - - [Fact] - public void DeserializeSingle_EmptyResponseWithMeta_CanDeserialize() - { - // arrange - var content = new Document - { - Meta = new Dictionary { { "foo", "bar" } } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - - // assert - Assert.Null(result.Data); - Assert.NotNull(result.Meta); - Assert.Equal("bar", result.Meta["foo"]); - } - - [Fact] - public void DeserializeSingle_EmptyResponseWithTopLevelLinks_CanDeserialize() - { - // arrange - var content = new Document - { - Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - - // assert - Assert.Null(result.Data); - Assert.NotNull(result.Links); - Assert.Equal(_linkValues["self"], result.Links.Self); - Assert.Equal(_linkValues["next"], result.Links.Next); - Assert.Equal(_linkValues["last"], result.Links.Last); - } - - [Fact] - public void DeserializeList_EmptyResponseWithTopLevelLinks_CanDeserialize() - { - // arrange - var content = new Documents - { - Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] }, - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeList(body); - - // assert - Assert.Empty(result.Data); - Assert.NotNull(result.Links); - Assert.Equal(_linkValues["self"], result.Links.Self); - Assert.Equal(_linkValues["next"], result.Links.Next); - Assert.Equal(_linkValues["last"], result.Links.Last); - } - - [Fact] - public void DeserializeSingle_ResourceWithAttributes_CanDeserialize() - { - // arrange - var content = CreateTestResourceDocument(); - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Null(result.Links); - Assert.Null(result.Meta); - Assert.Equal(1, entity.Id); - Assert.Equal(content.Data.Attributes["string-field"], entity.StringField); - } - - [Fact] - public void DeserializeSingle_MultipleDependentRelationshipsWithIncluded_CanDeserialize() - { - // arrange - var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); - content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); - var toOneAttributeValue = "populated-to-one member content"; - var toManyAttributeValue = "populated-to-manies member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "one-to-one-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } - }, - new ResourceObject() - { - Type = "one-to-many-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Equal(1, entity.Id); - Assert.NotNull(entity.PopulatedToOne); - Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); - Assert.Equal(toManyAttributeValue, entity.PopulatedToManies.First().AttributeMember); - Assert.NotNull(entity.PopulatedToManies); - Assert.NotNull(entity.EmptyToManies); - Assert.Empty(entity.EmptyToManies); - Assert.Null(entity.EmptyToOne); - } - - [Fact] - public void DeserializeSingle_MultiplePrincipalRelationshipsWithIncluded_CanDeserialize() - { - // arrange - var content = CreateDocumentWithRelationships("multi-dependents"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); - content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); - var toOneAttributeValue = "populated-to-one member content"; - var toManyAttributeValue = "populated-to-manies member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "one-to-one-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } - }, - new ResourceObject() - { - Type = "one-to-many-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Equal(1, entity.Id); - Assert.NotNull(entity.PopulatedToOne); - Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); - Assert.Equal(toManyAttributeValue, entity.PopulatedToMany.AttributeMember); - Assert.NotNull(entity.PopulatedToMany); - Assert.Null(entity.EmptyToMany); - Assert.Null(entity.EmptyToOne); - } - - [Fact] - public void DeserializeSingle_NestedIncluded_CanDeserialize() - { - // arrange - var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); - var toManyAttributeValue = "populated-to-manies member content"; - var nestedIncludeAttributeValue = "nested include member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "one-to-many-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } }, - Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } - }, - new ResourceObject() - { - Type = "one-to-many-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", nestedIncludeAttributeValue } } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Equal(1, entity.Id); - Assert.Null(entity.PopulatedToOne); - Assert.Null(entity.EmptyToManies); - Assert.Null(entity.EmptyToOne); - Assert.NotNull(entity.PopulatedToManies); - var includedEntity = entity.PopulatedToManies.First(); - Assert.Equal(toManyAttributeValue, includedEntity.AttributeMember); - var nestedIncludedEntity = includedEntity.Principal; - Assert.Equal(nestedIncludeAttributeValue, nestedIncludedEntity.AttributeMember); - } - - - [Fact] - public void DeserializeSingle_DeeplyNestedIncluded_CanDeserialize() - { - // arrange - var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("multi", CreateRelationshipData("multi-principals")); - var includedAttributeValue = "multi member content"; - var nestedIncludedAttributeValue = "nested include member content"; - var deeplyNestedIncludedAttributeValue = "deeply nested member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "multi-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, - Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } - }, - new ResourceObject() - { - Type = "one-to-many-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, - Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } - }, - new ResourceObject() - { - Type = "one-to-many-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } - }, - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Equal(1, entity.Id); - var included = entity.Multi; - Assert.Equal(10, included.Id); - Assert.Equal(includedAttributeValue, included.AttributeMember); - var nestedIncluded = included.PopulatedToManies.First(); - Assert.Equal(10, nestedIncluded.Id); - Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); - var deeplyNestedIncluded = nestedIncluded.Principal; - Assert.Equal(10, deeplyNestedIncluded.Id); - Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); - } - - - [Fact] - public void DeserializeList_DeeplyNestedIncluded_CanDeserialize() - { - // arrange - var content = new Documents { Data = new List { CreateDocumentWithRelationships("multi-principals").Data } }; - content.Data[0].Relationships.Add("multi", CreateRelationshipData("multi-principals")); - var includedAttributeValue = "multi member content"; - var nestedIncludedAttributeValue = "nested include member content"; - var deeplyNestedIncludedAttributeValue = "deeply nested member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "multi-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, - Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } - }, - new ResourceObject() - { - Type = "one-to-many-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, - Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } - }, - new ResourceObject() - { - Type = "one-to-many-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } - }, - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeList(body); - var entity = result.Data.First(); - - // assert - Assert.Equal(1, entity.Id); - var included = entity.Multi; - Assert.Equal(10, included.Id); - Assert.Equal(includedAttributeValue, included.AttributeMember); - var nestedIncluded = included.PopulatedToManies.First(); - Assert.Equal(10, nestedIncluded.Id); - Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); - var deeplyNestedIncluded = nestedIncluded.Principal; - Assert.Equal(10, deeplyNestedIncluded.Id); - Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); - } - } -} diff --git a/test/UnitTests/Deserialization/DasherizedResolverTests.cs b/test/UnitTests/Deserialization/DasherizedResolverTests.cs deleted file mode 100644 index ca746bfb91..0000000000 --- a/test/UnitTests/Deserialization/DasherizedResolverTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Serialization.Contracts; - -using Newtonsoft.Json; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class DasherizedResolverTests - { - [Fact] - public void Resolver_Dasherizes_Property_Names() - { - // arrange - var obj = new - { - myProp = "val" - }; - - // act - var result = JsonConvert.SerializeObject(obj, - Formatting.None, - new JsonSerializerSettings { ContractResolver = new DasherizedResolver() } - ); - - // assert - Assert.Equal("{\"my-prop\":\"val\"}", result); - } - } -} diff --git a/test/UnitTests/Deserialization/DeserializerTestsSetup.cs b/test/UnitTests/Deserialization/DeserializerTestsSetup.cs deleted file mode 100644 index 3c37ad52b3..0000000000 --- a/test/UnitTests/Deserialization/DeserializerTestsSetup.cs +++ /dev/null @@ -1,172 +0,0 @@ -using JsonApiDotNetCore.Models; -using System.Collections.Generic; -using System; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Serialization; - -namespace UnitTests.Deserialization -{ - public class DeserializerTestsSetup - { - protected readonly IResourceGraph _resourceGraph; - protected readonly JsonApiSerializerSettings _defaultSettings = new JsonApiSerializerSettings(); - - public DeserializerTestsSetup() - { - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - resourceGraphBuilder.AddResource("test-resource-with-list"); - // one to one relationships - resourceGraphBuilder.AddResource("one-to-one-principals"); - resourceGraphBuilder.AddResource("one-to-one-dependents"); - resourceGraphBuilder.AddResource("one-to-one-required-dependents"); - // one to many relationships - resourceGraphBuilder.AddResource("one-to-many-principals"); - resourceGraphBuilder.AddResource("one-to-many-dependents"); - resourceGraphBuilder.AddResource("one-to-many-required-dependents"); - // collective relationships - resourceGraphBuilder.AddResource("multi-principals"); - resourceGraphBuilder.AddResource("multi-dependents"); - _resourceGraph = resourceGraphBuilder.Build(); - } - - protected Document CreateDocumentWithRelationships(string mainType, string relationshipMemberName, string relatedType = null, bool isToManyData = false) - { - var content = CreateDocumentWithRelationships(mainType); - content.Data.Relationships.Add(relationshipMemberName, CreateRelationshipData(relatedType, isToManyData)); - return content; - } - - protected Document CreateDocumentWithRelationships(string mainType) - { - return new Document - { - Data = new ResourceObject - { - Id = "1", - Type = mainType, - Relationships = new Dictionary { } - } - }; - } - - protected RelationshipData CreateRelationshipData(string relatedType = null, bool isToManyData = false) - { - var data = new RelationshipData(); - var rio = relatedType == null ? null : new ResourceIdentifierObject { Id = "10", Type = relatedType }; - - if (isToManyData) - { - data.ExposedData = new List(); - if (relatedType != null) ((List)data.ExposedData).Add(rio); - } else - { - data.ExposedData = rio; - } - return data; - } - - protected Document CreateTestResourceDocument() - { - return new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { "string-field", "some string" }, - { "int-field", 1 }, - { "nullable-int-field", null }, - { "guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781" }, - { "date-time-field", "9/11/2019 11:41:40 AM" } - } - } - }; - } - - protected class TestResource : Identifiable - { - [Attr] public string StringField { get; set; } - [Attr] public DateTime DateTimeField { get; set; } - [Attr] public DateTime? NullableDateTimeField { get; set; } - [Attr] public int IntField { get; set; } - [Attr] public int? NullableIntField { get; set; } - [Attr] public Guid GuidField { get; set; } - [Attr] public ComplexType ComplexField { get; set; } - [Attr(isImmutable: true)] public string Immutable { get; set; } - } - - protected class TestResourceWithList : Identifiable - { - [Attr] public List ComplexFields { get; set; } - } - - protected class ComplexType - { - public string CompoundName { get; set; } - } - - protected class OneToOnePrincipal : IdentifiableWithAttribute - { - [HasOne] public OneToOneDependent Dependent { get; set; } - } - - protected class OneToOneDependent : IdentifiableWithAttribute - { - [HasOne] public OneToOnePrincipal Principal { get; set; } - public int? PrincipalId { get; set; } - } - - protected class OneToOneRequiredDependent : IdentifiableWithAttribute - { - [HasOne] public OneToOnePrincipal Principal { get; set; } - public int PrincipalId { get; set; } - } - - protected class OneToManyDependent : IdentifiableWithAttribute - { - [HasOne] public OneToManyPrincipal Principal { get; set; } - public int? PrincipalId { get; set; } - } - - protected class OneToManyRequiredDependent : IdentifiableWithAttribute - { - [HasOne] public OneToManyPrincipal Principal { get; set; } - public int PrincipalId { get; set; } - } - - protected class OneToManyPrincipal : IdentifiableWithAttribute - { - [HasMany] public List Dependents { get; set; } - } - - protected class IdentifiableWithAttribute : Identifiable - { - [Attr] public string AttributeMember { get; set; } - } - - protected class MultipleRelationshipsPrincipalPart : IdentifiableWithAttribute - { - [HasOne] public OneToOneDependent PopulatedToOne { get; set; } - [HasOne] public OneToOneDependent EmptyToOne { get; set; } - [HasMany] public List PopulatedToManies { get; set; } - [HasMany] public List EmptyToManies { get; set; } - [HasOne] public MultipleRelationshipsPrincipalPart Multi { get; set; } - } - - protected class MultipleRelationshipsDependentPart : IdentifiableWithAttribute - { - [HasOne] public OneToOnePrincipal PopulatedToOne { get; set; } - public int PopulatedToOneId { get; set; } - [HasOne] public OneToOnePrincipal EmptyToOne { get; set; } - public int? EmptyToOneId { get; set; } - [HasOne] public OneToManyPrincipal PopulatedToMany { get; set; } - public int PopulatedToManyId { get; set; } - [HasOne] public OneToManyPrincipal EmptyToMany { get; set; } - public int? EmptyToManyId { get; set; } - } - } -} diff --git a/test/UnitTests/Deserialization/JsonApiSerializerTests.cs b/test/UnitTests/Deserialization/JsonApiSerializerTests.cs deleted file mode 100644 index 6a1dcb602e..0000000000 --- a/test/UnitTests/Deserialization/JsonApiSerializerTests.cs +++ /dev/null @@ -1,285 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Extensions; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Request; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Serialization.Contracts; - -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class JsonApiSerializerTests - { - [Fact] - public void Can_Serialize_Complex_Types() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - - var serializer = GetSerializer(resourceGraphBuilder); - - var resource = new TestResource - { - ComplexMember = new ComplexType - { - CompoundName = "testname" - } - }; - - // act - var result = serializer.Serialize(resource); - - // assert - Assert.NotNull(result); - - var expectedFormatted = - @"{ - ""data"": { - ""attributes"": { - ""complex-member"": { - ""compound-name"": ""testname"" - } - }, - ""relationships"": { - ""children"": { - ""links"": { - ""self"": ""/test-resource//relationships/children"", - ""related"": ""/test-resource//children"" - } - } - }, - ""type"": ""test-resource"", - ""id"": """" - } - }"; - var expected = Regex.Replace(expectedFormatted, @"\s+", ""); - - Assert.Equal(expected, result); - } - - [Fact] - public void Can_Serialize_Deeply_Nested_Relationships() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - resourceGraphBuilder.AddResource("children"); - resourceGraphBuilder.AddResource("infections"); - - var serializer = GetSerializer( - resourceGraphBuilder, - new List { "children.infections" } - ); - - var resource = new TestResource - { - Id = 1, - Children = new List { - new ChildResource { - Id = 2, - Infections = new List { - new InfectionResource { Id = 4 }, - new InfectionResource { Id = 5 }, - } - }, - new ChildResource { - Id = 3 - } - } - }; - - // act - var result = serializer.Serialize(resource); - - // assert - Assert.NotNull(result); - - var expectedFormatted = - @"{ - ""data"": { - ""attributes"": { - ""complex-member"": null - }, - ""relationships"": { - ""children"": { - ""links"": { - ""self"": ""/test-resource/1/relationships/children"", - ""related"": ""/test-resource/1/children"" - }, - ""data"": [{ - ""type"": ""children"", - ""id"": ""2"" - }, { - ""type"": ""children"", - ""id"": ""3"" - }] - } - }, - ""type"": ""test-resource"", - ""id"": ""1"" - }, - ""included"": [ - { - ""attributes"": {}, - ""relationships"": { - ""infections"": { - ""links"": { - ""self"": ""/children/2/relationships/infections"", - ""related"": ""/children/2/infections"" - }, - ""data"": [{ - ""type"": ""infections"", - ""id"": ""4"" - }, { - ""type"": ""infections"", - ""id"": ""5"" - }] - }, - ""parent"": { - ""links"": { - ""self"": ""/children/2/relationships/parent"", - ""related"": ""/children/2/parent"" - } - } - }, - ""type"": ""children"", - ""id"": ""2"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infected"": { - ""links"": { - ""self"": ""/infections/4/relationships/infected"", - ""related"": ""/infections/4/infected"" - } - } - }, - ""type"": ""infections"", - ""id"": ""4"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infected"": { - ""links"": { - ""self"": ""/infections/5/relationships/infected"", - ""related"": ""/infections/5/infected"" - } - } - }, - ""type"": ""infections"", - ""id"": ""5"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infections"": { - ""links"": { - ""self"": ""/children/3/relationships/infections"", - ""related"": ""/children/3/infections"" - } - }, - ""parent"": { - ""links"": { - ""self"": ""/children/3/relationships/parent"", - ""related"": ""/children/3/parent"" - } - } - }, - ""type"": ""children"", - ""id"": ""3"" - } - ] - }"; - var expected = Regex.Replace(expectedFormatted, @"\s+", ""); - - Assert.Equal(expected, result); - } - - private JsonApiSerializer GetSerializer( - ResourceGraphBuilder resourceGraphBuilder, - List included = null) - { - var resourceGraph = resourceGraphBuilder.Build(); - var requestManagerMock = new Mock(); - requestManagerMock.Setup(m => m.GetContextEntity()).Returns(resourceGraph.GetContextEntity("test-resource")); - requestManagerMock.Setup(m => m.IncludedRelationships).Returns(included); - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); - jsonApiContextMock.Setup(m => m.RequestEntity).Returns(resourceGraph.GetContextEntity("test-resource")); - jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); - - - jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); - var pmMock = new Mock(); - jsonApiContextMock.Setup(m => m.PageManager).Returns(pmMock.Object); - - - - var jsonApiOptions = new JsonApiOptions(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var services = new ServiceCollection(); - - var mvcBuilder = services.AddMvcCore(); - - services - .AddJsonApiInternals(jsonApiOptions); - - var provider = services.BuildServiceProvider(); - var scoped = new TestScopedServiceProvider(provider); - - var documentBuilder = GetDocumentBuilder(jsonApiContextMock, requestManagerMock.Object, scopedServiceProvider: scoped); - var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); - - return serializer; - } - - private class TestResource : Identifiable - { - [Attr("complex-member")] - public ComplexType ComplexMember { get; set; } - - [HasMany("children")] public List Children { get; set; } - } - - private class ComplexType - { - public string CompoundName { get; set; } - } - - private class ChildResource : Identifiable - { - [HasMany("infections")] public List Infections { get; set; } - - [HasOne("parent")] public TestResource Parent { get; set; } - } - - private class InfectionResource : Identifiable - { - [HasOne("infected")] public ChildResource Infected { get; set; } - } - - private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, IRequestManager requestManager, TestScopedServiceProvider scopedServiceProvider = null) - { - var pageManagerMock = new Mock(); - - return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, requestManager, scopedServiceProvider: scopedServiceProvider); - - } - } -} diff --git a/test/UnitTests/Deserialization/SerializationTestsSetupBase.cs b/test/UnitTests/Deserialization/SerializationTestsSetupBase.cs deleted file mode 100644 index 5135a2e642..0000000000 --- a/test/UnitTests/Deserialization/SerializationTestsSetupBase.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Collections.Generic; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; - -namespace UnitTests.Deserialization -{ - public class SerializationTestsSetupBase - { - protected readonly IResourceGraph _resourceGraph; - protected readonly JsonApiSerializerSettings _defaultSettings = new JsonApiSerializerSettings(); - - public SerializationTestsSetupBase() - { - _resourceGraph = BuildGraph(); - } - - protected IResourceGraph BuildGraph() - { - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - resourceGraphBuilder.AddResource("test-resource-with-list"); - // one to one relationships - resourceGraphBuilder.AddResource("one-to-one-principals"); - resourceGraphBuilder.AddResource("one-to-one-dependents"); - resourceGraphBuilder.AddResource("one-to-one-required-dependents"); - // one to many relationships - resourceGraphBuilder.AddResource("one-to-many-principals"); - resourceGraphBuilder.AddResource("one-to-many-dependents"); - resourceGraphBuilder.AddResource("one-to-many-required-dependents"); - // collective relationships - resourceGraphBuilder.AddResource("multi-principals"); - resourceGraphBuilder.AddResource("multi-dependents"); - return resourceGraphBuilder.Build(); - } - - protected class TestResource : Identifiable - { - [Attr] public string StringField { get; set; } - [Attr] public DateTime DateTimeField { get; set; } - [Attr] public DateTime? NullableDateTimeField { get; set; } - [Attr] public int IntField { get; set; } - [Attr] public int? NullableIntField { get; set; } - [Attr] public Guid GuidField { get; set; } - [Attr] public ComplexType ComplexField { get; set; } - [Attr(isImmutable: true)] public string Immutable { get; set; } - } - - protected class TestResourceWithList : Identifiable - { - [Attr] public List ComplexFields { get; set; } - } - - protected class ComplexType - { - public string CompoundName { get; set; } - } - - protected class OneToOnePrincipal : IdentifiableWithAttribute - { - [HasOne] public OneToOneDependent Dependent { get; set; } - } - - protected class OneToOneDependent : IdentifiableWithAttribute - { - [HasOne] public OneToOnePrincipal Principal { get; set; } - public int? PrincipalId { get; set; } - } - - protected class OneToOneRequiredDependent : IdentifiableWithAttribute - { - [HasOne] public OneToOnePrincipal Principal { get; set; } - public int PrincipalId { get; set; } - } - - protected class OneToManyDependent : IdentifiableWithAttribute - { - [HasOne] public OneToManyPrincipal Principal { get; set; } - public int? PrincipalId { get; set; } - } - - protected class OneToManyRequiredDependent : IdentifiableWithAttribute - { - [HasOne] public OneToManyPrincipal Principal { get; set; } - public int PrincipalId { get; set; } - } - - protected class OneToManyPrincipal : IdentifiableWithAttribute - { - [HasMany] public List Dependents { get; set; } - } - - protected class IdentifiableWithAttribute : Identifiable - { - [Attr] public string AttributeMember { get; set; } - } - - protected class MultipleRelationshipsPrincipalPart : IdentifiableWithAttribute - { - [HasOne] public OneToOneDependent PopulatedToOne { get; set; } - [HasOne] public OneToOneDependent EmptyToOne { get; set; } - [HasMany] public List PopulatedToManies { get; set; } - [HasMany] public List EmptyToManies { get; set; } - [HasOne] public MultipleRelationshipsPrincipalPart Multi { get; set; } - } - - protected class MultipleRelationshipsDependentPart : IdentifiableWithAttribute - { - [HasOne] public OneToOnePrincipal PopulatedToOne { get; set; } - public int PopulatedToOneId { get; set; } - [HasOne] public OneToOnePrincipal EmptyToOne { get; set; } - public int? EmptyToOneId { get; set; } - [HasOne] public OneToManyPrincipal PopulatedToMany { get; set; } - public int PopulatedToManyId { get; set; } - [HasOne] public OneToManyPrincipal EmptyToMany { get; set; } - public int? EmptyToManyId { get; set; } - } - } -} \ No newline at end of file diff --git a/test/UnitTests/Deserialization/ServerDeserializerTests.cs b/test/UnitTests/Deserialization/ServerDeserializerTests.cs deleted file mode 100644 index 9d97bdf3e3..0000000000 --- a/test/UnitTests/Deserialization/ServerDeserializerTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using Moq; -using Newtonsoft.Json; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class ServerDeserializerTests : DeserializerTestsSetup - { - private readonly ServerDeserializer _deserializer; - private readonly Mock _fieldsManagerMock = new Mock(); - public ServerDeserializerTests() : base() - { - _deserializer = new ServerDeserializer(_resourceGraph, _defaultSettings, _fieldsManagerMock.Object); - } - - [Fact] - public void DeserializeAttributes_VariousUpdatedMembers_RegistersUpdatedFields() - { - // arrange - SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); - Document content = CreateTestResourceDocument(); - var body = JsonConvert.SerializeObject(content); - - // act - _deserializer.Deserialize(body); - - // assert - Assert.Equal(5, attributesToUpdate.Count); - Assert.Empty(relationshipsToUpdate); - } - - [Fact] - public void DeserializeAttributes_UpdatedImmutableMember_ThrowsInvalidOperationException() - { - // arrange - SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { "immutable", "some string" }, - } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act, assert - Assert.Throws(() => _deserializer.Deserialize(body)); - } - - [Fact] - public void DeserializeRelationships_MultipleDependentRelationships_RegistersUpdatedRelationships() - { - // arrange - SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); - var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); - content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); - var body = JsonConvert.SerializeObject(content); - - // act - _deserializer.Deserialize(body); - - // assert - Assert.Equal(4, relationshipsToUpdate.Count); - Assert.Empty(attributesToUpdate); - } - - [Fact] - public void DeserializeRelationships_MultiplePrincipalRelationships_RegistersUpdatedRelationships() - { - // arrange - SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); - var content = CreateDocumentWithRelationships("multi-dependents"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); - content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); - var body = JsonConvert.SerializeObject(content); - - // act - _deserializer.Deserialize(body); - - // assert - Assert.Equal(4, relationshipsToUpdate.Count); - Assert.Empty(attributesToUpdate); - } - - private void SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate) - { - attributesToUpdate = new List(); - relationshipsToUpdate = new List(); - _fieldsManagerMock.Setup(m => m.AttributesToUpdate).Returns(attributesToUpdate); - _fieldsManagerMock.Setup(m => m.RelationshipsToUpdate).Returns(relationshipsToUpdate); - } - } -} diff --git a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs index 3da8339437..ee8a42e432 100644 --- a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs +++ b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs @@ -6,6 +6,8 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Internal.Generics; using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Serialization.Contracts; + using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; @@ -18,7 +20,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using JsonApiDotNetCore.Internal.Contracts; - +using JsonApiDotNetCore.Serialization.Contracts; +using JsonApiDotNetCore.Managers.Contracts; namespace UnitTests.Extensions { @@ -32,7 +35,7 @@ public void AddJsonApiInternals_Adds_All_Required_Services() var jsonApiOptions = new JsonApiOptions(); services.AddDbContext(options => options.UseInMemoryDatabase("UnitTestDb"), ServiceLifetime.Transient); - + services.AddScoped>(); // act services.AddJsonApiInternals(jsonApiOptions); // this is required because the DbContextResolver requires access to the current HttpContext @@ -41,20 +44,24 @@ public void AddJsonApiInternals_Adds_All_Required_Services() var provider = services.BuildServiceProvider(); // assert + var requestManager = provider.GetService(); + Assert.NotNull(requestManager); + var graph = provider.GetService(); + Assert.NotNull(graph); + requestManager.SetRequestResource(graph.GetContextEntity()); + Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService(typeof(IEntityRepository))); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); - Assert.NotNull(provider.GetService()); - Assert.NotNull(provider.GetService()); - Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService>()); + Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); - Assert.NotNull(provider.GetService()); + Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService()); - Assert.NotNull(provider.GetService()); Assert.NotNull(provider.GetService(typeof(GenericProcessor))); } diff --git a/test/UnitTests/JsonApiContext/BasicTest.cs b/test/UnitTests/JsonApiContext/BasicTest.cs index 7dd134ea97..6c7e5660cb 100644 --- a/test/UnitTests/JsonApiContext/BasicTest.cs +++ b/test/UnitTests/JsonApiContext/BasicTest.cs @@ -45,7 +45,7 @@ public async Task GetAsync_Throw404OnNoEntityFound() } as IJsonApiOptions; var repositoryMock = new Mock>(); var queryManagerMock = new Mock(); - var pageManagerMock = new Mock(); + var pageManagerMock = new Mock(); var rgMock = new Mock(); var service = new CustomArticleService(repositoryMock.Object, jsonApiOptions, queryManagerMock.Object, pageManagerMock.Object, rgMock.Object); @@ -75,7 +75,7 @@ public async Task GetAsync_ShouldThrow404OnNoEntityFoundWithRelationships() var repositoryMock = new Mock>(); var requestManager = new Mock(); - var pageManagerMock = new Mock(); + var pageManagerMock = new Mock(); requestManager.Setup(qm => qm.GetRelationships()).Returns(new List() { "cookies" }); requestManager.SetupGet(rm => rm.QuerySet).Returns(new QuerySet { diff --git a/test/UnitTests/Models/LinkTests.cs b/test/UnitTests/Models/LinkTests.cs index e954ddf135..88f56a4a6d 100644 --- a/test/UnitTests/Models/LinkTests.cs +++ b/test/UnitTests/Models/LinkTests.cs @@ -1,4 +1,5 @@ using JsonApiDotNetCore.Models; +using JsonApiDotNetCore.Models.Links; using Xunit; namespace UnitTests.Models diff --git a/test/UnitTests/Models/RelationshipDataTests.cs b/test/UnitTests/Models/RelationshipDataTests.cs index ff00144b62..e4871ad70d 100644 --- a/test/UnitTests/Models/RelationshipDataTests.cs +++ b/test/UnitTests/Models/RelationshipDataTests.cs @@ -8,7 +8,7 @@ namespace UnitTests.Models public class RelationshipDataTests { [Fact] - public void Setting_ExposedData_To_List_Sets_ManyData() + public void Setting_ExposeData_To_List_Sets_ManyData() { // arrange var relationshipData = new RelationshipData(); @@ -20,17 +20,17 @@ public void Setting_ExposedData_To_List_Sets_ManyData() }; // act - relationshipData.ExposedData = relationships; + relationshipData.Data = relationships; // assert Assert.NotEmpty(relationshipData.ManyData); Assert.Equal("authors", relationshipData.ManyData[0].Type); Assert.Equal("9", relationshipData.ManyData[0].Id); - Assert.True(relationshipData.IsHasMany); + Assert.True(relationshipData.IsManyData); } [Fact] - public void Setting_ExposedData_To_JArray_Sets_ManyData() + public void Setting_ExposeData_To_JArray_Sets_ManyData() { // arrange var relationshipData = new RelationshipData(); @@ -44,17 +44,17 @@ public void Setting_ExposedData_To_JArray_Sets_ManyData() var relationships = JArray.Parse(relationshipsJson); // act - relationshipData.ExposedData = relationships; + relationshipData.Data = relationships; // assert Assert.NotEmpty(relationshipData.ManyData); Assert.Equal("authors", relationshipData.ManyData[0].Type); Assert.Equal("9", relationshipData.ManyData[0].Id); - Assert.True(relationshipData.IsHasMany); + Assert.True(relationshipData.IsManyData); } [Fact] - public void Setting_ExposedData_To_RIO_Sets_SingleData() + public void Setting_ExposeData_To_RIO_Sets_SingleData() { // arrange var relationshipData = new RelationshipData(); @@ -64,17 +64,17 @@ public void Setting_ExposedData_To_RIO_Sets_SingleData() }; // act - relationshipData.ExposedData = relationship; + relationshipData.Data = relationship; // assert Assert.NotNull(relationshipData.SingleData); Assert.Equal("authors", relationshipData.SingleData.Type); Assert.Equal("9", relationshipData.SingleData.Id); - Assert.False(relationshipData.IsHasMany); + Assert.False(relationshipData.IsManyData); } [Fact] - public void Setting_ExposedData_To_JObject_Sets_SingleData() + public void Setting_ExposeData_To_JObject_Sets_SingleData() { // arrange var relationshipData = new RelationshipData(); @@ -86,13 +86,13 @@ public void Setting_ExposedData_To_JObject_Sets_SingleData() var relationship = JObject.Parse(relationshipJson); // act - relationshipData.ExposedData = relationship; + relationshipData.Data = relationship; // assert Assert.NotNull(relationshipData.SingleData); Assert.Equal("authors", relationshipData.SingleData.Type); Assert.Equal("9", relationshipData.SingleData.Id); - Assert.False(relationshipData.IsHasMany); + Assert.False(relationshipData.IsManyData); } } } diff --git a/test/UnitTests/Models/ResourceDefinitionTests.cs b/test/UnitTests/Models/ResourceDefinitionTests.cs index 5885808407..776d795da6 100644 --- a/test/UnitTests/Models/ResourceDefinitionTests.cs +++ b/test/UnitTests/Models/ResourceDefinitionTests.cs @@ -1,146 +1,146 @@ -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Internal.Query; -using JsonApiDotNetCore.Models; -using System.Collections.Generic; -using System.Linq; -using Xunit; - -namespace UnitTests.Models -{ - public class ResourceDefinition_Scenario_Tests - { - private readonly IResourceGraph _graph; - - public ResourceDefinition_Scenario_Tests() - { - _graph = new ResourceGraphBuilder() - .AddResource("models") - .Build(); - } - - [Fact] - public void Request_Filter_Uses_Member_Expression() - { - // arrange - var resource = new RequestFilteredResource(isAdmin: true); - - // act - var attrs = resource.GetOutputAttrs(null); - - // assert - Assert.DoesNotContain(attrs, a => a.InternalAttributeName == nameof(Model.AlwaysExcluded)); - } - - [Fact] - public void Request_Filter_Uses_NewExpression() - { - // arrange - var resource = new RequestFilteredResource(isAdmin: false); - - // act - var attrs = resource.GetOutputAttrs(null); - - // assert - Assert.DoesNotContain(attrs, a => a.InternalAttributeName == nameof(Model.AlwaysExcluded)); - Assert.DoesNotContain(attrs, a => a.InternalAttributeName == nameof(Model.Password)); - } - - [Fact] - public void Instance_Filter_Uses_Member_Expression() - { - // arrange - var model = new Model { AlwaysExcluded = "Admin" }; - var resource = new InstanceFilteredResource(); - - // act - var attrs = resource.GetOutputAttrs(model); - - // assert - Assert.DoesNotContain(attrs, a => a.InternalAttributeName == nameof(Model.AlwaysExcluded)); - } - - [Fact] - public void Instance_Filter_Uses_NewExpression() - { - // arrange - var model = new Model { AlwaysExcluded = "Joe" }; - var resource = new InstanceFilteredResource(); - - // act - var attrs = resource.GetOutputAttrs(model); - - // assert - Assert.DoesNotContain(attrs, a => a.InternalAttributeName == nameof(Model.AlwaysExcluded)); - Assert.DoesNotContain(attrs, a => a.InternalAttributeName == nameof(Model.Password)); - } - - [Fact] - public void InstanceOutputAttrsAreSpecified_Returns_True_If_Instance_Method_Is_Overriden() - { - // act - var resource = new InstanceFilteredResource(); - - // assert - Assert.True(resource._instanceAttrsAreSpecified); - } - - [Fact] - public void InstanceOutputAttrsAreSpecified_Returns_False_If_Instance_Method_Is_Not_Overriden() - { - // act - var resource = new RequestFilteredResource(isAdmin: false); - - // assert - Assert.False(resource._instanceAttrsAreSpecified); - } - } - - public class Model : Identifiable - { - [Attr("name")] public string AlwaysExcluded { get; set; } - [Attr("password")] public string Password { get; set; } - [Attr("prop")] public string Prop { get; set; } - } - - public class RequestFilteredResource : ResourceDefinition - { - private readonly bool _isAdmin; - - // this constructor will be resolved from the container - // that means you can take on any dependency that is also defined in the container - public RequestFilteredResource(bool isAdmin) : base (new ResourceGraphBuilder().AddResource().Build()) - { - _isAdmin = isAdmin; - } - - // Called once per filtered resource in request. - protected override List OutputAttrs() - => _isAdmin - ? Remove(m => m.AlwaysExcluded) - : Remove(m => new { m.AlwaysExcluded, m.Password }, from: base.OutputAttrs()); - - public override QueryFilters GetQueryFilters() - => new QueryFilters { - { "is-active", (query, value) => query.Select(x => x) } - }; - public override PropertySortOrder GetDefaultSortOrder() - => new PropertySortOrder { - (t => t.Prop, SortDirection.Ascending) - }; - } - - public class InstanceFilteredResource : ResourceDefinition - { - public InstanceFilteredResource() : base(new ResourceGraphBuilder().AddResource().Build()) - { - } - - // Called once per resource instance - protected override List OutputAttrs(Model model) - => model.AlwaysExcluded == "Admin" - ? Remove(m => m.AlwaysExcluded, base.OutputAttrs()) - : Remove(m => new { m.AlwaysExcluded, m.Password }, from: base.OutputAttrs()); - } -} \ No newline at end of file +//using JsonApiDotNetCore.Builders; +//using JsonApiDotNetCore.Internal; +//using JsonApiDotNetCore.Internal.Contracts; +//using JsonApiDotNetCore.Internal.Query; +//using JsonApiDotNetCore.Models; +//using System.Collections.Generic; +//using System.Linq; +//using Xunit; + +//namespace UnitTests.Models +//{ +// public class ResourceDefinition_Scenario_Tests +// { +// private readonly IResourceGraph _graph; + +// public ResourceDefinition_Scenario_Tests() +// { +// _graph = new ResourceGraphBuilder() +// .AddResource("models") +// .Build(); +// } + +// [Fact] +// public void Request_Filter_Uses_Member_Expression() +// { +// // arrange +// var resource = new RequestFilteredResource(isAdmin: true); + +// // act +// var attrs = resource.GetOutputAttrs(null); + +// // assert +// Assert.DoesNotContain(attrs, a => a.InternalAttributeName == nameof(Model.AlwaysExcluded)); +// } + +// [Fact] +// public void Request_Filter_Uses_NewExpression() +// { +// // arrange +// var resource = new RequestFilteredResource(isAdmin: false); + +// // act +// var attrs = resource.GetOutputAttrs(null); + +// // assert +// Assert.DoesNotContain(attrs, a => a.InternalAttributeName == nameof(Model.AlwaysExcluded)); +// Assert.DoesNotContain(attrs, a => a.InternalAttributeName == nameof(Model.Password)); +// } + +// [Fact] +// public void Instance_Filter_Uses_Member_Expression() +// { +// // arrange +// var model = new Model { AlwaysExcluded = "Admin" }; +// var resource = new InstanceFilteredResource(); + +// // act +// var attrs = resource.GetOutputAttrs(model); + +// // assert +// Assert.DoesNotContain(attrs, a => a.InternalAttributeName == nameof(Model.AlwaysExcluded)); +// } + +// [Fact] +// public void Instance_Filter_Uses_NewExpression() +// { +// // arrange +// var model = new Model { AlwaysExcluded = "Joe" }; +// var resource = new InstanceFilteredResource(); + +// // act +// var attrs = resource.GetOutputAttrs(model); + +// // assert +// Assert.DoesNotContain(attrs, a => a.InternalAttributeName == nameof(Model.AlwaysExcluded)); +// Assert.DoesNotContain(attrs, a => a.InternalAttributeName == nameof(Model.Password)); +// } + +// [Fact] +// public void InstanceOutputAttrsAreSpecified_Returns_True_If_Instance_Method_Is_Overriden() +// { +// // act +// var resource = new InstanceFilteredResource(); + +// // assert +// Assert.True(resource.InstanceAttrsAreSpecified); +// } + +// [Fact] +// public void InstanceOutputAttrsAreSpecified_Returns_False_If_Instance_Method_Is_Not_Overriden() +// { +// // act +// var resource = new RequestFilteredResource(isAdmin: false); + +// // assert +// Assert.False(resource.InstanceAttrsAreSpecified); +// } +// } + +// public class Model : Identifiable +// { +// [Attr("name")] public string AlwaysExcluded { get; set; } +// [Attr("password")] public string Password { get; set; } +// [Attr("prop")] public string Prop { get; set; } +// } + +// public class RequestFilteredResource : ResourceDefinition +// { +// private readonly bool _isAdmin; + +// // this constructor will be resolved from the container +// // that means you can take on any dependency that is also defined in the container +// public RequestFilteredResource(bool isAdmin) : base(new ResourceGraphBuilder().AddResource().Build()) +// { +// _isAdmin = isAdmin; +// } + +// // Called once per filtered resource in request. +// protected override List OutputAttrs() +// => _isAdmin +// ? Remove(m => m.AlwaysExcluded) +// : Remove(m => new { m.AlwaysExcluded, m.Password }, from: base.OutputAttrs()); + +// public override QueryFilters GetQueryFilters() +// => new QueryFilters { +// { "is-active", (query, value) => query.Select(x => x) } +// }; +// public override PropertySortOrder GetDefaultSortOrder() +// => new PropertySortOrder { +// (t => t.Prop, SortDirection.Ascending) +// }; +// } + +// public class InstanceFilteredResource : ResourceDefinition +// { +// public InstanceFilteredResource() : base(new ResourceGraphBuilder().AddResource().Build()) +// { +// } + +// // Called once per resource instance +// protected override List OutputAttrs(Model model) +// => model.AlwaysExcluded == "Admin" +// ? Remove(m => m.AlwaysExcluded, base.OutputAttrs()) +// : Remove(m => new { m.AlwaysExcluded, m.Password }, from: base.OutputAttrs()); +// } +//} \ No newline at end of file diff --git a/test/UnitTests/ResourceHooks/DiscoveryTests.cs b/test/UnitTests/ResourceHooks/DiscoveryTests.cs index c3467c525d..fe6d798c37 100644 --- a/test/UnitTests/ResourceHooks/DiscoveryTests.cs +++ b/test/UnitTests/ResourceHooks/DiscoveryTests.cs @@ -61,11 +61,11 @@ public YetAnotherDummyResourceDefinition() : base(new ResourceGraphBuilder().Add public override IEnumerable BeforeDelete(IEntityHashSet affected, ResourcePipeline pipeline) { return affected; } - [LoadDatabaseValues(false)] + [LoaDatabaseValues(false)] public override void AfterDelete(HashSet entities, ResourcePipeline pipeline, bool succeeded) { } } [Fact] - public void LoadDatabaseValues_Attribute_Not_Allowed() + public void LoaDatabaseValues_Attribute_Not_Allowed() { // assert Assert.Throws(() => diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Create/AfterCreateTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Create/AfterCreateTests.cs index dea114facc..80f3966d60 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Create/AfterCreateTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Create/AfterCreateTests.cs @@ -16,8 +16,7 @@ public void AfterCreate() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act @@ -35,8 +34,7 @@ public void AfterCreate_Without_Parent_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act @@ -53,8 +51,7 @@ public void AfterCreate_Without_Child_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act @@ -71,8 +68,7 @@ public void AfterCreate_Without_Any_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Create/BeforeCreateTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Create/BeforeCreateTests.cs index bc2163df2f..a8370067f8 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Create/BeforeCreateTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Create/BeforeCreateTests.cs @@ -17,8 +17,7 @@ public void BeforeCreate() var todoDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act @@ -36,8 +35,7 @@ public void BeforeCreate_Without_Parent_Hook_Implemented() var todoDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act @@ -54,8 +52,7 @@ public void BeforeCreate_Without_Child_Hook_Implemented() var todoDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act @@ -71,8 +68,7 @@ public void BeforeCreate_Without_Any_Hook_Implemented() var todoDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Create/BeforeCreate_WithDbValues_Tests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Create/BeforeCreate_WithDbValues_Tests.cs index c574de145c..667f259591 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Create/BeforeCreate_WithDbValues_Tests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Create/BeforeCreate_WithDbValues_Tests.cs @@ -47,8 +47,7 @@ public void BeforeCreate() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); // act hookExecutor.BeforeCreate(todoList, ResourcePipeline.Post); @@ -73,8 +72,7 @@ public void BeforeCreate_Without_Parent_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); // act hookExecutor.BeforeCreate(todoList, ResourcePipeline.Post); @@ -94,8 +92,7 @@ public void BeforeCreate_Without_Child_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); // act hookExecutor.BeforeCreate(todoList, ResourcePipeline.Post); @@ -115,8 +112,7 @@ public void BeforeCreate_NoImplicit() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooksNoImplicit, ResourceHook.BeforeUpdate); var personDiscovery = SetDiscoverableHooks(targetHooksNoImplicit, ResourceHook.BeforeUpdateRelationship); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); // act hookExecutor.BeforeCreate(todoList, ResourcePipeline.Post); @@ -137,8 +133,7 @@ public void BeforeCreate_NoImplicit_Without_Parent_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooksNoImplicit, ResourceHook.BeforeUpdateRelationship); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); // act hookExecutor.BeforeCreate(todoList, ResourcePipeline.Post); @@ -158,8 +153,7 @@ public void BeforeCreate_NoImplicit_Without_Child_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooksNoImplicit, ResourceHook.BeforeUpdate); var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); // act hookExecutor.BeforeCreate(todoList, ResourcePipeline.Post); diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/AfterDeleteTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/AfterDeleteTests.cs index edc0f6e4ae..65c8cb4a54 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/AfterDeleteTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/AfterDeleteTests.cs @@ -15,7 +15,7 @@ public void AfterDelete() { // Arrange var discovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - var (contextMock, hookExecutor, resourceDefinitionMock) = CreateTestObjects(discovery); + var (_, hookExecutor, resourceDefinitionMock) = CreateTestObjects(discovery); var todoList = CreateTodoWithOwner(); // Act @@ -31,7 +31,7 @@ public void AfterDelete_Without_Any_Hook_Implemented() { // arrange var discovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var resourceDefinitionMock) = CreateTestObjects(discovery); + (var _, var hookExecutor, var resourceDefinitionMock) = CreateTestObjects(discovery); var todoList = CreateTodoWithOwner(); // act diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/BeforeDeleteTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/BeforeDeleteTests.cs index 887a322994..15b1c247b1 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/BeforeDeleteTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/BeforeDeleteTests.cs @@ -15,7 +15,7 @@ public void BeforeDelete() { // arrange var discovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var resourceDefinitionMock) = CreateTestObjects(discovery); + (var _, var hookExecutor, var resourceDefinitionMock) = CreateTestObjects(discovery); var todoList = CreateTodoWithOwner(); // act @@ -31,7 +31,7 @@ public void BeforeDelete_Without_Any_Hook_Implemented() { // arrange var discovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var resourceDefinitionMock) = CreateTestObjects(discovery); + (var _, var hookExecutor, var resourceDefinitionMock) = CreateTestObjects(discovery); var todoList = CreateTodoWithOwner(); // act diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/BeforeDelete_WithDbValue_Tests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/BeforeDelete_WithDbValue_Tests.cs index f63adcbd6e..0dc09d7b3d 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/BeforeDelete_WithDbValue_Tests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Delete/BeforeDelete_WithDbValue_Tests.cs @@ -39,8 +39,7 @@ public void BeforeDelete() var personDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); var todoDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); var passportDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); - (var contextMock, var hookExecutor, var personResourceMock, var todoResourceMock, - var passportResourceMock) = CreateTestObjects(personDiscovery, todoDiscovery, passportDiscovery, repoDbContextOptions: options); + var (_, hookExecutor, personResourceMock, todoResourceMock, passportResourceMock) = CreateTestObjects(personDiscovery, todoDiscovery, passportDiscovery, repoDbContextOptions: options); var todoList = CreateTodoWithOwner(); // act @@ -48,8 +47,8 @@ public void BeforeDelete() // assert personResourceMock.Verify(rd => rd.BeforeDelete(It.IsAny>(), It.IsAny()), Times.Once()); - todoResourceMock.Verify(rd => rd.BeforeImplicitUpdateRelationship(It.Is>( rh => CheckImplicitTodos(rh) ), ResourcePipeline.Delete), Times.Once()); - passportResourceMock.Verify(rd => rd.BeforeImplicitUpdateRelationship(It.Is>( rh => CheckImplicitPassports(rh) ), ResourcePipeline.Delete), Times.Once()); + todoResourceMock.Verify(rd => rd.BeforeImplicitUpdateRelationship(It.Is>(rh => CheckImplicitTodos(rh)), ResourcePipeline.Delete), Times.Once()); + passportResourceMock.Verify(rd => rd.BeforeImplicitUpdateRelationship(It.Is>(rh => CheckImplicitPassports(rh)), ResourcePipeline.Delete), Times.Once()); VerifyNoOtherCalls(personResourceMock, todoResourceMock, passportResourceMock); } @@ -60,8 +59,7 @@ public void BeforeDelete_No_Parent_Hooks() var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var todoDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); var passportDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); - (var contextMock, var hookExecutor, var personResourceMock, var todoResourceMock, - var passportResourceMock) = CreateTestObjects(personDiscovery, todoDiscovery, passportDiscovery, repoDbContextOptions: options); + var (_, hookExecutor, personResourceMock, todoResourceMock, passportResourceMock) = CreateTestObjects(personDiscovery, todoDiscovery, passportDiscovery, repoDbContextOptions: options); var todoList = CreateTodoWithOwner(); // act @@ -80,8 +78,7 @@ public void BeforeDelete_No_Children_Hooks() var personDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); var todoDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var passportDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var personResourceMock, var todoResourceMock, - var passportResourceMock) = CreateTestObjects(personDiscovery, todoDiscovery, passportDiscovery, repoDbContextOptions: options); + var (_, hookExecutor, personResourceMock, todoResourceMock, passportResourceMock) = CreateTestObjects(personDiscovery, todoDiscovery, passportDiscovery, repoDbContextOptions: options); var todoList = CreateTodoWithOwner(); // act diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/IdentifiableManyToMany_OnReturnTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/IdentifiableManyToMany_OnReturnTests.cs index fd29857c37..cc0f89b4a8 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/IdentifiableManyToMany_OnReturnTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/IdentifiableManyToMany_OnReturnTests.cs @@ -18,9 +18,8 @@ public void OnReturn() var articleDiscovery = SetDiscoverableHooks
(targetHooks, DisableDbValues); var joinDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var joinResourceMock, var tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateIdentifiableManyToManyData(); + var (_, hookExecutor, articleResourceMock, joinResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateIdentifiableManyToManyData(); // act hookExecutor.OnReturn(articles, ResourcePipeline.Get); @@ -39,9 +38,8 @@ public void OnReturn_GetRelationship() var articleDiscovery = SetDiscoverableHooks
(targetHooks, DisableDbValues); var joinDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var joinResourceMock, var tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateIdentifiableManyToManyData(); + var (_, hookExecutor, articleResourceMock, joinResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateIdentifiableManyToManyData(); // act hookExecutor.OnReturn(articles, ResourcePipeline.GetRelationship); @@ -59,9 +57,8 @@ public void OnReturn_Without_Parent_Hook_Implemented() var articleDiscovery = SetDiscoverableHooks
(NoHooks, DisableDbValues); var joinDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var joinResourceMock, var tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateIdentifiableManyToManyData(); + var (_, hookExecutor, articleResourceMock, joinResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateIdentifiableManyToManyData(); // act hookExecutor.OnReturn(articles, ResourcePipeline.Get); @@ -79,10 +76,9 @@ public void OnReturn_Without_Children_Hooks_Implemented() var articleDiscovery = SetDiscoverableHooks
(targetHooks, DisableDbValues); var joinDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var joinResourceMock, var tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); + var (_, hookExecutor, articleResourceMock, joinResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateIdentifiableManyToManyData(); + var (articles, joins, tags) = CreateIdentifiableManyToManyData(); // act hookExecutor.OnReturn(articles, ResourcePipeline.Get); @@ -100,9 +96,8 @@ public void OnReturn_Without_Grand_Children_Hooks_Implemented() var articleDiscovery = SetDiscoverableHooks
(targetHooks, DisableDbValues); var joinDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var joinResourceMock, var tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateIdentifiableManyToManyData(); + var (_, hookExecutor, articleResourceMock, joinResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateIdentifiableManyToManyData(); // act hookExecutor.OnReturn(articles, ResourcePipeline.Get); @@ -120,9 +115,8 @@ public void OnReturn_Without_Any_Descendant_Hooks_Implemented() var articleDiscovery = SetDiscoverableHooks
(targetHooks, DisableDbValues); var joinDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var joinResourceMock, var tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateIdentifiableManyToManyData(); + var (_, hookExecutor, articleResourceMock, joinResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateIdentifiableManyToManyData(); // act hookExecutor.OnReturn(articles, ResourcePipeline.Get); @@ -139,9 +133,8 @@ public void OnReturn_Without_Any_Hook_Implemented() var articleDiscovery = SetDiscoverableHooks
(NoHooks, DisableDbValues); var joinDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var joinResourceMock, var tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateIdentifiableManyToManyData(); + var (_, hookExecutor, articleResourceMock, joinResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateIdentifiableManyToManyData(); // act hookExecutor.OnReturn(articles, ResourcePipeline.Get); diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/ManyToMany_OnReturnTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/ManyToMany_OnReturnTests.cs index ad9577c3b5..88326d3994 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/ManyToMany_OnReturnTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/ManyToMany_OnReturnTests.cs @@ -47,9 +47,8 @@ public void OnReturn() // arrange var articleDiscovery = SetDiscoverableHooks
(targetHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateDummyData(); + var (_, _, hookExecutor, articleResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateDummyData(); // act hookExecutor.OnReturn(articles, ResourcePipeline.Get); @@ -66,9 +65,8 @@ public void OnReturn_Without_Parent_Hook_Implemented() // arrange var articleDiscovery = SetDiscoverableHooks
(NoHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateDummyData(); + var (_, _, hookExecutor, articleResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateDummyData(); // act hookExecutor.OnReturn(articles, ResourcePipeline.Get); @@ -84,9 +82,8 @@ public void OnReturn_Without_Children_Hooks_Implemented() // arrange var articleDiscovery = SetDiscoverableHooks
(targetHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateDummyData(); + var (_, _, hookExecutor, articleResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateDummyData(); // act hookExecutor.OnReturn(articles, ResourcePipeline.Get); @@ -102,10 +99,9 @@ public void OnReturn_Without_Any_Hook_Implemented() // arrange var articleDiscovery = SetDiscoverableHooks
(NoHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); + var (_, _, hookExecutor, articleResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateDummyData(); + var (articles, joins, tags) = CreateDummyData(); // act hookExecutor.OnReturn(articles, ResourcePipeline.Get); diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Read/BeforeReadTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Read/BeforeReadTests.cs index 3008998b53..08d940bb3a 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Read/BeforeReadTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Read/BeforeReadTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using JsonApiDotNetCore.Hooks; +using JsonApiDotNetCore.Models; using JsonApiDotNetCoreExample.Models; using Moq; using Xunit; @@ -16,10 +17,10 @@ public void BeforeRead() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var rqMock, var hookExecutor, var todoResourceMock) = CreateTestObjects(todoDiscovery); + var (iqMock, hookExecutor, todoResourceMock) = CreateTestObjects(todoDiscovery); var todoList = CreateTodoWithOwner(); - rqMock.Setup(c => c.IncludedRelationships).Returns(new List()); + iqMock.Setup(c => c.Get()).Returns(new List>()); // act hookExecutor.BeforeRead(ResourcePipeline.Get); // assert @@ -35,12 +36,11 @@ public void BeforeReadWithInclusion() var todoDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var rqMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (iqMock, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // eg a call on api/todo-items?include=owner,assignee,stake-holders - rqMock.Setup(c => c.IncludedRelationships).Returns(new List() { "owner", "assignee", "stake-holders" }); + iqMock.Setup(c => c.Get()).Returns(GetIncludedRelationshipsChains("owner", "assignee", "stake-holders")); // act hookExecutor.BeforeRead(ResourcePipeline.Get); @@ -58,12 +58,11 @@ public void BeforeReadWithNestedInclusion() var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var passportDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var rqMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock, var passportResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, passportDiscovery); + var (iqMock, hookExecutor, todoResourceMock, ownerResourceMock, passportResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, passportDiscovery); var todoList = CreateTodoWithOwner(); // eg a call on api/todo-items?include=owner.passport,assignee,stake-holders - rqMock.Setup(c => c.IncludedRelationships).Returns(new List() { "owner.passport", "assignee", "stake-holders" }); + iqMock.Setup(c => c.Get()).Returns(GetIncludedRelationshipsChains("owner.passport", "assignee", "stake-holders")); // act hookExecutor.BeforeRead(ResourcePipeline.Get); @@ -83,12 +82,11 @@ public void BeforeReadWithNestedInclusion_No_Parent_Hook_Implemented() var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var passportDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var rqMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock, var passportResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, passportDiscovery); + var (iqMock, hookExecutor, todoResourceMock, ownerResourceMock, passportResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, passportDiscovery); var todoList = CreateTodoWithOwner(); // eg a call on api/todo-items?include=owner.passport,assignee,stake-holders - rqMock.Setup(c => c.IncludedRelationships).Returns(new List() { "owner.passport", "assignee", "stake-holders" }); + iqMock.Setup(c => c.Get()).Returns(GetIncludedRelationshipsChains("owner.passport", "assignee", "stake-holders")); // act hookExecutor.BeforeRead(ResourcePipeline.Get); @@ -106,12 +104,11 @@ public void BeforeReadWithNestedInclusion_No_Child_Hook_Implemented() var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var passportDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var rqMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock, var passportResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, passportDiscovery); + var (iqMock, hookExecutor, todoResourceMock, ownerResourceMock, passportResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, passportDiscovery); var todoList = CreateTodoWithOwner(); // eg a call on api/todo-items?include=owner.passport,assignee,stake-holders - rqMock.Setup(c => c.IncludedRelationships).Returns(new List() { "owner.passport", "assignee", "stake-holders" }); + iqMock.Setup(c => c.Get()).Returns(GetIncludedRelationshipsChains("owner.passport", "assignee", "stake-holders")); // act hookExecutor.BeforeRead(ResourcePipeline.Get); @@ -129,12 +126,11 @@ public void BeforeReadWithNestedInclusion_No_Grandchild_Hook_Implemented() var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var passportDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var rqMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock, var passportResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, passportDiscovery); + var (iqMock, hookExecutor, todoResourceMock, ownerResourceMock, passportResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, passportDiscovery); var todoList = CreateTodoWithOwner(); // eg a call on api/todo-items?include=owner.passport,assignee,stake-holders - rqMock.Setup(c => c.IncludedRelationships).Returns(new List() { "owner.passport", "assignee", "stake-holders" }); + iqMock.Setup(c => c.Get()).Returns(GetIncludedRelationshipsChains("owner.passport", "assignee", "stake-holders")); // act hookExecutor.BeforeRead(ResourcePipeline.Get); @@ -153,12 +149,11 @@ public void BeforeReadWithNestedInclusion_Without_Any_Hook_Implemented() var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var passportDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var rqMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock, var passportResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, passportDiscovery); + var (iqMock, hookExecutor, todoResourceMock, ownerResourceMock, passportResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, passportDiscovery); var todoList = CreateTodoWithOwner(); // eg a call on api/todo-items?include=owner.passport,assignee,stake-holders - rqMock.Setup(c => c.IncludedRelationships).Returns(new List() { "owner.passport", "assignee", "stake-holders" }); + iqMock.Setup(c => c.Get()).Returns(GetIncludedRelationshipsChains("owner.passport", "assignee", "stake-holders")); // act hookExecutor.BeforeRead(ResourcePipeline.Get); diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Read/IdentifiableManyToMany_AfterReadTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Read/IdentifiableManyToMany_AfterReadTests.cs index 1d34524029..ac58056ca5 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Read/IdentifiableManyToMany_AfterReadTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Read/IdentifiableManyToMany_AfterReadTests.cs @@ -18,9 +18,8 @@ public void AfterRead() var articleDiscovery = SetDiscoverableHooks
(targetHooks, DisableDbValues); var joinDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var joinResourceMock, var tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateIdentifiableManyToManyData(); + var (_, hookExecutor, articleResourceMock, joinResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateIdentifiableManyToManyData(); // act hookExecutor.AfterRead(articles, ResourcePipeline.Get); @@ -39,9 +38,8 @@ public void AfterRead_Without_Parent_Hook_Implemented() var articleDiscovery = SetDiscoverableHooks
(NoHooks, DisableDbValues); var joinDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var joinResourceMock, var tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateIdentifiableManyToManyData(); + var (_, hookExecutor, articleResourceMock, joinResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateIdentifiableManyToManyData(); // act hookExecutor.AfterRead(articles, ResourcePipeline.Get); @@ -60,10 +58,9 @@ public void AfterRead_Without_Children_Hooks_Implemented() var joinDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var joinResourceMock, var tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); + var (_, hookExecutor, articleResourceMock, joinResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateIdentifiableManyToManyData(); + var (articles, joins, tags) = CreateIdentifiableManyToManyData(); // act hookExecutor.AfterRead(articles, ResourcePipeline.Get); @@ -81,9 +78,8 @@ public void AfterRead_Without_Grand_Children_Hooks_Implemented() var articleDiscovery = SetDiscoverableHooks
(targetHooks, DisableDbValues); var joinDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var joinResourceMock, var tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateIdentifiableManyToManyData(); + var (_, hookExecutor, articleResourceMock, joinResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateIdentifiableManyToManyData(); // act hookExecutor.AfterRead(articles, ResourcePipeline.Get); @@ -101,9 +97,8 @@ public void AfterRead_Without_Any_Descendant_Hooks_Implemented() var articleDiscovery = SetDiscoverableHooks
(targetHooks, DisableDbValues); var joinDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var joinResourceMock, var tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateIdentifiableManyToManyData(); + var (_, hookExecutor, articleResourceMock, joinResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateIdentifiableManyToManyData(); // act hookExecutor.AfterRead(articles, ResourcePipeline.Get); @@ -120,9 +115,8 @@ public void AfterRead_Without_Any_Hook_Implemented() var articleDiscovery = SetDiscoverableHooks
(NoHooks, DisableDbValues); var joinDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var joinResourceMock, var tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateIdentifiableManyToManyData(); + var (_, hookExecutor, articleResourceMock, joinResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, joinDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateIdentifiableManyToManyData(); // act hookExecutor.AfterRead(articles, ResourcePipeline.Get); diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Read/ManyToMany_AfterReadTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Read/ManyToMany_AfterReadTests.cs index dcdf81ef94..7f16620469 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Read/ManyToMany_AfterReadTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Read/ManyToMany_AfterReadTests.cs @@ -17,9 +17,8 @@ public void AfterRead() // arrange var articleDiscovery = SetDiscoverableHooks
(targetHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateManyToManyData(); + var (_, _, hookExecutor, articleResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateManyToManyData(); // act hookExecutor.AfterRead(articles, ResourcePipeline.Get); @@ -36,9 +35,8 @@ public void AfterRead_Without_Parent_Hook_Implemented() // arrange var articleDiscovery = SetDiscoverableHooks
(NoHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateManyToManyData(); + var (_, _, hookExecutor, articleResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateManyToManyData(); // act hookExecutor.AfterRead(articles, ResourcePipeline.Get); @@ -54,9 +52,8 @@ public void AfterRead_Without_Children_Hooks_Implemented() // arrange var articleDiscovery = SetDiscoverableHooks
(targetHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateManyToManyData(); + var (_, _, hookExecutor, articleResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateManyToManyData(); // act hookExecutor.AfterRead(articles, ResourcePipeline.Get); @@ -72,9 +69,8 @@ public void AfterRead_Without_Any_Hook_Implemented() // arrange var articleDiscovery = SetDiscoverableHooks
(NoHooks, DisableDbValues); var tagDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var articleResourceMock, - var tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); - (var articles, var joins, var tags) = CreateManyToManyData(); + var (_, _, hookExecutor, articleResourceMock, tagResourceMock) = CreateTestObjects(articleDiscovery, tagDiscovery); + var (articles, joins, tags) = CreateManyToManyData(); // act hookExecutor.AfterRead(articles, ResourcePipeline.Get); diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/ScenarioTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/ScenarioTests.cs index c79f633c83..a4f84892ed 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/ScenarioTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/ScenarioTests.cs @@ -16,8 +16,7 @@ public void Entity_Has_Multiple_Relations_To_Same_Type() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); +var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var person1 = new Person(); var todo = new TodoItem { Owner = person1 }; var person2 = new Person { AssignedTodoItems = new List() { todo } }; @@ -40,7 +39,7 @@ public void Entity_Has_Cyclic_Relations() { // arrange var todoDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock) = CreateTestObjects(todoDiscovery); + (var contextMock, var hookExecutor, var todoResourceMock) = CreateTestObjects(todoDiscovery); var todo = new TodoItem(); todo.ParentTodoItem = todo; todo.ChildrenTodoItems = new List { todo }; @@ -66,8 +65,8 @@ public void Entity_Has_Nested_Cyclic_Relations() var grandChild = new TodoItem() { ParentTodoItem = child, Id = 3 }; child.ChildrenTodoItems = new List { grandChild }; var greatGrandChild = new TodoItem() { ParentTodoItem = grandChild, Id = 4 }; - grandChild.ChildrenTodoItems = new List { greatGrandChild }; - greatGrandChild.ChildrenTodoItems = new List { rootTodo }; + grandChild.ChildrenTodoItems = new List { greatGrandChild }; + greatGrandChild.ChildrenTodoItems = new List { rootTodo }; var todoList = new List() { rootTodo }; // act diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/AfterUpdateTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/AfterUpdateTests.cs index e8d4f4fd60..c20176b6a2 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/AfterUpdateTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/AfterUpdateTests.cs @@ -16,8 +16,7 @@ public void AfterUpdate() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act @@ -35,8 +34,7 @@ public void AfterUpdate_Without_Parent_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act @@ -53,8 +51,7 @@ public void AfterUpdate_Without_Child_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act @@ -71,8 +68,7 @@ public void AfterUpdate_Without_Any_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdateTests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdateTests.cs index 807dc38b18..efc20d313f 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdateTests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdateTests.cs @@ -16,8 +16,7 @@ public void BeforeUpdate() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act @@ -35,8 +34,7 @@ public void BeforeUpdate_Without_Parent_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act @@ -54,8 +52,7 @@ public void BeforeUpdate_Without_Child_Hook_Implemented() var todoDiscovery = SetDiscoverableHooks(targetHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act @@ -72,8 +69,7 @@ public void BeforeUpdate_Without_Any_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery); var todoList = CreateTodoWithOwner(); // act diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdate_WithDbValues_Tests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdate_WithDbValues_Tests.cs index cdf078be67..680c4be827 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdate_WithDbValues_Tests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdate_WithDbValues_Tests.cs @@ -1,6 +1,4 @@ using JsonApiDotNetCore.Hooks; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Models; using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; using Microsoft.EntityFrameworkCore; @@ -52,8 +50,7 @@ public void BeforeUpdate() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); // act hookExecutor.BeforeUpdate(todoList, ResourcePipeline.Patch); @@ -83,10 +80,9 @@ public void BeforeUpdate_Deleting_Relationship() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); - (var rqMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); - var attr = ResourceGraph.Instance.GetContextEntity(typeof(TodoItem)).Relationships.Single(r => r.PublicRelationshipName == "one-to-one-person"); - rqMock.Setup(c => c.GetUpdatedRelationships()).Returns(new Dictionary() { { attr, new object() } }); + var (_, ufMock, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); + + ufMock.Setup(c => c.RelationshipsToUpdate).Returns(_fieldExplorer.GetRelationships((TodoItem t) => t.ToOnePerson)); // act var _todoList = new List() { new TodoItem { Id = this.todoList[0].Id } }; @@ -108,8 +104,7 @@ public void BeforeUpdate_Without_Parent_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); // act hookExecutor.BeforeUpdate(todoList, ResourcePipeline.Patch); @@ -133,8 +128,7 @@ public void BeforeUpdate_Without_Child_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); // act hookExecutor.BeforeUpdate(todoList, ResourcePipeline.Patch); @@ -154,8 +148,7 @@ public void BeforeUpdate_NoImplicit() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooksNoImplicit, ResourceHook.BeforeUpdate); var personDiscovery = SetDiscoverableHooks(targetHooksNoImplicit, ResourceHook.BeforeUpdateRelationship); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); // act hookExecutor.BeforeUpdate(todoList, ResourcePipeline.Patch); @@ -176,8 +169,7 @@ public void BeforeUpdate_NoImplicit_Without_Parent_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); var personDiscovery = SetDiscoverableHooks(targetHooksNoImplicit, ResourceHook.BeforeUpdateRelationship); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); // act hookExecutor.BeforeUpdate(todoList, ResourcePipeline.Patch); @@ -197,8 +189,7 @@ public void BeforeUpdate_NoImplicit_Without_Child_Hook_Implemented() // arrange var todoDiscovery = SetDiscoverableHooks(targetHooksNoImplicit, ResourceHook.BeforeUpdate); var personDiscovery = SetDiscoverableHooks(NoHooks, DisableDbValues); - (var contextMock, var hookExecutor, var todoResourceMock, - var ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); + var (_, _, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); // act hookExecutor.BeforeUpdate(todoList, ResourcePipeline.Patch); diff --git a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs index fbe909938f..82b3cb005c 100644 --- a/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs +++ b/test/UnitTests/ResourceHooks/ResourceHooksTestsSetup.cs @@ -16,12 +16,14 @@ using System.Linq; using Person = JsonApiDotNetCoreExample.Models.Person; using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Managers.Contracts; +using JsonApiDotNetCore.Serialization; +using JsonApiDotNetCore.Internal.Query; namespace UnitTests.ResourceHooks { public class HooksDummyData { + protected IExposedFieldExplorer _fieldExplorer; protected IResourceGraph _graph; protected ResourceHook[] NoHooks = new ResourceHook[0]; protected ResourceHook[] EnableDbValues = { ResourceHook.BeforeUpdate, ResourceHook.BeforeUpdateRelationship }; @@ -36,14 +38,16 @@ public class HooksDummyData public HooksDummyData() { _graph = new ResourceGraphBuilder() - .AddResource() - .AddResource() - .AddResource() - .AddResource
() - .AddResource() - .AddResource() - .AddResource() - .Build(); + .AddResource() + .AddResource() + .AddResource() + .AddResource
() + .AddResource() + .AddResource() + .AddResource() + .Build(); + + _fieldExplorer = new ExposedFieldExplorer(_graph); _todoFaker = new Faker().Rules((f, i) => i.Id = f.UniqueIndex + 1); _personFaker = new Faker().Rules((f, i) => i.Id = f.UniqueIndex + 1); @@ -137,33 +141,35 @@ protected List CreateTodoWithOwner() public class HooksTestsSetup : HooksDummyData { - (IResourceGraph, Mock, Mock, IJsonApiOptions) CreateMocks() + (IResourceGraph, Mock, Mock, Mock, IJsonApiOptions) CreateMocks() { var pfMock = new Mock(); var graph = _graph; - var rqMock = new Mock(); - var optionsMock = new JsonApiOptions { LoadDatabaseValues = false }; - return (graph, rqMock, pfMock, optionsMock); + var ufMock = new Mock(); + var iqsMock = new Mock(); + var optionsMock = new JsonApiOptions { LoaDatabaseValues = false }; + return (graph, ufMock, iqsMock, pfMock, optionsMock); } - internal (Mock requestManagerMock, ResourceHookExecutor, Mock>) CreateTestObjects(IHooksDiscovery mainDiscovery = null) + internal (Mock, ResourceHookExecutor, Mock>) CreateTestObjects(IHooksDiscovery mainDiscovery = null) where TMain : class, IIdentifiable { // creates the resource definition mock and corresponding ImplementedHooks discovery instance var mainResource = CreateResourceDefinition(mainDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - var (graph, rqMock, gpfMock, options) = CreateMocks(); + var (graph, ufMock, iqMock, gpfMock, options) = CreateMocks(); SetupProcessorFactoryForResourceDefinition(gpfMock, mainResource.Object, mainDiscovery, null); - var meta = new HookExecutorHelper(gpfMock.Object, graph, options); - var hookExecutor = new ResourceHookExecutor(meta, graph, rqMock.Object); + var execHelper = new HookExecutorHelper(gpfMock.Object, graph, options); + var traversalHelper = new TraversalHelper(graph, ufMock.Object); + var hookExecutor = new ResourceHookExecutor(execHelper, traversalHelper, ufMock.Object, iqMock.Object, graph); - return (rqMock, hookExecutor, mainResource); + return (iqMock, hookExecutor, mainResource); } - protected (Mock requestManagerMock, IResourceHookExecutor, Mock>, Mock>) + protected (Mock, Mock, IResourceHookExecutor, Mock>, Mock>) CreateTestObjects( IHooksDiscovery mainDiscovery = null, IHooksDiscovery nestedDiscovery = null, @@ -177,20 +183,21 @@ public class HooksTestsSetup : HooksDummyData var nestedResource = CreateResourceDefinition(nestedDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - var (graph, rqMock, gpfMock, options) = CreateMocks(); + var (graph, ufMock, iqMock, gpfMock, options) = CreateMocks(); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; - var traversalHelper = new TraversalHelper(graph, rqMock.Object); SetupProcessorFactoryForResourceDefinition(gpfMock, mainResource.Object, mainDiscovery, dbContext); SetupProcessorFactoryForResourceDefinition(gpfMock, nestedResource.Object, nestedDiscovery, dbContext); - var meta = new HookExecutorHelper(gpfMock.Object, graph, options); - var hookExecutor = new ResourceHookExecutor(meta, graph, rqMock.Object); - return (rqMock, hookExecutor, mainResource, nestedResource); + var execHelper = new HookExecutorHelper(gpfMock.Object, graph, options); + var traversalHelper = new TraversalHelper(graph, ufMock.Object); + var hookExecutor = new ResourceHookExecutor(execHelper, traversalHelper, ufMock.Object, iqMock.Object, graph); + + return (iqMock, ufMock, hookExecutor, mainResource, nestedResource); } - protected (Mock requestManagerMock, IResourceHookExecutor, Mock>, Mock>, Mock>) + protected (Mock, IResourceHookExecutor, Mock>, Mock>, Mock>) CreateTestObjects( IHooksDiscovery mainDiscovery = null, IHooksDiscovery firstNestedDiscovery = null, @@ -207,7 +214,7 @@ public class HooksTestsSetup : HooksDummyData var secondNestedResource = CreateResourceDefinition(secondNestedDiscovery); // mocking the GenericProcessorFactory and JsonApiContext and wiring them up. - var (graph, rqMock, gpfMock, options) = CreateMocks(); + var (graph, ufMock, iqMock, gpfMock, options) = CreateMocks(); var dbContext = repoDbContextOptions != null ? new AppDbContext(repoDbContextOptions) : null; @@ -215,10 +222,11 @@ public class HooksTestsSetup : HooksDummyData SetupProcessorFactoryForResourceDefinition(gpfMock, firstNestedResource.Object, firstNestedDiscovery, dbContext); SetupProcessorFactoryForResourceDefinition(gpfMock, secondNestedResource.Object, secondNestedDiscovery, dbContext); - var hookExecutorHelper = new HookExecutorHelper(gpfMock.Object, graph, options); - var hookExecutor = new ResourceHookExecutor(hookExecutorHelper, graph, rqMock.Object); + var execHelper = new HookExecutorHelper(gpfMock.Object, graph, options); + var traversalHelper = new TraversalHelper(graph, ufMock.Object); + var hookExecutor = new ResourceHookExecutor(execHelper, traversalHelper, ufMock.Object, iqMock.Object, graph); - return (rqMock, hookExecutor, mainResource, firstNestedResource, secondNestedResource); + return (iqMock, hookExecutor, mainResource, firstNestedResource, secondNestedResource); } protected IHooksDiscovery SetDiscoverableHooks(ResourceHook[] implementedHooks, params ResourceHook[] enableDbValuesHooks) @@ -308,7 +316,7 @@ void MockHooks(Mock> resourceDefinition) var processorFactory = new Mock(); var context = new Mock(); context.Setup(c => c.GenericProcessorFactory).Returns(processorFactory.Object); - context.Setup(c => c.Options).Returns(new JsonApiOptions { LoadDatabaseValues = false }); + context.Setup(c => c.Options).Returns(new JsonApiOptions { LoaDatabaseValues = false }); context.Setup(c => c.ResourceGraph).Returns(ResourceGraph.Instance); return (context, processorFactory); @@ -350,7 +358,7 @@ IJsonApiContext apiContext ) where TModel : class, IIdentifiable { IDbContextResolver resolver = CreateTestDbResolver(dbContext); - return new DefaultEntityRepository(apiContext, resolver); + return new DefaultEntityRepository(null, apiContext, resolver); } IDbContextResolver CreateTestDbResolver(AppDbContext dbContext) where TModel : class, IIdentifiable @@ -374,6 +382,30 @@ Mock> CreateResourceDefinition MockHooks(resourceDefinition); return resourceDefinition; } + + protected List> GetIncludedRelationshipsChains(params string[] chains) + { + var parsedChains = new List>(); + + foreach (var chain in chains) + parsedChains.Add(GetIncludedRelationshipsChain(chain)); + + return parsedChains; + } + + protected List GetIncludedRelationshipsChain(string chain) + { + var parsedChain = new List(); + var resourceContext = _graph.GetContextEntity(); + var splittedPath = chain.Split(QueryConstants.DOT); + foreach (var requestedRelationship in splittedPath) + { + var relationship = resourceContext.Relationships.Single(r => r.PublicRelationshipName == requestedRelationship); + parsedChain.Add(relationship); + resourceContext = _graph.GetContextEntity(relationship.DependentType); + } + return parsedChain; + } } } diff --git a/test/UnitTests/Serializ/BaseDeserializerTests.cs b/test/UnitTests/Serializ/BaseDeserializerTests.cs deleted file mode 100644 index 7cc858f793..0000000000 --- a/test/UnitTests/Serializ/BaseDeserializerTests.cs +++ /dev/null @@ -1,370 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using Newtonsoft.Json; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class BaseDeserializerTests : DeserializerTestsSetup - { - private readonly DeserializerBase _deserializer; - public BaseDeserializerTests() - { - _deserializer = new DeserializerBase(_resourceGraph, _defaultSettings); - } - - [Fact] - public void DeserializeResourceIdentifiers_SingleData_CanDeserialize() - { - // arange - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = (TestResource)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - - } - - [Fact] - public void DeserializeResourceIdentifiers_EmptySingleData_CanDeserialize() - { - // arange - var content = new Document { }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.Deserialize(body); - - // arrange - Assert.Null(result); - } - - [Fact] - public void DeserializeResourceIdentifiers_ArrayData_CanDeserialize() - { - // arange - var content = new Documents - { - Data = new List - { - new ResourceObject - { - Type = "test-resource", - Id = "1", - } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = (List)_deserializer.Deserialize(body); - - // assert - Assert.Equal("1", result.First().StringId); - } - - [Fact] - public void DeserializeResourceIdentifiers_EmptyArrayData_CanDeserialize() - { - var content = new Documents { Data = new List { } }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = (IList)_deserializer.Deserialize(body); - - // assert - Assert.Empty(result); - } - - [Theory] - [InlineData("string-field", "some string")] - [InlineData("string-field", null)] - [InlineData("int-field", null, true)] - [InlineData("int-field", 1)] - [InlineData("int-field", "1")] - [InlineData("nullable-int-field", null)] - [InlineData("nullable-int-field", "1")] - [InlineData("guid-field", "bad format", true)] - [InlineData("guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781")] - [InlineData("date-time-field", "9/11/2019 11:41:40 AM")] - [InlineData("date-time-field", null, true)] - [InlineData("nullable-date-time-field", null)] - public void DeserializeAttributes_VariousDataTypes_CanDeserialize(string member, object value, bool expectError = false) - { - // arrange - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { member, value } - } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act, assert - if (expectError) - { - Assert.ThrowsAny(() => _deserializer.Deserialize(body)); - return; - } - - // act - var entity = (TestResource)_deserializer.Deserialize(body); - - // assert - var pi = _resourceGraph.GetContextEntity("test-resource").Attributes.Single(attr => attr.PublicAttributeName == member).PropertyInfo; - var deserializedValue = pi.GetValue(entity); - - if (member == "int-field") - { - Assert.Equal(deserializedValue, 1); - } - else if (member == "nullable-int-field" && value == null) - { - Assert.Equal(deserializedValue, null); - } - else if (member == "nullable-int-field" && (string)value == "1") - { - Assert.Equal(deserializedValue, 1); - } - else if (member == "guid-field") - { - Assert.Equal(deserializedValue, Guid.Parse("1a68be43-cc84-4924-a421-7f4d614b7781")); - } - else if (member == "date-time-field") - { - Assert.Equal(deserializedValue, DateTime.Parse("9/11/2019 11:41:40 AM")); - } else - { - Assert.Equal(value, deserializedValue); - } - } - - [Fact] - public void DeserializeAttributes_ComplexType_CanDeserialize() - { - // arrange - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { "complex-field", new Dictionary { {"compound-name", "testName" } } } - } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = (TestResource)_deserializer.Deserialize(body); - - // assert - Assert.NotNull(result.ComplexField); - Assert.Equal("testName", result.ComplexField.CompoundName); - } - - [Fact] - public void DeserializeAttributes_ComplexListType_CanDeserialize() - { - // arrange - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource-with-list", - Id = "1", - Attributes = new Dictionary - { - { "complex-fields", new [] { new Dictionary { {"compound-name", "testName" } } } } - } - } - }; - var body = JsonConvert.SerializeObject(content); - - - // act - var result = (TestResourceWithList)_deserializer.Deserialize(body); - - // assert - Assert.NotNull(result.ComplexFields); - Assert.NotEmpty(result.ComplexFields); - Assert.Equal("testName", result.ComplexFields[0].CompoundName); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToOneDependent_NavigationPropertyIsNull() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToOnePrincipal)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Dependent); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_PopulatedOneToOneDependent_NavigationPropertyIsPopulated() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent", "one-to-one-dependents"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToOnePrincipal)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Equal(10, result.Dependent.Id); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToOnePrincipal_NavigationPropertyAndForeignKeyAreNull() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToOneDependent)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Principal); - Assert.Null(result.PrincipalId); - } - - [Fact] - public void DeserializeRelationships_EmptyRequiredOneToOnePrincipal_ThrowsFormatException() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-required-dependents", "principal"); - var body = JsonConvert.SerializeObject(content); - - // act, assert - Assert.ThrowsAny(() => _deserializer.Deserialize(body)); - } - - [Fact] - public void DeserializeRelationships_PopulatedOneToOnePrincipal_NavigationPropertyIsNullAndForeignKeyIsPopulated() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal", "one-to-one-principals"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToOneDependent)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Principal); - Assert.Equal(10, result.PrincipalId); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToManyPrincipal_NavigationAndForeignKeyAreNull() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToManyDependent)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Principal); - Assert.Null(result.PrincipalId); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToManyRequiredPrincipal_ThrowsFormatException() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-required-dependents", "principal"); - var body = JsonConvert.SerializeObject(content); - - // act, assert - Assert.ThrowsAny(() => _deserializer.Deserialize(body)); - } - - [Fact] - public void DeserializeRelationships_PopulatedOneToManyPrincipal_NavigationIsNullAndForeignKeyIsPopulated() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal", "one-to-many-principals"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToManyDependent)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Principal); - Assert.Equal(10, result.PrincipalId); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToManyDependent_NavigationIsNull() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToManyPrincipal)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Dependents); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_PopulatedOneToManyDependent_NavigationIsPopulated() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents", "one-to-many-dependents", isToManyData: true); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToManyPrincipal)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Equal(1, result.Dependents.Count); - Assert.Equal(10, result.Dependents.First().Id); - Assert.Null(result.AttributeMember); - } - } -} diff --git a/test/UnitTests/Serializ/ClientDeserializerTests.cs b/test/UnitTests/Serializ/ClientDeserializerTests.cs deleted file mode 100644 index 315b932b11..0000000000 --- a/test/UnitTests/Serializ/ClientDeserializerTests.cs +++ /dev/null @@ -1,333 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using Newtonsoft.Json; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class ClientDeserializerTests : DeserializerTestsSetup - { - private readonly Dictionary _linkValues = new Dictionary(); - private readonly ClientDeserializer _deserializer; - - public ClientDeserializerTests() - { - _deserializer = new ClientDeserializer(_resourceGraph, _defaultSettings); - _linkValues.Add("self", "http://example.com/articles"); - _linkValues.Add("next", "http://example.com/articles?page[offset]=2"); - _linkValues.Add("last", "http://example.com/articles?page[offset]=10"); - } - - [Fact] - public void DeserializeSingle_EmptyResponseWithMeta_CanDeserialize() - { - // arrange - var content = new Document - { - Meta = new Dictionary { { "foo", "bar" } } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - - // assert - Assert.Null(result.Data); - Assert.NotNull(result.Meta); - Assert.Equal("bar", result.Meta["foo"]); - } - - [Fact] - public void DeserializeSingle_EmptyResponseWithTopLevelLinks_CanDeserialize() - { - // arrange - var content = new Document - { - Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - - // assert - Assert.Null(result.Data); - Assert.NotNull(result.Links); - Assert.Equal(_linkValues["self"], result.Links.Self); - Assert.Equal(_linkValues["next"], result.Links.Next); - Assert.Equal(_linkValues["last"], result.Links.Last); - } - - [Fact] - public void DeserializeList_EmptyResponseWithTopLevelLinks_CanDeserialize() - { - // arrange - var content = new Documents - { - Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] }, - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeList(body); - - // assert - Assert.Empty(result.Data); - Assert.NotNull(result.Links); - Assert.Equal(_linkValues["self"], result.Links.Self); - Assert.Equal(_linkValues["next"], result.Links.Next); - Assert.Equal(_linkValues["last"], result.Links.Last); - } - - [Fact] - public void DeserializeSingle_ResourceWithAttributes_CanDeserialize() - { - // arrange - var content = CreateTestResourceDocument(); - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Null(result.Links); - Assert.Null(result.Meta); - Assert.Equal(1, entity.Id); - Assert.Equal(content.Data.Attributes["string-field"], entity.StringField); - } - - [Fact] - public void DeserializeSingle_MultipleDependentRelationshipsWithIncluded_CanDeserialize() - { - // arrange - var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); - content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); - var toOneAttributeValue = "populated-to-one member content"; - var toManyAttributeValue = "populated-to-manies member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "one-to-one-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } - }, - new ResourceObject() - { - Type = "one-to-many-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Equal(1, entity.Id); - Assert.NotNull(entity.PopulatedToOne); - Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); - Assert.Equal(toManyAttributeValue, entity.PopulatedToManies.First().AttributeMember); - Assert.NotNull(entity.PopulatedToManies); - Assert.NotNull(entity.EmptyToManies); - Assert.Empty(entity.EmptyToManies); - Assert.Null(entity.EmptyToOne); - } - - [Fact] - public void DeserializeSingle_MultiplePrincipalRelationshipsWithIncluded_CanDeserialize() - { - // arrange - var content = CreateDocumentWithRelationships("multi-dependents"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); - content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); - var toOneAttributeValue = "populated-to-one member content"; - var toManyAttributeValue = "populated-to-manies member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "one-to-one-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } - }, - new ResourceObject() - { - Type = "one-to-many-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Equal(1, entity.Id); - Assert.NotNull(entity.PopulatedToOne); - Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); - Assert.Equal(toManyAttributeValue, entity.PopulatedToMany.AttributeMember); - Assert.NotNull(entity.PopulatedToMany); - Assert.Null(entity.EmptyToMany); - Assert.Null(entity.EmptyToOne); - } - - [Fact] - public void DeserializeSingle_NestedIncluded_CanDeserialize() - { - // arrange - var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); - var toManyAttributeValue = "populated-to-manies member content"; - var nestedIncludeAttributeValue = "nested include member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "one-to-many-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } }, - Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } - }, - new ResourceObject() - { - Type = "one-to-many-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", nestedIncludeAttributeValue } } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Equal(1, entity.Id); - Assert.Null(entity.PopulatedToOne); - Assert.Null(entity.EmptyToManies); - Assert.Null(entity.EmptyToOne); - Assert.NotNull(entity.PopulatedToManies); - var includedEntity = entity.PopulatedToManies.First(); - Assert.Equal(toManyAttributeValue, includedEntity.AttributeMember); - var nestedIncludedEntity = includedEntity.Principal; - Assert.Equal(nestedIncludeAttributeValue, nestedIncludedEntity.AttributeMember); - } - - - [Fact] - public void DeserializeSingle_DeeplyNestedIncluded_CanDeserialize() - { - // arrange - var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("multi", CreateRelationshipData("multi-principals")); - var includedAttributeValue = "multi member content"; - var nestedIncludedAttributeValue = "nested include member content"; - var deeplyNestedIncludedAttributeValue = "deeply nested member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "multi-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, - Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } - }, - new ResourceObject() - { - Type = "one-to-many-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, - Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } - }, - new ResourceObject() - { - Type = "one-to-many-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } - }, - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Equal(1, entity.Id); - var included = entity.Multi; - Assert.Equal(10, included.Id); - Assert.Equal(includedAttributeValue, included.AttributeMember); - var nestedIncluded = included.PopulatedToManies.First(); - Assert.Equal(10, nestedIncluded.Id); - Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); - var deeplyNestedIncluded = nestedIncluded.Principal; - Assert.Equal(10, deeplyNestedIncluded.Id); - Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); - } - - - [Fact] - public void DeserializeList_DeeplyNestedIncluded_CanDeserialize() - { - // arrange - var content = new Documents { Data = new List { CreateDocumentWithRelationships("multi-principals").Data } }; - content.Data[0].Relationships.Add("multi", CreateRelationshipData("multi-principals")); - var includedAttributeValue = "multi member content"; - var nestedIncludedAttributeValue = "nested include member content"; - var deeplyNestedIncludedAttributeValue = "deeply nested member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "multi-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, - Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } - }, - new ResourceObject() - { - Type = "one-to-many-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, - Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } - }, - new ResourceObject() - { - Type = "one-to-many-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } - }, - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeList(body); - var entity = result.Data.First(); - - // assert - Assert.Equal(1, entity.Id); - var included = entity.Multi; - Assert.Equal(10, included.Id); - Assert.Equal(includedAttributeValue, included.AttributeMember); - var nestedIncluded = included.PopulatedToManies.First(); - Assert.Equal(10, nestedIncluded.Id); - Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); - var deeplyNestedIncluded = nestedIncluded.Principal; - Assert.Equal(10, deeplyNestedIncluded.Id); - Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); - } - } -} diff --git a/test/UnitTests/Serializ/DasherizedResolverTests.cs b/test/UnitTests/Serializ/DasherizedResolverTests.cs deleted file mode 100644 index ca746bfb91..0000000000 --- a/test/UnitTests/Serializ/DasherizedResolverTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Serialization.Contracts; - -using Newtonsoft.Json; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class DasherizedResolverTests - { - [Fact] - public void Resolver_Dasherizes_Property_Names() - { - // arrange - var obj = new - { - myProp = "val" - }; - - // act - var result = JsonConvert.SerializeObject(obj, - Formatting.None, - new JsonSerializerSettings { ContractResolver = new DasherizedResolver() } - ); - - // assert - Assert.Equal("{\"my-prop\":\"val\"}", result); - } - } -} diff --git a/test/UnitTests/Serializ/DeserializerTestsSetup.cs b/test/UnitTests/Serializ/DeserializerTestsSetup.cs deleted file mode 100644 index da10de7450..0000000000 --- a/test/UnitTests/Serializ/DeserializerTestsSetup.cs +++ /dev/null @@ -1,68 +0,0 @@ -using JsonApiDotNetCore.Models; -using System.Collections.Generic; -using System; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Serialization; - -namespace UnitTests.Deserialization -{ - public class DeserializerTestsSetup : SerializationTestBase - { - protected Document CreateDocumentWithRelationships(string mainType, string relationshipMemberName, string relatedType = null, bool isToManyData = false) - { - var content = CreateDocumentWithRelationships(mainType); - content.Data.Relationships.Add(relationshipMemberName, CreateRelationshipData(relatedType, isToManyData)); - return content; - } - - protected Document CreateDocumentWithRelationships(string mainType) - { - return new Document - { - Data = new ResourceObject - { - Id = "1", - Type = mainType, - Relationships = new Dictionary { } - } - }; - } - - protected RelationshipData CreateRelationshipData(string relatedType = null, bool isToManyData = false) - { - var data = new RelationshipData(); - var rio = relatedType == null ? null : new ResourceIdentifierObject { Id = "10", Type = relatedType }; - - if (isToManyData) - { - data.ExposedData = new List(); - if (relatedType != null) ((List)data.ExposedData).Add(rio); - } else - { - data.ExposedData = rio; - } - return data; - } - - protected Document CreateTestResourceDocument() - { - return new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { "string-field", "some string" }, - { "int-field", 1 }, - { "nullable-int-field", null }, - { "guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781" }, - { "date-time-field", "9/11/2019 11:41:40 AM" } - } - } - }; - } - } -} diff --git a/test/UnitTests/Serializ/JsonApiSerializerTests.cs b/test/UnitTests/Serializ/JsonApiSerializerTests.cs deleted file mode 100644 index 22a7a0ea93..0000000000 --- a/test/UnitTests/Serializ/JsonApiSerializerTests.cs +++ /dev/null @@ -1,285 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Extensions; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Request; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Serialization.Contracts; - -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class JsonApiSerializerTests - { - [Fact] - public void Can_Serialize_Complex_Types() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - - var serializer = GetSerializer(resourceGraphBuilder); - - var resource = new TestResource - { - ComplexMember = new ComplexType - { - CompoundName = "testname" - } - }; - - // act - var result = serializer.Serialize(resource); - - // assert - Assert.NotNull(result); - - var expectedFormatted = - @"{ - ""data"": { - ""attributes"": { - ""complex-member"": { - ""compound-name"": ""testname"" - } - }, - ""relationships"": { - ""children"": { - ""links"": { - ""self"": ""/test-resource//relationships/children"", - ""related"": ""/test-resource//children"" - } - } - }, - ""type"": ""test-resource"", - ""id"": """" - } - }"; - var expected = Regex.Replace(expectedFormatted, @"\s+", ""); - - Assert.Equal(expected, result); - } - - [Fact] - public void Can_Serialize_Deeply_Nested_Relationships() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - resourceGraphBuilder.AddResource("children"); - resourceGraphBuilder.AddResource("infections"); - - var serializer = GetSerializer( - resourceGraphBuilder, - new List { "children.infections" } - ); - - var resource = new TestResource - { - Id = 1, - Children = new List { - new ChildResource { - Id = 2, - Infections = new List { - new InfectionResource { Id = 4 }, - new InfectionResource { Id = 5 }, - } - }, - new ChildResource { - Id = 3 - } - } - }; - - // act - var result = serializer.Serialize(resource); - - // assert - Assert.NotNull(result); - - var expectedFormatted = - @"{ - ""data"": { - ""attributes"": { - ""complex-member"": null - }, - ""relationships"": { - ""children"": { - ""links"": { - ""self"": ""/test-resource/1/relationships/children"", - ""related"": ""/test-resource/1/children"" - }, - ""data"": [{ - ""type"": ""children"", - ""id"": ""2"" - }, { - ""type"": ""children"", - ""id"": ""3"" - }] - } - }, - ""type"": ""test-resource"", - ""id"": ""1"" - }, - ""included"": [ - { - ""attributes"": {}, - ""relationships"": { - ""infections"": { - ""links"": { - ""self"": ""/children/2/relationships/infections"", - ""related"": ""/children/2/infections"" - }, - ""data"": [{ - ""type"": ""infections"", - ""id"": ""4"" - }, { - ""type"": ""infections"", - ""id"": ""5"" - }] - }, - ""parent"": { - ""links"": { - ""self"": ""/children/2/relationships/parent"", - ""related"": ""/children/2/parent"" - } - } - }, - ""type"": ""children"", - ""id"": ""2"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infected"": { - ""links"": { - ""self"": ""/infections/4/relationships/infected"", - ""related"": ""/infections/4/infected"" - } - } - }, - ""type"": ""infections"", - ""id"": ""4"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infected"": { - ""links"": { - ""self"": ""/infections/5/relationships/infected"", - ""related"": ""/infections/5/infected"" - } - } - }, - ""type"": ""infections"", - ""id"": ""5"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infections"": { - ""links"": { - ""self"": ""/children/3/relationships/infections"", - ""related"": ""/children/3/infections"" - } - }, - ""parent"": { - ""links"": { - ""self"": ""/children/3/relationships/parent"", - ""related"": ""/children/3/parent"" - } - } - }, - ""type"": ""children"", - ""id"": ""3"" - } - ] - }"; - var expected = Regex.Replace(expectedFormatted, @"\s+", ""); - - Assert.Equal(expected, result); - } - - private JsonApiSerializer GetSerializer( - ResourceGraphBuilder resourceGraphBuilder, - List included = null) - { - var resourceGraph = resourceGraphBuilder.Build(); - var requestManagerMock = new Mock(); - requestManagerMock.Setup(m => m.GetRequestResource()).Returns(resourceGraph.GetContextEntity("test-resource")); - requestManagerMock.Setup(m => m.IncludedRelationships).Returns(included); - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); - jsonApiContextMock.Setup(m => m.RequestEntity).Returns(resourceGraph.GetContextEntity("test-resource")); - jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); - - - jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); - var pmMock = new Mock(); - jsonApiContextMock.Setup(m => m.PageManager).Returns(pmMock.Object); - - - - var jsonApiOptions = new JsonApiOptions(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var services = new ServiceCollection(); - - var mvcBuilder = services.AddMvcCore(); - - services - .AddJsonApiInternals(jsonApiOptions); - - var provider = services.BuildServiceProvider(); - var scoped = new TestScopedServiceProvider(provider); - - var documentBuilder = GetDocumentBuilder(jsonApiContextMock, requestManagerMock.Object, scopedServiceProvider: scoped); - var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); - - return serializer; - } - - private class TestResource : Identifiable - { - [Attr("complex-member")] - public ComplexType ComplexMember { get; set; } - - [HasMany("children")] public List Children { get; set; } - } - - private class ComplexType - { - public string CompoundName { get; set; } - } - - private class ChildResource : Identifiable - { - [HasMany("infections")] public List Infections { get; set; } - - [HasOne("parent")] public TestResource Parent { get; set; } - } - - private class InfectionResource : Identifiable - { - [HasOne("infected")] public ChildResource Infected { get; set; } - } - - private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, IRequestManager requestManager, TestScopedServiceProvider scopedServiceProvider = null) - { - var pageManagerMock = new Mock(); - - return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, requestManager, scopedServiceProvider: scopedServiceProvider); - - } - } -} diff --git a/test/UnitTests/Serializ/SerializationTestsSetupBase.cs b/test/UnitTests/Serializ/SerializationTestsSetupBase.cs deleted file mode 100644 index 5135a2e642..0000000000 --- a/test/UnitTests/Serializ/SerializationTestsSetupBase.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Collections.Generic; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; - -namespace UnitTests.Deserialization -{ - public class SerializationTestsSetupBase - { - protected readonly IResourceGraph _resourceGraph; - protected readonly JsonApiSerializerSettings _defaultSettings = new JsonApiSerializerSettings(); - - public SerializationTestsSetupBase() - { - _resourceGraph = BuildGraph(); - } - - protected IResourceGraph BuildGraph() - { - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - resourceGraphBuilder.AddResource("test-resource-with-list"); - // one to one relationships - resourceGraphBuilder.AddResource("one-to-one-principals"); - resourceGraphBuilder.AddResource("one-to-one-dependents"); - resourceGraphBuilder.AddResource("one-to-one-required-dependents"); - // one to many relationships - resourceGraphBuilder.AddResource("one-to-many-principals"); - resourceGraphBuilder.AddResource("one-to-many-dependents"); - resourceGraphBuilder.AddResource("one-to-many-required-dependents"); - // collective relationships - resourceGraphBuilder.AddResource("multi-principals"); - resourceGraphBuilder.AddResource("multi-dependents"); - return resourceGraphBuilder.Build(); - } - - protected class TestResource : Identifiable - { - [Attr] public string StringField { get; set; } - [Attr] public DateTime DateTimeField { get; set; } - [Attr] public DateTime? NullableDateTimeField { get; set; } - [Attr] public int IntField { get; set; } - [Attr] public int? NullableIntField { get; set; } - [Attr] public Guid GuidField { get; set; } - [Attr] public ComplexType ComplexField { get; set; } - [Attr(isImmutable: true)] public string Immutable { get; set; } - } - - protected class TestResourceWithList : Identifiable - { - [Attr] public List ComplexFields { get; set; } - } - - protected class ComplexType - { - public string CompoundName { get; set; } - } - - protected class OneToOnePrincipal : IdentifiableWithAttribute - { - [HasOne] public OneToOneDependent Dependent { get; set; } - } - - protected class OneToOneDependent : IdentifiableWithAttribute - { - [HasOne] public OneToOnePrincipal Principal { get; set; } - public int? PrincipalId { get; set; } - } - - protected class OneToOneRequiredDependent : IdentifiableWithAttribute - { - [HasOne] public OneToOnePrincipal Principal { get; set; } - public int PrincipalId { get; set; } - } - - protected class OneToManyDependent : IdentifiableWithAttribute - { - [HasOne] public OneToManyPrincipal Principal { get; set; } - public int? PrincipalId { get; set; } - } - - protected class OneToManyRequiredDependent : IdentifiableWithAttribute - { - [HasOne] public OneToManyPrincipal Principal { get; set; } - public int PrincipalId { get; set; } - } - - protected class OneToManyPrincipal : IdentifiableWithAttribute - { - [HasMany] public List Dependents { get; set; } - } - - protected class IdentifiableWithAttribute : Identifiable - { - [Attr] public string AttributeMember { get; set; } - } - - protected class MultipleRelationshipsPrincipalPart : IdentifiableWithAttribute - { - [HasOne] public OneToOneDependent PopulatedToOne { get; set; } - [HasOne] public OneToOneDependent EmptyToOne { get; set; } - [HasMany] public List PopulatedToManies { get; set; } - [HasMany] public List EmptyToManies { get; set; } - [HasOne] public MultipleRelationshipsPrincipalPart Multi { get; set; } - } - - protected class MultipleRelationshipsDependentPart : IdentifiableWithAttribute - { - [HasOne] public OneToOnePrincipal PopulatedToOne { get; set; } - public int PopulatedToOneId { get; set; } - [HasOne] public OneToOnePrincipal EmptyToOne { get; set; } - public int? EmptyToOneId { get; set; } - [HasOne] public OneToManyPrincipal PopulatedToMany { get; set; } - public int PopulatedToManyId { get; set; } - [HasOne] public OneToManyPrincipal EmptyToMany { get; set; } - public int? EmptyToManyId { get; set; } - } - } -} \ No newline at end of file diff --git a/test/UnitTests/Serializ/ServerDeserializerTests.cs b/test/UnitTests/Serializ/ServerDeserializerTests.cs deleted file mode 100644 index 9d97bdf3e3..0000000000 --- a/test/UnitTests/Serializ/ServerDeserializerTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using Moq; -using Newtonsoft.Json; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class ServerDeserializerTests : DeserializerTestsSetup - { - private readonly ServerDeserializer _deserializer; - private readonly Mock _fieldsManagerMock = new Mock(); - public ServerDeserializerTests() : base() - { - _deserializer = new ServerDeserializer(_resourceGraph, _defaultSettings, _fieldsManagerMock.Object); - } - - [Fact] - public void DeserializeAttributes_VariousUpdatedMembers_RegistersUpdatedFields() - { - // arrange - SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); - Document content = CreateTestResourceDocument(); - var body = JsonConvert.SerializeObject(content); - - // act - _deserializer.Deserialize(body); - - // assert - Assert.Equal(5, attributesToUpdate.Count); - Assert.Empty(relationshipsToUpdate); - } - - [Fact] - public void DeserializeAttributes_UpdatedImmutableMember_ThrowsInvalidOperationException() - { - // arrange - SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { "immutable", "some string" }, - } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act, assert - Assert.Throws(() => _deserializer.Deserialize(body)); - } - - [Fact] - public void DeserializeRelationships_MultipleDependentRelationships_RegistersUpdatedRelationships() - { - // arrange - SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); - var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); - content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); - var body = JsonConvert.SerializeObject(content); - - // act - _deserializer.Deserialize(body); - - // assert - Assert.Equal(4, relationshipsToUpdate.Count); - Assert.Empty(attributesToUpdate); - } - - [Fact] - public void DeserializeRelationships_MultiplePrincipalRelationships_RegistersUpdatedRelationships() - { - // arrange - SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); - var content = CreateDocumentWithRelationships("multi-dependents"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); - content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); - var body = JsonConvert.SerializeObject(content); - - // act - _deserializer.Deserialize(body); - - // assert - Assert.Equal(4, relationshipsToUpdate.Count); - Assert.Empty(attributesToUpdate); - } - - private void SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate) - { - attributesToUpdate = new List(); - relationshipsToUpdate = new List(); - _fieldsManagerMock.Setup(m => m.AttributesToUpdate).Returns(attributesToUpdate); - _fieldsManagerMock.Setup(m => m.RelationshipsToUpdate).Returns(relationshipsToUpdate); - } - } -} diff --git a/test/UnitTests/Serialization (copy)/BaseDeserializerTests.cs b/test/UnitTests/Serialization (copy)/BaseDeserializerTests.cs deleted file mode 100644 index 7cc858f793..0000000000 --- a/test/UnitTests/Serialization (copy)/BaseDeserializerTests.cs +++ /dev/null @@ -1,370 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using Newtonsoft.Json; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class BaseDeserializerTests : DeserializerTestsSetup - { - private readonly DeserializerBase _deserializer; - public BaseDeserializerTests() - { - _deserializer = new DeserializerBase(_resourceGraph, _defaultSettings); - } - - [Fact] - public void DeserializeResourceIdentifiers_SingleData_CanDeserialize() - { - // arange - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = (TestResource)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - - } - - [Fact] - public void DeserializeResourceIdentifiers_EmptySingleData_CanDeserialize() - { - // arange - var content = new Document { }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.Deserialize(body); - - // arrange - Assert.Null(result); - } - - [Fact] - public void DeserializeResourceIdentifiers_ArrayData_CanDeserialize() - { - // arange - var content = new Documents - { - Data = new List - { - new ResourceObject - { - Type = "test-resource", - Id = "1", - } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = (List)_deserializer.Deserialize(body); - - // assert - Assert.Equal("1", result.First().StringId); - } - - [Fact] - public void DeserializeResourceIdentifiers_EmptyArrayData_CanDeserialize() - { - var content = new Documents { Data = new List { } }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = (IList)_deserializer.Deserialize(body); - - // assert - Assert.Empty(result); - } - - [Theory] - [InlineData("string-field", "some string")] - [InlineData("string-field", null)] - [InlineData("int-field", null, true)] - [InlineData("int-field", 1)] - [InlineData("int-field", "1")] - [InlineData("nullable-int-field", null)] - [InlineData("nullable-int-field", "1")] - [InlineData("guid-field", "bad format", true)] - [InlineData("guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781")] - [InlineData("date-time-field", "9/11/2019 11:41:40 AM")] - [InlineData("date-time-field", null, true)] - [InlineData("nullable-date-time-field", null)] - public void DeserializeAttributes_VariousDataTypes_CanDeserialize(string member, object value, bool expectError = false) - { - // arrange - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { member, value } - } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act, assert - if (expectError) - { - Assert.ThrowsAny(() => _deserializer.Deserialize(body)); - return; - } - - // act - var entity = (TestResource)_deserializer.Deserialize(body); - - // assert - var pi = _resourceGraph.GetContextEntity("test-resource").Attributes.Single(attr => attr.PublicAttributeName == member).PropertyInfo; - var deserializedValue = pi.GetValue(entity); - - if (member == "int-field") - { - Assert.Equal(deserializedValue, 1); - } - else if (member == "nullable-int-field" && value == null) - { - Assert.Equal(deserializedValue, null); - } - else if (member == "nullable-int-field" && (string)value == "1") - { - Assert.Equal(deserializedValue, 1); - } - else if (member == "guid-field") - { - Assert.Equal(deserializedValue, Guid.Parse("1a68be43-cc84-4924-a421-7f4d614b7781")); - } - else if (member == "date-time-field") - { - Assert.Equal(deserializedValue, DateTime.Parse("9/11/2019 11:41:40 AM")); - } else - { - Assert.Equal(value, deserializedValue); - } - } - - [Fact] - public void DeserializeAttributes_ComplexType_CanDeserialize() - { - // arrange - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { "complex-field", new Dictionary { {"compound-name", "testName" } } } - } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = (TestResource)_deserializer.Deserialize(body); - - // assert - Assert.NotNull(result.ComplexField); - Assert.Equal("testName", result.ComplexField.CompoundName); - } - - [Fact] - public void DeserializeAttributes_ComplexListType_CanDeserialize() - { - // arrange - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource-with-list", - Id = "1", - Attributes = new Dictionary - { - { "complex-fields", new [] { new Dictionary { {"compound-name", "testName" } } } } - } - } - }; - var body = JsonConvert.SerializeObject(content); - - - // act - var result = (TestResourceWithList)_deserializer.Deserialize(body); - - // assert - Assert.NotNull(result.ComplexFields); - Assert.NotEmpty(result.ComplexFields); - Assert.Equal("testName", result.ComplexFields[0].CompoundName); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToOneDependent_NavigationPropertyIsNull() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToOnePrincipal)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Dependent); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_PopulatedOneToOneDependent_NavigationPropertyIsPopulated() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-principals", "dependent", "one-to-one-dependents"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToOnePrincipal)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Equal(10, result.Dependent.Id); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToOnePrincipal_NavigationPropertyAndForeignKeyAreNull() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToOneDependent)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Principal); - Assert.Null(result.PrincipalId); - } - - [Fact] - public void DeserializeRelationships_EmptyRequiredOneToOnePrincipal_ThrowsFormatException() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-required-dependents", "principal"); - var body = JsonConvert.SerializeObject(content); - - // act, assert - Assert.ThrowsAny(() => _deserializer.Deserialize(body)); - } - - [Fact] - public void DeserializeRelationships_PopulatedOneToOnePrincipal_NavigationPropertyIsNullAndForeignKeyIsPopulated() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-one-dependents", "principal", "one-to-one-principals"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToOneDependent)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Principal); - Assert.Equal(10, result.PrincipalId); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToManyPrincipal_NavigationAndForeignKeyAreNull() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToManyDependent)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Principal); - Assert.Null(result.PrincipalId); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToManyRequiredPrincipal_ThrowsFormatException() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-required-dependents", "principal"); - var body = JsonConvert.SerializeObject(content); - - // act, assert - Assert.ThrowsAny(() => _deserializer.Deserialize(body)); - } - - [Fact] - public void DeserializeRelationships_PopulatedOneToManyPrincipal_NavigationIsNullAndForeignKeyIsPopulated() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-dependents", "principal", "one-to-many-principals"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToManyDependent)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Principal); - Assert.Equal(10, result.PrincipalId); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_EmptyOneToManyDependent_NavigationIsNull() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents"); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToManyPrincipal)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Null(result.Dependents); - Assert.Null(result.AttributeMember); - } - - [Fact] - public void DeserializeRelationships_PopulatedOneToManyDependent_NavigationIsPopulated() - { - // arrange - var content = CreateDocumentWithRelationships("one-to-many-principals", "dependents", "one-to-many-dependents", isToManyData: true); - var body = JsonConvert.SerializeObject(content); - - // act - var result = (OneToManyPrincipal)_deserializer.Deserialize(body); - - // assert - Assert.Equal(1, result.Id); - Assert.Equal(1, result.Dependents.Count); - Assert.Equal(10, result.Dependents.First().Id); - Assert.Null(result.AttributeMember); - } - } -} diff --git a/test/UnitTests/Serialization (copy)/ClientDeserializerTests.cs b/test/UnitTests/Serialization (copy)/ClientDeserializerTests.cs deleted file mode 100644 index 315b932b11..0000000000 --- a/test/UnitTests/Serialization (copy)/ClientDeserializerTests.cs +++ /dev/null @@ -1,333 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using Newtonsoft.Json; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class ClientDeserializerTests : DeserializerTestsSetup - { - private readonly Dictionary _linkValues = new Dictionary(); - private readonly ClientDeserializer _deserializer; - - public ClientDeserializerTests() - { - _deserializer = new ClientDeserializer(_resourceGraph, _defaultSettings); - _linkValues.Add("self", "http://example.com/articles"); - _linkValues.Add("next", "http://example.com/articles?page[offset]=2"); - _linkValues.Add("last", "http://example.com/articles?page[offset]=10"); - } - - [Fact] - public void DeserializeSingle_EmptyResponseWithMeta_CanDeserialize() - { - // arrange - var content = new Document - { - Meta = new Dictionary { { "foo", "bar" } } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - - // assert - Assert.Null(result.Data); - Assert.NotNull(result.Meta); - Assert.Equal("bar", result.Meta["foo"]); - } - - [Fact] - public void DeserializeSingle_EmptyResponseWithTopLevelLinks_CanDeserialize() - { - // arrange - var content = new Document - { - Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - - // assert - Assert.Null(result.Data); - Assert.NotNull(result.Links); - Assert.Equal(_linkValues["self"], result.Links.Self); - Assert.Equal(_linkValues["next"], result.Links.Next); - Assert.Equal(_linkValues["last"], result.Links.Last); - } - - [Fact] - public void DeserializeList_EmptyResponseWithTopLevelLinks_CanDeserialize() - { - // arrange - var content = new Documents - { - Links = new RootLinks { Self = _linkValues["self"], Next = _linkValues["next"], Last = _linkValues["last"] }, - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeList(body); - - // assert - Assert.Empty(result.Data); - Assert.NotNull(result.Links); - Assert.Equal(_linkValues["self"], result.Links.Self); - Assert.Equal(_linkValues["next"], result.Links.Next); - Assert.Equal(_linkValues["last"], result.Links.Last); - } - - [Fact] - public void DeserializeSingle_ResourceWithAttributes_CanDeserialize() - { - // arrange - var content = CreateTestResourceDocument(); - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Null(result.Links); - Assert.Null(result.Meta); - Assert.Equal(1, entity.Id); - Assert.Equal(content.Data.Attributes["string-field"], entity.StringField); - } - - [Fact] - public void DeserializeSingle_MultipleDependentRelationshipsWithIncluded_CanDeserialize() - { - // arrange - var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); - content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); - var toOneAttributeValue = "populated-to-one member content"; - var toManyAttributeValue = "populated-to-manies member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "one-to-one-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } - }, - new ResourceObject() - { - Type = "one-to-many-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Equal(1, entity.Id); - Assert.NotNull(entity.PopulatedToOne); - Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); - Assert.Equal(toManyAttributeValue, entity.PopulatedToManies.First().AttributeMember); - Assert.NotNull(entity.PopulatedToManies); - Assert.NotNull(entity.EmptyToManies); - Assert.Empty(entity.EmptyToManies); - Assert.Null(entity.EmptyToOne); - } - - [Fact] - public void DeserializeSingle_MultiplePrincipalRelationshipsWithIncluded_CanDeserialize() - { - // arrange - var content = CreateDocumentWithRelationships("multi-dependents"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); - content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); - var toOneAttributeValue = "populated-to-one member content"; - var toManyAttributeValue = "populated-to-manies member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "one-to-one-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toOneAttributeValue } } - }, - new ResourceObject() - { - Type = "one-to-many-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Equal(1, entity.Id); - Assert.NotNull(entity.PopulatedToOne); - Assert.Equal(toOneAttributeValue, entity.PopulatedToOne.AttributeMember); - Assert.Equal(toManyAttributeValue, entity.PopulatedToMany.AttributeMember); - Assert.NotNull(entity.PopulatedToMany); - Assert.Null(entity.EmptyToMany); - Assert.Null(entity.EmptyToOne); - } - - [Fact] - public void DeserializeSingle_NestedIncluded_CanDeserialize() - { - // arrange - var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); - var toManyAttributeValue = "populated-to-manies member content"; - var nestedIncludeAttributeValue = "nested include member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "one-to-many-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", toManyAttributeValue } }, - Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } - }, - new ResourceObject() - { - Type = "one-to-many-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", nestedIncludeAttributeValue } } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Equal(1, entity.Id); - Assert.Null(entity.PopulatedToOne); - Assert.Null(entity.EmptyToManies); - Assert.Null(entity.EmptyToOne); - Assert.NotNull(entity.PopulatedToManies); - var includedEntity = entity.PopulatedToManies.First(); - Assert.Equal(toManyAttributeValue, includedEntity.AttributeMember); - var nestedIncludedEntity = includedEntity.Principal; - Assert.Equal(nestedIncludeAttributeValue, nestedIncludedEntity.AttributeMember); - } - - - [Fact] - public void DeserializeSingle_DeeplyNestedIncluded_CanDeserialize() - { - // arrange - var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("multi", CreateRelationshipData("multi-principals")); - var includedAttributeValue = "multi member content"; - var nestedIncludedAttributeValue = "nested include member content"; - var deeplyNestedIncludedAttributeValue = "deeply nested member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "multi-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, - Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } - }, - new ResourceObject() - { - Type = "one-to-many-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, - Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } - }, - new ResourceObject() - { - Type = "one-to-many-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } - }, - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeSingle(body); - var entity = result.Data; - - // assert - Assert.Equal(1, entity.Id); - var included = entity.Multi; - Assert.Equal(10, included.Id); - Assert.Equal(includedAttributeValue, included.AttributeMember); - var nestedIncluded = included.PopulatedToManies.First(); - Assert.Equal(10, nestedIncluded.Id); - Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); - var deeplyNestedIncluded = nestedIncluded.Principal; - Assert.Equal(10, deeplyNestedIncluded.Id); - Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); - } - - - [Fact] - public void DeserializeList_DeeplyNestedIncluded_CanDeserialize() - { - // arrange - var content = new Documents { Data = new List { CreateDocumentWithRelationships("multi-principals").Data } }; - content.Data[0].Relationships.Add("multi", CreateRelationshipData("multi-principals")); - var includedAttributeValue = "multi member content"; - var nestedIncludedAttributeValue = "nested include member content"; - var deeplyNestedIncludedAttributeValue = "deeply nested member content"; - content.Included = new List() - { - new ResourceObject() - { - Type = "multi-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", includedAttributeValue } }, - Relationships = new Dictionary { { "populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true) } } - }, - new ResourceObject() - { - Type = "one-to-many-dependents", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", nestedIncludedAttributeValue } }, - Relationships = new Dictionary { { "principal", CreateRelationshipData("one-to-many-principals") } } - }, - new ResourceObject() - { - Type = "one-to-many-principals", - Id = "10", - Attributes = new Dictionary() { {"attribute-member", deeplyNestedIncludedAttributeValue } } - }, - }; - var body = JsonConvert.SerializeObject(content); - - // act - var result = _deserializer.DeserializeList(body); - var entity = result.Data.First(); - - // assert - Assert.Equal(1, entity.Id); - var included = entity.Multi; - Assert.Equal(10, included.Id); - Assert.Equal(includedAttributeValue, included.AttributeMember); - var nestedIncluded = included.PopulatedToManies.First(); - Assert.Equal(10, nestedIncluded.Id); - Assert.Equal(nestedIncludedAttributeValue, nestedIncluded.AttributeMember); - var deeplyNestedIncluded = nestedIncluded.Principal; - Assert.Equal(10, deeplyNestedIncluded.Id); - Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); - } - } -} diff --git a/test/UnitTests/Serialization (copy)/DasherizedResolverTests.cs b/test/UnitTests/Serialization (copy)/DasherizedResolverTests.cs deleted file mode 100644 index ca746bfb91..0000000000 --- a/test/UnitTests/Serialization (copy)/DasherizedResolverTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Serialization.Contracts; - -using Newtonsoft.Json; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class DasherizedResolverTests - { - [Fact] - public void Resolver_Dasherizes_Property_Names() - { - // arrange - var obj = new - { - myProp = "val" - }; - - // act - var result = JsonConvert.SerializeObject(obj, - Formatting.None, - new JsonSerializerSettings { ContractResolver = new DasherizedResolver() } - ); - - // assert - Assert.Equal("{\"my-prop\":\"val\"}", result); - } - } -} diff --git a/test/UnitTests/Serialization (copy)/DeserializerTestsSetup.cs b/test/UnitTests/Serialization (copy)/DeserializerTestsSetup.cs deleted file mode 100644 index 3c37ad52b3..0000000000 --- a/test/UnitTests/Serialization (copy)/DeserializerTestsSetup.cs +++ /dev/null @@ -1,172 +0,0 @@ -using JsonApiDotNetCore.Models; -using System.Collections.Generic; -using System; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Serialization; - -namespace UnitTests.Deserialization -{ - public class DeserializerTestsSetup - { - protected readonly IResourceGraph _resourceGraph; - protected readonly JsonApiSerializerSettings _defaultSettings = new JsonApiSerializerSettings(); - - public DeserializerTestsSetup() - { - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - resourceGraphBuilder.AddResource("test-resource-with-list"); - // one to one relationships - resourceGraphBuilder.AddResource("one-to-one-principals"); - resourceGraphBuilder.AddResource("one-to-one-dependents"); - resourceGraphBuilder.AddResource("one-to-one-required-dependents"); - // one to many relationships - resourceGraphBuilder.AddResource("one-to-many-principals"); - resourceGraphBuilder.AddResource("one-to-many-dependents"); - resourceGraphBuilder.AddResource("one-to-many-required-dependents"); - // collective relationships - resourceGraphBuilder.AddResource("multi-principals"); - resourceGraphBuilder.AddResource("multi-dependents"); - _resourceGraph = resourceGraphBuilder.Build(); - } - - protected Document CreateDocumentWithRelationships(string mainType, string relationshipMemberName, string relatedType = null, bool isToManyData = false) - { - var content = CreateDocumentWithRelationships(mainType); - content.Data.Relationships.Add(relationshipMemberName, CreateRelationshipData(relatedType, isToManyData)); - return content; - } - - protected Document CreateDocumentWithRelationships(string mainType) - { - return new Document - { - Data = new ResourceObject - { - Id = "1", - Type = mainType, - Relationships = new Dictionary { } - } - }; - } - - protected RelationshipData CreateRelationshipData(string relatedType = null, bool isToManyData = false) - { - var data = new RelationshipData(); - var rio = relatedType == null ? null : new ResourceIdentifierObject { Id = "10", Type = relatedType }; - - if (isToManyData) - { - data.ExposedData = new List(); - if (relatedType != null) ((List)data.ExposedData).Add(rio); - } else - { - data.ExposedData = rio; - } - return data; - } - - protected Document CreateTestResourceDocument() - { - return new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { "string-field", "some string" }, - { "int-field", 1 }, - { "nullable-int-field", null }, - { "guid-field", "1a68be43-cc84-4924-a421-7f4d614b7781" }, - { "date-time-field", "9/11/2019 11:41:40 AM" } - } - } - }; - } - - protected class TestResource : Identifiable - { - [Attr] public string StringField { get; set; } - [Attr] public DateTime DateTimeField { get; set; } - [Attr] public DateTime? NullableDateTimeField { get; set; } - [Attr] public int IntField { get; set; } - [Attr] public int? NullableIntField { get; set; } - [Attr] public Guid GuidField { get; set; } - [Attr] public ComplexType ComplexField { get; set; } - [Attr(isImmutable: true)] public string Immutable { get; set; } - } - - protected class TestResourceWithList : Identifiable - { - [Attr] public List ComplexFields { get; set; } - } - - protected class ComplexType - { - public string CompoundName { get; set; } - } - - protected class OneToOnePrincipal : IdentifiableWithAttribute - { - [HasOne] public OneToOneDependent Dependent { get; set; } - } - - protected class OneToOneDependent : IdentifiableWithAttribute - { - [HasOne] public OneToOnePrincipal Principal { get; set; } - public int? PrincipalId { get; set; } - } - - protected class OneToOneRequiredDependent : IdentifiableWithAttribute - { - [HasOne] public OneToOnePrincipal Principal { get; set; } - public int PrincipalId { get; set; } - } - - protected class OneToManyDependent : IdentifiableWithAttribute - { - [HasOne] public OneToManyPrincipal Principal { get; set; } - public int? PrincipalId { get; set; } - } - - protected class OneToManyRequiredDependent : IdentifiableWithAttribute - { - [HasOne] public OneToManyPrincipal Principal { get; set; } - public int PrincipalId { get; set; } - } - - protected class OneToManyPrincipal : IdentifiableWithAttribute - { - [HasMany] public List Dependents { get; set; } - } - - protected class IdentifiableWithAttribute : Identifiable - { - [Attr] public string AttributeMember { get; set; } - } - - protected class MultipleRelationshipsPrincipalPart : IdentifiableWithAttribute - { - [HasOne] public OneToOneDependent PopulatedToOne { get; set; } - [HasOne] public OneToOneDependent EmptyToOne { get; set; } - [HasMany] public List PopulatedToManies { get; set; } - [HasMany] public List EmptyToManies { get; set; } - [HasOne] public MultipleRelationshipsPrincipalPart Multi { get; set; } - } - - protected class MultipleRelationshipsDependentPart : IdentifiableWithAttribute - { - [HasOne] public OneToOnePrincipal PopulatedToOne { get; set; } - public int PopulatedToOneId { get; set; } - [HasOne] public OneToOnePrincipal EmptyToOne { get; set; } - public int? EmptyToOneId { get; set; } - [HasOne] public OneToManyPrincipal PopulatedToMany { get; set; } - public int PopulatedToManyId { get; set; } - [HasOne] public OneToManyPrincipal EmptyToMany { get; set; } - public int? EmptyToManyId { get; set; } - } - } -} diff --git a/test/UnitTests/Serialization (copy)/JsonApiSerializerTests.cs b/test/UnitTests/Serialization (copy)/JsonApiSerializerTests.cs deleted file mode 100644 index 6a1dcb602e..0000000000 --- a/test/UnitTests/Serialization (copy)/JsonApiSerializerTests.cs +++ /dev/null @@ -1,285 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Extensions; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Request; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Serialization.Contracts; - -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class JsonApiSerializerTests - { - [Fact] - public void Can_Serialize_Complex_Types() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - - var serializer = GetSerializer(resourceGraphBuilder); - - var resource = new TestResource - { - ComplexMember = new ComplexType - { - CompoundName = "testname" - } - }; - - // act - var result = serializer.Serialize(resource); - - // assert - Assert.NotNull(result); - - var expectedFormatted = - @"{ - ""data"": { - ""attributes"": { - ""complex-member"": { - ""compound-name"": ""testname"" - } - }, - ""relationships"": { - ""children"": { - ""links"": { - ""self"": ""/test-resource//relationships/children"", - ""related"": ""/test-resource//children"" - } - } - }, - ""type"": ""test-resource"", - ""id"": """" - } - }"; - var expected = Regex.Replace(expectedFormatted, @"\s+", ""); - - Assert.Equal(expected, result); - } - - [Fact] - public void Can_Serialize_Deeply_Nested_Relationships() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - resourceGraphBuilder.AddResource("children"); - resourceGraphBuilder.AddResource("infections"); - - var serializer = GetSerializer( - resourceGraphBuilder, - new List { "children.infections" } - ); - - var resource = new TestResource - { - Id = 1, - Children = new List { - new ChildResource { - Id = 2, - Infections = new List { - new InfectionResource { Id = 4 }, - new InfectionResource { Id = 5 }, - } - }, - new ChildResource { - Id = 3 - } - } - }; - - // act - var result = serializer.Serialize(resource); - - // assert - Assert.NotNull(result); - - var expectedFormatted = - @"{ - ""data"": { - ""attributes"": { - ""complex-member"": null - }, - ""relationships"": { - ""children"": { - ""links"": { - ""self"": ""/test-resource/1/relationships/children"", - ""related"": ""/test-resource/1/children"" - }, - ""data"": [{ - ""type"": ""children"", - ""id"": ""2"" - }, { - ""type"": ""children"", - ""id"": ""3"" - }] - } - }, - ""type"": ""test-resource"", - ""id"": ""1"" - }, - ""included"": [ - { - ""attributes"": {}, - ""relationships"": { - ""infections"": { - ""links"": { - ""self"": ""/children/2/relationships/infections"", - ""related"": ""/children/2/infections"" - }, - ""data"": [{ - ""type"": ""infections"", - ""id"": ""4"" - }, { - ""type"": ""infections"", - ""id"": ""5"" - }] - }, - ""parent"": { - ""links"": { - ""self"": ""/children/2/relationships/parent"", - ""related"": ""/children/2/parent"" - } - } - }, - ""type"": ""children"", - ""id"": ""2"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infected"": { - ""links"": { - ""self"": ""/infections/4/relationships/infected"", - ""related"": ""/infections/4/infected"" - } - } - }, - ""type"": ""infections"", - ""id"": ""4"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infected"": { - ""links"": { - ""self"": ""/infections/5/relationships/infected"", - ""related"": ""/infections/5/infected"" - } - } - }, - ""type"": ""infections"", - ""id"": ""5"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infections"": { - ""links"": { - ""self"": ""/children/3/relationships/infections"", - ""related"": ""/children/3/infections"" - } - }, - ""parent"": { - ""links"": { - ""self"": ""/children/3/relationships/parent"", - ""related"": ""/children/3/parent"" - } - } - }, - ""type"": ""children"", - ""id"": ""3"" - } - ] - }"; - var expected = Regex.Replace(expectedFormatted, @"\s+", ""); - - Assert.Equal(expected, result); - } - - private JsonApiSerializer GetSerializer( - ResourceGraphBuilder resourceGraphBuilder, - List included = null) - { - var resourceGraph = resourceGraphBuilder.Build(); - var requestManagerMock = new Mock(); - requestManagerMock.Setup(m => m.GetContextEntity()).Returns(resourceGraph.GetContextEntity("test-resource")); - requestManagerMock.Setup(m => m.IncludedRelationships).Returns(included); - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); - jsonApiContextMock.Setup(m => m.RequestEntity).Returns(resourceGraph.GetContextEntity("test-resource")); - jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); - - - jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); - var pmMock = new Mock(); - jsonApiContextMock.Setup(m => m.PageManager).Returns(pmMock.Object); - - - - var jsonApiOptions = new JsonApiOptions(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var services = new ServiceCollection(); - - var mvcBuilder = services.AddMvcCore(); - - services - .AddJsonApiInternals(jsonApiOptions); - - var provider = services.BuildServiceProvider(); - var scoped = new TestScopedServiceProvider(provider); - - var documentBuilder = GetDocumentBuilder(jsonApiContextMock, requestManagerMock.Object, scopedServiceProvider: scoped); - var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); - - return serializer; - } - - private class TestResource : Identifiable - { - [Attr("complex-member")] - public ComplexType ComplexMember { get; set; } - - [HasMany("children")] public List Children { get; set; } - } - - private class ComplexType - { - public string CompoundName { get; set; } - } - - private class ChildResource : Identifiable - { - [HasMany("infections")] public List Infections { get; set; } - - [HasOne("parent")] public TestResource Parent { get; set; } - } - - private class InfectionResource : Identifiable - { - [HasOne("infected")] public ChildResource Infected { get; set; } - } - - private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, IRequestManager requestManager, TestScopedServiceProvider scopedServiceProvider = null) - { - var pageManagerMock = new Mock(); - - return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, requestManager, scopedServiceProvider: scopedServiceProvider); - - } - } -} diff --git a/test/UnitTests/Serialization (copy)/ServerDeserializerTests.cs b/test/UnitTests/Serialization (copy)/ServerDeserializerTests.cs deleted file mode 100644 index 9d97bdf3e3..0000000000 --- a/test/UnitTests/Serialization (copy)/ServerDeserializerTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using Moq; -using Newtonsoft.Json; -using Xunit; - -namespace UnitTests.Deserialization -{ - public class ServerDeserializerTests : DeserializerTestsSetup - { - private readonly ServerDeserializer _deserializer; - private readonly Mock _fieldsManagerMock = new Mock(); - public ServerDeserializerTests() : base() - { - _deserializer = new ServerDeserializer(_resourceGraph, _defaultSettings, _fieldsManagerMock.Object); - } - - [Fact] - public void DeserializeAttributes_VariousUpdatedMembers_RegistersUpdatedFields() - { - // arrange - SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); - Document content = CreateTestResourceDocument(); - var body = JsonConvert.SerializeObject(content); - - // act - _deserializer.Deserialize(body); - - // assert - Assert.Equal(5, attributesToUpdate.Count); - Assert.Empty(relationshipsToUpdate); - } - - [Fact] - public void DeserializeAttributes_UpdatedImmutableMember_ThrowsInvalidOperationException() - { - // arrange - SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); - var content = new Document - { - Data = new ResourceObject - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary - { - { "immutable", "some string" }, - } - } - }; - var body = JsonConvert.SerializeObject(content); - - // act, assert - Assert.Throws(() => _deserializer.Deserialize(body)); - } - - [Fact] - public void DeserializeRelationships_MultipleDependentRelationships_RegistersUpdatedRelationships() - { - // arrange - SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); - var content = CreateDocumentWithRelationships("multi-principals"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-dependents")); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("populated-to-manies", CreateRelationshipData("one-to-many-dependents", isToManyData: true)); - content.Data.Relationships.Add("empty-to-manies", CreateRelationshipData(isToManyData: true)); - var body = JsonConvert.SerializeObject(content); - - // act - _deserializer.Deserialize(body); - - // assert - Assert.Equal(4, relationshipsToUpdate.Count); - Assert.Empty(attributesToUpdate); - } - - [Fact] - public void DeserializeRelationships_MultiplePrincipalRelationships_RegistersUpdatedRelationships() - { - // arrange - SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate); - var content = CreateDocumentWithRelationships("multi-dependents"); - content.Data.Relationships.Add("populated-to-one", CreateRelationshipData("one-to-one-principals")); - content.Data.Relationships.Add("empty-to-one", CreateRelationshipData()); - content.Data.Relationships.Add("populated-to-many", CreateRelationshipData("one-to-many-principals")); - content.Data.Relationships.Add("empty-to-many", CreateRelationshipData()); - var body = JsonConvert.SerializeObject(content); - - // act - _deserializer.Deserialize(body); - - // assert - Assert.Equal(4, relationshipsToUpdate.Count); - Assert.Empty(attributesToUpdate); - } - - private void SetupFieldsManager(out List attributesToUpdate, out List relationshipsToUpdate) - { - attributesToUpdate = new List(); - relationshipsToUpdate = new List(); - _fieldsManagerMock.Setup(m => m.AttributesToUpdate).Returns(attributesToUpdate); - _fieldsManagerMock.Setup(m => m.RelationshipsToUpdate).Returns(relationshipsToUpdate); - } - } -} diff --git a/test/UnitTests/Serialization/SerializerBaseTests.cs b/test/UnitTests/Serialization/SerializerBaseTests.cs deleted file mode 100644 index d301caf1fa..0000000000 --- a/test/UnitTests/Serialization/SerializerBaseTests.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Extensions; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Request; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Serialization.Contracts; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Xunit; - -namespace UnitTests.Serialization -{ - public class JsonApiSerializerTests - { - [Fact] - public void Can_Serialize_Complex_Types() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - - var serializer = GetSerializer(resourceGraphBuilder); - - var resource = new TestResource - { - ComplexMember = new ComplexType - { - CompoundName = "testname" - } - }; - - // act - var result = serializer.Serialize(resource); - - // assert - Assert.NotNull(result); - - var expectedFormatted = - @"{ - ""data"": { - ""attributes"": { - ""complex-member"": { - ""compound-name"": ""testname"" - } - }, - ""relationships"": { - ""children"": { - ""links"": { - ""self"": ""/test-resource//relationships/children"", - ""related"": ""/test-resource//children"" - } - } - }, - ""type"": ""test-resource"", - ""id"": """" - } - }"; - var expected = Regex.Replace(expectedFormatted, @"\s+", ""); - - Assert.Equal(expected, result); - } - - [Fact] - public void Can_Serialize_Deeply_Nested_Relationships() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - resourceGraphBuilder.AddResource("children"); - resourceGraphBuilder.AddResource("infections"); - - var serializer = GetSerializer( - resourceGraphBuilder, - new List { "children.infections" } - ); - - var resource = new TestResource - { - Id = 1, - Children = new List { - new ChildResource { - Id = 2, - Infections = new List { - new InfectionResource { Id = 4 }, - new InfectionResource { Id = 5 }, - } - }, - new ChildResource { - Id = 3 - } - } - }; - - // act - var result = serializer.Serialize(resource); - - // assert - Assert.NotNull(result); - - var expectedFormatted = - @"{ - ""data"": { - ""attributes"": { - ""complex-member"": null - }, - ""relationships"": { - ""children"": { - ""links"": { - ""self"": ""/test-resource/1/relationships/children"", - ""related"": ""/test-resource/1/children"" - }, - ""data"": [{ - ""type"": ""children"", - ""id"": ""2"" - }, { - ""type"": ""children"", - ""id"": ""3"" - }] - } - }, - ""type"": ""test-resource"", - ""id"": ""1"" - }, - ""included"": [ - { - ""attributes"": {}, - ""relationships"": { - ""infections"": { - ""links"": { - ""self"": ""/children/2/relationships/infections"", - ""related"": ""/children/2/infections"" - }, - ""data"": [{ - ""type"": ""infections"", - ""id"": ""4"" - }, { - ""type"": ""infections"", - ""id"": ""5"" - }] - }, - ""parent"": { - ""links"": { - ""self"": ""/children/2/relationships/parent"", - ""related"": ""/children/2/parent"" - } - } - }, - ""type"": ""children"", - ""id"": ""2"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infected"": { - ""links"": { - ""self"": ""/infections/4/relationships/infected"", - ""related"": ""/infections/4/infected"" - } - } - }, - ""type"": ""infections"", - ""id"": ""4"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infected"": { - ""links"": { - ""self"": ""/infections/5/relationships/infected"", - ""related"": ""/infections/5/infected"" - } - } - }, - ""type"": ""infections"", - ""id"": ""5"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infections"": { - ""links"": { - ""self"": ""/children/3/relationships/infections"", - ""related"": ""/children/3/infections"" - } - }, - ""parent"": { - ""links"": { - ""self"": ""/children/3/relationships/parent"", - ""related"": ""/children/3/parent"" - } - } - }, - ""type"": ""children"", - ""id"": ""3"" - } - ] - }"; - var expected = Regex.Replace(expectedFormatted, @"\s+", ""); - - Assert.Equal(expected, result); - } - - private JsonApiSerializer GetSerializer( - ResourceGraphBuilder resourceGraphBuilder, - List included = null) - { - var resourceGraph = resourceGraphBuilder.Build(); - var requestManagerMock = new Mock(); - requestManagerMock.Setup(m => m.GetRequestResource()).Returns(resourceGraph.GetContextEntity("test-resource")); - requestManagerMock.Setup(m => m.IncludedRelationships).Returns(included); - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); - jsonApiContextMock.Setup(m => m.RequestEntity).Returns(resourceGraph.GetContextEntity("test-resource")); - jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); - - - jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); - var pmMock = new Mock(); - jsonApiContextMock.Setup(m => m.PageManager).Returns(pmMock.Object); - - - - var jsonApiOptions = new JsonApiOptions(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var services = new ServiceCollection(); - - var mvcBuilder = services.AddMvcCore(); - - services - .AddJsonApiInternals(jsonApiOptions); - - var provider = services.BuildServiceProvider(); - var scoped = new TestScopedServiceProvider(provider); - - var documentBuilder = GetDocumentBuilder(jsonApiContextMock, requestManagerMock.Object, scopedServiceProvider: scoped); - var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); - - return serializer; - } - - private class TestResource : Identifiable - { - [Attr("complex-member")] - public ComplexType ComplexMember { get; set; } - - [HasMany("children")] public List Children { get; set; } - } - - private class ComplexType - { - public string CompoundName { get; set; } - } - - private class ChildResource : Identifiable - { - [HasMany("infections")] public List Infections { get; set; } - - [HasOne("parent")] public TestResource Parent { get; set; } - } - - private class InfectionResource : Identifiable - { - [HasOne("infected")] public ChildResource Infected { get; set; } - } - - private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, IRequestManager requestManager, TestScopedServiceProvider scopedServiceProvider = null) - { - var pageManagerMock = new Mock(); - - return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, requestManager, scopedServiceProvider: scopedServiceProvider); - - } - } -} diff --git a/test/UnitTests/Serialization/SerializerTestsSetup.cs b/test/UnitTests/Serialization/SerializerTestsSetup.cs deleted file mode 100644 index b1d3a9a5e9..0000000000 --- a/test/UnitTests/Serialization/SerializerTestsSetup.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace UnitTests.Serialization -{ - public class SerializerTestsSetup - { - } -} \ No newline at end of file diff --git a/test/UnitTests/Serializer/JsonApiSerializerTests.cs b/test/UnitTests/Serializer/JsonApiSerializerTests.cs deleted file mode 100644 index d301caf1fa..0000000000 --- a/test/UnitTests/Serializer/JsonApiSerializerTests.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Extensions; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Request; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Serialization.Contracts; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using Xunit; - -namespace UnitTests.Serialization -{ - public class JsonApiSerializerTests - { - [Fact] - public void Can_Serialize_Complex_Types() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - - var serializer = GetSerializer(resourceGraphBuilder); - - var resource = new TestResource - { - ComplexMember = new ComplexType - { - CompoundName = "testname" - } - }; - - // act - var result = serializer.Serialize(resource); - - // assert - Assert.NotNull(result); - - var expectedFormatted = - @"{ - ""data"": { - ""attributes"": { - ""complex-member"": { - ""compound-name"": ""testname"" - } - }, - ""relationships"": { - ""children"": { - ""links"": { - ""self"": ""/test-resource//relationships/children"", - ""related"": ""/test-resource//children"" - } - } - }, - ""type"": ""test-resource"", - ""id"": """" - } - }"; - var expected = Regex.Replace(expectedFormatted, @"\s+", ""); - - Assert.Equal(expected, result); - } - - [Fact] - public void Can_Serialize_Deeply_Nested_Relationships() - { - // arrange - var resourceGraphBuilder = new ResourceGraphBuilder(); - resourceGraphBuilder.AddResource("test-resource"); - resourceGraphBuilder.AddResource("children"); - resourceGraphBuilder.AddResource("infections"); - - var serializer = GetSerializer( - resourceGraphBuilder, - new List { "children.infections" } - ); - - var resource = new TestResource - { - Id = 1, - Children = new List { - new ChildResource { - Id = 2, - Infections = new List { - new InfectionResource { Id = 4 }, - new InfectionResource { Id = 5 }, - } - }, - new ChildResource { - Id = 3 - } - } - }; - - // act - var result = serializer.Serialize(resource); - - // assert - Assert.NotNull(result); - - var expectedFormatted = - @"{ - ""data"": { - ""attributes"": { - ""complex-member"": null - }, - ""relationships"": { - ""children"": { - ""links"": { - ""self"": ""/test-resource/1/relationships/children"", - ""related"": ""/test-resource/1/children"" - }, - ""data"": [{ - ""type"": ""children"", - ""id"": ""2"" - }, { - ""type"": ""children"", - ""id"": ""3"" - }] - } - }, - ""type"": ""test-resource"", - ""id"": ""1"" - }, - ""included"": [ - { - ""attributes"": {}, - ""relationships"": { - ""infections"": { - ""links"": { - ""self"": ""/children/2/relationships/infections"", - ""related"": ""/children/2/infections"" - }, - ""data"": [{ - ""type"": ""infections"", - ""id"": ""4"" - }, { - ""type"": ""infections"", - ""id"": ""5"" - }] - }, - ""parent"": { - ""links"": { - ""self"": ""/children/2/relationships/parent"", - ""related"": ""/children/2/parent"" - } - } - }, - ""type"": ""children"", - ""id"": ""2"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infected"": { - ""links"": { - ""self"": ""/infections/4/relationships/infected"", - ""related"": ""/infections/4/infected"" - } - } - }, - ""type"": ""infections"", - ""id"": ""4"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infected"": { - ""links"": { - ""self"": ""/infections/5/relationships/infected"", - ""related"": ""/infections/5/infected"" - } - } - }, - ""type"": ""infections"", - ""id"": ""5"" - }, - { - ""attributes"": {}, - ""relationships"": { - ""infections"": { - ""links"": { - ""self"": ""/children/3/relationships/infections"", - ""related"": ""/children/3/infections"" - } - }, - ""parent"": { - ""links"": { - ""self"": ""/children/3/relationships/parent"", - ""related"": ""/children/3/parent"" - } - } - }, - ""type"": ""children"", - ""id"": ""3"" - } - ] - }"; - var expected = Regex.Replace(expectedFormatted, @"\s+", ""); - - Assert.Equal(expected, result); - } - - private JsonApiSerializer GetSerializer( - ResourceGraphBuilder resourceGraphBuilder, - List included = null) - { - var resourceGraph = resourceGraphBuilder.Build(); - var requestManagerMock = new Mock(); - requestManagerMock.Setup(m => m.GetRequestResource()).Returns(resourceGraph.GetContextEntity("test-resource")); - requestManagerMock.Setup(m => m.IncludedRelationships).Returns(included); - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph); - jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); - jsonApiContextMock.Setup(m => m.RequestEntity).Returns(resourceGraph.GetContextEntity("test-resource")); - jsonApiContextMock.Setup(m => m.RequestManager).Returns(requestManagerMock.Object); - - - jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); - var pmMock = new Mock(); - jsonApiContextMock.Setup(m => m.PageManager).Returns(pmMock.Object); - - - - var jsonApiOptions = new JsonApiOptions(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var services = new ServiceCollection(); - - var mvcBuilder = services.AddMvcCore(); - - services - .AddJsonApiInternals(jsonApiOptions); - - var provider = services.BuildServiceProvider(); - var scoped = new TestScopedServiceProvider(provider); - - var documentBuilder = GetDocumentBuilder(jsonApiContextMock, requestManagerMock.Object, scopedServiceProvider: scoped); - var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); - - return serializer; - } - - private class TestResource : Identifiable - { - [Attr("complex-member")] - public ComplexType ComplexMember { get; set; } - - [HasMany("children")] public List Children { get; set; } - } - - private class ComplexType - { - public string CompoundName { get; set; } - } - - private class ChildResource : Identifiable - { - [HasMany("infections")] public List Infections { get; set; } - - [HasOne("parent")] public TestResource Parent { get; set; } - } - - private class InfectionResource : Identifiable - { - [HasOne("infected")] public ChildResource Infected { get; set; } - } - - private DocumentBuilder GetDocumentBuilder(Mock jaContextMock, IRequestManager requestManager, TestScopedServiceProvider scopedServiceProvider = null) - { - var pageManagerMock = new Mock(); - - return new DocumentBuilder(jaContextMock.Object, pageManagerMock.Object, requestManager, scopedServiceProvider: scopedServiceProvider); - - } - } -} diff --git a/test/UnitTests/Serializer/SerializerBaseTests.cs b/test/UnitTests/Serializer/SerializerBaseTests.cs deleted file mode 100644 index be3602344e..0000000000 --- a/test/UnitTests/Serializer/SerializerBaseTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using System.Text.RegularExpressions; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Extensions; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Models; -using JsonApiDotNetCore.Serialization; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using UnitTests.Deserialization; -using Xunit; - -namespace UnitTests.Serialization -{ - public class SerializerBaseTests : SerializationTestModels - { - - - - } -} diff --git a/test/UnitTests/Serializer/SerializerTestsSetup.cs b/test/UnitTests/Serializer/SerializerTestsSetup.cs deleted file mode 100644 index b1d3a9a5e9..0000000000 --- a/test/UnitTests/Serializer/SerializerTestsSetup.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace UnitTests.Serialization -{ - public class SerializerTestsSetup - { - } -} \ No newline at end of file diff --git a/test/UnitTests/Services/QueryParserTests.cs b/test/UnitTests/Services/QueryParserTests.cs index 63e04004d0..9e9476433f 100644 --- a/test/UnitTests/Services/QueryParserTests.cs +++ b/test/UnitTests/Services/QueryParserTests.cs @@ -3,6 +3,7 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Managers.Contracts; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; @@ -17,6 +18,9 @@ public class QueryParserTests { private readonly Mock _requestMock; private readonly Mock _queryCollectionMock; + private readonly IInternalFieldsQueryService _fieldsQuery = new Mock().Object; + private readonly IInternalIncludedQueryService _includedQuery = new Mock().Object; + private readonly IContextEntityProvider _graph = new Mock().Object; public QueryParserTests() { @@ -40,7 +44,7 @@ public void Can_Build_Filters() .Setup(m => m.DisabledQueryParams) .Returns(QueryParams.None); - var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -66,7 +70,7 @@ public void Filters_Properly_Parses_DateTime_With_Operation() .Setup(m => m.DisabledQueryParams) .Returns(QueryParams.None); - var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -93,7 +97,7 @@ public void Filters_Properly_Parses_DateTime_Without_Operation() .Setup(m => m.DisabledQueryParams) .Returns(QueryParams.None); - var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -119,7 +123,7 @@ public void Can_Disable_Filters() .Setup(m => m.DisabledQueryParams) .Returns(QueryParams.Filters); - var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // Act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -142,7 +146,7 @@ public void Parse_EmptySortSegment_ReceivesJsonApiException(string stringSortQue .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // Act / Assert var exception = Assert.Throws(() => @@ -167,7 +171,7 @@ public void Can_Disable_Sort() .Setup(m => m.DisabledQueryParams) .Returns(QueryParams.Sort); - var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -192,7 +196,7 @@ public void Can_Disable_Include() .Setup(m => m.DisabledQueryParams) .Returns(QueryParams.Include); - var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -217,7 +221,7 @@ public void Can_Disable_Page() .Setup(m => m.DisabledQueryParams) .Returns(QueryParams.Page); - var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -242,7 +246,7 @@ public void Can_Disable_Fields() .Setup(m => m.DisabledQueryParams) .Returns(QueryParams.Fields); - var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -266,7 +270,7 @@ public void Can_Parse_Fields_Query() .Returns(query.GetEnumerator()); _requestMock - .Setup(m => m.GetContextEntity()) + .Setup(m => m.GetRequestResource()) .Returns(new ContextEntity { EntityName = type, @@ -280,7 +284,7 @@ public void Can_Parse_Fields_Query() Relationships = new List() }); - var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object); @@ -306,7 +310,7 @@ public void Throws_JsonApiException_If_Field_DoesNotExist() .Returns(query.GetEnumerator()); _requestMock - .Setup(m => m.GetContextEntity()) + .Setup(m => m.GetRequestResource()) .Returns(new ContextEntity { EntityName = type, @@ -314,7 +318,7 @@ public void Throws_JsonApiException_If_Field_DoesNotExist() Relationships = new List() }); - var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // act , assert var ex = Assert.Throws(() => queryParser.Parse(_queryCollectionMock.Object)); @@ -336,7 +340,7 @@ public void Can_Parse_Page_Size_Query(string value, int expectedValue, bool shou .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // act if (shouldThrow) @@ -366,7 +370,7 @@ public void Can_Parse_Page_Number_Query(string value, int expectedValue, bool sh .Setup(m => m.GetEnumerator()) .Returns(query.GetEnumerator()); - var queryParser = new QueryParser(_requestMock.Object, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // act if (shouldThrow) diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index 120249c9ea..924aa5a434 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -22,4 +22,8 @@ PreserveNewest + + + + From 39742a7297ce44f89f72cd086e03537680cfac5a Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Thu, 26 Sep 2019 17:54:04 +0200 Subject: [PATCH 25/26] fix: rm dasherized resolver --- src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs | 3 +-- .../Serialization/Deserializer/OperationsDeserializer.cs | 6 ++++++ .../Serialization/JsonApiSerializerSettings.cs | 1 - 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index 05feb6fa69..d45bc0f494 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -205,8 +205,7 @@ public class JsonApiOptions : IJsonApiOptions public JsonSerializerSettings SerializerSettings { get; } = new JsonSerializerSettings() { - NullValueHandling = NullValueHandling.Ignore, - ContractResolver = new DasherizedResolver() + NullValueHandling = NullValueHandling.Ignore }; public void BuildResourceGraph(Action builder) where TContext : DbContext diff --git a/src/JsonApiDotNetCore/Serialization/Deserializer/OperationsDeserializer.cs b/src/JsonApiDotNetCore/Serialization/Deserializer/OperationsDeserializer.cs index 372afbf6fe..d280902b3e 100644 --- a/src/JsonApiDotNetCore/Serialization/Deserializer/OperationsDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Deserializer/OperationsDeserializer.cs @@ -295,4 +295,10 @@ private ResourceObject GetLinkedResource(ResourceIdentifierObject relatedResourc } } } + + public interface IOperationsDeserializer + { + object Deserialize(string body); + object DocumentToObject(ResourceObject data, List included = null); + } } \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializerSettings.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializerSettings.cs index feda1920c3..a0f56bb5f8 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiSerializerSettings.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiSerializerSettings.cs @@ -9,7 +9,6 @@ public JsonSerializerSettings GetSettings() return new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, - ContractResolver = new DasherizedResolver(), DateParseHandling = DateParseHandling.None }; } From 3721b7d3fa716bed06ba7d0e76339e2c7c0f9468 Mon Sep 17 00:00:00 2001 From: Maurits Moeys Date: Fri, 27 Sep 2019 12:41:26 +0200 Subject: [PATCH 26/26] chore: remove JsonApiContext and corresponding interface --- benchmarks/Query/QueryParser_Benchmarks.cs | 4 +- .../JsonApiDeserializer_Benchmarks.cs | 2 +- .../Services/CustomArticleService.cs | 2 +- .../DocumentBuilderOptionsProvider.cs | 6 +- .../Controllers/BaseJsonApiController.cs | 2 +- .../Controllers/JsonApiCmdController.cs | 5 +- .../Controllers/JsonApiController.cs | 17 --- .../Controllers/JsonApiQueryController.cs | 4 +- src/JsonApiDotNetCore/Data/Article.cs | 4 - .../Data/DefaultEntityRepository.cs | 87 ++++++------ .../IServiceCollectionExtensions.cs | 2 +- .../Formatters/JsonApiReader.cs | 6 +- .../Hooks/Execution/DiffableEntityHashSet.cs | 2 +- .../Hooks/Traversal/TraversalHelper.cs | 2 +- .../Internal/{ => Exceptions}/Error.cs | 0 .../{ => Exceptions}/ErrorCollection.cs | 0 .../Internal/{ => Exceptions}/Exceptions.cs | 0 .../{ => Exceptions}/JsonApiException.cs | 0 .../JsonApiExceptionFactory.cs | 0 .../{ => Exceptions}/JsonApiRouteHandler.cs | 0 .../{ => Exceptions}/JsonApiSetupException.cs | 0 .../Internal/Query/AttrFilterQuery.cs | 2 +- .../Internal/Query/AttrSortQuery.cs | 2 +- .../Internal/Query/BaseAttrQuery.cs | 4 +- .../Internal/Query/BaseFilterQuery.cs | 2 +- .../Internal/Query/RelatedAttrFilterQuery.cs | 2 +- .../JsonApiDotNetCore.csproj | 1 + .../Managers/IUpdatedFieldManager.cs | 18 --- .../Middleware/JsonApiActionFilter.cs | 8 +- .../Middleware/RequestMiddleware.cs | 4 +- .../JsonApi}/ResourceObjectComparer.cs | 0 .../Contracts/IRequestManager.cs | 12 +- .../Contracts/IResourceGraphManager.cs | 0 .../Contracts/IUpdatedFields.cs | 18 +++ .../RequestManager.cs | 3 +- .../SerializableFields.cs | 0 .../IIncludedRelationshipsBuilder.cs | 0 .../IJsonApiSerializer.cs | 0 .../IServerSerializerFactory.cs | 0 .../Deserializer/OperationsDeserializer.cs | 8 +- .../Deserializer/ServerDeserializer.cs | 4 +- .../Serialization/Serializer/LinkBuilder.cs | 4 +- .../Serializer/ServerSerializer.cs | 4 +- .../Services/ControllerContext.cs | 25 ---- .../Services/EntityResourceService.cs | 38 ++---- .../Services/IJsonApiContext.cs | 13 -- .../Services/JsonApiContext.cs | 126 ------------------ .../Operations/OperationsProcessor.cs | 4 +- .../Services/QueryAccessor.cs | 4 +- .../Services/QueryComposer.cs | 4 +- src/JsonApiDotNetCore/Services/QueryParser.cs | 14 +- .../ServiceDiscoveryFacadeTests.cs | 4 +- test/UnitTests/Builders/LinkBuilderTests.cs | 4 +- .../IServiceCollectionExtensionsTests.cs | 2 +- test/UnitTests/JsonApiContext/BasicTest.cs | 4 +- .../Update/BeforeUpdate_WithDbValues_Tests.cs | 2 +- .../Deserializer/ServerDeserializerTests.cs | 4 +- .../Serializer/SerializerTestsSetup.cs | 6 +- test/UnitTests/Services/QueryAccessorTests.cs | 4 +- test/UnitTests/Services/QueryComposerTests.cs | 6 +- test/UnitTests/Services/QueryParserTests.cs | 12 +- 61 files changed, 149 insertions(+), 368 deletions(-) delete mode 100644 src/JsonApiDotNetCore/Data/Article.cs rename src/JsonApiDotNetCore/Internal/{ => Exceptions}/Error.cs (100%) rename src/JsonApiDotNetCore/Internal/{ => Exceptions}/ErrorCollection.cs (100%) rename src/JsonApiDotNetCore/Internal/{ => Exceptions}/Exceptions.cs (100%) rename src/JsonApiDotNetCore/Internal/{ => Exceptions}/JsonApiException.cs (100%) rename src/JsonApiDotNetCore/Internal/{ => Exceptions}/JsonApiExceptionFactory.cs (100%) rename src/JsonApiDotNetCore/Internal/{ => Exceptions}/JsonApiRouteHandler.cs (100%) rename src/JsonApiDotNetCore/Internal/{ => Exceptions}/JsonApiSetupException.cs (100%) delete mode 100644 src/JsonApiDotNetCore/Managers/IUpdatedFieldManager.cs rename src/JsonApiDotNetCore/{Serialization/Serializer => Models/JsonApi}/ResourceObjectComparer.cs (100%) rename src/JsonApiDotNetCore/{Managers => RequestServices}/Contracts/IRequestManager.cs (77%) rename src/JsonApiDotNetCore/{Managers => RequestServices}/Contracts/IResourceGraphManager.cs (100%) create mode 100644 src/JsonApiDotNetCore/RequestServices/Contracts/IUpdatedFields.cs rename src/JsonApiDotNetCore/{Managers => RequestServices}/RequestManager.cs (97%) rename src/JsonApiDotNetCore/{Models => RequestServices}/SerializableFields.cs (100%) rename src/JsonApiDotNetCore/Serialization/{Serializer => Contracts}/IIncludedRelationshipsBuilder.cs (100%) rename src/JsonApiDotNetCore/Serialization/{Serializer => Contracts}/IJsonApiSerializer.cs (100%) rename src/JsonApiDotNetCore/Serialization/{Serializer => Contracts}/IServerSerializerFactory.cs (100%) delete mode 100644 src/JsonApiDotNetCore/Services/ControllerContext.cs delete mode 100644 src/JsonApiDotNetCore/Services/JsonApiContext.cs diff --git a/benchmarks/Query/QueryParser_Benchmarks.cs b/benchmarks/Query/QueryParser_Benchmarks.cs index 7550a27818..d3e5e9df5b 100644 --- a/benchmarks/Query/QueryParser_Benchmarks.cs +++ b/benchmarks/Query/QueryParser_Benchmarks.cs @@ -22,7 +22,7 @@ public class QueryParser_Benchmarks { private const string DESCENDING_SORT = "-" + ATTRIBUTE; public QueryParser_Benchmarks() { - var requestMock = new Mock(); + var requestMock = new Mock(); requestMock.Setup(m => m.GetRequestResource()).Returns(new ContextEntity { Attributes = new List { new AttrAttribute(ATTRIBUTE, ATTRIBUTE) @@ -59,7 +59,7 @@ private void Run(int iterations, Action action) { // this facade allows us to expose and micro-benchmark protected methods private class BenchmarkFacade : QueryParser { public BenchmarkFacade( - IRequestManager requestManager, + IRequestContext requestManager, JsonApiOptions options) : base(requestManager, options) { } public void _ParseSortParameters(string value) => base.ParseSortParameters(value); diff --git a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs index dbaf3eb826..4214f447ba 100644 --- a/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs +++ b/benchmarks/Serialization/JsonApiDeserializer_Benchmarks.cs @@ -38,7 +38,7 @@ public JsonApideserializer_Benchmarks() { var resourceGraphBuilder = new ResourceGraphBuilder(); resourceGraphBuilder.AddResource(TYPE_NAME); var resourceGraph = resourceGraphBuilder.Build(); - var requestManagerMock = new Mock(); + var requestManagerMock = new Mock(); requestManagerMock.Setup(m => m.GetUpdatedAttributes()).Returns(new Dictionary()); diff --git a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs index e5beaecc09..83bb43fd19 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Services/CustomArticleService.cs @@ -16,7 +16,7 @@ public class CustomArticleService : EntityResourceService
public CustomArticleService( IEntityRepository
repository, IJsonApiOptions jsonApiOptions, - IRequestManager queryManager, + IRequestContext queryManager, IPageQueryService pageManager, IResourceGraph resourceGraph, IResourceHookExecutor resourceHookExecutor = null, diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilderOptionsProvider.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilderOptionsProvider.cs index 22d2c339d7..ee929bf801 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilderOptionsProvider.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilderOptionsProvider.cs @@ -6,12 +6,8 @@ namespace JsonApiDotNetCore.Builders { public class DocumentBuilderOptionsProvider : IDocumentBuilderOptionsProvider { - private readonly IJsonApiContext _jsonApiContext; - private readonly IHttpContextAccessor _httpContextAccessor; - - public DocumentBuilderOptionsProvider(IJsonApiOptions options, IHttpContextAccessor httpContextAccessor) + public DocumentBuilderOptionsProvider(IJsonApiOptions options) { - _httpContextAccessor = httpContextAccessor; } public SerializerBehaviour GetDocumentBuilderOptions() diff --git a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs index 1194bca652..cb9eb09510 100644 --- a/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs @@ -27,7 +27,7 @@ public class BaseJsonApiController private readonly ILogger> _logger; private readonly IJsonApiOptions _jsonApiOptions; private readonly IResourceGraph _resourceGraph; - + public BaseJsonApiController( IJsonApiOptions jsonApiOptions, IResourceGraph resourceGraphManager, diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs index 3ee26db3c6..ebcad6bd13 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiCmdController.cs @@ -12,10 +12,8 @@ public class JsonApiCmdController : JsonApiCmdController { public JsonApiCmdController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiOptions, - jsonApiContext, resourceService) + : base(jsonApiOptions, resourceService) { } } @@ -24,7 +22,6 @@ public class JsonApiCmdController { public JsonApiCmdController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IResourceService resourceService) : base(jsonApiOptions, resourceService) { } diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index 3ab5375db8..91d78f3ac2 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -81,23 +81,6 @@ public override async Task PatchRelationshipsAsync( /// public class JsonApiController : JsonApiController where T : class, IIdentifiable { - private IJsonApiOptions jsonApiOptions; - private IJsonApiContext jsonApiContext; - private IResourceService resourceService; - private ILoggerFactory loggerFactory; - - - /// - /// Normal constructor with int as default (old fashioned) - /// - /// - /// - [Obsolete("JsonApiContext is Obsolete, use constructor without jsonApiContext")] - public JsonApiController( - IJsonApiContext context, - IResourceService resourceService) : base(context.Options,context.ResourceGraph,resourceService) { - } - /// /// Base constructor with int as default /// diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs index 21ca22a578..9642efbcee 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiQueryController.cs @@ -11,9 +11,8 @@ public class JsonApiQueryController { public JsonApiQueryController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IResourceService resourceService) - : base(jsonApiOptions, jsonApiContext, resourceService) + : base(jsonApiOptions, resourceService) { } } @@ -22,7 +21,6 @@ public class JsonApiQueryController { public JsonApiQueryController( IJsonApiOptions jsonApiOptions, - IJsonApiContext jsonApiContext, IResourceService resourceService) : base(jsonApiOptions, resourceService) { } diff --git a/src/JsonApiDotNetCore/Data/Article.cs b/src/JsonApiDotNetCore/Data/Article.cs deleted file mode 100644 index 004d5d2f71..0000000000 --- a/src/JsonApiDotNetCore/Data/Article.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace JsonApiDotNetCore.Data -{ - -} diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index 35e7719a5d..d24746419e 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Contracts; using JsonApiDotNetCore.Internal.Generics; using JsonApiDotNetCore.Internal.Query; using JsonApiDotNetCore.Managers.Contracts; @@ -15,8 +16,6 @@ using Microsoft.Extensions.Logging; namespace JsonApiDotNetCore.Data { - - /// /// Provides a default repository implementation and is responsible for /// abstracting any EF Core APIs away from the service layer. @@ -26,46 +25,38 @@ public class DefaultEntityRepository IEntityFrameworkRepository where TEntity : class, IIdentifiable { - private readonly IRequestManager _requestManager; - private readonly IUpdatedFields _updatedFields; + private readonly IRequestContext _requestManager; + private readonly IUpdatedFields _updatedFields; private readonly DbContext _context; private readonly DbSet _dbSet; private readonly ILogger _logger; - private readonly IJsonApiContext _jsonApiContext; + private readonly IResourceGraph _resourceGraph; private readonly IGenericProcessorFactory _genericProcessorFactory; private readonly ResourceDefinition _resourceDefinition; - [Obsolete("Dont use jsonapicontext instantiation anymore")] public DefaultEntityRepository( - IUpdatedFields updatedFields, - IJsonApiContext jsonApiContext, + IUpdatedFields updatedFields, IDbContextResolver contextResolver, + IResourceGraph resourceGraph, + IGenericProcessorFactory genericProcessorFactory, ResourceDefinition resourceDefinition = null) - { - _updatedFields = updatedFields; - _requestManager = jsonApiContext.RequestManager; - _context = contextResolver.GetContext(); - _dbSet = _context.Set(); - _jsonApiContext = jsonApiContext; - _genericProcessorFactory = _jsonApiContext.GenericProcessorFactory; - _resourceDefinition = resourceDefinition; - } + : this(null, updatedFields, contextResolver, resourceGraph, genericProcessorFactory, resourceDefinition) + { } - [Obsolete("Dont use jsonapicontext instantiation anymore")] public DefaultEntityRepository( - IUpdatedFields updatedFields, ILoggerFactory loggerFactory, - IJsonApiContext jsonApiContext, + IUpdatedFields updatedFields, IDbContextResolver contextResolver, + IResourceGraph resourceGraph, + IGenericProcessorFactory genericProcessorFactory, ResourceDefinition resourceDefinition = null) { + _logger = loggerFactory.CreateLogger>(); _updatedFields = updatedFields; - _requestManager = jsonApiContext.RequestManager; + _resourceGraph = resourceGraph; + _genericProcessorFactory = genericProcessorFactory; _context = contextResolver.GetContext(); _dbSet = _context.Set(); - _jsonApiContext = jsonApiContext; - _logger = loggerFactory.CreateLogger>(); - _genericProcessorFactory = _jsonApiContext.GenericProcessorFactory; _resourceDefinition = resourceDefinition; } @@ -92,7 +83,7 @@ public virtual IQueryable Filter(IQueryable entities, FilterQu return defaultQueryFilter(entities, filterQuery); } } - return entities.Filter(new AttrFilterQuery(_requestManager, _jsonApiContext.ResourceGraph, filterQuery)); + return entities.Filter(new AttrFilterQuery(_requestManager, _resourceGraph, filterQuery)); } /// @@ -134,7 +125,7 @@ public virtual async Task GetAndIncludeAsync(TId id, string relationshi /// public virtual async Task CreateAsync(TEntity entity) { - foreach (var relationshipAttr in _updatedFields.RelationshipsToUpdate) + foreach (var relationshipAttr in _updatedFields.Relationships) { var trackedRelationshipValue = GetTrackedRelationshipValue(relationshipAttr, entity, out bool wasAlreadyTracked); LoadInverseRelationships(trackedRelationshipValue, relationshipAttr); @@ -196,7 +187,7 @@ private void LoadInverseRelationships(object trackedRelationshipValue, Relations private bool IsHasOneRelationship(string internalRelationshipName, Type type) { - var relationshipAttr = _jsonApiContext.ResourceGraph.GetContextEntity(type).Relationships.SingleOrDefault(r => r.InternalRelationshipName == internalRelationshipName); + var relationshipAttr = _resourceGraph.GetContextEntity(type).Relationships.SingleOrDefault(r => r.InternalRelationshipName == internalRelationshipName); if (relationshipAttr != null) { if (relationshipAttr is HasOneAttribute) return true; @@ -215,7 +206,7 @@ private bool IsHasOneRelationship(string internalRelationshipName, Type type) public void DetachRelationshipPointers(TEntity entity) { - foreach (var relationshipAttr in _updatedFields.RelationshipsToUpdate) + foreach (var relationshipAttr in _updatedFields.Relationships) { if (relationshipAttr is HasOneAttribute hasOneAttr) { @@ -257,10 +248,10 @@ public virtual async Task UpdateAsync(TEntity updatedEntity) if (databaseEntity == null) return null; - foreach (var attr in _updatedFields.AttributesToUpdate) + foreach (var attr in _updatedFields.Attributes) attr.SetValue(databaseEntity, attr.GetValue(updatedEntity)); - foreach (var relationshipAttr in _updatedFields.RelationshipsToUpdate) + foreach (var relationshipAttr in _updatedFields.Relationships) { /// loads databasePerson.todoItems LoadCurrentRelationships(databaseEntity, relationshipAttr); @@ -390,7 +381,7 @@ public virtual IQueryable Include(IQueryable entities, string : $"{internalRelationshipPath}.{relationship.RelationshipPath}"; if (i < relationshipChain.Length) - entity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.Type); + entity = _resourceGraph.GetContextEntity(relationship.Type); } return entities.Include(internalRelationshipPath); @@ -442,7 +433,6 @@ public async Task> ToListAsync(IQueryable entiti : entities.ToList(); } - /// /// Before assigning new relationship values (UpdateAsync), we need to /// attach the current database values of the relationship to the dbcontext, else @@ -560,27 +550,28 @@ private IIdentifiable AttachOrGetTracked(IIdentifiable relationshipValue) return null; } } + /// public class DefaultEntityRepository : DefaultEntityRepository, IEntityRepository where TEntity : class, IIdentifiable { - public DefaultEntityRepository( - IUpdatedFields updatedFields, - IJsonApiContext jsonApiContext, - IDbContextResolver contextResolver, - ResourceDefinition resourceDefinition = null) - : base(updatedFields, jsonApiContext, contextResolver, resourceDefinition) - { } - - public DefaultEntityRepository( - IUpdatedFields updatedFields, - ILoggerFactory loggerFactory, - IJsonApiContext jsonApiContext, - IDbContextResolver contextResolver, - ResourceDefinition resourceDefinition = null) - : base(updatedFields, loggerFactory, jsonApiContext, contextResolver, resourceDefinition) - { } + //public DefaultEntityRepository( + //IUpdatedFields updatedFields, + //IDbContextResolver contextResolver, + //IResourceGraph resourceGraph, + //IGenericProcessorFactory genericProcessorFactory, + //ResourceDefinition resourceDefinition = null) : base (updatedFields CO) + //{ } + + //public DefaultEntityRepository( + // IUpdatedFields updatedFields, + // ILoggerFactory loggerFactory, + // IDbContextResolver contextResolver, + // IResourceGraph resourceGraph, + // IGenericProcessorFactory genericProcessorFactory, + // ResourceDefinition resourceDefinition = null) + //{ } } } diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs index 0a6fc11d07..496c50827e 100644 --- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs @@ -202,7 +202,7 @@ public static void AddJsonApiInternals( services.AddSingleton(graph); services.AddScoped(typeof(ServerSerializer<>)); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs index c585196056..94343094c4 100644 --- a/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Formatters/JsonApiReader.cs @@ -17,12 +17,12 @@ public class JsonApiReader : IJsonApiReader { private readonly IOperationsDeserializer _operationsDeserializer; private readonly IJsonApiDeserializer _deserializer; - private readonly IRequestManager _requestManager; + private readonly IRequestContext _requestManager; private readonly ILogger _logger; public JsonApiReader(IJsonApiDeserializer deserializer, IOperationsDeserializer operationsDeserializer, - IRequestManager requestManager, + IRequestContext requestManager, ILoggerFactory loggerFactory) { _deserializer = deserializer; @@ -44,7 +44,7 @@ public Task ReadAsync(InputFormatterContext context) { var body = GetRequestBody(context.HttpContext.Request.Body); - if( _requestManager.IsBulkRequest) + if (_requestManager.IsBulkRequest) { var operations = _operationsDeserializer.Deserialize(body); return InputFormatterResult.SuccessAsync(operations); diff --git a/src/JsonApiDotNetCore/Hooks/Execution/DiffableEntityHashSet.cs b/src/JsonApiDotNetCore/Hooks/Execution/DiffableEntityHashSet.cs index 7ee4d37226..dd6602c3f9 100644 --- a/src/JsonApiDotNetCore/Hooks/Execution/DiffableEntityHashSet.cs +++ b/src/JsonApiDotNetCore/Hooks/Execution/DiffableEntityHashSet.cs @@ -56,7 +56,7 @@ internal DiffableEntityHashSet(IEnumerable requestEntities, Dictionary relationships, IUpdatedFields updatedFields) : this((HashSet)requestEntities, (HashSet)databaseEntities, TypeHelper.ConvertRelationshipDictionary(relationships), - TypeHelper.ConvertAttributeDictionary(updatedFields.AttributesToUpdate, (HashSet)requestEntities)) + TypeHelper.ConvertAttributeDictionary(updatedFields.Attributes, (HashSet)requestEntities)) { } diff --git a/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs b/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs index 8feb959288..66d7973e3e 100644 --- a/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs +++ b/src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs @@ -209,7 +209,7 @@ void RegisterRelationshipProxies(DependentType type) { DependentType dependentType = GetDependentTypeFromRelationship(attr); bool isContextRelation = false; - var relationshipsToUpdate = _updatedFields.RelationshipsToUpdate; + var relationshipsToUpdate = _updatedFields.Relationships; if (relationshipsToUpdate != null) isContextRelation = relationshipsToUpdate.Contains(attr); var proxy = new RelationshipProxy(attr, dependentType, isContextRelation); RelationshipProxies[attr] = proxy; diff --git a/src/JsonApiDotNetCore/Internal/Error.cs b/src/JsonApiDotNetCore/Internal/Exceptions/Error.cs similarity index 100% rename from src/JsonApiDotNetCore/Internal/Error.cs rename to src/JsonApiDotNetCore/Internal/Exceptions/Error.cs diff --git a/src/JsonApiDotNetCore/Internal/ErrorCollection.cs b/src/JsonApiDotNetCore/Internal/Exceptions/ErrorCollection.cs similarity index 100% rename from src/JsonApiDotNetCore/Internal/ErrorCollection.cs rename to src/JsonApiDotNetCore/Internal/Exceptions/ErrorCollection.cs diff --git a/src/JsonApiDotNetCore/Internal/Exceptions.cs b/src/JsonApiDotNetCore/Internal/Exceptions/Exceptions.cs similarity index 100% rename from src/JsonApiDotNetCore/Internal/Exceptions.cs rename to src/JsonApiDotNetCore/Internal/Exceptions/Exceptions.cs diff --git a/src/JsonApiDotNetCore/Internal/JsonApiException.cs b/src/JsonApiDotNetCore/Internal/Exceptions/JsonApiException.cs similarity index 100% rename from src/JsonApiDotNetCore/Internal/JsonApiException.cs rename to src/JsonApiDotNetCore/Internal/Exceptions/JsonApiException.cs diff --git a/src/JsonApiDotNetCore/Internal/JsonApiExceptionFactory.cs b/src/JsonApiDotNetCore/Internal/Exceptions/JsonApiExceptionFactory.cs similarity index 100% rename from src/JsonApiDotNetCore/Internal/JsonApiExceptionFactory.cs rename to src/JsonApiDotNetCore/Internal/Exceptions/JsonApiExceptionFactory.cs diff --git a/src/JsonApiDotNetCore/Internal/JsonApiRouteHandler.cs b/src/JsonApiDotNetCore/Internal/Exceptions/JsonApiRouteHandler.cs similarity index 100% rename from src/JsonApiDotNetCore/Internal/JsonApiRouteHandler.cs rename to src/JsonApiDotNetCore/Internal/Exceptions/JsonApiRouteHandler.cs diff --git a/src/JsonApiDotNetCore/Internal/JsonApiSetupException.cs b/src/JsonApiDotNetCore/Internal/Exceptions/JsonApiSetupException.cs similarity index 100% rename from src/JsonApiDotNetCore/Internal/JsonApiSetupException.cs rename to src/JsonApiDotNetCore/Internal/Exceptions/JsonApiSetupException.cs diff --git a/src/JsonApiDotNetCore/Internal/Query/AttrFilterQuery.cs b/src/JsonApiDotNetCore/Internal/Query/AttrFilterQuery.cs index 348469343a..ee62dcbfb0 100644 --- a/src/JsonApiDotNetCore/Internal/Query/AttrFilterQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/AttrFilterQuery.cs @@ -9,7 +9,7 @@ namespace JsonApiDotNetCore.Internal.Query public class AttrFilterQuery : BaseFilterQuery { public AttrFilterQuery( - IRequestManager requestManager, + IRequestContext requestManager, IResourceGraph resourceGraph, FilterQuery filterQuery) : base(requestManager, resourceGraph, filterQuery) diff --git a/src/JsonApiDotNetCore/Internal/Query/AttrSortQuery.cs b/src/JsonApiDotNetCore/Internal/Query/AttrSortQuery.cs index 9f78bd2d5d..d60825b503 100644 --- a/src/JsonApiDotNetCore/Internal/Query/AttrSortQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/AttrSortQuery.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.Internal.Query { public class AttrSortQuery : BaseAttrQuery { - public AttrSortQuery(IJsonApiContext jsonApiContext,SortQuery sortQuery) + public AttrSortQuery(IJsonApiContext jsonApiContext ,SortQuery sortQuery) :base(jsonApiContext.RequestManager,jsonApiContext.ResourceGraph, sortQuery) { if (Attribute == null) diff --git a/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs b/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs index 259ca84ee9..5e5bdb1b43 100644 --- a/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs @@ -14,10 +14,10 @@ namespace JsonApiDotNetCore.Internal.Query /// public abstract class BaseAttrQuery { - private readonly IRequestManager _requestManager; + private readonly IRequestContext _requestManager; private readonly IResourceGraph _resourceGraph; - public BaseAttrQuery(IRequestManager requestManager, IResourceGraph resourceGraph, BaseQuery baseQuery) + public BaseAttrQuery(IRequestContext requestManager, IResourceGraph resourceGraph, BaseQuery baseQuery) { _requestManager = requestManager ?? throw new ArgumentNullException(nameof(requestManager)); _resourceGraph = resourceGraph ?? throw new ArgumentNullException(nameof(resourceGraph)); diff --git a/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs b/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs index 1a12d50d67..1347823158 100644 --- a/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/BaseFilterQuery.cs @@ -11,7 +11,7 @@ namespace JsonApiDotNetCore.Internal.Query public class BaseFilterQuery : BaseAttrQuery { public BaseFilterQuery( - IRequestManager requestManager, + IRequestContext requestManager, IResourceGraph resourceGraph, FilterQuery filterQuery) : base(requestManager, resourceGraph, filterQuery) diff --git a/src/JsonApiDotNetCore/Internal/Query/RelatedAttrFilterQuery.cs b/src/JsonApiDotNetCore/Internal/Query/RelatedAttrFilterQuery.cs index af744135e7..1d0073265a 100644 --- a/src/JsonApiDotNetCore/Internal/Query/RelatedAttrFilterQuery.cs +++ b/src/JsonApiDotNetCore/Internal/Query/RelatedAttrFilterQuery.cs @@ -9,7 +9,7 @@ namespace JsonApiDotNetCore.Internal.Query public class RelatedAttrFilterQuery : BaseFilterQuery { public RelatedAttrFilterQuery( - IRequestManager requestManager, + IRequestContext requestManager, IResourceGraph resourceGraph, FilterQuery filterQuery) : base(requestManager: requestManager, diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index fe22326bbd..4b2f67bd28 100644 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -51,5 +51,6 @@ + diff --git a/src/JsonApiDotNetCore/Managers/IUpdatedFieldManager.cs b/src/JsonApiDotNetCore/Managers/IUpdatedFieldManager.cs deleted file mode 100644 index 4dbad4b97d..0000000000 --- a/src/JsonApiDotNetCore/Managers/IUpdatedFieldManager.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; -using JsonApiDotNetCore.Models; - -namespace JsonApiDotNetCore.Serialization -{ - public interface IUpdatedFields - { - List AttributesToUpdate { get; set; } - List RelationshipsToUpdate { get; set; } - } - - public class UpdatedFields: IUpdatedFields - { - public List AttributesToUpdate { get; set; } = new List(); - public List RelationshipsToUpdate { get; set; } = new List(); - } - -} diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs b/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs index ea3e54623e..aa8e00c8dd 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiActionFilter.cs @@ -14,13 +14,13 @@ namespace JsonApiDotNetCore.Middleware public class JsonApiActionFilter : IActionFilter { private readonly IResourceGraph _resourceGraph; - private readonly IRequestManager _requestManager; + private readonly IRequestContext _requestManager; private readonly IPageQueryService _pageManager; private readonly IQueryParser _queryParser; private readonly IJsonApiOptions _options; private HttpContext _httpContext; public JsonApiActionFilter(IResourceGraph resourceGraph, - IRequestManager requestManager, + IRequestContext requestManager, IPageQueryService pageManager, IQueryParser queryParser, IJsonApiOptions options) @@ -49,7 +49,6 @@ public void OnActionExecuting(ActionExecutingContext context) } - /// /// Parses the uri /// @@ -61,11 +60,10 @@ protected void HandleUriParameters() _requestManager.QuerySet = querySet; //this shouldn't be exposed? _pageManager.PageSize = querySet.PageQuery.PageSize ?? _pageManager.PageSize; _pageManager.CurrentPage = querySet.PageQuery.PageOffset ?? _pageManager.CurrentPage; - _requestManager.IncludedRelationships = _requestManager.QuerySet.IncludedRelationships; + } } - private string GetBasePath(string entityName) { var r = _httpContext.Request; diff --git a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs index ac1766579a..53555ccfb7 100644 --- a/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs @@ -19,7 +19,7 @@ public class RequestMiddleware { private readonly RequestDelegate _next; private HttpContext _httpContext; - private IRequestManager _requestManager; + private IRequestContext _requestManager; public RequestMiddleware(RequestDelegate next) { @@ -27,7 +27,7 @@ public RequestMiddleware(RequestDelegate next) } public async Task Invoke(HttpContext httpContext, - IRequestManager requestManager) + IRequestContext requestManager) { _httpContext = httpContext; _requestManager = requestManager; diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/ResourceObjectComparer.cs b/src/JsonApiDotNetCore/Models/JsonApi/ResourceObjectComparer.cs similarity index 100% rename from src/JsonApiDotNetCore/Serialization/Serializer/ResourceObjectComparer.cs rename to src/JsonApiDotNetCore/Models/JsonApi/ResourceObjectComparer.cs diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs b/src/JsonApiDotNetCore/RequestServices/Contracts/IRequestManager.cs similarity index 77% rename from src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs rename to src/JsonApiDotNetCore/RequestServices/Contracts/IRequestManager.cs index 634a5fe646..37dc6485dc 100644 --- a/src/JsonApiDotNetCore/Managers/Contracts/IRequestManager.cs +++ b/src/JsonApiDotNetCore/RequestServices/Contracts/IRequestManager.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.Managers.Contracts { - public interface IRequestManager : IQueryRequest + public interface IRequestContext : IQueryRequest { /// /// The request namespace. This may be an absolute or relative path @@ -27,16 +27,6 @@ public interface IRequestManager : IQueryRequest /// bool IsRelationshipPath { get; set; } /// - /// Gets the relationships as set in the query parameters - /// - /// - List GetRelationships(); - /// - /// Gets the sparse fields - /// - /// - List GetFields(); - /// /// Sets the current context entity for this entire request /// /// diff --git a/src/JsonApiDotNetCore/Managers/Contracts/IResourceGraphManager.cs b/src/JsonApiDotNetCore/RequestServices/Contracts/IResourceGraphManager.cs similarity index 100% rename from src/JsonApiDotNetCore/Managers/Contracts/IResourceGraphManager.cs rename to src/JsonApiDotNetCore/RequestServices/Contracts/IResourceGraphManager.cs diff --git a/src/JsonApiDotNetCore/RequestServices/Contracts/IUpdatedFields.cs b/src/JsonApiDotNetCore/RequestServices/Contracts/IUpdatedFields.cs new file mode 100644 index 0000000000..c7deab88eb --- /dev/null +++ b/src/JsonApiDotNetCore/RequestServices/Contracts/IUpdatedFields.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using JsonApiDotNetCore.Models; + +namespace JsonApiDotNetCore.Serialization +{ + public interface IUpdatedFields + { + List Attributes { get; set; } + List Relationships { get; set; } + } + + public class UpdatedFields: IUpdatedFields + { + public List Attributes { get; set; } = new List(); + public List Relationships { get; set; } = new List(); + } + +} diff --git a/src/JsonApiDotNetCore/Managers/RequestManager.cs b/src/JsonApiDotNetCore/RequestServices/RequestManager.cs similarity index 97% rename from src/JsonApiDotNetCore/Managers/RequestManager.cs rename to src/JsonApiDotNetCore/RequestServices/RequestManager.cs index 997d7d208f..3d878fd77d 100644 --- a/src/JsonApiDotNetCore/Managers/RequestManager.cs +++ b/src/JsonApiDotNetCore/RequestServices/RequestManager.cs @@ -11,7 +11,8 @@ namespace JsonApiDotNetCore.Managers { - class RequestManager : IRequestManager + + class RequestContext : IRequestContext { private ContextEntity _contextEntity; private IQueryParser _queryParser; diff --git a/src/JsonApiDotNetCore/Models/SerializableFields.cs b/src/JsonApiDotNetCore/RequestServices/SerializableFields.cs similarity index 100% rename from src/JsonApiDotNetCore/Models/SerializableFields.cs rename to src/JsonApiDotNetCore/RequestServices/SerializableFields.cs diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/IIncludedRelationshipsBuilder.cs b/src/JsonApiDotNetCore/Serialization/Contracts/IIncludedRelationshipsBuilder.cs similarity index 100% rename from src/JsonApiDotNetCore/Serialization/Serializer/IIncludedRelationshipsBuilder.cs rename to src/JsonApiDotNetCore/Serialization/Contracts/IIncludedRelationshipsBuilder.cs diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/IJsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/Contracts/IJsonApiSerializer.cs similarity index 100% rename from src/JsonApiDotNetCore/Serialization/Serializer/IJsonApiSerializer.cs rename to src/JsonApiDotNetCore/Serialization/Contracts/IJsonApiSerializer.cs diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/IServerSerializerFactory.cs b/src/JsonApiDotNetCore/Serialization/Contracts/IServerSerializerFactory.cs similarity index 100% rename from src/JsonApiDotNetCore/Serialization/Serializer/IServerSerializerFactory.cs rename to src/JsonApiDotNetCore/Serialization/Contracts/IServerSerializerFactory.cs diff --git a/src/JsonApiDotNetCore/Serialization/Deserializer/OperationsDeserializer.cs b/src/JsonApiDotNetCore/Serialization/Deserializer/OperationsDeserializer.cs index d280902b3e..cfaaf8c2e4 100644 --- a/src/JsonApiDotNetCore/Serialization/Deserializer/OperationsDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Deserializer/OperationsDeserializer.cs @@ -98,7 +98,7 @@ private object SetEntityAttributes( continue; var convertedValue = ConvertAttrValue(newValue, attr.PropertyInfo.PropertyType); attr.SetValue(entity, convertedValue); - _updatedFieldsManager.AttributesToUpdate.Add(attr); + _updatedFieldsManager.Attributes.Add(attr); } } @@ -201,7 +201,7 @@ private void SetHasOneForeignKeyValue(object entity, HasOneAttribute hasOneAttr, /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - if (convertedValue == null) _updatedFieldsManager.RelationshipsToUpdate.Add(hasOneAttr); + if (convertedValue == null) _updatedFieldsManager.Relationships.Add(hasOneAttr); } } @@ -226,7 +226,7 @@ private void SetHasOneNavigationPropertyValue(object entity, HasOneAttribute has /// store the updated relationship values in this property. For now /// just assigning null as value, will remove this property later as a whole. /// see #512 - _updatedFieldsManager.RelationshipsToUpdate.Add(hasOneAttr); + _updatedFieldsManager.Relationships.Add(hasOneAttr); } } @@ -253,7 +253,7 @@ private object SetHasManyRelationship(object entity, var convertedCollection = TypeHelper.ConvertCollection(relatedResources, attr.DependentType); attr.SetValue(entity, convertedCollection); - _updatedFieldsManager.RelationshipsToUpdate.Add(attr); + _updatedFieldsManager.Relationships.Add(attr); } return entity; diff --git a/src/JsonApiDotNetCore/Serialization/Deserializer/ServerDeserializer.cs b/src/JsonApiDotNetCore/Serialization/Deserializer/ServerDeserializer.cs index 29d1d7e271..39549b732b 100644 --- a/src/JsonApiDotNetCore/Serialization/Deserializer/ServerDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Deserializer/ServerDeserializer.cs @@ -25,12 +25,12 @@ protected override void AfterProcessField(IIdentifiable entity, IResourceField f if (field is AttrAttribute attr) { if (!attr.IsImmutable) - _updatedFields.AttributesToUpdate.Add(attr); + _updatedFields.Attributes.Add(attr); else throw new InvalidOperationException($"Attribute {attr.PublicAttributeName} is immutable and therefore cannot be updated."); } else if (field is RelationshipAttribute relationship) - _updatedFields.RelationshipsToUpdate.Add(relationship); + _updatedFields.Relationships.Add(relationship); } } } diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/LinkBuilder.cs b/src/JsonApiDotNetCore/Serialization/Serializer/LinkBuilder.cs index 4994bf36a9..1a70101298 100644 --- a/src/JsonApiDotNetCore/Serialization/Serializer/LinkBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Serializer/LinkBuilder.cs @@ -11,14 +11,14 @@ namespace JsonApiDotNetCore.Builders { public class LinkBuilder : ILinkBuilder { - private readonly IRequestManager _requestManager; + private readonly IRequestContext _requestManager; private readonly IGlobalLinksConfiguration _options; private readonly IPageQueryService _pageManager; private readonly ContextEntity _requestResourceContext; private readonly IContextEntityProvider _provider; public LinkBuilder(IGlobalLinksConfiguration options, - IRequestManager requestManager, + IRequestContext requestManager, IPageQueryService pageManager, IContextEntityProvider provider) { diff --git a/src/JsonApiDotNetCore/Serialization/Serializer/ServerSerializer.cs b/src/JsonApiDotNetCore/Serialization/Serializer/ServerSerializer.cs index 7f6f8729f2..61b29dd516 100644 --- a/src/JsonApiDotNetCore/Serialization/Serializer/ServerSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Serializer/ServerSerializer.cs @@ -13,10 +13,10 @@ namespace JsonApiDotNetCore.Builders public class ServerSerializerFactory : IJsonApiSerializerFactory { - private readonly IRequestManager _requestManager; + private readonly IRequestContext _requestManager; private readonly IServiceProvider _provider; - public ServerSerializerFactory(IRequestManager requestManager, IServiceProvider provider) + public ServerSerializerFactory(IRequestContext requestManager, IServiceProvider provider) { _requestManager = requestManager; _provider = provider; diff --git a/src/JsonApiDotNetCore/Services/ControllerContext.cs b/src/JsonApiDotNetCore/Services/ControllerContext.cs deleted file mode 100644 index 1984262b15..0000000000 --- a/src/JsonApiDotNetCore/Services/ControllerContext.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Reflection; -using JsonApiDotNetCore.Internal; - -namespace JsonApiDotNetCore.Services -{ - public interface IControllerContext - { - Type ControllerType { get; set; } - ContextEntity RequestEntity { get; set; } - TAttribute GetControllerAttribute() where TAttribute : Attribute; - } - - public class ControllerContext : IControllerContext - { - public Type ControllerType { get; set; } - public ContextEntity RequestEntity { get; set; } - - public TAttribute GetControllerAttribute() where TAttribute : Attribute - { - var attribute = ControllerType.GetTypeInfo().GetCustomAttribute(typeof(TAttribute)); - return attribute == null ? null : (TAttribute)attribute; - } - } -} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index ec6f055507..26aebf5637 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading.Tasks; using JsonApiDotNetCore.Internal.Contracts; +using JsonApiDotNetCore.Serialization; namespace JsonApiDotNetCore.Services { @@ -25,8 +26,9 @@ public class EntityResourceService : where TEntity : class, IIdentifiable { private readonly IPageQueryService _pageManager; - private readonly IRequestManager _requestManager; + private readonly IRequestContext _requestManager; private readonly IJsonApiOptions _options; + private readonly IUpdatedFields _updatedFields; private readonly IResourceGraph _resourceGraph; private readonly IEntityRepository _repository; private readonly ILogger _logger; @@ -36,7 +38,8 @@ public class EntityResourceService : public EntityResourceService( IEntityRepository repository, IJsonApiOptions options, - IRequestManager requestManager, + IUpdatedFields updatedFields, + IRequestContext requestManager, IPageQueryService pageManager, IResourceGraph resourceGraph, IResourceHookExecutor hookExecutor = null, @@ -46,6 +49,7 @@ public EntityResourceService( _requestManager = requestManager; _pageManager = pageManager; _options = options; + _updatedFields = updatedFields; _resourceGraph = resourceGraph; _repository = repository; if (mapper == null && typeof(TResource) != typeof(TEntity)) @@ -66,7 +70,7 @@ public virtual async Task CreateAsync(TResource resource) // this ensures relationships get reloaded from the database if they have // been requested // https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/343 - if (ShouldRelationshipsBeIncluded()) + if (ShouldIncludeRelationships()) { if (_repository is IEntityFrameworkRepository efRepository) efRepository.DetachRelationshipPointers(entity); @@ -287,28 +291,23 @@ private async Task GetWithRelationshipsAsync(TId id) { var query = _repository.Select(_repository.Get(), _requestManager.QuerySet?.Fields).Where(e => e.Id.Equals(id)); - _requestManager.GetRelationships().ForEach((Action)(r => - { - query = this._repository.Include((IQueryable)query, r); - })); + foreach (var r in _updatedFields.Relationships) + query = _repository.Include(query, r.InternalRelationshipName); TEntity value; // https://github.com/aspnet/EntityFrameworkCore/issues/6573 - if (_requestManager.GetFields()?.Count() > 0) - { + if (_updatedFields.Attributes.Count() > 0) value = query.FirstOrDefault(); - } else - { value = await _repository.FirstOrDefaultAsync(query); - } + return value; } private bool ShouldIncludeRelationships() { - return _requestManager.GetRelationships()?.Count() > 0; + return _updatedFields.Relationships.Count() > 0; } @@ -321,15 +320,6 @@ private bool IsNull(params object[] values) return false; } - /// - /// Should the relationships be included? - /// - /// - private bool ShouldRelationshipsBeIncluded() - { - return _requestManager.GetRelationships()?.Count() > 0; - - } /// /// Casts the entity given to `TResource` or maps it to its equal /// @@ -367,7 +357,7 @@ public class EntityResourceService : EntityResourceService repository, IJsonApiOptions apiOptions, - IRequestManager requestManager, + IRequestContext requestManager, IResourceGraph resourceGraph, IPageQueryService pageManager, ILoggerFactory loggerFactory = null, @@ -396,7 +386,7 @@ public class EntityResourceService : EntityResourceService repository, IJsonApiOptions options, - IRequestManager requestManager, + IRequestContext requestManager, IPageQueryService pageManager, IResourceGraph resourceGraph, ILoggerFactory loggerFactory = null, diff --git a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs index 4bc7fe3550..3ccdcc0604 100644 --- a/src/JsonApiDotNetCore/Services/IJsonApiContext.cs +++ b/src/JsonApiDotNetCore/Services/IJsonApiContext.cs @@ -20,9 +20,7 @@ public interface IJsonApiApplication public interface IQueryRequest { - List IncludedRelationships { get; set; } QuerySet QuerySet { get; set; } - PageQueryService PageManager { get; set; } } public interface IJsonApiRequest : IJsonApiApplication, IQueryRequest @@ -61,15 +59,4 @@ public interface IJsonApiRequest : IJsonApiApplication, IQueryRequest /// bool IsRelationshipPath { get; } } - - public interface IJsonApiContext : IJsonApiRequest - { - [Obsolete("Use standalone IRequestManager")] - IRequestManager RequestManager { get; set; } - [Obsolete("Use standalone IPageManager")] - IPageQueryService PageManager { get; set; } - IJsonApiContext ApplyContext(object controller); - //IMetaBuilder MetaBuilder { get; set; } - IGenericProcessorFactory GenericProcessorFactory { get; set; } - } } diff --git a/src/JsonApiDotNetCore/Services/JsonApiContext.cs b/src/JsonApiDotNetCore/Services/JsonApiContext.cs deleted file mode 100644 index 4fd38b4c1a..0000000000 --- a/src/JsonApiDotNetCore/Services/JsonApiContext.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using JsonApiDotNetCore.Builders; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Internal.Contracts; -using JsonApiDotNetCore.Internal.Generics; -using JsonApiDotNetCore.Internal.Query; -using JsonApiDotNetCore.Managers.Contracts; -using JsonApiDotNetCore.Models; -using Microsoft.AspNetCore.Http; - -namespace JsonApiDotNetCore.Services -{ - public class JsonApiContext : IJsonApiContext - { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IQueryParser _queryParser; - private readonly IControllerContext _controllerContext; - - public JsonApiContext( - IResourceGraph resourceGraph, - IHttpContextAccessor httpContextAccessor, - IJsonApiOptions options, - - IGenericProcessorFactory genericProcessorFactory, - IQueryParser queryParser, - IPageQueryService pageManager, - IRequestManager requestManager, - IControllerContext controllerContext) - { - RequestManager = requestManager; - PageManager = pageManager; - ResourceGraph = resourceGraph; - _httpContextAccessor = httpContextAccessor; - Options = options; - GenericProcessorFactory = genericProcessorFactory; - _queryParser = queryParser; - _controllerContext = controllerContext; - } - - public IJsonApiOptions Options { get; set; } - [Obsolete("Please use the standalone `IResourceGraph`")] - public IResourceGraph ResourceGraph { get; set; } - [Obsolete("Use the proxied member IControllerContext.RequestEntity instead.")] - public ContextEntity RequestEntity { get => _controllerContext.RequestEntity; set => _controllerContext.RequestEntity = value; } - - [Obsolete("Use IRequestManager")] - public QuerySet QuerySet { get; set; } - [Obsolete("Use IRequestManager")] - public bool IsRelationshipData { get; set; } - [Obsolete("Use IRequestManager")] - public bool IsRelationshipPath { get; private set; } - [Obsolete("Use IRequestManager")] - public List IncludedRelationships { get; set; } - public IPageQueryService PageManager { get; set; } - //public IMetaBuilder MetaBuilder { get; set; } - public IGenericProcessorFactory GenericProcessorFactory { get; set; } - public Type ControllerType { get; set; } - public Dictionary DocumentMeta { get; set; } - public bool IsBulkOperationRequest { get; set; } - - [Obsolete("Please use the standalone Requestmanager")] - public IRequestManager RequestManager { get; set; } - PageQueryService IQueryRequest.PageManager { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - - [Obsolete("This is no longer necessary")] - public IJsonApiContext ApplyContext(object controller) - { - if (controller == null) - throw new JsonApiException(500, $"Cannot ApplyContext from null controller for type {typeof(T)}"); - - _controllerContext.ControllerType = controller.GetType(); - _controllerContext.RequestEntity = ResourceGraph.GetContextEntity(typeof(T)); - if (_controllerContext.RequestEntity == null) - throw new JsonApiException(500, $"A resource has not been properly defined for type '{typeof(T)}'. Ensure it has been registered on the ResourceGraph."); - - var context = _httpContextAccessor.HttpContext; - - if (context.Request.Query.Count > 0) - { - QuerySet = _queryParser.Parse(context.Request.Query); - IncludedRelationships = QuerySet.IncludedRelationships; - } - - IsRelationshipPath = PathIsRelationship(context.Request.Path.Value); - - return this; - } - - internal static bool PathIsRelationship(string requestPath) - { - // while(!Debugger.IsAttached) { Thread.Sleep(1000); } - const string relationships = "relationships"; - const char pathSegmentDelimiter = '/'; - - var span = requestPath.AsSpan(); - - // we need to iterate over the string, from the end, - // checking whether or not the 2nd to last path segment - // is "relationships" - // -2 is chosen in case the path ends with '/' - for (var i = requestPath.Length - 2; i >= 0; i--) - { - // if there are not enough characters left in the path to - // contain "relationships" - if (i < relationships.Length) - return false; - - // we have found the first instance of '/' - if (span[i] == pathSegmentDelimiter) - { - // in the case of a "relationships" route, the next - // path segment will be "relationships" - return ( - span.Slice(i - relationships.Length, relationships.Length) - .SequenceEqual(relationships.AsSpan()) - ); - } - } - - return false; - } - } -} diff --git a/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs b/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs index f7c4b85ca0..e8ad318a2b 100644 --- a/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs +++ b/src/JsonApiDotNetCore/Services/Operations/OperationsProcessor.cs @@ -22,14 +22,14 @@ public class OperationsProcessor : IOperationsProcessor private readonly IOperationProcessorResolver _processorResolver; private readonly DbContext _dbContext; private readonly IJsonApiContext _jsonApiContext; - private readonly IRequestManager _requestManager; + private readonly IRequestContext _requestManager; private readonly IResourceGraph _resourceGraph; public OperationsProcessor( IOperationProcessorResolver processorResolver, IDbContextResolver dbContextResolver, IJsonApiContext jsonApiContext, - IRequestManager requestManager, + IRequestContext requestManager, IResourceGraph resourceGraph) { _processorResolver = processorResolver; diff --git a/src/JsonApiDotNetCore/Services/QueryAccessor.cs b/src/JsonApiDotNetCore/Services/QueryAccessor.cs index 7721f8e85a..4b8204d8a4 100644 --- a/src/JsonApiDotNetCore/Services/QueryAccessor.cs +++ b/src/JsonApiDotNetCore/Services/QueryAccessor.cs @@ -23,7 +23,7 @@ public interface IQueryAccessor ///
public class QueryAccessor : IQueryAccessor { - private readonly IRequestManager _requestManager; + private readonly IRequestContext _requestManager; private readonly ILogger _logger; /// @@ -32,7 +32,7 @@ public class QueryAccessor : IQueryAccessor /// /// public QueryAccessor( - IRequestManager requestManager, + IRequestContext requestManager, ILogger logger) { _requestManager = requestManager; diff --git a/src/JsonApiDotNetCore/Services/QueryComposer.cs b/src/JsonApiDotNetCore/Services/QueryComposer.cs index b96b83718a..9734c5e9a9 100644 --- a/src/JsonApiDotNetCore/Services/QueryComposer.cs +++ b/src/JsonApiDotNetCore/Services/QueryComposer.cs @@ -6,12 +6,12 @@ namespace JsonApiDotNetCore.Services { public interface IQueryComposer { - string Compose(IRequestManager jsonApiContext); + string Compose(IRequestContext jsonApiContext); } public class QueryComposer : IQueryComposer { - public string Compose(IRequestManager requestManager) + public string Compose(IRequestContext requestManager) { string result = ""; if (requestManager != null && requestManager.QuerySet != null) diff --git a/src/JsonApiDotNetCore/Services/QueryParser.cs b/src/JsonApiDotNetCore/Services/QueryParser.cs index 4d78b2a8ab..da1d193393 100644 --- a/src/JsonApiDotNetCore/Services/QueryParser.cs +++ b/src/JsonApiDotNetCore/Services/QueryParser.cs @@ -22,20 +22,23 @@ public class QueryParser : IQueryParser { private readonly IInternalIncludedQueryService _includedQuery; private readonly IInternalFieldsQueryService _fieldQuery; - private readonly IRequestManager _requestManager; + private readonly IPageQueryService _pageQuery; + private readonly IRequestContext _requestManager; private readonly IJsonApiOptions _options; private readonly ContextEntity _requestResource; private readonly IContextEntityProvider _provider; public QueryParser(IInternalIncludedQueryService includedRelationships, IInternalFieldsQueryService fieldQuery, - IRequestManager requestManager, + IRequestContext requestManager, + IPageQueryService pageQuery, IContextEntityProvider provider, IJsonApiOptions options) { _includedQuery = includedRelationships; _fieldQuery = fieldQuery; _requestManager = requestManager; + _pageQuery = pageQuery; _provider = provider; _requestResource = requestManager.GetRequestResource(); _options = options; @@ -43,7 +46,7 @@ public QueryParser(IInternalIncludedQueryService includedRelationships, public virtual QuerySet Parse(IQueryCollection query) { - var querySet = new QuerySet(); + var querySet = new QuerySet(); var disabledQueries = _requestManager.DisabledQueryParams; foreach (var pair in query) { @@ -212,7 +215,6 @@ protected virtual List ParseIncludedRelationships(string value) _includedQuery.Register(parsedChain); } - return inclusions; } @@ -231,11 +233,12 @@ protected virtual List ParseFieldsQuery(string key, string value) { if (relationship != default) { - var relationProperty = _options.ResourceGraph.GetContextEntity(relationship.DependentType); + var relationProperty = _resourceGraph.GetContextEntity(relationship.DependentType); var attr = relationProperty.Attributes.SingleOrDefault(a => a.Is(field)); if (attr == null) throw new JsonApiException(400, $"'{relationship.DependentType.Name}' does not contain '{field}'."); + _fieldQuery.Register(attr, relationship); // e.g. "Owner.Name" includedFields.Add(relationship.InternalRelationshipName + "." + attr.InternalAttributeName); @@ -246,6 +249,7 @@ protected virtual List ParseFieldsQuery(string key, string value) if (attr == null) throw new JsonApiException(400, $"'{_requestResource.EntityName}' does not contain '{field}'."); + _fieldQuery.Register(attr, relationship); // e.g. "Name" includedFields.Add(attr.InternalAttributeName); } diff --git a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs index e8b09d7409..5f433e4262 100644 --- a/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs +++ b/test/DiscoveryTests/ServiceDiscoveryFacadeTests.cs @@ -69,7 +69,7 @@ public void AddCurrentAssembly_Adds_Services_To_Container() _services.AddSingleton(new JsonApiOptions()); _services.AddScoped((_) => new Mock().Object); - _services.AddScoped((_) => new Mock().Object); + _services.AddScoped((_) => new Mock().Object); _services.AddScoped((_) => new Mock().Object); _services.AddScoped((_) => new Mock().Object); _facade.AddCurrentAssembly(); @@ -101,7 +101,7 @@ public class TestModelService : EntityResourceService public TestModelService( IEntityRepository repository, IJsonApiOptions options, - IRequestManager requestManager, + IRequestContext requestManager, IPageQueryService pageManager, IResourceGraph resourceGraph, ILoggerFactory loggerFactory = null, diff --git a/test/UnitTests/Builders/LinkBuilderTests.cs b/test/UnitTests/Builders/LinkBuilderTests.cs index d42e245c77..7b8bfea14c 100644 --- a/test/UnitTests/Builders/LinkBuilderTests.cs +++ b/test/UnitTests/Builders/LinkBuilderTests.cs @@ -168,9 +168,9 @@ private bool CheckPages(TopLevelLinks links, bool pages) return links.First == null && links.Prev == null && links.Next == null && links.Last == null; } - private IRequestManager GetRequestManager(ContextEntity resourceContext = null) + private IRequestContext GetRequestManager(ContextEntity resourceContext = null) { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(m => m.BasePath).Returns(_host); mock.Setup(m => m.GetRequestResource()).Returns(resourceContext); return mock.Object; diff --git a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs index ee8a42e432..47e5807c02 100644 --- a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs +++ b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs @@ -44,7 +44,7 @@ public void AddJsonApiInternals_Adds_All_Required_Services() var provider = services.BuildServiceProvider(); // assert - var requestManager = provider.GetService(); + var requestManager = provider.GetService(); Assert.NotNull(requestManager); var graph = provider.GetService(); Assert.NotNull(graph); diff --git a/test/UnitTests/JsonApiContext/BasicTest.cs b/test/UnitTests/JsonApiContext/BasicTest.cs index 6c7e5660cb..218b69327d 100644 --- a/test/UnitTests/JsonApiContext/BasicTest.cs +++ b/test/UnitTests/JsonApiContext/BasicTest.cs @@ -44,7 +44,7 @@ public async Task GetAsync_Throw404OnNoEntityFound() IncludeTotalRecordCount = false } as IJsonApiOptions; var repositoryMock = new Mock>(); - var queryManagerMock = new Mock(); + var queryManagerMock = new Mock(); var pageManagerMock = new Mock(); var rgMock = new Mock(); var service = new CustomArticleService(repositoryMock.Object, jsonApiOptions, queryManagerMock.Object, pageManagerMock.Object, rgMock.Object); @@ -74,7 +74,7 @@ public async Task GetAsync_ShouldThrow404OnNoEntityFoundWithRelationships() } as IJsonApiOptions; var repositoryMock = new Mock>(); - var requestManager = new Mock(); + var requestManager = new Mock(); var pageManagerMock = new Mock(); requestManager.Setup(qm => qm.GetRelationships()).Returns(new List() { "cookies" }); requestManager.SetupGet(rm => rm.QuerySet).Returns(new QuerySet diff --git a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdate_WithDbValues_Tests.cs b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdate_WithDbValues_Tests.cs index 680c4be827..1210015951 100644 --- a/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdate_WithDbValues_Tests.cs +++ b/test/UnitTests/ResourceHooks/ResourceHookExecutor/Update/BeforeUpdate_WithDbValues_Tests.cs @@ -82,7 +82,7 @@ public void BeforeUpdate_Deleting_Relationship() var personDiscovery = SetDiscoverableHooks(targetHooks, EnableDbValues); var (_, ufMock, hookExecutor, todoResourceMock, ownerResourceMock) = CreateTestObjects(todoDiscovery, personDiscovery, repoDbContextOptions: options); - ufMock.Setup(c => c.RelationshipsToUpdate).Returns(_fieldExplorer.GetRelationships((TodoItem t) => t.ToOnePerson)); + ufMock.Setup(c => c.Relationshipss)).Returns(_fieldExplorer.GetRelationships((TodoItem t) => t.ToOnePerson)); // act var _todoList = new List() { new TodoItem { Id = this.todoList[0].Id } }; diff --git a/test/UnitTests/Serialization/Deserializer/ServerDeserializerTests.cs b/test/UnitTests/Serialization/Deserializer/ServerDeserializerTests.cs index 6b098c4ddb..8694064151 100644 --- a/test/UnitTests/Serialization/Deserializer/ServerDeserializerTests.cs +++ b/test/UnitTests/Serialization/Deserializer/ServerDeserializerTests.cs @@ -100,8 +100,8 @@ private void SetupFieldsManager(out List attributesToUpdate, out { attributesToUpdate = new List(); relationshipsToUpdate = new List(); - _fieldsManagerMock.Setup(m => m.AttributesToUpdate).Returns(attributesToUpdate); - _fieldsManagerMock.Setup(m => m.RelationshipsToUpdate).Returns(relationshipsToUpdate); + _fieldsManagerMock.Setup(m => m.Attributes).Returns(attributesToUpdate); + _fieldsManagerMock.Setup(m => m.Relationshipss)).Returns(relationshipsToUpdate); } } } diff --git a/test/UnitTests/Serialization/Serializer/SerializerTestsSetup.cs b/test/UnitTests/Serialization/Serializer/SerializerTestsSetup.cs index ab318a940f..6c95a37ecf 100644 --- a/test/UnitTests/Serialization/Serializer/SerializerTestsSetup.cs +++ b/test/UnitTests/Serialization/Serializer/SerializerTestsSetup.cs @@ -69,9 +69,9 @@ protected IMetaBuilder GetMetaBuilder(Dictionary meta = nu return mock.Object; } - protected IRequestManager GetRequestManager() where T : class, IIdentifiable + protected IRequestContext GetRequestManager() where T : class, IIdentifiable { - var mock = new Mock(); + var mock = new Mock(); mock.Setup(m => m.GetRequestResource()).Returns(_resourceGraph.GetContextEntity()); return mock.Object; } @@ -99,7 +99,7 @@ protected ISerializableFields GetSerializableFields() return mock.Object; } - protected IIncludedQueryService GetIncludedRelationships(List> inclusionChains = null) + protected IIncludedQueryService GetIncludedRelationships(List> inclusionChains = null) { var mock = new Mock(); if (inclusionChains != null) diff --git a/test/UnitTests/Services/QueryAccessorTests.cs b/test/UnitTests/Services/QueryAccessorTests.cs index 7c23addd56..9a8c98f69b 100644 --- a/test/UnitTests/Services/QueryAccessorTests.cs +++ b/test/UnitTests/Services/QueryAccessorTests.cs @@ -13,13 +13,13 @@ namespace UnitTests.Services { public class QueryAccessorTests { - private readonly Mock _rmMock; + private readonly Mock _rmMock; private readonly Mock> _loggerMock; private readonly Mock _queryMock; public QueryAccessorTests() { - _rmMock = new Mock(); + _rmMock = new Mock(); _loggerMock = new Mock>(); _queryMock = new Mock(); } diff --git a/test/UnitTests/Services/QueryComposerTests.cs b/test/UnitTests/Services/QueryComposerTests.cs index 607c321b6c..4f31a7f4c6 100644 --- a/test/UnitTests/Services/QueryComposerTests.cs +++ b/test/UnitTests/Services/QueryComposerTests.cs @@ -26,7 +26,7 @@ public void Can_ComposeEqual_FilterStringForUrl() filters.Add(filter); querySet.Filters = filters; - var rmMock = new Mock(); + var rmMock = new Mock(); rmMock .Setup(m => m.QuerySet) .Returns(querySet); @@ -49,7 +49,7 @@ public void Can_ComposeLessThan_FilterStringForUrl() filters.Add(filter); filters.Add(filter2); querySet.Filters = filters; - var rmMock = new Mock(); + var rmMock = new Mock(); rmMock .Setup(m => m.QuerySet) .Returns(querySet); @@ -68,7 +68,7 @@ public void NoFilter_Compose_EmptyStringReturned() // arrange var querySet = new QuerySet(); - var rmMock = new Mock(); + var rmMock = new Mock(); rmMock .Setup(m => m.QuerySet) .Returns(querySet); diff --git a/test/UnitTests/Services/QueryParserTests.cs b/test/UnitTests/Services/QueryParserTests.cs index 9e9476433f..ac0200f8e2 100644 --- a/test/UnitTests/Services/QueryParserTests.cs +++ b/test/UnitTests/Services/QueryParserTests.cs @@ -16,15 +16,15 @@ namespace UnitTests.Services { public class QueryParserTests { - private readonly Mock _requestMock; + private readonly Mock _requestMock; private readonly Mock _queryCollectionMock; - private readonly IInternalFieldsQueryService _fieldsQuery = new Mock().Object; - private readonly IInternalIncludedQueryService _includedQuery = new Mock().Object; - private readonly IContextEntityProvider _graph = new Mock().Object; + private readonly IInternalFieldsQueryService _fieldsQuery = new Mock().Object; + private readonly IInternalIncludedQueryService _includedQuery = new Mock().Object; + private readonly IContextEntityProvider _graph = new Mock().Object; public QueryParserTests() { - _requestMock = new Mock(); + _requestMock = new Mock(); _queryCollectionMock = new Mock(); } @@ -44,7 +44,7 @@ public void Can_Build_Filters() .Setup(m => m.DisabledQueryParams) .Returns(QueryParams.None); - var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); + var queryParser = new QueryParser(_includedQuery, _fieldsQuery, _requestMock.Object, _graph, new JsonApiOptions()); // act var querySet = queryParser.Parse(_queryCollectionMock.Object);