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
69#[typetag::serde(name = "network")]
70impl ResourceOutputsDefinition for NetworkOutputs {
71 fn resource_type() -> ResourceType {
72 Network::RESOURCE_TYPE.clone()
73 }
74
75 fn as_any(&self) -> &dyn Any {
76 self
77 }
78
79 fn box_clone(&self) -> Box<dyn ResourceOutputsDefinition> {
80 Box::new(self.clone())
81 }
82
83 fn outputs_eq(&self, other: &dyn ResourceOutputsDefinition) -> bool {
84 other.as_any().downcast_ref::<NetworkOutputs>() == Some(self)
85 }
86}
87
88#[typetag::serde(name = "network")]
89impl ResourceDefinition for Network {
90 fn resource_type() -> ResourceType {
91 Self::RESOURCE_TYPE.clone()
92 }
93
94 fn get_resource_type(&self) -> ResourceType {
95 Self::resource_type()
96 }
97
98 fn id(&self) -> &str {
99 &self.id
100 }
101
102 fn get_dependencies(&self) -> Vec<ResourceRef> {
103 Vec::new() }
105
106 fn validate_update(&self, new_config: &dyn ResourceDefinition) -> Result<()> {
107 let new_network = new_config
108 .as_any()
109 .downcast_ref::<Network>()
110 .ok_or_else(|| {
111 AlienError::new(ErrorData::UnexpectedResourceType {
112 resource_id: self.id.clone(),
113 expected: Self::RESOURCE_TYPE,
114 actual: new_config.get_resource_type(),
115 })
116 })?;
117
118 if self.id != new_network.id {
119 return Err(AlienError::new(ErrorData::InvalidResourceUpdate {
120 resource_id: self.id.clone(),
121 reason: "the 'id' field is immutable".to_string(),
122 }));
123 }
124
125 match (&self.settings, &new_network.settings) {
128 (NetworkSettings::UseDefault, NetworkSettings::UseDefault) => Ok(()),
129 (NetworkSettings::Create { .. }, NetworkSettings::Create { .. }) => Ok(()),
130 (NetworkSettings::ByoVpcAws { .. }, NetworkSettings::ByoVpcAws { .. }) => Ok(()),
131 (NetworkSettings::ByoVpcGcp { .. }, NetworkSettings::ByoVpcGcp { .. }) => Ok(()),
132 (NetworkSettings::ByoVnetAzure { .. }, NetworkSettings::ByoVnetAzure { .. }) => Ok(()),
133 _ => Err(AlienError::new(ErrorData::InvalidResourceUpdate {
134 resource_id: self.id.clone(),
135 reason: "cannot change network type (e.g., from 'create' to 'use-default')"
136 .to_string(),
137 })),
138 }
139 }
140
141 fn as_any(&self) -> &dyn Any {
142 self
143 }
144
145 fn as_any_mut(&mut self) -> &mut dyn Any {
146 self
147 }
148
149 fn box_clone(&self) -> Box<dyn ResourceDefinition> {
150 Box::new(self.clone())
151 }
152
153 fn resource_eq(&self, other: &dyn ResourceDefinition) -> bool {
154 other.as_any().downcast_ref::<Network>() == Some(self)
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn test_network_create_settings() {
164 let network = Network::new("default-network".to_string())
165 .settings(NetworkSettings::Create {
166 cidr: Some("10.0.0.0/16".to_string()),
167 availability_zones: 3,
168 })
169 .build();
170
171 assert_eq!(network.id(), "default-network");
172 match &network.settings {
173 NetworkSettings::Create {
174 cidr,
175 availability_zones,
176 } => {
177 assert_eq!(cidr.as_deref(), Some("10.0.0.0/16"));
178 assert_eq!(*availability_zones, 3);
179 }
180 _ => panic!("Expected Create settings"),
181 }
182 }
183
184 #[test]
185 fn test_network_byo_vpc_aws_settings() {
186 let network = Network::new("default-network".to_string())
187 .settings(NetworkSettings::ByoVpcAws {
188 vpc_id: "vpc-12345".to_string(),
189 public_subnet_ids: vec!["subnet-pub-1".to_string(), "subnet-pub-2".to_string()],
190 private_subnet_ids: vec!["subnet-priv-1".to_string(), "subnet-priv-2".to_string()],
191 security_group_ids: vec!["sg-123".to_string()],
192 })
193 .build();
194
195 match &network.settings {
196 NetworkSettings::ByoVpcAws {
197 vpc_id,
198 public_subnet_ids,
199 private_subnet_ids,
200 security_group_ids,
201 } => {
202 assert_eq!(vpc_id, "vpc-12345");
203 assert_eq!(public_subnet_ids.len(), 2);
204 assert_eq!(private_subnet_ids.len(), 2);
205 assert_eq!(security_group_ids.len(), 1);
206 }
207 _ => panic!("Expected ByoVpcAws settings"),
208 }
209 }
210
211 #[test]
212 fn test_network_validate_update_same_type() {
213 let network1 = Network::new("default-network".to_string())
214 .settings(NetworkSettings::Create {
215 cidr: Some("10.0.0.0/16".to_string()),
216 availability_zones: 2,
217 })
218 .build();
219
220 let network2 = Network::new("default-network".to_string())
221 .settings(NetworkSettings::Create {
222 cidr: Some("10.0.0.0/16".to_string()),
223 availability_zones: 3, })
225 .build();
226
227 assert!(network1.validate_update(&network2).is_ok());
229 }
230
231 #[test]
232 fn test_network_validate_update_different_type_fails() {
233 let network1 = Network::new("default-network".to_string())
234 .settings(NetworkSettings::Create {
235 cidr: Some("10.0.0.0/16".to_string()),
236 availability_zones: 2,
237 })
238 .build();
239
240 let network2 = Network::new("default-network".to_string())
241 .settings(NetworkSettings::ByoVpcAws {
242 vpc_id: "vpc-12345".to_string(),
243 public_subnet_ids: vec![],
244 private_subnet_ids: vec![],
245 security_group_ids: vec![],
246 })
247 .build();
248
249 let result = network1.validate_update(&network2);
251 assert!(result.is_err());
252 }
253
254 #[test]
255 fn test_network_validate_update_use_default() {
256 let network1 = Network::new("default-network".to_string())
257 .settings(NetworkSettings::UseDefault)
258 .build();
259
260 let network2 = Network::new("default-network".to_string())
261 .settings(NetworkSettings::UseDefault)
262 .build();
263
264 assert!(network1.validate_update(&network2).is_ok());
265 }
266
267 #[test]
268 fn test_network_validate_update_use_default_to_create_fails() {
269 let network1 = Network::new("default-network".to_string())
270 .settings(NetworkSettings::UseDefault)
271 .build();
272
273 let network2 = Network::new("default-network".to_string())
274 .settings(NetworkSettings::Create {
275 cidr: None,
276 availability_zones: 2,
277 })
278 .build();
279
280 assert!(network1.validate_update(&network2).is_err());
281 }
282
283 #[test]
284 fn test_network_serialization() {
285 let network = Network::new("default-network".to_string())
286 .settings(NetworkSettings::Create {
287 cidr: None,
288 availability_zones: 2,
289 })
290 .build();
291
292 let json = serde_json::to_string(&network).unwrap();
293 let deserialized: Network = serde_json::from_str(&json).unwrap();
294 assert_eq!(network, deserialized);
295 }
296}