blob: 51ffb3d4319cdd30f31b7593e5873d0a9239afb9 [file] [log] [blame]
// Copyright 2014 The Chromium OS 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 "buffet/commands/command_dictionary.h"
#include <base/values.h>
#include <chromeos/strings/string_utils.h>
#include "buffet/commands/command_definition.h"
#include "buffet/commands/schema_constants.h"
namespace buffet {
std::vector<std::string> CommandDictionary::GetCommandNamesByCategory(
const std::string& category) const {
std::vector<std::string> names;
for (const auto& pair : definitions_) {
if (pair.second->GetCategory() == category)
names.push_back(pair.first);
}
return names;
}
bool CommandDictionary::LoadCommands(const base::DictionaryValue& json,
const std::string& category,
const CommandDictionary* base_commands,
chromeos::ErrorPtr* error) {
std::map<std::string, std::shared_ptr<const CommandDefinition>> new_defs;
// |json| contains a list of nested objects with the following structure:
// {"<pkg_name>": {"<cmd_name>": {"parameters": {object_schema}}, ...}, ...}
// Iterate over packages
base::DictionaryValue::Iterator package_iter(json);
while (!package_iter.IsAtEnd()) {
std::string package_name = package_iter.key();
const base::DictionaryValue* package_value = nullptr;
if (!package_iter.value().GetAsDictionary(&package_value)) {
chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
errors::commands::kTypeMismatch,
"Expecting an object for package '%s'",
package_name.c_str());
return false;
}
// Iterate over command definitions within the current package.
base::DictionaryValue::Iterator command_iter(*package_value);
while (!command_iter.IsAtEnd()) {
std::string command_name = command_iter.key();
if (command_name.empty()) {
chromeos::Error::AddToPrintf(
error, errors::commands::kDomain,
errors::commands::kInvalidCommandName,
"Unnamed command encountered in package '%s'",
package_name.c_str());
return false;
}
const base::DictionaryValue* command_value = nullptr;
if (!command_iter.value().GetAsDictionary(&command_value)) {
chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
errors::commands::kTypeMismatch,
"Expecting an object for command '%s'",
command_name.c_str());
return false;
}
// Construct the compound command name as "pkg_name.cmd_name".
std::string full_command_name = chromeos::string_utils::Join(
'.', package_name, command_name);
// Get the "parameters" definition of the command and read it into
// an object schema.
const base::DictionaryValue* command_schema_def = nullptr;
if (!command_value->GetDictionaryWithoutPathExpansion(
commands::attributes::kCommand_Parameters, &command_schema_def)) {
chromeos::Error::AddToPrintf(
error, errors::commands::kDomain,
errors::commands::kPropertyMissing,
"Command definition '%s' is missing property '%s'",
full_command_name.c_str(),
commands::attributes::kCommand_Parameters);
return false;
}
const ObjectSchema* base_def = nullptr;
if (base_commands) {
const CommandDefinition* cmd =
base_commands->FindCommand(full_command_name);
if (cmd)
base_def = cmd->GetParameters().get();
// If the base command dictionary was provided but the command was not
// found in it, this must be a custom (vendor) command. GCD spec states
// that all custom command names must begin with "_". Let's enforce
// this rule here.
if (!base_def) {
if (command_name.front() != '_') {
chromeos::Error::AddToPrintf(
error, errors::commands::kDomain,
errors::commands::kInvalidCommandName,
"The name of custom command '%s' in package '%s'"
" must start with '_'",
command_name.c_str(), package_name.c_str());
return false;
}
}
}
auto command_schema = std::make_shared<ObjectSchema>();
if (!command_schema->FromJson(command_schema_def, base_def, error)) {
chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
errors::commands::kInvalidObjectSchema,
"Invalid definition for command '%s'",
full_command_name.c_str());
return false;
}
auto command_def = std::make_shared<CommandDefinition>(category,
command_schema);
new_defs.insert(std::make_pair(full_command_name, command_def));
command_iter.Advance();
}
package_iter.Advance();
}
// Verify that newly loaded command definitions do not override existing
// definitions in another category. This is unlikely, but we don't want to let
// one vendor daemon to define the same commands already handled by another
// daemon on the same device.
for (const auto& pair : new_defs) {
auto iter = definitions_.find(pair.first);
if (iter != definitions_.end()) {
chromeos::Error::AddToPrintf(
error, errors::commands::kDomain,
errors::commands::kDuplicateCommandDef,
"Definition for command '%s' overrides an earlier "
"definition in category '%s'",
pair.first.c_str(), iter->second->GetCategory().c_str());
return false;
}
}
// Now that we successfully loaded all the command definitions,
// remove previous definitions of commands from the same category.
std::vector<std::string> names = GetCommandNamesByCategory(category);
for (const std::string& name : names)
definitions_.erase(name);
// Insert new definitions into the global map.
definitions_.insert(new_defs.begin(), new_defs.end());
return true;
}
std::unique_ptr<base::DictionaryValue> CommandDictionary::GetCommandsAsJson(
bool full_schema, chromeos::ErrorPtr* error) const {
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
for (const auto& pair : definitions_) {
std::unique_ptr<base::DictionaryValue> definition =
pair.second->GetParameters()->ToJson(full_schema, error);
if (!definition) {
dict.reset();
return dict;
}
auto cmd_name_parts = chromeos::string_utils::SplitAtFirst(pair.first, '.');
std::string package_name = cmd_name_parts.first;
std::string command_name = cmd_name_parts.second;
base::DictionaryValue* package = nullptr;
if (!dict->GetDictionaryWithoutPathExpansion(package_name, &package)) {
// If this is the first time we encounter this package, create a JSON
// object for it.
package = new base::DictionaryValue;
dict->SetWithoutPathExpansion(package_name, package);
}
base::DictionaryValue* command_def = new base::DictionaryValue;
command_def->SetWithoutPathExpansion(
commands::attributes::kCommand_Parameters, definition.release());
package->SetWithoutPathExpansion(command_name, command_def);
}
return dict;
}
const CommandDefinition* CommandDictionary::FindCommand(
const std::string& command_name) const {
auto pair = definitions_.find(command_name);
return (pair != definitions_.end()) ? pair->second.get() : nullptr;
}
void CommandDictionary::Clear() {
definitions_.clear();
}
} // namespace buffet