diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs
index 7e1246ffbb..5cd190d1b0 100644
--- a/src/Service.Tests/Configuration/ConfigurationTests.cs
+++ b/src/Service.Tests/Configuration/ConfigurationTests.cs
@@ -51,6 +51,26 @@ public class ConfigurationTests
private const int RETRY_COUNT = 5;
private const int RETRY_WAIT_SECONDS = 1;
+ ///
+ /// A valid REST API request body with correct parameter types for all the fields.
+ ///
+ public const string REQUEST_BODY_WITH_CORRECT_PARAM_TYPES = @"
+ {
+ ""title"": ""New book"",
+ ""publisher_id"": 1234
+ }
+ ";
+
+ ///
+ /// An invalid REST API request body with incorrect parameter type for publisher_id field.
+ ///
+ public const string REQUEST_BODY_WITH_INCORRECT_PARAM_TYPES = @"
+ {
+ ""title"": ""New book"",
+ ""publisher_id"": ""one""
+ }
+ ";
+
public TestContext TestContext { get; set; }
[TestInitialize]
@@ -946,6 +966,65 @@ public async Task TestPathRewriteMiddlewareForGraphQL(
}
}
+ ///
+ /// Validates the error message that is returned for REST requests with incorrect parameter type
+ /// when the engine is running in Production mode. The error messages in Production mode is
+ /// very generic to not reveal information about the underlying database objects backing the entity.
+ /// This test runs against a MsSql database. However, generic error messages will be returned in Production
+ /// mode when run against PostgreSql and MySql databases.
+ ///
+ /// Type of REST request
+ /// Endpoint for the REST request
+ /// Right error message that should be shown to the end user
+ [DataTestMethod]
+ [TestCategory(TestCategory.MSSQL)]
+ [DataRow(RestMethod.Get, "/api/Book/id/one", null, "Invalid value provided for field: id", DisplayName = "Validates the error message for a GET request with incorrect primary key parameter type on a table in production mode")]
+ [DataRow(RestMethod.Get, "/api/books_view_all/id/one", null, "Invalid value provided for field: id", DisplayName = "Validates the error message for a GET request with incorrect primary key parameter type on a view in production mode")]
+ [DataRow(RestMethod.Get, "/api/GetBook?id=one", REQUEST_BODY_WITH_CORRECT_PARAM_TYPES, "Invalid value provided for field: id", DisplayName = "Validates the error message for a GET request on a stored-procedure with incorrect parameter type in production mode")]
+ [DataRow(RestMethod.Get, "/api/GQLmappings/column1/one", null, "Invalid value provided for field: column1", DisplayName = "Validates the error message for a GET request with incorrect primary key parameter type with alias defined for primary key column on a table in production mode")]
+ [DataRow(RestMethod.Post, "/api/Book", REQUEST_BODY_WITH_INCORRECT_PARAM_TYPES, "Invalid value provided for field: publisher_id", DisplayName = "Validates the error message for a POST request with incorrect parameter type in the request body on a table in production mode")]
+ [DataRow(RestMethod.Put, "/api/Book/id/one", REQUEST_BODY_WITH_CORRECT_PARAM_TYPES, "Invalid value provided for field: id", DisplayName = "Validates the error message for a PUT request with incorrect primary key parameter type on a table in production mode")]
+ [DataRow(RestMethod.Put, "/api/Book/id/1", REQUEST_BODY_WITH_INCORRECT_PARAM_TYPES, "Invalid value provided for field: publisher_id", DisplayName = "Validates the error message for a bad PUT request with incorrect parameter type in the request body on a table in production mode")]
+ [DataRow(RestMethod.Patch, "/api/Book/id/one", REQUEST_BODY_WITH_CORRECT_PARAM_TYPES, "Invalid value provided for field: id", DisplayName = "Validates the error message for a PATCH request with incorrect primary key parameter type on a table in production mode")]
+ [DataRow(RestMethod.Patch, "/api/Book/id/1", REQUEST_BODY_WITH_INCORRECT_PARAM_TYPES, "Invalid value provided for field: publisher_id", DisplayName = "Validates the error message for a PATCH request with incorrect parameter type in the request body on a table in production mode")]
+ [DataRow(RestMethod.Delete, "/api/Book/id/one", REQUEST_BODY_WITH_CORRECT_PARAM_TYPES, "Invalid value provided for field: id", DisplayName = "Validates the error message for a DELETE request with incorrect primary key parameter type on a table in production mode")]
+ public async Task TestGenericErrorMessageForRestApiInProductionMode(
+ RestMethod requestType,
+ string requestPath,
+ string requestBody,
+ string expectedErrorMessage)
+ {
+ const string CUSTOM_CONFIG = "custom-config.json";
+ TestHelper.ConstructNewConfigWithSpecifiedHostMode(CUSTOM_CONFIG, HostModeType.Production, TestCategory.MSSQL);
+ string[] args = new[]
+ {
+ $"--ConfigFileName={CUSTOM_CONFIG}"
+ };
+
+ using (TestServer server = new(Program.CreateWebHostBuilder(args)))
+ using (HttpClient client = server.CreateClient())
+ {
+ HttpMethod httpMethod = SqlTestHelper.ConvertRestMethodToHttpMethod(requestType);
+ HttpRequestMessage request;
+ if (requestType is RestMethod.Get || requestType is RestMethod.Delete)
+ {
+ request = new(httpMethod, requestPath);
+ }
+ else
+ {
+ request = new(httpMethod, requestPath)
+ {
+ Content = JsonContent.Create(requestBody)
+ };
+ }
+
+ HttpResponseMessage response = await client.SendAsync(request);
+ string body = await response.Content.ReadAsStringAsync();
+ Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
+ Assert.IsTrue(body.Contains(expectedErrorMessage));
+ }
+ }
+
///
/// Tests that the when Rest or GraphQL is disabled Globally,
/// any requests made will get a 404 response.
diff --git a/src/Service.Tests/SqlTests/SqlTestHelper.cs b/src/Service.Tests/SqlTests/SqlTestHelper.cs
index 6947cfe076..c78f7e2b1c 100644
--- a/src/Service.Tests/SqlTests/SqlTestHelper.cs
+++ b/src/Service.Tests/SqlTests/SqlTestHelper.cs
@@ -226,7 +226,7 @@ public static HttpMethod GetHttpMethodFromOperation(Config.Operation operationTy
///
///
/// HttpMethod corresponding the RestMethod provided as input.
- private static HttpMethod ConvertRestMethodToHttpMethod(RestMethod? restMethod)
+ public static HttpMethod ConvertRestMethodToHttpMethod(RestMethod? restMethod)
{
switch (restMethod)
{
diff --git a/src/Service.Tests/TestHelper.cs b/src/Service.Tests/TestHelper.cs
index e668c841a6..c29f4ea81c 100644
--- a/src/Service.Tests/TestHelper.cs
+++ b/src/Service.Tests/TestHelper.cs
@@ -237,5 +237,30 @@ public static void AddMissingEntitiesToConfig(RuntimeConfig config, string entit
},
""entities"": {}" +
"}";
+
+ ///
+ /// Utility method that reads the config file for a given database type and constructs a
+ /// new config file with changes just in the host mode section.
+ ///
+ /// Name of the new config file to be constructed
+ /// HostMode for the engine
+ /// Database type
+ public static void ConstructNewConfigWithSpecifiedHostMode(string configFileName, HostModeType hostModeType, string databaseType)
+ {
+ RuntimeConfigProvider configProvider = TestHelper.GetRuntimeConfigProvider(databaseType);
+ RuntimeConfig config = configProvider.GetRuntimeConfiguration();
+ HostGlobalSettings customHostGlobalSettings = config.HostGlobalSettings with { Mode = hostModeType };
+ JsonElement serializedCustomHostGlobalSettings =
+ JsonSerializer.SerializeToElement(customHostGlobalSettings, RuntimeConfig.SerializerOptions);
+ Dictionary customRuntimeSettings = new(config.RuntimeSettings);
+ customRuntimeSettings.Remove(GlobalSettingsType.Host);
+ customRuntimeSettings.Add(GlobalSettingsType.Host, serializedCustomHostGlobalSettings);
+ RuntimeConfig configWithCustomHostMode =
+ config with { RuntimeSettings = customRuntimeSettings };
+ File.WriteAllText(
+ configFileName,
+ JsonSerializer.Serialize(configWithCustomHostMode, RuntimeConfig.SerializerOptions));
+
+ }
}
}
diff --git a/src/Service/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs
index 1f38b029b7..f98d30b5c8 100644
--- a/src/Service/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs
+++ b/src/Service/Resolvers/Sql Query Structures/BaseSqlQueryStructure.cs
@@ -330,30 +330,6 @@ protected List GenerateOutputColumns()
return outputColumns;
}
- ///
- /// Gets the value of the parameter cast as the system type
- /// of the column this parameter is associated with
- ///
- /// columnName is not a valid column of table or param
- /// does not have a valid value type
- protected object GetParamAsColumnSystemType(string param, string columnName)
- {
- Type systemType = GetColumnSystemType(columnName);
- try
- {
- return ParseParamAsSystemType(param, systemType);
- }
- catch (Exception e) when (e is FormatException || e is ArgumentNullException || e is OverflowException)
- {
- throw new DataApiBuilderException(
- message: $"Parameter \"{param}\" cannot be resolved as column \"{columnName}\" " +
- $"with type \"{systemType.Name}\".",
- statusCode: HttpStatusCode.BadRequest,
- subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest,
- innerException: e);
- }
- }
-
///
/// Tries to parse the string parameter to the given system type
/// Useful for inferring parameter types for columns or procedure parameters
@@ -506,5 +482,62 @@ public void ProcessOdataClause(FilterClause odataClause)
{
return filterClause.Expression.Accept(visitor);
}
+
+ ///
+ /// Gets the value of the parameter cast as the system type
+ ///
+ /// Field value as a string
+ /// Field name whose value is being converted to the specified system type. This is used only for constructing the error messages incase of conversion failures
+ /// System type to which the parameter value is parsed to
+ /// The parameter value parsed to the specified system type
+ /// Raised when the conversion of parameter value to the specified system type fails. The error message returned will be different in development
+ /// and production modes. In production mode, the error message returned will be generic so as to not reveal information about the database object backing the entity
+ protected object GetParamAsSystemType(string fieldValue, string fieldName, Type systemType)
+ {
+ try
+ {
+ return ParseParamAsSystemType(fieldValue, systemType);
+ }
+ catch (Exception e) when (e is FormatException || e is ArgumentNullException || e is OverflowException)
+ {
+
+ string errorMessage;
+ SourceType sourceTypeOfDbObject = MetadataProvider.EntityToDatabaseObject[EntityName].SourceType;
+ if (MetadataProvider.IsDevelopmentMode())
+ {
+ if (sourceTypeOfDbObject is SourceType.StoredProcedure)
+ {
+ errorMessage = $@"Parameter ""{fieldValue}"" cannot be resolved as stored procedure parameter ""{fieldName}"" " +
+ $@"with type ""{systemType.Name}"".";
+ }
+ else
+ {
+ errorMessage = $"Parameter \"{fieldValue}\" cannot be resolved as column \"{fieldName}\" " +
+ $"with type \"{systemType.Name}\".";
+ }
+ }
+ else
+ {
+ string fieldNameToBeDisplayedInErrorMessage = fieldName;
+ if (sourceTypeOfDbObject is SourceType.Table || sourceTypeOfDbObject is SourceType.View)
+ {
+ if (MetadataProvider.TryGetExposedColumnName(EntityName, fieldName, out string? exposedName))
+ {
+ fieldNameToBeDisplayedInErrorMessage = exposedName!;
+ }
+ }
+
+ errorMessage = $"Invalid value provided for field: {fieldNameToBeDisplayedInErrorMessage}";
+ }
+
+ throw new DataApiBuilderException(
+ message: errorMessage,
+ statusCode: HttpStatusCode.BadRequest,
+ subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest,
+ innerException: e);
+
+ }
+ }
+
}
}
diff --git a/src/Service/Resolvers/Sql Query Structures/SqlDeleteQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/SqlDeleteQueryStructure.cs
index be434bbcc7..53f974ae2b 100644
--- a/src/Service/Resolvers/Sql Query Structures/SqlDeleteQueryStructure.cs
+++ b/src/Service/Resolvers/Sql Query Structures/SqlDeleteQueryStructure.cs
@@ -53,7 +53,7 @@ public SqlDeleteStructure(
Predicates.Add(new Predicate(
new PredicateOperand(new Column(DatabaseObject.SchemaName, DatabaseObject.Name, backingColumn!)),
PredicateOperation.Equal,
- new PredicateOperand($"{MakeParamWithValue(GetParamAsColumnSystemType(param.Value.ToString()!, backingColumn!))}")
+ new PredicateOperand($"{MakeParamWithValue(GetParamAsSystemType(param.Value.ToString()!, backingColumn!, GetColumnSystemType(backingColumn!)))}")
));
}
}
diff --git a/src/Service/Resolvers/Sql Query Structures/SqlExecuteQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/SqlExecuteQueryStructure.cs
index 856260cb57..f854d0fdb9 100644
--- a/src/Service/Resolvers/Sql Query Structures/SqlExecuteQueryStructure.cs
+++ b/src/Service/Resolvers/Sql Query Structures/SqlExecuteQueryStructure.cs
@@ -44,22 +44,18 @@ public SqlExecuteStructure(
if (requestParams.TryGetValue(paramKey, out object? requestParamValue))
{
// Parameterize, then add referencing parameter to ProcedureParameters dictionary
- try
+ string? parametrizedName = null;
+ if (requestParamValue is not null)
{
- string parameterizedName = MakeParamWithValue(requestParamValue is null ? null :
- GetParamAsProcedureParameterType(requestParamValue.ToString()!, paramKey));
- ProcedureParameters.Add(paramKey, $"{parameterizedName}");
+ Type systemType = GetUnderlyingStoredProcedureDefinition().Parameters[paramKey].SystemType!;
+ parametrizedName = MakeParamWithValue(GetParamAsSystemType(requestParamValue.ToString()!, paramKey, systemType));
}
- catch (ArgumentException ex)
+ else
{
- // In the case GetParamAsProcedureParameterType fails to parse as SystemType from database metadata
- // Keep message being returned to the client more generalized to not expose schema info
- throw new DataApiBuilderException(
- message: $"Invalid value supplied for field: {paramKey}",
- statusCode: HttpStatusCode.BadRequest,
- subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest,
- innerException: ex);
+ parametrizedName = MakeParamWithValue(value: null);
}
+
+ ProcedureParameters.Add(paramKey, $"{parametrizedName}");
}
else
{
@@ -80,30 +76,5 @@ public SqlExecuteStructure(
}
}
}
-
- ///
- /// Gets the value of the parameter cast as the system type
- /// of the stored procedure parameter this parameter is associated with
- ///
- private object GetParamAsProcedureParameterType(string param, string procParamName)
- {
- Type systemType = GetUnderlyingStoredProcedureDefinition().Parameters[procParamName].SystemType!;
- try
- {
- return ParseParamAsSystemType(param, systemType);
- }
- catch (Exception e)
- {
- if (e is FormatException ||
- e is ArgumentNullException ||
- e is OverflowException)
- {
- throw new ArgumentException($@"Parameter ""{param}"" cannot be resolved as stored procedure parameter ""{procParamName}"" " +
- $@"with type ""{systemType.Name}"".", innerException: e);
- }
-
- throw;
- }
- }
}
}
diff --git a/src/Service/Resolvers/Sql Query Structures/SqlInsertQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/SqlInsertQueryStructure.cs
index e5e689097f..c187845ebf 100644
--- a/src/Service/Resolvers/Sql Query Structures/SqlInsertQueryStructure.cs
+++ b/src/Service/Resolvers/Sql Query Structures/SqlInsertQueryStructure.cs
@@ -1,12 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System;
using System.Collections.Generic;
-using System.Net;
using Azure.DataApiBuilder.Auth;
using Azure.DataApiBuilder.Config;
-using Azure.DataApiBuilder.Service.Exceptions;
using Azure.DataApiBuilder.Service.GraphQLBuilder.Mutations;
using Azure.DataApiBuilder.Service.Models;
using Azure.DataApiBuilder.Service.Services;
@@ -90,28 +87,17 @@ private void PopulateColumnsAndParams(string columnName, object? value)
InsertColumns.Add(columnName);
string paramName;
- try
+ if (value is not null)
{
- if (value != null)
- {
- paramName = MakeParamWithValue(
- GetParamAsColumnSystemType(value.ToString()!, columnName));
- }
- else
- {
- paramName = MakeParamWithValue(null);
- }
-
- Values.Add($"{paramName}");
+ paramName = MakeParamWithValue(
+ GetParamAsSystemType(value.ToString()!, columnName, GetColumnSystemType(columnName)));
}
- catch (ArgumentException ex)
+ else
{
- throw new DataApiBuilderException(
- message: ex.Message,
- statusCode: HttpStatusCode.BadRequest,
- subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest,
- innerException: ex);
+ paramName = MakeParamWithValue(value: null);
}
+
+ Values.Add($"{paramName}");
}
///
diff --git a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs
index 0d8865af54..8a21f38f7e 100644
--- a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs
+++ b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs
@@ -482,23 +482,12 @@ public void AddPaginationPredicate(IEnumerable afterJsonValues
return;
}
- try
- {
- foreach (PaginationColumn column in afterJsonValues)
- {
- column.TableAlias = SourceAlias;
- column.ParamName = column.Value is not null ?
- MakeParamWithValue(GetParamAsColumnSystemType(column.Value!.ToString()!, column.ColumnName)) :
- MakeParamWithValue(null);
- }
- }
- catch (ArgumentException ex)
+ foreach (PaginationColumn column in afterJsonValues)
{
- throw new DataApiBuilderException(
- message: ex.Message,
- statusCode: HttpStatusCode.BadRequest,
- subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest,
- innerException: ex);
+ column.TableAlias = SourceAlias;
+ column.ParamName = column.Value is not null ?
+ MakeParamWithValue(GetParamAsSystemType(column.Value!.ToString()!, column.ColumnName, GetColumnSystemType(column.ColumnName))) :
+ MakeParamWithValue(value: null);
}
PaginationMetadata.PaginationPredicate = new KeysetPaginationPredicate(afterJsonValues.ToList());
@@ -520,7 +509,7 @@ private void PopulateParamsAndPredicates(string field, string backingColumn, obj
if (value != null)
{
parameterName = MakeParamWithValue(
- GetParamAsColumnSystemType(value.ToString()!, backingColumn));
+ GetParamAsSystemType(value.ToString()!, backingColumn, GetColumnSystemType(backingColumn)));
Predicates.Add(new Predicate(
new PredicateOperand(new Column(DatabaseObject.SchemaName, DatabaseObject.Name, backingColumn, SourceAlias)),
op,
diff --git a/src/Service/Resolvers/Sql Query Structures/SqlUpdateQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/SqlUpdateQueryStructure.cs
index f10ac8faff..e10aba4bde 100644
--- a/src/Service/Resolvers/Sql Query Structures/SqlUpdateQueryStructure.cs
+++ b/src/Service/Resolvers/Sql Query Structures/SqlUpdateQueryStructure.cs
@@ -182,7 +182,7 @@ private Predicate CreatePredicateForParam(KeyValuePair param)
new PredicateOperand(
new Column(tableSchema: DatabaseObject.SchemaName, tableName: DatabaseObject.Name, param.Key)),
PredicateOperation.Equal,
- new PredicateOperand($"{MakeParamWithValue(GetParamAsColumnSystemType(param.Value.ToString()!, param.Key))}"));
+ new PredicateOperand($"{MakeParamWithValue(GetParamAsSystemType(param.Value.ToString()!, param.Key, GetColumnSystemType(param.Key)))}"));
}
return predicate;
diff --git a/src/Service/Resolvers/Sql Query Structures/SqlUpsertQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/SqlUpsertQueryStructure.cs
index f81b446097..500190cea6 100644
--- a/src/Service/Resolvers/Sql Query Structures/SqlUpsertQueryStructure.cs
+++ b/src/Service/Resolvers/Sql Query Structures/SqlUpsertQueryStructure.cs
@@ -121,9 +121,9 @@ private void PopulateColumns(
MetadataProvider.TryGetBackingColumn(EntityName, param.Key, out string? backingColumn);
// Create Parameter and map it to column for downstream logic to utilize.
string paramIdentifier;
- if (param.Value != null)
+ if (param.Value is not null)
{
- paramIdentifier = MakeParamWithValue(GetParamAsColumnSystemType(param.Value.ToString()!, backingColumn!));
+ paramIdentifier = MakeParamWithValue(GetParamAsSystemType(param.Value.ToString()!, backingColumn!, GetColumnSystemType(backingColumn!)));
}
else
{
diff --git a/src/Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs b/src/Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs
index fb6ee3a6eb..13b4be19aa 100644
--- a/src/Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs
+++ b/src/Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs
@@ -23,6 +23,7 @@ public class CosmosSqlMetadataProvider : ISqlMetadataProvider
private readonly RuntimeConfig _runtimeConfig;
private Dictionary _partitionKeyPaths = new();
private Dictionary _graphQLSingularTypeToEntityNameMap = new();
+ private readonly RuntimeConfigProvider _runtimeConfigProvider;
///
public Dictionary GraphQLStoredProcedureExposedNameToEntityNameMap { get; set; } = new();
@@ -35,6 +36,7 @@ public class CosmosSqlMetadataProvider : ISqlMetadataProvider
public CosmosSqlMetadataProvider(RuntimeConfigProvider runtimeConfigProvider, IFileSystem fileSystem)
{
_fileSystem = fileSystem;
+ _runtimeConfigProvider = runtimeConfigProvider;
_runtimeConfig = runtimeConfigProvider.GetRuntimeConfiguration();
_entities = _runtimeConfig.Entities;
@@ -235,5 +237,10 @@ public string GetDefaultSchemaName()
{
return string.Empty;
}
+
+ public bool IsDevelopmentMode()
+ {
+ return _runtimeConfigProvider.IsDeveloperMode();
+ }
}
}
diff --git a/src/Service/Services/MetadataProviders/ISqlMetadataProvider.cs b/src/Service/Services/MetadataProviders/ISqlMetadataProvider.cs
index b628101dd4..9b09c0af50 100644
--- a/src/Service/Services/MetadataProviders/ISqlMetadataProvider.cs
+++ b/src/Service/Services/MetadataProviders/ISqlMetadataProvider.cs
@@ -159,5 +159,11 @@ public DatabaseObject GetDatabaseObjectForGraphQLType(string graphqlType)
/// Retrieves the default schema name for this metadata provider.
///
public string GetDefaultSchemaName();
+
+ ///
+ /// Returns true when the engine is running in Development mode. When running in Production
+ /// mode, it returns false.
+ ///
+ public bool IsDevelopmentMode();
}
}
diff --git a/src/Service/Services/MetadataProviders/SqlMetadataProvider.cs b/src/Service/Services/MetadataProviders/SqlMetadataProvider.cs
index e5edd2471e..d467b5362c 100644
--- a/src/Service/Services/MetadataProviders/SqlMetadataProvider.cs
+++ b/src/Service/Services/MetadataProviders/SqlMetadataProvider.cs
@@ -1492,6 +1492,11 @@ public bool VerifyForeignKeyExistsInDB(
///
public void SetPartitionKeyPath(string database, string container, string partitionKeyPath)
=> throw new NotImplementedException();
+
+ public bool IsDevelopmentMode()
+ {
+ return _runtimeConfigProvider.IsDeveloperMode();
+ }
}
}