@@ -20,18 +20,86 @@ use std::error::Error as StdError;
20
20
use std:: fmt;
21
21
use tracing:: trace;
22
22
23
+ #[ derive( Debug ) ]
24
+ struct NoMatchingAuthSchemeError ( ExploredList ) ;
25
+
26
+ impl fmt:: Display for NoMatchingAuthSchemeError {
27
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
28
+ let explored = & self . 0 ;
29
+
30
+ // Use the information we have about the auth options that were explored to construct
31
+ // as helpful of an error message as possible.
32
+ if explored. items ( ) . count ( ) == 0 {
33
+ return f. write_str (
34
+ "no auth options are available. This can happen if there's \
35
+ a problem with the service model, or if there is a codegen bug.",
36
+ ) ;
37
+ }
38
+ if explored
39
+ . items ( )
40
+ . all ( |explored| matches ! ( explored. result, ExploreResult :: NoAuthScheme ) )
41
+ {
42
+ return f. write_str (
43
+ "no auth schemes are registered. This can happen if there's \
44
+ a problem with the service model, or if there is a codegen bug.",
45
+ ) ;
46
+ }
47
+
48
+ let mut try_add_identity = false ;
49
+ let mut likely_bug = false ;
50
+ f. write_str ( "failed to select an auth scheme to sign the request with." ) ?;
51
+ for item in explored. items ( ) {
52
+ write ! (
53
+ f,
54
+ " \" {}\" wasn't a valid option because " ,
55
+ item. scheme_id. as_str( )
56
+ ) ?;
57
+ f. write_str ( match item. result {
58
+ ExploreResult :: NoAuthScheme => {
59
+ likely_bug = true ;
60
+ "no auth scheme was registered for it."
61
+ }
62
+ ExploreResult :: NoIdentityResolver => {
63
+ try_add_identity = true ;
64
+ "there was no identity resolver for it."
65
+ }
66
+ ExploreResult :: MissingEndpointConfig => {
67
+ likely_bug = true ;
68
+ "there is auth config in the endpoint config, but this scheme wasn't listed in it \
69
+ (see https://github.com/smithy-lang/smithy-rs/discussions/3281 for more details)."
70
+ }
71
+ ExploreResult :: NotExplored => {
72
+ debug_assert ! ( false , "this should be unreachable" ) ;
73
+ "<unknown>"
74
+ }
75
+ } ) ?;
76
+ }
77
+ if try_add_identity {
78
+ f. write_str ( " Be sure to set an identity, such as credentials, auth token, or other identity type that is required for this service." ) ?;
79
+ } else if likely_bug {
80
+ f. write_str ( " This is likely a bug." ) ?;
81
+ }
82
+ if explored. truncated {
83
+ f. write_str ( " Note: there were other auth schemes that were evaluated that weren't listed here." ) ?;
84
+ }
85
+
86
+ Ok ( ( ) )
87
+ }
88
+ }
89
+
90
+ impl StdError for NoMatchingAuthSchemeError { }
91
+
23
92
#[ derive( Debug ) ]
24
93
enum AuthOrchestrationError {
25
- NoMatchingAuthScheme ,
94
+ MissingEndpointConfig ,
26
95
BadAuthSchemeEndpointConfig ( Cow < ' static , str > ) ,
27
96
}
28
97
29
98
impl fmt:: Display for AuthOrchestrationError {
30
99
fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
31
100
match self {
32
- Self :: NoMatchingAuthScheme => f. write_str (
33
- "no auth scheme matched auth scheme options. This is a bug. Please file an issue." ,
34
- ) ,
101
+ // This error is never bubbled up
102
+ Self :: MissingEndpointConfig => f. write_str ( "missing endpoint config" ) ,
35
103
Self :: BadAuthSchemeEndpointConfig ( message) => f. write_str ( message) ,
36
104
}
37
105
}
@@ -59,6 +127,8 @@ pub(super) async fn orchestrate_auth(
59
127
"orchestrating auth" ,
60
128
) ;
61
129
130
+ let mut explored = ExploredList :: default ( ) ;
131
+
62
132
// Iterate over IDs of possibly-supported auth schemes
63
133
for & scheme_id in options. as_ref ( ) {
64
134
// For each ID, try to resolve the corresponding auth scheme.
@@ -95,16 +165,21 @@ pub(super) async fn orchestrate_auth(
95
165
) ?;
96
166
return Ok ( ( ) ) ;
97
167
}
98
- Err ( AuthOrchestrationError :: NoMatchingAuthScheme ) => {
168
+ Err ( AuthOrchestrationError :: MissingEndpointConfig ) => {
169
+ explored. push ( scheme_id, ExploreResult :: MissingEndpointConfig ) ;
99
170
continue ;
100
171
}
101
172
Err ( other_err) => return Err ( other_err. into ( ) ) ,
102
173
}
174
+ } else {
175
+ explored. push ( scheme_id, ExploreResult :: NoIdentityResolver ) ;
103
176
}
177
+ } else {
178
+ explored. push ( scheme_id, ExploreResult :: NoAuthScheme ) ;
104
179
}
105
180
}
106
181
107
- Err ( AuthOrchestrationError :: NoMatchingAuthScheme . into ( ) )
182
+ Err ( NoMatchingAuthSchemeError ( explored ) . into ( ) )
108
183
}
109
184
110
185
fn extract_endpoint_auth_scheme_config (
@@ -135,10 +210,66 @@ fn extract_endpoint_auth_scheme_config(
135
210
. and_then ( Document :: as_string) ;
136
211
config_scheme_id == Some ( scheme_id. as_str ( ) )
137
212
} )
138
- . ok_or ( AuthOrchestrationError :: NoMatchingAuthScheme ) ?;
213
+ . ok_or ( AuthOrchestrationError :: MissingEndpointConfig ) ?;
139
214
Ok ( AuthSchemeEndpointConfig :: from ( Some ( auth_scheme_config) ) )
140
215
}
141
216
217
+ #[ derive( Debug ) ]
218
+ enum ExploreResult {
219
+ NotExplored ,
220
+ NoAuthScheme ,
221
+ NoIdentityResolver ,
222
+ MissingEndpointConfig ,
223
+ }
224
+
225
+ /// Information about an evaluated auth option.
226
+ /// This should be kept small so it can fit in an array on the stack.
227
+ #[ derive( Debug ) ]
228
+ struct ExploredAuthOption {
229
+ scheme_id : AuthSchemeId ,
230
+ result : ExploreResult ,
231
+ }
232
+ impl Default for ExploredAuthOption {
233
+ fn default ( ) -> Self {
234
+ Self {
235
+ scheme_id : AuthSchemeId :: new ( "" ) ,
236
+ result : ExploreResult :: NotExplored ,
237
+ }
238
+ }
239
+ }
240
+
241
+ const MAX_EXPLORED_LIST_LEN : usize = 8 ;
242
+
243
+ /// Stack allocated list of explored auth options for error messaging
244
+ #[ derive( Default ) ]
245
+ struct ExploredList {
246
+ items : [ ExploredAuthOption ; MAX_EXPLORED_LIST_LEN ] ,
247
+ len : usize ,
248
+ truncated : bool ,
249
+ }
250
+ impl ExploredList {
251
+ fn items ( & self ) -> impl Iterator < Item = & ExploredAuthOption > {
252
+ self . items . iter ( ) . take ( self . len )
253
+ }
254
+
255
+ fn push ( & mut self , scheme_id : AuthSchemeId , result : ExploreResult ) {
256
+ if self . len + 1 >= self . items . len ( ) {
257
+ self . truncated = true ;
258
+ } else {
259
+ self . items [ self . len ] = ExploredAuthOption { scheme_id, result } ;
260
+ self . len += 1 ;
261
+ }
262
+ }
263
+ }
264
+ impl fmt:: Debug for ExploredList {
265
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
266
+ f. debug_struct ( "ExploredList" )
267
+ . field ( "items" , & & self . items [ 0 ..self . len ] )
268
+ . field ( "truncated" , & self . truncated )
269
+ . finish ( )
270
+ }
271
+ }
272
+
142
273
#[ cfg( all( test, feature = "test-util" ) ) ]
143
274
mod tests {
144
275
use super :: * ;
@@ -481,4 +612,86 @@ mod tests {
481
612
. unwrap( )
482
613
) ;
483
614
}
615
+
616
+ #[ test]
617
+ fn friendly_error_messages ( ) {
618
+ let err = NoMatchingAuthSchemeError ( ExploredList :: default ( ) ) ;
619
+ assert_eq ! (
620
+ "no auth options are available. This can happen if there's a problem with \
621
+ the service model, or if there is a codegen bug.",
622
+ err. to_string( )
623
+ ) ;
624
+
625
+ let mut list = ExploredList :: default ( ) ;
626
+ list. push (
627
+ AuthSchemeId :: new ( "SigV4" ) ,
628
+ ExploreResult :: NoIdentityResolver ,
629
+ ) ;
630
+ list. push (
631
+ AuthSchemeId :: new ( "SigV4a" ) ,
632
+ ExploreResult :: NoIdentityResolver ,
633
+ ) ;
634
+ let err = NoMatchingAuthSchemeError ( list) ;
635
+ assert_eq ! (
636
+ "failed to select an auth scheme to sign the request with. \
637
+ \" SigV4\" wasn't a valid option because there was no identity resolver for it. \
638
+ \" SigV4a\" wasn't a valid option because there was no identity resolver for it. \
639
+ Be sure to set an identity, such as credentials, auth token, or other identity \
640
+ type that is required for this service.",
641
+ err. to_string( )
642
+ ) ;
643
+
644
+ // It should prioritize the suggestion to try an identity before saying it's a bug
645
+ let mut list = ExploredList :: default ( ) ;
646
+ list. push (
647
+ AuthSchemeId :: new ( "SigV4" ) ,
648
+ ExploreResult :: NoIdentityResolver ,
649
+ ) ;
650
+ list. push (
651
+ AuthSchemeId :: new ( "SigV4a" ) ,
652
+ ExploreResult :: MissingEndpointConfig ,
653
+ ) ;
654
+ let err = NoMatchingAuthSchemeError ( list) ;
655
+ assert_eq ! (
656
+ "failed to select an auth scheme to sign the request with. \
657
+ \" SigV4\" wasn't a valid option because there was no identity resolver for it. \
658
+ \" SigV4a\" wasn't a valid option because there is auth config in the endpoint \
659
+ config, but this scheme wasn't listed in it (see \
660
+ https://github.com/smithy-lang/smithy-rs/discussions/3281 for more details). \
661
+ Be sure to set an identity, such as credentials, auth token, or other identity \
662
+ type that is required for this service.",
663
+ err. to_string( )
664
+ ) ;
665
+
666
+ // Otherwise, it should suggest it's a bug
667
+ let mut list = ExploredList :: default ( ) ;
668
+ list. push (
669
+ AuthSchemeId :: new ( "SigV4a" ) ,
670
+ ExploreResult :: MissingEndpointConfig ,
671
+ ) ;
672
+ let err = NoMatchingAuthSchemeError ( list) ;
673
+ assert_eq ! (
674
+ "failed to select an auth scheme to sign the request with. \
675
+ \" SigV4a\" wasn't a valid option because there is auth config in the endpoint \
676
+ config, but this scheme wasn't listed in it (see \
677
+ https://github.com/smithy-lang/smithy-rs/discussions/3281 for more details). \
678
+ This is likely a bug.",
679
+ err. to_string( )
680
+ ) ;
681
+
682
+ // Truncation should be indicated
683
+ let mut list = ExploredList :: default ( ) ;
684
+ for _ in 0 ..=MAX_EXPLORED_LIST_LEN {
685
+ list. push (
686
+ AuthSchemeId :: new ( "dontcare" ) ,
687
+ ExploreResult :: MissingEndpointConfig ,
688
+ ) ;
689
+ }
690
+ let err = NoMatchingAuthSchemeError ( list) . to_string ( ) ;
691
+ if !err. contains (
692
+ "Note: there were other auth schemes that were evaluated that weren't listed here" ,
693
+ ) {
694
+ panic ! ( "The error should indicate that the explored list was truncated." ) ;
695
+ }
696
+ }
484
697
}
0 commit comments