{"record":{"id":"f7da52069471f408","repo":"zed-industries/zed","slug":"user-content-must-contain-at-least-one-part","errorCode":null,"errorMessage":"User content must contain at least one part","messagePattern":"User content must contain at least one part","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/google_ai/src/google_ai.rs","lineNumber":94,"sourceCode":"    }\n}\n\npub fn validate_generate_content_request(request: &GenerateContentRequest) -> Result<()> {\n    if request.model.is_empty() {\n        bail!(\"Model must be specified\");\n    }\n\n    if request.contents.is_empty() {\n        bail!(\"Request must contain at least one content item\");\n    }\n\n    if let Some(user_content) = request\n        .contents\n        .iter()\n        .find(|content| content.role == Role::User)\n        && user_content.parts.is_empty()\n    {\n        bail!(\"User content must contain at least one part\");\n    }\n\n    Ok(())\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub enum Task {\n    #[serde(rename = \"generateContent\")]\n    GenerateContent,\n    #[serde(rename = \"streamGenerateContent\")]\n    StreamGenerateContent,\n    #[serde(rename = \"embedContent\")]\n    EmbedContent,\n    #[serde(rename = \"batchEmbedContents\")]\n    BatchEmbedContents,\n}\n\n#[derive(Debug, Serialize, Deserialize)]","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/zed-industries/zed/blob/5a9b9558db01a6b906cec2fb70a797affdc58cdd/crates/google_ai/src/google_ai.rs#L76-L112","documentation":"validate_generate_content_request locates the first content item with role == User and rejects the request if its 'parts' vector is empty. A user turn with zero parts serializes to an empty message that Google's API refuses, so the guard catches it client-side.","triggerScenarios":"A user message whose parts list is empty because mapping code filtered out every part (unsupported content types); manually constructed Content structs in tests or glue code; compaction or editing logic that removes parts but keeps the container.","commonSituations":"History conversion drops image/thinking/redacted parts and leaves nothing; message structs built with vec![] parts; an empty user prompt after trimming.","solutions":["Ensure user content carries at least one part (usually a text part)","When mapping history, drop the whole content item if all its parts were filtered out, or insert a placeholder text part","Add unit tests over the message-mapping code for degenerate histories"],"exampleFix":"// before\nContent { role: Role::User, parts: vec![] } // passes type checks, fails validation\n\n// after: either fill a part or omit the empty content entirely\nContent { role: Role::User, parts: vec![Part::TextPart(\" \".into())] }","handlingStrategy":"validation","validationCode":"// before calling the API\nif let Some(user_content) = request.contents.iter().find(|c| c.role == Role::User) {\n    anyhow::ensure!(!user_content.parts.is_empty(), \"user content must have at least one part\");\n}","typeGuard":"fn user_content_is_nonempty(request: &GenerateContentRequest) -> bool {\n    request.contents.iter().find(|c| c.role == Role::User)\n        .map(|c| !c.parts.is_empty()).unwrap_or(true)\n}","tryCatchPattern":null,"preventionTips":["When mapping history, drop content items whose parts were all filtered out instead of emitting empty ones","Add round-trip tests: build request, validate, assert Ok","Never construct Content structs with vec![] parts in glue code"],"tags":["google-ai","request-validation","llm-provider","message-mapping"],"backgroundTag":"empty-request-payload","analyzedSha":"5a9b9558db01a6b906cec2fb70a797affdc58cdd","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}