1use std::collections::HashMap;
8
9use alien_error::AlienError;
10use serde::{Deserialize, Serialize};
11
12use crate::bindings::{
13 ArtifactRegistryBinding, BindingValue, ContainerAppsEnvironmentBinding, KvBinding,
14 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}
42
43#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
47#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
48#[serde(transparent)]
49pub struct ExternalBindings(pub HashMap<String, ExternalBinding>);
50
51impl ExternalBindings {
52 pub fn new() -> Self {
54 Self(HashMap::new())
55 }
56
57 pub fn is_empty(&self) -> bool {
59 self.0.is_empty()
60 }
61
62 pub fn has(&self, resource_id: &str) -> bool {
64 self.0.contains_key(resource_id)
65 }
66
67 pub fn get(&self, resource_id: &str) -> Option<&ExternalBinding> {
69 self.0.get(resource_id)
70 }
71
72 pub fn get_storage(&self, id: &str) -> crate::error::Result<Option<&StorageBinding>> {
75 match self.0.get(id) {
76 Some(ExternalBinding::Storage(b)) => Ok(Some(b)),
77 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
78 resource_id: id.to_string(),
79 expected: "storage".to_string(),
80 actual: other.binding_type().to_string(),
81 })),
82 None => Ok(None),
83 }
84 }
85
86 pub fn get_queue(&self, id: &str) -> crate::error::Result<Option<&QueueBinding>> {
89 match self.0.get(id) {
90 Some(ExternalBinding::Queue(b)) => Ok(Some(b)),
91 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
92 resource_id: id.to_string(),
93 expected: "queue".to_string(),
94 actual: other.binding_type().to_string(),
95 })),
96 None => Ok(None),
97 }
98 }
99
100 pub fn get_kv(&self, id: &str) -> crate::error::Result<Option<&KvBinding>> {
103 match self.0.get(id) {
104 Some(ExternalBinding::Kv(b)) => Ok(Some(b)),
105 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
106 resource_id: id.to_string(),
107 expected: "kv".to_string(),
108 actual: other.binding_type().to_string(),
109 })),
110 None => Ok(None),
111 }
112 }
113
114 pub fn get_artifact_registry(
117 &self,
118 id: &str,
119 ) -> crate::error::Result<Option<&ArtifactRegistryBinding>> {
120 match self.0.get(id) {
121 Some(ExternalBinding::ArtifactRegistry(b)) => Ok(Some(b)),
122 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
123 resource_id: id.to_string(),
124 expected: "artifact_registry".to_string(),
125 actual: other.binding_type().to_string(),
126 })),
127 None => Ok(None),
128 }
129 }
130
131 pub fn get_vault(&self, id: &str) -> crate::error::Result<Option<&VaultBinding>> {
134 match self.0.get(id) {
135 Some(ExternalBinding::Vault(b)) => Ok(Some(b)),
136 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
137 resource_id: id.to_string(),
138 expected: "vault".to_string(),
139 actual: other.binding_type().to_string(),
140 })),
141 None => Ok(None),
142 }
143 }
144
145 pub fn get_container_apps_environment(
148 &self,
149 id: &str,
150 ) -> crate::error::Result<Option<&ContainerAppsEnvironmentBinding>> {
151 match self.0.get(id) {
152 Some(ExternalBinding::ContainerAppsEnvironment(b)) => Ok(Some(b)),
153 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
154 resource_id: id.to_string(),
155 expected: "azure_container_apps_environment".to_string(),
156 actual: other.binding_type().to_string(),
157 })),
158 None => Ok(None),
159 }
160 }
161
162 pub fn insert(&mut self, resource_id: impl Into<String>, binding: ExternalBinding) {
164 self.0.insert(resource_id.into(), binding);
165 }
166}
167
168impl ExternalBinding {
169 pub fn binding_type(&self) -> &'static str {
171 match self {
172 ExternalBinding::Storage(_) => "storage",
173 ExternalBinding::Queue(_) => "queue",
174 ExternalBinding::Kv(_) => "kv",
175 ExternalBinding::ArtifactRegistry(_) => "artifact_registry",
176 ExternalBinding::Vault(_) => "vault",
177 ExternalBinding::ContainerAppsEnvironment(_) => "azure_container_apps_environment",
178 }
179 }
180
181 pub fn to_resource_outputs(&self) -> Option<ResourceOutputs> {
190 match self {
191 ExternalBinding::ContainerAppsEnvironment(binding) => {
192 let environment_name = match &binding.environment_name {
195 BindingValue::Value(v) => v.clone(),
196 _ => return None,
197 };
198 let resource_id = match &binding.resource_id {
199 BindingValue::Value(v) => v.clone(),
200 _ => return None,
201 };
202 let resource_group_name = match &binding.resource_group_name {
203 BindingValue::Value(v) => v.clone(),
204 _ => return None,
205 };
206 let default_domain = match &binding.default_domain {
207 BindingValue::Value(v) => v.clone(),
208 _ => return None,
209 };
210 let static_ip = binding.static_ip.as_ref().and_then(|v| match v {
211 BindingValue::Value(v) => Some(v.clone()),
212 _ => None,
213 });
214
215 Some(ResourceOutputs::new(AzureContainerAppsEnvironmentOutputs {
216 environment_name,
217 resource_id,
218 resource_group_name,
219 default_domain,
220 static_ip,
221 }))
222 }
223 _ => None,
225 }
226 }
227}
228
229pub fn validate_binding_type(
231 resource: &Resource,
232 binding: &ExternalBinding,
233) -> crate::error::Result<()> {
234 let resource_type = resource.resource_type();
235 let resource_type_str = resource_type.as_ref();
236
237 let valid = match (resource_type_str, binding) {
238 ("storage", ExternalBinding::Storage(_)) => true,
239 ("queue", ExternalBinding::Queue(_)) => true,
240 ("kv", ExternalBinding::Kv(_)) => true,
241 ("artifact_registry", ExternalBinding::ArtifactRegistry(_)) => true,
242 ("vault", ExternalBinding::Vault(_)) => true,
243 ("azure_container_apps_environment", ExternalBinding::ContainerAppsEnvironment(_)) => true,
244 _ => false,
245 };
246
247 if !valid {
248 return Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
249 resource_id: resource.id().to_string(),
250 expected: resource_type_str.to_string(),
251 actual: binding.binding_type().to_string(),
252 }));
253 }
254 Ok(())
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use crate::bindings::{KvBinding, StorageBinding};
261
262 #[test]
263 fn test_external_bindings_storage() {
264 let mut bindings = ExternalBindings::new();
265 bindings.insert(
266 "data-storage",
267 ExternalBinding::Storage(StorageBinding::s3("my-bucket")),
268 );
269
270 assert!(bindings.has("data-storage"));
271 assert!(bindings.get_storage("data-storage").unwrap().is_some());
272 assert!(bindings.get_queue("data-storage").is_err()); }
274
275 #[test]
276 fn test_external_bindings_kv() {
277 let mut bindings = ExternalBindings::new();
278 bindings.insert(
279 "cache",
280 ExternalBinding::Kv(KvBinding::redis("redis://localhost:6379")),
281 );
282
283 assert!(bindings.has("cache"));
284 assert!(bindings.get_kv("cache").unwrap().is_some());
285 assert!(bindings.get_storage("cache").is_err()); }
287
288 #[test]
289 fn test_external_bindings_serialization() {
290 let mut bindings = ExternalBindings::new();
291 bindings.insert(
292 "data",
293 ExternalBinding::Storage(StorageBinding::s3("test-bucket")),
294 );
295
296 let json = serde_json::to_string(&bindings).unwrap();
297 let deserialized: ExternalBindings = serde_json::from_str(&json).unwrap();
298 assert_eq!(bindings, deserialized);
299 }
300}