Skip to main content

diskann_tools/utils/
cmd_tool_error.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6use std::{error::Error, fmt};
7
8/// Error that wraps all errors generated by command-line tools
9#[derive(PartialEq)]
10pub struct CMDToolError {
11    pub details: String,
12}
13
14impl fmt::Display for CMDToolError {
15    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
16        write!(f, "{}", self.details)
17    }
18}
19
20// Implement `Debug` in terms of `Display` so when `CMDToolError` hits the top level, we
21// get proper formatting.
22impl fmt::Debug for CMDToolError {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        <Self as fmt::Display>::fmt(self, f)
25    }
26}
27
28impl Error for CMDToolError {
29    fn description(&self) -> &str {
30        &self.details
31    }
32}
33
34impl From<std::io::Error> for CMDToolError {
35    fn from(err: std::io::Error) -> Self {
36        CMDToolError {
37            details: err.to_string(),
38        }
39    }
40}
41
42impl From<rand_distr::NormalError> for CMDToolError {
43    fn from(err: rand_distr::NormalError) -> Self {
44        CMDToolError {
45            details: err.to_string(),
46        }
47    }
48}
49impl From<diskann::ANNError> for CMDToolError {
50    fn from(err: diskann::ANNError) -> Self {
51        CMDToolError {
52            details: err.to_string(),
53        }
54    }
55}
56impl From<diskann_utils::io::ReadBinError> for CMDToolError {
57    fn from(err: diskann_utils::io::ReadBinError) -> Self {
58        CMDToolError {
59            details: err.to_string(),
60        }
61    }
62}
63impl From<diskann_utils::io::SaveBinError> for CMDToolError {
64    fn from(err: diskann_utils::io::SaveBinError) -> Self {
65        CMDToolError {
66            details: err.to_string(),
67        }
68    }
69}
70impl From<diskann::graph::config::ConfigError> for CMDToolError {
71    fn from(err: diskann::graph::config::ConfigError) -> Self {
72        CMDToolError {
73            details: err.to_string(),
74        }
75    }
76}
77
78impl From<diskann_label_filter::JsonlReadError> for CMDToolError {
79    fn from(err: diskann_label_filter::JsonlReadError) -> Self {
80        CMDToolError {
81            details: err.to_string(),
82        }
83    }
84}
85
86impl<T, U> From<diskann_utils::io::MetadataError<T, U>> for CMDToolError
87where
88    T: std::error::Error + Send + Sync + 'static,
89    U: std::error::Error + Send + Sync + 'static,
90{
91    fn from(err: diskann_utils::io::MetadataError<T, U>) -> Self {
92        // Leverage the existing conversion chain: MetadataError -> ANNError -> CMDToolError
93        let ann_error: diskann::ANNError = err.into();
94        ann_error.into()
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_cmd_tool_error_display() {
104        let error = CMDToolError {
105            details: "test error".to_string(),
106        };
107        assert_eq!(format!("{}", error), "test error");
108    }
109
110    #[test]
111    fn test_cmd_tool_error_debug() {
112        let error = CMDToolError {
113            details: "test error".to_string(),
114        };
115        assert_eq!(format!("{:?}", error), "test error");
116    }
117
118    #[test]
119    fn test_cmd_tool_error_description() {
120        let error = CMDToolError {
121            details: "test error".to_string(),
122        };
123        #[allow(deprecated)]
124        {
125            assert_eq!(error.description(), "test error");
126        }
127    }
128
129    #[test]
130    fn test_from_io_error() {
131        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
132        let cmd_error: CMDToolError = io_error.into();
133        assert!(cmd_error.details.contains("file not found"));
134    }
135
136    #[test]
137    fn test_from_normal_error() {
138        let normal_error = rand_distr::NormalError::BadVariance;
139        let cmd_error: CMDToolError = normal_error.into();
140        // Just verify the error was converted and has some details
141        assert!(!cmd_error.details.is_empty());
142    }
143
144    #[test]
145    fn test_from_ann_error() {
146        use diskann::ANNErrorKind;
147        let ann_error = diskann::ANNError::new(
148            ANNErrorKind::IndexError,
149            std::io::Error::other("test error"),
150        );
151        let cmd_error: CMDToolError = ann_error.into();
152        assert!(cmd_error.details.contains("test error"));
153    }
154
155    #[test]
156    fn test_from_config_error() {
157        // Construct an invalid graph config to trigger a real ConfigError
158        let config_error = diskann::graph::config::Builder::new(
159            10,
160            diskann::graph::config::MaxDegree::new(0),
161            50,
162            diskann::graph::config::PruneKind::TriangleInequality,
163        )
164        .build()
165        .unwrap_err();
166        let cmd_error: CMDToolError = config_error.into();
167        // Just verify the error was converted and has some details
168        assert!(!cmd_error.details.is_empty());
169    }
170
171    #[test]
172    fn test_from_jsonl_read_error() {
173        use diskann_label_filter::JsonlReadError;
174        let jsonl_error = JsonlReadError::IoError(std::io::Error::new(
175            std::io::ErrorKind::InvalidData,
176            "invalid jsonl",
177        ));
178        let cmd_error: CMDToolError = jsonl_error.into();
179        assert!(cmd_error.details.contains("invalid jsonl"));
180    }
181}