rusty_cat/binary/
binary_download_config.rs1use std::time::Duration;
2
3use crate::error::{InnerErrorCode, MeowError};
4
5pub const DEFAULT_BINARY_MAX_BODY_BYTES: u64 = 5 * 1024 * 1024;
7pub const BINARY_ABSOLUTE_MAX_BODY_BYTES: u64 = 64 * 1024 * 1024;
9pub(crate) const BINARY_MAX_RETRY_DELAYS: usize = 8;
10pub(crate) const BINARY_MAX_REDIRECTS: usize = 10;
11
12#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct BinaryDownloadConfig {
18 max_body_bytes: u64,
19 request_timeout: Option<Duration>,
20 tcp_keepalive: Option<Duration>,
21 redirect_limit: usize,
22 retry_delays: Vec<Duration>,
23}
24
25#[derive(Clone, Debug)]
27pub struct BinaryDownloadConfigBuilder {
28 config: BinaryDownloadConfig,
29}
30
31impl Default for BinaryDownloadConfig {
32 fn default() -> Self {
33 Self {
34 max_body_bytes: DEFAULT_BINARY_MAX_BODY_BYTES,
35 request_timeout: None,
36 tcp_keepalive: None,
37 redirect_limit: 5,
38 retry_delays: vec![Duration::from_millis(300), Duration::from_millis(800)],
39 }
40 }
41}
42
43impl BinaryDownloadConfig {
44 pub fn builder() -> BinaryDownloadConfigBuilder {
46 BinaryDownloadConfigBuilder {
47 config: Self::default(),
48 }
49 }
50
51 pub fn max_body_bytes(&self) -> u64 {
53 self.max_body_bytes
54 }
55
56 pub fn request_timeout(&self) -> Option<Duration> {
58 self.request_timeout
59 }
60
61 pub fn tcp_keepalive(&self) -> Option<Duration> {
63 self.tcp_keepalive
64 }
65
66 pub fn redirect_limit(&self) -> usize {
68 self.redirect_limit
69 }
70
71 pub fn retry_delays(&self) -> &[Duration] {
73 &self.retry_delays
74 }
75
76 pub(crate) fn validate(&self) -> Result<(), MeowError> {
77 if !(1..=BINARY_ABSOLUTE_MAX_BODY_BYTES).contains(&self.max_body_bytes) {
78 return Err(parameter_error(format!(
79 "binary max_body_bytes must be in 1..={BINARY_ABSOLUTE_MAX_BODY_BYTES}"
80 )));
81 }
82 if self.request_timeout.is_some_and(|value| value.is_zero()) {
83 return Err(parameter_error(
84 "binary request_timeout must be greater than zero",
85 ));
86 }
87 if self.tcp_keepalive.is_some_and(|value| value.is_zero()) {
88 return Err(parameter_error(
89 "binary tcp_keepalive must be greater than zero",
90 ));
91 }
92 if self.redirect_limit > BINARY_MAX_REDIRECTS {
93 return Err(parameter_error(format!(
94 "binary redirect_limit must be <= {BINARY_MAX_REDIRECTS}"
95 )));
96 }
97 if self.retry_delays.len() > BINARY_MAX_RETRY_DELAYS {
98 return Err(parameter_error(format!(
99 "binary retry_delays must contain at most {BINARY_MAX_RETRY_DELAYS} entries"
100 )));
101 }
102 if self.retry_delays.iter().any(Duration::is_zero) {
103 return Err(parameter_error(
104 "binary retry delays must be greater than zero",
105 ));
106 }
107 Ok(())
108 }
109}
110
111impl BinaryDownloadConfigBuilder {
112 pub fn max_body_bytes(mut self, value: u64) -> Self {
114 self.config.max_body_bytes = value;
115 self
116 }
117
118 pub fn request_timeout(mut self, value: Duration) -> Self {
120 self.config.request_timeout = Some(value);
121 self
122 }
123
124 pub fn tcp_keepalive(mut self, value: Duration) -> Self {
126 self.config.tcp_keepalive = Some(value);
127 self
128 }
129
130 pub fn redirect_limit(mut self, value: usize) -> Self {
132 self.config.redirect_limit = value;
133 self
134 }
135
136 pub fn retry_delays(mut self, value: Vec<Duration>) -> Self {
138 self.config.retry_delays = value;
139 self
140 }
141
142 pub fn build(self) -> Result<BinaryDownloadConfig, MeowError> {
144 self.config.validate()?;
145 Ok(self.config)
146 }
147}
148
149fn parameter_error(message: impl Into<String>) -> MeowError {
150 MeowError::from_code(InnerErrorCode::ParameterEmpty, message.into())
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn defaults_are_bounded() {
159 let config = BinaryDownloadConfig::default();
160 assert_eq!(config.max_body_bytes(), 5 * 1024 * 1024);
161 assert_eq!(config.redirect_limit(), 5);
162 assert_eq!(config.retry_delays().len(), 2);
163 config.validate().expect("defaults must be valid");
164 }
165
166 #[test]
167 fn invalid_memory_and_timing_values_are_rejected() {
168 for max in [0, BINARY_ABSOLUTE_MAX_BODY_BYTES + 1] {
169 let error = BinaryDownloadConfig::builder()
170 .max_body_bytes(max)
171 .build()
172 .expect_err("invalid maximum must fail");
173 assert_eq!(error.code(), InnerErrorCode::ParameterEmpty as i32);
174 }
175 assert!(BinaryDownloadConfig::builder()
176 .request_timeout(Duration::ZERO)
177 .build()
178 .is_err());
179 assert!(BinaryDownloadConfig::builder()
180 .tcp_keepalive(Duration::ZERO)
181 .build()
182 .is_err());
183 assert!(BinaryDownloadConfig::builder()
184 .retry_delays(vec![Duration::ZERO])
185 .build()
186 .is_err());
187 }
188}