1use std::collections::HashMap;
8
9use alien_error::AlienError;
10use serde::{Deserialize, Serialize};
11
12use crate::bindings::{
13 ArtifactRegistryBinding, KvBinding, QueueBinding, StorageBinding, VaultBinding,
14};
15use crate::error::ErrorData;
16use crate::Resource;
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
23#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
24#[serde(tag = "type", rename_all = "snake_case")]
25pub enum ExternalBinding {
26 Storage(StorageBinding),
28 Queue(QueueBinding),
30 Kv(KvBinding),
32 ArtifactRegistry(ArtifactRegistryBinding),
34 Vault(VaultBinding),
36}
37
38#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
42#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
43#[serde(transparent)]
44pub struct ExternalBindings(pub HashMap<String, ExternalBinding>);
45
46impl ExternalBindings {
47 pub fn new() -> Self {
49 Self(HashMap::new())
50 }
51
52 pub fn has(&self, resource_id: &str) -> bool {
54 self.0.contains_key(resource_id)
55 }
56
57 pub fn get(&self, resource_id: &str) -> Option<&ExternalBinding> {
59 self.0.get(resource_id)
60 }
61
62 pub fn get_storage(&self, id: &str) -> crate::error::Result<Option<&StorageBinding>> {
65 match self.0.get(id) {
66 Some(ExternalBinding::Storage(b)) => Ok(Some(b)),
67 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
68 resource_id: id.to_string(),
69 expected: "storage".to_string(),
70 actual: other.binding_type().to_string(),
71 })),
72 None => Ok(None),
73 }
74 }
75
76 pub fn get_queue(&self, id: &str) -> crate::error::Result<Option<&QueueBinding>> {
79 match self.0.get(id) {
80 Some(ExternalBinding::Queue(b)) => Ok(Some(b)),
81 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
82 resource_id: id.to_string(),
83 expected: "queue".to_string(),
84 actual: other.binding_type().to_string(),
85 })),
86 None => Ok(None),
87 }
88 }
89
90 pub fn get_kv(&self, id: &str) -> crate::error::Result<Option<&KvBinding>> {
93 match self.0.get(id) {
94 Some(ExternalBinding::Kv(b)) => Ok(Some(b)),
95 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
96 resource_id: id.to_string(),
97 expected: "kv".to_string(),
98 actual: other.binding_type().to_string(),
99 })),
100 None => Ok(None),
101 }
102 }
103
104 pub fn get_artifact_registry(
107 &self,
108 id: &str,
109 ) -> crate::error::Result<Option<&ArtifactRegistryBinding>> {
110 match self.0.get(id) {
111 Some(ExternalBinding::ArtifactRegistry(b)) => Ok(Some(b)),
112 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
113 resource_id: id.to_string(),
114 expected: "artifact_registry".to_string(),
115 actual: other.binding_type().to_string(),
116 })),
117 None => Ok(None),
118 }
119 }
120
121 pub fn get_vault(&self, id: &str) -> crate::error::Result<Option<&VaultBinding>> {
124 match self.0.get(id) {
125 Some(ExternalBinding::Vault(b)) => Ok(Some(b)),
126 Some(other) => Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
127 resource_id: id.to_string(),
128 expected: "vault".to_string(),
129 actual: other.binding_type().to_string(),
130 })),
131 None => Ok(None),
132 }
133 }
134
135 pub fn insert(&mut self, resource_id: impl Into<String>, binding: ExternalBinding) {
137 self.0.insert(resource_id.into(), binding);
138 }
139}
140
141impl ExternalBinding {
142 pub fn binding_type(&self) -> &'static str {
144 match self {
145 ExternalBinding::Storage(_) => "storage",
146 ExternalBinding::Queue(_) => "queue",
147 ExternalBinding::Kv(_) => "kv",
148 ExternalBinding::ArtifactRegistry(_) => "artifact_registry",
149 ExternalBinding::Vault(_) => "vault",
150 }
151 }
152}
153
154pub fn validate_binding_type(
156 resource: &Resource,
157 binding: &ExternalBinding,
158) -> crate::error::Result<()> {
159 let resource_type = resource.resource_type();
160 let resource_type_str = resource_type.as_ref();
161
162 let valid = match (resource_type_str, binding) {
163 ("storage", ExternalBinding::Storage(_)) => true,
164 ("queue", ExternalBinding::Queue(_)) => true,
165 ("kv", ExternalBinding::Kv(_)) => true,
166 ("artifact_registry", ExternalBinding::ArtifactRegistry(_)) => true,
167 ("vault", ExternalBinding::Vault(_)) => true,
168 _ => false,
169 };
170
171 if !valid {
172 return Err(AlienError::new(ErrorData::ExternalBindingTypeMismatch {
173 resource_id: resource.id().to_string(),
174 expected: resource_type_str.to_string(),
175 actual: binding.binding_type().to_string(),
176 }));
177 }
178 Ok(())
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::bindings::{KvBinding, StorageBinding};
185
186 #[test]
187 fn test_external_bindings_storage() {
188 let mut bindings = ExternalBindings::new();
189 bindings.insert(
190 "data-storage",
191 ExternalBinding::Storage(StorageBinding::s3("my-bucket")),
192 );
193
194 assert!(bindings.has("data-storage"));
195 assert!(bindings.get_storage("data-storage").unwrap().is_some());
196 assert!(bindings.get_queue("data-storage").is_err()); }
198
199 #[test]
200 fn test_external_bindings_kv() {
201 let mut bindings = ExternalBindings::new();
202 bindings.insert(
203 "cache",
204 ExternalBinding::Kv(KvBinding::redis("redis://localhost:6379")),
205 );
206
207 assert!(bindings.has("cache"));
208 assert!(bindings.get_kv("cache").unwrap().is_some());
209 assert!(bindings.get_storage("cache").is_err()); }
211
212 #[test]
213 fn test_external_bindings_serialization() {
214 let mut bindings = ExternalBindings::new();
215 bindings.insert(
216 "data",
217 ExternalBinding::Storage(StorageBinding::s3("test-bucket")),
218 );
219
220 let json = serde_json::to_string(&bindings).unwrap();
221 let deserialized: ExternalBindings = serde_json::from_str(&json).unwrap();
222 assert_eq!(bindings, deserialized);
223 }
224}