Route commands without path to suitable component

Use the trait name to find the first component that implements
that trait to send the command to.

BUG: 25917421
Change-Id: Ife284100fb7d0bf94416bf5ba2ab9b797076ce23
Reviewed-on: https://weave-review.googlesource.com/1783
Reviewed-by: Vitaly Buka <vitalybuka@google.com>
diff --git a/src/component_manager.cc b/src/component_manager.cc
index bf92a78..98001bb 100644
--- a/src/component_manager.cc
+++ b/src/component_manager.cc
@@ -171,14 +171,18 @@
 
   std::string component_path = command_instance->GetComponent();
   if (component_path.empty()) {
-    // Get the name of the first top-level component.
-    base::DictionaryValue::Iterator it(components_);
-    if (it.IsAtEnd()) {
-      Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
-                   "component_not_found", "There are no components defined");
+    // Find the component to which to route this command. Get the trait name
+    // from the command name and find the first component that has this trait.
+    auto trait_name = SplitAtFirst(command_instance->GetName(), ".", true).first;
+    component_path = FindComponentWithTrait(trait_name);
+    if (component_path.empty()) {
+      Error::AddToPrintf(
+          error, FROM_HERE, errors::commands::kDomain, "unrouted_command",
+          "Unable route command '%s' because there is no component supporting"
+          "trait '%s'", command_instance->GetName().c_str(),
+          trait_name.c_str());
       return false;
     }
-    component_path = it.key();
     command_instance->SetComponent(component_path);
   }
 
@@ -414,6 +418,25 @@
   return Token{on_server_state_updated_.Add(callback).release()};
 }
 
+std::string ComponentManager::FindComponentWithTrait(
+    const std::string& trait) const {
+  for (base::DictionaryValue::Iterator it(components_); !it.IsAtEnd();
+       it.Advance()) {
+    const base::ListValue* supported_traits = nullptr;
+    const base::DictionaryValue* component = nullptr;
+    CHECK(it.value().GetAsDictionary(&component));
+    if (component->GetList("traits", &supported_traits)) {
+      for (const base::Value* value : *supported_traits) {
+        std::string supported_trait;
+        CHECK(value->GetAsString(&supported_trait));
+        if (trait == supported_trait)
+          return it.key();
+      }
+    }
+  }
+  return std::string{};
+}
+
 base::DictionaryValue* ComponentManager::FindComponentGraftNode(
     const std::string& path, ErrorPtr* error) {
   base::DictionaryValue* root = nullptr;
diff --git a/src/component_manager.h b/src/component_manager.h
index 2deaa30..031b88a 100644
--- a/src/component_manager.h
+++ b/src/component_manager.h
@@ -169,6 +169,14 @@
   Token AddServerStateUpdatedCallback(
       const base::Callback<void(UpdateID)>& callback);
 
+  // Helper method for legacy API to obtain first component that implements
+  // the given trait. This is useful for routing commands that have no component
+  // path specified.
+  // Returns empty string if no components are found.
+  // This method only searches for component on the top level of components
+  // tree. No sub-components are searched.
+  std::string FindComponentWithTrait(const std::string& trait) const;
+
  private:
   // A helper method to find a JSON element of component at |path| to add new
   // sub-components to.
diff --git a/src/component_manager_unittest.cc b/src/component_manager_unittest.cc
index 83f9933..31949d7 100644
--- a/src/component_manager_unittest.cc
+++ b/src/component_manager_unittest.cc
@@ -31,38 +31,38 @@
 }
 
 // Creates sample trait/component trees:
-// {

-//   "traits": {

-//     "t1": {},

-//     "t2": {},

-//     "t3": {},

-//     "t4": {},

-//     "t5": {},

-//     "t6": {},

-//   },

-//   "components": {

-//     "comp1": {

-//       "traits": [ "t1" ],

-//       "components": {

-//         "comp2": [

-//           { "traits": [ "t2" ] },

-//           {

-//             "traits": [ "t3" ],

-//             "components": {

-//               "comp3": {

-//                 "traits": [ "t4" ],

-//                 "components": {

-//                   "comp4": {

-//                     "traits": [ "t5", "t6" ]

-//                   }

-//                 }

-//               }

-//             }

-//           }

-//         ],

-//       }

-//     }

-//   }

+// {
+//   "traits": {
+//     "t1": {},
+//     "t2": {},
+//     "t3": {},
+//     "t4": {},
+//     "t5": {},
+//     "t6": {},
+//   },
+//   "components": {
+//     "comp1": {
+//       "traits": [ "t1" ],
+//       "components": {
+//         "comp2": [
+//           { "traits": [ "t2" ] },
+//           {
+//             "traits": [ "t3" ],
+//             "components": {
+//               "comp3": {
+//                 "traits": [ "t4" ],
+//                 "components": {
+//                   "comp4": {
+//                     "traits": [ "t5", "t6" ]
+//                   }
+//                 }
+//               }
+//             }
+//           }
+//         ],
+//       }
+//     }
+//   }
 // }
 void CreateTestComponentTree(ComponentManager* manager) {
   const char kTraits[] = R"({"t1":{},"t2":{},"t3":{},"t4":{},"t5":{},"t6":{}})";
@@ -670,7 +670,7 @@
   // Component comp1 doesn't have trait2.
   EXPECT_FALSE(manager.AddCommand(*command3, UserRole::kOwner, &id, nullptr));
 
-  // No component specified, use the first top-level component (comp1)
+  // No component specified, find the suitable component
   const char kCommand4[] = R"({
     "name": "trait1.command1",
     "parameters": {}
@@ -680,6 +680,16 @@
   auto cmd = manager.FindCommand(id);
   ASSERT_NE(nullptr, cmd);
   EXPECT_EQ("comp1", cmd->GetComponent());
+
+  const char kCommand5[] = R"({
+    "name": "trait2.command1",
+    "parameters": {}
+  })";
+  auto command5 = CreateDictionaryValue(kCommand5);
+  EXPECT_TRUE(manager.AddCommand(*command5, UserRole::kOwner, &id, nullptr));
+  cmd = manager.FindCommand(id);
+  ASSERT_NE(nullptr, cmd);
+  EXPECT_EQ("comp2", cmd->GetComponent());
 }
 
 TEST(ComponentManager, AddCommandHandler) {
@@ -750,30 +760,30 @@
   auto state1 = CreateDictionaryValue(kState1);
   ASSERT_TRUE(manager.SetStateProperties("comp1", *state1, nullptr));
   const char kExpected1[] = R"({
-    "comp1": {

-      "traits": [ "t1" ],

-      "state": {"t1": {"p1": 0, "p2": "foo"}},

-      "components": {

-        "comp2": [

-          {

-            "traits": [ "t2" ]

-          },

-          {

-            "traits": [ "t3" ],

-            "components": {

-              "comp3": {

-                "traits": [ "t4" ],

-                "components": {

-                  "comp4": {

-                    "traits": [ "t5", "t6" ]

-                  }

-                }

-              }

-            }

-          }

-        ]

-      }

-    }

+    "comp1": {
+      "traits": [ "t1" ],
+      "state": {"t1": {"p1": 0, "p2": "foo"}},
+      "components": {
+        "comp2": [
+          {
+            "traits": [ "t2" ]
+          },
+          {
+            "traits": [ "t3" ],
+            "components": {
+              "comp3": {
+                "traits": [ "t4" ],
+                "components": {
+                  "comp4": {
+                    "traits": [ "t5", "t6" ]
+                  }
+                }
+              }
+            }
+          }
+        ]
+      }
+    }
   })";
   EXPECT_JSON_EQ(kExpected1, manager.GetComponents());
 
@@ -782,30 +792,30 @@
   ASSERT_TRUE(manager.SetStateProperties("comp1", *state2, nullptr));
 
   const char kExpected2[] = R"({
-    "comp1": {

-      "traits": [ "t1" ],

-      "state": {"t1": {"p1": {"bar": "baz"}, "p2": "foo"}},

-      "components": {

-        "comp2": [

-          {

-            "traits": [ "t2" ]

-          },

-          {

-            "traits": [ "t3" ],

-            "components": {

-              "comp3": {

-                "traits": [ "t4" ],

-                "components": {

-                  "comp4": {

-                    "traits": [ "t5", "t6" ]

-                  }

-                }

-              }

-            }

-          }

-        ]

-      }

-    }

+    "comp1": {
+      "traits": [ "t1" ],
+      "state": {"t1": {"p1": {"bar": "baz"}, "p2": "foo"}},
+      "components": {
+        "comp2": [
+          {
+            "traits": [ "t2" ]
+          },
+          {
+            "traits": [ "t3" ],
+            "components": {
+              "comp3": {
+                "traits": [ "t4" ],
+                "components": {
+                  "comp4": {
+                    "traits": [ "t5", "t6" ]
+                  }
+                }
+              }
+            }
+          }
+        ]
+      }
+    }
   })";
   EXPECT_JSON_EQ(kExpected2, manager.GetComponents());
 
@@ -815,31 +825,31 @@
                                          nullptr));
 
   const char kExpected3[] = R"({
-    "comp1": {

-      "traits": [ "t1" ],

-      "state": {"t1": {"p1": {"bar": "baz"}, "p2": "foo"}},

-      "components": {

-        "comp2": [

-          {

-            "traits": [ "t2" ]

-          },

-          {

-            "traits": [ "t3" ],

-            "components": {

-              "comp3": {

-                "traits": [ "t4" ],

-                "components": {

-                  "comp4": {

-                    "traits": [ "t5", "t6" ],

-                    "state": { "t5": { "p1": 1 } }

-                  }

-                }

-              }

-            }

-          }

-        ]

-      }

-    }

+    "comp1": {
+      "traits": [ "t1" ],
+      "state": {"t1": {"p1": {"bar": "baz"}, "p2": "foo"}},
+      "components": {
+        "comp2": [
+          {
+            "traits": [ "t2" ]
+          },
+          {
+            "traits": [ "t3" ],
+            "components": {
+              "comp3": {
+                "traits": [ "t4" ],
+                "components": {
+                  "comp4": {
+                    "traits": [ "t5", "t6" ],
+                    "state": { "t5": { "p1": 1 } }
+                  }
+                }
+              }
+            }
+          }
+        ]
+      }
+    }
   })";
   EXPECT_JSON_EQ(kExpected3, manager.GetComponents());
 }
@@ -853,33 +863,33 @@
       nullptr));
 
   const char kExpected[] = R"({
-    "comp1": {

-      "traits": [ "t1" ],

-      "components": {

-        "comp2": [

-          {

-            "traits": [ "t2" ]

-          },

-          {

-            "traits": [ "t3" ],

-            "components": {

-              "comp3": {

-                "traits": [ "t4" ],

-                "components": {

-                  "comp4": {

-                    "traits": [ "t5", "t6" ],

-                    "state": {

-                      "t5": { "p1": 3 },

-                      "t6": { "p2": 5 }

-                    }

-                  }

-                }

-              }

-            }

-          }

-        ]

-      }

-    }

+    "comp1": {
+      "traits": [ "t1" ],
+      "components": {
+        "comp2": [
+          {
+            "traits": [ "t2" ]
+          },
+          {
+            "traits": [ "t3" ],
+            "components": {
+              "comp3": {
+                "traits": [ "t4" ],
+                "components": {
+                  "comp4": {
+                    "traits": [ "t5", "t6" ],
+                    "state": {
+                      "t5": { "p1": 3 },
+                      "t6": { "p2": 5 }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        ]
+      }
+    }
   })";
   EXPECT_JSON_EQ(kExpected, manager.GetComponents());
 }
@@ -908,12 +918,12 @@
   ASSERT_TRUE(manager.SetStateProperty("comp1", "trait1.prop1", p1, nullptr));
 
   const char kExpected1[] = R"({
-    "comp1": {

-      "traits": [ "trait1", "trait2" ],

-      "state": {

-        "trait1": { "prop1": "foo" }

-      }

-    }

+    "comp1": {
+      "traits": [ "trait1", "trait2" ],
+      "state": {
+        "trait1": { "prop1": "foo" }
+      }
+    }
   })";
   EXPECT_JSON_EQ(kExpected1, manager.GetComponents());
 
@@ -921,13 +931,13 @@
   ASSERT_TRUE(manager.SetStateProperty("comp1", "trait2.prop3", p2, nullptr));
 
   const char kExpected2[] = R"({
-    "comp1": {

-      "traits": [ "trait1", "trait2" ],

-      "state": {

-        "trait1": { "prop1": "foo" },

-        "trait2": { "prop3": 2 }

-      }

-    }

+    "comp1": {
+      "traits": [ "trait1", "trait2" ],
+      "state": {
+        "trait1": { "prop1": "foo" },
+        "trait2": { "prop3": 2 }
+      }
+    }
   })";
   EXPECT_JSON_EQ(kExpected2, manager.GetComponents());
   // Just the package name without property:
@@ -1085,4 +1095,22 @@
   EXPECT_EQ(snapshot.update_id, updates2.front());
 }
 
+TEST(ComponentManager, FindComponentWithTrait) {
+  ComponentManager manager;
+  const char kTraits[] = R"({
+    "trait1": {},
+    "trait2": {},
+    "trait3": {}
+  })";
+  auto traits = CreateDictionaryValue(kTraits);
+  ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
+  ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1", "trait2"}, nullptr));
+  ASSERT_TRUE(manager.AddComponent("", "comp2", {"trait3"}, nullptr));
+
+  EXPECT_EQ("comp1", manager.FindComponentWithTrait("trait1"));
+  EXPECT_EQ("comp1", manager.FindComponentWithTrait("trait2"));
+  EXPECT_EQ("comp2", manager.FindComponentWithTrait("trait3"));
+  EXPECT_EQ("", manager.FindComponentWithTrait("trait4"));
+}
+
 }  // namespace weave