azure_functions/bindings/
service_bus_message.rs

1use crate::{
2    http::Body,
3    rpc::{typed_data::Data, TypedData},
4    FromVec,
5};
6use serde::de::Error;
7use serde::Deserialize;
8use serde_json::{from_str, Result, Value};
9use std::borrow::Cow;
10use std::fmt;
11use std::str::from_utf8;
12
13/// Represents a Service Bus message output binding.
14///
15/// The following binding attributes are supported:
16///
17/// | Name                | Description                                                                                                                               |
18/// |---------------------|-------------------------------------------------------------------------------------------------------------------------------------------|
19/// | `name`              | The name of the parameter being bound.                                                                                                    |
20/// | `queue_name`        | The name of the queue. Use only if sending queue messages, not for a topic.                                                               |
21/// | `topic_name`        | The name of the topic. Use only if sending topic messages, not for a queue.                                                               |
22/// | `subscription_name` | The name of the subscription. Use only if sending topic messages, not for a queue.                                                        |
23/// | `connection`        | The name of an app setting that contains the Service Bus connection string to use for this binding. Defaults to `AzureWebJobsServiceBus`. |
24///
25/// # Examples
26///
27/// An example that creates a Service Bus queue message based on a HTTP trigger:
28///
29/// ```rust
30/// use azure_functions::{
31///     bindings::{HttpRequest, ServiceBusMessage},
32///     func,
33/// };
34///
35/// #[func]
36/// #[binding(name = "$return", queue_name = "example", connection = "connection")]
37/// pub fn create_queue_message(req: HttpRequest) -> ServiceBusMessage {
38///     format!(
39///         "Hello from Rust, {}!\n",
40///         req.query_params().get("name").map_or("stranger", |x| x)
41///     )
42///     .into()
43/// }
44/// ```
45///
46/// An example that creates a Service Bus topic message based on a HTTP trigger:
47///
48/// ```rust
49/// use azure_functions::{
50///     bindings::{HttpRequest, ServiceBusMessage},
51///     func,
52/// };
53///
54/// #[func]
55/// #[binding(
56///     name = "$return",
57///     topic_name = "mytopic",
58///     subscription_name = "mysubscription",
59///     connection = "connection"
60/// )]
61/// pub fn create_topic_message(req: HttpRequest) -> ServiceBusMessage {
62///     format!(
63///         "Hello from Rust, {}!\n",
64///         req.query_params().get("name").map_or("stranger", |x| x)
65///     )
66///     .into()
67/// }
68/// ```
69#[derive(Debug, Clone)]
70pub struct ServiceBusMessage(TypedData);
71
72impl ServiceBusMessage {
73    /// Gets the content of the message as a string.
74    ///
75    /// Returns None if there is no valid string representation of the message.
76    pub fn as_str(&self) -> Option<&str> {
77        match &self.0.data {
78            Some(Data::String(s)) => Some(s),
79            Some(Data::Json(s)) => Some(s),
80            Some(Data::Bytes(b)) => from_utf8(b).ok(),
81            Some(Data::Stream(s)) => from_utf8(s).ok(),
82            _ => None,
83        }
84    }
85
86    /// Gets the content of the message as a slice of bytes.
87    pub fn as_bytes(&self) -> &[u8] {
88        match &self.0.data {
89            Some(Data::String(s)) => s.as_bytes(),
90            Some(Data::Json(s)) => s.as_bytes(),
91            Some(Data::Bytes(b)) => b,
92            Some(Data::Stream(s)) => s,
93            _ => panic!("unexpected data for service bus message content"),
94        }
95    }
96
97    /// Deserializes the message as JSON to the requested type.
98    pub fn as_json<'b, T>(&'b self) -> Result<T>
99    where
100        T: Deserialize<'b>,
101    {
102        from_str(
103            self.as_str().ok_or_else(|| {
104                ::serde_json::Error::custom("service bus message is not valid UTF-8")
105            })?,
106        )
107    }
108}
109
110impl fmt::Display for ServiceBusMessage {
111    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112        write!(f, "{}", self.as_str().unwrap_or(""))
113    }
114}
115
116impl<'a> From<&'a str> for ServiceBusMessage {
117    fn from(content: &'a str) -> Self {
118        ServiceBusMessage(TypedData {
119            data: Some(Data::String(content.to_owned())),
120        })
121    }
122}
123
124impl From<String> for ServiceBusMessage {
125    fn from(content: String) -> Self {
126        ServiceBusMessage(TypedData {
127            data: Some(Data::String(content)),
128        })
129    }
130}
131
132impl From<&Value> for ServiceBusMessage {
133    fn from(content: &Value) -> Self {
134        ServiceBusMessage(TypedData {
135            data: Some(Data::Json(content.to_string())),
136        })
137    }
138}
139
140impl From<Value> for ServiceBusMessage {
141    fn from(content: Value) -> Self {
142        ServiceBusMessage(TypedData {
143            data: Some(Data::Json(content.to_string())),
144        })
145    }
146}
147
148impl<'a> From<&'a [u8]> for ServiceBusMessage {
149    fn from(content: &'a [u8]) -> Self {
150        ServiceBusMessage(TypedData {
151            data: Some(Data::Bytes(content.to_owned())),
152        })
153    }
154}
155
156impl From<Vec<u8>> for ServiceBusMessage {
157    fn from(content: Vec<u8>) -> Self {
158        ServiceBusMessage(TypedData {
159            data: Some(Data::Bytes(content)),
160        })
161    }
162}
163
164#[doc(hidden)]
165impl From<TypedData> for ServiceBusMessage {
166    fn from(data: TypedData) -> Self {
167        ServiceBusMessage(data)
168    }
169}
170
171#[doc(hidden)]
172impl FromVec<ServiceBusMessage> for TypedData {
173    fn from_vec(vec: Vec<ServiceBusMessage>) -> Self {
174        TypedData {
175            data: Some(Data::Json(
176                Value::Array(vec.into_iter().map(Into::into).collect()).to_string(),
177            )),
178        }
179    }
180}
181
182impl Into<String> for ServiceBusMessage {
183    fn into(self) -> String {
184        match self.0.data {
185            Some(Data::String(s)) => s,
186            Some(Data::Json(s)) => s,
187            Some(Data::Bytes(b)) => String::from_utf8(b)
188                .expect("service bus message does not contain valid UTF-8 bytes"),
189            Some(Data::Stream(s)) => String::from_utf8(s)
190                .expect("service bus message does not contain valid UTF-8 bytes"),
191            _ => panic!("unexpected data for service bus message content"),
192        }
193    }
194}
195
196impl Into<Value> for ServiceBusMessage {
197    fn into(self) -> Value {
198        // TODO: this is not an efficient encoding for bytes/stream
199        match self.0.data {
200            Some(Data::String(s)) => Value::String(s),
201            Some(Data::Json(s)) => {
202                from_str(&s).expect("service bus message does not contain valid JSON data")
203            }
204            Some(Data::Bytes(b)) => Value::Array(
205                b.iter()
206                    .map(|n| Value::Number(u64::from(*n).into()))
207                    .collect(),
208            ),
209            Some(Data::Stream(s)) => Value::Array(
210                s.iter()
211                    .map(|n| Value::Number(u64::from(*n).into()))
212                    .collect(),
213            ),
214            _ => panic!("unexpected data for service bus message content"),
215        }
216    }
217}
218
219impl Into<Vec<u8>> for ServiceBusMessage {
220    fn into(self) -> Vec<u8> {
221        match self.0.data {
222            Some(Data::String(s)) => s.into_bytes(),
223            Some(Data::Json(s)) => s.into_bytes(),
224            Some(Data::Bytes(b)) => b,
225            Some(Data::Stream(s)) => s,
226            _ => panic!("unexpected data for service bus message content"),
227        }
228    }
229}
230
231impl<'a> Into<Body<'a>> for ServiceBusMessage {
232    fn into(self) -> Body<'a> {
233        match self.0.data {
234            Some(Data::String(s)) => s.into(),
235            Some(Data::Json(s)) => Body::Json(Cow::from(s)),
236            Some(Data::Bytes(b)) => b.into(),
237            Some(Data::Stream(s)) => s.into(),
238            _ => panic!("unexpected data for service bus message content"),
239        }
240    }
241}
242
243#[doc(hidden)]
244impl Into<TypedData> for ServiceBusMessage {
245    fn into(self) -> TypedData {
246        self.0
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253    use serde::{Deserialize, Serialize};
254    use serde_json::{json, to_value};
255    use std::fmt::Write;
256
257    #[test]
258    fn it_has_string_content() {
259        const MESSAGE: &'static str = "test message";
260
261        let message: ServiceBusMessage = MESSAGE.into();
262        assert_eq!(message.as_str().unwrap(), MESSAGE);
263
264        let data: TypedData = message.into();
265        assert_eq!(data.data, Some(Data::String(MESSAGE.to_string())));
266    }
267
268    #[test]
269    fn it_has_json_content() {
270        #[derive(Serialize, Deserialize)]
271        struct SerializedData {
272            message: String,
273        };
274
275        const MESSAGE: &'static str = "test";
276
277        let data = SerializedData {
278            message: MESSAGE.to_string(),
279        };
280
281        let message: ServiceBusMessage = ::serde_json::to_value(data).unwrap().into();
282        assert_eq!(
283            message.as_json::<SerializedData>().unwrap().message,
284            MESSAGE
285        );
286
287        let data: TypedData = message.into();
288        assert_eq!(
289            data.data,
290            Some(Data::Json(r#"{"message":"test"}"#.to_string()))
291        );
292    }
293
294    #[test]
295    fn it_has_bytes_content() {
296        const MESSAGE: &'static [u8] = &[1, 2, 3];
297
298        let message: ServiceBusMessage = MESSAGE.into();
299        assert_eq!(message.as_bytes(), MESSAGE);
300
301        let data: TypedData = message.into();
302        assert_eq!(data.data, Some(Data::Bytes(MESSAGE.to_vec())));
303    }
304
305    #[test]
306    fn it_displays_as_a_string() {
307        const MESSAGE: &'static str = "test";
308
309        let message: ServiceBusMessage = MESSAGE.into();
310
311        let mut s = String::new();
312        write!(s, "{}", message).unwrap();
313
314        assert_eq!(s, MESSAGE);
315    }
316
317    #[test]
318    fn it_converts_from_str() {
319        let message: ServiceBusMessage = "test".into();
320        assert_eq!(message.as_str().unwrap(), "test");
321    }
322
323    #[test]
324    fn it_converts_from_string() {
325        let message: ServiceBusMessage = "test".to_string().into();
326        assert_eq!(message.as_str().unwrap(), "test");
327    }
328
329    #[test]
330    fn it_converts_from_json() {
331        let message: ServiceBusMessage = to_value("hello world").unwrap().into();
332        assert_eq!(message.as_str().unwrap(), r#""hello world""#);
333    }
334
335    #[test]
336    fn it_converts_from_u8_slice() {
337        let message: ServiceBusMessage = [0, 1, 2][..].into();
338        assert_eq!(message.as_bytes(), [0, 1, 2]);
339    }
340
341    #[test]
342    fn it_converts_from_u8_vec() {
343        let message: ServiceBusMessage = vec![0, 1, 2].into();
344        assert_eq!(message.as_bytes(), [0, 1, 2]);
345    }
346
347    #[test]
348    fn it_converts_to_string() {
349        let message: ServiceBusMessage = "hello world!".into();
350        let s: String = message.into();
351        assert_eq!(s, "hello world!");
352    }
353
354    #[test]
355    fn it_converts_to_json() {
356        let message: ServiceBusMessage = json!({"hello": "world"}).into();
357        let value: Value = message.into();
358        assert_eq!(value.to_string(), r#"{"hello":"world"}"#);
359    }
360
361    #[test]
362    fn it_converts_to_bytes() {
363        let message: ServiceBusMessage = vec![1, 2, 3].into();
364        let bytes: Vec<u8> = message.into();
365        assert_eq!(bytes, [1, 2, 3]);
366    }
367
368    #[test]
369    fn it_converts_to_body() {
370        let message: ServiceBusMessage = "hello world!".into();
371        let body: Body = message.into();
372        assert_eq!(body.as_str().unwrap(), "hello world!");
373
374        let message: ServiceBusMessage = json!({"hello": "world"}).into();
375        let body: Body = message.into();
376        assert_eq!(body.as_str().unwrap(), r#"{"hello":"world"}"#);
377
378        let message: ServiceBusMessage = vec![1, 2, 3].into();
379        let body: Body = message.into();
380        assert_eq!(body.as_bytes(), [1, 2, 3]);
381    }
382
383    #[test]
384    fn it_converts_to_typed_data() {
385        let message: ServiceBusMessage = "test".into();
386        let data: TypedData = message.into();
387        assert_eq!(data.data, Some(Data::String("test".to_string())));
388
389        let message: ServiceBusMessage = to_value("test").unwrap().into();
390        let data: TypedData = message.into();
391        assert_eq!(data.data, Some(Data::Json(r#""test""#.to_string())));
392
393        let message: ServiceBusMessage = vec![1, 2, 3].into();
394        let data: TypedData = message.into();
395        assert_eq!(data.data, Some(Data::Bytes([1, 2, 3].to_vec())));
396    }
397}