blob: 0fdae6504270df5f9ba38172d19e892eb3f2aee9 [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>
Alex Vakulenkoa8b95bc2014-08-27 11:00:57 -07008#include <chromeos/strings/string_utils.h>
Alex Vakulenko7c36b672014-07-16 14:50:58 -07009
10#include "buffet/commands/command_definition.h"
11#include "buffet/commands/schema_constants.h"
Alex Vakulenko7c36b672014-07-16 14:50:58 -070012
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 Vakulenko5f472062014-08-14 17:54:04 -070028 chromeos::ErrorPtr* error) {
Alex Vakulenko5ef75792015-03-19 15:50:44 -070029 CommandMap new_defs;
Alex Vakulenko7c36b672014-07-16 14:50:58 -070030
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 Vakulenkoac8037d2014-11-11 11:42:05 -080039 chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
Alex Vakulenko5f472062014-08-14 17:54:04 -070040 errors::commands::kTypeMismatch,
41 "Expecting an object for package '%s'",
42 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()) {
Alex Vakulenko5f472062014-08-14 17:54:04 -070050 chromeos::Error::AddToPrintf(
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080051 error, FROM_HERE, errors::commands::kDomain,
Alex Vakulenko5f472062014-08-14 17:54:04 -070052 errors::commands::kInvalidCommandName,
53 "Unnamed command encountered in package '%s'",
54 package_name.c_str());
Alex Vakulenkofd448692014-07-22 07:46:53 -070055 return false;
56 }
Anton Muhin71fb9d52014-11-21 22:22:39 +040057 const base::DictionaryValue* command_def_json = nullptr;
58 if (!command_iter.value().GetAsDictionary(&command_def_json)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080059 chromeos::Error::AddToPrintf(error, FROM_HERE,
60 errors::commands::kDomain,
Alex Vakulenko5f472062014-08-14 17:54:04 -070061 errors::commands::kTypeMismatch,
62 "Expecting an object for command '%s'",
63 command_name.c_str());
Alex Vakulenko7c36b672014-07-16 14:50:58 -070064 return false;
65 }
66 // Construct the compound command name as "pkg_name.cmd_name".
Vitaly Bukadb770e72015-03-10 19:33:33 -070067 std::string full_command_name =
68 chromeos::string_utils::Join(".", package_name, command_name);
Alex Vakulenkofd448692014-07-22 07:46:53 -070069
Anton Muhin71fb9d52014-11-21 22:22:39 +040070 const ObjectSchema* base_parameters_def = nullptr;
Vitaly Buka4129dfa2015-04-29 12:16:58 -070071 const ObjectSchema* base_progress_def = nullptr;
Anton Muhin71fb9d52014-11-21 22:22:39 +040072 const ObjectSchema* base_results_def = nullptr;
Alex Vakulenko5e86fee2015-04-17 08:47:45 -070073 // By default make it available to all clients.
74 auto visibility = CommandDefinition::Visibility::GetAll();
Alex Vakulenkofd448692014-07-22 07:46:53 -070075 if (base_commands) {
Anton Muhincfde8692014-11-25 03:36:59 +040076 auto cmd = base_commands->FindCommand(full_command_name);
Anton Muhin71fb9d52014-11-21 22:22:39 +040077 if (cmd) {
Alex Vakulenko5ef75792015-03-19 15:50:44 -070078 base_parameters_def = cmd->GetParameters();
Vitaly Buka4129dfa2015-04-29 12:16:58 -070079 base_progress_def = cmd->GetProgress();
Alex Vakulenko5ef75792015-03-19 15:50:44 -070080 base_results_def = cmd->GetResults();
Alex Vakulenko5e86fee2015-04-17 08:47:45 -070081 visibility = cmd->GetVisibility();
Anton Muhin71fb9d52014-11-21 22:22:39 +040082 }
Alex Vakulenkofd448692014-07-22 07:46:53 -070083
84 // If the base command dictionary was provided but the command was not
85 // found in it, this must be a custom (vendor) command. GCD spec states
86 // that all custom command names must begin with "_". Let's enforce
87 // this rule here.
Anton Muhin71fb9d52014-11-21 22:22:39 +040088 if (!cmd) {
Alex Vakulenkofd448692014-07-22 07:46:53 -070089 if (command_name.front() != '_') {
Alex Vakulenko5f472062014-08-14 17:54:04 -070090 chromeos::Error::AddToPrintf(
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080091 error, FROM_HERE, errors::commands::kDomain,
Alex Vakulenko5f472062014-08-14 17:54:04 -070092 errors::commands::kInvalidCommandName,
93 "The name of custom command '%s' in package '%s'"
94 " must start with '_'",
95 command_name.c_str(), package_name.c_str());
Alex Vakulenkofd448692014-07-22 07:46:53 -070096 return false;
97 }
98 }
99 }
100
Anton Muhin71fb9d52014-11-21 22:22:39 +0400101 auto parameters_schema = BuildObjectSchema(
102 command_def_json,
103 commands::attributes::kCommand_Parameters,
104 base_parameters_def,
105 full_command_name,
106 error);
107 if (!parameters_schema)
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700108 return false;
Anton Muhin71fb9d52014-11-21 22:22:39 +0400109
Vitaly Buka4129dfa2015-04-29 12:16:58 -0700110 auto progress_schema = BuildObjectSchema(
111 command_def_json, commands::attributes::kCommand_Progress,
112 base_progress_def, full_command_name, error);
113 if (!progress_schema)
114 return false;
115
Anton Muhin71fb9d52014-11-21 22:22:39 +0400116 auto results_schema = BuildObjectSchema(
117 command_def_json,
118 commands::attributes::kCommand_Results,
119 base_results_def,
120 full_command_name,
121 error);
122 if (!results_schema)
123 return false;
124
Alex Vakulenko5e86fee2015-04-17 08:47:45 -0700125 std::string value;
126 using commands::attributes::kCommand_Visibility;
127 if (command_def_json->GetString(kCommand_Visibility, &value)) {
128 if (!visibility.FromString(value, error)) {
129 chromeos::Error::AddToPrintf(
130 error, FROM_HERE, errors::commands::kDomain,
131 errors::commands::kInvalidCommandVisibility,
132 "Error parsing command '%s'", full_command_name.c_str());
133 return false;
134 }
135 }
136
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700137 std::unique_ptr<CommandDefinition> command_def{
Vitaly Buka4129dfa2015-04-29 12:16:58 -0700138 new CommandDefinition{category,
139 std::move(parameters_schema),
140 std::move(progress_schema),
141 std::move(results_schema)}};
Alex Vakulenko5e86fee2015-04-17 08:47:45 -0700142 command_def->SetVisibility(visibility);
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700143 new_defs.emplace(full_command_name, std::move(command_def));
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700144
145 command_iter.Advance();
146 }
147 package_iter.Advance();
148 }
149
150 // Verify that newly loaded command definitions do not override existing
151 // definitions in another category. This is unlikely, but we don't want to let
152 // one vendor daemon to define the same commands already handled by another
153 // daemon on the same device.
154 for (const auto& pair : new_defs) {
155 auto iter = definitions_.find(pair.first);
Christopher Wiley13fca9d2015-01-14 09:56:34 -0800156 CHECK(iter == definitions_.end())
157 << "Definition for command '" << pair.first
158 << "' overrides an earlier definition in category '"
159 << iter->second->GetCategory().c_str() << "'";
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700160 }
161
162 // Now that we successfully loaded all the command definitions,
163 // remove previous definitions of commands from the same category.
164 std::vector<std::string> names = GetCommandNamesByCategory(category);
165 for (const std::string& name : names)
166 definitions_.erase(name);
167
168 // Insert new definitions into the global map.
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700169 for (auto& pair : new_defs)
170 definitions_.emplace(pair.first, std::move(pair.second));
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700171 return true;
172}
173
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700174std::unique_ptr<ObjectSchema> CommandDictionary::BuildObjectSchema(
Anton Muhin71fb9d52014-11-21 22:22:39 +0400175 const base::DictionaryValue* command_def_json,
176 const char* property_name,
177 const ObjectSchema* base_def,
178 const std::string& command_name,
179 chromeos::ErrorPtr* error) {
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700180 auto object_schema = ObjectSchema::Create();
Anton Muhin71fb9d52014-11-21 22:22:39 +0400181
182 const base::DictionaryValue* schema_def = nullptr;
183 if (!command_def_json->GetDictionaryWithoutPathExpansion(property_name,
184 &schema_def)) {
Vitaly Buka10cf5c72015-04-29 12:05:10 -0700185 return object_schema;
Anton Muhin71fb9d52014-11-21 22:22:39 +0400186 }
187
188 if (!object_schema->FromJson(schema_def, base_def, error)) {
189 chromeos::Error::AddToPrintf(error, FROM_HERE,
190 errors::commands::kDomain,
191 errors::commands::kInvalidObjectSchema,
192 "Invalid definition for command '%s'",
193 command_name.c_str());
194 return {};
195 }
196
197 return object_schema;
198}
199
Alex Vakulenko45109442014-07-29 11:07:10 -0700200std::unique_ptr<base::DictionaryValue> CommandDictionary::GetCommandsAsJson(
Alex Vakulenko9ea5a322015-04-17 15:35:34 -0700201 const std::function<bool(const CommandDefinition*)>& filter,
202 bool full_schema,
203 chromeos::ErrorPtr* error) const {
Alex Vakulenko45109442014-07-29 11:07:10 -0700204 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
205 for (const auto& pair : definitions_) {
Alex Vakulenko9ea5a322015-04-17 15:35:34 -0700206 // Check if the command definition has the desired visibility.
207 // If not, then skip it.
208 if (!filter(pair.second.get()))
209 continue;
210
Vitaly Bukab6b49e52015-05-01 10:53:06 -0700211 std::unique_ptr<base::DictionaryValue> parameters =
Alex Vakulenko45109442014-07-29 11:07:10 -0700212 pair.second->GetParameters()->ToJson(full_schema, error);
Vitaly Bukab6b49e52015-05-01 10:53:06 -0700213 if (!parameters)
214 return {};
215 // Progress and results are not part of public commandDefs.
216
Vitaly Bukadb770e72015-03-10 19:33:33 -0700217 auto cmd_name_parts = chromeos::string_utils::SplitAtFirst(pair.first, ".");
Alex Vakulenko45109442014-07-29 11:07:10 -0700218 std::string package_name = cmd_name_parts.first;
219 std::string command_name = cmd_name_parts.second;
220 base::DictionaryValue* package = nullptr;
221 if (!dict->GetDictionaryWithoutPathExpansion(package_name, &package)) {
222 // If this is the first time we encounter this package, create a JSON
223 // object for it.
224 package = new base::DictionaryValue;
225 dict->SetWithoutPathExpansion(package_name, package);
226 }
227 base::DictionaryValue* command_def = new base::DictionaryValue;
Alex Vakulenko5e86fee2015-04-17 08:47:45 -0700228 command_def->Set(commands::attributes::kCommand_Parameters,
Vitaly Bukab6b49e52015-05-01 10:53:06 -0700229 parameters.release());
Alex Vakulenko45109442014-07-29 11:07:10 -0700230 package->SetWithoutPathExpansion(command_name, command_def);
231 }
232 return dict;
233}
234
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700235const CommandDefinition* CommandDictionary::FindCommand(
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700236 const std::string& command_name) const {
237 auto pair = definitions_.find(command_name);
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700238 return (pair != definitions_.end()) ? pair->second.get() : nullptr;
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700239}
240
Alex Vakulenkoe03af6d2015-04-20 11:00:54 -0700241CommandDefinition* CommandDictionary::FindCommand(
242 const std::string& command_name) {
243 auto pair = definitions_.find(command_name);
244 return (pair != definitions_.end()) ? pair->second.get() : nullptr;
245}
246
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700247void CommandDictionary::Clear() {
248 definitions_.clear();
249}
250
251} // namespace buffet