This is an automated email from the ASF dual-hosted git repository. martinzink pushed a commit to branch minifi_rust_pgp in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
commit 3f92464beff980d19d18ef1cb7b06762c6bb110f Author: Martin Zink <[email protected]> AuthorDate: Wed Jul 8 10:30:49 2026 +0200 MINIFICPP-2869 Improve rust api property validation and property getters --- .../extensions/minifi_rs_playground/Cargo.toml | 1 + .../controller_services/dog_controller_service.rs | 21 ++- .../lorem_ipsum_controller_service.rs | 12 +- .../src/processors/generate_flow_file.rs | 28 ++-- .../processors/generate_flow_file/properties.rs | 23 +--- .../src/processors/get_file.rs | 39 ++---- .../src/processors/get_file/properties.rs | 47 ++----- .../src/processors/kamikaze_processor.rs | 24 ++-- .../processors/kamikaze_processor/properties.rs | 15 +- .../src/processors/log_attribute.rs | 27 ++-- .../src/processors/log_attribute/properties.rs | 33 ++--- .../src/processors/lorem_ipsum_cs_user.rs | 11 +- .../processors/lorem_ipsum_cs_user/properties.rs | 16 +-- .../src/processors/put_file.rs | 32 ++--- .../src/processors/put_file/properties.rs | 20 +-- .../processors/put_file/unix_only_properties.rs | 10 +- .../src/processors/zoo_processor.rs | 12 +- minifi_rust/minifi_native/Cargo.toml | 3 + minifi_rust/minifi_native/src/api.rs | 5 +- minifi_rust/minifi_native/src/api/attribute.rs | 4 + minifi_rust/minifi_native/src/api/errors.rs | 27 +++- minifi_rust/minifi_native/src/api/logger.rs | 18 ++- .../minifi_native/src/api/process_context.rs | 79 +---------- .../api/processor_wrappers/flow_file_transform.rs | 52 +++++++ .../utils/context_session_flowfile_bundle.rs | 2 +- minifi_rust/minifi_native/src/api/property.rs | 152 +++++++++++++++------ minifi_rust/minifi_native/src/api/relationship.rs | 8 ++ .../src/c_ffi/c_ffi_controller_service_context.rs | 2 +- .../minifi_native/src/c_ffi/c_ffi_property.rs | 26 ++-- minifi_rust/minifi_native/src/lib.rs | 6 +- .../src/mock/mock_controller_service_context.rs | 2 +- minifi_rust/minifi_native_macros/src/lib.rs | 35 +++++ minifi_rust/minifi_rs_behave/src/main.rs | 1 + 33 files changed, 407 insertions(+), 386 deletions(-) diff --git a/minifi_rust/extensions/minifi_rs_playground/Cargo.toml b/minifi_rust/extensions/minifi_rs_playground/Cargo.toml index 8d8682660..ec90190e3 100644 --- a/minifi_rust/extensions/minifi_rs_playground/Cargo.toml +++ b/minifi_rust/extensions/minifi_rs_playground/Cargo.toml @@ -16,6 +16,7 @@ hex = "0.4.3" strum_macros = "0.28.0" lipsum = "0.9.1" + [dev-dependencies] tempfile = "3.22.0" filetime = "0.2.26" diff --git a/minifi_rust/extensions/minifi_rs_playground/src/controller_services/dog_controller_service.rs b/minifi_rust/extensions/minifi_rs_playground/src/controller_services/dog_controller_service.rs index df2521213..40d2357b3 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/controller_services/dog_controller_service.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/controller_services/dog_controller_service.rs @@ -18,11 +18,11 @@ use crate::controller_services::animal_controller_apis::{ CanFlyControllerApi, NumberOfLegsControllerApi, }; -use minifi_native::ControllerServiceApi; use minifi_native::macros::ComponentIdentifier; +use minifi_native::{ControllerServiceApi, property_constraint}; use minifi_native::{ ControllerServiceDefinition, EnableControllerService, GetProperty, Logger, MinifiError, - Property, ProvidedInterface, StandardPropertyValidator, create_provided_interface, + Property, ProvidedInterface, create_provided_interface, }; pub(crate) const HAS_JETPACK: Property = Property { @@ -32,9 +32,7 @@ pub(crate) const HAS_JETPACK: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some("false"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<bool>(), }; pub(crate) const EXTRA_INFO: Property = Property { @@ -44,9 +42,7 @@ pub(crate) const EXTRA_INFO: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, + constraints: None, }; #[allow(dead_code)] // extra_info is only used by {:?} @@ -73,11 +69,10 @@ impl EnableControllerService for DogControllerRs { where Self: Sized, { - let has_jetpack = context.get_bool_property(&HAS_JETPACK)?.ok_or( - MinifiError::missing_required_property("Has jetpack is required"), - )?; - - let extra_info = context.get_property(&EXTRA_INFO)?.unwrap_or("".into()); + let has_jetpack = context.get_req_property::<bool>(&HAS_JETPACK)?; + let extra_info = context + .get_property::<String>(&EXTRA_INFO)? + .unwrap_or("".into()); Ok(Self { has_jetpack, diff --git a/minifi_rust/extensions/minifi_rs_playground/src/controller_services/lorem_ipsum_controller_service.rs b/minifi_rust/extensions/minifi_rs_playground/src/controller_services/lorem_ipsum_controller_service.rs index 25341971e..1f1ff5720 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/controller_services/lorem_ipsum_controller_service.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/controller_services/lorem_ipsum_controller_service.rs @@ -19,7 +19,7 @@ use lipsum::lipsum; use minifi_native::macros::ComponentIdentifier; use minifi_native::{ ControllerServiceDefinition, EnableControllerService, GetProperty, Logger, MinifiError, - Property, ProvidedInterface, StandardPropertyValidator, + Property, ProvidedInterface, property_constraint, }; const LENGTH: Property = Property { @@ -29,9 +29,7 @@ const LENGTH: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some("25"), - validator: StandardPropertyValidator::U64Validator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<u64>(), }; #[derive(Debug, ComponentIdentifier)] @@ -44,11 +42,7 @@ impl EnableControllerService for LoremIpsumControllerService { where Self: Sized, { - let length = context - .get_u64_property(&LENGTH)? - .ok_or(MinifiError::missing_required_property("Length is required"))?; - - let data = lipsum(length as usize); + let data = lipsum(context.get_req_property::<usize>(&LENGTH)?); Ok(Self { data }) } } diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file.rs index 55bb376da..4464ef4e7 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file.rs @@ -19,8 +19,8 @@ use minifi_native::macros::ComponentIdentifier; use minifi_native::{ - GetProperty, Logger, MinifiError, OnTriggerResult, ProcessContext, ProcessSession, Schedule, - Trigger, + DataSize, GetProperty, Logger, MinifiError, OnTriggerResult, ProcessContext, ProcessSession, + Schedule, Trigger, }; use rand::RngExt; use rand::distr::Alphanumeric; @@ -52,22 +52,14 @@ impl Schedule for GenerateFlowFileRs { where Self: Sized, { - let is_unique = context - .get_bool_property(&properties::UNIQUE_FLOW_FILES)? - .expect("Required property"); - let is_text = context - .get_property(&properties::DATA_FORMAT)? - .expect("Required property") - .as_str() - == "Text"; - let has_custom_text = context.get_property(&properties::CUSTOM_TEXT)?.is_some(); - - let file_size = context - .get_size_property(&properties::FILE_SIZE)? - .expect("Required property"); - let batch_size = context - .get_u64_property(&properties::BATCH_SIZE)? - .expect("Required property"); + let is_unique = context.get_req_property::<bool>(&properties::UNIQUE_FLOW_FILES)?; + let is_text = context.get_req_property::<String>(&properties::DATA_FORMAT)? == "Text"; + let has_custom_text = context + .get_property::<String>(&properties::CUSTOM_TEXT)? + .is_some(); + + let file_size = context.get_req_property::<DataSize>(&properties::FILE_SIZE)?; + let batch_size = context.get_req_property::<u64>(&properties::BATCH_SIZE)?; let mode = Self::get_mode(is_unique, is_text, has_custom_text, file_size); let data_generated_during_on_schedule = diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/properties.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/properties.rs index 16d0b8a71..34e68d904 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/properties.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/properties.rs @@ -15,7 +15,8 @@ // specific language governing permissions and limitations // under the License. -use minifi_native::{Property, StandardPropertyValidator}; +use minifi_native::PropertyConstraints::AllowedValues; +use minifi_native::{DataSize, Property, property_constraint}; pub(crate) const FILE_SIZE: Property = Property { name: "File Size", @@ -24,9 +25,7 @@ pub(crate) const FILE_SIZE: Property = Property { is_sensitive: false, supports_expr_lang: true, default_value: Some("1 kB"), - validator: StandardPropertyValidator::DataSizeValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<DataSize>(), }; pub(crate) const BATCH_SIZE: Property = Property { @@ -36,9 +35,7 @@ pub(crate) const BATCH_SIZE: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some("1"), - validator: StandardPropertyValidator::U64Validator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<u64>(), }; pub(crate) const DATA_FORMAT: Property = Property { @@ -48,9 +45,7 @@ pub(crate) const DATA_FORMAT: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some("Binary"), - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &["Text", "Binary"], - allowed_type: None, + constraints: Some(AllowedValues(&["Text", "Binary"])), }; pub(crate) const UNIQUE_FLOW_FILES: Property = Property { @@ -60,9 +55,7 @@ pub(crate) const UNIQUE_FLOW_FILES: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some("true"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<bool>(), }; pub(crate) const CUSTOM_TEXT: Property = Property { @@ -72,7 +65,5 @@ pub(crate) const CUSTOM_TEXT: Property = Property { is_sensitive: false, supports_expr_lang: true, default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, + constraints: None, }; diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file.rs index 1847ea632..dc365e9c4 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file.rs @@ -26,8 +26,8 @@ use crate::processors::get_file::properties::{ }; use minifi_native::macros::ComponentIdentifier; use minifi_native::{ - GetProperty, IoState, Logger, MinifiError, OnTriggerResult, ProcessContext, ProcessSession, - Schedule, Trigger, debug, info, trace, warn, + DataSize, GetProperty, IoState, Logger, MinifiError, OnTriggerResult, ProcessContext, + ProcessSession, Schedule, Trigger, debug, info, trace, warn, }; use std::collections::VecDeque; use std::error; @@ -223,10 +223,7 @@ impl Schedule for GetFileRs { where Self: Sized, { - let input_directory: PathBuf = context - .get_property(&DIRECTORY)? - .expect("Required property") - .into(); + let input_directory = context.get_req_property::<PathBuf>(&DIRECTORY)?; if !input_directory.is_dir() { return Err(MinifiError::schedule_err(format!( "{:?} is not a valid directory", @@ -234,25 +231,17 @@ impl Schedule for GetFileRs { ))); } - let recursive = context - .get_bool_property(&RECURSE)? - .expect("Required property"); - - let keep_source_file = context - .get_bool_property(&KEEP_SOURCE_FILE)? - .expect("Required property"); - - let poll_interval = context.get_duration_property(&properties::POLLING_INTERVAL)?; - let min_size = context.get_size_property(&MIN_SIZE)?; - let max_size = context.get_size_property(&MAX_SIZE)?; - let min_age = context.get_duration_property(&MIN_AGE)?; - let max_age = context.get_duration_property(&MAX_AGE)?; - let batch_size = context - .get_u64_property(&BATCH_SIZE)? - .expect("required property"); - let ignore_hidden_files = context - .get_bool_property(&IGNORE_HIDDEN_FILES)? - .expect("required property"); + let recursive = context.get_req_property::<bool>(&RECURSE)?; + + let keep_source_file = context.get_req_property::<bool>(&KEEP_SOURCE_FILE)?; + + let poll_interval = context.get_property::<Duration>(&properties::POLLING_INTERVAL)?; + let min_size = context.get_property::<DataSize>(&MIN_SIZE)?; + let max_size = context.get_property::<DataSize>(&MAX_SIZE)?; + let min_age = context.get_property::<Duration>(&MIN_AGE)?; + let max_age = context.get_property::<Duration>(&MAX_AGE)?; + let batch_size = context.get_req_property::<u64>(&BATCH_SIZE)?; + let ignore_hidden_files = context.get_req_property::<bool>(&IGNORE_HIDDEN_FILES)?; Ok(GetFileRs { recursive, diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/properties.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/properties.rs index b43009b28..b413b842d 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/properties.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/properties.rs @@ -15,7 +15,8 @@ // specific language governing permissions and limitations // under the License. -use minifi_native::{Property, StandardPropertyValidator}; +use minifi_native::{DataSize, Property, PropertyConstraints, property_constraint}; +use std::time::Duration; pub(crate) const DIRECTORY: Property = Property { name: "Input Directory", @@ -24,33 +25,27 @@ pub(crate) const DIRECTORY: Property = Property { is_sensitive: false, supports_expr_lang: true, default_value: None, - validator: StandardPropertyValidator::NonBlankValidator, - allowed_values: &[], - allowed_type: None, + constraints: PropertyConstraints::non_blank(), }; pub(crate) const RECURSE: Property = Property { name: "Recurse Subdirectories", description: "Indicates whether or not to pull files from subdirectories", - is_required: false, + is_required: true, is_sensitive: false, supports_expr_lang: false, default_value: Some("true"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<bool>(), }; pub(crate) const KEEP_SOURCE_FILE: Property = Property { name: "Keep Source File", description: "If true, the file is not deleted after it has been copied to the Content Repository", - is_required: false, + is_required: true, is_sensitive: false, supports_expr_lang: false, default_value: Some("false"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<bool>(), }; pub(crate) const MIN_AGE: Property = Property { @@ -60,9 +55,7 @@ pub(crate) const MIN_AGE: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::TimePeriodValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<Duration>(), }; pub(crate) const MAX_AGE: Property = Property { @@ -72,9 +65,7 @@ pub(crate) const MAX_AGE: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::TimePeriodValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<Duration>(), }; pub(crate) const MIN_SIZE: Property = Property { @@ -84,9 +75,7 @@ pub(crate) const MIN_SIZE: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::DataSizeValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<DataSize>(), }; pub(crate) const MAX_SIZE: Property = Property { @@ -96,9 +85,7 @@ pub(crate) const MAX_SIZE: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::DataSizeValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<DataSize>(), }; pub(crate) const IGNORE_HIDDEN_FILES: Property = Property { @@ -108,9 +95,7 @@ pub(crate) const IGNORE_HIDDEN_FILES: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some("true"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<bool>(), }; pub(crate) const POLLING_INTERVAL: Property = Property { @@ -120,9 +105,7 @@ pub(crate) const POLLING_INTERVAL: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::TimePeriodValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<Duration>(), }; pub(crate) const BATCH_SIZE: Property = Property { @@ -132,7 +115,5 @@ pub(crate) const BATCH_SIZE: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some("10"), - validator: StandardPropertyValidator::U64Validator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<u64>(), }; diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor.rs index 6686c0b40..f215d8c78 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor.rs @@ -21,15 +21,19 @@ mod properties; mod relationships; use crate::controller_services::lorem_ipsum_controller_service::LoremIpsumControllerService; -use crate::processors::kamikaze_processor::properties::NOT_REGISTERED_PROPERTY; -use minifi_native::macros::ComponentIdentifier; +use crate::processors::kamikaze_processor::properties::{ + NOT_REGISTERED_PROPERTY, SCHEDULE_BEHAVIOUR, TRIGGER_BEHAVIOUR, +}; +use minifi_native::macros::{ComponentIdentifier, PropertyType}; use minifi_native::{ GetProperty, Logger, MinifiError, OnTriggerResult, ProcessContext, ProcessSession, Schedule, Trigger, }; use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames}; -#[derive(Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, IntoStaticStr)] +#[derive( + Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, IntoStaticStr, PropertyType, +)] #[strum(serialize_all = "PascalCase", const_into_str)] enum KamikazeBehaviour { ReturnErr, @@ -49,15 +53,11 @@ impl Schedule for KamikazeProcessorRs { where Self: Sized, { - let trigger_behaviour = context - .get_property(&properties::TRIGGER_BEHAVIOUR)? - .expect("required property") - .parse::<KamikazeBehaviour>()?; + let trigger_behaviour = + context.get_req_property::<KamikazeBehaviour>(&TRIGGER_BEHAVIOUR)?; - let schedule_behaviour = context - .get_property(&properties::SCHEDULE_BEHAVIOUR)? - .expect("required property") - .parse::<KamikazeBehaviour>()?; + let schedule_behaviour = + context.get_req_property::<KamikazeBehaviour>(&SCHEDULE_BEHAVIOUR)?; match schedule_behaviour { KamikazeBehaviour::ReturnErr => Err(MinifiError::schedule_err( @@ -65,7 +65,7 @@ impl Schedule for KamikazeProcessorRs { )), KamikazeBehaviour::ReturnOk => Ok(KamikazeProcessorRs { trigger_behaviour }), KamikazeBehaviour::GetNotRegisteredProperty => { - let _ = context.get_property(&NOT_REGISTERED_PROPERTY)?; + let _ = context.get_property::<String>(&NOT_REGISTERED_PROPERTY)?; Ok(KamikazeProcessorRs { trigger_behaviour }) } KamikazeBehaviour::Panic => { diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/properties.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/properties.rs index 6ee86b1bd..510890763 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/properties.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/properties.rs @@ -16,8 +16,7 @@ // under the License. use crate::processors::kamikaze_processor::KamikazeBehaviour; -use minifi_native::{Property, StandardPropertyValidator}; -use strum::VariantNames; +use minifi_native::{Property, property_constraint}; pub(crate) const SCHEDULE_BEHAVIOUR: Property = Property { name: "Schedule Behaviour", @@ -26,9 +25,7 @@ pub(crate) const SCHEDULE_BEHAVIOUR: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some(KamikazeBehaviour::ReturnOk.into_str()), - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: KamikazeBehaviour::VARIANTS, - allowed_type: None, + constraints: property_constraint::<KamikazeBehaviour>(), }; pub(crate) const TRIGGER_BEHAVIOUR: Property = Property { @@ -38,9 +35,7 @@ pub(crate) const TRIGGER_BEHAVIOUR: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some(KamikazeBehaviour::ReturnOk.into_str()), - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: KamikazeBehaviour::VARIANTS, - allowed_type: None, + constraints: property_constraint::<KamikazeBehaviour>(), }; pub(crate) const NOT_REGISTERED_PROPERTY: Property = Property { @@ -50,7 +45,5 @@ pub(crate) const NOT_REGISTERED_PROPERTY: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, + constraints: None, }; diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute.rs index 3e59e2d52..c59eb5096 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute.rs @@ -121,25 +121,15 @@ impl Trigger for LogAttributeRs { impl Schedule for LogAttributeRs { fn schedule<P: GetProperty, L>(context: &P, _logger: &L) -> Result<Self, MinifiError> { - let log_level = context - .get_property(&LOG_LEVEL)? - .expect("required property") - .parse::<LogLevel>()?; - - let log_payload = context - .get_bool_property(&LOG_PAYLOAD)? - .expect("required property"); - - let flow_files_to_log = context - .get_property(&FLOW_FILES_TO_LOG)? - .expect("required property") - .parse::<usize>()?; + let log_level = context.get_req_property::<LogLevel>(&LOG_LEVEL)?; + let log_payload = context.get_req_property::<bool>(&LOG_PAYLOAD)?; + let flow_files_to_log = context.get_req_property::<usize>(&FLOW_FILES_TO_LOG)?; fn get_csv_property<P: GetProperty>( context: &P, property: &Property, ) -> Result<Option<Vec<String>>, MinifiError> { - Ok(context.get_property(property)?.map(|s| { + Ok(context.get_property::<String>(property)?.map(|s| { s.split(',') .map(|s| s.trim().to_string()) .collect::<Vec<String>>() @@ -152,13 +142,12 @@ impl Schedule for LogAttributeRs { let dash_line = format!( "{:-^50}", context - .get_property(&properties::LOG_PREFIX)? - .unwrap_or(String::new()) + .get_property::<String>(&properties::LOG_PREFIX)? + .unwrap_or_default() ); - let hex_encode_payload = context - .get_bool_property(&properties::HEX_ENCODE_PAYLOAD)? - .expect("required property"); + let hex_encode_payload = + context.get_req_property::<bool>(&properties::HEX_ENCODE_PAYLOAD)?; Ok(LogAttributeRs { log_level, diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute/properties.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute/properties.rs index 48002c92f..b4c5d47ad 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute/properties.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute/properties.rs @@ -15,8 +15,7 @@ // specific language governing permissions and limitations // under the License. -use minifi_native::{LogLevel, Property, StandardPropertyValidator}; -use strum::VariantNames; +use minifi_native::{LogLevel, Property, property_constraint}; pub(crate) const LOG_LEVEL: Property = Property { name: "Log Level", @@ -24,10 +23,8 @@ pub(crate) const LOG_LEVEL: Property = Property { is_required: true, is_sensitive: false, supports_expr_lang: false, - default_value: Some("Info"), // todo! it would be nicer to come from enum value but wasnt able to use into_const_str from another crate - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: LogLevel::VARIANTS, - allowed_type: None, + default_value: Some(LogLevel::Info.into_str()), + constraints: property_constraint::<LogLevel>(), }; pub(crate) const ATTRIBUTES_TO_LOG: Property = Property { @@ -37,9 +34,7 @@ pub(crate) const ATTRIBUTES_TO_LOG: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, + constraints: None, }; pub(crate) const ATTRIBUTES_TO_IGNORE: Property = Property { @@ -49,9 +44,7 @@ pub(crate) const ATTRIBUTES_TO_IGNORE: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, + constraints: None, }; pub(crate) const LOG_PAYLOAD: Property = Property { @@ -61,9 +54,7 @@ pub(crate) const LOG_PAYLOAD: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some("false"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<bool>(), }; pub(crate) const LOG_PREFIX: Property = Property { @@ -73,9 +64,7 @@ pub(crate) const LOG_PREFIX: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, + constraints: None, }; pub(crate) const FLOW_FILES_TO_LOG: Property = Property { @@ -85,9 +74,7 @@ pub(crate) const FLOW_FILES_TO_LOG: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some("1"), - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<u64>(), }; pub(crate) const HEX_ENCODE_PAYLOAD: Property = Property { @@ -97,7 +84,5 @@ pub(crate) const HEX_ENCODE_PAYLOAD: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some("false"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<bool>(), }; diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user.rs index 2c4cdb517..b44fcc811 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user.rs @@ -21,7 +21,7 @@ mod properties; use crate::controller_services::lorem_ipsum_controller_service::LoremIpsumControllerService; use crate::processors::lorem_ipsum_cs_user::properties::CONTROLLER_SERVICE; use crate::processors::lorem_ipsum_cs_user::relationships::SUCCESS; -use minifi_native::macros::ComponentIdentifier; +use minifi_native::macros::{ComponentIdentifier, PropertyType}; use minifi_native::{ Content, FlowFileSource, GeneratedFlowFile, GetControllerService, GetProperty, Logger, MinifiError, Schedule, trace, @@ -29,7 +29,9 @@ use minifi_native::{ use std::collections::HashMap; use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames}; -#[derive(Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, IntoStaticStr)] +#[derive( + Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, IntoStaticStr, PropertyType, +)] #[strum(serialize_all = "PascalCase", const_into_str)] enum WriteMethod { Buffer, @@ -46,10 +48,7 @@ impl Schedule for LoremIpsumCSUser { where Self: Sized, { - let write_method = context - .get_property(&properties::WRITE_METHOD)? - .expect("required property") - .parse::<WriteMethod>()?; + let write_method = context.get_req_property::<WriteMethod>(&properties::WRITE_METHOD)?; Ok(Self { write_method }) } } diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user/properties.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user/properties.rs index b78ab6d23..b424e5ad1 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user/properties.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user/properties.rs @@ -16,9 +16,9 @@ // under the License. use crate::controller_services::lorem_ipsum_controller_service::LoremIpsumControllerService; -use minifi_native::ComponentIdentifier; -use minifi_native::{Property, StandardPropertyValidator}; -use strum::VariantNames; +use crate::processors::lorem_ipsum_cs_user::WriteMethod; +use minifi_native::Property; +use minifi_native::property_constraint; pub(crate) const CONTROLLER_SERVICE: Property = Property { name: "Lorem Ipsum Controller Service", @@ -27,9 +27,7 @@ pub(crate) const CONTROLLER_SERVICE: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: Some(LoremIpsumControllerService::CLASS_NAME), + constraints: property_constraint::<LoremIpsumControllerService>(), }; pub(crate) const WRITE_METHOD: Property = Property { @@ -38,8 +36,6 @@ pub(crate) const WRITE_METHOD: Property = Property { is_required: true, is_sensitive: false, supports_expr_lang: false, - default_value: Some(super::WriteMethod::Buffer.into_str()), - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: super::WriteMethod::VARIANTS, - allowed_type: None, + default_value: Some(WriteMethod::Buffer.into_str()), + constraints: property_constraint::<WriteMethod>(), }; diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file.rs index 4d554c132..5fa35a63e 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file.rs @@ -18,10 +18,10 @@ // This is the (not production ready) reimplementation of the already existing standard PutFile processor use crate::processors::put_file::relationships::{FAILURE, SUCCESS}; -use minifi_native::macros::ComponentIdentifier; +use minifi_native::macros::{ComponentIdentifier, PropertyType}; use minifi_native::{ FlowFileTransform, GetAttribute, GetControllerService, GetId, GetProperty, InputStream, Logger, - MinifiError, Schedule, TransformedFlowFile, trace, warn, + MinifiError, Schedule, TransformedFlowFile, trace, unwrap_or_route, warn, }; use std::path::{Path, PathBuf}; use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames}; @@ -32,7 +32,9 @@ mod relationships; #[cfg(unix)] mod unix_only_properties; -#[derive(Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, IntoStaticStr)] +#[derive( + Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, IntoStaticStr, PropertyType, +)] #[strum(serialize_all = "camelCase", const_into_str)] enum ConflictResolutionStrategy { Fail, @@ -109,14 +111,12 @@ impl PutFileRs { where Ctx: GetProperty + GetAttribute + GetId, { - let directory = context - .get_property(&properties::DIRECTORY)? - .expect("required property"); + let directory = context.get_req_property::<PathBuf>(&properties::DIRECTORY)?; let file_name = context .get_attribute("filename")? .unwrap_or(context.get_id()?); - Ok(PathBuf::from(directory).join(file_name)) + Ok(directory.join(file_name)) } fn prepare_destination(&self, destination: &Path) -> std::io::Result<()> { @@ -163,7 +163,7 @@ impl PutFileRs { let parse_permission = |property: &minifi_native::Property| -> Result<Option<std::fs::Permissions>, MinifiError> { Ok(context - .get_property(property)? + .get_property::<String>(property)? .map(|perm_str| u32::from_str_radix(&perm_str, 8)) .transpose()? .map(std::fs::Permissions::from_mode)) @@ -188,15 +188,11 @@ impl PutFileRs { impl Schedule for PutFileRs { fn schedule<P: GetProperty, L: Logger>(context: &P, _logger: &L) -> Result<Self, MinifiError> { let conflict_resolution_strategy = context - .get_property(&properties::CONFLICT_RESOLUTION)? - .expect("required property") - .parse::<ConflictResolutionStrategy>()?; + .get_req_property::<ConflictResolutionStrategy>(&properties::CONFLICT_RESOLUTION)?; - let try_make_dirs = context - .get_bool_property(&properties::CREATE_DIRS)? - .expect("required property"); + let try_make_dirs = context.get_req_property::<bool>(&properties::CREATE_DIRS)?; - let maximum_file_count = context.get_u64_property(&properties::MAX_FILE_COUNT)?; + let maximum_file_count = context.get_property::<u64>(&properties::MAX_FILE_COUNT)?; let unix_permissions = Self::parse_unix_permissions(context)?; @@ -222,10 +218,8 @@ impl FlowFileTransform for PutFileRs { ) -> Result<TransformedFlowFile<'a>, MinifiError> { trace!(logger, "on_trigger: {:?}", self); - let Ok(destination_path) = Self::get_destination_path(context) else { - warn!(logger, "Invalid destination path"); - return Ok(TransformedFlowFile::route_without_changes(&FAILURE)); - }; + let destination_path = + unwrap_or_route!(Self::get_destination_path(context), &FAILURE, logger); if self.directory_is_full(&destination_path) { warn!(logger, "Directory is full"); diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/properties.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/properties.rs index 4facd860f..c93128172 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/properties.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/properties.rs @@ -15,10 +15,8 @@ // specific language governing permissions and limitations // under the License. -use minifi_native::{Property, StandardPropertyValidator}; -use strum::VariantNames; - use super::ConflictResolutionStrategy; +use minifi_native::{Property, PropertyConstraints, property_constraint}; pub(crate) const DIRECTORY: Property = Property { name: "Directory", description: "The output directory to which to put files", @@ -26,9 +24,7 @@ pub(crate) const DIRECTORY: Property = Property { is_sensitive: false, supports_expr_lang: true, default_value: Some("."), - validator: StandardPropertyValidator::NonBlankValidator, - allowed_values: &[], - allowed_type: None, + constraints: PropertyConstraints::non_blank(), }; pub(crate) const CONFLICT_RESOLUTION: Property = Property { @@ -38,9 +34,7 @@ pub(crate) const CONFLICT_RESOLUTION: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some(ConflictResolutionStrategy::Fail.into_str()), - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: ConflictResolutionStrategy::VARIANTS, - allowed_type: None, + constraints: property_constraint::<ConflictResolutionStrategy>(), }; pub(crate) const CREATE_DIRS: Property = Property { @@ -50,9 +44,7 @@ pub(crate) const CREATE_DIRS: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: Some("true"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<bool>(), }; pub(crate) const MAX_FILE_COUNT: Property = Property { @@ -62,7 +54,5 @@ pub(crate) const MAX_FILE_COUNT: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, // Diverged from the original implementation, u64 with no default describes the behavior better - validator: StandardPropertyValidator::U64Validator, - allowed_values: &[], - allowed_type: None, + constraints: property_constraint::<u64>(), }; diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/unix_only_properties.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/unix_only_properties.rs index f7cbd7811..d7a75da41 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/unix_only_properties.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/unix_only_properties.rs @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -use minifi_native::{Property, StandardPropertyValidator}; +use minifi_native::Property; pub(crate) const PERMISSIONS: Property = Property { name: "Permissions", @@ -24,9 +24,7 @@ pub(crate) const PERMISSIONS: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, + constraints: None, }; pub(crate) const DIRECTORY_PERMISSIONS: Property = Property { @@ -36,7 +34,5 @@ pub(crate) const DIRECTORY_PERMISSIONS: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, + constraints: None, }; diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/zoo_processor.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/zoo_processor.rs index 2bbdb9a55..20eb46a5b 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/zoo_processor.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/zoo_processor.rs @@ -18,12 +18,12 @@ use crate::controller_services::animal_controller_apis::{ CanFlyControllerApi, NumberOfLegsControllerApi, }; -use minifi_native::ControllerServiceApi; use minifi_native::macros::ComponentIdentifier; +use minifi_native::property_constraint; use minifi_native::{ GetProperty, Logger, MinifiError, OnTriggerResult, OutputAttribute, ProcessContext, ProcessSession, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, - Schedule, StandardPropertyValidator, Trigger, critical, info, + Schedule, Trigger, critical, info, }; pub(crate) const CAN_FLY_SERVICE: Property = Property { @@ -33,9 +33,7 @@ pub(crate) const CAN_FLY_SERVICE: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: Some(<dyn CanFlyControllerApi>::INTERFACE_NAME), + constraints: property_constraint::<dyn CanFlyControllerApi>(), }; pub(crate) const NUMBER_OF_LEGS: Property = Property { @@ -45,9 +43,7 @@ pub(crate) const NUMBER_OF_LEGS: Property = Property { is_sensitive: false, supports_expr_lang: false, default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: Some(<dyn NumberOfLegsControllerApi>::INTERFACE_NAME), + constraints: property_constraint::<dyn NumberOfLegsControllerApi>(), }; #[derive(Debug, ComponentIdentifier)] diff --git a/minifi_rust/minifi_native/Cargo.toml b/minifi_rust/minifi_native/Cargo.toml index 5998c4fd3..b3f75648b 100644 --- a/minifi_rust/minifi_native/Cargo.toml +++ b/minifi_rust/minifi_native/Cargo.toml @@ -13,3 +13,6 @@ strum_macros = "0.28.0" humantime = "2.3.0" byte-unit = "5.1.6" itertools = "0.14.0" + +[features] +test-utils = [] diff --git a/minifi_rust/minifi_native/src/api.rs b/minifi_rust/minifi_native/src/api.rs index 6152b94ca..37d62acd0 100644 --- a/minifi_rust/minifi_native/src/api.rs +++ b/minifi_rust/minifi_native/src/api.rs @@ -38,6 +38,9 @@ pub use process_session::{InputStream, OutputStream, ProcessSession}; pub use raw_controller_service::RawControllerService; pub use raw_processor::{OnTriggerResult, ProcessorInputRequirement, RawProcessor, ThreadingModel}; -pub use property::StandardPropertyValidator; +pub use property::{ + DataSize, PropertyConstraints, PropertyType, ProvidesPropertyConstraint, + StandardPropertyValidator, property_constraint, +}; pub use relationship::Relationship; diff --git a/minifi_rust/minifi_native/src/api/attribute.rs b/minifi_rust/minifi_native/src/api/attribute.rs index 9e6b316eb..571ea7ce7 100644 --- a/minifi_rust/minifi_native/src/api/attribute.rs +++ b/minifi_rust/minifi_native/src/api/attribute.rs @@ -25,4 +25,8 @@ pub struct OutputAttribute { pub trait GetAttribute { fn get_attribute(&self, name: &str) -> Result<Option<String>, MinifiError>; + fn get_required_attribute(&self, name: &str) -> Result<String, MinifiError> { + self.get_attribute(name)? + .ok_or(MinifiError::missing_required_attribute(name.to_owned())) + } } diff --git a/minifi_rust/minifi_native/src/api/errors.rs b/minifi_rust/minifi_native/src/api/errors.rs index 633d413ad..2c833a997 100644 --- a/minifi_rust/minifi_native/src/api/errors.rs +++ b/minifi_rust/minifi_native/src/api/errors.rs @@ -17,9 +17,10 @@ use minifi_native_sys::minifi_status; use std::borrow::Cow; +use std::error::Error; use std::ffi::NulError; use std::fmt; -use std::num::{NonZeroU32, ParseIntError}; +use std::num::{NonZeroU32, ParseFloatError, ParseIntError}; use std::str::ParseBoolError; #[derive(Debug, Clone)] @@ -30,6 +31,7 @@ pub enum ParseError { Duration(humantime::DurationError), Size(byte_unit::ParseError), Nul(NulError), + Float(ParseFloatError), Other, } @@ -37,6 +39,7 @@ pub enum ParseError { pub enum MinifiError { UnknownError, StatusError((Cow<'static, str>, NonZeroU32)), + MissingRequiredAttribute(Cow<'static, str>), MissingRequiredProperty(Cow<'static, str>), ControllerServiceError(Cow<'static, str>), ValidationError(Cow<'static, str>), @@ -89,6 +92,18 @@ impl From<NulError> for MinifiError { } } +impl From<ParseFloatError> for MinifiError { + fn from(err: ParseFloatError) -> Self { + MinifiError::Parse(ParseError::Float(err)) + } +} + +impl From<std::convert::Infallible> for MinifiError { + fn from(_: std::convert::Infallible) -> Self { + unreachable!("Infallible errors can never happen") + } +} + impl MinifiError { pub(crate) fn to_status(&self) -> minifi_status { match self { @@ -125,9 +140,17 @@ impl MinifiError { MinifiError::MissingRequiredProperty(msg.into()) } + pub fn missing_required_attribute<S: Into<Cow<'static, str>>>(msg: S) -> Self { + MinifiError::MissingRequiredAttribute(msg.into()) + } + pub fn controller_service_err<S: Into<Cow<'static, str>>>(msg: S) -> Self { MinifiError::ControllerServiceError(msg.into()) } + + pub fn parse_err() -> Self { + MinifiError::Parse(ParseError::Other) + } } impl fmt::Display for MinifiError { @@ -158,3 +181,5 @@ impl fmt::Display for MinifiError { } } } + +impl Error for MinifiError {} diff --git a/minifi_rust/minifi_native/src/api/logger.rs b/minifi_rust/minifi_native/src/api/logger.rs index b7df51817..80a20b6f6 100644 --- a/minifi_rust/minifi_native/src/api/logger.rs +++ b/minifi_rust/minifi_native/src/api/logger.rs @@ -18,9 +18,23 @@ use std::fmt; use std::fmt::Debug; -use strum_macros::{Display, EnumString, VariantNames}; +use minifi_native_macros::PropertyType; +use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Display, EnumString, VariantNames)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Display, + EnumString, + VariantNames, + IntoStaticStr, + PropertyType, +)] #[strum(serialize_all = "PascalCase", const_into_str)] pub enum LogLevel { Trace, diff --git a/minifi_rust/minifi_native/src/api/process_context.rs b/minifi_rust/minifi_native/src/api/process_context.rs index 0f114cf47..528dceb42 100644 --- a/minifi_rust/minifi_native/src/api/process_context.rs +++ b/minifi_rust/minifi_native/src/api/process_context.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use crate::StandardPropertyValidator::*; use crate::api::RawControllerService; use crate::api::component_definition_traits::ComponentIdentifier; use crate::api::flow_file::FlowFile; @@ -24,8 +23,6 @@ use crate::{ ControllerServiceApi, ControllerServiceDefinition, EnableControllerService, GetProperty, MinifiError, Property, }; -use std::str::FromStr; -use std::time::Duration; pub trait ProcessContext { type FlowFile: FlowFile; @@ -36,80 +33,6 @@ pub trait ProcessContext { flow_file: Option<&Self::FlowFile>, ) -> Result<Option<String>, MinifiError>; - fn get_bool_property( - &self, - property: &Property, - flow_file: Option<&Self::FlowFile>, - ) -> Result<Option<bool>, MinifiError> { - if property.validator != BoolValidator { - return Err(MinifiError::validation_err(format!( - "to use get_bool_property {:?} must have BoolValidator", - property - ))); - } - - if let Some(property_val) = self.get_property(property, flow_file)? { - Ok(Some(bool::from_str(&property_val)?)) - } else { - Ok(None) - } - } - - fn get_duration_property( - &self, - property: &Property, - flow_file: Option<&Self::FlowFile>, - ) -> Result<Option<Duration>, MinifiError> { - if property.validator != TimePeriodValidator { - return Err(MinifiError::validation_err(format!( - "to use get_duration_property {:?} must have TimePeriodValidator", - property - ))); - } - - if let Some(property_val) = self.get_property(property, flow_file)? { - Ok(Some(humantime::parse_duration(property_val.as_str())?)) - } else { - Ok(None) - } - } - - fn get_size_property( - &self, - property: &Property, - flow_file: Option<&Self::FlowFile>, - ) -> Result<Option<u64>, MinifiError> { - if property.validator != DataSizeValidator { - return Err(MinifiError::validation_err(format!( - "to use get_size_property {:?} must have DataSizeValidator", - property - ))); - } - if let Some(property_val) = self.get_property(property, flow_file)? { - Ok(Some(byte_unit::Byte::from_str(&property_val)?.as_u64())) - } else { - Ok(None) - } - } - - fn get_u64_property( - &self, - property: &Property, - flow_file: Option<&Self::FlowFile>, - ) -> Result<Option<u64>, MinifiError> { - if property.validator != U64Validator { - return Err(MinifiError::validation_err(format!( - "to use get_u64_property {:?} must have U64Validator", - property - ))); - } - if let Some(property_val) = self.get_property(property, flow_file)? { - Ok(Some(u64::from_str(&property_val)?)) - } else { - Ok(None) - } - } - fn get_raw_controller_service<Cs>( &self, property: &Property, @@ -133,7 +56,7 @@ impl<S> GetProperty for S where S: ProcessContext, { - fn get_property(&self, property: &Property) -> Result<Option<String>, MinifiError> { + fn get_raw_property(&self, property: &Property) -> Result<Option<String>, MinifiError> { self.get_property(property, None) } } diff --git a/minifi_rust/minifi_native/src/api/processor_wrappers/flow_file_transform.rs b/minifi_rust/minifi_native/src/api/processor_wrappers/flow_file_transform.rs index f856ca83d..ef2342b5b 100644 --- a/minifi_rust/minifi_native/src/api/processor_wrappers/flow_file_transform.rs +++ b/minifi_rust/minifi_native/src/api/processor_wrappers/flow_file_transform.rs @@ -67,6 +67,19 @@ impl<'a> TransformedFlowFile<'a> { pub fn attributes_to_add(&self) -> &HashMap<String, String> { &self.attributes_to_add } + + #[cfg(any(test, feature = "test-utils"))] + pub fn into_bytes(self) -> std::io::Result<Option<Vec<u8>>> { + match self.new_content { + Some(Content::Buffer(vec)) => Ok(Some(vec)), + Some(Content::Stream(mut stream)) => { + let mut buffer = Vec::new(); + stream.read_to_end(&mut buffer)?; + Ok(Some(buffer)) + } + None => Ok(None), + } + } } pub trait FlowFileTransform { @@ -200,3 +213,42 @@ where } } } + +#[macro_export] +macro_rules! unwrap_or_route { + ($result:expr, $route:expr) => { + match $result { + Ok(v) => v, + Err(_e) => { + return Ok(TransformedFlowFile::route_without_changes($route)); + } + } + }; + + ($result:expr, $route:expr, $custom_logger:expr) => { + match $result { + Ok(v) => v, + Err(e) => { + minifi_native::error!( + $custom_logger, + "Failed to unwrap due to {}. Routing flow file.", + e + ); + return Ok(TransformedFlowFile::route_without_changes($route)); + } + } + }; + + ($result:expr, $route:expr, $custom_logger:expr, $context:expr) => { + match $result { + Ok(v) => v, + Err(e) => { + error!( + $custom_logger, + "Failed to {} due to {}. Routing flow file.", $context, e + ); + return Ok(TransformedFlowFile::route_without_changes($route)); + } + } + }; +} diff --git a/minifi_rust/minifi_native/src/api/processor_wrappers/utils/context_session_flowfile_bundle.rs b/minifi_rust/minifi_native/src/api/processor_wrappers/utils/context_session_flowfile_bundle.rs index 8745b2fb7..c05942066 100644 --- a/minifi_rust/minifi_native/src/api/processor_wrappers/utils/context_session_flowfile_bundle.rs +++ b/minifi_rust/minifi_native/src/api/processor_wrappers/utils/context_session_flowfile_bundle.rs @@ -55,7 +55,7 @@ where PC: ProcessContext, PS: ProcessSession<FlowFile = PC::FlowFile>, { - fn get_property(&self, property: &Property) -> Result<Option<String>, MinifiError> { + fn get_raw_property(&self, property: &Property) -> Result<Option<String>, MinifiError> { self.context.get_property(property, self.flow_file) } } diff --git a/minifi_rust/minifi_native/src/api/property.rs b/minifi_rust/minifi_native/src/api/property.rs index 952b6eb68..97c414894 100644 --- a/minifi_rust/minifi_native/src/api/property.rs +++ b/minifi_rust/minifi_native/src/api/property.rs @@ -26,7 +26,6 @@ use std::time::Duration; #[derive(Debug, Eq, PartialEq)] pub enum StandardPropertyValidator { - AlwaysValidValidator, NonBlankValidator, TimePeriodValidator, BoolValidator, @@ -36,6 +35,17 @@ pub enum StandardPropertyValidator { PortValidator, } +#[derive(Debug, PartialEq)] +pub enum PropertyConstraints { + Validator(StandardPropertyValidator), + AllowedValues(&'static [&'static str]), + ControllerService(&'static str), +} + +pub trait ProvidesPropertyConstraint { + const PROPERTY_CONSTRAINT: Option<PropertyConstraints>; +} + #[derive(Debug)] pub struct Property { pub name: &'static str, @@ -44,69 +54,123 @@ pub struct Property { pub is_sensitive: bool, pub supports_expr_lang: bool, pub default_value: Option<&'static str>, - pub validator: StandardPropertyValidator, - pub allowed_values: &'static [&'static str], - pub allowed_type: Option<&'static str>, + pub constraints: Option<PropertyConstraints>, } -pub trait GetProperty { - fn get_property(&self, property: &Property) -> Result<Option<String>, MinifiError>; - fn get_bool_property(&self, property: &Property) -> Result<Option<bool>, MinifiError> { - if property.validator != BoolValidator { - return Err(MinifiError::validation_err(format!( - "to use get_bool_property {:?} must have BoolValidator", - property - ))); - } +pub trait PropertyType { + type Output; + const EXPECTED_CONSTRAINTS: Option<PropertyConstraints> = None; - if let Some(property_val) = self.get_property(property)? { - Ok(Some(bool::from_str(&property_val)?)) - } else { - Ok(None) - } + fn parse(s: &str) -> Result<Self::Output, MinifiError>; +} + +impl PropertyConstraints { + pub const fn non_blank() -> Option<Self> { + Some(Self::Validator( + StandardPropertyValidator::NonBlankValidator, + )) } +} - fn get_duration_property(&self, property: &Property) -> Result<Option<Duration>, MinifiError> { - if property.validator != TimePeriodValidator { - return Err(MinifiError::validation_err(format!( - "to use get_duration_property {:?} must have TimePeriodValidator", - property - ))); - } +pub const fn property_constraint<T: ProvidesPropertyConstraint + ?Sized>() +-> Option<PropertyConstraints> { + T::PROPERTY_CONSTRAINT +} - if let Some(property_val) = self.get_property(property)? { - Ok(Some(humantime::parse_duration(property_val.as_str())?)) - } else { - Ok(None) +macro_rules! impl_from_str_property { + ($t:ty, $validator:expr) => { + impl PropertyType for $t { + type Output = $t; + const EXPECTED_CONSTRAINTS: Option<PropertyConstraints> = $validator; + + fn parse(s: &str) -> Result<Self::Output, MinifiError> { + s.parse::<$t>().map_err(Into::into) + } + } + impl ProvidesPropertyConstraint for $t { + const PROPERTY_CONSTRAINT: Option<PropertyConstraints> = $validator; } + }; + ($t:ty) => { + impl_from_str_property!($t, None); + }; +} + +impl_from_str_property!(String); +impl_from_str_property!(std::path::PathBuf); +impl_from_str_property!(f64); +impl_from_str_property!(f32); +impl_from_str_property!(i64); +impl_from_str_property!(i32); +impl_from_str_property!(bool, Some(PropertyConstraints::Validator(BoolValidator))); +impl_from_str_property!(u64, Some(PropertyConstraints::Validator(U64Validator))); +impl_from_str_property!(u32, Some(PropertyConstraints::Validator(U64Validator))); +impl_from_str_property!(usize, Some(PropertyConstraints::Validator(U64Validator))); + +impl PropertyType for Duration { + type Output = Duration; + const EXPECTED_CONSTRAINTS: Option<PropertyConstraints> = + Some(PropertyConstraints::Validator(TimePeriodValidator)); + fn parse(s: &str) -> Result<Self::Output, MinifiError> { + humantime::parse_duration(s).map_err(Into::into) } +} +impl ProvidesPropertyConstraint for Duration { + const PROPERTY_CONSTRAINT: Option<PropertyConstraints> = Self::EXPECTED_CONSTRAINTS; +} - fn get_size_property(&self, property: &Property) -> Result<Option<u64>, MinifiError> { - if property.validator != DataSizeValidator { +pub struct DataSize; +impl PropertyType for DataSize { + type Output = u64; + const EXPECTED_CONSTRAINTS: Option<PropertyConstraints> = + Some(PropertyConstraints::Validator(DataSizeValidator)); + fn parse(s: &str) -> Result<Self::Output, MinifiError> { + byte_unit::Byte::from_str(s) + .map(|b| b.as_u64()) + .map_err(Into::into) + } +} + +impl ProvidesPropertyConstraint for DataSize { + const PROPERTY_CONSTRAINT: Option<PropertyConstraints> = Self::EXPECTED_CONSTRAINTS; +} + +pub trait GetProperty { + fn get_raw_property(&self, property: &Property) -> Result<Option<String>, MinifiError>; + + fn get_property<T: PropertyType>( + &self, + property: &Property, + ) -> Result<Option<T::Output>, MinifiError> { + if let Some(expected) = T::EXPECTED_CONSTRAINTS + && Some(expected) != property.constraints + { return Err(MinifiError::validation_err(format!( - "to use get_size_property {:?} must have DataSizeValidator", - property + "to use get_property for this type, {:?} must have validator {:?}", + property.name, + T::EXPECTED_CONSTRAINTS ))); } - if let Some(property_val) = self.get_property(property)? { - Ok(Some(byte_unit::Byte::from_str(&property_val)?.as_u64())) + + if let Some(property_val) = self.get_raw_property(property)? { + Ok(Some(T::parse(&property_val)?)) } else { Ok(None) } } - fn get_u64_property(&self, property: &Property) -> Result<Option<u64>, MinifiError> { - if property.validator != U64Validator { + fn get_req_property<T: PropertyType>( + &self, + property: &Property, + ) -> Result<T::Output, MinifiError> { + if !property.is_required { return Err(MinifiError::validation_err(format!( - "to use get_u64_property {:?} must have U64Validator", - property + "to use get_req_property, {:?} must be required", + property.name ))); } - if let Some(property_val) = self.get_property(property)? { - Ok(Some(u64::from_str(&property_val)?)) - } else { - Ok(None) - } + self.get_property::<T>(property)? + .ok_or_else(|| MinifiError::missing_required_property(property.name)) } } diff --git a/minifi_rust/minifi_native/src/api/relationship.rs b/minifi_rust/minifi_native/src/api/relationship.rs index 45041a2ba..df3e34081 100644 --- a/minifi_rust/minifi_native/src/api/relationship.rs +++ b/minifi_rust/minifi_native/src/api/relationship.rs @@ -15,8 +15,16 @@ // specific language governing permissions and limitations // under the License. +use std::fmt::{Display, Formatter}; + #[derive(Debug, Eq, PartialEq)] pub struct Relationship { pub name: &'static str, pub description: &'static str, } + +impl Display for Relationship { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} diff --git a/minifi_rust/minifi_native/src/c_ffi/c_ffi_controller_service_context.rs b/minifi_rust/minifi_native/src/c_ffi/c_ffi_controller_service_context.rs index 2284c8dc7..2c285fd4f 100644 --- a/minifi_rust/minifi_native/src/c_ffi/c_ffi_controller_service_context.rs +++ b/minifi_rust/minifi_native/src/c_ffi/c_ffi_controller_service_context.rs @@ -61,7 +61,7 @@ unsafe extern "C" fn property_callback( } impl<'a> GetProperty for CffiControllerServiceContext<'a> { - fn get_property(&self, property: &Property) -> Result<Option<String>, MinifiError> { + fn get_raw_property(&self, property: &Property) -> Result<Option<String>, MinifiError> { let mut result: Option<String> = None; let property_name: StringView = StringView::new(property.name); diff --git a/minifi_rust/minifi_native/src/c_ffi/c_ffi_property.rs b/minifi_rust/minifi_native/src/c_ffi/c_ffi_property.rs index d2fe2c1b7..ec2c0d3d7 100644 --- a/minifi_rust/minifi_native/src/c_ffi/c_ffi_property.rs +++ b/minifi_rust/minifi_native/src/c_ffi/c_ffi_property.rs @@ -16,6 +16,7 @@ // under the License. use super::c_ffi_primitives::StaticStrAsMinifiCStr; +use crate::api::property::PropertyConstraints; use crate::{Property, StandardPropertyValidator}; use minifi_native_sys::{ minifi_property_definition, minifi_string_view, minifi_validator, @@ -76,11 +77,14 @@ impl Property { fn create_c_allowed_values_vec_vec(properties: &[Self]) -> Vec<Vec<minifi_string_view>> { properties .iter() - .map(|p| { - p.allowed_values + .map(|p| match p.constraints { + Some(PropertyConstraints::AllowedValues(allowed_values)) => allowed_values .iter() .map(|av| av.as_minifi_c_type()) - .collect() + .collect(), + _ => { + vec![] + } }) .collect() } @@ -88,9 +92,11 @@ impl Property { fn create_c_allowed_types_vec(properties: &[Self]) -> Vec<minifi_string_view> { properties .iter() - .map(|p| match p.allowed_type { - Some(dv) => dv.as_minifi_c_type(), - None => minifi_string_view { + .map(|p| match p.constraints { + Some(PropertyConstraints::ControllerService(allowed_type)) => { + allowed_type.as_minifi_c_type() + } + _ => minifi_string_view { data: ptr::null(), length: 0, }, @@ -125,7 +131,10 @@ impl Property { }, allowed_values_count: allowed_values.len(), allowed_values_ptr: allowed_values.as_ptr(), - validator: property.validator.as_minifi_c_type(), + validator: match &property.constraints { + Some(PropertyConstraints::Validator(s)) => s.as_minifi_c_type(), + _ => minifi_validator_MINIFI_VALIDATOR_ALWAYS_VALID, + }, allowed_type: if allowed_type.data.is_null() { std::ptr::null() } else { @@ -147,9 +156,6 @@ impl Property { impl StandardPropertyValidator { pub(crate) fn as_minifi_c_type(&self) -> minifi_validator { match self { - StandardPropertyValidator::AlwaysValidValidator => { - minifi_validator_MINIFI_VALIDATOR_ALWAYS_VALID - } StandardPropertyValidator::NonBlankValidator => { minifi_validator_MINIFI_VALIDATOR_NON_BLANK } diff --git a/minifi_rust/minifi_native/src/lib.rs b/minifi_rust/minifi_native/src/lib.rs index 2d6bf1205..17906ce4e 100644 --- a/minifi_rust/minifi_native/src/lib.rs +++ b/minifi_rust/minifi_native/src/lib.rs @@ -14,6 +14,7 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. +extern crate self as minifi_native; mod api; pub mod c_ffi; @@ -54,8 +55,9 @@ pub use api::process_session::IoState; pub use api::attribute::{GetAttribute, OutputAttribute}; pub use api::{ - FlowFile, GetId, InputStream, OnTriggerResult, OutputStream, ProcessContext, ProcessSession, - ProcessorInputRequirement, Relationship, StandardPropertyValidator, + DataSize, FlowFile, GetId, InputStream, OnTriggerResult, OutputStream, ProcessContext, + ProcessSession, ProcessorInputRequirement, PropertyConstraints, PropertyType, + ProvidesPropertyConstraint, Relationship, StandardPropertyValidator, property_constraint, }; pub use minifi_native_macros as macros; diff --git a/minifi_rust/minifi_native/src/mock/mock_controller_service_context.rs b/minifi_rust/minifi_native/src/mock/mock_controller_service_context.rs index 2a594ef8a..f67bd5008 100644 --- a/minifi_rust/minifi_native/src/mock/mock_controller_service_context.rs +++ b/minifi_rust/minifi_native/src/mock/mock_controller_service_context.rs @@ -23,7 +23,7 @@ pub struct MockControllerServiceContext { } impl GetProperty for MockControllerServiceContext { - fn get_property(&self, property: &Property) -> Result<Option<String>, MinifiError> { + fn get_raw_property(&self, property: &Property) -> Result<Option<String>, MinifiError> { self.properties.get_property(property, None) } } diff --git a/minifi_rust/minifi_native_macros/src/lib.rs b/minifi_rust/minifi_native_macros/src/lib.rs index fa8e56336..8aff6eaf7 100644 --- a/minifi_rust/minifi_native_macros/src/lib.rs +++ b/minifi_rust/minifi_native_macros/src/lib.rs @@ -31,6 +31,11 @@ pub fn derive_component_identifier(input: TokenStream) -> TokenStream { const GROUP_NAME: &'static str = env!("CARGO_PKG_NAME"); const VERSION: &'static str = env!("CARGO_PKG_VERSION"); } + + impl ::minifi_native::ProvidesPropertyConstraint for #name { + const PROPERTY_CONSTRAINT: Option<::minifi_native::PropertyConstraints> = + Some(::minifi_native::PropertyConstraints::ControllerService(<Self as ::minifi_native::ComponentIdentifier>::CLASS_NAME)); + } }; TokenStream::from(expanded) @@ -48,6 +53,36 @@ pub fn controller_service_api(_attr: TokenStream, item: TokenStream) -> TokenStr impl ::minifi_native::ControllerServiceApi for dyn #name { const INTERFACE_NAME: &'static str = concat!(module_path!(), "::", #name_str); } + + impl ::minifi_native::ProvidesPropertyConstraint for dyn #name { + const PROPERTY_CONSTRAINT: Option<::minifi_native::PropertyConstraints> = + Some(::minifi_native::PropertyConstraints::ControllerService(<Self as ::minifi_native::ControllerServiceApi>::INTERFACE_NAME)); + } + }; + + TokenStream::from(expanded) +} + +#[proc_macro_derive(PropertyType)] +pub fn derive_property_type(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = &input.ident; + + let expanded = quote! { + impl ::minifi_native::PropertyType for #name { + type Output = #name; + const EXPECTED_CONSTRAINTS: Option<::minifi_native::PropertyConstraints> = + Some(::minifi_native::PropertyConstraints::AllowedValues( + <#name as ::strum::VariantNames>::VARIANTS + )); + fn parse(s: &str) -> Result<Self::Output, ::minifi_native::MinifiError> { + s.parse::<#name>().map_err(Into::into) + } + } + + impl ::minifi_native::ProvidesPropertyConstraint for #name { + const PROPERTY_CONSTRAINT: Option<::minifi_native::PropertyConstraints> = <Self as ::minifi_native::PropertyType>::EXPECTED_CONSTRAINTS; + } }; TokenStream::from(expanded) diff --git a/minifi_rust/minifi_rs_behave/src/main.rs b/minifi_rust/minifi_rs_behave/src/main.rs index f0d8c12bc..2e8517f09 100644 --- a/minifi_rust/minifi_rs_behave/src/main.rs +++ b/minifi_rust/minifi_rs_behave/src/main.rs @@ -95,6 +95,7 @@ impl BehaveRunner { .arg("pip") .arg("install") .arg(self.minifi_behave_path.to_string_lossy().as_ref()) + .arg("certifi") .status() .expect("Failed to install dependencies") }
