Skip to content

Commit c0261d6

Browse files
committed
Add Method::from_static
Allows creating constant `Method` instances, e.g.: const PROPFIND: Method = Method::from_static(b"PROPFIND"); Fixes: #587
1 parent a912445 commit c0261d6

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed

Diff for: src/method.rs

+118
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
//! assert_eq!(Method::POST.as_str(), "POST");
1616
//! ```
1717
18+
use extension::StaticExtension;
19+
1820
use self::extension::{AllocatedExtension, InlineExtension};
1921
use self::Inner::*;
2022

@@ -64,6 +66,8 @@ enum Inner {
6466
ExtensionInline(InlineExtension),
6567
// Otherwise, allocate it
6668
ExtensionAllocated(AllocatedExtension),
69+
// Statically allocated data
70+
ExtensionStatic(StaticExtension),
6771
}
6872

6973
impl Method {
@@ -134,6 +138,30 @@ impl Method {
134138
}
135139
}
136140

141+
/// Convert static bytes into a `Method`.
142+
pub const fn from_static(src: &'static [u8]) -> Method {
143+
match src {
144+
b"OPTIONS" => Method::OPTIONS,
145+
b"GET" => Method::GET,
146+
b"POST" => Method::POST,
147+
b"PUT" => Method::PUT,
148+
b"DELETE" => Method::DELETE,
149+
b"HEAD" => Method::HEAD,
150+
b"TRACE" => Method::TRACE,
151+
b"CONNECT" => Method::CONNECT,
152+
b"PATCH" => Method::PATCH,
153+
src => {
154+
if src.len() <= 15 {
155+
let inline = InlineExtension::from_static(src);
156+
Method(ExtensionInline(inline))
157+
} else {
158+
let allocated = StaticExtension::from_static(src);
159+
Method(ExtensionStatic(allocated))
160+
}
161+
}
162+
}
163+
}
164+
137165
fn extension_inline(src: &[u8]) -> Result<Method, InvalidMethod> {
138166
let inline = InlineExtension::new(src)?;
139167

@@ -176,6 +204,7 @@ impl Method {
176204
Patch => "PATCH",
177205
ExtensionInline(ref inline) => inline.as_str(),
178206
ExtensionAllocated(ref allocated) => allocated.as_str(),
207+
ExtensionStatic(ref s) => s.as_str(),
179208
}
180209
}
181210
}
@@ -316,6 +345,9 @@ mod extension {
316345
// Invariant: self.0 contains valid UTF-8.
317346
pub struct AllocatedExtension(Box<[u8]>);
318347

348+
#[derive(Clone, PartialEq, Eq, Hash)]
349+
pub struct StaticExtension(&'static [u8]);
350+
319351
impl InlineExtension {
320352
// Method::from_bytes() assumes this is at least 7
321353
pub const MAX: usize = 15;
@@ -330,6 +362,34 @@ mod extension {
330362
Ok(InlineExtension(data, src.len() as u8))
331363
}
332364

365+
/// Convert static bytes into an `InlineExtension`.
366+
///
367+
/// # Panics
368+
///
369+
/// If the input bytes are not a valid method name or if the method name is over 15 bytes.
370+
pub const fn from_static(src: &'static [u8]) -> InlineExtension {
371+
let mut i = 0;
372+
let mut dst = [0u8; 15];
373+
if src.len() > 15 {
374+
// panicking in const requires Rust 1.57.0
375+
#[allow(unconditional_panic)]
376+
([] as [u8; 0])[0];
377+
}
378+
while i < src.len() {
379+
let byte = src[i];
380+
let v = METHOD_CHARS[byte as usize];
381+
if v == 0 {
382+
// panicking in const requires Rust 1.57.0
383+
#[allow(unconditional_panic)]
384+
([] as [u8; 0])[0];
385+
}
386+
dst[i] = byte;
387+
i += 1;
388+
}
389+
390+
InlineExtension(dst, i as u8)
391+
}
392+
333393
pub fn as_str(&self) -> &str {
334394
let InlineExtension(ref data, len) = self;
335395
// Safety: the invariant of InlineExtension ensures that the first
@@ -356,6 +416,32 @@ mod extension {
356416
}
357417
}
358418

419+
impl StaticExtension {
420+
pub const fn from_static(src: &'static [u8]) -> StaticExtension {
421+
let mut i = 0;
422+
while i < src.len() {
423+
let byte = src[i];
424+
let v = METHOD_CHARS[byte as usize];
425+
if v == 0 {
426+
// panicking in const requires Rust 1.57.0
427+
#[allow(unconditional_panic)]
428+
([] as [u8; 0])[0];
429+
}
430+
i += 1;
431+
}
432+
433+
// Invariant: data is exactly src.len() long and write_checked
434+
// ensures that the first src.len() bytes of data are valid UTF-8.
435+
StaticExtension(src)
436+
}
437+
438+
pub fn as_str(&self) -> &str {
439+
// Safety: the invariant of StaticExtension ensures that self.0
440+
// contains valid UTF-8.
441+
unsafe { str::from_utf8_unchecked(&self.0) }
442+
}
443+
}
444+
359445
// From the RFC 9110 HTTP Semantics, section 9.1, the HTTP method is case-sensitive and can
360446
// contain the following characters:
361447
//
@@ -436,6 +522,38 @@ mod test {
436522
assert_eq!(Method::GET, &Method::GET);
437523
}
438524

525+
#[test]
526+
fn test_from_static() {
527+
// First class variant
528+
assert_eq!(
529+
Method::from_static(b"GET"),
530+
Method::from_bytes(b"GET").unwrap()
531+
);
532+
// Inline, len < 15
533+
assert_eq!(
534+
Method::from_static(b"PROPFIND"),
535+
Method::from_bytes(b"PROPFIND").unwrap()
536+
);
537+
// Inline, len == 15
538+
assert_eq!(Method::from_static(b"GET"), Method::GET);
539+
assert_eq!(
540+
Method::from_static(b"123456789012345").to_string(),
541+
"123456789012345".to_string()
542+
);
543+
// Ref, len > 15
544+
Method::from_static(b"1234567890123456");
545+
assert_eq!(
546+
Method::from_static(b"1234567890123456").to_string(),
547+
"1234567890123456".to_string()
548+
);
549+
}
550+
551+
#[test]
552+
#[should_panic]
553+
fn test_from_static_bad() {
554+
Method::from_static(b"\0");
555+
}
556+
439557
#[test]
440558
fn test_invalid_method() {
441559
assert!(Method::from_str("").is_err());

0 commit comments

Comments
 (0)