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/plain")]
39 TextPlain,
40 #[accept(mediatype = "application/json")]
41 ApplicationJson,
42 #[accept(mediatype = "application/ld+json")]
43 ApplicationLdJson,
44 }
45
46 #[tokio::test]
47 async fn test_accept_extractor_basic() -> Result<(), Box<dyn std::error::Error>> {
48 let req = Request::builder()
49 .header("accept", "application/json,text/plain")
50 .body(Body::from(""))?;
51 let state = ();
52 let media_type = Accept::from_request(req, &state)
53 .await
54 .expect("Expected no rejection");
55 let Accept::ApplicationJson = media_type else {
56 panic!("expected application/json")
57 };
58 Ok(())
59 }
60
61 #[tokio::test]
62 async fn test_accept_extractor_q() -> Result<(), Box<dyn std::error::Error>> {
63 let req = Request::builder()
64 .header("accept", "application/json;q=0.9,text/plain")
65 .body(Body::from(""))?;
66 let state = ();
67 let media_type = Accept::from_request(req, &state)
68 .await
69 .expect("Expected no rejection");
70 let Accept::TextPlain = media_type else {
71 panic!("expected text/plain")
72 };
73 Ok(())
74 }
75
76 #[tokio::test]
77 async fn test_accept_extractor_specifity() -> Result<(), Box<dyn std::error::Error>> {
78 let req = Request::builder()
79 .header("accept", "text/*,text/plain")
80 .body(Body::from(""))?;
81 let state = ();
82 let media_type = Accept::from_request(req, &state)
83 .await
84 .expect("Expected no rejection");
85 let Accept::TextPlain = media_type else {
86 panic!("expected text/plain")
87 };
88 Ok(())
89 }
90
91 #[tokio::test]
92 async fn test_accept_extractor_suffix() -> Result<(), Box<dyn std::error::Error>> {
93 let req = Request::builder()
94 .header("accept", "text/*,application/ld+json,text/plain")
95 .body(Body::from(""))?;
96 let state = ();
97 let media_type = Accept::from_request(req, &state)
98 .await
99 .expect("Expected no rejection");
100 let Accept::ApplicationLdJson = media_type else {
101 panic!("expected application/ldjson")
102 };
103 Ok(())
104 }
105
106 #[tokio::test]
107 async fn test_accept_extractor_no_match() -> Result<(), Box<dyn std::error::Error>> {
108 let req = Request::builder()
109 .header("accept", "text/csv")
110 .body(Body::from(""))?;
111 let state = ();
112 let media_type = Accept::from_request(req, &state).await;
113 let Err(AcceptRejection::NoSupportedMediaTypeFound) = media_type else {
114 panic!("expected no supported media type found")
115 };
116 Ok(())
117 }
118
119 #[tokio::test]
120 async fn test_accept_extractor_star() -> Result<(), Box<dyn std::error::Error>> {
121 let req = Request::builder()
122 .header("accept", "text/csv,text/*")
123 .body(Body::from(""))?;
124 let state = ();
125 let media_type = Accept::from_request(req, &state).await;
126 let Ok(Accept::TextPlain) = media_type else {
127 panic!("expected text/*, got {:?}", media_type)
128 };
129 Ok(())
130 }
131
132 #[tokio::test]
133 async fn test_accept_extractor_star_star() -> Result<(), Box<dyn std::error::Error>> {
134 let req = Request::builder()
135 .header("accept", "text/csv,*/*")
136 .body(Body::from(""))?;
137 let state = ();
138 let media_type = Accept::from_request(req, &state).await;
139 let Ok(Accept::TextPlain) = media_type else {
140 panic!("expected text/plain")
141 };
142 Ok(())
143 }
144
145 #[derive(Debug, AcceptExtractor, Default)]
146 enum AcceptWithDefault {
147 #[accept(mediatype = "application/json")]
148 ApplicationJson,
149 #[default]
150 #[accept(mediatype = "text/plain")]
151 TextPlain,
152 }
153
154 #[tokio::test]
155 async fn test_accept_extractor_default() -> Result<(), Box<dyn std::error::Error>> {
156 let req = Request::builder()
157 .header("accept", "text/csv")
158 .body(Body::from(""))?;
159 let state = ();
160 let media_type = AcceptWithDefault::from_request(req, &state).await;
161 let Err(AcceptRejection::NoSupportedMediaTypeFound) = media_type else {
162 panic!("expected no supported media type found")
163 };
164
165 let req = Request::builder().body(Body::from(""))?;
166 let state = ();
167 let media_type = AcceptWithDefault::from_request(req, &state).await;
168 let Ok(AcceptWithDefault::TextPlain) = media_type else {
169 panic!("expected text/plain (default)")
170 };
171 Ok(())
172 }
173
174 #[tokio::test]
175 async fn test_accept_extractor_star_star_default() -> Result<(), Box<dyn std::error::Error>> {
176 let req = Request::builder()
177 .header("accept", "text/csv,*/*")
178 .body(Body::from(""))?;
179 let state = ();
180 let media_type = AcceptWithDefault::from_request(req, &state).await;
181 let Ok(AcceptWithDefault::TextPlain) = media_type else {
182 panic!("expected text/plain (default)")
183 };
184 Ok(())
185 }
186}