openleadr_wire/
resource_group.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_with::{DefaultOnNull, serde_as, skip_serializing_none};
4use std::{fmt::Display, str::FromStr};
5use validator::Validate;
6
7use crate::{
8 Identifier, IdentifierError, resource::ResourceId, target::Target, values_map::ValuesMap,
9};
10
11#[skip_serializing_none]
15#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
16#[serde(
17 rename_all = "camelCase",
18 tag = "objectType",
19 rename = "RESOURCE_GROUP"
20)]
21pub struct ResourceGroup {
22 pub id: ResourceGroupId,
24 #[serde(with = "crate::serde_rfc3339")]
26 pub created_date_time: DateTime<Utc>,
27 #[serde(with = "crate::serde_rfc3339")]
29 pub modification_date_time: DateTime<Utc>,
30
31 #[serde(flatten)]
32 #[validate(nested)]
33 pub content: BlResourceGroupRequest,
34}
35
36#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
37#[serde(tag = "type", content = "id", rename_all = "snake_case")]
38pub enum ResourceGroupChild {
39 ResourceGroup(ResourceGroupId),
40 VenResource(ResourceId),
41}
42
43impl ResourceGroupChild {
44 pub fn as_str(&self) -> &str {
45 match self {
46 ResourceGroupChild::VenResource(id) => id.as_str(),
47 ResourceGroupChild::ResourceGroup(id) => id.as_str(),
48 }
49 }
50}
51
52#[skip_serializing_none]
53#[serde_as]
54#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Validate)]
55#[serde(rename_all = "camelCase")]
56pub struct BlResourceGroupRequest {
57 #[serde(default)]
59 #[serde_as(deserialize_as = "DefaultOnNull")]
60 pub targets: Vec<Target>,
61 #[serde(deserialize_with = "crate::string_within_range_inclusive::<1, 128, _>")]
63 pub resource_group_name: String,
64 pub attributes: Option<Vec<ValuesMap>>,
66
67 #[serde(default)]
70 #[serde_as(deserialize_as = "DefaultOnNull")]
71 pub children: Vec<ResourceGroupChild>,
72}
73
74#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Hash, Eq)]
75pub struct ResourceGroupId(pub(crate) Identifier);
76
77impl Display for ResourceGroupId {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 write!(f, "{}", self.0)
80 }
81}
82
83impl ResourceGroupId {
84 pub fn as_str(&self) -> &str {
85 self.0.as_str()
86 }
87
88 pub fn new(identifier: &str) -> Option<Self> {
89 Some(Self(identifier.parse().ok()?))
90 }
91}
92
93impl FromStr for ResourceGroupId {
94 type Err = IdentifierError;
95
96 fn from_str(s: &str) -> Result<Self, Self::Err> {
97 Ok(Self(s.parse()?))
98 }
99}
100
101#[cfg(test)]
102mod test {
103 use crate::{resource::ResourceId, resource_group::ResourceGroupChild};
104
105 use super::{BlResourceGroupRequest, ResourceGroup, ResourceGroupId};
106
107 #[test]
108 fn example_roundtrip() {
109 let example = r#"
110 {
111 "id": "test-resource-group",
112 "createdDateTime": "2023-06-15T09:30:00Z",
113 "modificationDateTime": "2023-06-15T09:30:00Z",
114 "resourceGroupName": "RESOURCE_GROUP_0999",
115 "objectType": "RESOURCE_GROUP",
116 "targets": [
117 "resource_group_0999"
118 ],
119 "children": [
120 {
121 "type":"resource_group",
122 "id":"resource-group-1"
123 },
124 {
125 "type":"ven_resource",
126 "id":"ven-1"
127 }
128 ]
129 }"#;
130
131 let parsed = serde_json::from_str::<ResourceGroup>(example).unwrap();
132
133 let expected = ResourceGroup {
134 id: ResourceGroupId("test-resource-group".parse().unwrap()),
135 created_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
136 modification_date_time: "2023-06-15T09:30:00Z".parse().unwrap(),
137 content: BlResourceGroupRequest {
138 attributes: None,
139 targets: vec!["resource_group_0999".parse().unwrap()],
140 resource_group_name: "RESOURCE_GROUP_0999".to_string(),
141 children: vec![
142 ResourceGroupChild::ResourceGroup(
143 ResourceGroupId::new("resource-group-1").unwrap(),
144 ),
145 ResourceGroupChild::VenResource(ResourceId::new("ven-1").unwrap()),
146 ],
147 },
148 };
149
150 assert_eq!(expected, parsed);
151
152 let serialized = serde_json::to_string_pretty(&expected).unwrap();
153
154 let num_object_type = serialized
156 .as_bytes()
157 .windows("objectType".len())
158 .filter(|&w| w == "objectType".as_bytes())
159 .count();
160
161 assert_eq!(num_object_type, 1);
162
163 assert!(serialized.contains(r#""objectType": "RESOURCE_GROUP""#));
165
166 let parsed = serde_json::from_str::<ResourceGroup>(&serialized).unwrap();
167 assert_eq!(expected, parsed);
168 }
169
170 #[test]
171 fn request_discriminator() {
172 let bl_request = r#"
173 {
174 "resourceGroupName": "RESOURCE_0999",
175 "objectType": "BL_RESOURCE_GROUP_REQUEST",
176 "targets": []
177 }"#;
178
179 let parsed_bl_request = serde_json::from_str::<BlResourceGroupRequest>(bl_request).unwrap();
180
181 assert_eq!(
182 parsed_bl_request,
183 BlResourceGroupRequest {
184 targets: vec![],
185 resource_group_name: "RESOURCE_0999".to_string(),
186 attributes: None,
187 children: vec![],
188 }
189 );
190 }
191}