1#[cfg(feature = "debugger")]
2use std::collections::BTreeMap;
3use std::fs::File;
4use std::io::Write;
5use std::path::{Path, PathBuf};
6
7use log::info;
8use url::Url;
9
10use flowcore::model::flow_definition::FlowDefinition;
11use flowcore::model::flow_manifest::{DEFAULT_MANIFEST_FILENAME, FlowManifest};
12use flowcore::model::function_definition::FunctionDefinition;
13use flowcore::model::input::Input;
14use flowcore::model::metadata::MetaData;
15#[cfg(feature = "debugger")]
16use flowcore::model::name::HasName;
17#[cfg(feature = "debugger")]
18use flowcore::model::route::HasRoute;
19use flowcore::model::runtime_function::RuntimeFunction;
20
21use crate::compiler::compile::CompilerTables;
22use crate::errors::*;
23
24pub fn create_manifest(
28 flow: &FlowDefinition,
29 debug_symbols: bool,
30 manifest_url: &Url,
31 tables: &CompilerTables,
32 #[cfg(feature = "debugger")] source_urls: BTreeMap<String, Url>,
33) -> Result<FlowManifest> {
34 info!("Writing flow manifest to '{}'", manifest_url);
35
36 let mut manifest = FlowManifest::new(MetaData::from(flow));
37
38 for function in &tables.functions {
40 manifest.add_function(function_to_runtimefunction(
41 manifest_url,
42 function,
43 debug_symbols,
44 ).chain_err(|| "Could not convert function to runtime function")?);
45 }
46
47 manifest.set_lib_references(&tables.libs);
48 manifest.set_context_references(&tables.context_functions);
49 #[cfg(feature = "debugger")]
50 manifest.set_source_urls(source_urls);
51
52 Ok(manifest)
53}
54
55pub fn write_flow_manifest(
59 flow: FlowDefinition,
60 debug_symbols: bool,
61 destination: &Path,
62 tables: &CompilerTables,
63 #[cfg(feature = "debugger")] source_urls: BTreeMap<String, Url>,
64) -> Result<PathBuf> {
65 info!("\n==== Generating Manifest");
66
67 let mut filename = destination.to_path_buf();
68 filename.push(DEFAULT_MANIFEST_FILENAME);
69 filename.set_extension("json");
70 let mut manifest_file =
71 File::create(&filename).chain_err(|| "Could not create manifest file")?;
72 let manifest_url =
73 Url::from_file_path(&filename).map_err(|_| "Could not parse Url from file path")?;
74 let manifest = create_manifest(
75 &flow,
76 debug_symbols,
77 &manifest_url,
78 tables,
79 #[cfg(feature = "debugger")] source_urls,
80 )
81 .chain_err(|| "Could not create manifest from parsed flow and compiler tables")?;
82
83 manifest_file
84 .write_all(
85 serde_json::to_string_pretty(&manifest)
86 .chain_err(|| "Could not pretty format the manifest JSON contents")?
87 .as_bytes(),
88 )
89 .chain_err(|| "Could not write manifest data bytes to created manifest file")?;
90
91 Ok(filename)
92}
93
94fn function_to_runtimefunction(
99 manifest_url: &Url,
100 function: &FunctionDefinition,
101 debug_symbols: bool,
102) -> Result<RuntimeFunction> {
103 #[cfg(feature = "debugger")]
104 let name = if debug_symbols {
105 function.alias().to_string()
106 } else {
107 "".to_string()
108 };
109
110 #[cfg(feature = "debugger")]
111 let route = if debug_symbols {
112 function.route().to_string()
113 } else {
114 "".to_string()
115 };
116
117 let implementation_location = implementation_location_relative(function, manifest_url)
119 .chain_err(|| "Could not create Url for relative implementation location")?;
120
121 let mut runtime_inputs = vec![];
122 for input in function.get_inputs() {
123 runtime_inputs.push(Input::from(input));
124 }
125
126 Ok(RuntimeFunction::new(
127 #[cfg(feature = "debugger")]
128 name,
129 #[cfg(feature = "debugger")]
130 route,
131 implementation_location,
132 runtime_inputs,
133 function.get_id(),
134 function.get_flow_id(),
135 function.get_output_connections(),
136 debug_symbols,
137 ))
138}
139
140fn implementation_location_relative(function: &FunctionDefinition, manifest_url: &Url) -> Result<String> {
144 if let Some(ref lib_reference) = function.get_lib_reference() {
145 Ok(lib_reference.to_string())
146 } else if let Some(ref context_reference) = function.get_context_reference() {
147 Ok(context_reference.to_string())
148 } else {
149 let implementation_path = function.get_implementation();
150 let implementation_url = Url::from_file_path(implementation_path)
151 .map_err(|_| { format!("Could not create Url from file path: {implementation_path}") })?
152 .to_string();
153
154 let mut manifest_base_url = manifest_url.clone();
155 manifest_base_url
156 .path_segments_mut()
157 .map_err(|_| "cannot be base")?
158 .pop();
159
160 info!("Manifest base = '{}'", manifest_base_url.to_string());
161 info!("Absolute implementation path = '{implementation_path}'");
162 let relative_path =
163 implementation_url.replace(&format!("{}/", manifest_base_url.as_str()), "");
164 info!("Relative implementation path = '{}'", relative_path);
165 Ok(relative_path)
166 }
167}
168
169#[cfg(test)]
170mod test {
171 use serde_json::json;
172 use url::Url;
173
174 use flowcore::model::datatype::{ARRAY_TYPE, GENERIC_TYPE, STRING_TYPE};
175 use flowcore::model::function_definition::FunctionDefinition;
176 use flowcore::model::input::InputInitializer;
177 use flowcore::model::io::IO;
178 use flowcore::model::name::Name;
179 use flowcore::model::output_connection::{OutputConnection, Source};
180 use flowcore::model::output_connection::Source::Output;
181 use flowcore::model::route::Route;
182
183 use super::function_to_runtimefunction;
184
185 #[test]
186 fn function_with_sub_route_output_generation() {
187 let function = FunctionDefinition::new(
188 Name::from("Stdout"),
189 false,
190 "context://stdio/stdout".to_string(),
191 Name::from("print"),
192 vec![],
193 vec![
194 IO::new(vec!(GENERIC_TYPE.into()), Route::default()),
195 IO::new(vec!(STRING_TYPE.into()), Route::default()),
196 ],
197 Url::parse("file:///fake/file").expect("Could not parse Url"),
198 Route::from("/flow0/stdout"),
199 None,
200 Some(Url::parse("context://stdio/stdout").expect("Could not parse Url")),
201 vec![
202 OutputConnection::new(
203 Source::default(),
204 1,
205 0,
206 0,
207 String::default(),
208 #[cfg(feature = "debugger")]
209 String::default(),
210 ),
211 OutputConnection::new(
212 Output("sub_route".into()),
213 2,
214 0,
215 0,
216 String::default(),
217 #[cfg(feature = "debugger")]
218 String::default(),
219 ),
220 ],
221 0,
222 0,
223 );
224
225 let expected = "{
226 'function_id': 0,
227 'flow_id': 0,
228 'implementation_location': 'context://stdio/stdout',
229 'output_connections': [
230 {
231 'destination_id': 1,
232 'destination_io_number': 0,
233 'destination_flow_id': 0
234 },
235 {
236 'source': {
237 'Output': 'sub_route'
238 },
239 'destination_id': 2,
240 'destination_io_number': 0,
241 'destination_flow_id': 0
242 }
243 ]
244}";
245
246 let br = Box::new(function) as Box<FunctionDefinition>;
247
248 let runtime_process = function_to_runtimefunction(
249 &Url::parse("file://test").expect("Couldn't parse test Url"),
250 &br,
251 false,
252 )
253 .expect("Could not convert compile time function to runtime function");
254
255 let serialized_process = serde_json::to_string_pretty(&runtime_process)
256 .expect("Could not convert function content to json");
257 assert_eq!(serialized_process, expected.replace('\'', "\""));
258 }
259
260 #[test]
261 fn function_generation() {
262 let function = FunctionDefinition::new(
263 Name::from("Stdout"),
264 false,
265 "context://stdio/stdout".to_string(),
266 Name::from("print"),
267 vec![],
268 vec![IO::new(vec!(STRING_TYPE.into()), Route::default())],
269 Url::parse("file:///fake/file").expect("Could not parse Url"),
270 Route::from("/flow0/stdout"),
271 None,
272 Some(Url::parse("context://stdio/stdout").expect("Could not parse Url")),
273 vec![OutputConnection::new(
274 Source::default(),
275 1,
276 0,
277 0,
278 String::default(),
279 #[cfg(feature = "debugger")]
280 String::default(),
281 )],
282 0,
283 0,
284 );
285
286 let expected = "{
287 'function_id': 0,
288 'flow_id': 0,
289 'implementation_location': 'context://stdio/stdout',
290 'output_connections': [
291 {
292 'destination_id': 1,
293 'destination_io_number': 0,
294 'destination_flow_id': 0
295 }
296 ]
297}";
298
299 let br = Box::new(function) as Box<FunctionDefinition>;
300
301 let process = function_to_runtimefunction(
302 &Url::parse("file://test").expect("Couldn't parse test Url"),
303 &br,
304 false,
305 )
306 .expect("Could not convert compile time function to runtime function");
307
308 let serialized_process = serde_json::to_string_pretty(&process)
309 .expect("Could not convert function content to json");
310 assert_eq!(serialized_process, expected.replace('\'', "\""));
311 }
312
313 #[test]
314 fn function_with_initialized_input_generation() {
315 let mut io = IO::new(vec!(STRING_TYPE.into()), Route::default());
316 io.set_initializer(Some(InputInitializer::Once(json!("Hello")))).expect("Could not set initializer");
317
318 let function = FunctionDefinition::new(
319 Name::from("Stdout"),
320 false,
321 "context://stdio/stdout".to_string(),
322 Name::from("print"),
323 vec![io],
324 vec![],
325 Url::parse("file:///fake/file").expect("Could not parse Url"),
326 Route::from("/flow0/stdout"),
327 None,
328 Some(Url::parse("context://stdio/stdout").expect("Could not parse Url")),
329 vec![],
330 0,
331 0,
332 );
333
334 let expected = "{
335 'function_id': 0,
336 'flow_id': 0,
337 'implementation_location': 'context://stdio/stdout',
338 'inputs': [
339 {
340 'initializer': {
341 'once': 'Hello'
342 }
343 }
344 ]
345}";
346
347 let br = Box::new(function) as Box<FunctionDefinition>;
348 let process = function_to_runtimefunction(
349 &Url::parse("file://test").expect("Couldn't parse test Url"),
350 &br,
351 false,
352 )
353 .expect("Could not convert compile time function to runtime function");
354
355 let serialized_process = serde_json::to_string_pretty(&process)
356 .expect("Could not convert function content to json");
357 assert_eq!(expected.replace('\'', "\""), serialized_process);
358 }
359
360 #[test]
361 fn function_with_constant_input_generation() {
362 let mut io = IO::new(vec!(STRING_TYPE.into()), Route::default());
363 io.set_initializer(Some(InputInitializer::Always(json!("Hello")))).expect("Could not set initializer");
364
365 let function = FunctionDefinition::new(
366 Name::from("Stdout"),
367 false,
368 "context://stdio/stdout".to_string(),
369 Name::from("print"),
370 vec![io],
371 vec![],
372 Url::parse("file:///fake/file").expect("Could not parse Url"),
373 Route::from("/flow0/stdout"),
374 None,
375 Some(Url::parse("context://stdio/stdout").expect("Could not parse Url")),
376 vec![],
377 0,
378 0,
379 );
380
381 let expected = "{
382 'function_id': 0,
383 'flow_id': 0,
384 'implementation_location': 'context://stdio/stdout',
385 'inputs': [
386 {
387 'initializer': {
388 'always': 'Hello'
389 }
390 }
391 ]
392}";
393
394 let br = Box::new(function) as Box<FunctionDefinition>;
395 let process = function_to_runtimefunction(
396 &Url::parse("file://test").expect("Couldn't parse test Url"),
397 &br,
398 false,
399 )
400 .expect("Could not convert compile time function to runtime function");
401
402 let serialized_process = serde_json::to_string_pretty(&process)
403 .expect("Could not convert function content to json");
404 assert_eq!(expected.replace('\'', "\""), serialized_process);
405 }
406
407 #[test]
408 fn function_with_array_input_generation() {
409 let io = IO::new(vec!("array/string".into()), Route::default());
410
411 let function = FunctionDefinition::new(
412 Name::from("Stdout"),
413 false,
414 "context://stdio/stdout".to_string(),
415 Name::from("print"),
416 vec![io],
417 vec![],
418 Url::parse("file:///fake/file").expect("Could not parse Url"),
419 Route::from("/flow0/stdout"),
420 None,
421 Some(Url::parse("context://stdio/stdout").expect("Could not parse Url")),
422 vec![],
423 0,
424 0,
425 );
426
427 let expected = "{
428 'function_id': 0,
429 'flow_id': 0,
430 'implementation_location': 'context://stdio/stdout',
431 'inputs': [
432 {
433 'array_order': 1
434 }
435 ]
436}";
437
438 let br = Box::new(function) as Box<FunctionDefinition>;
439 let process = function_to_runtimefunction(
440 &Url::parse("file://test").expect("Couldn't parse test Url"),
441 &br,
442 false,
443 )
444 .expect("Could not convert compile time function to runtime function");
445
446 let serialized_process = serde_json::to_string_pretty(&process)
447 .expect("Could not convert function content to json");
448 assert_eq!(serialized_process, expected.replace('\'', "\""));
449 }
450
451 fn test_function() -> FunctionDefinition {
452 FunctionDefinition::new(
453 Name::from("Stdout"),
454 false,
455 "context://stdio/stdout".to_string(),
456 Name::from("print"),
457 vec![],
458 vec![IO::new(vec!(STRING_TYPE.into()), Route::default())],
459 Url::parse("file:///fake/file").expect("Could not parse Url"),
460 Route::from("/flow0/stdout"),
461 None,
462 Some(Url::parse("context://stdio/stdout")
463 .expect("Could not parse Url")),
464 vec![OutputConnection::new(
465 Source::default(),
466 1,
467 0,
468 0,
469 String::default(),
470 #[cfg(feature = "debugger")]
471 String::default(),
472 )],
473 0,
474 0,
475 )
476 }
477
478 #[test]
479 fn function_to_code_with_debug_generation() {
480 let function = test_function();
481
482 #[cfg(feature = "debugger")]
483 let expected = "{
484 'name': 'print',
485 'route': '/flow0/stdout',
486 'function_id': 0,
487 'flow_id': 0,
488 'implementation_location': 'context://stdio/stdout',
489 'output_connections': [
490 {
491 'destination_id': 1,
492 'destination_io_number': 0,
493 'destination_flow_id': 0
494 }
495 ]
496}";
497 #[cfg(not(feature = "debugger"))]
498 let expected = "{
499 'function_id': 0,
500 'flow_id': 0,
501 'implementation_location': 'context://stdio/stdout',
502 'output_connections': [
503 {
504 'destination_id': 1,
505 'destination_io_number': 0,
506 'destination_flow_id': 0
507 }
508 ]
509}";
510 let br = Box::new(function) as Box<FunctionDefinition>;
511
512 let process = function_to_runtimefunction(
513 &Url::parse("file://test").expect("Couldn't parse test Url"),
514 &br,
515 true,
516 )
517 .expect("Could not convert compile time function to runtime function");
518
519 let serialized_process = serde_json::to_string_pretty(&process)
520 .expect("Could not convert function content to json");
521 assert_eq!(serialized_process, expected.replace('\'', "\""));
522 }
523
524 #[test]
525 fn function_with_array_element_output_generation() {
526 let function = FunctionDefinition::new(
527 Name::from("Stdout"),
528 false,
529 "context://stdio/stdout".to_string(),
530 Name::from("print"),
531 vec![],
532 vec![IO::new(vec!(ARRAY_TYPE.into()), Route::default())],
533 Url::parse("file:///fake/file").expect("Could not parse Url"),
534 Route::from("/flow0/stdout"),
535 None,
536 Some(Url::parse("context://stdio/stdout").expect("Could not parse Url")),
537 vec![OutputConnection::new(
538 Output("/0".into()),
539 1,
540 0,
541 0,
542 String::default(),
543 #[cfg(feature = "debugger")]
544 String::default(),
545 )],
546 0,
547 0,
548 );
549
550 let expected = "{
551 'function_id': 0,
552 'flow_id': 0,
553 'implementation_location': 'context://stdio/stdout',
554 'output_connections': [
555 {
556 'source': {
557 'Output': '/0'
558 },
559 'destination_id': 1,
560 'destination_io_number': 0,
561 'destination_flow_id': 0
562 }
563 ]
564}";
565
566 let br = Box::new(function) as Box<FunctionDefinition>;
567
568 let process = function_to_runtimefunction(
569 &Url::parse("file://test").expect("Couldn't parse test Url"),
570 &br,
571 false,
572 )
573 .expect("Could not convert compile time function to runtime function");
574
575 let serialized_process = serde_json::to_string_pretty(&process)
576 .expect("Could not convert function content to json");
577 assert_eq!(serialized_process, expected.replace('\'', "\""));
578 }
579}