{"record":{"id":"b003287f0a7bfa2e","repo":"gitbutlerapp/gitbutler","slug":"field-is-required-b00328","errorCode":null,"errorMessage":"{field} is required","messagePattern":"(.+?) is required","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-napi/src/ai.rs","lineNumber":275,"sourceCode":"                }\n            },\n        )?;\n        response\n            .filter(|response| !response.trim().is_empty())\n            .context(\"AI provider returned an empty response\")\n    })\n    .await\n    .context(\"AI response task failed\")\n    .and_then(|result| result)\n    .map_err(to_napi_err)?;\n\n    Ok(response)\n}\n\nfn required_message<'a>(value: &'a str, field: &str) -> Result<&'a str> {\n    let value = value.trim();\n    if value.is_empty() {\n        bail!(\"{field} is required\")\n    }\n    Ok(value)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use but_llm::{\n        AI_LMSTUDIO_MODEL_NAME_KEY, AI_MODEL_PROVIDER_KEY, AI_OPENAI_CUSTOM_ENDPOINT_KEY,\n        DEFAULT_ANTHROPIC_MODEL, DEFAULT_LMSTUDIO_ENDPOINT, DEFAULT_LMSTUDIO_MODEL,\n        DEFAULT_OLLAMA_ENDPOINT, DEFAULT_OLLAMA_MODEL, DEFAULT_OPENAI_MODEL,\n    };\n\n    fn valid_update() -> AiConfigurationUpdate {\n        AiConfigurationUpdate {\n            provider: \"openai\".into(),\n            openai_key_option: \"butlerAPI\".into(),\n            openai_model: DEFAULT_OPENAI_MODEL.into(),","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-napi/src/ai.rs#L257-L293","documentation":"`required_message` is the shared guard for required string fields on the AI NAPI surface: it trims the input and rejects empty or whitespace-only strings with '<field> is required', naming the offending field. It runs before any request is dispatched to the provider.","triggerScenarios":"Calling an AI entry point in but-napi ai.rs with a required string argument that trims to empty — for example an untouched form submitting an empty message/prompt.","commonSituations":"UI submitting before the user types anything; whitespace-only input produced by templating; optional-vs-required field mismatch between frontend and native module versions.","solutions":["Pass a value that is non-empty after trimming for the field named in the message","Disable submit in the UI until required fields are filled","Trim client-side and treat whitespace-only as empty"],"exampleFix":"// before\nawait ai.request({ message: '   ' }); // bails: message is required\n\n// after\nconst message = messageInput.trim();\nif (!message) throw new Error('Message is required');\nawait ai.request({ message });","handlingStrategy":"validation","validationCode":"// TypeScript: enforce non-empty required fields before calling native code\nfunction requireNonEmpty(value: string | undefined, field: string): string {\n  const v = (value ?? '').trim();\n  if (!v) throw new Error(`${field} is required`);\n  return v;\n}\nawait ai.request({ message: requireNonEmpty(input.message, 'Message') });","typeGuard":"// TypeScript\nconst isNonEmpty = (v?: string | null): boolean => !!v && v.trim().length > 0;","tryCatchPattern":"Catch the error, parse the '<field> is required' message, and focus/highlight the named field in the UI.","preventionTips":["Disable submit controls while required fields are empty after trimming","Normalize whitespace-only input to empty at the form boundary"],"tags":["validation","required-field","ai","napi"],"backgroundTag":"required-field-validation","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}