Skip to content

Commit dbb30c4

Browse files
committed
way better error reporting for Additional properties
1 parent 23ffb62 commit dbb30c4

2 files changed

Lines changed: 65 additions & 6 deletions

File tree

src/validation/config.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,17 @@ impl ConfigValidator {
6262
if !errors.is_empty() {
6363
eprintln!(); // Add newline before first error
6464
for error in &errors {
65+
let error_msg = error.to_string();
6566
let instance_path = Self::refine_instance_path(error);
67+
68+
// Use key span for property-related errors
6669
let validation_error =
67-
spanned_validator.create_error(&instance_path, error.to_string());
70+
if error_msg.contains("Additional properties are not allowed") {
71+
spanned_validator.create_error_for_key(&instance_path, error_msg)
72+
} else {
73+
spanned_validator.create_error(&instance_path, error_msg)
74+
};
75+
6876
eprintln!("{:?}", Report::new(validation_error));
6977
}
7078
anyhow::bail!(
@@ -187,9 +195,17 @@ impl ConfigValidator {
187195
if !errors.is_empty() {
188196
eprintln!(); // Add newline before first error
189197
for error in &errors {
198+
let error_msg = error.to_string();
190199
let instance_path = Self::refine_instance_path(error);
200+
201+
// Use key span for property-related errors
191202
let validation_error =
192-
spanned_validator.create_error(&instance_path, error.to_string());
203+
if error_msg.contains("Additional properties are not allowed") {
204+
spanned_validator.create_error_for_key(&instance_path, error_msg)
205+
} else {
206+
spanned_validator.create_error(&instance_path, error_msg)
207+
};
208+
193209
eprintln!("{:?}", Report::new(validation_error));
194210
}
195211
anyhow::bail!(

src/validation/error_reporter.rs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub struct ValidationError {
2323

2424
pub struct SpannedValidator {
2525
spans: HashMap<String, SpanInfo>,
26+
key_spans: HashMap<String, SpanInfo>, // Track key spans separately
2627
json_value: serde_json::Value,
2728
source: String,
2829
file_path: String,
@@ -38,10 +39,13 @@ impl SpannedValidator {
3839

3940
// Strip spans and build lookup map
4041
let mut spans = HashMap::new();
41-
let json_value = Self::strip_spans_and_index(&spanned_yaml, String::new(), &mut spans);
42+
let mut key_spans = HashMap::new();
43+
let json_value =
44+
Self::strip_spans_and_index(&spanned_yaml, String::new(), &mut spans, &mut key_spans);
4245

4346
Ok(Self {
4447
spans,
48+
key_spans,
4549
json_value,
4650
source,
4751
file_path: file_path_str,
@@ -73,10 +77,33 @@ impl SpannedValidator {
7377
}
7478
}
7579

80+
pub fn create_error_for_key(&self, instance_path: &str, message: String) -> ValidationError {
81+
let pointer_str = instance_path;
82+
83+
// For key errors, look up key span first, then fall back to value span
84+
let span_info = self
85+
.key_spans
86+
.get(pointer_str)
87+
.or_else(|| self.spans.get(pointer_str))
88+
.or_else(|| self.spans.get(""))
89+
.cloned()
90+
.unwrap_or(SpanInfo { start: 0, end: 0 });
91+
92+
ValidationError {
93+
source_code: crate::error_utils::create_named_source(
94+
Path::new(&self.file_path),
95+
self.source.clone(),
96+
),
97+
span: SourceSpan::new(span_info.start.into(), span_info.end - span_info.start),
98+
message,
99+
}
100+
}
101+
76102
fn strip_spans_and_index(
77103
spanned: &Spanned<YamlValue>,
78104
path: String,
79105
spans: &mut HashMap<String, SpanInfo>,
106+
key_spans: &mut HashMap<String, SpanInfo>,
80107
) -> serde_json::Value {
81108
// Record span for this path
82109
let span = spanned.span();
@@ -115,7 +142,7 @@ impl SpannedValidator {
115142
} else {
116143
format!("{path}/{i}")
117144
};
118-
Self::strip_spans_and_index(item, item_path, spans)
145+
Self::strip_spans_and_index(item, item_path, spans, key_spans)
119146
})
120147
.collect();
121148
serde_json::Value::Array(arr)
@@ -129,14 +156,30 @@ impl SpannedValidator {
129156
} else {
130157
format!("{path}/{key}")
131158
};
132-
let value = Self::strip_spans_and_index(value_spanned, value_path, spans);
159+
160+
// Record the key span for this path
161+
let key_span = key_spanned.span();
162+
key_spans.insert(
163+
value_path.clone(),
164+
SpanInfo {
165+
start: key_span.start.unwrap_or_default().byte_index,
166+
end: key_span.end.unwrap_or_default().byte_index,
167+
},
168+
);
169+
170+
let value = Self::strip_spans_and_index(
171+
value_spanned,
172+
value_path,
173+
spans,
174+
key_spans,
175+
);
133176
obj.insert(key.clone(), value);
134177
}
135178
}
136179
serde_json::Value::Object(obj)
137180
}
138181
YamlValue::Tagged(tagged_value) => {
139-
Self::strip_spans_and_index(&tagged_value.value, path, spans)
182+
Self::strip_spans_and_index(&tagged_value.value, path, spans, key_spans)
140183
}
141184
}
142185
}

0 commit comments

Comments
 (0)