Skip to main content

translation/
translation_error.rs

1use std::error::Error;
2use std::fmt;
3
4use crate::ffi;
5
6const FAILURE_REASON_DELIMITER: &str = "";
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9/// Enumerates failures reported by Translation.framework and the Swift bridge.
10pub enum TranslationError {
11    /// An argument failed validation before reaching the framework.
12    InvalidArgument(String),
13    /// Translation.framework is unavailable on the current macOS version.
14    UnavailableOnThisMacOS(String),
15    /// The framework operation timed out.
16    TimedOut(String),
17    /// The source language is unsupported by Translation.framework.
18    UnsupportedSourceLanguage(String),
19    /// The target language is unsupported by Translation.framework.
20    UnsupportedTargetLanguage(String),
21    /// The source and target pairing is unsupported by Translation.framework.
22    UnsupportedLanguagePairing(String),
23    /// The framework could not identify the input language.
24    UnableToIdentifyLanguage(String),
25    /// The request contained no translatable content.
26    NothingToTranslate(String),
27    /// The session was already cancelled.
28    AlreadyCancelled(String),
29    /// Required translation resources are not installed.
30    NotInstalled(String),
31    /// The Swift bridge or Translation.framework returned an underlying error.
32    Framework(String),
33    /// An unknown status or payload was returned by the bridge.
34    Unknown(String),
35}
36
37impl TranslationError {
38    #[must_use]
39    /// Returns the primary error description from the framework payload.
40    pub fn error_description(&self) -> &str {
41        split_payload(self.message()).0
42    }
43
44    #[must_use]
45    /// Returns the framework failure reason, when one was provided.
46    pub fn failure_reason(&self) -> Option<&str> {
47        split_payload(self.message()).1
48    }
49
50    fn message(&self) -> &str {
51        match self {
52            Self::InvalidArgument(message)
53            | Self::UnavailableOnThisMacOS(message)
54            | Self::TimedOut(message)
55            | Self::UnsupportedSourceLanguage(message)
56            | Self::UnsupportedTargetLanguage(message)
57            | Self::UnsupportedLanguagePairing(message)
58            | Self::UnableToIdentifyLanguage(message)
59            | Self::NothingToTranslate(message)
60            | Self::AlreadyCancelled(message)
61            | Self::NotInstalled(message)
62            | Self::Framework(message)
63            | Self::Unknown(message) => message,
64        }
65    }
66
67    pub(crate) fn from_status_parts(
68        status: i32,
69        description: String,
70        failure_reason: Option<String>,
71    ) -> Self {
72        let message = encode_payload(description, failure_reason);
73        match status {
74            ffi::status::INVALID_ARGUMENT => Self::InvalidArgument(message),
75            ffi::status::UNAVAILABLE_ON_THIS_MACOS => Self::UnavailableOnThisMacOS(message),
76            ffi::status::TIMED_OUT => Self::TimedOut(message),
77            ffi::status::UNSUPPORTED_SOURCE_LANGUAGE => Self::UnsupportedSourceLanguage(message),
78            ffi::status::UNSUPPORTED_TARGET_LANGUAGE => Self::UnsupportedTargetLanguage(message),
79            ffi::status::UNSUPPORTED_LANGUAGE_PAIRING => Self::UnsupportedLanguagePairing(message),
80            ffi::status::UNABLE_TO_IDENTIFY_LANGUAGE => Self::UnableToIdentifyLanguage(message),
81            ffi::status::NOTHING_TO_TRANSLATE => Self::NothingToTranslate(message),
82            ffi::status::ALREADY_CANCELLED => Self::AlreadyCancelled(message),
83            ffi::status::NOT_INSTALLED => Self::NotInstalled(message),
84            ffi::status::FRAMEWORK_ERROR => Self::Framework(message),
85            _ => Self::Unknown(message),
86        }
87    }
88}
89
90impl fmt::Display for TranslationError {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        match self.failure_reason() {
93            Some(failure_reason) => {
94                write!(f, "{}: {failure_reason}", self.error_description())
95            }
96            None => f.write_str(self.error_description()),
97        }
98    }
99}
100
101impl Error for TranslationError {}
102
103fn encode_payload(description: String, failure_reason: Option<String>) -> String {
104    match failure_reason {
105        Some(failure_reason) if !failure_reason.is_empty() => {
106            format!("{description}{FAILURE_REASON_DELIMITER}{failure_reason}")
107        }
108        _ => description,
109    }
110}
111
112fn split_payload(message: &str) -> (&str, Option<&str>) {
113    match message.split_once(FAILURE_REASON_DELIMITER) {
114        Some((description, failure_reason)) if !failure_reason.is_empty() => {
115            (description, Some(failure_reason))
116        }
117        _ => (message, None),
118    }
119}