Webthing-CPP: a modern CPP implementation of the WebThings API


LCOV - code coverage report
Current view: top level - webthing - property.hpp (source / functions) Coverage Total Hit
Test: filtered_coverage.info Lines: 100.0 % 66 66
Test Date: 2025-03-15 12:45:00 Functions: 85.5 % 69 59

            Line data    Source code
       1              : // Webthing-CPP
       2              : // SPDX-FileCopyrightText: 2023-present Benno Waldhauer
       3              : // SPDX-License-Identifier: MIT
       4              : 
       5              : #pragma once
       6              : 
       7              : #include <string>
       8              : #include <bw/webthing/errors.hpp>
       9              : #include <bw/webthing/json_validator.hpp>
      10              : #include <bw/webthing/utils.hpp>
      11              : #include <bw/webthing/value.hpp>
      12              : 
      13              : namespace bw::webthing {
      14              : 
      15              : template<class T> class Property;
      16              : 
      17              : template<class T>
      18           30 : json property_status_message(const Property<T>& property)
      19              : {
      20          240 :     return json({
      21              :         {"messageType", "propertyStatus"}, 
      22              :         {"data", property_value_object(property)}
      23          120 :     });
      24          180 : }
      25              : 
      26              : template<class T>
      27           45 : json property_value_object(const Property<T>& property)
      28              : {
      29           45 :     auto value_as_json = property.get_value() ? json(*property.get_value()) : json();
      30          225 :     return json({{property.get_name(), value_as_json}});
      31          180 : }
      32              : 
      33              : class PropertyBase
      34              : {
      35              : public:
      36              : 
      37           29 :     PropertyBase(std::string name, json metadata, bool wraps_double)
      38           29 :         : name(name)
      39           29 :         , metadata(metadata)
      40           29 :         , wraps_double(wraps_double)
      41              :     {
      42           29 :         if(this->metadata.type() != json::value_t::object)
      43            3 :             throw PropertyError("Only json::object is allowed as meta data.");
      44              : 
      45           28 :         href = "/properties/" + this->name;
      46           32 :     }
      47              : 
      48           59 :     virtual ~PropertyBase() = default;
      49              :     virtual json get_property_value_object() const = 0;
      50              : 
      51           17 :     json as_property_description() const
      52              :     {
      53           17 :         json description = metadata;
      54              : 
      55           17 :         if(!description["links"].is_array())
      56           17 :             description["links"] = json::array();
      57              : 
      58          136 :         description["links"] += {{"rel", "property"}, {"href", href_prefix + href}};
      59              : 
      60           17 :         return description;
      61          119 :     }
      62              : 
      63           29 :     void set_href_prefix(std::string prefix)
      64              :     {
      65           29 :         href_prefix = prefix;
      66           29 :     }
      67              : 
      68              :     std::string get_href() const
      69              :     {
      70              :         return href_prefix +  href;
      71              :     }
      72              : 
      73           62 :     std::string get_name() const
      74              :     {
      75           62 :         return name;
      76              :     }
      77              : 
      78            1 :     json get_metadata() const
      79              :     {
      80            1 :         return metadata;
      81              :     }
      82              : 
      83           24 :     template<class T> std::optional<T> get_value() const
      84              :     {
      85           24 :         return dynamic_cast<const Property<T>&>(*this).get_value();
      86              :     }
      87              : 
      88           36 :     template<class T> void set_value(T value)
      89              :     {
      90              :         try{
      91           30 :            if(wraps_double && !std::is_same_v<T, double>)
      92            1 :                return set_value(try_static_cast<double>(value));
      93              : 
      94           35 :             auto property = dynamic_cast<Property<T>&>(*this);
      95           34 :             property.set_value(value);
      96           31 :         }
      97           14 :         catch(std::bad_cast&)
      98              :         {
      99           12 :             throw PropertyError("Property value type not matching");
     100              :         }
     101              :     }
     102              : 
     103              : protected:
     104              :     std::string name;
     105              :     std::string href_prefix;
     106              :     std::string href;
     107              :     json metadata;
     108              :     const bool wraps_double;
     109              : };
     110              : 
     111              : typedef std::function<void (json)> PropertyChangedCallback;
     112              : 
     113              : template<class T>
     114              : class Property : public PropertyBase
     115              : {
     116              : public:
     117           29 :     Property(PropertyChangedCallback changed_callback, std::string name, std::shared_ptr<Value<T>> value, json metadata = json::object())
     118              :         : PropertyBase(name, metadata, std::is_same_v<T, double>)
     119           28 :         , property_change_callback(changed_callback)
     120           31 :         , value(value)
     121              :     {
     122              :         // Add value change observer to notify the Thing about a property change.
     123           28 :         if(property_change_callback)
     124           58 :             this->value->add_observer([&](auto v){property_change_callback(property_status_message(*this));});
     125           28 :     }
     126              : 
     127              :     // Validate new proptery value before setting it.
     128           31 :     void validate_value(const T& value) const
     129              :     {
     130           31 :         if(metadata.contains("readOnly"))
     131              :         {
     132            2 :             auto json_ro = metadata["readOnly"];
     133            2 :             bool read_only = json_ro.is_boolean() && json_ro.template get<bool>();
     134            2 :             if(read_only)
     135            3 :                 throw PropertyError("Read-only property");
     136            2 :         }
     137              : 
     138              :         try
     139              :         {
     140           35 :             validate_value_by_scheme(value, metadata);
     141              :         }
     142           10 :         catch(std::exception& ex)
     143              :         {
     144           15 :             throw PropertyError("Invalid property value - " + std::string(ex.what()));
     145              :         }
     146           25 :     }
     147              : 
     148           15 :     json get_property_value_object() const
     149              :     {
     150           15 :         return property_value_object(*this);
     151              :     }
     152              : 
     153              :     // Get the current property value.
     154          114 :     std::optional<T> get_value() const
     155              :     {
     156          114 :         return value->get();
     157              :     }
     158              : 
     159              :     // Set the current value of the property.
     160              :     // throws PropertyError If value could not be set.
     161           31 :     void set_value(T value)
     162              :     {
     163           31 :         this->validate_value(value);
     164           25 :         this->value->set(value);
     165           25 :     }
     166              : 
     167              : private:
     168              :     std::shared_ptr<Value<T>> value;
     169              :     PropertyChangedCallback property_change_callback;
     170              : };
     171              : 
     172              : } // bw::webthing
        

Generated by: LCOV version 2.0-1