use std::collections::HashSet;
use std::fmt;
use error_chain::bail;
use serde_derive::{Deserialize, Serialize};
use crate::errors::*;
use crate::model::datatype::{DataType, GENERIC_TYPE};
use crate::model::datatype::HasDataTypes;
use crate::model::input::InputInitializer;
use crate::model::name::HasName;
use crate::model::name::Name;
use crate::model::route::HasRoute;
use crate::model::route::Route;
use crate::model::route::SetIORoutes;
use crate::model::validation::Validate;
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Default)]
#[allow(clippy::upper_case_acronyms)]
pub enum IOType {
#[default]
FunctionInput,
FunctionOutput,
FlowInput,
FlowOutput,
}
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
#[serde(deny_unknown_fields)]
#[allow(clippy::upper_case_acronyms)]
pub struct IO {
#[serde(default = "Name::default")]
#[serde(skip_serializing_if = "Name::empty")]
name: Name,
#[serde(rename = "type", default = "default_types",
deserialize_with = "super::datatype_array_serde::datatype_or_datatype_array")]
datatypes: Vec<DataType>,
#[serde(rename = "value")]
initializer: Option<InputInitializer>,
#[serde(skip_deserializing)]
flow_initializer: Option<InputInitializer>,
#[serde(skip_deserializing)]
route: Route,
#[serde(skip_deserializing, default = "IOType::default")]
io_type: IOType,
}
impl IO {
pub fn new<R: Into<Route>>(datatypes: Vec<DataType>, route: R) -> Self {
IO {
datatypes,
route: route.into(),
..Default::default()
}
}
pub fn new_named<R: Into<Route>, N: Into<Name>>(
datatypes: Vec<DataType>,
route: R,
name: N,
) -> Self {
IO {
datatypes,
route: route.into(),
name: name.into(),
..Default::default()
}
}
pub fn flow_io(&self) -> bool {
self.io_type == IOType::FlowInput || self.io_type == IOType::FlowOutput
}
pub fn function_io(&self) -> bool {
self.io_type == IOType::FunctionInput || self.io_type == IOType::FunctionOutput
}
pub fn io_type(&self) -> &IOType {
&self.io_type
}
pub fn set_io_type(&mut self, io_type: IOType) {
self.io_type = io_type;
}
pub fn datatypes(&self) -> &Vec<DataType> {
&self.datatypes
}
pub fn set_route(&mut self, route: &Route, io_type: &IOType) {
self.route = route.clone();
self.io_type = io_type.clone();
}
fn set_route_from_parent(&mut self, parent: &Route, io_type: &IOType) {
if self.name().is_empty() {
self.set_route(parent, io_type);
} else {
self.set_route(&Route::from(&format!("{parent}/{}", self.name)), io_type);
}
}
pub fn set_datatypes(&mut self, datatypes: &[DataType]) {
self.datatypes = datatypes.to_vec()
}
pub fn get_initializer(&self) -> &Option<InputInitializer> {
&self.initializer
}
pub fn get_flow_initializer(&self) -> &Option<InputInitializer> {
&self.flow_initializer
}
pub fn set_initializer(&mut self, function_initializer: Option<InputInitializer>) -> Result<()> {
match self.initializer {
Some(_) => bail!("Attempt to set two InputInitializers on IO @ {}", self.route),
None => {
if let Some(initializer) = &function_initializer {
DataType::compatible_types( &[DataType::value_type(initializer.get_value())], self.datatypes(), &Route::default())
.chain_err(|| "Incompatible type of initializer and Input")?;
}
self.initializer = function_initializer
}
}
Ok(())
}
pub fn set_flow_initializer(&mut self, flow_initializer: Option<InputInitializer>)
-> Result<()> {
match self.flow_initializer {
Some(_) => bail!("Attempt to set two InputInitializers on same IO"),
None => {
if let Some(initializer) = &flow_initializer {
DataType::compatible_types( &[DataType::value_type(initializer.get_value())], self.datatypes(), &Route::default())
.chain_err(|| "Incompatible type of flow initializer and Input")?;
}
self.flow_initializer = flow_initializer
}
}
Ok(())
}
}
impl fmt::Display for IO {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} @ {}", self.name, self.route)
}
}
impl HasName for IO {
fn name(&self) -> &Name {
&self.name
}
fn alias(&self) -> &Name {
&self.name
}
}
impl HasDataTypes for IO {
fn datatypes(&self) -> &Vec<DataType> {
&self.datatypes
}
}
impl HasRoute for IO {
fn route(&self) -> &Route {
&self.route
}
fn route_mut(&mut self) -> &mut Route {
&mut self.route
}
}
fn default_types() -> Vec<DataType> {
vec!(DataType::from(GENERIC_TYPE))
}
impl Validate for IO {
fn validate(&self) -> Result<()> {
if self.datatypes.is_empty() {
bail!("There must be one or more valid data types specified on an IO");
}
for datatype in self.datatypes() {
datatype.valid()?;
}
Ok(())
}
}
#[allow(clippy::upper_case_acronyms)]
pub type IOSet = Vec<IO>;
impl Validate for IOSet {
fn validate(&self) -> Result<()> {
let mut name_set = HashSet::new();
for io in self {
io.validate()?;
if io.name.is_empty() && self.len() > 1 {
bail!("Cannot have empty IO name when there are multiple IOs");
}
if !name_set.insert(&io.name) {
bail!("Two IOs cannot have the same name: '{}'", io.name);
}
}
Ok(())
}
}
impl SetIORoutes for IOSet {
fn set_io_routes_from_parent(&mut self, parent: &Route, io_type: IOType) {
for io in self {
io.set_route_from_parent(parent, &io_type)
}
}
}
pub trait Find {
fn find_by_subroute(
&mut self,
subroute: &Route,
) -> Result<IO>;
}
impl Find for IOSet {
fn find_by_subroute(
&mut self,
sub_route: &Route,
) -> Result<IO> {
for io in self {
for datatype in io.datatypes().clone() {
if datatype.is_array() {
let (array_route, index, array_index) = sub_route.without_trailing_array_index();
if array_index && (Route::from(io.name()) == array_route.into_owned())
{
let mut found = io.clone();
found.set_datatypes(&[datatype.array_type()
.ok_or("No type within array")?]);
let new_route = Route::from(format!("{}/{index}", found.route()));
found.set_route(&new_route, &io.io_type);
return Ok(found);
}
}
if Route::from(io.name()) == *sub_route {
return Ok(io.clone());
}
}
}
bail!("No output with sub-route '{}' was found", sub_route)
}
}
#[cfg(test)]
mod test {
use url::Url;
use crate::deserializers::deserializer::get_deserializer;
use crate::errors::*;
use crate::model::datatype::{DataType, GENERIC_TYPE, STRING_TYPE};
use crate::model::io::{IOSet, IOType};
use crate::model::name::HasName;
use crate::model::name::Name;
use crate::model::route::Route;
use crate::model::validation::Validate;
use super::Find;
use super::IO;
fn toml_from_str(content: &str) -> Result<IO> {
let url = Url::parse("file:///fake.toml").expect("Could not parse URL");
let deserializer = get_deserializer::<IO>(&url).expect("Could not get deserializer");
deserializer.deserialize(content, Some(&url))
}
#[test]
fn deserialize_empty_string() {
let output: IO = match toml_from_str("") {
Ok(x) => x,
Err(_) => panic!("TOML does not parse"),
};
assert!(output.validate().is_ok(), "IO does not validate()");
assert_eq!(output.datatypes[0], DataType::from(GENERIC_TYPE));
assert_eq!(output.name, Name::default());
}
#[test]
fn deserialize_valid_type() {
let input_str = "
type = 'string'
";
let output: IO = match toml_from_str(input_str) {
Ok(x) => x,
Err(_) => panic!("TOML does not parse"),
};
assert!(output.validate().is_ok(), "IO does not validate()");
}
#[test]
fn deserialize_invalid_type() {
let input_str = "
type = 'Unknown'
";
let output: IO = match toml_from_str(input_str) {
Ok(x) => x,
Err(_) => panic!("TOML does not parse"),
};
assert!(output.validate().is_err());
}
#[test]
fn deserialize_name() {
let input_str = "
name = '/sub_route'
type = 'string'
";
let output: IO = match toml_from_str(input_str) {
Ok(x) => x,
Err(_) => panic!("TOML does not parse"),
};
assert!(output.validate().is_ok(), "IO does not validate()");
assert_eq!("/sub_route", output.name.to_string());
}
#[test]
fn deserialize_valid_string_type() {
let input_str = "
name = 'input'
type = 'string'
";
let input: IO = match toml_from_str(input_str) {
Ok(x) => x,
Err(_) => panic!("TOML does not parse"),
};
assert!(input.validate().is_ok(), "IO does not validate()");
}
#[test]
fn methods_work() {
let input_str = "
name = 'input'
type = 'string'
";
let input: IO = match toml_from_str(input_str) {
Ok(x) => x,
Err(_) => panic!("TOML does not parse"),
};
assert_eq!(Name::from("input"), *input.name());
assert_eq!(1, input.datatypes().len());
assert_eq!(DataType::from(STRING_TYPE), input.datatypes()[0]);
}
#[test]
fn deserialize_valid_json_type() {
let input_str = "
name = 'input'
type = 'object'
";
let input: IO = match toml_from_str(input_str) {
Ok(x) => x,
Err(_) => panic!("TOML does not parse"),
};
assert!(input.validate().is_ok(), "IO does not validate()");
}
#[test]
fn deserialize_extra_field_fails() {
let input_str = "
name = 'input'
foo = 'extra token'
type = 'object'
";
let input: Result<IO> = toml_from_str(input_str);
assert!(input.is_err());
}
#[test]
fn unique_io_names_validate() {
let io0 = IO {
name: Name::from("io_name"),
datatypes: vec!(DataType::from(STRING_TYPE)),
io_type: IOType::FunctionInput,
initializer: None,
..Default::default()
};
let io1 = IO {
name: Name::from("different_name"),
datatypes: vec!(DataType::from(STRING_TYPE)),
io_type: IOType::FunctionInput,
initializer: None,
..Default::default()
};
let ioset = vec![io0, io1] as IOSet;
assert!(ioset.validate().is_ok(), "IOSet does not validate()");
}
#[test]
fn non_unique_io_names_wont_validate() {
let io0 = IO {
name: Name::from("io_name"),
datatypes: vec!(DataType::from(STRING_TYPE)),
io_type: IOType::FunctionInput,
initializer: None,
..Default::default()
};
let io1 = io0.clone();
let ioset = vec![io0, io1] as IOSet;
assert!(ioset.validate().is_err());
}
#[test]
fn multiple_inputs_empty_name_not_allowed() {
let io0 = IO {
name: Name::from("io_name"),
datatypes: vec!(DataType::from(STRING_TYPE)),
io_type: IOType::FunctionInput,
initializer: None,
..Default::default()
};
let io1 = IO {
datatypes: vec!(DataType::from(STRING_TYPE)),
io_type: IOType::FunctionInput,
initializer: None,
..Default::default()
};
let ioset = vec![io0, io1] as IOSet;
assert!(ioset.validate().is_err());
}
#[test]
fn no_datatypes_not_allowed() {
let io = IO {
name: Name::from("io_name"),
datatypes: vec!(),
io_type: IOType::FunctionInput,
initializer: None,
..Default::default()
};
assert!(io.validate().is_err());
}
#[test]
fn get_array_element_of_root_output() {
let mut outputs = vec![IO::new(vec![DataType::from("array/number")], "")] as IOSet;
let output = outputs
.find_by_subroute(&Route::from("/0"))
.expect("Expected to find an IO");
assert_eq!(*output.name(), Name::default());
}
}