Document key-value tree transforms and improve checks
authorTeemu Murtola <teemu.murtola@gmail.com>
Fri, 11 Nov 2016 19:02:19 +0000 (21:02 +0200)
committerMark Abraham <mark.j.abraham@gmail.com>
Thu, 9 Feb 2017 13:10:49 +0000 (14:10 +0100)
Add assertions to provide better messages in case of unintended usage,
and document the interface used to specify rules.  This should cover all
of the key-value tree code that is currently exposed for mdp input
handling through IInputRecExtension, with the exception of
KeyValueTreeObjectBuilder that is exposed if one uses toObject().

Change-Id: Ic36a2c41ba418abfa46cc9b37d6cd7eed5a896b6

src/gromacs/utility/keyvaluetree.cpp
src/gromacs/utility/keyvaluetree.h
src/gromacs/utility/keyvaluetreebuilder.h
src/gromacs/utility/keyvaluetreetransform.cpp
src/gromacs/utility/keyvaluetreetransform.h

index 34216610f1ce44078bf429d16610bc654b7bbde5..c3791a72cf443c71ce69846f2c4d854d35e1f61e 100644 (file)
@@ -79,6 +79,11 @@ std::string formatSingleValue(const KeyValueTreeValue &value)
  * KeyValueTreePath
  */
 
+KeyValueTreePath::KeyValueTreePath(const char *path)
+    : path_(splitPathElements(path))
+{
+}
+
 KeyValueTreePath::KeyValueTreePath(const std::string &path)
     : path_(splitPathElements(path))
 {
@@ -93,6 +98,24 @@ std::string KeyValueTreePath::toString() const
  * KeyValueTreeObject
  */
 
+bool KeyValueTreeObject::hasDistinctProperties(const KeyValueTreeObject &obj) const
+{
+    for (const auto &prop : obj.values_)
+    {
+        if (keyExists(prop.key()))
+        {
+            GMX_RELEASE_ASSERT(!prop.value().isArray(),
+                               "Comparison of arrays not implemented");
+            if (prop.value().isObject() && valueMap_.at(prop.key()).isObject())
+            {
+                return valueMap_.at(prop.key()).asObject().hasDistinctProperties(prop.value().asObject());
+            }
+            return false;
+        }
+    }
+    return true;
+}
+
 void KeyValueTreeObject::writeUsing(TextWriter *writer) const
 {
     for (const auto &prop : properties())
index 0f0c438012671a3b78e2f693483301dcb6402b5f..0da98a390b49360cd449677225e18ab9bb226505 100644 (file)
@@ -60,14 +60,41 @@ class KeyValueTreeArray;
 class KeyValueTreeObject;
 class TextWriter;
 
+/*! \libinternal \brief
+ * Identifies an entry in a key-value tree.
+ *
+ * This class is mainly an internal utility within the key-value tree
+ * implementation, but it is exposed on the API level where string-based
+ * specification of a location in the tree is necessary.  Constructors are not
+ * explicit to allow passing a simple string in contexts where a
+ * KeyValueTreePath is expected.
+ *
+ * The string specifying a location should start with a `/`, followed by the
+ * names of the properties separated by `/`.  For example, `/a/b/c` specifies
+ * property `c` in an object that is the value of `b` in an object that is the
+ * value of `a` at the root of the tree.
+ * Currently, there is no support for specifying paths to values within arrays
+ * (since none of the places where this is used implement array handling,
+ * either).
+ *
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
 class KeyValueTreePath
 {
     public:
+        //! Creates an empty path (corresponds to the root object).
         KeyValueTreePath() = default;
+        //! Creates a path from given string representation.
+        KeyValueTreePath(const char *path);
+        //! Creates a path from given string representation.
         KeyValueTreePath(const std::string &path);
 
+        //! Adds another element to the path, making it a child of the old path.
         void append(const std::string &key) { path_.push_back(key); }
+        //! Removes the last element in the path, making it the parent path.
         void pop_back() { return path_.pop_back(); }
+        //! Removes and returns the last element in the path.
         std::string pop_last()
         {
             std::string result = std::move(path_.back());
@@ -75,11 +102,16 @@ class KeyValueTreePath
             return result;
         }
 
+        //! Whether the path is empty (pointing to the root object).
         bool empty() const { return path_.empty(); }
+        //! Returns the number of elements (=nesting level) in the path.
         size_t size() const { return path_.size(); }
+        //! Returns the i'th path element.
         const std::string &operator[](int i) const { return path_[i]; }
+        //! Returns all the path elements.
         const std::vector<std::string> &elements() const { return path_; }
 
+        //! Formats the path as a string for display.
         std::string toString() const;
 
     private:
@@ -95,6 +127,8 @@ class KeyValueTreeValue
         bool isType() const { return value_.isType<T>(); }
         std::type_index type() const { return value_.type(); }
 
+        KeyValueTreeArray &asArray();
+        KeyValueTreeObject       &asObject();
         const KeyValueTreeArray  &asArray() const;
         const KeyValueTreeObject &asObject() const;
         template <typename T>
@@ -105,9 +139,6 @@ class KeyValueTreeValue
     private:
         explicit KeyValueTreeValue(Variant &&value) : value_(std::move(value)) {}
 
-        KeyValueTreeArray &asArray();
-        KeyValueTreeObject &asObject();
-
         Variant             value_;
 
         friend class KeyValueTreeBuilder;
@@ -182,6 +213,7 @@ class KeyValueTreeObject
             return valueMap_.at(key);
         }
 
+        bool hasDistinctProperties(const KeyValueTreeObject &obj) const;
         void writeUsing(TextWriter *writer) const;
 
     private:
@@ -192,6 +224,7 @@ class KeyValueTreeObject
         std::map<std::string, KeyValueTreeValue>::iterator
         addProperty(const std::string &key, KeyValueTreeValue &&value)
         {
+            GMX_RELEASE_ASSERT(!keyExists(key), "Duplicate key value");
             values_.reserve(values_.size() + 1);
             auto iter = valueMap_.insert(std::make_pair(key, std::move(value))).first;
             values_.push_back(KeyValueTreeProperty(iter));
index d8ce0d9edd2e7fe6b032ac31119ebeeb06ed02b5..4fd4ff3af3d62963a1a0d21d6576e792f39db026 100644 (file)
@@ -47,6 +47,7 @@
 #include <utility>
 #include <vector>
 
+#include "gromacs/utility/gmxassert.h"
 #include "gromacs/utility/keyvaluetree.h"
 #include "gromacs/utility/variant.h"
 
@@ -213,6 +214,11 @@ class KeyValueTreeObjectBuilder
             auto iter = object_->addProperty(key, KeyValueTreeBuilder::createValue<KeyValueTreeArray>());
             return KeyValueTreeObjectArrayBuilder(&iter->second.asArray());
         }
+
+        bool objectHasDistinctProperties(const KeyValueTreeObject &obj) const
+        {
+            return object_->hasDistinctProperties(obj);
+        }
         void mergeObject(KeyValueTreeValue &&value)
         {
             mergeObject(std::move(value.asObject()));
@@ -226,7 +232,12 @@ class KeyValueTreeObjectBuilder
         }
 
         bool keyExists(const std::string &key) const { return object_->keyExists(key); }
-        KeyValueTreeObjectBuilder getObject(const std::string &key) const
+        const KeyValueTreeValue &getValue(const std::string &key) const
+        {
+            GMX_ASSERT(keyExists(key), "Requested non-existent value");
+            return (*object_)[key];
+        }
+        KeyValueTreeObjectBuilder getObject(const std::string &key)
         {
             return KeyValueTreeObjectBuilder(&(*object_)[key].asObject());
         }
index cf80b3753a56335833ab80e7e588823a62da00d7..a204819f896efab0a84cb16a1c890de1c0884630 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2016, by the GROMACS development team, led by
+ * Copyright (c) 2016,2017, by the GROMACS development team, led by
  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
  * and including many others, as listed in the AUTHORS file in the
  * top-level source directory and at http://www.gromacs.org.
@@ -39,6 +39,7 @@
 #include <functional>
 #include <map>
 #include <memory>
+#include <typeindex>
 #include <vector>
 
 #include "gromacs/utility/exceptions.h"
@@ -90,11 +91,15 @@ class KeyValueTreeBackMapping : public IKeyValueTreeBackMapping
                 void setMapping(const KeyValueTreePath  &path,
                                 const KeyValueTreeValue &value)
                 {
+                    GMX_RELEASE_ASSERT(sourcePath_.empty(),
+                                       "Multiple entries map to same path");
                     if (value.isObject())
                     {
                         const KeyValueTreeObject &object = value.asObject();
                         for (const auto &prop : object.properties())
                         {
+                            GMX_RELEASE_ASSERT(!prop.value().isObject(),
+                                               "Nested objects not implemented");
                             childEntries_[prop.key()] = Entry(path);
                         }
                     }
@@ -153,7 +158,7 @@ class KeyValueTreeTransformerImpl : public IKeyValueTreeTransformRules
                 typedef std::map<std::string, Rule, StringCompare> ChildRuleMap;
 
                 explicit Rule(StringCompareType keyMatchType)
-                    : childRules_(keyMatchType)
+                    : expectedType_(typeid(void)), childRules_(keyMatchType)
                 {
                 }
 
@@ -205,6 +210,7 @@ class KeyValueTreeTransformerImpl : public IKeyValueTreeTransformRules
 
                 KeyValueTreePath            targetPath_;
                 std::string                 targetKey_;
+                std::type_index             expectedType_;
                 TransformFunction           transform_;
                 ChildRuleMap                childRules_;
         };
@@ -277,11 +283,16 @@ class KeyValueTreeTransformerImpl : public IKeyValueTreeTransformRules
 void KeyValueTreeTransformerImpl::Transformer::doTransform(
         const Rule *rule, const KeyValueTreeValue &value)
 {
-    if (rule->transform_)
+    if (rule->transform_ != nullptr)
     {
         KeyValueTreeValueBuilder valueBuilder;
         try
         {
+            if (value.type() != rule->expectedType_)
+            {
+                // TODO: Better error message.
+                GMX_THROW(InvalidInputError("Unexpected type of value"));
+            }
             rule->transform_(&valueBuilder, value);
         }
         catch (UserInputError &ex)
@@ -325,6 +336,8 @@ void KeyValueTreeTransformerImpl::Transformer::applyTransformedValue(
     {
         if (objBuilder.keyExists(key))
         {
+            GMX_RELEASE_ASSERT(objBuilder.getValue(key).isObject(),
+                               "Inconsistent transform (different items map to same path)");
             objBuilder = objBuilder.getObject(key);
         }
         else
@@ -337,7 +350,14 @@ void KeyValueTreeTransformerImpl::Transformer::applyTransformedValue(
     mapEntry->setMapping(context_, value);
     if (objBuilder.keyExists(rule->targetKey_))
     {
-        objBuilder.getObject(rule->targetKey_).mergeObject(std::move(value));
+        GMX_RELEASE_ASSERT(value.isObject(),
+                           "Inconsistent transform (different items map to same path)");
+        GMX_RELEASE_ASSERT(objBuilder.getValue(rule->targetKey_).isObject(),
+                           "Inconsistent transform (different items map to same path)");
+        objBuilder = objBuilder.getObject(rule->targetKey_);
+        GMX_RELEASE_ASSERT(objBuilder.objectHasDistinctProperties(value.asObject()),
+                           "Inconsistent transform (different items map to same path)");
+        objBuilder.mergeObject(std::move(value));
     }
     else
     {
@@ -393,23 +413,34 @@ class KeyValueTreeTransformRuleBuilder::Data
     public:
         typedef internal::KeyValueTreeTransformerImpl::Rule Rule;
 
-        Data() : keyMatchType_(StringCompareType::Exact) {}
+        Data()
+            : expectedType_(typeid(void)),
+              keyMatchType_(StringCompareType::Exact), keyMatchRule_(false)
+        {
+        }
 
         void createRule(internal::KeyValueTreeTransformerImpl *impl)
         {
-            if (toPath_.empty())
+            if (keyMatchRule_)
             {
                 createRuleWithKeyMatchType(impl);
                 return;
             }
+            GMX_RELEASE_ASSERT(transform_ != nullptr,
+                               "Transform has not been specified");
             Rule *rule = impl->getOrCreateRootRule();
             for (const std::string &key : fromPath_.elements())
             {
+                GMX_RELEASE_ASSERT(rule->targetKey_.empty(),
+                                   "Cannot specify multiple rules from a single path");
                 rule = rule->getOrCreateChildRule(key);
             }
-            rule->targetKey_  = toPath_.pop_last();
-            rule->targetPath_ = std::move(toPath_);
-            rule->transform_  = transform_;
+            GMX_RELEASE_ASSERT(rule->targetKey_.empty(),
+                               "Cannot specify multiple rules from a single path");
+            rule->targetKey_    = toPath_.pop_last();
+            rule->targetPath_   = std::move(toPath_);
+            rule->expectedType_ = expectedType_;
+            rule->transform_    = transform_;
         }
 
         void createRuleWithKeyMatchType(internal::KeyValueTreeTransformerImpl *impl)
@@ -432,8 +463,10 @@ class KeyValueTreeTransformRuleBuilder::Data
 
         KeyValueTreePath         fromPath_;
         KeyValueTreePath         toPath_;
+        std::type_index          expectedType_;
         Rule::TransformFunction  transform_;
         StringCompareType        keyMatchType_;
+        bool                     keyMatchRule_;
 };
 
 /********************************************************************
@@ -454,12 +487,17 @@ KeyValueTreeTransformRuleBuilder::~KeyValueTreeTransformRuleBuilder()
     }
 }
 
-void KeyValueTreeTransformRuleBuilder::setFromPath(const std::string &path)
+void KeyValueTreeTransformRuleBuilder::setFromPath(const KeyValueTreePath &path)
 {
     data_->fromPath_ = path;
 }
 
-void KeyValueTreeTransformRuleBuilder::setToPath(const std::string &path)
+void KeyValueTreeTransformRuleBuilder::setExpectedType(const std::type_index &type)
+{
+    data_->expectedType_ = type;
+}
+
+void KeyValueTreeTransformRuleBuilder::setToPath(const KeyValueTreePath &path)
 {
     data_->toPath_ = path;
 }
@@ -467,6 +505,7 @@ void KeyValueTreeTransformRuleBuilder::setToPath(const std::string &path)
 void KeyValueTreeTransformRuleBuilder::setKeyMatchType(StringCompareType keyMatchType)
 {
     data_->keyMatchType_ = keyMatchType;
+    data_->keyMatchRule_ = true;
 }
 
 void KeyValueTreeTransformRuleBuilder::addTransformToVariant(
index 68f121f36ffc45b49c37abff76037fa72e70682b..2c584609ed95a4377c604b89f8075fbe11e773de 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2016, by the GROMACS development team, led by
+ * Copyright (c) 2016,2017, by the GROMACS development team, led by
  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
  * and including many others, as listed in the AUTHORS file in the
  * top-level source directory and at http://www.gromacs.org.
@@ -36,6 +36,8 @@
  * \brief
  * Declares utilities for transforming key-value trees.
  *
+ * See \ref page_mdmodules for the main use case that these support.
+ *
  * \author Teemu Murtola <teemu.murtola@gmail.com>
  * \inlibraryapi
  * \ingroup module_utility
@@ -45,6 +47,7 @@
 
 #include <functional>
 #include <string>
+#include <typeindex>
 #include <vector>
 
 #include "gromacs/utility/classhelpers.h"
@@ -59,6 +62,7 @@ class KeyValueTreeObjectBuilder;
 
 enum class StringCompareType;
 
+class KeyValueTreeTransformResult;
 class KeyValueTreeTransformRuleBuilder;
 
 namespace internal
@@ -66,86 +70,95 @@ namespace internal
 class KeyValueTreeTransformerImpl;
 }
 
+/*! \libinternal \brief
+ * Interface to declare rules for transforming key-value trees.
+ *
+ * This interface is used to add transformation rules for key-value trees.
+ * A transformation is a set of rules that is used to map an input key-value
+ * tree to an output key-value tree, with possible conversion steps performed
+ * in the process.  Currently, each rule maps one item from the source tree to
+ * one item in the target tree (it is possible to expand a single value into an
+ * object with multiple properties).  See KeyValueTreeTransformRuleBuilder for
+ * the kinds of rules currently supported.
+ *
+ * The main use currently is in converting flat-format mdp files to a
+ * structured internal representation.
+ *
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
 class IKeyValueTreeTransformRules
 {
     public:
+        /*! \brief
+         * Creates a new rule.
+         *
+         * Properties of the new rule must be specified using the returned
+         * builder.
+         */
         virtual KeyValueTreeTransformRuleBuilder addRule() = 0;
 
     protected:
         ~IKeyValueTreeTransformRules();
 };
 
-class IKeyValueTreeBackMapping
-{
-    public:
-        virtual ~IKeyValueTreeBackMapping();
-
-        virtual KeyValueTreePath
-        originalPath(const KeyValueTreePath &path) const = 0;
-};
-
-class KeyValueTreeTransformResult
-{
-    public:
-        KeyValueTreeObject object() { return std::move(object_); }
-        const IKeyValueTreeBackMapping &backMapping() const { return *mapping_; }
-
-    private:
-        typedef std::unique_ptr<IKeyValueTreeBackMapping> MappingPointer;
-
-        KeyValueTreeTransformResult(KeyValueTreeObject &&object,
-                                    MappingPointer     &&mapping)
-            : object_(std::move(object)), mapping_(std::move(mapping))
-        {
-        }
-
-        KeyValueTreeObject                         object_;
-        std::unique_ptr<IKeyValueTreeBackMapping>  mapping_;
-
-        friend class internal::KeyValueTreeTransformerImpl;
-};
-
-class KeyValueTreeTransformer
-{
-    public:
-        KeyValueTreeTransformer();
-        ~KeyValueTreeTransformer();
-
-        IKeyValueTreeTransformRules *rules();
-
-        std::vector<KeyValueTreePath> mappedPaths() const;
-
-        KeyValueTreeTransformResult
-        transform(const KeyValueTreeObject  &tree,
-                  IKeyValueTreeErrorHandler *errorHandler) const;
-
-    private:
-        PrivateImplPointer<internal::KeyValueTreeTransformerImpl> impl_;
-};
-
+/*! \libinternal \brief
+ * Provides methods to specify one transformation rule.
+ *
+ * \if internal
+ * The builder is implemented as a set of nested objects, each of which is
+ * provides methods for setting a particular property of the rule.  Setting a
+ * property returns another object that has relevant methods for the context.
+ * This provides some structure to the methods, and catches at least some types
+ * of incorrect rules already at compile time.
+ * Additionally, if you use an IDE with completion facilities, it can nicely
+ * guide you through which values you need to specify.
+ * All values are stored within the main builder object, and the rule is
+ * created at the end of the statement.
+ * \endif
+ *
+ * \inlibraryapi
+ * \ingroup module_utility
+ */
 class KeyValueTreeTransformRuleBuilder
 {
     public:
+        /*! \internal \brief
+         * Base class used for implementing parameter provider objects.
+         */
         class Base
         {
             protected:
+                //! Creates a parameter provider object within given builder.
                 explicit Base(KeyValueTreeTransformRuleBuilder *builder)
                     : builder_(builder)
                 {
                 }
 
+                //! The parent builder.
                 KeyValueTreeTransformRuleBuilder *builder_;
         };
 
+        /*! \libinternal \brief
+         * Properties that can be specified after from().to().
+         *
+         * \tparam FromType Type specified for from() to map from.
+         * \tparam ToType Type specified for to() to map to.
+         */
         template <typename FromType, typename ToType>
         class ToValue : public Base
         {
             public:
+                //! Creates a parameter provider object within given builder.
                 explicit ToValue(KeyValueTreeTransformRuleBuilder *builder)
                     : Base(builder)
                 {
                 }
 
+                /*! \brief
+                 * Specifies the transformation function to convert the value
+                 * from FromType to ToType.
+                 */
                 void transformWith(std::function<ToType(const FromType &)> transform)
                 {
                     builder_->addTransformToVariant(
@@ -156,15 +169,28 @@ class KeyValueTreeTransformRuleBuilder
                 }
         };
 
+        /*! \libinternal \brief
+         * Properties that can be specified after from().toObject().
+         *
+         * \tparam FromType Type specified for from() to map from.
+         */
         template <typename FromType>
         class ToObject : public Base
         {
             public:
+                //! Creates a parameter provider object within given builder.
                 explicit ToObject(KeyValueTreeTransformRuleBuilder *builder)
                     : Base(builder)
                 {
                 }
 
+                /*! \brief
+                 * Specifies the transformation function to build the output
+                 * object.
+                 *
+                 * The transform should build the output object with the
+                 * provided builder.
+                 */
                 void transformWith(std::function<void(KeyValueTreeObjectBuilder *, const FromType &)> transform)
                 {
                     builder_->addTransformToObject(
@@ -175,49 +201,109 @@ class KeyValueTreeTransformRuleBuilder
                 }
         };
 
+        /*! \libinternal \brief
+         * Properties that can be specified after from().
+         *
+         * \tparam FromType Type specified for from() to map from.
+         */
         template <typename FromType>
         class AfterFrom : public Base
         {
             public:
+                //! Creates a parameter provider object within given builder.
                 explicit AfterFrom(KeyValueTreeTransformRuleBuilder *builder)
                     : Base(builder)
                 {
                 }
 
+                /*! \brief
+                 * Specifies a rule that maps to a value at given path.
+                 *
+                 * \tparam ToType  Type to map to.
+                 * \param[in] path Path to map to.
+                 *
+                 * It is an error if multiple rules map to the same path, or to
+                 * a parent path of the target of an existing rule.
+                 * Note that it is possible to have a to() rule map to a child
+                 * of a toObject() rule, provided that the path is not created
+                 * by the object rule.
+                 */
                 template <typename ToType>
-                ToValue<FromType, ToType> to(const std::string &path)
+                ToValue<FromType, ToType> to(const KeyValueTreePath &path)
                 {
                     builder_->setToPath(path);
                     return ToValue<FromType, ToType>(builder_);
                 }
 
-                ToObject<FromType> toObject(const std::string &path)
+                /*! \brief
+                 * Specifies a rule that maps to an object (collection of named
+                 * values) at given path.
+                 *
+                 * \param[in] path Path to map to.
+                 *
+                 * It is an error if multiple rules map to the same path, or to
+                 * a parent path of the target of an existing rule.
+                 * However, it is allowed to have two toObject() rules map to
+                 * the same path, provided that the properties they produce are
+                 * distinct.
+                 */
+                ToObject<FromType> toObject(const KeyValueTreePath &path)
                 {
                     builder_->setToPath(path);
                     return ToObject<FromType>(builder_);
                 }
         };
 
+        //! Internal constructor for creating a builder.
         explicit KeyValueTreeTransformRuleBuilder(internal::KeyValueTreeTransformerImpl *impl);
+        //! Supports returning the builder from IKeyValueTreeTransformRules::addRule().
         KeyValueTreeTransformRuleBuilder(KeyValueTreeTransformRuleBuilder &&)            = default;
+        //! Supports returning the builder from IKeyValueTreeTransformRules::addRule().
         KeyValueTreeTransformRuleBuilder &operator=(KeyValueTreeTransformRuleBuilder &&) = default;
         ~KeyValueTreeTransformRuleBuilder();
 
+        /*! \brief
+         * Specifies a rule that maps a value at given path.
+         *
+         * \tparam FromType Type of value expected at `path`.
+         * \param[in] path Path to map in this rule.
+         *
+         * If the input tree has `path`, but it is not of type `FromType`,
+         * the transform will produce an error.
+         *
+         * It is an error to use the same path in two from() rules.  Similarly,
+         * it is an error to use a child path of a path used in a different
+         * from() rule.
+         */
         template <typename FromType>
-        AfterFrom<FromType> from(const std::string &path)
+        AfterFrom<FromType> from(const KeyValueTreePath &path)
         {
             setFromPath(path);
+            setExpectedType(typeid(FromType));
             return AfterFrom<FromType>(this);
         }
-        void keyMatchType(const std::string &path, StringCompareType keyMatchType)
+        /*! \brief
+         * Specifies how strings are matched when matching rules against a path.
+         *
+         * For properties of the object at `path`, `keyMatchType` is used for
+         * string comparison.
+         *
+         * This rule must be specified first for a path, before any other
+         * from() rule specifies the path or a subpath.
+         * The rule only applies to immediate properties at the given path, not
+         * recursively.
+         * It is an error to specify the match type multiple times for a path.
+         */
+        void keyMatchType(const KeyValueTreePath &path, StringCompareType keyMatchType)
         {
             setFromPath(path);
             setKeyMatchType(keyMatchType);
         }
 
     private:
-        void setFromPath(const std::string &path);
-        void setToPath(const std::string &path);
+        void setFromPath(const KeyValueTreePath &path);
+        void setExpectedType(const std::type_index &type);
+        void setToPath(const KeyValueTreePath &path);
         void setKeyMatchType(StringCompareType keyMatchType);
         void addTransformToVariant(std::function<Variant(const Variant &)> transform);
         void addTransformToObject(std::function<void(KeyValueTreeObjectBuilder *, const Variant &)> transform);
@@ -228,6 +314,54 @@ class KeyValueTreeTransformRuleBuilder
         std::unique_ptr<Data>                  data_;
 };
 
+class KeyValueTreeTransformer
+{
+    public:
+        KeyValueTreeTransformer();
+        ~KeyValueTreeTransformer();
+
+        IKeyValueTreeTransformRules *rules();
+
+        std::vector<KeyValueTreePath> mappedPaths() const;
+
+        KeyValueTreeTransformResult
+        transform(const KeyValueTreeObject  &tree,
+                  IKeyValueTreeErrorHandler *errorHandler) const;
+
+    private:
+        PrivateImplPointer<internal::KeyValueTreeTransformerImpl> impl_;
+};
+
+class IKeyValueTreeBackMapping
+{
+    public:
+        virtual ~IKeyValueTreeBackMapping();
+
+        virtual KeyValueTreePath
+        originalPath(const KeyValueTreePath &path) const = 0;
+};
+
+class KeyValueTreeTransformResult
+{
+    public:
+        KeyValueTreeObject object() { return std::move(object_); }
+        const IKeyValueTreeBackMapping &backMapping() const { return *mapping_; }
+
+    private:
+        typedef std::unique_ptr<IKeyValueTreeBackMapping> MappingPointer;
+
+        KeyValueTreeTransformResult(KeyValueTreeObject &&object,
+                                    MappingPointer     &&mapping)
+            : object_(std::move(object)), mapping_(std::move(mapping))
+        {
+        }
+
+        KeyValueTreeObject  object_;
+        MappingPointer      mapping_;
+
+        friend class internal::KeyValueTreeTransformerImpl;
+};
+
 } // namespace gmx
 
 #endif