mirror of
https://github.com/openai/codex.git
synced 2026-10-19 16:17:49 +00:00
Defer unknown TUI config fields to the shared strict-config policy (#54766)
## Why Unknown fields in TUI keymaps and profile TUI settings fail deserialization even when strict config validation is disabled. ## What changed Remove Serde's `deny_unknown_fields` from these settings so unknown fields are ignored during deserialization and reported by the shared strict-config policy. Keep `schemars(deny_unknown_fields)` so the generated schema continues to disallow unknown fields. GitOrigin-RevId: 06985161bc3bbb35648712288c1d00bdd50953ba
This commit is contained in:
@@ -75,7 +75,6 @@ pub struct ConfigProfile {
|
|||||||
|
|
||||||
/// TUI settings supported inside a named profile.
|
/// TUI settings supported inside a named profile.
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct ProfileTui {
|
pub struct ProfileTui {
|
||||||
/// Preferred layout for resume/fork session picker results.
|
/// Preferred layout for resume/fork session picker results.
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
//!
|
//!
|
||||||
//! Responsibilities:
|
//! Responsibilities:
|
||||||
//!
|
//!
|
||||||
//! 1. Define strongly typed config contexts/actions with unknown-field
|
//! 1. Define strongly typed config contexts/actions. Unknown fields are ignored
|
||||||
//! rejection.
|
//! during deserialization and reported by the shared strict-config policy.
|
||||||
//! 2. Normalize accepted key aliases into canonical names.
|
//! 2. Normalize accepted key aliases into canonical names.
|
||||||
//! 3. Reject malformed bindings early with user-facing diagnostics.
|
//! 3. Reject malformed bindings early with user-facing diagnostics.
|
||||||
//!
|
//!
|
||||||
@@ -131,7 +131,6 @@ impl KeybindingsSpec {
|
|||||||
|
|
||||||
/// Global keybindings. These are used when a context does not define an override.
|
/// Global keybindings. These are used when a context does not define an override.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct TuiGlobalKeymap {
|
pub struct TuiGlobalKeymap {
|
||||||
/// Prefix for persistent, context-aware shortcut menus.
|
/// Prefix for persistent, context-aware shortcut menus.
|
||||||
@@ -168,7 +167,6 @@ pub struct TuiGlobalKeymap {
|
|||||||
|
|
||||||
/// Chat context keybindings.
|
/// Chat context keybindings.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct TuiChatKeymap {
|
pub struct TuiChatKeymap {
|
||||||
/// Open saved prompts and slash commands.
|
/// Open saved prompts and slash commands.
|
||||||
@@ -197,7 +195,6 @@ pub struct TuiChatKeymap {
|
|||||||
|
|
||||||
/// Composer context keybindings. These override corresponding `global` actions.
|
/// Composer context keybindings. These override corresponding `global` actions.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct TuiComposerKeymap {
|
pub struct TuiComposerKeymap {
|
||||||
/// Submit the current composer draft.
|
/// Submit the current composer draft.
|
||||||
@@ -214,7 +211,6 @@ pub struct TuiComposerKeymap {
|
|||||||
|
|
||||||
/// Editor context keybindings for text editing inside text areas.
|
/// Editor context keybindings for text editing inside text areas.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct TuiEditorKeymap {
|
pub struct TuiEditorKeymap {
|
||||||
/// Insert a newline in the editor.
|
/// Insert a newline in the editor.
|
||||||
@@ -388,7 +384,6 @@ pub struct TuiVimOperatorKeymap {
|
|||||||
|
|
||||||
/// Search motions shared by Vim normal and operator-pending input.
|
/// Search motions shared by Vim normal and operator-pending input.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct TuiVimSearchKeymap {
|
pub struct TuiVimSearchKeymap {
|
||||||
/// Search forward in the active buffer (`/`).
|
/// Search forward in the active buffer (`/`).
|
||||||
@@ -403,7 +398,6 @@ pub struct TuiVimSearchKeymap {
|
|||||||
|
|
||||||
/// Vim text-object keybindings for modal editing inside text areas.
|
/// Vim text-object keybindings for modal editing inside text areas.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct TuiVimTextObjectKeymap {
|
pub struct TuiVimTextObjectKeymap {
|
||||||
/// Text object: word.
|
/// Text object: word.
|
||||||
@@ -428,7 +422,6 @@ pub struct TuiVimTextObjectKeymap {
|
|||||||
|
|
||||||
/// Pager context keybindings for transcript and static overlays.
|
/// Pager context keybindings for transcript and static overlays.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct TuiPagerKeymap {
|
pub struct TuiPagerKeymap {
|
||||||
/// Scroll up by one row.
|
/// Scroll up by one row.
|
||||||
@@ -457,7 +450,6 @@ pub struct TuiPagerKeymap {
|
|||||||
|
|
||||||
/// List selection context keybindings for popup-style selectable lists.
|
/// List selection context keybindings for popup-style selectable lists.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct TuiListKeymap {
|
pub struct TuiListKeymap {
|
||||||
/// Move list selection up.
|
/// Move list selection up.
|
||||||
@@ -484,7 +476,6 @@ pub struct TuiListKeymap {
|
|||||||
|
|
||||||
/// Shortcuts specific to the shared agents overview.
|
/// Shortcuts specific to the shared agents overview.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct TuiAgentsKeymap {
|
pub struct TuiAgentsKeymap {
|
||||||
/// Open the session resume picker.
|
/// Open the session resume picker.
|
||||||
@@ -515,7 +506,6 @@ pub struct TuiAgentsKeymap {
|
|||||||
|
|
||||||
/// Approval overlay keybindings.
|
/// Approval overlay keybindings.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct TuiApprovalKeymap {
|
pub struct TuiApprovalKeymap {
|
||||||
/// Open the full-screen approval details view.
|
/// Open the full-screen approval details view.
|
||||||
@@ -547,7 +537,6 @@ pub struct TuiApprovalKeymap {
|
|||||||
/// `RuntimeKeymap` first so precedence, empty-list unbinding, and duplicate-key
|
/// `RuntimeKeymap` first so precedence, empty-list unbinding, and duplicate-key
|
||||||
/// validation are applied consistently.
|
/// validation are applied consistently.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[schemars(deny_unknown_fields)]
|
#[schemars(deny_unknown_fields)]
|
||||||
pub struct TuiKeymap {
|
pub struct TuiKeymap {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -767,75 +756,6 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn misplaced_action_at_keymap_root_is_rejected() {
|
|
||||||
// Actions placed directly under [tui.keymap] instead of a context
|
|
||||||
// sub-table (e.g. [tui.keymap.global]) must produce a parse error,
|
|
||||||
// not be silently ignored.
|
|
||||||
let toml_input = r#"
|
|
||||||
open_transcript = "ctrl-s"
|
|
||||||
"#;
|
|
||||||
let result = toml::from_str::<TuiKeymap>(toml_input);
|
|
||||||
assert!(
|
|
||||||
result.is_err(),
|
|
||||||
"expected error for action at keymap root, got: {result:?}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn misspelled_action_under_context_is_rejected() {
|
|
||||||
let toml_input = r#"
|
|
||||||
[global]
|
|
||||||
open_transcrip = "ctrl-x"
|
|
||||||
"#;
|
|
||||||
let err = toml::from_str::<TuiKeymap>(toml_input)
|
|
||||||
.expect_err("expected unknown action under context");
|
|
||||||
assert!(
|
|
||||||
err.to_string().contains("open_transcrip"),
|
|
||||||
"expected error to mention misspelled field, got: {err}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn misspelled_vim_text_object_action_is_rejected() {
|
|
||||||
let toml_input = r#"
|
|
||||||
[vim_text_object]
|
|
||||||
double_quotes = "shift-quote"
|
|
||||||
"#;
|
|
||||||
let err = toml::from_str::<TuiKeymap>(toml_input)
|
|
||||||
.expect_err("expected unknown vim text object action");
|
|
||||||
assert!(
|
|
||||||
err.to_string().contains("double_quotes"),
|
|
||||||
"expected error to mention misspelled field, got: {err}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn removed_backtrack_actions_are_rejected() {
|
|
||||||
for (context, action) in [
|
|
||||||
("global", "edit_previous_message"),
|
|
||||||
("global", "confirm_edit_previous_message"),
|
|
||||||
("chat", "edit_previous_message"),
|
|
||||||
("chat", "confirm_edit_previous_message"),
|
|
||||||
("pager", "edit_previous_message"),
|
|
||||||
("pager", "edit_next_message"),
|
|
||||||
("pager", "confirm_edit_message"),
|
|
||||||
] {
|
|
||||||
let toml_input = format!(
|
|
||||||
r#"
|
|
||||||
[{context}]
|
|
||||||
{action} = "ctrl-x"
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
let err = toml::from_str::<TuiKeymap>(&toml_input)
|
|
||||||
.expect_err("expected removed backtrack action to be rejected");
|
|
||||||
assert!(
|
|
||||||
err.to_string().contains(action),
|
|
||||||
"expected error to mention removed field {action}, got: {err}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn action_under_global_context_is_accepted() {
|
fn action_under_global_context_is_accepted() {
|
||||||
let toml_input = r#"
|
let toml_input = r#"
|
||||||
|
|||||||
@@ -4738,21 +4738,6 @@ async fn legacy_remote_thread_store_endpoint_is_rejected() {
|
|||||||
assert!(err.to_string().contains("no longer supported"));
|
assert!(err.to_string().contains("no longer supported"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn profile_tui_rejects_unsupported_settings() {
|
|
||||||
let err = toml::from_str::<ConfigToml>(
|
|
||||||
r#"profile = "work"
|
|
||||||
|
|
||||||
[profiles.work.tui]
|
|
||||||
theme = "dark"
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.expect_err("profile TUI config should only accept supported fields");
|
|
||||||
|
|
||||||
assert!(err.to_string().contains("unknown field"));
|
|
||||||
assert!(err.to_string().contains("theme"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn runtime_config_resolves_session_picker_view_default_and_override() {
|
async fn runtime_config_resolves_session_picker_view_default_and_override() {
|
||||||
let cfg = Config::load_from_base_config_with_overrides(
|
let cfg = Config::load_from_base_config_with_overrides(
|
||||||
|
|||||||
Reference in New Issue
Block a user