use std::collections::HashMap;
use alien_error::AlienError;
use serde::{Deserialize, Serialize};
use crate::bindings::{
ArtifactRegistryBinding, BindingValue, ContainerAppsEnvironmentBinding, KvBinding,
QueueBinding, StorageBinding, VaultBinding,
};
use crate::error::ErrorData;
use crate::resource::ResourceOutputs;
use crate::resources::AzureContainerAppsEnvironmentOutputs;
use crate::Resource;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ExternalBinding {
Storage(StorageBinding),
Queue(QueueBinding),
Kv(KvBinding),
ArtifactRegistry(ArtifactRegistryBinding),
Vault(VaultBinding),
ContainerAppsEnvironment(ContainerAppsEnvironmentBinding),
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(transparent)]
pub struct ExternalBindings(pub HashMap<String, ExternalBinding>);
impl ExternalBindings {
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn has(&self, resource_id: &str) -> bool {
self.0.contains_key(resource_id)
}
pub fn get(&self, resource_id: &str) -> Option<&ExternalBinding> {
self.0.get(resource_id)
}
pub fn get_storage(&self, id: &str) -> crate::error::Result<Option<&StorageBinding>> {
match self.0.get(id) {
Some(ExternalBinding::Storage(b)) => Ok(Some(b)),
Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
resource_id: id.to_string(),
expected: "storage".to_string(),
actual: other.binding_type().to_string(),
})),
None => Ok(None),
}
}
pub fn get_queue(&self, id: &str) -> crate::error::Result<Option<&QueueBinding>> {
match self.0.get(id) {
Some(ExternalBinding::Queue(b)) => Ok(Some(b)),
Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
resource_id: id.to_string(),
expected: "queue".to_string(),
actual: other.binding_type().to_string(),
})),
None => Ok(None),
}
}
pub fn get_kv(&self, id: &str) -> crate::error::Result<Option<&KvBinding>> {
match self.0.get(id) {
Some(ExternalBinding::Kv(b)) => Ok(Some(b)),
Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
resource_id: id.to_string(),
expected: "kv".to_string(),
actual: other.binding_type().to_string(),
})),
None => Ok(None),
}
}
pub fn get_artifact_registry(
&self,
id: &str,
) -> crate::error::Result<Option<&ArtifactRegistryBinding>> {
match self.0.get(id) {
Some(ExternalBinding::ArtifactRegistry(b)) => Ok(Some(b)),
Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
resource_id: id.to_string(),
expected: "artifact_registry".to_string(),
actual: other.binding_type().to_string(),
})),
None => Ok(None),
}
}
pub fn get_vault(&self, id: &str) -> crate::error::Result<Option<&VaultBinding>> {
match self.0.get(id) {
Some(ExternalBinding::Vault(b)) => Ok(Some(b)),
Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
resource_id: id.to_string(),
expected: "vault".to_string(),
actual: other.binding_type().to_string(),
})),
None => Ok(None),
}
}
pub fn get_container_apps_environment(
&self,
id: &str,
) -> crate::error::Result<Option<&ContainerAppsEnvironmentBinding>> {
match self.0.get(id) {
Some(ExternalBinding::ContainerAppsEnvironment(b)) => Ok(Some(b)),
Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
resource_id: id.to_string(),
expected: "azure_container_apps_environment".to_string(),
actual: other.binding_type().to_string(),
})),
None => Ok(None),
}
}
pub fn insert(&mut self, resource_id: impl Into<String>, binding: ExternalBinding) {
self.0.insert(resource_id.into(), binding);
}
}
impl ExternalBinding {
pub fn binding_type(&self) -> &'static str {
match self {
ExternalBinding::Storage(_) => "storage",
ExternalBinding::Queue(_) => "queue",
ExternalBinding::Kv(_) => "kv",
ExternalBinding::ArtifactRegistry(_) => "artifact_registry",
ExternalBinding::Vault(_) => "vault",
ExternalBinding::ContainerAppsEnvironment(_) => "azure_container_apps_environment",
}
}
pub fn to_resource_outputs(&self) -> Option<ResourceOutputs> {
match self {
ExternalBinding::ContainerAppsEnvironment(binding) => {
let environment_name = match &binding.environment_name {
BindingValue::Value(v) => v.clone(),
_ => return None,
};
let resource_id = match &binding.resource_id {
BindingValue::Value(v) => v.clone(),
_ => return None,
};
let resource_group_name = match &binding.resource_group_name {
BindingValue::Value(v) => v.clone(),
_ => return None,
};
let default_domain = match &binding.default_domain {
BindingValue::Value(v) => v.clone(),
_ => return None,
};
let static_ip = binding.static_ip.as_ref().and_then(|v| match v {
BindingValue::Value(v) => Some(v.clone()),
_ => None,
});
Some(ResourceOutputs::new(AzureContainerAppsEnvironmentOutputs {
environment_name,
resource_id,
resource_group_name,
default_domain,
static_ip,
custom_domain_verification_id: None,
}))
}
_ => None,
}
}
}
pub fn validate_binding_type(
resource: &Resource,
binding: &ExternalBinding,
) -> crate::error::Result<()> {
let resource_type = resource.resource_type();
let resource_type_str = resource_type.as_ref();
let valid = match (resource_type_str, binding) {
("storage", ExternalBinding::Storage(_)) => true,
("queue", ExternalBinding::Queue(_)) => true,
("kv", ExternalBinding::Kv(_)) => true,
("artifact_registry", ExternalBinding::ArtifactRegistry(_)) => true,
("vault", ExternalBinding::Vault(_)) => true,
("azure_container_apps_environment", ExternalBinding::ContainerAppsEnvironment(_)) => true,
_ => false,
};
if !valid {
return Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
resource_id: resource.id().to_string(),
expected: resource_type_str.to_string(),
actual: binding.binding_type().to_string(),
}));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bindings::{KvBinding, StorageBinding};
#[test]
fn test_external_bindings_storage() {
let mut bindings = ExternalBindings::new();
bindings.insert(
"data-storage",
ExternalBinding::Storage(StorageBinding::s3("my-bucket")),
);
assert!(bindings.has("data-storage"));
assert!(bindings.get_storage("data-storage").unwrap().is_some());
assert!(bindings.get_queue("data-storage").is_err()); }
#[test]
fn test_external_bindings_kv() {
let mut bindings = ExternalBindings::new();
bindings.insert(
"cache",
ExternalBinding::Kv(KvBinding::redis("redis://localhost:6379")),
);
assert!(bindings.has("cache"));
assert!(bindings.get_kv("cache").unwrap().is_some());
assert!(bindings.get_storage("cache").is_err()); }
#[test]
fn test_external_bindings_serialization() {
let mut bindings = ExternalBindings::new();
bindings.insert(
"data",
ExternalBinding::Storage(StorageBinding::s3("test-bucket")),
);
let json = serde_json::to_string(&bindings).unwrap();
let deserialized: ExternalBindings = serde_json::from_str(&json).unwrap();
assert_eq!(bindings, deserialized);
}
}