@@ -10,12 +10,12 @@ use crate::JSON_CONTENT_TYPE;
10
10
/// Error structure for the Aggregator Client.
11
11
#[ derive( Error , Debug ) ]
12
12
pub enum AggregatorClientError {
13
- /// The aggregator host has returned a technical error.
14
- #[ error( "remote server technical error " ) ]
13
+ /// Error raised when querying the aggregator returned a 5XX error.
14
+ #[ error( "Internal error of the Aggregator " ) ]
15
15
RemoteServerTechnical ( #[ source] StdError ) ,
16
16
17
- /// The aggregator host responded it cannot fulfill our request .
18
- #[ error( "remote server logical error " ) ]
17
+ /// Error raised when querying the aggregator returned a 4XX error .
18
+ #[ error( "Invalid request to the Aggregator " ) ]
19
19
RemoteServerLogical ( #[ source] StdError ) ,
20
20
21
21
/// Could not reach aggregator.
@@ -33,6 +33,10 @@ pub enum AggregatorClientError {
33
33
/// Failed to join the query endpoint to the aggregator url
34
34
#[ error( "Invalid endpoint" ) ]
35
35
InvalidEndpoint ( #[ source] StdError ) ,
36
+
37
+ /// No signer registration round opened yet
38
+ #[ error( "A signer registration round is not opened yet, please try again later" ) ]
39
+ RegistrationRoundNotYetOpened ( #[ source] StdError ) ,
36
40
}
37
41
38
42
impl AggregatorClientError {
@@ -49,7 +53,10 @@ impl AggregatorClientError {
49
53
Self :: RemoteServerLogical ( anyhow ! ( root_cause) )
50
54
} else if error_code. is_server_error ( ) {
51
55
let root_cause = Self :: get_root_cause ( response) . await ;
52
- Self :: RemoteServerTechnical ( anyhow ! ( root_cause) )
56
+ match error_code. as_u16 ( ) {
57
+ 550 => Self :: RegistrationRoundNotYetOpened ( anyhow ! ( root_cause) ) ,
58
+ _ => Self :: RemoteServerTechnical ( anyhow ! ( root_cause) ) ,
59
+ }
53
60
} else {
54
61
let response_text = response. text ( ) . await . unwrap_or_default ( ) ;
55
62
Self :: UnhandledStatusCode ( error_code, response_text)
@@ -87,3 +94,162 @@ impl AggregatorClientError {
87
94
}
88
95
}
89
96
}
97
+
98
+ #[ cfg( test) ]
99
+ mod tests {
100
+ use http:: response:: Builder as HttpResponseBuilder ;
101
+ use serde_json:: json;
102
+
103
+ use super :: * ;
104
+
105
+ macro_rules! assert_error_text_contains {
106
+ ( $error: expr, $expect_contains: expr) => {
107
+ let error = & $error;
108
+ assert!(
109
+ error. contains( $expect_contains) ,
110
+ "Expected error message to contain '{}'\n got '{error:?}'" ,
111
+ $expect_contains,
112
+ ) ;
113
+ } ;
114
+ }
115
+
116
+ fn build_text_response < T : Into < String > > ( status_code : StatusCode , body : T ) -> Response {
117
+ HttpResponseBuilder :: new ( )
118
+ . status ( status_code)
119
+ . body ( body. into ( ) )
120
+ . unwrap ( )
121
+ . into ( )
122
+ }
123
+
124
+ fn build_json_response < T : serde:: Serialize > ( status_code : StatusCode , body : & T ) -> Response {
125
+ HttpResponseBuilder :: new ( )
126
+ . status ( status_code)
127
+ . header ( header:: CONTENT_TYPE , JSON_CONTENT_TYPE )
128
+ . body ( serde_json:: to_string ( & body) . unwrap ( ) )
129
+ . unwrap ( )
130
+ . into ( )
131
+ }
132
+
133
+ #[ tokio:: test]
134
+ async fn test_4xx_errors_are_handled_as_remote_server_logical ( ) {
135
+ let response = build_text_response ( StatusCode :: BAD_REQUEST , "error text" ) ;
136
+ let handled_error = AggregatorClientError :: from_response ( response) . await ;
137
+
138
+ assert ! (
139
+ matches!(
140
+ handled_error,
141
+ AggregatorClientError :: RemoteServerLogical ( ..)
142
+ ) ,
143
+ "Expected error to be RemoteServerLogical\n got '{handled_error:?}'" ,
144
+ ) ;
145
+ }
146
+
147
+ #[ tokio:: test]
148
+ async fn test_5xx_errors_are_handled_as_remote_server_technical ( ) {
149
+ let response = build_text_response ( StatusCode :: INTERNAL_SERVER_ERROR , "error text" ) ;
150
+ let handled_error = AggregatorClientError :: from_response ( response) . await ;
151
+
152
+ assert ! (
153
+ matches!(
154
+ handled_error,
155
+ AggregatorClientError :: RemoteServerTechnical ( ..)
156
+ ) ,
157
+ "Expected error to be RemoteServerLogical\n got '{handled_error:?}'" ,
158
+ ) ;
159
+ }
160
+
161
+ #[ tokio:: test]
162
+ async fn test_550_error_is_handled_as_registration_round_not_yet_opened ( ) {
163
+ let response = build_text_response ( StatusCode :: from_u16 ( 550 ) . unwrap ( ) , "Not yet available" ) ;
164
+ let handled_error = AggregatorClientError :: from_response ( response) . await ;
165
+
166
+ assert ! (
167
+ matches!(
168
+ handled_error,
169
+ AggregatorClientError :: RegistrationRoundNotYetOpened ( ..)
170
+ ) ,
171
+ "Expected error to be RegistrationRoundNotYetOpened\n got '{handled_error:?}'" ,
172
+ ) ;
173
+ }
174
+
175
+ #[ tokio:: test]
176
+ async fn test_non_4xx_or_5xx_errors_are_handled_as_unhandled_status_code_and_contains_response_text ( )
177
+ {
178
+ let response = build_text_response ( StatusCode :: OK , "ok text" ) ;
179
+ let handled_error = AggregatorClientError :: from_response ( response) . await ;
180
+
181
+ assert ! (
182
+ matches!(
183
+ handled_error,
184
+ AggregatorClientError :: UnhandledStatusCode ( ..) if format!( "{handled_error:?}" ) . contains( "ok text" )
185
+ ) ,
186
+ "Expected error to be UnhandledStatusCode with 'ok text' in error text\n got '{handled_error:?}'" ,
187
+ ) ;
188
+ }
189
+
190
+ #[ tokio:: test]
191
+ async fn test_root_cause_of_non_json_response_contains_response_plain_text ( ) {
192
+ let error_text = "An error occurred; please try again later." ;
193
+ let response = build_text_response ( StatusCode :: EXPECTATION_FAILED , error_text) ;
194
+
195
+ assert_error_text_contains ! (
196
+ AggregatorClientError :: get_root_cause( response) . await ,
197
+ "expectation failed: An error occurred; please try again later."
198
+ ) ;
199
+ }
200
+
201
+ #[ tokio:: test]
202
+ async fn test_root_cause_of_json_formatted_client_error_response_contains_error_label_and_message ( )
203
+ {
204
+ let client_error = ClientError :: new ( "label" , "message" ) ;
205
+ let response = build_json_response ( StatusCode :: BAD_REQUEST , & client_error) ;
206
+
207
+ assert_error_text_contains ! (
208
+ AggregatorClientError :: get_root_cause( response) . await ,
209
+ "bad request: label: message"
210
+ ) ;
211
+ }
212
+
213
+ #[ tokio:: test]
214
+ async fn test_root_cause_of_json_formatted_server_error_response_contains_error_label_and_message ( )
215
+ {
216
+ let server_error = ServerError :: new ( "message" ) ;
217
+ let response = build_json_response ( StatusCode :: BAD_REQUEST , & server_error) ;
218
+
219
+ assert_error_text_contains ! (
220
+ AggregatorClientError :: get_root_cause( response) . await ,
221
+ "bad request: message"
222
+ ) ;
223
+ }
224
+
225
+ #[ tokio:: test]
226
+ async fn test_root_cause_of_unknown_formatted_json_response_contains_json_key_value_pairs ( ) {
227
+ let response = build_json_response (
228
+ StatusCode :: INTERNAL_SERVER_ERROR ,
229
+ & json ! ( { "second" : "unknown" , "first" : "foreign" } ) ,
230
+ ) ;
231
+
232
+ assert_error_text_contains ! (
233
+ AggregatorClientError :: get_root_cause( response) . await ,
234
+ r#"internal server error: {"first":"foreign","second":"unknown"}"#
235
+ ) ;
236
+ }
237
+
238
+ #[ tokio:: test]
239
+ async fn test_root_cause_with_invalid_json_response_still_contains_response_status_name ( ) {
240
+ let response = HttpResponseBuilder :: new ( )
241
+ . status ( StatusCode :: BAD_REQUEST )
242
+ . header ( header:: CONTENT_TYPE , JSON_CONTENT_TYPE )
243
+ . body ( r#"{"invalid":"unexpected dot", "key": "value".}"# )
244
+ . unwrap ( )
245
+ . into ( ) ;
246
+
247
+ let root_cause = AggregatorClientError :: get_root_cause ( response) . await ;
248
+
249
+ assert_error_text_contains ! ( root_cause, "bad request" ) ;
250
+ assert ! (
251
+ !root_cause. contains( "bad request: " ) ,
252
+ "Expected error message should not contain additional information \n got '{root_cause:?}'"
253
+ ) ;
254
+ }
255
+ }
0 commit comments