Diagnostics
When Bodu.Text.Configuration encounters a problem it cannot silently resolve, it produces a ConfigurationDiagnostic - a structured record naming the diagnostic code, severity, source location, and a human-readable message. This guide covers the diagnostic surface end-to-end: how the parser surfaces diagnostics, how the modes interact, the severity scale, and the full catalogue of ConfigurationDiagnosticCode values.
For the parse-time options that control diagnostic behaviour - DiagnosticMode, the profile presets - see Parsing and profiles. For the view-time surface, see Views and resolution.
Diagnostic mode
ConfigurationDiagnosticMode determines what the parser does when it encounters a recoverable error:
| Mode | Effect |
|---|---|
Throw (default) |
The first recoverable error raises ConfigurationParseException carrying a single ConfigurationDiagnostic. Parsing stops. |
Collect |
The parser continues past recoverable errors. Diagnostics accumulate on the ConfigurationParseResult.Diagnostics list. |
Ignore |
Recoverable diagnostics are dropped silently. Non-recoverable errors still throw. |
The Bodu, EditorConfigCompatible, and Strict profiles use Throw. The Relaxed profile uses Collect - handy for IDE-style validation where you want to surface every issue at once.
using Bodu.Text.Configuration;
ConfigurationParseResult result = ConfigurationDocument.ParseWithDiagnostics(
source,
new ConfigurationParseOptions { DiagnosticMode = ConfigurationDiagnosticMode.Collect });
foreach (ConfigurationDiagnostic d in result.Diagnostics)
{
Console.WriteLine($"{d.Severity} {d.Code} at {d.Location}: {d.Message}");
}
ConfigurationDocument document = result.Document; // still populated, on a best-effort basis
Diagnostic severity
ConfigurationDiagnosticSeverity classifies each diagnostic:
| Severity | Meaning |
|---|---|
Info |
Informational observation; the parse continues normally. |
Warning |
Non-fatal warning; the parse continues, the document is still produced. |
Error |
Recoverable error; under Collect mode the parse continues, under Throw mode it stops. |
The severity is fixed per diagnostic code - there is no "promote warnings to errors" knob.
The ConfigurationDiagnostic type
ConfigurationDiagnostic is an immutable sealed class with four read-only properties, constructed from severity, code, message, and location:
var diagnostic = new ConfigurationDiagnostic(
ConfigurationDiagnosticSeverity.Warning,
ConfigurationDiagnosticCode.DuplicateKey,
"Key 'indent_size' is declared twice.",
new ConfigurationSourceLocation(lineNumber: 12, linePosition: 1, length: 11));
Location is a ConfigurationSourceLocation carrying the 1-based LineNumber and LinePosition (only LineNumber is reliably populated; position and length are approximate) plus an optional Path set when the document was loaded from a file. The default ToString() returns "{Severity} {Code} at {Location}: {Message}", suitable for log lines and IDE diagnostics.
The ConfigurationDiagnosticCode catalogue
Stable codes - the values do not change across versions, so consumers can build IDE squiggle rules or build-time enforcement around them.
Structural errors
| Code | Trigger |
|---|---|
MissingEquals (1) |
A property line had no = separator (e.g. key value instead of key = value). |
EmptyKey (2) |
A property line declared an empty key (= value). |
UnterminatedSectionHeader (4) |
A section opened with [ but no closing ] appeared. |
EmptySectionHeader (5) |
A section header had no name between [ and ]. |
TrailingContentAfterSectionHeader (14) |
Text appeared after ] when SectionHeaderMode did not allow it. |
Duplicate handling
| Code | Trigger |
|---|---|
DuplicateKey (3) |
A key appeared more than once in a section when DuplicateKeyMode = Disallowed. |
DuplicateSection (12) |
A section name appeared more than once when DuplicateSectionMode = Disallowed. |
Key validation
| Code | Trigger |
|---|---|
InvalidKeyCharacter (8) |
A key contained an illegal character (depends on KeyOptions). |
InvalidEscape (9) |
A malformed escape sequence (e.g. \x followed by non-hex) appeared in a value. |
KeyTooLong (11) |
The key exceeded MaxKeyLength (default 1024 chars). |
LineTooLong (10) |
The line exceeded MaxLineLength (default 8192 chars). |
Glob-pattern compilation
These fire when a section header is compiled as a ConfigurationPattern:
| Code | Trigger |
|---|---|
UnbalancedBrace (6) |
A { in a section glob had no matching }. |
UnbalancedBracket (7) |
A [ in a section glob had no matching ]. |
NumericRangeTooLarge (13) |
A {n1..n2} range expanded to more than the parser's expansion cap. |
BraceNestingTooDeep (15) |
Brace nesting exceeded the parser's nesting cap. |
PatternTooLong (16) |
The compiled glob pattern exceeded the max compilable length. |
Default code
None (0) - no specific code. Reserved for diagnostics produced from custom validators that do not have a catalogue entry; not used by the shipped parser.
Diagnostics vs exceptions
The parser raises three kinds of exception:
ConfigurationParseException(aFormatException) - raised whenDiagnosticMode = Throwhits the first recoverable error, or for any non-recoverable error. Exposes the primaryDiagnosticand the fullDiagnosticsarray of everything gathered before the failure;Locationforwards to the primary diagnostic.ArgumentException/ArgumentNullException- for invalid inputs (null source, unreadable stream). Standard BCL contract.InvalidOperationException- raised byResolvewhen the options require a path root (MissingPathRootMode.Throw), the document carries none, and no target path is supplied.
Diagnostics under Collect and Ignore modes never throw - the parser carries on, populates the result document on a best-effort basis, and emits the diagnostic list for the caller to inspect.
Working example - IDE-style validation
using Bodu.Text.Configuration;
// Parse with diagnostic collection so every issue is surfaced, not just the first.
ConfigurationParseResult result = ConfigurationDocument.ParseWithDiagnostics(
source,
ConfigurationParseOptions.Relaxed);
foreach (ConfigurationDiagnostic d in result.Diagnostics)
{
switch (d.Severity)
{
case ConfigurationDiagnosticSeverity.Info:
log.Info($"{d.Code} at line {d.Location.LineNumber}: {d.Message}");
break;
case ConfigurationDiagnosticSeverity.Warning:
log.Warn($"{d.Code} at line {d.Location.LineNumber}: {d.Message}");
break;
case ConfigurationDiagnosticSeverity.Error:
log.Error($"{d.Code} at line {d.Location.LineNumber}: {d.Message}");
break;
}
}
if (result.Diagnostics.All(d => d.Severity != ConfigurationDiagnosticSeverity.Error))
{
Configure(result.Document);
}
The Relaxed profile is the right starting point for code-style validators and IDE plug-ins: it surfaces every recoverable issue without stopping the parse, and the resulting document is still usable for downstream analysis.
See also
- Parsing and profiles - how
DiagnosticModeinteracts with the profile defaults. - Views and resolution -
ConfigurationView, key projection, typed lookup. Bodu.Text.ConfigurationAPI reference.- Configuration guides - every guide in this topic, across Bodu.Text.Configuration and Bodu.Extensions.Configuration.Text.