mirror of
https://github.com/openai/codex.git
synced 2026-10-19 08:16:48 +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.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct ProfileTui {
|
||||
/// Preferred layout for resume/fork session picker results.
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
//!
|
||||
//! Responsibilities:
|
||||
//!
|
||||
//! 1. Define strongly typed config contexts/actions with unknown-field
|
||||
//! rejection.
|
||||
//! 1. Define strongly typed config contexts/actions. Unknown fields are ignored
|
||||
//! during deserialization and reported by the shared strict-config policy.
|
||||
//! 2. Normalize accepted key aliases into canonical names.
|
||||
//! 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.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiGlobalKeymap {
|
||||
/// Prefix for persistent, context-aware shortcut menus.
|
||||
@@ -168,7 +167,6 @@ pub struct TuiGlobalKeymap {
|
||||
|
||||
/// Chat context keybindings.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiChatKeymap {
|
||||
/// Open saved prompts and slash commands.
|
||||
@@ -197,7 +195,6 @@ pub struct TuiChatKeymap {
|
||||
|
||||
/// Composer context keybindings. These override corresponding `global` actions.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiComposerKeymap {
|
||||
/// Submit the current composer draft.
|
||||
@@ -214,7 +211,6 @@ pub struct TuiComposerKeymap {
|
||||
|
||||
/// Editor context keybindings for text editing inside text areas.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiEditorKeymap {
|
||||
/// Insert a newline in the editor.
|
||||
@@ -388,7 +384,6 @@ pub struct TuiVimOperatorKeymap {
|
||||
|
||||
/// Search motions shared by Vim normal and operator-pending input.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiVimSearchKeymap {
|
||||
/// Search forward in the active buffer (`/`).
|
||||
@@ -403,7 +398,6 @@ pub struct TuiVimSearchKeymap {
|
||||
|
||||
/// Vim text-object keybindings for modal editing inside text areas.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiVimTextObjectKeymap {
|
||||
/// Text object: word.
|
||||
@@ -428,7 +422,6 @@ pub struct TuiVimTextObjectKeymap {
|
||||
|
||||
/// Pager context keybindings for transcript and static overlays.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiPagerKeymap {
|
||||
/// Scroll up by one row.
|
||||
@@ -457,7 +450,6 @@ pub struct TuiPagerKeymap {
|
||||
|
||||
/// List selection context keybindings for popup-style selectable lists.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiListKeymap {
|
||||
/// Move list selection up.
|
||||
@@ -484,7 +476,6 @@ pub struct TuiListKeymap {
|
||||
|
||||
/// Shortcuts specific to the shared agents overview.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiAgentsKeymap {
|
||||
/// Open the session resume picker.
|
||||
@@ -515,7 +506,6 @@ pub struct TuiAgentsKeymap {
|
||||
|
||||
/// Approval overlay keybindings.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiApprovalKeymap {
|
||||
/// Open the full-screen approval details view.
|
||||
@@ -547,7 +537,6 @@ pub struct TuiApprovalKeymap {
|
||||
/// `RuntimeKeymap` first so precedence, empty-list unbinding, and duplicate-key
|
||||
/// validation are applied consistently.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct TuiKeymap {
|
||||
#[serde(default)]
|
||||
@@ -767,75 +756,6 @@ mod tests {
|
||||
use super::*;
|
||||
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]
|
||||
fn action_under_global_context_is_accepted() {
|
||||
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"));
|
||||
}
|
||||
|
||||
#[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]
|
||||
async fn runtime_config_resolves_session_picker_view_default_and_override() {
|
||||
let cfg = Config::load_from_base_config_with_overrides(
|
||||
|
||||
Reference in New Issue
Block a user