YAML I/O add support for validate()
authorNick Kledzik <kledzik@apple.com>
Thu, 21 Nov 2013 00:28:07 +0000 (00:28 +0000)
committerNick Kledzik <kledzik@apple.com>
Thu, 21 Nov 2013 00:28:07 +0000 (00:28 +0000)
MappingTrait template specializations can now have a validate() method which
performs semantic checking. For details, see <http://llvm.org/docs/YamlIO.html>.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@195286 91177308-0d34-0410-b5e6-96231b3b80d8

docs/YamlIO.rst
include/llvm/Support/YAMLTraits.h
unittests/Support/YAMLIOTest.cpp

index 5ec0a01f8d82dd134bba1d2b470729c57a3618cb..46609a3acd61ae8f97254182cc9dcd463c0bfb58 100644 (file)
@@ -647,6 +647,44 @@ mappings, as long as they are convertable.
 To check a tag, inside your mapping() method you can use io.mapTag() to specify
 what the tag should be.  This will also add that tag when writing yaml.
 
+Validation
+----------
+
+Sometimes in a yaml map, each key/value pair is valid, but the combination is
+not.  This is similar to something having no syntax errors, but still having
+semantic errors.  To support semantic level checking, YAML I/O allows
+an optional ``validate()`` method in a MappingTraits template specialization.  
+
+When parsing yaml, the ``validate()`` method is call *after* all key/values in 
+the map have been processed. Any error message returned by the ``validate()`` 
+method during input will be printed just a like a syntax error would be printed.
+When writing yaml, the ``validate()`` method is called *before* the yaml 
+key/values  are written.  Any error during output will trigger an ``assert()`` 
+because it is a programming error to have invalid struct values.
+
+
+.. code-block:: c++
+
+    using llvm::yaml::MappingTraits;
+    using llvm::yaml::IO;
+    
+    struct Stuff {
+      ...
+    };
+
+    template <>
+    struct MappingTraits<Stuff> {
+      static void mapping(IO &io, Stuff &stuff) {
+      ...
+      }
+      static StringRef validate(IO &io, Stuff &stuff) {
+        // Look at all fields in 'stuff' and if there
+        // are any bad values return a string describing
+        // the error.  Otherwise return an empty string.
+        return StringRef();
+      }
+    };
+
 
 Sequence
 ========
index 27c1393b3ac6a936f5b2842d651eb3d0e2093b15..1716a9d36e8053981e131af70f645ff5aa2a16df 100644 (file)
@@ -44,6 +44,8 @@ template<class T>
 struct MappingTraits {
   // Must provide:
   // static void mapping(IO &io, T &fields);
+  // Optionally may provide:
+  // static StringRef validate(IO &io, T &fields);
 };
 
 
@@ -226,6 +228,23 @@ public:
   static bool const value = (sizeof(test<MappingTraits<T> >(0)) == 1);
 };
 
+// Test if MappingTraits<T>::validate() is defined on type T.
+template <class T>
+struct has_MappingValidateTraits
+{
+  typedef StringRef (*Signature_validate)(class IO&, T&);
+
+  template <typename U>
+  static char test(SameType<Signature_validate, &U::validate>*);
+
+  template <typename U>
+  static double test(...);
+
+public:
+  static bool const value = (sizeof(test<MappingTraits<T> >(0)) == 1);
+};
+
+
 
 // Test if SequenceTraits<T> is defined on type T.
 template <class T>
@@ -309,7 +328,15 @@ struct missingTraits : public  llvm::integral_constant<bool,
                                       && !has_SequenceTraits<T>::value
                                       && !has_DocumentListTraits<T>::value >  {};
 
+template<typename T>
+struct validatedMappingTraits : public  llvm::integral_constant<bool,
+                                       has_MappingTraits<T>::value
+                                    && has_MappingValidateTraits<T>::value> {};
 
+template<typename T>
+struct unvalidatedMappingTraits : public  llvm::integral_constant<bool,
+                                        has_MappingTraits<T>::value
+                                    && !has_MappingValidateTraits<T>::value> {};
 // Base class for Input and Output.
 class IO {
 public:
@@ -483,7 +510,27 @@ yamlize(IO &io, T &Val, bool) {
 
 
 template<typename T>
-typename llvm::enable_if_c<has_MappingTraits<T>::value, void>::type
+typename llvm::enable_if_c<validatedMappingTraits<T>::value, void>::type
+yamlize(IO &io, T &Val, bool) {
+  io.beginMapping();
+  if (io.outputting()) {
+    StringRef Err = MappingTraits<T>::validate(io, Val);
+    if (!Err.empty()) {
+      llvm::errs() << Err << "\n";
+      assert(Err.empty() && "invalid struct trying to be written as yaml");
+    }
+  }
+  MappingTraits<T>::mapping(io, Val);
+  if (!io.outputting()) {
+    StringRef Err = MappingTraits<T>::validate(io, Val);
+    if (!Err.empty())
+      io.setError(Err);
+  }
+  io.endMapping();
+}
+
+template<typename T>
+typename llvm::enable_if_c<unvalidatedMappingTraits<T>::value, void>::type
 yamlize(IO &io, T &Val, bool) {
   io.beginMapping();
   MappingTraits<T>::mapping(io, Val);
index 07d70459fb89c8447aeded18fdad2821082bbf97..52a8f6b88c243333e2ed26677ebcf6dc8ea02fe9 100644 (file)
@@ -27,6 +27,13 @@ using llvm::yaml::Hex32;
 using llvm::yaml::Hex64;
 
 
+
+
+static void suppressErrorMessages(const llvm::SMDiagnostic &, void *) {
+}
+
+
+
 //===----------------------------------------------------------------------===//
 //  Test MappingTraits
 //===----------------------------------------------------------------------===//
@@ -1115,17 +1122,50 @@ TEST(YAMLIO, TestTaggedDocumentsWriteAndRead) {
 }
 
 
-
 //===----------------------------------------------------------------------===//
-//  Test error handling
+//  Test mapping validation
 //===----------------------------------------------------------------------===//
 
+struct MyValidation {
+  double value;
+};
 
+LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(MyValidation)
 
-static void suppressErrorMessages(const llvm::SMDiagnostic &, void *) {
+namespace llvm {
+namespace yaml {
+  template <>
+  struct MappingTraits<MyValidation> {
+    static void mapping(IO &io, MyValidation &d) {
+        io.mapRequired("value", d.value);
+    }
+    static StringRef validate(IO &io, MyValidation &d) {
+        if (d.value < 0)
+          return "negative value";
+        return StringRef();
+    }
+  };
+ }
 }
 
 
+//
+// Test that validate() is called and complains about the negative value.
+//
+TEST(YAMLIO, TestValidatingInput) {
+  std::vector<MyValidation> docList;
+  Input yin("--- \nvalue:  3.0\n"
+            "--- \nvalue:  -1.0\n...\n",
+            NULL, suppressErrorMessages);
+  yin >> docList;
+  EXPECT_TRUE(yin.error());
+}
+
+
+//===----------------------------------------------------------------------===//
+//  Test error handling
+//===----------------------------------------------------------------------===//
+
 //
 // Test error handling of unknown enumerated scalar
 //