| // Copyright 2015 The Weave Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/component_manager_impl.h" |
| |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| |
| #include "src/commands/schema_constants.h" |
| #include "src/json_error_codes.h" |
| #include "src/string_utils.h" |
| #include "src/utils.h" |
| |
| namespace weave { |
| |
| namespace { |
| // Max of 100 state update events should be enough in the queue. |
| const size_t kMaxStateChangeQueueSize = 100; |
| |
| const EnumToStringMap<UserRole>::Map kMap[] = { |
| {UserRole::kViewer, commands::attributes::kCommand_Role_Viewer}, |
| {UserRole::kUser, commands::attributes::kCommand_Role_User}, |
| {UserRole::kOwner, commands::attributes::kCommand_Role_Owner}, |
| {UserRole::kManager, commands::attributes::kCommand_Role_Manager}, |
| }; |
| } // anonymous namespace |
| |
| template <> |
| LIBWEAVE_EXPORT EnumToStringMap<UserRole>::EnumToStringMap() |
| : EnumToStringMap(kMap) {} |
| |
| ComponentManagerImpl::ComponentManagerImpl(provider::TaskRunner* task_runner, |
| base::Clock* clock) |
| : clock_{clock ? clock : &default_clock_}, |
| command_queue_{task_runner, clock_} {} |
| |
| ComponentManagerImpl::~ComponentManagerImpl() {} |
| |
| bool ComponentManagerImpl::AddComponent(const std::string& path, |
| const std::string& name, |
| const std::vector<std::string>& traits, |
| ErrorPtr* error) { |
| base::DictionaryValue* root = &components_; |
| if (!path.empty()) { |
| root = FindComponentGraftNode(path, error); |
| if (!root) |
| return false; |
| } |
| if (root->GetWithoutPathExpansion(name, nullptr)) { |
| return Error::AddToPrintf(error, FROM_HERE, errors::commands::kInvalidState, |
| "Component '%s' already exists at path '%s'", |
| name.c_str(), path.c_str()); |
| } |
| |
| // Check to make sure the declared traits are already defined. |
| for (const std::string& trait : traits) { |
| if (!FindTraitDefinition(trait)) { |
| return Error::AddToPrintf(error, FROM_HERE, |
| errors::commands::kInvalidPropValue, |
| "Trait '%s' is undefined", trait.c_str()); |
| } |
| } |
| std::unique_ptr<base::DictionaryValue> dict{new base::DictionaryValue}; |
| std::unique_ptr<base::ListValue> traits_list{new base::ListValue}; |
| traits_list->AppendStrings(traits); |
| dict->Set("traits", traits_list.release()); |
| root->SetWithoutPathExpansion(name, dict.release()); |
| for (const auto& cb : on_componet_tree_changed_) |
| cb.Run(); |
| return true; |
| } |
| |
| bool ComponentManagerImpl::AddComponentArrayItem( |
| const std::string& path, |
| const std::string& name, |
| const std::vector<std::string>& traits, |
| ErrorPtr* error) { |
| base::DictionaryValue* root = &components_; |
| if (!path.empty()) { |
| root = FindComponentGraftNode(path, error); |
| if (!root) |
| return false; |
| } |
| base::ListValue* array_value = nullptr; |
| if (!root->GetListWithoutPathExpansion(name, &array_value)) { |
| array_value = new base::ListValue; |
| root->SetWithoutPathExpansion(name, array_value); |
| } |
| std::unique_ptr<base::DictionaryValue> dict{new base::DictionaryValue}; |
| std::unique_ptr<base::ListValue> traits_list{new base::ListValue}; |
| traits_list->AppendStrings(traits); |
| dict->Set("traits", traits_list.release()); |
| array_value->Append(dict.release()); |
| for (const auto& cb : on_componet_tree_changed_) |
| cb.Run(); |
| return true; |
| } |
| |
| bool ComponentManagerImpl::RemoveComponent(const std::string& path, |
| const std::string& name, |
| ErrorPtr* error) { |
| base::DictionaryValue* root = &components_; |
| if (!path.empty()) { |
| root = FindComponentGraftNode(path, error); |
| if (!root) |
| return false; |
| } |
| |
| if (!root->RemoveWithoutPathExpansion(name, nullptr)) { |
| return Error::AddToPrintf(error, FROM_HERE, errors::commands::kInvalidState, |
| "Component '%s' does not exist at path '%s'", |
| name.c_str(), path.c_str()); |
| } |
| |
| for (const auto& cb : on_componet_tree_changed_) |
| cb.Run(); |
| return true; |
| } |
| |
| bool ComponentManagerImpl::RemoveComponentArrayItem(const std::string& path, |
| const std::string& name, |
| size_t index, |
| ErrorPtr* error) { |
| base::DictionaryValue* root = &components_; |
| if (!path.empty()) { |
| root = FindComponentGraftNode(path, error); |
| if (!root) |
| return false; |
| } |
| |
| base::ListValue* array_value = nullptr; |
| if (!root->GetListWithoutPathExpansion(name, &array_value)) { |
| return Error::AddToPrintf( |
| error, FROM_HERE, errors::commands::kInvalidState, |
| "There is no component array named '%s' at path '%s'", name.c_str(), |
| path.c_str()); |
| } |
| |
| if (!array_value->Remove(index, nullptr)) { |
| return Error::AddToPrintf( |
| error, FROM_HERE, errors::commands::kInvalidState, |
| "Component array '%s' at path '%s' does not have an element %zu", |
| name.c_str(), path.c_str(), index); |
| } |
| |
| for (const auto& cb : on_componet_tree_changed_) |
| cb.Run(); |
| return true; |
| } |
| |
| void ComponentManagerImpl::AddComponentTreeChangedCallback( |
| const base::Closure& callback) { |
| on_componet_tree_changed_.push_back(callback); |
| callback.Run(); |
| } |
| |
| bool ComponentManagerImpl::LoadTraits(const base::DictionaryValue& dict, |
| ErrorPtr* error) { |
| bool modified = false; |
| bool result = true; |
| // Check if any of the new traits are already defined. If so, make sure the |
| // definition is exactly the same, or else this is an error. |
| for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) { |
| if (it.value().GetType() != base::Value::TYPE_DICTIONARY) { |
| Error::AddToPrintf(error, FROM_HERE, errors::commands::kTypeMismatch, |
| "Trait '%s' must be an object", it.key().c_str()); |
| result = false; |
| break; |
| } |
| const base::DictionaryValue* existing_def = nullptr; |
| if (traits_.GetDictionary(it.key(), &existing_def)) { |
| if (!existing_def->Equals(&it.value())) { |
| Error::AddToPrintf(error, FROM_HERE, errors::commands::kTypeMismatch, |
| "Trait '%s' cannot be redefined", it.key().c_str()); |
| result = false; |
| break; |
| } |
| } else { |
| traits_.Set(it.key(), it.value().DeepCopy()); |
| modified = true; |
| } |
| } |
| |
| if (modified) { |
| for (const auto& cb : on_trait_changed_) |
| cb.Run(); |
| } |
| return result; |
| } |
| |
| bool ComponentManagerImpl::LoadTraits(const std::string& json, |
| ErrorPtr* error) { |
| std::unique_ptr<const base::DictionaryValue> dict = LoadJsonDict(json, error); |
| if (!dict) |
| return false; |
| return LoadTraits(*dict, error); |
| } |
| |
| void ComponentManagerImpl::AddTraitDefChangedCallback( |
| const base::Closure& callback) { |
| on_trait_changed_.push_back(callback); |
| callback.Run(); |
| } |
| |
| void ComponentManagerImpl::AddCommand( |
| std::unique_ptr<CommandInstance> command_instance) { |
| command_queue_.Add(std::move(command_instance)); |
| } |
| |
| std::unique_ptr<CommandInstance> ComponentManagerImpl::ParseCommandInstance( |
| const base::DictionaryValue& command, |
| Command::Origin command_origin, |
| UserRole role, |
| std::string* id, |
| ErrorPtr* error) { |
| std::string command_id; |
| auto command_instance = |
| CommandInstance::FromJson(&command, command_origin, &command_id, error); |
| // If we fail to validate the command definition, but there was a command ID |
| // specified there, return it to the caller when requested. This will be |
| // used to abort cloud commands. |
| if (id) |
| *id = command_id; |
| |
| if (!command_instance) |
| return nullptr; |
| |
| UserRole minimal_role; |
| if (!GetMinimalRole(command_instance->GetName(), &minimal_role, error)) |
| return nullptr; |
| |
| if (role < minimal_role) { |
| return Error::AddToPrintf(error, FROM_HERE, "access_denied", |
| "User role '%s' less than minimal: '%s'", |
| EnumToString(role).c_str(), |
| EnumToString(minimal_role).c_str()); |
| } |
| |
| std::string component_path = command_instance->GetComponent(); |
| if (component_path.empty()) { |
| // 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()) { |
| return Error::AddToPrintf( |
| error, FROM_HERE, "unrouted_command", |
| "Unable route command '%s' because there is no component supporting" |
| "trait '%s'", |
| command_instance->GetName().c_str(), trait_name.c_str()); |
| } |
| command_instance->SetComponent(component_path); |
| } |
| |
| const base::DictionaryValue* component = FindComponent(component_path, error); |
| if (!component) |
| return nullptr; |
| |
| // Check that the command's trait is supported by the given component. |
| auto pair = SplitAtFirst(command_instance->GetName(), ".", true); |
| |
| bool trait_supported = false; |
| const base::ListValue* supported_traits = nullptr; |
| if (component->GetList("traits", &supported_traits)) { |
| for (const base::Value* value : *supported_traits) { |
| std::string trait; |
| CHECK(value->GetAsString(&trait)); |
| if (trait == pair.first) { |
| trait_supported = true; |
| break; |
| } |
| } |
| } |
| |
| if (!trait_supported) { |
| return Error::AddToPrintf(error, FROM_HERE, "trait_not_supported", |
| "Component '%s' doesn't support trait '%s'", |
| component_path.c_str(), pair.first.c_str()); |
| } |
| |
| if (command_id.empty()) { |
| command_id = std::to_string(++next_command_id_); |
| command_instance->SetID(command_id); |
| if (id) |
| *id = command_id; |
| } |
| |
| return command_instance; |
| } |
| |
| CommandInstance* ComponentManagerImpl::FindCommand(const std::string& id) { |
| return command_queue_.Find(id); |
| } |
| |
| void ComponentManagerImpl::AddCommandAddedCallback( |
| const CommandQueue::CommandCallback& callback) { |
| command_queue_.AddCommandAddedCallback(callback); |
| } |
| |
| void ComponentManagerImpl::AddCommandRemovedCallback( |
| const CommandQueue::CommandCallback& callback) { |
| command_queue_.AddCommandRemovedCallback(callback); |
| } |
| |
| void ComponentManagerImpl::AddCommandHandler( |
| const std::string& component_path, |
| const std::string& command_name, |
| const Device::CommandHandlerCallback& callback) { |
| // If both component_path and command_name are empty, we are adding the |
| // default handler for all commands. |
| if (!component_path.empty() || !command_name.empty()) { |
| CHECK(FindCommandDefinition(command_name)) << "Command undefined: " |
| << command_name; |
| } |
| command_queue_.AddCommandHandler(component_path, command_name, callback); |
| } |
| |
| const base::DictionaryValue* ComponentManagerImpl::FindComponent( |
| const std::string& path, |
| ErrorPtr* error) const { |
| return FindComponentAt(&components_, path, error); |
| } |
| |
| const base::DictionaryValue* ComponentManagerImpl::FindTraitDefinition( |
| const std::string& name) const { |
| const base::DictionaryValue* trait = nullptr; |
| traits_.GetDictionaryWithoutPathExpansion(name, &trait); |
| return trait; |
| } |
| |
| const base::DictionaryValue* ComponentManagerImpl::FindCommandDefinition( |
| const std::string& command_name) const { |
| const base::DictionaryValue* definition = nullptr; |
| std::vector<std::string> components = Split(command_name, ".", true, false); |
| // Make sure the |command_name| came in form of trait_name.command_name. |
| if (components.size() != 2) |
| return definition; |
| std::string key = base::StringPrintf("%s.commands.%s", components[0].c_str(), |
| components[1].c_str()); |
| traits_.GetDictionary(key, &definition); |
| return definition; |
| } |
| |
| bool ComponentManagerImpl::GetMinimalRole(const std::string& command_name, |
| UserRole* minimal_role, |
| ErrorPtr* error) const { |
| const base::DictionaryValue* command = FindCommandDefinition(command_name); |
| if (!command) { |
| return Error::AddToPrintf( |
| error, FROM_HERE, errors::commands::kInvalidCommandName, |
| "Command definition for '%s' not found", command_name.c_str()); |
| } |
| std::string value; |
| // The JSON definition has been pre-validated already in LoadCommands, so |
| // just using CHECKs here. |
| CHECK(command->GetString(commands::attributes::kCommand_Role, &value)); |
| CHECK(StringToEnum(value, minimal_role)); |
| return true; |
| } |
| |
| void ComponentManagerImpl::AddStateChangedCallback( |
| const base::Closure& callback) { |
| on_state_changed_.push_back(callback); |
| callback.Run(); // Force to read current state. |
| } |
| |
| bool ComponentManagerImpl::SetStateProperties(const std::string& component_path, |
| const base::DictionaryValue& dict, |
| ErrorPtr* error) { |
| base::DictionaryValue* component = |
| FindMutableComponent(component_path, error); |
| if (!component) |
| return false; |
| |
| base::DictionaryValue* state = nullptr; |
| if (!component->GetDictionary("state", &state)) { |
| state = new base::DictionaryValue; |
| component->Set("state", state); |
| } |
| state->MergeDictionary(&dict); |
| last_state_change_id_++; |
| auto& queue = state_change_queues_[component_path]; |
| if (!queue) |
| queue.reset(new StateChangeQueue{kMaxStateChangeQueueSize}); |
| base::Time timestamp = clock_->Now(); |
| queue->NotifyPropertiesUpdated(timestamp, dict); |
| for (const auto& cb : on_state_changed_) |
| cb.Run(); |
| return true; |
| } |
| |
| bool ComponentManagerImpl::SetStatePropertiesFromJson( |
| const std::string& component_path, |
| const std::string& json, |
| ErrorPtr* error) { |
| std::unique_ptr<const base::DictionaryValue> dict = LoadJsonDict(json, error); |
| return dict && SetStateProperties(component_path, *dict, error); |
| } |
| |
| const base::Value* ComponentManagerImpl::GetStateProperty( |
| const std::string& component_path, |
| const std::string& name, |
| ErrorPtr* error) const { |
| const base::DictionaryValue* component = FindComponent(component_path, error); |
| if (!component) |
| return nullptr; |
| auto pair = SplitAtFirst(name, ".", true); |
| if (pair.first.empty()) { |
| return Error::AddToPrintf(error, FROM_HERE, |
| errors::commands::kPropertyMissing, |
| "Empty state package in '%s'", name.c_str()); |
| } |
| if (pair.second.empty()) { |
| return Error::AddToPrintf( |
| error, FROM_HERE, errors::commands::kPropertyMissing, |
| "State property name not specified in '%s'", name.c_str()); |
| } |
| std::string key = base::StringPrintf("state.%s", name.c_str()); |
| const base::Value* value = nullptr; |
| if (!component->Get(key, &value)) { |
| return Error::AddToPrintf(error, FROM_HERE, |
| errors::commands::kPropertyMissing, |
| "State property '%s' not found in component '%s'", |
| name.c_str(), component_path.c_str()); |
| } |
| return value; |
| } |
| |
| bool ComponentManagerImpl::SetStateProperty(const std::string& component_path, |
| const std::string& name, |
| const base::Value& value, |
| ErrorPtr* error) { |
| base::DictionaryValue dict; |
| auto pair = SplitAtFirst(name, ".", true); |
| if (pair.first.empty()) { |
| return Error::AddToPrintf(error, FROM_HERE, |
| errors::commands::kPropertyMissing, |
| "Empty state package in '%s'", name.c_str()); |
| } |
| if (pair.second.empty()) { |
| return Error::AddToPrintf( |
| error, FROM_HERE, errors::commands::kPropertyMissing, |
| "State property name not specified in '%s'", name.c_str()); |
| } |
| dict.Set(name, value.DeepCopy()); |
| return SetStateProperties(component_path, dict, error); |
| } |
| |
| ComponentManager::StateSnapshot |
| ComponentManagerImpl::GetAndClearRecordedStateChanges() { |
| StateSnapshot snapshot; |
| snapshot.update_id = GetLastStateChangeId(); |
| for (auto& pair : state_change_queues_) { |
| auto changes = pair.second->GetAndClearRecordedStateChanges(); |
| auto component = pair.first; |
| auto conv = [component](weave::StateChange& change) { |
| return ComponentStateChange{change.timestamp, component, |
| std::move(change.changed_properties)}; |
| }; |
| std::transform(changes.begin(), changes.end(), |
| std::back_inserter(snapshot.state_changes), conv); |
| } |
| |
| // Sort events by the timestamp. |
| auto pred = [](const ComponentStateChange& lhs, |
| const ComponentStateChange& rhs) { |
| return lhs.timestamp < rhs.timestamp; |
| }; |
| std::sort(snapshot.state_changes.begin(), snapshot.state_changes.end(), pred); |
| state_change_queues_.clear(); |
| return snapshot; |
| } |
| |
| void ComponentManagerImpl::NotifyStateUpdatedOnServer(UpdateID id) { |
| on_server_state_updated_.Notify(id); |
| } |
| |
| ComponentManager::Token ComponentManagerImpl::AddServerStateUpdatedCallback( |
| const base::Callback<void(UpdateID)>& callback) { |
| if (state_change_queues_.empty()) |
| callback.Run(GetLastStateChangeId()); |
| return Token{on_server_state_updated_.Add(callback).release()}; |
| } |
| |
| std::string ComponentManagerImpl::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{}; |
| } |
| |
| bool ComponentManagerImpl::AddLegacyCommandDefinitions( |
| const base::DictionaryValue& dict, |
| ErrorPtr* error) { |
| bool result = true; |
| bool modified = false; |
| for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) { |
| const base::DictionaryValue* command_dict = nullptr; |
| if (!it.value().GetAsDictionary(&command_dict)) { |
| Error::AddToPrintf(error, FROM_HERE, errors::commands::kTypeMismatch, |
| "Package '%s' must be an object", it.key().c_str()); |
| result = false; |
| continue; |
| } |
| AddTraitToLegacyComponent(it.key()); |
| for (base::DictionaryValue::Iterator it_def(*command_dict); |
| !it_def.IsAtEnd(); it_def.Advance()) { |
| std::string key = base::StringPrintf("%s.commands.%s", it.key().c_str(), |
| it_def.key().c_str()); |
| if (traits_.GetDictionary(key, nullptr)) { |
| Error::AddToPrintf(error, FROM_HERE, |
| errors::commands::kInvalidPropValue, |
| "Redefining command '%s.%s'", it.key().c_str(), |
| it_def.key().c_str()); |
| result = false; |
| continue; |
| } |
| traits_.Set(key, it_def.value().DeepCopy()); |
| modified = true; |
| } |
| } |
| |
| if (modified) { |
| for (const auto& cb : on_trait_changed_) |
| cb.Run(); |
| } |
| return result; |
| } |
| |
| bool ComponentManagerImpl::AddLegacyStateDefinitions( |
| const base::DictionaryValue& dict, |
| ErrorPtr* error) { |
| bool result = true; |
| bool modified = false; |
| for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) { |
| const base::DictionaryValue* state_dict = nullptr; |
| if (!it.value().GetAsDictionary(&state_dict)) { |
| Error::AddToPrintf(error, FROM_HERE, errors::commands::kTypeMismatch, |
| "Package '%s' must be an object", it.key().c_str()); |
| result = false; |
| continue; |
| } |
| AddTraitToLegacyComponent(it.key()); |
| for (base::DictionaryValue::Iterator it_def(*state_dict); !it_def.IsAtEnd(); |
| it_def.Advance()) { |
| std::string key = base::StringPrintf("%s.state.%s", it.key().c_str(), |
| it_def.key().c_str()); |
| if (traits_.GetDictionary(key, nullptr)) { |
| Error::AddToPrintf(error, FROM_HERE, |
| errors::commands::kInvalidPropValue, |
| "Redefining state property '%s.%s'", |
| it.key().c_str(), it_def.key().c_str()); |
| result = false; |
| continue; |
| } |
| traits_.Set(key, it_def.value().DeepCopy()); |
| modified = true; |
| } |
| } |
| |
| if (modified) { |
| for (const auto& cb : on_trait_changed_) |
| cb.Run(); |
| } |
| return result; |
| } |
| |
| const base::DictionaryValue& ComponentManagerImpl::GetLegacyState() const { |
| legacy_state_.Clear(); |
| // Build state from components. |
| for (base::DictionaryValue::Iterator it(components_); !it.IsAtEnd(); |
| it.Advance()) { |
| const base::DictionaryValue* component_dict = nullptr; |
| const base::DictionaryValue* component_state = nullptr; |
| if (it.value().GetAsDictionary(&component_dict) && |
| component_dict->GetDictionary("state", &component_state)) { |
| legacy_state_.MergeDictionary(component_state); |
| } |
| } |
| return legacy_state_; |
| } |
| |
| const base::DictionaryValue& ComponentManagerImpl::GetLegacyCommandDefinitions() |
| const { |
| legacy_command_defs_.Clear(); |
| // Build commandDefs from traits. |
| for (base::DictionaryValue::Iterator it(traits_); !it.IsAtEnd(); |
| it.Advance()) { |
| const base::DictionaryValue* trait_dict = nullptr; |
| const base::DictionaryValue* trait_commands = nullptr; |
| if (it.value().GetAsDictionary(&trait_dict) && |
| trait_dict->GetDictionary("commands", &trait_commands)) { |
| base::DictionaryValue dict; |
| dict.Set(it.key(), trait_commands->DeepCopy()); |
| legacy_command_defs_.MergeDictionary(&dict); |
| } |
| } |
| return legacy_command_defs_; |
| } |
| |
| void ComponentManagerImpl::AddTraitToLegacyComponent(const std::string& trait) { |
| // First check if we already have a component supporting this trait. |
| if (!FindComponentWithTrait(trait).empty()) |
| return; |
| |
| // If not, add this trait to the first component available. |
| base::DictionaryValue* component = nullptr; |
| base::DictionaryValue::Iterator it(components_); |
| if (it.IsAtEnd()) { |
| // No components at all. Create a new one with dummy name. |
| // This normally wouldn't happen since libweave creates its own component |
| // at startup. |
| component = new base::DictionaryValue; |
| components_.Set("__weave__", component); |
| } else { |
| CHECK(components_.GetDictionary(it.key(), &component)); |
| } |
| base::ListValue* traits = nullptr; |
| if (!component->GetList("traits", &traits)) { |
| traits = new base::ListValue; |
| component->Set("traits", traits); |
| } |
| traits->AppendString(trait); |
| } |
| |
| base::DictionaryValue* ComponentManagerImpl::FindComponentGraftNode( |
| const std::string& path, |
| ErrorPtr* error) { |
| base::DictionaryValue* root = nullptr; |
| base::DictionaryValue* component = FindMutableComponent(path, error); |
| if (component && !component->GetDictionary("components", &root)) { |
| root = new base::DictionaryValue; |
| component->Set("components", root); |
| } |
| return root; |
| } |
| |
| base::DictionaryValue* ComponentManagerImpl::FindMutableComponent( |
| const std::string& path, |
| ErrorPtr* error) { |
| return const_cast<base::DictionaryValue*>( |
| FindComponentAt(&components_, path, error)); |
| } |
| |
| const base::DictionaryValue* ComponentManagerImpl::FindComponentAt( |
| const base::DictionaryValue* root, |
| const std::string& path, |
| ErrorPtr* error) { |
| auto parts = Split(path, ".", true, false); |
| std::string root_path; |
| for (size_t i = 0; i < parts.size(); i++) { |
| auto element = SplitAtFirst(parts[i], "[", true); |
| int array_index = -1; |
| if (element.first.empty()) { |
| return Error::AddToPrintf( |
| error, FROM_HERE, errors::commands::kPropertyMissing, |
| "Empty path element at '%s'", root_path.c_str()); |
| } |
| if (!element.second.empty()) { |
| if (element.second.back() != ']') { |
| return Error::AddToPrintf( |
| error, FROM_HERE, errors::commands::kPropertyMissing, |
| "Invalid array element syntax '%s'", parts[i].c_str()); |
| } |
| element.second.pop_back(); |
| std::string index_str; |
| base::TrimWhitespaceASCII(element.second, base::TrimPositions::TRIM_ALL, |
| &index_str); |
| if (!base::StringToInt(index_str, &array_index) || array_index < 0) { |
| return Error::AddToPrintf( |
| error, FROM_HERE, errors::commands::kInvalidPropValue, |
| "Invalid array index '%s'", element.second.c_str()); |
| } |
| } |
| |
| if (!root_path.empty()) { |
| // We have processed at least one item in the path before, so now |root| |
| // points to the actual parent component. We need the root to point to |
| // the 'components' element containing child sub-components instead. |
| if (!root->GetDictionary("components", &root)) { |
| return Error::AddToPrintf(error, FROM_HERE, |
| errors::commands::kPropertyMissing, |
| "Component '%s' does not exist at '%s'", |
| element.first.c_str(), root_path.c_str()); |
| } |
| } |
| |
| const base::Value* value = nullptr; |
| if (!root->GetWithoutPathExpansion(element.first, &value)) { |
| Error::AddToPrintf(error, FROM_HERE, errors::commands::kPropertyMissing, |
| "Component '%s' does not exist at '%s'", |
| element.first.c_str(), root_path.c_str()); |
| return nullptr; |
| } |
| |
| if (value->GetType() == base::Value::TYPE_LIST && array_index < 0) { |
| return Error::AddToPrintf(error, FROM_HERE, |
| errors::commands::kTypeMismatch, |
| "Element '%s.%s' is an array", |
| root_path.c_str(), element.first.c_str()); |
| } |
| if (value->GetType() == base::Value::TYPE_DICTIONARY && array_index >= 0) { |
| return Error::AddToPrintf(error, FROM_HERE, |
| errors::commands::kTypeMismatch, |
| "Element '%s.%s' is not an array", |
| root_path.c_str(), element.first.c_str()); |
| } |
| |
| if (value->GetType() == base::Value::TYPE_DICTIONARY) { |
| CHECK(value->GetAsDictionary(&root)); |
| } else { |
| const base::ListValue* component_array = nullptr; |
| CHECK(value->GetAsList(&component_array)); |
| const base::Value* component_value = nullptr; |
| if (!component_array->Get(array_index, &component_value) || |
| !component_value->GetAsDictionary(&root)) { |
| return Error::AddToPrintf( |
| error, FROM_HERE, errors::commands::kPropertyMissing, |
| "Element '%s.%s' does not contain item #%d", root_path.c_str(), |
| element.first.c_str(), array_index); |
| } |
| } |
| if (!root_path.empty()) |
| root_path += '.'; |
| root_path += parts[i]; |
| } |
| return root; |
| } |
| |
| } // namespace weave |