1use super::BindingValue;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(tag = "service", rename_all = "lowercase")]
13pub enum ContainerBinding {
14 Horizon(HorizonContainerBinding),
16 Kubernetes(KubernetesContainerBinding),
18 Local(LocalContainerBinding),
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct HorizonContainerBinding {
26 pub container_name: BindingValue<String>,
28 pub internal_url: BindingValue<String>,
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub public_url: Option<BindingValue<String>>,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(rename_all = "camelCase")]
38pub struct KubernetesContainerBinding {
39 pub name: BindingValue<String>,
41 pub namespace: BindingValue<String>,
43 pub service_name: BindingValue<String>,
45 pub service_port: BindingValue<u16>,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub public_url: Option<BindingValue<String>>,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct LocalContainerBinding {
56 pub container_name: BindingValue<String>,
58 pub internal_url: BindingValue<String>,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub public_url: Option<BindingValue<String>>,
63}
64
65impl ContainerBinding {
66 pub fn horizon(
68 container_name: impl Into<BindingValue<String>>,
69 internal_url: impl Into<BindingValue<String>>,
70 ) -> Self {
71 Self::Horizon(HorizonContainerBinding {
72 container_name: container_name.into(),
73 internal_url: internal_url.into(),
74 public_url: None,
75 })
76 }
77
78 pub fn horizon_with_public_url(
80 container_name: impl Into<BindingValue<String>>,
81 internal_url: impl Into<BindingValue<String>>,
82 public_url: impl Into<BindingValue<String>>,
83 ) -> Self {
84 Self::Horizon(HorizonContainerBinding {
85 container_name: container_name.into(),
86 internal_url: internal_url.into(),
87 public_url: Some(public_url.into()),
88 })
89 }
90
91 pub fn local(
93 container_name: impl Into<BindingValue<String>>,
94 internal_url: impl Into<BindingValue<String>>,
95 ) -> Self {
96 Self::Local(LocalContainerBinding {
97 container_name: container_name.into(),
98 internal_url: internal_url.into(),
99 public_url: None,
100 })
101 }
102
103 pub fn local_with_public_url(
105 container_name: impl Into<BindingValue<String>>,
106 internal_url: impl Into<BindingValue<String>>,
107 public_url: impl Into<BindingValue<String>>,
108 ) -> Self {
109 Self::Local(LocalContainerBinding {
110 container_name: container_name.into(),
111 internal_url: internal_url.into(),
112 public_url: Some(public_url.into()),
113 })
114 }
115
116 pub fn kubernetes(
118 name: impl Into<BindingValue<String>>,
119 namespace: impl Into<BindingValue<String>>,
120 service_name: impl Into<BindingValue<String>>,
121 service_port: impl Into<BindingValue<u16>>,
122 ) -> Self {
123 Self::Kubernetes(KubernetesContainerBinding {
124 name: name.into(),
125 namespace: namespace.into(),
126 service_name: service_name.into(),
127 service_port: service_port.into(),
128 public_url: None,
129 })
130 }
131
132 pub fn kubernetes_with_public_url(
134 name: impl Into<BindingValue<String>>,
135 namespace: impl Into<BindingValue<String>>,
136 service_name: impl Into<BindingValue<String>>,
137 service_port: impl Into<BindingValue<u16>>,
138 public_url: impl Into<BindingValue<String>>,
139 ) -> Self {
140 Self::Kubernetes(KubernetesContainerBinding {
141 name: name.into(),
142 namespace: namespace.into(),
143 service_name: service_name.into(),
144 service_port: service_port.into(),
145 public_url: Some(public_url.into()),
146 })
147 }
148
149 pub fn get_internal_url(&self) -> Option<String> {
152 match self {
153 ContainerBinding::Horizon(binding) => {
154 if let BindingValue::Value(url) = &binding.internal_url {
156 Some(url.clone())
157 } else {
158 None
159 }
160 }
161 ContainerBinding::Kubernetes(binding) => {
162 if let (
164 BindingValue::Value(service_name),
165 BindingValue::Value(namespace),
166 BindingValue::Value(port),
167 ) = (
168 &binding.service_name,
169 &binding.namespace,
170 &binding.service_port,
171 ) {
172 Some(format!(
173 "http://{}.{}.svc.cluster.local:{}",
174 service_name, namespace, port
175 ))
176 } else {
177 None
178 }
179 }
180 ContainerBinding::Local(binding) => {
181 if let BindingValue::Value(url) = &binding.internal_url {
182 Some(url.clone())
183 } else {
184 None
185 }
186 }
187 }
188 }
189
190 pub fn get_public_url(&self) -> Option<&BindingValue<String>> {
192 match self {
193 ContainerBinding::Horizon(binding) => binding.public_url.as_ref(),
194 ContainerBinding::Kubernetes(binding) => binding.public_url.as_ref(),
195 ContainerBinding::Local(binding) => binding.public_url.as_ref(),
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn test_horizon_binding() {
206 let binding = ContainerBinding::horizon("api", "http://api.svc:8080");
207
208 if let ContainerBinding::Horizon(horizon_binding) = binding {
209 assert_eq!(
210 horizon_binding.container_name,
211 BindingValue::Value("api".to_string())
212 );
213 assert_eq!(
214 horizon_binding.internal_url,
215 BindingValue::Value("http://api.svc:8080".to_string())
216 );
217 assert!(horizon_binding.public_url.is_none());
218 } else {
219 panic!("Expected Horizon binding");
220 }
221 }
222
223 #[test]
224 fn test_horizon_binding_with_public_url() {
225 let binding = ContainerBinding::horizon_with_public_url(
226 "api",
227 "http://api.svc:8080",
228 "https://api.example.com",
229 );
230
231 if let ContainerBinding::Horizon(horizon_binding) = binding {
232 assert_eq!(
233 horizon_binding.public_url,
234 Some(BindingValue::Value("https://api.example.com".to_string()))
235 );
236 } else {
237 panic!("Expected Horizon binding");
238 }
239 }
240
241 #[test]
242 fn test_local_binding() {
243 let binding = ContainerBinding::local("my-container", "http://my-container.svc:8080");
244
245 if let ContainerBinding::Local(local_binding) = binding {
246 assert_eq!(
247 local_binding.container_name,
248 BindingValue::Value("my-container".to_string())
249 );
250 assert_eq!(
251 local_binding.internal_url,
252 BindingValue::Value("http://my-container.svc:8080".to_string())
253 );
254 assert!(local_binding.public_url.is_none());
255 } else {
256 panic!("Expected Local binding");
257 }
258 }
259
260 #[test]
261 fn test_local_binding_with_public_url() {
262 let binding = ContainerBinding::local_with_public_url(
263 "my-container",
264 "http://my-container.svc:8080",
265 "http://localhost:62844",
266 );
267
268 if let ContainerBinding::Local(local_binding) = binding {
269 assert_eq!(
270 local_binding.public_url,
271 Some(BindingValue::Value("http://localhost:62844".to_string()))
272 );
273 } else {
274 panic!("Expected Local binding");
275 }
276 }
277
278 #[test]
279 fn test_get_internal_url() {
280 let horizon = ContainerBinding::horizon("api", "http://api.svc:8080");
281 assert_eq!(
282 horizon.get_internal_url(),
283 Some("http://api.svc:8080".to_string())
284 );
285
286 let local = ContainerBinding::local("api", "http://api.svc:3000");
287 assert_eq!(
288 local.get_internal_url(),
289 Some("http://api.svc:3000".to_string())
290 );
291 }
292
293 #[test]
294 fn test_serialization_roundtrip() {
295 let binding = ContainerBinding::horizon_with_public_url(
296 "api",
297 "http://api.svc:8080",
298 "https://api.example.com",
299 );
300
301 let serialized = serde_json::to_string(&binding).unwrap();
302 let deserialized: ContainerBinding = serde_json::from_str(&serialized).unwrap();
303 assert_eq!(binding, deserialized);
304 }
305}