{"record":{"id":"a289e46a426ab515","repo":"sxyazi/yazi","slug":"invalid-condition-expr","errorCode":null,"errorMessage":"Invalid condition: {expr}","messagePattern":"Invalid condition: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"yazi-shared/src/condition.rs","lineNumber":69,"sourceCode":"\t\t\t// Keep repeated `!` right-associative by making `! >= !` false.\n\t\t\tEqual if matches!((self, other), (Self::Not, Self::Not)) => None,\n\t\t\tordering => Some(ordering),\n\t\t}\n\t}\n}\n\n#[derive(Debug, DeserializeFromStr)]\npub struct Condition {\n\tops: Vec<ConditionOp>,\n}\n\nimpl FromStr for Condition {\n\ttype Err = anyhow::Error;\n\n\tfn from_str(expr: &str) -> Result<Self, Self::Err> {\n\t\tlet cond = Self::build(expr);\n\t\tif cond.eval(|_| true).is_none() {\n\t\t\tbail!(\"Invalid condition: {expr}\");\n\t\t}\n\n\t\tOk(cond)\n\t}\n}\n\nimpl Condition {\n\tfn build(expr: &str) -> Self {\n\t\tlet mut stack: Vec<ConditionOp> = vec![];\n\t\tlet mut output: Vec<ConditionOp> = vec![];\n\n\t\tlet mut chars = expr.chars().peekable();\n\t\twhile let Some(token) = chars.next() {\n\t\t\tlet op = ConditionOp::new(token);\n\t\t\tmatch op {\n\t\t\t\tConditionOp::Or | ConditionOp::And | ConditionOp::Not => {\n\t\t\t\t\twhile matches!(stack.last(), Some(last) if last >= &op) {\n\t\t\t\t\t\toutput.push(stack.pop().unwrap());","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/sxyazi/yazi/blob/5f901b886b14de1f17460b6e52e9de5d67f8aba9/yazi-shared/src/condition.rs#L51-L87","documentation":"`Condition::from_str` parses a boolean condition expression (operators `|`, `&`, `!`, parentheses). After building the parser, it sanity-checks the expression by evaluating with all terms true; if evaluation returns None the syntax is structurally invalid (e.g. dangling operators, unbalanced parens, stray characters) and it bails with the offending expression.","triggerScenarios":"Parsing a condition string containing empty input, unmatched `(`/`)`, operators without operands like `a & & b`, `!` with no term, or invalid characters that produce no evaluable result, e.g. in config fields parsed as Condition via DeserializeFromStr.","commonSituations":"Hand-edited yazi.toml/rules condition strings with typos; generating conditions programmatically and joining with `&` when a list is empty; forgetting that plain words are terms and symbols are operators.","solutions":["Fix the expression syntax: balanced parentheses and operands around every `|`/`&`/`!`","Verify each non-operator token is a valid term and remove stray characters","Test the expression evaluates as expected before putting it in config"],"exampleFix":"-- before (dangling operator)\ncondition = \"mime & \"\n-- after\ncondition = \"mime & big\"","handlingStrategy":"validation","validationCode":"local function check_balanced(expr)\n  local depth = 0\n  for c in expr:gmatch(\"%(\") do depth = depth + 1 end\n  for c in expr:gmatch(\"%)\") do depth = depth - 1 end\n  assert(depth == 0 and #expr > 0, \"unbalanced or empty condition: \" .. expr)\nend","typeGuard":"fn valid_condition(expr: &str) -> Option<Condition> { expr.parse::<Condition>().ok() }","tryCatchPattern":"let cond = expr.parse::<Condition>()\n    .with_context(|| format(\"bad condition expression: {expr}\"))?;","preventionTips":["Keep operands on both sides of every `|`, `&`, and `!`","Balance all parentheses and avoid stray characters","Prefer single-term conditions unless combining is needed","Test conditions in isolation before committing to config"],"tags":["parsing","condition-expression","config","validation"],"backgroundTag":"invalid-condition-expression","analyzedSha":"5f901b886b14de1f17460b6e52e9de5d67f8aba9","analyzedAt":"2026-09-02T18:38:25.566Z","contentChangedAt":"2026-09-02T18:38:25.566Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}