Skip to content

Commit a316132

Browse files
committed
fix: Support wild card with acceptable_content_types
1 parent 5989849 commit a316132

File tree

4 files changed

+44
-18
lines changed

4 files changed

+44
-18
lines changed

src/content_negotiation.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::cmp::Ordering;
66
use itertools::Itertools;
77

88
use crate::Resource;
9-
use crate::context::WebmachineRequest;
9+
use crate::context::{WebmachineContext, WebmachineRequest};
1010
use crate::headers::HeaderValue;
1111

1212
/// Enum to represent a match with media types
@@ -16,12 +16,22 @@ pub enum MediaTypeMatch {
1616
Full,
1717
/// Match where the sub-type was a wild card
1818
SubStar,
19-
/// Full whild card match (type and sub-type)
19+
/// Full wild card match (type and sub-type)
2020
Star,
2121
/// Does not match
2222
None
2323
}
2424

25+
impl MediaTypeMatch {
26+
/// If the match is a full or partial match
27+
pub fn is_match(&self) -> bool {
28+
match self {
29+
MediaTypeMatch::None => false,
30+
_ => true
31+
}
32+
}
33+
}
34+
2535
/// Struct to represent a media type
2636
#[derive(Debug, Clone, PartialEq)]
2737
pub struct MediaType {
@@ -74,15 +84,19 @@ impl MediaType {
7484

7585
/// If this media type matches the other media type
7686
pub fn matches(&self, other: &MediaType) -> MediaTypeMatch {
77-
if other.main == "*" {
78-
MediaTypeMatch::Star
79-
} else if self.main == other.main && other.sub == "*" {
80-
MediaTypeMatch::SubStar
81-
} else if self.main == other.main && self.sub == other.sub {
82-
MediaTypeMatch::Full
87+
if other.main == "*" {
88+
if other.sub == "*" || self.sub == other.sub {
89+
MediaTypeMatch::Star
8390
} else {
84-
MediaTypeMatch::None
91+
MediaTypeMatch::None
8592
}
93+
} else if self.main == other.main && other.sub == "*" {
94+
MediaTypeMatch::SubStar
95+
} else if self.main == other.main && self.sub == other.sub {
96+
MediaTypeMatch::Full
97+
} else {
98+
MediaTypeMatch::None
99+
}
86100
}
87101

88102
/// Converts this media type into a string
@@ -137,6 +151,20 @@ pub fn matching_content_type(resource: &(dyn Resource + Send + Sync), request: &
137151
}
138152
}
139153

154+
/// Determines if the media type accepted by the resource matches the media type
155+
/// provided by the client. Returns the match if there is one.
156+
pub fn acceptable_content_type(
157+
resource: &(dyn Resource + Send + Sync),
158+
context: &mut WebmachineContext
159+
) -> bool {
160+
let ct = context.request.content_type().as_media_type();
161+
resource.acceptable_content_types(context)
162+
.iter()
163+
.any(|acceptable_ct| {
164+
ct.matches(&MediaType::parse_string(acceptable_ct)).is_match()
165+
})
166+
}
167+
140168
/// Struct to represent a media language
141169
#[derive(Debug, Clone, PartialEq)]
142170
pub struct MediaLanguage {

src/content_negotiation_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ fn media_type_matches_test() {
145145
expect!(media_type.matches(&MediaType { main: "application".to_string(), sub: "*".to_string(), weight: 1.0 })).to(be_equal_to(MediaTypeMatch::SubStar));
146146
expect!(media_type.matches(&MediaType { main: "*".to_string(), sub: "*".to_string(), weight: 1.0 })).to(be_equal_to(MediaTypeMatch::Star));
147147
expect!(media_type.matches(&MediaType { main: "application".to_string(), sub: "application".to_string(), weight: 1.0 })).to(be_equal_to(MediaTypeMatch::None));
148+
expect!(media_type.matches(&MediaType { main: "*".to_string(), sub: "json".to_string(), weight: 1.0 })).to(be_equal_to(MediaTypeMatch::Star));
149+
expect!(media_type.matches(&MediaType { main: "*".to_string(), sub: "xml".to_string(), weight: 1.0 })).to(be_equal_to(MediaTypeMatch::None));
148150
}
149151

150152
#[test]

src/lib.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ use tracing::{debug, error, trace};
140140

141141
use context::{WebmachineContext, WebmachineRequest, WebmachineResponse};
142142
use headers::HeaderValue;
143-
143+
use crate::content_negotiation::acceptable_content_type;
144144
use crate::paths::map_path;
145145

146146
#[macro_use] pub mod headers;
@@ -234,6 +234,7 @@ pub trait Resource {
234234

235235
/// The list of acceptable content types. Defaults to 'application/json'. If the content type
236236
/// of the request is not in this list, a '415 Unsupported Media Type' response is returned.
237+
/// Wild cards can be used, like `*/*`, `type/*` or `*/sub-type`.
237238
fn acceptable_content_types(&self, _context: &mut WebmachineContext) -> Vec<&str> {
238239
vec!["application/json"]
239240
}
@@ -431,6 +432,7 @@ pub struct WebmachineResource {
431432
pub unsupported_content_headers: WebmachineCallback<bool>,
432433
/// The list of acceptable content types. Defaults to 'application/json'. If the content type
433434
/// of the request is not in this list, a '415 Unsupported Media Type' response is returned.
435+
/// Wild cards can be used, like `*/*`, `type/*` or `*/sub-type`.
434436
pub acceptable_content_types: Vec<String>,
435437
/// If the entity length on PUT or POST is invalid, this should return false, which will result
436438
/// in a '413 Request Entity Too Large' response. Defaults to true.
@@ -1011,13 +1013,8 @@ async fn execute_decision(
10111013
DecisionResult::wrap(resource.unsupported_content_headers(context), "unsupported content headers")
10121014
},
10131015
Decision::B5UnknownContentType => {
1014-
DecisionResult::wrap(context.request.is_put_or_post() && resource.acceptable_content_types(context)
1015-
.iter()
1016-
.find(|ct| {
1017-
let ct = HeaderValue::parse_string(ct);
1018-
context.request.content_type().value.to_lowercase() == ct.value.to_lowercase()
1019-
})
1020-
.is_none(), "acceptable content types")
1016+
DecisionResult::wrap(context.request.is_put_or_post() && !acceptable_content_type(resource, context),
1017+
"acceptable content types")
10211018
},
10221019
Decision::B4RequestEntityTooLarge => {
10231020
DecisionResult::wrap(context.request.is_put_or_post() && !resource.valid_entity_length(context),

src/tests.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,6 @@ async fn execute_state_machine_handles_content_types_with_parameters() {
323323
..WebmachineResource::default()
324324
};
325325
execute_state_machine(&mut context, &resource).await;
326-
dbg!(&context);
327326
expect!(context.response.status).to_not(be_equal_to(415));
328327
}
329328

0 commit comments

Comments
 (0)