asserhttp 0.7.1

Fluent http response assertions
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
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
use std::io::Cursor;

use actix_web::HttpResponse;
use serde::{Deserialize, Serialize};
use serde_json::json;

use rocket_test::Resp;

#[path = "rocket_test.rs"]
mod rocket_test;

#[path = "reqwest_test.rs"]
mod reqwest_test;

#[path = "surf_test.rs"]
mod surf_test;

#[path = "ureq_test.rs"]
mod ureq_test;

#[path = "hyper_test.rs"]
mod hyper_test;

#[path = "axum_test.rs"]
mod axum_test;

#[path = "awc_test.rs"]
mod awc_test;

#[path = "isahc_test.rs"]
mod isahc_test;

#[path = "actix_test.rs"]
mod actix_test;

pub enum Stub {
    Full,
    StatusAccepted,
    StatusBadRequest,
    StatusConflict,
    StatusCreated,
    StatusForbidden,
    StatusGone,
    StatusMovedPermanently,
    StatusNoContent,
    StatusNotFound,
    StatusOk,
    StatusPartialContent,
    StatusInternalServerError,
    StatusUnauthorized,
    HeaderJson,
    HeaderMany,
    HeaderMulti,
    HeaderCacheControl,
    HeaderOne,
    HeaderText,
    HeaderXml,
    BodyJson,
    BodyJsonAbsent,
    BodyBytes,
    BodyBytesAbsent,
    BodyText,
    BodyTextAbsent,
}

impl Stub {
    fn responses(&self) -> Responses {
        use axum::response::IntoResponse as _;

        let mut axum_response = axum::response::Response::builder()
            .status(axum::http::StatusCode::OK)
            .body(json!({"a": "b"}).to_string())
            .unwrap();
        axum_response
            .headers_mut()
            .insert("content-type", "application/json".parse().unwrap());
        match self {
            Stub::Full => Responses(
                HttpResponse::Ok()
                    .append_header(("content-type", "application/json"))
                    .body(json!({"a": "b"}).to_string()),
                Resp(
                    rocket::Response::build()
                        .status(rocket::http::Status::Ok)
                        .raw_header("content-type", "application/json")
                        .streamed_body(Cursor::new(json!({"a": "b"}).to_string()))
                        .finalize(),
                ),
                axum_response.into_response(),
            ),
            Stub::StatusAccepted => Responses(
                HttpResponse::Accepted().finish(),
                rocket::http::Status::Accepted.into(),
                axum::http::StatusCode::ACCEPTED.into_response(),
            ),
            Stub::StatusBadRequest => Responses(
                HttpResponse::BadRequest().finish(),
                rocket::http::Status::BadRequest.into(),
                axum::http::StatusCode::BAD_REQUEST.into_response(),
            ),
            Stub::StatusConflict => Responses(
                HttpResponse::Conflict().finish(),
                rocket::http::Status::Conflict.into(),
                axum::http::StatusCode::CONFLICT.into_response(),
            ),
            Stub::StatusCreated => Responses(
                HttpResponse::Created().finish(),
                rocket::http::Status::Created.into(),
                axum::http::StatusCode::CREATED.into_response(),
            ),
            Stub::StatusForbidden => Responses(
                HttpResponse::Forbidden().finish(),
                rocket::http::Status::Forbidden.into(),
                axum::http::StatusCode::FORBIDDEN.into_response(),
            ),
            Stub::StatusGone => Responses(
                HttpResponse::Gone().finish(),
                rocket::http::Status::Gone.into(),
                axum::http::StatusCode::GONE.into_response(),
            ),
            Stub::StatusMovedPermanently => Responses(
                HttpResponse::MovedPermanently().finish(),
                rocket::http::Status::MovedPermanently.into(),
                axum::http::StatusCode::MOVED_PERMANENTLY.into_response(),
            ),
            Stub::StatusNoContent => Responses(
                HttpResponse::NoContent().finish(),
                rocket::http::Status::NoContent.into(),
                axum::http::StatusCode::NO_CONTENT.into_response(),
            ),
            Stub::StatusNotFound => Responses(
                HttpResponse::NotFound().finish(),
                rocket::http::Status::NotFound.into(),
                axum::http::StatusCode::NOT_FOUND.into_response(),
            ),
            Stub::StatusOk => Responses(
                HttpResponse::Ok().finish(),
                rocket::http::Status::Ok.into(),
                axum::http::StatusCode::OK.into_response(),
            ),
            Stub::StatusPartialContent => Responses(
                HttpResponse::PartialContent().finish(),
                rocket::http::Status::PartialContent.into(),
                axum::http::StatusCode::PARTIAL_CONTENT.into_response(),
            ),
            Stub::StatusInternalServerError => Responses(
                HttpResponse::InternalServerError().finish(),
                rocket::http::Status::InternalServerError.into(),
                axum::http::StatusCode::INTERNAL_SERVER_ERROR.into_response(),
            ),
            Stub::StatusUnauthorized => Responses(
                HttpResponse::Unauthorized().finish(),
                rocket::http::Status::Unauthorized.into(),
                axum::http::StatusCode::UNAUTHORIZED.into_response(),
            ),
            Stub::HeaderJson => Responses(
                HttpResponse::Ok().append_header(("content-type", "application/json")).finish(),
                vec![("content-type", "application/json")].into(),
                [("content-type", "application/json")].into_response(),
            ),
            Stub::HeaderMany => Responses(
                HttpResponse::Ok()
                    .append_header(("x-a", "a"))
                    .append_header(("x-b", "b"))
                    .finish(),
                vec![("x-a", "a"), ("x-b", "b")].into(),
                [("x-a", "a"), ("x-b", "b")].into_response(),
            ),
            Stub::HeaderMulti => Responses(
                HttpResponse::Ok().append_header(("x-m", "a, b")).finish(),
                vec![("x-m", vec!["a", "b"])].into(),
                [("x-m", "a, b")].into_response(),
            ),
            Stub::HeaderCacheControl => Responses(
                HttpResponse::Ok()
                    .append_header(("cache-control", "no-cache, no-store"))
                    .finish(),
                vec![("cache-control", vec!["no-cache", "no-store"])].into(),
                [("cache-control", "no-cache, no-store")].into_response(),
            ),
            Stub::HeaderOne => Responses(
                HttpResponse::Ok().append_header(("x-a", "a")).finish(),
                vec![("x-a", "a")].into(),
                [("x-a", "a")].into_response(),
            ),
            Stub::HeaderText => Responses(
                HttpResponse::Ok().append_header(("content-type", "text/plain")).finish(),
                vec![("content-type", "text/plain")].into(),
                [("content-type", "text/plain")].into_response(),
            ),
            Stub::HeaderXml => Responses(
                HttpResponse::Ok().append_header(("content-type", "application/xml")).finish(),
                vec![("content-type", "application/xml")].into(),
                [("content-type", "application/xml")].into_response(),
            ),
            Stub::BodyJson => Responses(
                HttpResponse::Ok().body(json!({"a": "b"}).to_string()),
                json!({"a": "b"}).into(),
                axum::Json(json!({"a": "b"})).into_response(),
            ),
            Stub::BodyJsonAbsent => Responses(
                HttpResponse::Ok().finish(),
                rocket::http::Status::Ok.into(),
                axum::http::StatusCode::OK.into_response(),
            ),
            Stub::BodyBytes => Responses(HttpResponse::Ok().body("abcd"), "abcd".into(), b"abcd".into_response()),
            Stub::BodyBytesAbsent => Responses(
                HttpResponse::Ok().finish(),
                rocket::http::Status::Ok.into(),
                axum::http::StatusCode::OK.into_response(),
            ),
            Stub::BodyText => Responses(HttpResponse::Ok().body("abcd"), "abcd".into(), "abcd".into_response()),
            Stub::BodyTextAbsent => Responses(
                HttpResponse::Ok().finish(),
                rocket::http::Status::Ok.into(),
                axum::http::StatusCode::OK.into_response(),
            ),
        }
    }
}

pub struct Responses(pub HttpResponse, pub Resp, pub axum::response::Response);

#[derive(Deserialize, Serialize, Debug, Eq, PartialEq)]
pub struct TestBody {
    pub a: String,
}

#[macro_export]
macro_rules! asserhttp_test {
    ($fn_name:ident, $stub:literal, $resp:expr, $($(.$meth:ident($( $arg:expr ),*))+),+) => {
        $($crate::reqwest_test!($fn_name, $stub, $( .$meth($($arg),*) )* );)+
        $($crate::surf_test!($fn_name, $stub, $( .$meth($($arg),*) )* );)+
        $($crate::ureq_test!($fn_name, $stub, $( .$meth($($arg),*) )* );)+
        $($crate::hyper_test!($fn_name, $stub, $( .$meth($($arg),*) )* );)+
        $($crate::awc_test!($fn_name, $stub, $( .$meth($($arg),*) )* );)+
        $($crate::isahc_test!($fn_name, $stub, $( .$meth($($arg),*) )* );)+
        $($crate::actix_test!($fn_name, $resp.0, $( .$meth($($arg),*) )* );)+
        $($crate::rocket_test!($fn_name, $resp.1, $( .$meth($($arg),*) )* );)+
        $($crate::axum_test!($fn_name, $resp.2, $( .$meth($($arg),*) )* );)+
    };
    ($fn_name:ident, $stub:literal, $resp:expr, $panic_msg:literal, $($(.$meth:ident($( $arg:expr ),*))+),+) => {
        $($crate::reqwest_test!($fn_name, $stub, $panic_msg, $( .$meth($($arg),*) )* );)+
        $($crate::surf_test!($fn_name, $stub, $panic_msg, $( .$meth($($arg),*) )* );)+
        $($crate::ureq_test!($fn_name, $stub, $panic_msg, $( .$meth($($arg),*) )* );)+
        $($crate::hyper_test!($fn_name, $stub, $panic_msg, $( .$meth($($arg),*) )* );)+
        $($crate::awc_test!($fn_name, $stub, $panic_msg, $( .$meth($($arg),*) )* );)+
        $($crate::isahc_test!($fn_name, $stub, $panic_msg, $( .$meth($($arg),*) )* );)+
        $($crate::actix_test!($fn_name, $resp.0, $panic_msg, $( .$meth($($arg),*) )* );)+
        $($crate::rocket_test!($fn_name, $resp.1, $panic_msg, $( .$meth($($arg),*) )* );)+
        $($crate::axum_test!($fn_name, $resp.2, $panic_msg, $( .$meth($($arg),*) )* );)+
    };
    ($fn_name:ident, $stub:literal, $resp:expr, $error:expr, $($(.$meth:ident($( $arg:expr ),*))+),+) => {
        $($crate::reqwest_test!($fn_name, $stub, $error, $( .$meth($($arg),*) )*);)+
        $($crate::surf_test!($fn_name, $stub, $error, $( .$meth($($arg),*) )* );)+
        $($crate::ureq_test!($fn_name, $stub, $error, $( .$meth($($arg),*) )* );)+
        $($crate::hyper_test!($fn_name, $stub, $error, $( .$meth($($arg),*) )* );)+
        $($crate::awc_test!($fn_name, $stub, $error, $( .$meth($($arg),*) )* );)+
        $($crate::isahc_test!($fn_name, $stub, $error, $( .$meth($($arg),*) )* );)+
        $($crate::actix_test!($fn_name, $resp.0, $error, $( .$meth($($arg),*) )* );)+
        $($crate::rocket_test!($fn_name, $resp.1, $error, $( .$meth($($arg),*) )* );)+
        $($crate::axum_test!($fn_name, $resp.2, $error, $( .$meth($($arg),*) )* );)+
    };
}

mod status {
    use super::Stub::*;

    asserhttp_test!(status_should_succeed, "status/ok.json", StatusOk.responses(), .expect_status(200));
    asserhttp_test!(status_enum_should_succeed, "status/ok.json", StatusOk.responses(), .expect_status(Status::Ok));
    asserhttp_test!(status_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be '100' but was '200'", .expect_status(100));
    asserhttp_test!(status_closure_should_succeed, "status/ok.json", StatusOk.responses(), .expect_status(|s| assert_eq!(s, 200)));
    asserhttp_test!(status_closure_should_fail, "status/ok.json", StatusOk.responses(), "", .expect_status(|s| assert_eq!(s, 400)));

    asserhttp_test!(fallible_status_should_succeed, "status/ok.json", StatusOk.responses(), .try_expect_status(200).unwrap());
    asserhttp_test!(fallible_status_enum_should_succeed, "status/ok.json", StatusOk.responses(), .try_expect_status(Status::Ok).unwrap());
    asserhttp_test!(fallible_status_should_fail, "status/ok.json", StatusOk.responses(), AsserhttpError::StatusMismatch { expected: 100, actual: 200 }, .try_expect_status(100));
    asserhttp_test!(fallible_status_closure_should_succeed, "status/ok.json", StatusOk.responses(), .try_expect_status(|actual| if actual != 200 { Err(AsserhttpError::StatusMismatch { expected: 200, actual }) } else { Ok(()) }).unwrap());
    asserhttp_test!(fallible_status_closure_should_fail, "status/ok.json", StatusOk.responses(), AsserhttpError::StatusMismatch { expected: 400, actual: 200 }, .try_expect_status(|actual| if actual != 400 { Err(AsserhttpError::StatusMismatch { expected: 400, actual }) } else { Ok(()) }));

    asserhttp_test!(status_ok_should_succeed, "status/ok.json", StatusOk.responses(), .expect_status_ok());
    asserhttp_test!(status_ok_should_fail, "status/created.json", StatusCreated.responses(), "expected status to be '200' but was '201'", .expect_status_ok());
    asserhttp_test!(status_created_should_succeed, "status/created.json", StatusCreated.responses(), .expect_status_created());
    asserhttp_test!(status_created_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be '201' but was '200'", .expect_status_created());
    asserhttp_test!(status_accepted_should_succeed, "status/accepted.json", StatusAccepted.responses(), .expect_status_accepted());
    asserhttp_test!(status_accepted_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be '202' but was '200'", .expect_status_accepted());
    asserhttp_test!(status_no_content_should_succeed, "status/no-content.json", StatusNoContent.responses(), .expect_status_no_content());
    asserhttp_test!(status_no_content_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be '204' but was '200'", .expect_status_no_content());
    asserhttp_test!(status_partial_content_should_succeed, "status/partial-content.json", StatusPartialContent.responses(), .expect_status_partial_content());
    asserhttp_test!(status_partial_content_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be '206' but was '200'", .expect_status_partial_content());
    asserhttp_test!(status_bad_request_should_succeed, "status/bad-request.json", StatusBadRequest.responses(), .expect_status_bad_request());
    asserhttp_test!(status_bad_request_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be '400' but was '200'", .expect_status_bad_request());
    asserhttp_test!(status_unauthorized_should_succeed, "status/unauthorized.json", StatusUnauthorized.responses(), .expect_status_unauthorized());
    asserhttp_test!(status_unauthorized_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be '401' but was '200'", .expect_status_unauthorized());
    asserhttp_test!(status_forbidden_should_succeed, "status/forbidden.json", StatusForbidden.responses(), .expect_status_forbidden());
    asserhttp_test!(status_forbidden_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be '403' but was '200'", .expect_status_forbidden());
    asserhttp_test!(status_not_found_should_succeed, "status/not-found.json", StatusNotFound.responses(), .expect_status_not_found());
    asserhttp_test!(status_not_found_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be '404' but was '200'", .expect_status_not_found());
    asserhttp_test!(status_conflict_should_succeed, "status/conflict.json", StatusConflict.responses(), .expect_status_conflict());
    asserhttp_test!(status_conflict_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be '409' but was '200'", .expect_status_conflict());
    asserhttp_test!(status_gone_should_succeed, "status/gone.json", StatusGone.responses(), .expect_status_gone());
    asserhttp_test!(status_gone_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be '410' but was '200'", .expect_status_gone());
    asserhttp_test!(status_internal_server_error_should_succeed, "status/server-error.json", StatusInternalServerError.responses(), .expect_status_internal_server_error());
    asserhttp_test!(status_internal_server_error_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be '500' but was '200'", .expect_status_internal_server_error());

    asserhttp_test!(status_range_should_succeed, "status/created.json", StatusCreated.responses(), .expect_status_in_range(200, 300));
    asserhttp_test!(status_range_should_fail_when_higher, "status/server-error.json", StatusInternalServerError.responses(), "expected status to be in [300;400[ but was '500'", .expect_status_in_range(300, 400));
    asserhttp_test!(status_range_should_fail_when_lower, "status/ok.json", StatusOk.responses(), "expected status to be in [300;400[ but was '200'", .expect_status_in_range(300, 400));
    asserhttp_test!(status_range_should_succeed_in_inclusive_lower_range, "status/ok.json", StatusOk.responses(), .expect_status_in_range(200, 202));
    asserhttp_test!(status_range_should_fail_when_in_exclusive_upper_range, "status/accepted.json", StatusAccepted.responses(), "expected status to be in [200;202[ but was '202'", .expect_status_in_range(200, 202));

    asserhttp_test!(status_success_should_succeed, "status/ok.json", StatusOk.responses(), .expect_status_success());
    asserhttp_test!(status_success_should_fail, "status/bad-request.json", StatusBadRequest.responses(), "expected status to be in [200;300[ but was '400'", .expect_status_success());
    asserhttp_test!(status_redirection_should_succeed, "status/moved-permanently.json", StatusMovedPermanently.responses(), .expect_status_redirection());
    asserhttp_test!(status_redirection_should_fail, "status/server-error.json", StatusInternalServerError.responses(), "expected status to be in [300;400[ but was '500'", .expect_status_redirection());
    asserhttp_test!(status_client_error_should_succeed, "status/bad-request.json", StatusBadRequest.responses(), .expect_status_client_error());
    asserhttp_test!(status_client_error_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be in [400;500[ but was '200'", .expect_status_client_error());
    asserhttp_test!(status_server_error_should_succeed, "status/server-error.json", StatusInternalServerError.responses(), .expect_status_server_error());
    asserhttp_test!(status_server_error_should_fail, "status/ok.json", StatusOk.responses(), "expected status to be in [500;600[ but was '200'", .expect_status_server_error());
}

mod header {
    use super::Stub::*;
    use serde_json::json;

    asserhttp_test!(header_should_succeed, "header/one.json", HeaderOne.responses(), .expect_header("x-a", "a"));
    asserhttp_test!(header_const_should_succeed, "header/json.json", HeaderJson.responses(), .expect_header(headers::CONTENT_TYPE, "application/json"));
    asserhttp_test!(header_should_match_key_ignoring_case, "header/one.json", HeaderOne.responses(), .expect_header("X-A", "a"));
    asserhttp_test!(header_should_fail_because_key_case_sensitive, "header/one.json", HeaderOne.responses(), "expected header 'x-a' to be equal to 'A' but was 'a'", .expect_header("x-a", "A"));
    asserhttp_test!(header_should_fail_when_wrong_key, "header/one.json", HeaderOne.responses(), "expected one header named 'x-b' but none found", .expect_header("x-b", "a"));
    asserhttp_test!(header_const_should_fail_when_wrong_key, "header/json.json", HeaderJson.responses(), "expected one header named 'accept' but none found", .expect_header(headers::ACCEPT, "application/json"));
    asserhttp_test!(header_should_fail_when_wrong_value, "header/one.json", HeaderOne.responses(), "expected header 'x-a' to be equal to 'b' but was 'a'", .expect_header("x-a", "b"));
    asserhttp_test!(header_many_should_succeed, "header/many.json", HeaderMany.responses(), .expect_header("x-a", "a").expect_header("x-b", "b"));
    asserhttp_test!(header_should_fail_when_multivalued, "header/multi.json", HeaderMulti.responses(), "expected header 'x-m' to be single valued. Had '2' values '[\"a\", \"b\"]'. Use 'expect_headers' instead", .expect_header("x-m", "a"));

    asserhttp_test!(fallible_header_should_succeed, "header/one.json", HeaderOne.responses(), .try_expect_header("x-a", "a").unwrap());
    asserhttp_test!(fallible_header_const_should_succeed, "header/json.json", HeaderJson.responses(), .try_expect_header(headers::CONTENT_TYPE, "application/json").unwrap());
    asserhttp_test!(fallible_header_should_match_key_ignoring_case, "header/one.json", HeaderOne.responses(), .try_expect_header("X-A", "a").unwrap());
    asserhttp_test!(fallible_header_should_fail_because_key_case_sensitive, "header/one.json", HeaderOne.responses(), AsserhttpError::HeaderValueMismatch { key: HeaderKey::from("x-a"), actual: HeaderValue::from("a"), expected: HeaderValue::from("A") }, .try_expect_header("x-a", "A"));
    asserhttp_test!(fallible_header_should_fail_when_wrong_key, "header/one.json", HeaderOne.responses(), AsserhttpError::HeaderAbsent { key: HeaderKey::from("x-b") }, .try_expect_header("x-b", "a"));
    asserhttp_test!(fallible_header_const_should_fail_when_wrong_key, "header/json.json", HeaderJson.responses(), AsserhttpError::HeaderAbsent { key: HeaderKey::from("accept") }, .try_expect_header(headers::ACCEPT, "application/json"));
    asserhttp_test!(fallible_header_should_fail_when_wrong_value, "header/one.json", HeaderOne.responses(), AsserhttpError::HeaderValueMismatch { key: HeaderKey::from("x-a"), actual: HeaderValue::from("a"), expected: HeaderValue::from("b") }, .try_expect_header("x-a", "b"));
    asserhttp_test!(fallible_header_many_should_succeed, "header/many.json", HeaderMany.responses(), .try_expect_header("x-a", "a").unwrap().try_expect_header("x-b", "b").unwrap());
    asserhttp_test!(fallible_header_should_fail_when_multivalued, "header/multi.json", HeaderMulti.responses(), AsserhttpError::MultivaluedHeader { key: HeaderKey::from("x-m"), values_count: 2, actual_values: HeaderValues::from(["a", "b"]) }, .try_expect_header("x-m", "a"));

    asserhttp_test!(header_closure_should_succeed, "header/one.json", HeaderOne.responses(), .expect_header("x-a", |h| assert_eq!(h, "a")));
    asserhttp_test!(header_closure_should_fail, "header/one.json", HeaderOne.responses(), "", .expect_header("x-a", |h| assert_eq!(h, "b")));
    asserhttp_test!(header_closure_should_fail_when_wrong_key, "header/one.json", HeaderOne.responses(), "", .expect_header("x-w", |h| assert_eq!(h, "a")));

    asserhttp_test!(fallible_header_closure_should_succeed, "header/one.json", HeaderOne.responses(), .try_expect_header("x-a", |h| { assert_eq!(h, "a"); Ok(()) }).unwrap());
    asserhttp_test!(fallible_header_closure_should_fail_when_wrong_key, "header/one.json", HeaderOne.responses(), "", .try_expect_header("x-w", |h| { assert_eq!(h, "a"); Ok(()) }).unwrap());

    asserhttp_test!(header_multi_should_succeed, "header/multi.json", HeaderMulti.responses(), .expect_headers("x-m", ["a", "b"]));
    asserhttp_test!(header_multi_const_should_succeed, "header/cache-control.json", HeaderCacheControl.responses(), .expect_headers(headers::CACHE_CONTROL, ["no-cache", "no-store"]));
    asserhttp_test!(header_multi_should_match_key_ignoring_case, "header/multi.json", HeaderMulti.responses(), .expect_headers("X-M", ["a", "b"]));
    asserhttp_test!(header_multi_should_fail_when_key_missing, "header/multi.json", HeaderMulti.responses(), "expected one header named 'x-b' but none found", .expect_headers("x-b", ["a", "b"]));
    asserhttp_test!(header_multi_const_should_fail_when_key_missing, "header/multi.json", HeaderMulti.responses(), "expected one header named 'cache-control' but none found", .expect_headers(headers::CACHE_CONTROL, ["a", "b"]));
    asserhttp_test!(header_multi_should_fail_when_one_value_missing, "header/multi.json", HeaderMulti.responses(), "expected header 'x-m' to contain values '[\"a\"]' but contained '[\"a\", \"b\"]'", .expect_headers("x-m", ["a"]));
    asserhttp_test!(header_multi_should_fail_when_one_value_not_eq, "header/multi.json", HeaderMulti.responses(), "expected header 'x-m' to contain values '[\"a\", \"c\"]' but contained '[\"a\", \"b\"]'", .expect_headers("x-m", ["a", "c"]));
    asserhttp_test!(header_multi_should_fail_because_value_case_sensitive, "header/multi.json", HeaderMulti.responses(), "expected header 'x-m' to contain values '[\"a\", \"B\"]' but contained '[\"a\", \"b\"]'", .expect_headers("x-m", ["a", "B"]));
    asserhttp_test!(header_multi_should_fail_when_one_expected_value_missing, "header/multi.json", HeaderMulti.responses(), "expected header 'x-m' to contain values '[\"a\", \"b\", \"c\"]' but contained '[\"a\", \"b\"]'", .expect_headers("x-m", ["a", "b", "c"]));

    asserhttp_test!(fallible_header_multi_should_succeed, "header/multi.json", HeaderMulti.responses(), .try_expect_headers("x-m", ["a", "b"]).unwrap());
    asserhttp_test!(fallible_header_multi_const_should_succeed, "header/cache-control.json", HeaderCacheControl.responses(), .try_expect_headers(headers::CACHE_CONTROL, ["no-cache", "no-store"]).unwrap());
    asserhttp_test!(fallible_header_multi_should_match_key_ignoring_case, "header/multi.json", HeaderMulti.responses(), .try_expect_headers("X-M", ["a", "b"]).unwrap());
    asserhttp_test!(fallible_header_multi_should_fail_when_key_missing, "header/multi.json", HeaderMulti.responses(), AsserhttpError::HeaderAbsent { key: HeaderKey::from("x-b") }, .try_expect_headers("x-b", ["a", "b"]));
    asserhttp_test!(fallible_header_multi_const_should_fail_when_key_missing, "header/multi.json", HeaderMulti.responses(), AsserhttpError::HeaderAbsent { key: HeaderKey::from("cache-control") }, .try_expect_headers(headers::CACHE_CONTROL, ["a", "b"]));
    asserhttp_test!(fallible_header_multi_should_fail_when_one_value_missing, "header/multi.json", HeaderMulti.responses(), AsserhttpError::HeaderValuesMismatch { key: HeaderKey::from("x-m"), actual_values: HeaderValues::from(["a", "b"]), expected: HeaderValues::from(["a"]) }, .try_expect_headers("x-m", ["a"]));
    asserhttp_test!(fallible_header_multi_should_fail_when_one_value_not_eq, "header/multi.json", HeaderMulti.responses(), AsserhttpError::HeaderValuesMismatch { key: HeaderKey::from("x-m"), actual_values: HeaderValues::from(["a", "b"]), expected: HeaderValues::from(["a", "c"]) }, .try_expect_headers("x-m", ["a", "c"]));
    asserhttp_test!(fallible_header_multi_should_fail_because_value_case_sensitive, "header/multi.json", HeaderMulti.responses(), AsserhttpError::HeaderValuesMismatch { key: HeaderKey::from("x-m"), actual_values: HeaderValues::from(["a", "b"]), expected: HeaderValues::from(["a", "B"]) }, .try_expect_headers("x-m", ["a", "B"]));
    asserhttp_test!(fallible_header_multi_should_fail_when_one_expected_value_missing, "header/multi.json", HeaderMulti.responses(), AsserhttpError::HeaderValuesMismatch { key: HeaderKey::from("x-m"), actual_values: HeaderValues::from(["a", "b"]), expected: HeaderValues::from(["a", "b", "c"]) }, .try_expect_headers("x-m", ["a", "b", "c"]));

    asserhttp_test!(header_multi_closure_should_succeed, "header/multi.json", HeaderMulti.responses(), .expect_headers("x-m", |h: Vec<&str>| assert!(h.contains(&"a") && h.contains(&"b"))));
    asserhttp_test!(header_multi_closure_should_fail, "header/multi.json", HeaderMulti.responses(), "", .expect_headers("x-m", |h: Vec<&str>| assert!(h.contains(&"c"))));
    asserhttp_test!(header_multi_closure_should_fail_when_wrong_key, "header/multi.json", HeaderMulti.responses(), "", .expect_headers("x-w", |h: Vec<&str>| assert!(h.contains(&"a") && h.contains(&"b"))));

    asserhttp_test!(fallible_header_multi_closure_should_succeed, "header/multi.json", HeaderMulti.responses(), .try_expect_headers("x-m", |h: Vec<&str>| { assert!(h.contains(&"a") && h.contains(&"b")); Ok(()) }).unwrap());
    asserhttp_test!(fallible_header_multi_closure_should_fail_when_wrong_key, "header/multi.json", HeaderMulti.responses(), "", .try_expect_headers("x-w", |h: Vec<&str>| { assert!(h.contains(&"a") && h.contains(&"b")); Ok(()) }).unwrap());

    asserhttp_test!(header_present_should_succeed, "header/one.json", HeaderOne.responses(), .expect_header_present("x-a"));
    asserhttp_test!(header_present_const_should_succeed, "header/json.json", HeaderJson.responses(), .expect_header_present(headers::CONTENT_TYPE));
    asserhttp_test!(header_present_should_succeed_ignoring_case, "header/one.json", HeaderOne.responses(), .expect_header_present("X-A"));
    asserhttp_test!(header_present_should_fail_when_absent, "header/one.json", HeaderOne.responses(), "expected one header named 'x-b' but none found", .expect_header_present("x-b"));
    asserhttp_test!(header_present_const_should_fail_when_absent, "header/json.json", HeaderJson.responses(), "expected one header named 'accept' but none found", .expect_header_present(headers::ACCEPT));

    asserhttp_test!(fallible_header_present_should_succeed, "header/one.json", HeaderOne.responses(), .try_expect_header_present("x-a").unwrap());
    asserhttp_test!(fallible_header_present_const_should_succeed, "header/json.json", HeaderJson.responses(), .try_expect_header_present(headers::CONTENT_TYPE).unwrap());
    asserhttp_test!(fallible_header_present_should_succeed_ignoring_case, "header/one.json", HeaderOne.responses(), .try_expect_header_present("X-A").unwrap());
    asserhttp_test!(fallible_header_present_should_fail_when_absent, "header/one.json", HeaderOne.responses(), AsserhttpError::HeaderAbsent { key: HeaderKey::from("x-b") }, .try_expect_header_present("x-b"));
    asserhttp_test!(fallible_header_present_const_should_fail_when_absent, "header/json.json", HeaderJson.responses(), AsserhttpError::HeaderAbsent { key: HeaderKey::from("accept") }, .try_expect_header_present(headers::ACCEPT));

    asserhttp_test!(header_absent_should_succeed, "header/one.json", HeaderOne.responses(), .expect_header_absent("x-b"));
    asserhttp_test!(header_absent_const_should_succeed, "header/one.json", HeaderOne.responses(), .expect_header_absent(headers::ACCEPT));
    asserhttp_test!(header_absent_should_fail_when_present, "header/one.json", HeaderOne.responses(), "expected no header named 'x-a' but one found", .expect_header_absent("x-a"));
    asserhttp_test!(header_absent_should_fail_when_present_ignoring_case, "header/one.json", HeaderOne.responses(), "expected no header named 'X-A' but one found", .expect_header_absent("X-A"));
    asserhttp_test!(header_absent_const_should_fail_when_present, "header/json.json", HeaderJson.responses(), "expected no header named 'content-type' but one found", .expect_header_absent(headers::CONTENT_TYPE));

    asserhttp_test!(fallible_header_absent_should_succeed, "header/one.json", HeaderOne.responses(), .try_expect_header_absent("x-b").unwrap());
    asserhttp_test!(fallible_header_absent_const_should_succeed, "header/one.json", HeaderOne.responses(), .try_expect_header_absent(headers::ACCEPT).unwrap());
    asserhttp_test!(fallible_header_absent_should_fail_when_present, "header/one.json", HeaderOne.responses(), AsserhttpError::HeaderPresent { key: HeaderKey::from("x-a") }, .try_expect_header_absent("x-a"));
    asserhttp_test!(fallible_header_absent_should_fail_when_present_ignoring_case, "header/one.json", HeaderOne.responses(), AsserhttpError::HeaderPresent { key: HeaderKey::from("X-A") }, .try_expect_header_absent("X-A"));
    asserhttp_test!(fallible_header_absent_const_should_fail_when_present, "header/json.json", HeaderJson.responses(), AsserhttpError::HeaderPresent { key: HeaderKey::from("content-type") }, .try_expect_header_absent(headers::CONTENT_TYPE));

    asserhttp_test!(header_content_type_json_should_succeed, "header/json.json", HeaderJson.responses(), .expect_content_type_json());
    asserhttp_test!(header_content_type_json_should_fail, "header/xml.json", HeaderXml.responses(), "expected header 'content-type' to be equal to 'application/json' but was 'application/xml'", .expect_content_type_json());

    asserhttp_test!(header_content_type_text_should_succeed, "header/text.json", HeaderText.responses(), .expect_content_type_text());
    asserhttp_test!(header_content_type_text_should_fail, "header/xml.json", HeaderXml.responses(), "expected header 'content-type' to be equal to 'text/plain' but was 'application/xml'", .expect_content_type_text());

    asserhttp_test!(expect_header_first_should_not_be_destructive, "full.json", Full.responses(), .expect_content_type_json().expect_status_ok().expect_body_json_eq(json!({"a": "b"})));

    // can't be tested through macros. It just has to compile
    mod relax_type {
        use asserhttp::*;

        #[test]
        #[stubr::mock("header/one.json")]
        fn header_accepts_any_str_key() {
            isahc::get(stubr.uri()).expect_header("x-a", "a");
            isahc::get(stubr.uri()).expect_header("x-a".to_string(), "a");
            let key: String = "x-a".to_string();
            isahc::get(stubr.uri()).expect_header(key.as_str(), "a");
            isahc::get(stubr.uri()).expect_header(&"x-a".to_string(), "a");
        }

        #[test]
        #[stubr::mock("header/one.json")]
        fn header_accepts_any_str_value() {
            isahc::get(stubr.uri()).expect_header("x-a", "a");
            isahc::get(stubr.uri()).expect_header("x-a", "a".to_string());
            let value: String = "a".to_string();
            isahc::get(stubr.uri()).expect_header("x-a", value.as_str());
            isahc::get(stubr.uri()).expect_header("x-a", &"a".to_string());
        }

        #[test]
        #[stubr::mock("header/multi.json")]
        fn header_accepts_any_str_values() {
            // array
            isahc::get(stubr.uri()).expect_headers("x-m", ["a", "b"]);
            isahc::get(stubr.uri()).expect_headers("x-m", ["a", "b"]);
            isahc::get(stubr.uri()).expect_headers("x-m", ["a".to_string(), "b".to_string()]);
            isahc::get(stubr.uri()).expect_headers("x-m", &["a".to_string(), "b".to_string()]);
            isahc::get(stubr.uri()).expect_headers("x-m", [&"a".to_string(), &"b".to_string()]);
            isahc::get(stubr.uri()).expect_headers("x-m", [&"a".to_string(), &"b".to_string()]);
            let (a, b) = ("a".to_string(), "b".to_string());
            isahc::get(stubr.uri()).expect_headers("x-m", [a.as_str(), b.as_str()]);
            isahc::get(stubr.uri()).expect_headers("x-m", [a.as_str(), b.as_str()]);
            // vec
            isahc::get(stubr.uri()).expect_headers("x-m", vec!["a", "b"]);
            isahc::get(stubr.uri()).expect_headers("x-m", vec!["a".to_string(), "b".to_string()]);
            isahc::get(stubr.uri()).expect_headers("x-m", vec![&"a".to_string(), &"b".to_string()]);
            let (a, b) = ("a".to_string(), "b".to_string());
            isahc::get(stubr.uri()).expect_headers("x-m", vec![a.as_str(), b.as_str()]);
        }
    }
}

mod body {
    use serde_json::{json, Value};

    use super::{Stub::*, *};

    // json
    asserhttp_test!(body_json_should_succeed, "body/json/value.json", BodyJson.responses(), .expect_body_json(|b: Value| assert_eq!(b, json!({"a": "b"}))));
    asserhttp_test!(body_json_struct_should_succeed, "body/json/value.json", BodyJson.responses(), .expect_body_json(|b: TestBody| assert_eq!(b, TestBody { a: String::from("b") })));
    asserhttp_test!(body_json_should_fail_when_closure_fails, "body/json/value.json", BodyJson.responses(), "", .expect_body_json(|b: Value| assert_eq!(b, json!({"a": "c"}))));

    asserhttp_test!(fallible_body_json_should_succeed, "body/json/value.json", BodyJson.responses(), .try_expect_body_json(|b: Value| { assert_eq!(b, json!({"a": "b"})); Ok(()) }).unwrap());
    asserhttp_test!(fallible_body_json_struct_should_succeed, "body/json/value.json", BodyJson.responses(), .try_expect_body_json(|b: TestBody| { assert_eq!(b, TestBody { a: String::from("b") }); Ok(()) }).unwrap());
    asserhttp_test!(fallible_body_json_should_fail_when_closure_fails, "body/json/value.json", BodyJson.responses(), "", .try_expect_body_json(|b: Value| { assert_eq!(b, json!({"a": "c"})); Ok(()) }).unwrap());

    asserhttp_test!(body_json_eq_should_succeed, "body/json/value.json", BodyJson.responses(), .expect_body_json_eq(json!({"a": "b"})));
    asserhttp_test!(body_json_struct_eq_should_succeed, "body/json/value.json", BodyJson.responses(), .expect_body_json_eq(TestBody { a: String::from("b") }));
    asserhttp_test!(body_json_eq_should_fail_when_not_eq, "body/json/value.json", BodyJson.responses(), "json atoms at path \".a\" are not equal:\n    lhs:\n        \"b\"\n    rhs:\n        \"c\"", .expect_body_json_eq(json!({"a": "c"})));
    asserhttp_test!(body_json_eq_should_fail_when_absent, "body/json/absent.json", BodyJsonAbsent.responses(), "expected a response body but none was present", .expect_body_json_eq(json!({"a": "b"})));

    asserhttp_test!(fallible_body_json_eq_should_succeed, "body/json/value.json", BodyJson.responses(), .try_expect_body_json_eq(json!({"a": "b"})).unwrap());
    asserhttp_test!(fallible_body_json_struct_eq_should_succeed, "body/json/value.json", BodyJson.responses(), .try_expect_body_json_eq(TestBody { a: String::from("b") }).unwrap());
    asserhttp_test!(fallible_body_json_eq_should_fail_when_not_eq, "body/json/value.json", BodyJson.responses(), AsserhttpError::JsonBodyMismatch("json atoms at path \".a\" are not equal:\n    lhs:\n        \"b\"\n    rhs:\n        \"c\"".to_string()), .try_expect_body_json_eq(json!({"a": "c"})));
    asserhttp_test!(fallible_body_json_eq_should_fail_when_absent, "body/json/absent.json", BodyJsonAbsent.responses(), AsserhttpError::BodyAbsent, .try_expect_body_json_eq(json!({"a": "b"})));

    // text
    asserhttp_test!(body_text_should_succeed, "body/text/value.json", BodyText.responses(), .expect_body_text(|b| assert_eq!(b, String::from("abcd"))));
    asserhttp_test!(body_text_should_fail_when_closure_fails, "body/text/value.json", BodyText.responses(), "", .expect_body_text(|b| assert_eq!(b, String::from("dcba"))));

    asserhttp_test!(fallible_body_text_should_succeed, "body/text/value.json", BodyText.responses(), .try_expect_body_text(|b| { assert_eq!(b, String::from("abcd")); Ok(()) }).unwrap());
    asserhttp_test!(fallible_body_text_should_fail_when_closure_fails, "body/text/value.json", BodyText.responses(), "", .try_expect_body_text(|b| { assert_eq!(b, String::from("dcba")); Ok(()) }).unwrap());

    asserhttp_test!(body_text_eq_should_succeed, "body/text/value.json", BodyText.responses(), .expect_body_text_eq("abcd"));
    asserhttp_test!(body_text_eq_should_fail_when_not_eq, "body/text/value.json", BodyText.responses(), "expected body to be 'dcba' but was 'abcd'", .expect_body_text_eq("dcba"));
    asserhttp_test!(body_text_eq_should_fail_when_absent, "body/text/absent.json", BodyTextAbsent.responses(), "expected a response body but none was present", .expect_body_text_eq("abcd"));

    asserhttp_test!(fallible_body_text_eq_should_succeed, "body/text/value.json", BodyText.responses(), .try_expect_body_text_eq("abcd").unwrap());
    asserhttp_test!(fallible_body_text_eq_should_fail_when_not_eq, "body/text/value.json", BodyText.responses(), AsserhttpError::TextBodyMismatch { actual: "abcd".to_string(), expected: "dcba".to_string() }, .try_expect_body_text_eq("dcba"));
    asserhttp_test!(fallible_body_text_eq_should_fail_when_absent, "body/text/absent.json", BodyTextAbsent.responses(), AsserhttpError::BodyAbsent, .try_expect_body_text_eq("abcd"));

    // regex
    asserhttp_test!(body_text_regex_should_succeed, "body/text/value.json", BodyText.responses(), .expect_body_text_matches("[a-d]+"));
    asserhttp_test!(body_text_regex_should_fail_when_does_not_match, "body/text/value.json", BodyText.responses(), "expected body to match regex '[e-h]+' but was 'abcd'", .expect_body_text_matches("[e-h]+"));
    asserhttp_test!(body_text_regex_should_fail_when_absent, "body/text/absent.json", BodyTextAbsent.responses(), "expected a response body but none was present", .expect_body_text_matches("[a-d]+"));

    asserhttp_test!(fallible_body_text_regex_should_succeed, "body/text/value.json", BodyText.responses(), .try_expect_body_text_matches("[a-d]+").unwrap());
    asserhttp_test!(fallible_body_text_regex_should_fail_when_does_not_match, "body/text/value.json", BodyText.responses(), AsserhttpError::RegexBodyMismatch { actual: "abcd".to_string(), regex: "[e-h]+".to_string() }, .try_expect_body_text_matches("[e-h]+"));
    asserhttp_test!(fallible_body_text_regex_should_fail_when_absent, "body/text/absent.json", BodyTextAbsent.responses(), AsserhttpError::BodyAbsent, .try_expect_body_text_matches("[a-d]+"));

    // bytes
    asserhttp_test!(body_bytes_should_succeed, "body/bytes/value.json", BodyBytes.responses(), .expect_body_bytes(|b| assert_eq!(b, b"abcd")));
    asserhttp_test!(body_bytes_should_fail_when_closure_fails, "body/bytes/value.json", BodyBytes.responses(), "", .expect_body_bytes(|b| assert_eq!(b, b"dcba")));

    asserhttp_test!(fallible_body_bytes_should_succeed, "body/bytes/value.json", BodyBytes.responses(), .try_expect_body_bytes(|b| { assert_eq!(b, b"abcd"); Ok(()) }).unwrap());
    asserhttp_test!(fallible_body_bytes_should_fail_when_closure_fails, "body/bytes/value.json", BodyBytes.responses(), "", .try_expect_body_bytes(|b| { assert_eq!(b, b"dcba"); Ok(()) } ).unwrap());

    asserhttp_test!(body_bytes_eq_should_succeed, "body/bytes/value.json", BodyBytes.responses(), .expect_body_bytes_eq(b"abcd"));
    asserhttp_test!(body_bytes_eq_should_fail_when_not_eq, "body/bytes/value.json", BodyBytes.responses(), "expected body to be '[100, 99, 98, 97]' but was '[97, 98, 99, 100]'", .expect_body_bytes_eq(b"dcba"));
    asserhttp_test!(body_bytes_eq_should_fail_when_absent, "body/bytes/absent.json", BodyBytesAbsent.responses(), "expected a response body but none was present", .expect_body_bytes_eq(b"abcd"));

    asserhttp_test!(fallible_body_bytes_eq_should_succeed, "body/bytes/value.json", BodyBytes.responses(), .try_expect_body_bytes_eq(b"abcd").unwrap());
    asserhttp_test!(fallible_body_bytes_eq_should_fail_when_not_eq, "body/bytes/value.json", BodyBytes.responses(), AsserhttpError::BytesBodyMismatch{actual: vec![97, 98, 99, 100], expected: vec![100, 99, 98, 97]}, .try_expect_body_bytes_eq(b"dcba"));
    asserhttp_test!(fallible_body_bytes_eq_should_fail_when_absent, "body/bytes/absent.json", BodyBytesAbsent.responses(), AsserhttpError::BodyAbsent, .try_expect_body_bytes_eq(b"abcd"));

    // present
    asserhttp_test!(body_present_should_succeed, "body/bytes/value.json", BodyBytes.responses(), .expect_body_present());
    asserhttp_test!(body_present_should_fail_when_absent, "body/bytes/absent.json", BodyBytesAbsent.responses(), "expected a response body but none was present", .expect_body_present());

    asserhttp_test!(fallible_body_present_should_succeed, "body/bytes/value.json", BodyBytes.responses(), .try_expect_body_present().unwrap());
    asserhttp_test!(fallible_body_present_should_fail_when_absent, "body/bytes/absent.json", BodyBytesAbsent.responses(), AsserhttpError::BodyAbsent, .try_expect_body_present());

    // absent
    asserhttp_test!(body_absent_should_succeed, "body/bytes/absent.json", BodyBytesAbsent.responses(), .expect_body_absent());
    asserhttp_test!(body_absent_should_fail_when_present, "body/bytes/value.json", BodyBytes.responses(), "expected no response body but one was present", .expect_body_absent());

    asserhttp_test!(fallible_body_absent_should_succeed, "body/bytes/absent.json", BodyBytesAbsent.responses(), .try_expect_body_absent().unwrap());
    asserhttp_test!(fallible_body_absent_should_fail_when_present, "body/bytes/value.json", BodyBytes.responses(), AsserhttpError::BodyPresent, .try_expect_body_absent());

    asserhttp_test!(expect_body_first_should_not_be_destructive, "full.json", Full.responses(), .expect_body_json_eq(json!({"a": "b"})).expect_status_ok().expect_content_type_json());
}

mod customizable {
    use super::Stub::*;
    use asserhttp::AsserhttpResult;

    asserhttp::asserhttp_customize!(MyHttpDsl);

    pub trait MyHttpDsl<T>: asserhttp::Asserhttp<T> {
        fn is_status_ok(&mut self) -> &mut T {
            self.expect_status_ok()
        }

        fn try_is_status_ok(&mut self) -> AsserhttpResult<&mut T> {
            self.try_expect_status(200)
        }

        fn is_json(&mut self) -> &mut T {
            self.expect_content_type_json()
        }

        fn try_is_json(&mut self) -> AsserhttpResult<&mut T> {
            self.try_expect_header(asserhttp::headers::CONTENT_TYPE, Self::APPLICATION_JSON)
        }

        fn has_body(&mut self) -> &mut T {
            self.expect_body_present()
        }

        fn try_has_body(&mut self) -> AsserhttpResult<&mut T> {
            self.try_expect_body_present()
        }
    }

    asserhttp_test!(custom_status_should_succeed, "status/ok.json", StatusOk.responses(), .is_status_ok());
    asserhttp_test!(fallible_custom_status_should_succeed, "status/ok.json", StatusOk.responses(), .try_is_status_ok().unwrap());
    asserhttp_test!(custom_content_type_json_should_succeed, "header/json.json", HeaderJson.responses(), .is_json());
    asserhttp_test!(fallible_custom_content_type_json_should_succeed, "header/json.json", HeaderJson.responses(), .try_is_json().unwrap());
    asserhttp_test!(custom_body_present_should_succeed, "body/bytes/value.json", BodyBytes.responses(), .has_body());
    asserhttp_test!(fallible_custom_body_present_should_succeed, "body/bytes/value.json", BodyBytes.responses(), .try_has_body().unwrap());
}