1use crate::{EgressLandscape, Handle, HandleCollection, IngressLandscape};
2use serde::de::{Error, IgnoredAny, MapAccess, Visitor};
3use serde::{Deserialize, Deserializer};
4use serde_value::Value;
5use std::collections::BTreeMap;
6use std::fmt::Formatter;
7use strut_factory::impl_deserialize_field;
8
9#[derive(Debug, Default, Clone, PartialEq)]
19pub struct RabbitMqConfig {
20 default_handle: Handle,
21 extra_handles: HandleCollection,
22 ingress: IngressLandscape,
23 egress: EgressLandscape,
24}
25
26impl RabbitMqConfig {
27 pub fn default_handle(&self) -> &Handle {
29 &self.default_handle
30 }
31
32 pub fn extra_handles(&self) -> &HandleCollection {
34 &self.extra_handles
35 }
36
37 pub fn ingress(&self) -> &IngressLandscape {
39 &self.ingress
40 }
41
42 pub fn egress(&self) -> &EgressLandscape {
44 &self.egress
45 }
46}
47
48impl AsRef<RabbitMqConfig> for RabbitMqConfig {
49 fn as_ref(&self) -> &RabbitMqConfig {
50 self
51 }
52}
53
54const _: () = {
55 impl<'de> Deserialize<'de> for RabbitMqConfig {
56 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
57 where
58 D: Deserializer<'de>,
59 {
60 deserializer.deserialize_map(RabbitMqConfigVisitor)
61 }
62 }
63
64 struct RabbitMqConfigVisitor;
65
66 impl<'de> Visitor<'de> for RabbitMqConfigVisitor {
67 type Value = RabbitMqConfig;
68
69 fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
70 formatter.write_str("a map of application RabbitMQ configuration")
71 }
72
73 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
74 where
75 A: MapAccess<'de>,
76 {
77 let mut default_handle = None;
78 let mut extra_handles = None;
79 let mut ingress = None;
80 let mut egress = None;
81
82 let mut discarded = BTreeMap::new();
83
84 while let Some(key) = map.next_key::<Value>()? {
85 let field = RabbitMqConfigField::deserialize(key.clone()).map_err(Error::custom)?;
86
87 match field {
88 RabbitMqConfigField::default_handle => {
89 field.poll(&mut map, &mut default_handle)?
90 }
91 RabbitMqConfigField::extra_handles => {
92 field.poll(&mut map, &mut extra_handles)?
93 }
94 RabbitMqConfigField::ingress => field.poll(&mut map, &mut ingress)?,
95 RabbitMqConfigField::egress => field.poll(&mut map, &mut egress)?,
96 RabbitMqConfigField::__ignore => {
97 discarded.insert(key, map.next_value()?);
98 IgnoredAny
99 }
100 };
101 }
102
103 if default_handle.is_none() {
104 default_handle =
105 Some(Handle::deserialize(Value::Map(discarded)).map_err(Error::custom)?);
106 }
107
108 Ok(RabbitMqConfig {
109 default_handle: default_handle.unwrap_or_default(),
110 extra_handles: extra_handles.unwrap_or_default(),
111 ingress: ingress.unwrap_or_default(),
112 egress: egress.unwrap_or_default(),
113 })
114 }
115 }
116
117 impl_deserialize_field!(
118 RabbitMqConfigField,
119 strut_deserialize::Slug::eq_as_slugs,
120 default_handle | default,
121 extra_handles | extra | extras,
122 ingress | inbound | incoming | subscriber | subscribers,
123 egress | outbound | outgoing | publisher | publishers,
124 );
125};
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::{DsnChunks, Egress, Exchange, Ingress};
131 use pretty_assertions::assert_eq;
132
133 #[test]
134 fn empty() {
135 let input = "";
137 let expected_output = RabbitMqConfig::default();
138
139 let actual_output = serde_yml::from_str::<RabbitMqConfig>(input).unwrap();
141
142 assert_eq!(expected_output, actual_output);
144 }
145
146 #[test]
147 fn full() {
148 let input = r#"
150host: custom-domain.com
151port: 6879
152user: test_user
153vhost: /custom
154extra:
155 other_handle:
156 vhost: /other
157inbound:
158 in_route:
159 exchange: amq.topic
160 queue: inbound_queue
161 binding_key: inbound_binding_key
162outbound:
163 out_route:
164 exchange: amq.fanout
165"#;
166 let expected_output = RabbitMqConfig {
167 default_handle: Handle::new(
168 "default",
169 DsnChunks {
170 host: "custom-domain.com",
171 port: 6879,
172 user: "test_user",
173 vhost: "/custom",
174 ..Default::default()
175 },
176 ),
177 extra_handles: HandleCollection::from([(
178 "other_handle",
179 Handle::new(
180 "other_handle",
181 DsnChunks {
182 vhost: "/other",
183 ..Default::default()
184 },
185 ),
186 )]),
187 ingress: IngressLandscape::from([(
188 "in_route",
189 Ingress::builder()
190 .with_name("in_route")
191 .with_exchange(Exchange::AmqTopic)
192 .with_queue_named("inbound_queue")
193 .with_binding_key("inbound_binding_key")
194 .build()
195 .unwrap(),
196 )]),
197 egress: EgressLandscape::from([(
198 "out_route",
199 Egress::builder()
200 .with_name("out_route")
201 .with_exchange("amq.fanout")
202 .with_routing_key("")
203 .build()
204 .unwrap(),
205 )]),
206 };
207
208 let actual_output = serde_yml::from_str::<RabbitMqConfig>(input).unwrap();
210
211 assert_eq!(expected_output, actual_output);
213 }
214}