diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
new file mode 100644
index 0000000..75811a2
--- /dev/null
+++ b/.github/workflows/CI.yml
@@ -0,0 +1,29 @@
+name: CI
+
+on:
+ pull_request:
+ branches: [ "main" ]
+ push:
+ branches: [ "main" ]
+
+jobs:
+ build-and-test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build --no-restore --configuration Release
+
+ - name: Run tests
+ run: dotnet test --no-build --configuration Release --verbosity normal
diff --git a/.gitignore b/.gitignore
index 3fc3b2a..92e6d18 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,7 +11,8 @@
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
-[Rr]eleases/
+Releases/
+!releases/
x64/
x86/
[Ww][Ii][Nn]32/
@@ -33,6 +34,7 @@ ScaffoldingReadMe.txt
# NuGet Packages
*.nupkg
+!releases/*.nupkg
# NuGet Symbol Packages
*.snupkg
diff --git a/Directory.Build.props b/Directory.Build.props
index ab5b304..48358df 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -5,9 +5,9 @@
true
enable
enable
- net8.0;net9.0
- 13
+ net10.0
+ 14
-
+
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 7943cd7..a47b9c1 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,7 +4,18 @@
true
-
-
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/RestExceptions.slnx b/RestExceptions.slnx
index 755a2b4..65ffb2b 100644
--- a/RestExceptions.slnx
+++ b/RestExceptions.slnx
@@ -1,6 +1,6 @@
-
+
@@ -13,7 +13,13 @@
+
+
+
-
+
+
+
+
\ No newline at end of file
diff --git a/examples/RestExceptions.Demo/Program.cs b/examples/RestExceptions.Demo/Program.cs
index 8a1f784..744f036 100644
--- a/examples/RestExceptions.Demo/Program.cs
+++ b/examples/RestExceptions.Demo/Program.cs
@@ -65,3 +65,6 @@
app.UseHttpsRedirection();
app.Run();
+
+// Needed so WebApplicationFactory in tests can find the entry point
+public partial class Program;
diff --git a/releases/RestExceptions.10.0.0.nupkg b/releases/RestExceptions.10.0.0.nupkg
new file mode 100644
index 0000000..72d7298
Binary files /dev/null and b/releases/RestExceptions.10.0.0.nupkg differ
diff --git a/releases/RestExceptions.9.0.11.nupkg b/releases/RestExceptions.9.0.11.nupkg
new file mode 100644
index 0000000..cbeaa49
Binary files /dev/null and b/releases/RestExceptions.9.0.11.nupkg differ
diff --git a/releases/RestExceptions.9.0.7.1.nupkg b/releases/RestExceptions.9.0.7.1.nupkg
new file mode 100644
index 0000000..4c64f51
Binary files /dev/null and b/releases/RestExceptions.9.0.7.1.nupkg differ
diff --git a/releases/RestExceptions.9.0.7.2.nupkg b/releases/RestExceptions.9.0.7.2.nupkg
new file mode 100644
index 0000000..3890654
Binary files /dev/null and b/releases/RestExceptions.9.0.7.2.nupkg differ
diff --git a/releases/RestExceptions.9.0.7.3.nupkg b/releases/RestExceptions.9.0.7.3.nupkg
new file mode 100644
index 0000000..75275d8
Binary files /dev/null and b/releases/RestExceptions.9.0.7.3.nupkg differ
diff --git a/releases/RestExceptions.9.0.7.4.nupkg b/releases/RestExceptions.9.0.7.4.nupkg
new file mode 100644
index 0000000..22af6fd
Binary files /dev/null and b/releases/RestExceptions.9.0.7.4.nupkg differ
diff --git a/releases/RestExceptions.9.0.7.nupkg b/releases/RestExceptions.9.0.7.nupkg
new file mode 100644
index 0000000..f823fb4
Binary files /dev/null and b/releases/RestExceptions.9.0.7.nupkg differ
diff --git a/releases/RestExceptions.9.0.8.nupkg b/releases/RestExceptions.9.0.8.nupkg
new file mode 100644
index 0000000..11b43a6
Binary files /dev/null and b/releases/RestExceptions.9.0.8.nupkg differ
diff --git a/releases/RestExceptions.9.0.9.nupkg b/releases/RestExceptions.9.0.9.nupkg
new file mode 100644
index 0000000..bf1bcf8
Binary files /dev/null and b/releases/RestExceptions.9.0.9.nupkg differ
diff --git a/src/RestExceptions/Exceptions/4xx/BadRequestRestException.cs b/src/RestExceptions/Exceptions/4xx/BadRequestRestException.cs
index da83709..cbb22c0 100644
--- a/src/RestExceptions/Exceptions/4xx/BadRequestRestException.cs
+++ b/src/RestExceptions/Exceptions/4xx/BadRequestRestException.cs
@@ -10,6 +10,6 @@ public class BadRequestRestException(
{
public static string DefaultMessage => "Malformed request syntax.";
- public override string Title => "Bad request";
+ public override string Title => "Bad Request";
public override HttpStatusCode StatusCode => HttpStatusCode.BadRequest;
}
diff --git a/src/RestExceptions/RestExceptions.csproj b/src/RestExceptions/RestExceptions.csproj
index da43fcc..d2a636a 100644
--- a/src/RestExceptions/RestExceptions.csproj
+++ b/src/RestExceptions/RestExceptions.csproj
@@ -2,7 +2,7 @@
RestExceptions
RestExceptions
- 9.0.11
+ 10.0.0
Stratis-Dermanoutsos
Stratis-OSS
@@ -16,17 +16,12 @@
Extensible Web API middleware that maps all exceptions to standardized RFC7807-compliant HTTP responses.
open-source http oss rest errors csharp dotnet exceptions problem-details rfc7807
README.md
- * Updated dependencies to latest versions.
+ * Upgraded to .NET 10 and C# 14.
true
-
-
-
-
-
diff --git a/tests/RestExceptions.IntegrationTests/ApiFactory.cs b/tests/RestExceptions.IntegrationTests/ApiFactory.cs
new file mode 100644
index 0000000..883ad41
--- /dev/null
+++ b/tests/RestExceptions.IntegrationTests/ApiFactory.cs
@@ -0,0 +1,14 @@
+using Microsoft.AspNetCore.Mvc.Testing;
+
+namespace RestExceptions.IntegrationTests;
+
+public class ApiFactory : WebApplicationFactory
+{
+ public HttpClient CreateHttpsClient()
+ {
+ return CreateClient(new WebApplicationFactoryClientOptions
+ {
+ BaseAddress = new Uri("https://localhost")
+ });
+ }
+}
diff --git a/tests/RestExceptions.IntegrationTests/RestExceptions.IntegrationTests.csproj b/tests/RestExceptions.IntegrationTests/RestExceptions.IntegrationTests.csproj
new file mode 100644
index 0000000..49c63f5
--- /dev/null
+++ b/tests/RestExceptions.IntegrationTests/RestExceptions.IntegrationTests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/RestExceptions.IntegrationTests/Services/Abstractions/ApiTests.cs b/tests/RestExceptions.IntegrationTests/Services/Abstractions/ApiTests.cs
new file mode 100644
index 0000000..fc740a0
--- /dev/null
+++ b/tests/RestExceptions.IntegrationTests/Services/Abstractions/ApiTests.cs
@@ -0,0 +1,18 @@
+using System.Net.Http.Json;
+using Microsoft.AspNetCore.Mvc;
+
+// ReSharper disable once CheckNamespace
+namespace RestExceptions.IntegrationTests.Services;
+
+public abstract class ApiTests(ApiFactory factory)
+{
+ protected readonly HttpClient Client = factory.CreateHttpsClient();
+
+ protected async Task GetProblemDetailsAsync(string path, int statusCode)
+ {
+ var resp = await Client.GetAsync(Path.Join(path, statusCode.ToString()));
+
+ var result = await resp.Content.ReadFromJsonAsync();
+ return result;
+ }
+}
diff --git a/tests/RestExceptions.IntegrationTests/Services/RestExceptionsTests.cs b/tests/RestExceptions.IntegrationTests/Services/RestExceptionsTests.cs
new file mode 100644
index 0000000..138e40f
--- /dev/null
+++ b/tests/RestExceptions.IntegrationTests/Services/RestExceptionsTests.cs
@@ -0,0 +1,56 @@
+using Shouldly;
+
+namespace RestExceptions.IntegrationTests.Services;
+
+public class RestExceptionsTests(ApiFactory factory) : ApiTests(factory), IClassFixture
+{
+ [Theory]
+ [InlineData(0, "Internal Server Error")]
+ [InlineData(400, "Bad Request")]
+ [InlineData(401, "Unauthorized")]
+ [InlineData(402, "Payment Required")]
+ [InlineData(403, "Forbidden")]
+ [InlineData(404, "Not Found")]
+ [InlineData(405, "Method Not Allowed")]
+ [InlineData(406, "Not Acceptable")]
+ [InlineData(407, "Proxy Authentication Required")]
+ [InlineData(408, "Request Timeout")]
+ [InlineData(409, "Conflict")]
+ [InlineData(410, "Gone")]
+ [InlineData(411, "Length Required")]
+ [InlineData(412, "Precondition Failed")]
+ [InlineData(413, "Content Too Large")]
+ [InlineData(414, "URI Too Long")]
+ [InlineData(415, "Unsupported Media Type")]
+ [InlineData(416, "Range Not Satisfiable")]
+ [InlineData(417, "Expectation Failed")]
+ [InlineData(421, "Misdirected Request")]
+ [InlineData(422, "Unprocessable Content")]
+ [InlineData(423, "Locked")]
+ [InlineData(424, "Failed Dependency")]
+ [InlineData(426, "Upgrade Required")]
+ [InlineData(428, "Precondition Required")]
+ [InlineData(429, "Too Many Requests")]
+ [InlineData(431, "Request Header Fields Too Large")]
+ [InlineData(451, "Unavailable For Legal Reasons")]
+ [InlineData(500, "Internal Server Error")]
+ [InlineData(501, "Not Implemented")]
+ [InlineData(502, "Bad Gateway")]
+ [InlineData(503, "Service Unavailable")]
+ [InlineData(504, "Gateway Timeout")]
+ [InlineData(505, "HTTP Version Not Supported")]
+ [InlineData(506, "Variant Also Negotiates")]
+ [InlineData(507, "Insufficient Storage")]
+ [InlineData(508, "Loop Detected")]
+ [InlineData(510, "Not Extended")]
+ [InlineData(511, "Network Authentication Required")]
+ public async Task Status_Code_To_Http_Error_Title(int statusCode, string expected)
+ {
+ // Act
+ var problemDetails = await GetProblemDetailsAsync("error", statusCode);
+ var result = problemDetails?.Title ?? string.Empty;
+
+ // Assert
+ result.ShouldBe(expected);
+ }
+}