walker: diagnostics-severity model + input_verdict (ADR-0027 step A)
Adds `Severity` (Error / Warning, ordered so Error > Warning)
and `Diagnostic { severity, span, message }` in
`walker::outcome`, plus a `diagnostics` field on `WalkResult`
— the schema-aware findings layered on a structurally-valid
parse (ADR-0027 §2).
`input_verdict(source, schema)` is the validity-indicator
entry point: `None` when the input would run clean (and for
empty input), `Some(Error)` for a parse failure or unknown
command, `Some(Warning)` for the ADR-0026 expression flags.
The verdict is the highest severity across the parse outcome
and the diagnostics set.
`diagnostics` is empty at this step — the schema-existence
(ERROR) and expression (WARNING) passes that fill it land
next. Covered by `input_verdict` unit tests.
This commit is contained in:
@@ -29,6 +29,7 @@ use crate::dsl::walker::outcome::{
|
|||||||
|
|
||||||
pub use context::ColumnInfo;
|
pub use context::ColumnInfo;
|
||||||
pub use highlight::highlight_runs;
|
pub use highlight::highlight_runs;
|
||||||
|
pub use outcome::{Diagnostic, Severity};
|
||||||
|
|
||||||
/// Resolve the hint-panel mode at the end of `source`
|
/// Resolve the hint-panel mode at the end of `source`
|
||||||
/// (ADR-0024 §HintMode-per-node, §Phase D §typed-value-slots).
|
/// (ADR-0024 §HintMode-per-node, §Phase D §typed-value-slots).
|
||||||
@@ -291,6 +292,44 @@ pub fn completion_probe(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The validity-indicator verdict for `source` (ADR-0027 §3).
|
||||||
|
///
|
||||||
|
/// `None` — the input would run clean (the indicator shows
|
||||||
|
/// nothing); empty / whitespace-only input is also `None`.
|
||||||
|
/// `Some(Error)` — pressing Enter now fails (a structural
|
||||||
|
/// parse failure, or a schema-existence diagnostic).
|
||||||
|
/// `Some(Warning)` — it runs, but is very likely not intended
|
||||||
|
/// (the ADR-0026 expression flags).
|
||||||
|
///
|
||||||
|
/// The verdict is the highest severity across the parse
|
||||||
|
/// outcome and the `diagnostics` set (ADR-0027 §2).
|
||||||
|
#[must_use]
|
||||||
|
pub fn input_verdict(
|
||||||
|
source: &str,
|
||||||
|
schema: Option<&crate::completion::SchemaCache>,
|
||||||
|
) -> Option<outcome::Severity> {
|
||||||
|
use outcome::Severity;
|
||||||
|
if source.trim().is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut ctx = schema.map_or_else(
|
||||||
|
context::WalkContext::new,
|
||||||
|
context::WalkContext::with_schema,
|
||||||
|
);
|
||||||
|
let (result, _cmd) = walk(source, outcome::WalkBound::EndOfInput, &mut ctx);
|
||||||
|
let Some(result) = result else {
|
||||||
|
// The first token is not a registered command word —
|
||||||
|
// typing this and pressing Enter fails.
|
||||||
|
return Some(Severity::Error);
|
||||||
|
};
|
||||||
|
let outcome_severity = match result.outcome {
|
||||||
|
outcome::WalkOutcome::Match { .. } => None,
|
||||||
|
_ => Some(Severity::Error),
|
||||||
|
};
|
||||||
|
let diag_severity = result.diagnostics.iter().map(|d| d.severity).max();
|
||||||
|
outcome_severity.into_iter().chain(diag_severity).max()
|
||||||
|
}
|
||||||
|
|
||||||
/// What the grammar would accept at the end of `source`
|
/// What the grammar would accept at the end of `source`
|
||||||
/// (ADR-0024 §architecture, Phase F walker-driven completion).
|
/// (ADR-0024 §architecture, Phase F walker-driven completion).
|
||||||
///
|
///
|
||||||
@@ -570,6 +609,9 @@ pub fn walk<'a>(
|
|||||||
matched_path: path,
|
matched_path: path,
|
||||||
per_byte_class: per_byte,
|
per_byte_class: per_byte,
|
||||||
tail_expected,
|
tail_expected,
|
||||||
|
// Filled by the schema-existence and expression passes
|
||||||
|
// (ADR-0027 §2); empty here at the structural layer.
|
||||||
|
diagnostics: Vec::new(),
|
||||||
};
|
};
|
||||||
(Some(result), cmd)
|
(Some(result), cmd)
|
||||||
}
|
}
|
||||||
@@ -1236,6 +1278,45 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- input_verdict (ADR-0027 §3) --------------------------
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_verdict_clean_command_is_none() {
|
||||||
|
assert_eq!(super::input_verdict("quit", None), None);
|
||||||
|
assert_eq!(super::input_verdict("show table Customers", None), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_verdict_empty_input_is_none() {
|
||||||
|
assert_eq!(super::input_verdict("", None), None);
|
||||||
|
assert_eq!(super::input_verdict(" ", None), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_verdict_incomplete_command_is_error() {
|
||||||
|
assert_eq!(
|
||||||
|
super::input_verdict("create table", None),
|
||||||
|
Some(super::Severity::Error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_verdict_unknown_command_is_error() {
|
||||||
|
assert_eq!(
|
||||||
|
super::input_verdict("frobnicate the gizmo", None),
|
||||||
|
Some(super::Severity::Error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_verdict_mismatched_token_is_error() {
|
||||||
|
// `quit` takes no argument — trailing junk fails.
|
||||||
|
assert_eq!(
|
||||||
|
super::input_verdict("quit now", None),
|
||||||
|
Some(super::Severity::Error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn walker_parses_insert_with_explicit_column_list() {
|
fn walker_parses_insert_with_explicit_column_list() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -159,11 +159,52 @@ pub struct ByteClass {
|
|||||||
pub class: HighlightClass,
|
pub class: HighlightClass,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Severity of a pre-submit [`Diagnostic`] (ADR-0027 §1).
|
||||||
|
///
|
||||||
|
/// Ordered so `Error > Warning` — taking the `max` across a
|
||||||
|
/// diagnostic set gives the validity indicator's verdict.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum Severity {
|
||||||
|
/// Valid and runnable, but very likely not what the user
|
||||||
|
/// intends — a type-mismatched comparison, `= NULL`
|
||||||
|
/// (ADR-0026 §7).
|
||||||
|
Warning,
|
||||||
|
/// Known to fail if submitted now — a parse error, or a
|
||||||
|
/// reference to a table / column that does not exist.
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A problem the walker found in the input *before* submission
|
||||||
|
/// (ADR-0027 §2). The severity drives the validity indicator;
|
||||||
|
/// the span drives highlighting; the message is the hint text.
|
||||||
|
///
|
||||||
|
/// Note: the parse outcome (`Incomplete` / `Mismatch` /
|
||||||
|
/// `ValidationFailed`) is *not* recorded as a `Diagnostic` —
|
||||||
|
/// the indicator reads it from `WalkOutcome` directly
|
||||||
|
/// (ADR-0027 §2, "the highest severity across the outcome and
|
||||||
|
/// the diagnostics"). `diagnostics` carries the schema-aware
|
||||||
|
/// findings layered on top of a structurally-valid parse.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Diagnostic {
|
||||||
|
pub severity: Severity,
|
||||||
|
/// Byte range in the source the diagnostic refers to.
|
||||||
|
pub span: (usize, usize),
|
||||||
|
/// Ready-to-show message — already catalog-translated by
|
||||||
|
/// whichever pass produced the diagnostic.
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct WalkResult {
|
pub struct WalkResult {
|
||||||
pub outcome: WalkOutcome,
|
pub outcome: WalkOutcome,
|
||||||
pub matched_path: MatchedPath,
|
pub matched_path: MatchedPath,
|
||||||
pub per_byte_class: Vec<ByteClass>,
|
pub per_byte_class: Vec<ByteClass>,
|
||||||
|
/// Schema-aware pre-submit findings layered on a
|
||||||
|
/// structurally-valid parse (ADR-0027): unknown table /
|
||||||
|
/// column references (ERROR), type-mismatched WHERE
|
||||||
|
/// comparisons and `= NULL` (WARNING). Empty when the
|
||||||
|
/// input does not parse — those are the outcome's job.
|
||||||
|
pub diagnostics: Vec<Diagnostic>,
|
||||||
/// Optional-Optional expectations the walker could have
|
/// Optional-Optional expectations the walker could have
|
||||||
/// accepted but didn't because the outer shape ran out at a
|
/// accepted but didn't because the outer shape ran out at a
|
||||||
/// node boundary (ADR-0024 §architecture, round-5 follow-up).
|
/// node boundary (ADR-0024 §architecture, round-5 follow-up).
|
||||||
|
|||||||
Reference in New Issue
Block a user