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

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

commit 5bbfdc8ef7181211b02328be71cb780700f2d761
Author: Martin Zink <[email protected]>
AuthorDate: Thu Jul 30 18:43:57 2026 +0200

    property rework
---
 .../features/controller_apis.feature               |   2 +-
 .../minifi_rs_playground/minifi_rs_playground.md   |  30 +--
 .../controller_services/dog_controller_service.rs  |  41 ++--
 .../controller_services/duck_controller_service.rs |   4 +-
 .../dummy_controller_service.rs                    |   4 +-
 .../lorem_ipsum_controller_service.rs              |  17 +-
 .../asciify_german/processor_definition.rs         |   5 +-
 .../src/processors/count_actual_logging.rs         |   6 +-
 .../src/processors/duplicate_text.rs               |   4 +-
 .../src/processors/generate_flow_file.rs           |  30 ++-
 .../generate_flow_file/processor_definition.rs     |   7 +-
 .../processors/generate_flow_file/properties.rs    |  73 +++---
 .../src/processors/generate_flow_file/tests.rs     |  30 +--
 .../src/processors/get_file.rs                     |  24 +-
 .../processors/get_file/processor_definition.rs    |   5 +-
 .../src/processors/get_file/properties.rs          | 137 ++++--------
 .../src/processors/get_file/tests.rs               |  18 +-
 .../src/processors/kamikaze_processor.rs           |  17 +-
 .../kamikaze_processor/processor_definition.rs     |   7 +-
 .../processors/kamikaze_processor/properties.rs    |  48 ++--
 .../src/processors/kamikaze_processor/tests.rs     |  18 +-
 .../src/processors/log_attribute.rs                |  46 ++--
 .../log_attribute/processor_definition.rs          |   5 +-
 .../src/processors/log_attribute/properties.rs     | 112 ++++------
 .../src/processors/lorem_ipsum_cs_user.rs          |  18 +-
 .../lorem_ipsum_cs_user/processor_definition.rs    |   6 +-
 .../processors/lorem_ipsum_cs_user/properties.rs   |  31 +--
 .../src/processors/put_file.rs                     |  66 +-----
 .../processors/put_file/processor_definition.rs    |  38 ++--
 .../src/processors/put_file/properties.rs          |  57 ++---
 .../processors/put_file/unix_only_properties.rs    |  27 +--
 .../src/processors/put_file/unix_permissions.rs    |  60 +++++
 .../src/processors/zoo_processor.rs                |  38 +---
 minifi_rust/minifi_native/src/api.rs               |   3 +-
 .../src/api/component_definition_traits.rs         |   6 +-
 .../minifi_native/src/api/process_context.rs       |  47 ++--
 .../utils/context_session_flowfile_bundle.rs       |  23 +-
 minifi_rust/minifi_native/src/api/property.rs      | 247 +++++++++++++++------
 .../src/c_ffi/c_ffi_controller_service_context.rs  |  14 +-
 .../c_ffi/c_ffi_controller_service_definition.rs   |   6 +-
 .../src/c_ffi/c_ffi_process_context.rs             |  54 ++---
 .../src/c_ffi/c_ffi_processor_definition.rs        |   6 +-
 .../minifi_native/src/c_ffi/c_ffi_property.rs      |  10 +-
 minifi_rust/minifi_native/src/lib.rs               |  11 +-
 .../src/mock/mock_controller_service_context.rs    |   6 +-
 .../minifi_native/src/mock/mock_process_context.rs |  46 ++--
 minifi_rust/minifi_native_macros/src/lib.rs        |  22 +-
 47 files changed, 764 insertions(+), 768 deletions(-)

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 c93f89616..8bbc2dd7e 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 4078b7301..e05adf0e0 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<br/>false   | Indicates 
whether or not hidden files should be ignored                                   
                                                                 |
-| **Input Directory**     |               |                  | The input 
directory from which to pull files<br/>**Supports Expression Language: true**   
                                                                 |
-| Keep Source File        | false         | true<br/>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<br/>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<br/>false   | Indicates 
whether or not hidden files should be ignored                                   
                                                                 |
+| **Input Directory**        |               |                  | The input 
directory from which to pull files<br/>**Supports Expression Language: true**   
                                                                 |
+| **Keep Source File**       | false         | true<br/>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<br/>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 40d2357b3..d3307a3b5 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,37 +19,25 @@ use crate::controller_services::animal_controller_apis::{
     CanFlyControllerApi, NumberOfLegsControllerApi,
 };
 use minifi_native::macros::ComponentIdentifier;
-use minifi_native::{ControllerServiceApi, property_constraint};
 use minifi_native::{
-    ControllerServiceDefinition, EnableControllerService, GetProperty, Logger, 
MinifiError,
-    Property, ProvidedInterface, 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"),
-    constraints: property_constraint::<bool>(),
-};
+pub(crate) const HAS_JETPACK: Property<bool> =
+    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,
-    constraints: None,
-};
+pub(crate) const EXTRA_INFO: Property<Option<String>> = 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<String>,
 }
 
 impl NumberOfLegsControllerApi for DogControllerRs {
@@ -69,10 +57,8 @@ impl EnableControllerService for DogControllerRs {
     where
         Self: Sized,
     {
-        let has_jetpack = context.get_req_property::<bool>(&HAS_JETPACK)?;
-        let extra_info = context
-            .get_property::<String>(&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,
@@ -83,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<Self>] = &[
         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 d1d2518a1..5072e4314 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<Self>] = &[
         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 2b593b9af..ae38805c4 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<Self>] = &[];
 }
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 1f1ff5720..7dbb2309d 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,18 +19,11 @@ use lipsum::lipsum;
 use minifi_native::macros::ComponentIdentifier;
 use minifi_native::{
     ControllerServiceDefinition, EnableControllerService, GetProperty, Logger, 
MinifiError,
-    Property, ProvidedInterface, property_constraint,
+    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"),
-    constraints: property_constraint::<u64>(),
-};
+const LENGTH: Property<usize> =
+    Property::new("Length", "How many words to generate").with_default("25");
 
 #[derive(Debug, ComponentIdentifier)]
 pub(crate) struct LoremIpsumControllerService {
@@ -42,13 +35,13 @@ impl EnableControllerService for 
LoremIpsumControllerService {
     where
         Self: Sized,
     {
-        let data = lipsum(context.get_req_property::<usize>(&LENGTH)?);
+        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<Self>] = &[];
 }
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 eb3f359d8..2a64c8a3e 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 95b25910c..518751d62 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 8d388cb20..ee4ae055e 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 4464ef4e7..959ef7820 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,18 +17,28 @@
 
 // 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::{
-    DataSize, GetProperty, Logger, MinifiError, OnTriggerResult, 
ProcessContext, ProcessSession,
-    Schedule, Trigger,
+    GetProperty, Logger, MinifiError, OnTriggerResult, ProcessContext, 
ProcessSession, Schedule,
+    Trigger,
 };
 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,14 +62,12 @@ impl Schedule for GenerateFlowFileRs {
     where
         Self: Sized,
     {
-        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 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_req_property::<DataSize>(&properties::FILE_SIZE)?;
-        let batch_size = 
context.get_req_property::<u64>(&properties::BATCH_SIZE)?;
+        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 =
@@ -154,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 1f75416e4..d3f9a9391 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 34e68d904..d6f62767f 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,55 +15,34 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use minifi_native::PropertyConstraints::AllowedValues;
-use minifi_native::{DataSize, Property, property_constraint};
+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"),
-    constraints: property_constraint::<DataSize>(),
-};
+pub(crate) const FILE_SIZE: Property<DataSize> =
+    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"),
-    constraints: property_constraint::<u64>(),
-};
+pub(crate) const BATCH_SIZE: Property<u64> = 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"),
-    constraints: Some(AllowedValues(&["Text", "Binary"])),
-};
+pub(crate) const DATA_FORMAT: Property<DataFormat> = 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"),
-    constraints: property_constraint::<bool>(),
-};
+pub(crate) const UNIQUE_FLOW_FILES: Property<bool> = 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,
-    constraints: None,
-};
+pub(crate) const CUSTOM_TEXT: Property<Option<String>> = 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 39b2facd4..710ed98dc 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 dc365e9c4..ab2f0c037 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::{
-    DataSize, GetProperty, IoState, Logger, MinifiError, OnTriggerResult, 
ProcessContext,
-    ProcessSession, Schedule, Trigger, debug, info, trace, warn,
+    GetProperty, IoState, Logger, MinifiError, OnTriggerResult, 
ProcessContext, ProcessSession,
+    Schedule, Trigger, debug, info, trace, warn,
 };
 use std::collections::VecDeque;
 use std::error;
@@ -223,7 +223,7 @@ impl Schedule for GetFileRs {
     where
         Self: Sized,
     {
-        let input_directory = context.get_req_property::<PathBuf>(&DIRECTORY)?;
+        let input_directory = context.get_property(&DIRECTORY)?;
         if !input_directory.is_dir() {
             return Err(MinifiError::schedule_err(format!(
                 "{:?} is not a valid directory",
@@ -231,17 +231,17 @@ impl Schedule for GetFileRs {
             )));
         }
 
-        let recursive = context.get_req_property::<bool>(&RECURSE)?;
+        let recursive = context.get_property(&RECURSE)?;
 
-        let keep_source_file = 
context.get_req_property::<bool>(&KEEP_SOURCE_FILE)?;
+        let keep_source_file = context.get_property(&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)?;
+        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 88e1a4b9a..59171d3d0 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 b413b842d..39f6f60b6 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,105 +15,60 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use minifi_native::{DataSize, Property, PropertyConstraints, 
property_constraint};
+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,
-    constraints: PropertyConstraints::non_blank(),
-};
+pub(crate) const DIRECTORY: Property<NonBlankPath> = 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: true,
-    is_sensitive: false,
-    supports_expr_lang: false,
-    default_value: Some("true"),
-    constraints: property_constraint::<bool>(),
-};
+pub(crate) const RECURSE: Property<bool> = 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: true,
-    is_sensitive: false,
-    supports_expr_lang: false,
-    default_value: Some("false"),
-    constraints: property_constraint::<bool>(),
-};
+pub(crate) const KEEP_SOURCE_FILE: Property<bool> = 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,
-    constraints: property_constraint::<Duration>(),
-};
+pub(crate) const MIN_AGE: Property<Option<Duration>> = 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,
-    constraints: property_constraint::<Duration>(),
-};
+pub(crate) const MAX_AGE: Property<Option<Duration>> = 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,
-    constraints: property_constraint::<DataSize>(),
-};
+pub(crate) const MIN_SIZE: Property<Option<DataSize>> = 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,
-    constraints: property_constraint::<DataSize>(),
-};
+pub(crate) const MAX_SIZE: Property<Option<DataSize>> = 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"),
-    constraints: property_constraint::<bool>(),
-};
+pub(crate) const IGNORE_HIDDEN_FILES: Property<bool> = 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,
-    constraints: property_constraint::<Duration>(),
-};
+pub(crate) const POLLING_INTERVAL: Property<Option<Duration>> = 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"),
-    constraints: property_constraint::<u64>(),
-};
+pub(crate) const BATCH_SIZE: Property<u64> = 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 34449bd41..50691c773 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 f215d8c78..f6f843576 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,9 +20,8 @@
 mod properties;
 mod relationships;
 
-use 
crate::controller_services::lorem_ipsum_controller_service::LoremIpsumControllerService;
 use crate::processors::kamikaze_processor::properties::{
-    NOT_REGISTERED_PROPERTY, SCHEDULE_BEHAVIOUR, TRIGGER_BEHAVIOUR,
+    NOT_REGISTERED_PROPERTY, SCHEDULE_BEHAVIOUR, TRIGGER_BEHAVIOUR, 
UNREGISTERED_CONTROLLER_SERVICE,
 };
 use minifi_native::macros::{ComponentIdentifier, PropertyType};
 use minifi_native::{
@@ -53,11 +52,9 @@ impl Schedule for KamikazeProcessorRs {
     where
         Self: Sized,
     {
-        let trigger_behaviour =
-            context.get_req_property::<KamikazeBehaviour>(&TRIGGER_BEHAVIOUR)?;
+        let trigger_behaviour = context.get_property(&TRIGGER_BEHAVIOUR)?;
 
-        let schedule_behaviour =
-            
context.get_req_property::<KamikazeBehaviour>(&SCHEDULE_BEHAVIOUR)?;
+        let schedule_behaviour = context.get_property(&SCHEDULE_BEHAVIOUR)?;
 
         match schedule_behaviour {
             KamikazeBehaviour::ReturnErr => Err(MinifiError::schedule_err(
@@ -65,7 +62,7 @@ impl Schedule for KamikazeProcessorRs {
             )),
             KamikazeBehaviour::ReturnOk => Ok(KamikazeProcessorRs { 
trigger_behaviour }),
             KamikazeBehaviour::GetNotRegisteredProperty => {
-                let _ = 
context.get_property::<String>(&NOT_REGISTERED_PROPERTY)?;
+                let _ = context.get_property(&NOT_REGISTERED_PROPERTY)?;
                 Ok(KamikazeProcessorRs { trigger_behaviour })
             }
             KamikazeBehaviour::Panic => {
@@ -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::<LoremIpsumControllerService>(
-                    &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 0d50359fc..51c8ff0c8 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 510890763..72f914536 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,35 +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, property_constraint};
+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()),
-    constraints: property_constraint::<KamikazeBehaviour>(),
-};
+pub(crate) const SCHEDULE_BEHAVIOUR: Property<KamikazeBehaviour> = 
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()),
-    constraints: property_constraint::<KamikazeBehaviour>(),
-};
+pub(crate) const TRIGGER_BEHAVIOUR: Property<KamikazeBehaviour> =
+    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,
-    constraints: None,
-};
+pub(crate) const NOT_REGISTERED_PROPERTY: Property<Option<String>> = 
Property::new(
+    "Kamikaze Processor Property",
+    "Property purposely left out of Processor description",
+);
+
+pub(crate) const UNREGISTERED_CONTROLLER_SERVICE: 
Property<LoremIpsumControllerService> =
+    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 f5560a25a..dd6e4794f 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 c59eb5096..48de81360 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<String>;
+    fn parse(s: &str) -> Result<Self::Output, MinifiError> {
+        Ok(s.split(',')
+            .map(|s| s.trim().to_string())
+            .collect::<Vec<String>>())
+    }
+}
+impl PropertySchema for AttributeList {
+    const CONSTRAINT: Option<PropertyConstraints> =
+        Some(PropertyConstraints::Validator(NonBlankValidator));
+    const IS_REQUIRED: bool = true;
+}
+
 #[derive(Debug, ComponentIdentifier)]
 pub(crate) struct LogAttributeRs {
     log_level: LogLevel,
@@ -121,33 +138,20 @@ 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_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::<String>(property)?.map(|s| {
-                s.split(',')
-                    .map(|s| s.trim().to_string())
-                    .collect::<Vec<String>>()
-            }))
-        }
-
-        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::<String>(&properties::LOG_PREFIX)?
+                .get_property(&properties::LOG_PREFIX)?
                 .unwrap_or_default()
         );
 
-        let hex_encode_payload =
-            context.get_req_property::<bool>(&properties::HEX_ENCODE_PAYLOAD)?;
+        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 e916d456a..b5732645d 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 b4c5d47ad..38484faab 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,74 +15,44 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use minifi_native::{LogLevel, Property, property_constraint};
-
-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(LogLevel::Info.into_str()),
-    constraints: property_constraint::<LogLevel>(),
-};
-
-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,
-    constraints: 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,
-    constraints: 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"),
-    constraints: property_constraint::<bool>(),
-};
-
-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,
-    constraints: 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"),
-    constraints: property_constraint::<u64>(),
-};
-
-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"),
-    constraints: property_constraint::<bool>(),
-};
+use crate::processors::log_attribute::AttributeList;
+use minifi_native::{LogLevel, Property};
+
+pub(crate) const LOG_LEVEL: Property<LogLevel> = 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<Option<AttributeList>> = 
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<Option<AttributeList>> = 
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<bool> = 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<Option<String>> = 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<usize> = 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<bool> = 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 b44fcc811..f8a684d9f 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,8 +18,9 @@
 // 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, PropertyType};
 use minifi_native::{
@@ -48,7 +49,7 @@ impl Schedule for LoremIpsumCSUser {
     where
         Self: Sized,
     {
-        let write_method = 
context.get_req_property::<WriteMethod>(&properties::WRITE_METHOD)?;
+        let write_method = context.get_property(&properties::WRITE_METHOD)?;
         Ok(Self { write_method })
     }
 }
@@ -60,11 +61,12 @@ impl FlowFileSource for LoremIpsumCSUser {
         logger: &LoggerImpl,
     ) -> Result<Vec<GeneratedFlowFile<'a>>, MinifiError> {
         trace!(logger, "generate call {:?}", self);
-        let controller_service = context
-            
.get_controller_service::<LoremIpsumControllerService>(&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 7d204a22f..78c09ef0c 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 b424e5ad1..0742c6fb0 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,27 +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 crate::processors::lorem_ipsum_cs_user::WriteMethod;
 use minifi_native::Property;
-use minifi_native::property_constraint;
 
-pub(crate) const CONTROLLER_SERVICE: Property = Property {
-    name: "Lorem Ipsum Controller Service",
-    description: "Name of the lorem ipsum controller service",
-    is_required: true,
-    is_sensitive: false,
-    supports_expr_lang: false,
-    default_value: None,
-    constraints: property_constraint::<LoremIpsumControllerService>(),
-};
+pub(crate) const CONTROLLER_SERVICE: Property<LoremIpsumControllerService> = 
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(WriteMethod::Buffer.into_str()),
-    constraints: property_constraint::<WriteMethod>(),
-};
+pub(crate) const DUMMY_CONTROLLER_SERVICE: 
Property<Option<DummyControllerService>> = Property::new(
+    "Dummy Controller Service",
+    "Optional dummy controller service",
+);
+
+pub(crate) const WRITE_METHOD: Property<WriteMethod> =
+    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 5fa35a63e..e81c861ca 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,6 +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 crate::processors::put_file::unix_permissions::PutFileUnixPermissions;
 use minifi_native::macros::{ComponentIdentifier, PropertyType};
 use minifi_native::{
     FlowFileTransform, GetAttribute, GetControllerService, GetId, GetProperty, 
InputStream, Logger,
@@ -32,6 +33,8 @@ mod relationships;
 #[cfg(unix)]
 mod unix_only_properties;
 
+mod unix_permissions;
+
 #[derive(
     Debug, Clone, Copy, PartialEq, Display, EnumString, VariantNames, 
IntoStaticStr, PropertyType,
 )]
@@ -42,45 +45,6 @@ enum ConflictResolutionStrategy {
     Ignore,
 }
 
-#[cfg(unix)]
-#[derive(Debug)]
-struct PutFileUnixPermissions {
-    file_permissions: Option<std::fs::Permissions>,
-    directory_permissions: Option<std::fs::Permissions>,
-}
-
-#[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,
@@ -111,7 +75,7 @@ impl PutFileRs {
     where
         Ctx: GetProperty + GetAttribute + GetId,
     {
-        let directory = 
context.get_req_property::<PathBuf>(&properties::DIRECTORY)?;
+        let directory = context.get_property(&properties::DIRECTORY)?;
 
         let file_name = context
             .get_attribute("filename")?
@@ -159,17 +123,9 @@ impl PutFileRs {
     fn parse_unix_permissions<P: GetProperty>(
         context: &P,
     ) -> Result<PutFileUnixPermissions, MinifiError> {
-        use std::os::unix::fs::PermissionsExt;
-        let parse_permission =
-            |property: &minifi_native::Property| -> 
Result<Option<std::fs::Permissions>, MinifiError> {
-                Ok(context
-                    .get_property::<String>(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,12 +143,12 @@ 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_req_property::<ConflictResolutionStrategy>(&properties::CONFLICT_RESOLUTION)?;
+        let conflict_resolution_strategy =
+            context.get_property(&properties::CONFLICT_RESOLUTION)?;
 
-        let try_make_dirs = 
context.get_req_property::<bool>(&properties::CREATE_DIRS)?;
+        let try_make_dirs = context.get_property(&properties::CREATE_DIRS)?;
 
-        let maximum_file_count = 
context.get_property::<u64>(&properties::MAX_FILE_COUNT)?;
+        let maximum_file_count = 
context.get_property(&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/processor_definition.rs
 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/processor_definition.rs
index 602c2ff11..93be840a4 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 c93128172..1c1bc8426 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
@@ -16,43 +16,26 @@
 // under the License.
 
 use super::ConflictResolutionStrategy;
-use minifi_native::{Property, PropertyConstraints, property_constraint};
-pub(crate) const DIRECTORY: Property = Property {
-    name: "Directory",
-    description: "The output directory to which to put files",
-    is_required: true,
-    is_sensitive: false,
-    supports_expr_lang: true,
-    default_value: Some("."),
-    constraints: PropertyConstraints::non_blank(),
-};
+use minifi_native::{NonBlankPath, Property};
 
-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()),
-    constraints: property_constraint::<ConflictResolutionStrategy>(),
-};
+pub(crate) const DIRECTORY: Property<NonBlankPath> =
+    Property::new("Directory", "The output directory to which to put files")
+        .supports_expression_language()
+        .with_default(".");
 
-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"),
-    constraints: property_constraint::<bool>(),
-};
+pub(crate) const CONFLICT_RESOLUTION: Property<ConflictResolutionStrategy> = 
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 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
-    constraints: property_constraint::<u64>(),
-};
+pub(crate) const CREATE_DIRS: Property<bool> = 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<Option<u64>> = 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 d7a75da41..ccaba59ad 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,24 +15,15 @@
 // specific language governing permissions and limitations
 // under the License.
 
+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,
-    constraints: None,
-};
+pub(crate) const PERMISSIONS: Property<Option<UnixPermission>> = 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,
-    constraints: None,
-};
+pub(crate) const DIRECTORY_PERMISSIONS: Property<Option<UnixPermission>> = 
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 000000000..b921246ef
--- /dev/null
+++ 
b/minifi_rust/extensions/minifi_rs_playground/src/processors/put_file/unix_permissions.rs
@@ -0,0 +1,60 @@
+use minifi_native::{MinifiError, PropertyConstraints, PropertySchema, 
PropertyType};
+use std::os::unix::fs::PermissionsExt;
+use std::path::Path;
+
+#[cfg(unix)]
+pub struct UnixPermission {}
+
+#[cfg(unix)]
+impl PropertySchema for UnixPermission {
+    const CONSTRAINT: Option<PropertyConstraints> = None;
+    const IS_REQUIRED: bool = true;
+}
+
+#[cfg(unix)]
+impl PropertyType for UnixPermission {
+    type Output = std::fs::Permissions;
+
+    fn parse(s: &str) -> Result<Self::Output, MinifiError> {
+        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<std::fs::Permissions>,
+    pub(crate) directory_permissions: Option<std::fs::Permissions>,
+}
+
+#[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)]
+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(())
+    }
+}
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 20eb46a5b..86d67c779 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,32 +19,17 @@ use crate::controller_services::animal_controller_apis::{
     CanFlyControllerApi, NumberOfLegsControllerApi,
 };
 use minifi_native::macros::ComponentIdentifier;
-use minifi_native::property_constraint;
 use minifi_native::{
     GetProperty, Logger, MinifiError, OnTriggerResult, OutputAttribute, 
ProcessContext,
-    ProcessSession, ProcessorDefinition, ProcessorInputRequirement, Property, 
Relationship,
-    Schedule, 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,
-    constraints: property_constraint::<dyn CanFlyControllerApi>(),
-};
+pub(crate) const CAN_FLY_SERVICE: Property<dyn CanFlyControllerApi> =
+    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,
-    constraints: property_constraint::<dyn NumberOfLegsControllerApi>(),
-};
+pub(crate) const NUMBER_OF_LEGS: Property<dyn NumberOfLegsControllerApi> =
+    Property::new("Number of Legs service", "Test NumberOfLegsService");
 
 #[derive(Debug, ComponentIdentifier)]
 pub(crate) struct ZooProcessorRs {}
@@ -74,9 +59,7 @@ impl Trigger for ZooProcessorRs {
         Lggr: Logger,
     {
         info!(logger, "{:?}", self);
-        if let Some(maybe_flyer) =
-            context.get_controller_service_api::<dyn 
CanFlyControllerApi>(&CAN_FLY_SERVICE)?
-        {
+        if let Some(maybe_flyer) = 
context.get_controller_service_api(&CAN_FLY_SERVICE)? {
             critical!(
                 logger,
                 "Can {:?} fly? {}",
@@ -84,9 +67,7 @@ impl Trigger for ZooProcessorRs {
                 maybe_flyer.can_fly()
             );
         }
-        if let Some(legged) =
-            context.get_controller_service_api::<dyn 
NumberOfLegsControllerApi>(&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)
@@ -100,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/src/api.rs 
b/minifi_rust/minifi_native/src/api.rs
index 37d62acd0..49f3d88d5 100644
--- a/minifi_rust/minifi_native/src/api.rs
+++ b/minifi_rust/minifi_native/src/api.rs
@@ -39,8 +39,7 @@ pub use raw_controller_service::RawControllerService;
 pub use raw_processor::{OnTriggerResult, ProcessorInputRequirement, 
RawProcessor, ThreadingModel};
 
 pub use property::{
-    DataSize, PropertyConstraints, PropertyType, ProvidesPropertyConstraint,
-    StandardPropertyValidator, property_constraint,
+    DataSize, NonBlankPath, PropertyConstraints, PropertyType, 
StandardPropertyValidator,
 };
 
 pub use relationship::Relationship;
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 b0d67b2a9..5e0e8a126 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<Self>];
 }
diff --git a/minifi_rust/minifi_native/src/api/process_context.rs 
b/minifi_rust/minifi_native/src/api/process_context.rs
index 528dceb42..672255dd4 100644
--- a/minifi_rust/minifi_native/src/api/process_context.rs
+++ b/minifi_rust/minifi_native/src/api/process_context.rs
@@ -18,35 +18,36 @@
 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 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<K: PropertySchema + ?Sized>(
         &self,
-        property: &Property,
+        property: &Property<K>,
         flow_file: Option<&Self::FlowFile>,
     ) -> Result<Option<String>, MinifiError>;
 
-    fn get_raw_controller_service<Cs>(
+    fn get_raw_controller_service<Cs, K>(
         &self,
-        property: &Property,
+        property: &Property<K>,
     ) -> Result<Option<&Cs>, MinifiError>
     where
-        Cs: RawControllerService + ComponentIdentifier + 'static;
+        Cs: RawControllerService + ComponentIdentifier + 'static,
+        K: PropertySchema + ?Sized;
 
-    fn get_controller_service<Cs>(&self, property: &Property) -> 
Result<Option<&Cs>, MinifiError>
+    fn get_controller_service<Cs>(
+        &self,
+        property: &Property<Cs>,
+    ) -> Result<Option<&Cs>, MinifiError>
     where
-        Cs: EnableControllerService + ComponentIdentifier + 'static;
+        Cs: EnableControllerService + ComponentIdentifier + PropertySchema + 
'static;
 
-    fn get_controller_service_api<Trait: ?Sized + ControllerServiceApi>(
+    fn get_controller_service_api<Trait: ?Sized + ControllerServiceApi + 
PropertySchema>(
         &self,
-        property: &Property,
+        property: &Property<Trait>,
     ) -> Result<Option<Box<&Trait>>, MinifiError>;
 
     fn report_metrics(&self, metrics: Vec<(String, f64)>) -> Result<(), 
MinifiError>;
@@ -56,8 +57,11 @@ impl<S> GetProperty for S
 where
     S: ProcessContext,
 {
-    fn get_raw_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError> {
-        self.get_property(property, None)
+    fn get_raw_property<K: PropertySchema + ?Sized>(
+        &self,
+        property: &Property<K>,
+    ) -> Result<Option<String>, MinifiError> {
+        self.get_raw_property(property, None)
     }
 }
 
@@ -65,10 +69,15 @@ impl<S> GetControllerService for S
 where
     S: ProcessContext,
 {
-    fn get_controller_service<Cs>(&self, property: &Property) -> 
Result<Option<&Cs>, MinifiError>
+    fn get_controller_service<K>(
+        &self,
+        property: &Property<K>,
+    ) -> Result<K::Output<'_>, MinifiError>
     where
-        Cs: EnableControllerService + ComponentIdentifier + 
ControllerServiceDefinition + 'static,
+        K: ControllerServiceValue + ?Sized,
     {
-        ProcessContext::get_controller_service::<Cs>(self, property)
+        let cs_property = property.with_marker::<K::Cs>();
+        let service = ProcessContext::get_controller_service::<K::Cs>(self, 
&cs_property)?;
+        K::from_service(service, property.name)
     }
 }
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 c05942066..4f0716c77 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<FlowFile = PC::FlowFile>,
 {
-    fn get_raw_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError> {
-        self.context.get_property(property, self.flow_file)
+    fn get_raw_property<K: PropertySchema + ?Sized>(
+        &self,
+        property: &Property<K>,
+    ) -> Result<Option<String>, MinifiError> {
+        self.context.get_raw_property(property, self.flow_file)
     }
 }
 
@@ -65,11 +67,14 @@ where
     PC: ProcessContext,
     PS: ProcessSession<FlowFile = PC::FlowFile>,
 {
-    fn get_controller_service<Cs>(&self, property: &Property) -> 
Result<Option<&Cs>, MinifiError>
+    fn get_controller_service<K>(
+        &self,
+        property: &Property<K>,
+    ) -> Result<K::Output<'_>, 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 97c414894..d98224247 100644
--- a/minifi_rust/minifi_native/src/api/property.rs
+++ b/minifi_rust/minifi_native/src/api/property.rs
@@ -16,11 +16,12 @@
 // 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;
 
@@ -42,12 +43,7 @@ pub enum PropertyConstraints {
     ControllerService(&'static str),
 }
 
-pub trait ProvidesPropertyConstraint {
-    const PROPERTY_CONSTRAINT: Option<PropertyConstraints>;
-}
-
-#[derive(Debug)]
-pub struct Property {
+pub struct PropertyDefinition {
     pub name: &'static str,
     pub description: &'static str,
     pub is_required: bool,
@@ -57,38 +53,129 @@ pub struct Property {
     pub constraints: Option<PropertyConstraints>,
 }
 
-pub trait PropertyType {
-    type Output;
-    const EXPECTED_CONSTRAINTS: Option<PropertyConstraints> = None;
+#[macro_export]
+macro_rules! property_definitions {
+    ($($property:expr),* $(,)?) => {
+        &[$($property.definition()),*]
+    };
+}
+
+pub struct Property<K: ?Sized + PropertySchema> {
+    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<K>,
+}
+
+impl<K: ?Sized + PropertySchema> Property<K> {
+    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,
+        }
+    }
+
+    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,
+        }
+    }
+
+    pub(crate) const fn with_marker<K2: ?Sized + PropertySchema>(&self) -> 
Property<K2> {
+        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<PropertyConstraints>;
+    const IS_REQUIRED: bool;
+}
+
+impl<T: PropertySchema> PropertySchema for Option<T> {
+    const CONSTRAINT: Option<PropertyConstraints> = T::CONSTRAINT;
+    const IS_REQUIRED: bool = false;
+}
+
+pub trait PropertyType: PropertySchema {
+    type Output;
     fn parse(s: &str) -> Result<Self::Output, MinifiError>;
 }
 
-impl PropertyConstraints {
-    pub const fn non_blank() -> Option<Self> {
-        Some(Self::Validator(
-            StandardPropertyValidator::NonBlankValidator,
-        ))
+pub trait PropertyValue: PropertySchema {
+    type Output;
+    fn from_raw(raw: Option<String>, name: &str) -> Result<Self::Output, 
MinifiError>;
+}
+
+impl<T: PropertyType> PropertyValue for T {
+    type Output = T::Output;
+    fn from_raw(raw: Option<String>, name: &str) -> Result<Self::Output, 
MinifiError> {
+        match raw {
+            Some(value) => T::parse(&value),
+            None => 
Err(MinifiError::missing_required_property(name.to_string())),
+        }
     }
 }
 
-pub const fn property_constraint<T: ProvidesPropertyConstraint + ?Sized>()
--> Option<PropertyConstraints> {
-    T::PROPERTY_CONSTRAINT
+impl<T: PropertyType> PropertyValue for Option<T> {
+    type Output = Option<T::Output>;
+    fn from_raw(raw: Option<String>, _name: &str) -> Result<Self::Output, 
MinifiError> {
+        match raw {
+            Some(value) => Ok(Some(T::parse(&value)?)),
+            None => Ok(None),
+        }
+    }
 }
 
 macro_rules! impl_from_str_property {
-    ($t:ty, $validator:expr) => {
+    ($t:ty, $constraint:expr) => {
         impl PropertyType for $t {
             type Output = $t;
-            const EXPECTED_CONSTRAINTS: Option<PropertyConstraints> = 
$validator;
 
             fn parse(s: &str) -> Result<Self::Output, MinifiError> {
                 s.parse::<$t>().map_err(Into::into)
             }
         }
-        impl ProvidesPropertyConstraint for $t {
-            const PROPERTY_CONSTRAINT: Option<PropertyConstraints> = 
$validator;
+        impl PropertySchema for $t {
+            const CONSTRAINT: Option<PropertyConstraints> = $constraint;
+            const IS_REQUIRED: bool = true;
         }
     };
     ($t:ty) => {
@@ -109,73 +196,109 @@ impl_from_str_property!(usize, 
Some(PropertyConstraints::Validator(U64Validator)
 
 impl PropertyType for Duration {
     type Output = Duration;
-    const EXPECTED_CONSTRAINTS: Option<PropertyConstraints> =
-        Some(PropertyConstraints::Validator(TimePeriodValidator));
     fn parse(s: &str) -> Result<Self::Output, MinifiError> {
         humantime::parse_duration(s).map_err(Into::into)
     }
 }
-impl ProvidesPropertyConstraint for Duration {
-    const PROPERTY_CONSTRAINT: Option<PropertyConstraints> = 
Self::EXPECTED_CONSTRAINTS;
+impl PropertySchema for Duration {
+    const CONSTRAINT: Option<PropertyConstraints> =
+        Some(PropertyConstraints::Validator(TimePeriodValidator));
+    const IS_REQUIRED: bool = true;
 }
 
 pub struct DataSize;
 impl PropertyType for DataSize {
     type Output = u64;
-    const EXPECTED_CONSTRAINTS: Option<PropertyConstraints> =
-        Some(PropertyConstraints::Validator(DataSizeValidator));
     fn parse(s: &str) -> Result<Self::Output, MinifiError> {
         byte_unit::Byte::from_str(s)
             .map(|b| b.as_u64())
             .map_err(Into::into)
     }
 }
+impl PropertySchema for DataSize {
+    const CONSTRAINT: Option<PropertyConstraints> =
+        Some(PropertyConstraints::Validator(DataSizeValidator));
+    const IS_REQUIRED: bool = true;
+}
 
-impl ProvidesPropertyConstraint for DataSize {
-    const PROPERTY_CONSTRAINT: Option<PropertyConstraints> = 
Self::EXPECTED_CONSTRAINTS;
+pub struct NonBlankPath;
+impl PropertyType for NonBlankPath {
+    type Output = std::path::PathBuf;
+    fn parse(s: &str) -> Result<Self::Output, MinifiError> {
+        <std::path::PathBuf as PropertyType>::parse(s)
+    }
+}
+impl PropertySchema for NonBlankPath {
+    const CONSTRAINT: Option<PropertyConstraints> =
+        Some(PropertyConstraints::Validator(NonBlankValidator));
+    const IS_REQUIRED: bool = true;
 }
 
 pub trait GetProperty {
-    fn get_raw_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError>;
+    fn get_raw_property<K: PropertySchema + ?Sized>(
+        &self,
+        property: &Property<K>,
+    ) -> Result<Option<String>, MinifiError>;
 
-    fn get_property<T: PropertyType>(
+    fn get_property<K: PropertyValue + ?Sized>(
         &self,
-        property: &Property,
-    ) -> Result<Option<T::Output>, MinifiError> {
-        if let Some(expected) = T::EXPECTED_CONSTRAINTS
-            && Some(expected) != property.constraints
-        {
-            return Err(MinifiError::validation_err(format!(
-                "to use get_property for this type, {:?} must have validator 
{:?}",
-                property.name,
-                T::EXPECTED_CONSTRAINTS
-            )));
-        }
+        property: &Property<K>,
+    ) -> Result<K::Output, MinifiError> {
+        K::from_raw(self.get_raw_property(property)?, property.name)
+    }
+}
 
-        if let Some(property_val) = self.get_raw_property(property)? {
-            Ok(Some(T::parse(&property_val)?))
-        } else {
-            Ok(None)
-        }
+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<Self::Output<'a>, MinifiError>;
+}
+
+impl<Cs> 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()))
     }
+}
 
-    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_req_property, {:?} must be required",
-                property.name
-            )));
-        }
-        self.get_property::<T>(property)?
-            .ok_or_else(|| 
MinifiError::missing_required_property(property.name))
+impl<Cs> ControllerServiceValue for Option<Cs>
+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<Option<&'a Cs>, MinifiError> {
+        Ok(service)
     }
 }
 
 pub trait GetControllerService {
-    fn get_controller_service<Cs>(&self, property: &Property) -> 
Result<Option<&Cs>, MinifiError>
+    fn get_controller_service<K>(
+        &self,
+        property: &Property<K>,
+    ) -> Result<K::Output<'_>, MinifiError>
     where
-        Cs: EnableControllerService + ComponentIdentifier + 
ControllerServiceDefinition + 'static;
+        K: ControllerServiceValue + ?Sized;
 }
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 2c285fd4f..e0efd4afb 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_raw_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError> {
+    fn get_raw_property<K: PropertySchema + ?Sized>(
+        &self,
+        property: &Property<K>,
+    ) -> Result<Option<String>, MinifiError> {
         let mut result: Option<String> = 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 738071dc7..95fe4bb0d 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<P>],
     ) -> 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 60dfc033c..4604b24a5 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<K: PropertySchema + ?Sized>(
         &self,
-        property: &Property,
+        property: &Property<K>,
         flow_file: Option<&Self::FlowFile>,
     ) -> Result<Option<String>, 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<Cs>(
+    fn get_raw_controller_service<Cs, K>(
         &self,
-        property: &Property,
+        property: &Property<K>,
     ) -> Result<Option<&'a Cs>, 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<Cs>(&self, property: &Property) -> 
Result<Option<&Cs>, MinifiError>
+    fn get_controller_service<Cs>(
+        &self,
+        property: &Property<Cs>,
+    ) -> Result<Option<&Cs>, MinifiError>
     where
-        Cs: EnableControllerService + ComponentIdentifier + 'static,
+        Cs: EnableControllerService + ComponentIdentifier + PropertySchema + 
'static,
     {
-        match self.get_raw_controller_service::<ControllerService<Cs, 
CffiLogger>>(property)? {
+        match self.get_raw_controller_service::<ControllerService<Cs, 
CffiLogger>, Cs>(property)? {
             None => Ok(None),
             Some(f) => Ok(f.get_implementation()),
         }
     }
 
-    fn get_controller_service_api<Trait: ?Sized + ControllerServiceApi>(
+    fn get_controller_service_api<Trait: ?Sized + ControllerServiceApi + 
PropertySchema>(
         &self,
-        property: &Property,
+        property: &Property<Trait>,
     ) -> Result<Option<Box<&Trait>>, 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 68602aca6..a5c6688c0 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 ec2c0d3d7..f252a9e1d 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
@@ -17,7 +17,7 @@
 
 use super::c_ffi_primitives::StaticStrAsMinifiCStr;
 use crate::api::property::PropertyConstraints;
-use crate::{Property, StandardPropertyValidator};
+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,
@@ -60,7 +60,7 @@ impl CProperties {
     }
 }
 
-impl Property {
+impl PropertyDefinition {
     fn create_c_default_value_holder(properties: &[Self]) -> 
Vec<minifi_string_view> {
         properties
             .iter()
@@ -105,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());
diff --git a/minifi_rust/minifi_native/src/lib.rs 
b/minifi_rust/minifi_native/src/lib.rs
index 17906ce4e..ce54f9090 100644
--- a/minifi_rust/minifi_native/src/lib.rs
+++ b/minifi_rust/minifi_native/src/lib.rs
@@ -46,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};
 
@@ -55,9 +58,9 @@ pub use api::process_session::IoState;
 pub use api::attribute::{GetAttribute, OutputAttribute};
 
 pub use api::{
-    DataSize, FlowFile, GetId, InputStream, OnTriggerResult, OutputStream, 
ProcessContext,
-    ProcessSession, ProcessorInputRequirement, PropertyConstraints, 
PropertyType,
-    ProvidesPropertyConstraint, Relationship, StandardPropertyValidator, 
property_constraint,
+    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 f67bd5008..86a8a8158 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_raw_property(&self, property: &Property) -> Result<Option<String>, 
MinifiError> {
+    fn get_raw_property<K: PropertySchema + ?Sized>(
+        &self,
+        property: &Property<K>,
+    ) -> Result<Option<String>, 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 be05daa16..56ba24236 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<K: PropertySchema + ?Sized>(
         &self,
-        property: &Property,
+        property: &Property<K>,
         _flow_file: Option<&MockFlowFile>,
     ) -> Result<Option<String>, 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<K: PropertySchema + ?Sized>(
         &self,
-        property: &Property,
+        property: &Property<K>,
         _flow_file: Option<&Self::FlowFile>,
     ) -> Result<Option<String>, MinifiError> {
         self.properties.get_property(property, _flow_file)
     }
 
-    fn get_raw_controller_service<Cs>(
+    fn get_raw_controller_service<Cs, K>(
         &self,
-        property: &Property,
+        property: &Property<K>,
     ) -> Result<Option<&Cs>, 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<dyn Any>`.
-        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<Cs>(&self, property: &Property) -> 
Result<Option<&Cs>, MinifiError>
+    fn get_controller_service<Cs>(
+        &self,
+        property: &Property<Cs>,
+    ) -> Result<Option<&Cs>, 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<Trait: ?Sized + ControllerServiceApi>(
+    fn get_controller_service_api<Trait: ?Sized + ControllerServiceApi + 
PropertySchema>(
         &self,
-        _property: &Property,
+        _property: &Property<Trait>,
     ) -> Result<Option<Box<&Trait>>, 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 8aff6eaf7..873005507 100644
--- a/minifi_rust/minifi_native_macros/src/lib.rs
+++ b/minifi_rust/minifi_native_macros/src/lib.rs
@@ -32,9 +32,10 @@ pub fn derive_component_identifier(input: TokenStream) -> 
TokenStream {
             const VERSION: &'static str = env!("CARGO_PKG_VERSION");
         }
 
-        impl ::minifi_native::ProvidesPropertyConstraint for #name {
-            const PROPERTY_CONSTRAINT: 
Option<::minifi_native::PropertyConstraints> =
+        impl ::minifi_native::PropertySchema for #name {
+            const CONSTRAINT: Option<::minifi_native::PropertyConstraints> =
                 
Some(::minifi_native::PropertyConstraints::ControllerService(<Self as 
::minifi_native::ComponentIdentifier>::CLASS_NAME));
+            const IS_REQUIRED: bool = true;
         }
     };
 
@@ -54,9 +55,10 @@ pub fn controller_service_api(_attr: TokenStream, item: 
TokenStream) -> TokenStr
             const INTERFACE_NAME: &'static str = concat!(module_path!(), "::", 
#name_str);
         }
 
-        impl ::minifi_native::ProvidesPropertyConstraint for dyn #name {
-            const PROPERTY_CONSTRAINT: 
Option<::minifi_native::PropertyConstraints> =
+        impl ::minifi_native::PropertySchema for dyn #name {
+            const CONSTRAINT: Option<::minifi_native::PropertyConstraints> =
                 
Some(::minifi_native::PropertyConstraints::ControllerService(<Self as 
::minifi_native::ControllerServiceApi>::INTERFACE_NAME));
+            const IS_REQUIRED: bool = true;
         }
     };
 
@@ -71,17 +73,17 @@ pub fn derive_property_type(input: TokenStream) -> 
TokenStream {
     let expanded = quote! {
         impl ::minifi_native::PropertyType for #name {
             type Output = #name;
-            const EXPECTED_CONSTRAINTS: 
Option<::minifi_native::PropertyConstraints> =
-                Some(::minifi_native::PropertyConstraints::AllowedValues(
-                    <#name as ::strum::VariantNames>::VARIANTS
-                ));
             fn parse(s: &str) -> Result<Self::Output, 
::minifi_native::MinifiError> {
                 s.parse::<#name>().map_err(Into::into)
             }
         }
 
-        impl ::minifi_native::ProvidesPropertyConstraint for #name {
-            const PROPERTY_CONSTRAINT: 
Option<::minifi_native::PropertyConstraints> = <Self as 
::minifi_native::PropertyType>::EXPECTED_CONSTRAINTS;
+        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;
         }
     };
 

Reply via email to