blob: 68e963c6f702d29bf33155fd299c6e249e585783 [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;
71 const ObjectSchema* base_results_def = nullptr;
Alex Vakulenkofd448692014-07-22 07:46:53 -070072 if (base_commands) {
Anton Muhincfde8692014-11-25 03:36:59 +040073 auto cmd = base_commands->FindCommand(full_command_name);
Anton Muhin71fb9d52014-11-21 22:22:39 +040074 if (cmd) {
Alex Vakulenko5ef75792015-03-19 15:50:44 -070075 base_parameters_def = cmd->GetParameters();
76 base_results_def = cmd->GetResults();
Anton Muhin71fb9d52014-11-21 22:22:39 +040077 }
Alex Vakulenkofd448692014-07-22 07:46:53 -070078
79 // If the base command dictionary was provided but the command was not
80 // found in it, this must be a custom (vendor) command. GCD spec states
81 // that all custom command names must begin with "_". Let's enforce
82 // this rule here.
Anton Muhin71fb9d52014-11-21 22:22:39 +040083 if (!cmd) {
Alex Vakulenkofd448692014-07-22 07:46:53 -070084 if (command_name.front() != '_') {
Alex Vakulenko5f472062014-08-14 17:54:04 -070085 chromeos::Error::AddToPrintf(
Alex Vakulenkoac8037d2014-11-11 11:42:05 -080086 error, FROM_HERE, errors::commands::kDomain,
Alex Vakulenko5f472062014-08-14 17:54:04 -070087 errors::commands::kInvalidCommandName,
88 "The name of custom command '%s' in package '%s'"
89 " must start with '_'",
90 command_name.c_str(), package_name.c_str());
Alex Vakulenkofd448692014-07-22 07:46:53 -070091 return false;
92 }
93 }
94 }
95
Anton Muhin71fb9d52014-11-21 22:22:39 +040096 auto parameters_schema = BuildObjectSchema(
97 command_def_json,
98 commands::attributes::kCommand_Parameters,
99 base_parameters_def,
100 full_command_name,
101 error);
102 if (!parameters_schema)
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700103 return false;
Anton Muhin71fb9d52014-11-21 22:22:39 +0400104
105 auto results_schema = BuildObjectSchema(
106 command_def_json,
107 commands::attributes::kCommand_Results,
108 base_results_def,
109 full_command_name,
110 error);
111 if (!results_schema)
112 return false;
113
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700114 std::unique_ptr<CommandDefinition> command_def{
115 new CommandDefinition{category, std::move(parameters_schema),
116 std::move(results_schema)}
117 };
118 new_defs.emplace(full_command_name, std::move(command_def));
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700119
120 command_iter.Advance();
121 }
122 package_iter.Advance();
123 }
124
125 // Verify that newly loaded command definitions do not override existing
126 // definitions in another category. This is unlikely, but we don't want to let
127 // one vendor daemon to define the same commands already handled by another
128 // daemon on the same device.
129 for (const auto& pair : new_defs) {
130 auto iter = definitions_.find(pair.first);
Christopher Wiley13fca9d2015-01-14 09:56:34 -0800131 CHECK(iter == definitions_.end())
132 << "Definition for command '" << pair.first
133 << "' overrides an earlier definition in category '"
134 << iter->second->GetCategory().c_str() << "'";
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700135 }
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.
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700144 for (auto& pair : new_defs)
145 definitions_.emplace(pair.first, std::move(pair.second));
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700146 return true;
147}
148
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700149std::unique_ptr<ObjectSchema> CommandDictionary::BuildObjectSchema(
Anton Muhin71fb9d52014-11-21 22:22:39 +0400150 const base::DictionaryValue* command_def_json,
151 const char* property_name,
152 const ObjectSchema* base_def,
153 const std::string& command_name,
154 chromeos::ErrorPtr* error) {
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700155 auto object_schema = ObjectSchema::Create();
Anton Muhin71fb9d52014-11-21 22:22:39 +0400156
157 const base::DictionaryValue* schema_def = nullptr;
158 if (!command_def_json->GetDictionaryWithoutPathExpansion(property_name,
159 &schema_def)) {
160 chromeos::Error::AddToPrintf(
161 error, FROM_HERE, errors::commands::kDomain,
162 errors::commands::kPropertyMissing,
163 "Command definition '%s' is missing property '%s'",
164 command_name.c_str(),
165 property_name);
166 return {};
167 }
168
169 if (!object_schema->FromJson(schema_def, base_def, error)) {
170 chromeos::Error::AddToPrintf(error, FROM_HERE,
171 errors::commands::kDomain,
172 errors::commands::kInvalidObjectSchema,
173 "Invalid definition for command '%s'",
174 command_name.c_str());
175 return {};
176 }
177
178 return object_schema;
179}
180
Alex Vakulenko45109442014-07-29 11:07:10 -0700181std::unique_ptr<base::DictionaryValue> CommandDictionary::GetCommandsAsJson(
Alex Vakulenko5f472062014-08-14 17:54:04 -0700182 bool full_schema, chromeos::ErrorPtr* error) const {
Alex Vakulenko45109442014-07-29 11:07:10 -0700183 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
184 for (const auto& pair : definitions_) {
185 std::unique_ptr<base::DictionaryValue> definition =
186 pair.second->GetParameters()->ToJson(full_schema, error);
187 if (!definition) {
188 dict.reset();
189 return dict;
190 }
Vitaly Bukadb770e72015-03-10 19:33:33 -0700191 auto cmd_name_parts = chromeos::string_utils::SplitAtFirst(pair.first, ".");
Alex Vakulenko45109442014-07-29 11:07:10 -0700192 std::string package_name = cmd_name_parts.first;
193 std::string command_name = cmd_name_parts.second;
194 base::DictionaryValue* package = nullptr;
195 if (!dict->GetDictionaryWithoutPathExpansion(package_name, &package)) {
196 // If this is the first time we encounter this package, create a JSON
197 // object for it.
198 package = new base::DictionaryValue;
199 dict->SetWithoutPathExpansion(package_name, package);
200 }
201 base::DictionaryValue* command_def = new base::DictionaryValue;
202 command_def->SetWithoutPathExpansion(
203 commands::attributes::kCommand_Parameters, definition.release());
204 package->SetWithoutPathExpansion(command_name, command_def);
205 }
206 return dict;
207}
208
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700209const CommandDefinition* CommandDictionary::FindCommand(
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700210 const std::string& command_name) const {
211 auto pair = definitions_.find(command_name);
Alex Vakulenko5ef75792015-03-19 15:50:44 -0700212 return (pair != definitions_.end()) ? pair->second.get() : nullptr;
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700213}
214
215void CommandDictionary::Clear() {
216 definitions_.clear();
217}
218
219} // namespace buffet