diff --git a/databroker/src/broker.rs b/databroker/src/broker.rs index 943b633a..aa6ac01e 100644 --- a/databroker/src/broker.rs +++ b/databroker/src/broker.rs @@ -46,7 +46,7 @@ pub enum ActuationError { TransmissionFailure, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum UpdateError { NotFound, WrongType, @@ -79,6 +79,9 @@ pub struct Metadata { pub entry_type: EntryType, pub change_type: ChangeType, pub description: String, + // Min and Max are typically never arrays + pub min: Option, + pub max: Option, pub allowed: Option, pub unit: Option, } @@ -219,6 +222,8 @@ pub struct EntryUpdate { // order to be able to convey "update it to None" which would // mean setting it to `Some(None)`. pub allowed: Option>, + pub min: Option>, + pub max: Option>, pub unit: Option, } @@ -259,26 +264,42 @@ impl Entry { Ok(()) } + /** + * DataType is VSS type, where we have also smaller type based on 8/16 bits + * That we do not have for DataValue + */ pub fn validate_allowed_type(&self, allowed: &Option) -> Result<(), UpdateError> { if let Some(allowed_values) = allowed { match (allowed_values, &self.metadata.data_type) { (DataValue::BoolArray(_allowed_values), DataType::Bool) => Ok(()), (DataValue::StringArray(_allowed_values), DataType::String) => Ok(()), + (DataValue::Int32Array(_allowed_values), DataType::Int8) => Ok(()), + (DataValue::Int32Array(_allowed_values), DataType::Int16) => Ok(()), (DataValue::Int32Array(_allowed_values), DataType::Int32) => Ok(()), (DataValue::Int64Array(_allowed_values), DataType::Int64) => Ok(()), + (DataValue::Uint32Array(_allowed_values), DataType::Uint8) => Ok(()), + (DataValue::Uint32Array(_allowed_values), DataType::Uint16) => Ok(()), (DataValue::Uint32Array(_allowed_values), DataType::Uint32) => Ok(()), (DataValue::Uint64Array(_allowed_values), DataType::Uint64) => Ok(()), (DataValue::FloatArray(_allowed_values), DataType::Float) => Ok(()), (DataValue::DoubleArray(_allowed_values), DataType::Double) => Ok(()), (DataValue::BoolArray(_allowed_values), DataType::BoolArray) => Ok(()), (DataValue::StringArray(_allowed_values), DataType::StringArray) => Ok(()), + (DataValue::Int32Array(_allowed_values), DataType::Int8Array) => Ok(()), + (DataValue::Int32Array(_allowed_values), DataType::Int16Array) => Ok(()), (DataValue::Int32Array(_allowed_values), DataType::Int32Array) => Ok(()), (DataValue::Int64Array(_allowed_values), DataType::Int64Array) => Ok(()), + (DataValue::Uint32Array(_allowed_values), DataType::Uint8Array) => Ok(()), + (DataValue::Uint32Array(_allowed_values), DataType::Uint16Array) => Ok(()), (DataValue::Uint32Array(_allowed_values), DataType::Uint32Array) => Ok(()), (DataValue::Uint64Array(_allowed_values), DataType::Uint64Array) => Ok(()), (DataValue::FloatArray(_allowed_values), DataType::FloatArray) => Ok(()), (DataValue::DoubleArray(_allowed_values), DataType::DoubleArray) => Ok(()), - _ => Err(UpdateError::WrongType {}), + _ => { + debug!("Unexpected combination - VSS datatype is {:?}, but list of allowed value use {:?}", + &self.metadata.data_type, allowed_values); + Err(UpdateError::WrongType {}) + } } } else { // it is allowed to set allowed to None @@ -417,12 +438,52 @@ impl Entry { } } + /// Checks if value fulfils min/max condition + /// Returns OutOfBounds if not fulfilled + fn validate_value_min_max(&self, value: &DataValue) -> Result<(), UpdateError> { + // Validate Min/Max + if let Some(min) = &self.metadata.min { + debug!("Checking min, comparing value {:?} and {:?}", value, min); + match value.greater_than_equal(min) { + Ok(true) => {} + _ => return Err(UpdateError::OutOfBounds), + }; + } + if let Some(max) = &self.metadata.max { + debug!("Checking max, comparing value {:?} and {:?}", value, max); + match value.less_than_equal(max) { + Ok(true) => {} + _ => return Err(UpdateError::OutOfBounds), + }; + } + Ok(()) + } + fn validate_value(&self, value: &DataValue) -> Result<(), UpdateError> { // Not available is always valid if value == &DataValue::NotAvailable { return Ok(()); } + // For numeric non-arrays check min/max + // For arrays we check later on value + match self.metadata.data_type { + DataType::Int8 + | DataType::Int16 + | DataType::Int32 + | DataType::Int64 + | DataType::Uint8 + | DataType::Uint16 + | DataType::Uint32 + | DataType::Uint64 + | DataType::Float + | DataType::Double => match self.validate_value_min_max(value) { + Ok(_) => {} + Err(err) => return Err(err), + }, + _ => {} + } + // Validate value match self.metadata.data_type { DataType::Bool => match value { @@ -496,106 +557,138 @@ impl Entry { }, DataType::Int8Array => match &value { DataValue::Int32Array(array) => { - let mut out_of_bounds = false; for value in array { match i8::try_from(*value) { - Ok(_) => {} - Err(_) => { - out_of_bounds = true; - break; - } + Ok(_) => match self.validate_value_min_max(&DataValue::Int32(*value)) { + Ok(_) => {} + Err(err) => return Err(err), + }, + Err(_) => return Err(UpdateError::OutOfBounds), } } - if out_of_bounds { - Err(UpdateError::OutOfBounds) - } else { - Ok(()) - } + Ok(()) } _ => Err(UpdateError::WrongType), }, DataType::Int16Array => match &value { DataValue::Int32Array(array) => { - let mut out_of_bounds = false; for value in array { match i16::try_from(*value) { - Ok(_) => {} - Err(_) => { - out_of_bounds = true; - break; - } + Ok(_) => match self.validate_value_min_max(&DataValue::Int32(*value)) { + Ok(_) => {} + Err(err) => return Err(err), + }, + Err(_) => return Err(UpdateError::OutOfBounds), } } - if out_of_bounds { - Err(UpdateError::OutOfBounds) - } else { - Ok(()) - } + Ok(()) } _ => Err(UpdateError::WrongType), }, DataType::Int32Array => match value { - DataValue::Int32Array(_) => Ok(()), + DataValue::Int32Array(array) => { + for value in array { + match self.validate_value_min_max(&DataValue::Int32(*value)) { + Ok(_) => {} + Err(err) => return Err(err), + } + } + Ok(()) + } _ => Err(UpdateError::WrongType), }, DataType::Int64Array => match value { - DataValue::Int64Array(_) => Ok(()), + DataValue::Int64Array(array) => { + for value in array { + match self.validate_value_min_max(&DataValue::Int64(*value)) { + Ok(_) => {} + Err(err) => return Err(err), + } + } + Ok(()) + } _ => Err(UpdateError::WrongType), }, DataType::Uint8Array => match &value { DataValue::Uint32Array(array) => { - let mut out_of_bounds = false; for value in array { match u8::try_from(*value) { - Ok(_) => {} - Err(_) => { - out_of_bounds = true; - break; + Ok(_) => { + match self.validate_value_min_max(&DataValue::Uint32(*value)) { + Ok(_) => {} + Err(err) => return Err(err), + } } + Err(_) => return Err(UpdateError::OutOfBounds), } } - if out_of_bounds { - Err(UpdateError::OutOfBounds) - } else { - Ok(()) - } + Ok(()) } _ => Err(UpdateError::WrongType), }, DataType::Uint16Array => match &value { DataValue::Uint32Array(array) => { - let mut out_of_bounds = false; for value in array { match u16::try_from(*value) { - Ok(_) => {} - Err(_) => { - out_of_bounds = true; - break; + Ok(_) => { + match self.validate_value_min_max(&DataValue::Uint32(*value)) { + Ok(_) => {} + Err(err) => return Err(err), + } } + Err(_) => return Err(UpdateError::OutOfBounds), } } - if out_of_bounds { - Err(UpdateError::OutOfBounds) - } else { - Ok(()) - } + Ok(()) } _ => Err(UpdateError::WrongType), }, DataType::Uint32Array => match value { - DataValue::Uint32Array(_) => Ok(()), + DataValue::Uint32Array(array) => { + for value in array { + match self.validate_value_min_max(&DataValue::Uint32(*value)) { + Ok(_) => {} + Err(err) => return Err(err), + } + } + Ok(()) + } _ => Err(UpdateError::WrongType), }, DataType::Uint64Array => match value { - DataValue::Uint64Array(_) => Ok(()), + DataValue::Uint64Array(array) => { + for value in array { + match self.validate_value_min_max(&DataValue::Uint64(*value)) { + Ok(_) => {} + Err(err) => return Err(err), + } + } + Ok(()) + } _ => Err(UpdateError::WrongType), }, DataType::FloatArray => match value { - DataValue::FloatArray(_) => Ok(()), + DataValue::FloatArray(array) => { + for value in array { + match self.validate_value_min_max(&DataValue::Float(*value)) { + Ok(_) => {} + Err(err) => return Err(err), + } + } + Ok(()) + } _ => Err(UpdateError::WrongType), }, DataType::DoubleArray => match value { - DataValue::DoubleArray(_) => Ok(()), + DataValue::DoubleArray(array) => { + for value in array { + match self.validate_value_min_max(&DataValue::Double(*value)) { + Ok(_) => {} + Err(err) => return Err(err), + } + } + Ok(()) + } _ => Err(UpdateError::WrongType), }, } @@ -1179,6 +1272,8 @@ impl<'a, 'b> DatabaseWriteAccess<'a, 'b> { change_type: ChangeType, entry_type: EntryType, description: String, + min: Option, + max: Option, allowed: Option, datapoint: Option, unit: Option, @@ -1211,6 +1306,8 @@ impl<'a, 'b> DatabaseWriteAccess<'a, 'b> { entry_type, description, allowed, + min, + max, unit, }, datapoint: match datapoint.clone() { @@ -1305,6 +1402,8 @@ impl<'a, 'b> AuthorizedAccess<'a, 'b> { change_type: ChangeType, entry_type: EntryType, description: String, + min: Option, + max: Option, allowed: Option, unit: Option, ) -> Result { @@ -1319,6 +1418,8 @@ impl<'a, 'b> AuthorizedAccess<'a, 'b> { change_type, entry_type, description, + min, + max, allowed, None, unit, @@ -1850,7 +1951,8 @@ impl Default for DataBroker { } #[cfg(test)] -mod tests { +/// Public test module to allow other files to reuse helper functions +pub mod tests { use crate::permissions; use super::*; @@ -1877,6 +1979,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max Some(DataValue::BoolArray(Vec::from([true]))), Some("kg".to_string()), ) @@ -1909,6 +2013,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test datapoint 2".to_owned(), + None, // min + None, // max None, Some("km".to_string()), ) @@ -1938,6 +2044,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test datapoint 1 (modified)".to_owned(), + None, // min + None, // max None, None, ) @@ -1959,6 +2067,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test signal 3".to_owned(), + None, // min + None, // max Some(DataValue::Int32Array(Vec::from([1, 2, 3, 4]))), None, ) @@ -1983,6 +2093,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) @@ -1996,6 +2108,8 @@ mod tests { ChangeType::OnChange, EntryType::Actuator, "Test datapoint 2".to_owned(), + None, // min + None, // max None, None, ) @@ -2039,6 +2153,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -2071,6 +2187,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -2093,6 +2211,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -2144,6 +2264,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max Some(DataValue::Int32Array(vec![100])), None, ) @@ -2165,6 +2287,8 @@ mod tests { data_type: None, description: None, allowed: Some(Some(DataValue::Int32Array(vec![100]))), + min: None, + max: None, unit: None, }, )]) @@ -2187,6 +2311,8 @@ mod tests { data_type: None, description: None, allowed: Some(Some(DataValue::BoolArray(vec![true]))), + min: None, + max: None, unit: None, }, )]) @@ -2209,6 +2335,8 @@ mod tests { data_type: None, description: None, allowed: Some(None), + min: None, + max: None, unit: None, }, )]) @@ -2232,6 +2360,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -2252,6 +2382,696 @@ mod tests { } } + // Helper for adding an int8 signal and adding value + async fn helper_add_int8( + broker: &DataBroker, + name: &str, + value: i32, + timestamp: std::time::SystemTime, + ) -> Result> { + let authorized_access = broker.authorized_access(&permissions::ALLOW_ALL); + let entry_id = authorized_access + .add_entry( + name.to_owned(), + DataType::Int8, + ChangeType::OnChange, + EntryType::Sensor, + "Some Description That Does Not Matter".to_owned(), + Some(types::DataValue::Int32(-5)), // min + Some(types::DataValue::Int32(10)), // max + None, + None, + ) + .await + .unwrap(); + + match authorized_access + .update_entries([( + entry_id, + EntryUpdate { + path: None, + datapoint: Some(Datapoint { + ts: timestamp, + source_ts: None, + value: types::DataValue::Int32(value), + }), + actuator_target: None, + entry_type: None, + data_type: None, + description: None, + allowed: None, + min: None, + max: None, + unit: None, + }, + )]) + .await + { + Ok(_) => Ok(entry_id), + Err(details) => Err(details), + } + } + + #[tokio::test] + async fn test_update_entries_min_max_int8() { + let broker = DataBroker::default(); + + let timestamp = std::time::SystemTime::now(); + + match helper_add_int8(&broker, "test.datapoint1", -6, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + + if helper_add_int8(&broker, "test.datapoint2", -5, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + + match helper_add_int8(&broker, "test.datapoint3", 11, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + + if helper_add_int8(&broker, "test.datapoint4", 10, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + } + + // Helper for adding an int8 signal and adding value + async fn helper_add_int16( + broker: &DataBroker, + name: &str, + value: i32, + timestamp: std::time::SystemTime, + ) -> Result> { + let authorized_access = broker.authorized_access(&permissions::ALLOW_ALL); + let entry_id = authorized_access + .add_entry( + name.to_owned(), + DataType::Int16, + ChangeType::OnChange, + EntryType::Sensor, + "Some Description That Does Not Matter".to_owned(), + Some(types::DataValue::Int32(-5)), // min + Some(types::DataValue::Int32(10)), // max + None, + None, + ) + .await + .unwrap(); + + match authorized_access + .update_entries([( + entry_id, + EntryUpdate { + path: None, + datapoint: Some(Datapoint { + ts: timestamp, + source_ts: None, + value: types::DataValue::Int32(value), + }), + actuator_target: None, + entry_type: None, + data_type: None, + description: None, + allowed: None, + min: None, + max: None, + unit: None, + }, + )]) + .await + { + Ok(_) => Ok(entry_id), + Err(details) => Err(details), + } + } + + #[tokio::test] + async fn test_update_entries_min_max_int16() { + let broker = DataBroker::default(); + + let timestamp = std::time::SystemTime::now(); + + match helper_add_int16(&broker, "test.datapoint1", -6, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + + if helper_add_int16(&broker, "test.datapoint2", -5, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + + match helper_add_int16(&broker, "test.datapoint3", 11, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + + if helper_add_int16(&broker, "test.datapoint4", 10, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + } + + // Helper for adding an int32 signal and adding value + pub async fn helper_add_int32( + broker: &DataBroker, + name: &str, + value: i32, + timestamp: std::time::SystemTime, + ) -> Result> { + let authorized_access = broker.authorized_access(&permissions::ALLOW_ALL); + let entry_id = authorized_access + .add_entry( + name.to_owned(), + DataType::Int32, + ChangeType::OnChange, + EntryType::Sensor, + "Some Description That Does Not Matter".to_owned(), + Some(types::DataValue::Int32(-500)), // min + Some(types::DataValue::Int32(1000)), // max + None, + None, + ) + .await + .unwrap(); + + match authorized_access + .update_entries([( + entry_id, + EntryUpdate { + path: None, + datapoint: Some(Datapoint { + ts: timestamp, + source_ts: None, + value: types::DataValue::Int32(value), + }), + actuator_target: None, + entry_type: None, + data_type: None, + description: None, + allowed: None, + min: None, + max: None, + unit: None, + }, + )]) + .await + { + Ok(_) => Ok(entry_id), + Err(details) => Err(details), + } + } + + #[tokio::test] + async fn test_update_entries_min_exceeded() { + let broker = DataBroker::default(); + + let timestamp = std::time::SystemTime::now(); + + match helper_add_int32(&broker, "test.datapoint1", -501, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + } + + #[tokio::test] + async fn test_update_entries_min_equal() { + let broker = DataBroker::default(); + + let timestamp = std::time::SystemTime::now(); + + if helper_add_int32(&broker, "test.datapoint1", -500, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + } + + #[tokio::test] + async fn test_update_entries_max_exceeded() { + let broker = DataBroker::default(); + + let timestamp = std::time::SystemTime::now(); + + match helper_add_int32(&broker, "test.datapoint1", 1001, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + } + + #[tokio::test] + async fn test_update_entries_max_equal() { + let broker = DataBroker::default(); + + let timestamp = std::time::SystemTime::now(); + + if helper_add_int32(&broker, "test.datapoint1", 1000, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + } + + /// Helper for adding an int64 signal and adding value + async fn helper_add_int64( + broker: &DataBroker, + name: &str, + value: i64, + timestamp: std::time::SystemTime, + ) -> Result> { + let authorized_access = broker.authorized_access(&permissions::ALLOW_ALL); + let entry_id = authorized_access + .add_entry( + name.to_owned(), + DataType::Int64, + ChangeType::OnChange, + EntryType::Sensor, + "Some Description That Does Not Matter".to_owned(), + Some(types::DataValue::Int64(-500000)), // min + Some(types::DataValue::Int64(10000000)), // max + None, + None, + ) + .await + .unwrap(); + + match authorized_access + .update_entries([( + entry_id, + EntryUpdate { + path: None, + datapoint: Some(Datapoint { + ts: timestamp, + source_ts: None, + value: types::DataValue::Int64(value), + }), + actuator_target: None, + entry_type: None, + data_type: None, + description: None, + allowed: None, + min: None, + max: None, + unit: None, + }, + )]) + .await + { + Ok(_) => Ok(entry_id), + Err(details) => Err(details), + } + } + + #[tokio::test] + async fn test_update_entries_min_max_int64() { + let broker = DataBroker::default(); + + let timestamp = std::time::SystemTime::now(); + + match helper_add_int64(&broker, "test.datapoint1", -500001, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + + if helper_add_int64(&broker, "test.datapoint2", -500000, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + + match helper_add_int64(&broker, "test.datapoint3", 10000001, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + + if helper_add_int64(&broker, "test.datapoint4", 10000000, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + } + + /// Helper for adding an uint8 signal and adding value + async fn helper_add_uint8( + broker: &DataBroker, + name: &str, + value: u32, + timestamp: std::time::SystemTime, + ) -> Result> { + let authorized_access = broker.authorized_access(&permissions::ALLOW_ALL); + let entry_id = authorized_access + .add_entry( + name.to_owned(), + DataType::Uint8, + ChangeType::OnChange, + EntryType::Sensor, + "Some Description That Does Not Matter".to_owned(), + Some(types::DataValue::Uint32(3)), // min + Some(types::DataValue::Uint32(26)), // max + None, + None, + ) + .await + .unwrap(); + + match authorized_access + .update_entries([( + entry_id, + EntryUpdate { + path: None, + datapoint: Some(Datapoint { + ts: timestamp, + source_ts: None, + value: types::DataValue::Uint32(value), + }), + actuator_target: None, + entry_type: None, + data_type: None, + description: None, + allowed: None, + min: None, + max: None, + unit: None, + }, + )]) + .await + { + Ok(_) => Ok(entry_id), + Err(details) => Err(details), + } + } + + #[tokio::test] + async fn test_update_entries_min_max_uint8() { + let broker = DataBroker::default(); + + let timestamp = std::time::SystemTime::now(); + + match helper_add_uint8(&broker, "test.datapoint1", 2, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + + if helper_add_uint8(&broker, "test.datapoint2", 3, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + + match helper_add_uint8(&broker, "test.datapoint3", 27, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + + if helper_add_uint8(&broker, "test.datapoint4", 26, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + } + + // Helper for adding an int32 signal and adding value + async fn helper_add_int32array( + broker: &DataBroker, + name: &str, + value1: i32, + value2: i32, + timestamp: std::time::SystemTime, + ) -> Result> { + let authorized_access = broker.authorized_access(&permissions::ALLOW_ALL); + let entry_id = authorized_access + .add_entry( + name.to_owned(), + DataType::Int32Array, + ChangeType::OnChange, + EntryType::Sensor, + "Some Description That Does Not Matter".to_owned(), + Some(types::DataValue::Int32(-500)), // min + Some(types::DataValue::Int32(1000)), // max + None, + None, + ) + .await + .unwrap(); + + match authorized_access + .update_entries([( + entry_id, + EntryUpdate { + path: None, + datapoint: Some(Datapoint { + ts: timestamp, + source_ts: None, + value: types::DataValue::Int32Array(Vec::from([value1, value2])), + }), + actuator_target: None, + entry_type: None, + data_type: None, + description: None, + allowed: None, + min: None, + max: None, + unit: None, + }, + )]) + .await + { + Ok(_) => Ok(entry_id), + Err(details) => Err(details), + } + } + + #[tokio::test] + async fn test_update_entries_min_exceeded_int32array() { + let broker = DataBroker::default(); + + let timestamp = std::time::SystemTime::now(); + + // First item out of bound + match helper_add_int32array(&broker, "test.datapoint1", -501, -500, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + // Second item out of bound + match helper_add_int32array(&broker, "test.datapoint2", -500, -501, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + } + + #[tokio::test] + async fn test_update_entries_min_equal_int32array() { + let broker = DataBroker::default(); + + let timestamp = std::time::SystemTime::now(); + + if helper_add_int32array(&broker, "test.datapoint1", -500, -500, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + } + + #[tokio::test] + async fn test_update_entries_max_exceeded_int32array() { + let broker = DataBroker::default(); + + let timestamp = std::time::SystemTime::now(); + + match helper_add_int32array(&broker, "test.datapoint1", 1001, 1000, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + match helper_add_int32array(&broker, "test.datapoint2", 100, 1001, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + } + + #[tokio::test] + async fn test_update_entries_max_equal_int32array() { + let broker = DataBroker::default(); + + let timestamp = std::time::SystemTime::now(); + + if helper_add_int32array(&broker, "test.datapoint1", 1000, 1000, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + } + + // Helper for adding an double array signal and adding value + async fn helper_add_doublearray( + broker: &DataBroker, + name: &str, + value1: f64, + value2: f64, + timestamp: std::time::SystemTime, + ) -> Result> { + let authorized_access = broker.authorized_access(&permissions::ALLOW_ALL); + let entry_id = authorized_access + .add_entry( + name.to_owned(), + DataType::DoubleArray, + ChangeType::OnChange, + EntryType::Sensor, + "Some Description That Does Not Matter".to_owned(), + Some(types::DataValue::Double(-500.2)), // min + Some(types::DataValue::Double(1000.2)), // max + None, + None, + ) + .await + .unwrap(); + + match authorized_access + .update_entries([( + entry_id, + EntryUpdate { + path: None, + datapoint: Some(Datapoint { + ts: timestamp, + source_ts: None, + value: types::DataValue::DoubleArray(Vec::from([value1, value2])), + }), + actuator_target: None, + entry_type: None, + data_type: None, + description: None, + allowed: None, + min: None, + max: None, + unit: None, + }, + )]) + .await + { + Ok(_) => Ok(entry_id), + Err(details) => Err(details), + } + } + + #[tokio::test] + async fn test_update_entries_min_max_doublearray() { + let broker = DataBroker::default(); + + let timestamp = std::time::SystemTime::now(); + + // First item out of bound + match helper_add_doublearray(&broker, "test.datapoint1", -500.3, -500.0, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + // Second item out of bound + match helper_add_doublearray(&broker, "test.datapoint2", -500.0, -500.3, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + + // Both on min + if helper_add_doublearray(&broker, "test.datapoint3", -500.2, -500.2, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + + // First tto large + match helper_add_doublearray(&broker, "test.datapoint4", 1000.3, 1000.0, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + + // Second too large + match helper_add_doublearray(&broker, "test.datapoint5", 1000.0, 1000.3, timestamp).await { + Err(err_vec) => { + assert_eq!(err_vec.len(), 1); + assert_eq!(err_vec.first().expect("").1, UpdateError::OutOfBounds) + } + _ => panic!("Failure expected"), + } + + // Both on max + if helper_add_doublearray(&broker, "test.datapoint6", 1000.2, 1000.2, timestamp) + .await + .is_err() + { + panic!("Success expected") + } + } + #[tokio::test] async fn test_subscribe_query_and_get() { let broker = DataBroker::default(); @@ -2264,6 +3084,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) @@ -2303,6 +3125,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -2347,6 +3171,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) @@ -2405,6 +3231,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -2449,6 +3277,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) @@ -2488,6 +3318,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -2524,6 +3356,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test datapoint 1 (new description)".to_owned(), + None, // min + None, // max None, None, ) @@ -2547,6 +3381,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -2589,6 +3425,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) @@ -2602,6 +3440,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test datapoint 2".to_owned(), + None, // min + None, // max None, None, ) @@ -2645,6 +3485,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, ), @@ -2662,6 +3504,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, ), @@ -2703,6 +3547,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Run of the mill test array".to_owned(), + None, // min + None, // max None, None, ) @@ -2725,6 +3571,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -2768,6 +3616,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Run of the mill test array".to_owned(), + None, // min + None, // max None, None, ) @@ -2795,6 +3645,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -2843,6 +3695,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Run of the mill test array".to_owned(), + None, // min + None, // max Some(DataValue::StringArray(vec![ String::from("yes"), String::from("no"), @@ -2875,6 +3729,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -2932,6 +3788,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -2987,6 +3845,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -3011,6 +3871,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Run of the mill test array".to_owned(), + None, // min + None, // max None, None, ) @@ -3033,6 +3895,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -3062,6 +3926,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -3097,6 +3963,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Run of the mill test array".to_owned(), + None, // min + None, // max None, None, ) @@ -3119,6 +3987,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -3148,6 +4018,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -3186,6 +4058,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Run of the mill test array".to_owned(), + None, // min + None, // max None, None, ) @@ -3208,6 +4082,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -3251,6 +4127,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) @@ -3295,6 +4173,8 @@ mod tests { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]) @@ -3345,6 +4225,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Run of the mill test signal".to_owned(), + None, // min + None, // max None, None, ) @@ -3357,6 +4239,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Run of the mill test signal".to_owned(), + None, // min + None, // max None, None, ) @@ -3388,6 +4272,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test signal 3".to_owned(), + None, // min + None, // max Some(DataValue::Int32Array(Vec::from([1, 2, 3, 4]))), None, ) @@ -3402,6 +4288,8 @@ mod tests { ChangeType::OnChange, EntryType::Sensor, "Test datapoint".to_owned(), + None, // min + None, // max Some(DataValue::BoolArray(Vec::from([true]))), None, ) diff --git a/databroker/src/grpc/kuksa_val_v1/val.rs b/databroker/src/grpc/kuksa_val_v1/val.rs index 9cbbfc8d..38e0acbe 100644 --- a/databroker/src/grpc/kuksa_val_v1/val.rs +++ b/databroker/src/grpc/kuksa_val_v1/val.rs @@ -33,6 +33,7 @@ use crate::broker::SubscriptionError; use crate::broker::{AuthorizedAccess, EntryReadAccess}; use crate::glob::Matcher; use crate::permissions::Permissions; +use crate::types::{DataType, DataValue}; const MAX_REQUEST_PATH_LENGTH: usize = 1000; @@ -792,72 +793,147 @@ fn proto_entry_from_entry_and_fields( } if all || fields.contains(&proto::Field::MetadataValueRestriction) { metadata_is_set = true; - metadata.value_restriction = match entry.metadata().allowed.as_ref() { - Some(allowed) => match allowed { - broker::DataValue::StringArray(vec) => Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::String( - proto::ValueRestrictionString { - allowed_values: vec.clone(), - }, - )), - }), - broker::DataValue::Int32Array(vec) => Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::Signed( - proto::ValueRestrictionInt { - allowed_values: vec.iter().cloned().map(i64::from).collect(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - )), - }), - broker::DataValue::Int64Array(vec) => Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::Signed( - proto::ValueRestrictionInt { - allowed_values: vec.clone(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - )), - }), - broker::DataValue::Uint32Array(vec) => Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::Unsigned( - proto::ValueRestrictionUint { - allowed_values: vec.iter().cloned().map(u64::from).collect(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - )), - }), - broker::DataValue::Uint64Array(vec) => Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::Unsigned( - proto::ValueRestrictionUint { - allowed_values: vec.clone(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - )), - }), - broker::DataValue::FloatArray(vec) => Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::FloatingPoint( - proto::ValueRestrictionFloat { - allowed_values: vec.iter().cloned().map(f64::from).collect(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - )), - }), - broker::DataValue::DoubleArray(vec) => Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::FloatingPoint( - proto::ValueRestrictionFloat { - allowed_values: vec.clone(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - )), - }), - _ => None, - }, - None => None, + debug!("Datatype {:?} to be handled", entry.metadata().data_type); + match entry.metadata().data_type { + DataType::String | DataType::StringArray => { + let allowed = match entry.metadata().allowed.as_ref() { + Some(broker::DataValue::StringArray(vec)) => vec.clone(), + _ => Vec::new(), + }; + + if !allowed.is_empty() { + metadata.value_restriction = Some(proto::ValueRestriction { + r#type: Some(proto::value_restriction::Type::String( + proto::ValueRestrictionString { + allowed_values: allowed, + }, + )), + }); + }; + } + DataType::Int8 + | DataType::Int16 + | DataType::Int32 + | DataType::Int64 + | DataType::Int8Array + | DataType::Int16Array + | DataType::Int32Array + | DataType::Int64Array => { + let min_value = match entry.metadata().min { + Some(DataValue::Int32(value)) => Some(i64::from(value)), + Some(DataValue::Int64(value)) => Some(value), + _ => None, + }; + let max_value = match entry.metadata().max { + Some(DataValue::Int32(value)) => Some(i64::from(value)), + Some(DataValue::Int64(value)) => Some(value), + _ => None, + }; + let allowed = match entry.metadata().allowed.as_ref() { + Some(allowed) => match allowed { + broker::DataValue::Int32Array(vec) => { + vec.iter().cloned().map(i64::from).collect() + } + broker::DataValue::Int64Array(vec) => vec.to_vec(), + _ => Vec::new(), + }, + _ => Vec::new(), + }; + + if min_value.is_some() | max_value.is_some() | !allowed.is_empty() { + metadata.value_restriction = Some(proto::ValueRestriction { + r#type: Some(proto::value_restriction::Type::Signed( + proto::ValueRestrictionInt { + allowed_values: allowed, + min: min_value, + max: max_value, + }, + )), + }); + }; + } + DataType::Uint8 + | DataType::Uint16 + | DataType::Uint32 + | DataType::Uint64 + | DataType::Uint8Array + | DataType::Uint16Array + | DataType::Uint32Array + | DataType::Uint64Array => { + let min_value = match entry.metadata().min { + Some(DataValue::Uint32(value)) => Some(u64::from(value)), + Some(DataValue::Uint64(value)) => Some(value), + _ => None, + }; + let max_value = match entry.metadata().max { + Some(DataValue::Uint32(value)) => Some(u64::from(value)), + Some(DataValue::Uint64(value)) => Some(value), + _ => None, + }; + let allowed = match entry.metadata().allowed.as_ref() { + Some(allowed) => match allowed { + broker::DataValue::Uint32Array(vec) => { + vec.iter().cloned().map(u64::from).collect() + } + broker::DataValue::Uint64Array(vec) => vec.to_vec(), + _ => Vec::new(), + }, + _ => Vec::new(), + }; + + if min_value.is_some() | max_value.is_some() | !allowed.is_empty() { + metadata.value_restriction = Some(proto::ValueRestriction { + r#type: Some(proto::value_restriction::Type::Unsigned( + proto::ValueRestrictionUint { + allowed_values: allowed, + min: min_value, + max: max_value, + }, + )), + }); + }; + } + DataType::Float + | DataType::Double + | DataType::FloatArray + | DataType::DoubleArray => { + let min_value = match entry.metadata().min { + Some(DataValue::Float(value)) => Some(f64::from(value)), + Some(DataValue::Double(value)) => Some(value), + _ => None, + }; + let max_value = match entry.metadata().max { + Some(DataValue::Float(value)) => Some(f64::from(value)), + Some(DataValue::Double(value)) => Some(value), + _ => None, + }; + let allowed = match entry.metadata().allowed.as_ref() { + Some(allowed) => match allowed { + broker::DataValue::FloatArray(vec) => { + vec.iter().cloned().map(f64::from).collect() + } + broker::DataValue::DoubleArray(vec) => vec.to_vec(), + _ => Vec::new(), + }, + _ => Vec::new(), + }; + + if min_value.is_some() | max_value.is_some() | !allowed.is_empty() { + metadata.value_restriction = Some(proto::ValueRestriction { + r#type: Some(proto::value_restriction::Type::FloatingPoint( + proto::ValueRestrictionFloat { + allowed_values: allowed, + min: min_value, + max: max_value, + }, + )), + }); + }; + } + + _ => { + debug!("Datatype {:?} not yet handled", entry.metadata().data_type); + } } } if all || fields.contains(&proto::Field::MetadataActuator) { @@ -984,6 +1060,8 @@ impl broker::EntryUpdate { data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, } } @@ -1007,6 +1085,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) @@ -1062,6 +1142,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, Some("km/h".to_owned()), ) @@ -1158,6 +1240,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) @@ -1171,6 +1255,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Test branch datapoint 2".to_owned(), + None, // min + None, // max None, None, ) @@ -1219,6 +1305,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) diff --git a/databroker/src/grpc/kuksa_val_v2/conversions.rs b/databroker/src/grpc/kuksa_val_v2/conversions.rs index 1178493e..a2262d81 100644 --- a/databroker/src/grpc/kuksa_val_v2/conversions.rs +++ b/databroker/src/grpc/kuksa_val_v2/conversions.rs @@ -11,6 +11,7 @@ // * SPDX-License-Identifier: Apache-2.0 // ********************************************************************************/ use crate::broker; +use crate::types::{DataType, DataValue}; use databroker_proto::kuksa::val::v2 as proto; use kuksa::proto::v2::{ BoolArray, DoubleArray, FloatArray, Int32Array, Int64Array, StringArray, Uint32Array, @@ -18,6 +19,7 @@ use kuksa::proto::v2::{ }; use std::time::SystemTime; +use tracing::debug; impl From<&proto::Datapoint> for broker::Datapoint { fn from(datapoint: &proto::Datapoint) -> Self { @@ -213,6 +215,148 @@ impl From<&proto::Datapoint> for broker::DataValue { } } +fn value_restriction_from(metadata: &broker::Metadata) -> Option { + match metadata.data_type { + DataType::String | DataType::StringArray => { + let allowed = match metadata.allowed.as_ref() { + Some(broker::DataValue::StringArray(vec)) => vec.clone(), + _ => Vec::new(), + }; + + if !allowed.is_empty() { + return Some(proto::ValueRestriction { + r#type: Some(proto::value_restriction::Type::String( + proto::ValueRestrictionString { + allowed_values: allowed, + }, + )), + }); + }; + } + DataType::Int8 + | DataType::Int16 + | DataType::Int32 + | DataType::Int64 + | DataType::Int8Array + | DataType::Int16Array + | DataType::Int32Array + | DataType::Int64Array => { + let min_value = match metadata.min { + Some(DataValue::Int32(value)) => Some(i64::from(value)), + Some(DataValue::Int64(value)) => Some(value), + _ => None, + }; + let max_value = match metadata.max { + Some(DataValue::Int32(value)) => Some(i64::from(value)), + Some(DataValue::Int64(value)) => Some(value), + _ => None, + }; + let allowed = match metadata.allowed.as_ref() { + Some(allowed) => match allowed { + broker::DataValue::Int32Array(vec) => { + vec.iter().cloned().map(i64::from).collect() + } + broker::DataValue::Int64Array(vec) => vec.to_vec(), + _ => Vec::new(), + }, + _ => Vec::new(), + }; + + if min_value.is_some() | max_value.is_some() | !allowed.is_empty() { + return Some(proto::ValueRestriction { + r#type: Some(proto::value_restriction::Type::Signed( + proto::ValueRestrictionInt { + allowed_values: allowed, + min: min_value, + max: max_value, + }, + )), + }); + }; + } + DataType::Uint8 + | DataType::Uint16 + | DataType::Uint32 + | DataType::Uint64 + | DataType::Uint8Array + | DataType::Uint16Array + | DataType::Uint32Array + | DataType::Uint64Array => { + let min_value = match metadata.min { + Some(DataValue::Uint32(value)) => Some(u64::from(value)), + Some(DataValue::Uint64(value)) => Some(value), + _ => None, + }; + let max_value = match metadata.max { + Some(DataValue::Uint32(value)) => Some(u64::from(value)), + Some(DataValue::Uint64(value)) => Some(value), + _ => None, + }; + let allowed = match metadata.allowed.as_ref() { + Some(allowed) => match allowed { + broker::DataValue::Uint32Array(vec) => { + vec.iter().cloned().map(u64::from).collect() + } + broker::DataValue::Uint64Array(vec) => vec.to_vec(), + _ => Vec::new(), + }, + _ => Vec::new(), + }; + + if min_value.is_some() | max_value.is_some() | !allowed.is_empty() { + return Some(proto::ValueRestriction { + r#type: Some(proto::value_restriction::Type::Unsigned( + proto::ValueRestrictionUint { + allowed_values: allowed, + min: min_value, + max: max_value, + }, + )), + }); + }; + } + DataType::Float | DataType::Double | DataType::FloatArray | DataType::DoubleArray => { + let min_value = match metadata.min { + Some(DataValue::Float(value)) => Some(f64::from(value)), + Some(DataValue::Double(value)) => Some(value), + _ => None, + }; + let max_value = match metadata.max { + Some(DataValue::Float(value)) => Some(f64::from(value)), + Some(DataValue::Double(value)) => Some(value), + _ => None, + }; + let allowed = match metadata.allowed.as_ref() { + Some(allowed) => match allowed { + broker::DataValue::FloatArray(vec) => { + vec.iter().cloned().map(f64::from).collect() + } + broker::DataValue::DoubleArray(vec) => vec.to_vec(), + _ => Vec::new(), + }, + _ => Vec::new(), + }; + + if min_value.is_some() | max_value.is_some() | !allowed.is_empty() { + return Some(proto::ValueRestriction { + r#type: Some(proto::value_restriction::Type::FloatingPoint( + proto::ValueRestrictionFloat { + allowed_values: allowed, + min: min_value, + max: max_value, + }, + )), + }); + }; + } + + _ => { + debug!("Datatype {:?} not yet handled", metadata.data_type); + } + }; + None +} + impl From<&broker::Metadata> for proto::Metadata { fn from(metadata: &broker::Metadata) -> Self { proto::Metadata { @@ -223,85 +367,7 @@ impl From<&broker::Metadata> for proto::Metadata { comment: None, deprecation: None, unit: metadata.unit.clone(), - value_restriction: match metadata.allowed.as_ref() { - Some(allowed) => match allowed { - broker::DataValue::StringArray(vec) => Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::String( - proto::ValueRestrictionString { - allowed_values: vec.clone(), - }, - )), - }), - broker::DataValue::Int32Array(vec) => { - Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::Signed( - proto::ValueRestrictionInt { - allowed_values: vec.iter().cloned().map(i64::from).collect(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - )), - }) - } - broker::DataValue::Int64Array(vec) => { - Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::Signed( - proto::ValueRestrictionInt { - allowed_values: vec.clone(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - )), - }) - } - broker::DataValue::Uint32Array(vec) => { - Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::Unsigned( - proto::ValueRestrictionUint { - allowed_values: vec.iter().cloned().map(u64::from).collect(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - )), - }) - } - broker::DataValue::Uint64Array(vec) => { - Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::Unsigned( - proto::ValueRestrictionUint { - allowed_values: vec.clone(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - )), - }) - } - broker::DataValue::FloatArray(vec) => { - Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::FloatingPoint( - proto::ValueRestrictionFloat { - allowed_values: vec.iter().cloned().map(f64::from).collect(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - )), - }) - } - broker::DataValue::DoubleArray(vec) => { - Some(proto::ValueRestriction { - r#type: Some(proto::value_restriction::Type::FloatingPoint( - proto::ValueRestrictionFloat { - allowed_values: vec.clone(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - )), - }) - } - _ => None, - }, - None => None, - }, + value_restriction: value_restriction_from(metadata), } } } @@ -391,7 +457,7 @@ impl broker::UpdateError { ), broker::UpdateError::OutOfBounds => tonic::Status::new( tonic::Code::OutOfRange, - format!("Index out of bounds (id: {})", id), + format!("Value out of bounds (id: {})", id), ), broker::UpdateError::UnsupportedType => tonic::Status::new( tonic::Code::Unimplemented, diff --git a/databroker/src/grpc/kuksa_val_v2/val.rs b/databroker/src/grpc/kuksa_val_v2/val.rs index 39034d0f..d6026e1a 100644 --- a/databroker/src/grpc/kuksa_val_v2/val.rs +++ b/databroker/src/grpc/kuksa_val_v2/val.rs @@ -32,7 +32,7 @@ use databroker_proto::kuksa::val::v2::{ use kuksa::proto::v2::{ signal_id, ActuateRequest, ActuateResponse, BatchActuateStreamRequest, ListMetadataResponse, - Metadata, ProvideActuationResponse, + ProvideActuationResponse, }; use std::collections::HashSet; use tokio::{select, sync::mpsc}; @@ -487,125 +487,7 @@ impl proto::val_server::Val for broker::DataBroker { .for_each_entry(|entry| { let entry_metadata = &entry.metadata(); if matcher.is_match(&entry_metadata.glob_path) { - metadata_response.push(Metadata { - id: entry_metadata.id, - data_type: proto::DataType::from(entry_metadata.data_type.clone()) - as i32, - entry_type: proto::EntryType::from( - entry_metadata.entry_type.clone(), - ) as i32, - description: Some(entry_metadata.description.clone()), - comment: None, - deprecation: None, - unit: entry_metadata.unit.clone(), - value_restriction: match entry.metadata().allowed.as_ref() { - Some(allowed) => match allowed { - broker::DataValue::StringArray(vec) => { - Some(proto::ValueRestriction { - r#type: Some( - proto::value_restriction::Type::String( - proto::ValueRestrictionString { - allowed_values: vec.clone(), - }, - ), - ), - }) - } - broker::DataValue::Int32Array(vec) => { - Some(proto::ValueRestriction { - r#type: Some( - proto::value_restriction::Type::Signed( - proto::ValueRestrictionInt { - allowed_values: vec - .iter() - .cloned() - .map(i64::from) - .collect(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - ), - ), - }) - } - broker::DataValue::Int64Array(vec) => { - Some(proto::ValueRestriction { - r#type: Some( - proto::value_restriction::Type::Signed( - proto::ValueRestrictionInt { - allowed_values: vec.clone(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - ), - ), - }) - } - broker::DataValue::Uint32Array(vec) => { - Some(proto::ValueRestriction { - r#type: Some( - proto::value_restriction::Type::Unsigned( - proto::ValueRestrictionUint { - allowed_values: vec - .iter() - .cloned() - .map(u64::from) - .collect(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - ), - ), - }) - } - broker::DataValue::Uint64Array(vec) => { - Some(proto::ValueRestriction { - r#type: Some( - proto::value_restriction::Type::Unsigned( - proto::ValueRestrictionUint { - allowed_values: vec.clone(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - ), - ), - }) - } - broker::DataValue::FloatArray(vec) => { - Some(proto::ValueRestriction { - r#type: Some( - proto::value_restriction::Type::FloatingPoint( - proto::ValueRestrictionFloat { - allowed_values: vec - .iter() - .cloned() - .map(f64::from) - .collect(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - ), - ), - }) - } - broker::DataValue::DoubleArray(vec) => { - Some(proto::ValueRestriction { - r#type: Some( - proto::value_restriction::Type::FloatingPoint( - proto::ValueRestrictionFloat { - allowed_values: vec.clone(), - min: None, // TODO: Implement - max: None, // TODO: Implement - }, - ), - ), - }) - } - _ => None, - }, - None => None, - }, - }) + metadata_response.push(proto::Metadata::from(*entry_metadata)); } }) .await; @@ -683,6 +565,8 @@ impl proto::val_server::Val for broker::DataBroker { data_type: None, description: None, allowed: None, + max: None, + min: None, unit: None, }, ); @@ -994,6 +878,8 @@ async fn publish_values( data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, ) @@ -1114,58 +1000,15 @@ mod tests { PublishValuesRequest, SignalId, Value, }; - // Helper for adding an int32 signal and adding value - async fn helper_add_int32( - broker: &DataBroker, - name: &str, - value: i32, - timestamp: std::time::SystemTime, - ) -> i32 { - let authorized_access = broker.authorized_access(&permissions::ALLOW_ALL); - let entry_id = authorized_access - .add_entry( - name.to_owned(), - broker::DataType::Int32, - broker::ChangeType::OnChange, - broker::EntryType::Sensor, - "Some Description That Does Not Matter".to_owned(), - None, - None, - ) - .await - .unwrap(); - - let _ = authorized_access - .update_entries([( - entry_id, - broker::EntryUpdate { - path: None, - datapoint: Some(broker::Datapoint { - //ts: std::time::SystemTime::now(), - ts: timestamp, - source_ts: None, - value: broker::types::DataValue::Int32(value), - }), - actuator_target: None, - entry_type: None, - data_type: None, - description: None, - allowed: None, - unit: None, - }, - )]) - .await; - - entry_id - } - #[tokio::test] async fn test_get_value_id_ok() { let broker = DataBroker::default(); let timestamp = std::time::SystemTime::now(); - let entry_id = helper_add_int32(&broker, "test.datapoint1", -64, timestamp).await; + let entry_id = broker::tests::helper_add_int32(&broker, "test.datapoint1", -64, timestamp) + .await + .expect("Shall succeed"); let request = proto::GetValueRequest { signal_id: Some(proto::SignalId { @@ -1211,7 +1054,9 @@ mod tests { let timestamp = std::time::SystemTime::now(); - let _entry_id = helper_add_int32(&broker, "test.datapoint1", -64, timestamp).await; + let _entry_id = broker::tests::helper_add_int32(&broker, "test.datapoint1", -64, timestamp) + .await + .expect("Shall succeed"); let request = proto::GetValueRequest { signal_id: Some(proto::SignalId { @@ -1259,7 +1104,9 @@ mod tests { let timestamp = std::time::SystemTime::now(); - let entry_id = helper_add_int32(&broker, "test.datapoint1", -64, timestamp).await; + let entry_id = broker::tests::helper_add_int32(&broker, "test.datapoint1", -64, timestamp) + .await + .expect("Shall succeed"); let request = proto::GetValueRequest { signal_id: Some(proto::SignalId { @@ -1294,6 +1141,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Some Description hat Does Not Matter".to_owned(), + None, // min + None, // max None, None, ) @@ -1506,12 +1355,16 @@ mod tests { let mut entry_id = -1; if config.first_exist { - entry_id = helper_add_int32(&broker, SIGNAL1, -64, timestamp).await; + entry_id = broker::tests::helper_add_int32(&broker, SIGNAL1, -64, timestamp) + .await + .expect("Shall succeed"); } let mut entry_id2 = -1; if config.second_exist { - entry_id2 = helper_add_int32(&broker, SIGNAL2, -13, timestamp).await; + entry_id2 = broker::tests::helper_add_int32(&broker, SIGNAL2, -13, timestamp) + .await + .expect("Shall succeed"); } let mut permission_builder = permissions::PermissionBuilder::new(); @@ -1798,6 +1651,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) @@ -1853,6 +1708,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) @@ -1896,6 +1753,66 @@ mod tests { } } + #[tokio::test] + /// For kuksa_val_v2 we only have a single test to test min/max violations + /// More detailed test cases for different cases/datatypes in broker.rs + async fn test_publish_value_min_max_not_fulfilled() { + let broker = DataBroker::default(); + let authorized_access = broker.authorized_access(&permissions::ALLOW_ALL); + + let entry_id = authorized_access + .add_entry( + "test.datapoint1".to_owned(), + broker::DataType::Uint8, + broker::ChangeType::OnChange, + broker::EntryType::Sensor, + "Test datapoint 1".to_owned(), + Some(broker::types::DataValue::Uint32(3)), // min + Some(broker::types::DataValue::Uint32(26)), // max + None, + None, + ) + .await + .unwrap(); + + let request = proto::PublishValueRequest { + signal_id: Some(proto::SignalId { + signal: Some(proto::signal_id::Signal::Id(entry_id)), + }), + data_point: { + let timestamp = Some(std::time::SystemTime::now().into()); + + let value = proto::Value { + typed_value: Some(proto::value::TypedValue::Uint32(27)), + }; + + Some(proto::Datapoint { + timestamp, + value: Some(value), + }) + }, + }; + + // Manually insert permissions + let mut publish_value_request = tonic::Request::new(request); + publish_value_request + .extensions_mut() + .insert(permissions::ALLOW_ALL.clone()); + + match broker.publish_value(publish_value_request).await { + Ok(_) => { + // Handle the successful response + panic!("Should not happen!"); + } + Err(status) => { + // Handle the error from the publish_value function + assert_eq!(status.code(), tonic::Code::OutOfRange); + // As of the today the first added datapoint get value 0 by default. + assert_eq!(status.message(), "Value out of bounds (id: 0)"); + } + } + } + async fn publish_value( broker: &DataBroker, entry_id: i32, @@ -2008,6 +1925,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Some Description that Does Not Matter".to_owned(), + None, // min + None, // max None, None, ) @@ -2152,6 +2071,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Some Description that Does Not Matter".to_owned(), + None, // min + None, // max None, None, ) @@ -2262,6 +2183,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) @@ -2346,6 +2269,62 @@ mod tests { } } + #[tokio::test] + async fn test_list_metadata_min_max() { + let broker = DataBroker::default(); + let authorized_access = broker.authorized_access(&permissions::ALLOW_ALL); + + authorized_access + .add_entry( + "test.datapoint1".to_owned(), + broker::DataType::Int32, + broker::ChangeType::OnChange, + broker::EntryType::Sensor, + "Test datapoint 1".to_owned(), + Some(broker::types::DataValue::Int32(-7)), // min + Some(broker::types::DataValue::Int32(19)), // max + None, + None, + ) + .await + .expect("Register datapoint should succeed"); + + let mut data_req = tonic::Request::new(proto::ListMetadataRequest { + root: "test.datapoint1".to_owned(), + filter: "".to_owned(), + }); + + // Manually insert permissions + data_req + .extensions_mut() + .insert(permissions::ALLOW_ALL.clone()); + + match proto::val_server::Val::list_metadata(&broker, data_req) + .await + .map(|res| res.into_inner()) + { + Ok(list_response) => { + let entries_size = list_response.metadata.len(); + assert_eq!(entries_size, 1); + + let value_restriction = Some(proto::ValueRestriction { + r#type: Some(proto::value_restriction::Type::Signed( + proto::ValueRestrictionInt { + allowed_values: Vec::new(), + min: Some(-7), + max: Some(19), + }, + )), + }); + assert_eq!( + list_response.metadata.first().unwrap().value_restriction, + value_restriction + ) + } + Err(_status) => panic!("failed to execute get request"), + } + } + #[tokio::test] async fn test_list_metadata_using_wildcard() { let broker = DataBroker::default(); @@ -2358,6 +2337,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) @@ -2371,6 +2352,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Test branch datapoint 2".to_owned(), + None, // min + None, // max None, None, ) @@ -2430,6 +2413,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Sensor, "Test datapoint 1".to_owned(), + None, // min + None, // max None, None, ) @@ -2527,6 +2512,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Actuator, "Some funny description".to_owned(), + None, // min + None, // max None, None, ) @@ -2568,6 +2555,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Actuator, "Some funny description".to_owned(), + None, // min + None, // max None, None, ) @@ -2621,6 +2610,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Actuator, "Some funny description".to_owned(), + None, // min + None, // max None, None, ) @@ -2673,6 +2664,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Actuator, "Some funny description".to_owned(), + None, // min + None, // max None, None, ) @@ -2686,6 +2679,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Actuator, "Some funny description".to_owned(), + None, // min + None, // max None, None, ) @@ -2755,6 +2750,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Actuator, "Some funny description".to_owned(), + None, // min + None, // max None, None, ) @@ -2768,6 +2765,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Actuator, "Some funny description".to_owned(), + None, // min + None, // max None, None, ) @@ -2880,6 +2879,8 @@ mod tests { broker::ChangeType::OnChange, broker::EntryType::Actuator, "Some funny description".to_owned(), + None, // min + None, // max None, None, ) diff --git a/databroker/src/grpc/sdv_databroker_v1/broker.rs b/databroker/src/grpc/sdv_databroker_v1/broker.rs index 532ae1f1..32669fd0 100644 --- a/databroker/src/grpc/sdv_databroker_v1/broker.rs +++ b/databroker/src/grpc/sdv_databroker_v1/broker.rs @@ -131,6 +131,8 @@ impl proto::broker_server::Broker for broker::DataBroker { data_type: None, description: None, allowed: None, + max: None, + min: None, unit: None, }, )); diff --git a/databroker/src/grpc/sdv_databroker_v1/collector.rs b/databroker/src/grpc/sdv_databroker_v1/collector.rs index 4bec1701..e0c792b8 100644 --- a/databroker/src/grpc/sdv_databroker_v1/collector.rs +++ b/databroker/src/grpc/sdv_databroker_v1/collector.rs @@ -60,6 +60,8 @@ impl proto::collector_server::Collector for broker::DataBroker { data_type: None, description: None, allowed: None, + max: None, + min: None, unit: None, }, ) @@ -129,6 +131,8 @@ impl proto::collector_server::Collector for broker::DataBroker { data_type: None, description: None, allowed: None, + max: None, + min: None, unit: None, } ) @@ -207,6 +211,8 @@ impl proto::collector_server::Collector for broker::DataBroker { broker::ChangeType::from(&change_type), broker::types::EntryType::Sensor, metadata.description, + None, // min + None, // max None, None, ) diff --git a/databroker/src/main.rs b/databroker/src/main.rs index 4633874f..be0ab129 100644 --- a/databroker/src/main.rs +++ b/databroker/src/main.rs @@ -64,6 +64,8 @@ async fn add_kuksa_attribute( description, None, None, + None, + None, ) .await { @@ -82,6 +84,8 @@ async fn add_kuksa_attribute( data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]; @@ -115,7 +119,7 @@ async fn read_metadata_file<'a, 'b>( let entries = vss::parse_vss_from_reader(buffered)?; for (path, entry) in entries { - debug!("Adding VSS datapoint type {}", path); + debug!("Adding VSS datapoint {}", path); match database .add_entry( @@ -124,6 +128,8 @@ async fn read_metadata_file<'a, 'b>( entry.change_type, entry.entry_type, entry.description, + entry.min, + entry.max, entry.allowed, entry.unit, ) @@ -145,6 +151,8 @@ async fn read_metadata_file<'a, 'b>( data_type: None, description: None, allowed: None, + min: None, + max: None, unit: None, }, )]; diff --git a/databroker/src/types.rs b/databroker/src/types.rs index 6d9241fd..90368844 100644 --- a/databroker/src/types.rs +++ b/databroker/src/types.rs @@ -233,158 +233,21 @@ impl DataValue { } } - pub fn less_than(&self, other: &DataValue) -> Result { - match (&self, other) { - (DataValue::Int32(value), DataValue::Int32(other_value)) => Ok(value < other_value), - (DataValue::Int32(value), DataValue::Int64(other_value)) => { - Ok(i64::from(*value) < *other_value) - } - (DataValue::Int32(value), DataValue::Uint32(other_value)) => { - Ok(i64::from(*value) < i64::from(*other_value)) - } - (DataValue::Int32(value), DataValue::Uint64(other_value)) => { - if *value < 0 { - Ok(true) // Negative value must be less than unsigned - } else { - match u64::try_from(*value) { - Ok(value) => Ok(value < *other_value), - Err(_) => Err(CastError {}), - } - } - } - (DataValue::Int32(value), DataValue::Float(other_value)) => { - Ok(f64::from(*value) < f64::from(*other_value)) - } - (DataValue::Int32(value), DataValue::Double(other_value)) => { - Ok(f64::from(*value) < *other_value) - } + pub fn greater_than_equal(&self, other: &DataValue) -> Result { + match self.greater_than(other) { + Ok(true) => Ok(true), + _ => self.equals(other), + } + } - (DataValue::Int64(value), DataValue::Int32(other_value)) => { - Ok(*value < i64::from(*other_value)) - } - (DataValue::Int64(value), DataValue::Int64(other_value)) => Ok(value < other_value), - (DataValue::Int64(value), DataValue::Uint32(other_value)) => { - Ok(*value < i64::from(*other_value)) - } - (DataValue::Int64(value), DataValue::Uint64(other_value)) => { - if *value < 0 { - Ok(true) // Negative value must be less than unsigned - } else { - match u64::try_from(*value) { - Ok(value) => Ok(value < *other_value), - Err(_) => Err(CastError {}), - } - } - } - (DataValue::Int64(value), DataValue::Float(other_value)) => match i32::try_from(*value) - { - Ok(value) => Ok(f64::from(value) < f64::from(*other_value)), - Err(_) => Err(CastError {}), - }, - (DataValue::Int64(value), DataValue::Double(other_value)) => { - match i32::try_from(*value) { - Ok(value) => Ok(f64::from(value) < *other_value), - Err(_) => Err(CastError {}), - } - } + pub fn less_than(&self, other: &DataValue) -> Result { + other.greater_than(self) + } - (DataValue::Uint32(value), DataValue::Int32(other_value)) => { - Ok(i64::from(*value) < i64::from(*other_value)) - } - (DataValue::Uint32(value), DataValue::Int64(other_value)) => { - Ok(i64::from(*value) < *other_value) - } - (DataValue::Uint32(value), DataValue::Uint32(other_value)) => Ok(value < other_value), - (DataValue::Uint32(value), DataValue::Uint64(other_value)) => { - Ok(u64::from(*value) < *other_value) - } - (DataValue::Uint32(value), DataValue::Float(other_value)) => { - Ok(f64::from(*value) < f64::from(*other_value)) - } - (DataValue::Uint32(value), DataValue::Double(other_value)) => { - Ok(f64::from(*value) < *other_value) - } - (DataValue::Uint64(value), DataValue::Int32(other_value)) => { - if *other_value < 0 { - Ok(false) // Unsigned cannot be less than a negative value - } else { - match u64::try_from(*other_value) { - Ok(other_value) => Ok(*value < other_value), - Err(_) => Err(CastError {}), - } - } - } - (DataValue::Uint64(value), DataValue::Int64(other_value)) => { - if *other_value < 0 { - Ok(false) // Unsigned cannot be less than a negative value - } else { - match u64::try_from(*other_value) { - Ok(other_value) => Ok(*value < other_value), - Err(_) => Err(CastError {}), - } - } - } - (DataValue::Uint64(value), DataValue::Uint32(other_value)) => { - Ok(*value < u64::from(*other_value)) - } - (DataValue::Uint64(value), DataValue::Uint64(other_value)) => Ok(value < other_value), - (DataValue::Uint64(value), DataValue::Float(other_value)) => { - match u32::try_from(*value) { - Ok(value) => Ok(f64::from(value) < f64::from(*other_value)), - Err(_) => Err(CastError {}), - } - } - (DataValue::Uint64(value), DataValue::Double(other_value)) => { - match u32::try_from(*value) { - Ok(value) => Ok(f64::from(value) < *other_value), - Err(_) => Err(CastError {}), - } - } - (DataValue::Float(value), DataValue::Int32(other_value)) => { - Ok(f64::from(*value) < f64::from(*other_value)) - } - (DataValue::Float(value), DataValue::Int64(other_value)) => { - match i32::try_from(*other_value) { - Ok(other_value) => Ok(f64::from(*value) < f64::from(other_value)), - Err(_) => Err(CastError {}), - } - } - (DataValue::Float(value), DataValue::Uint32(other_value)) => { - Ok(f64::from(*value) < f64::from(*other_value)) - } - (DataValue::Float(value), DataValue::Uint64(other_value)) => { - match u32::try_from(*other_value) { - Ok(other_value) => Ok(f64::from(*value) < f64::from(other_value)), - Err(_) => Err(CastError {}), - } - } - (DataValue::Float(value), DataValue::Float(other_value)) => Ok(value < other_value), - (DataValue::Float(value), DataValue::Double(other_value)) => { - Ok(f64::from(*value) < *other_value) - } - (DataValue::Double(value), DataValue::Int32(other_value)) => { - Ok(*value < f64::from(*other_value)) - } - (DataValue::Double(value), DataValue::Int64(other_value)) => { - match i32::try_from(*other_value) { - Ok(other_value) => Ok(*value < f64::from(other_value)), - Err(_) => Err(CastError {}), - } - } - (DataValue::Double(value), DataValue::Uint32(other_value)) => { - Ok(*value < f64::from(*other_value)) - } - (DataValue::Double(value), DataValue::Uint64(other_value)) => { - match u32::try_from(*other_value) { - Ok(other_value) => Ok(*value < f64::from(other_value)), - Err(_) => Err(CastError {}), - } - } - (DataValue::Double(value), DataValue::Float(other_value)) => { - Ok(*value < f64::from(*other_value)) - } - (DataValue::Double(value), DataValue::Double(other_value)) => Ok(value < other_value), - _ => Err(CastError {}), + pub fn less_than_equal(&self, other: &DataValue) -> Result { + match self.less_than(other) { + Ok(true) => Ok(true), + _ => self.equals(other), } } diff --git a/databroker/src/viss/v2/server.rs b/databroker/src/viss/v2/server.rs index 91a69f62..c839b703 100644 --- a/databroker/src/viss/v2/server.rs +++ b/databroker/src/viss/v2/server.rs @@ -168,6 +168,8 @@ impl Viss for Server { entry_type: None, data_type: None, description: None, + min: None, + max: None, allowed: None, unit: None, }) diff --git a/databroker/src/vss.rs b/databroker/src/vss.rs index a79b0d54..f5d1a8b0 100644 --- a/databroker/src/vss.rs +++ b/databroker/src/vss.rs @@ -203,6 +203,10 @@ impl From for types::DataType { } } +/// Try to extract an array matching the given DataType. +/// Will success if the value is None or a an array of matching type +/// Will fail if the value is a "single" value, i.e. not an array +/// This method is useful for instance when extracting the "allowed" field fn try_from_json_array( array: Option>, data_type: &types::DataType, @@ -251,6 +255,11 @@ fn try_from_json_array( } } +/// Try to extract a value matching the given DataType. +/// Will success if the value is None or a a value of matching type +/// Will fail if the value does not match the given type, +/// for example if a single value is given for an array type or vice versa +/// This method is useful for instance when extracting the "default" value fn try_from_json_value( value: Option, data_type: &types::DataType, @@ -350,6 +359,44 @@ fn try_from_json_value( } } +/// Try to extract a single value matching the given DataType, +/// i.e. if an array type is given it will try to find a single value of the base type +/// For example Int32 if the type is Int32 or Int32Array +/// Will success if the value is of matching base type +/// Will fail otherwise +/// This method is useful for instance when extracting the "min"/"max" field +fn try_from_json_single_value( + value: Option, + data_type: &types::DataType, +) -> Result, Error> { + match data_type { + types::DataType::StringArray => try_from_json_value(value, &types::DataType::String), + types::DataType::BoolArray => try_from_json_value(value, &types::DataType::Bool), + types::DataType::Int8Array => try_from_json_value(value, &types::DataType::Int8), + types::DataType::Int16Array => try_from_json_value(value, &types::DataType::Int16), + types::DataType::Int32Array => try_from_json_value(value, &types::DataType::Int32), + types::DataType::Int64Array => try_from_json_value(value, &types::DataType::Int64), + types::DataType::Uint8Array => try_from_json_value(value, &types::DataType::Uint8), + types::DataType::Uint16Array => try_from_json_value(value, &types::DataType::Uint16), + types::DataType::Uint32Array => try_from_json_value(value, &types::DataType::Uint32), + types::DataType::Uint64Array => try_from_json_value(value, &types::DataType::Uint64), + types::DataType::FloatArray => try_from_json_value(value, &types::DataType::Float), + types::DataType::DoubleArray => try_from_json_value(value, &types::DataType::Double), + types::DataType::String + | types::DataType::Bool + | types::DataType::Int8 + | types::DataType::Int16 + | types::DataType::Int32 + | types::DataType::Int64 + | types::DataType::Uint8 + | types::DataType::Uint16 + | types::DataType::Uint32 + | types::DataType::Uint64 + | types::DataType::Float + | types::DataType::Double => try_from_json_value(value, data_type), + } +} + fn flatten_vss_tree(root: RootEntry) -> Result, Error> { let mut entries = BTreeMap::new(); @@ -396,8 +443,8 @@ fn add_entry( description: entry.description, comment: entry.comment, unit: entry.unit, - min: try_from_json_value(entry.min, &data_type)?, - max: try_from_json_value(entry.max, &data_type)?, + min: try_from_json_single_value(entry.min, &data_type)?, + max: try_from_json_single_value(entry.max, &data_type)?, allowed: try_from_json_array(entry.allowed, &data_type)?, default: None, // isn't used by actuators data_type, @@ -421,8 +468,8 @@ fn add_entry( description: entry.description, comment: entry.comment, unit: entry.unit, - min: try_from_json_value(entry.min, &data_type)?, - max: try_from_json_value(entry.max, &data_type)?, + min: try_from_json_single_value(entry.min, &data_type)?, + max: try_from_json_single_value(entry.max, &data_type)?, allowed: try_from_json_array(entry.allowed, &data_type)?, default: try_from_json_value(entry.default, &data_type)?, change_type: determine_change_type( @@ -450,8 +497,8 @@ fn add_entry( description: entry.description, comment: entry.comment, unit: entry.unit, - min: try_from_json_value(entry.min, &data_type)?, - max: try_from_json_value(entry.max, &data_type)?, + min: try_from_json_single_value(entry.min, &data_type)?, + max: try_from_json_single_value(entry.max, &data_type)?, allowed: try_from_json_array(entry.allowed, &data_type)?, change_type: determine_change_type(entry.change_type, types::EntryType::Sensor), default: None, // isn't used by sensors diff --git a/databroker/tests/world/mod.rs b/databroker/tests/world/mod.rs index b2545076..e33feb72 100644 --- a/databroker/tests/world/mod.rs +++ b/databroker/tests/world/mod.rs @@ -202,6 +202,8 @@ impl DataBrokerWorld { change_type, entry_type, "N/A".to_string(), + None, // min + None, // max None, None, )