diff --git a/.github/references/ubuntu_22_04_clang_arm_manifest.json b/.github/references/ubuntu_22_04_clang_arm_manifest.json index e4589f8035..fb6489c297 100644 --- a/.github/references/ubuntu_22_04_clang_arm_manifest.json +++ b/.github/references/ubuntu_22_04_clang_arm_manifest.json @@ -12206,7 +12206,7 @@ "name": "Keep Source File", "description": "If true, the file is not deleted after it has been copied to the Content Repository", "validator": "BOOLEAN_VALIDATOR", - "required": "false", + "required": "true", "sensitive": "false", "expressionLanguageScope": "NONE", "defaultValue": "false" @@ -12255,7 +12255,7 @@ "name": "Recurse Subdirectories", "description": "Indicates whether or not to pull files from subdirectories", "validator": "BOOLEAN_VALIDATOR", - "required": "false", + "required": "true", "sensitive": "false", "expressionLanguageScope": "NONE", "defaultValue": "true" @@ -12357,7 +12357,7 @@ "Attributes to Ignore": { "name": "Attributes to Ignore", "description": "A comma-separated list of Attributes to ignore. If not specified, no attributes will be ignored.", - "validator": "VALID", + "validator": "NON_BLANK_VALIDATOR", "required": "false", "sensitive": "false", "expressionLanguageScope": "NONE" @@ -12365,7 +12365,7 @@ "Attributes to Log": { "name": "Attributes to Log", "description": "A comma-separated list of Attributes to Log. If not specified, all attributes will be logged.", - "validator": "VALID", + "validator": "NON_BLANK_VALIDATOR", "required": "false", "sensitive": "false", "expressionLanguageScope": "NONE" @@ -12373,7 +12373,7 @@ "FlowFiles To Log": { "name": "FlowFiles To Log", "description": "Number of flow files to log. If set to zero all flow files will be logged. Please note that this may block other threads from running if not used judiciously.", - "validator": "VALID", + "validator": "NON_NEGATIVE_INTEGER_VALIDATOR", "required": "true", "sensitive": "false", "expressionLanguageScope": "NONE", @@ -12460,6 +12460,19 @@ }, { "propertyDescriptors": { + "Dummy Controller Service": { + "typeProvidedByValue": { + "type": "minifi_rs_playground.controller_services.dummy_controller_service.DummyControllerService", + "group": "org.apache.nifi.minifi.rust.test", + "artifact": "minifi_rs_playground" + }, + "name": "Dummy Controller Service", + "description": "Optional dummy controller service", + "validator": "VALID", + "required": "false", + "sensitive": "false", + "expressionLanguageScope": "NONE" + }, "Lorem Ipsum Controller Service": { "typeProvidedByValue": { "type": "minifi_rs_playground.controller_services.lorem_ipsum_controller_service.LoremIpsumControllerService", diff --git a/minifi_rust/extensions/minifi_rs_playground/Cargo.toml b/minifi_rust/extensions/minifi_rs_playground/Cargo.toml index 8d8682660c..ec90190e37 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/features/controller_apis.feature b/minifi_rust/extensions/minifi_rs_playground/features/controller_apis.feature index c93f896160..8bbc2dd7e3 100644 --- a/minifi_rust/extensions/minifi_rs_playground/features/controller_apis.feature +++ b/minifi_rust/extensions/minifi_rs_playground/features/controller_apis.feature @@ -23,6 +23,6 @@ Feature: Testing controller service api casting And the "Extra information" property of the Wolfie the magical controller service is set to "The dog (Canis familiaris or Canis lupus familiaris) is a domesticated descendant of wolves." When the MiNiFi instance starts up - Then the Minifi logs contain the following message: "[minifi_rs_playground::processors::zoo_processor::ZooProcessorRs] [critical] Can DogControllerRs { has_jetpack: true, extra_info: "The dog (Canis familiaris or Canis lupus familiaris) is a domesticated descendant of wolves." } fly? true" in less than 10 seconds + Then the Minifi logs contain the following message: "[minifi_rs_playground::processors::zoo_processor::ZooProcessorRs] [critical] Can DogControllerRs { has_jetpack: true, extra_info: Some("The dog (Canis familiaris or Canis lupus familiaris) is a domesticated descendant of wolves.") } fly?" in less than 10 seconds And the Minifi logs do not contain errors And the Minifi logs do not contain warnings diff --git a/minifi_rust/extensions/minifi_rs_playground/minifi_rs_playground.md b/minifi_rust/extensions/minifi_rs_playground/minifi_rs_playground.md index 4078b7301a..e05adf0e0e 100644 --- a/minifi_rust/extensions/minifi_rs_playground/minifi_rs_playground.md +++ b/minifi_rust/extensions/minifi_rs_playground/minifi_rs_playground.md @@ -130,24 +130,24 @@ RUST TEST PROCESSOR: Creates FlowFiles from files in a directory. MiNiFi will ig In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. The table also indicates any default values, and whether a property supports the NiFi Expression Language. -| Name | Default Value | Allowable Values | Description | -|-------------------------|---------------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **Batch Size** | 10 | | The maximum number of files to pull in each iteration | -| **Ignore Hidden Files** | true | true
false | Indicates whether or not hidden files should be ignored | -| **Input Directory** | | | The input directory from which to pull files
**Supports Expression Language: true** | -| Keep Source File | false | true
false | If true, the file is not deleted after it has been copied to the Content Repository | -| Maximum File Age | | | The maximum age that a file must be in order to be pulled; any file older than this amount of time (according to last modification date) will be ignored | -| Maximum File Size | | | The maximum size that a file can be in order to be pulled | -| Minimum File Age | | | The minimum age that a file must be in order to be pulled; any file younger than this amount of time (according to last modification date) will be ignored | -| Minimum File Size | | | The minimum size that a file can be in order to be pulled | -| Polling Interval | | | Indicates how long to wait before performing a directory listing | -| Recurse Subdirectories | true | true
false | Indicates whether or not to pull files from subdirectories | +| Name | Default Value | Allowable Values | Description | +|----------------------------|---------------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Batch Size** | 10 | | The maximum number of files to pull in each iteration | +| **Ignore Hidden Files** | true | true
false | Indicates whether or not hidden files should be ignored | +| **Input Directory** | | | The input directory from which to pull files
**Supports Expression Language: true** | +| **Keep Source File** | false | true
false | If true, the file is not deleted after it has been copied to the Content Repository | +| Maximum File Age | | | The maximum age that a file must be in order to be pulled; any file older than this amount of time (according to last modification date) will be ignored | +| Maximum File Size | | | The maximum size that a file can be in order to be pulled | +| Minimum File Age | | | The minimum age that a file must be in order to be pulled; any file younger than this amount of time (according to last modification date) will be ignored | +| Minimum File Size | | | The minimum size that a file can be in order to be pulled | +| Polling Interval | | | Indicates how long to wait before performing a directory listing | +| **Recurse Subdirectories** | true | true
false | Indicates whether or not to pull files from subdirectories | ### Relationships -| Name | Description | -|---------|----------------------------------------------| -| success | FlowFiles are transferred here after logging | +| Name | Description | +|---------|--------------------------------------------| +| success | The created FlowFiles are transferred here | ### Output Attributes 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 df25212130..d3307a3b54 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,42 +18,26 @@ use crate::controller_services::animal_controller_apis::{ CanFlyControllerApi, NumberOfLegsControllerApi, }; -use minifi_native::ControllerServiceApi; use minifi_native::macros::ComponentIdentifier; use minifi_native::{ - ControllerServiceDefinition, EnableControllerService, GetProperty, Logger, MinifiError, - Property, ProvidedInterface, StandardPropertyValidator, create_provided_interface, + ControllerServiceApi, ControllerServiceDefinition, EnableControllerService, GetProperty, + Logger, MinifiError, Property, PropertyDefinition, ProvidedInterface, + create_provided_interface, property_definitions, }; -pub(crate) const HAS_JETPACK: Property = Property { - name: "Has Jetpack", - description: "Whether or not the dog has a jetpack", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some("false"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const HAS_JETPACK: Property = + Property::new("Has Jetpack", "Whether or not the dog has a jetpack").with_default("false"); -pub(crate) const EXTRA_INFO: Property = Property { - name: "Extra information", - description: "We need this to verify the casting was done correctly", - is_required: false, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const EXTRA_INFO: Property> = Property::new( + "Extra information", + "We need this to verify the casting was done correctly", +); #[allow(dead_code)] // extra_info is only used by {:?} #[derive(Debug, ComponentIdentifier)] pub(crate) struct DogControllerRs { has_jetpack: bool, - extra_info: String, + extra_info: Option, } impl NumberOfLegsControllerApi for DogControllerRs { @@ -73,11 +57,8 @@ 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_property(&HAS_JETPACK)?; + let extra_info = context.get_property(&EXTRA_INFO)?; Ok(Self { has_jetpack, @@ -88,7 +69,8 @@ impl EnableControllerService for DogControllerRs { impl ControllerServiceDefinition for DogControllerRs { const DESCRIPTION: &'static str = "RUST TEST CONTROLLER SERVICE: DogControllerRs"; - const PROPERTIES: &'static [Property] = &[HAS_JETPACK, EXTRA_INFO]; + const PROPERTIES: &'static [PropertyDefinition] = + property_definitions![HAS_JETPACK, EXTRA_INFO]; const PROVIDED_APIS: &'static [ProvidedInterface] = &[ create_provided_interface!(dyn CanFlyControllerApi), create_provided_interface!(dyn NumberOfLegsControllerApi), diff --git a/minifi_rust/extensions/minifi_rs_playground/src/controller_services/duck_controller_service.rs b/minifi_rust/extensions/minifi_rs_playground/src/controller_services/duck_controller_service.rs index d1d2518a11..5072e4314f 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/controller_services/duck_controller_service.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/controller_services/duck_controller_service.rs @@ -22,7 +22,7 @@ use minifi_native::ControllerServiceApi; use minifi_native::macros::ComponentIdentifier; use minifi_native::{ ControllerServiceDefinition, EnableControllerService, GetProperty, Logger, MinifiError, - Property, ProvidedInterface, create_provided_interface, + PropertyDefinition, ProvidedInterface, create_provided_interface, }; #[derive(Debug, ComponentIdentifier)] @@ -51,7 +51,7 @@ impl EnableControllerService for DuckControllerRs { impl ControllerServiceDefinition for DuckControllerRs { const DESCRIPTION: &'static str = "RUST TEST CONTROLLER SERVICE: DuckControllerRs"; - const PROPERTIES: &'static [Property] = &[]; + const PROPERTIES: &'static [PropertyDefinition] = &[]; const PROVIDED_APIS: &'static [ProvidedInterface] = &[ create_provided_interface!(dyn CanFlyControllerApi), create_provided_interface!(dyn NumberOfLegsControllerApi), diff --git a/minifi_rust/extensions/minifi_rs_playground/src/controller_services/dummy_controller_service.rs b/minifi_rust/extensions/minifi_rs_playground/src/controller_services/dummy_controller_service.rs index 2b593b9aff..ae38805c43 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/controller_services/dummy_controller_service.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/controller_services/dummy_controller_service.rs @@ -18,7 +18,7 @@ use minifi_native::macros::ComponentIdentifier; use minifi_native::{ ControllerServiceDefinition, EnableControllerService, GetProperty, Logger, MinifiError, - Property, ProvidedInterface, + PropertyDefinition, ProvidedInterface, }; #[derive(Debug, ComponentIdentifier)] @@ -35,6 +35,6 @@ impl EnableControllerService for DummyControllerService { impl ControllerServiceDefinition for DummyControllerService { const DESCRIPTION: &'static str = "RUST TEST CONTROLLER SERVICE: Dummy Controller Service"; - const PROPERTIES: &'static [Property] = &[]; + const PROPERTIES: &'static [PropertyDefinition] = &[]; const PROVIDED_APIS: &'static [ProvidedInterface] = &[]; } 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 25341971ea..7dbb2309de 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,20 +19,11 @@ use lipsum::lipsum; use minifi_native::macros::ComponentIdentifier; use minifi_native::{ ControllerServiceDefinition, EnableControllerService, GetProperty, Logger, MinifiError, - Property, ProvidedInterface, StandardPropertyValidator, + Property, PropertyDefinition, ProvidedInterface, property_definitions, }; -const LENGTH: Property = Property { - name: "Length", - description: "How many words to generate", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some("25"), - validator: StandardPropertyValidator::U64Validator, - allowed_values: &[], - allowed_type: None, -}; +const LENGTH: Property = + Property::new("Length", "How many words to generate").with_default("25"); #[derive(Debug, ComponentIdentifier)] pub(crate) struct LoremIpsumControllerService { @@ -44,17 +35,13 @@ 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_property(&LENGTH)?); Ok(Self { data }) } } impl ControllerServiceDefinition for LoremIpsumControllerService { const DESCRIPTION: &'static str = "RUST TEST CONTROLLER SERVICE: Holds generated lorem ipsum"; - const PROPERTIES: &'static [Property] = &[LENGTH]; + const PROPERTIES: &'static [PropertyDefinition] = property_definitions![LENGTH]; const PROVIDED_APIS: &'static [ProvidedInterface] = &[]; } diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/asciify_german/processor_definition.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/asciify_german/processor_definition.rs index eb3f359d83..2a64c8a3ea 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/asciify_german/processor_definition.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/asciify_german/processor_definition.rs @@ -17,7 +17,8 @@ use crate::processors::asciify_german::AsciifyGerman; use minifi_native::{ - OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, PropertyDefinition, + Relationship, }; impl ProcessorDefinition for AsciifyGerman { @@ -28,5 +29,5 @@ impl ProcessorDefinition for AsciifyGerman { const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[]; const RELATIONSHIPS: &'static [Relationship] = &[super::relationships::SUCCESS, super::relationships::FAILURE]; - const PROPERTIES: &'static [Property] = &[]; + const PROPERTIES: &'static [PropertyDefinition] = &[]; } diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/count_actual_logging.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/count_actual_logging.rs index 95b25910cc..518751d62a 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/count_actual_logging.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/count_actual_logging.rs @@ -20,8 +20,8 @@ use minifi_native::macros::ComponentIdentifier; use minifi_native::{ GetProperty, Logger, MinifiError, MutTrigger, OnTriggerResult, OutputAttribute, ProcessContext, - ProcessSession, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, - Schedule, debug, info, trace, + ProcessSession, ProcessorDefinition, ProcessorInputRequirement, PropertyDefinition, + Relationship, Schedule, debug, info, trace, }; #[derive(Debug, ComponentIdentifier)] @@ -72,5 +72,5 @@ impl ProcessorDefinition for CountActualLogging { const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[]; const RELATIONSHIPS: &'static [Relationship] = &[]; - const PROPERTIES: &'static [Property] = &[]; + const PROPERTIES: &'static [PropertyDefinition] = &[]; } diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/duplicate_text.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/duplicate_text.rs index 8d388cb204..ee4ae055e0 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/duplicate_text.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/duplicate_text.rs @@ -19,7 +19,7 @@ use minifi_native::macros::ComponentIdentifier; use minifi_native::{ GetAttribute, GetControllerService, GetProperty, InputStream, Logger, MinifiError, MutFlowFileStreamTransform, OutputAttribute, OutputStream, ProcessorDefinition, - ProcessorInputRequirement, Property, Relationship, Schedule, TransformStreamResult, + ProcessorInputRequirement, PropertyDefinition, Relationship, Schedule, TransformStreamResult, }; use std::collections::HashMap; @@ -67,5 +67,5 @@ impl ProcessorDefinition for DuplicateStreamText { const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[]; const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS]; - const PROPERTIES: &'static [Property] = &[]; + const PROPERTIES: &'static [PropertyDefinition] = &[]; } 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 55bb376da8..959ef78209 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 @@ -17,7 +17,7 @@ // This is the (not production ready) reimplementation of the already existing standard GenerateFlowFile processor -use minifi_native::macros::ComponentIdentifier; +use minifi_native::macros::{ComponentIdentifier, PropertyType}; use minifi_native::{ GetProperty, Logger, MinifiError, OnTriggerResult, ProcessContext, ProcessSession, Schedule, Trigger, @@ -25,10 +25,20 @@ use minifi_native::{ use rand::RngExt; use rand::distr::Alphanumeric; use std::cmp::PartialEq; +use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames}; mod properties; mod relationships; +#[derive( + Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, IntoStaticStr, PropertyType, +)] +#[strum(const_into_str)] +pub(crate) enum DataFormat { + Text, + Binary, +} + #[derive(Debug, PartialEq)] enum Mode { UniqueBytes, @@ -52,22 +62,12 @@ 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 is_unique = context.get_property(&properties::UNIQUE_FLOW_FILES)?; + let is_text = context.get_property(&properties::DATA_FORMAT)? == DataFormat::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 file_size = context.get_property(&properties::FILE_SIZE)?; + let batch_size = context.get_property(&properties::BATCH_SIZE)?; let mode = Self::get_mode(is_unique, is_text, has_custom_text, file_size); let data_generated_during_on_schedule = @@ -162,7 +162,7 @@ impl Trigger for GenerateFlowFileRs { // flow files. custom_text_for_batch = Some( context - .get_property(&properties::CUSTOM_TEXT, None)? + .get_raw_property(&properties::CUSTOM_TEXT, None)? .ok_or_else(|| { MinifiError::trigger_err( "GenerateFlowFile is in CustomText mode but the \"Custom Text\" \ diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/processor_definition.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/processor_definition.rs index 1f75416e4d..d3f9a93910 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/processor_definition.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/processor_definition.rs @@ -18,7 +18,8 @@ use super::properties::*; use super::{GenerateFlowFileRs, relationships}; use minifi_native::{ - OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, PropertyDefinition, + Relationship, property_definitions, }; impl ProcessorDefinition for GenerateFlowFileRs { @@ -28,11 +29,11 @@ impl ProcessorDefinition for GenerateFlowFileRs { const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[]; const RELATIONSHIPS: &'static [Relationship] = &[relationships::SUCCESS]; - const PROPERTIES: &'static [Property] = &[ + const PROPERTIES: &'static [PropertyDefinition] = property_definitions![ FILE_SIZE, BATCH_SIZE, DATA_FORMAT, UNIQUE_FLOW_FILES, - CUSTOM_TEXT, + CUSTOM_TEXT ]; } 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 16d0b8a718..d6f62767f7 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,64 +15,34 @@ // specific language governing permissions and limitations // under the License. -use minifi_native::{Property, StandardPropertyValidator}; +use super::DataFormat; +use minifi_native::{DataSize, Property}; -pub(crate) const FILE_SIZE: Property = Property { - name: "File Size", - description: "The size of the file that will be used", - is_required: true, - is_sensitive: false, - supports_expr_lang: true, - default_value: Some("1 kB"), - validator: StandardPropertyValidator::DataSizeValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const FILE_SIZE: Property = + Property::new("File Size", "The size of the file that will be used") + .supports_expression_language() + .with_default("1 kB"); -pub(crate) const BATCH_SIZE: Property = Property { - name: "Batch Size", - description: "The number of FlowFiles to be transferred in each invocation", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some("1"), - validator: StandardPropertyValidator::U64Validator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const BATCH_SIZE: Property = Property::new( + "Batch Size", + "The number of FlowFiles to be transferred in each invocation", +) +.with_default("1"); -pub(crate) const DATA_FORMAT: Property = Property { - name: "Data Format", - description: "Specifies whether the data should be Text or Binary", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some("Binary"), - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &["Text", "Binary"], - allowed_type: None, -}; +pub(crate) const DATA_FORMAT: Property = Property::new( + "Data Format", + "Specifies whether the data should be Text or Binary", +) +.with_default(DataFormat::Binary.into_str()); -pub(crate) const UNIQUE_FLOW_FILES: Property = Property { - name: "Unique FlowFiles", - description: "If true, each FlowFile that is generated will be unique. If false, a random value will be generated and all FlowFiles will get the same content but this offers much higher throughput (but see the description of Custom Text for special non-random use cases)", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some("true"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const UNIQUE_FLOW_FILES: Property = Property::new( + "Unique FlowFiles", + "If true, each FlowFile that is generated will be unique. If false, a random value will be generated and all FlowFiles will get the same content but this offers much higher throughput (but see the description of Custom Text for special non-random use cases)", +) +.with_default("true"); -pub(crate) const CUSTOM_TEXT: Property = Property { - name: "Custom Text", - description: "If Data Format is text and if Unique FlowFiles is false, then this custom text will be used as content of the generated FlowFiles and the File Size will be ignored. Finally, if Expression Language is used, evaluation will be performed only once per batch of generated FlowFiles", - is_required: false, - is_sensitive: false, - supports_expr_lang: true, - default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const CUSTOM_TEXT: Property> = Property::new( + "Custom Text", + "If Data Format is text and if Unique FlowFiles is false, then this custom text will be used as content of the generated FlowFiles and the File Size will be ignored. Finally, if Expression Language is used, evaluation will be performed only once per batch of generated FlowFiles", +) +.supports_expression_language(); diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/tests.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/tests.rs index 39b2facd47..710ed98dc3 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/tests.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/tests.rs @@ -32,13 +32,13 @@ fn generate_flow_file_empty_test() { let mut context = MockProcessContext::new(); context .properties - .insert(properties::FILE_SIZE.name.to_string(), "0".to_string()); + .insert(properties::FILE_SIZE.name().to_string(), "0".to_string()); context .properties - .insert(UNIQUE_FLOW_FILES.name.to_string(), "false".to_string()); + .insert(UNIQUE_FLOW_FILES.name().to_string(), "false".to_string()); context .properties - .insert(DATA_FORMAT.name.to_string(), "Text".to_string()); + .insert(DATA_FORMAT.name().to_string(), "Text".to_string()); let processor = GenerateFlowFileRs::schedule(&context, &logger).unwrap(); let mut session = MockProcessSession::new(); @@ -58,16 +58,16 @@ fn generate_custom_text() { let mut context = MockProcessContext::new(); context .properties - .insert(properties::FILE_SIZE.name.to_string(), "0".to_string()); + .insert(properties::FILE_SIZE.name().to_string(), "0".to_string()); context .properties - .insert(UNIQUE_FLOW_FILES.name.to_string(), "false".to_string()); + .insert(UNIQUE_FLOW_FILES.name().to_string(), "false".to_string()); context .properties - .insert(DATA_FORMAT.name.to_string(), "Text".to_string()); + .insert(DATA_FORMAT.name().to_string(), "Text".to_string()); context .properties - .insert(CUSTOM_TEXT.name.to_string(), "foo bar baz".to_string()); + .insert(CUSTOM_TEXT.name().to_string(), "foo bar baz".to_string()); let logger = MockLogger::new(); let processor = GenerateFlowFileRs::schedule(&context, &logger).unwrap(); @@ -89,16 +89,16 @@ fn random_bytes_unique() { let mut context = MockProcessContext::new(); context .properties - .insert(properties::FILE_SIZE.name.to_string(), "40 B".to_string()); + .insert(properties::FILE_SIZE.name().to_string(), "40 B".to_string()); context .properties - .insert(UNIQUE_FLOW_FILES.name.to_string(), "true".to_string()); + .insert(UNIQUE_FLOW_FILES.name().to_string(), "true".to_string()); context .properties - .insert(DATA_FORMAT.name.to_string(), "Bytes".to_string()); + .insert(DATA_FORMAT.name().to_string(), "Binary".to_string()); context .properties - .insert(BATCH_SIZE.name.to_string(), "2".to_string()); + .insert(BATCH_SIZE.name().to_string(), "2".to_string()); let logger = MockLogger::new(); let processor = GenerateFlowFileRs::schedule(&context, &logger).unwrap(); @@ -124,16 +124,16 @@ fn random_bytes_non_unique() { let mut context = MockProcessContext::new(); context .properties - .insert(properties::FILE_SIZE.name.to_string(), "40 B".to_string()); + .insert(properties::FILE_SIZE.name().to_string(), "40 B".to_string()); context .properties - .insert(UNIQUE_FLOW_FILES.name.to_string(), "false".to_string()); + .insert(UNIQUE_FLOW_FILES.name().to_string(), "false".to_string()); context .properties - .insert(DATA_FORMAT.name.to_string(), "Bytes".to_string()); + .insert(DATA_FORMAT.name().to_string(), "Binary".to_string()); context .properties - .insert(BATCH_SIZE.name.to_string(), "2".to_string()); + .insert(BATCH_SIZE.name().to_string(), "2".to_string()); let logger = MockLogger::new(); let processor = GenerateFlowFileRs::schedule(&context, &logger).unwrap(); 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 1847ea632e..ab2f0c037e 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 @@ -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_property(&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_property(&RECURSE)?; + + let keep_source_file = context.get_property(&KEEP_SOURCE_FILE)?; + + let poll_interval = context.get_property(&properties::POLLING_INTERVAL)?; + let min_size = context.get_property(&MIN_SIZE)?; + let max_size = context.get_property(&MAX_SIZE)?; + let min_age = context.get_property(&MIN_AGE)?; + let max_age = context.get_property(&MAX_AGE)?; + let batch_size = context.get_property(&BATCH_SIZE)?; + let ignore_hidden_files = context.get_property(&IGNORE_HIDDEN_FILES)?; Ok(GetFileRs { recursive, diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/processor_definition.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/processor_definition.rs index 88e1a4b9a7..59171d3d0c 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/processor_definition.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/processor_definition.rs @@ -21,7 +21,8 @@ use crate::processors::get_file::output_attributes::{ use crate::processors::get_file::properties::*; use crate::processors::get_file::{GetFileRs, relationships}; use minifi_native::{ - OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, PropertyDefinition, + Relationship, property_definitions, }; impl ProcessorDefinition for GetFileRs { @@ -32,7 +33,7 @@ impl ProcessorDefinition for GetFileRs { const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[ABSOLUTE_PATH_OUTPUT_ATTRIBUTE, FILENAME_OUTPUT_ATTRIBUTE]; const RELATIONSHIPS: &'static [Relationship] = &[relationships::SUCCESS]; - const PROPERTIES: &'static [Property] = &[ + const PROPERTIES: &'static [PropertyDefinition] = property_definitions![ DIRECTORY, POLLING_INTERVAL, RECURSE, 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 b43009b28e..39f6f60b61 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,124 +15,60 @@ // specific language governing permissions and limitations // under the License. -use minifi_native::{Property, StandardPropertyValidator}; +use minifi_native::{DataSize, NonBlankPath, Property}; +use std::time::Duration; -pub(crate) const DIRECTORY: Property = Property { - name: "Input Directory", - description: "The input directory from which to pull files", - is_required: true, - is_sensitive: false, - supports_expr_lang: true, - default_value: None, - validator: StandardPropertyValidator::NonBlankValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const DIRECTORY: Property = Property::new( + "Input Directory", + "The input directory from which to pull files", +) +.supports_expression_language(); -pub(crate) const RECURSE: Property = Property { - name: "Recurse Subdirectories", - description: "Indicates whether or not to pull files from subdirectories", - is_required: false, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some("true"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const RECURSE: Property = Property::new( + "Recurse Subdirectories", + "Indicates whether or not to pull files from subdirectories", +) +.with_default("true"); -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_sensitive: false, - supports_expr_lang: false, - default_value: Some("false"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const KEEP_SOURCE_FILE: Property = Property::new( + "Keep Source File", + "If true, the file is not deleted after it has been copied to the Content Repository", +) +.with_default("false"); -pub(crate) const MIN_AGE: Property = Property { - name: "Minimum File Age", - description: "The minimum age that a file must be in order to be pulled; any file younger than this amount of time (according to last modification date) will be ignored", - is_required: false, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::TimePeriodValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const MIN_AGE: Property> = Property::new( + "Minimum File Age", + "The minimum age that a file must be in order to be pulled; any file younger than this amount of time (according to last modification date) will be ignored", +); -pub(crate) const MAX_AGE: Property = Property { - name: "Maximum File Age", - description: "The maximum age that a file must be in order to be pulled; any file older than this amount of time (according to last modification date) will be ignored", - is_required: false, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::TimePeriodValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const MAX_AGE: Property> = Property::new( + "Maximum File Age", + "The maximum age that a file must be in order to be pulled; any file older than this amount of time (according to last modification date) will be ignored", +); -pub(crate) const MIN_SIZE: Property = Property { - name: "Minimum File Size", - description: "The minimum size that a file can be in order to be pulled", - is_required: false, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::DataSizeValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const MIN_SIZE: Property> = Property::new( + "Minimum File Size", + "The minimum size that a file can be in order to be pulled", +); -pub(crate) const MAX_SIZE: Property = Property { - name: "Maximum File Size", - description: "The maximum size that a file can be in order to be pulled", - is_required: false, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::DataSizeValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const MAX_SIZE: Property> = Property::new( + "Maximum File Size", + "The maximum size that a file can be in order to be pulled", +); -pub(crate) const IGNORE_HIDDEN_FILES: Property = Property { - name: "Ignore Hidden Files", - description: "Indicates whether or not hidden files should be ignored", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some("true"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const IGNORE_HIDDEN_FILES: Property = Property::new( + "Ignore Hidden Files", + "Indicates whether or not hidden files should be ignored", +) +.with_default("true"); -pub(crate) const POLLING_INTERVAL: Property = Property { - name: "Polling Interval", - description: "Indicates how long to wait before performing a directory listing", - is_required: false, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::TimePeriodValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const POLLING_INTERVAL: Property> = Property::new( + "Polling Interval", + "Indicates how long to wait before performing a directory listing", +); -pub(crate) const BATCH_SIZE: Property = Property { - name: "Batch Size", - description: "The maximum number of files to pull in each iteration", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some("10"), - validator: StandardPropertyValidator::U64Validator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const BATCH_SIZE: Property = Property::new( + "Batch Size", + "The maximum number of files to pull in each iteration", +) +.with_default("10"); diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/tests.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/tests.rs index 34449bd419..50691c773f 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/tests.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/tests.rs @@ -113,12 +113,12 @@ fn test_complex_dir_with_filter( let mut context = MockProcessContext::new(); context.properties.insert( - DIRECTORY.name.to_string(), + DIRECTORY.name().to_string(), test_directory.path().to_str().unwrap().to_string(), ); context .properties - .insert(BATCH_SIZE.name.to_string(), "10".to_string()); + .insert(BATCH_SIZE.name().to_string(), "10".to_string()); context .properties @@ -154,10 +154,10 @@ fn test_complex_dir_with_filter( #[test] fn complex_dir_with_filters() { - test_complex_dir_with_filter(MIN_AGE.name, "5 min", "old"); - test_complex_dir_with_filter(MAX_AGE.name, "5 min", "new"); - test_complex_dir_with_filter(MIN_SIZE.name, "50 B", "large"); - test_complex_dir_with_filter(MAX_SIZE.name, "50 B", "small"); + test_complex_dir_with_filter(MIN_AGE.name(), "5 min", "old"); + test_complex_dir_with_filter(MAX_AGE.name(), "5 min", "new"); + test_complex_dir_with_filter(MIN_SIZE.name(), "50 B", "large"); + test_complex_dir_with_filter(MAX_SIZE.name(), "50 B", "small"); } #[test] @@ -169,16 +169,16 @@ fn test_hidden_files_and_batch_size() { let mut context = MockProcessContext::new(); context.properties.insert( - DIRECTORY.name.to_string(), + DIRECTORY.name().to_string(), temp_dir.path().to_str().unwrap().to_string(), ); context .properties - .insert(BATCH_SIZE.name.to_string(), "2".to_string()); + .insert(BATCH_SIZE.name().to_string(), "2".to_string()); context .properties - .insert(IGNORE_HIDDEN_FILES.name.to_string(), "false".to_string()); + .insert(IGNORE_HIDDEN_FILES.name().to_string(), "false".to_string()); let mut session = MockProcessSession::new(); let get_file = GetFileRs::schedule(&context, &MockLogger::new()).unwrap(); 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 6686c0b400..f6f843576b 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 @@ -20,16 +20,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, UNREGISTERED_CONTROLLER_SERVICE, +}; +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 +52,9 @@ impl Schedule for KamikazeProcessorRs { where Self: Sized, { - let trigger_behaviour = context - .get_property(&properties::TRIGGER_BEHAVIOUR)? - .expect("required property") - .parse::()?; + let trigger_behaviour = context.get_property(&TRIGGER_BEHAVIOUR)?; - let schedule_behaviour = context - .get_property(&properties::SCHEDULE_BEHAVIOUR)? - .expect("required property") - .parse::()?; + let schedule_behaviour = context.get_property(&SCHEDULE_BEHAVIOUR)?; match schedule_behaviour { KamikazeBehaviour::ReturnErr => Err(MinifiError::schedule_err( @@ -99,13 +96,11 @@ impl Trigger for KamikazeProcessorRs { panic!("KamikazeProcessor::trigger panic") } KamikazeBehaviour::GetNotRegisteredProperty => { - let _ = context.get_property(&NOT_REGISTERED_PROPERTY, None)?; + let _ = context.get_raw_property(&NOT_REGISTERED_PROPERTY, None)?; Ok(OnTriggerResult::Ok) } KamikazeBehaviour::GetInvalidControllerService => { - let _ = context.get_controller_service::( - &NOT_REGISTERED_PROPERTY, - )?; + let _ = context.get_controller_service(&UNREGISTERED_CONTROLLER_SERVICE)?; Ok(OnTriggerResult::Ok) } } diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/processor_definition.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/processor_definition.rs index 0d50359fc9..51c8ff0c8c 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/processor_definition.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/processor_definition.rs @@ -17,7 +17,8 @@ use super::*; use minifi_native::{ - OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, PropertyDefinition, + Relationship, property_definitions, }; impl ProcessorDefinition for KamikazeProcessorRs { @@ -27,8 +28,8 @@ impl ProcessorDefinition for KamikazeProcessorRs { const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[]; const RELATIONSHIPS: &'static [Relationship] = &[relationships::SUCCESS]; - const PROPERTIES: &'static [Property] = &[ + const PROPERTIES: &'static [PropertyDefinition] = property_definitions![ properties::SCHEDULE_BEHAVIOUR, - properties::TRIGGER_BEHAVIOUR, + properties::TRIGGER_BEHAVIOUR ]; } 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 6ee86b1bda..72f9145369 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 @@ -15,42 +15,27 @@ // specific language governing permissions and limitations // under the License. +use crate::controller_services::lorem_ipsum_controller_service::LoremIpsumControllerService; use crate::processors::kamikaze_processor::KamikazeBehaviour; -use minifi_native::{Property, StandardPropertyValidator}; -use strum::VariantNames; +use minifi_native::Property; -pub(crate) const SCHEDULE_BEHAVIOUR: Property = Property { - name: "Schedule Behaviour", - description: "What to do during the on_schedule method", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some(KamikazeBehaviour::ReturnOk.into_str()), - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: KamikazeBehaviour::VARIANTS, - allowed_type: None, -}; +pub(crate) const SCHEDULE_BEHAVIOUR: Property = Property::new( + "Schedule Behaviour", + "What to do during the on_schedule method", +) +.with_default(KamikazeBehaviour::ReturnOk.into_str()); -pub(crate) const TRIGGER_BEHAVIOUR: Property = Property { - name: "Trigger Behaviour", - description: "What to do during the trigger method", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some(KamikazeBehaviour::ReturnOk.into_str()), - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: KamikazeBehaviour::VARIANTS, - allowed_type: None, -}; +pub(crate) const TRIGGER_BEHAVIOUR: Property = + Property::new("Trigger Behaviour", "What to do during the trigger method") + .with_default(KamikazeBehaviour::ReturnOk.into_str()); -pub(crate) const NOT_REGISTERED_PROPERTY: Property = Property { - name: "Kamikaze Processor Property", - description: "Property purposely left out of Processor description", - is_required: false, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const NOT_REGISTERED_PROPERTY: Property> = Property::new( + "Kamikaze Processor Property", + "Property purposely left out of Processor description", +); + +pub(crate) const UNREGISTERED_CONTROLLER_SERVICE: Property = + Property::new( + "Kamikaze Processor Property", + "Property purposely left out of Processor description", + ); diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/tests.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/tests.rs index f5560a25a8..dd6e4794f9 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/tests.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/tests.rs @@ -31,9 +31,10 @@ fn on_schedule_ok() { #[test] fn on_schedule_err() { let mut context = MockProcessContext::new(); - context - .properties - .insert(SCHEDULE_BEHAVIOUR.name.to_string(), "ReturnErr".to_string()); + context.properties.insert( + SCHEDULE_BEHAVIOUR.name().to_string(), + "ReturnErr".to_string(), + ); let processor = KamikazeProcessorRs::schedule(&context, &MockLogger::new()); assert!(matches!(processor, Err(ScheduleError(_)))); } @@ -43,7 +44,7 @@ fn on_schedule_panic() { let mut context = MockProcessContext::new(); context .properties - .insert(SCHEDULE_BEHAVIOUR.name.to_string(), "Panic".to_string()); + .insert(SCHEDULE_BEHAVIOUR.name().to_string(), "Panic".to_string()); let result = std::panic::catch_unwind(AssertUnwindSafe(|| { KamikazeProcessorRs::schedule(&context, &MockLogger::new()) @@ -68,9 +69,10 @@ fn on_trigger_ok() { #[test] fn on_trigger_err() { let mut context = MockProcessContext::new(); - context - .properties - .insert(TRIGGER_BEHAVIOUR.name.to_string(), "ReturnErr".to_string()); + context.properties.insert( + TRIGGER_BEHAVIOUR.name().to_string(), + "ReturnErr".to_string(), + ); let processor = KamikazeProcessorRs::schedule(&context, &MockLogger::new()).unwrap(); let mut session = MockProcessSession::new(); @@ -85,7 +87,7 @@ fn on_trigger_panic() { let mut context = MockProcessContext::new(); context .properties - .insert(TRIGGER_BEHAVIOUR.name.to_string(), "Panic".to_string()); + .insert(TRIGGER_BEHAVIOUR.name().to_string(), "Panic".to_string()); let processor = KamikazeProcessorRs::schedule(&context, &MockLogger::new()).unwrap(); let mut session = MockProcessSession::new(); 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 3e59e2d522..48de813600 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 @@ -18,15 +18,32 @@ // This is the (not production ready) reimplementation of the already existing standard LogAttribute processor use crate::processors::log_attribute::properties::{FLOW_FILES_TO_LOG, LOG_LEVEL, LOG_PAYLOAD}; +use minifi_native::StandardPropertyValidator::NonBlankValidator; use minifi_native::macros::ComponentIdentifier; use minifi_native::{ GetProperty, LogLevel, Logger, MinifiError, OnTriggerResult, ProcessContext, ProcessSession, - Property, Schedule, Trigger, debug, log, trace, + PropertyConstraints, PropertySchema, PropertyType, Schedule, Trigger, debug, log, trace, }; mod properties; mod relationships; +struct AttributeList {} + +impl PropertyType for AttributeList { + type Output = Vec; + fn parse(s: &str) -> Result { + Ok(s.split(',') + .map(|s| s.trim().to_string()) + .collect::>()) + } +} +impl PropertySchema for AttributeList { + const CONSTRAINT: Option = + Some(PropertyConstraints::Validator(NonBlankValidator)); + const IS_REQUIRED: bool = true; +} + #[derive(Debug, ComponentIdentifier)] pub(crate) struct LogAttributeRs { log_level: LogLevel, @@ -121,44 +138,20 @@ impl Trigger for LogAttributeRs { impl Schedule for LogAttributeRs { fn schedule(context: &P, _logger: &L) -> Result { - let log_level = context - .get_property(&LOG_LEVEL)? - .expect("required property") - .parse::()?; - - 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::()?; - - fn get_csv_property( - context: &P, - property: &Property, - ) -> Result>, MinifiError> { - Ok(context.get_property(property)?.map(|s| { - s.split(',') - .map(|s| s.trim().to_string()) - .collect::>() - })) - } - - let attributes_to_log = get_csv_property(context, &properties::ATTRIBUTES_TO_LOG)?; - let attributes_to_ignore = get_csv_property(context, &properties::ATTRIBUTES_TO_IGNORE)?; + let log_level = context.get_property(&LOG_LEVEL)?; + let log_payload = context.get_property(&LOG_PAYLOAD)?; + let flow_files_to_log = context.get_property(&FLOW_FILES_TO_LOG)?; + let attributes_to_log = context.get_property(&properties::ATTRIBUTES_TO_LOG)?; + let attributes_to_ignore = context.get_property(&properties::ATTRIBUTES_TO_IGNORE)?; let dash_line = format!( "{:-^50}", context .get_property(&properties::LOG_PREFIX)? - .unwrap_or(String::new()) + .unwrap_or_default() ); - let hex_encode_payload = context - .get_bool_property(&properties::HEX_ENCODE_PAYLOAD)? - .expect("required property"); + let hex_encode_payload = context.get_property(&properties::HEX_ENCODE_PAYLOAD)?; Ok(LogAttributeRs { log_level, diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute/processor_definition.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute/processor_definition.rs index e916d456ac..b5732645da 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute/processor_definition.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute/processor_definition.rs @@ -18,7 +18,8 @@ use crate::processors::log_attribute::properties::*; use crate::processors::log_attribute::{LogAttributeRs, relationships}; use minifi_native::{ - OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, PropertyDefinition, + Relationship, property_definitions, }; impl ProcessorDefinition for LogAttributeRs { @@ -29,7 +30,7 @@ impl ProcessorDefinition for LogAttributeRs { const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[]; const RELATIONSHIPS: &'static [Relationship] = &[relationships::SUCCESS]; - const PROPERTIES: &'static [Property] = &[ + const PROPERTIES: &'static [PropertyDefinition] = property_definitions![ LOG_LEVEL, ATTRIBUTES_TO_LOG, ATTRIBUTES_TO_IGNORE, 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 48002c92fc..38484faabc 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,89 +15,44 @@ // specific language governing permissions and limitations // under the License. -use minifi_native::{LogLevel, Property, StandardPropertyValidator}; -use strum::VariantNames; - -pub(crate) const LOG_LEVEL: Property = Property { - name: "Log Level", - description: "The Log Level to use when logging the Attributes", - 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, -}; - -pub(crate) const ATTRIBUTES_TO_LOG: Property = Property { - name: "Attributes to Log", - description: "A comma-separated list of Attributes to Log. If not specified, all attributes will be logged.", - is_required: false, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, -}; - -pub(crate) const ATTRIBUTES_TO_IGNORE: Property = Property { - name: "Attributes to Ignore", - description: "A comma-separated list of Attributes to ignore. If not specified, no attributes will be ignored.", - is_required: false, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, -}; - -pub(crate) const LOG_PAYLOAD: Property = Property { - name: "Log Payload", - description: "If true, the FlowFile's payload will be logged, in addition to its attributes. Otherwise, just the Attributes will be logged.", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some("false"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, -}; - -pub(crate) const LOG_PREFIX: Property = Property { - name: "Log Prefix", - description: "Log prefix appended to the log lines. It helps to distinguish the output of multiple LogAttribute processors.", - is_required: false, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, -}; - -pub(crate) const FLOW_FILES_TO_LOG: Property = Property { - name: "FlowFiles To Log", - description: "Number of flow files to log. If set to zero all flow files will be logged. Please note that this may block other threads from running if not used judiciously.", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some("1"), - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, -}; - -pub(crate) const HEX_ENCODE_PAYLOAD: Property = Property { - name: "Hexencode Payload", - description: "If true, the FlowFile's payload will be logged in a hexencoded format", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some("false"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, -}; +use crate::processors::log_attribute::AttributeList; +use minifi_native::{LogLevel, Property}; + +pub(crate) const LOG_LEVEL: Property = Property::new( + "Log Level", + "The Log Level to use when logging the Attributes", +) +.with_default(LogLevel::Info.into_str()); + +pub(crate) const ATTRIBUTES_TO_LOG: Property> = Property::new( + "Attributes to Log", + "A comma-separated list of Attributes to Log. If not specified, all attributes will be logged.", +); + +pub(crate) const ATTRIBUTES_TO_IGNORE: Property> = Property::new( + "Attributes to Ignore", + "A comma-separated list of Attributes to ignore. If not specified, no attributes will be ignored.", +); + +pub(crate) const LOG_PAYLOAD: Property = Property::new( + "Log Payload", + "If true, the FlowFile's payload will be logged, in addition to its attributes. Otherwise, just the Attributes will be logged.", +) +.with_default("false"); + +pub(crate) const LOG_PREFIX: Property> = Property::new( + "Log Prefix", + "Log prefix appended to the log lines. It helps to distinguish the output of multiple LogAttribute processors.", +); + +pub(crate) const FLOW_FILES_TO_LOG: Property = Property::new( + "FlowFiles To Log", + "Number of flow files to log. If set to zero all flow files will be logged. Please note that this may block other threads from running if not used judiciously.", +) +.with_default("1"); + +pub(crate) const HEX_ENCODE_PAYLOAD: Property = Property::new( + "Hexencode Payload", + "If true, the FlowFile's payload will be logged in a hexencoded format", +) +.with_default("false"); 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 2c4cdb5170..f8a684d9f7 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 @@ -18,10 +18,11 @@ // Simple test processor that uses a controller service 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::properties::{ + CONTROLLER_SERVICE, DUMMY_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 +30,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 +49,7 @@ impl Schedule for LoremIpsumCSUser { where Self: Sized, { - let write_method = context - .get_property(&properties::WRITE_METHOD)? - .expect("required property") - .parse::()?; + let write_method = context.get_property(&properties::WRITE_METHOD)?; Ok(Self { write_method }) } } @@ -61,11 +61,12 @@ impl FlowFileSource for LoremIpsumCSUser { logger: &LoggerImpl, ) -> Result>, MinifiError> { trace!(logger, "generate call {:?}", self); - let controller_service = context - .get_controller_service::(&CONTROLLER_SERVICE)? - .ok_or(MinifiError::missing_required_property( - "A valid usable controller service is required", - ))?; + let dummy_controller_service = context.get_controller_service(&DUMMY_CONTROLLER_SERVICE)?; + trace!( + logger, + "optional dummy controller service: {:?}", dummy_controller_service + ); + let controller_service = context.get_controller_service(&CONTROLLER_SERVICE)?; match self.write_method { WriteMethod::Buffer => { let generated_flow_file = GeneratedFlowFile::new( diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user/processor_definition.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user/processor_definition.rs index 7d204a22fd..78c09ef0c9 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user/processor_definition.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user/processor_definition.rs @@ -19,7 +19,8 @@ use super::LoremIpsumCSUser; use super::properties::*; use crate::processors::lorem_ipsum_cs_user::relationships::SUCCESS; use minifi_native::{ - OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, PropertyDefinition, + Relationship, property_definitions, }; impl ProcessorDefinition for LoremIpsumCSUser { @@ -30,5 +31,6 @@ impl ProcessorDefinition for LoremIpsumCSUser { const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[]; const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS]; - const PROPERTIES: &'static [Property] = &[CONTROLLER_SERVICE, WRITE_METHOD]; + const PROPERTIES: &'static [PropertyDefinition] = + property_definitions![CONTROLLER_SERVICE, DUMMY_CONTROLLER_SERVICE, 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 b78ab6d23b..0742c6fb01 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 @@ -15,31 +15,20 @@ // specific language governing permissions and limitations // under the License. +use crate::controller_services::dummy_controller_service::DummyControllerService; 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; -pub(crate) const CONTROLLER_SERVICE: Property = Property { - name: "Lorem Ipsum Controller Service", - description: "Name of the lorem ipsum controller service", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: Some(LoremIpsumControllerService::CLASS_NAME), -}; +pub(crate) const CONTROLLER_SERVICE: Property = Property::new( + "Lorem Ipsum Controller Service", + "Name of the lorem ipsum controller service", +); -pub(crate) const WRITE_METHOD: Property = Property { - name: "Write Method", - description: "Which API to test", - 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, -}; +pub(crate) const DUMMY_CONTROLLER_SERVICE: Property> = Property::new( + "Dummy Controller Service", + "Optional dummy controller service", +); + +pub(crate) const WRITE_METHOD: Property = + Property::new("Write Method", "Which API to test").with_default(WriteMethod::Buffer.into_str()); 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 4d554c132e..e81c861ca0 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,11 @@ // 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 crate::processors::put_file::unix_permissions::PutFileUnixPermissions; +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 +33,11 @@ mod relationships; #[cfg(unix)] mod unix_only_properties; -#[derive(Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, IntoStaticStr)] +mod unix_permissions; + +#[derive( + Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, IntoStaticStr, PropertyType, +)] #[strum(serialize_all = "camelCase", const_into_str)] enum ConflictResolutionStrategy { Fail, @@ -40,45 +45,6 @@ enum ConflictResolutionStrategy { Ignore, } -#[cfg(unix)] -#[derive(Debug)] -struct PutFileUnixPermissions { - file_permissions: Option, - directory_permissions: Option, -} - -#[cfg(unix)] -impl PutFileUnixPermissions { - fn set_directory_permissions(&self, path: &Path) -> std::io::Result<()> { - if let Some(permissions) = self.directory_permissions.clone() { - return std::fs::set_permissions(path, permissions); - } - Ok(()) - } - - fn set_file_permissions(&self, file: &Path) -> std::io::Result<()> { - if let Some(permissions) = self.file_permissions.clone() { - return std::fs::set_permissions(file, permissions); - } - Ok(()) - } -} - -#[cfg(windows)] -#[derive(Debug)] -struct PutFileUnixPermissions {} - -#[cfg(windows)] -impl PutFileUnixPermissions { - fn set_directory_permissions(&self, _path: &Path) -> std::io::Result<()> { - Ok(()) - } - - fn set_file_permissions(&self, _file: &Path) -> std::io::Result<()> { - Ok(()) - } -} - #[derive(Debug, ComponentIdentifier)] pub(crate) struct PutFileRs { conflict_resolution_strategy: ConflictResolutionStrategy, @@ -109,14 +75,12 @@ impl PutFileRs { where Ctx: GetProperty + GetAttribute + GetId, { - let directory = context - .get_property(&properties::DIRECTORY)? - .expect("required property"); + let directory = context.get_property(&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<()> { @@ -159,17 +123,9 @@ impl PutFileRs { fn parse_unix_permissions( context: &P, ) -> Result { - use std::os::unix::fs::PermissionsExt; - let parse_permission = - |property: &minifi_native::Property| -> Result, MinifiError> { - Ok(context - .get_property(property)? - .map(|perm_str| u32::from_str_radix(&perm_str, 8)) - .transpose()? - .map(std::fs::Permissions::from_mode)) - }; - let file_permissions = parse_permission(&unix_only_properties::PERMISSIONS)?; - let directory_permissions = parse_permission(&unix_only_properties::DIRECTORY_PERMISSIONS)?; + let file_permissions = context.get_property(&unix_only_properties::PERMISSIONS)?; + let directory_permissions = + context.get_property(&unix_only_properties::DIRECTORY_PERMISSIONS)?; Ok(PutFileUnixPermissions { file_permissions, @@ -187,16 +143,12 @@ impl PutFileRs { impl Schedule for PutFileRs { fn schedule(context: &P, _logger: &L) -> Result { - let conflict_resolution_strategy = context - .get_property(&properties::CONFLICT_RESOLUTION)? - .expect("required property") - .parse::()?; + let conflict_resolution_strategy = + context.get_property(&properties::CONFLICT_RESOLUTION)?; - let try_make_dirs = context - .get_bool_property(&properties::CREATE_DIRS)? - .expect("required property"); + let try_make_dirs = context.get_property(&properties::CREATE_DIRS)?; - let maximum_file_count = context.get_u64_property(&properties::MAX_FILE_COUNT)?; + let maximum_file_count = context.get_property(&properties::MAX_FILE_COUNT)?; let unix_permissions = Self::parse_unix_permissions(context)?; @@ -222,10 +174,8 @@ impl FlowFileTransform for PutFileRs { ) -> Result, 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/processor_definition.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/processor_definition.rs index 602c2ff115..93be840a4e 100644 --- a/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/processor_definition.rs +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/processor_definition.rs @@ -17,38 +17,34 @@ use super::*; use minifi_native::{ - OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, + OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, PropertyDefinition, + Relationship, property_definitions, }; -#[cfg(windows)] -const fn get_properties() -> &'static [Property] { - &[ +impl ProcessorDefinition for PutFileRs { + const DESCRIPTION: &'static str = + "RUST TEST PROCESSOR: Writes the contents of a FlowFile to the local file system."; + const INPUT_REQUIREMENT: ProcessorInputRequirement = ProcessorInputRequirement::Required; + const SUPPORTS_DYNAMIC_PROPERTIES: bool = false; + const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; + const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[]; + const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS, FAILURE]; + + #[cfg(windows)] + const PROPERTIES: &'static [PropertyDefinition] = property_definitions![ properties::DIRECTORY, properties::CONFLICT_RESOLUTION, properties::CREATE_DIRS, properties::MAX_FILE_COUNT, - ] -} + ]; -#[cfg(unix)] -const fn get_properties() -> &'static [Property] { - &[ + #[cfg(unix)] + const PROPERTIES: &'static [PropertyDefinition] = property_definitions![ properties::DIRECTORY, properties::CONFLICT_RESOLUTION, properties::CREATE_DIRS, properties::MAX_FILE_COUNT, unix_only_properties::PERMISSIONS, unix_only_properties::DIRECTORY_PERMISSIONS, - ] -} - -impl ProcessorDefinition for PutFileRs { - const DESCRIPTION: &'static str = - "RUST TEST PROCESSOR: Writes the contents of a FlowFile to the local file system."; - const INPUT_REQUIREMENT: ProcessorInputRequirement = ProcessorInputRequirement::Required; - const SUPPORTS_DYNAMIC_PROPERTIES: bool = false; - const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; - const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[]; - const RELATIONSHIPS: &'static [Relationship] = &[SUCCESS, FAILURE]; - const PROPERTIES: &'static [Property] = get_properties(); + ]; } 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 4facd860f2..1c1bc84263 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,54 +15,27 @@ // specific language governing permissions and limitations // under the License. -use minifi_native::{Property, StandardPropertyValidator}; -use strum::VariantNames; - use super::ConflictResolutionStrategy; -pub(crate) const DIRECTORY: Property = Property { - name: "Directory", - description: "The output directory to which to put files", - is_required: true, - is_sensitive: false, - supports_expr_lang: true, - default_value: Some("."), - validator: StandardPropertyValidator::NonBlankValidator, - allowed_values: &[], - allowed_type: None, -}; +use minifi_native::{NonBlankPath, Property}; + +pub(crate) const DIRECTORY: Property = + Property::new("Directory", "The output directory to which to put files") + .supports_expression_language() + .with_default("."); -pub(crate) const CONFLICT_RESOLUTION: Property = Property { - name: "Conflict Resolution Strategy", - description: "Indicates what should happen when a file with the same name already exists in the output directory", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some(ConflictResolutionStrategy::Fail.into_str()), - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: ConflictResolutionStrategy::VARIANTS, - allowed_type: None, -}; +pub(crate) const CONFLICT_RESOLUTION: Property = Property::new( + "Conflict Resolution Strategy", + "Indicates what should happen when a file with the same name already exists in the output directory", +) +.with_default(ConflictResolutionStrategy::Fail.into_str()); -pub(crate) const CREATE_DIRS: Property = Property { - name: "Create Missing Directories", - description: "If true, then missing destination directories will be created. If false, flowfiles are penalized and sent to failure.", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: Some("true"), - validator: StandardPropertyValidator::BoolValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const CREATE_DIRS: Property = Property::new( + "Create Missing Directories", + "If true, then missing destination directories will be created. If false, flowfiles are penalized and sent to failure.", +) +.with_default("true"); -pub(crate) const MAX_FILE_COUNT: Property = Property { - name: "Maximum File Count", - description: "Specifies the maximum number of files that can exist in the output directory", - is_required: false, - 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, -}; +pub(crate) const MAX_FILE_COUNT: Property> = Property::new( + "Maximum File Count", + "Specifies the maximum number of files that can exist in the output directory", +); 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 f7cbd7811b..ccaba59ad6 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,28 +15,15 @@ // specific language governing permissions and limitations // under the License. -use minifi_native::{Property, StandardPropertyValidator}; +use crate::processors::put_file::unix_permissions::UnixPermission; +use minifi_native::Property; -pub(crate) const PERMISSIONS: Property = Property { - name: "Permissions", - description: "Sets the permissions on the output file to the value of this attribute. Must be an octal number (e.g. 644 or 0755). Not supported on Windows systems.", - is_required: false, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const PERMISSIONS: Property> = Property::new( + "Permissions", + "Sets the permissions on the output file to the value of this attribute. Must be an octal number (e.g. 644 or 0755). Not supported on Windows systems.", +); -pub(crate) const DIRECTORY_PERMISSIONS: Property = Property { - name: "Directory Permissions", - description: "Sets the permissions on the directories being created if 'Create Missing Directories' property is set. Must be an octal number (e.g. 644 or 0755). Not supported on Windows systems.", - is_required: false, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: None, -}; +pub(crate) const DIRECTORY_PERMISSIONS: Property> = Property::new( + "Directory Permissions", + "Sets the permissions on the directories being created if 'Create Missing Directories' property is set. Must be an octal number (e.g. 644 or 0755). Not supported on Windows systems.", +); diff --git a/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/unix_permissions.rs b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/unix_permissions.rs new file mode 100644 index 0000000000..be98676aec --- /dev/null +++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/unix_permissions.rs @@ -0,0 +1,62 @@ +use minifi_native::{MinifiError, PropertyConstraints, PropertySchema, PropertyType}; +use std::path::Path; + +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; + +#[cfg(unix)] +pub struct UnixPermission {} + +#[cfg(unix)] +impl PropertySchema for UnixPermission { + const CONSTRAINT: Option = None; + const IS_REQUIRED: bool = true; +} + +#[cfg(unix)] +impl PropertyType for UnixPermission { + type Output = std::fs::Permissions; + + fn parse(s: &str) -> Result { + Ok(std::fs::Permissions::from_mode(u32::from_str_radix(s, 8)?)) + } +} + +#[cfg(unix)] +#[derive(Debug)] +pub(super) struct PutFileUnixPermissions { + pub(crate) file_permissions: Option, + pub(crate) directory_permissions: Option, +} + +#[cfg(unix)] +impl PutFileUnixPermissions { + pub(crate) fn set_directory_permissions(&self, path: &Path) -> std::io::Result<()> { + if let Some(permissions) = self.directory_permissions.clone() { + return std::fs::set_permissions(path, permissions); + } + Ok(()) + } + + pub(crate) fn set_file_permissions(&self, file: &Path) -> std::io::Result<()> { + if let Some(permissions) = self.file_permissions.clone() { + return std::fs::set_permissions(file, permissions); + } + Ok(()) + } +} + +#[cfg(windows)] +#[derive(Debug)] +pub(crate) struct PutFileUnixPermissions {} + +#[cfg(windows)] +impl PutFileUnixPermissions { + pub(crate) fn set_directory_permissions(&self, _path: &Path) -> std::io::Result<()> { + Ok(()) + } + + pub(crate) fn set_file_permissions(&self, _file: &Path) -> std::io::Result<()> { + Ok(()) + } +} 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 2bbdb9a55d..86d67c7792 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,37 +18,18 @@ use crate::controller_services::animal_controller_apis::{ CanFlyControllerApi, NumberOfLegsControllerApi, }; -use minifi_native::ControllerServiceApi; use minifi_native::macros::ComponentIdentifier; use minifi_native::{ GetProperty, Logger, MinifiError, OnTriggerResult, OutputAttribute, ProcessContext, - ProcessSession, ProcessorDefinition, ProcessorInputRequirement, Property, Relationship, - Schedule, StandardPropertyValidator, Trigger, critical, info, + ProcessSession, ProcessorDefinition, ProcessorInputRequirement, Property, PropertyDefinition, + Relationship, Schedule, Trigger, critical, info, property_definitions, }; -pub(crate) const CAN_FLY_SERVICE: Property = Property { - name: "Can fly service", - description: "Test CanFlyService", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: Some(::INTERFACE_NAME), -}; +pub(crate) const CAN_FLY_SERVICE: Property = + Property::new("Can fly service", "Test CanFlyService"); -pub(crate) const NUMBER_OF_LEGS: Property = Property { - name: "Number of Legs service", - description: "Test NumberOfLegsService", - is_required: true, - is_sensitive: false, - supports_expr_lang: false, - default_value: None, - validator: StandardPropertyValidator::AlwaysValidValidator, - allowed_values: &[], - allowed_type: Some(::INTERFACE_NAME), -}; +pub(crate) const NUMBER_OF_LEGS: Property = + Property::new("Number of Legs service", "Test NumberOfLegsService"); #[derive(Debug, ComponentIdentifier)] pub(crate) struct ZooProcessorRs {} @@ -78,9 +59,7 @@ impl Trigger for ZooProcessorRs { Lggr: Logger, { info!(logger, "{:?}", self); - if let Some(maybe_flyer) = - context.get_controller_service_api::(&CAN_FLY_SERVICE)? - { + if let Some(maybe_flyer) = context.get_controller_service_api(&CAN_FLY_SERVICE)? { critical!( logger, "Can {:?} fly? {}", @@ -88,9 +67,7 @@ impl Trigger for ZooProcessorRs { maybe_flyer.can_fly() ); } - if let Some(legged) = - context.get_controller_service_api::(&NUMBER_OF_LEGS)? - { + if let Some(legged) = context.get_controller_service_api(&NUMBER_OF_LEGS)? { critical!(logger, "{:?} has {} legs", legged, legged.number_of_legs()); } Ok(OnTriggerResult::Ok) @@ -104,5 +81,6 @@ impl ProcessorDefinition for ZooProcessorRs { const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool = false; const OUTPUT_ATTRIBUTES: &'static [OutputAttribute] = &[]; const RELATIONSHIPS: &'static [Relationship] = &[]; - const PROPERTIES: &'static [Property] = &[CAN_FLY_SERVICE, NUMBER_OF_LEGS]; + const PROPERTIES: &'static [PropertyDefinition] = + property_definitions![CAN_FLY_SERVICE, NUMBER_OF_LEGS]; } diff --git a/minifi_rust/minifi_native/Cargo.toml b/minifi_rust/minifi_native/Cargo.toml index 5998c4fd32..b3f75648bc 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 6152b94ca5..49f3d88d51 100644 --- a/minifi_rust/minifi_native/src/api.rs +++ b/minifi_rust/minifi_native/src/api.rs @@ -38,6 +38,8 @@ 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, NonBlankPath, PropertyConstraints, PropertyType, StandardPropertyValidator, +}; 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 9e6b316ebd..571ea7ce74 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, MinifiError>; + fn get_required_attribute(&self, name: &str) -> Result { + self.get_attribute(name)? + .ok_or(MinifiError::missing_required_attribute(name.to_owned())) + } } diff --git a/minifi_rust/minifi_native/src/api/component_definition_traits.rs b/minifi_rust/minifi_native/src/api/component_definition_traits.rs index b0d67b2a94..5e0e8a126a 100644 --- a/minifi_rust/minifi_native/src/api/component_definition_traits.rs +++ b/minifi_rust/minifi_native/src/api/component_definition_traits.rs @@ -16,7 +16,7 @@ // under the License. use crate::api::provided_interface::ProvidedInterface; -use crate::{OutputAttribute, ProcessorInputRequirement, Property, Relationship}; +use crate::{OutputAttribute, ProcessorInputRequirement, PropertyDefinition, Relationship}; pub trait ComponentIdentifier { const CLASS_NAME: &'static str; @@ -31,11 +31,11 @@ pub trait ProcessorDefinition { const SUPPORTS_DYNAMIC_RELATIONSHIPS: bool; const OUTPUT_ATTRIBUTES: &'static [OutputAttribute]; const RELATIONSHIPS: &'static [Relationship]; - const PROPERTIES: &'static [Property]; + const PROPERTIES: &'static [PropertyDefinition]; } pub trait ControllerServiceDefinition: Sized + 'static { const DESCRIPTION: &'static str; - const PROPERTIES: &'static [Property]; + const PROPERTIES: &'static [PropertyDefinition]; const PROVIDED_APIS: &'static [ProvidedInterface]; } diff --git a/minifi_rust/minifi_native/src/api/errors.rs b/minifi_rust/minifi_native/src/api/errors.rs index 633d413ad7..2c833a9976 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 for MinifiError { } } +impl From for MinifiError { + fn from(err: ParseFloatError) -> Self { + MinifiError::Parse(ParseError::Float(err)) + } +} + +impl From 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>>(msg: S) -> Self { + MinifiError::MissingRequiredAttribute(msg.into()) + } + pub fn controller_service_err>>(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 b7df518176..80a20b6f65 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 0f114cf479..672255dd4f 100644 --- a/minifi_rust/minifi_native/src/api/process_context.rs +++ b/minifi_rust/minifi_native/src/api/process_context.rs @@ -15,115 +15,39 @@ // 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; -use crate::api::property::GetControllerService; -use crate::{ - ControllerServiceApi, ControllerServiceDefinition, EnableControllerService, GetProperty, - MinifiError, Property, -}; -use std::str::FromStr; -use std::time::Duration; +use crate::api::property::{ControllerServiceValue, GetControllerService, PropertySchema}; +use crate::{ControllerServiceApi, EnableControllerService, GetProperty, MinifiError, Property}; pub trait ProcessContext { type FlowFile: FlowFile; - fn get_property( + fn get_raw_property( &self, - property: &Property, + property: &Property, flow_file: Option<&Self::FlowFile>, ) -> Result, MinifiError>; - fn get_bool_property( + fn get_raw_controller_service( &self, - property: &Property, - flow_file: Option<&Self::FlowFile>, - ) -> Result, 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, 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, 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, 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( - &self, - property: &Property, + property: &Property, ) -> Result, MinifiError> where - Cs: RawControllerService + ComponentIdentifier + 'static; + Cs: RawControllerService + ComponentIdentifier + 'static, + K: PropertySchema + ?Sized; - fn get_controller_service(&self, property: &Property) -> Result, MinifiError> + fn get_controller_service( + &self, + property: &Property, + ) -> Result, MinifiError> where - Cs: EnableControllerService + ComponentIdentifier + 'static; + Cs: EnableControllerService + ComponentIdentifier + PropertySchema + 'static; - fn get_controller_service_api( + fn get_controller_service_api( &self, - property: &Property, + property: &Property, ) -> Result>, MinifiError>; fn report_metrics(&self, metrics: Vec<(String, f64)>) -> Result<(), MinifiError>; @@ -133,8 +57,11 @@ impl GetProperty for S where S: ProcessContext, { - fn get_property(&self, property: &Property) -> Result, MinifiError> { - self.get_property(property, None) + fn get_raw_property( + &self, + property: &Property, + ) -> Result, MinifiError> { + self.get_raw_property(property, None) } } @@ -142,10 +69,15 @@ impl GetControllerService for S where S: ProcessContext, { - fn get_controller_service(&self, property: &Property) -> Result, MinifiError> + fn get_controller_service( + &self, + property: &Property, + ) -> Result, MinifiError> where - Cs: EnableControllerService + ComponentIdentifier + ControllerServiceDefinition + 'static, + K: ControllerServiceValue + ?Sized, { - ProcessContext::get_controller_service::(self, property) + let cs_property = property.with_marker::(); + let service = ProcessContext::get_controller_service::(self, &cs_property)?; + K::from_service(service, property.name) } } 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 f856ca83d8..ef2342b5b6 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 { &self.attributes_to_add } + + #[cfg(any(test, feature = "test-utils"))] + pub fn into_bytes(self) -> std::io::Result>> { + 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 8745b2fb73..4f0716c77e 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 @@ -17,11 +17,10 @@ use crate::api::attribute::GetAttribute; use crate::api::flow_file::GetId; -use crate::api::property::{GetControllerService, GetProperty}; -use crate::{ - ComponentIdentifier, EnableControllerService, MinifiError, ProcessContext, ProcessSession, - Property, +use crate::api::property::{ + ControllerServiceValue, GetControllerService, GetProperty, PropertySchema, }; +use crate::{MinifiError, ProcessContext, ProcessSession, Property}; pub struct ContextSessionFlowFileBundle<'a, PC, PS> where @@ -55,8 +54,11 @@ where PC: ProcessContext, PS: ProcessSession, { - fn get_property(&self, property: &Property) -> Result, MinifiError> { - self.context.get_property(property, self.flow_file) + fn get_raw_property( + &self, + property: &Property, + ) -> Result, MinifiError> { + self.context.get_raw_property(property, self.flow_file) } } @@ -65,11 +67,14 @@ where PC: ProcessContext, PS: ProcessSession, { - fn get_controller_service(&self, property: &Property) -> Result, MinifiError> + fn get_controller_service( + &self, + property: &Property, + ) -> Result, MinifiError> where - Cs: EnableControllerService + ComponentIdentifier + 'static, + K: ControllerServiceValue + ?Sized, { - self.context.get_controller_service(property) + GetControllerService::get_controller_service(self.context, property) } } diff --git a/minifi_rust/minifi_native/src/api/property.rs b/minifi_rust/minifi_native/src/api/property.rs index 952b6eb688..16e17dbe86 100644 --- a/minifi_rust/minifi_native/src/api/property.rs +++ b/minifi_rust/minifi_native/src/api/property.rs @@ -16,17 +16,18 @@ // under the License. use crate::StandardPropertyValidator::{ - BoolValidator, DataSizeValidator, TimePeriodValidator, U64Validator, + BoolValidator, DataSizeValidator, NonBlankValidator, TimePeriodValidator, U64Validator, }; use crate::{ ComponentIdentifier, ControllerServiceDefinition, EnableControllerService, MinifiError, }; +use std::marker::PhantomData; use std::str::FromStr; use std::time::Duration; +use minifi_native::StandardPropertyValidator::{F64Validator, I64Validator}; #[derive(Debug, Eq, PartialEq)] pub enum StandardPropertyValidator { - AlwaysValidValidator, NonBlankValidator, TimePeriodValidator, BoolValidator, @@ -34,84 +35,272 @@ pub enum StandardPropertyValidator { U64Validator, DataSizeValidator, PortValidator, + F64Validator } -#[derive(Debug)] -pub struct Property { +#[derive(Debug, PartialEq)] +pub enum PropertyConstraints { + Validator(StandardPropertyValidator), + AllowedValues(&'static [&'static str]), + ControllerService(&'static str), +} + +pub struct PropertyDefinition { pub name: &'static str, pub description: &'static str, pub is_required: bool, 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, } -pub trait GetProperty { - fn get_property(&self, property: &Property) -> Result, MinifiError>; - fn get_bool_property(&self, property: &Property) -> Result, MinifiError> { - if property.validator != BoolValidator { - return Err(MinifiError::validation_err(format!( - "to use get_bool_property {:?} must have BoolValidator", - property - ))); - } +#[macro_export] +macro_rules! property_definitions { + ($($property:expr),* $(,)?) => { + &[$($property.definition()),*] + }; +} + +pub struct Property { + pub(crate) name: &'static str, + pub(crate) description: &'static str, + pub(crate) is_sensitive: bool, + pub(crate) supports_expr_lang: bool, + pub(crate) default_value: Option<&'static str>, + pub(crate) marker: PhantomData, +} - if let Some(property_val) = self.get_property(property)? { - Ok(Some(bool::from_str(&property_val)?)) - } else { - Ok(None) +impl Property { + pub const fn new(name: &'static str, description: &'static str) -> Self { + Property { + name, + description, + is_sensitive: false, + supports_expr_lang: false, + default_value: None, + marker: PhantomData, } } - fn get_duration_property(&self, property: &Property) -> Result, MinifiError> { - if property.validator != TimePeriodValidator { - return Err(MinifiError::validation_err(format!( - "to use get_duration_property {:?} must have TimePeriodValidator", - property - ))); + pub const fn sensitive(mut self) -> Self { + self.is_sensitive = true; + self + } + + pub const fn supports_expression_language(mut self) -> Self { + self.supports_expr_lang = true; + self + } + + pub const fn with_default(mut self, default_value: &'static str) -> Self { + self.default_value = Some(default_value); + self + } + + pub const fn name(&self) -> &'static str { + self.name + } + + pub const fn definition(&self) -> PropertyDefinition { + PropertyDefinition { + name: self.name, + description: self.description, + is_required: K::IS_REQUIRED || self.default_value.is_some(), + is_sensitive: self.is_sensitive, + supports_expr_lang: self.supports_expr_lang, + default_value: self.default_value, + constraints: K::CONSTRAINT, } + } - if let Some(property_val) = self.get_property(property)? { - Ok(Some(humantime::parse_duration(property_val.as_str())?)) - } else { - Ok(None) + pub(crate) const fn with_marker(&self) -> Property { + Property { + name: self.name, + description: self.description, + is_sensitive: self.is_sensitive, + supports_expr_lang: self.supports_expr_lang, + default_value: self.default_value, + marker: PhantomData, } } +} + +pub trait PropertySchema { + const CONSTRAINT: Option; + const IS_REQUIRED: bool; +} + +impl PropertySchema for Option { + const CONSTRAINT: Option = T::CONSTRAINT; + const IS_REQUIRED: bool = false; +} - fn get_size_property(&self, property: &Property) -> Result, MinifiError> { - if property.validator != DataSizeValidator { - return Err(MinifiError::validation_err(format!( - "to use get_size_property {:?} must have DataSizeValidator", - property - ))); +pub trait PropertyType: PropertySchema { + type Output; + fn parse(s: &str) -> Result; +} + +pub trait PropertyValue: PropertySchema { + type Output; + fn from_raw(raw: Option, name: &str) -> Result; +} + +impl PropertyValue for T { + type Output = T::Output; + fn from_raw(raw: Option, name: &str) -> Result { + match raw { + Some(value) => T::parse(&value), + None => Err(MinifiError::missing_required_property(name.to_string())), } - if let Some(property_val) = self.get_property(property)? { - Ok(Some(byte_unit::Byte::from_str(&property_val)?.as_u64())) - } else { - Ok(None) + } +} + +impl PropertyValue for Option { + type Output = Option; + fn from_raw(raw: Option, _name: &str) -> Result { + match raw { + Some(value) => Ok(Some(T::parse(&value)?)), + None => Ok(None), } } +} + +macro_rules! impl_from_str_property { + ($t:ty, $constraint:expr) => { + impl PropertyType for $t { + type Output = $t; - fn get_u64_property(&self, property: &Property) -> Result, MinifiError> { - if property.validator != U64Validator { - return Err(MinifiError::validation_err(format!( - "to use get_u64_property {:?} must have U64Validator", - property - ))); + fn parse(s: &str) -> Result { + s.parse::<$t>().map_err(Into::into) + } } - if let Some(property_val) = self.get_property(property)? { - Ok(Some(u64::from_str(&property_val)?)) - } else { - Ok(None) + impl PropertySchema for $t { + const CONSTRAINT: Option = $constraint; + const IS_REQUIRED: bool = true; } + }; + ($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, Some(PropertyConstraints::Validator(F64Validator))); +impl_from_str_property!(f32, Some(PropertyConstraints::Validator(F64Validator))); +impl_from_str_property!(i64, Some(PropertyConstraints::Validator(I64Validator))); +impl_from_str_property!(i32, Some(PropertyConstraints::Validator(I64Validator))); +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; + fn parse(s: &str) -> Result { + humantime::parse_duration(s).map_err(Into::into) + } +} +impl PropertySchema for Duration { + const CONSTRAINT: Option = + Some(PropertyConstraints::Validator(TimePeriodValidator)); + const IS_REQUIRED: bool = true; +} + +pub struct DataSize; +impl PropertyType for DataSize { + type Output = u64; + fn parse(s: &str) -> Result { + byte_unit::Byte::from_str(s) + .map(|b| b.as_u64()) + .map_err(Into::into) + } +} +impl PropertySchema for DataSize { + const CONSTRAINT: Option = + Some(PropertyConstraints::Validator(DataSizeValidator)); + const IS_REQUIRED: bool = true; +} + +pub struct NonBlankPath; +impl PropertyType for NonBlankPath { + type Output = std::path::PathBuf; + fn parse(s: &str) -> Result { + ::parse(s) + } +} +impl PropertySchema for NonBlankPath { + const CONSTRAINT: Option = + Some(PropertyConstraints::Validator(NonBlankValidator)); + const IS_REQUIRED: bool = true; +} + +pub trait GetProperty { + fn get_raw_property( + &self, + property: &Property, + ) -> Result, MinifiError>; + + fn get_property( + &self, + property: &Property, + ) -> Result { + K::from_raw(self.get_raw_property(property)?, property.name) + } +} + +pub trait ControllerServiceValue: PropertySchema { + type Cs: EnableControllerService + + ComponentIdentifier + + ControllerServiceDefinition + + PropertySchema + + 'static; + type Output<'a>; + fn from_service<'a>( + service: Option<&'a Self::Cs>, + name: &str, + ) -> Result, MinifiError>; +} + +impl ControllerServiceValue for Cs +where + Cs: EnableControllerService + + ComponentIdentifier + + ControllerServiceDefinition + + PropertySchema + + 'static, +{ + type Cs = Cs; + type Output<'a> = &'a Cs; + fn from_service<'a>(service: Option<&'a Cs>, name: &str) -> Result<&'a Cs, MinifiError> { + service.ok_or_else(|| MinifiError::missing_required_property(name.to_string())) + } +} + +impl ControllerServiceValue for Option +where + Cs: EnableControllerService + + ComponentIdentifier + + ControllerServiceDefinition + + PropertySchema + + 'static, +{ + type Cs = Cs; + type Output<'a> = Option<&'a Cs>; + fn from_service<'a>( + service: Option<&'a Cs>, + _name: &str, + ) -> Result, MinifiError> { + Ok(service) } } pub trait GetControllerService { - fn get_controller_service(&self, property: &Property) -> Result, MinifiError> + fn get_controller_service( + &self, + property: &Property, + ) -> Result, MinifiError> where - Cs: EnableControllerService + ComponentIdentifier + ControllerServiceDefinition + 'static; + K: ControllerServiceValue + ?Sized; } diff --git a/minifi_rust/minifi_native/src/api/relationship.rs b/minifi_rust/minifi_native/src/api/relationship.rs index 45041a2ba5..df3e340816 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 2284c8dc75..e0efd4afb8 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 @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use crate::api::property::PropertySchema; use crate::c_ffi::c_ffi_primitives::StringView; use crate::{GetProperty, MinifiError, Property}; use minifi_native_sys::{ @@ -22,7 +23,6 @@ use minifi_native_sys::{ minifi_status_MINIFI_STATUS_PROPERTY_NOT_SET, minifi_status_MINIFI_STATUS_SUCCESS, minifi_string_view, }; -use std::borrow::Cow; use std::ffi::c_void; use std::num::NonZeroU32; @@ -61,7 +61,10 @@ unsafe extern "C" fn property_callback( } impl<'a> GetProperty for CffiControllerServiceContext<'a> { - fn get_property(&self, property: &Property) -> Result, MinifiError> { + fn get_raw_property( + &self, + property: &Property, + ) -> Result, MinifiError> { let mut result: Option = None; let property_name: StringView = StringView::new(property.name); @@ -77,12 +80,7 @@ impl<'a> GetProperty for CffiControllerServiceContext<'a> { #[allow(non_upper_case_globals)] match status { minifi_status_MINIFI_STATUS_SUCCESS => Ok(result), - minifi_status_MINIFI_STATUS_PROPERTY_NOT_SET => match property.is_required { - true => Err(MinifiError::MissingRequiredProperty(Cow::from( - property.name, - ))), - false => Ok(None), - }, + minifi_status_MINIFI_STATUS_PROPERTY_NOT_SET => Ok(None), err_code => Err(MinifiError::StatusError(( format!( "minifi_controller_service_context_get_property({:?})", diff --git a/minifi_rust/minifi_native/src/c_ffi/c_ffi_controller_service_definition.rs b/minifi_rust/minifi_native/src/c_ffi/c_ffi_controller_service_definition.rs index 738071dc7a..95fe4bb0d1 100644 --- a/minifi_rust/minifi_native/src/c_ffi/c_ffi_controller_service_definition.rs +++ b/minifi_rust/minifi_native/src/c_ffi/c_ffi_controller_service_definition.rs @@ -21,7 +21,7 @@ use crate::c_ffi::c_ffi_property::CProperties; use crate::c_ffi::{CffiLogger, StaticStrAsMinifiCStr}; use crate::{ ComponentIdentifier, ControllerService, ControllerServiceDefinition, EnableControllerService, - LogLevel, Property, ProvidedInterface, + LogLevel, PropertyDefinition, ProvidedInterface, }; use minifi_native_sys::{ minifi_controller_service_callbacks, minifi_controller_service_context, @@ -71,10 +71,10 @@ where { pub fn new( description_text: &'static str, - properties: &'static [Property], + properties: &'static [PropertyDefinition], provided_interfaces: &'static [ProvidedInterface

], ) -> Self { - let c_properties = Property::create_c_properties(properties); + let c_properties = PropertyDefinition::create_c_properties(properties); let mut c_provided_apis = Vec::new(); for provided_interface in provided_interfaces { c_provided_apis.push(provided_interface.name.as_minifi_c_type()); diff --git a/minifi_rust/minifi_native/src/c_ffi/c_ffi_process_context.rs b/minifi_rust/minifi_native/src/c_ffi/c_ffi_process_context.rs index 60dfc033c0..4604b24a5b 100644 --- a/minifi_rust/minifi_native/src/c_ffi/c_ffi_process_context.rs +++ b/minifi_rust/minifi_native/src/c_ffi/c_ffi_process_context.rs @@ -19,12 +19,12 @@ use super::c_ffi_flow_file::CffiFlowFile; use super::c_ffi_primitives::StringView; use crate::api::ProcessContext; use crate::api::controller_service::ControllerService; +use crate::api::property::PropertySchema; use crate::c_ffi::{CffiLogger, StaticStrAsMinifiCStr}; use crate::{ ComponentIdentifier, ControllerServiceApi, EnableControllerService, MinifiError, Property, }; use minifi_native_sys::*; -use std::borrow::Cow; use std::ffi::c_void; use std::num::NonZeroU32; @@ -65,9 +65,9 @@ unsafe extern "C" fn get_property_callback( impl<'a> ProcessContext for CffiProcessContext<'a> { type FlowFile = CffiFlowFile<'a>; // FlowFile shouldn't outlive the ProcessContext - fn get_property( + fn get_raw_property( &self, - property: &Property, + property: &Property, flow_file: Option<&Self::FlowFile>, ) -> Result, MinifiError> { let ff_ptr = flow_file.map_or(std::ptr::null_mut(), |ff| ff.get_ptr()); @@ -85,12 +85,7 @@ impl<'a> ProcessContext for CffiProcessContext<'a> { &mut result as *mut _ as *mut c_void, ) { minifi_status_MINIFI_STATUS_SUCCESS => Ok(result), - minifi_status_MINIFI_STATUS_PROPERTY_NOT_SET => match property.is_required { - true => Err(MinifiError::MissingRequiredProperty(Cow::from( - property.name, - ))), - false => Ok(None), - }, + minifi_status_MINIFI_STATUS_PROPERTY_NOT_SET => Ok(None), err_code => Err(MinifiError::StatusError(( format!("minifi_process_context_get_property({:?})", property.name).into(), NonZeroU32::new_unchecked(err_code), @@ -99,12 +94,13 @@ impl<'a> ProcessContext for CffiProcessContext<'a> { } } - fn get_raw_controller_service( + fn get_raw_controller_service( &self, - property: &Property, + property: &Property, ) -> Result, MinifiError> where Cs: ComponentIdentifier + 'static, + K: PropertySchema + ?Sized, { let str_view = StringView::new(property.name); @@ -117,39 +113,43 @@ impl<'a> ProcessContext for CffiProcessContext<'a> { Cs::CLASS_NAME.as_minifi_c_type(), &mut controller_service_ptr, ); - if get_cs_status != minifi_status_MINIFI_STATUS_SUCCESS { - return Err(MinifiError::StatusError(( + #[allow(non_upper_case_globals)] + match get_cs_status { + minifi_status_MINIFI_STATUS_PROPERTY_NOT_SET => Ok(None), + minifi_status_MINIFI_STATUS_SUCCESS => Ok(Some( + (controller_service_ptr as *const Cs) + .as_ref() + .expect("C returned a null pointer"), + )), + err => Err(MinifiError::StatusError(( format!( "minifi_process_context_get_controller_service_from_property::<{:?}>({:?})", Cs::CLASS_NAME, - property + property.name ) .into(), - NonZeroU32::new_unchecked(get_cs_status), - ))); + NonZeroU32::new_unchecked(err), + ))), } - let cs_ref: &Cs = { - (controller_service_ptr as *const Cs) - .as_ref() - .expect("C returned a null pointer") - }; - Ok(Some(cs_ref)) } } - fn get_controller_service(&self, property: &Property) -> Result, MinifiError> + fn get_controller_service( + &self, + property: &Property, + ) -> Result, MinifiError> where - Cs: EnableControllerService + ComponentIdentifier + 'static, + Cs: EnableControllerService + ComponentIdentifier + PropertySchema + 'static, { - match self.get_raw_controller_service::>(property)? { + match self.get_raw_controller_service::, Cs>(property)? { None => Ok(None), Some(f) => Ok(f.get_implementation()), } } - fn get_controller_service_api( + fn get_controller_service_api( &self, - property: &Property, + property: &Property, ) -> Result>, MinifiError> { let str_view = StringView::new(property.name); let interface_view = StringView::new(Trait::INTERFACE_NAME); diff --git a/minifi_rust/minifi_native/src/c_ffi/c_ffi_processor_definition.rs b/minifi_rust/minifi_native/src/c_ffi/c_ffi_processor_definition.rs index 68602aca69..a5c6688c05 100644 --- a/minifi_rust/minifi_native/src/c_ffi/c_ffi_processor_definition.rs +++ b/minifi_rust/minifi_native/src/c_ffi/c_ffi_processor_definition.rs @@ -28,7 +28,7 @@ use crate::c_ffi::c_ffi_output_attribute::COutputAttributes; use crate::c_ffi::c_ffi_property::CProperties; use crate::{ ComponentIdentifier, LogLevel, MultiThreaded, OutputAttribute, Processor, ProcessorDefinition, - Property, Schedule, SingleThreaded, + PropertyDefinition, Schedule, SingleThreaded, }; use crate::{OnTriggerResult, Relationship}; use minifi_native_sys::*; @@ -130,10 +130,10 @@ where supports_dynamic_relationships: bool, output_attributes: &'static [OutputAttribute], relationships: &'static [Relationship], - properties: &'static [Property], + properties: &'static [PropertyDefinition], ) -> Self { let c_relationships = Relationship::create_c_vec(relationships); - let c_properties = Property::create_c_properties(properties); + let c_properties = PropertyDefinition::create_c_properties(properties); let c_output_attributes = COutputAttributes::new(output_attributes); Self { 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 d2fe2c1b7d..f252a9e1da 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,7 +16,8 @@ // under the License. use super::c_ffi_primitives::StaticStrAsMinifiCStr; -use crate::{Property, StandardPropertyValidator}; +use crate::api::property::PropertyConstraints; +use crate::{PropertyDefinition, StandardPropertyValidator}; use minifi_native_sys::{ minifi_property_definition, minifi_string_view, minifi_validator, minifi_validator_MINIFI_VALIDATOR_ALWAYS_VALID, minifi_validator_MINIFI_VALIDATOR_BOOLEAN, @@ -59,7 +60,7 @@ impl CProperties { } } -impl Property { +impl PropertyDefinition { fn create_c_default_value_holder(properties: &[Self]) -> Vec { properties .iter() @@ -76,11 +77,14 @@ impl Property { fn create_c_allowed_values_vec_vec(properties: &[Self]) -> Vec> { 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 { 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, }, @@ -99,9 +105,9 @@ impl Property { } pub(crate) fn create_c_properties(properties: &[Self]) -> CProperties { - let c_default_values = Property::create_c_default_value_holder(properties); - let c_allowed_values = Property::create_c_allowed_values_vec_vec(properties); - let c_allowed_types = Property::create_c_allowed_types_vec(properties); + let c_default_values = PropertyDefinition::create_c_default_value_holder(properties); + let c_allowed_values = PropertyDefinition::create_c_allowed_values_vec_vec(properties); + let c_allowed_types = PropertyDefinition::create_c_allowed_types_vec(properties); assert_eq!(c_default_values.len(), properties.len()); assert_eq!(c_allowed_values.len(), properties.len()); assert_eq!(c_allowed_types.len(), properties.len()); @@ -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 2d6bf1205c..ce54f9090c 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; @@ -45,7 +46,10 @@ pub use api::raw_processor::{MultiThreaded, SingleThreaded}; pub use api::logger::{LogLevel, Logger}; -pub use api::property::{GetControllerService, GetProperty, Property}; +pub use api::property::{ + ControllerServiceValue, GetControllerService, GetProperty, Property, PropertyDefinition, + PropertySchema, PropertyValue, +}; pub use api::provided_interface::{ControllerServiceApi, ProvidedInterface}; @@ -54,8 +58,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, NonBlankPath, OnTriggerResult, OutputStream, + ProcessContext, ProcessSession, ProcessorInputRequirement, PropertyConstraints, PropertyType, + Relationship, StandardPropertyValidator, }; 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 2a594ef8a9..86a8a81580 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 @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use crate::api::property::PropertySchema; use crate::mock::mock_process_context::MockPropertyMap; use crate::{GetProperty, MinifiError, Property}; @@ -23,7 +24,10 @@ pub struct MockControllerServiceContext { } impl GetProperty for MockControllerServiceContext { - fn get_property(&self, property: &Property) -> Result, MinifiError> { + fn get_raw_property( + &self, + property: &Property, + ) -> Result, MinifiError> { self.properties.get_property(property, None) } } diff --git a/minifi_rust/minifi_native/src/mock/mock_process_context.rs b/minifi_rust/minifi_native/src/mock/mock_process_context.rs index be05daa16d..56ba242364 100644 --- a/minifi_rust/minifi_native/src/mock/mock_process_context.rs +++ b/minifi_rust/minifi_native/src/mock/mock_process_context.rs @@ -15,13 +15,13 @@ // specific language governing permissions and limitations // under the License. +use crate::api::property::PropertySchema; use crate::api::{GetId, ProcessContext, RawControllerService}; use crate::{ ComponentIdentifier, ControllerServiceApi, EnableControllerService, GetAttribute, MinifiError, MockFlowFile, Property, }; use std::any::Any; -use std::borrow::Cow; use std::collections::HashMap; pub struct MockPropertyMap { @@ -55,23 +55,17 @@ impl MockPropertyMap { } impl MockPropertyMap { - pub fn get_property( + pub fn get_property( &self, - property: &Property, + property: &Property, _flow_file: Option<&MockFlowFile>, ) -> Result, MinifiError> { - if let Some(property) = self.properties.get(property.name) { - Ok(Some(property.clone())) + if let Some(value) = self.properties.get(property.name) { + Ok(Some(value.clone())) } else { - if let Some(default_val) = property.default_value { - return Ok(Some(default_val.to_string())); - } - match property.is_required { - true => Err(MinifiError::MissingRequiredProperty(Cow::from( - property.name, - ))), - false => Ok(None), - } + Ok(property + .default_value + .map(|default_val| default_val.to_string())) } } } @@ -85,24 +79,25 @@ pub struct MockProcessContext { impl ProcessContext for MockProcessContext { type FlowFile = MockFlowFile; - fn get_property( + fn get_raw_property( &self, - property: &Property, + property: &Property, _flow_file: Option<&Self::FlowFile>, ) -> Result, MinifiError> { self.properties.get_property(property, _flow_file) } - fn get_raw_controller_service( + fn get_raw_controller_service( &self, - property: &Property, + property: &Property, ) -> Result, MinifiError> where Cs: RawControllerService + ComponentIdentifier + 'static, + K: PropertySchema + ?Sized, { // Mirror `get_controller_service`: resolve the property to a // service name and downcast the registered `Box`. - if let Some(service_name) = self.get_property(property, None)? { + if let Some(service_name) = self.get_raw_property(property, None)? { Ok(self .controller_services .get(&service_name) @@ -112,11 +107,14 @@ impl ProcessContext for MockProcessContext { } } - fn get_controller_service(&self, property: &Property) -> Result, MinifiError> + fn get_controller_service( + &self, + property: &Property, + ) -> Result, MinifiError> where - Cs: EnableControllerService + ComponentIdentifier + 'static, + Cs: EnableControllerService + ComponentIdentifier + PropertySchema + 'static, { - if let Some(service_name) = self.get_property(property, None)? { + if let Some(service_name) = self.get_raw_property(property, None)? { Ok(self .controller_services .get(&service_name) @@ -126,9 +124,9 @@ impl ProcessContext for MockProcessContext { } } - fn get_controller_service_api( + fn get_controller_service_api( &self, - _property: &Property, + _property: &Property, ) -> Result>, MinifiError> { // A fully-typed mock for `dyn Trait` interfaces would need per-property // registration keyed by both property name and interface name; the diff --git a/minifi_rust/minifi_native_macros/src/lib.rs b/minifi_rust/minifi_native_macros/src/lib.rs index fa8e563361..8730055078 100644 --- a/minifi_rust/minifi_native_macros/src/lib.rs +++ b/minifi_rust/minifi_native_macros/src/lib.rs @@ -31,6 +31,12 @@ 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::PropertySchema for #name { + const CONSTRAINT: Option<::minifi_native::PropertyConstraints> = + Some(::minifi_native::PropertyConstraints::ControllerService(::CLASS_NAME)); + const IS_REQUIRED: bool = true; + } }; TokenStream::from(expanded) @@ -48,6 +54,37 @@ 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::PropertySchema for dyn #name { + const CONSTRAINT: Option<::minifi_native::PropertyConstraints> = + Some(::minifi_native::PropertyConstraints::ControllerService(::INTERFACE_NAME)); + const IS_REQUIRED: bool = true; + } + }; + + 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; + fn parse(s: &str) -> Result { + s.parse::<#name>().map_err(Into::into) + } + } + + impl ::minifi_native::PropertySchema for #name { + const CONSTRAINT: Option<::minifi_native::PropertyConstraints> = + Some(::minifi_native::PropertyConstraints::AllowedValues( + <#name as ::strum::VariantNames>::VARIANTS + )); + const IS_REQUIRED: bool = true; + } }; TokenStream::from(expanded) diff --git a/minifi_rust/minifi_rs_behave/src/main.rs b/minifi_rust/minifi_rs_behave/src/main.rs index f0d8c12bc4..2e8517f099 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") }