1use crate::codegen::{bindings, Function};
2use lazy_static::lazy_static;
3use semver::Version;
4use std::collections::{hash_map::Iter, HashMap};
5
6const 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 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}