Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a3a805e
fix schema validation message for no entities
aaronburtle Mar 24, 2026
f99a646
slight refactor to avoid duplicate logs
aaronburtle Mar 25, 2026
72ec3c1
use out param for parallel callers operations
aaronburtle Mar 25, 2026
791c935
format issues
aaronburtle Mar 25, 2026
ba6e433
Merge branch 'main' into dev/aaronburtle/fix-schema-validation-no-ent…
aaronburtle Mar 25, 2026
b72e7f4
change bool name
aaronburtle Mar 25, 2026
2b953b4
Fix LogError/comment issues found in PR review
Copilot Mar 25, 2026
1fa3dc0
Merge branch 'main' into dev/aaronburtle/fix-schema-validation-no-ent…
aaronburtle Mar 27, 2026
e2028cf
Merge branch 'main' into dev/aaronburtle/fix-schema-validation-no-ent…
souvikghosh04 Apr 3, 2026
ff35e28
address comments
aaronburtle Apr 3, 2026
b6e5d9f
Merge branch 'dev/aaronburtle/fix-schema-validation-no-entities' of g…
aaronburtle Apr 3, 2026
d2d61a2
Merge branch 'main' into dev/aaronburtle/fix-schema-validation-no-ent…
aaronburtle Apr 3, 2026
aab6a2f
Merge branch 'main' into dev/aaronburtle/fix-schema-validation-no-ent…
souvikghosh04 Apr 3, 2026
ff3cdff
fix accidental merging
aaronburtle Apr 3, 2026
e9f5463
Merge branch 'dev/aaronburtle/fix-schema-validation-no-entities' of g…
aaronburtle Apr 3, 2026
ad4d38a
cleanup assertions, logic for isParsed
aaronburtle Apr 3, 2026
d24d80a
dont delete file
aaronburtle Apr 6, 2026
e5e92e1
Merge branch 'main' into dev/aaronburtle/fix-schema-validation-no-ent…
souvikghosh04 Apr 8, 2026
46d1cf1
Merge branch 'main' into dev/aaronburtle/fix-schema-validation-no-ent…
aaronburtle Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions src/Cli.Tests/EndToEndTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1093,11 +1093,6 @@ public async Task TestExitOfRuntimeEngineWithInvalidConfig(
{
output = await process.StandardError.ReadLineAsync();
Assert.IsNotNull(output);
StringAssert.Contains(output, $"Deserialization of the configuration file failed.", StringComparison.Ordinal);

output = await process.StandardOutput.ReadLineAsync();
Assert.IsNotNull(output);
StringAssert.Contains(output, $"Error: Failed to parse the config file: {TEST_RUNTIME_CONFIG_FILE}.", StringComparison.Ordinal);

output = await process.StandardOutput.ReadLineAsync();
Assert.IsNotNull(output);
Comment thread
aaronburtle marked this conversation as resolved.
Expand Down
4 changes: 2 additions & 2 deletions src/Cli.Tests/EnvironmentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ public async Task FailureToStartEngineWhenEnvVarNamedWrong()
);

string? output = await process.StandardError.ReadLineAsync();
Assert.AreEqual("Deserialization of the configuration file failed during a post-processing step.", output);
output = await process.StandardError.ReadToEndAsync();
Assert.IsNotNull(output);
// Clean error message with no wrapper prefix or stack trace.
StringAssert.Contains(output, "A valid Connection String should be provided.", StringComparison.Ordinal);
process.Kill();
}
Expand Down
50 changes: 50 additions & 0 deletions src/Cli.Tests/ValidateConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,56 @@ public void TestValidateConfigFailsWithNoEntities()
}
}

/// <summary>
/// Validates that when the config has no entities or autoentities, TryParseConfig
/// sets a clean error message (not a raw exception with stack trace) and
/// IsConfigValid returns false without throwing.
/// Regression test for https://github.com/Azure/data-api-builder/issues/3268
/// </summary>
[TestMethod]
public void TestValidateConfigWithNoEntitiesProducesCleanError()
{
string configWithoutEntities = $"{{{SAMPLE_SCHEMA_DATA_SOURCE},{RUNTIME_SECTION}}}";

// Verify TryParseConfig produces a clean error without stack traces.
RuntimeConfigLoader.LastParseError = null;

StringWriter errorWriter = new();
TextWriter originalError = Console.Error;
Console.SetError(errorWriter);
try
{
bool parsed = RuntimeConfigLoader.TryParseConfig(configWithoutEntities, out _);
Assert.IsFalse(parsed, "Config with no entities should fail to parse.");
}
finally
{
Console.SetError(originalError);
}

// LastParseError should contain the clean validation message.
Assert.IsNotNull(RuntimeConfigLoader.LastParseError,
"LastParseError should be set when config parsing fails.");
StringAssert.Contains(RuntimeConfigLoader.LastParseError,
"Configuration file should contain either at least the entities or autoentities property",
"Parse error should contain the clean validation message.");

// Console.Error output should be clean (no stack trace, no "Deserialization" wrapper).
string stderrOutput = errorWriter.ToString();
Assert.IsFalse(stderrOutput.Contains("Stack Trace"),
"Stack trace should not be present in stderr output.");
Assert.IsFalse(stderrOutput.Contains("Deserialization of the configuration file failed"),
"Deserialization wrapper should not appear for DataApiBuilderException errors.");
StringAssert.Contains(stderrOutput,
"Configuration file should contain either at least the entities or autoentities property",
"stderr should show just the clean error message.");

// Verify IsConfigValid also returns false cleanly (no exception thrown).
((MockFileSystem)_fileSystem!).AddFile(TEST_RUNTIME_CONFIG_FILE, configWithoutEntities);
ValidateOptions validateOptions = new(TEST_RUNTIME_CONFIG_FILE);
Assert.IsFalse(ConfigGenerator.IsConfigValid(validateOptions, _runtimeConfigLoader!, _fileSystem!));
}

/// <summary>
/// This Test is used to verify that the validate command is able to catch when data source field is missing.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Cli/Commands/ValidateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public int Handler(ILogger logger, FileSystemRuntimeConfigLoader loader, IFileSy
}
else
{
logger.LogError("Config is invalid. Check above logs for details.");
logger.LogInformation("Config is invalid.");
}

return isValidConfig ? CliReturnCode.SUCCESS : CliReturnCode.GENERAL_ERROR;
Expand Down
26 changes: 25 additions & 1 deletion src/Cli/ConfigGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2564,7 +2564,9 @@ public static bool TryStartEngineWithOptions(StartOptions options, FileSystemRun
// Replaces all the environment variables while deserializing when starting DAB.
if (!loader.TryLoadKnownConfig(out RuntimeConfig? deserializedRuntimeConfig, replaceEnvVar: true))
{
_logger.LogError("Failed to parse the config file: {runtimeConfigFile}.", runtimeConfigFile);
string? parseError = RuntimeConfigLoader.LastParseError;
RuntimeConfigLoader.LastParseError = null;
_logger.LogError("{parseError}", parseError ?? $"Failed to parse the config file: {runtimeConfigFile}.");
Comment thread
aaronburtle marked this conversation as resolved.
Outdated
return false;
}
else
Expand Down Expand Up @@ -2643,6 +2645,28 @@ public static bool IsConfigValid(ValidateOptions options, FileSystemRuntimeConfi

RuntimeConfigProvider runtimeConfigProvider = new(loader);

// Suppress Console.Error during config loading so that parse errors
// are routed through the CLI's structured logger instead of raw stderr.
TextWriter originalError = Console.Error;
Console.SetError(TextWriter.Null);
bool configLoaded;
try
{
configLoaded = runtimeConfigProvider.TryGetConfig(out RuntimeConfig? _);
}
finally
{
Console.SetError(originalError);
}
Comment thread
aaronburtle marked this conversation as resolved.
Outdated

if (!configLoaded)
{
string? parseError = RuntimeConfigLoader.LastParseError;
RuntimeConfigLoader.LastParseError = null;
_logger.LogError("{parseError}", parseError ?? "Failed to parse the config file.");
return false;
}

ILogger<RuntimeConfigValidator> runtimeConfigValidatorLogger = LoggerFactoryForCli.CreateLogger<RuntimeConfigValidator>();
RuntimeConfigValidator runtimeConfigValidator = new(runtimeConfigProvider, fileSystem, runtimeConfigValidatorLogger, true);

Expand Down
20 changes: 14 additions & 6 deletions src/Config/RuntimeConfigLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ public abstract class RuntimeConfigLoader

public bool IsNewConfigValidated;

/// <summary>
/// Stores the last config parse error message for callers that need
/// to retrieve and log it through their own logging infrastructure.
/// </summary>
public static string? LastParseError { get; set; }

Comment thread
aaronburtle marked this conversation as resolved.
Outdated
public RuntimeConfigLoader(HotReloadEventHandler<HotReloadEventArgs>? handler = null, string? connectionString = null)
{
_changeToken = new DabChangeToken();
Expand Down Expand Up @@ -263,17 +269,19 @@ public static bool TryParseConfig(string json,
ex is JsonException ||
ex is DataApiBuilderException)
{
string errorMessage = ex is JsonException ? "Deserialization of the configuration file failed." :
"Deserialization of the configuration file failed during a post-processing step.";
// Store the parse error so callers (e.g. CLI) can retrieve and
// log it through their own structured logging infrastructure.
LastParseError = ex is DataApiBuilderException
? ex.Message
: $"Deserialization of the configuration file failed. {ex.Message}";

Comment thread
aaronburtle marked this conversation as resolved.
Outdated
// logger can be null when called from CLI
if (logger is null)
if (logger is not null)
{
Console.Error.WriteLine(errorMessage + $"\n" + $"Message:\n {ex.Message}\n" + $"Stack Trace:\n {ex.StackTrace}");
logger.LogError(exception: ex, message: "{ParseError}", LastParseError);
}
else
{
logger.LogError(exception: ex, message: errorMessage);
Console.Error.WriteLine(LastParseError);
}

config = null;
Expand Down
6 changes: 4 additions & 2 deletions src/Service.Tests/Configuration/RuntimeConfigLoaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ public async Task FailLoadMultiDataSourceConfigDuplicateEntities(string configPa
loader.TryLoadConfig("dab-config.json", out RuntimeConfig _);
string error = sw.ToString();

Assert.IsTrue(error.StartsWith("Deserialization of the configuration file failed during a post-processing step."));
Assert.IsTrue(error.Contains("An item with the same key has already been added."));
Assert.IsTrue(error.Contains("An item with the same key has already been added."),
"Error should contain the specific validation message.");
Assert.IsFalse(error.Contains("Stack Trace"),
"Stack trace should not be present in error output.");
}

/// <summary>
Expand Down
Loading