{"record":{"id":"78b10411f0161f29","repo":"zellij-org/zellij","slug":"empty-character-string","errorCode":null,"errorMessage":"Empty character string","messagePattern":"Empty character string","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"zellij-utils/src/ipc/protobuf_conversion.rs","lineNumber":3165,"sourceCode":"{\n    type Error = anyhow::Error;\n    fn try_from(\n        key: crate::client_server_contract::client_server_contract::KeyWithModifier,\n    ) -> Result<Self> {\n        use crate::ipc::enum_conversions::{bare_key_from_proto_i32, key_modifier_from_proto_i32};\n        use std::collections::BTreeSet;\n\n        // Handle character keys specially\n        let bare_key = if key.bare_key\n            == crate::client_server_contract::client_server_contract::BareKey::Char as i32\n        {\n            let character_str = key\n                .character\n                .ok_or_else(|| anyhow!(\"Character key missing character data\"))?;\n            let character = character_str\n                .chars()\n                .next()\n                .ok_or_else(|| anyhow!(\"Empty character string\"))?;\n            crate::data::BareKey::Char(character)\n        } else {\n            bare_key_from_proto_i32(key.bare_key)?\n        };\n\n        let key_modifiers: Result<BTreeSet<_>> = key\n            .key_modifiers\n            .into_iter()\n            .map(|modifier| key_modifier_from_proto_i32(modifier))\n            .collect();\n\n        Ok(Self {\n            bare_key,\n            key_modifiers: key_modifiers?,\n        })\n    }\n}\n","sourceCodeStart":3147,"sourceCodeEnd":3183,"githubUrl":"https://github.com/zellij-org/zellij/blob/98a0837077492d53dd252ab30bc3e43e41e504f4/zellij-utils/src/ipc/protobuf_conversion.rs#L3147-L3183","documentation":"When converting a protobuf KeyWithModifier with bare_key == BareKey::Char, the converter takes the character string and reads its first char with .chars().next(). A Some(\"\") (present but empty string) yields None from chars().next(), so the conversion fails with 'Empty character string'. This is distinct from the missing-field error: the field was sent but carried no character.","triggerScenarios":"Sending KeyWithModifier { bare_key: Char, character: Some(String::new()) } - e.g. serializing a key event where the character was stripped, lowercased/truncated to nothing, or defaulted to an empty string by a template.","commonSituations":"Key-forwarding code that does character.map(|c| c.to_string()) on an empty slice; string processing that empties the char (e.g. filtering multi-byte sequences); test fixtures using empty strings as placeholders.","solutions":["Ensure character contains at least one char: Some(c.to_string()) where c: char.","If there is no character, do not send bare_key = Char; use the appropriate named key or drop the event.","Add a unit test over every key event your code emits, asserting character is Some and non-empty.","Validate at the boundary: reject Char keys with empty character strings before they reach the converter."],"exampleFix":"// before\nlet key = KeyWithModifier {\n    bare_key: BareKey::Char as i32,\n    key_modifiers: vec![],\n    character: Some(String::new()), // -> \"Empty character string\"\n};\n\n// after\nlet key = KeyWithModifier {\n    bare_key: BareKey::Char as i32,\n    key_modifiers: vec![],\n    character: Some(\"a\".to_string()),\n};","handlingStrategy":"validation","validationCode":"fn key_is_convertible(k: &KeyWithModifier) -> bool {\n    if k.bare_key == BareKey::Char as i32 {\n        k.character.as_deref().map(|s| !s.is_empty()).unwrap_or(false)\n    } else {\n        true\n    }\n}","typeGuard":"fn char_key_has_payload(k: &KeyWithModifier) -> bool {\n    match (k.bare_key == BareKey::Char as i32, k.character.as_deref()) {\n        (true, Some(s)) => s.chars().next().is_some(),\n        (true, None) => false,\n        (false, _) => true,\n    }\n}","tryCatchPattern":"match zellij_utils::data::Key::try_from(proto_key) {\n    Ok(key) => handle_key(key),\n    Err(e) if e.to_string().contains(\"Empty character string\") => {\n        log::warn!(\"dropping Char key with empty character string\");\n    },\n    Err(e) => return Err(e),\n}","preventionTips":["Only the first char of the string is used - never send multi-char or empty strings by design.","Build the field from a Rust char: Some(c.to_string()) so it is never empty.","Fuzz key serialization with edge cases: empty string, multi-byte chars, combining marks."],"tags":["zellij","ipc","protobuf","rust","keyboard","input","validation"],"backgroundTag":null,"analyzedSha":"98a0837077492d53dd252ab30bc3e43e41e504f4","analyzedAt":"2026-08-16T13:02:01.396Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}