Skip to main content

fastapi_http/
range.rs

1//! HTTP Range request parsing and response generation (RFC 7233).
2//!
3//! This module provides support for:
4//!
5//! - Parsing `Range` headers (bytes=start-end, bytes=start-, bytes=-suffix)
6//! - Validating ranges against resource sizes
7//! - Generating `Content-Range` headers
8//! - Building 206 Partial Content responses
9//! - Handling 416 Range Not Satisfiable errors
10//!
11//! # Example
12//!
13//! ```ignore
14//! use fastapi_http::range::{Range, parse_range_header};
15//!
16//! let range_header = "bytes=0-499";
17//! let file_size = 1000;
18//!
19//! match parse_range_header(range_header, file_size) {
20//!     Ok(ranges) => {
21//!         // Handle partial content response
22//!         for range in ranges {
23//!             println!("Serve bytes {}-{}", range.start, range.end);
24//!         }
25//!     }
26//!     Err(e) => {
27//!         // Return 416 Range Not Satisfiable
28//!     }
29//! }
30//! ```
31
32use std::fmt;
33
34/// A validated byte range within a resource.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct ByteRange {
37    /// Start byte offset (inclusive).
38    pub start: u64,
39    /// End byte offset (inclusive).
40    pub end: u64,
41}
42
43impl ByteRange {
44    /// Create a new byte range.
45    ///
46    /// # Panics
47    ///
48    /// Panics if start > end.
49    #[must_use]
50    pub fn new(start: u64, end: u64) -> Self {
51        assert!(start <= end, "start must be <= end");
52        Self { start, end }
53    }
54
55    /// Get the length of this range in bytes.
56    #[must_use]
57    pub fn len(&self) -> u64 {
58        self.end.saturating_sub(self.start).saturating_add(1)
59    }
60
61    /// Check if the range is empty (zero length).
62    #[must_use]
63    pub fn is_empty(&self) -> bool {
64        false // A valid ByteRange always has at least 1 byte
65    }
66
67    /// Format as a Content-Range header value.
68    ///
69    /// Returns a string like "bytes 0-499/1000".
70    #[must_use]
71    pub fn content_range_header(&self, total_size: u64) -> String {
72        format!("bytes {}-{}/{}", self.start, self.end, total_size)
73    }
74}
75
76impl fmt::Display for ByteRange {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        write!(f, "{}-{}", self.start, self.end)
79    }
80}
81
82/// Errors that can occur when parsing or validating range requests.
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub enum RangeError {
85    /// The Range header syntax is invalid.
86    InvalidSyntax(String),
87    /// The range unit is not "bytes".
88    UnsupportedUnit(String),
89    /// The range is not satisfiable for the given resource size.
90    NotSatisfiable {
91        /// The size of the resource.
92        resource_size: u64,
93    },
94    /// Too many ranges requested (limit exceeded).
95    MultipleRangesNotSupported,
96}
97
98impl fmt::Display for RangeError {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        match self {
101            Self::InvalidSyntax(msg) => write!(f, "invalid range syntax: {msg}"),
102            Self::UnsupportedUnit(unit) => write!(f, "unsupported range unit: {unit}"),
103            Self::NotSatisfiable { resource_size } => {
104                write!(
105                    f,
106                    "range not satisfiable for resource of size {resource_size}"
107                )
108            }
109            Self::MultipleRangesNotSupported => write!(f, "too many ranges requested"),
110        }
111    }
112}
113
114impl std::error::Error for RangeError {}
115
116/// A parsed range specification before validation against resource size.
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub enum RangeSpec {
119    /// bytes=start-end (both specified).
120    FromTo { start: u64, end: u64 },
121    /// bytes=start- (from start to end of resource).
122    From { start: u64 },
123    /// bytes=-suffix (last N bytes).
124    Suffix { length: u64 },
125}
126
127impl RangeSpec {
128    /// Validate and resolve this range specification against a resource size.
129    ///
130    /// Returns a concrete `ByteRange` if the range is satisfiable.
131    ///
132    /// # Errors
133    ///
134    /// Returns `RangeError::NotSatisfiable` if the range cannot be satisfied.
135    pub fn resolve(self, resource_size: u64) -> Result<ByteRange, RangeError> {
136        if resource_size == 0 {
137            return Err(RangeError::NotSatisfiable { resource_size });
138        }
139
140        match self {
141            Self::FromTo { start, end } => {
142                // RFC 7233: If the last-byte-pos is >= resource size, use resource_size - 1
143                let end = end.min(resource_size - 1);
144
145                if start > end || start >= resource_size {
146                    return Err(RangeError::NotSatisfiable { resource_size });
147                }
148
149                Ok(ByteRange::new(start, end))
150            }
151            Self::From { start } => {
152                if start >= resource_size {
153                    return Err(RangeError::NotSatisfiable { resource_size });
154                }
155                Ok(ByteRange::new(start, resource_size - 1))
156            }
157            Self::Suffix { length } => {
158                if length == 0 {
159                    return Err(RangeError::NotSatisfiable { resource_size });
160                }
161                // Last N bytes
162                let start = resource_size.saturating_sub(length);
163                Ok(ByteRange::new(start, resource_size - 1))
164            }
165        }
166    }
167}
168
169/// Parse a Range header value and resolve it against a resource size.
170///
171/// Supports the following formats:
172/// - `bytes=0-499` - First 500 bytes
173/// - `bytes=500-999` - Bytes 500-999
174/// - `bytes=500-` - From byte 500 to end
175/// - `bytes=-500` - Last 500 bytes
176/// - `bytes=0-0, 500-999` - Multiple ranges
177///
178/// # Errors
179///
180/// Returns an error if:
181/// - The syntax is invalid
182/// - The unit is not "bytes"
183/// - Too many ranges are specified (limit exceeded)
184/// - The range is not satisfiable for the given resource size
185///
186/// # Examples
187///
188/// ```
189/// use fastapi_http::range::parse_range_header;
190///
191/// // First 500 bytes of a 1000-byte resource
192/// let ranges = parse_range_header("bytes=0-499", 1000).unwrap();
193/// assert_eq!(ranges[0].start, 0);
194/// assert_eq!(ranges[0].end, 499);
195/// assert_eq!(ranges[0].len(), 500);
196///
197/// // Last 100 bytes
198/// let ranges = parse_range_header("bytes=-100", 1000).unwrap();
199/// assert_eq!(ranges[0].start, 900);
200/// assert_eq!(ranges[0].end, 999);
201///
202/// // From byte 500 to end
203/// let ranges = parse_range_header("bytes=500-", 1000).unwrap();
204/// assert_eq!(ranges[0].start, 500);
205/// assert_eq!(ranges[0].end, 999);
206/// ```
207pub fn parse_range_header(header: &str, resource_size: u64) -> Result<Vec<ByteRange>, RangeError> {
208    let specs = parse_range_spec(header)?;
209
210    let mut ranges = Vec::with_capacity(specs.len());
211    for spec in specs {
212        match spec.resolve(resource_size) {
213            Ok(r) => ranges.push(r),
214            Err(RangeError::NotSatisfiable { .. }) => {
215                // Ignore individual unsatisfiable ranges; if none overlap at all,
216                // return a 416 for the full request.
217            }
218            Err(e) => return Err(e),
219        }
220    }
221
222    if ranges.is_empty() {
223        return Err(RangeError::NotSatisfiable { resource_size });
224    }
225
226    normalize_ranges(&mut ranges);
227    Ok(ranges)
228}
229
230/// Parse a Range header into `RangeSpec` entries without validating against resource size.
231///
232/// This is useful when you want to parse the header before knowing the resource size.
233///
234/// # Errors
235///
236/// Returns an error if the syntax is invalid or too many ranges are specified.
237pub fn parse_range_spec(header: &str) -> Result<Vec<RangeSpec>, RangeError> {
238    let header = header.trim();
239
240    // Split on '='
241    let (unit, range_set) = header
242        .split_once('=')
243        .ok_or_else(|| RangeError::InvalidSyntax("missing '=' separator".to_string()))?;
244
245    let unit = unit.trim();
246    let range_set = range_set.trim();
247
248    // Only support "bytes" unit
249    if !unit.eq_ignore_ascii_case("bytes") {
250        return Err(RangeError::UnsupportedUnit(unit.to_string()));
251    }
252
253    const MAX_RANGES: usize = 16;
254
255    let mut specs = Vec::new();
256    for part in range_set.split(',') {
257        let part = part.trim();
258        if part.is_empty() {
259            return Err(RangeError::InvalidSyntax("empty range".to_string()));
260        }
261        specs.push(parse_single_range(part)?);
262        if specs.len() > MAX_RANGES {
263            return Err(RangeError::MultipleRangesNotSupported);
264        }
265    }
266
267    Ok(specs)
268}
269
270/// Parse a single range specification (without the unit prefix).
271fn parse_single_range(range: &str) -> Result<RangeSpec, RangeError> {
272    let range = range.trim();
273
274    if range.is_empty() {
275        return Err(RangeError::InvalidSyntax("empty range".to_string()));
276    }
277
278    // Check for suffix range: -500
279    if range.starts_with('-') {
280        let suffix = &range[1..];
281        let length: u64 = suffix
282            .parse()
283            .map_err(|_| RangeError::InvalidSyntax(format!("invalid suffix length: {suffix}")))?;
284        return Ok(RangeSpec::Suffix { length });
285    }
286
287    // Split on '-'
288    let (start_str, end_str) = range
289        .split_once('-')
290        .ok_or_else(|| RangeError::InvalidSyntax("missing '-' separator".to_string()))?;
291
292    let start: u64 = start_str
293        .trim()
294        .parse()
295        .map_err(|_| RangeError::InvalidSyntax(format!("invalid start: {start_str}")))?;
296
297    let end_str = end_str.trim();
298
299    if end_str.is_empty() {
300        // Open-ended: bytes=500-
301        Ok(RangeSpec::From { start })
302    } else {
303        // Bounded: bytes=0-499
304        let end: u64 = end_str
305            .parse()
306            .map_err(|_| RangeError::InvalidSyntax(format!("invalid end: {end_str}")))?;
307        Ok(RangeSpec::FromTo { start, end })
308    }
309}
310
311fn normalize_ranges(ranges: &mut Vec<ByteRange>) {
312    ranges.sort_by_key(|r| r.start);
313
314    let mut out: Vec<ByteRange> = Vec::with_capacity(ranges.len());
315    for r in ranges.drain(..) {
316        match out.last_mut() {
317            None => out.push(r),
318            Some(last) => {
319                // Merge overlapping or adjacent ranges (e.g., 0-10 and 11-20).
320                if r.start <= last.end.saturating_add(1) {
321                    last.end = last.end.max(r.end);
322                } else {
323                    out.push(r);
324                }
325            }
326        }
327    }
328    *ranges = out;
329}
330
331/// Check if a request supports range requests based on Accept-Ranges.
332///
333/// Returns `true` if the resource can serve partial content.
334#[must_use]
335pub fn supports_ranges(accept_ranges: Option<&str>) -> bool {
336    match accept_ranges {
337        Some(value) => value
338            .split(',')
339            .map(str::trim)
340            .any(|unit| unit.eq_ignore_ascii_case("bytes")),
341        None => false,
342    }
343}
344
345/// Generate the Accept-Ranges header value for byte range support.
346#[must_use]
347pub const fn accept_ranges_bytes() -> &'static str {
348    "bytes"
349}
350
351/// Generate a Content-Range header for an unsatisfiable range.
352///
353/// Returns a string like "bytes */1000" for a 416 response.
354#[must_use]
355pub fn content_range_unsatisfiable(resource_size: u64) -> String {
356    format!("bytes */{resource_size}")
357}
358
359/// Result of validating an If-Range precondition.
360#[derive(Debug, Clone, Copy, PartialEq, Eq)]
361pub enum IfRangeResult {
362    /// The condition passed - serve partial content.
363    ServePartial,
364    /// The condition failed - serve full content (ignore Range header).
365    ServeFull,
366}
367
368/// Check an If-Range precondition header against a validator.
369///
370/// The If-Range header contains either:
371/// - An ETag value (e.g., `"abc123"`)
372/// - A Last-Modified date (e.g., `Wed, 21 Oct 2015 07:28:00 GMT`)
373///
374/// If the If-Range value matches the current resource, the Range request
375/// should be honored (return 206 Partial Content). Otherwise, the full
376/// resource should be returned (ignore the Range header, return 200 OK).
377///
378/// # Arguments
379///
380/// * `if_range` - The If-Range header value from the request
381/// * `etag` - The current ETag of the resource (if available)
382/// * `last_modified` - The current Last-Modified of the resource (if available)
383///
384/// # Returns
385///
386/// - `IfRangeResult::ServePartial` if the condition is satisfied
387/// - `IfRangeResult::ServeFull` if the condition fails or no validators are available
388///
389/// # Example
390///
391/// ```
392/// use fastapi_http::range::{check_if_range, IfRangeResult};
393///
394/// // ETag match
395/// let result = check_if_range(
396///     "\"abc123\"",
397///     Some("\"abc123\""),
398///     None,
399/// );
400/// assert_eq!(result, IfRangeResult::ServePartial);
401///
402/// // ETag mismatch
403/// let result = check_if_range(
404///     "\"abc123\"",
405///     Some("\"def456\""),
406///     None,
407/// );
408/// assert_eq!(result, IfRangeResult::ServeFull);
409/// ```
410#[must_use]
411pub fn check_if_range(
412    if_range: &str,
413    etag: Option<&str>,
414    last_modified: Option<&str>,
415) -> IfRangeResult {
416    let if_range = if_range.trim();
417
418    // Empty If-Range means the condition is satisfied
419    if if_range.is_empty() {
420        return IfRangeResult::ServePartial;
421    }
422
423    // Check if it looks like an ETag (starts with " or W/)
424    if if_range.starts_with('"') || if_range.starts_with("W/") {
425        // Compare as ETag
426        if let Some(current_etag) = etag {
427            // Strong comparison for If-Range (weak ETags don't match)
428            if etag_strong_match(if_range, current_etag) {
429                return IfRangeResult::ServePartial;
430            }
431        }
432        IfRangeResult::ServeFull
433    } else {
434        // Assume it's a date, compare as Last-Modified
435        if let Some(current_last_modified) = last_modified {
436            // Simple string comparison (dates should be in HTTP date format)
437            if if_range == current_last_modified {
438                return IfRangeResult::ServePartial;
439            }
440        }
441        IfRangeResult::ServeFull
442    }
443}
444
445/// Check if two ETags match using strong comparison.
446///
447/// For If-Range, weak ETags (W/"...") don't match. Only strong ETags match.
448fn etag_strong_match(etag1: &str, etag2: &str) -> bool {
449    // Weak ETags start with W/
450    if etag1.starts_with("W/") || etag2.starts_with("W/") {
451        return false;
452    }
453
454    // Both must be strong ETags and equal
455    etag1 == etag2
456}
457
458#[cfg(test)]
459mod tests {
460    use super::*;
461
462    // =========================================================================
463    // ByteRange tests
464    // =========================================================================
465
466    #[test]
467    fn byte_range_new() {
468        let range = ByteRange::new(0, 499);
469        assert_eq!(range.start, 0);
470        assert_eq!(range.end, 499);
471    }
472
473    #[test]
474    fn byte_range_len() {
475        let range = ByteRange::new(0, 499);
476        assert_eq!(range.len(), 500);
477
478        let range = ByteRange::new(0, 0);
479        assert_eq!(range.len(), 1);
480
481        let range = ByteRange::new(100, 199);
482        assert_eq!(range.len(), 100);
483    }
484
485    #[test]
486    fn byte_range_content_range_header() {
487        let range = ByteRange::new(0, 499);
488        assert_eq!(range.content_range_header(1000), "bytes 0-499/1000");
489
490        let range = ByteRange::new(500, 999);
491        assert_eq!(range.content_range_header(1000), "bytes 500-999/1000");
492    }
493
494    #[test]
495    fn byte_range_display() {
496        let range = ByteRange::new(0, 499);
497        assert_eq!(format!("{range}"), "0-499");
498    }
499
500    #[test]
501    #[should_panic(expected = "start must be <= end")]
502    fn byte_range_invalid() {
503        let _ = ByteRange::new(500, 100);
504    }
505
506    // =========================================================================
507    // RangeSpec tests
508    // =========================================================================
509
510    #[test]
511    fn range_spec_from_to_valid() {
512        let spec = RangeSpec::FromTo { start: 0, end: 499 };
513        let range = spec.resolve(1000).unwrap();
514        assert_eq!(range.start, 0);
515        assert_eq!(range.end, 499);
516    }
517
518    #[test]
519    fn range_spec_from_to_clamped() {
520        // End exceeds resource size, should be clamped
521        let spec = RangeSpec::FromTo {
522            start: 0,
523            end: 9999,
524        };
525        let range = spec.resolve(1000).unwrap();
526        assert_eq!(range.start, 0);
527        assert_eq!(range.end, 999);
528    }
529
530    #[test]
531    fn range_spec_from_to_not_satisfiable() {
532        let spec = RangeSpec::FromTo {
533            start: 1000,
534            end: 1500,
535        };
536        let err = spec.resolve(1000).unwrap_err();
537        assert_eq!(
538            err,
539            RangeError::NotSatisfiable {
540                resource_size: 1000
541            }
542        );
543    }
544
545    #[test]
546    fn range_spec_from_valid() {
547        let spec = RangeSpec::From { start: 500 };
548        let range = spec.resolve(1000).unwrap();
549        assert_eq!(range.start, 500);
550        assert_eq!(range.end, 999);
551    }
552
553    #[test]
554    fn range_spec_from_not_satisfiable() {
555        let spec = RangeSpec::From { start: 1000 };
556        let err = spec.resolve(1000).unwrap_err();
557        assert_eq!(
558            err,
559            RangeError::NotSatisfiable {
560                resource_size: 1000
561            }
562        );
563    }
564
565    #[test]
566    fn range_spec_suffix_valid() {
567        let spec = RangeSpec::Suffix { length: 100 };
568        let range = spec.resolve(1000).unwrap();
569        assert_eq!(range.start, 900);
570        assert_eq!(range.end, 999);
571    }
572
573    #[test]
574    fn range_spec_suffix_exceeds_size() {
575        // Suffix larger than resource, returns entire resource
576        let spec = RangeSpec::Suffix { length: 2000 };
577        let range = spec.resolve(1000).unwrap();
578        assert_eq!(range.start, 0);
579        assert_eq!(range.end, 999);
580    }
581
582    #[test]
583    fn range_spec_suffix_zero() {
584        let spec = RangeSpec::Suffix { length: 0 };
585        let err = spec.resolve(1000).unwrap_err();
586        assert_eq!(
587            err,
588            RangeError::NotSatisfiable {
589                resource_size: 1000
590            }
591        );
592    }
593
594    #[test]
595    fn range_spec_empty_resource() {
596        let spec = RangeSpec::From { start: 0 };
597        let err = spec.resolve(0).unwrap_err();
598        assert_eq!(err, RangeError::NotSatisfiable { resource_size: 0 });
599    }
600
601    // =========================================================================
602    // parse_range_header tests
603    // =========================================================================
604
605    #[test]
606    fn parse_range_from_to() {
607        let ranges = parse_range_header("bytes=0-499", 1000).unwrap();
608        assert_eq!(ranges.len(), 1);
609        assert_eq!(ranges[0].start, 0);
610        assert_eq!(ranges[0].end, 499);
611        assert_eq!(ranges[0].len(), 500);
612    }
613
614    #[test]
615    fn parse_range_from() {
616        let ranges = parse_range_header("bytes=500-", 1000).unwrap();
617        assert_eq!(ranges.len(), 1);
618        assert_eq!(ranges[0].start, 500);
619        assert_eq!(ranges[0].end, 999);
620    }
621
622    #[test]
623    fn parse_range_suffix() {
624        let ranges = parse_range_header("bytes=-100", 1000).unwrap();
625        assert_eq!(ranges.len(), 1);
626        assert_eq!(ranges[0].start, 900);
627        assert_eq!(ranges[0].end, 999);
628    }
629
630    #[test]
631    fn parse_range_with_spaces() {
632        let ranges = parse_range_header("  bytes = 0 - 499  ", 1000).unwrap();
633        assert_eq!(ranges.len(), 1);
634        assert_eq!(ranges[0].start, 0);
635        assert_eq!(ranges[0].end, 499);
636    }
637
638    #[test]
639    fn parse_range_invalid_unit() {
640        let err = parse_range_header("items=0-10", 100).unwrap_err();
641        assert!(matches!(err, RangeError::UnsupportedUnit(_)));
642    }
643
644    #[test]
645    fn parse_range_multiple_ranges() {
646        let ranges = parse_range_header("bytes=0-10, 20-30", 100).unwrap();
647        assert_eq!(ranges.len(), 2);
648        assert_eq!(ranges[0], ByteRange::new(0, 10));
649        assert_eq!(ranges[1], ByteRange::new(20, 30));
650    }
651
652    #[test]
653    fn parse_range_too_many_ranges_rejected() {
654        let header = (0..17)
655            .map(|i| format!("{i}-{i}"))
656            .collect::<Vec<_>>()
657            .join(", ");
658        let header = format!("bytes={header}");
659        let err = parse_range_header(&header, 1000).unwrap_err();
660        assert_eq!(err, RangeError::MultipleRangesNotSupported);
661    }
662
663    #[test]
664    fn parse_range_invalid_syntax_no_equals() {
665        let err = parse_range_header("bytes 0-10", 100).unwrap_err();
666        assert!(matches!(err, RangeError::InvalidSyntax(_)));
667    }
668
669    #[test]
670    fn parse_range_invalid_syntax_no_dash() {
671        let err = parse_range_header("bytes=100", 100).unwrap_err();
672        assert!(matches!(err, RangeError::InvalidSyntax(_)));
673    }
674
675    #[test]
676    fn parse_range_invalid_start() {
677        let err = parse_range_header("bytes=abc-100", 1000).unwrap_err();
678        assert!(matches!(err, RangeError::InvalidSyntax(_)));
679    }
680
681    #[test]
682    fn parse_range_invalid_end() {
683        let err = parse_range_header("bytes=0-xyz", 1000).unwrap_err();
684        assert!(matches!(err, RangeError::InvalidSyntax(_)));
685    }
686
687    #[test]
688    fn parse_range_not_satisfiable() {
689        let err = parse_range_header("bytes=1000-2000", 500).unwrap_err();
690        assert_eq!(err, RangeError::NotSatisfiable { resource_size: 500 });
691    }
692
693    // =========================================================================
694    // Helper function tests
695    // =========================================================================
696
697    #[test]
698    fn test_accept_ranges_bytes() {
699        assert_eq!(accept_ranges_bytes(), "bytes");
700    }
701
702    #[test]
703    fn test_content_range_unsatisfiable() {
704        assert_eq!(content_range_unsatisfiable(1000), "bytes */1000");
705    }
706
707    #[test]
708    fn test_supports_ranges() {
709        assert!(supports_ranges(Some("bytes")));
710        assert!(supports_ranges(Some("Bytes")));
711        assert!(supports_ranges(Some("bytes, other")));
712        assert!(!supports_ranges(Some("none")));
713        assert!(!supports_ranges(Some("None")));
714        assert!(!supports_ranges(Some("items")));
715        assert!(!supports_ranges(Some("")));
716        assert!(!supports_ranges(Some("   ")));
717        assert!(!supports_ranges(None));
718    }
719
720    // =========================================================================
721    // RangeError Display tests
722    // =========================================================================
723
724    #[test]
725    fn range_error_display() {
726        let err = RangeError::InvalidSyntax("test".to_string());
727        assert!(format!("{err}").contains("invalid range syntax"));
728
729        let err = RangeError::UnsupportedUnit("items".to_string());
730        assert!(format!("{err}").contains("unsupported range unit: items"));
731
732        let err = RangeError::NotSatisfiable { resource_size: 500 };
733        assert!(format!("{err}").contains("range not satisfiable"));
734
735        let err = RangeError::MultipleRangesNotSupported;
736        assert!(format!("{err}").contains("too many ranges requested"));
737    }
738
739    // =========================================================================
740    // If-Range tests
741    // =========================================================================
742
743    #[test]
744    fn if_range_etag_match() {
745        let result = check_if_range("\"abc123\"", Some("\"abc123\""), None);
746        assert_eq!(result, IfRangeResult::ServePartial);
747    }
748
749    #[test]
750    fn if_range_etag_mismatch() {
751        let result = check_if_range("\"abc123\"", Some("\"def456\""), None);
752        assert_eq!(result, IfRangeResult::ServeFull);
753    }
754
755    #[test]
756    fn if_range_etag_no_current() {
757        let result = check_if_range("\"abc123\"", None, None);
758        assert_eq!(result, IfRangeResult::ServeFull);
759    }
760
761    #[test]
762    fn if_range_weak_etag_never_matches() {
763        // Weak ETag in If-Range
764        let result = check_if_range("W/\"abc123\"", Some("W/\"abc123\""), None);
765        assert_eq!(result, IfRangeResult::ServeFull);
766
767        // Weak ETag in current
768        let result = check_if_range("\"abc123\"", Some("W/\"abc123\""), None);
769        assert_eq!(result, IfRangeResult::ServeFull);
770    }
771
772    #[test]
773    fn if_range_date_match() {
774        let date = "Wed, 21 Oct 2015 07:28:00 GMT";
775        let result = check_if_range(date, None, Some(date));
776        assert_eq!(result, IfRangeResult::ServePartial);
777    }
778
779    #[test]
780    fn if_range_date_mismatch() {
781        let result = check_if_range(
782            "Wed, 21 Oct 2015 07:28:00 GMT",
783            None,
784            Some("Thu, 22 Oct 2015 07:28:00 GMT"),
785        );
786        assert_eq!(result, IfRangeResult::ServeFull);
787    }
788
789    #[test]
790    fn if_range_date_no_current() {
791        let result = check_if_range("Wed, 21 Oct 2015 07:28:00 GMT", None, None);
792        assert_eq!(result, IfRangeResult::ServeFull);
793    }
794
795    #[test]
796    fn if_range_empty_header() {
797        let result = check_if_range("", None, None);
798        assert_eq!(result, IfRangeResult::ServePartial);
799    }
800
801    #[test]
802    fn if_range_whitespace_trimmed() {
803        let result = check_if_range("  \"abc123\"  ", Some("\"abc123\""), None);
804        assert_eq!(result, IfRangeResult::ServePartial);
805    }
806}