@@ -21,6 +21,17 @@ pub struct CheckResult {
2121 pub headers : BrinHeaders ,
2222}
2323
24+ /// Optional query parameters for a check request
25+ #[ derive( Debug , Default ) ]
26+ pub struct CheckOptions < ' a > {
27+ pub details : bool ,
28+ pub webhook : Option < & ' a str > ,
29+ pub tolerance : Option < & ' a str > ,
30+ pub refresh : bool ,
31+ pub mode : Option < & ' a str > ,
32+ pub format : Option < & ' a str > ,
33+ }
34+
2435/// Client for the brin API
2536pub struct BrinClient {
2637 client : Client ,
@@ -43,24 +54,34 @@ impl BrinClient {
4354 ///
4455 /// - `origin` — e.g. `"npm"`, `"pypi"`, `"repo"`, `"mcp"`, `"skill"`, `"domain"`, `"commit"`
4556 /// - `identifier` — the artifact identifier, e.g. `"express"`, `"owner/repo"`, `"owner/repo@sha"`
46- /// - `details` — if true, appends `?details=true` to include sub-scores
47- /// - `webhook` — if provided, appends `?webhook=<url>` so the API POSTs tier events
57+ /// - `opts` — optional query parameters (details, webhook, tolerance, refresh, mode, format)
4858 pub async fn check (
4959 & self ,
5060 origin : & str ,
5161 identifier : & str ,
52- details : bool ,
53- webhook : Option < & str > ,
62+ opts : & CheckOptions < ' _ > ,
5463 ) -> Result < CheckResult > {
5564 let url = format ! ( "{}/{}/{}" , self . base_url, origin, identifier) ;
5665
5766 let mut query: Vec < ( & str , String ) > = Vec :: new ( ) ;
58- if details {
67+ if opts . details {
5968 query. push ( ( "details" , "true" . into ( ) ) ) ;
6069 }
61- if let Some ( wh) = webhook {
70+ if let Some ( wh) = opts . webhook {
6271 query. push ( ( "webhook" , wh. to_string ( ) ) ) ;
6372 }
73+ if let Some ( t) = opts. tolerance {
74+ query. push ( ( "tolerance" , t. to_string ( ) ) ) ;
75+ }
76+ if opts. refresh {
77+ query. push ( ( "refresh" , "true" . into ( ) ) ) ;
78+ }
79+ if let Some ( m) = opts. mode {
80+ query. push ( ( "mode" , m. to_string ( ) ) ) ;
81+ }
82+ if let Some ( f) = opts. format {
83+ query. push ( ( "format" , f. to_string ( ) ) ) ;
84+ }
6485
6586 let response = self
6687 . client
@@ -168,7 +189,10 @@ mod tests {
168189 . await ;
169190
170191 let client = BrinClient :: new ( & server. uri ( ) ) ;
171- let result = client. check ( "npm" , "express" , false , None ) . await . unwrap ( ) ;
192+ let result = client
193+ . check ( "npm" , "express" , & CheckOptions :: default ( ) )
194+ . await
195+ . unwrap ( ) ;
172196
173197 // body is valid JSON containing expected fields
174198 let v: serde_json:: Value = serde_json:: from_str ( & result. body ) . unwrap ( ) ;
@@ -200,7 +224,7 @@ mod tests {
200224
201225 let client = BrinClient :: new ( & server. uri ( ) ) ;
202226 let result = client
203- . check ( "repo" , "expressjs/express" , false , None )
227+ . check ( "repo" , "expressjs/express" , & CheckOptions :: default ( ) )
204228 . await
205229 . unwrap ( ) ;
206230
@@ -227,7 +251,7 @@ mod tests {
227251
228252 let client = BrinClient :: new ( & server. uri ( ) ) ;
229253 let result = client
230- . check ( "npm" , "lodash@4.17.21" , false , None )
254+ . check ( "npm" , "lodash@4.17.21" , & CheckOptions :: default ( ) )
231255 . await
232256 . unwrap ( ) ;
233257
@@ -250,7 +274,11 @@ mod tests {
250274 . await ;
251275
252276 let client = BrinClient :: new ( & server. uri ( ) ) ;
253- let result = client. check ( "npm" , "express" , true , None ) . await . unwrap ( ) ;
277+ let opts = CheckOptions {
278+ details : true ,
279+ ..Default :: default ( )
280+ } ;
281+ let result = client. check ( "npm" , "express" , & opts) . await . unwrap ( ) ;
254282
255283 let v: serde_json:: Value = serde_json:: from_str ( & result. body ) . unwrap ( ) ;
256284 assert ! (
@@ -274,8 +302,10 @@ mod tests {
274302 . await ;
275303
276304 let client = BrinClient :: new ( & server. uri ( ) ) ;
277- // details=false — should succeed without the query param being required
278- let result = client. check ( "npm" , "express" , false , None ) . await . unwrap ( ) ;
305+ let result = client
306+ . check ( "npm" , "express" , & CheckOptions :: default ( ) )
307+ . await
308+ . unwrap ( ) ;
279309 let v: serde_json:: Value = serde_json:: from_str ( & result. body ) . unwrap ( ) ;
280310 assert ! ( v[ "sub_scores" ] . is_null( ) || !v. as_object( ) . unwrap( ) . contains_key( "sub_scores" ) ) ;
281311 }
@@ -294,10 +324,11 @@ mod tests {
294324 . await ;
295325
296326 let client = BrinClient :: new ( & server. uri ( ) ) ;
297- let result = client
298- . check ( "npm" , "express" , false , Some ( "https://my-server.com/cb" ) )
299- . await
300- . unwrap ( ) ;
327+ let opts = CheckOptions {
328+ webhook : Some ( "https://my-server.com/cb" ) ,
329+ ..Default :: default ( )
330+ } ;
331+ let result = client. check ( "npm" , "express" , & opts) . await . unwrap ( ) ;
301332
302333 let v: serde_json:: Value = serde_json:: from_str ( & result. body ) . unwrap ( ) ;
303334 assert_eq ! ( v[ "verdict" ] , "safe" ) ;
@@ -316,10 +347,12 @@ mod tests {
316347 . await ;
317348
318349 let client = BrinClient :: new ( & server. uri ( ) ) ;
319- let result = client
320- . check ( "npm" , "express" , true , Some ( "https://my-server.com/cb" ) )
321- . await
322- . unwrap ( ) ;
350+ let opts = CheckOptions {
351+ details : true ,
352+ webhook : Some ( "https://my-server.com/cb" ) ,
353+ ..Default :: default ( )
354+ } ;
355+ let result = client. check ( "npm" , "express" , & opts) . await . unwrap ( ) ;
323356
324357 let v: serde_json:: Value = serde_json:: from_str ( & result. body ) . unwrap ( ) ;
325358 assert ! ( v[ "sub_scores" ] . is_object( ) ) ;
@@ -339,7 +372,10 @@ mod tests {
339372 . await ;
340373
341374 let client = BrinClient :: new ( & server. uri ( ) ) ;
342- let result = client. check ( "npm" , "express" , false , None ) . await . unwrap ( ) ;
375+ let result = client
376+ . check ( "npm" , "express" , & CheckOptions :: default ( ) )
377+ . await
378+ . unwrap ( ) ;
343379
344380 assert ! ( result. headers. score. is_none( ) ) ;
345381 assert ! ( result. headers. verdict. is_none( ) ) ;
@@ -361,7 +397,7 @@ mod tests {
361397
362398 let client = BrinClient :: new ( & server. uri ( ) ) ;
363399 let err = client
364- . check ( "npm" , "nonexistent" , false , None )
400+ . check ( "npm" , "nonexistent" , & CheckOptions :: default ( ) )
365401 . await
366402 . unwrap_err ( ) ;
367403
@@ -383,10 +419,137 @@ mod tests {
383419
384420 let client = BrinClient :: new ( & server. uri ( ) ) ;
385421 let err = client
386- . check ( "npm" , "express" , false , None )
422+ . check ( "npm" , "express" , & CheckOptions :: default ( ) )
387423 . await
388424 . unwrap_err ( ) ;
389425
390426 assert ! ( err. to_string( ) . contains( "error" ) ) ;
391427 }
428+
429+ // ── check — ?tolerance=<level> ───────────────────────────────────────
430+
431+ #[ tokio:: test]
432+ async fn check_tolerance_appends_query_param ( ) {
433+ let server = MockServer :: start ( ) . await ;
434+
435+ Mock :: given ( method ( "GET" ) )
436+ . and ( path ( "/npm/express" ) )
437+ . and ( query_param ( "tolerance" , "lenient" ) )
438+ . respond_with ( ResponseTemplate :: new ( 200 ) . set_body_json ( safe_body ( ) ) )
439+ . mount ( & server)
440+ . await ;
441+
442+ let client = BrinClient :: new ( & server. uri ( ) ) ;
443+ let opts = CheckOptions {
444+ tolerance : Some ( "lenient" ) ,
445+ ..Default :: default ( )
446+ } ;
447+ let result = client. check ( "npm" , "express" , & opts) . await . unwrap ( ) ;
448+
449+ let v: serde_json:: Value = serde_json:: from_str ( & result. body ) . unwrap ( ) ;
450+ assert_eq ! ( v[ "verdict" ] , "safe" ) ;
451+ }
452+
453+ // ── check — ?refresh=true ────────────────────────────────────────────
454+
455+ #[ tokio:: test]
456+ async fn check_refresh_appends_query_param ( ) {
457+ let server = MockServer :: start ( ) . await ;
458+
459+ Mock :: given ( method ( "GET" ) )
460+ . and ( path ( "/npm/express" ) )
461+ . and ( query_param ( "refresh" , "true" ) )
462+ . respond_with ( ResponseTemplate :: new ( 200 ) . set_body_json ( safe_body ( ) ) )
463+ . mount ( & server)
464+ . await ;
465+
466+ let client = BrinClient :: new ( & server. uri ( ) ) ;
467+ let opts = CheckOptions {
468+ refresh : true ,
469+ ..Default :: default ( )
470+ } ;
471+ let result = client. check ( "npm" , "express" , & opts) . await . unwrap ( ) ;
472+
473+ let v: serde_json:: Value = serde_json:: from_str ( & result. body ) . unwrap ( ) ;
474+ assert_eq ! ( v[ "verdict" ] , "safe" ) ;
475+ }
476+
477+ // ── check — ?mode=full ───────────────────────────────────────────────
478+
479+ #[ tokio:: test]
480+ async fn check_mode_appends_query_param ( ) {
481+ let server = MockServer :: start ( ) . await ;
482+
483+ Mock :: given ( method ( "GET" ) )
484+ . and ( path ( "/npm/express" ) )
485+ . and ( query_param ( "mode" , "full" ) )
486+ . respond_with ( ResponseTemplate :: new ( 200 ) . set_body_json ( safe_body ( ) ) )
487+ . mount ( & server)
488+ . await ;
489+
490+ let client = BrinClient :: new ( & server. uri ( ) ) ;
491+ let opts = CheckOptions {
492+ mode : Some ( "full" ) ,
493+ ..Default :: default ( )
494+ } ;
495+ let result = client. check ( "npm" , "express" , & opts) . await . unwrap ( ) ;
496+
497+ let v: serde_json:: Value = serde_json:: from_str ( & result. body ) . unwrap ( ) ;
498+ assert_eq ! ( v[ "verdict" ] , "safe" ) ;
499+ }
500+
501+ // ── check — ?format=<fmt> ────────────────────────────────────────────
502+
503+ #[ tokio:: test]
504+ async fn check_format_appends_query_param ( ) {
505+ let server = MockServer :: start ( ) . await ;
506+
507+ Mock :: given ( method ( "GET" ) )
508+ . and ( path ( "/npm/express" ) )
509+ . and ( query_param ( "format" , "simple" ) )
510+ . respond_with ( ResponseTemplate :: new ( 200 ) . set_body_string ( "safe 85" ) )
511+ . mount ( & server)
512+ . await ;
513+
514+ let client = BrinClient :: new ( & server. uri ( ) ) ;
515+ let opts = CheckOptions {
516+ format : Some ( "simple" ) ,
517+ ..Default :: default ( )
518+ } ;
519+ let result = client. check ( "npm" , "express" , & opts) . await . unwrap ( ) ;
520+
521+ assert_eq ! ( result. body, "safe 85" ) ;
522+ }
523+
524+ // ── check — all new params combined ──────────────────────────────────
525+
526+ #[ tokio:: test]
527+ async fn check_all_new_params_combined ( ) {
528+ let server = MockServer :: start ( ) . await ;
529+
530+ Mock :: given ( method ( "GET" ) )
531+ . and ( path ( "/npm/express" ) )
532+ . and ( query_param ( "details" , "true" ) )
533+ . and ( query_param ( "tolerance" , "yolo" ) )
534+ . and ( query_param ( "refresh" , "true" ) )
535+ . and ( query_param ( "mode" , "full" ) )
536+ . and ( query_param ( "format" , "json" ) )
537+ . respond_with ( ResponseTemplate :: new ( 200 ) . set_body_json ( safe_body_with_sub_scores ( ) ) )
538+ . mount ( & server)
539+ . await ;
540+
541+ let client = BrinClient :: new ( & server. uri ( ) ) ;
542+ let opts = CheckOptions {
543+ details : true ,
544+ tolerance : Some ( "yolo" ) ,
545+ refresh : true ,
546+ mode : Some ( "full" ) ,
547+ format : Some ( "json" ) ,
548+ ..Default :: default ( )
549+ } ;
550+ let result = client. check ( "npm" , "express" , & opts) . await . unwrap ( ) ;
551+
552+ let v: serde_json:: Value = serde_json:: from_str ( & result. body ) . unwrap ( ) ;
553+ assert ! ( v[ "sub_scores" ] . is_object( ) ) ;
554+ }
392555}
0 commit comments