1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use std::ops::Range;
4
5use bytes::Bytes;
6
7pub mod headers;
8
9use crate::headers::{
10 content_range::{Bound, HttpContentRange, Unsatisfiable},
11 range::HttpRange,
12};
13
14pub fn serve_file_with_http_range(
18 body: Bytes,
19 http_range: Option<HttpRange>,
20) -> Result<BodyRange<Bytes>, UnsatisfiableRange> {
21 let size = u64::try_from(body.len()).expect("we do not support 128bit usize");
22
23 let content_range = file_range(size, http_range)?;
24
25 let start = usize::try_from(content_range.range.start).expect("u64 doesn't fit usize");
26 let end = usize::try_from(content_range.range.end).expect("u64 doesn't fit usize");
27
28 Ok(BodyRange {
29 body: body.slice(start..end),
30 header: content_range.header,
31 })
32}
33
34pub fn file_range(
38 size: u64,
39 http_range: Option<HttpRange>,
40) -> Result<ContentRange, UnsatisfiableRange> {
41 let Some(http_range) = http_range else {
42 return Ok(ContentRange {
43 header: None,
44 range: 0..size,
45 });
46 };
47
48 let range = match http_range {
49 HttpRange::StartingPoint(start) if start < size => start..size,
50 HttpRange::Range(range) if range.start() < size => {
51 range.start()..range.end().saturating_add(1).min(size)
52 }
53 HttpRange::Suffix(suffix) if suffix > 0 && size > 0 => size.saturating_sub(suffix)..size,
54 _ => {
55 let content_range = HttpContentRange::Unsatisfiable(Unsatisfiable::new(size));
56 return Err(UnsatisfiableRange(content_range));
57 }
58 };
59
60 let content_range =
61 HttpContentRange::Bound(Bound::new(range.start..=range.end - 1, Some(size)).unwrap());
62
63 Ok(ContentRange {
64 header: Some(content_range),
65 range,
66 })
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct BodyRange<T> {
76 body: T,
77 header: Option<HttpContentRange>,
78}
79
80impl<T> BodyRange<T> {
81 pub fn body(&self) -> &T {
83 &self.body
84 }
85
86 pub fn into_body(self) -> T {
87 self.body
88 }
89
90 pub fn header(&self) -> Option<HttpContentRange> {
93 self.header
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct ContentRange {
102 header: Option<HttpContentRange>,
103 range: Range<u64>,
104}
105
106impl ContentRange {
107 pub fn header(&self) -> Option<HttpContentRange> {
110 self.header
111 }
112
113 pub fn range(&self) -> &Range<u64> {
115 &self.range
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct UnsatisfiableRange(HttpContentRange);
124
125impl UnsatisfiableRange {
126 pub fn header(&self) -> HttpContentRange {
128 self.0
129 }
130}
131
132#[cfg(feature = "axum")]
133mod axum {
134 use crate::{BodyRange, UnsatisfiableRange};
135
136 use axum_core::response::{IntoResponse, Response};
137 use bytes::Bytes;
138 use http::{HeaderValue, StatusCode, header::CONTENT_RANGE};
139
140 impl IntoResponse for BodyRange<Bytes> {
141 fn into_response(self) -> Response {
142 match self.header {
143 Some(range) => (
144 StatusCode::PARTIAL_CONTENT,
145 [(CONTENT_RANGE, HeaderValue::from(&range))],
146 self.body,
147 )
148 .into_response(),
149 None => (StatusCode::OK, self.body).into_response(),
150 }
151 }
152 }
153
154 impl IntoResponse for UnsatisfiableRange {
155 fn into_response(self) -> Response {
156 (
157 StatusCode::RANGE_NOT_SATISFIABLE,
158 [(CONTENT_RANGE, HeaderValue::from(&self.0))],
159 )
160 .into_response()
161 }
162 }
163}