1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Application mode: Development vs Production.
//!
//! Controls framework behavior such as startup banner verbosity
//! and built-in request logging.
//!
//! 环境识别遵循 ASP.NET Core 约定,但**默认值偏向安全**:
//! 1. 环境变量 `APP_ENV`(不区分大小写)显式指定,取值 `Production`/`Prod` 或
//! `Development`/`Dev`;
//! 2. 未设置或值非法时默认 `Production`,并输出**一次**警告 —— 安全开关不能
//! 取决于“环境变量忘了写”;本机开发必须显式 `APP_ENV=Development`。
//!
//! 框架据此自动加载 `appsettings.{Environment}.json` overlay。
use std::sync::Once;
/// Controls how the framework behaves at startup and at runtime.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AppMode {
/// Full startup banner, per-request console logging, verbose diagnostics.
///
/// Opt-in only (`APP_ENV=Development`): local development must say so
/// explicitly.
Development,
/// Minimal one-line startup message, no framework-initiated console logging.
///
/// The default: a forgotten `APP_ENV` keeps the production guards on instead
/// of silently weakening them.
#[default]
Production,
}
impl AppMode {
/// 环境模式对应的 overlay 文件名,如 `appsettings.Development.json`。
pub fn overlay_filename(&self) -> &'static str {
match self {
AppMode::Development => "appsettings.Development.json",
AppMode::Production => "appsettings.Production.json",
}
}
/// 从环境变量 `APP_ENV` 解析运行模式;未设置或值非法时返回 `Production`。
///
/// 接受(不区分大小写):`Production` / `Prod` / `Development` / `Dev`。
/// 未设置或值非法时输出一次 `tracing::warn!`:安全默认值必须显式关掉,
/// 本机开发设置 `APP_ENV=Development` 即可。
pub fn from_env() -> Self {
Self::from_env_var("APP_ENV")
}
/// 从指定环境变量名解析运行模式。
pub fn from_env_var(var: &str) -> Self {
match std::env::var(var) {
Ok(raw) => match Self::parse(&raw) {
Some(mode) => mode,
None => {
warn_invalid_once(var, &raw);
AppMode::Production
}
},
Err(_) => {
warn_unset_once(var);
AppMode::Production
}
}
}
/// 解析环境变量取值;`Production`/`Prod`、`Development`/`Dev`(不区分大小写,
/// 允许首尾空白),其它取值返回 `None`。
fn parse(raw: &str) -> Option<AppMode> {
match raw.trim().to_lowercase().as_str() {
"production" | "prod" => Some(AppMode::Production),
"development" | "dev" => Some(AppMode::Development),
_ => None,
}
}
}
/// 未设置环境变量时的警告:每次进程只提示一次,避免每次 `from_env()` 刷屏。
fn warn_unset_once(var: &str) {
static WARNED: Once = Once::new();
WARNED.call_once(|| {
tracing::warn!(
"{var} is not set; defaulting to Production. \
Set APP_ENV=Development for local development."
);
});
}
/// 环境变量取值非法时的警告:每次进程只提示一次(合法值不受影响)。
fn warn_invalid_once(var: &str, raw: &str) {
static WARNED: Once = Once::new();
WARNED.call_once(|| {
tracing::warn!(
"Invalid {var}={raw:?}; expected Production/Prod or Development/Dev. \
Falling back to Production."
);
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_mode_is_production() {
// Safe default: a missing APP_ENV keeps the production guards armed.
assert_eq!(AppMode::default(), AppMode::Production);
}
#[test]
fn parse_accepts_known_values_case_insensitively() {
assert_eq!(AppMode::parse("Production"), Some(AppMode::Production));
assert_eq!(AppMode::parse("prod"), Some(AppMode::Production));
assert_eq!(AppMode::parse(" PROD "), Some(AppMode::Production));
assert_eq!(AppMode::parse("Development"), Some(AppMode::Development));
assert_eq!(AppMode::parse("DEV"), Some(AppMode::Development));
}
#[test]
fn parse_rejects_unknown_values() {
assert_eq!(AppMode::parse("staging"), None);
assert_eq!(AppMode::parse(""), None);
assert_eq!(AppMode::parse("1"), None);
}
#[test]
fn from_env_var_unset_falls_back_to_production() {
// A variable name that is never set: no `set_var`, so no cross-test
// environment pollution (the warning is emitted once per process).
let mode = AppMode::from_env_var("WEBX_TEST_APP_ENV_THAT_IS_NEVER_SET");
assert_eq!(mode, AppMode::Production);
}
#[test]
fn from_env_var_invalid_falls_back_to_production() {
std::env::set_var("WEBX_TEST_APP_ENV_INVALID", "staging");
let mode = AppMode::from_env_var("WEBX_TEST_APP_ENV_INVALID");
std::env::remove_var("WEBX_TEST_APP_ENV_INVALID");
assert_eq!(mode, AppMode::Production);
}
#[test]
fn from_env_var_valid_value_is_honoured() {
std::env::set_var("WEBX_TEST_APP_ENV_PRODUCTION", "prod");
let mode = AppMode::from_env_var("WEBX_TEST_APP_ENV_PRODUCTION");
std::env::remove_var("WEBX_TEST_APP_ENV_PRODUCTION");
assert_eq!(mode, AppMode::Production);
}
#[test]
fn from_env_var_explicit_development_opts_out() {
std::env::set_var("WEBX_TEST_APP_ENV_DEVELOPMENT", "dev");
let mode = AppMode::from_env_var("WEBX_TEST_APP_ENV_DEVELOPMENT");
std::env::remove_var("WEBX_TEST_APP_ENV_DEVELOPMENT");
assert_eq!(mode, AppMode::Development);
}
}