Skip to content

Commit 3a8879e

Browse files
committed
feat: Add a Resource trait for Webmachine resources
1 parent eab3bf4 commit 3a8879e

File tree

6 files changed

+502
-102
lines changed

6 files changed

+502
-102
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ edition = "2024"
1212

1313
[dependencies]
1414
anyhow = "1.0.98"
15+
async-trait = "0.1.86"
1516
bytes = "1.10.1"
1617
chrono = "0.4.41"
1718
futures = "0.3.31"

src/content_negotiation.rs

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
//! The `content_negotiation` module deals with handling media types, languages, charsets and
22
//! encodings as per https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html.
33
4-
use itertools::Itertools;
54
use std::cmp::Ordering;
6-
use crate::headers::HeaderValue;
5+
6+
use itertools::Itertools;
7+
8+
use crate::Resource;
79
use crate::context::WebmachineRequest;
8-
use crate::WebmachineResource;
10+
use crate::headers::HeaderValue;
911

1012
/// Enum to represent a match with media types
1113
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
@@ -116,22 +118,22 @@ pub fn sort_media_types(media_types: &Vec<HeaderValue>) -> Vec<HeaderValue> {
116118

117119
/// Determines if the media types produced by the resource matches the acceptable media types
118120
/// provided by the client. Returns the match if there is one.
119-
pub fn matching_content_type(resource: &WebmachineResource, request: &WebmachineRequest) -> Option<String> {
121+
pub fn matching_content_type(resource: &(dyn Resource + Send + Sync), request: &WebmachineRequest) -> Option<String> {
120122
if request.has_accept_header() {
121123
let acceptable_media_types = sort_media_types(&request.accept());
122-
resource.produces.iter()
123-
.cloned()
124+
resource.produces().iter()
124125
.cartesian_product(acceptable_media_types.iter())
125126
.map(|(produced, acceptable)| {
126127
let acceptable_media_type = acceptable.as_media_type();
127-
let produced_media_type = MediaType::parse_string(produced.as_str());
128+
let produced_media_type = MediaType::parse_string(produced);
128129
(produced_media_type.clone(), acceptable_media_type.clone(), produced_media_type.matches(&acceptable_media_type))
129130
})
130131
.sorted_by(|a, b| Ord::cmp(&a.2, &b.2))
131132
.filter(|val| val.2 != MediaTypeMatch::None)
132-
.next().map(|result| result.0.to_string())
133+
.next()
134+
.map(|result| result.0.to_string())
133135
} else {
134-
resource.produces.first().map(|s| s.to_string())
136+
resource.produces().first().map(|s| s.to_string())
135137
}
136138
}
137139

@@ -221,25 +223,25 @@ pub fn sort_media_languages(media_languages: &Vec<HeaderValue>) -> Vec<MediaLang
221223

222224
/// Determines if the languages produced by the resource matches the acceptable languages
223225
/// provided by the client. Returns the match if there is one.
224-
pub fn matching_language(resource: &WebmachineResource, request: &WebmachineRequest) -> Option<String> {
226+
pub fn matching_language(resource: &(dyn Resource + Send + Sync), request: &WebmachineRequest) -> Option<String> {
225227
if request.has_accept_language_header() && !request.accept_language().is_empty() {
226228
let acceptable_languages = sort_media_languages(&request.accept_language());
227-
if resource.languages_provided.is_empty() {
229+
if resource.languages_provided().is_empty() {
228230
acceptable_languages.first().map(|lang| lang.to_string())
229231
} else {
230232
acceptable_languages.iter()
231-
.cartesian_product(resource.languages_provided.iter())
233+
.cartesian_product(resource.languages_provided().iter())
232234
.map(|(acceptable_language, produced_language)| {
233235
let produced_language = MediaLanguage::parse_string(produced_language);
234236
(produced_language.clone(), produced_language.matches(&acceptable_language))
235237
})
236238
.find(|val| val.1)
237239
.map(|result| result.0.to_string())
238240
}
239-
} else if resource.languages_provided.is_empty() {
241+
} else if resource.languages_provided().is_empty() {
240242
Some("*".to_string())
241243
} else {
242-
resource.languages_provided.first().map(|s| s.to_string())
244+
resource.languages_provided().first().map(|s| s.to_string())
243245
}
244246
}
245247

@@ -311,25 +313,25 @@ pub fn sort_media_charsets(charsets: &Vec<HeaderValue>) -> Vec<Charset> {
311313

312314
/// Determines if the charsets produced by the resource matches the acceptable charsets
313315
/// provided by the client. Returns the match if there is one.
314-
pub fn matching_charset(resource: &WebmachineResource, request: &WebmachineRequest) -> Option<String> {
316+
pub fn matching_charset(resource: &(dyn Resource + Send + Sync), request: &WebmachineRequest) -> Option<String> {
315317
if request.has_accept_charset_header() && !request.accept_charset().is_empty() {
316318
let acceptable_charsets = sort_media_charsets(&request.accept_charset());
317-
if resource.charsets_provided.is_empty() {
319+
if resource.charsets_provided().is_empty() {
318320
acceptable_charsets.first().map(|cs| cs.to_string())
319321
} else {
320322
acceptable_charsets.iter()
321-
.cartesian_product(resource.charsets_provided.iter())
323+
.cartesian_product(resource.charsets_provided().iter())
322324
.map(|(acceptable_charset, provided_charset)| {
323325
let provided_charset = Charset::parse_string(provided_charset);
324326
(provided_charset.clone(), provided_charset.matches(&acceptable_charset))
325327
})
326328
.find(|val| val.1)
327329
.map(|result| result.0.to_string())
328330
}
329-
} else if resource.charsets_provided.is_empty() {
331+
} else if resource.charsets_provided().is_empty() {
330332
Some("ISO-8859-1".to_string())
331333
} else {
332-
resource.charsets_provided.first().map(|s| s.to_string())
334+
resource.charsets_provided().first().map(|s| s.to_string())
333335
}
334336
}
335337

@@ -401,29 +403,29 @@ pub fn sort_encodings(encodings: &Vec<HeaderValue>) -> Vec<Encoding> {
401403

402404
/// Determines if the encodings supported by the resource matches the acceptable encodings
403405
/// provided by the client. Returns the match if there is one.
404-
pub fn matching_encoding(resource: &WebmachineResource, request: &WebmachineRequest) -> Option<String> {
406+
pub fn matching_encoding(resource: &(dyn Resource + Send + Sync), request: &WebmachineRequest) -> Option<String> {
405407
let identity = Encoding::parse_string("identity");
406408
if request.has_accept_encoding_header() {
407409
let acceptable_encodings = sort_encodings(&request.accept_encoding());
408-
if resource.encodings_provided.is_empty() {
410+
if resource.encodings_provided().is_empty() {
409411
if acceptable_encodings.contains(&identity) {
410412
Some("identity".to_string())
411413
} else {
412414
None
413415
}
414416
} else {
415417
acceptable_encodings.iter()
416-
.cartesian_product(resource.encodings_provided.iter())
418+
.cartesian_product(resource.encodings_provided().iter())
417419
.map(|(acceptable_encoding, provided_encoding)| {
418420
let provided_encoding = Encoding::parse_string(provided_encoding);
419421
(provided_encoding.clone(), provided_encoding.matches(&acceptable_encoding))
420422
})
421423
.find(|val| val.1)
422424
.map(|result| { result.0.to_string() })
423425
}
424-
} else if resource.encodings_provided.is_empty() {
426+
} else if resource.encodings_provided().is_empty() {
425427
Some("identity".to_string())
426428
} else {
427-
resource.encodings_provided.first().map(|s| s.to_string())
429+
resource.encodings_provided().first().map(|s| s.to_string())
428430
}
429431
}

src/context.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,18 +197,18 @@ impl WebmachineResponse {
197197
}
198198

199199
/// Adds standard CORS headers to the response
200-
pub fn add_cors_headers(&mut self, allowed_methods: &Vec<String>) {
200+
pub fn add_cors_headers(&mut self, allowed_methods: &[&str]) {
201201
let cors_headers = WebmachineResponse::cors_headers(allowed_methods);
202202
for (k, v) in cors_headers {
203203
self.add_header(k.as_str(), v.iter().map(HeaderValue::basic).collect());
204204
}
205205
}
206206

207207
/// Returns a HashMap of standard CORS headers
208-
pub fn cors_headers(allowed_methods: &Vec<String>) -> HashMap<String, Vec<String>> {
208+
pub fn cors_headers(allowed_methods: &[&str]) -> HashMap<String, Vec<String>> {
209209
hashmap!{
210210
"Access-Control-Allow-Origin".to_string() => vec!["*".to_string()],
211-
"Access-Control-Allow-Methods".to_string() => allowed_methods.clone(),
211+
"Access-Control-Allow-Methods".to_string() => allowed_methods.iter().map(|v| v.to_string()).collect(),
212212
"Access-Control-Allow-Headers".to_string() => vec!["Content-Type".to_string()]
213213
}
214214
}

src/headers.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,13 @@ impl PartialEq<HeaderValue> for HeaderValue {
211211
}
212212
}
213213

214+
impl PartialEq<&HeaderValue> for HeaderValue {
215+
fn eq(&self, other: &&HeaderValue) -> bool {
216+
self == *other
217+
}
218+
}
219+
220+
214221
impl PartialEq<String> for HeaderValue {
215222
fn eq(&self, other: &String) -> bool {
216223
self.value == *other

0 commit comments

Comments
 (0)