Skip to main content

wechat_pub_rs/
config.rs

1//! Configuration management for the WeChat Official Account SDK.
2//!
3//! This module provides a centralized configuration system that enables:
4//! - Type-safe configuration management
5//! - Environment variable integration
6//! - YAML configuration file support
7//! - Builder pattern for easy setup
8//! - Configuration validation
9//!
10//! ## Usage
11//!
12//! ```rust
13//! use wechat_pub_rs::config::{Config, SecurityConfig, PerformanceConfig, HttpConfig};
14//! use wechat_pub_rs::Result;
15//!
16//! fn example() -> Result<()> {
17//!     // Create default configuration
18//!     let config = Config::default();
19//!
20//!     // Build custom configuration
21//!     let config = Config::builder()
22//!         .security(SecurityConfig::builder()
23//!             .max_upload_size(20 * 1024 * 1024) // 20MB
24//!             .build())
25//!         .http(HttpConfig::builder()
26//!             .request_timeout_secs(60)
27//!             .build())
28//!         .performance(PerformanceConfig::builder()
29//!             .max_concurrent_uploads(10)
30//!             .cache_ttl_minutes(30)
31//!             .build())
32//!         .build();
33//!
34//!     // Load from environment variables
35//!     let config = Config::from_env()?;
36//!     Ok(())
37//! }
38//! ```
39
40use crate::error::{Result, WeChatError};
41use serde::{Deserialize, Serialize};
42use std::time::Duration;
43
44/// Main configuration structure for the WeChat SDK.
45#[derive(Debug, Clone, Serialize, Deserialize, Default)]
46pub struct Config {
47    /// Security-related configuration
48    pub security: SecurityConfig,
49    /// Performance-related configuration
50    pub performance: PerformanceConfig,
51    /// HTTP client configuration
52    pub http: HttpConfig,
53    /// Cache configuration
54    pub cache: CacheConfig,
55    /// Retry configuration
56    pub retry: RetryConfig,
57}
58
59/// Security configuration settings.
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct SecurityConfig {
62    /// Maximum allowed file size for uploads in bytes (default: 10MB)
63    pub max_upload_size: u64,
64    /// Maximum allowed file size for downloads in bytes (default: 20MB)
65    pub max_download_size: u64,
66    /// Whether to validate file paths for security (default: true)
67    pub validate_file_paths: bool,
68    /// Whether to sanitize filenames (default: true)
69    pub sanitize_filenames: bool,
70    /// List of blocked file extensions for security
71    pub blocked_extensions: Vec<String>,
72}
73
74/// Performance configuration settings.
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct PerformanceConfig {
77    /// Maximum number of concurrent uploads (default: 5)
78    pub max_concurrent_uploads: usize,
79    /// Cache TTL in minutes (default: 15)
80    pub cache_ttl_minutes: u64,
81    /// Maximum cache size in entries (default: 1000)
82    pub max_cache_entries: usize,
83    /// Whether to enable parallel processing (default: true)
84    pub enable_parallel_processing: bool,
85}
86
87/// HTTP client configuration settings.
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct HttpConfig {
90    /// Request timeout in seconds (default: 30)
91    pub request_timeout_secs: u64,
92    /// Connection timeout in seconds (default: 10)
93    pub connect_timeout_secs: u64,
94    /// Base URL for WeChat API (default: "https://api.weixin.qq.com")
95    pub base_url: String,
96    /// User agent string for requests
97    pub user_agent: String,
98}
99
100/// Cache configuration settings.
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct CacheConfig {
103    /// Whether to enable material lookup caching (default: true)
104    pub enable_material_cache: bool,
105    /// Whether to enable token caching (default: true)
106    pub enable_token_cache: bool,
107    /// Cache cleanup interval in minutes (default: 60)
108    pub cleanup_interval_minutes: u64,
109}
110
111/// Retry configuration settings.
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct RetryConfig {
114    /// Maximum number of retry attempts (default: 3)
115    pub max_attempts: u32,
116    /// Base delay between retries in milliseconds (default: 500)
117    pub base_delay_ms: u64,
118    /// Maximum delay between retries in seconds (default: 30)
119    pub max_delay_secs: u64,
120    /// Exponential backoff factor (default: 2.0)
121    pub backoff_factor: f64,
122    /// Whether to add jitter to retry delays (default: true)
123    pub enable_jitter: bool,
124}
125
126impl Default for SecurityConfig {
127    fn default() -> Self {
128        Self {
129            max_upload_size: 10 * 1024 * 1024,   // 10MB
130            max_download_size: 20 * 1024 * 1024, // 20MB
131            validate_file_paths: true,
132            sanitize_filenames: true,
133            blocked_extensions: vec![
134                "exe".to_string(),
135                "bat".to_string(),
136                "cmd".to_string(),
137                "com".to_string(),
138                "pif".to_string(),
139                "scr".to_string(),
140                "vbs".to_string(),
141                "js".to_string(),
142                "jar".to_string(),
143                "sh".to_string(),
144                "php".to_string(),
145                "asp".to_string(),
146                "aspx".to_string(),
147                "jsp".to_string(),
148            ],
149        }
150    }
151}
152
153impl Default for PerformanceConfig {
154    fn default() -> Self {
155        Self {
156            max_concurrent_uploads: 5,
157            cache_ttl_minutes: 15,
158            max_cache_entries: 1000,
159            enable_parallel_processing: true,
160        }
161    }
162}
163
164impl Default for HttpConfig {
165    fn default() -> Self {
166        Self {
167            request_timeout_secs: 30,
168            connect_timeout_secs: 10,
169            base_url: "https://api.weixin.qq.com".to_string(),
170            user_agent: format!("wechat-pub-rs/{}", env!("CARGO_PKG_VERSION")),
171        }
172    }
173}
174
175impl Default for CacheConfig {
176    fn default() -> Self {
177        Self {
178            enable_material_cache: true,
179            enable_token_cache: true,
180            cleanup_interval_minutes: 60,
181        }
182    }
183}
184
185impl Default for RetryConfig {
186    fn default() -> Self {
187        Self {
188            max_attempts: 3,
189            base_delay_ms: 500,
190            max_delay_secs: 30,
191            backoff_factor: 2.0,
192            enable_jitter: true,
193        }
194    }
195}
196
197impl Config {
198    /// Creates a new configuration builder.
199    pub fn builder() -> ConfigBuilder {
200        ConfigBuilder::default()
201    }
202
203    /// Loads configuration from environment variables.
204    pub fn from_env() -> Result<Self> {
205        let mut config = Self::default();
206
207        // Security settings
208        if let Ok(val) = std::env::var("WECHAT_MAX_UPLOAD_SIZE") {
209            config.security.max_upload_size = val
210                .parse()
211                .map_err(|_| WeChatError::config_error("Invalid WECHAT_MAX_UPLOAD_SIZE value"))?;
212        }
213
214        if let Ok(val) = std::env::var("WECHAT_MAX_DOWNLOAD_SIZE") {
215            config.security.max_download_size = val
216                .parse()
217                .map_err(|_| WeChatError::config_error("Invalid WECHAT_MAX_DOWNLOAD_SIZE value"))?;
218        }
219
220        // Performance settings
221        if let Ok(val) = std::env::var("WECHAT_MAX_CONCURRENT_UPLOADS") {
222            config.performance.max_concurrent_uploads = val.parse().map_err(|_| {
223                WeChatError::config_error("Invalid WECHAT_MAX_CONCURRENT_UPLOADS value")
224            })?;
225        }
226
227        if let Ok(val) = std::env::var("WECHAT_CACHE_TTL_MINUTES") {
228            config.performance.cache_ttl_minutes = val
229                .parse()
230                .map_err(|_| WeChatError::config_error("Invalid WECHAT_CACHE_TTL_MINUTES value"))?;
231        }
232
233        // HTTP settings
234        if let Ok(val) = std::env::var("WECHAT_REQUEST_TIMEOUT") {
235            config.http.request_timeout_secs = val
236                .parse()
237                .map_err(|_| WeChatError::config_error("Invalid WECHAT_REQUEST_TIMEOUT value"))?;
238        }
239
240        if let Ok(val) = std::env::var("WECHAT_BASE_URL") {
241            config.http.base_url = val;
242        }
243
244        // Retry settings
245        if let Ok(val) = std::env::var("WECHAT_MAX_RETRIES") {
246            config.retry.max_attempts = val
247                .parse()
248                .map_err(|_| WeChatError::config_error("Invalid WECHAT_MAX_RETRIES value"))?;
249        }
250
251        config.validate()?;
252        Ok(config)
253    }
254
255    /// Validates the configuration for consistency and constraints.
256    pub fn validate(&self) -> Result<()> {
257        // Validate security settings
258        if self.security.max_upload_size == 0 {
259            return Err(WeChatError::config_error(
260                "max_upload_size must be greater than 0",
261            ));
262        }
263
264        if self.security.max_download_size == 0 {
265            return Err(WeChatError::config_error(
266                "max_download_size must be greater than 0",
267            ));
268        }
269
270        // Validate performance settings
271        if self.performance.max_concurrent_uploads == 0 {
272            return Err(WeChatError::config_error(
273                "max_concurrent_uploads must be greater than 0",
274            ));
275        }
276
277        if self.performance.max_concurrent_uploads > 20 {
278            return Err(WeChatError::config_error(
279                "max_concurrent_uploads should not exceed 20",
280            ));
281        }
282
283        // Validate HTTP settings
284        if self.http.request_timeout_secs == 0 {
285            return Err(WeChatError::config_error(
286                "request_timeout_secs must be greater than 0",
287            ));
288        }
289
290        if self.http.connect_timeout_secs == 0 {
291            return Err(WeChatError::config_error(
292                "connect_timeout_secs must be greater than 0",
293            ));
294        }
295
296        if self.http.base_url.is_empty() {
297            return Err(WeChatError::config_error("base_url cannot be empty"));
298        }
299
300        // Validate retry settings
301        if self.retry.max_attempts == 0 {
302            return Err(WeChatError::config_error(
303                "max_attempts must be greater than 0",
304            ));
305        }
306
307        if self.retry.backoff_factor < 1.0 {
308            return Err(WeChatError::config_error("backoff_factor must be >= 1.0"));
309        }
310
311        Ok(())
312    }
313
314    /// Converts retry config to Duration types for easier use.
315    pub fn retry_base_delay(&self) -> Duration {
316        Duration::from_millis(self.retry.base_delay_ms)
317    }
318
319    /// Converts retry config to Duration types for easier use.
320    pub fn retry_max_delay(&self) -> Duration {
321        Duration::from_secs(self.retry.max_delay_secs)
322    }
323
324    /// Converts HTTP timeout to Duration types for easier use.
325    pub fn request_timeout(&self) -> Duration {
326        Duration::from_secs(self.http.request_timeout_secs)
327    }
328
329    /// Converts HTTP timeout to Duration types for easier use.
330    pub fn connect_timeout(&self) -> Duration {
331        Duration::from_secs(self.http.connect_timeout_secs)
332    }
333
334    /// Converts cache TTL to Duration types for easier use.
335    pub fn cache_ttl(&self) -> Duration {
336        Duration::from_secs(self.performance.cache_ttl_minutes * 60)
337    }
338}
339
340/// Builder for creating Config instances.
341#[derive(Debug, Default)]
342pub struct ConfigBuilder {
343    security: Option<SecurityConfig>,
344    performance: Option<PerformanceConfig>,
345    http: Option<HttpConfig>,
346    cache: Option<CacheConfig>,
347    retry: Option<RetryConfig>,
348}
349
350impl ConfigBuilder {
351    /// Sets the security configuration.
352    pub fn security(mut self, security: SecurityConfig) -> Self {
353        self.security = Some(security);
354        self
355    }
356
357    /// Sets the performance configuration.
358    pub fn performance(mut self, performance: PerformanceConfig) -> Self {
359        self.performance = Some(performance);
360        self
361    }
362
363    /// Sets the HTTP configuration.
364    pub fn http(mut self, http: HttpConfig) -> Self {
365        self.http = Some(http);
366        self
367    }
368
369    /// Sets the cache configuration.
370    pub fn cache(mut self, cache: CacheConfig) -> Self {
371        self.cache = Some(cache);
372        self
373    }
374
375    /// Sets the retry configuration.
376    pub fn retry(mut self, retry: RetryConfig) -> Self {
377        self.retry = Some(retry);
378        self
379    }
380
381    /// Builds the configuration.
382    pub fn build(self) -> Config {
383        Config {
384            security: self.security.unwrap_or_default(),
385            performance: self.performance.unwrap_or_default(),
386            http: self.http.unwrap_or_default(),
387            cache: self.cache.unwrap_or_default(),
388            retry: self.retry.unwrap_or_default(),
389        }
390    }
391}
392
393// Builder implementations for individual config sections
394
395impl SecurityConfig {
396    /// Creates a new security config builder.
397    pub fn builder() -> SecurityConfigBuilder {
398        SecurityConfigBuilder::default()
399    }
400}
401
402impl PerformanceConfig {
403    /// Creates a new performance config builder.
404    pub fn builder() -> PerformanceConfigBuilder {
405        PerformanceConfigBuilder::default()
406    }
407}
408
409impl HttpConfig {
410    /// Creates a new HTTP config builder.
411    pub fn builder() -> HttpConfigBuilder {
412        HttpConfigBuilder::default()
413    }
414}
415
416impl CacheConfig {
417    /// Creates a new cache config builder.
418    pub fn builder() -> CacheConfigBuilder {
419        CacheConfigBuilder::default()
420    }
421}
422
423impl RetryConfig {
424    /// Creates a new retry config builder.
425    pub fn builder() -> RetryConfigBuilder {
426        RetryConfigBuilder::default()
427    }
428}
429
430/// Builder for SecurityConfig.
431#[derive(Debug, Default)]
432pub struct SecurityConfigBuilder {
433    max_upload_size: Option<u64>,
434    max_download_size: Option<u64>,
435    validate_file_paths: Option<bool>,
436    sanitize_filenames: Option<bool>,
437    blocked_extensions: Option<Vec<String>>,
438}
439
440impl SecurityConfigBuilder {
441    pub fn max_upload_size(mut self, size: u64) -> Self {
442        self.max_upload_size = Some(size);
443        self
444    }
445
446    pub fn max_download_size(mut self, size: u64) -> Self {
447        self.max_download_size = Some(size);
448        self
449    }
450
451    pub fn validate_file_paths(mut self, validate: bool) -> Self {
452        self.validate_file_paths = Some(validate);
453        self
454    }
455
456    pub fn sanitize_filenames(mut self, sanitize: bool) -> Self {
457        self.sanitize_filenames = Some(sanitize);
458        self
459    }
460
461    pub fn blocked_extensions(mut self, extensions: Vec<String>) -> Self {
462        self.blocked_extensions = Some(extensions);
463        self
464    }
465
466    pub fn build(self) -> SecurityConfig {
467        let default = SecurityConfig::default();
468        SecurityConfig {
469            max_upload_size: self.max_upload_size.unwrap_or(default.max_upload_size),
470            max_download_size: self.max_download_size.unwrap_or(default.max_download_size),
471            validate_file_paths: self
472                .validate_file_paths
473                .unwrap_or(default.validate_file_paths),
474            sanitize_filenames: self
475                .sanitize_filenames
476                .unwrap_or(default.sanitize_filenames),
477            blocked_extensions: self
478                .blocked_extensions
479                .unwrap_or(default.blocked_extensions),
480        }
481    }
482}
483
484/// Builder for PerformanceConfig.
485#[derive(Debug, Default)]
486pub struct PerformanceConfigBuilder {
487    max_concurrent_uploads: Option<usize>,
488    cache_ttl_minutes: Option<u64>,
489    max_cache_entries: Option<usize>,
490    enable_parallel_processing: Option<bool>,
491}
492
493impl PerformanceConfigBuilder {
494    pub fn max_concurrent_uploads(mut self, count: usize) -> Self {
495        self.max_concurrent_uploads = Some(count);
496        self
497    }
498
499    pub fn cache_ttl_minutes(mut self, minutes: u64) -> Self {
500        self.cache_ttl_minutes = Some(minutes);
501        self
502    }
503
504    pub fn max_cache_entries(mut self, entries: usize) -> Self {
505        self.max_cache_entries = Some(entries);
506        self
507    }
508
509    pub fn enable_parallel_processing(mut self, enable: bool) -> Self {
510        self.enable_parallel_processing = Some(enable);
511        self
512    }
513
514    pub fn build(self) -> PerformanceConfig {
515        let default = PerformanceConfig::default();
516        PerformanceConfig {
517            max_concurrent_uploads: self
518                .max_concurrent_uploads
519                .unwrap_or(default.max_concurrent_uploads),
520            cache_ttl_minutes: self.cache_ttl_minutes.unwrap_or(default.cache_ttl_minutes),
521            max_cache_entries: self.max_cache_entries.unwrap_or(default.max_cache_entries),
522            enable_parallel_processing: self
523                .enable_parallel_processing
524                .unwrap_or(default.enable_parallel_processing),
525        }
526    }
527}
528
529/// Builder for HttpConfig.
530#[derive(Debug, Default)]
531pub struct HttpConfigBuilder {
532    request_timeout_secs: Option<u64>,
533    connect_timeout_secs: Option<u64>,
534    base_url: Option<String>,
535    user_agent: Option<String>,
536}
537
538impl HttpConfigBuilder {
539    pub fn request_timeout_secs(mut self, timeout: u64) -> Self {
540        self.request_timeout_secs = Some(timeout);
541        self
542    }
543
544    pub fn connect_timeout_secs(mut self, timeout: u64) -> Self {
545        self.connect_timeout_secs = Some(timeout);
546        self
547    }
548
549    pub fn base_url(mut self, url: String) -> Self {
550        self.base_url = Some(url);
551        self
552    }
553
554    pub fn user_agent(mut self, agent: String) -> Self {
555        self.user_agent = Some(agent);
556        self
557    }
558
559    pub fn build(self) -> HttpConfig {
560        let default = HttpConfig::default();
561        HttpConfig {
562            request_timeout_secs: self
563                .request_timeout_secs
564                .unwrap_or(default.request_timeout_secs),
565            connect_timeout_secs: self
566                .connect_timeout_secs
567                .unwrap_or(default.connect_timeout_secs),
568            base_url: self.base_url.unwrap_or(default.base_url),
569            user_agent: self.user_agent.unwrap_or(default.user_agent),
570        }
571    }
572}
573
574/// Builder for CacheConfig.
575#[derive(Debug, Default)]
576pub struct CacheConfigBuilder {
577    enable_material_cache: Option<bool>,
578    enable_token_cache: Option<bool>,
579    cleanup_interval_minutes: Option<u64>,
580}
581
582impl CacheConfigBuilder {
583    pub fn enable_material_cache(mut self, enable: bool) -> Self {
584        self.enable_material_cache = Some(enable);
585        self
586    }
587
588    pub fn enable_token_cache(mut self, enable: bool) -> Self {
589        self.enable_token_cache = Some(enable);
590        self
591    }
592
593    pub fn cleanup_interval_minutes(mut self, minutes: u64) -> Self {
594        self.cleanup_interval_minutes = Some(minutes);
595        self
596    }
597
598    pub fn build(self) -> CacheConfig {
599        let default = CacheConfig::default();
600        CacheConfig {
601            enable_material_cache: self
602                .enable_material_cache
603                .unwrap_or(default.enable_material_cache),
604            enable_token_cache: self
605                .enable_token_cache
606                .unwrap_or(default.enable_token_cache),
607            cleanup_interval_minutes: self
608                .cleanup_interval_minutes
609                .unwrap_or(default.cleanup_interval_minutes),
610        }
611    }
612}
613
614/// Builder for RetryConfig.
615#[derive(Debug, Default)]
616pub struct RetryConfigBuilder {
617    max_attempts: Option<u32>,
618    base_delay_ms: Option<u64>,
619    max_delay_secs: Option<u64>,
620    backoff_factor: Option<f64>,
621    enable_jitter: Option<bool>,
622}
623
624impl RetryConfigBuilder {
625    pub fn max_attempts(mut self, attempts: u32) -> Self {
626        self.max_attempts = Some(attempts);
627        self
628    }
629
630    pub fn base_delay_ms(mut self, delay: u64) -> Self {
631        self.base_delay_ms = Some(delay);
632        self
633    }
634
635    pub fn max_delay_secs(mut self, delay: u64) -> Self {
636        self.max_delay_secs = Some(delay);
637        self
638    }
639
640    pub fn backoff_factor(mut self, factor: f64) -> Self {
641        self.backoff_factor = Some(factor);
642        self
643    }
644
645    pub fn enable_jitter(mut self, enable: bool) -> Self {
646        self.enable_jitter = Some(enable);
647        self
648    }
649
650    pub fn build(self) -> RetryConfig {
651        let default = RetryConfig::default();
652        RetryConfig {
653            max_attempts: self.max_attempts.unwrap_or(default.max_attempts),
654            base_delay_ms: self.base_delay_ms.unwrap_or(default.base_delay_ms),
655            max_delay_secs: self.max_delay_secs.unwrap_or(default.max_delay_secs),
656            backoff_factor: self.backoff_factor.unwrap_or(default.backoff_factor),
657            enable_jitter: self.enable_jitter.unwrap_or(default.enable_jitter),
658        }
659    }
660}
661
662#[cfg(test)]
663mod tests {
664    use super::*;
665
666    #[test]
667    fn test_default_config() {
668        let config = Config::default();
669        assert!(config.validate().is_ok());
670
671        // Test default values
672        assert_eq!(config.security.max_upload_size, 10 * 1024 * 1024);
673        assert_eq!(config.performance.max_concurrent_uploads, 5);
674        assert_eq!(config.http.request_timeout_secs, 30);
675        assert_eq!(config.retry.max_attempts, 3);
676    }
677
678    #[test]
679    fn test_config_builder() {
680        let config = Config::builder()
681            .security(
682                SecurityConfig::builder()
683                    .max_upload_size(5 * 1024 * 1024)
684                    .validate_file_paths(false)
685                    .build(),
686            )
687            .performance(
688                PerformanceConfig::builder()
689                    .max_concurrent_uploads(10)
690                    .cache_ttl_minutes(30)
691                    .build(),
692            )
693            .build();
694
695        assert_eq!(config.security.max_upload_size, 5 * 1024 * 1024);
696        assert!(!config.security.validate_file_paths);
697        assert_eq!(config.performance.max_concurrent_uploads, 10);
698        assert_eq!(config.performance.cache_ttl_minutes, 30);
699    }
700
701    #[test]
702    fn test_config_validation() {
703        let mut config = Config::default();
704        config.security.max_upload_size = 0;
705        assert!(config.validate().is_err());
706
707        let mut config = Config::default();
708        config.performance.max_concurrent_uploads = 0;
709        assert!(config.validate().is_err());
710
711        let mut config = Config::default();
712        config.performance.max_concurrent_uploads = 25;
713        assert!(config.validate().is_err());
714
715        let mut config = Config::default();
716        config.retry.backoff_factor = 0.5;
717        assert!(config.validate().is_err());
718    }
719
720    #[test]
721    fn test_duration_conversions() {
722        let config = Config::default();
723
724        assert_eq!(config.retry_base_delay(), Duration::from_millis(500));
725        assert_eq!(config.retry_max_delay(), Duration::from_secs(30));
726        assert_eq!(config.request_timeout(), Duration::from_secs(30));
727        assert_eq!(config.connect_timeout(), Duration::from_secs(10));
728        assert_eq!(config.cache_ttl(), Duration::from_secs(15 * 60));
729    }
730
731    #[test]
732    fn test_environment_loading() {
733        // Set some environment variables
734        unsafe {
735            std::env::set_var("WECHAT_MAX_UPLOAD_SIZE", "5242880"); // 5MB
736            std::env::set_var("WECHAT_MAX_CONCURRENT_UPLOADS", "10");
737            std::env::set_var("WECHAT_REQUEST_TIMEOUT", "60");
738        }
739
740        let config = Config::from_env().unwrap();
741
742        assert_eq!(config.security.max_upload_size, 5242880);
743        assert_eq!(config.performance.max_concurrent_uploads, 10);
744        assert_eq!(config.http.request_timeout_secs, 60);
745
746        // Clean up
747        unsafe {
748            std::env::remove_var("WECHAT_MAX_UPLOAD_SIZE");
749            std::env::remove_var("WECHAT_MAX_CONCURRENT_UPLOADS");
750            std::env::remove_var("WECHAT_REQUEST_TIMEOUT");
751        }
752    }
753
754    #[test]
755    fn test_invalid_environment_values() {
756        unsafe {
757            std::env::set_var("WECHAT_MAX_UPLOAD_SIZE", "invalid");
758        }
759        assert!(Config::from_env().is_err());
760        unsafe {
761            std::env::remove_var("WECHAT_MAX_UPLOAD_SIZE");
762        }
763    }
764}