fgerlits commented on code in PR #1926:
URL: https://github.com/apache/nifi-minifi-cpp/pull/1926#discussion_r1986999605


##########
extensions/expression-language/ProcessContextExpr.cpp:
##########
@@ -16,67 +16,68 @@
  */
 
 #include "ProcessContextExpr.h"
+
 #include <memory>
 #include <string>
 
+#include "asio/detail/mutex.hpp"
+#include "utils/PropertyErrors.h"
+
 namespace org::apache::nifi::minifi::core {
 
-bool ProcessContextExpr::getProperty(bool supports_expression_language, 
std::string_view property_name, std::string& value, const FlowFile* const 
flow_file) {
-  if (!supports_expression_language) {
-    return ProcessContextImpl::getProperty(property_name, value);
-  }
-  std::string name{property_name};
-  if (expressions_.find(name) == expressions_.end()) {
-    std::string expression_str;
-    if (!ProcessContextImpl::getProperty(name, expression_str)) {
-      return false;
-    }
-    logger_->log_debug("Compiling expression for {}/{}: {}", 
getProcessorNode()->getName(), name, expression_str);
-    expressions_.emplace(name, expression::compile(expression_str));
-    expression_strs_.insert_or_assign(name, expression_str);
+nonstd::expected<std::string, std::error_code> 
ProcessContextExpr::getProperty(const std::string_view name, const FlowFile* 
flow_file) const {
+  const auto property = getProcessor().getPropertyReference(name);
+  if (!property) {
+    return nonstd::make_unexpected(PropertyErrorCode::NotSupportedProperty);
   }
 
-  minifi::expression::Parameters p(sharedFromThis<VariableRegistry>(), 
flow_file);
-  value = expressions_[name](p).asString();
-  logger_->log_debug(R"(expression "{}" of property "{}" evaluated to: {})", 
expression_strs_[name], name, value);
-  return true;
-}
-
-bool ProcessContextExpr::getProperty(const Property& property, std::string& 
value, const FlowFile* const flow_file) {
-  return getProperty(property.supportsExpressionLanguage(), 
property.getName(), value, flow_file);
-}
-
-bool ProcessContextExpr::getProperty(const PropertyReference& property, 
std::string& value, const FlowFile* const flow_file) {
-  return getProperty(property.supports_expression_language, property.name, 
value, flow_file);
+  if (!property->supports_expression_language) {
+    return ProcessContextImpl::getProperty(name, flow_file);
+  }
+  if (!cached_expressions_.contains(name)) {
+    auto expression_str = ProcessContextImpl::getProperty(name, flow_file);
+    if (!expression_str) { return expression_str; }
+    cached_expressions_.emplace(std::string{name}, 
expression::compile(*expression_str));
+  }
+  expression::Parameters p(this, flow_file);
+  auto result = cached_expressions_[std::string{name}](p).asString();
+  if (!property->validator->validate(result)) {
+    return nonstd::make_unexpected(PropertyErrorCode::ValidationFailed);
+  }
+  return result;
 }
 
-bool ProcessContextExpr::getDynamicProperty(const Property &property, 
std::string &value, const FlowFile* const flow_file) {
-  if (!property.supportsExpressionLanguage()) {
-    return ProcessContextImpl::getDynamicProperty(property.getName(), value);
-  }
-  auto name = property.getName();
-  if (dynamic_property_expressions_.find(name) == 
dynamic_property_expressions_.end()) {
-    std::string expression_str;
-    ProcessContextImpl::getDynamicProperty(name, expression_str);
-    logger_->log_debug("Compiling expression for {}/{}: {}", 
getProcessorNode()->getName(), name, expression_str);
-    dynamic_property_expressions_.emplace(name, 
expression::compile(expression_str));
-    expression_strs_.insert_or_assign(name, expression_str);
+nonstd::expected<std::string, std::error_code> 
ProcessContextExpr::getDynamicProperty(const std::string_view name, const 
FlowFile* flow_file) const {
+  if (!cached_dynamic_expressions_.contains(name)) {
+    auto expression_str = ProcessContextImpl::getDynamicProperty(name, 
flow_file);
+    if (!expression_str) { return expression_str; }
+    cached_dynamic_expressions_.emplace(std::string{name}, 
expression::compile(*expression_str));
   }
-  minifi::expression::Parameters p(sharedFromThis<VariableRegistry>(), 
flow_file);
-  value = dynamic_property_expressions_[name](p).asString();
-  logger_->log_debug(R"(expression "{}" of dynamic property "{}" evaluated to: 
{})", expression_strs_[name], name, value);
-  return true;
+  const expression::Parameters p(this, flow_file);
+  return cached_dynamic_expressions_[std::string{name}](p).asString();
 }
 
+nonstd::expected<void, std::error_code> ProcessContextExpr::setProperty(const 
std::string_view name, std::string value) {
+  cached_expressions_.erase(std::string{name});
+  return ProcessContextImpl::setProperty(name, std::move(value));
+}
 
-bool ProcessContextExpr::setProperty(const std::string& property, std::string 
value) {
-  expressions_.erase(property);
-  return ProcessContextImpl::setProperty(property, value);
+nonstd::expected<void, std::error_code> 
ProcessContextExpr::setDynamicProperty(std::string name, std::string value) {
+  cached_dynamic_expressions_.erase(name);
+  return ProcessContextImpl::setDynamicProperty(std::move(name), 
std::move(value));
 }
 
-bool ProcessContextExpr::setDynamicProperty(const std::string& property, 
std::string value) {
-  dynamic_property_expressions_.erase(property);
-  return ProcessContextImpl::setDynamicProperty(property, value);
+std::map<std::string, std::string> 
ProcessContextExpr::getDynamicProperties(const FlowFile* flow_file) const {
+  auto dynamic_props = ProcessContextImpl::getDynamicProperties(flow_file);
+  for (auto& [dynamic_property_name, dynamic_property_value] : dynamic_props) {
+    if (!cached_dynamic_expressions_.contains(dynamic_property_name)) {

Review Comment:
   Do we need to skip dynamic properties which do not support expression 
language?



##########
extensions/kafka/ConsumeKafka.h:
##########
@@ -23,26 +23,27 @@
 #include <utility>
 #include <vector>
 
-#include "KafkaConnection.h"
 #include "KafkaProcessorBase.h"
+#include "core/logging/LoggerFactory.h"
 #include "core/PropertyDefinition.h"
 #include "core/PropertyDefinitionBuilder.h"
 #include "core/PropertyType.h"
 #include "core/RelationshipDefinition.h"
-#include "core/logging/LoggerFactory.h"
 #include "io/StreamPipe.h"
 #include "rdkafka.h"
 #include "rdkafka_utils.h"
+#include "KafkaConnection.h"
 #include "utils/ArrayUtils.h"
 
 namespace org::apache::nifi::minifi {
 
 namespace core {
-class ConsumeKafkaMaxPollTimePropertyType : public TimePeriodPropertyType {
+class ConsumeKafkaMaxPollTimePropertyType final : public PropertyValidator {

Review Comment:
   ```suggestion
   class ConsumeKafkaMaxPollTimePropertyValidator final : public 
PropertyValidator {
   ```



##########
extensions/kafka/ConsumeKafka.cpp:
##########
@@ -139,14 +137,12 @@ void 
ConsumeKafka::extend_config_from_dynamic_properties(const core::ProcessCont
   using utils::setKafkaConfigurationField;
 
   const std::vector<std::string> dynamic_prop_keys = 
context.getDynamicPropertyKeys();
-  if (dynamic_prop_keys.empty()) { return; }
-  logger_->log_info(
-      "Loading {} extra kafka configuration fields from ConsumeKafka dynamic "
-      "properties:",
-      dynamic_prop_keys.size());
-  for (const std::string& key: dynamic_prop_keys) {
-    std::string value;
-    gsl_Expects(context.getDynamicProperty(key, value));
+  if (dynamic_prop_keys.empty()) {
+    return;
+  }
+  logger_->log_info("Loading {} extra kafka configuration fields from 
ConsumeKafka dynamic properties:", dynamic_prop_keys.size());
+  for (const std::string& key : dynamic_prop_keys) {
+    std::string value = context.getDynamicProperty(key) | utils::expect("");

Review Comment:
   I understand this exception can't really happen, but despite (or because of) 
this, I would prefer to have a non-empty error message, ideally containing 
`key` and something like "this should never happen", to help debugging if it 
happens anyway.



##########
minifi-api/include/minifi-cpp/core/Property.h:
##########
@@ -53,158 +47,67 @@ class Property {
 
   Property();
 
-  Property(const PropertyReference&);
+  Property(const PropertyReference &);
 
   virtual ~Property() = default;
 
-  void setTransient() {
-    is_transient_ = true;
-  }
+  void setTransient() { is_transient_ = true; }
 
-  bool isTransient() const {
-    return is_transient_;
-  }
+  bool isTransient() const { return is_transient_; }
+  std::vector<std::string> getAllowedValues() const { return allowed_values_; }
+  void setAllowedValues(std::vector<std::string> allowed_values) { 
allowed_values_ = std::move(allowed_values); }
+  std::optional<std::string> getDefaultValue() const { return default_value_; }
   std::string getName() const;
   std::string getDisplayName() const;
   std::vector<std::string> getAllowedTypes() const;
   std::string getDescription() const;
   const PropertyValidator& getValidator() const;
-  const PropertyValue &getValue() const;
+  [[nodiscard]] nonstd::expected<std::span<const std::string>, 
std::error_code> getAllValues() const;
+  [[nodiscard]] nonstd::expected<std::string_view, std::error_code> getValue() 
const;
+  nonstd::expected<void, std::error_code> setValue(std::string value);
+  nonstd::expected<void, std::error_code> appendValue(std::string value);
+  void clearValues();
   bool getRequired() const;
   bool isSensitive() const;
   bool supportsExpressionLanguage() const;
   std::vector<std::string> getDependentProperties() const;
   std::vector<std::pair<std::string, std::string>> getExclusiveOfProperties() 
const;
   std::vector<std::string> getValues();
-
-  const PropertyValue &getDefaultValue() const {
-    return default_value_;
-  }
-
-  template<typename T = std::string>
-  void setValue(const T &value) {
-    if (!is_collection_) {
-      values_.clear();
-      values_.push_back(default_value_);
-    } else {
-      values_.push_back(default_value_);
-    }
-    PropertyValue& vn = values_.back();
-    vn.setValidator(*validator_);
-    vn = value;
-    ValidationResult result = vn.validate(name_);
-    if (!result.valid) {
-      throw utils::internal::InvalidValueException(name_ + " value validation 
failed");
-    }
+  PropertyReference getReference() const {
+    return PropertyReference(name_, display_name_, description_, is_required_, 
is_sensitive_, {}, {}, {}, {}, default_value_, validator_, supports_el_);

Review Comment:
   This is dangerous, as it returns a view to the string inside the `Property`, 
so it will become dangling if `this` is deleted. If the only purpose of 
`getReference()` is to avoid a copy, then I think copying would be better.
   
   I'm sure you have checked and this is not a problem right now, but it could 
become a problem later.
   
   Maybe we can avoid copying in some other way, e.g. using the name of the 
property instead of the whole object.



##########
extensions/expression-language/tests/ProcessContextExprTests.cpp:
##########
@@ -99,37 +99,4 @@ TEST_CASE("ProcessContextExpr can update existing processor 
properties", "[setPr
       
CHECK(context->getProperty(minifi::DummyProcessor::ExpressionLanguageProperty, 
nullptr) == "bar");
     }
   }
-
-  SECTION("Set and get simple dynamic property") {
-    static constexpr auto simple_property_definition = 
minifi::core::PropertyDefinitionBuilder<>::createProperty("Simple Dynamic 
Property")
-        .withDescription("A simple dynamic string property")
-        .build();
-    core::Property simple_property{simple_property_definition};
-    std::string property_value;
-
-    context->setDynamicProperty(simple_property.getName(), "foo");
-    CHECK(context->getDynamicProperty(simple_property, property_value, 
nullptr));
-    CHECK(property_value == "foo");
-
-    context->setDynamicProperty(simple_property.getName(), "bar");
-    CHECK(context->getDynamicProperty(simple_property, property_value, 
nullptr));
-    CHECK(property_value == "bar");
-  }
-
-  SECTION("Set and get expression language dynamic property") {
-    static constexpr auto expression_language_property_definition = 
minifi::core::PropertyDefinitionBuilder<>::createProperty("Expression Language 
Dynamic Property")
-        .withDescription("A dynamic property which supports expression 
language")
-        .supportsExpressionLanguage(true)
-        .build();
-    core::Property 
expression_language_property{expression_language_property_definition};
-    std::string property_value;
-
-    context->setDynamicProperty(expression_language_property.getName(), "foo");
-    CHECK(context->getDynamicProperty(expression_language_property, 
property_value, nullptr));
-    CHECK(property_value == "foo");
-
-    context->setDynamicProperty(expression_language_property.getName(), "bar");
-    CHECK(context->getDynamicProperty(expression_language_property, 
property_value, nullptr));
-    CHECK(property_value == "bar");
-  }

Review Comment:
   Why were these deleted? The `get/setDynamicProperty()` functions still exist.



##########
utils/src/core/PropertyType.cpp:
##########


Review Comment:
   this file should be deleted



##########
extensions/gcp/processors/FetchGCSObject.cpp:
##########
@@ -124,18 +124,19 @@ void FetchGCSObject::onTrigger(core::ProcessContext& 
context, core::ProcessSessi
   FetchFromGCSCallback callback(client, *bucket, *object_name);
   callback.setEncryptionKey(encryption_key_);
 
-  if (auto gen_str = context.getProperty(ObjectGeneration, flow_file.get()); 
gen_str && !gen_str->empty()) {
-    try {
-      int64_t gen = 0;
-      utils::internal::ValueParser(*gen_str).parse(gen).parseEnd();
-      callback.setGeneration(gcs::Generation(gen));
-    } catch (const utils::internal::ValueException&) {
-      logger_->log_error("Invalid generation: {}", *gen_str);
+  gcs::Generation generation;
+  if (const auto object_generation_str =  
context.getProperty(ObjectGeneration, flow_file.get()); object_generation_str 
&& !object_generation_str->empty()) {
+    if (const auto geni64 = 
parsing::parseIntegral<int64_t>(*object_generation_str)) {
+      generation = gcs::Generation{*geni64};
+    } else {
+      logger_->log_error("Invalid generation: {}", *object_generation_str);
       session.transfer(flow_file, Failure);
       return;
     }
   }
 
+  callback.setGeneration(generation);

Review Comment:
   The logic has changed here: previously, if the property was not set, we did 
not call `setGeneration()`; now we call it with a default-constructed 
`gcs::Generation` argument. Is this intentional?



##########
utils/include/utils/expected.h:
##########
@@ -176,11 +177,26 @@ auto operator|(Expected&& object, 
transform_error_wrapper<F> f) {
   static_assert(valid_unexpected_type<transformed_error_type>, "transformError 
expects a function returning a valid unexpected type");
   using transformed_expected_type = nonstd::expected<value_type, 
transformed_error_type>;
   if (object.has_value()) {
-    return transformed_expected_type{std::forward<Expected>(object)};
+    return transformed_expected_type{std::forward<Expected>(object).value()};
   }
   return transformed_expected_type{nonstd::unexpect, 
std::invoke(std::forward<F>(f.function), 
std::forward<Expected>(object).error())};
 }
 
+template<expected Expected>
+std::optional<typename std::remove_cvref_t<Expected>::value_type> 
operator|(Expected&& object, to_optional_wrapper) {
+  if (object) {
+    return std::move(*object);
+  }
+  return std::nullopt;
+}
+
+template<expected Expected>
+typename std::remove_cvref_t<Expected>::value_type operator|(Expected&& 
object, expect_wrapper e) {
+  if (object) {
+    return std::move(*object);
+  }
+  throw std::runtime_error(e.reason);

Review Comment:
   It would be nice to avoid losing information if we can, for example:
   * `throw std::runtime_error(fmt::format("{}: {}", e.reason, 
object.error().message()))` if `Expected::error_type` is `error_code`;
   * `throw std::runtime_error(fmt::format("{}: {}", e.reason, 
object.error()))` if `Expected::error_type` is printable using `fmt::format()`.
   



##########
utils/src/core/ConfigurableComponentImpl.cpp:
##########
@@ -0,0 +1,149 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "core/ConfigurableComponentImpl.h"
+
+#include "minifi-cpp/core/Property.h"
+#include "utils/PropertyErrors.h"
+
+namespace org::apache::nifi::minifi::core {
+void ConfigurableComponentImpl::setSupportedProperties(std::span<const 
core::PropertyReference> properties) {
+  if (!canEdit()) { return; }
+
+  std::lock_guard<std::mutex> lock(configuration_mutex_);
+
+  supported_properties_.clear();
+  for (const auto& item: properties) { 
supported_properties_.emplace(item.name, item); }
+}
+
+nonstd::expected<std::string, std::error_code> 
ConfigurableComponentImpl::getProperty(const std::string_view name) const {
+  const std::lock_guard<std::mutex> lock(configuration_mutex_);
+  const auto it = supported_properties_.find(name);
+  if (it == supported_properties_.end()) { return 
nonstd::make_unexpected(PropertyErrorCode::NotSupportedProperty); }
+  const Property& prop = it->second;
+  return prop.getValue() | utils::transform([](const std::string_view 
value_view) -> std::string { return std::string{value_view}; });
+}
+
+nonstd::expected<void, std::error_code> 
ConfigurableComponentImpl::setProperty(const std::string_view name, std::string 
value) {
+  const std::lock_guard<std::mutex> lock(configuration_mutex_);
+  const auto it = supported_properties_.find(name);
+  if (it == supported_properties_.end()) { return 
nonstd::make_unexpected(PropertyErrorCode::NotSupportedProperty); }
+  Property& prop = it->second;
+
+  return prop.setValue(std::move(value));
+}
+
+nonstd::expected<void, std::error_code> 
ConfigurableComponentImpl::clearProperty(const std::string_view name) {
+  const std::lock_guard<std::mutex> lock(configuration_mutex_);
+  const auto it = supported_properties_.find(name);
+  if (it == supported_properties_.end()) { return 
nonstd::make_unexpected(PropertyErrorCode::NotSupportedProperty); }
+  Property& prop = it->second;
+
+  prop.clearValues();
+  return {};
+}
+
+nonstd::expected<void, std::error_code> 
ConfigurableComponentImpl::appendProperty(const std::string_view name, 
std::string value) {
+  const std::lock_guard<std::mutex> lock(configuration_mutex_);
+  const auto it = supported_properties_.find(name);
+  if (it == supported_properties_.end()) { return 
nonstd::make_unexpected(PropertyErrorCode::NotSupportedProperty); }
+  Property& prop = it->second;
+
+  return prop.appendValue(std::move(value));
+}
+
+nonstd::expected<std::string, std::error_code> 
ConfigurableComponentImpl::getDynamicProperty(const std::string_view name) 
const {
+  const std::lock_guard<std::mutex> lock(configuration_mutex_);
+  if (!supportsDynamicProperties()) {
+    return 
nonstd::make_unexpected(PropertyErrorCode::DynamicPropertiesNotSupported);
+  }
+  const auto it = dynamic_properties_.find(name);
+  if (it == dynamic_properties_.end()) { return 
nonstd::make_unexpected(PropertyErrorCode::PropertyNotSet); }
+  const Property& prop = it->second;
+  return prop.getValue() | utils::transform([](const std::string_view 
value_view) -> std::string { return std::string{value_view}; });
+}
+
+nonstd::expected<void, std::error_code> 
ConfigurableComponentImpl::setDynamicProperty(std::string name, std::string 
value) {
+  const std::lock_guard<std::mutex> lock(configuration_mutex_);
+  if (!supportsDynamicProperties()) {
+    return 
nonstd::make_unexpected(PropertyErrorCode::DynamicPropertiesNotSupported);
+  }
+  Property& prop = dynamic_properties_[std::move(name)];
+  return prop.setValue(std::move(value));
+}
+
+nonstd::expected<void, std::error_code> 
ConfigurableComponentImpl::appendDynamicProperty(const std::string_view name, 
std::string value) {
+  const std::lock_guard<std::mutex> lock(configuration_mutex_);
+  if (!supportsDynamicProperties()) {
+    return 
nonstd::make_unexpected(PropertyErrorCode::DynamicPropertiesNotSupported);
+  }
+  Property& prop = dynamic_properties_[std::string{name}];
+  return prop.appendValue(std::move(value));
+}
+
+std::vector<std::string> ConfigurableComponentImpl::getDynamicPropertyKeys() 
const {
+  std::lock_guard<std::mutex> lock(configuration_mutex_);
+
+  return dynamic_properties_ | ranges::views::transform([](const auto& kv) { 
return kv.first; }) | ranges::to<std::vector>();
+}
+
+std::map<std::string, std::string> 
ConfigurableComponentImpl::getDynamicProperties() const {
+  std::lock_guard<std::mutex> lock(configuration_mutex_);
+  std::map<std::string, std::string> result;
+  for(const auto& [key,value]: dynamic_properties_) {
+      result[key] = value.getValue().value_or("");
+  }
+  return result;
+}
+
+nonstd::expected<PropertyReference, std::error_code> 
ConfigurableComponentImpl::getPropertyReference(const std::string_view name) 
const {

Review Comment:
   I don't like this for the same reason as `Property::getReference()`. Can we 
get rid of it?



##########
extensions/kafka/PublishKafka.cpp:
##########
@@ -335,48 +335,35 @@ void PublishKafka::onSchedule(core::ProcessContext& 
context, core::ProcessSessio
   interrupted_ = false;
 
   // Try to get a KafkaConnection
-  std::string client_id;
-  std::string brokers;
-  if (!context.getProperty(ClientName, client_id, nullptr)) {
-    throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Client Name property missing 
or invalid");
-  }
-  if (!context.getProperty(SeedBrokers, brokers, nullptr)) {
-    throw Exception(PROCESS_SCHEDULE_EXCEPTION, "Known Brokers property 
missing or invalid");
-  }
-
+  std::string client_id = utils::parseProperty(context, ClientName);
+  std::string brokers = utils::parseProperty(context, SeedBrokers);
   // Get some properties not (only) used directly to set up librdkafka
 
   // Batch Size
-  context.getProperty(BatchSize, batch_size_);
+  batch_size_ = utils::parseU64Property(context, BatchSize);
   logger_->log_debug("PublishKafka: Batch Size [{}]", batch_size_);
 
   // Target Batch Payload Size
-  context.getProperty(TargetBatchPayloadSize, target_batch_payload_size_);
+  target_batch_payload_size_ = utils::parseDataSizeProperty(context, 
TargetBatchPayloadSize);
   logger_->log_debug("PublishKafka: Target Batch Payload Size [{}]", 
target_batch_payload_size_);
 
   // Max Flow Segment Size
-  context.getProperty(MaxFlowSegSize, max_flow_seg_size_);
+  max_flow_seg_size_ = utils::parseDataSizeProperty(context, MaxFlowSegSize);
   logger_->log_debug("PublishKafka: Max Flow Segment Size [{}]", 
max_flow_seg_size_);
 
   // Attributes to Send as Headers
-  if (const auto attribute_name_regex = 
context.getProperty(AttributeNameRegex);
-      attribute_name_regex && !attribute_name_regex->empty()) {
-    attributeNameRegex_ = utils::Regex(*attribute_name_regex);
-    logger_->log_debug("PublishKafka: AttributeNameRegex [{}]", 
*attribute_name_regex);
-  }
+  attributeNameRegex_ = context.getProperty(AttributeNameRegex)
+    | utils::transform([](const auto pattern_str) { return 
utils::Regex{pattern_str}; })

Review Comment:
   very minor, but I think we want to move this:
   ```suggestion
       | utils::transform([](const auto pattern_str) { return 
utils::Regex{std::move(pattern_str)}; })
   ```



##########
utils/src/core/ConfigurableComponentImpl.cpp:
##########
@@ -0,0 +1,149 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "core/ConfigurableComponentImpl.h"
+
+#include "minifi-cpp/core/Property.h"
+#include "utils/PropertyErrors.h"
+
+namespace org::apache::nifi::minifi::core {
+void ConfigurableComponentImpl::setSupportedProperties(std::span<const 
core::PropertyReference> properties) {
+  if (!canEdit()) { return; }
+
+  std::lock_guard<std::mutex> lock(configuration_mutex_);
+
+  supported_properties_.clear();
+  for (const auto& item: properties) { 
supported_properties_.emplace(item.name, item); }
+}
+
+nonstd::expected<std::string, std::error_code> 
ConfigurableComponentImpl::getProperty(const std::string_view name) const {
+  const std::lock_guard<std::mutex> lock(configuration_mutex_);
+  const auto it = supported_properties_.find(name);
+  if (it == supported_properties_.end()) { return 
nonstd::make_unexpected(PropertyErrorCode::NotSupportedProperty); }
+  const Property& prop = it->second;
+  return prop.getValue() | utils::transform([](const std::string_view 
value_view) -> std::string { return std::string{value_view}; });
+}
+
+nonstd::expected<void, std::error_code> 
ConfigurableComponentImpl::setProperty(const std::string_view name, std::string 
value) {
+  const std::lock_guard<std::mutex> lock(configuration_mutex_);
+  const auto it = supported_properties_.find(name);
+  if (it == supported_properties_.end()) { return 
nonstd::make_unexpected(PropertyErrorCode::NotSupportedProperty); }
+  Property& prop = it->second;
+
+  return prop.setValue(std::move(value));
+}
+
+nonstd::expected<void, std::error_code> 
ConfigurableComponentImpl::clearProperty(const std::string_view name) {
+  const std::lock_guard<std::mutex> lock(configuration_mutex_);
+  const auto it = supported_properties_.find(name);
+  if (it == supported_properties_.end()) { return 
nonstd::make_unexpected(PropertyErrorCode::NotSupportedProperty); }
+  Property& prop = it->second;
+
+  prop.clearValues();
+  return {};
+}
+
+nonstd::expected<void, std::error_code> 
ConfigurableComponentImpl::appendProperty(const std::string_view name, 
std::string value) {
+  const std::lock_guard<std::mutex> lock(configuration_mutex_);
+  const auto it = supported_properties_.find(name);
+  if (it == supported_properties_.end()) { return 
nonstd::make_unexpected(PropertyErrorCode::NotSupportedProperty); }
+  Property& prop = it->second;
+
+  return prop.appendValue(std::move(value));
+}
+
+nonstd::expected<std::string, std::error_code> 
ConfigurableComponentImpl::getDynamicProperty(const std::string_view name) 
const {
+  const std::lock_guard<std::mutex> lock(configuration_mutex_);
+  if (!supportsDynamicProperties()) {
+    return 
nonstd::make_unexpected(PropertyErrorCode::DynamicPropertiesNotSupported);
+  }
+  const auto it = dynamic_properties_.find(name);
+  if (it == dynamic_properties_.end()) { return 
nonstd::make_unexpected(PropertyErrorCode::PropertyNotSet); }
+  const Property& prop = it->second;
+  return prop.getValue() | utils::transform([](const std::string_view 
value_view) -> std::string { return std::string{value_view}; });
+}
+
+nonstd::expected<void, std::error_code> 
ConfigurableComponentImpl::setDynamicProperty(std::string name, std::string 
value) {
+  const std::lock_guard<std::mutex> lock(configuration_mutex_);
+  if (!supportsDynamicProperties()) {
+    return 
nonstd::make_unexpected(PropertyErrorCode::DynamicPropertiesNotSupported);
+  }
+  Property& prop = dynamic_properties_[std::move(name)];
+  return prop.setValue(std::move(value));
+}

Review Comment:
   OK, I think I see the answer to my earlier question: previously, dynamic 
properties were explicitly set to support expression language, now they are 
left as `supports_el_ == false` but are implicitly treated as supporting EL.
   
   If we are planning to remove this flag in the near future and allow all 
properties to support EL, then this is fine. Otherwise, I would prefer to 
continue to explicitly mark dynamic properties with `supports_el_ == true`.



##########
utils/include/utils/ParsingUtils.h:
##########
@@ -0,0 +1,94 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+
+
+#include "ParsingErrors.h"
+#include "StringUtils.h"
+#include "TimeUtil.h"
+#include "Literals.h"
+#include "fmt/format.h"
+
+namespace org::apache::nifi::minifi::parsing {
+nonstd::expected<bool, std::error_code> parseBool(std::string_view input);
+
+template<std::integral T>
+nonstd::expected<T, std::error_code> parseIntegralMinMax(std::string_view 
input, T minimum, T maximum);
+template<std::integral T>
+nonstd::expected<T, std::error_code> parseIntegral(std::string_view input);
+
+template<class TargetDuration>
+nonstd::expected<TargetDuration, std::error_code> 
parseDurationMinMax(std::string_view input, TargetDuration minimum, 
TargetDuration maximum);
+template<class TargetDuration = std::chrono::milliseconds>
+nonstd::expected<TargetDuration, std::error_code> 
parseDuration(std::string_view input);
+
+nonstd::expected<uint64_t, std::error_code> 
parseDataSizeMinMax(std::string_view input, uint64_t minimum, uint64_t maximum);
+nonstd::expected<uint64_t, std::error_code> parseDataSize(std::string_view 
input);
+
+nonstd::expected<uint32_t, std::error_code> parsePermissions(std::string_view 
input);
+
+
+template<std::integral T>
+nonstd::expected<T, std::error_code> parseIntegralMinMax(const 
std::string_view input, const T minimum, const T maximum) {
+  const auto trimmed_input = utils::string::trim(input);
+  T t{};
+  const auto [ptr, ec] = std::from_chars(trimmed_input.data(), 
trimmed_input.data() + trimmed_input.size(), t);
+  if (ec != std::errc()) {
+    return 
nonstd::make_unexpected(core::ParsingErrorCode::GeneralParsingError);
+  }
+  if (ptr != trimmed_input.data() + trimmed_input.size()) {
+    return 
nonstd::make_unexpected(core::ParsingErrorCode::GeneralParsingError);
+  }
+
+  if (t < minimum) { return 
nonstd::make_unexpected(core::ParsingErrorCode::SmallerThanMinimum); }
+  if (t > maximum) { return 
nonstd::make_unexpected(core::ParsingErrorCode::LargerThanMaximum); }
+  return t;
+}
+
+template<std::integral T>
+nonstd::expected<T, std::error_code> parseIntegral(const std::string_view 
input) {
+  return parseIntegralMinMax<T>(input, std::numeric_limits<T>::min(), 
std::numeric_limits<T>::max());
+}
+
+template<class TargetDuration>

Review Comment:
   `TargetDuration` could default to `milliseconds`, for the cases where we are 
only interested in whether `input` is a valid value



##########
extensions/mqtt/processors/PublishMQTT.cpp:
##########
@@ -138,11 +132,15 @@ bool PublishMQTT::sendMessage(const 
std::vector<std::byte>& buffer, const std::s
 }
 
 void PublishMQTT::checkProperties() {
+  auto is_property_explicitly_set = [this](const std::string_view 
property_name) -> bool {
+    const auto property_values = getAllPropertyValues(property_name) | 
utils::expect("It should only be called on valid property");

Review Comment:
   Here, and in a few more places, it would make more sense to abort (with e.g. 
a `gsl_Expect`) than to throw an exception, since this would be a programming 
error.
   
   Maybe we could have another version of `utils::expect` (with a different 
name) which aborts instead of throwing?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to