@@ -63,6 +63,13 @@ impl ClientError {
6363 }
6464}
6565
66+ fn parse_error_message ( payload : serde_json:: Value ) -> String {
67+ payload[ "error_message" ]
68+ . as_str ( )
69+ . expect ( "Error message from api.rerobots.net should be a string" )
70+ . to_string ( )
71+ }
72+
6673#[ cfg( not( test) ) ]
6774fn get_origin ( ) -> String {
6875 option_env ! ( "REROBOTS_ORIGIN" )
@@ -96,7 +103,8 @@ impl TokenClaims {
96103 pub fn new ( api_token : & str ) -> Result < TokenClaims , & str > {
97104 let alg = PKeyWithDigest {
98105 digest : MessageDigest :: sha256 ( ) ,
99- key : PKey :: public_key_from_pem ( PUBLIC_KEY ) . unwrap ( ) ,
106+ key : PKey :: public_key_from_pem ( PUBLIC_KEY )
107+ . expect ( "PEM text should be parsed into public key" ) ,
100108 } ;
101109
102110 let result: Result < Token < Header , Claims , _ > , jwt:: error:: Error > =
@@ -109,10 +117,19 @@ impl TokenClaims {
109117 } ,
110118 } ;
111119 let claims = parsed_tok. claims ( ) ;
112- let subject = claims. registered . subject . as_ref ( ) . unwrap ( ) ;
120+ let subject = claims
121+ . registered
122+ . subject
123+ . as_ref ( )
124+ . ok_or ( "Token has subject claim" ) ?;
113125 let expiration = claims. registered . expiration ;
114126 let organization = if claims. private . contains_key ( "org" ) {
115- Some ( claims. private [ "org" ] . as_str ( ) . unwrap ( ) . into ( ) )
127+ Some (
128+ claims. private [ "org" ]
129+ . as_str ( )
130+ . expect ( "org claim is string" )
131+ . into ( ) ,
132+ )
116133 } else {
117134 None
118135 } ;
@@ -129,7 +146,10 @@ impl TokenClaims {
129146 match self . expiration {
130147 Some ( exp) => {
131148 let now = std:: time:: SystemTime :: now ( ) ;
132- let utime = now. duration_since ( std:: time:: UNIX_EPOCH ) . unwrap ( ) . as_secs ( ) ;
149+ let utime = now
150+ . duration_since ( std:: time:: UNIX_EPOCH )
151+ . expect ( "Duration since Unix epoch should be computable" )
152+ . as_secs ( ) ;
133153 exp < utime
134154 }
135155 None => false ,
@@ -150,7 +170,9 @@ impl std::fmt::Display for TokenClaims {
150170 write ! (
151171 f,
152172 "expiration: {}" ,
153- Utc . timestamp_opt( exp as i64 , 0 ) . unwrap( )
173+ Utc . timestamp_opt( exp as i64 , 0 )
174+ . single( )
175+ . expect( "Expiration (in seconds) should be parsed as timestamp" )
154176 )
155177 }
156178 None => write ! ( f, "expiration: (never)" ) ,
@@ -161,8 +183,13 @@ impl std::fmt::Display for TokenClaims {
161183fn create_client ( token : Option < String > ) -> Result < awc:: Client , Box < dyn std:: error:: Error > > {
162184 let authheader = match token {
163185 Some ( tok) => Some ( format ! ( "Bearer {tok}" ) ) ,
164- None => std:: env:: var_os ( "REROBOTS_API_TOKEN" )
165- . map ( |tok| format ! ( "Bearer {}" , tok. into_string( ) . unwrap( ) ) ) ,
186+ None => std:: env:: var_os ( "REROBOTS_API_TOKEN" ) . map ( |tok| {
187+ format ! (
188+ "Bearer {}" ,
189+ tok. into_string( )
190+ . expect( "REROBOTS_API_TOKEN variable should be valid string" )
191+ )
192+ } ) ,
166193 } ;
167194
168195 let client = awc:: Client :: builder ( ) ;
@@ -202,7 +229,7 @@ pub fn api_search(
202229 Ok ( payload)
203230 } else if resp. status ( ) == 400 {
204231 let payload: serde_json:: Value = serde_json:: from_slice ( resp. body ( ) . await ?. as_ref ( ) ) ?;
205- ClientError :: newbox ( String :: from ( payload[ "error_message" ] . as_str ( ) . unwrap ( ) ) )
232+ ClientError :: newbox ( parse_error_message ( payload) )
206233 } else {
207234 ClientError :: newbox ( format ! ( "server indicated error: {}" , resp. status( ) ) )
208235 }
@@ -232,7 +259,7 @@ pub fn api_instances(
232259 Ok ( payload)
233260 } else if resp. status ( ) == 400 {
234261 let payload: serde_json:: Value = serde_json:: from_slice ( resp. body ( ) . await ?. as_ref ( ) ) ?;
235- ClientError :: newbox ( String :: from ( payload[ "error_message" ] . as_str ( ) . unwrap ( ) ) )
262+ ClientError :: newbox ( parse_error_message ( payload) )
236263 } else {
237264 ClientError :: newbox ( format ! ( "server indicated error: {}" , resp. status( ) ) )
238265 }
@@ -248,13 +275,18 @@ fn select_instance<S: ToString>(
248275 Some ( inid) => Ok ( inid. to_string ( ) ) ,
249276 None => {
250277 let payload = api_instances ( token, false ) ?;
251- let instances = payload[ "workspace_instances" ] . as_array ( ) . unwrap ( ) ;
278+ let instances = payload[ "workspace_instances" ]
279+ . as_array ( )
280+ . ok_or ( "workspace_instances should be array" ) ?;
252281 if instances. is_empty ( ) {
253282 ClientError :: newbox ( "no active instances" )
254283 } else if instances. len ( ) > 1 {
255284 ClientError :: newbox ( "ambiguous command because more than one active instance" )
256285 } else {
257- Ok ( instances[ 0 ] . as_str ( ) . unwrap ( ) . to_string ( ) )
286+ Ok ( instances[ 0 ]
287+ . as_str ( )
288+ . ok_or ( "Elements of workspace_instances should be strings" ) ?
289+ . to_string ( ) )
258290 }
259291 }
260292 }
@@ -285,7 +317,7 @@ pub fn api_instance_info<S: ToString>(
285317 ClientError :: newbox ( "instance not found" )
286318 } else if resp. status ( ) == 400 {
287319 let payload: serde_json:: Value = serde_json:: from_slice ( resp. body ( ) . await ?. as_ref ( ) ) ?;
288- ClientError :: newbox ( String :: from ( payload[ "error_message" ] . as_str ( ) . unwrap ( ) ) )
320+ ClientError :: newbox ( parse_error_message ( payload) )
289321 } else {
290322 ClientError :: newbox ( format ! ( "server indicated error: {}" , resp. status( ) ) )
291323 }
@@ -312,12 +344,15 @@ pub fn get_instance_sshkey<S: ToString>(
312344 if resp. status ( ) == 200 {
313345 let payload: serde_json:: Value = serde_json:: from_slice ( resp. body ( ) . await ?. as_ref ( ) ) ?;
314346 debug ! ( "resp to GET {}: {}" , path, serde_json:: to_string( & payload) ?) ;
315- Ok ( payload[ "key" ] . as_str ( ) . unwrap ( ) . to_string ( ) )
347+ Ok ( payload[ "key" ]
348+ . as_str ( )
349+ . ok_or ( "key should be string" ) ?
350+ . to_string ( ) )
316351 } else if resp. status ( ) == 404 {
317352 ClientError :: newbox ( "instance not found" )
318353 } else if resp. status ( ) == 400 {
319354 let payload: serde_json:: Value = serde_json:: from_slice ( resp. body ( ) . await ?. as_ref ( ) ) ?;
320- ClientError :: newbox ( String :: from ( payload[ "error_message" ] . as_str ( ) . unwrap ( ) ) )
355+ ClientError :: newbox ( parse_error_message ( payload) )
321356 } else {
322357 ClientError :: newbox ( format ! ( "server indicated error: {}" , resp. status( ) ) )
323358 }
@@ -357,14 +392,12 @@ pub fn api_wdeployment_info<S: std::fmt::Display>(
357392 ) ;
358393 payload
359394 . as_object_mut ( )
360- . unwrap ( )
395+ . expect ( "Response is JSON object" )
361396 . insert ( "cap" . to_string ( ) , rules_payload) ;
362397 } else if resp. status ( ) == 400 {
363398 let payload: serde_json:: Value =
364399 serde_json:: from_slice ( resp. body ( ) . await ?. as_ref ( ) ) ?;
365- return ClientError :: newbox ( String :: from (
366- payload[ "error_message" ] . as_str ( ) . unwrap ( ) ,
367- ) ) ;
400+ return ClientError :: newbox ( parse_error_message ( payload) ) ;
368401 } else {
369402 return ClientError :: newbox ( format ! (
370403 "server indicated error: {}" ,
@@ -378,7 +411,7 @@ pub fn api_wdeployment_info<S: std::fmt::Display>(
378411 ClientError :: newbox ( "workspace deployment not found" )
379412 } else if resp. status ( ) == 400 {
380413 let payload: serde_json:: Value = serde_json:: from_slice ( resp. body ( ) . await ?. as_ref ( ) ) ?;
381- ClientError :: newbox ( String :: from ( payload[ "error_message" ] . as_str ( ) . unwrap ( ) ) )
414+ ClientError :: newbox ( parse_error_message ( payload) )
382415 } else {
383416 ClientError :: newbox ( format ! ( "server indicated error: {}" , resp. status( ) ) )
384417 }
@@ -408,7 +441,7 @@ pub fn api_terminate_instance(
408441 ClientError :: newbox ( "instance not found" )
409442 } else if resp. status ( ) == 400 {
410443 let payload: serde_json:: Value = serde_json:: from_slice ( resp. body ( ) . await ?. as_ref ( ) ) ?;
411- ClientError :: newbox ( String :: from ( payload[ "error_message" ] . as_str ( ) . unwrap ( ) ) )
444+ ClientError :: newbox ( parse_error_message ( payload) )
412445 } else {
413446 ClientError :: newbox ( format ! ( "server indicated error: {}" , resp. status( ) ) )
414447 }
@@ -448,7 +481,7 @@ pub fn api_launch_instance(
448481 Ok ( payload)
449482 } else if resp. status ( ) == 400 {
450483 let payload: serde_json:: Value = serde_json:: from_slice ( resp. body ( ) . await ?. as_ref ( ) ) ?;
451- ClientError :: newbox ( String :: from ( payload[ "error_message" ] . as_str ( ) . unwrap ( ) ) )
484+ ClientError :: newbox ( parse_error_message ( payload) )
452485 } else {
453486 ClientError :: newbox ( format ! ( "server indicated error: {}" , resp. status( ) ) )
454487 }
@@ -462,8 +495,10 @@ mod tests {
462495 use super :: api_search;
463496 use super :: TokenClaims ;
464497
498+ type TestResult = Result < ( ) , Box < dyn std:: error:: Error > > ;
499+
465500 #[ test]
466- fn empty_search ( ) {
501+ fn empty_search ( ) -> TestResult {
467502 let _m = mock ( "GET" , "/deployments?info=t" )
468503 . with_status ( 200 )
469504 . with_header ( "content-type" , "application/json" )
@@ -476,13 +511,16 @@ mod tests {
476511 )
477512 . create ( ) ;
478513
479- let res = api_search ( None , None , None ) . unwrap ( ) ;
480- let wds = res[ "workspace_deployments" ] . as_array ( ) . unwrap ( ) ;
481- assert_eq ! ( wds. len( ) , 0 )
514+ let res = api_search ( None , None , None ) ?;
515+ let wds = res[ "workspace_deployments" ]
516+ . as_array ( )
517+ . ok_or ( "workspace_deployments should be array" ) ?;
518+ assert_eq ! ( wds. len( ) , 0 ) ;
519+ Ok ( ( ) )
482520 }
483521
484522 #[ test]
485- fn search_with_1 ( ) {
523+ fn search_with_1 ( ) -> TestResult {
486524 let _m = mock ( "GET" , "/deployments?info=t" )
487525 . with_status ( 200 )
488526 . with_header ( "content-type" , "application/json" )
@@ -512,27 +550,42 @@ mod tests {
512550 )
513551 . create ( ) ;
514552
515- let res = api_search ( None , None , None ) . unwrap ( ) ;
516- let wds = res[ "workspace_deployments" ] . as_array ( ) . unwrap ( ) ;
553+ let res = api_search ( None , None , None ) ?;
554+ let wds = res[ "workspace_deployments" ]
555+ . as_array ( )
556+ . ok_or ( "workspace_deployments should be array" ) ?;
517557 assert_eq ! ( wds. len( ) , 1 ) ;
518558 assert_eq ! ( wds[ 0 ] , "82051afa-b331-4b82-8bd4-9eea9ad78241" ) ;
519- let info = res[ "info" ] [ wds[ 0 ] . as_str ( ) . unwrap ( ) ] . as_object ( ) . unwrap ( ) ;
559+ let info = res[ "info" ] [ wds[ 0 ]
560+ . as_str ( )
561+ . ok_or ( "Elements of workspace_deployments should be strings" ) ?]
562+ . as_object ( )
563+ . ok_or ( "info should be JSON object" ) ?;
520564 assert_eq ! ( info[ "type" ] , "fixed_misty2" ) ;
565+
566+ Ok ( ( ) )
521567 }
522568
523569 #[ test]
524- fn detect_expired_token ( ) {
570+ fn detect_expired_token ( ) -> TestResult {
525571 const EXPIRED_TOKEN : & str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0X3VzZXIiLCJpc3MiOiJyZXJvYm90cy5uZXQiLCJhdWQiOiJyZXJvYm90cy5uZXQiLCJleHAiOjE2Nzk2ODQ0MjQsIm5iZiI6MTY3OTU5ODAyNH0.R9Z4Y5tVHJiPTGfEjUljIGmzor4SAmUmdXyuvQBF2oc6QVHFfxGD-QnVaDfyB6Q2WbBiMWsvDgsI2X56t_-Cd7tZio-VQLL-AEwu1uTnOnt3aXwYB211M7b5ZEFrNN5BNS00FqnMpOQ5DfWKzYUqzvVV4gbxdPhLD2PUpMvT6-F-9NgtR_Z5VEeR-rzRI1-A0KYP9tWHh8GeEPb4D449wtcp-a-Hy6GHOFGGJupSkiB1aJ0T-b1CPlEDN9uwgEq4N_2hHMXwYiyc5Qpo5PABAB_BhM0CP43ca2M9n67om9oQZHqnkon_RWJDSjNAGCn8aZGSfKv0E2pahXfqjhWn6Eakb_R4VDNFBIy6xAl1i0NT-YDdF8-4kLA0sxIoLnk812LvmoifsFsmuv1cdSAAccYdyMjwyQDNCMjFCSJSR6pwZhpfsaUB1frTXWaFteA8yGf8bkL59i3yWherji7yfRY-aepVSH2Hjw_bHxVIPq3mulrhW0XI8qk6uPS1CB5F4Thqqvf_G-qIx0HJWAzF6zTkoAjhOa-BUIuxGo2w17fxK2RhzoOfMZWSfQqifKdCxhuGNOTpyD7nsK9OQP9_S1krOLSvavWuPfTV5GN-NhmRSAcg8Qcv1UkGguZaAFlHlGOzlw4PI_9qGhIxPj7t-PjHyETdH4IrdilpQkXgZuw" ;
526- let tc = TokenClaims :: new ( EXPIRED_TOKEN ) . unwrap ( ) ;
572+ let tc = TokenClaims :: new ( EXPIRED_TOKEN ) ? ;
527573 assert ! ( tc. is_expired( ) ) ;
528574 assert_eq ! ( tc. subject, "test_user" ) ;
529575 assert_eq ! ( tc. organization, None ) ;
530- assert_eq ! ( tc. expiration. unwrap ( ) , 1679684424 ) ;
576+ assert_eq ! ( tc. expiration, Some ( 1679684424 ) ) ;
531577
532578 let mut tok = String :: from ( EXPIRED_TOKEN ) ;
533579 tok. push ( 'F' ) ;
534- let error = TokenClaims :: new ( & tok) . unwrap_err ( ) ;
580+ let error = match TokenClaims :: new ( & tok) {
581+ Ok ( _) => {
582+ panic ! ( "Error should be detected when parsing broken token text" )
583+ }
584+ Err ( e) => e,
585+ } ;
535586 assert_eq ! ( error, "not a valid signature" ) ;
587+
588+ Ok ( ( ) )
536589 }
537590
538591 #[ test]
0 commit comments