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