1#![warn(missing_docs)]
13#![deny(unsafe_code)] #![allow(clippy::items_after_test_module)]
15
16#[cfg(test)]
17#[path = "../tests/helpers/mod.rs"]
18#[macro_use]
19mod helpers;
20
21#[cfg(not(target_arch = "wasm32"))]
22#[cfg(test)]
23#[path = "../tests/features/mod.rs"]
24mod features;
25
26#[cfg(test)]
27#[path = "../tests/unit/lib_test.rs"]
28mod lib_test;
29
30pub use vrp_core as core;
31pub use vrp_pragmatic as pragmatic;
32#[cfg(feature = "scientific-format")]
33pub use vrp_scientific as scientific;
34
35pub mod extensions;
36
37use crate::extensions::import::import_problem;
38use crate::extensions::solve::config::{create_builder_from_config, Config};
39use std::io::{BufReader, BufWriter};
40use std::sync::Arc;
41use vrp_core::models::Problem as CoreProblem;
42use vrp_core::prelude::{GenericError, Solver};
43use vrp_pragmatic::format::problem::{serialize_problem, PragmaticProblem, Problem};
44use vrp_pragmatic::format::solution::{write_pragmatic, PragmaticOutputType};
45use vrp_pragmatic::format::FormatError;
46use vrp_pragmatic::get_unique_locations;
47use vrp_pragmatic::validation::ValidationContext;
48
49#[cfg(not(target_arch = "wasm32"))]
50#[allow(unsafe_code)]
51mod c_interop {
52 use super::*;
53 use crate::extensions::solve::config::read_config;
54 use std::ffi::{CStr, CString};
55 use std::os::raw::c_char;
56 use std::panic;
57 use std::panic::UnwindSafe;
58 use std::slice;
59 use vrp_core::prelude::GenericError;
60 use vrp_pragmatic::format::problem::{deserialize_matrix, deserialize_problem};
61 use vrp_pragmatic::format::{CoordIndex, MultiFormatError};
62
63 type Callback = extern "C" fn(*const c_char);
64
65 fn to_string(pointer: *const c_char) -> String {
66 let slice = unsafe { CStr::from_ptr(pointer).to_bytes() };
67 std::str::from_utf8(slice).unwrap().to_string()
68 }
69
70 fn call_back(result: Result<String, GenericError>, success: Callback, failure: Callback) {
71 match result {
72 Ok(ok) => {
73 let ok = CString::new(ok.as_bytes()).unwrap();
74 success(ok.as_ptr());
75 }
76 Err(err) => {
77 let error = CString::new(err.to_string().as_bytes()).unwrap();
78 failure(error.as_ptr());
79 }
80 };
81 }
82
83 fn catch_panic<F: FnOnce() + UnwindSafe>(failure: Callback, action: F) {
84 if let Err(err) = panic::catch_unwind(action) {
85 let message = err
86 .downcast_ref::<&str>()
87 .cloned()
88 .or_else(|| err.downcast_ref::<String>().map(|str| str.as_str()))
89 .map(|msg| format!("panic: '{msg}'"))
90 .unwrap_or_else(|| "panic with unknown type".to_string());
91
92 let error = CString::new(message.as_bytes()).unwrap();
93 failure(error.as_ptr());
94 }
95 }
96
97 #[no_mangle]
100 extern "C" fn get_routing_locations(problem: *const c_char, success: Callback, failure: Callback) {
101 catch_panic(failure, || {
102 let problem = to_string(problem);
103 let problem = BufReader::new(problem.as_bytes());
104 let result =
105 deserialize_problem(problem).map_err(From::from).and_then(|problem| get_locations_serialized(&problem));
106
107 call_back(result, success, failure);
108 });
109 }
110
111 #[no_mangle]
113 extern "C" fn convert_to_pragmatic(
114 format: *const c_char,
115 inputs: *const *const c_char,
116 input_len: usize,
117 success: Callback,
118 failure: Callback,
119 ) {
120 catch_panic(failure, || {
121 let format = to_string(format);
122 let inputs = unsafe { slice::from_raw_parts(inputs, input_len).to_vec() };
123 let inputs = inputs.iter().map(|p| to_string(*p)).collect::<Vec<_>>();
124 let readers = inputs.iter().map(|p| BufReader::new(p.as_bytes())).collect::<Vec<_>>();
125
126 match import_problem(format.as_str(), Some(readers)) {
127 Ok(problem) => {
128 let mut writer = BufWriter::new(Vec::new());
129 serialize_problem(&problem, &mut writer).unwrap();
130 let bytes = writer.into_inner().expect("cannot use writer");
131 let problem = CString::new(bytes).unwrap();
132
133 success(problem.as_ptr());
134 }
135 Err(err) => {
136 let error = CString::new(err.to_string().as_bytes()).unwrap();
137 failure(error.as_ptr());
138 }
139 }
140 });
141 }
142
143 #[no_mangle]
145 extern "C" fn validate_pragmatic(
146 problem: *const c_char,
147 matrices: *const *const c_char,
148 matrices_len: usize,
149 success: Callback,
150 failure: Callback,
151 ) {
152 catch_panic(failure, || {
153 let problem = to_string(problem);
154 let matrices = unsafe { slice::from_raw_parts(matrices, matrices_len).to_vec() };
155 let matrices = matrices.iter().map(|m| to_string(*m)).collect::<Vec<_>>();
156
157 let problem = deserialize_problem(BufReader::new(problem.as_bytes()));
158 let matrices = matrices
159 .iter()
160 .map(|matrix| deserialize_matrix(BufReader::new(matrix.as_bytes())))
161 .collect::<Result<Vec<_>, _>>();
162
163 let result = match (problem, matrices) {
164 (Ok(problem), Ok(matrices)) => {
165 let matrices = if matrices.is_empty() { None } else { Some(&matrices) };
166 let coord_index = CoordIndex::new(&problem);
167
168 ValidationContext::new(&problem, matrices, &coord_index).validate()
169 }
170 (Err(errors), Ok(_)) | (Ok(_), Err(errors)) => Err(errors),
171 (Err(errors1), Err(errors2)) => {
172 Err(MultiFormatError::from(errors1.into_iter().chain(errors2).collect::<Vec<_>>()))
173 }
174 }
175 .map_err(From::from)
176 .map(|_| "[]".to_string());
177
178 call_back(result, success, failure);
179 });
180 }
181
182 #[no_mangle]
184 extern "C" fn solve_pragmatic(
185 problem: *const c_char,
186 matrices: *const *const c_char,
187 matrices_len: usize,
188 config: *const c_char,
189 success: Callback,
190 failure: Callback,
191 ) {
192 catch_panic(failure, || {
193 let problem = to_string(problem);
194 let matrices = unsafe { slice::from_raw_parts(matrices, matrices_len).to_vec() };
195 let matrices = matrices.iter().map(|m| to_string(*m)).collect::<Vec<_>>();
196
197 let result =
198 if matrices.is_empty() { problem.read_pragmatic() } else { (problem, matrices).read_pragmatic() }
199 .map_err(From::from)
200 .and_then(|problem| {
201 read_config(BufReader::new(to_string(config).as_bytes()))
202 .map_err(|err| GenericError::from(serialize_as_config_error(err.to_string().as_str())))
203 .map(|config| (problem, config))
204 })
205 .and_then(|(problem, config)| get_solution_serialized(Arc::new(problem), config));
206
207 call_back(result, success, failure);
208 });
209 }
210
211 #[cfg(test)]
212 mod tests {
213 use super::*;
214 use crate::helpers::generate::SIMPLE_PROBLEM;
215
216 #[test]
217 fn can_use_to_string() {
218 let c_str = CString::new("asd").unwrap();
219 assert_eq!(to_string(c_str.as_ptr() as *const c_char), "asd".to_string());
220 }
221
222 #[test]
223 fn can_use_callback() {
224 extern "C" fn success1(_: *const c_char) {}
227 extern "C" fn failure1(_: *const c_char) {
228 unreachable!()
229 }
230 call_back(Ok("success".to_string()), success1, failure1);
231
232 extern "C" fn success2(_: *const c_char) {
233 unreachable!()
234 }
235 extern "C" fn failure2(_: *const c_char) {}
236 call_back(Err("failure".into()), success2, failure2);
237 }
238
239 #[test]
240 fn can_catch_panic_with_string_literal() {
241 extern "C" fn callback(msg: *const c_char) {
242 assert_eq!(to_string(msg), "panic: 'invaders detected!'");
243 }
244 catch_panic(callback, || panic!("invaders detected!"));
245 catch_panic(callback, || panic!("invaders {}!", "detected"));
246 }
247
248 #[test]
249 fn can_get_locations() {
250 extern "C" fn success(locations: *const c_char) {
251 let locations = to_string(locations);
252 assert!(locations.starts_with('['));
253 assert!(locations.ends_with(']'));
254 assert!(locations.len() > 2);
255 }
256 extern "C" fn failure(err: *const c_char) {
257 unreachable!("got {}", to_string(err))
258 }
259
260 let problem = CString::new(SIMPLE_PROBLEM).unwrap();
261 get_routing_locations(problem.as_ptr() as *const c_char, success, failure)
262 }
263
264 #[test]
265 fn can_validate_simple_problem() {
266 extern "C" fn success(solution: *const c_char) {
267 assert_eq!(to_string(solution), "[]")
268 }
269 extern "C" fn failure(err: *const c_char) {
270 unreachable!("{}", to_string(err))
271 }
272
273 let problem = CString::new(SIMPLE_PROBLEM).unwrap();
274 let matrices = CString::new("[]").unwrap();
275
276 validate_pragmatic(
277 problem.as_ptr() as *const c_char,
278 matrices.as_ptr() as *const *const c_char,
279 0,
280 success,
281 failure,
282 );
283 }
284
285 #[test]
286 fn can_validate_empty_problem() {
287 extern "C" fn success(solution: *const c_char) {
288 unreachable!("got {}", to_string(solution))
289 }
290 extern "C" fn failure(err: *const c_char) {
291 let err = to_string(err);
292 assert!(err.contains("E0000"));
293 assert!(err.contains("cause"));
294 assert!(err.contains("action"));
295 }
296
297 let problem = CString::new("").unwrap();
298 let matrices = CString::new("[]").unwrap();
299
300 validate_pragmatic(
301 problem.as_ptr() as *const c_char,
302 matrices.as_ptr() as *const *const c_char,
303 0,
304 success,
305 failure,
306 );
307 }
308
309 #[test]
310 fn can_solve_problem() {
311 extern "C" fn success(solution: *const c_char) {
312 let solution = to_string(solution);
313 assert!(solution.starts_with('{'));
314 assert!(solution.ends_with('}'));
315 assert!(solution.len() > 2);
316 }
317 extern "C" fn failure(err: *const c_char) {
318 unreachable!("{}", to_string(err))
319 }
320
321 let problem = CString::new(SIMPLE_PROBLEM).unwrap();
322 let matrices = CString::new("[]").unwrap();
323 let config = CString::new("{\"termination\": {\"max-generations\": 1}}").unwrap();
324
325 solve_pragmatic(
326 problem.as_ptr() as *const c_char,
327 matrices.as_ptr() as *const *const c_char,
328 0,
329 config.as_ptr() as *const c_char,
330 success,
331 failure,
332 );
333 }
334 }
335}
336
337#[cfg(feature = "py_bindings")]
338#[cfg(not(target_arch = "wasm32"))]
339mod py_interop {
340 use super::*;
341 use crate::extensions::solve::config::read_config;
342 use pyo3::exceptions::PyOSError;
343 use pyo3::prelude::*;
344 use std::io::BufReader;
345 use vrp_pragmatic::format::problem::{deserialize_matrix, deserialize_problem};
346 use vrp_pragmatic::format::CoordIndex;
347
348 #[pyfunction]
352 fn convert_to_pragmatic(format: &str, inputs: Vec<String>) -> PyResult<String> {
353 let readers = inputs.iter().map(|p| BufReader::new(p.as_bytes())).collect::<Vec<_>>();
354 import_problem(format, Some(readers))
355 .and_then(|problem| {
356 let mut writer = BufWriter::new(Vec::new());
357 serialize_problem(&problem, &mut writer).unwrap();
358
359 writer
360 .into_inner()
361 .map_err(|err| format!("BufWriter: {err}").into())
362 .and_then(|bytes| String::from_utf8(bytes).map_err(|err| format!("StringUTF8: {err}").into()))
363 })
364 .map_err(|err| PyOSError::new_err(err.to_string()))
365 }
366
367 #[pyfunction]
369 fn get_routing_locations(problem: String) -> PyResult<String> {
370 deserialize_problem(BufReader::new(problem.as_bytes()))
371 .map_err(From::from)
372 .and_then(|problem| get_locations_serialized(&problem))
373 .map_err(|err| PyOSError::new_err(err.to_string()))
374 }
375
376 #[pyfunction]
378 fn solve_pragmatic(problem: String, matrices: Vec<String>, config: String) -> PyResult<String> {
379 deserialize_problem(BufReader::new(problem.as_bytes()))
381 .and_then(|problem| {
382 matrices
383 .iter()
384 .map(|m| deserialize_matrix(BufReader::new(m.as_bytes())))
385 .collect::<Result<Vec<_>, _>>()
386 .map(|matrices| (problem, matrices))
387 })
388 .and_then(|(problem, matrices)| {
389 let matrices = if matrices.is_empty() { None } else { Some(&matrices) };
390 let coord_index = CoordIndex::new(&problem);
391
392 ValidationContext::new(&problem, matrices, &coord_index).validate()
393 })
394 .map_err(|errs| PyOSError::new_err(errs.to_string()))?;
395
396 if matrices.is_empty() { problem.read_pragmatic() } else { (problem, matrices).read_pragmatic() }
398 .map_err(From::from)
399 .and_then(|problem| {
400 read_config(BufReader::new(config.as_bytes()))
401 .map_err(|err| GenericError::from(serialize_as_config_error(err.to_string().as_str())))
402 .map(|config| (problem, config))
403 })
404 .and_then(|(problem, config)| get_solution_serialized(Arc::new(problem), config))
405 .map_err(|err| PyOSError::new_err(err.to_string()))
406 }
407
408 #[pymodule]
409 fn vrp_cli(m: &Bound<'_, PyModule>) -> PyResult<()> {
410 m.add_function(wrap_pyfunction!(convert_to_pragmatic, m)?)?;
411 m.add_function(wrap_pyfunction!(get_routing_locations, m)?)?;
412 m.add_function(wrap_pyfunction!(solve_pragmatic, m)?)?;
413 Ok(())
414 }
415}
416
417#[cfg(target_arch = "wasm32")]
418mod wasm {
419 extern crate serde_json;
420 extern crate wasm_bindgen;
421
422 use super::*;
423 use vrp_pragmatic::format::problem::Matrix;
424 use vrp_pragmatic::format::CoordIndex;
425 use wasm_bindgen::prelude::*;
426
427 #[wasm_bindgen]
430 pub fn get_routing_locations(problem: JsValue) -> Result<JsValue, JsValue> {
431 let problem: Problem =
432 serde_wasm_bindgen::from_value(problem).map_err(|err| JsValue::from_str(err.to_string().as_str()))?;
433
434 get_locations_serialized(&problem)
435 .map(|locations| JsValue::from_str(locations.as_str()))
436 .map_err(|err| JsValue::from_str(err.to_string().as_str()))
437 }
438
439 #[wasm_bindgen]
441 pub fn validate_pragmatic(problem: JsValue, matrices: JsValue) -> Result<JsValue, JsValue> {
442 let problem: Problem =
443 serde_wasm_bindgen::from_value(problem).map_err(|err| JsValue::from_str(err.to_string().as_str()))?;
444 let matrices: Vec<Matrix> =
445 serde_wasm_bindgen::from_value(matrices).map_err(|err| JsValue::from_str(err.to_string().as_str()))?;
446 let coord_index = CoordIndex::new(&problem);
447
448 let matrices = if matrices.is_empty() { None } else { Some(&matrices) };
449 ValidationContext::new(&problem, matrices, &coord_index)
450 .validate()
451 .map_err(|errs| JsValue::from_str(errs.to_json().as_str()))
452 .map(|_| JsValue::from_str("[]"))
453 }
454
455 #[wasm_bindgen]
457 pub fn convert_to_pragmatic(format: &str, inputs: JsValue) -> Result<JsValue, JsValue> {
458 let inputs: Vec<String> =
459 serde_wasm_bindgen::from_value(inputs).map_err(|err| JsValue::from_str(err.to_string().as_str()))?;
460
461 let readers = inputs.iter().map(|input| BufReader::new(input.as_bytes())).collect();
462
463 match import_problem(format, Some(readers)) {
464 Ok(problem) => {
465 let mut writer = BufWriter::new(Vec::new());
466 serialize_problem(&problem, &mut writer).unwrap();
467
468 let bytes = writer.into_inner().unwrap();
469 let result = String::from_utf8(bytes).unwrap();
470
471 Ok(JsValue::from_str(result.as_str()))
472 }
473 Err(err) => Err(JsValue::from_str(err.to_string().as_str())),
474 }
475 }
476
477 #[wasm_bindgen]
479 pub fn solve_pragmatic(problem: JsValue, matrices: JsValue, config: JsValue) -> Result<JsValue, JsValue> {
480 let problem: Problem =
481 serde_wasm_bindgen::from_value(problem).map_err(|err| JsValue::from_str(err.to_string().as_str()))?;
482
483 let matrices: Vec<Matrix> =
484 serde_wasm_bindgen::from_value(matrices).map_err(|err| JsValue::from_str(err.to_string().as_str()))?;
485
486 let problem = Arc::new(
487 if matrices.is_empty() { problem.read_pragmatic() } else { (problem, matrices).read_pragmatic() }
488 .map_err(|errs| JsValue::from_str(errs.to_json().as_str()))?,
489 );
490
491 let config: Config = serde_wasm_bindgen::from_value(config)
492 .map_err(|err| serialize_as_config_error(&err.to_string()))
493 .map_err(|err| JsValue::from_str(err.as_str()))?;
494
495 get_solution_serialized(problem, config)
496 .map(|problem| JsValue::from_str(problem.as_str()))
497 .map_err(|err| JsValue::from_str(&err.to_string()))
498 }
499}
500
501pub fn get_locations_serialized(problem: &Problem) -> Result<String, GenericError> {
503 let locations = get_unique_locations(problem);
506 serde_json::to_string_pretty(&locations).map_err(|err| err.to_string().into())
507}
508
509pub fn get_solution_serialized(problem: Arc<CoreProblem>, config: Config) -> Result<String, GenericError> {
511 let solution = create_builder_from_config(problem.clone(), Default::default(), &config)
512 .and_then(|builder| builder.build())
513 .map(|config| Solver::new(problem.clone(), config))
514 .and_then(|solver| solver.solve())
515 .map_err(|err| {
516 FormatError::new(
517 "E0003".to_string(),
518 "cannot find any solution".to_string(),
519 format!("please submit a bug and share original problem and routing matrix. Error: '{err}'"),
520 )
521 .to_json()
522 })?;
523
524 let output_type = if config.output.and_then(|output_cfg| output_cfg.include_geojson).unwrap_or(false) {
525 PragmaticOutputType::Combined
526 } else {
527 Default::default()
528 };
529
530 let mut writer = BufWriter::new(Vec::new());
531 write_pragmatic(problem.as_ref(), &solution, output_type, &mut writer)?;
532
533 let bytes = writer.into_inner().map_err(|err| format!("{err}"))?;
534 let result = String::from_utf8(bytes).map_err(|err| format!("{err}"))?;
535
536 Ok(result)
537}
538
539fn serialize_as_config_error(err: &str) -> String {
540 FormatError::new(
541 "E0004".to_string(),
542 "cannot read config".to_string(),
543 format!("check config definition. Error: '{err}'"),
544 )
545 .to_json()
546}