1use std::collections::HashMap;
8
9use alien_error::AlienError;
10use serde::{Deserialize, Serialize};
11
12use crate::bindings::{
13 ArtifactRegistryBinding, BindingValue, ContainerAppsEnvironmentBinding, KvBinding,
14 PostgresBinding, QueueBinding, StorageBinding, VaultBinding,
15};
16use crate::error::ErrorData;
17use crate::resource::ResourceOutputs;
18use crate::resources::AzureContainerAppsEnvironmentOutputs;
19use crate::Resource;
20
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
26#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
27#[serde(tag = "type", rename_all = "snake_case")]
28pub enum ExternalBinding {
29 Storage(StorageBinding),
31 Queue(QueueBinding),
33 Kv(KvBinding),
35 ArtifactRegistry(ArtifactRegistryBinding),
37 Vault(VaultBinding),
39 ContainerAppsEnvironment(ContainerAppsEnvironmentBinding),
41 Postgres(PostgresBinding),
43}
44
45#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
49#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
50#[serde(transparent)]
51pub struct ExternalBindings(pub HashMap<String, ExternalBinding>);
52
53impl ExternalBindings {
54 pub fn new() -> Self {
56 Self(HashMap::new())
57 }
58
59 pub fn is_empty(&self) -> bool {
61 self.0.is_empty()
62 }
63
64 pub fn has(&self, resource_id: &str) -> bool {
66 self.0.contains_key(resource_id)
67 }
68
69 pub fn get(&self, resource_id: &str) -> Option<&ExternalBinding> {
71 self.0.get(resource_id)
72 }
73
74 pub fn get_storage(&self, id: &str) -> crate::error::Result<Option<&StorageBinding>> {
77 match self.0.get(id) {
78 Some(ExternalBinding::Storage(b)) => Ok(Some(b)),
79 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
80 resource_id: id.to_string(),
81 expected: "storage".to_string(),
82 actual: other.binding_type().to_string(),
83 })),
84 None => Ok(None),
85 }
86 }
87
88 pub fn get_queue(&self, id: &str) -> crate::error::Result<Option<&QueueBinding>> {
91 match self.0.get(id) {
92 Some(ExternalBinding::Queue(b)) => Ok(Some(b)),
93 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
94 resource_id: id.to_string(),
95 expected: "queue".to_string(),
96 actual: other.binding_type().to_string(),
97 })),
98 None => Ok(None),
99 }
100 }
101
102 pub fn get_kv(&self, id: &str) -> crate::error::Result<Option<&KvBinding>> {
105 match self.0.get(id) {
106 Some(ExternalBinding::Kv(b)) => Ok(Some(b)),
107 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
108 resource_id: id.to_string(),
109 expected: "kv".to_string(),
110 actual: other.binding_type().to_string(),
111 })),
112 None => Ok(None),
113 }
114 }
115
116 pub fn get_artifact_registry(
119 &self,
120 id: &str,
121 ) -> crate::error::Result<Option<&ArtifactRegistryBinding>> {
122 match self.0.get(id) {
123 Some(ExternalBinding::ArtifactRegistry(b)) => Ok(Some(b)),
124 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
125 resource_id: id.to_string(),
126 expected: "artifact_registry".to_string(),
127 actual: other.binding_type().to_string(),
128 })),
129 None => Ok(None),
130 }
131 }
132
133 pub fn get_vault(&self, id: &str) -> crate::error::Result<Option<&VaultBinding>> {
136 match self.0.get(id) {
137 Some(ExternalBinding::Vault(b)) => Ok(Some(b)),
138 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
139 resource_id: id.to_string(),
140 expected: "vault".to_string(),
141 actual: other.binding_type().to_string(),
142 })),
143 None => Ok(None),
144 }
145 }
146
147 pub fn get_container_apps_environment(
150 &self,
151 id: &str,
152 ) -> crate::error::Result<Option<&ContainerAppsEnvironmentBinding>> {
153 match self.0.get(id) {
154 Some(ExternalBinding::ContainerAppsEnvironment(b)) => Ok(Some(b)),
155 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
156 resource_id: id.to_string(),
157 expected: "azure_container_apps_environment".to_string(),
158 actual: other.binding_type().to_string(),
159 })),
160 None => Ok(None),
161 }
162 }
163
164 pub fn get_postgres(&self, id: &str) -> crate::error::Result<Option<&PostgresBinding>> {
166 match self.0.get(id) {
167 Some(ExternalBinding::Postgres(b)) => Ok(Some(b)),
168 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
169 resource_id: id.to_string(),
170 expected: "postgres".to_string(),
171 actual: other.binding_type().to_string(),
172 })),
173 None => Ok(None),
174 }
175 }
176
177 pub fn insert(&mut self, resource_id: impl Into<String>, binding: ExternalBinding) {
179 self.0.insert(resource_id.into(), binding);
180 }
181}
182
183impl ExternalBinding {
184 pub fn binding_type(&self) -> &'static str {
186 match self {
187 ExternalBinding::Storage(_) => "storage",
188 ExternalBinding::Queue(_) => "queue",
189 ExternalBinding::Kv(_) => "kv",
190 ExternalBinding::ArtifactRegistry(_) => "artifact_registry",
191 ExternalBinding::Vault(_) => "vault",
192 ExternalBinding::ContainerAppsEnvironment(_) => "azure_container_apps_environment",
193 ExternalBinding::Postgres(_) => "postgres",
194 }
195 }
196
197 pub fn to_resource_outputs(&self) -> Option<ResourceOutputs> {
206 match self {
207 ExternalBinding::ContainerAppsEnvironment(binding) => {
208 let environment_name = match &binding.environment_name {
211 BindingValue::Value(v) => v.clone(),
212 _ => return None,
213 };
214 let resource_id = match &binding.resource_id {
215 BindingValue::Value(v) => v.clone(),
216 _ => return None,
217 };
218 let resource_group_name = match &binding.resource_group_name {
219 BindingValue::Value(v) => v.clone(),
220 _ => return None,
221 };
222 let default_domain = match &binding.default_domain {
223 BindingValue::Value(v) => v.clone(),
224 _ => return None,
225 };
226 let static_ip = binding.static_ip.as_ref().and_then(|v| match v {
227 BindingValue::Value(v) => Some(v.clone()),
228 _ => None,
229 });
230
231 Some(ResourceOutputs::new(AzureContainerAppsEnvironmentOutputs {
232 environment_name,
233 resource_id,
234 resource_group_name,
235 default_domain,
236 static_ip,
237 custom_domain_verification_id: None,
238 }))
239 }
240 _ => None,
242 }
243 }
244}
245
246pub fn validate_binding_type(
248 resource: &Resource,
249 binding: &ExternalBinding,
250) -> crate::error::Result<()> {
251 let resource_type = resource.resource_type();
252 let resource_type_str = resource_type.as_ref();
253
254 let valid = match (resource_type_str, binding) {
255 ("storage", ExternalBinding::Storage(_)) => true,
256 ("queue", ExternalBinding::Queue(_)) => true,
257 ("kv", ExternalBinding::Kv(_)) => true,
258 ("artifact_registry", ExternalBinding::ArtifactRegistry(_)) => true,
259 ("vault", ExternalBinding::Vault(_)) => true,
260 ("azure_container_apps_environment", ExternalBinding::ContainerAppsEnvironment(_)) => true,
261 ("postgres", ExternalBinding::Postgres(_)) => true,
262 _ => false,
263 };
264
265 if !valid {
266 return Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
267 resource_id: resource.id().to_string(),
268 expected: resource_type_str.to_string(),
269 actual: binding.binding_type().to_string(),
270 }));
271 }
272 Ok(())
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278 use crate::bindings::{KvBinding, StorageBinding};
279
280 #[test]
281 fn test_external_bindings_storage() {
282 let mut bindings = ExternalBindings::new();
283 bindings.insert(
284 "data-storage",
285 ExternalBinding::Storage(StorageBinding::s3("my-bucket")),
286 );
287
288 assert!(bindings.has("data-storage"));
289 assert!(bindings.get_storage("data-storage").unwrap().is_some());
290 assert!(bindings.get_queue("data-storage").is_err()); }
292
293 #[test]
294 fn test_external_bindings_kv() {
295 let mut bindings = ExternalBindings::new();
296 bindings.insert(
297 "cache",
298 ExternalBinding::Kv(KvBinding::redis("redis://localhost:6379")),
299 );
300
301 assert!(bindings.has("cache"));
302 assert!(bindings.get_kv("cache").unwrap().is_some());
303 assert!(bindings.get_storage("cache").is_err()); }
305
306 #[test]
307 fn test_external_bindings_serialization() {
308 let mut bindings = ExternalBindings::new();
309 bindings.insert(
310 "data",
311 ExternalBinding::Storage(StorageBinding::s3("test-bucket")),
312 );
313
314 let json = serde_json::to_string(&bindings).unwrap();
315 let deserialized: ExternalBindings = serde_json::from_str(&json).unwrap();
316 assert_eq!(bindings, deserialized);
317 }
318}