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