1#![allow(unused_assignments)]
2
3use std::collections::{HashMap, HashSet};
4
5use miette::{Diagnostic, SourceSpan};
6use thiserror::Error;
7
8use crate::ast::{VarFile, VarType};
9use crate::lexer::Span;
10
11#[derive(Error, Debug, Diagnostic)]
12pub enum ValidationError {
13 #[error("duplicate feature name: `{name}`")]
14 DuplicateFeature {
15 name: String,
16 #[label("first defined here")]
17 first: SourceSpan,
18 #[label("duplicate defined here")]
19 duplicate: SourceSpan,
20 },
21
22 #[error("duplicate feature id `{id}`")]
23 DuplicateFeatureId {
24 id: u32,
25 #[label("first defined here")]
26 first: SourceSpan,
27 #[label("duplicate defined here")]
28 duplicate: SourceSpan,
29 },
30
31 #[error("duplicate variable name `{name}` in feature `{feature}`")]
32 DuplicateVariable {
33 feature: String,
34 name: String,
35 #[label("first defined here")]
36 first: SourceSpan,
37 #[label("duplicate defined here")]
38 duplicate: SourceSpan,
39 },
40
41 #[error("duplicate variable id `{id}` in feature `{feature}`")]
42 DuplicateVariableId {
43 feature: String,
44 id: u32,
45 #[label("first defined here")]
46 first: SourceSpan,
47 #[label("duplicate defined here")]
48 duplicate: SourceSpan,
49 },
50
51 #[error("duplicate struct name: `{name}`")]
52 DuplicateStruct {
53 name: String,
54 #[label("first defined here")]
55 first: SourceSpan,
56 #[label("duplicate defined here")]
57 duplicate: SourceSpan,
58 },
59
60 #[error("duplicate struct id `{id}`")]
61 DuplicateStructId {
62 id: u32,
63 #[label("first defined here")]
64 first: SourceSpan,
65 #[label("duplicate defined here")]
66 duplicate: SourceSpan,
67 },
68
69 #[error("duplicate field name `{name}` in struct `{struct_name}`")]
70 DuplicateField {
71 struct_name: String,
72 name: String,
73 #[label("first defined here")]
74 first: SourceSpan,
75 #[label("duplicate defined here")]
76 duplicate: SourceSpan,
77 },
78
79 #[error("duplicate field id `{id}` in struct `{struct_name}`")]
80 DuplicateFieldId {
81 struct_name: String,
82 id: u32,
83 #[label("first defined here")]
84 first: SourceSpan,
85 #[label("duplicate defined here")]
86 duplicate: SourceSpan,
87 },
88
89 #[error("unknown struct type `{type_name}` for variable `{variable}` in feature `{feature}`")]
90 UnknownStructType {
91 feature: String,
92 variable: String,
93 type_name: String,
94 #[label("used here")]
95 span: SourceSpan,
96 },
97
98 #[error("unknown field `{field}` in struct literal for type `{struct_name}`")]
99 UnknownStructField {
100 struct_name: String,
101 field: String,
102 #[label("used here")]
103 span: SourceSpan,
104 },
105}
106
107fn span_to_source_span(span: &Span) -> SourceSpan {
108 SourceSpan::from(span.offset)
109}
110
111pub fn validate(var_file: &VarFile) -> Result<(), Vec<ValidationError>> {
112 let mut errors = Vec::new();
113
114 let mut struct_names_set: HashSet<&str> = HashSet::new();
116
117 let mut struct_names: HashMap<&str, &Span> = HashMap::new();
119 let mut struct_ids: HashMap<u32, &Span> = HashMap::new();
120 for struct_def in &var_file.structs {
121 struct_names_set.insert(&struct_def.name);
122
123 if let Some(first_span) = struct_names.get(struct_def.name.as_str()) {
124 errors.push(ValidationError::DuplicateStruct {
125 name: struct_def.name.clone(),
126 first: span_to_source_span(first_span),
127 duplicate: span_to_source_span(&struct_def.span),
128 });
129 } else {
130 struct_names.insert(&struct_def.name, &struct_def.span);
131 }
132
133 if let Some(first_span) = struct_ids.get(&struct_def.id) {
134 errors.push(ValidationError::DuplicateStructId {
135 id: struct_def.id,
136 first: span_to_source_span(first_span),
137 duplicate: span_to_source_span(&struct_def.span),
138 });
139 } else {
140 struct_ids.insert(struct_def.id, &struct_def.span);
141 }
142
143 let mut field_names: HashMap<&str, &Span> = HashMap::new();
145 let mut field_ids: HashMap<u32, &Span> = HashMap::new();
146 for field in &struct_def.fields {
147 if let Some(first_span) = field_names.get(field.name.as_str()) {
148 errors.push(ValidationError::DuplicateField {
149 struct_name: struct_def.name.clone(),
150 name: field.name.clone(),
151 first: span_to_source_span(first_span),
152 duplicate: span_to_source_span(&field.span),
153 });
154 } else {
155 field_names.insert(&field.name, &field.span);
156 }
157
158 if let Some(first_span) = field_ids.get(&field.id) {
159 errors.push(ValidationError::DuplicateFieldId {
160 struct_name: struct_def.name.clone(),
161 id: field.id,
162 first: span_to_source_span(first_span),
163 duplicate: span_to_source_span(&field.span),
164 });
165 } else {
166 field_ids.insert(field.id, &field.span);
167 }
168 }
169 }
170
171 let struct_field_names: HashMap<&str, HashSet<&str>> = var_file
173 .structs
174 .iter()
175 .map(|s| {
176 let fields: HashSet<&str> = s.fields.iter().map(|f| f.name.as_str()).collect();
177 (s.name.as_str(), fields)
178 })
179 .collect();
180
181 let mut feature_names: HashMap<&str, &Span> = HashMap::new();
183 let mut feature_ids: HashMap<u32, &Span> = HashMap::new();
184 for feature in &var_file.features {
185 if let Some(first_span) = feature_names.get(feature.name.as_str()) {
186 errors.push(ValidationError::DuplicateFeature {
187 name: feature.name.clone(),
188 first: span_to_source_span(first_span),
189 duplicate: span_to_source_span(&feature.span),
190 });
191 } else {
192 feature_names.insert(&feature.name, &feature.span);
193 }
194
195 if let Some(first_span) = feature_ids.get(&feature.id) {
196 errors.push(ValidationError::DuplicateFeatureId {
197 id: feature.id,
198 first: span_to_source_span(first_span),
199 duplicate: span_to_source_span(&feature.span),
200 });
201 } else {
202 feature_ids.insert(feature.id, &feature.span);
203 }
204 }
205
206 for feature in &var_file.features {
209 let mut var_names: HashMap<&str, &Span> = HashMap::new();
210 let mut var_ids: HashMap<u32, &Span> = HashMap::new();
211 for variable in &feature.variables {
212 if let Some(first_span) = var_names.get(variable.name.as_str()) {
213 errors.push(ValidationError::DuplicateVariable {
214 feature: feature.name.clone(),
215 name: variable.name.clone(),
216 first: span_to_source_span(first_span),
217 duplicate: span_to_source_span(&variable.span),
218 });
219 } else {
220 var_names.insert(&variable.name, &variable.span);
221 }
222
223 if let Some(first_span) = var_ids.get(&variable.id) {
224 errors.push(ValidationError::DuplicateVariableId {
225 feature: feature.name.clone(),
226 id: variable.id,
227 first: span_to_source_span(first_span),
228 duplicate: span_to_source_span(&variable.span),
229 });
230 } else {
231 var_ids.insert(variable.id, &variable.span);
232 }
233
234 if let VarType::Struct(ref struct_name) = variable.var_type {
236 if !struct_names_set.contains(struct_name.as_str()) {
237 errors.push(ValidationError::UnknownStructType {
238 feature: feature.name.clone(),
239 variable: variable.name.clone(),
240 type_name: struct_name.clone(),
241 span: span_to_source_span(&variable.span),
242 });
243 }
244
245 if let crate::ast::Value::Struct { fields, .. } = &variable.default {
247 if let Some(valid_fields) = struct_field_names.get(struct_name.as_str()) {
248 for field_name in fields.keys() {
249 if !valid_fields.contains(field_name.as_str()) {
250 errors.push(ValidationError::UnknownStructField {
251 struct_name: struct_name.clone(),
252 field: field_name.clone(),
253 span: span_to_source_span(&variable.span),
254 });
255 }
256 }
257 }
258 }
259 }
260 }
261 }
262
263 if errors.is_empty() {
264 Ok(())
265 } else {
266 Err(errors)
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use crate::lexer::lex;
274 use crate::parser::parse;
275
276 fn parse_and_validate(input: &str) -> Result<VarFile, Vec<ValidationError>> {
277 let tokens = lex(input).expect("lex failed");
278 let var_file = parse(tokens).expect("parse failed");
279 validate(&var_file)?;
280 Ok(var_file)
281 }
282
283 #[test]
284 fn valid_file_passes() {
285 let input = r#"1: Feature Checkout = {
286 1: enabled Boolean = true
287 2: max_items Integer = 50
288}
289
2902: Feature Search = {
291 1: query String = "default"
292}"#;
293 assert!(parse_and_validate(input).is_ok());
294 }
295
296 #[test]
297 fn duplicate_feature_name_error() {
298 let input = r#"1: Feature Checkout = {
299 1: enabled Boolean = true
300}
301
3022: Feature Checkout = {
303 1: max_items Integer = 50
304}"#;
305 let err = parse_and_validate(input).unwrap_err();
306 assert_eq!(err.len(), 1);
307 match &err[0] {
308 ValidationError::DuplicateFeature { name, .. } => {
309 assert_eq!(name, "Checkout");
310 }
311 _ => panic!("expected DuplicateFeature error"),
312 }
313 }
314
315 #[test]
316 fn duplicate_variable_name_error() {
317 let input = r#"1: Feature Checkout = {
318 1: enabled Boolean = true
319 2: enabled Boolean = false
320}"#;
321 let err = parse_and_validate(input).unwrap_err();
322 assert_eq!(err.len(), 1);
323 match &err[0] {
324 ValidationError::DuplicateVariable { feature, name, .. } => {
325 assert_eq!(feature, "Checkout");
326 assert_eq!(name, "enabled");
327 }
328 _ => panic!("expected DuplicateVariable error"),
329 }
330 }
331
332 #[test]
333 fn error_has_correct_line_info() {
334 let input = r#"1: Feature Checkout = {
335 1: enabled Boolean = true
336}
337
3382: Feature Checkout = {
339 1: max_items Integer = 50
340}"#;
341 let err = parse_and_validate(input).unwrap_err();
342 match &err[0] {
343 ValidationError::DuplicateFeature {
344 first, duplicate, ..
345 } => {
346 assert_eq!(first.offset(), 0);
348 assert!(duplicate.offset() > 0);
350 }
351 _ => panic!("expected DuplicateFeature error"),
352 }
353 }
354
355 #[test]
356 fn duplicate_feature_id_error() {
357 let input = r#"1: Feature Checkout = {
358 1: enabled Boolean = true
359}
360
3611: Feature Search = {
362 1: query String = "default"
363}"#;
364 let err = parse_and_validate(input).unwrap_err();
365 assert_eq!(err.len(), 1);
366 match &err[0] {
367 ValidationError::DuplicateFeatureId { id, .. } => {
368 assert_eq!(*id, 1);
369 }
370 _ => panic!("expected DuplicateFeatureId error"),
371 }
372 }
373
374 #[test]
375 fn duplicate_variable_id_error() {
376 let input = r#"1: Feature Checkout = {
377 1: enabled Boolean = true
378 1: max_items Integer = 50
379}"#;
380 let err = parse_and_validate(input).unwrap_err();
381 assert_eq!(err.len(), 1);
382 match &err[0] {
383 ValidationError::DuplicateVariableId { feature, id, .. } => {
384 assert_eq!(feature, "Checkout");
385 assert_eq!(*id, 1);
386 }
387 _ => panic!("expected DuplicateVariableId error"),
388 }
389 }
390
391 #[test]
392 fn valid_file_with_struct_passes() {
393 let input = r#"1: Struct Theme = {
394 1: dark_mode Boolean = false
395 2: font_size Integer = 14
396}
397
3981: Feature Dashboard = {
399 1: enabled Boolean = true
400 2: theme Theme = Theme {}
401}"#;
402 assert!(parse_and_validate(input).is_ok());
403 }
404
405 #[test]
406 fn duplicate_struct_name_error() {
407 let input = r#"1: Struct Theme = {
408 1: dark_mode Boolean = false
409}
410
4112: Struct Theme = {
412 1: font_size Integer = 14
413}"#;
414 let err = parse_and_validate(input).unwrap_err();
415 assert_eq!(err.len(), 1);
416 match &err[0] {
417 ValidationError::DuplicateStruct { name, .. } => {
418 assert_eq!(name, "Theme");
419 }
420 _ => panic!("expected DuplicateStruct error"),
421 }
422 }
423
424 #[test]
425 fn duplicate_struct_id_error() {
426 let input = r#"1: Struct Theme = {
427 1: dark_mode Boolean = false
428}
429
4301: Struct Config = {
431 1: retries Integer = 3
432}"#;
433 let err = parse_and_validate(input).unwrap_err();
434 assert_eq!(err.len(), 1);
435 match &err[0] {
436 ValidationError::DuplicateStructId { id, .. } => {
437 assert_eq!(*id, 1);
438 }
439 _ => panic!("expected DuplicateStructId error"),
440 }
441 }
442
443 #[test]
444 fn duplicate_field_name_error() {
445 let input = r#"1: Struct Theme = {
446 1: dark_mode Boolean = false
447 2: dark_mode Boolean = true
448}"#;
449 let err = parse_and_validate(input).unwrap_err();
450 assert_eq!(err.len(), 1);
451 match &err[0] {
452 ValidationError::DuplicateField {
453 struct_name, name, ..
454 } => {
455 assert_eq!(struct_name, "Theme");
456 assert_eq!(name, "dark_mode");
457 }
458 _ => panic!("expected DuplicateField error"),
459 }
460 }
461
462 #[test]
463 fn duplicate_field_id_error() {
464 let input = r#"1: Struct Theme = {
465 1: dark_mode Boolean = false
466 1: font_size Integer = 14
467}"#;
468 let err = parse_and_validate(input).unwrap_err();
469 assert_eq!(err.len(), 1);
470 match &err[0] {
471 ValidationError::DuplicateFieldId {
472 struct_name, id, ..
473 } => {
474 assert_eq!(struct_name, "Theme");
475 assert_eq!(*id, 1);
476 }
477 _ => panic!("expected DuplicateFieldId error"),
478 }
479 }
480
481 #[test]
482 fn unknown_struct_type_error() {
483 let input = r#"1: Feature Dashboard = {
484 1: theme UnknownType = UnknownType {}
485}"#;
486 let err = parse_and_validate(input).unwrap_err();
487 assert_eq!(err.len(), 1);
488 match &err[0] {
489 ValidationError::UnknownStructType {
490 feature,
491 variable,
492 type_name,
493 ..
494 } => {
495 assert_eq!(feature, "Dashboard");
496 assert_eq!(variable, "theme");
497 assert_eq!(type_name, "UnknownType");
498 }
499 _ => panic!("expected UnknownStructType error"),
500 }
501 }
502
503 #[test]
504 fn unknown_struct_field_in_literal_error() {
505 let input = r#"1: Struct Theme = {
506 1: dark_mode Boolean = false
507}
508
5091: Feature Dashboard = {
510 1: theme Theme = Theme { nonexistent = true }
511}"#;
512 let err = parse_and_validate(input).unwrap_err();
513 assert_eq!(err.len(), 1);
514 match &err[0] {
515 ValidationError::UnknownStructField {
516 struct_name, field, ..
517 } => {
518 assert_eq!(struct_name, "Theme");
519 assert_eq!(field, "nonexistent");
520 }
521 _ => panic!("expected UnknownStructField error"),
522 }
523 }
524}