1use crate::arch::Arch;
33use crate::error::Error;
34use gentoo_interner::{DefaultInterner, Interned, Interner};
35use std::fmt;
36use std::str::FromStr;
37
38#[derive(Debug, PartialEq, Eq)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
61#[cfg_attr(feature = "serde", serde(bound = ""))]
62pub struct Variant<I = DefaultInterner>
63where
64 I: Interner,
65{
66 pub arch: Arch<I>,
68 flavor: Interned<I>,
70}
71
72impl<I: Interner> Clone for Variant<I> {
73 fn clone(&self) -> Self {
74 Self {
75 arch: self.arch.clone(),
76 flavor: self.flavor.clone(),
77 }
78 }
79}
80
81impl<I: Interner> Copy for Variant<I> where Interned<I>: Copy {}
82
83impl<I: Interner> Variant<I> {
84 pub(crate) fn new(arch: Arch<I>, flavor: &str) -> Self {
86 Self {
87 arch,
88 flavor: Interned::intern(flavor),
89 }
90 }
91
92 pub fn parse(arch: &str, flavor: &str) -> Result<Self, Error> {
94 let arch = Arch::intern(arch);
95 Ok(Self::new(arch, flavor))
96 }
97
98 pub fn flavor(&self) -> &str {
100 self.flavor.resolve()
101 }
102
103 pub fn keyword(&self) -> &str {
105 self.arch.as_str()
106 }
107}
108
109impl<I: Interner> fmt::Display for Variant<I> {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 write!(f, "{}-{}", self.arch.as_str(), self.flavor())
112 }
113}
114
115impl<I: Interner> FromStr for Variant<I> {
116 type Err = Error;
117
118 fn from_str(s: &str) -> Result<Self, Self::Err> {
119 let (arch_str, flavor_str) = s.split_once('-').ok_or_else(|| {
120 Error::ParseError(format!(
121 "Invalid variant format: expected arch-flavor, got '{s}'"
122 ))
123 })?;
124 Self::parse(arch_str, flavor_str)
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::arch::KnownArch;
132
133 #[test]
134 fn test_variant_creation() {
135 let variant: Variant = Variant::new(Arch::Known(KnownArch::X86_64), "systemd");
136 assert_eq!(variant.arch, Arch::Known(KnownArch::X86_64));
137 assert_eq!(variant.flavor(), "systemd");
138 }
139
140 #[test]
141 fn test_variant_keyword() {
142 assert_eq!(
143 Variant::new(<Arch>::Known(KnownArch::AArch64), "systemd").keyword(),
144 "arm64"
145 );
146 assert_eq!(
147 Variant::new(<Arch>::Known(KnownArch::X86), "openrc").keyword(),
148 "x86"
149 );
150 }
151
152 #[test]
153 fn test_variant_parsing() {
154 let variant: Variant = Variant::parse("amd64", "systemd").unwrap();
155 assert_eq!(variant.arch, Arch::Known(KnownArch::X86_64));
156
157 let variant: Variant = Variant::parse("arm", "openrc").unwrap();
158 assert_eq!(variant.arch, Arch::Known(KnownArch::Arm));
159 }
160
161 #[test]
162 fn test_from_str() {
163 let variant = "arm64-openrc".parse::<Variant>().unwrap();
164 assert!(matches!(variant.arch, Arch::Known(KnownArch::AArch64)));
165
166 let variant = "amd64-musl-hardened-openrc".parse::<Variant>().unwrap();
167 assert_eq!(variant.arch, Arch::Known(KnownArch::X86_64));
168 assert_eq!(variant.flavor(), "musl-hardened-openrc");
169
170 assert!("arm64".parse::<Variant>().is_err());
171 }
172
173 #[test]
174 fn test_display() {
175 assert_eq!(
176 Variant::new(<Arch>::Known(KnownArch::AArch64), "openrc").to_string(),
177 "arm64-openrc"
178 );
179 assert_eq!(
180 Variant::new(<Arch>::Known(KnownArch::X86_64), "musl-hardened-openrc").to_string(),
181 "amd64-musl-hardened-openrc"
182 );
183 }
184
185 #[cfg(feature = "serde")]
188 mod serde {
189 use super::*;
190
191 #[test]
192 fn variant_known_arch_serializes_as_strings() {
193 let variant: Variant = "amd64-systemd".parse().unwrap();
194 let json = serde_json::to_string(&variant).unwrap();
195 assert_eq!(json, r#"{"arch":"amd64","flavor":"systemd"}"#);
196 }
197
198 #[test]
199 fn variant_known_arch_roundtrip() {
200 let original: Variant = "amd64-systemd".parse().unwrap();
201 let json = serde_json::to_string(&original).unwrap();
202 let restored: Variant = serde_json::from_str(&json).unwrap();
203 assert_eq!(original, restored);
204 assert_eq!(restored.flavor(), "systemd");
205 }
206
207 #[test]
208 fn variant_exotic_arch_roundtrip() {
209 let original: Variant = "mymachine-openrc".parse().unwrap();
210 let json = serde_json::to_string(&original).unwrap();
211 assert_eq!(json, r#"{"arch":"mymachine","flavor":"openrc"}"#);
212 let restored: Variant = serde_json::from_str(&json).unwrap();
213 assert_eq!(original, restored);
214 }
215
216 #[test]
217 fn variant_complex_flavor_roundtrip() {
218 let original: Variant = "amd64-musl-hardened-openrc".parse().unwrap();
219 let json = serde_json::to_string(&original).unwrap();
220 assert_eq!(json, r#"{"arch":"amd64","flavor":"musl-hardened-openrc"}"#);
221 let restored: Variant = serde_json::from_str(&json).unwrap();
222 assert_eq!(original, restored);
223 }
224 }
225}