asupersync 0.3.4

Spec-first, cancel-correct, capability-secure async runtime for Rust.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
//! OTLP unexpected status code retry classifier audit test.
//!
//! **AUDIT SCOPE**: Verifies OTLP-Trace exporter retry classifier behavior
//! with unexpected HTTP status codes per OTLP specification requirements.
//!
//! **OTLP RETRY CLASSIFIER SPECIFICATION**:
//! - 4xx codes (except 429) are TERMINAL - MUST NOT retry
//! - 429 Rate Limited is RETRYABLE with optional Retry-After
//! - 502/503/504 server errors are RETRYABLE
//! - Other 5xx codes are implementation-specific (non-retryable is acceptable)
//! - Unexpected codes (1xx, 3xx) are TERMINAL
//! - NOT: retry 4xx client errors (violates HTTP semantics)
//! - NOT: treat all 5xx as retryable (could amplify cascading failures)
//!
//! **CURRENT IMPLEMENTATION VERIFICATION**:
//! - Lines 1046-1080 in otel.rs implement OTLP-compliant retry classifier
//! - Correctly treats 4xx (except 429) as terminal client errors
//! - Correctly handles unexpected codes like HTTP 451

#![cfg(test)]
#![allow(dead_code)]

use std::time::Duration;

/// OTLP error fixture for retry classifier behavior.
#[derive(Debug, Clone)]
pub struct OtlpClassifierErrorFixture {
    message: String,
    status_code: u16,
    retry_after: Option<Duration>,
    retryable: bool,
}

impl OtlpClassifierErrorFixture {
    fn retryable(status_code: u16, retry_after: Option<Duration>) -> Self {
        Self {
            message: format!("Retryable OTLP error: {}", status_code),
            status_code,
            retry_after,
            retryable: true,
        }
    }

    fn non_retryable(message: String) -> Self {
        Self {
            message,
            status_code: 0, // Default for non-retryable errors
            retry_after: None,
            retryable: false,
        }
    }

    fn is_retryable(&self) -> bool {
        self.retryable
    }
}

/// HTTP response fixture for retry classifier behavior.
#[derive(Debug, Clone)]
pub struct ResponseFixture {
    status: u16,
    headers: Vec<(String, String)>,
}

impl ResponseFixture {
    fn new(status: u16) -> Self {
        Self {
            status,
            headers: vec![],
        }
    }

    fn with_retry_after(mut self, seconds: u64) -> Self {
        self.headers
            .push(("Retry-After".to_string(), seconds.to_string()));
        self
    }

    fn status(&self) -> u16 {
        self.status
    }

    fn headers(&self) -> &Vec<(String, String)> {
        &self.headers
    }
}

/// Retry classifier logic from send_request_once().
fn classify_otlp_response_status(
    response: &ResponseFixture,
) -> Result<(), OtlpClassifierErrorFixture> {
    // Replicate the exact logic from lines 1046-1080 in otel.rs
    match response.status() {
        200..=299 => Ok(()),
        429 => {
            // Rate limited - check for Retry-After header
            let retry_after = response
                .headers()
                .iter()
                .find(|(name, _)| name.eq_ignore_ascii_case("retry-after"))
                .and_then(|(_, value)| value.parse::<u64>().ok())
                .map(std::time::Duration::from_secs);
            Err(OtlpClassifierErrorFixture::retryable(
                response.status(),
                retry_after,
            ))
        }
        502..=504 => {
            // Retryable server errors per OTLP spec
            Err(OtlpClassifierErrorFixture::retryable(
                response.status(),
                None,
            ))
        }
        400..=499 => {
            // Client errors - not retryable
            Err(OtlpClassifierErrorFixture::non_retryable(format!(
                "OTLP client error: {} - batch dropped",
                response.status()
            )))
        }
        500..=599 => {
            // Other server errors - not retryable per OTLP spec
            Err(OtlpClassifierErrorFixture::non_retryable(format!(
                "OTLP server error: {} - batch dropped",
                response.status()
            )))
        }
        _ => Err(OtlpClassifierErrorFixture::non_retryable(format!(
            "Unexpected OTLP response status: {}",
            response.status()
        ))),
    }
}

/// **AUDIT TEST**: Verify OTLP retry classifier handles unexpected 4xx codes correctly.
///
/// **SCENARIO**: Collector returns HTTP 451 Unavailable For Legal Reasons.
/// **REQUIREMENT**: 4xx codes (except 429) MUST be terminal per OTLP spec.
/// **ASSESSMENT**: Current implementation vs OTLP retry classifier requirements.
#[test]
fn audit_otlp_retry_classifier_unexpected_4xx_codes() {
    println!("🔍 AUDIT: OTLP retry classifier for unexpected 4xx status codes");

    println!("📋 OTLP retry classifier specification:");
    println!("   • 4xx codes (except 429) are TERMINAL - MUST NOT retry");
    println!("   • 429 Rate Limited is RETRYABLE with optional Retry-After");
    println!("   • NOT: retry 4xx client errors (violates HTTP semantics)");
    println!("   • NOT: assume retry will succeed for client errors");

    // **TEST SCENARIOS**: Various unexpected 4xx codes
    let unexpected_4xx_scenarios = vec![
        (400, "Bad Request"),
        (401, "Unauthorized"),
        (403, "Forbidden"),
        (404, "Not Found"),
        (405, "Method Not Allowed"),
        (408, "Request Timeout"),
        (409, "Conflict"),
        (410, "Gone"),
        (413, "Payload Too Large"),
        (414, "URI Too Long"),
        (415, "Unsupported Media Type"),
        (422, "Unprocessable Entity"),
        (451, "Unavailable For Legal Reasons"), // Primary test case
        (499, "Client Closed Request"),         // Nginx extension
    ];

    println!("📊 Testing unexpected 4xx status codes:");

    let mut terminal_count = 0;
    let mut retryable_count = 0;

    for (status_code, description) in unexpected_4xx_scenarios {
        println!("   Testing: HTTP {} - {}", status_code, description);

        let response = ResponseFixture::new(status_code);
        let result = classify_otlp_response_status(&response);

        match result {
            Ok(()) => {
                println!("     ❌ UNEXPECTED: Treated as success");
                panic!(
                    "4xx status {} should never be treated as success",
                    status_code
                );
            }
            Err(otlp_error) => {
                if otlp_error.is_retryable() {
                    println!("     ❌ SPEC VIOLATION: Classified as retryable");
                    retryable_count += 1;
                } else {
                    println!("     ✅ SPEC COMPLIANT: Classified as terminal");
                    terminal_count += 1;
                }
            }
        }
    }

    // **OTLP COMPLIANCE VERIFICATION**
    println!("📊 4xx status code classification results:");
    println!("   Terminal (correct): {}", terminal_count);
    println!("   Retryable (incorrect): {}", retryable_count);

    assert_eq!(
        retryable_count, 0,
        "All 4xx codes (except 429) must be terminal per OTLP spec"
    );
    assert!(
        terminal_count > 0,
        "Should have tested at least one 4xx code"
    );

    println!("✅ OTLP UNEXPECTED 4XX AUDIT COMPLETE");
    println!("🏆 FINDING: Current retry classifier is SPEC-COMPLIANT");
}

/// **AUDIT TEST**: Verify 429 Rate Limited special case handling.
///
/// **SCENARIO**: Collector returns HTTP 429 with/without Retry-After header.
/// **REQUIREMENT**: 429 MUST be retryable per OTLP spec, unlike other 4xx codes.
/// **ASSESSMENT**: Special case exception handling in retry classifier.
#[test]
fn audit_otlp_retry_classifier_429_special_case() {
    println!("🔍 AUDIT: OTLP retry classifier 429 Rate Limited special case");

    println!("📋 HTTP 429 Rate Limited requirements:");
    println!("   • 429 is RETRYABLE (exception to 4xx terminal rule)");
    println!("   • Retry-After header should be parsed if present");
    println!("   • No Retry-After header is still retryable");

    // **SCENARIO 1**: 429 without Retry-After header
    let response_no_header = ResponseFixture::new(429);
    let result_no_header = classify_otlp_response_status(&response_no_header);

    match result_no_header {
        Ok(()) => {
            println!("❌ UNEXPECTED: 429 treated as success");
            panic!("429 Rate Limited should be retryable error, not success");
        }
        Err(otlp_error) => {
            if otlp_error.is_retryable() {
                println!("✅ CORRECT: 429 without Retry-After is retryable");
            } else {
                println!("❌ SPEC VIOLATION: 429 should be retryable");
                panic!("429 Rate Limited must be retryable per OTLP spec");
            }
        }
    }

    // **SCENARIO 2**: 429 with Retry-After header
    let response_with_header = ResponseFixture::new(429).with_retry_after(60);
    let result_with_header = classify_otlp_response_status(&response_with_header);

    match result_with_header {
        Ok(()) => {
            println!("❌ UNEXPECTED: 429 with Retry-After treated as success");
            panic!("429 Rate Limited should be retryable error, not success");
        }
        Err(otlp_error) => {
            if otlp_error.is_retryable() {
                println!("✅ CORRECT: 429 with Retry-After is retryable");
                // The dedicated header-parsing audit covers the retry_after value.
            } else {
                println!("❌ SPEC VIOLATION: 429 with Retry-After should be retryable");
                panic!("429 Rate Limited with Retry-After must be retryable");
            }
        }
    }

    println!("✅ HTTP 429 SPECIAL CASE AUDIT COMPLETE");
    println!("🏆 FINDING: 429 correctly treated as retryable exception");
}

/// **AUDIT TEST**: Verify OTLP-compliant 5xx server error classification.
///
/// **SCENARIO**: Various 5xx server errors with retryable/non-retryable classification.
/// **REQUIREMENT**: Only 502/503/504 are retryable per OTLP best practices.
/// **ASSESSMENT**: Conservative approach to prevent amplifying cascading failures.
#[test]
fn audit_otlp_retry_classifier_5xx_server_errors() {
    println!("🔍 AUDIT: OTLP retry classifier 5xx server error handling");

    println!("📋 OTLP 5xx server error classification:");
    println!("   • 502 Bad Gateway: RETRYABLE (temporary proxy issue)");
    println!("   • 503 Service Unavailable: RETRYABLE (temporary overload)");
    println!("   • 504 Gateway Timeout: RETRYABLE (temporary timeout)");
    println!("   • Other 5xx: NON-RETRYABLE (prevent cascade amplification)");

    let server_error_scenarios = vec![
        (500, "Internal Server Error", false), // Not retryable (permanent)
        (501, "Not Implemented", false),       // Not retryable (permanent)
        (502, "Bad Gateway", true),            // Retryable (temporary)
        (503, "Service Unavailable", true),    // Retryable (temporary)
        (504, "Gateway Timeout", true),        // Retryable (temporary)
        (505, "HTTP Version Not Supported", false), // Not retryable (permanent)
        (507, "Insufficient Storage", false),  // Not retryable (likely permanent)
        (508, "Loop Detected", false),         // Not retryable (configuration issue)
        (511, "Network Authentication Required", false), // Not retryable (auth issue)
        (599, "Network Connect Timeout Error", false), // Not retryable (infrastructure)
    ];

    println!("📊 Testing 5xx server error classification:");

    let mut correctly_classified = 0;
    let mut incorrectly_classified = 0;

    for (status_code, description, should_be_retryable) in server_error_scenarios {
        println!("   Testing: HTTP {} - {}", status_code, description);

        let response = ResponseFixture::new(status_code);
        let result = classify_otlp_response_status(&response);

        match result {
            Ok(()) => {
                println!("     ❌ UNEXPECTED: 5xx treated as success");
                incorrectly_classified += 1;
            }
            Err(otlp_error) => {
                let is_retryable = otlp_error.is_retryable();
                if is_retryable == should_be_retryable {
                    println!(
                        "     ✅ CORRECT: Classified as {}",
                        if is_retryable {
                            "retryable"
                        } else {
                            "terminal"
                        }
                    );
                    correctly_classified += 1;
                } else {
                    println!(
                        "     ❌ INCORRECT: Expected {}, got {}",
                        if should_be_retryable {
                            "retryable"
                        } else {
                            "terminal"
                        },
                        if is_retryable {
                            "retryable"
                        } else {
                            "terminal"
                        }
                    );
                    incorrectly_classified += 1;
                }
            }
        }
    }

    // **5XX CLASSIFICATION ASSESSMENT**
    println!("📊 5xx server error classification results:");
    println!("   Correctly classified: {}", correctly_classified);
    println!("   Incorrectly classified: {}", incorrectly_classified);

    assert_eq!(
        incorrectly_classified, 0,
        "All 5xx codes should be classified per OTLP best practices"
    );

    println!("✅ 5XX SERVER ERROR AUDIT COMPLETE");
    println!("🏆 FINDING: Conservative 5xx handling prevents cascade amplification");
}

/// **AUDIT TEST**: Verify edge case status codes (1xx, 3xx, invalid) handling.
///
/// **SCENARIO**: Collector returns unexpected status codes outside normal ranges.
/// **REQUIREMENT**: All unexpected codes should be terminal (fail-safe).
/// **ASSESSMENT**: Robustness against protocol violations or proxy interference.
#[test]
fn audit_otlp_retry_classifier_edge_case_status_codes() {
    println!("🔍 AUDIT: OTLP retry classifier edge case status code handling");

    println!("📋 Edge case status code requirements:");
    println!("   • 1xx Informational: TERMINAL (unexpected for OTLP)");
    println!("   • 3xx Redirection: TERMINAL (OTLP doesn't support redirects)");
    println!("   • Invalid codes: TERMINAL (protocol violation)");

    let edge_case_scenarios = vec![
        (100, "Continue"),
        (101, "Switching Protocols"),
        (102, "Processing"),
        (300, "Multiple Choices"),
        (301, "Moved Permanently"),
        (302, "Found"),
        (304, "Not Modified"),
        (307, "Temporary Redirect"),
        (308, "Permanent Redirect"),
        (0, "Invalid Status"),  // Invalid code
        (999, "Custom Status"), // Non-standard code
    ];

    println!("📊 Testing edge case status codes:");

    let mut terminal_count = 0;
    let mut non_terminal_count = 0;

    for (status_code, description) in edge_case_scenarios {
        println!("   Testing: HTTP {} - {}", status_code, description);

        let response = ResponseFixture::new(status_code);
        let result = classify_otlp_response_status(&response);

        match result {
            Ok(()) => {
                println!("     ❌ UNEXPECTED: Edge case treated as success");
                non_terminal_count += 1;
            }
            Err(otlp_error) => {
                if otlp_error.is_retryable() {
                    println!("     ❌ RISKY: Edge case classified as retryable");
                    non_terminal_count += 1;
                } else {
                    println!("     ✅ SAFE: Edge case classified as terminal");
                    terminal_count += 1;
                }
            }
        }
    }

    // **EDGE CASE ROBUSTNESS VERIFICATION**
    println!("📊 Edge case handling results:");
    println!("   Terminal (safe): {}", terminal_count);
    println!("   Non-terminal (risky): {}", non_terminal_count);

    assert_eq!(
        non_terminal_count, 0,
        "All edge case status codes should be terminal for fail-safe behavior"
    );

    println!("✅ EDGE CASE STATUS CODE AUDIT COMPLETE");
    println!("🏆 FINDING: Fail-safe handling of unexpected status codes");
}