scl_lib/api_objects/
separation_context.rs1use crate::api_objects::{
3 Error, MetaData, Result, SclName, SclObject, TransitionError, ValueSpaceExhaustedError,
4};
5use serde::{Deserialize, Serialize};
6use std::fmt::{Display, Formatter};
7
8#[cfg(feature = "openapi")]
9use schemars::JsonSchema;
10
11#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
12#[cfg_attr(feature = "openapi", derive(JsonSchema))]
13#[serde(rename_all = "camelCase")]
14pub struct SeparationContext {
15 #[serde(flatten)]
16 pub metadata: MetaData,
17
18 #[serde(default = "VlanTag::deserialization_default")]
21 pub vlan_tag: VlanTag,
22}
23
24impl SclObject for SeparationContext {
25 const PREFIX: &'static str = "/scs";
27
28 fn name(&self) -> &SclName {
29 &self.metadata.name
30 }
31
32 fn separation_context(&self) -> Option<&SclName> {
47 Some(&self.metadata.name)
48 }
49
50 fn api_endpoint<'a>(
63 sc: impl Into<Option<&'a str>>,
64 name: impl Into<Option<&'a str>>,
65 ) -> String {
66 if let Some(value) = sc.into().or_else(|| name.into()) {
67 format!("{}/{}", Self::PREFIX, value)
68 } else {
69 Self::PREFIX.into()
70 }
71 }
72
73 fn db_key<'a>(sc: impl Into<Option<&'a str>>, name: impl Into<Option<&'a str>>) -> String {
86 if let Some(value) = sc.into().or_else(|| name.into()) {
87 format!("{}/{}", Self::PREFIX, value)
88 } else {
89 Self::PREFIX.into()
90 }
91 }
92
93 fn metadata(&self) -> &MetaData {
94 &self.metadata
95 }
96
97 fn metadata_mut(&mut self) -> &mut MetaData {
98 &mut self.metadata
99 }
100
101 fn referenced_db_keys(&self) -> Vec<String> {
102 vec![]
103 }
104
105 fn validate_fields_before_create(&self) -> Result<()> {
106 self.metadata.validate_fields_before_create()
107 }
108
109 fn validate_fields_before_update(
110 _current_db_state: &Self,
111 _proposed_new_state: &Self,
112 ) -> Result<()> {
113 Err(TransitionError::Other("Regular updates are not supported!".to_string()).into())
114 }
115}
116
117#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
118#[cfg_attr(feature = "openapi", derive(JsonSchema))]
119#[serde(try_from = "u16")]
120pub struct VlanTag(#[cfg_attr(feature = "openapi", validate(range(min = 1, max = 4094)))] u16);
121
122impl VlanTag {
123 pub fn value(&self) -> u16 {
124 self.0
125 }
126
127 fn deserialization_default() -> Self {
131 Self::try_from(1).unwrap()
132 }
133}
134
135impl TryFrom<u16> for VlanTag {
136 type Error = Error;
137
138 fn try_from(value: u16) -> Result<Self> {
139 if value > 0 && value < 4095 {
140 Ok(Self(value))
141 } else {
142 Err(Error::ParsingFailed(
143 "Out of range [0 < VlanTag < 4095]".to_string(),
144 ))
145 }
146 }
147}
148
149impl Display for VlanTag {
150 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
151 write!(f, "{}", self.0)
152 }
153}
154
155#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
158#[cfg_attr(feature = "openapi", derive(JsonSchema))]
159pub struct AvailableVlanTags {
160 tags: Vec<VlanTag>,
161
162 pub metadata: MetaData,
164}
165
166impl AvailableVlanTags {
167 pub fn take(&mut self) -> Result<VlanTag> {
168 self.tags
169 .pop()
170 .ok_or_else(|| ValueSpaceExhaustedError::NoFreeVlanTagLeft.into())
171 }
172
173 pub fn free(&mut self, vlan_tag: VlanTag) {
174 assert!(!self.tags.contains(&vlan_tag)); self.tags.push(vlan_tag);
176 }
177}
178
179impl Default for AvailableVlanTags {
180 fn default() -> Self {
181 Self {
182 tags: (1..=4094).map(|n| VlanTag::try_from(n).unwrap()).collect(),
183 metadata: MetaData::new(SclName::try_from("vlan-tags").unwrap()),
184 }
185 }
186}
187
188impl SclObject for AvailableVlanTags {
189 const PREFIX: &'static str = "/vars/vlan-tags";
191
192 fn name(&self) -> &SclName {
193 &self.metadata.name
194 }
195
196 fn api_endpoint<'a>(
208 _sc: impl Into<Option<&'a str>>,
209 _name: impl Into<Option<&'a str>>,
210 ) -> String {
211 Self::PREFIX.into()
212 }
213
214 fn db_key<'a>(_sc: impl Into<Option<&'a str>>, _name: impl Into<Option<&'a str>>) -> String {
226 Self::PREFIX.into()
227 }
228
229 fn metadata(&self) -> &MetaData {
230 &self.metadata
231 }
232
233 fn metadata_mut(&mut self) -> &mut MetaData {
234 &mut self.metadata
235 }
236
237 fn referenced_db_keys(&self) -> Vec<String> {
238 vec![]
239 }
240
241 fn validate_fields_before_create(&self) -> Result<()> {
242 if *self != Self::default() {
243 Err(Error::IllegalInitialValue(
244 "VLAN tags have been initialized in a wrong way!".to_string(),
245 ))
246 } else {
247 Ok(())
248 }
249 }
250
251 fn validate_fields_before_update(
252 _current_db_state: &Self,
253 _proposed_new_state: &Self,
254 ) -> Result<()> {
255 Err(TransitionError::Other("VLAN tags may not be updated directly!".to_string()).into())
257 }
258}
259
260#[cfg(test)]
261mod test {
262 use super::*;
263 use crate::api_objects::test::{
264 detect_invalid_metadata_create_mutations, detect_invalid_metadata_update_mutations,
265 detect_invalid_update_mutations, parse_json_dir,
266 };
267 use std::path::Path;
268
269 #[test]
270 fn parse_sample_json() {
271 parse_json_dir::<SeparationContext>(Path::new("../test/sample_json/scs"));
272 }
273
274 #[test]
275 fn vlan_tag_try_from() {
276 assert!(VlanTag::try_from(0).is_err());
277 assert!(VlanTag::try_from(1).is_ok());
278 assert!(VlanTag::try_from(2).is_ok());
279 assert!(VlanTag::try_from(4093).is_ok());
280 assert!(VlanTag::try_from(4094).is_ok());
281 assert!(VlanTag::try_from(4095).is_err());
282 }
283
284 #[test]
285 fn vlan_tag_deserialization_default() {
286 let _ = VlanTag::deserialization_default(); }
288
289 fn example_sc() -> SeparationContext {
290 SeparationContext {
291 metadata: MetaData {
292 name: SclName::try_from("example").unwrap(),
293 resource_version: Default::default(),
294 deletion_mark: None,
295 },
296 vlan_tag: VlanTag::try_from(42).unwrap(),
297 }
298 }
299
300 #[test]
301 fn validate_sc_fields_before_create() {
302 let sc = example_sc();
303 assert!(sc.validate_fields_before_create().is_ok());
304 }
305
306 #[test]
307 fn validate_sc_fields_before_update() {
308 let current = example_sc();
309
310 #[allow(clippy::type_complexity)]
311 let invalid_mutations: Vec<Box<dyn Fn(&mut SeparationContext)>> = vec![
312 Box::new(|t| t.vlan_tag = VlanTag::try_from(999).unwrap()),
313 Box::new(|_| ()),
314 ];
315
316 detect_invalid_update_mutations(¤t, ¤t, invalid_mutations);
317 }
318
319 #[test]
320 fn detect_invalid_metadata_mutations() {
321 let t = example_sc();
322 detect_invalid_metadata_create_mutations(&t);
323 detect_invalid_metadata_update_mutations(&t);
324 }
325
326 #[test]
327 fn validate_avt_fields_before_create() {
328 let mut tags = AvailableVlanTags::default();
329 assert!(tags.validate_fields_before_create().is_ok());
330 let _ = tags.take().unwrap();
331 assert!(tags.validate_fields_before_create().is_err());
332 }
333
334 #[test]
335 fn validate_avt_fields_before_update() {
336 let mut current = AvailableVlanTags::default();
337 let _ = current.take().unwrap();
338
339 #[allow(clippy::type_complexity)]
340 let invalid_mutations: Vec<Box<dyn Fn(&mut AvailableVlanTags)>> = vec![
341 Box::new(|t| {
342 let _ = t.take().unwrap();
343 }),
344 Box::new(|t| t.free(VlanTag::try_from(4094).unwrap())),
345 Box::new(|_| ()),
346 ];
347
348 detect_invalid_update_mutations(¤t, ¤t, invalid_mutations);
349 }
350}