44// Type-checking engine for TypedQLiser.
55// Uses the plugin system to delegate language-specific checks.
66
7- use anyhow:: { Context , Result } ;
87use crate :: manifest:: Manifest ;
98use crate :: plugins:: { self , Schema } ;
9+ use anyhow:: { Context , Result } ;
1010
1111/// Result of type-checking a single query.
1212#[ derive( Debug , Clone ) ]
@@ -54,7 +54,10 @@ static LEVEL_NAMES: [&str; 10] = [
5454fn load_schema ( manifest : & Manifest ) -> Result < Option < Schema > > {
5555 match manifest. typedql . schema_source . as_str ( ) {
5656 "file" => {
57- let path = manifest. database . schema_file . as_ref ( )
57+ let path = manifest
58+ . database
59+ . schema_file
60+ . as_ref ( )
5861 . context ( "schema-source is 'file' but database.schema-file not set" ) ?;
5962 let content = std:: fs:: read_to_string ( path)
6063 . with_context ( || format ! ( "Failed to read schema file: {}" , path) ) ?;
@@ -64,7 +67,9 @@ fn load_schema(manifest: &Manifest) -> Result<Option<Schema>> {
6467 }
6568 "introspect" => {
6669 // TODO: connect to database and introspect schema
67- eprintln ! ( "Warning: schema introspection not yet implemented. Use schema-source = \" file\" with a schema JSON file." ) ;
70+ eprintln ! (
71+ "Warning: schema introspection not yet implemented. Use schema-source = \" file\" with a schema JSON file."
72+ ) ;
6873 Ok ( None )
6974 }
7075 "none" => Ok ( None ) ,
@@ -73,7 +78,11 @@ fn load_schema(manifest: &Manifest) -> Result<Option<Schema>> {
7378}
7479
7580/// Check queries against type safety levels using the appropriate language plugin.
76- pub fn check_queries ( manifest : & Manifest , single_query : Option < & str > , _proofs : bool ) -> Result < Vec < CheckResult > > {
81+ pub fn check_queries (
82+ manifest : & Manifest ,
83+ single_query : Option < & str > ,
84+ _proofs : bool ,
85+ ) -> Result < Vec < CheckResult > > {
7786 let plugin = plugins:: get_plugin ( & manifest. typedql . language ) ?;
7887 let schema = load_schema ( manifest) ?;
7988 let mut results = Vec :: new ( ) ;
@@ -120,27 +129,29 @@ fn check_single_query(
120129 // Skip if configured to skip, or if a previous level failed
121130 if manifest. levels . skip . contains ( & level) || stop {
122131 level_results. push ( LevelResult {
123- level, name, status : LevelStatus :: Skipped , messages : vec ! [ ] ,
132+ level,
133+ name,
134+ status : LevelStatus :: Skipped ,
135+ messages : vec ! [ ] ,
124136 } ) ;
125137 continue ;
126138 }
127139
128140 let ( status, messages) = match level {
129141 // Level 1: Parse-time safety
130- 1 => {
131- match plugin. parse_check ( query) {
132- Ok ( ( ) ) => ( LevelStatus :: Passed , vec ! [ ] ) ,
133- Err ( e) => ( LevelStatus :: Failed , vec ! [ format!( "{}" , e) ] ) ,
134- }
135- }
142+ 1 => match plugin. parse_check ( query) {
143+ Ok ( ( ) ) => ( LevelStatus :: Passed , vec ! [ ] ) ,
144+ Err ( e) => ( LevelStatus :: Failed , vec ! [ format!( "{}" , e) ] ) ,
145+ } ,
136146
137147 // Level 2: Schema-binding safety
138148 2 => {
139149 if let Some ( s) = schema {
140150 match plugin. schema_check ( query, s) {
141151 Ok ( issues) if issues. is_empty ( ) => ( LevelStatus :: Passed , vec ! [ ] ) ,
142152 Ok ( issues) => {
143- let msgs: Vec < String > = issues. iter ( ) . map ( |i| i. message . clone ( ) ) . collect ( ) ;
153+ let msgs: Vec < String > =
154+ issues. iter ( ) . map ( |i| i. message . clone ( ) ) . collect ( ) ;
144155 ( LevelStatus :: Failed , msgs)
145156 }
146157 Err ( e) => ( LevelStatus :: Failed , vec ! [ format!( "{}" , e) ] ) ,
@@ -156,7 +167,8 @@ fn check_single_query(
156167 match plugin. type_check ( query, s) {
157168 Ok ( issues) if issues. is_empty ( ) => ( LevelStatus :: Passed , vec ! [ ] ) ,
158169 Ok ( issues) => {
159- let msgs: Vec < String > = issues. iter ( ) . map ( |i| i. message . clone ( ) ) . collect ( ) ;
170+ let msgs: Vec < String > =
171+ issues. iter ( ) . map ( |i| i. message . clone ( ) ) . collect ( ) ;
160172 ( LevelStatus :: Failed , msgs)
161173 }
162174 Err ( e) => ( LevelStatus :: Failed , vec ! [ format!( "{}" , e) ] ) ,
@@ -172,7 +184,8 @@ fn check_single_query(
172184 match plugin. null_check ( query, s) {
173185 Ok ( issues) if issues. is_empty ( ) => ( LevelStatus :: Passed , vec ! [ ] ) ,
174186 Ok ( issues) => {
175- let msgs: Vec < String > = issues. iter ( ) . map ( |i| i. message . clone ( ) ) . collect ( ) ;
187+ let msgs: Vec < String > =
188+ issues. iter ( ) . map ( |i| i. message . clone ( ) ) . collect ( ) ;
176189 // Null issues are warnings at level 4, not hard failures
177190 if manifest. levels . enforce . contains ( & 4 ) {
178191 ( LevelStatus :: Failed , msgs)
@@ -192,10 +205,18 @@ fn check_single_query(
192205 // Check for string interpolation patterns that suggest injection risk.
193206 // A query with $1, $2 (parameterised) is safe. A query with concatenation is not.
194207 // For MVP: pass if query contains parameter placeholders, warn if it contains quotes around variables.
195- let has_params = query. contains ( "$1" ) || query. contains ( "?" ) || query. contains ( ":param" ) ;
196- let has_concat = query. contains ( "' +" ) || query. contains ( "' ||" ) || query. contains ( "format!" ) ;
208+ let has_params =
209+ query. contains ( "$1" ) || query. contains ( "?" ) || query. contains ( ":param" ) ;
210+ let has_concat =
211+ query. contains ( "' +" ) || query. contains ( "' ||" ) || query. contains ( "format!" ) ;
197212 if has_concat {
198- ( LevelStatus :: Failed , vec ! [ "Query appears to use string concatenation — injection risk" . to_string( ) ] )
213+ (
214+ LevelStatus :: Failed ,
215+ vec ! [
216+ "Query appears to use string concatenation — injection risk"
217+ . to_string( ) ,
218+ ] ,
219+ )
199220 } else if has_params || !query. contains ( '\'' ) {
200221 ( LevelStatus :: Passed , vec ! [ ] )
201222 } else {
@@ -204,7 +225,10 @@ fn check_single_query(
204225 }
205226
206227 // Levels 6-10: not yet implemented
207- _ => ( LevelStatus :: Skipped , vec ! [ "Not yet implemented" . to_string( ) ] ) ,
228+ _ => (
229+ LevelStatus :: Skipped ,
230+ vec ! [ "Not yet implemented" . to_string( ) ] ,
231+ ) ,
208232 } ;
209233
210234 if status == LevelStatus :: Failed && manifest. levels . enforce . contains ( & level) {
@@ -215,7 +239,12 @@ fn check_single_query(
215239 max_level = level;
216240 }
217241
218- level_results. push ( LevelResult { level, name, status, messages } ) ;
242+ level_results. push ( LevelResult {
243+ level,
244+ name,
245+ status,
246+ messages,
247+ } ) ;
219248 }
220249
221250 // Truncate query for display
@@ -240,9 +269,16 @@ pub fn report_results(results: &[CheckResult], manifest: &Manifest, ci: bool) ->
240269 for result in results {
241270 let target = manifest. typedql . level ;
242271 let achieved = result. level_achieved ;
243- let status_str = if achieved >= target { "\x1b [32mPASS\x1b [0m" } else { "\x1b [31mFAIL\x1b [0m" } ;
272+ let status_str = if achieved >= target {
273+ "\x1b [32mPASS\x1b [0m"
274+ } else {
275+ "\x1b [31mFAIL\x1b [0m"
276+ } ;
244277
245- println ! ( "{} [L{}/{}] {}" , status_str, achieved, target, result. location) ;
278+ println ! (
279+ "{} [L{}/{}] {}" ,
280+ status_str, achieved, target, result. location
281+ ) ;
246282
247283 if achieved < target {
248284 errors += 1 ;
@@ -264,8 +300,12 @@ pub fn report_results(results: &[CheckResult], manifest: &Manifest, ci: bool) ->
264300 }
265301 }
266302
267- println ! ( "\n {} queries checked, {} passed, {} failed" ,
268- results. len( ) , results. len( ) as u32 - errors, errors) ;
303+ println ! (
304+ "\n {} queries checked, {} passed, {} failed" ,
305+ results. len( ) ,
306+ results. len( ) as u32 - errors,
307+ errors
308+ ) ;
269309
270310 if ci && errors > 0 {
271311 std:: process:: exit ( 1 ) ;
0 commit comments