Skip to main content

ssh_cli/domain/
error.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Domain construction errors and secret helpers.
3#![forbid(unsafe_code)]
4
5use secrecy::{ExposeSecret, SecretString};
6use std::fmt;
7
8/// Error constructing a domain value (maps to [`crate::errors::SshCliError::Domain`]).
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct DomainError {
11    /// Stable machine-oriented code (field/invariant).
12    pub code: &'static str,
13    /// Human/agent message (no secrets).
14    pub message: String,
15}
16
17impl DomainError {
18    /// Builds a domain error.
19    #[must_use]
20    pub fn new(code: &'static str, message: impl Into<String>) -> Self {
21        Self {
22            code,
23            message: message.into(),
24        }
25    }
26}
27
28impl fmt::Display for DomainError {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        write!(f, "{}: {}", self.code, self.message)
31    }
32}
33
34impl std::error::Error for DomainError {}
35
36/// Maps [`DomainError`] into the crate result type via [`From`].
37#[inline]
38pub fn domain_err(e: DomainError) -> crate::errors::SshCliError {
39    e.into()
40}
41
42/// True when a [`SecretString`] holds a non-empty secret (G-TYPE-12 — single helper).
43#[must_use]
44pub fn secret_nonempty(s: &SecretString) -> bool {
45    !s.expose_secret().is_empty()
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn secret_nonempty_helper() {
54        assert!(!secret_nonempty(&SecretString::from(String::new())));
55        assert!(secret_nonempty(&SecretString::from("x".to_string())));
56    }
57
58    #[test]
59    fn domain_into_ssh_cli_error() {
60        let e: crate::errors::SshCliError =
61            DomainError::new("host", "empty").into();
62        assert!(matches!(e, crate::errors::SshCliError::Domain(_)));
63    }
64}