In a [recent PR](https://github.com/openai/codex/pull/9182), I made some improvements to config error messages so errors didn't leave app server clients in a dead state. This is a follow-on PR to make these error messages more readable and actionable for both TUI and GUI users. For example, see #9668 where the user was understandably confused about the source of the problem and how to fix it. The improved error message: 1. Clearly identifies the config file where the error was found (which is more important now that we support layered configs) 2. Provides a line and column number of the error 3. Displays the line where the error occurred and underlines it For example, if my `config.toml` includes the following: ```toml [features] collaboration_modes = "true" ``` Here's the current CLI error message: ``` Error loading config.toml: invalid type: string "true", expected a boolean in `features` ``` And here's the improved message: ``` Error loading config.toml: /Users/etraut/.codex/config.toml:43:23: invalid type: string "true", expected a boolean | 43 | collaboration_modes = "true" | ^^^^^^ ``` The bulk of the new logic is contained within a new module `config_loader/diagnostics.rs` that is responsible for calculating the text range for a given toml path (which is more involved than I would have expected). In addition, this PR adds the file name and text range to the `ConfigWarningNotification` app server struct. This allows GUI clients to present the user with a better error message and an optional link to open the errant config file. This was a suggestion from @.bolinfest when he reviewed my previous PR.
70 lines
2 KiB
Rust
70 lines
2 KiB
Rust
use starlark::Error as StarlarkError;
|
|
use thiserror::Error;
|
|
|
|
pub type Result<T> = std::result::Result<T, Error>;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct TextPosition {
|
|
pub line: usize,
|
|
pub column: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct TextRange {
|
|
pub start: TextPosition,
|
|
pub end: TextPosition,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct ErrorLocation {
|
|
pub path: String,
|
|
pub range: TextRange,
|
|
}
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum Error {
|
|
#[error("invalid decision: {0}")]
|
|
InvalidDecision(String),
|
|
#[error("invalid pattern element: {0}")]
|
|
InvalidPattern(String),
|
|
#[error("invalid example: {0}")]
|
|
InvalidExample(String),
|
|
#[error("invalid rule: {0}")]
|
|
InvalidRule(String),
|
|
#[error(
|
|
"expected every example to match at least one rule. rules: {rules:?}; unmatched examples: \
|
|
{examples:?}"
|
|
)]
|
|
ExampleDidNotMatch {
|
|
rules: Vec<String>,
|
|
examples: Vec<String>,
|
|
},
|
|
#[error("expected example to not match rule `{rule}`: {example}")]
|
|
ExampleDidMatch { rule: String, example: String },
|
|
#[error("starlark error: {0}")]
|
|
Starlark(StarlarkError),
|
|
}
|
|
|
|
impl Error {
|
|
pub fn location(&self) -> Option<ErrorLocation> {
|
|
match self {
|
|
Error::Starlark(err) => err.span().map(|span| {
|
|
let resolved = span.resolve_span();
|
|
ErrorLocation {
|
|
path: span.filename().to_string(),
|
|
range: TextRange {
|
|
start: TextPosition {
|
|
line: resolved.begin.line + 1,
|
|
column: resolved.begin.column + 1,
|
|
},
|
|
end: TextPosition {
|
|
line: resolved.end.line + 1,
|
|
column: resolved.end.column + 1,
|
|
},
|
|
},
|
|
}
|
|
}),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|