1#![deny(warnings)]
15#![deny(clippy::pedantic, clippy::unwrap_used)]
16#![deny(missing_docs)]
17pub use axum_accept_macros::AcceptExtractor;
18pub use axum_accept_shared::AcceptRejection;
19
20#[doc(hidden)]
21pub use axum_accept_shared::parse_mediatypes;
22
23#[cfg(doctest)]
24#[doc = include_str!("../../README.md")]
25pub struct ReadmeDoctests;
26
27#[cfg(test)]
28mod tests {
29 use super::*;
30 use crate as axum_accept; use axum::{
32 body::Body,
33 extract::{FromRequest, Request},
34 };
35
36 #[derive(Debug, AcceptExtractor)]
37 enum Accept {
38 #[accept(mediatype = "text/*")]
39 Text,
40 #[accept(mediatype = "text/plain")]
41 TextPlain,
42 #[accept(mediatype = "application/json")]
43 ApplicationJson,
44 #[accept(mediatype = "application/ld+json")]
45 ApplicationLdJson,
46 }
47
48 #[tokio::test]
49 async fn test_accept_extractor_basic() -> Result<(), Box<dyn std::error::Error>> {
50 let req = Request::builder()
51 .header("accept", "application/json,text/plain")
52 .body(Body::from(""))?;
53 let state = ();
54 let media_type = Accept::from_request(req, &state)
55 .await
56 .expect("Expected no rejection");
57 let Accept::ApplicationJson = media_type else {
58 panic!("expected application/json")
59 };
60 Ok(())
61 }
62
63 #[tokio::test]
64 async fn test_accept_extractor_q() -> Result<(), Box<dyn std::error::Error>> {
65 let req = Request::builder()
66 .header("accept", "application/json;q=0.9,text/plain")
67 .body(Body::from(""))?;
68 let state = ();
69 let media_type = Accept::from_request(req, &state)
70 .await
71 .expect("Expected no rejection");
72 let Accept::TextPlain = media_type else {
73 panic!("expected text/plain")
74 };
75 Ok(())
76 }
77
78 #[tokio::test]
79 async fn test_accept_extractor_specifity() -> Result<(), Box<dyn std::error::Error>> {
80 let req = Request::builder()
81 .header("accept", "text/*,text/plain")
82 .body(Body::from(""))?;
83 let state = ();
84 let media_type = Accept::from_request(req, &state)
85 .await
86 .expect("Expected no rejection");
87 let Accept::TextPlain = media_type else {
88 panic!("expected text/plain")
89 };
90 Ok(())
91 }
92
93 #[tokio::test]
94 async fn test_accept_extractor_suffix() -> Result<(), Box<dyn std::error::Error>> {
95 let req = Request::builder()
96 .header("accept", "text/*,application/ld+json,text/plain")
97 .body(Body::from(""))?;
98 let state = ();
99 let media_type = Accept::from_request(req, &state)
100 .await
101 .expect("Expected no rejection");
102 let Accept::ApplicationLdJson = media_type else {
103 panic!("expected application/ldjson")
104 };
105 Ok(())
106 }
107
108 #[tokio::test]
109 async fn test_accept_extractor_no_match() -> Result<(), Box<dyn std::error::Error>> {
110 let req = Request::builder()
111 .header("accept", "text/csv")
112 .body(Body::from(""))?;
113 let state = ();
114 let media_type = Accept::from_request(req, &state).await;
115 let Err(AcceptRejection::NoSupportedMediaTypeFound) = media_type else {
116 panic!("expected no supported media type found")
117 };
118 Ok(())
119 }
120
121 #[derive(Debug, AcceptExtractor, Default)]
122 enum AcceptWithDefault {
123 #[default]
124 #[accept(mediatype = "text/plain")]
125 TextPlain,
126 #[accept(mediatype = "application/json")]
127 ApplicationJson,
128 }
129
130 #[tokio::test]
131 async fn test_accept_extractor_default() -> Result<(), Box<dyn std::error::Error>> {
132 let req = Request::builder()
133 .header("accept", "text/csv")
134 .body(Body::from(""))?;
135 let state = ();
136 let media_type = AcceptWithDefault::from_request(req, &state).await;
137 let Err(AcceptRejection::NoSupportedMediaTypeFound) = media_type else {
138 panic!("expected no supported media type found")
139 };
140
141 let req = Request::builder().body(Body::from(""))?;
142 let state = ();
143 let media_type = AcceptWithDefault::from_request(req, &state).await;
144 let Ok(AcceptWithDefault::TextPlain) = media_type else {
145 panic!("expected text/plain (default)")
146 };
147 Ok(())
148 }
149}