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