diff --git a/.travis.yml b/.travis.yml index dea6a483b7..234e836e7e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ services: before_script: - psql -c 'create database JsonApiDotNetCoreExample;' -U postgres mono: none -dotnet: 1.0.1 +dotnet: 1.0.4 # https://www.microsoft.com/net/download/linux branches: only: - master diff --git a/Build.ps1 b/Build.ps1 index 4934457d3d..9a5e17d170 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -1,7 +1,12 @@ $revision = @{ $true = $env:APPVEYOR_BUILD_NUMBER; $false = 1 }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; $revision = "{0:D4}" -f [convert]::ToInt32($revision, 10) -dotnet restore .\src\JsonApiDotNetCore\JsonApiDotNetCore.csproj +dotnet restore + +dotnet test ./test/UnitTests/UnitTests.csproj +dotnet test ./test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj +dotnet test ./test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj + dotnet build .\src\JsonApiDotNetCore -c Release echo "APPVEYOR_REPO_TAG: $env:APPVEYOR_REPO_TAG" diff --git a/JsonApiDotnetCore.sln b/JsonApiDotnetCore.sln index 71cba7f264..e3c197bbc5 100644 --- a/JsonApiDotnetCore.sln +++ b/JsonApiDotnetCore.sln @@ -22,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoEntityFrameworkExample", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoEntityFrameworkTests", "test\NoEntityFrameworkTests\NoEntityFrameworkTests.csproj", "{73DA578D-A63F-4956-83ED-6D7102E09140}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "test\UnitTests\UnitTests.csproj", "{6D4BD85A-A262-44C6-8572-FE3A30410BF3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -80,6 +82,18 @@ Global {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|x64 + {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|x64.Build.0 = Debug|x64 + {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|x86.ActiveCfg = Debug|x86 + {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Debug|x86.Build.0 = Debug|x86 + {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|x64 + {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|x64.Build.0 = Release|x64 + {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|x86.ActiveCfg = Release|x86 + {6D4BD85A-A262-44C6-8572-FE3A30410BF3}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -90,5 +104,6 @@ Global {0B959765-40D2-43B5-87EE-FE2FEF9DBED5} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} {570165EC-62B5-4684-A139-8D2A30DD4475} = {7A2B7ADD-ECB5-4D00-AA6A-D45BD11C97CF} {73DA578D-A63F-4956-83ED-6D7102E09140} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} + {6D4BD85A-A262-44C6-8572-FE3A30410BF3} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 9bdf00dae6..b5f3a9cd55 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,15 @@ A framework for building [json:api](http://jsonapi.org/) compliant web servers. ## Installation And Usage -See the documentation [here](https://research-institute.github.io/json-api-dotnet-core) \ No newline at end of file +See the documentation [here](https://research-institute.github.io/json-api-dotnet-core) + + +## .Net Core v2 Notes + +Branch `feat/core-2` is where I am working on .Net Core 2 compatibility tests and package upgrades. +There are several blockers to be aware of: + +- Microsoft.AspNetCore.* packages target the runtime (netcoreapp) instead of netstandard. +This will be fixed in future versions. +- EF bug against netcoreapp2.0 runtime ([EntityFramework#8021](https://github.com/aspnet/EntityFramework/issues/8021)) +- Can't run acceptance testing against postgres on preview runtime [pgsql.EntityFrameworkCore.PostgreSQL#171](https://github.com/npgsql/Npgsql.EntityFrameworkCore.PostgreSQL/issues/171#issuecomment-301287257) diff --git a/appveyor.yml b/appveyor.yml index 5c73d4e86c..55c43a7f82 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,17 +1,40 @@ version: '{build}' os: Visual Studio 2017 + +environment: + POSTGRES_PORT: tcp://localhost:5432 + POSTGRES_ENV_POSTGRES_USER: postgres + POSTGRES_ENV_POSTGRES_PASSWORD: Password12! + POSTGRES_ENV_POSTGRES_DB: JsonApiDotNetCoreExample + PGUSER: postgres + PGPASSWORD: Password12! + Data:DefaultConnection: "Host=localhost;Port=5432;Database=JsonApiDotNetCoreExample;User ID=postgres;Password=Password12!" + pull_requests: do_not_increment_build_number: true + branches: only: - master - develop + - unstable + nuget: disable_publish_on_pr: true + +init: + - SET PATH=C:\Program Files\PostgreSQL\9.6\bin\;%PATH% + +services: + - postgresql + build_script: +- ps: createdb JsonApiDotNetCoreExample - ps: dotnet --version - ps: .\Build.ps1 + test: off + artifacts: - path: .\**\artifacts\**\*.nupkg name: NuGet @@ -23,10 +46,17 @@ deploy: skip_symbols: true on: branch: develop +- provider: NuGet + server: https://www.myget.org/F/jadnc/api/v2/package + api_key: + secure: 6CeYcZ4Ze+57gxfeuHzqP6ldbUkPtF6pfpVM1Gw/K2jExFrAz763gNAQ++tiacq3 + skip_symbols: true + on: + branch: unstable - provider: NuGet name: production api_key: secure: /fsEOgG4EdtNd6DPmko9h3NxQwx1IGDcFreGTKd2KA56U2KEkpX/L/pCGpCIEf2s on: branch: master - appveyor_repo_tag: true \ No newline at end of file + appveyor_repo_tag: true diff --git a/build.sh b/build.sh index d4d4fbe65a..441470e65d 100755 --- a/build.sh +++ b/build.sh @@ -3,10 +3,8 @@ #exit if any command fails set -e -dotnet restore ./src/JsonApiDotNetCore/JsonApiDotNetCore.csproj -dotnet restore ./src/JsonApiDotNetCoreExample/JsonApiDotNetCoreExample.csproj -dotnet restore ./test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj -dotnet restore ./test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj +dotnet restore +dotnet test ./test/UnitTests/UnitTests.csproj dotnet test ./test/JsonApiDotNetCoreExampleTests/JsonApiDotNetCoreExampleTests.csproj dotnet test ./test/NoEntityFrameworkTests/NoEntityFrameworkTests.csproj \ No newline at end of file diff --git a/docs/Errors.md b/docs/Errors.md index 70102d157d..7b7a8928b0 100644 --- a/docs/Errors.md +++ b/docs/Errors.md @@ -28,8 +28,11 @@ public void MyMethod() { public override async Task PostAsync([FromBody] MyEntity entity) { if(_db.IsFull) - return new ObjectResult(new CustomError("507", "Database is full.", "Theres no more room.", "Sorry.")); + return Error(new CustomError("507", "Database is full.", "Theres no more room.", "Sorry.")); + if(model.Validations.IsValid == false) + return Errors(model.Validations.Select(v => v.GetErrors())); + // ... } ``` \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs index d6ab13ae3e..2ff94b3ef3 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiController.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiController.cs @@ -64,7 +64,7 @@ public virtual async Task GetAsync(TId id) [HttpGet("{id}/relationships/{relationshipName}")] public virtual async Task GetRelationshipsAsync(TId id, string relationshipName) { - var relationship = _resourceService.GetRelationshipAsync(id, relationshipName); + var relationship = _resourceService.GetRelationshipsAsync(id, relationshipName); if(relationship == null) return NotFound(); diff --git a/src/JsonApiDotNetCore/Controllers/JsonApiControllerMixin.cs b/src/JsonApiDotNetCore/Controllers/JsonApiControllerMixin.cs index 85e7425b75..fafc70f161 100644 --- a/src/JsonApiDotNetCore/Controllers/JsonApiControllerMixin.cs +++ b/src/JsonApiDotNetCore/Controllers/JsonApiControllerMixin.cs @@ -1,3 +1,6 @@ +using System.Collections.Generic; +using System.Linq; +using JsonApiDotNetCore.Internal; using Microsoft.AspNetCore.Mvc; namespace JsonApiDotNetCore.Controllers @@ -13,5 +16,37 @@ protected IActionResult Forbidden() { return new StatusCodeResult(403); } + + protected IActionResult Error(Error error) + { + var errorCollection = new ErrorCollection { + Errors = new List { error } + }; + var result = new ObjectResult(errorCollection); + result.StatusCode = error.StatusCode; + + return result; + } + + protected IActionResult Errors(ErrorCollection errors) + { + var result = new ObjectResult(errors); + result.StatusCode = GetErrorStatusCode(errors); + + return result; + } + + private int GetErrorStatusCode(ErrorCollection errors) + { + var statusCodes = errors.Errors + .Select(e => e.StatusCode) + .Distinct() + .ToList(); + + if(statusCodes.Count == 1) + return statusCodes[0]; + + return int.Parse(statusCodes.Max().ToString()[0] + "00"); + } } } diff --git a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs index a9ccac907b..2005478bbf 100644 --- a/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs +++ b/src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs @@ -49,7 +49,10 @@ public DefaultEntityRepository( public virtual IQueryable Get() { - return _dbSet.Select(_jsonApiContext.QuerySet?.Fields); + if(_jsonApiContext.QuerySet?.Fields != null && _jsonApiContext.QuerySet.Fields.Any()) + return _dbSet.Select(_jsonApiContext.QuerySet?.Fields); + + return _dbSet; } public virtual IQueryable Filter(IQueryable entities, FilterQuery filterQuery) @@ -85,9 +88,16 @@ public virtual async Task GetAsync(TId id) public virtual async Task GetAndIncludeAsync(TId id, string relationshipName) { - return await Get() + _logger.LogDebug($"[JADN] GetAndIncludeAsync({id}, {relationshipName})"); + + var result = await Get() .Include(relationshipName) - .SingleOrDefaultAsync(e => e.Id.Equals(id)); + .Where(e => e.Id.Equals(id)) + .ToListAsync(); + + _logger.LogDebug($"[JADN] Found {result.Count} entity"); + + return result.SingleOrDefault(); } public virtual async Task CreateAsync(TEntity entity) diff --git a/src/JsonApiDotNetCore/Internal/ContextGraph.cs b/src/JsonApiDotNetCore/Internal/ContextGraph.cs index fbf97cfc00..7881926c23 100644 --- a/src/JsonApiDotNetCore/Internal/ContextGraph.cs +++ b/src/JsonApiDotNetCore/Internal/ContextGraph.cs @@ -46,7 +46,7 @@ public string GetRelationshipName(string relationshipName) e.EntityType == entityType) .Relationships .FirstOrDefault(r => - r.InternalRelationshipName.ToLower() == relationshipName.ToLower()) + r.PublicRelationshipName.ToLower() == relationshipName.ToLower()) ?.InternalRelationshipName; } } diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index bee09ee5d1..6ae3272ca4 100755 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -1,10 +1,9 @@  - 2.0.2 + 2.0.5 netstandard1.6 JsonApiDotNetCore JsonApiDotNetCore - 1.1.1 $(PackageTargetFallback);dnxcore50;portable-net45+win8 diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index 19ec28ee6a..fbbd4f69e2 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -31,6 +31,11 @@ public object Deserialize(string requestBody) return entity; } + public object Deserialize(string requestBody) + { + return (TEntity)Deserialize(requestBody); + } + public object DeserializeRelationship(string requestBody) { var data = JToken.Parse(requestBody)["data"]; diff --git a/src/JsonApiDotNetCore/Services/EntityResourceService.cs b/src/JsonApiDotNetCore/Services/EntityResourceService.cs index f2c1ef777c..d3c303510b 100644 --- a/src/JsonApiDotNetCore/Services/EntityResourceService.cs +++ b/src/JsonApiDotNetCore/Services/EntityResourceService.cs @@ -87,11 +87,13 @@ public async Task GetRelationshipsAsync(TId id, string relationshipName) public async Task GetRelationshipAsync(TId id, string relationshipName) { relationshipName = _jsonApiContext.ContextGraph - .GetRelationshipName(relationshipName.ToProperCase()); + .GetRelationshipName(relationshipName); if (relationshipName == null) throw new JsonApiException("422", "Relationship name not specified."); + _logger.LogTrace($"Looking up '{relationshipName}'..."); + var entity = await _entities.GetAndIncludeAsync(id, relationshipName); if (entity == null) throw new JsonApiException("404", $"Relationship {relationshipName} not found."); @@ -116,7 +118,7 @@ public async Task UpdateAsync(TId id, T entity) public async Task UpdateRelationshipsAsync(TId id, string relationshipName, List relationships) { relationshipName = _jsonApiContext.ContextGraph - .GetRelationshipName(relationshipName.ToProperCase()); + .GetRelationshipName(relationshipName); if (relationshipName == null) throw new JsonApiException("422", "Relationship name not specified."); diff --git a/test/NoEntityFrameworkTests/appsettings.json b/test/NoEntityFrameworkTests/appsettings.json index 898a1ad601..40ed14033f 100644 --- a/test/NoEntityFrameworkTests/appsettings.json +++ b/test/NoEntityFrameworkTests/appsettings.json @@ -1,6 +1,6 @@ { "Data": { - "DefaultConnection": "Host=localhost;Port=5432;Database=JsonApiDotNetCoreExample;User ID=postgres;Password=postgres" + "DefaultConnection": "Host=localhost;Port=5432;Database=JsonApiDotNetCoreExample;User ID=postgres;Password=" }, "Logging": { "IncludeScopes": false, diff --git a/test/UnitTests/.gitignore b/test/UnitTests/.gitignore new file mode 100644 index 0000000000..0ca27f04e1 --- /dev/null +++ b/test/UnitTests/.gitignore @@ -0,0 +1,234 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ diff --git a/test/UnitTests/JsonApiControllerMixin_Tests.cs b/test/UnitTests/JsonApiControllerMixin_Tests.cs new file mode 100644 index 0000000000..f201b29fcf --- /dev/null +++ b/test/UnitTests/JsonApiControllerMixin_Tests.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Internal; +using Microsoft.AspNetCore.Mvc; +using Xunit; + +namespace UnitTests +{ + public class JsonApiControllerMixin_Tests : JsonApiControllerMixin + { + + [Fact] + public void Errors_Correctly_Infers_Status_Code() + { + // arrange + var errors422 = new ErrorCollection { + Errors = new List { + new Error("422", "bad specific"), + new Error("422", "bad other specific"), + } + }; + + var errors400 = new ErrorCollection { + Errors = new List { + new Error("200", "weird"), + new Error("400", "bad"), + new Error("422", "bad specific"), + } + }; + + var errors500 = new ErrorCollection { + Errors = new List { + new Error("200", "weird"), + new Error("400", "bad"), + new Error("422", "bad specific"), + new Error("500", "really bad"), + new Error("502", "really bad specific"), + } + }; + + + // act + var result422 = this.Errors(errors422); + var result400 = this.Errors(errors400); + var result500 = this.Errors(errors500); + + // assert + var response422 = Assert.IsType(result422); + var response400 = Assert.IsType(result400); + var response500 = Assert.IsType(result500); + + Assert.Equal(422, response422.StatusCode); + Assert.Equal(400, response400.StatusCode); + Assert.Equal(500, response500.StatusCode); + } + } +} diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj new file mode 100644 index 0000000000..b0626bbb85 --- /dev/null +++ b/test/UnitTests/UnitTests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp1.1 + + false + + + + + + + + + + + + +