@@ -11,20 +11,46 @@ use super::schemas::{
1111 SchemaRetriever , get_config_base_schema, get_config_schema, get_workflow_config_schema,
1212} ;
1313
14+ // Use JSON Schema draft-07 for validation (stable and well-tested)
15+ // TODO: Upgrade to draft 2020-12 once we update our schemas
16+ use jsonschema:: draft7 as schema_draft;
17+
1418pub struct ConfigValidator ;
1519
1620impl ConfigValidator {
1721 pub fn new ( ) -> Self {
1822 Self
1923 }
2024
25+ /// Extract a more specific instance path for certain validation errors
26+ fn refine_instance_path ( error : & jsonschema:: ValidationError ) -> String {
27+ let error_msg = error. to_string ( ) ;
28+
29+ // For "additional properties" errors, extract the property name and append to path
30+ if error_msg. contains ( "Additional properties are not allowed" ) {
31+ // Extract property names from error message like "('asdf' was unexpected)"
32+ if let Some ( start) = error_msg. find ( "('" ) {
33+ if let Some ( end) = error_msg[ start + 2 ..] . find ( "'" ) {
34+ let prop_name = & error_msg[ start + 2 ..start + 2 + end] ;
35+ return if error. instance_path . to_string ( ) . is_empty ( ) {
36+ format ! ( "/{prop_name}" )
37+ } else {
38+ format ! ( "{}/{prop_name}" , error. instance_path)
39+ } ;
40+ }
41+ }
42+ }
43+
44+ error. instance_path . to_string ( )
45+ }
46+
2147 pub fn validate_config ( & self , config_path : & Path ) -> Result < ( ) > {
2248 // 1. Schema validation with beautiful miette error reporting
2349 let spanned_validator = SpannedValidator :: new ( config_path)
2450 . map_err ( |e| anyhow:: anyhow!( "Failed to parse YAML from {config_path:?}: {e}" ) ) ?;
2551
2652 let schema = get_config_schema ( ) . context ( "Failed to parse config schema" ) ?;
27- let validator = jsonschema :: draft7 :: options ( )
53+ let validator = schema_draft :: options ( )
2854 . with_retriever ( SchemaRetriever )
2955 . build ( & schema)
3056 . context ( "Failed to compile config schema" ) ?;
@@ -36,8 +62,9 @@ impl ConfigValidator {
3662 if !errors. is_empty ( ) {
3763 eprintln ! ( ) ; // Add newline before first error
3864 for error in & errors {
39- let validation_error = spanned_validator
40- . create_error ( & error. instance_path . to_string ( ) , error. to_string ( ) ) ;
65+ let instance_path = Self :: refine_instance_path ( error) ;
66+ let validation_error =
67+ spanned_validator. create_error ( & instance_path, error. to_string ( ) ) ;
4168 eprintln ! ( "{:?}" , Report :: new( validation_error) ) ;
4269 }
4370 anyhow:: bail!(
@@ -65,7 +92,7 @@ impl ConfigValidator {
6592 . with_context ( || format ! ( "Failed to parse rendered YAML from: {source_path:?}" ) ) ?;
6693
6794 let schema = get_config_schema ( ) . context ( "Failed to parse config schema" ) ?;
68- let validator = jsonschema :: draft7 :: options ( )
95+ let validator = schema_draft :: options ( )
6996 . with_retriever ( SchemaRetriever )
7097 . build ( & schema)
7198 . context ( "Failed to compile config schema" ) ?;
@@ -94,7 +121,7 @@ impl ConfigValidator {
94121 // Use base schema for fragments (allows any subset of properties)
95122 let schema = get_config_base_schema ( ) . context ( "Failed to parse config base schema" ) ?;
96123
97- let validator = jsonschema :: draft7 :: options ( )
124+ let validator = schema_draft :: options ( )
98125 . with_retriever ( SchemaRetriever )
99126 . build ( & schema)
100127 . context ( "Failed to compile config base schema" ) ?;
@@ -122,7 +149,7 @@ impl ConfigValidator {
122149
123150 // Use base schema for fragments (allows any subset of properties)
124151 let schema = get_config_base_schema ( ) . context ( "Failed to parse config base schema" ) ?;
125- let validator = jsonschema :: draft7 :: options ( )
152+ let validator = schema_draft :: options ( )
126153 . with_retriever ( SchemaRetriever )
127154 . build ( & schema)
128155 . context ( "Failed to compile config base schema" ) ?;
@@ -148,7 +175,7 @@ impl ConfigValidator {
148175
149176 let schema =
150177 get_workflow_config_schema ( ) . context ( "Failed to parse workflow config schema" ) ?;
151- let validator = jsonschema :: draft7 :: options ( )
178+ let validator = schema_draft :: options ( )
152179 . with_retriever ( SchemaRetriever )
153180 . build ( & schema)
154181 . context ( "Failed to compile workflow config schema" ) ?;
@@ -160,8 +187,9 @@ impl ConfigValidator {
160187 if !errors. is_empty ( ) {
161188 eprintln ! ( ) ; // Add newline before first error
162189 for error in & errors {
163- let validation_error = spanned_validator
164- . create_error ( & error. instance_path . to_string ( ) , error. to_string ( ) ) ;
190+ let instance_path = Self :: refine_instance_path ( error) ;
191+ let validation_error =
192+ spanned_validator. create_error ( & instance_path, error. to_string ( ) ) ;
165193 eprintln ! ( "{:?}" , Report :: new( validation_error) ) ;
166194 }
167195 anyhow:: bail!(
@@ -179,7 +207,7 @@ impl ConfigValidator {
179207 // Validate against the full schema
180208 let schema = get_config_schema ( ) . context ( "Failed to parse config schema" ) ?;
181209
182- let validator = jsonschema :: draft7 :: options ( )
210+ let validator = schema_draft :: options ( )
183211 . with_retriever ( SchemaRetriever )
184212 . build ( & schema)
185213 . context ( "Failed to compile config schema" ) ?;
0 commit comments