From e07185ce9b810c57ddf6f7542ad4ca27afc4a981 Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Tue, 21 Apr 2026 01:29:25 +0100 Subject: [PATCH] Fixed: Add missing error documentation to public API functions Document # Errors sections for 40 functions across 5 crates that were missing or had vague error documentation: - llm-coding-tools-core: BashRequest::parse, fs helpers, path resolvers - llm-coding-tools-agents: AgentLoader methods, Ruleset, runtime builders - llm-coding-tools-bubblewrap: wrap_command, build_command_wrap, probe - llm-coding-tools-serdesai: to_serdes_result, with_tool - llm-coding-tools-models-dev: load, load_at, sync functions --- src/llm-coding-tools-agents/src/extensions.rs | 2 ++ src/llm-coding-tools-agents/src/loader.rs | 34 +++++++++++++------ .../src/path/resolver.rs | 6 ++-- .../src/runtime/builder.rs | 3 ++ .../src/runtime/task.rs | 6 ++++ src/llm-coding-tools-bubblewrap/src/probe.rs | 7 ++++ .../src/wrap/blocking.rs | 4 +-- .../src/wrap/command.rs | 6 ++++ .../src/wrap/tokio.rs | 4 +-- .../src/fs/blocking_impl.rs | 16 +++++++++ .../src/fs/tokio_impl.rs | 16 +++++++++ .../src/path/absolute.rs | 2 ++ src/llm-coding-tools-core/src/path/allowed.rs | 4 +++ .../src/path/allowed_glob/normalize.rs | 4 +++ .../src/tools/bash/mod.rs | 4 +++ src/llm-coding-tools-core/src/tools/edit.rs | 18 ++++++++++ src/llm-coding-tools-core/src/tools/glob.rs | 5 +++ src/llm-coding-tools-core/src/tools/grep.rs | 5 +++ src/llm-coding-tools-core/src/tools/read.rs | 5 +++ src/llm-coding-tools-core/src/tools/todo.rs | 13 +++++++ .../src/tools/webfetch/mod.rs | 4 +++ src/llm-coding-tools-core/src/tools/write.rs | 13 +++++++ .../src/api/catalog_sources.rs | 7 ++++ .../src/api/schema.rs | 4 +++ .../src/cache/path.rs | 8 ++--- .../src/cache/payload.rs | 5 ++- .../src/catalog/load_cache.rs | 9 ++--- .../src/catalog/mod.rs | 32 +++++++++++------ .../src/catalog/sync.rs | 32 +++++++++++++---- .../src/agent_ext.rs | 7 ++++ src/llm-coding-tools-serdesai/src/convert.rs | 4 +++ 31 files changed, 243 insertions(+), 46 deletions(-) diff --git a/src/llm-coding-tools-agents/src/extensions.rs b/src/llm-coding-tools-agents/src/extensions.rs index ec7fa25d..67342eec 100644 --- a/src/llm-coding-tools-agents/src/extensions.rs +++ b/src/llm-coding-tools-agents/src/extensions.rs @@ -43,6 +43,8 @@ pub trait RulesetExt { } impl RulesetExt for Ruleset { + /// # Errors + /// - Returns [`ExpandError`] when a permission pattern is invalid (contains `:`, `//`, or empty segments). fn from_permission_config( config: &IndexMap, ) -> Result { diff --git a/src/llm-coding-tools-agents/src/loader.rs b/src/llm-coding-tools-agents/src/loader.rs index 9d9cf6f5..dbc59484 100644 --- a/src/llm-coding-tools-agents/src/loader.rs +++ b/src/llm-coding-tools-agents/src/loader.rs @@ -81,8 +81,8 @@ impl AgentLoader { /// * `directory` - Root directory to scan /// /// # Errors - /// - /// Returns an error only for directory-level failures (e.g., path is not a directory). + /// - Returns [`AgentLoadError::Io`] when the directory path exists but is not a directory. + /// - Returns [`AgentLoadError::Io`] when the directory cannot be accessed (permissions, etc.). pub fn add_directory( &self, catalog: &mut AgentCatalog, @@ -103,10 +103,9 @@ impl AgentLoader { /// * `on_error` - Callback invoked for each file that fails to load /// /// # Errors - /// - /// Returns an error only for directory-level failures (e.g., path is not a directory). - /// Individual file load failures are reported via `on_error` and do not fail the overall - /// operation. + /// - Returns [`AgentLoadError::Io`] when the directory path exists but is not a directory. + /// - Returns [`AgentLoadError::Io`] when the directory cannot be accessed (permissions, etc.). + /// - Individual file load failures are reported via `on_error` and do not fail the operation. pub fn add_directory_with_errors( &self, catalog: &mut AgentCatalog, @@ -135,6 +134,12 @@ impl AgentLoader { /// /// * `catalog` - The catalog to insert the agent into /// * `path` - Path to a markdown file with YAML frontmatter + /// + /// # Errors + /// - Returns [`AgentLoadError::SchemaValidation`] when the file name (stem) is empty. + /// - Returns [`AgentLoadError::Io`] when the file cannot be read. + /// - Returns [`AgentLoadError::Parse`] when frontmatter parsing fails. + /// - Returns [`AgentLoadError::SchemaValidation`] when schema validation fails. pub fn add_file( &self, catalog: &mut AgentCatalog, @@ -165,6 +170,12 @@ impl AgentLoader { /// * `catalog` - The catalog to insert the agent into /// * `path` - Path to a markdown file with YAML frontmatter /// * `name` - Explicit agent name to use + /// + /// # Errors + /// - Returns [`AgentLoadError::SchemaValidation`] when the explicit name is empty. + /// - Returns [`AgentLoadError::Io`] when the file cannot be read. + /// - Returns [`AgentLoadError::Parse`] when frontmatter parsing fails. + /// - Returns [`AgentLoadError::SchemaValidation`] when schema validation fails. pub fn add_file_named( &self, catalog: &mut AgentCatalog, @@ -213,10 +224,8 @@ impl AgentLoader { /// * `default_name` - Agent name to use if not specified in frontmatter /// /// # Errors - /// - /// Returns an error if: - /// - Parsing fails (propagates the underlying parse error) - /// - The resulting agent name is empty + /// - Returns [`AgentLoadError::Parse`] when frontmatter parsing fails. + /// - Returns [`AgentLoadError::SchemaValidation`] when the resulting agent name is empty. pub fn add_from_str( &self, catalog: &mut AgentCatalog, @@ -238,6 +247,11 @@ impl AgentLoader { /// * `catalog` - The catalog to insert the agent into /// * `bytes` - Raw markdown bytes with YAML frontmatter /// * `default_name` - Agent name to use if not specified in frontmatter + /// + /// # Errors + /// - Returns [`AgentLoadError::SchemaValidation`] when `bytes` is not valid UTF-8. + /// - Returns [`AgentLoadError::Parse`] when frontmatter parsing fails. + /// - Returns [`AgentLoadError::SchemaValidation`] when the resulting agent name is empty. pub fn add_from_bytes( &self, catalog: &mut AgentCatalog, diff --git a/src/llm-coding-tools-agents/src/path/resolver.rs b/src/llm-coding-tools-agents/src/path/resolver.rs index bf5e03d9..d6cbb2d9 100644 --- a/src/llm-coding-tools-agents/src/path/resolver.rs +++ b/src/llm-coding-tools-agents/src/path/resolver.rs @@ -86,10 +86,10 @@ impl PathResolver for FileToolResolver { /// The cheapest [`FileToolResolver`] variant satisfying the tool's permission config. /// /// # Errors -/// -/// - Returns [`ToolError::InvalidPath`] when shell expansion fails (e.g., unresolvable `$VAR` in a pattern). -/// - Returns [`ToolError::InvalidPattern`] when a glob pattern is syntactically invalid. /// - Returns [`ToolError::InvalidPath`] when the workspace root does not exist or cannot be canonicalized. +/// - Returns [`ToolError::PermissionDenied`] when the tool is disabled by configuration (`deny`). +/// - Returns [`ToolError::InvalidPath`] when shell expansion fails (e.g., unresolvable environment variable). +/// - Returns [`ToolError::InvalidPattern`] when a glob pattern is syntactically invalid. pub fn build_resolver_for_tool( config: &IndexMap, tool_name: &str, diff --git a/src/llm-coding-tools-agents/src/runtime/builder.rs b/src/llm-coding-tools-agents/src/runtime/builder.rs index 8bb49375..d6a5c51a 100644 --- a/src/llm-coding-tools-agents/src/runtime/builder.rs +++ b/src/llm-coding-tools-agents/src/runtime/builder.rs @@ -70,6 +70,9 @@ impl AgentRuntimeBuilder { } /// Finishes building and returns the [`AgentRuntime`]. + /// + /// # Errors + /// - Returns [`ExpandError`] when any agent's permission configuration contains invalid patterns. #[inline] pub fn build(self) -> Result { AgentRuntime::from_parts(self.catalog, self.defaults, self.task_settings, self.tools) diff --git a/src/llm-coding-tools-agents/src/runtime/task.rs b/src/llm-coding-tools-agents/src/runtime/task.rs index 8faafac9..1fe26be4 100644 --- a/src/llm-coding-tools-agents/src/runtime/task.rs +++ b/src/llm-coding-tools-agents/src/runtime/task.rs @@ -31,6 +31,9 @@ pub struct TaskTargetSummary { /// # Returns /// One [`TaskTargetSummary`] per callable target, sorted by name. Empty if /// `caller_name` is not in the catalog or no non-primary targets are available. +/// +/// # Errors +/// - Returns [`ExpandError`] when the caller's `permission.task` configuration contains invalid patterns. pub fn summarize_callable_targets( catalog: &AgentCatalog, caller_name: &str, @@ -52,6 +55,9 @@ pub fn summarize_callable_targets( /// `mode: all` and `mode: subagent` targets for OpenCode compatibility. When /// `permission.task` is present, its rules filter target names with the normal /// last-match-wins permission semantics. +/// +/// # Errors +/// - Returns [`ExpandError`] when the caller's `permission.task` configuration contains invalid patterns. pub fn callable_targets<'a>( catalog: &'a AgentCatalog, caller_name: &str, diff --git a/src/llm-coding-tools-bubblewrap/src/probe.rs b/src/llm-coding-tools-bubblewrap/src/probe.rs index 9dc9bd1a..5aca7760 100644 --- a/src/llm-coding-tools-bubblewrap/src/probe.rs +++ b/src/llm-coding-tools-bubblewrap/src/probe.rs @@ -57,6 +57,13 @@ pub(crate) fn probe_availability() -> Availability { /// If `availability` already indicates unavailability, returns early without /// probing again. Otherwise re-checks the host and returns an [`Arc`] on /// success or a [`LinuxBwrapError::Execution`] on failure. +/// +/// # Errors +/// - Returns [`LinuxBwrapError::Execution`] when the provided `availability` already +/// indicates unavailability (via [`Availability::reason`]). +/// - Returns [`LinuxBwrapError::Execution`] when the `bwrap` binary cannot be found on `PATH`. +/// - Returns [`LinuxBwrapError::Execution`] when `bwrap` exists but the current environment +/// cannot create sandboxes (e.g., missing user namespace capabilities). pub(crate) fn resolve_backend_or_error_for( preset: Option, availability: &Availability, diff --git a/src/llm-coding-tools-bubblewrap/src/wrap/blocking.rs b/src/llm-coding-tools-bubblewrap/src/wrap/blocking.rs index c998bb66..c60bf8fb 100644 --- a/src/llm-coding-tools-bubblewrap/src/wrap/blocking.rs +++ b/src/llm-coding-tools-bubblewrap/src/wrap/blocking.rs @@ -13,8 +13,8 @@ use std::process::Stdio; /// Builds a sync [`CommandWrap`] from a [`Profile`]. /// /// # Errors -/// -/// Returns [`LinuxBwrapError`] on invalid per-command workdir. +/// - Returns [`LinuxBwrapError::InvalidPath`] when `workdir` is not an absolute path, +/// does not exist, is not a directory, or is not visible inside the sandbox. pub fn build_command_wrap( profile: &Profile, command: &str, diff --git a/src/llm-coding-tools-bubblewrap/src/wrap/command.rs b/src/llm-coding-tools-bubblewrap/src/wrap/command.rs index 29de04f8..be2dad44 100644 --- a/src/llm-coding-tools-bubblewrap/src/wrap/command.rs +++ b/src/llm-coding-tools-bubblewrap/src/wrap/command.rs @@ -72,6 +72,12 @@ fn resolve_sandbox_cwd<'a>( /// Builds a `bwrap` command line that runs `command` inside the sandbox /// described by `profile`. +/// +/// # Errors +/// - Returns [`LinuxBwrapError::InvalidPath`] when `workdir` is not an absolute path. +/// - Returns [`LinuxBwrapError::InvalidPath`] when `workdir` does not exist or is not a directory. +/// - Returns [`LinuxBwrapError::InvalidPath`] when `workdir` is not visible inside the sandbox +/// (not under workspace, synthetic home, cache root, or any mounted directory). #[inline] pub fn wrap_command<'a>( profile: &'a Profile, diff --git a/src/llm-coding-tools-bubblewrap/src/wrap/tokio.rs b/src/llm-coding-tools-bubblewrap/src/wrap/tokio.rs index 5fbf709a..1b268914 100644 --- a/src/llm-coding-tools-bubblewrap/src/wrap/tokio.rs +++ b/src/llm-coding-tools-bubblewrap/src/wrap/tokio.rs @@ -13,8 +13,8 @@ use std::process::Stdio; /// Builds an async [`CommandWrap`] from a [`Profile`]. /// /// # Errors -/// -/// Returns [`LinuxBwrapError`] on invalid per-command workdir. +/// - Returns [`LinuxBwrapError::InvalidPath`] when `workdir` is not an absolute path, +/// does not exist, is not a directory, or is not visible inside the sandbox. pub fn build_command_wrap( profile: &Profile, command: &str, diff --git a/src/llm-coding-tools-core/src/fs/blocking_impl.rs b/src/llm-coding-tools-core/src/fs/blocking_impl.rs index bdf04f86..3a6eea67 100644 --- a/src/llm-coding-tools-core/src/fs/blocking_impl.rs +++ b/src/llm-coding-tools-core/src/fs/blocking_impl.rs @@ -4,21 +4,37 @@ use crate::error::ToolResult; use std::path::Path; /// Reads a file to string. +/// +/// # Errors +/// - Returns [`ToolError::Io`] when the file cannot be read (e.g., file does not exist, +/// permission denied, or other I/O error). pub fn read_to_string(path: impl AsRef) -> ToolResult { Ok(std::fs::read_to_string(path)?) } /// Writes content to a file. +/// +/// # Errors +/// - Returns [`ToolError::Io`] when the file cannot be written (e.g., parent directory +/// does not exist, permission denied, or other I/O error). pub fn write(path: impl AsRef, contents: impl AsRef<[u8]>) -> ToolResult<()> { Ok(std::fs::write(path, contents)?) } /// Creates a directory and all parent directories. +/// +/// # Errors +/// - Returns [`ToolError::Io`] when the directory cannot be created (e.g., permission +/// denied or other I/O error). pub fn create_dir_all(path: impl AsRef) -> ToolResult<()> { Ok(std::fs::create_dir_all(path)?) } /// Opens a file for buffered reading. +/// +/// # Errors +/// - Returns [`ToolError::Io`] when the file cannot be opened (e.g., file does not exist, +/// permission denied, or other I/O error). pub fn open_buffered( path: impl AsRef, capacity: usize, diff --git a/src/llm-coding-tools-core/src/fs/tokio_impl.rs b/src/llm-coding-tools-core/src/fs/tokio_impl.rs index e98d0740..31f150a5 100644 --- a/src/llm-coding-tools-core/src/fs/tokio_impl.rs +++ b/src/llm-coding-tools-core/src/fs/tokio_impl.rs @@ -4,21 +4,37 @@ use crate::error::ToolResult; use std::path::Path; /// Reads a file to string. +/// +/// # Errors +/// - Returns [`ToolError::Io`] when the file cannot be read (e.g., file does not exist, +/// permission denied, or other I/O error). pub async fn read_to_string(path: impl AsRef) -> ToolResult { Ok(tokio::fs::read_to_string(path).await?) } /// Writes content to a file. +/// +/// # Errors +/// - Returns [`ToolError::Io`] when the file cannot be written (e.g., parent directory +/// does not exist, permission denied, or other I/O error). pub async fn write(path: impl AsRef, contents: impl AsRef<[u8]>) -> ToolResult<()> { Ok(tokio::fs::write(path, contents).await?) } /// Creates a directory and all parent directories. +/// +/// # Errors +/// - Returns [`ToolError::Io`] when the directory cannot be created (e.g., permission +/// denied or other I/O error). pub async fn create_dir_all(path: impl AsRef) -> ToolResult<()> { Ok(tokio::fs::create_dir_all(path).await?) } /// Opens a file for buffered reading. +/// +/// # Errors +/// - Returns [`ToolError::Io`] when the file cannot be opened (e.g., file does not exist, +/// permission denied, or other I/O error). pub async fn open_buffered( path: impl AsRef, capacity: usize, diff --git a/src/llm-coding-tools-core/src/path/absolute.rs b/src/llm-coding-tools-core/src/path/absolute.rs index 27359bfa..b80d9a1d 100644 --- a/src/llm-coding-tools-core/src/path/absolute.rs +++ b/src/llm-coding-tools-core/src/path/absolute.rs @@ -34,6 +34,8 @@ impl PathResolver for AbsolutePathResolver { true } + /// # Errors + /// - Returns [`ToolError::InvalidPath`] when `path` is not an absolute path. fn resolve(&self, path: &str) -> ToolResult { let path = PathBuf::from(path); if !path.is_absolute() { diff --git a/src/llm-coding-tools-core/src/path/allowed.rs b/src/llm-coding-tools-core/src/path/allowed.rs index 1b9531b5..23b74876 100644 --- a/src/llm-coding-tools-core/src/path/allowed.rs +++ b/src/llm-coding-tools-core/src/path/allowed.rs @@ -68,6 +68,10 @@ impl AllowedPathResolver { /// Each directory is resolved during construction to ensure consistent path /// comparison. Returns an error if any directory doesn't exist or can't be /// resolved. + /// + /// # Errors + /// - Returns [`ToolError::InvalidPath`] when any provided path is not an existing directory. + /// - Returns [`ToolError::InvalidPath`] when path canonicalization fails. pub fn new(allowed_paths: impl IntoIterator>) -> ToolResult { let canonicalized: Result, _> = allowed_paths .into_iter() diff --git a/src/llm-coding-tools-core/src/path/allowed_glob/normalize.rs b/src/llm-coding-tools-core/src/path/allowed_glob/normalize.rs index 2b71e121..9b9bb5c5 100644 --- a/src/llm-coding-tools-core/src/path/allowed_glob/normalize.rs +++ b/src/llm-coding-tools-core/src/path/allowed_glob/normalize.rs @@ -40,6 +40,10 @@ pub(crate) fn expand_pattern( /// /// Wraps the internal expansion logic with fail-fast error handling: returns /// `ToolError::InvalidPath` if expansion fails (e.g., unset variable). +/// +/// # Errors +/// - Returns [`ToolError::InvalidPath`] when shell expansion fails (e.g., unset +/// environment variable in the path pattern). pub fn expand_shell(path: &str) -> ToolResult { expand_pattern(path) .map(|cow| PathBuf::from(cow.into_owned())) diff --git a/src/llm-coding-tools-core/src/tools/bash/mod.rs b/src/llm-coding-tools-core/src/tools/bash/mod.rs index 9d6e0392..b922b0b2 100644 --- a/src/llm-coding-tools-core/src/tools/bash/mod.rs +++ b/src/llm-coding-tools-core/src/tools/bash/mod.rs @@ -78,6 +78,10 @@ pub struct BashRequest { impl BashRequest { /// Parses a raw JSON tool payload into a bash request. + /// + /// # Errors + /// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized + /// into a [`BashRequest`] (e.g., missing `command` field or invalid field types). pub fn parse(args: Value) -> ToolResult { serde_json::from_value(args).map_err(ToolError::from) } diff --git a/src/llm-coding-tools-core/src/tools/edit.rs b/src/llm-coding-tools-core/src/tools/edit.rs index 781082d4..472b8134 100644 --- a/src/llm-coding-tools-core/src/tools/edit.rs +++ b/src/llm-coding-tools-core/src/tools/edit.rs @@ -45,6 +45,11 @@ pub struct EditRequest { impl EditRequest { /// Parses a raw JSON tool payload into an edit request. + /// + /// # Errors + /// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized + /// into an [`EditRequest`] (e.g., missing required `file_path`, `old_string`, + /// or `new_string` fields, or invalid field types). pub fn parse(args: Value) -> ToolResult { serde_json::from_value(args).map_err(ToolError::from) } @@ -94,6 +99,19 @@ impl EditSettings { /// Performs exact string replacement in a file. /// /// Returns success message with replacement count. +/// +/// # Errors +/// - Returns [`EditError::EmptyOldString`] when `request.old_string` is empty. +/// - Returns [`EditError::IdenticalStrings`] when `old_string` and `new_string` +/// are identical. +/// - Returns [`EditError::Tool`] wrapping [`ToolError::InvalidPath`] when path +/// resolution fails. +/// - Returns [`EditError::Tool`] wrapping [`ToolError::Io`] when reading the file fails. +/// - Returns [`EditError::NotFound`] when `old_string` is not found in the file content. +/// - Returns [`EditError::AmbiguousMatch`] when `replace_all=false` and multiple +/// occurrences of `old_string` are found (requires more context to identify unique match). +/// - Returns [`EditError::Tool`] wrapping [`ToolError::Io`] when writing the modified +/// content back to the file fails. #[maybe_async::maybe_async] pub async fn edit_file( resolver: &R, diff --git a/src/llm-coding-tools-core/src/tools/glob.rs b/src/llm-coding-tools-core/src/tools/glob.rs index eb0c9717..2335f1f0 100644 --- a/src/llm-coding-tools-core/src/tools/glob.rs +++ b/src/llm-coding-tools-core/src/tools/glob.rs @@ -21,6 +21,11 @@ pub struct GlobRequest { impl GlobRequest { /// Parses a raw JSON tool payload into a glob request. + /// + /// # Errors + /// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized + /// into a [`GlobRequest`] (e.g., missing required `pattern` or `path` fields, + /// or invalid field types). pub fn parse(args: Value) -> ToolResult { serde_json::from_value(args).map_err(ToolError::from) } diff --git a/src/llm-coding-tools-core/src/tools/grep.rs b/src/llm-coding-tools-core/src/tools/grep.rs index 932ccd5d..f8a50e98 100644 --- a/src/llm-coding-tools-core/src/tools/grep.rs +++ b/src/llm-coding-tools-core/src/tools/grep.rs @@ -33,6 +33,11 @@ pub struct GrepRequest { impl GrepRequest { /// Parses a raw JSON tool payload into a grep request. + /// + /// # Errors + /// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized + /// into a [`GrepRequest`] (e.g., missing required `pattern` or `path` fields, + /// or invalid field types). pub fn parse(args: Value) -> ToolResult { serde_json::from_value(args).map_err(ToolError::from) } diff --git a/src/llm-coding-tools-core/src/tools/read.rs b/src/llm-coding-tools-core/src/tools/read.rs index d09b10d1..546303ea 100644 --- a/src/llm-coding-tools-core/src/tools/read.rs +++ b/src/llm-coding-tools-core/src/tools/read.rs @@ -24,6 +24,11 @@ pub struct ReadRequest { impl ReadRequest { /// Parses a raw JSON tool payload into a read request. + /// + /// # Errors + /// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized + /// into a [`ReadRequest`] (e.g., missing required `file_path` field or + /// invalid field types). pub fn parse(args: Value) -> ToolResult { serde_json::from_value(args).map_err(ToolError::from) } diff --git a/src/llm-coding-tools-core/src/tools/todo.rs b/src/llm-coding-tools-core/src/tools/todo.rs index ed071748..07e4ca4d 100644 --- a/src/llm-coding-tools-core/src/tools/todo.rs +++ b/src/llm-coding-tools-core/src/tools/todo.rs @@ -75,6 +75,11 @@ pub struct TodoWriteRequest { impl TodoWriteRequest { /// Parses a raw JSON tool payload into a todo-write request. + /// + /// # Errors + /// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized + /// into a [`TodoWriteRequest`] (e.g., missing `todos` field or invalid todo + /// structure). pub fn parse(args: Value) -> ToolResult { serde_json::from_value(args).map_err(ToolError::from) } @@ -86,6 +91,10 @@ pub struct TodoReadRequest {} impl TodoReadRequest { /// Parses a raw JSON tool payload into a todo-read request. + /// + /// # Errors + /// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized + /// into a [`TodoReadRequest`]. pub fn parse(args: Value) -> ToolResult { serde_json::from_value(args).map_err(ToolError::from) } @@ -102,6 +111,10 @@ impl TodoState { /// Writes/replaces the todo list with new items. /// /// Validates that all todos have non-empty id and content. +/// +/// # Errors +/// - Returns [`ToolError::Validation`] when any todo has an empty or whitespace-only `id`. +/// - Returns [`ToolError::Validation`] when any todo has empty or whitespace-only `content`. pub fn write_todos(state: &TodoState, request: TodoWriteRequest) -> ToolResult { for todo in &request.todos { if todo.id.trim().is_empty() { diff --git a/src/llm-coding-tools-core/src/tools/webfetch/mod.rs b/src/llm-coding-tools-core/src/tools/webfetch/mod.rs index 317f73a0..6b1d399e 100644 --- a/src/llm-coding-tools-core/src/tools/webfetch/mod.rs +++ b/src/llm-coding-tools-core/src/tools/webfetch/mod.rs @@ -21,6 +21,10 @@ pub struct WebFetchRequest { impl WebFetchRequest { /// Parses a raw JSON tool payload into a webfetch request. + /// + /// # Errors + /// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized + /// into a [`WebFetchRequest`] (e.g., missing `url` field or invalid field types). pub fn parse(args: Value) -> ToolResult { serde_json::from_value(args).map_err(ToolError::from) } diff --git a/src/llm-coding-tools-core/src/tools/write.rs b/src/llm-coding-tools-core/src/tools/write.rs index 4ab20e64..c381b42a 100644 --- a/src/llm-coding-tools-core/src/tools/write.rs +++ b/src/llm-coding-tools-core/src/tools/write.rs @@ -15,6 +15,11 @@ pub struct WriteRequest { impl WriteRequest { /// Parses a raw JSON tool payload into a write request. + /// + /// # Errors + /// - Returns [`ToolError::Json`] when the JSON payload cannot be deserialized + /// into a [`WriteRequest`] (e.g., missing required `file_path` or `content` + /// fields, or invalid field types). pub fn parse(args: Value) -> ToolResult { serde_json::from_value(args).map_err(ToolError::from) } @@ -37,6 +42,14 @@ impl WriteSettings { /// Writes content to a file, creating parent directories if needed. /// /// Overwrites existing files. Returns a success message with byte count. +/// +/// # Errors +/// - Returns [`ToolError::InvalidPath`] when `resolver.resolve()` fails to +/// resolve `request.file_path` (e.g., path is not absolute or violates policy). +/// - Returns [`ToolError::Io`] when parent directory creation fails (e.g., +/// permission denied, read-only filesystem). +/// - Returns [`ToolError::Io`] when writing to the file fails (e.g., disk full, +/// permission denied, I/O error). #[maybe_async::maybe_async] pub async fn write_file( resolver: &R, diff --git a/src/llm-coding-tools-models-dev/src/api/catalog_sources.rs b/src/llm-coding-tools-models-dev/src/api/catalog_sources.rs index 27a5dc93..a3c93cef 100644 --- a/src/llm-coding-tools-models-dev/src/api/catalog_sources.rs +++ b/src/llm-coding-tools-models-dev/src/api/catalog_sources.rs @@ -20,6 +20,13 @@ use llm_coding_tools_core::models::{ Modality, ModelCatalogBuildError, ModelInfo, ProviderIdx, ProviderType, }; +/// Parses models.dev API JSON bytes into a cache payload. +/// +/// # Errors +/// - Returns [`CatalogError::Json`] when `json_bytes` cannot be parsed as valid +/// models.dev API JSON. +/// - Returns [`CatalogError::ModelCatalogBuild`] with [`ModelCatalogBuildError::TooManyProviders`] +/// when the number of providers exceeds `u16::MAX + 1` (65,536). pub(crate) fn cache_payload_from_api_json_bytes( json_bytes: &[u8], ) -> CatalogResult { diff --git a/src/llm-coding-tools-models-dev/src/api/schema.rs b/src/llm-coding-tools-models-dev/src/api/schema.rs index 3e0f4c12..a8ab3060 100644 --- a/src/llm-coding-tools-models-dev/src/api/schema.rs +++ b/src/llm-coding-tools-models-dev/src/api/schema.rs @@ -86,6 +86,10 @@ pub(crate) struct ApiModelLimit { /// /// Input must match the current models.dev shape: a flat top-level object where /// each key is a provider id and each value is a provider entry. +/// +/// # Errors +/// - Returns [`CatalogError::Json`] when `json_bytes` is not valid JSON or does not +/// match the expected models.dev API schema structure. #[inline] pub(crate) fn parse_api_json( json_bytes: &[u8], diff --git a/src/llm-coding-tools-models-dev/src/cache/path.rs b/src/llm-coding-tools-models-dev/src/cache/path.rs index e43d256e..b799cf2d 100644 --- a/src/llm-coding-tools-models-dev/src/cache/path.rs +++ b/src/llm-coding-tools-models-dev/src/cache/path.rs @@ -28,10 +28,10 @@ const CACHE_FILENAME: &str = "models.dev.catalog.v1.cache"; /// The full path to the cache file. /// /// # Errors -/// -/// Returns [`CatalogError::CachePathNotFound`] when: -/// - The environment variable is not set AND -/// - The platform cache directory cannot be determined +/// - Returns [`CatalogError::Configuration`] when `LLM_CODING_TOOLS_MODELS_DEV_CACHE_PATH` +/// is set but empty. +/// - Returns [`CatalogError::CachePathNotFound`] when the environment variable is not set +/// and the platform cache directory cannot be determined. /// /// # Examples /// diff --git a/src/llm-coding-tools-models-dev/src/cache/payload.rs b/src/llm-coding-tools-models-dev/src/cache/payload.rs index 3c98dbce..82076c9b 100644 --- a/src/llm-coding-tools-models-dev/src/cache/payload.rs +++ b/src/llm-coding-tools-models-dev/src/cache/payload.rs @@ -105,9 +105,8 @@ pub(crate) fn decode_cache_payload(bytes: &[u8]) -> CatalogResult CatalogResult { diff --git a/src/llm-coding-tools-models-dev/src/catalog/load_cache.rs b/src/llm-coding-tools-models-dev/src/catalog/load_cache.rs index 8e807411..922b0726 100644 --- a/src/llm-coding-tools-models-dev/src/catalog/load_cache.rs +++ b/src/llm-coding-tools-models-dev/src/catalog/load_cache.rs @@ -12,10 +12,11 @@ use crate::error::{CatalogError, CatalogResult}; /// Decompresses cache file data and rebuilds a catalog from it. /// /// # Errors -/// -/// Returns [`CatalogError`] when zstd decompression fails, the decompressed -/// length does not match the cache metadata, the serialized payload cannot be -/// decoded, or catalog reconstruction fails. +/// - Returns [`CatalogError::Zstd`] when zstd decompression fails. +/// - Returns [`CatalogError::CacheFormat`] when the decompressed length does not +/// match the cache metadata. +/// - Returns [`CatalogError::BitcodeDecode`] when the serialized payload cannot be decoded. +/// - Returns [`CatalogError::ModelCatalogBuild`] when catalog reconstruction fails. pub(crate) fn load_catalog_from_cache_file_data( cache_file: &CacheFileData, source: CatalogLoadSource, diff --git a/src/llm-coding-tools-models-dev/src/catalog/mod.rs b/src/llm-coding-tools-models-dev/src/catalog/mod.rs index 9a541b13..1ce3cf8c 100644 --- a/src/llm-coding-tools-models-dev/src/catalog/mod.rs +++ b/src/llm-coding-tools-models-dev/src/catalog/mod.rs @@ -75,9 +75,20 @@ impl ModelsDevCatalog { /// # if let Some(entry) = result.catalog.lookup("openai", "gpt-4") { /// # println!("API URL: {}", entry.0.api_url); /// # } - /// # Ok(()) /// # } /// ``` + /// + /// # Errors + /// - Returns [`CatalogError::CachePathNotFound`] when the environment variable is not set + /// and the platform cache directory cannot be determined. + /// - Returns [`CatalogError::Configuration`] when the environment variable is set but empty. + /// - Returns [`CatalogError::Io`] when cache file I/O fails without a usable fallback. + /// - Returns [`CatalogError::Reqwest`] when the HTTP request fails and no valid cache + /// is available for fallback. + /// - Returns [`CatalogError::CacheFormat`] when the cache file is truncated or corrupted. + /// - Returns [`CatalogError::Zstd`] when zstd decompression fails. + /// - Returns [`CatalogError::BitcodeDecode`] when the cached payload cannot be decoded. + /// - Returns [`CatalogError::ModelCatalogBuild`] when catalog reconstruction fails. #[maybe_async::maybe_async] pub async fn load() -> Result { let path = shared_cache_path()?; @@ -103,22 +114,22 @@ impl ModelsDevCatalog { /// information. /// /// # Errors - /// - /// Returns [`CatalogError`] under the same conditions as [`load`](Self::load), - /// plus: - /// - The parent directory cannot be created - /// - The path is not a valid file path + /// - Returns [`CatalogError::Io`] when cache file I/O fails without a usable fallback. + /// - Returns [`CatalogError::Reqwest`] when the HTTP request fails and no valid cache + /// is available for fallback. + /// - Returns [`CatalogError::CacheFormat`] when the cache file is truncated or corrupted. + /// - Returns [`CatalogError::Zstd`] when zstd decompression fails. + /// - Returns [`CatalogError::BitcodeDecode`] when the cached payload cannot be decoded. + /// - Returns [`CatalogError::ModelCatalogBuild`] when catalog reconstruction fails. /// /// # Examples /// /// ``` /// use llm_coding_tools_models_dev::ModelsDevCatalog; - /// use std::path::PathBuf; /// /// # #[cfg(feature = "tokio")] /// # async fn example() -> Result<(), Box> { - /// let cache_path = PathBuf::from("/tmp/my-cache.cache"); - /// let result = ModelsDevCatalog::load_at(&cache_path).await?; + /// let result = ModelsDevCatalog::load_at("/tmp/models.dev.cache").await?; /// /// // Use the catalog /// if let Some(entry) = result.catalog.lookup("openai", "gpt-4") { @@ -129,8 +140,7 @@ impl ModelsDevCatalog { /// /// # #[cfg(feature = "blocking")] /// # fn example() -> Result<(), Box> { - /// # let cache_path = PathBuf::from("/tmp/my-cache.cache"); - /// # let result = ModelsDevCatalog::load_at(&cache_path)?; + /// # let result = ModelsDevCatalog::load_at("/tmp/models.dev.cache")?; /// # if let Some(entry) = result.catalog.lookup("openai", "gpt-4") { /// # println!("API URL: {}", entry.0.api_url); /// # } diff --git a/src/llm-coding-tools-models-dev/src/catalog/sync.rs b/src/llm-coding-tools-models-dev/src/catalog/sync.rs index d63023d6..764844ae 100644 --- a/src/llm-coding-tools-models-dev/src/catalog/sync.rs +++ b/src/llm-coding-tools-models-dev/src/catalog/sync.rs @@ -72,9 +72,17 @@ fn is_transient_status(status: StatusCode) -> bool { /// Loads the catalog at `path` using the default models.dev endpoint. /// /// # Errors -/// -/// Returns the same errors as [`load_catalog_from_url`] while targeting the -/// default production URL. +/// - Returns [`CatalogError::CachePathNotFound`] when the environment variable is not set +/// and the platform cache directory cannot be determined. +/// - Returns [`CatalogError::Configuration`] when the environment variable is set but empty. +/// - Returns [`CatalogError::Io`] when cache file I/O fails without a usable fallback. +/// - Returns [`CatalogError::Reqwest`] when the HTTP request fails and no cache is available. +/// - Returns [`CatalogError::CacheFormat`] when the cache file is truncated, has length +/// mismatches, or contains invalid data. +/// - Returns [`CatalogError::Zstd`] when zstd decompression of cached data fails. +/// - Returns [`CatalogError::BitcodeDecode`] when the cached payload cannot be decoded. +/// - Returns [`CatalogError::ModelCatalogBuild`] when catalog reconstruction from cached +/// or downloaded data fails. pub(crate) async fn load_catalog_at_path(path: &Path) -> CatalogResult { let url = models_dev_api_url(); load_catalog_from_url(path, url.as_ref()).await @@ -99,10 +107,20 @@ pub(crate) async fn load_catalog_at_path(path: &Path) -> CatalogResult { /// Maps a [`ToolError`](llm_coding_tools_core::ToolError) to /// [`AgentBuildError::ToolSettingsValidation`]. + /// + /// # Errors + /// - Returns [`AgentBuildError::ToolSettingsValidation`] when the original result + /// contains a [`ToolError`], preserving the tool name and original error. fn with_tool(self, tool: &'static str) -> Result; } impl ToolResultExt for Result { + /// # Errors + /// - Returns [`AgentBuildError::ToolSettingsValidation`] when the original result + /// contains a [`ToolError`], preserving the tool name and original error. fn with_tool(self, tool: &'static str) -> Result { self.map_err(|source| AgentBuildError::ToolSettingsValidation { tool, source }) } diff --git a/src/llm-coding-tools-serdesai/src/convert.rs b/src/llm-coding-tools-serdesai/src/convert.rs index 61240fde..e98d2a9c 100644 --- a/src/llm-coding-tools-serdesai/src/convert.rs +++ b/src/llm-coding-tools-serdesai/src/convert.rs @@ -47,6 +47,10 @@ fn output_to_return(output: ToolOutput) -> ToolReturn { /// /// [`ToolResult`]: llm_coding_tools_core::ToolResult /// [`ToolResult`]: serdes_ai::tools::ToolResult +/// +/// # Errors +/// - Returns [`SerdesError`] when the core [`ToolResult`] contains a [`ToolError`], +/// converted via [`core_error_to_serdes`]. #[inline] pub fn to_serdes_result( tool_name: &str,