Skip to main content

vtcode_core/tools/
error_helpers.rs

1//! Shared error helpers to reduce repetitive .with_context() patterns
2
3use anyhow::{Context, Result};
4use std::fmt::Display;
5use std::path::Path;
6
7// ---------------------------------------------------------------------------
8// Shared guidance constants — single source of truth for tool-facing messages
9// ---------------------------------------------------------------------------
10
11/// Standard hint when the model sends a patch payload to the wrong tool.
12pub const PATCH_PARAMETER_HINT: &str = "(use 'input' or 'patch' parameter)";
13
14/// Canonical guidance for routing large edits away from edit_file.
15pub const USE_PATCH_FOR_LARGE_EDITS: &str = "For larger edits, use apply_patch instead.";
16
17/// Canonical guidance for routing large-file edits away from edit_file.
18pub const USE_PATCH_OR_WRITE_FOR_LARGE_FILES: &str = "For large files, use apply_patch or a dedicated write operation.";
19
20/// Canonical guidance to read a file before attempting a precise edit or patch.
21pub const READ_FILE_FIRST_HINT: &str = "Use read_file first to get the exact text, then copy it precisely.";
22
23/// Wrap a file operation result with standardized path context.
24/// Works with any `Result<T, E>` where `E` implements `std::error::Error`.
25pub fn with_file_context<T, E>(result: std::result::Result<T, E>, operation: impl Display, path: &Path) -> Result<T>
26where
27    E: std::error::Error + Send + Sync + 'static,
28{
29    result.with_context(|| format!("Failed to {operation} '{}'", path.display()))
30}
31
32/// Like [`with_file_context`] but accepts a string path instead of `&Path`.
33pub fn with_path_context<T, E>(
34    result: std::result::Result<T, E>,
35    operation: impl Display,
36    path: impl Display,
37) -> Result<T>
38where
39    E: std::error::Error + Send + Sync + 'static,
40{
41    result.with_context(|| format!("Failed to {operation} {path}"))
42}
43
44/// Extract a required string field from JSON args, with a consistent error message
45#[cold]
46pub fn require_string_field(args: &serde_json::Value, field: &str, tool_name: &str) -> Result<String> {
47    args.get(field)
48        .and_then(|v| v.as_str())
49        .map(|s| s.to_string())
50        .ok_or_else(|| anyhow::anyhow!("{field} is required for {tool_name}"))
51}
52
53/// Extract an optional string field from JSON args
54pub fn optional_string_field(args: &serde_json::Value, field: &str) -> Option<String> {
55    args.get(field).and_then(|v| v.as_str()).map(|s| s.to_string())
56}
57
58/// Extract a required integer field from JSON args
59#[cold]
60pub fn require_int_field(args: &serde_json::Value, field: &str, tool_name: &str) -> Result<i64> {
61    args.get(field)
62        .and_then(|v| v.as_i64())
63        .ok_or_else(|| anyhow::anyhow!("{field} is required for {tool_name}"))
64}
65
66/// Extract a required value from an `Option<T>`, with a consistent error message
67/// referencing the field name and tool/operation name.
68#[cold]
69pub fn require_field<T>(value: Option<T>, field: &str, tool_name: &str) -> Result<T> {
70    value.ok_or_else(|| anyhow::anyhow!("{field} is required for {tool_name}"))
71}
72
73/// Deserialize JSON tool arguments into a typed struct with a consistent error message.
74///
75/// Replaces the repeated pattern of `serde_json::from_value(args).context("Error: Invalid 'X' arguments...")`
76/// scattered across tool implementations.
77pub fn deserialize_tool_args<T: serde::de::DeserializeOwned>(args: &serde_json::Value, tool_name: &str) -> Result<T> {
78    serde_json::from_value(args.clone()).map_err(|e| anyhow::anyhow!("Error: Invalid '{tool_name}' arguments: {e}"))
79}