Use spec loop for config enum warnings

This commit is contained in:
Ahmed Ibrahim
2026-05-07 02:30:57 +03:00
parent bd84525f3c
commit bf4737b520

View File

@@ -31,19 +31,226 @@ use codex_protocol::protocol::AskForApproval;
use serde::de::DeserializeOwned;
use toml::Value as TomlValue;
const SHELL_ENVIRONMENT_POLICY: &str = "shell_environment_policy";
const CONTEXT_SIZE: &str = "context_size";
const DEFAULT_TOOLS_APPROVAL_MODE: &str = "default_tools_approval_mode";
const APPROVAL_MODE: &str = "approval_mode";
use self::PathSegment::AnyTable;
use self::PathSegment::Key;
macro_rules! check_fields {
($value:expr, $path:expr, $warnings:expr, $( $key:expr => $ty:ty ),+ $(,)?) => {
$(
check_value::<$ty>($value, &child_path($path, $key), $warnings);
)+
};
type FieldParser = fn(&TomlValue) -> bool;
type EnumFieldSpec = (&'static [PathSegment], FieldParser);
/// One segment in a known enum-valued config path.
enum PathSegment {
/// Match this literal table key.
Key(&'static str),
/// Expand across every table child at this point, such as `profiles.*`.
AnyTable,
}
static ENUM_FIELD_SPECS: &[EnumFieldSpec] = &[
(&[Key("approval_policy")], parses_as::<AskForApproval>),
(&[Key("approvals_reviewer")], parses_as::<ApprovalsReviewer>),
(&[Key("sandbox_mode")], parses_as::<SandboxMode>),
(
&[Key("forced_login_method")],
parses_as::<ForcedLoginMethod>,
),
(
&[Key("cli_auth_credentials_store")],
parses_as::<AuthCredentialsStoreMode>,
),
(
&[Key("mcp_oauth_credentials_store")],
parses_as::<OAuthCredentialsStoreMode>,
),
(&[Key("file_opener")], parses_as::<UriBasedFileOpener>),
(
&[Key("model_reasoning_effort")],
parses_as::<ReasoningEffort>,
),
(
&[Key("plan_mode_reasoning_effort")],
parses_as::<ReasoningEffort>,
),
(
&[Key("model_reasoning_summary")],
parses_as::<ReasoningSummary>,
),
(&[Key("model_verbosity")], parses_as::<Verbosity>),
(&[Key("personality")], parses_as::<Personality>),
(&[Key("service_tier")], parses_as::<ServiceTier>),
(
&[Key("experimental_thread_store")],
parses_as::<ThreadStoreToml>,
),
(&[Key("web_search")], parses_as::<WebSearchMode>),
(
&[Key("shell_environment_policy"), Key("inherit")],
parses_as::<ShellEnvironmentPolicyInherit>,
),
(
&[Key("history"), Key("persistence")],
parses_as::<HistoryPersistence>,
),
(
&[Key("tui"), Key("notifications")],
parses_as::<Notifications>,
),
(
&[Key("tui"), Key("notification_method")],
parses_as::<NotificationMethod>,
),
(
&[Key("tui"), Key("notification_condition")],
parses_as::<NotificationCondition>,
),
(
&[Key("tui"), Key("alternate_screen")],
parses_as::<AltScreenMode>,
),
(
&[Key("tui"), Key("session_picker_view")],
parses_as::<SessionPickerViewMode>,
),
(
&[Key("realtime"), Key("version")],
parses_as::<RealtimeWsVersion>,
),
(&[Key("realtime"), Key("type")], parses_as::<RealtimeWsMode>),
(
&[Key("realtime"), Key("transport")],
parses_as::<RealtimeTransport>,
),
(&[Key("realtime"), Key("voice")], parses_as::<RealtimeVoice>),
(
&[Key("tools"), Key("web_search"), Key("context_size")],
parses_as::<WebSearchContextSize>,
),
(
&[Key("windows"), Key("sandbox")],
parses_as::<WindowsSandboxModeToml>,
),
(
&[Key("profiles"), AnyTable, Key("service_tier")],
parses_as::<ServiceTier>,
),
(
&[Key("profiles"), AnyTable, Key("approval_policy")],
parses_as::<AskForApproval>,
),
(
&[Key("profiles"), AnyTable, Key("approvals_reviewer")],
parses_as::<ApprovalsReviewer>,
),
(
&[Key("profiles"), AnyTable, Key("sandbox_mode")],
parses_as::<SandboxMode>,
),
(
&[Key("profiles"), AnyTable, Key("model_reasoning_effort")],
parses_as::<ReasoningEffort>,
),
(
&[Key("profiles"), AnyTable, Key("plan_mode_reasoning_effort")],
parses_as::<ReasoningEffort>,
),
(
&[Key("profiles"), AnyTable, Key("model_reasoning_summary")],
parses_as::<ReasoningSummary>,
),
(
&[Key("profiles"), AnyTable, Key("model_verbosity")],
parses_as::<Verbosity>,
),
(
&[Key("profiles"), AnyTable, Key("personality")],
parses_as::<Personality>,
),
(
&[Key("profiles"), AnyTable, Key("web_search")],
parses_as::<WebSearchMode>,
),
(
&[
Key("profiles"),
AnyTable,
Key("tools"),
Key("web_search"),
Key("context_size"),
],
parses_as::<WebSearchContextSize>,
),
(
&[Key("profiles"), AnyTable, Key("windows"), Key("sandbox")],
parses_as::<WindowsSandboxModeToml>,
),
(
&[
Key("profiles"),
AnyTable,
Key("tui"),
Key("session_picker_view"),
],
parses_as::<SessionPickerViewMode>,
),
(
&[
Key("mcp_servers"),
AnyTable,
Key("default_tools_approval_mode"),
],
parses_as::<AppToolApproval>,
),
(
&[
Key("mcp_servers"),
AnyTable,
Key("tools"),
AnyTable,
Key("approval_mode"),
],
parses_as::<AppToolApproval>,
),
(
&[Key("apps"), AnyTable, Key("default_tools_approval_mode")],
parses_as::<AppToolApproval>,
),
(
&[
Key("apps"),
AnyTable,
Key("tools"),
AnyTable,
Key("approval_mode"),
],
parses_as::<AppToolApproval>,
),
(
&[
Key("plugins"),
AnyTable,
Key("mcp_servers"),
AnyTable,
Key("default_tools_approval_mode"),
],
parses_as::<AppToolApproval>,
),
(
&[
Key("plugins"),
AnyTable,
Key("mcp_servers"),
AnyTable,
Key("tools"),
AnyTable,
Key("approval_mode"),
],
parses_as::<AppToolApproval>,
),
(
&[Key("marketplaces"), AnyTable, Key("source_type")],
parses_as::<MarketplaceSourceType>,
),
];
/// Deserializes config while turning known invalid enum-valued settings into warnings.
///
/// The config structs use `serde_with::DefaultOnError` on their enum fields so
@@ -55,211 +262,34 @@ pub(crate) fn deserialize_with_enum_warnings(
value: TomlValue,
) -> Result<(TomlValue, ConfigToml, Vec<String>), toml::de::Error> {
let mut sanitized = value;
let warnings = remove_invalid_enum_values(&mut sanitized);
let mut warnings = Vec::new();
for (spec_path, parser) in ENUM_FIELD_SPECS {
for path in matching_paths(&sanitized, spec_path) {
remove_if_invalid(&mut sanitized, &path, *parser, &mut warnings);
}
}
let parsed = sanitized.clone().try_into::<ConfigToml>()?;
Ok((sanitized, parsed, warnings))
}
fn remove_invalid_enum_values(value: &mut TomlValue) -> Vec<String> {
let mut warnings = Vec::new();
let path = Vec::new();
scan_config_toml(value, &path, &mut warnings);
warnings
}
fn scan_config_toml(value: &mut TomlValue, path: &[String], warnings: &mut Vec<String>) {
check_fields!(
value,
path,
warnings,
"approval_policy" => AskForApproval,
"approvals_reviewer" => ApprovalsReviewer,
"sandbox_mode" => SandboxMode,
"forced_login_method" => ForcedLoginMethod,
"cli_auth_credentials_store" => AuthCredentialsStoreMode,
"mcp_oauth_credentials_store" => OAuthCredentialsStoreMode,
"file_opener" => UriBasedFileOpener,
"model_reasoning_effort" => ReasoningEffort,
"plan_mode_reasoning_effort" => ReasoningEffort,
"model_reasoning_summary" => ReasoningSummary,
"model_verbosity" => Verbosity,
"personality" => Personality,
"service_tier" => ServiceTier,
"experimental_thread_store" => ThreadStoreToml,
"web_search" => WebSearchMode,
);
scan_shell_environment_policy(value, &child_path(path, SHELL_ENVIRONMENT_POLICY), warnings);
scan_history(value, &child_path(path, "history"), warnings);
scan_tui(value, &child_path(path, "tui"), warnings);
scan_realtime(value, &child_path(path, "realtime"), warnings);
scan_tools(value, &child_path(path, "tools"), warnings);
scan_windows(value, &child_path(path, "windows"), warnings);
scan_profiles(value, &child_path(path, "profiles"), warnings);
scan_mcp_servers(value, &child_path(path, "mcp_servers"), warnings);
scan_apps(value, &child_path(path, "apps"), warnings);
scan_plugins(value, &child_path(path, "plugins"), warnings);
scan_marketplaces(value, &child_path(path, "marketplaces"), warnings);
}
fn scan_shell_environment_policy(
value: &mut TomlValue,
path: &[String],
warnings: &mut Vec<String>,
) {
check_fields!(
value,
path,
warnings,
"inherit" => ShellEnvironmentPolicyInherit,
);
}
fn scan_history(value: &mut TomlValue, path: &[String], warnings: &mut Vec<String>) {
check_fields!(
value,
path,
warnings,
"persistence" => HistoryPersistence,
);
}
fn scan_tui(value: &mut TomlValue, path: &[String], warnings: &mut Vec<String>) {
check_fields!(
value,
path,
warnings,
"notifications" => Notifications,
"notification_method" => NotificationMethod,
"notification_condition" => NotificationCondition,
"alternate_screen" => AltScreenMode,
"session_picker_view" => SessionPickerViewMode,
);
}
fn scan_realtime(value: &mut TomlValue, path: &[String], warnings: &mut Vec<String>) {
check_fields!(
value,
path,
warnings,
"version" => RealtimeWsVersion,
"type" => RealtimeWsMode,
"transport" => RealtimeTransport,
"voice" => RealtimeVoice,
);
}
fn scan_tools(value: &mut TomlValue, path: &[String], warnings: &mut Vec<String>) {
check_fields!(
value,
&child_path(path, "web_search"),
warnings,
CONTEXT_SIZE => WebSearchContextSize,
);
}
fn scan_windows(value: &mut TomlValue, path: &[String], warnings: &mut Vec<String>) {
check_fields!(
value,
path,
warnings,
"sandbox" => WindowsSandboxModeToml,
);
}
fn scan_profiles(value: &mut TomlValue, path: &[String], warnings: &mut Vec<String>) {
for profile_path in child_table_paths(value, path) {
scan_profile(value, &profile_path, warnings);
}
}
fn scan_profile(value: &mut TomlValue, path: &[String], warnings: &mut Vec<String>) {
check_fields!(
value,
path,
warnings,
"service_tier" => ServiceTier,
"approval_policy" => AskForApproval,
"approvals_reviewer" => ApprovalsReviewer,
"sandbox_mode" => SandboxMode,
"model_reasoning_effort" => ReasoningEffort,
"plan_mode_reasoning_effort" => ReasoningEffort,
"model_reasoning_summary" => ReasoningSummary,
"model_verbosity" => Verbosity,
"personality" => Personality,
"web_search" => WebSearchMode,
);
scan_tools(value, &child_path(path, "tools"), warnings);
scan_windows(value, &child_path(path, "windows"), warnings);
check_fields!(
value,
&child_path(path, "tui"),
warnings,
"session_picker_view" => SessionPickerViewMode,
);
}
fn scan_mcp_servers(value: &mut TomlValue, path: &[String], warnings: &mut Vec<String>) {
for server_path in child_table_paths(value, path) {
scan_tool_approval_overrides(value, &server_path, warnings);
}
}
fn scan_apps(value: &mut TomlValue, path: &[String], warnings: &mut Vec<String>) {
for app_path in child_table_paths(value, path) {
scan_tool_approval_overrides(value, &app_path, warnings);
}
}
fn scan_plugins(value: &mut TomlValue, path: &[String], warnings: &mut Vec<String>) {
for plugin_path in child_table_paths(value, path) {
for server_path in child_table_paths(value, &child_path(&plugin_path, "mcp_servers")) {
scan_tool_approval_overrides(value, &server_path, warnings);
}
}
}
fn scan_tool_approval_overrides(
value: &mut TomlValue,
path: &[String],
warnings: &mut Vec<String>,
) {
check_fields!(
value,
path,
warnings,
DEFAULT_TOOLS_APPROVAL_MODE => AppToolApproval,
);
for tool_path in child_table_paths(value, &child_path(path, "tools")) {
check_fields!(
value,
&tool_path,
warnings,
APPROVAL_MODE => AppToolApproval,
);
}
}
fn scan_marketplaces(value: &mut TomlValue, path: &[String], warnings: &mut Vec<String>) {
for marketplace_path in child_table_paths(value, path) {
check_fields!(
value,
&marketplace_path,
warnings,
"source_type" => MarketplaceSourceType,
);
}
}
/// Checks one known enum leaf by parsing the raw TOML value as the real field type.
fn check_value<T>(value: &mut TomlValue, path: &[String], warnings: &mut Vec<String>)
fn parses_as<T>(value: &TomlValue) -> bool
where
T: DeserializeOwned,
{
value.clone().try_into::<T>().is_ok()
}
/// Removes one known enum leaf when its raw TOML value does not parse as its real type.
fn remove_if_invalid(
value: &mut TomlValue,
path: &[String],
parser: FieldParser,
warnings: &mut Vec<String>,
) {
let Some(raw_value) = value_at_path(value, path) else {
return;
};
if raw_value.clone().try_into::<T>().is_ok() {
if parser(raw_value) {
return;
}
let Some(invalid_value) = remove_value_at_path(value, path) else {
@@ -271,15 +301,37 @@ where
));
}
/// Returns table child paths in TOML map order without holding a borrow.
fn child_table_paths(value: &TomlValue, path: &[String]) -> Vec<Vec<String>> {
let Some(table) = value_at_path(value, path).and_then(TomlValue::as_table) else {
return Vec::new();
};
table
.iter()
.filter_map(|(key, value)| value.is_table().then(|| child_path(path, key)))
.collect()
/// Expands a spec path into concrete TOML paths without holding borrows across mutation.
fn matching_paths(value: &TomlValue, spec_path: &[PathSegment]) -> Vec<Vec<String>> {
let mut paths = vec![Vec::new()];
for segment in spec_path {
match segment {
Key(key) => {
for path in &mut paths {
path.push((*key).to_string());
}
}
AnyTable => {
let mut expanded = Vec::new();
for path in paths {
let Some(table) = value_at_path(value, &path).and_then(TomlValue::as_table)
else {
continue;
};
expanded.extend(table.iter().filter_map(|(key, value)| {
if !value.is_table() {
return None;
}
let mut child = path.clone();
child.push(key.clone());
Some(child)
}));
}
paths = expanded;
}
}
}
paths
}
fn value_at_path<'a>(value: &'a TomlValue, path: &[String]) -> Option<&'a TomlValue> {
@@ -299,12 +351,6 @@ fn remove_value_at_path(value: &mut TomlValue, path: &[String]) -> Option<TomlVa
current.as_table_mut()?.remove(last)
}
fn child_path(path: &[String], key: &str) -> Vec<String> {
let mut child = path.to_vec();
child.push(key.to_string());
child
}
fn display_path(path: &[String]) -> String {
path.iter()
.map(|key| display_key(key))