seaworthycode 1.1.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/index.js +739 -42
  2. package/dist/index.js.map +1 -1
  3. package/package.json +14 -12
  4. package/rules/external/configuration/insecure-cookie-flags.test.yaml +114 -0
  5. package/rules/external/configuration/insecure-cookie-flags.yaml +77 -0
  6. package/rules/external/configuration/missing-ssl-verification.test.yaml +130 -0
  7. package/rules/external/configuration/missing-ssl-verification.yaml +123 -0
  8. package/rules/external/data-exposure/debug-artifacts.test.yaml +143 -0
  9. package/rules/external/data-exposure/debug-artifacts.yaml +57 -0
  10. package/rules/external/data-exposure/hardcoded-ip.test.yaml +131 -0
  11. package/rules/external/data-exposure/hardcoded-ip.yaml +67 -0
  12. package/rules/external/data-exposure/localstorage-sensitive-data.test.yaml +58 -0
  13. package/rules/external/data-exposure/localstorage-sensitive-data.yaml +60 -0
  14. package/rules/external/dependencies/deprecated-functions.test.yaml +92 -0
  15. package/rules/external/dependencies/deprecated-functions.yaml +66 -0
  16. package/rules/external/ops/hardcoded-port.test.yaml +141 -0
  17. package/rules/external/ops/hardcoded-port.yaml +91 -0
  18. package/rules/external/ops/insecure-file-permissions.test.yaml +111 -0
  19. package/rules/external/ops/insecure-file-permissions.yaml +91 -0
  20. package/rules/external/resilience/missing-memory-limit.test.yaml +54 -0
  21. package/rules/external/resilience/missing-memory-limit.yaml +59 -0
  22. package/rules/external/resilience/missing-timeout-config.test.yaml +179 -0
  23. package/rules/external/resilience/missing-timeout-config.yaml +124 -0
  24. package/rules/external/security/broken-crypto-algorithm.test.yaml +104 -0
  25. package/rules/external/security/broken-crypto-algorithm.yaml +79 -0
  26. package/rules/external/security/http-method-override.test.yaml +40 -0
  27. package/rules/external/security/http-method-override.yaml +35 -0
  28. package/rules/external/security/http-no-tls.test.yaml +115 -0
  29. package/rules/external/security/http-no-tls.yaml +59 -0
  30. package/rules/external/security/path-traversal.test.yaml +188 -0
  31. package/rules/external/security/path-traversal.yaml +210 -0
  32. package/rules/external/security/unsafe-innerhtml.test.yaml +72 -0
  33. package/rules/external/security/unsafe-innerhtml.yaml +46 -0
  34. package/rules/external/security/untrusted-deserialization.test.yaml +79 -0
  35. package/rules/external/security/untrusted-deserialization.yaml +48 -0
  36. package/rules/external/security/weak-encryption-mode.test.yaml +126 -0
  37. package/rules/external/security/weak-encryption-mode.yaml +104 -0
  38. package/rules/external/security/weak-hash-algorithm.test.yaml +121 -0
  39. package/rules/external/security/weak-hash-algorithm.yaml +87 -0
  40. package/rules/external/security/xxe-parsing.test.yaml +76 -0
  41. package/rules/external/security/xxe-parsing.yaml +63 -0
  42. package/rules/internal/security/dangerous-eval.test.yaml +126 -0
  43. package/rules/internal/security/dangerous-eval.yaml +134 -0
  44. package/rules/internal/security/no-document-write.test.yaml +33 -0
  45. package/rules/internal/security/no-document-write.yaml +40 -0
  46. package/rules/internal/taint/MATRIX.md +16 -0
  47. package/rules/internal/taint/bash.test.yaml +84 -0
  48. package/rules/internal/taint/bash.yaml +166 -0
  49. package/rules/internal/taint/dvwa.test.yaml +217 -0
  50. package/rules/internal/taint/go.test.yaml +207 -0
  51. package/rules/internal/taint/go.yaml +325 -0
  52. package/rules/internal/taint/java.test.yaml +158 -0
  53. package/rules/internal/taint/java.yaml +318 -0
  54. package/rules/internal/taint/javascript.test.yaml +374 -0
  55. package/rules/internal/taint/javascript.yaml +439 -0
  56. package/rules/internal/taint/php.test.yaml +134 -0
  57. package/rules/internal/taint/php.yaml +331 -0
  58. package/rules/internal/taint/python.test.yaml +427 -0
  59. package/rules/internal/taint/python.yaml +636 -0
  60. package/rules/internal/taint/ruby.test.yaml +148 -0
  61. package/rules/internal/taint/ruby.yaml +233 -0
  62. package/rules/internal/taint/rust.test.yaml +585 -0
  63. package/rules/internal/taint/rust.yaml +483 -0
  64. package/rules/internal/taint/tsx.test.yaml +140 -0
  65. package/rules/internal/taint/tsx.yaml +51 -0
  66. package/rules/internal/taint/typescript.test.yaml +393 -0
  67. package/rules/internal/taint/typescript.yaml +660 -0
  68. package/wasm/tree-sitter-sql.wasm +0 -0
@@ -0,0 +1,585 @@
1
+ language: rust
2
+ cases:
3
+ # === POSITIVE FLOW TESTS ===
4
+
5
+ - name: "Axum Json to sqlx::query via format! (SQL injection)"
6
+ language: rust
7
+ code: |
8
+ async fn handler(Json(payload): Json<UserPayload>) {
9
+ let query_str = format!("SELECT * FROM users WHERE name = '{}'", payload.name);
10
+ sqlx::query(&query_str).execute(&pool).await;
11
+ }
12
+ expect:
13
+ count: 2
14
+ flows:
15
+ - sourceId: axum.extract.Json
16
+ sinkId: sqlx.query
17
+ - sourceId: axum.extract.Json
18
+ sinkId: format.sql-sqli
19
+
20
+ - name: "Axum Query to diesel::sql_query (SQL injection)"
21
+ language: rust
22
+ code: |
23
+ async fn handler(Query(params): Query<SearchParams>) {
24
+ let sql = format!("SELECT * FROM items WHERE query = '{}'", params.q);
25
+ diesel::sql_query(sql).execute(&conn);
26
+ }
27
+ expect:
28
+ count: 2
29
+ flows:
30
+ - sourceId: axum.extract.Query
31
+ sinkId: diesel.sql_query
32
+ - sourceId: axum.extract.Query
33
+ sinkId: format.sql-sqli
34
+
35
+ - name: "SQL injection via format! SQL pattern"
36
+ language: rust
37
+ code: |
38
+ async fn handler(Query(params): Query<SearchParams>) {
39
+ let query = format!("SELECT * FROM items WHERE query = '{}'", params.q);
40
+ }
41
+ expect:
42
+ count: 1
43
+ flows:
44
+ - sourceId: axum.extract.Query
45
+ sinkId: format.sql-sqli
46
+
47
+ - name: "Command injection — env var to Command::new"
48
+ language: rust
49
+ code: |
50
+ fn run() {
51
+ let target = std::env::var("DEPLOY_TARGET").unwrap();
52
+ std::process::Command::new(target).spawn().unwrap();
53
+ }
54
+ expect:
55
+ count: 2
56
+ flows:
57
+ - sourceId: std.env.var
58
+ sinkId: std.process.Command.new
59
+ - sourceId: std.env.var
60
+ sinkId: std.process.Command.code-exec
61
+
62
+ - name: "Command injection — CLI args to Command::new"
63
+ language: rust
64
+ code: |
65
+ fn main() {
66
+ let arg = std::env::args().nth(1).unwrap();
67
+ std::process::Command::new(arg).output().unwrap();
68
+ }
69
+ expect:
70
+ count: 2
71
+ flows:
72
+ - sourceId: std.env.args
73
+ sinkId: std.process.Command.new
74
+ - sourceId: std.env.args
75
+ sinkId: std.process.Command.code-exec
76
+
77
+ - name: "Path traversal — Axum Query to fs::File::open"
78
+ language: rust
79
+ code: |
80
+ async fn get_file(Query(params): Query<FileParams>) {
81
+ std::fs::File::open(params.filename).unwrap();
82
+ }
83
+ expect:
84
+ count: 1
85
+ flows:
86
+ - sourceId: axum.extract.Query
87
+ sinkId: std.fs.File.open
88
+
89
+ - name: "Path traversal — env var to fs::read_to_string"
90
+ language: rust
91
+ code: |
92
+ fn main() {
93
+ let path = std::env::var("CONFIG_PATH").unwrap();
94
+ std::fs::read_to_string(path).unwrap();
95
+ }
96
+ expect:
97
+ count: 1
98
+ flows:
99
+ - sourceId: std.env.var
100
+ sinkId: std.fs.File.open
101
+
102
+ - name: "XSS — Axum Json to Html response"
103
+ language: rust
104
+ code: |
105
+ async fn render(Json(body): Json<TemplateBody>) {
106
+ axum::response::Html(body.content);
107
+ }
108
+ expect:
109
+ count: 1
110
+ flows:
111
+ - sourceId: axum.extract.Json
112
+ sinkId: axum.response.Html
113
+
114
+ - name: "XSS — Actix Json to HttpResponse::body"
115
+ language: rust
116
+ code: |
117
+ async fn render(body: actix_web::web::Json<TemplateBody>) {
118
+ HttpResponse::Ok().body(body.content);
119
+ }
120
+ expect:
121
+ count: 1
122
+ flows:
123
+ - sourceId: actix.web.Json
124
+ sinkId: actix.HttpResponse.body
125
+
126
+ - name: "URL injection — Axum Query to Redirect"
127
+ language: rust
128
+ code: |
129
+ async fn handle(Query(params): Query<RedirectParams>) {
130
+ axum::response::Redirect::to(&params.next);
131
+ }
132
+ expect:
133
+ count: 1
134
+ flows:
135
+ - sourceId: axum.extract.Query
136
+ sinkId: axum.response.Redirect
137
+
138
+ - name: "URL injection — Actix Query to redirect"
139
+ language: rust
140
+ code: |
141
+ async fn handle(params: actix_web::web::Query<RedirectParams>) {
142
+ HttpResponse::Found().insert_header(("Location", params.next.clone()));
143
+ }
144
+ expect:
145
+ count: 1
146
+ flows:
147
+ - sourceId: actix.web.Query
148
+ sinkId: actix.HttpResponse.redirect
149
+
150
+ - name: "Code execution — env var to libloading"
151
+ language: rust
152
+ code: |
153
+ fn load() {
154
+ let path = std::env::var("LIB_PATH").unwrap();
155
+ unsafe { libloading::Library::new(path).unwrap() };
156
+ }
157
+ expect:
158
+ count: 1
159
+ flows:
160
+ - sourceId: std.env.var
161
+ sinkId: libloading.Library.new
162
+
163
+ - name: "Network response to SQL injection"
164
+ language: rust
165
+ code: |
166
+ async fn fetch() {
167
+ let res = reqwest::get("http://example.com").await.unwrap().text().await.unwrap();
168
+ sqlx::query(&res).execute(&pool).await;
169
+ }
170
+ expect:
171
+ count: 1
172
+ flows:
173
+ - sourceId: reqwest.response.text
174
+ sinkId: sqlx.query
175
+
176
+ - name: "Network response to command injection"
177
+ language: rust
178
+ code: |
179
+ async fn run() {
180
+ let res = reqwest::get("http://example.com").await.unwrap().text().await.unwrap();
181
+ std::process::Command::new(res).spawn().unwrap();
182
+ }
183
+ expect:
184
+ count: 2
185
+ flows:
186
+ - sourceId: reqwest.response.text
187
+ sinkId: std.process.Command.new
188
+ - sourceId: reqwest.response.text
189
+ sinkId: std.process.Command.code-exec
190
+
191
+ - name: "Cookie to SQL injection"
192
+ language: rust
193
+ code: |
194
+ async fn handle(cookie: axum::extract::CookieJar) {
195
+ let val = cookie.get("session").unwrap().value();
196
+ sqlx::query(val).execute(&pool).await;
197
+ }
198
+ expect:
199
+ count: 1
200
+ flows:
201
+ - sourceId: axum.extract.CookieJar
202
+ sinkId: sqlx.query
203
+
204
+ - name: "Actix Form to path traversal"
205
+ language: rust
206
+ code: |
207
+ async fn handle(form: actix_web::web::Form<UploadForm>) {
208
+ std::fs::File::open(&form.path).unwrap();
209
+ }
210
+ expect:
211
+ count: 1
212
+ flows:
213
+ - sourceId: actix.web.Form
214
+ sinkId: std.fs.File.open
215
+
216
+ - name: "stdin to command injection"
217
+ language: rust
218
+ code: |
219
+ fn main() {
220
+ let mut input = String::new();
221
+ std::io::stdin().read_line(&mut input).unwrap();
222
+ std::process::Command::new(input).spawn().unwrap();
223
+ }
224
+ expect:
225
+ count: 2
226
+ flows:
227
+ - sourceId: std.io.stdin
228
+ sinkId: std.process.Command.new
229
+ - sourceId: std.io.stdin
230
+ sinkId: std.process.Command.code-exec
231
+
232
+ # === SANITISED PATH TESTS ===
233
+
234
+ - name: "SQL injection sanitised by parameterised query"
235
+ language: rust
236
+ code: |
237
+ async fn handler(Json(payload): Json<UserPayload>) {
238
+ sqlx::query("SELECT * FROM users WHERE name = ?").bind(payload.name).execute(&pool).await;
239
+ }
240
+ expect:
241
+ count: 0
242
+
243
+ - name: "SQL injection sanitised by parse::<i32>()"
244
+ language: rust
245
+ code: |
246
+ async fn handler(Query(params): Query<UserParams>) {
247
+ let id = params.id.parse::<i32>().unwrap();
248
+ let sql = format!("SELECT * FROM users WHERE id = {}", id);
249
+ sqlx::query(&sql).execute(&pool).await;
250
+ }
251
+ expect:
252
+ count: 0
253
+
254
+ - name: "XSS sanitised by html_escape::encode_safe"
255
+ language: rust
256
+ code: |
257
+ async fn render(Json(body): Json<TemplateBody>) {
258
+ let escaped = html_escape::encode_safe(&body.content);
259
+ axum::response::Html(escaped);
260
+ }
261
+ expect:
262
+ count: 0
263
+
264
+ - name: "Path traversal sanitised by Path::canonicalize"
265
+ language: rust
266
+ code: |
267
+ async fn get_file(Query(params): Query<FileParams>) {
268
+ let path = std::path::Path::new(&params.filename).canonicalize().unwrap();
269
+ std::fs::File::open(path).unwrap();
270
+ }
271
+ expect:
272
+ count: 0
273
+
274
+ - name: "Command injection sanitised by shell_words::join"
275
+ language: rust
276
+ code: |
277
+ fn run() {
278
+ let arg = std::env::var("CMD_ARG").unwrap();
279
+ let safe_arg = shell_words::join(vec![arg]);
280
+ std::process::Command::new("ls").arg(safe_arg).spawn().unwrap();
281
+ }
282
+ expect:
283
+ count: 0
284
+
285
+ - name: "URL injection sanitised by url::Url::parse"
286
+ language: rust
287
+ code: |
288
+ async fn handle(Query(params): Query<RedirectParams>) {
289
+ let safe_url = url::Url::parse(&params.next).unwrap();
290
+ axum::response::Redirect::to(safe_url.as_str());
291
+ }
292
+ expect:
293
+ count: 0
294
+
295
+ # === SANITISER CROSS-CLASS TESTS ===
296
+
297
+ - name: "html_escape does not cover sql-injection"
298
+ language: rust
299
+ code: |
300
+ async fn render(Json(body): Json<TemplateBody>) {
301
+ let escaped = html_escape::encode_safe(&body.content);
302
+ sqlx::query(&escaped).execute(&pool).await;
303
+ }
304
+ expect:
305
+ count: 1
306
+ flows:
307
+ - sourceId: axum.extract.Json
308
+ sinkId: sqlx.query
309
+
310
+ - name: "parse::<i32>() then to_string() — taint engine still tracks flow"
311
+ language: rust
312
+ code: |
313
+ async fn handler(Query(params): Query<UserParams>) {
314
+ let id = params.id.parse::<i32>().unwrap().to_string();
315
+ axum::response::Html(id);
316
+ }
317
+ expect:
318
+ count: 1
319
+ flows:
320
+ - sourceId: axum.extract.Query
321
+ sinkId: axum.response.Html
322
+
323
+ # === BFS PROPAGATION TESTS ===
324
+
325
+ - name: "format! interpolation propagates taint to sink"
326
+ language: rust
327
+ code: |
328
+ async fn handler(Query(params): Query<SearchParams>) {
329
+ let query = format!("{}", params.q);
330
+ sqlx::query(&query).execute(&pool).await;
331
+ }
332
+ expect:
333
+ count: 1
334
+ flows:
335
+ - sourceId: axum.extract.Query
336
+ sinkId: sqlx.query
337
+
338
+ - name: "String concatenation (+) propagates taint"
339
+ language: rust
340
+ code: |
341
+ async fn handler(Query(params): Query<SearchParams>) {
342
+ let query = "SELECT * FROM items WHERE query = ".to_string() + &params.q;
343
+ sqlx::query(&query).execute(&pool).await;
344
+ }
345
+ expect:
346
+ count: 1
347
+ flows:
348
+ - sourceId: axum.extract.Query
349
+ sinkId: sqlx.query
350
+
351
+ - name: "let binding propagation"
352
+ language: rust
353
+ code: |
354
+ async fn handler(Query(params): Query<SearchParams>) {
355
+ let tainted = params.q;
356
+ sqlx::query(&tainted).execute(&pool).await;
357
+ }
358
+ expect:
359
+ count: 1
360
+ flows:
361
+ - sourceId: axum.extract.Query
362
+ sinkId: sqlx.query
363
+
364
+ - name: "Struct destructuring propagation"
365
+ language: rust
366
+ code: |
367
+ async fn handler(Query(params): Query<SearchParams>) {
368
+ let SearchParams { q: query_val } = params;
369
+ sqlx::query(&query_val).execute(&pool).await;
370
+ }
371
+ expect:
372
+ count: 1
373
+ flows:
374
+ - sourceId: axum.extract.Query
375
+ sinkId: sqlx.query
376
+
377
+ - name: "match arm merging — taint in one arm reaches sink"
378
+ language: rust
379
+ code: |
380
+ async fn handler(Query(params): Query<SearchParams>) {
381
+ let query = match Some(&params.q) {
382
+ Some(val) => val.clone(),
383
+ None => "safe".to_string()
384
+ };
385
+ sqlx::query(&query).execute(&pool).await;
386
+ }
387
+ expect:
388
+ count: 1
389
+ flows:
390
+ - sourceId: axum.extract.Query
391
+ sinkId: sqlx.query
392
+
393
+ - name: "if let branch merging"
394
+ language: rust
395
+ code: |
396
+ async fn handler(Query(params): Query<SearchParams>) {
397
+ let mut query = String::new();
398
+ if let Some(val) = Some(&params.q) {
399
+ query = val.clone();
400
+ } else {
401
+ query = "safe".to_string();
402
+ }
403
+ sqlx::query(&query).execute(&pool).await;
404
+ }
405
+ expect:
406
+ count: 1
407
+ flows:
408
+ - sourceId: axum.extract.Query
409
+ sinkId: sqlx.query
410
+
411
+ - name: "if/else branch merging"
412
+ language: rust
413
+ code: |
414
+ async fn handler(Query(params): Query<SearchParams>) {
415
+ let mut query = String::new();
416
+ if true {
417
+ query = params.q.clone();
418
+ } else {
419
+ query = "safe".to_string();
420
+ }
421
+ sqlx::query(&query).execute(&pool).await;
422
+ }
423
+ expect:
424
+ count: 1
425
+ flows:
426
+ - sourceId: axum.extract.Query
427
+ sinkId: sqlx.query
428
+
429
+ - name: "for loop fixpoint iteration"
430
+ language: rust
431
+ code: |
432
+ async fn handler(Query(params): Query<SearchParams>) {
433
+ let mut query = String::new();
434
+ for item in vec![params.q] {
435
+ query = item;
436
+ }
437
+ sqlx::query(&query).execute(&pool).await;
438
+ }
439
+ expect:
440
+ count: 1
441
+ flows:
442
+ - sourceId: axum.extract.Query
443
+ sinkId: sqlx.query
444
+
445
+ - name: "Reference propagation (&tainted)"
446
+ language: rust
447
+ code: |
448
+ async fn handler(Query(params): Query<SearchParams>) {
449
+ let ref_val = &params.q;
450
+ sqlx::query(ref_val).execute(&pool).await;
451
+ }
452
+ expect:
453
+ count: 1
454
+ flows:
455
+ - sourceId: axum.extract.Query
456
+ sinkId: sqlx.query
457
+
458
+ - name: "Dereference propagation (*tainted)"
459
+ language: rust
460
+ code: |
461
+ async fn handler(Query(params): Query<SearchParams>) {
462
+ let ref_val = &params.q;
463
+ let deref_val = *ref_val;
464
+ sqlx::query(deref_val).execute(&pool).await;
465
+ }
466
+ expect:
467
+ count: 1
468
+ flows:
469
+ - sourceId: axum.extract.Query
470
+ sinkId: sqlx.query
471
+
472
+ - name: "Type cast propagation (as)"
473
+ language: rust
474
+ code: |
475
+ fn main() {
476
+ let path = std::env::var("LIB_PATH").unwrap();
477
+ let cast_val = path as *const u8;
478
+ unsafe { libloading::Library::new(cast_val).unwrap() };
479
+ }
480
+ expect:
481
+ count: 1
482
+ flows:
483
+ - sourceId: std.env.var
484
+ sinkId: libloading.Library.new
485
+
486
+ - name: "Closure parameter propagation"
487
+ language: rust
488
+ code: |
489
+ async fn handler(Query(params): Query<SearchParams>) {
490
+ let closure = |x| {
491
+ sqlx::query(x).execute(&pool);
492
+ };
493
+ closure(&params.q);
494
+ }
495
+ expect:
496
+ count: 1
497
+ flows:
498
+ - sourceId: axum.extract.Query
499
+ sinkId: sqlx.query
500
+
501
+ - name: "Mutator method — vec.push(tainted)"
502
+ language: rust
503
+ code: |
504
+ fn run() {
505
+ let mut args = vec![];
506
+ let tainted = std::env::var("CMD_ARG").unwrap();
507
+ args.push(tainted);
508
+ std::process::Command::new("ls").arg(&args[0]).spawn().unwrap();
509
+ }
510
+ expect:
511
+ count: 1
512
+ flows:
513
+ - sourceId: std.env.var
514
+ sinkId: std.process.Command.arg
515
+
516
+ - name: "Inter-function propagation"
517
+ language: rust
518
+ code: |
519
+ fn helper(x: String) -> String {
520
+ x
521
+ }
522
+ fn main() {
523
+ let tainted = std::env::var("CMD").unwrap();
524
+ let propagated = helper(tainted);
525
+ std::process::Command::new(propagated).spawn().unwrap();
526
+ }
527
+ expect:
528
+ count: 2
529
+ flows:
530
+ - sourceId: std.env.var
531
+ sinkId: std.process.Command.new
532
+ - sourceId: std.env.var
533
+ sinkId: std.process.Command.code-exec
534
+
535
+ - name: "Attribute assignment — obj.field = tainted"
536
+ language: rust
537
+ code: |
538
+ struct Config {
539
+ path: String,
540
+ }
541
+ fn main() {
542
+ let mut cfg = Config { path: "safe".to_string() };
543
+ let tainted = std::env::var("PATH").unwrap();
544
+ cfg.path = tainted;
545
+ std::fs::File::open(cfg.path).unwrap();
546
+ }
547
+ expect:
548
+ count: 1
549
+ flows:
550
+ - sourceId: std.env.var
551
+ sinkId: std.fs.File.open
552
+
553
+ - name: "Subscript assignment — map[key] = tainted"
554
+ language: rust
555
+ code: |
556
+ use std::collections::HashMap;
557
+ fn main() {
558
+ let mut map: HashMap<String, String> = HashMap::new();
559
+ let tainted = std::env::var("KEY").unwrap();
560
+ map.insert("key".to_string(), tainted);
561
+ }
562
+ expect:
563
+ count: 0
564
+
565
+ # === NO-SOURCE-MATCH AND ANTI-FIXTURE TESTS ===
566
+
567
+ - name: "No source match — plain code"
568
+ language: rust
569
+ code: |
570
+ fn main() {
571
+ let path = "/etc/passwd";
572
+ std::fs::File::open(path).unwrap();
573
+ }
574
+ expect:
575
+ count: 0
576
+
577
+ - name: "Safe anti-fixture"
578
+ language: rust
579
+ code: |
580
+ async fn handler(Query(params): Query<UserParams>) {
581
+ let id = params.id.parse::<i32>().unwrap();
582
+ sqlx::query("SELECT * FROM users WHERE id = ?").bind(id).execute(&pool).await;
583
+ }
584
+ expect:
585
+ count: 0