Skip to main content

use_os_family/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7/// Broad operating system family vocabulary.
8#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum OsFamily {
10    /// Unix-like operating systems.
11    Unix,
12    /// Microsoft Windows operating systems.
13    Windows,
14    /// WebAssembly and WASI targets.
15    Wasm,
16    /// Unknown operating system family.
17    Unknown,
18    /// A caller-defined family name.
19    Custom(String),
20}
21
22impl fmt::Display for OsFamily {
23    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
24        match self {
25            Self::Unix => formatter.write_str("unix"),
26            Self::Windows => formatter.write_str("windows"),
27            Self::Wasm => formatter.write_str("wasm"),
28            Self::Unknown => formatter.write_str("unknown"),
29            Self::Custom(value) => formatter.write_str(value),
30        }
31    }
32}
33
34impl FromStr for OsFamily {
35    type Err = OsFamilyParseError;
36
37    fn from_str(value: &str) -> Result<Self, Self::Err> {
38        let trimmed = value.trim();
39
40        if trimmed.is_empty() {
41            return Err(OsFamilyParseError::Empty);
42        }
43
44        let key = trimmed.to_ascii_lowercase().replace(['_', ' '], "-");
45
46        match key.as_str() {
47            "unix" | "linux" | "macos" | "mac-os" | "darwin" | "bsd" | "freebsd" | "openbsd"
48            | "netbsd" => Ok(Self::Unix),
49            "windows" | "win" | "win32" | "win64" | "mswindows" => Ok(Self::Windows),
50            "wasm" | "webassembly" | "wasi" | "wasip1" | "wasip2" => Ok(Self::Wasm),
51            "unknown" => Ok(Self::Unknown),
52            _ => Ok(Self::Custom(trimmed.to_string())),
53        }
54    }
55}
56
57/// Error returned when parsing an OS family fails.
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub enum OsFamilyParseError {
60    /// The family name was empty after trimming whitespace.
61    Empty,
62}
63
64impl fmt::Display for OsFamilyParseError {
65    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66        match self {
67            Self::Empty => formatter.write_str("OS family cannot be empty"),
68        }
69    }
70}
71
72impl Error for OsFamilyParseError {}
73
74#[cfg(test)]
75mod tests {
76    use super::{OsFamily, OsFamilyParseError};
77
78    #[test]
79    fn parses_known_families() -> Result<(), OsFamilyParseError> {
80        assert_eq!("unix".parse::<OsFamily>()?, OsFamily::Unix);
81        assert_eq!("windows".parse::<OsFamily>()?, OsFamily::Windows);
82        assert_eq!("wasm".parse::<OsFamily>()?, OsFamily::Wasm);
83        assert_eq!("unknown".parse::<OsFamily>()?, OsFamily::Unknown);
84        Ok(())
85    }
86
87    #[test]
88    fn parses_obvious_aliases() -> Result<(), OsFamilyParseError> {
89        assert_eq!("linux".parse::<OsFamily>()?, OsFamily::Unix);
90        assert_eq!("darwin".parse::<OsFamily>()?, OsFamily::Unix);
91        assert_eq!("win32".parse::<OsFamily>()?, OsFamily::Windows);
92        assert_eq!("wasi".parse::<OsFamily>()?, OsFamily::Wasm);
93        Ok(())
94    }
95
96    #[test]
97    fn stores_custom_families() -> Result<(), OsFamilyParseError> {
98        assert_eq!(
99            "plan9".parse::<OsFamily>()?,
100            OsFamily::Custom("plan9".to_string())
101        );
102        Ok(())
103    }
104
105    #[test]
106    fn rejects_empty_family_names() {
107        assert_eq!("  ".parse::<OsFamily>(), Err(OsFamilyParseError::Empty));
108    }
109
110    #[test]
111    fn displays_canonical_names() {
112        assert_eq!(OsFamily::Unix.to_string(), "unix");
113        assert_eq!(OsFamily::Windows.to_string(), "windows");
114        assert_eq!(OsFamily::Wasm.to_string(), "wasm");
115        assert_eq!(
116            OsFamily::Custom("custom-os".to_string()).to_string(),
117            "custom-os"
118        );
119    }
120}