azure_functions/
registry.rs

1use crate::codegen::{bindings, Function};
2use lazy_static::lazy_static;
3use semver::Version;
4use std::collections::{hash_map::Iter, HashMap};
5
6// Note: package names are expected to be lowercase.
7const STORAGE_PACKAGE_NAME: &str = "microsoft.azure.webjobs.extensions.storage";
8const STORAGE_PACKAGE_VERSION: &str = "3.0.3";
9const EVENT_GRID_PACKAGE_NAME: &str = "microsoft.azure.webjobs.extensions.eventgrid";
10const EVENT_GRID_PACKAGE_VERSION: &str = "2.0.0";
11const EVENT_HUBS_PACKAGE_NAME: &str = "microsoft.azure.webjobs.extensions.eventhubs";
12const EVENT_HUBS_PACKAGE_VERSION: &str = "3.0.3";
13const COSMOS_DB_PACKAGE_NAME: &str = "microsoft.azure.webjobs.extensions.cosmosdb";
14const COSMOS_DB_PACKAGE_VERSION: &str = "3.0.3";
15const SIGNALR_PACKAGE_NAME: &str = "microsoft.azure.webjobs.extensions.signalrservice";
16const SIGNALR_PACKAGE_VERSION: &str = "1.0.0";
17const SERVICE_BUS_PACKAGE_NAME: &str = "microsoft.azure.webjobs.extensions.servicebus";
18const SERVICE_BUS_PACKAGE_VERSION: &str = "3.0.3";
19const TWILIO_PACKAGE_NAME: &str = "microsoft.azure.webjobs.extensions.twilio";
20const TWILIO_PACKAGE_VERSION: &str = "3.0.0";
21const SEND_GRID_PACKAGE_NAME: &str = "microsoft.azure.webjobs.extensions.sendgrid";
22const SEND_GRID_PACKAGE_VERSION: &str = "3.0.0";
23const DURABLE_TASK_PACKAGE_NAME: &str = "microsoft.azure.webjobs.extensions.durabletask";
24const DURABLE_TASK_PACKAGE_VERSION: &str = "2.0.0";
25
26lazy_static! {
27    // This comes from https://github.com/Azure/azure-functions-core-tools/blob/master/src/Azure.Functions.Cli/Common/Constants.cs#L63
28    static ref BINDING_EXTENSIONS: HashMap<&'static str, (&'static str, &'static str)> = {
29        let mut map = HashMap::new();
30        map.insert(
31            bindings::BlobTrigger::binding_type(),
32            (STORAGE_PACKAGE_NAME, STORAGE_PACKAGE_VERSION),
33        );
34        map.insert(
35            bindings::Blob::binding_type(),
36            (STORAGE_PACKAGE_NAME, STORAGE_PACKAGE_VERSION),
37        );
38        map.insert(
39            bindings::Queue::binding_type(),
40            (STORAGE_PACKAGE_NAME, STORAGE_PACKAGE_VERSION),
41        );
42        map.insert(
43            bindings::QueueTrigger::binding_type(),
44            (STORAGE_PACKAGE_NAME, STORAGE_PACKAGE_VERSION),
45        );
46        map.insert(
47            bindings::Table::binding_type(),
48            (STORAGE_PACKAGE_NAME, STORAGE_PACKAGE_VERSION),
49        );
50        map.insert(
51            bindings::EventGridTrigger::binding_type(),
52            (EVENT_GRID_PACKAGE_NAME, EVENT_GRID_PACKAGE_VERSION),
53        );
54        map.insert(
55            bindings::EventHubTrigger::binding_type(),
56            (EVENT_HUBS_PACKAGE_NAME, EVENT_HUBS_PACKAGE_VERSION),
57        );
58        map.insert(
59            bindings::EventHub::binding_type(),
60            (EVENT_HUBS_PACKAGE_NAME, EVENT_HUBS_PACKAGE_VERSION),
61        );
62        map.insert(
63            bindings::CosmosDbTrigger::binding_type(),
64            (COSMOS_DB_PACKAGE_NAME, COSMOS_DB_PACKAGE_VERSION),
65        );
66        map.insert(
67            bindings::CosmosDb::binding_type(),
68            (COSMOS_DB_PACKAGE_NAME, COSMOS_DB_PACKAGE_VERSION),
69        );
70        map.insert(
71            bindings::SignalRConnectionInfo::binding_type(),
72            (SIGNALR_PACKAGE_NAME, SIGNALR_PACKAGE_VERSION),
73        );
74        map.insert(
75            bindings::SignalR::binding_type(),
76            (SIGNALR_PACKAGE_NAME, SIGNALR_PACKAGE_VERSION),
77        );
78        map.insert(
79            bindings::ServiceBusTrigger::binding_type(),
80            (SERVICE_BUS_PACKAGE_NAME, SERVICE_BUS_PACKAGE_VERSION),
81        );
82        map.insert(
83            bindings::ServiceBus::binding_type(),
84            (SERVICE_BUS_PACKAGE_NAME, SERVICE_BUS_PACKAGE_VERSION),
85        );
86        map.insert(
87            bindings::TwilioSms::binding_type(),
88            (TWILIO_PACKAGE_NAME, TWILIO_PACKAGE_VERSION),
89        );
90        map.insert(
91            bindings::SendGrid::binding_type(),
92            (SEND_GRID_PACKAGE_NAME, SEND_GRID_PACKAGE_VERSION),
93        );
94        map.insert(
95            bindings::DurableClient::binding_type(),
96            (DURABLE_TASK_PACKAGE_NAME, DURABLE_TASK_PACKAGE_VERSION),
97        );
98        map.insert(
99            bindings::OrchestrationTrigger::binding_type(),
100            (DURABLE_TASK_PACKAGE_NAME, DURABLE_TASK_PACKAGE_VERSION),
101        );
102        map.insert(
103            bindings::ActivityTrigger::binding_type(),
104            (DURABLE_TASK_PACKAGE_NAME, DURABLE_TASK_PACKAGE_VERSION),
105        );
106        map
107    };
108}
109
110pub struct Registry<'a> {
111    functions: HashMap<String, &'a Function>,
112    registered: HashMap<String, &'a Function>,
113}
114
115impl<'a> Registry<'a> {
116    pub fn new(functions: &[&'a Function]) -> Registry<'a> {
117        Registry {
118            functions: functions
119                .iter()
120                .by_ref()
121                .fold(HashMap::new(), |mut map, func| {
122                    if map.insert(func.name.clone().into_owned(), func).is_some() {
123                        panic!("Azure Function '{}' has already been registered; ensure all functions have unique names.", func.name);
124                    }
125                    map
126                }),
127            registered: HashMap::new(),
128        }
129    }
130
131    pub fn register(&mut self, id: &str, name: &str) -> bool {
132        match self.functions.get(name) {
133            Some(info) => {
134                self.registered.insert(id.to_owned(), info);
135                true
136            }
137            None => false,
138        }
139    }
140
141    pub fn get(&self, id: &str) -> Option<&'a Function> {
142        self.registered.get(id).cloned()
143    }
144
145    pub fn iter(&self) -> Iter<String, &'a Function> {
146        self.functions.iter()
147    }
148
149    pub fn build_extensions_map(&self, extensions: &[(&str, &str)]) -> HashMap<String, String> {
150        let mut map = HashMap::new();
151
152        for function in self.functions.iter() {
153            for binding in function.1.bindings.iter() {
154                if let Some(t) = binding.binding_type() {
155                    if let Some(extension) = BINDING_EXTENSIONS.get(t) {
156                        Self::insert_extension(&mut map, extension.0, extension.1);
157                    }
158                }
159            }
160        }
161
162        for extension in extensions {
163            Self::insert_extension(&mut map, &extension.0.to_lowercase(), extension.1);
164        }
165
166        map
167    }
168
169    fn insert_extension(map: &mut HashMap<String, String>, name: &str, version: &str) {
170        match map.get_mut(name) {
171            Some(current) => {
172                if Version::parse(version) > Version::parse(current) {
173                    *current = version.to_owned();
174                }
175            }
176            None => {
177                map.insert(name.to_owned(), version.to_owned());
178            }
179        };
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186    use crate::codegen::bindings::{Binding, Direction};
187    use std::borrow::Cow;
188
189    #[test]
190    fn it_creates_an_emptry_registry_from_an_empty_slice() {
191        let registry = Registry::new(&[]);
192        assert_eq!(registry.iter().count(), 0);
193    }
194
195    #[test]
196    fn it_creates_a_registry_from_a_list_of_functions() {
197        let registry = Registry::new(&[
198            &Function {
199                name: Cow::Borrowed("function1"),
200                disabled: false,
201                bindings: Cow::Borrowed(&[]),
202                invoker: None,
203                manifest_dir: None,
204                file: None,
205            },
206            &Function {
207                name: Cow::Borrowed("function2"),
208                disabled: false,
209                bindings: Cow::Borrowed(&[]),
210                invoker: None,
211                manifest_dir: None,
212                file: None,
213            },
214            &Function {
215                name: Cow::Borrowed("function3"),
216                disabled: false,
217                bindings: Cow::Borrowed(&[]),
218                invoker: None,
219                manifest_dir: None,
220                file: None,
221            },
222        ]);
223        assert_eq!(registry.iter().count(), 3);
224        assert!(registry
225            .iter()
226            .all(|(k, _)| *k == "function1" || *k == "function2" || *k == "function3"));
227    }
228
229    #[test]
230    fn it_registers_a_function() {
231        let mut registry = Registry::new(&[&Function {
232            name: Cow::Borrowed("function1"),
233            disabled: false,
234            bindings: Cow::Borrowed(&[]),
235            invoker: None,
236            manifest_dir: None,
237            file: None,
238        }]);
239        assert_eq!(registry.iter().count(), 1);
240
241        let p1 = *registry.iter().nth(0).unwrap().1;
242
243        assert!(registry.register("id", "function1"));
244
245        let p2 = registry.get("id").unwrap();
246        assert_eq!(p1 as *const _, p2 as *const _);
247    }
248
249    #[test]
250    fn it_returns_false_if_function_is_not_present() {
251        let mut registry = Registry::new(&[&Function {
252            name: Cow::Borrowed("function1"),
253            disabled: false,
254            bindings: Cow::Borrowed(&[]),
255            invoker: None,
256            manifest_dir: None,
257            file: None,
258        }]);
259        assert_eq!(registry.iter().count(), 1);
260
261        assert_eq!(registry.register("id", "not_present"), false);
262    }
263
264    #[test]
265    fn it_builds_an_extensions_map() {
266        let registry = Registry::new(&[&Function {
267            name: Cow::Borrowed("function1"),
268            disabled: false,
269            bindings: Cow::Borrowed(&[
270                Binding::Http(bindings::Http {
271                    name: Cow::Borrowed("binding1"),
272                }),
273                Binding::Queue(bindings::Queue {
274                    name: Cow::Borrowed("binding2"),
275                    queue_name: Cow::Borrowed("some_queue"),
276                    connection: None,
277                }),
278                Binding::Blob(bindings::Blob {
279                    name: Cow::Borrowed("binding3"),
280                    path: Cow::Borrowed("some_path"),
281                    connection: None,
282                    direction: Direction::Out,
283                }),
284            ]),
285            invoker: None,
286            manifest_dir: None,
287            file: None,
288        }]);
289
290        let map = registry.build_extensions_map(&[]);
291        assert_eq!(map.len(), 1);
292        assert_eq!(
293            map.get(STORAGE_PACKAGE_NAME),
294            Some(&STORAGE_PACKAGE_VERSION.to_owned())
295        );
296    }
297
298    #[test]
299    fn it_uses_the_latest_extension_version() {
300        let registry = Registry::new(&[&Function {
301            name: Cow::Borrowed("function"),
302            disabled: false,
303            bindings: Cow::Borrowed(&[Binding::Queue(bindings::Queue {
304                name: Cow::Borrowed("binding"),
305                queue_name: Cow::Borrowed("some_queue"),
306                connection: None,
307            })]),
308            invoker: None,
309            manifest_dir: None,
310            file: None,
311        }]);
312
313        let map =
314            registry.build_extensions_map(&[(&STORAGE_PACKAGE_NAME.to_uppercase(), "1000.0.0")]);
315        assert_eq!(map.len(), 1);
316        assert_eq!(map.get(STORAGE_PACKAGE_NAME), Some(&"1000.0.0".to_owned()));
317    }
318
319    #[test]
320    fn it_builds_an_empty_extensions_map() {
321        let registry = Registry::new(&[&Function {
322            name: Cow::Borrowed("function1"),
323            disabled: false,
324            bindings: Cow::Borrowed(&[Binding::Http(bindings::Http {
325                name: Cow::Borrowed("binding1"),
326            })]),
327            invoker: None,
328            manifest_dir: None,
329            file: None,
330        }]);
331        assert_eq!(registry.build_extensions_map(&[]).len(), 0);
332    }
333}