blob: 51ffb3d4319cdd30f31b7593e5873d0a9239afb9 [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 Vakulenko7c36b672014-07-16 14:50:58 -070029 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 Vakulenko5f472062014-08-14 17:54:04 -070039 chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
40 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(
51 error, errors::commands::kDomain,
52 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 }
Alex Vakulenko7c36b672014-07-16 14:50:58 -070057 const base::DictionaryValue* command_value = nullptr;
58 if (!command_iter.value().GetAsDictionary(&command_value)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -070059 chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
60 errors::commands::kTypeMismatch,
61 "Expecting an object for command '%s'",
62 command_name.c_str());
Alex Vakulenko7c36b672014-07-16 14:50:58 -070063 return false;
64 }
65 // Construct the compound command name as "pkg_name.cmd_name".
Alex Vakulenkob8fc1df2014-08-20 15:38:07 -070066 std::string full_command_name = chromeos::string_utils::Join(
67 '.', package_name, command_name);
Alex Vakulenko7c36b672014-07-16 14:50:58 -070068 // Get the "parameters" definition of the command and read it into
69 // an object schema.
70 const base::DictionaryValue* command_schema_def = nullptr;
71 if (!command_value->GetDictionaryWithoutPathExpansion(
72 commands::attributes::kCommand_Parameters, &command_schema_def)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -070073 chromeos::Error::AddToPrintf(
74 error, errors::commands::kDomain,
75 errors::commands::kPropertyMissing,
76 "Command definition '%s' is missing property '%s'",
77 full_command_name.c_str(),
78 commands::attributes::kCommand_Parameters);
Alex Vakulenko7c36b672014-07-16 14:50:58 -070079 return false;
80 }
Alex Vakulenkofd448692014-07-22 07:46:53 -070081
82 const ObjectSchema* base_def = nullptr;
83 if (base_commands) {
84 const CommandDefinition* cmd =
85 base_commands->FindCommand(full_command_name);
86 if (cmd)
87 base_def = cmd->GetParameters().get();
88
89 // If the base command dictionary was provided but the command was not
90 // found in it, this must be a custom (vendor) command. GCD spec states
91 // that all custom command names must begin with "_". Let's enforce
92 // this rule here.
93 if (!base_def) {
94 if (command_name.front() != '_') {
Alex Vakulenko5f472062014-08-14 17:54:04 -070095 chromeos::Error::AddToPrintf(
96 error, errors::commands::kDomain,
97 errors::commands::kInvalidCommandName,
98 "The name of custom command '%s' in package '%s'"
99 " must start with '_'",
100 command_name.c_str(), package_name.c_str());
Alex Vakulenkofd448692014-07-22 07:46:53 -0700101 return false;
102 }
103 }
104 }
105
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700106 auto command_schema = std::make_shared<ObjectSchema>();
Alex Vakulenkofd448692014-07-22 07:46:53 -0700107 if (!command_schema->FromJson(command_schema_def, base_def, error)) {
Alex Vakulenko5f472062014-08-14 17:54:04 -0700108 chromeos::Error::AddToPrintf(error, errors::commands::kDomain,
109 errors::commands::kInvalidObjectSchema,
110 "Invalid definition for command '%s'",
111 full_command_name.c_str());
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700112 return false;
113 }
114 auto command_def = std::make_shared<CommandDefinition>(category,
115 command_schema);
Alex Vakulenkofd448692014-07-22 07:46:53 -0700116 new_defs.insert(std::make_pair(full_command_name, command_def));
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700117
118 command_iter.Advance();
119 }
120 package_iter.Advance();
121 }
122
123 // Verify that newly loaded command definitions do not override existing
124 // definitions in another category. This is unlikely, but we don't want to let
125 // one vendor daemon to define the same commands already handled by another
126 // daemon on the same device.
127 for (const auto& pair : new_defs) {
128 auto iter = definitions_.find(pair.first);
129 if (iter != definitions_.end()) {
Alex Vakulenko5f472062014-08-14 17:54:04 -0700130 chromeos::Error::AddToPrintf(
131 error, errors::commands::kDomain,
132 errors::commands::kDuplicateCommandDef,
133 "Definition for command '%s' overrides an earlier "
134 "definition in category '%s'",
135 pair.first.c_str(), iter->second->GetCategory().c_str());
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700136 return false;
137 }
138 }
139
140 // Now that we successfully loaded all the command definitions,
141 // remove previous definitions of commands from the same category.
142 std::vector<std::string> names = GetCommandNamesByCategory(category);
143 for (const std::string& name : names)
144 definitions_.erase(name);
145
146 // Insert new definitions into the global map.
147 definitions_.insert(new_defs.begin(), new_defs.end());
148 return true;
149}
150
Alex Vakulenko45109442014-07-29 11:07:10 -0700151std::unique_ptr<base::DictionaryValue> CommandDictionary::GetCommandsAsJson(
Alex Vakulenko5f472062014-08-14 17:54:04 -0700152 bool full_schema, chromeos::ErrorPtr* error) const {
Alex Vakulenko45109442014-07-29 11:07:10 -0700153 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
154 for (const auto& pair : definitions_) {
155 std::unique_ptr<base::DictionaryValue> definition =
156 pair.second->GetParameters()->ToJson(full_schema, error);
157 if (!definition) {
158 dict.reset();
159 return dict;
160 }
Alex Vakulenkob8fc1df2014-08-20 15:38:07 -0700161 auto cmd_name_parts = chromeos::string_utils::SplitAtFirst(pair.first, '.');
Alex Vakulenko45109442014-07-29 11:07:10 -0700162 std::string package_name = cmd_name_parts.first;
163 std::string command_name = cmd_name_parts.second;
164 base::DictionaryValue* package = nullptr;
165 if (!dict->GetDictionaryWithoutPathExpansion(package_name, &package)) {
166 // If this is the first time we encounter this package, create a JSON
167 // object for it.
168 package = new base::DictionaryValue;
169 dict->SetWithoutPathExpansion(package_name, package);
170 }
171 base::DictionaryValue* command_def = new base::DictionaryValue;
172 command_def->SetWithoutPathExpansion(
173 commands::attributes::kCommand_Parameters, definition.release());
174 package->SetWithoutPathExpansion(command_name, command_def);
175 }
176 return dict;
177}
178
Alex Vakulenko7c36b672014-07-16 14:50:58 -0700179const CommandDefinition* CommandDictionary::FindCommand(
180 const std::string& command_name) const {
181 auto pair = definitions_.find(command_name);
182 return (pair != definitions_.end()) ? pair->second.get() : nullptr;
183}
184
185void CommandDictionary::Clear() {
186 definitions_.clear();
187}
188
189} // namespace buffet