azure_functions/bindings/
table.rs

1use crate::{
2    http::Body,
3    rpc::{typed_data::Data, TypedData},
4};
5use serde_json::{from_str, json, Map, Value};
6use std::fmt;
7
8/// Represents an Azure Storage table input or output binding.
9///
10/// The following binding attributes are supported:
11///
12/// | Name            | Description                                                                                                                        |
13/// |-----------------|------------------------------------------------------------------------------------------------------------------------------------|
14/// | `name`          | The name of the parameter being bound.                                                                                             |
15/// | `table_name`    | The name of the table.                                                                                                             |
16/// | `partition_key` | The partition key of the table entity to read or write.                                                                            |
17/// | `row_key`       | The row key of the table entity to read or write.                                                                                  |
18/// | `filter`        | The maximum number of entities to read (optional; input only).                                                                     |
19/// | `take`          | The OData filter expression of entities to read (optional; input only).                                                            |
20/// | `connection`    | The name of an app setting that contains the Storage connection string to use for this binding. Defaults to `AzureWebJobsStorage`. |
21///
22/// # Examples
23///
24/// Read a table storage row based on a key posted to the `example` queue:
25///
26/// ```rust
27/// use azure_functions::bindings::{QueueTrigger, Table};
28/// use azure_functions::func;
29/// use log::info;
30///
31/// #[func]
32/// #[binding(name = "trigger", queue_name = "example")]
33/// #[binding(name = "table", table_name = "MyTable", partition_key = "MyPartition", row_key = "{queueTrigger}")]
34/// pub fn log_row(trigger: QueueTrigger, table: Table) {
35///     info!("Row: {:?}", table.rows().nth(0));
36/// }
37/// ```
38/// Run an Azure Storage table query based on a HTTP request:
39///
40/// ```rust
41/// use azure_functions::bindings::{HttpRequest, Table};
42/// use azure_functions::func;
43/// use log::info;
44///
45/// #[func]
46/// #[binding(name = "table", table_name = "MyTable", filter = "{filter}")]
47/// pub fn log_rows(req: HttpRequest, table: Table) {
48///     for row in table.rows() {
49///         info!("Row: {:?}", row);
50///     }
51/// }
52#[derive(Default, Debug, Clone)]
53pub struct Table(Value);
54
55/// Represents the data of an Azure Storage table row.
56pub type Row = Map<String, Value>;
57
58impl Table {
59    /// Creates a new table binding.
60    ///
61    /// The new table binding can be used for output.
62    pub fn new() -> Table {
63        Table(Value::Array(Vec::new()))
64    }
65
66    /// Gets whether or not the table binding is empty (no rows).
67    pub fn is_empty(&self) -> bool {
68        self.0.as_array().unwrap().is_empty()
69    }
70
71    /// Gets the current length of the rows stored in the table binding.
72    pub fn len(&self) -> usize {
73        self.0.as_array().unwrap().len()
74    }
75
76    /// Gets the iterator over the rows stored in the table binding.
77    ///
78    /// For input bindings, this will be the rows returned from either a single entity lookup
79    /// or a filter query.
80    ///
81    /// For output bindings, this will be the rows that have been added to the table binding.
82    pub fn rows(&self) -> impl Iterator<Item = &Row> {
83        self.0
84            .as_array()
85            .unwrap()
86            .iter()
87            .map(|x| x.as_object().unwrap())
88    }
89
90    /// Adds a new row to the table binding with the specified partition and row keys.
91    pub fn add_row(&mut self, partition_key: &str, row_key: &str) -> &mut Row {
92        let array = self.0.as_array_mut().unwrap();
93
94        array.push(json!({
95            "PartitionKey": partition_key,
96            "RowKey": row_key
97        }));
98
99        array.last_mut().unwrap().as_object_mut().unwrap()
100    }
101
102    /// Adds a row as a value to the table.
103    pub fn add_row_value(&mut self, value: Value) {
104        let array = self.0.as_array_mut().unwrap();
105
106        array.push(value);
107    }
108
109    /// Gets the table as a JSON value.
110    pub fn as_value(&self) -> &Value {
111        &self.0
112    }
113}
114
115impl fmt::Display for Table {
116    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117        write!(f, "{}", self.0)
118    }
119}
120
121#[doc(hidden)]
122impl From<TypedData> for Table {
123    fn from(data: TypedData) -> Self {
124        match &data.data {
125            Some(Data::Json(s)) => {
126                let mut rows: Value =
127                    from_str(s).expect("expected valid JSON data for table binding");
128
129                if rows.is_object() {
130                    rows = Value::Array(vec![rows]);
131                }
132
133                if !rows.is_array() {
134                    panic!("expected an object or array for table binding data");
135                }
136
137                Table(rows)
138            }
139            _ => Table::new(),
140        }
141    }
142}
143
144impl Into<Value> for Table {
145    fn into(self) -> Value {
146        self.0
147    }
148}
149
150impl<'a> Into<Body<'a>> for Table {
151    fn into(self) -> Body<'a> {
152        self.0.into()
153    }
154}
155
156#[doc(hidden)]
157impl Into<TypedData> for Table {
158    fn into(self) -> TypedData {
159        TypedData {
160            data: Some(Data::Json(self.0.to_string())),
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use std::fmt::Write;
169
170    #[test]
171    fn it_constructs_an_empty_table() {
172        let table = Table::new();
173        assert_eq!(table.len(), 0);
174        assert_eq!(table.rows().count(), 0);
175        assert!(table.is_empty());
176    }
177
178    #[test]
179    fn it_is_not_empty_when_rows_are_present() {
180        let mut table = Table::new();
181        table.add_row("partition1", "row1");
182        assert!(!table.is_empty());
183    }
184
185    #[test]
186    fn it_has_a_length_equal_to_number_of_rows() {
187        let mut table = Table::new();
188        assert_eq!(table.len(), 0);
189        table.add_row("partition1", "row1");
190        table.add_row("partition2", "row2");
191        table.add_row("partition3", "row3");
192        assert_eq!(table.len(), 3);
193    }
194
195    #[test]
196    fn it_iterates_rows() {
197        let mut table = Table::new();
198        assert_eq!(table.len(), 0);
199        table.add_row("partition1", "row1");
200        table.add_row("partition2", "row2");
201        table.add_row("partition3", "row3");
202        assert_eq!(table.len(), 3);
203
204        for (i, row) in table.rows().enumerate() {
205            assert_eq!(
206                row.get("PartitionKey").unwrap().as_str().unwrap(),
207                format!("partition{}", i + 1)
208            );
209            assert_eq!(
210                row.get("RowKey").unwrap().as_str().unwrap(),
211                format!("row{}", i + 1)
212            );
213        }
214    }
215
216    #[test]
217    fn it_adds_row_value() {
218        let mut table = Table::new();
219        assert_eq!(table.len(), 0);
220        table.add_row_value(json!({
221            "PartitionKey": "partition1",
222            "RowKey": "row1",
223            "data": "hello world"
224        }));
225
226        assert_eq!(
227            table.to_string(),
228            r#"[{"PartitionKey":"partition1","RowKey":"row1","data":"hello world"}]"#
229        );
230    }
231
232    #[test]
233    fn it_casts_to_value_reference() {
234        let mut table = Table::new();
235        table.add_row("partition1", "row1");
236
237        assert_eq!(
238            table.as_value().to_string(),
239            r#"[{"PartitionKey":"partition1","RowKey":"row1"}]"#
240        );
241    }
242
243    #[test]
244    fn it_displays_as_a_string() {
245        let mut table = Table::new();
246        {
247            let row = table.add_row("partition1", "row1");
248            row.insert("data".to_string(), Value::String("value".to_string()));
249        }
250        let mut s = String::new();
251        write!(s, "{}", table).unwrap();
252
253        assert_eq!(
254            s,
255            r#"[{"PartitionKey":"partition1","RowKey":"row1","data":"value"}]"#
256        );
257    }
258
259    #[test]
260    fn it_converts_from_typed_data() {
261        const TABLE: &'static str =
262            r#"[{"PartitionKey":"partition1","RowKey":"row1","data":"value"}]"#;
263
264        let data = TypedData {
265            data: Some(Data::Json(TABLE.to_string())),
266        };
267
268        let table: Table = data.into();
269        assert_eq!(table.len(), 1);
270        assert_eq!(table.to_string(), TABLE);
271
272        let data = TypedData {
273            data: Some(Data::String("".to_string())),
274        };
275
276        let table: Table = data.into();
277        assert_eq!(table.len(), 0);
278        assert!(table.is_empty());
279    }
280
281    #[test]
282    fn it_converts_to_json() {
283        let mut table = Table::new();
284        table.add_row("partition1", "row1");
285
286        let value: Value = table.into();
287
288        assert_eq!(
289            value.to_string(),
290            r#"[{"PartitionKey":"partition1","RowKey":"row1"}]"#
291        );
292    }
293
294    #[test]
295    fn it_converts_to_body() {
296        const TABLE: &'static str =
297            r#"[{"PartitionKey":"partition1","RowKey":"row1","data":"value"}]"#;
298
299        let data = TypedData {
300            data: Some(Data::Json(TABLE.to_string())),
301        };
302
303        let table: Table = data.into();
304        let body: Body = table.into();
305        assert_eq!(
306            body.as_str().unwrap(),
307            r#"[{"PartitionKey":"partition1","RowKey":"row1","data":"value"}]"#
308        );
309    }
310
311    #[test]
312    fn it_converts_to_typed_data() {
313        let mut table = Table::new();
314        {
315            let row = table.add_row("partition1", "row1");
316            row.insert("data".to_string(), Value::String("value".to_string()));
317        }
318        let data: TypedData = table.into();
319        assert_eq!(
320            data.data,
321            Some(Data::Json(
322                r#"[{"PartitionKey":"partition1","RowKey":"row1","data":"value"}]"#.to_string()
323            ))
324        );
325    }
326}