Add defaulted() in Format.h to avoid throwing on missing keys; add string-y dynamic...
authorTudor Bosman <tudorb@fb.com>
Wed, 30 Apr 2014 15:41:45 +0000 (08:41 -0700)
committerDave Watson <davejwatson@fb.com>
Tue, 20 May 2014 19:53:57 +0000 (12:53 -0700)
Test Plan: folly/test

Reviewed By: simpkins@fb.com

FB internal diff: D1303911

folly/Format-inl.h
folly/Format.h
folly/dynamic-inl.h
folly/dynamic.h
folly/test/FormatTest.cpp

index 5575151c27c0cef40435c569f2d0ca0e436b640f..79ea1f7915bb040a24879f4c6feacac093a89fc6 100644 (file)
@@ -912,6 +912,11 @@ struct IndexableTraitsSeq : public FormatTraitsBase {
   static const value_type& at(const C& c, int idx) {
     return c.at(idx);
   }
+
+  static const value_type& at(const C& c, int idx,
+                              const value_type& dflt) {
+    return (idx >= 0 && idx < c.size()) ? c.at(idx) : dflt;
+  }
 };
 
 // Base class for associative types (maps)
@@ -921,6 +926,11 @@ struct IndexableTraitsAssoc : public FormatTraitsBase {
   static const value_type& at(const C& c, int idx) {
     return c.at(static_cast<typename C::key_type>(idx));
   }
+  static const value_type& at(const C& c, int idx,
+                              const value_type& dflt) {
+    auto pos = c.find(static_cast<typename C::key_type>(idx));
+    return pos != c.end() ? pos->second : dflt;
+  }
 };
 
 // std::array
@@ -991,6 +1001,28 @@ class FormatValue<
   const T& val_;
 };
 
+template <class Container, class Value>
+class FormatValue<
+  detail::DefaultValueWrapper<Container, Value>,
+  typename detail::IndexableTraits<Container>::enabled> {
+ public:
+  explicit FormatValue(const detail::DefaultValueWrapper<Container, Value>& val)
+    : val_(val) { }
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    FormatValue<typename std::decay<
+      typename detail::IndexableTraits<Container>::value_type>::type>(
+          detail::IndexableTraits<Container>::at(
+              val_.container,
+              arg.splitIntKey(),
+              val_.defaultValue)).format(arg, cb);
+  }
+
+ private:
+  const detail::DefaultValueWrapper<Container, Value>& val_;
+};
+
 namespace detail {
 
 // Define enabled, key_type, convert from StringPiece to the key types
@@ -1032,6 +1064,11 @@ template <class T> struct KeyableTraitsAssoc : public FormatTraitsBase {
   static const value_type& at(const T& map, StringPiece key) {
     return map.at(KeyFromStringPiece<key_type>::convert(key));
   }
+  static const value_type& at(const T& map, StringPiece key,
+                              const value_type& dflt) {
+    auto pos = map.find(KeyFromStringPiece<key_type>::convert(key));
+    return pos != map.end() ? pos->second : dflt;
+  }
 };
 
 // Define enabled, key_type, value_type, at() for supported string-keyed
@@ -1076,6 +1113,28 @@ class FormatValue<
   const T& val_;
 };
 
+template <class Container, class Value>
+class FormatValue<
+  detail::DefaultValueWrapper<Container, Value>,
+  typename detail::KeyableTraits<Container>::enabled> {
+ public:
+  explicit FormatValue(const detail::DefaultValueWrapper<Container, Value>& val)
+    : val_(val) { }
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    FormatValue<typename std::decay<
+      typename detail::KeyableTraits<Container>::value_type>::type>(
+          detail::KeyableTraits<Container>::at(
+              val_.container,
+              arg.splitKey(),
+              val_.defaultValue)).format(arg, cb);
+  }
+
+ private:
+  const detail::DefaultValueWrapper<Container, Value>& val_;
+};
+
 // Partial specialization of FormatValue for pairs
 template <class A, class B>
 class FormatValue<std::pair<A, B>> {
index 970ca405cd28dbc4247fd19dc4d3c5faa9a15c98..eb8b5f81348059f0861705b6981e09150f054711 100644 (file)
@@ -257,6 +257,31 @@ Formatter<true, Container> vformatChecked(StringPiece fmt,
   return f;
 }
 
+/**
+ * Wrap a sequence or associative container so that out-of-range lookups
+ * return a default value rather than throwing an exception.
+ *
+ * Usage:
+ * format("[no_such_key"], defaulted(map, 42))  -> 42
+ */
+namespace detail {
+template <class Container, class Value> struct DefaultValueWrapper {
+  DefaultValueWrapper(const Container& container, const Value& defaultValue)
+    : container(container),
+      defaultValue(defaultValue) {
+  }
+
+  const Container& container;
+  const Value& defaultValue;
+};
+}  // namespace
+
+template <class Container, class Value>
+detail::DefaultValueWrapper<Container, Value>
+defaulted(const Container& c, const Value& v) {
+  return detail::DefaultValueWrapper<Container, Value>(c, v);
+}
+
 /**
  * Append formatted output to a string.
  *
index 35b8e3ea819b8c91463a13b6237991fd090a832f..204d48f91f7af271f92f58e995a4f6cc2efe8973 100644 (file)
@@ -260,6 +260,12 @@ inline dynamic::dynamic(ObjectMaker (*)())
   new (getAddress<ObjectImpl>()) ObjectImpl();
 }
 
+inline dynamic::dynamic(StringPiece s)
+  : type_(STRING)
+{
+  new (&u_.string) fbstring(s.data(), s.size());
+}
+
 inline dynamic::dynamic(char const* s)
   : type_(STRING)
 {
@@ -272,6 +278,18 @@ inline dynamic::dynamic(std::string const& s)
   new (&u_.string) fbstring(s);
 }
 
+inline dynamic::dynamic(fbstring const& s)
+  : type_(STRING)
+{
+  new (&u_.string) fbstring(s);
+}
+
+inline dynamic::dynamic(fbstring&& s)
+  : type_(STRING)
+{
+  new (&u_.string) fbstring(std::move(s));
+}
+
 inline dynamic::dynamic(std::initializer_list<dynamic> il)
   : type_(ARRAY)
 {
@@ -907,7 +925,52 @@ class FormatValue<dynamic> {
   const dynamic& val_;
 };
 
-}
+template <class V>
+class FormatValue<detail::DefaultValueWrapper<dynamic, V>> {
+ public:
+  explicit FormatValue(
+      const detail::DefaultValueWrapper<dynamic, V>& val)
+    : val_(val) { }
+
+  template <class FormatCallback>
+  void format(FormatArg& arg, FormatCallback& cb) const {
+    auto& c = val_.container;
+    switch (c.type()) {
+    case dynamic::NULLT:
+    case dynamic::BOOL:
+    case dynamic::INT64:
+    case dynamic::STRING:
+    case dynamic::DOUBLE:
+      FormatValue<dynamic>(c).format(arg, cb);
+      break;
+    case dynamic::ARRAY:
+      {
+        int key = arg.splitIntKey();
+        if (key >= 0 && key < c.size()) {
+          FormatValue<dynamic>(c.at(key)).format(arg, cb);
+        } else{
+          FormatValue<V>(val_.defaultValue).format(arg, cb);
+        }
+      }
+      break;
+    case dynamic::OBJECT:
+      {
+        auto pos = c.find(arg.splitKey());
+        if (pos != c.items().end()) {
+          FormatValue<dynamic>(pos->second).format(arg, cb);
+        } else {
+          FormatValue<V>(val_.defaultValue).format(arg, cb);
+        }
+      }
+      break;
+    }
+  }
+
+ private:
+  const detail::DefaultValueWrapper<dynamic, V>& val_;
+};
+
+}  // namespaces
 
 #undef FB_DYNAMIC_APPLY
 
index 1494a557047feff2c25a36b8376c9dd781ef9b7a..d388ce72109065a72bb6dea71362b0608794a836 100644 (file)
 #ifndef FOLLY_DYNAMIC_H_
 #define FOLLY_DYNAMIC_H_
 
-#include <unordered_map>
+#include <cstdint>
+#include <initializer_list>
 #include <memory>
-#include <string>
-#include <utility>
 #include <ostream>
+#include <string>
 #include <type_traits>
-#include <initializer_list>
+#include <unordered_map>
+#include <utility>
 #include <vector>
-#include <cstdint>
+
 #include <boost/operators.hpp>
 
-#include "folly/Traits.h"
 #include "folly/FBString.h"
+#include "folly/Range.h"
+#include "folly/Traits.h"
 
 namespace folly {
 
@@ -143,8 +145,11 @@ public:
   /*
    * String compatibility constructors.
    */
+  /* implicit */ dynamic(StringPiece val);
   /* implicit */ dynamic(char const* val);
   /* implicit */ dynamic(std::string const& val);
+  /* implicit */ dynamic(fbstring const& val);
+  /* implicit */ dynamic(fbstring&& val);
 
   /*
    * This is part of the plumbing for object(), above.  Used to create
@@ -326,7 +331,6 @@ public:
    */
   const_item_iterator find(dynamic const&) const;
 
-
   /*
    * If this is an object, returns whether it contains a field with
    * the given name.  Otherwise throws TypeError.
index 53e7f6586df61c0f86f1fe89bee6e136607498e3..65d5c29e2e16c8a77fd29c51bad48c720b2dc21e 100644 (file)
@@ -135,6 +135,12 @@ TEST(Format, Simple) {
   const std::vector<int> v2 = v1;
   EXPECT_EQ("0020", fstr("{0[1]:04}", v2));
   EXPECT_EQ("0020", vstr("{1:04}", v2));
+  EXPECT_THROW(fstr("{0[3]:04}", v2), std::out_of_range);
+  EXPECT_THROW(vstr("{3:04}", v2), std::out_of_range);
+  EXPECT_EQ("0020", fstr("{0[1]:04}", defaulted(v2, 42)));
+  EXPECT_EQ("0020", vstr("{1:04}", defaulted(v2, 42)));
+  EXPECT_EQ("0042", fstr("{0[3]:04}", defaulted(v2, 42)));
+  EXPECT_EQ("0042", vstr("{3:04}", defaulted(v2, 42)));
 
   const int p[] = {10, 20, 30};
   const int* q = p;
@@ -154,10 +160,22 @@ TEST(Format, Simple) {
   std::map<int, std::string> m { {10, "hello"}, {20, "world"} };
   EXPECT_EQ("worldXX", fstr("{[20]:X<7}", m));
   EXPECT_EQ("worldXX", vstr("{20:X<7}", m));
+  EXPECT_THROW(fstr("{[42]:X<7}", m), std::out_of_range);
+  EXPECT_THROW(vstr("{42:X<7}", m), std::out_of_range);
+  EXPECT_EQ("worldXX", fstr("{[20]:X<7}", defaulted(m, "meow")));
+  EXPECT_EQ("worldXX", vstr("{20:X<7}", defaulted(m, "meow")));
+  EXPECT_EQ("meowXXX", fstr("{[42]:X<7}", defaulted(m, "meow")));
+  EXPECT_EQ("meowXXX", vstr("{42:X<7}", defaulted(m, "meow")));
 
   std::map<std::string, std::string> m2 { {"hello", "world"} };
   EXPECT_EQ("worldXX", fstr("{[hello]:X<7}", m2));
   EXPECT_EQ("worldXX", vstr("{hello:X<7}", m2));
+  EXPECT_THROW(fstr("{[none]:X<7}", m2), std::out_of_range);
+  EXPECT_THROW(vstr("{none:X<7}", m2), std::out_of_range);
+  EXPECT_EQ("worldXX", fstr("{[hello]:X<7}", defaulted(m2, "meow")));
+  EXPECT_EQ("worldXX", vstr("{hello:X<7}", defaulted(m2, "meow")));
+  EXPECT_EQ("meowXXX", fstr("{[none]:X<7}", defaulted(m2, "meow")));
+  EXPECT_EQ("meowXXX", vstr("{none:X<7}", defaulted(m2, "meow")));
 
   // Test indexing in strings
   EXPECT_EQ("61 62", fstr("{0[0]:x} {0[1]:x}", "abcde"));
@@ -260,7 +278,20 @@ TEST(Format, dynamic) {
       "}");
 
   EXPECT_EQ("world", fstr("{0[hello]}", dyn));
+  EXPECT_THROW(fstr("{0[none]}", dyn), std::out_of_range);
+  EXPECT_EQ("world", fstr("{0[hello]}", defaulted(dyn, "meow")));
+  EXPECT_EQ("meow", fstr("{0[none]}", defaulted(dyn, "meow")));
+
   EXPECT_EQ("20", fstr("{0[x.0]}", dyn));
+  EXPECT_THROW(fstr("{0[x.2]}", dyn), std::out_of_range);
+
+  // No support for "deep" defaulting (dyn["x"] is not defaulted)
+  auto v = dyn.at("x");
+  EXPECT_EQ("20", fstr("{0[0]}", v));
+  EXPECT_THROW(fstr("{0[2]}", v), std::out_of_range);
+  EXPECT_EQ("20", fstr("{0[0]}", defaulted(v, 42)));
+  EXPECT_EQ("42", fstr("{0[2]}", defaulted(v, 42)));
+
   EXPECT_EQ("42", fstr("{0[y.a]}", dyn));
 
   EXPECT_EQ("(null)", fstr("{}", dynamic(nullptr)));