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