use super::error::{ArtefactError, Result};
use serde::Serialize;
use std::fmt;
const SUPPORTED_TARGETS: &[&str] = &[
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"x86_64-pc-windows-msvc",
];
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
#[serde(transparent)]
pub struct TargetTriple(String);
impl TargetTriple {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_inner(self) -> String {
self.0
}
#[must_use]
pub fn supported() -> &'static [&'static str] {
SUPPORTED_TARGETS
}
#[must_use]
pub fn library_extension(&self) -> &'static str {
if self.is_windows() {
".dll"
} else if self.is_darwin() {
".dylib"
} else {
".so"
}
}
#[must_use]
pub fn library_prefix(&self) -> &'static str {
if self.is_windows() { "" } else { "lib" }
}
#[must_use]
pub fn is_windows(&self) -> bool {
self.0.contains("windows")
}
fn is_darwin(&self) -> bool {
self.0.contains("darwin")
}
}
impl<'de> serde::Deserialize<'de> for TargetTriple {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let s = <String as serde::Deserialize>::deserialize(deserializer)?;
Self::try_from(s).map_err(serde::de::Error::custom)
}
}
impl TryFrom<&str> for TargetTriple {
type Error = ArtefactError;
fn try_from(value: &str) -> Result<Self> {
if SUPPORTED_TARGETS.contains(&value) {
Ok(Self(value.to_owned()))
} else {
Err(ArtefactError::UnsupportedTarget {
value: value.to_owned(),
expected: SUPPORTED_TARGETS.join(", "),
})
}
}
}
impl TryFrom<String> for TargetTriple {
type Error = ArtefactError;
fn try_from(value: String) -> Result<Self> {
Self::try_from(value.as_str())
}
}
impl AsRef<str> for TargetTriple {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for TargetTriple {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[test]
fn accepts_all_supported_targets() {
for target in SUPPORTED_TARGETS {
let triple = TargetTriple::try_from(*target);
assert!(triple.is_ok(), "expected {target} to be accepted");
assert_eq!(triple.expect("checked above").as_str(), *target);
}
}
#[test]
fn rejects_unsupported_target() {
let result = TargetTriple::try_from("wasm32-unknown-unknown");
assert!(result.is_err());
let err = result.expect_err("expected rejection of unsupported target");
assert!(
matches!(err, ArtefactError::UnsupportedTarget { .. }),
"expected UnsupportedTarget, got {err:?}"
);
}
#[test]
fn rejects_empty_string() {
let result = TargetTriple::try_from("");
assert!(result.is_err());
}
#[test]
fn display_shows_inner_value() {
let triple = TargetTriple::try_from("aarch64-apple-darwin").expect("known good");
assert_eq!(format!("{triple}"), "aarch64-apple-darwin");
}
#[test]
fn from_owned_string_accepts_valid() {
let triple = TargetTriple::try_from(String::from("x86_64-pc-windows-msvc"));
assert!(triple.is_ok());
}
#[test]
fn from_owned_string_rejects_invalid() {
let result = TargetTriple::try_from(String::from("mips-unknown-linux"));
assert!(result.is_err());
}
#[test]
fn supported_returns_all_five_targets() {
let supported = TargetTriple::supported();
assert_eq!(supported.len(), 5);
}
#[rstest]
#[case::linux_x86("x86_64-unknown-linux-gnu", ".so", "lib")]
#[case::linux_arm("aarch64-unknown-linux-gnu", ".so", "lib")]
#[case::macos_x86("x86_64-apple-darwin", ".dylib", "lib")]
#[case::macos_arm("aarch64-apple-darwin", ".dylib", "lib")]
#[case::windows("x86_64-pc-windows-msvc", ".dll", "")]
fn library_naming_for_target(#[case] triple: &str, #[case] ext: &str, #[case] prefix: &str) {
let t = TargetTriple::try_from(triple).expect("valid");
assert_eq!(t.library_extension(), ext);
assert_eq!(t.library_prefix(), prefix);
}
#[test]
fn serde_round_trip() {
let t = TargetTriple::try_from("x86_64-unknown-linux-gnu").expect("valid");
let json = serde_json::to_string(&t).expect("serialize");
let back: TargetTriple = serde_json::from_str(&json).expect("deserialize");
assert_eq!(t, back);
}
#[test]
fn deserialize_rejects_unsupported() {
let json = r#""wasm32-unknown-unknown""#;
let result: std::result::Result<TargetTriple, _> = serde_json::from_str(json);
assert!(result.is_err());
}
}