use std::fmt;
use log::debug;
use serde_derive::{Deserialize, Serialize};
use crate::errors::*;
use crate::model::datatype::DataType;
use crate::model::io::IO;
use crate::model::name::Name;
use crate::model::route::HasRoute;
use crate::model::route::Route;
use crate::model::validation::Validate;
#[derive(Deserialize, Serialize, Default, Debug, Clone)]
#[serde(deny_unknown_fields)]
pub struct Connection {
#[serde(default, skip_serializing_if = "String::is_empty")]
name: Name,
from: Route,
#[serde(deserialize_with = "super::route_array_serde::route_or_route_array")]
to: Vec<Route>,
#[serde(skip)]
from_io: IO,
#[serde(skip)]
to_io: IO,
#[serde(skip)]
level: usize,
#[serde(skip)]
origin_flow_id: usize,
}
#[derive(Debug)]
#[allow(clippy::upper_case_acronyms)]
pub enum Direction {
FROM,
TO,
}
impl fmt::Display for Connection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match (self.from_io.flow_io(), self.to_io.flow_io()) {
(true, true) => write!(
f,
"(f){} --> {}(f)",
self.from_io.route(),
self.to_io.route()
),
(true, false) => write!(f, "(f){} --> {}", self.from_io.route(), self.to_io.route()),
(false, true) => write!(
f,
" {} --> {}(f)",
self.from_io.route(),
self.to_io.route()
),
(false, false) => write!(f, " {} --> {}", self.from_io.route(), self.to_io.route()),
}
}
}
impl Validate for Connection {
fn validate(&self) -> Result<()> {
self.name.validate()?;
self.from.validate()?;
for destination in &self.to {
destination.validate()?;
}
Ok(())
}
}
impl Connection {
pub fn new<R>(from_route: R, to_route: R) -> Self
where
R: Into<Route>,
{
Connection {
from: from_route.into(),
to: vec![to_route.into()],
..Default::default()
}
}
#[cfg(feature = "debugger")]
pub fn name(&self) -> &Name {
&self.name
}
pub fn connect(&mut self, from_io: IO, to_io: IO, level: usize) -> Result<()> {
let from_io_subroute = "";
DataType::compatible_types(from_io.datatypes(), to_io.datatypes(),
&Route::from(from_io_subroute))
.chain_err(|| format!("Incompatible source and destination types:\n\
Source: '{}/{from_io_subroute}' of types {:?}\n\
Destination: '{}' of types {:?}",
from_io.route(), from_io.datatypes(),
to_io.route(), to_io.datatypes()))?;
debug!(
"Connection built from '{}' to '{}'",
from_io.route(),
to_io.route()
);
self.from_io = from_io;
self.to_io = to_io;
self.level = level;
Ok(())
}
pub fn from(&self) -> &Route {
&self.from
}
pub fn from_io(&self) -> &IO {
&self.from_io
}
pub fn from_io_mut(&mut self) -> &mut IO {
&mut self.from_io
}
pub fn to(&self) -> &Vec<Route> {
&self.to
}
pub fn to_io(&self) -> &IO {
&self.to_io
}
pub fn to_io_mut(&mut self) -> &mut IO {
&mut self.to_io
}
pub fn set_origin_flow_id(&mut self, origin_flow_id: usize) {
self.origin_flow_id = origin_flow_id;
}
pub fn level(&self) -> usize {
self.level
}
}
#[cfg(test)]
mod test {
use url::Url;
use crate::deserializers::deserializer::get_deserializer;
use crate::errors::*;
use crate::model::validation::Validate;
use super::Connection;
fn toml_from_str(content: &str) -> Result<Connection> {
let url = Url::parse("file:///fake.toml").expect("Could not parse URL");
let deserializer =
get_deserializer::<Connection>(&url).expect("Could not get deserializer");
deserializer.deserialize(content, Some(&url))
}
#[test]
fn single_destination_deserialization() {
let input_str = r#"
from = "source"
to = "destination"
"#;
let connection: Result<Connection> = toml_from_str(input_str);
assert!(connection.is_ok(), "Could not deserialize Connection");
}
#[test]
fn multiple_destination_deserialization() {
let input_str = r#"
from = "source"
to = ["destination", "destination2"]
"#;
let connection: Result<Connection> = toml_from_str(input_str);
assert!(connection.is_ok(), "Could not deserialize Connection");
}
#[test]
fn display_connection() {
let connection1 = Connection::new("input/number", "process_1");
println!("Connection: {connection1}");
}
#[test]
fn validate_connection() {
let connection1 = Connection::new("input/number", "process_1");
assert!(connection1.validate().is_ok());
}
#[test]
fn deserialize_extra_field_fails() {
let input_str = r#"
name = "input"
foo = "extra token"
type = "object"
"#;
let connection: Result<Connection> = toml_from_str(input_str);
assert!(
connection.is_err(),
"Deserialized invalid connection TOML without error, but should not have."
);
}
}