1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
13pub enum IpStack {
14 #[default]
16 None,
17 V4Only,
19 V6Only,
21 DualStack,
23}
24
25impl IpStack {
26 pub(crate) const fn from_flags(have_v4: bool, have_v6: bool) -> Self {
28 match (have_v4, have_v6) {
29 (true, true) => IpStack::DualStack,
30 (true, false) => IpStack::V4Only,
31 (false, true) => IpStack::V6Only,
32 (false, false) => IpStack::None,
33 }
34 }
35
36 pub const fn name(&self) -> &'static str {
41 match self {
42 IpStack::None => "None",
43 IpStack::V4Only => "V4Only",
44 IpStack::V6Only => "V6Only",
45 IpStack::DualStack => "DualStack",
46 }
47 }
48
49 pub const fn has_ipv4(&self) -> bool {
52 matches!(self, IpStack::V4Only | IpStack::DualStack)
53 }
54
55 pub const fn has_ipv6(&self) -> bool {
58 matches!(self, IpStack::V6Only | IpStack::DualStack)
59 }
60
61 pub const fn is_dual_stack(&self) -> bool {
63 matches!(self, IpStack::DualStack)
64 }
65}
66
67impl fmt::Display for IpStack {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 f.write_str(self.name())
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::IpStack;
78
79 #[test]
80 fn from_flags_covers_all_combinations() {
81 assert_eq!(IpStack::from_flags(false, false), IpStack::None);
82 assert_eq!(IpStack::from_flags(true, false), IpStack::V4Only);
83 assert_eq!(IpStack::from_flags(false, true), IpStack::V6Only);
84 assert_eq!(IpStack::from_flags(true, true), IpStack::DualStack);
85 }
86
87 #[test]
88 fn default_is_none() {
89 assert_eq!(IpStack::default(), IpStack::None);
90 }
91
92 #[test]
93 fn has_ipv4_is_true_only_for_v4_and_dual() {
94 assert!(!IpStack::None.has_ipv4());
95 assert!(IpStack::V4Only.has_ipv4());
96 assert!(!IpStack::V6Only.has_ipv4());
97 assert!(IpStack::DualStack.has_ipv4());
98 }
99
100 #[test]
101 fn has_ipv6_is_true_only_for_v6_and_dual() {
102 assert!(!IpStack::None.has_ipv6());
103 assert!(!IpStack::V4Only.has_ipv6());
104 assert!(IpStack::V6Only.has_ipv6());
105 assert!(IpStack::DualStack.has_ipv6());
106 }
107
108 #[test]
109 fn is_dual_stack_is_true_only_for_dual() {
110 assert!(!IpStack::None.is_dual_stack());
111 assert!(!IpStack::V4Only.is_dual_stack());
112 assert!(!IpStack::V6Only.is_dual_stack());
113 assert!(IpStack::DualStack.is_dual_stack());
114 }
115
116 #[test]
117 fn name_and_display_agree() {
118 for variant in [
119 IpStack::None,
120 IpStack::V4Only,
121 IpStack::V6Only,
122 IpStack::DualStack,
123 ] {
124 assert_eq!(variant.name(), variant.to_string());
125 }
126 assert_eq!(IpStack::DualStack.name(), "DualStack");
127 }
128
129 #[test]
130 fn serde_round_trips() {
131 for variant in [
132 IpStack::None,
133 IpStack::V4Only,
134 IpStack::V6Only,
135 IpStack::DualStack,
136 ] {
137 let json = serde_json::to_string(&variant).expect("serialize");
138 let back: IpStack = serde_json::from_str(&json).expect("deserialize");
139 assert_eq!(variant, back);
140 }
141 }
142}