#![allow(clippy::print_stdout)]
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::too_many_lines)]
use std::convert::Infallible;
use std::sync::Arc;
use bytes::Bytes;
use http::{Request, Response, StatusCode};
use http_body_util::Full;
use tower::layer::Layer;
use tower::{Service, ServiceExt};
use tower_conneg::{
ClientConfig, ClientNegotiateLayer, ClientRequestExt, ErasedFormat, JsonFormat, XmlFormat,
};
type MockRequest = Request<Full<Bytes>>;
#[tokio::main]
async fn main() {
let json: Arc<dyn ErasedFormat> = Arc::new(JsonFormat);
let xml: Arc<dyn ErasedFormat> = Arc::new(XmlFormat);
let config = ClientConfig::builder()
.formats([json.clone(), xml.clone()]) .fallback_format(json.clone()) .build();
let layer = ClientNegotiateLayer::new(config);
let mock_server = tower::service_fn(|req: MockRequest| async move {
let content_type = req
.headers()
.get(http::header::CONTENT_TYPE)
.cloned()
.unwrap_or_else(|| http::HeaderValue::from_static("application/json"));
let body = Full::new(Bytes::from(
r#"{"id":1,"name":"Alice","email":"alice@example.com"}"#,
));
let response = Response::builder()
.status(StatusCode::OK)
.header(http::header::CONTENT_TYPE, content_type)
.body(body)
.unwrap_or_else(|_| Response::new(Full::new(Bytes::new())));
Ok::<_, Infallible>(response)
});
let mut client = layer.layer(mock_server);
println!("=== Basic Request ===");
assert!(client.cached_format().is_none());
println!("Cached format before first request: None");
let request = Request::builder()
.method("POST")
.uri("https://api.example.com/users")
.body(Full::new(Bytes::new()))
.expect("request should build");
let response = client
.ready()
.await
.expect("service should be ready")
.call(request)
.await
.expect("request should succeed");
println!("Response status: {}", response.status());
println!("\n=== Format Caching ===");
let cached = client.cached_format().expect("format should be cached");
println!(
"Cached format after first request: {}",
cached.content_type_header().to_str().unwrap_or("unknown")
);
let request2 = Request::builder()
.method("POST")
.uri("https://api.example.com/users")
.body(Full::new(Bytes::new()))
.expect("request should build");
let _ = client
.ready()
.await
.expect("service should be ready")
.call(request2)
.await
.expect("request should succeed");
let still_cached = client
.cached_format()
.expect("format should still be cached");
assert_eq!(
cached.content_type_header(),
still_cached.content_type_header()
);
println!("Format remains cached for subsequent requests");
println!("\n=== Per-Request Format Override ===");
let request_override = Request::builder()
.method("POST")
.uri("https://api.example.com/users")
.body(Full::new(Bytes::new()))
.expect("request should build")
.with_format(xml.clone());
let response_xml = client
.ready()
.await
.expect("service should be ready")
.call(request_override)
.await
.expect("request should succeed");
let xml_content_type = response_xml
.headers()
.get(http::header::CONTENT_TYPE)
.and_then(|h| h.to_str().ok())
.unwrap_or("unknown");
println!("Override request used: {xml_content_type}");
let after_override = client
.cached_format()
.expect("format should still be cached");
assert_eq!(
cached.content_type_header(),
after_override.content_type_header()
);
println!("Cache unchanged after override request");
println!("\n=== 415 Handling ===");
let config_415 = ClientConfig::builder()
.formats([json.clone(), xml.clone()])
.fallback_format(json.clone())
.build();
let mock_server_415 = tower::service_fn(|_req: MockRequest| async move {
let response = Response::builder()
.status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
.header("accept-post", "application/xml")
.body(Full::new(Bytes::new()))
.unwrap_or_else(|_| Response::new(Full::new(Bytes::new())));
Ok::<_, Infallible>(response)
});
let mut client_415 = ClientNegotiateLayer::new(config_415).layer(mock_server_415);
let request_415 = Request::builder()
.method("POST")
.uri("https://api.example.com/users")
.body(Full::new(Bytes::new()))
.expect("request should build");
let response_415 = client_415
.ready()
.await
.expect("service should be ready")
.call(request_415)
.await
.expect("request should succeed");
println!("Response status: {}", response_415.status());
let cached_after_415 = client_415
.cached_format()
.expect("format should be cached from 415");
println!(
"Cached format after 415: {} (from server's Accept-Post)",
cached_after_415
.content_type_header()
.to_str()
.unwrap_or("unknown")
);
println!("\n=== Summary ===");
println!("1. Configure ClientConfig with formats in priority order");
println!("2. Wrap your HTTP client with ClientNegotiateLayer");
println!("3. Make requests - Content-Type and Accept headers are automatic");
println!("4. The format is cached after the first successful request");
println!("5. Use .with_format() to override for specific requests");
println!("6. On 415, the server's preferred format is cached for next time");
}