Skip to content

Commit 73098a9

Browse files
Cosmos DB: Throw proper error message if entity is not present in the runtime config (#2272)
## Why make this change? Customers are getting` System.Collections.Generic.KeyNotFoundException` when there configuration is missing for an entity defined in schema file. ## What is this change? As part of this PR, throwing proper message saying, entity is not present in runtimeconfig. Similar changes were made at this PR also #2186 ## How was this tested? - [ ] Integration Tests - [ ] Unit Tests Closes #2266 #887 --------- Co-authored-by: Sean Leonard <[email protected]>
1 parent 9aaf41e commit 73098a9

File tree

4 files changed

+69
-64
lines changed

4 files changed

+69
-64
lines changed

src/Core/Services/MetadataProviders/CosmosSqlMetadataProvider.cs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ private void ParseSchemaGraphQLFieldsForJoins()
161161
{
162162
string modelName = GraphQLNaming.ObjectTypeToEntityName(node);
163163

164+
AssertIfEntityIsAvailableInConfig(modelName);
165+
164166
if (EntityWithJoins.TryGetValue(modelName, out List<EntityDbPolicyCosmosModel>? entityWithJoins))
165167
{
166168
entityWithJoins.Add(new(Path: CosmosQueryStructure.COSMOSDB_CONTAINER_DEFAULT_ALIAS, EntityName: modelName));
@@ -222,6 +224,9 @@ private void ProcessSchema(
222224
}
223225

224226
string entityType = field.Type.NamedType().Name.Value;
227+
228+
AssertIfEntityIsAvailableInConfig(entityType);
229+
225230
// If the entity is already visited, then it is a circular reference
226231
if (!trackerForFields.Add(entityType))
227232
{
@@ -241,11 +246,12 @@ private void ProcessSchema(
241246
}
242247

243248
EntityDbPolicyCosmosModel currentEntity = new(
244-
Path: currentPath,
245-
EntityName: entityType,
246-
ColumnName: field.Name.Value,
247-
Alias: alias);
249+
Path: currentPath,
250+
EntityName: entityType,
251+
ColumnName: field.Name.Value,
252+
Alias: alias);
248253

254+
// If entity is defined in the runtime config, only then generate Join for this entity
249255
if (EntityWithJoins.ContainsKey(entityType))
250256
{
251257
EntityWithJoins[entityType].Add(currentEntity);
@@ -255,7 +261,7 @@ private void ProcessSchema(
255261
EntityWithJoins.Add(
256262
entityType,
257263
new List<EntityDbPolicyCosmosModel>() {
258-
currentEntity
264+
currentEntity
259265
});
260266
}
261267

@@ -282,6 +288,18 @@ private void ProcessSchema(
282288
}
283289
}
284290

291+
private void AssertIfEntityIsAvailableInConfig(string entityName)
292+
{
293+
// If the entity is not present in the runtime config, throw an exception as we are expecting all the entities to be present in the runtime config.
294+
if (!_runtimeConfig.Entities.ContainsKey(entityName))
295+
{
296+
throw new DataApiBuilderException(
297+
message: $"The entity '{entityName}' was not found in the runtime config.",
298+
statusCode: System.Net.HttpStatusCode.ServiceUnavailable,
299+
subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError);
300+
}
301+
}
302+
285303
/// <inheritdoc />
286304
public string GetDatabaseObjectName(string entityName)
287305
{

src/Service.Tests/Configuration/ConfigurationTests.cs

Lines changed: 42 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
using System.Threading.Tasks;
2020
using System.Web;
2121
using Azure.DataApiBuilder.Config;
22-
using Azure.DataApiBuilder.Config.NamingPolicies;
2322
using Azure.DataApiBuilder.Config.ObjectModel;
2423
using Azure.DataApiBuilder.Core.AuthenticationHelpers;
2524
using Azure.DataApiBuilder.Core.Authorization;
@@ -432,7 +431,7 @@ type Character {
432431
moons: [Moon],
433432
}
434433
435-
type Planet @model(name:""Planet"") {
434+
type Planet @model(name:""PlanetAlias"") {
436435
id : ID!,
437436
name : String,
438437
character: Character
@@ -453,7 +452,7 @@ type Character {
453452
moons: Moon,
454453
}
455454
456-
type Planet @model(name:""Planet"") {
455+
type Planet @model(name:""PlanetAlias"") {
457456
id : ID!,
458457
name : String,
459458
character: Character
@@ -3068,67 +3067,55 @@ public void ValidateGraphQLSchemaForCircularReference(string schema)
30683067
}
30693068

30703069
/// <summary>
3071-
/// When you query, DAB loads schema and check for defined entities in the config file which get load during DAB initialization, and
3072-
/// it fails during this check if entity is not defined in the config file. In this test case, we are testing the error message is appropriate.
3070+
/// GraphQL Schema types defined -> Character and Planet
3071+
/// DAB runtime config entities defined -> Planet(Not defined: Character)
3072+
/// Mismatch of entities and types between provided GraphQL schema file and DAB config results in actionable error message.
30733073
/// </summary>
3074+
/// <exception cref="ApplicationException"></exception>
30743075
[TestMethod, TestCategory(TestCategory.COSMOSDBNOSQL)]
3075-
public async Task TestErrorMessageWithoutKeyFieldsInConfig()
3076+
public void ValidateGraphQLSchemaEntityPresentInConfig()
30763077
{
3077-
Dictionary<string, object> dbOptions = new();
3078-
HyphenatedNamingPolicy namingPolicy = new();
3079-
3080-
dbOptions.Add(namingPolicy.ConvertName(nameof(CosmosDbNoSQLDataSourceOptions.Database)), "graphqldb");
3081-
dbOptions.Add(namingPolicy.ConvertName(nameof(CosmosDbNoSQLDataSourceOptions.Container)), "dummy");
3082-
dbOptions.Add(namingPolicy.ConvertName(nameof(CosmosDbNoSQLDataSourceOptions.Schema)), "custom-schema.gql");
3083-
3084-
DataSource dataSource = new(DatabaseType.CosmosDB_NoSQL,
3085-
GetConnectionStringFromEnvironmentConfig(environment: TestCategory.COSMOSDBNOSQL), Options: dbOptions);
3086-
3087-
// Add a dummy entity in config file just to make sure the config file is valid.
3088-
Entity entity = new(
3089-
Source: new("EntityName", EntitySourceType.Table, null, null),
3090-
Rest: new(Enabled: false),
3091-
GraphQL: new("", ""),
3092-
Permissions: new[] { GetMinimalPermissionConfig(AuthorizationResolver.ROLE_ANONYMOUS) },
3093-
Relationships: null,
3094-
Mappings: null
3095-
);
3096-
RuntimeConfig configuration = InitMinimalRuntimeConfig(dataSource, new(), new(), entity, "EntityName");
3097-
3098-
const string CUSTOM_CONFIG = "custom-config.json";
3099-
3100-
File.WriteAllText(CUSTOM_CONFIG, configuration.ToJson());
3078+
string GRAPHQL_SCHEMA = @"
3079+
type Character {
3080+
id : ID,
3081+
name : String,
3082+
}
31013083
3102-
string[] args = new[]
3084+
type Planet @model(name:""PlanetAlias"") {
3085+
id : ID!,
3086+
name : String,
3087+
characters : [Character]
3088+
}";
3089+
// Read the base config from the file system
3090+
TestHelper.SetupDatabaseEnvironment(TestCategory.COSMOSDBNOSQL);
3091+
FileSystemRuntimeConfigLoader baseLoader = TestHelper.GetRuntimeConfigLoader();
3092+
if (!baseLoader.TryLoadKnownConfig(out RuntimeConfig baseConfig))
31033093
{
3104-
$"--ConfigFileName={CUSTOM_CONFIG}"
3105-
};
3094+
throw new ApplicationException("Failed to load the default CosmosDB_NoSQL config and cannot continue with tests.");
3095+
}
31063096

3107-
using (TestServer server = new(Program.CreateWebHostBuilder(args)))
3108-
using (HttpClient client = server.CreateClient())
3109-
{
3110-
// When you query, DAB loads schema and check for defined entities in the config file and
3111-
// it fails during that, since entity is not defined in the config file.
3112-
string query = @"{
3113-
Planet {
3114-
items{
3115-
id
3116-
}
3117-
}
3118-
}";
3097+
Dictionary<string, Entity> entities = new(baseConfig.Entities);
3098+
entities.Remove("Character");
31193099

3120-
object payload = new { query };
3100+
RuntimeConfig runtimeConfig = new(Schema: baseConfig.Schema,
3101+
DataSource: baseConfig.DataSource,
3102+
Runtime: baseConfig.Runtime,
3103+
Entities: new(entities));
31213104

3122-
HttpRequestMessage graphQLRequest = new(HttpMethod.Post, "/graphql")
3123-
{
3124-
Content = JsonContent.Create(payload)
3125-
};
3105+
// Setup a mock file system, and use that one with the loader/provider for the config
3106+
MockFileSystem fileSystem = new(new Dictionary<string, MockFileData>()
3107+
{
3108+
{ @"../schema.gql", new MockFileData(GRAPHQL_SCHEMA) },
3109+
{ DEFAULT_CONFIG_FILE_NAME, new MockFileData(runtimeConfig.ToJson()) }
3110+
});
3111+
FileSystemRuntimeConfigLoader loader = new(fileSystem);
3112+
RuntimeConfigProvider provider = new(loader);
31263113

3127-
DataApiBuilderException ex = await Assert.ThrowsExceptionAsync<DataApiBuilderException>(async () => await client.SendAsync(graphQLRequest));
3128-
Assert.AreEqual("The entity 'Planet' was not found in the runtime config.", ex.Message);
3129-
Assert.AreEqual(HttpStatusCode.ServiceUnavailable, ex.StatusCode);
3130-
Assert.AreEqual(DataApiBuilderException.SubStatusCodes.ConfigValidationError, ex.SubStatusCode);
3131-
}
3114+
DataApiBuilderException exception =
3115+
Assert.ThrowsException<DataApiBuilderException>(() => new CosmosSqlMetadataProvider(provider, fileSystem));
3116+
Assert.AreEqual("The entity 'Character' was not found in the runtime config.", exception.Message);
3117+
Assert.AreEqual(HttpStatusCode.ServiceUnavailable, exception.StatusCode);
3118+
Assert.AreEqual(DataApiBuilderException.SubStatusCodes.ConfigValidationError, exception.SubStatusCode);
31323119
}
31333120

31343121
/// <summary>

src/Service.Tests/Multidab-config.CosmosDb_NoSql.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
}
3434
},
3535
"entities": {
36-
"Planet": {
36+
"PlanetAlias": {
3737
"source": {
3838
"object": "graphqldb.planet"
3939
},
@@ -124,7 +124,7 @@
124124
}
125125
]
126126
},
127-
"StarAlias": {
127+
"Star": {
128128
"source": {
129129
"object": "graphqldb.star"
130130
},

src/Service.Tests/schema.gql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ type Character @model(name:"Character") {
77
star: Star
88
}
99

10-
type Planet @model(name:"Planet"){
10+
type Planet @model(name:"PlanetAlias"){
1111
id : ID,
1212
name : String,
1313
character: Character,
@@ -16,7 +16,7 @@ type Planet @model(name:"Planet"){
1616
stars: [Star]
1717
}
1818

19-
type Star @model(name:"StarAlias"){
19+
type Star {
2020
id : ID,
2121
name : String
2222
}

0 commit comments

Comments
 (0)