diff --git a/src/Core/Models/SqlTypeConstants.cs b/src/Core/Models/SqlTypeConstants.cs index 6315e88936..e7ae40364a 100644 --- a/src/Core/Models/SqlTypeConstants.cs +++ b/src/Core/Models/SqlTypeConstants.cs @@ -47,6 +47,7 @@ public static class SqlTypeConstants { "time", true }, // SqlDbType.Time { "datetime2", true }, // SqlDbType.DateTime2 { "datetimeoffset", true }, // SqlDbType.DateTimeOffset + { "json", true }, // SqlDbType.Json (SQL Server 2025+) treated as string { "", false }, // SqlDbType.Udt and SqlDbType.Structured provided by SQL as empty strings (unsupported) { "numeric", true} // Not present in SqlDbType, however can be returned by sql functions like LAG and should map to decimal. }; diff --git a/src/Core/Resolvers/MsSqlDbExceptionParser.cs b/src/Core/Resolvers/MsSqlDbExceptionParser.cs index 3e4bf32328..008bd99319 100644 --- a/src/Core/Resolvers/MsSqlDbExceptionParser.cs +++ b/src/Core/Resolvers/MsSqlDbExceptionParser.cs @@ -30,7 +30,10 @@ public MsSqlDbExceptionParser(RuntimeConfigProvider configProvider) : base(confi "4435", "4436", "4437", "4438", "4439", "4440", "4441", "4442", "4443", "4444", "4445", "4446", "4447", "4448", "4450", "4451", "4452", "4453", "4454", "4455", "4456", - "4457", "4933", "4934", "4936", "4988", "8102" + "4457", "4933", "4934", "4936", "4988", "8102", + // JSON data type validation errors (SQL Server 2025+). Invalid JSON + // supplied for a json column is a client error, mapped to HTTP 400. + "13608", "13609", "13610", "13611", "13612", "13613", "13614" }); TransientExceptionCodes.UnionWith(new List diff --git a/src/Core/Services/TypeHelper.cs b/src/Core/Services/TypeHelper.cs index 0a95744abb..cc55ce97a3 100644 --- a/src/Core/Services/TypeHelper.cs +++ b/src/Core/Services/TypeHelper.cs @@ -97,6 +97,7 @@ public static class TypeHelper [SqlDbType.Float] = typeof(double), [SqlDbType.Image] = typeof(byte[]), [SqlDbType.Int] = typeof(int), + [SqlDbType.Json] = typeof(string), [SqlDbType.Money] = typeof(decimal), [SqlDbType.NChar] = typeof(char), [SqlDbType.NText] = typeof(string), diff --git a/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs b/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs index c54fe6db3d..199fc24fff 100644 --- a/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs +++ b/src/Service.Tests/OpenApiDocumentor/CLRtoJsonValueTypeUnitTests.cs @@ -111,4 +111,17 @@ public void ResolveUnderlyingTypeForNullableValueType(Type nullableType) Assert.AreNotEqual(notExpected: JsonDataType.Undefined, actual: TypeHelper.GetJsonDataTypeFromSystemType(nullableType)); Assert.IsNotNull(TypeHelper.GetDbTypeFromSystemType(nullableType)); } + + /// + /// Validates that the SQL Server 2025+ 'json' data type literal resolves to the + /// CLR string type and maps to JsonDataType.String, i.e. DAB treats a JSON column + /// just like a string column. + /// + [TestMethod] + public void JsonSqlDbTypeResolvesToString() + { + Type resolvedType = TypeHelper.GetSystemTypeFromSqlDbType("json"); + Assert.AreEqual(typeof(string), resolvedType); + Assert.AreEqual(JsonDataType.String, TypeHelper.GetJsonDataTypeFromSystemType(resolvedType)); + } } diff --git a/src/Service.Tests/UnitTests/DbExceptionParserUnitTests.cs b/src/Service.Tests/UnitTests/DbExceptionParserUnitTests.cs index 02801de3e2..35f827cd8c 100644 --- a/src/Service.Tests/UnitTests/DbExceptionParserUnitTests.cs +++ b/src/Service.Tests/UnitTests/DbExceptionParserUnitTests.cs @@ -94,5 +94,42 @@ public void TestIsTransientExceptionMethod(bool expected, int number) Assert.AreEqual(expected, dbExceptionParser.IsTransientException(SqlTestHelper.CreateSqlException(number))); } + + /// + /// Validates that JSON data type validation error codes raised by SQL Server 2025+ + /// when invalid JSON is supplied for a json column are mapped to HTTP 400 Bad Request. + /// + /// SQL error code populated in SqlException.Number. + [DataTestMethod] + [DataRow(13608, DisplayName = "JSON validation error code 13608 maps to 400")] + [DataRow(13609, DisplayName = "JSON validation error code 13609 maps to 400")] + [DataRow(13610, DisplayName = "JSON validation error code 13610 maps to 400")] + [DataRow(13611, DisplayName = "JSON validation error code 13611 maps to 400")] + [DataRow(13612, DisplayName = "JSON validation error code 13612 maps to 400")] + [DataRow(13613, DisplayName = "JSON validation error code 13613 maps to 400")] + [DataRow(13614, DisplayName = "JSON validation error code 13614 maps to 400")] + public void TestJsonValidationErrorsMapToBadRequest(int number) + { + RuntimeConfig mockConfig = new( + Schema: "", + DataSource: new(DatabaseType.MSSQL, "", new()), + Runtime: new( + Rest: new(), + GraphQL: new(), + Mcp: new(), + Host: new(null, null, HostMode.Development) + ), + Entities: new(new Dictionary()) + ); + MockFileSystem fileSystem = new(); + fileSystem.AddFile(FileSystemRuntimeConfigLoader.DEFAULT_CONFIG_FILE_NAME, new MockFileData(mockConfig.ToJson())); + FileSystemRuntimeConfigLoader loader = new(fileSystem); + RuntimeConfigProvider provider = new(loader); + DbExceptionParser dbExceptionParser = new MsSqlDbExceptionParser(provider); + + Assert.AreEqual( + System.Net.HttpStatusCode.BadRequest, + dbExceptionParser.GetHttpStatusCodeForException(SqlTestHelper.CreateSqlException(number))); + } } }