Skip to main content

xcstrings_mcp/
error.rs

1use std::path::PathBuf;
2
3#[derive(Debug, thiserror::Error)]
4pub enum XcStringsError {
5    #[error("file not found: {path}")]
6    FileNotFound { path: PathBuf },
7
8    #[error("invalid path {path}: {reason}")]
9    InvalidPath { path: PathBuf, reason: String },
10
11    #[error("not an .xcstrings file: {path}")]
12    NotXcStrings { path: PathBuf },
13
14    #[error("invalid format: {0}")]
15    InvalidFormat(String),
16
17    #[error("JSON parse error: {0}")]
18    JsonParse(String),
19
20    #[error("locale not found: {0}")]
21    LocaleNotFound(String),
22
23    #[error("locale already exists: {0}")]
24    LocaleAlreadyExists(String),
25
26    #[error("no active file — call parse_xcstrings first")]
27    NoActiveFile,
28
29    #[error("validation failed: {0}")]
30    ValidationFailed(String),
31
32    #[error("format specifier mismatch: {0}")]
33    FormatSpecifierMismatch(String),
34
35    #[error("missing plural form: {0}")]
36    MissingPluralForm(String),
37
38    #[error("invalid batch size: {0}")]
39    InvalidBatchSize(String),
40
41    #[error("file too large: {size_mb}MB (max {max_mb}MB)")]
42    FileTooLarge { size_mb: u64, max_mb: u64 },
43
44    #[error("key marked as shouldTranslate=false: {0}")]
45    ShouldNotTranslate(String),
46
47    #[error("file is locked by another process (likely Xcode): {path}")]
48    FileLocked { path: PathBuf },
49
50    #[error(transparent)]
51    Io(#[from] std::io::Error),
52
53    #[error(transparent)]
54    Serde(#[from] serde_json::Error),
55
56    #[error("unexpected error: {0}")]
57    Unexpected(String),
58}
59
60impl From<XcStringsError> for rmcp::model::ErrorData {
61    fn from(e: XcStringsError) -> Self {
62        rmcp::model::ErrorData::new(rmcp::model::ErrorCode::INTERNAL_ERROR, e.to_string(), None)
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn error_converts_to_mcp_error() {
72        let err = XcStringsError::NoActiveFile;
73        let mcp_err: rmcp::model::ErrorData = err.into();
74        assert!(mcp_err.message.contains("no active file"));
75    }
76
77    #[test]
78    fn error_display() {
79        let err = XcStringsError::FileNotFound {
80            path: PathBuf::from("/test.xcstrings"),
81        };
82        assert!(err.to_string().contains("/test.xcstrings"));
83
84        let err = XcStringsError::FileTooLarge {
85            size_mb: 100,
86            max_mb: 50,
87        };
88        assert!(err.to_string().contains("100"));
89        assert!(err.to_string().contains("50"));
90    }
91}