1use crate::error::{ErrorData, Result};
8use crate::resource::{ResourceDefinition, ResourceOutputsDefinition, ResourceRef};
9use crate::stack_settings::NetworkSettings;
10use crate::ResourceType;
11use alien_error::AlienError;
12use bon::Builder;
13use serde::{Deserialize, Serialize};
14use std::any::Any;
15use std::fmt::Debug;
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
22#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
23#[serde(rename_all = "camelCase", deny_unknown_fields)]
24#[builder(start_fn = new)]
25pub struct Network {
26 #[builder(start_fn)]
28 pub id: String,
29
30 pub settings: NetworkSettings,
32}
33
34impl Network {
35 pub const RESOURCE_TYPE: ResourceType = ResourceType::from_static("network");
37
38 pub fn id(&self) -> &str {
40 &self.id
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
51#[serde(rename_all = "camelCase")]
52pub struct NetworkOutputs {
53 pub network_id: String,
55
56 pub availability_zones: u8,
58
59 pub has_public_subnets: bool,
61
62 pub has_nat_gateway: bool,
64
65 pub cidr: Option<String>,
67}
68
69impl ResourceOutputsDefinition for NetworkOutputs {
70 fn get_resource_type(&self) -> ResourceType {
71 Network::RESOURCE_TYPE.clone()
72 }
73
74 fn as_any(&self) -> &dyn Any {
75 self
76 }
77
78 fn box_clone(&self) -> Box<dyn ResourceOutputsDefinition> {
79 Box::new(self.clone())
80 }
81
82 fn outputs_eq(&self, other: &dyn ResourceOutputsDefinition) -> bool {
83 other.as_any().downcast_ref::<NetworkOutputs>() == Some(self)
84 }
85
86 fn to_json_value(&self) -> serde_json::Result<serde_json::Value> {
87 serde_json::to_value(self)
88 }
89}
90
91impl ResourceDefinition for Network {
92 fn get_resource_type(&self) -> ResourceType {
93 Self::RESOURCE_TYPE
94 }
95
96 fn id(&self) -> &str {
97 &self.id
98 }
99
100 fn get_dependencies(&self) -> Vec<ResourceRef> {
101 Vec::new() }
103
104 fn validate_update(&self, new_config: &dyn ResourceDefinition) -> Result<()> {
105 let new_network = new_config
106 .as_any()
107 .downcast_ref::<Network>()
108 .ok_or_else(|| {
109 AlienError::new(ErrorData::UnexpectedResourceType {
110 resource_id: self.id.clone(),
111 expected: Self::RESOURCE_TYPE,
112 actual: new_config.get_resource_type(),
113 })
114 })?;
115
116 if self.id != new_network.id {
117 return Err(AlienError::new(ErrorData::InvalidResourceUpdate {
118 resource_id: self.id.clone(),
119 reason: "the 'id' field is immutable".to_string(),
120 }));
121 }
122
123 match (&self.settings, &new_network.settings) {
126 (NetworkSettings::UseDefault, NetworkSettings::UseDefault) => Ok(()),
127 (NetworkSettings::Create { .. }, NetworkSettings::Create { .. }) => Ok(()),
128 (NetworkSettings::ByoVpcAws { .. }, NetworkSettings::ByoVpcAws { .. }) => Ok(()),
129 (NetworkSettings::ByoVpcGcp { .. }, NetworkSettings::ByoVpcGcp { .. }) => Ok(()),
130 (NetworkSettings::ByoVnetAzure { .. }, NetworkSettings::ByoVnetAzure { .. }) => Ok(()),
131 _ => Err(AlienError::new(ErrorData::InvalidResourceUpdate {
132 resource_id: self.id.clone(),
133 reason: "cannot change network type (e.g., from 'create' to 'use-default')"
134 .to_string(),
135 })),
136 }
137 }
138
139 fn as_any(&self) -> &dyn Any {
140 self
141 }
142
143 fn as_any_mut(&mut self) -> &mut dyn Any {
144 self
145 }
146
147 fn box_clone(&self) -> Box<dyn ResourceDefinition> {
148 Box::new(self.clone())
149 }
150
151 fn resource_eq(&self, other: &dyn ResourceDefinition) -> bool {
152 other.as_any().downcast_ref::<Network>() == Some(self)
153 }
154
155 fn to_json_value(&self) -> serde_json::Result<serde_json::Value> {
156 serde_json::to_value(self)
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn test_network_create_settings() {
166 let network = Network::new("default-network".to_string())
167 .settings(NetworkSettings::Create {
168 cidr: Some("10.0.0.0/16".to_string()),
169 availability_zones: 3,
170 })
171 .build();
172
173 assert_eq!(network.id(), "default-network");
174 match &network.settings {
175 NetworkSettings::Create {
176 cidr,
177 availability_zones,
178 } => {
179 assert_eq!(cidr.as_deref(), Some("10.0.0.0/16"));
180 assert_eq!(*availability_zones, 3);
181 }
182 _ => panic!("Expected Create settings"),
183 }
184 }
185
186 #[test]
187 fn test_network_byo_vpc_aws_settings() {
188 let network = Network::new("default-network".to_string())
189 .settings(NetworkSettings::ByoVpcAws {
190 vpc_id: "vpc-12345".to_string(),
191 public_subnet_ids: vec!["subnet-pub-1".to_string(), "subnet-pub-2".to_string()],
192 private_subnet_ids: vec!["subnet-priv-1".to_string(), "subnet-priv-2".to_string()],
193 security_group_ids: vec!["sg-123".to_string()],
194 })
195 .build();
196
197 match &network.settings {
198 NetworkSettings::ByoVpcAws {
199 vpc_id,
200 public_subnet_ids,
201 private_subnet_ids,
202 security_group_ids,
203 } => {
204 assert_eq!(vpc_id, "vpc-12345");
205 assert_eq!(public_subnet_ids.len(), 2);
206 assert_eq!(private_subnet_ids.len(), 2);
207 assert_eq!(security_group_ids.len(), 1);
208 }
209 _ => panic!("Expected ByoVpcAws settings"),
210 }
211 }
212
213 #[test]
214 fn test_network_validate_update_same_type() {
215 let network1 = Network::new("default-network".to_string())
216 .settings(NetworkSettings::Create {
217 cidr: Some("10.0.0.0/16".to_string()),
218 availability_zones: 2,
219 })
220 .build();
221
222 let network2 = Network::new("default-network".to_string())
223 .settings(NetworkSettings::Create {
224 cidr: Some("10.0.0.0/16".to_string()),
225 availability_zones: 3, })
227 .build();
228
229 assert!(network1.validate_update(&network2).is_ok());
231 }
232
233 #[test]
234 fn test_network_validate_update_different_type_fails() {
235 let network1 = Network::new("default-network".to_string())
236 .settings(NetworkSettings::Create {
237 cidr: Some("10.0.0.0/16".to_string()),
238 availability_zones: 2,
239 })
240 .build();
241
242 let network2 = Network::new("default-network".to_string())
243 .settings(NetworkSettings::ByoVpcAws {
244 vpc_id: "vpc-12345".to_string(),
245 public_subnet_ids: vec![],
246 private_subnet_ids: vec![],
247 security_group_ids: vec![],
248 })
249 .build();
250
251 let result = network1.validate_update(&network2);
253 assert!(result.is_err());
254 }
255
256 #[test]
257 fn test_network_validate_update_use_default() {
258 let network1 = Network::new("default-network".to_string())
259 .settings(NetworkSettings::UseDefault)
260 .build();
261
262 let network2 = Network::new("default-network".to_string())
263 .settings(NetworkSettings::UseDefault)
264 .build();
265
266 assert!(network1.validate_update(&network2).is_ok());
267 }
268
269 #[test]
270 fn test_network_validate_update_use_default_to_create_fails() {
271 let network1 = Network::new("default-network".to_string())
272 .settings(NetworkSettings::UseDefault)
273 .build();
274
275 let network2 = Network::new("default-network".to_string())
276 .settings(NetworkSettings::Create {
277 cidr: None,
278 availability_zones: 2,
279 })
280 .build();
281
282 assert!(network1.validate_update(&network2).is_err());
283 }
284
285 #[test]
286 fn test_network_serialization() {
287 let network = Network::new("default-network".to_string())
288 .settings(NetworkSettings::Create {
289 cidr: None,
290 availability_zones: 2,
291 })
292 .build();
293
294 let json = serde_json::to_string(&network).unwrap();
295 let deserialized: Network = serde_json::from_str(&json).unwrap();
296 assert_eq!(network, deserialized);
297 }
298}