Skip to main content

tauri_plugin_biometric/
error.rs

1// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5use serde::{Serialize, ser::Serializer};
6
7/// Alias for a [`Result`](std::result::Result) with the error type [`Error`].
8pub type Result<T> = std::result::Result<T, Error>;
9
10/// The error types returned by this plugin.
11#[derive(Debug, thiserror::Error)]
12pub enum Error {
13    /// An I/O error occurred.
14    #[error(transparent)]
15    Io(#[from] std::io::Error),
16    /// The invocation of the underlying Android or iOS plugin failed, for example because
17    /// biometric authentication was unavailable, was not enrolled, failed, or was canceled by
18    /// the user.
19    #[cfg(mobile)]
20    #[error(transparent)]
21    PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
22    /// The authentication prompt failed or was canceled by the user or the system.
23    #[cfg(desktop)]
24    #[error("Biometric authentication failed: {message}")]
25    AuthenticationFailed {
26        /// A human-readable description of the failure, as reported by the operating system.
27        message: String,
28        /// The error code, using the same identifiers as [`Status::error_code`](crate::Status::error_code).
29        code: Option<String>,
30    },
31    /// Biometric authentication cannot be used on this device.
32    #[cfg(desktop)]
33    #[error("Biometric unavailable: {message}")]
34    Unavailable {
35        /// A human-readable reason why biometric authentication is unavailable.
36        message: String,
37        /// The error code, using the same identifiers as [`Status::error_code`](crate::Status::error_code).
38        code: Option<String>,
39    },
40}
41
42impl Serialize for Error {
43    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
44    where
45        S: Serializer,
46    {
47        match self {
48            // Same `{ message, code }` shape the Android and iOS plugins reject with.
49            #[cfg(desktop)]
50            Self::AuthenticationFailed { message, code } | Self::Unavailable { message, code } => {
51                #[derive(Serialize)]
52                struct Payload<'a> {
53                    message: &'a str,
54                    #[serde(skip_serializing_if = "Option::is_none")]
55                    code: Option<&'a str>,
56                }
57                Payload {
58                    message,
59                    code: code.as_deref(),
60                }
61                .serialize(serializer)
62            }
63            _ => serializer.serialize_str(self.to_string().as_ref()),
64        }
65    }
66}
67
68#[cfg(all(test, desktop))]
69mod tests {
70    use super::Error;
71
72    #[test]
73    fn desktop_errors_serialize_like_mobile_rejections() {
74        let err = Error::AuthenticationFailed {
75            message: "Canceled by user.".into(),
76            code: Some("userCancel".into()),
77        };
78        assert_eq!(
79            serde_json::to_value(&err).unwrap(),
80            serde_json::json!({ "message": "Canceled by user.", "code": "userCancel" })
81        );
82
83        let err = Error::Unavailable {
84            message: "No biometry".into(),
85            code: None,
86        };
87        assert_eq!(
88            serde_json::to_value(&err).unwrap(),
89            serde_json::json!({ "message": "No biometry" })
90        );
91    }
92}