blob: dcba8c956cfb1f09218ab667331b8f49587b08e9 [file] [log] [blame]
Alex Vakulenko7c36b672014-07-16 14:50:58 -07001// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "buffet/commands/command_dictionary.h"
6
7#include <base/values.h>
8
9#include "buffet/commands/command_definition.h"
10#include "buffet/commands/schema_constants.h"
11#include "buffet/string_utils.h"
12
13namespace buffet {
14
15std::vector<std::string> CommandDictionary::GetCommandNamesByCategory(
16 const std::string& category) const {
17 std::vector<std::string> names;
18 for (const auto& pair : definitions_) {
19 if (pair.second->GetCategory() == category)
20 names.push_back(pair.first);
21 }
22 return names;
23}
24
25bool CommandDictionary::LoadCommands(const base::DictionaryValue& json,
26 const std::string& category,
Alex Vakulenkofd448692014-07-22 07:46:53 -070027 const CommandDictionary* base_commands,
Alex Vakulenko7c36b672014-07-16 14:50:58 -070028 ErrorPtr* error) {
29 std::map<std::string, std::shared_ptr<const CommandDefinition>> new_defs;
30
31 // |json| contains a list of nested objects with the following structure:
32 // {"<pkg_name>": {"<cmd_name>": {"parameters": {object_schema}}, ...}, ...}
33 // Iterate over packages
34 base::DictionaryValue::Iterator package_iter(json);
35 while (!package_iter.IsAtEnd()) {
Alex Vakulenkofd448692014-07-22 07:46:53 -070036 std::string package_name = package_iter.key();
Alex Vakulenko7c36b672014-07-16 14:50:58 -070037 const base::DictionaryValue* package_value = nullptr;
38 if (!package_iter.value().GetAsDictionary(&package_value)) {
Alex Vakulenko9ac962f2014-07-22 07:34:56 -070039 Error::AddToPrintf(error, errors::commands::kDomain,
40 errors::commands::kTypeMismatch,
Alex Vakulenko7c36b672014-07-16 14:50:58 -070041 "Expecting an object for package '%s'",
Alex Vakulenkofd448692014-07-22 07:46:53 -070042 package_name.c_str());
Alex Vakulenko7c36b672014-07-16 14:50:58 -070043 return false;
44 }
45 // Iterate over command definitions within the current package.
46 base::DictionaryValue::Iterator command_iter(*package_value);
47 while (!command_iter.IsAtEnd()) {
Alex Vakulenkofd448692014-07-22 07:46:53 -070048 std::string command_name = command_iter.key();
49 if (command_name.empty()) {
50 Error::AddToPrintf(error, errors::commands::kDomain,
51 errors::commands::kInvalidCommandName,
52 "Unnamed command encountered in package '%s'",
53 package_name.c_str());
54 return false;
55 }
Alex Vakulenko7c36b672014-07-16 14:50:58 -070056 const base::DictionaryValue* command_value = nullptr;
57 if (!command_iter.value().GetAsDictionary(&command_value)) {
Alex Vakulenko9ac962f2014-07-22 07:34:56 -070058 Error::AddToPrintf(error, errors::commands::kDomain,
59 errors::commands::kTypeMismatch,
Alex Vakulenko7c36b672014-07-16 14:50:58 -070060 "Expecting an object for command '%s'",
Alex Vakulenkofd448692014-07-22 07:46:53 -070061 command_name.c_str());
Alex Vakulenko7c36b672014-07-16 14:50:58 -070062 return false;
63 }
64 // Construct the compound command name as "pkg_name.cmd_name".
Alex Vakulenkofd448692014-07-22 07:46:53 -070065 std::string full_command_name = string_utils::Join('.', package_name,
66 command_name);
Alex Vakulenko7c36b672014-07-16 14:50:58 -070067 // Get the "parameters" definition of the command and read it into
68 // an object schema.
69 const base::DictionaryValue* command_schema_def = nullptr;
70 if (!command_value->GetDictionaryWithoutPathExpansion(
71 commands::attributes::kCommand_Parameters, &command_schema_def)) {
Alex Vakulenko9ac962f2014-07-22 07:34:56 -070072 Error::AddToPrintf(error, errors::commands::kDomain,
73 errors::commands::kPropertyMissing,
Alex Vakulenko7c36b672014-07-16 14:50:58 -070074 "Command definition '%s' is missing property '%s'",
Alex Vakulenkofd448692014-07-22 07:46:53 -070075 full_command_name.c_str(),
Alex Vakulenko7c36b672014-07-16 14:50:58 -070076 commands::attributes::kCommand_Parameters);
77 return false;
78 }
Alex Vakulenkofd448692014-07-22 07:46:53 -070079
80 const ObjectSchema* base_def = nullptr;
81 if (base_commands) {
82 const CommandDefinition* cmd =
83 base_commands->FindCommand(full_command_name);
84 if (cmd)
85 base_def = cmd->GetParameters().get();
86
87 // If the base command dictionary was provided but the command was not
88 // found in it, this must be a custom (vendor) command. GCD spec states
89 // that all custom command names must begin with "_". Let's enforce
90 // this rule here.
91 if (!base_def) {
92 if (command_name.front() != '_') {
93 Error::AddToPrintf(error, errors::commands::kDomain,
94 errors::commands::kInvalidCommandName,
95 "The name of custom command '%s' in package '%s'"
96 " must start with '_'",
97 command_name.c_str(), package_name.c_str());
98 return false;
99 }
100 }
101 }
102
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700103 auto command_schema = std::make_shared<ObjectSchema>();
Alex Vakulenkofd448692014-07-22 07:46:53 -0700104 if (!command_schema->FromJson(command_schema_def, base_def, error)) {
Alex Vakulenko9ac962f2014-07-22 07:34:56 -0700105 Error::AddToPrintf(error, errors::commands::kDomain,
106 errors::commands::kInvalidObjectSchema,
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700107 "Invalid definition for command '%s'",
Alex Vakulenkofd448692014-07-22 07:46:53 -0700108 full_command_name.c_str());
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700109 return false;
110 }
111 auto command_def = std::make_shared<CommandDefinition>(category,
112 command_schema);
Alex Vakulenkofd448692014-07-22 07:46:53 -0700113 new_defs.insert(std::make_pair(full_command_name, command_def));
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700114
115 command_iter.Advance();
116 }
117 package_iter.Advance();
118 }
119
120 // Verify that newly loaded command definitions do not override existing
121 // definitions in another category. This is unlikely, but we don't want to let
122 // one vendor daemon to define the same commands already handled by another
123 // daemon on the same device.
124 for (const auto& pair : new_defs) {
125 auto iter = definitions_.find(pair.first);
126 if (iter != definitions_.end()) {
Alex Vakulenko9ac962f2014-07-22 07:34:56 -0700127 Error::AddToPrintf(error, errors::commands::kDomain,
128 errors::commands::kDuplicateCommandDef,
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700129 "Definition for command '%s' overrides an earlier "
130 "definition in category '%s'",
131 pair.first.c_str(),
132 iter->second->GetCategory().c_str());
133 return false;
134 }
135 }
136
137 // Now that we successfully loaded all the command definitions,
138 // remove previous definitions of commands from the same category.
139 std::vector<std::string> names = GetCommandNamesByCategory(category);
140 for (const std::string& name : names)
141 definitions_.erase(name);
142
143 // Insert new definitions into the global map.
144 definitions_.insert(new_defs.begin(), new_defs.end());
145 return true;
146}
147
148const CommandDefinition* CommandDictionary::FindCommand(
149 const std::string& command_name) const {
150 auto pair = definitions_.find(command_name);
151 return (pair != definitions_.end()) ? pair->second.get() : nullptr;
152}
153
154void CommandDictionary::Clear() {
155 definitions_.clear();
156}
157
158} // namespace buffet