This is an automated email from the ASF dual-hosted git repository.

martinzink pushed a commit to branch minifi_rust_impr
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit 37bc79d4559086b0e91fb2ea187338ddbcd3ce81
Author: Martin Zink <[email protected]>
AuthorDate: Wed Jul 8 10:30:49 2026 +0200

    minifi_rust improvements
---
 .../extensions/minifi_rs_playground/Cargo.toml     |   1 +
 .../controller_services/dog_controller_service.rs  |  18 ++-
 .../lorem_ipsum_controller_service.rs              |  11 +-
 .../src/processors/generate_flow_file.rs           |  28 ++---
 .../processors/generate_flow_file/properties.rs    |  21 +---
 .../src/processors/get_file.rs                     |  39 +++---
 .../src/processors/get_file/properties.rs          |  45 ++-----
 .../src/processors/kamikaze_processor.rs           |  24 ++--
 .../processors/kamikaze_processor/properties.rs    |  15 +--
 .../src/processors/log_attribute.rs                |  27 ++---
 .../src/processors/log_attribute/properties.rs     |  32 ++---
 .../src/processors/lorem_ipsum_cs_user.rs          |  11 +-
 .../processors/lorem_ipsum_cs_user/properties.rs   |  11 +-
 .../src/processors/put_file.rs                     |  24 ++--
 .../src/processors/put_file/properties.rs          |  20 +--
 .../processors/put_file/unix_only_properties.rs    |  11 +-
 .../src/processors/zoo_processor.rs                |  11 +-
 minifi_rust/minifi_native/Cargo.toml               |   3 +
 minifi_rust/minifi_native/src/api.rs               |   2 +-
 minifi_rust/minifi_native/src/api/attribute.rs     |   4 +
 minifi_rust/minifi_native/src/api/errors.rs        |  27 ++++-
 minifi_rust/minifi_native/src/api/logger.rs        |  18 ++-
 .../minifi_native/src/api/process_context.rs       |  79 +-----------
 .../api/processor_wrappers/flow_file_transform.rs  |  52 ++++++++
 .../utils/context_session_flowfile_bundle.rs       |   2 +-
 minifi_rust/minifi_native/src/api/property.rs      | 135 ++++++++++++++-------
 minifi_rust/minifi_native/src/api/relationship.rs  |   8 ++
 .../src/c_ffi/c_ffi_controller_service_context.rs  |   2 +-
 .../minifi_native/src/c_ffi/c_ffi_property.rs      |  24 ++--
 minifi_rust/minifi_native/src/lib.rs               |   6 +-
 .../src/mock/mock_controller_service_context.rs    |   2 +-
 minifi_rust/minifi_native_macros/src/lib.rs        |  21 ++++
 minifi_rust/minifi_rs_behave/Dockerfile.alpine     |   1 +
 minifi_rust/minifi_rs_behave/Dockerfile.debian     |   8 +-
 minifi_rust/minifi_rs_behave/src/main.rs           |   1 +
 35 files changed, 373 insertions(+), 371 deletions(-)

diff --git a/minifi_rust/extensions/minifi_rs_playground/Cargo.toml 
b/minifi_rust/extensions/minifi_rs_playground/Cargo.toml
index 8d8682660..ec90190e3 100644
--- a/minifi_rust/extensions/minifi_rs_playground/Cargo.toml
+++ b/minifi_rust/extensions/minifi_rs_playground/Cargo.toml
@@ -16,6 +16,7 @@ hex = "0.4.3"
 strum_macros = "0.28.0"
 lipsum = "0.9.1"
 
+
 [dev-dependencies]
 tempfile = "3.22.0"
 filetime = "0.2.26"
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/controller_services/dog_controller_service.rs
 
b/minifi_rust/extensions/minifi_rs_playground/src/controller_services/dog_controller_service.rs
index df2521213..2fa133742 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
@@ -19,6 +19,7 @@ use crate::controller_services::animal_controller_apis::{
     CanFlyControllerApi, NumberOfLegsControllerApi,
 };
 use minifi_native::ControllerServiceApi;
+use minifi_native::PropertyConstraints::{NoConstraints, Validator};
 use minifi_native::macros::ComponentIdentifier;
 use minifi_native::{
     ControllerServiceDefinition, EnableControllerService, GetProperty, Logger, 
MinifiError,
@@ -32,9 +33,7 @@ pub(crate) const HAS_JETPACK: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some("false"),
-    validator: StandardPropertyValidator::BoolValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::BoolValidator),
 };
 
 pub(crate) const EXTRA_INFO: Property = Property {
@@ -44,9 +43,7 @@ pub(crate) const EXTRA_INFO: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: NoConstraints,
 };
 
 #[allow(dead_code)] // extra_info is only used by {:?}
@@ -73,11 +70,10 @@ impl EnableControllerService for DogControllerRs {
     where
         Self: Sized,
     {
-        let has_jetpack = context.get_bool_property(&HAS_JETPACK)?.ok_or(
-            MinifiError::missing_required_property("Has jetpack is required"),
-        )?;
-
-        let extra_info = 
context.get_property(&EXTRA_INFO)?.unwrap_or("".into());
+        let has_jetpack = context.get_req_property::<bool>(&HAS_JETPACK)?;
+        let extra_info = context
+            .get_property::<String>(&EXTRA_INFO)?
+            .unwrap_or("".into());
 
         Ok(Self {
             has_jetpack,
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/controller_services/lorem_ipsum_controller_service.rs
 
b/minifi_rust/extensions/minifi_rs_playground/src/controller_services/lorem_ipsum_controller_service.rs
index 25341971e..7c692ba39 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
@@ -16,6 +16,7 @@
 // under the License.
 
 use lipsum::lipsum;
+use minifi_native::PropertyConstraints::Validator;
 use minifi_native::macros::ComponentIdentifier;
 use minifi_native::{
     ControllerServiceDefinition, EnableControllerService, GetProperty, Logger, 
MinifiError,
@@ -29,9 +30,7 @@ const LENGTH: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some("25"),
-    validator: StandardPropertyValidator::U64Validator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::U64Validator),
 };
 
 #[derive(Debug, ComponentIdentifier)]
@@ -44,11 +43,7 @@ impl EnableControllerService for LoremIpsumControllerService 
{
     where
         Self: Sized,
     {
-        let length = context
-            .get_u64_property(&LENGTH)?
-            .ok_or(MinifiError::missing_required_property("Length is 
required"))?;
-
-        let data = lipsum(length as usize);
+        let data = lipsum(context.get_req_property::<usize>(&LENGTH)?);
         Ok(Self { data })
     }
 }
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file.rs
 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file.rs
index 55bb376da..4464ef4e7 100644
--- 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file.rs
+++ 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file.rs
@@ -19,8 +19,8 @@
 
 use minifi_native::macros::ComponentIdentifier;
 use minifi_native::{
-    GetProperty, Logger, MinifiError, OnTriggerResult, ProcessContext, 
ProcessSession, Schedule,
-    Trigger,
+    DataSize, GetProperty, Logger, MinifiError, OnTriggerResult, 
ProcessContext, ProcessSession,
+    Schedule, Trigger,
 };
 use rand::RngExt;
 use rand::distr::Alphanumeric;
@@ -52,22 +52,14 @@ impl Schedule for GenerateFlowFileRs {
     where
         Self: Sized,
     {
-        let is_unique = context
-            .get_bool_property(&properties::UNIQUE_FLOW_FILES)?
-            .expect("Required property");
-        let is_text = context
-            .get_property(&properties::DATA_FORMAT)?
-            .expect("Required property")
-            .as_str()
-            == "Text";
-        let has_custom_text = 
context.get_property(&properties::CUSTOM_TEXT)?.is_some();
-
-        let file_size = context
-            .get_size_property(&properties::FILE_SIZE)?
-            .expect("Required property");
-        let batch_size = context
-            .get_u64_property(&properties::BATCH_SIZE)?
-            .expect("Required property");
+        let is_unique = 
context.get_req_property::<bool>(&properties::UNIQUE_FLOW_FILES)?;
+        let is_text = 
context.get_req_property::<String>(&properties::DATA_FORMAT)? == "Text";
+        let has_custom_text = context
+            .get_property::<String>(&properties::CUSTOM_TEXT)?
+            .is_some();
+
+        let file_size = 
context.get_req_property::<DataSize>(&properties::FILE_SIZE)?;
+        let batch_size = 
context.get_req_property::<u64>(&properties::BATCH_SIZE)?;
 
         let mode = Self::get_mode(is_unique, is_text, has_custom_text, 
file_size);
         let data_generated_during_on_schedule =
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/properties.rs
 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/generate_flow_file/properties.rs
index 16d0b8a71..468b76f7c 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,6 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use minifi_native::PropertyConstraints::{AllowedValues, NoConstraints, 
Validator};
 use minifi_native::{Property, StandardPropertyValidator};
 
 pub(crate) const FILE_SIZE: Property = Property {
@@ -24,9 +25,7 @@ pub(crate) const FILE_SIZE: Property = Property {
     is_sensitive: false,
     supports_expr_lang: true,
     default_value: Some("1 kB"),
-    validator: StandardPropertyValidator::DataSizeValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::DataSizeValidator),
 };
 
 pub(crate) const BATCH_SIZE: Property = Property {
@@ -36,9 +35,7 @@ pub(crate) const BATCH_SIZE: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some("1"),
-    validator: StandardPropertyValidator::U64Validator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::U64Validator),
 };
 
 pub(crate) const DATA_FORMAT: Property = Property {
@@ -48,9 +45,7 @@ pub(crate) const DATA_FORMAT: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some("Binary"),
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: &["Text", "Binary"],
-    allowed_type: None,
+    constraints: AllowedValues(&["Text", "Binary"]),
 };
 
 pub(crate) const UNIQUE_FLOW_FILES: Property = Property {
@@ -60,9 +55,7 @@ pub(crate) const UNIQUE_FLOW_FILES: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some("true"),
-    validator: StandardPropertyValidator::BoolValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::BoolValidator),
 };
 
 pub(crate) const CUSTOM_TEXT: Property = Property {
@@ -72,7 +65,5 @@ pub(crate) const CUSTOM_TEXT: Property = Property {
     is_sensitive: false,
     supports_expr_lang: true,
     default_value: None,
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: NoConstraints,
 };
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file.rs 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file.rs
index 1847ea632..dc365e9c4 100644
--- a/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file.rs
+++ b/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file.rs
@@ -26,8 +26,8 @@ use crate::processors::get_file::properties::{
 };
 use minifi_native::macros::ComponentIdentifier;
 use minifi_native::{
-    GetProperty, IoState, Logger, MinifiError, OnTriggerResult, 
ProcessContext, ProcessSession,
-    Schedule, Trigger, debug, info, trace, warn,
+    DataSize, GetProperty, IoState, Logger, MinifiError, OnTriggerResult, 
ProcessContext,
+    ProcessSession, Schedule, Trigger, debug, info, trace, warn,
 };
 use std::collections::VecDeque;
 use std::error;
@@ -223,10 +223,7 @@ impl Schedule for GetFileRs {
     where
         Self: Sized,
     {
-        let input_directory: PathBuf = context
-            .get_property(&DIRECTORY)?
-            .expect("Required property")
-            .into();
+        let input_directory = context.get_req_property::<PathBuf>(&DIRECTORY)?;
         if !input_directory.is_dir() {
             return Err(MinifiError::schedule_err(format!(
                 "{:?} is not a valid directory",
@@ -234,25 +231,17 @@ impl Schedule for GetFileRs {
             )));
         }
 
-        let recursive = context
-            .get_bool_property(&RECURSE)?
-            .expect("Required property");
-
-        let keep_source_file = context
-            .get_bool_property(&KEEP_SOURCE_FILE)?
-            .expect("Required property");
-
-        let poll_interval = 
context.get_duration_property(&properties::POLLING_INTERVAL)?;
-        let min_size = context.get_size_property(&MIN_SIZE)?;
-        let max_size = context.get_size_property(&MAX_SIZE)?;
-        let min_age = context.get_duration_property(&MIN_AGE)?;
-        let max_age = context.get_duration_property(&MAX_AGE)?;
-        let batch_size = context
-            .get_u64_property(&BATCH_SIZE)?
-            .expect("required property");
-        let ignore_hidden_files = context
-            .get_bool_property(&IGNORE_HIDDEN_FILES)?
-            .expect("required property");
+        let recursive = context.get_req_property::<bool>(&RECURSE)?;
+
+        let keep_source_file = 
context.get_req_property::<bool>(&KEEP_SOURCE_FILE)?;
+
+        let poll_interval = 
context.get_property::<Duration>(&properties::POLLING_INTERVAL)?;
+        let min_size = context.get_property::<DataSize>(&MIN_SIZE)?;
+        let max_size = context.get_property::<DataSize>(&MAX_SIZE)?;
+        let min_age = context.get_property::<Duration>(&MIN_AGE)?;
+        let max_age = context.get_property::<Duration>(&MAX_AGE)?;
+        let batch_size = context.get_req_property::<u64>(&BATCH_SIZE)?;
+        let ignore_hidden_files = 
context.get_req_property::<bool>(&IGNORE_HIDDEN_FILES)?;
 
         Ok(GetFileRs {
             recursive,
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/properties.rs
 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/get_file/properties.rs
index b43009b28..f27d83841 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,6 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use minifi_native::PropertyConstraints::Validator;
 use minifi_native::{Property, StandardPropertyValidator};
 
 pub(crate) const DIRECTORY: Property = Property {
@@ -24,33 +25,27 @@ pub(crate) const DIRECTORY: Property = Property {
     is_sensitive: false,
     supports_expr_lang: true,
     default_value: None,
-    validator: StandardPropertyValidator::NonBlankValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::NonBlankValidator),
 };
 
 pub(crate) const RECURSE: Property = Property {
     name: "Recurse Subdirectories",
     description: "Indicates whether or not to pull files from subdirectories",
-    is_required: false,
+    is_required: true,
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some("true"),
-    validator: StandardPropertyValidator::BoolValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::BoolValidator),
 };
 
 pub(crate) const KEEP_SOURCE_FILE: Property = Property {
     name: "Keep Source File",
     description: "If true, the file is not deleted after it has been copied to 
the Content Repository",
-    is_required: false,
+    is_required: true,
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some("false"),
-    validator: StandardPropertyValidator::BoolValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::BoolValidator),
 };
 
 pub(crate) const MIN_AGE: Property = Property {
@@ -60,9 +55,7 @@ pub(crate) const MIN_AGE: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::TimePeriodValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::TimePeriodValidator),
 };
 
 pub(crate) const MAX_AGE: Property = Property {
@@ -72,9 +65,7 @@ pub(crate) const MAX_AGE: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::TimePeriodValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::TimePeriodValidator),
 };
 
 pub(crate) const MIN_SIZE: Property = Property {
@@ -84,9 +75,7 @@ pub(crate) const MIN_SIZE: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::DataSizeValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::DataSizeValidator),
 };
 
 pub(crate) const MAX_SIZE: Property = Property {
@@ -96,9 +85,7 @@ pub(crate) const MAX_SIZE: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::DataSizeValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::DataSizeValidator),
 };
 
 pub(crate) const IGNORE_HIDDEN_FILES: Property = Property {
@@ -108,9 +95,7 @@ pub(crate) const IGNORE_HIDDEN_FILES: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some("true"),
-    validator: StandardPropertyValidator::BoolValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::BoolValidator),
 };
 
 pub(crate) const POLLING_INTERVAL: Property = Property {
@@ -120,9 +105,7 @@ pub(crate) const POLLING_INTERVAL: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::TimePeriodValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::TimePeriodValidator),
 };
 
 pub(crate) const BATCH_SIZE: Property = Property {
@@ -132,7 +115,5 @@ pub(crate) const BATCH_SIZE: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some("10"),
-    validator: StandardPropertyValidator::U64Validator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::U64Validator),
 };
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor.rs
 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor.rs
index 6686c0b40..f215d8c78 100644
--- 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor.rs
+++ 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor.rs
@@ -21,15 +21,19 @@ mod properties;
 mod relationships;
 
 use 
crate::controller_services::lorem_ipsum_controller_service::LoremIpsumControllerService;
-use crate::processors::kamikaze_processor::properties::NOT_REGISTERED_PROPERTY;
-use minifi_native::macros::ComponentIdentifier;
+use crate::processors::kamikaze_processor::properties::{
+    NOT_REGISTERED_PROPERTY, SCHEDULE_BEHAVIOUR, TRIGGER_BEHAVIOUR,
+};
+use minifi_native::macros::{ComponentIdentifier, PropertyType};
 use minifi_native::{
     GetProperty, Logger, MinifiError, OnTriggerResult, ProcessContext, 
ProcessSession, Schedule,
     Trigger,
 };
 use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames};
 
-#[derive(Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, 
IntoStaticStr)]
+#[derive(
+    Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, 
IntoStaticStr, PropertyType,
+)]
 #[strum(serialize_all = "PascalCase", const_into_str)]
 enum KamikazeBehaviour {
     ReturnErr,
@@ -49,15 +53,11 @@ impl Schedule for KamikazeProcessorRs {
     where
         Self: Sized,
     {
-        let trigger_behaviour = context
-            .get_property(&properties::TRIGGER_BEHAVIOUR)?
-            .expect("required property")
-            .parse::<KamikazeBehaviour>()?;
+        let trigger_behaviour =
+            context.get_req_property::<KamikazeBehaviour>(&TRIGGER_BEHAVIOUR)?;
 
-        let schedule_behaviour = context
-            .get_property(&properties::SCHEDULE_BEHAVIOUR)?
-            .expect("required property")
-            .parse::<KamikazeBehaviour>()?;
+        let schedule_behaviour =
+            
context.get_req_property::<KamikazeBehaviour>(&SCHEDULE_BEHAVIOUR)?;
 
         match schedule_behaviour {
             KamikazeBehaviour::ReturnErr => Err(MinifiError::schedule_err(
@@ -65,7 +65,7 @@ impl Schedule for KamikazeProcessorRs {
             )),
             KamikazeBehaviour::ReturnOk => Ok(KamikazeProcessorRs { 
trigger_behaviour }),
             KamikazeBehaviour::GetNotRegisteredProperty => {
-                let _ = context.get_property(&NOT_REGISTERED_PROPERTY)?;
+                let _ = 
context.get_property::<String>(&NOT_REGISTERED_PROPERTY)?;
                 Ok(KamikazeProcessorRs { trigger_behaviour })
             }
             KamikazeBehaviour::Panic => {
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/properties.rs
 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/properties.rs
index 6ee86b1bd..1db6c3ada 100644
--- 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/properties.rs
+++ 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/kamikaze_processor/properties.rs
@@ -16,7 +16,8 @@
 // under the License.
 
 use crate::processors::kamikaze_processor::KamikazeBehaviour;
-use minifi_native::{Property, StandardPropertyValidator};
+use minifi_native::Property;
+use minifi_native::PropertyConstraints::{AllowedValues, NoConstraints};
 use strum::VariantNames;
 
 pub(crate) const SCHEDULE_BEHAVIOUR: Property = Property {
@@ -26,9 +27,7 @@ pub(crate) const SCHEDULE_BEHAVIOUR: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some(KamikazeBehaviour::ReturnOk.into_str()),
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: KamikazeBehaviour::VARIANTS,
-    allowed_type: None,
+    constraints: AllowedValues(KamikazeBehaviour::VARIANTS),
 };
 
 pub(crate) const TRIGGER_BEHAVIOUR: Property = Property {
@@ -38,9 +37,7 @@ pub(crate) const TRIGGER_BEHAVIOUR: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some(KamikazeBehaviour::ReturnOk.into_str()),
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: KamikazeBehaviour::VARIANTS,
-    allowed_type: None,
+    constraints: AllowedValues(KamikazeBehaviour::VARIANTS),
 };
 
 pub(crate) const NOT_REGISTERED_PROPERTY: Property = Property {
@@ -50,7 +47,5 @@ pub(crate) const NOT_REGISTERED_PROPERTY: Property = Property 
{
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: NoConstraints,
 };
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute.rs 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute.rs
index 3e59e2d52..c59eb5096 100644
--- 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute.rs
+++ 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute.rs
@@ -121,25 +121,15 @@ impl Trigger for LogAttributeRs {
 
 impl Schedule for LogAttributeRs {
     fn schedule<P: GetProperty, L>(context: &P, _logger: &L) -> Result<Self, 
MinifiError> {
-        let log_level = context
-            .get_property(&LOG_LEVEL)?
-            .expect("required property")
-            .parse::<LogLevel>()?;
-
-        let log_payload = context
-            .get_bool_property(&LOG_PAYLOAD)?
-            .expect("required property");
-
-        let flow_files_to_log = context
-            .get_property(&FLOW_FILES_TO_LOG)?
-            .expect("required property")
-            .parse::<usize>()?;
+        let log_level = context.get_req_property::<LogLevel>(&LOG_LEVEL)?;
+        let log_payload = context.get_req_property::<bool>(&LOG_PAYLOAD)?;
+        let flow_files_to_log = 
context.get_req_property::<usize>(&FLOW_FILES_TO_LOG)?;
 
         fn get_csv_property<P: GetProperty>(
             context: &P,
             property: &Property,
         ) -> Result<Option<Vec<String>>, MinifiError> {
-            Ok(context.get_property(property)?.map(|s| {
+            Ok(context.get_property::<String>(property)?.map(|s| {
                 s.split(',')
                     .map(|s| s.trim().to_string())
                     .collect::<Vec<String>>()
@@ -152,13 +142,12 @@ impl Schedule for LogAttributeRs {
         let dash_line = format!(
             "{:-^50}",
             context
-                .get_property(&properties::LOG_PREFIX)?
-                .unwrap_or(String::new())
+                .get_property::<String>(&properties::LOG_PREFIX)?
+                .unwrap_or_default()
         );
 
-        let hex_encode_payload = context
-            .get_bool_property(&properties::HEX_ENCODE_PAYLOAD)?
-            .expect("required property");
+        let hex_encode_payload =
+            context.get_req_property::<bool>(&properties::HEX_ENCODE_PAYLOAD)?;
 
         Ok(LogAttributeRs {
             log_level,
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute/properties.rs
 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/log_attribute/properties.rs
index 48002c92f..a8bf651ae 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,6 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use minifi_native::PropertyConstraints::{AllowedValues, NoConstraints, 
Validator};
+use minifi_native::StandardPropertyValidator::U64Validator;
 use minifi_native::{LogLevel, Property, StandardPropertyValidator};
 use strum::VariantNames;
 
@@ -24,10 +26,8 @@ pub(crate) const LOG_LEVEL: Property = Property {
     is_required: true,
     is_sensitive: false,
     supports_expr_lang: false,
-    default_value: Some("Info"), // todo! it would be nicer to come from enum 
value but wasnt able to use into_const_str from another crate
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: LogLevel::VARIANTS,
-    allowed_type: None,
+    default_value: Some(LogLevel::Info.into_str()),
+    constraints: AllowedValues(LogLevel::VARIANTS),
 };
 
 pub(crate) const ATTRIBUTES_TO_LOG: Property = Property {
@@ -37,9 +37,7 @@ pub(crate) const ATTRIBUTES_TO_LOG: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: NoConstraints,
 };
 
 pub(crate) const ATTRIBUTES_TO_IGNORE: Property = Property {
@@ -49,9 +47,7 @@ pub(crate) const ATTRIBUTES_TO_IGNORE: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: NoConstraints,
 };
 
 pub(crate) const LOG_PAYLOAD: Property = Property {
@@ -61,9 +57,7 @@ pub(crate) const LOG_PAYLOAD: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some("false"),
-    validator: StandardPropertyValidator::BoolValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::BoolValidator),
 };
 
 pub(crate) const LOG_PREFIX: Property = Property {
@@ -73,9 +67,7 @@ pub(crate) const LOG_PREFIX: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: NoConstraints,
 };
 
 pub(crate) const FLOW_FILES_TO_LOG: Property = Property {
@@ -85,9 +77,7 @@ pub(crate) const FLOW_FILES_TO_LOG: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some("1"),
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(U64Validator),
 };
 
 pub(crate) const HEX_ENCODE_PAYLOAD: Property = Property {
@@ -97,7 +87,5 @@ pub(crate) const HEX_ENCODE_PAYLOAD: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some("false"),
-    validator: StandardPropertyValidator::BoolValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::BoolValidator),
 };
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user.rs
 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user.rs
index 2c4cdb517..b44fcc811 100644
--- 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user.rs
+++ 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user.rs
@@ -21,7 +21,7 @@ mod properties;
 use 
crate::controller_services::lorem_ipsum_controller_service::LoremIpsumControllerService;
 use crate::processors::lorem_ipsum_cs_user::properties::CONTROLLER_SERVICE;
 use crate::processors::lorem_ipsum_cs_user::relationships::SUCCESS;
-use minifi_native::macros::ComponentIdentifier;
+use minifi_native::macros::{ComponentIdentifier, PropertyType};
 use minifi_native::{
     Content, FlowFileSource, GeneratedFlowFile, GetControllerService, 
GetProperty, Logger,
     MinifiError, Schedule, trace,
@@ -29,7 +29,9 @@ use minifi_native::{
 use std::collections::HashMap;
 use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames};
 
-#[derive(Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, 
IntoStaticStr)]
+#[derive(
+    Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, 
IntoStaticStr, PropertyType,
+)]
 #[strum(serialize_all = "PascalCase", const_into_str)]
 enum WriteMethod {
     Buffer,
@@ -46,10 +48,7 @@ impl Schedule for LoremIpsumCSUser {
     where
         Self: Sized,
     {
-        let write_method = context
-            .get_property(&properties::WRITE_METHOD)?
-            .expect("required property")
-            .parse::<WriteMethod>()?;
+        let write_method = 
context.get_req_property::<WriteMethod>(&properties::WRITE_METHOD)?;
         Ok(Self { write_method })
     }
 }
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user/properties.rs
 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/lorem_ipsum_cs_user/properties.rs
index b78ab6d23..57a00ebfa 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
@@ -17,7 +17,8 @@
 
 use 
crate::controller_services::lorem_ipsum_controller_service::LoremIpsumControllerService;
 use minifi_native::ComponentIdentifier;
-use minifi_native::{Property, StandardPropertyValidator};
+use minifi_native::Property;
+use minifi_native::PropertyConstraints::{AllowedType, AllowedValues};
 use strum::VariantNames;
 
 pub(crate) const CONTROLLER_SERVICE: Property = Property {
@@ -27,9 +28,7 @@ pub(crate) const CONTROLLER_SERVICE: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: &[],
-    allowed_type: Some(LoremIpsumControllerService::CLASS_NAME),
+    constraints: AllowedType(LoremIpsumControllerService::CLASS_NAME),
 };
 
 pub(crate) const WRITE_METHOD: Property = Property {
@@ -39,7 +38,5 @@ pub(crate) const WRITE_METHOD: Property = Property {
     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,
+    constraints: AllowedValues(super::WriteMethod::VARIANTS),
 };
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file.rs 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file.rs
index 4d554c132..85bc5fb4a 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,7 +18,7 @@
 // This is the (not production ready) reimplementation of the already existing 
standard PutFile processor
 
 use crate::processors::put_file::relationships::{FAILURE, SUCCESS};
-use minifi_native::macros::ComponentIdentifier;
+use minifi_native::macros::{ComponentIdentifier, PropertyType};
 use minifi_native::{
     FlowFileTransform, GetAttribute, GetControllerService, GetId, GetProperty, 
InputStream, Logger,
     MinifiError, Schedule, TransformedFlowFile, trace, warn,
@@ -32,7 +32,9 @@ mod relationships;
 #[cfg(unix)]
 mod unix_only_properties;
 
-#[derive(Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, 
IntoStaticStr)]
+#[derive(
+    Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, 
IntoStaticStr, PropertyType,
+)]
 #[strum(serialize_all = "camelCase", const_into_str)]
 enum ConflictResolutionStrategy {
     Fail,
@@ -109,14 +111,12 @@ impl PutFileRs {
     where
         Ctx: GetProperty + GetAttribute + GetId,
     {
-        let directory = context
-            .get_property(&properties::DIRECTORY)?
-            .expect("required property");
+        let directory = 
context.get_req_property::<PathBuf>(&properties::DIRECTORY)?;
 
         let file_name = context
             .get_attribute("filename")?
             .unwrap_or(context.get_id()?);
-        Ok(PathBuf::from(directory).join(file_name))
+        Ok(directory.join(file_name))
     }
 
     fn prepare_destination(&self, destination: &Path) -> std::io::Result<()> {
@@ -163,7 +163,7 @@ impl PutFileRs {
         let parse_permission =
             |property: &minifi_native::Property| -> 
Result<Option<std::fs::Permissions>, MinifiError> {
                 Ok(context
-                    .get_property(property)?
+                    .get_property::<String>(property)?
                     .map(|perm_str| u32::from_str_radix(&perm_str, 8))
                     .transpose()?
                     .map(std::fs::Permissions::from_mode))
@@ -188,15 +188,11 @@ impl PutFileRs {
 impl Schedule for PutFileRs {
     fn schedule<P: GetProperty, L: Logger>(context: &P, _logger: &L) -> 
Result<Self, MinifiError> {
         let conflict_resolution_strategy = context
-            .get_property(&properties::CONFLICT_RESOLUTION)?
-            .expect("required property")
-            .parse::<ConflictResolutionStrategy>()?;
+            
.get_req_property::<ConflictResolutionStrategy>(&properties::CONFLICT_RESOLUTION)?;
 
-        let try_make_dirs = context
-            .get_bool_property(&properties::CREATE_DIRS)?
-            .expect("required property");
+        let try_make_dirs = 
context.get_req_property::<bool>(&properties::CREATE_DIRS)?;
 
-        let maximum_file_count = 
context.get_u64_property(&properties::MAX_FILE_COUNT)?;
+        let maximum_file_count = 
context.get_property::<u64>(&properties::MAX_FILE_COUNT)?;
 
         let unix_permissions = Self::parse_unix_permissions(context)?;
 
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/properties.rs
 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/properties.rs
index 4facd860f..f25e8fc47 100644
--- 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/properties.rs
+++ 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/properties.rs
@@ -15,10 +15,10 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use super::ConflictResolutionStrategy;
+use minifi_native::PropertyConstraints::{AllowedValues, Validator};
 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",
@@ -26,9 +26,7 @@ pub(crate) const DIRECTORY: Property = Property {
     is_sensitive: false,
     supports_expr_lang: true,
     default_value: Some("."),
-    validator: StandardPropertyValidator::NonBlankValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::NonBlankValidator),
 };
 
 pub(crate) const CONFLICT_RESOLUTION: Property = Property {
@@ -38,9 +36,7 @@ pub(crate) const CONFLICT_RESOLUTION: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some(ConflictResolutionStrategy::Fail.into_str()),
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: ConflictResolutionStrategy::VARIANTS,
-    allowed_type: None,
+    constraints: AllowedValues(ConflictResolutionStrategy::VARIANTS),
 };
 
 pub(crate) const CREATE_DIRS: Property = Property {
@@ -50,9 +46,7 @@ pub(crate) const CREATE_DIRS: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: Some("true"),
-    validator: StandardPropertyValidator::BoolValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::BoolValidator),
 };
 
 pub(crate) const MAX_FILE_COUNT: Property = Property {
@@ -62,7 +56,5 @@ pub(crate) const MAX_FILE_COUNT: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None, // Diverged from the original implementation, u64 
with no default describes the behavior better
-    validator: StandardPropertyValidator::U64Validator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: Validator(StandardPropertyValidator::U64Validator),
 };
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/unix_only_properties.rs
 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/unix_only_properties.rs
index f7cbd7811..e5e99f229 100644
--- 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/unix_only_properties.rs
+++ 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/unix_only_properties.rs
@@ -15,7 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use minifi_native::{Property, StandardPropertyValidator};
+use minifi_native::Property;
+use minifi_native::PropertyConstraints::NoConstraints;
 
 pub(crate) const PERMISSIONS: Property = Property {
     name: "Permissions",
@@ -24,9 +25,7 @@ pub(crate) const PERMISSIONS: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: NoConstraints,
 };
 
 pub(crate) const DIRECTORY_PERMISSIONS: Property = Property {
@@ -36,7 +35,5 @@ pub(crate) const DIRECTORY_PERMISSIONS: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: &[],
-    allowed_type: None,
+    constraints: NoConstraints,
 };
diff --git 
a/minifi_rust/extensions/minifi_rs_playground/src/processors/zoo_processor.rs 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/zoo_processor.rs
index 2bbdb9a55..dc166722d 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
@@ -19,11 +19,12 @@ use crate::controller_services::animal_controller_apis::{
     CanFlyControllerApi, NumberOfLegsControllerApi,
 };
 use minifi_native::ControllerServiceApi;
+use minifi_native::PropertyConstraints::AllowedType;
 use minifi_native::macros::ComponentIdentifier;
 use minifi_native::{
     GetProperty, Logger, MinifiError, OnTriggerResult, OutputAttribute, 
ProcessContext,
     ProcessSession, ProcessorDefinition, ProcessorInputRequirement, Property, 
Relationship,
-    Schedule, StandardPropertyValidator, Trigger, critical, info,
+    Schedule, Trigger, critical, info,
 };
 
 pub(crate) const CAN_FLY_SERVICE: Property = Property {
@@ -33,9 +34,7 @@ pub(crate) const CAN_FLY_SERVICE: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: &[],
-    allowed_type: Some(<dyn CanFlyControllerApi>::INTERFACE_NAME),
+    constraints: AllowedType(<dyn CanFlyControllerApi>::INTERFACE_NAME),
 };
 
 pub(crate) const NUMBER_OF_LEGS: Property = Property {
@@ -45,9 +44,7 @@ pub(crate) const NUMBER_OF_LEGS: Property = Property {
     is_sensitive: false,
     supports_expr_lang: false,
     default_value: None,
-    validator: StandardPropertyValidator::AlwaysValidValidator,
-    allowed_values: &[],
-    allowed_type: Some(<dyn NumberOfLegsControllerApi>::INTERFACE_NAME),
+    constraints: AllowedType(<dyn NumberOfLegsControllerApi>::INTERFACE_NAME),
 };
 
 #[derive(Debug, ComponentIdentifier)]
diff --git a/minifi_rust/minifi_native/Cargo.toml 
b/minifi_rust/minifi_native/Cargo.toml
index 5998c4fd3..b3f75648b 100644
--- a/minifi_rust/minifi_native/Cargo.toml
+++ b/minifi_rust/minifi_native/Cargo.toml
@@ -13,3 +13,6 @@ strum_macros = "0.28.0"
 humantime = "2.3.0"
 byte-unit = "5.1.6"
 itertools = "0.14.0"
+
+[features]
+test-utils = []
diff --git a/minifi_rust/minifi_native/src/api.rs 
b/minifi_rust/minifi_native/src/api.rs
index 6152b94ca..066fc91c3 100644
--- a/minifi_rust/minifi_native/src/api.rs
+++ b/minifi_rust/minifi_native/src/api.rs
@@ -38,6 +38,6 @@ pub use process_session::{InputStream, OutputStream, 
ProcessSession};
 pub use raw_controller_service::RawControllerService;
 pub use raw_processor::{OnTriggerResult, ProcessorInputRequirement, 
RawProcessor, ThreadingModel};
 
-pub use property::StandardPropertyValidator;
+pub use property::{DataSize, PropertyConstraints, PropertyType, 
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 9e6b316eb..571ea7ce7 100644
--- a/minifi_rust/minifi_native/src/api/attribute.rs
+++ b/minifi_rust/minifi_native/src/api/attribute.rs
@@ -25,4 +25,8 @@ pub struct OutputAttribute {
 
 pub trait GetAttribute {
     fn get_attribute(&self, name: &str) -> Result<Option<String>, MinifiError>;
+    fn get_required_attribute(&self, name: &str) -> Result<String, 
MinifiError> {
+        self.get_attribute(name)?
+            .ok_or(MinifiError::missing_required_attribute(name.to_owned()))
+    }
 }
diff --git a/minifi_rust/minifi_native/src/api/errors.rs 
b/minifi_rust/minifi_native/src/api/errors.rs
index 633d413ad..2c833a997 100644
--- a/minifi_rust/minifi_native/src/api/errors.rs
+++ b/minifi_rust/minifi_native/src/api/errors.rs
@@ -17,9 +17,10 @@
 
 use minifi_native_sys::minifi_status;
 use std::borrow::Cow;
+use std::error::Error;
 use std::ffi::NulError;
 use std::fmt;
-use std::num::{NonZeroU32, ParseIntError};
+use std::num::{NonZeroU32, ParseFloatError, ParseIntError};
 use std::str::ParseBoolError;
 
 #[derive(Debug, Clone)]
@@ -30,6 +31,7 @@ pub enum ParseError {
     Duration(humantime::DurationError),
     Size(byte_unit::ParseError),
     Nul(NulError),
+    Float(ParseFloatError),
     Other,
 }
 
@@ -37,6 +39,7 @@ pub enum ParseError {
 pub enum MinifiError {
     UnknownError,
     StatusError((Cow<'static, str>, NonZeroU32)),
+    MissingRequiredAttribute(Cow<'static, str>),
     MissingRequiredProperty(Cow<'static, str>),
     ControllerServiceError(Cow<'static, str>),
     ValidationError(Cow<'static, str>),
@@ -89,6 +92,18 @@ impl From<NulError> for MinifiError {
     }
 }
 
+impl From<ParseFloatError> for MinifiError {
+    fn from(err: ParseFloatError) -> Self {
+        MinifiError::Parse(ParseError::Float(err))
+    }
+}
+
+impl From<std::convert::Infallible> for MinifiError {
+    fn from(_: std::convert::Infallible) -> Self {
+        unreachable!("Infallible errors can never happen")
+    }
+}
+
 impl MinifiError {
     pub(crate) fn to_status(&self) -> minifi_status {
         match self {
@@ -125,9 +140,17 @@ impl MinifiError {
         MinifiError::MissingRequiredProperty(msg.into())
     }
 
+    pub fn missing_required_attribute<S: Into<Cow<'static, str>>>(msg: S) -> 
Self {
+        MinifiError::MissingRequiredAttribute(msg.into())
+    }
+
     pub fn controller_service_err<S: Into<Cow<'static, str>>>(msg: S) -> Self {
         MinifiError::ControllerServiceError(msg.into())
     }
+
+    pub fn parse_err() -> Self {
+        MinifiError::Parse(ParseError::Other)
+    }
 }
 
 impl fmt::Display for MinifiError {
@@ -158,3 +181,5 @@ impl fmt::Display for MinifiError {
         }
     }
 }
+
+impl Error for MinifiError {}
diff --git a/minifi_rust/minifi_native/src/api/logger.rs 
b/minifi_rust/minifi_native/src/api/logger.rs
index b7df51817..80a20b6f6 100644
--- a/minifi_rust/minifi_native/src/api/logger.rs
+++ b/minifi_rust/minifi_native/src/api/logger.rs
@@ -18,9 +18,23 @@
 use std::fmt;
 use std::fmt::Debug;
 
-use strum_macros::{Display, EnumString, VariantNames};
+use minifi_native_macros::PropertyType;
+use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames};
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Display, 
EnumString, VariantNames)]
+#[derive(
+    Debug,
+    Clone,
+    Copy,
+    PartialEq,
+    Eq,
+    PartialOrd,
+    Ord,
+    Display,
+    EnumString,
+    VariantNames,
+    IntoStaticStr,
+    PropertyType,
+)]
 #[strum(serialize_all = "PascalCase", const_into_str)]
 pub enum LogLevel {
     Trace,
diff --git a/minifi_rust/minifi_native/src/api/process_context.rs 
b/minifi_rust/minifi_native/src/api/process_context.rs
index 0f114cf47..528dceb42 100644
--- a/minifi_rust/minifi_native/src/api/process_context.rs
+++ b/minifi_rust/minifi_native/src/api/process_context.rs
@@ -15,7 +15,6 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use crate::StandardPropertyValidator::*;
 use crate::api::RawControllerService;
 use crate::api::component_definition_traits::ComponentIdentifier;
 use crate::api::flow_file::FlowFile;
@@ -24,8 +23,6 @@ use crate::{
     ControllerServiceApi, ControllerServiceDefinition, 
EnableControllerService, GetProperty,
     MinifiError, Property,
 };
-use std::str::FromStr;
-use std::time::Duration;
 
 pub trait ProcessContext {
     type FlowFile: FlowFile;
@@ -36,80 +33,6 @@ pub trait ProcessContext {
         flow_file: Option<&Self::FlowFile>,
     ) -> Result<Option<String>, MinifiError>;
 
-    fn get_bool_property(
-        &self,
-        property: &Property,
-        flow_file: Option<&Self::FlowFile>,
-    ) -> Result<Option<bool>, MinifiError> {
-        if property.validator != BoolValidator {
-            return Err(MinifiError::validation_err(format!(
-                "to use get_bool_property {:?} must have BoolValidator",
-                property
-            )));
-        }
-
-        if let Some(property_val) = self.get_property(property, flow_file)? {
-            Ok(Some(bool::from_str(&property_val)?))
-        } else {
-            Ok(None)
-        }
-    }
-
-    fn get_duration_property(
-        &self,
-        property: &Property,
-        flow_file: Option<&Self::FlowFile>,
-    ) -> Result<Option<Duration>, MinifiError> {
-        if property.validator != TimePeriodValidator {
-            return Err(MinifiError::validation_err(format!(
-                "to use get_duration_property {:?} must have 
TimePeriodValidator",
-                property
-            )));
-        }
-
-        if let Some(property_val) = self.get_property(property, flow_file)? {
-            Ok(Some(humantime::parse_duration(property_val.as_str())?))
-        } else {
-            Ok(None)
-        }
-    }
-
-    fn get_size_property(
-        &self,
-        property: &Property,
-        flow_file: Option<&Self::FlowFile>,
-    ) -> Result<Option<u64>, MinifiError> {
-        if property.validator != DataSizeValidator {
-            return Err(MinifiError::validation_err(format!(
-                "to use get_size_property {:?} must have DataSizeValidator",
-                property
-            )));
-        }
-        if let Some(property_val) = self.get_property(property, flow_file)? {
-            Ok(Some(byte_unit::Byte::from_str(&property_val)?.as_u64()))
-        } else {
-            Ok(None)
-        }
-    }
-
-    fn get_u64_property(
-        &self,
-        property: &Property,
-        flow_file: Option<&Self::FlowFile>,
-    ) -> Result<Option<u64>, MinifiError> {
-        if property.validator != U64Validator {
-            return Err(MinifiError::validation_err(format!(
-                "to use get_u64_property {:?} must have U64Validator",
-                property
-            )));
-        }
-        if let Some(property_val) = self.get_property(property, flow_file)? {
-            Ok(Some(u64::from_str(&property_val)?))
-        } else {
-            Ok(None)
-        }
-    }
-
     fn get_raw_controller_service<Cs>(
         &self,
         property: &Property,
@@ -133,7 +56,7 @@ impl<S> GetProperty for S
 where
     S: ProcessContext,
 {
-    fn get_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError> {
+    fn get_raw_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError> {
         self.get_property(property, None)
     }
 }
diff --git 
a/minifi_rust/minifi_native/src/api/processor_wrappers/flow_file_transform.rs 
b/minifi_rust/minifi_native/src/api/processor_wrappers/flow_file_transform.rs
index f856ca83d..797542dcd 100644
--- 
a/minifi_rust/minifi_native/src/api/processor_wrappers/flow_file_transform.rs
+++ 
b/minifi_rust/minifi_native/src/api/processor_wrappers/flow_file_transform.rs
@@ -67,6 +67,19 @@ impl<'a> TransformedFlowFile<'a> {
     pub fn attributes_to_add(&self) -> &HashMap<String, String> {
         &self.attributes_to_add
     }
+
+    #[cfg(any(test, feature = "test-utils"))]
+    pub fn into_bytes(self) -> std::io::Result<Option<Vec<u8>>> {
+        match self.new_content {
+            Some(Content::Buffer(vec)) => Ok(Some(vec)),
+            Some(Content::Stream(mut stream)) => {
+                let mut buffer = Vec::new();
+                stream.read_to_end(&mut buffer)?;
+                Ok(Some(buffer))
+            }
+            None => Ok(None),
+        }
+    }
 }
 
 pub trait FlowFileTransform {
@@ -200,3 +213,42 @@ where
         }
     }
 }
+
+#[macro_export]
+macro_rules! unwrap_or_route {
+    ($result:expr, $route:expr) => {
+        match $result {
+            Ok(v) => v,
+            Err(e) => {
+                return Ok(TransformedFlowFile::route_without_changes($route));
+            }
+        }
+    };
+
+    ($result:expr, $route:expr, $custom_logger:expr) => {
+        match $result {
+            Ok(v) => v,
+            Err(e) => {
+                minifi_native::error!(
+                    $custom_logger,
+                    "Failed to unwrap due to {}. Routing flow file.",
+                    e
+                );
+                return Ok(TransformedFlowFile::route_without_changes($route));
+            }
+        }
+    };
+
+    ($result:expr, $route:expr, $custom_logger:expr, $context:expr) => {
+        match $result {
+            Ok(v) => v,
+            Err(e) => {
+                error!(
+                    $custom_logger,
+                    "Failed to {} due to {}. Routing flow file.", $context, e
+                );
+                return Ok(TransformedFlowFile::route_without_changes($route));
+            }
+        }
+    };
+}
diff --git 
a/minifi_rust/minifi_native/src/api/processor_wrappers/utils/context_session_flowfile_bundle.rs
 
b/minifi_rust/minifi_native/src/api/processor_wrappers/utils/context_session_flowfile_bundle.rs
index 8745b2fb7..c05942066 100644
--- 
a/minifi_rust/minifi_native/src/api/processor_wrappers/utils/context_session_flowfile_bundle.rs
+++ 
b/minifi_rust/minifi_native/src/api/processor_wrappers/utils/context_session_flowfile_bundle.rs
@@ -55,7 +55,7 @@ where
     PC: ProcessContext,
     PS: ProcessSession<FlowFile = PC::FlowFile>,
 {
-    fn get_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError> {
+    fn get_raw_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError> {
         self.context.get_property(property, self.flow_file)
     }
 }
diff --git a/minifi_rust/minifi_native/src/api/property.rs 
b/minifi_rust/minifi_native/src/api/property.rs
index 952b6eb68..52b2adb31 100644
--- a/minifi_rust/minifi_native/src/api/property.rs
+++ b/minifi_rust/minifi_native/src/api/property.rs
@@ -15,6 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use crate::PropertyConstraints::NoConstraints;
 use crate::StandardPropertyValidator::{
     BoolValidator, DataSizeValidator, TimePeriodValidator, U64Validator,
 };
@@ -26,7 +27,6 @@ use std::time::Duration;
 
 #[derive(Debug, Eq, PartialEq)]
 pub enum StandardPropertyValidator {
-    AlwaysValidValidator,
     NonBlankValidator,
     TimePeriodValidator,
     BoolValidator,
@@ -36,6 +36,20 @@ pub enum StandardPropertyValidator {
     PortValidator,
 }
 
+#[derive(Debug, PartialEq)]
+pub enum PropertyConstraints {
+    NoConstraints,
+    Validator(StandardPropertyValidator),
+    AllowedValues(&'static [&'static str]),
+    AllowedType(&'static str),
+}
+
+impl From<StandardPropertyValidator> for PropertyConstraints {
+    fn from(value: StandardPropertyValidator) -> Self {
+        PropertyConstraints::Validator(value)
+    }
+}
+
 #[derive(Debug)]
 pub struct Property {
     pub name: &'static str,
@@ -44,69 +58,100 @@ pub struct Property {
     pub is_sensitive: bool,
     pub supports_expr_lang: bool,
     pub default_value: Option<&'static str>,
-    pub validator: StandardPropertyValidator,
-    pub allowed_values: &'static [&'static str],
-    pub allowed_type: Option<&'static str>,
+    pub constraints: PropertyConstraints,
 }
 
-pub trait GetProperty {
-    fn get_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError>;
-    fn get_bool_property(&self, property: &Property) -> Result<Option<bool>, 
MinifiError> {
-        if property.validator != BoolValidator {
-            return Err(MinifiError::validation_err(format!(
-                "to use get_bool_property {:?} must have BoolValidator",
-                property
-            )));
-        }
+pub trait PropertyType {
+    type Output;
+    const EXPECTED_CONSTRAINTS: PropertyConstraints = 
PropertyConstraints::NoConstraints;
 
-        if let Some(property_val) = self.get_property(property)? {
-            Ok(Some(bool::from_str(&property_val)?))
-        } else {
-            Ok(None)
-        }
-    }
+    fn parse(s: &str) -> Result<Self::Output, MinifiError>;
+}
 
-    fn get_duration_property(&self, property: &Property) -> 
Result<Option<Duration>, MinifiError> {
-        if property.validator != TimePeriodValidator {
-            return Err(MinifiError::validation_err(format!(
-                "to use get_duration_property {:?} must have 
TimePeriodValidator",
-                property
-            )));
-        }
+macro_rules! impl_from_str_property {
+    ($t:ty, $validator:expr) => {
+        impl PropertyType for $t {
+            type Output = $t;
+            const EXPECTED_CONSTRAINTS: PropertyConstraints = $validator;
 
-        if let Some(property_val) = self.get_property(property)? {
-            Ok(Some(humantime::parse_duration(property_val.as_str())?))
-        } else {
-            Ok(None)
+            fn parse(s: &str) -> Result<Self::Output, MinifiError> {
+                s.parse::<$t>().map_err(Into::into)
+            }
         }
+    };
+    ($t:ty) => {
+        impl_from_str_property!($t, PropertyConstraints::NoConstraints);
+    };
+}
+
+impl_from_str_property!(String);
+impl_from_str_property!(std::path::PathBuf);
+impl_from_str_property!(f64);
+impl_from_str_property!(f32);
+impl_from_str_property!(i64);
+impl_from_str_property!(i32);
+impl_from_str_property!(bool, PropertyConstraints::Validator(BoolValidator));
+impl_from_str_property!(u64, PropertyConstraints::Validator(U64Validator));
+impl_from_str_property!(u32, PropertyConstraints::Validator(U64Validator));
+impl_from_str_property!(usize, PropertyConstraints::Validator(U64Validator));
+
+impl PropertyType for Duration {
+    type Output = Duration;
+    const EXPECTED_CONSTRAINTS: PropertyConstraints =
+        PropertyConstraints::Validator(TimePeriodValidator);
+    fn parse(s: &str) -> Result<Self::Output, MinifiError> {
+        humantime::parse_duration(s).map_err(Into::into)
+    }
+}
+
+pub struct DataSize;
+impl PropertyType for DataSize {
+    type Output = u64;
+    const EXPECTED_CONSTRAINTS: PropertyConstraints =
+        PropertyConstraints::Validator(DataSizeValidator);
+    fn parse(s: &str) -> Result<Self::Output, MinifiError> {
+        byte_unit::Byte::from_str(s)
+            .map(|b| b.as_u64())
+            .map_err(Into::into)
     }
+}
+
+pub trait GetProperty {
+    fn get_raw_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError>;
 
-    fn get_size_property(&self, property: &Property) -> Result<Option<u64>, 
MinifiError> {
-        if property.validator != DataSizeValidator {
+    fn get_property<T: PropertyType>(
+        &self,
+        property: &Property,
+    ) -> Result<Option<T::Output>, MinifiError> {
+        if T::EXPECTED_CONSTRAINTS != NoConstraints
+            && T::EXPECTED_CONSTRAINTS != property.constraints
+        {
             return Err(MinifiError::validation_err(format!(
-                "to use get_size_property {:?} must have DataSizeValidator",
-                property
+                "to use get_property for this type, {:?} must have validator 
{:?}",
+                property.name,
+                T::EXPECTED_CONSTRAINTS
             )));
         }
-        if let Some(property_val) = self.get_property(property)? {
-            Ok(Some(byte_unit::Byte::from_str(&property_val)?.as_u64()))
+
+        if let Some(property_val) = self.get_raw_property(property)? {
+            Ok(Some(T::parse(&property_val)?))
         } else {
             Ok(None)
         }
     }
 
-    fn get_u64_property(&self, property: &Property) -> Result<Option<u64>, 
MinifiError> {
-        if property.validator != U64Validator {
+    fn get_req_property<T: PropertyType>(
+        &self,
+        property: &Property,
+    ) -> Result<T::Output, MinifiError> {
+        if !property.is_required {
             return Err(MinifiError::validation_err(format!(
-                "to use get_u64_property {:?} must have U64Validator",
-                property
+                "to use get_req_property, {:?} must be required",
+                property.name
             )));
         }
-        if let Some(property_val) = self.get_property(property)? {
-            Ok(Some(u64::from_str(&property_val)?))
-        } else {
-            Ok(None)
-        }
+        self.get_property::<T>(property)?
+            .ok_or_else(|| 
MinifiError::missing_required_property(property.name))
     }
 }
 
diff --git a/minifi_rust/minifi_native/src/api/relationship.rs 
b/minifi_rust/minifi_native/src/api/relationship.rs
index 45041a2ba..df3e34081 100644
--- a/minifi_rust/minifi_native/src/api/relationship.rs
+++ b/minifi_rust/minifi_native/src/api/relationship.rs
@@ -15,8 +15,16 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use std::fmt::{Display, Formatter};
+
 #[derive(Debug, Eq, PartialEq)]
 pub struct Relationship {
     pub name: &'static str,
     pub description: &'static str,
 }
+
+impl Display for Relationship {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.name)
+    }
+}
diff --git 
a/minifi_rust/minifi_native/src/c_ffi/c_ffi_controller_service_context.rs 
b/minifi_rust/minifi_native/src/c_ffi/c_ffi_controller_service_context.rs
index 2284c8dc7..2c285fd4f 100644
--- a/minifi_rust/minifi_native/src/c_ffi/c_ffi_controller_service_context.rs
+++ b/minifi_rust/minifi_native/src/c_ffi/c_ffi_controller_service_context.rs
@@ -61,7 +61,7 @@ unsafe extern "C" fn property_callback(
 }
 
 impl<'a> GetProperty for CffiControllerServiceContext<'a> {
-    fn get_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError> {
+    fn get_raw_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError> {
         let mut result: Option<String> = None;
         let property_name: StringView = StringView::new(property.name);
 
diff --git a/minifi_rust/minifi_native/src/c_ffi/c_ffi_property.rs 
b/minifi_rust/minifi_native/src/c_ffi/c_ffi_property.rs
index d2fe2c1b7..05d23b0c2 100644
--- a/minifi_rust/minifi_native/src/c_ffi/c_ffi_property.rs
+++ b/minifi_rust/minifi_native/src/c_ffi/c_ffi_property.rs
@@ -16,6 +16,7 @@
 // under the License.
 
 use super::c_ffi_primitives::StaticStrAsMinifiCStr;
+use crate::api::property::PropertyConstraints;
 use crate::{Property, StandardPropertyValidator};
 use minifi_native_sys::{
     minifi_property_definition, minifi_string_view, minifi_validator,
@@ -76,11 +77,14 @@ impl Property {
     fn create_c_allowed_values_vec_vec(properties: &[Self]) -> 
Vec<Vec<minifi_string_view>> {
         properties
             .iter()
-            .map(|p| {
-                p.allowed_values
+            .map(|p| match p.constraints {
+                PropertyConstraints::AllowedValues(allowed_values) => 
allowed_values
                     .iter()
                     .map(|av| av.as_minifi_c_type())
-                    .collect()
+                    .collect(),
+                _ => {
+                    vec![]
+                }
             })
             .collect()
     }
@@ -88,9 +92,9 @@ impl Property {
     fn create_c_allowed_types_vec(properties: &[Self]) -> 
Vec<minifi_string_view> {
         properties
             .iter()
-            .map(|p| match p.allowed_type {
-                Some(dv) => dv.as_minifi_c_type(),
-                None => minifi_string_view {
+            .map(|p| match p.constraints {
+                PropertyConstraints::AllowedType(allowed_type) => 
allowed_type.as_minifi_c_type(),
+                _ => minifi_string_view {
                     data: ptr::null(),
                     length: 0,
                 },
@@ -125,7 +129,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 {
+                        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 +154,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 c467b68e8..104eee006 100644
--- a/minifi_rust/minifi_native/src/lib.rs
+++ b/minifi_rust/minifi_native/src/lib.rs
@@ -14,6 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
+extern crate self as minifi_native;
 
 mod api;
 pub mod c_ffi;
@@ -54,8 +55,9 @@ pub use api::process_session::IoState;
 pub use api::attribute::{GetAttribute, OutputAttribute};
 
 pub use api::{
-    FlowFile, GetId, InputStream, OnTriggerResult, OutputStream, 
ProcessContext, ProcessSession,
-    ProcessorInputRequirement, Relationship, StandardPropertyValidator,
+    DataSize, FlowFile, GetId, InputStream, OnTriggerResult, OutputStream, 
ProcessContext,
+    ProcessSession, ProcessorInputRequirement, PropertyConstraints, 
PropertyType, 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 2a594ef8a..f67bd5008 100644
--- a/minifi_rust/minifi_native/src/mock/mock_controller_service_context.rs
+++ b/minifi_rust/minifi_native/src/mock/mock_controller_service_context.rs
@@ -23,7 +23,7 @@ pub struct MockControllerServiceContext {
 }
 
 impl GetProperty for MockControllerServiceContext {
-    fn get_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError> {
+    fn get_raw_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError> {
         self.properties.get_property(property, None)
     }
 }
diff --git a/minifi_rust/minifi_native_macros/src/lib.rs 
b/minifi_rust/minifi_native_macros/src/lib.rs
index fa8e56336..10f24656d 100644
--- a/minifi_rust/minifi_native_macros/src/lib.rs
+++ b/minifi_rust/minifi_native_macros/src/lib.rs
@@ -52,3 +52,24 @@ pub fn controller_service_api(_attr: TokenStream, item: 
TokenStream) -> TokenStr
 
     TokenStream::from(expanded)
 }
+
+#[proc_macro_derive(PropertyType)]
+pub fn derive_property_type(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    let name = &input.ident;
+
+    let expanded = quote! {
+        impl ::minifi_native::PropertyType for #name {
+            type Output = #name;
+            const EXPECTED_CONSTRAINTS: ::minifi_native::PropertyConstraints =
+                ::minifi_native::PropertyConstraints::AllowedValues(
+                    <#name as ::strum::VariantNames>::VARIANTS
+                );
+            fn parse(s: &str) -> Result<Self::Output, 
::minifi_native::MinifiError> {
+                s.parse::<#name>().map_err(Into::into)
+            }
+        }
+    };
+
+    TokenStream::from(expanded)
+}
diff --git a/minifi_rust/minifi_rs_behave/Dockerfile.alpine 
b/minifi_rust/minifi_rs_behave/Dockerfile.alpine
index 032c26ec5..dd0cd8a40 100644
--- a/minifi_rust/minifi_rs_behave/Dockerfile.alpine
+++ b/minifi_rust/minifi_rs_behave/Dockerfile.alpine
@@ -23,3 +23,4 @@ RUN cargo build --release
 # Export Stage
 FROM scratch AS bin-export
 COPY --from=builder /app/target/release/libminifi_rs_playground.so /
+COPY --from=builder /app/target/release/libminifi_tensor.so /
diff --git a/minifi_rust/minifi_rs_behave/Dockerfile.debian 
b/minifi_rust/minifi_rs_behave/Dockerfile.debian
index 274ed3cb5..db44c5b49 100644
--- a/minifi_rust/minifi_rs_behave/Dockerfile.debian
+++ b/minifi_rust/minifi_rs_behave/Dockerfile.debian
@@ -1,5 +1,8 @@
-FROM rust:slim-bullseye AS chef
-RUN apt-get update && apt-get install -y clang lld pkg-config curl tar && 
cargo install cargo-chef
+FROM rust:slim-bookworm AS chef
+RUN apt update && apt install -y wget software-properties-common gnupg sudo
+RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor -o 
/usr/share/keyrings/llvm-snapshot.gpg
+RUN echo "deb [signed-by=/usr/share/keyrings/llvm-snapshot.gpg] 
http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" | tee 
/etc/apt/sources.list.d/llvm-toolchain-18.list
+RUN apt update && apt install -y clang-18 lld-18 clangd-18 libclang-18-dev lld 
pkg-config curl tar && cargo install cargo-chef
 WORKDIR /app
 
 FROM chef AS planner
@@ -23,3 +26,4 @@ RUN cargo build --release
 # Export Stage
 FROM scratch AS bin-export
 COPY --from=builder /app/target/release/libminifi_rs_playground.so /
+COPY --from=builder /app/target/release/libminifi_tensor.so /
diff --git a/minifi_rust/minifi_rs_behave/src/main.rs 
b/minifi_rust/minifi_rs_behave/src/main.rs
index f0d8c12bc..2e8517f09 100644
--- a/minifi_rust/minifi_rs_behave/src/main.rs
+++ b/minifi_rust/minifi_rs_behave/src/main.rs
@@ -95,6 +95,7 @@ impl BehaveRunner {
             .arg("pip")
             .arg("install")
             .arg(self.minifi_behave_path.to_string_lossy().as_ref())
+            .arg("certifi")
             .status()
             .expect("Failed to install dependencies")
     }

Reply via email to