Resetting weave-release-1.1 to 03cd192
BUG=25936428
TEST=None
Change-Id: Id60979589bc8c2c15fc5e0126dc836c3ed8b5f8b
Reviewed-on: https://weave-review.googlesource.com/1683
Reviewed-by: Ben Henry <benhenry@google.com>
diff --git a/.gitignore b/.gitignore
index 3ee16a1..4011af4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,4 @@
-*.target.mk
*~
/out/
/third_party/include
/third_party/lib
-gomacc.lock
-Makefile
diff --git a/README b/README
index 9b357a0..bf9b70b 100644
--- a/README
+++ b/README
@@ -71,6 +71,7 @@
libtool
gyp
libexpat1-dev
+ ninja-build
For tests:
@@ -89,18 +90,18 @@
---------
Everywhere below Debug can be replaced with Release.
-Generate build files:
+Generate ninja build files:
gyp -I libweave_common.gypi --toplevel-dir=. --depth=. \
- -f make libweave_standalone.gyp
+ -f ninja libweave_standalone.gyp
Build library with tests:
- make
+ ninja -C out/Debug
Build library only:
- make libweave
+ ninja -C out/Debug libweave
Testing
-------
@@ -129,3 +130,7 @@
repo upload .
Go to the url from the output of "repo upload" and add reviewers.
+
+Known Issues
+------------
+* No big-endian support. Pairing fails on big-endian hardware.
diff --git a/examples/build.sh b/examples/build.sh
index 262c56b..e4c412d 100755
--- a/examples/build.sh
+++ b/examples/build.sh
@@ -8,7 +8,7 @@
cd $ROOT_DIR
-gyp -Ilibweave_common.gypi --toplevel-dir=. --depth=. -f make $DIR/daemon/examples.gyp
+gyp -Ilibweave_common.gypi --toplevel-dir=. --depth=. -f ninja $DIR/daemon/examples.gyp
if [ -z "$BUILD_CONFIG" ]; then
export BUILD_CONFIG=Debug
@@ -20,7 +20,7 @@
fi
export CORES=`cat /proc/cpuinfo | grep processor | wc -l`
-BUILDTYPE=$BUILD_CONFIG make -j $CORES $BUILD_TARGET || exit 1
+ninja -j $CORES -C out/${BUILD_CONFIG} $BUILD_TARGET || exit 1
if [[ $BUILD_TARGET == *"libweave_testrunner"* ]]; then
out/${BUILD_CONFIG}/libweave_testrunner --gtest_break_on_failure || exit 1
diff --git a/examples/daemon/ledflasher/ledflasher.cc b/examples/daemon/ledflasher/ledflasher.cc
index 9e4a9e1..38314f5 100644
--- a/examples/daemon/ledflasher/ledflasher.cc
+++ b/examples/daemon/ledflasher/ledflasher.cc
@@ -25,7 +25,7 @@
device_ = device;
device->AddStateDefinitionsFromJson(R"({
- "_ledflasher": {"_leds": {"type": "array", "items": {"type": "boolean"}}}
+ "_ledflasher": {"_leds": {"items": "boolean"}}
})");
device->SetStatePropertiesFromJson(R"({
@@ -35,18 +35,16 @@
device->AddCommandDefinitionsFromJson(R"({
"_ledflasher": {
- "_set":{
- "minimalRole": "user",
- "parameters": {
- "_led": {"type": "integer", "minimum": 1, "maximum": 3},
- "_on": {"type": "boolean"}
- }
- },
- "_toggle":{
- "minimalRole": "user",
- "parameters": {
- "_led": {"type": "integer", "minimum": 1, "maximum": 3}
- }
+ "_set":{
+ "parameters": {
+ "_led": {"minimum": 1, "maximum": 3},
+ "_on": "boolean"
+ }
+ },
+ "_toggle":{
+ "parameters": {
+ "_led": {"minimum": 1, "maximum": 3}
+ }
}
}
})");
@@ -66,10 +64,9 @@
return;
LOG(INFO) << "received command: " << cmd->GetName();
int32_t led_index = 0;
- const auto& params = cmd->GetParameters();
bool cmd_value = false;
- if (params.GetInteger("_led", &led_index) &&
- params.GetBoolean("_on", &cmd_value)) {
+ if (cmd->GetParameters()->GetInteger("_led", &led_index) &&
+ cmd->GetParameters()->GetBoolean("_on", &cmd_value)) {
// Display this command in terminal
LOG(INFO) << cmd->GetName() << " _led: " << led_index
<< ", _on: " << (cmd_value ? "true" : "false");
@@ -96,9 +93,8 @@
if (!cmd)
return;
LOG(INFO) << "received command: " << cmd->GetName();
- const auto& params = cmd->GetParameters();
int32_t led_index = 0;
- if (params.GetInteger("_led", &led_index)) {
+ if (cmd->GetParameters()->GetInteger("_led", &led_index)) {
LOG(INFO) << cmd->GetName() << " _led: " << led_index;
led_index--;
led_status_[led_index] = ~led_status_[led_index];
diff --git a/examples/daemon/light/light.cc b/examples/daemon/light/light.cc
index 90680d2..d54de93 100644
--- a/examples/daemon/light/light.cc
+++ b/examples/daemon/light/light.cc
@@ -18,31 +18,31 @@
device_ = device;
device->AddStateDefinitionsFromJson(R"({
- "onOff": {"state": {"type": "string", "enum": ["on", "standby"]}},
- "brightness": {"brightness": {"type": "integer"}},
+ "onOff": {"state": ["on", "standby"]},
+ "brightness": {"brightness": "integer"},
"colorXY": {
"colorSetting": {
"properties": {
- "colorX": {"type": "number", "minimum": 0.0, "maximum": 1.0},
- "colorY": {"type": "number", "minimum": 0.0, "maximum": 1.0}
+ "colorX": {"minimum": 0.0, "maximum": 1.0},
+ "colorY": {"minimum": 0.0, "maximum": 1.0}
}
},
"colorCapRed": {
"properties": {
- "colorX": {"type": "number", "minimum": 0.0, "maximum": 1.0},
- "colorY": {"type": "number", "minimum": 0.0, "maximum": 1.0}
+ "colorX": {"minimum": 0.0, "maximum": 1.0},
+ "colorY": {"minimum": 0.0, "maximum": 1.0}
}
},
"colorCapGreen": {
"properties": {
- "colorX": {"type": "number", "minimum": 0.0, "maximum": 1.0},
- "colorY": {"type": "number", "minimum": 0.0, "maximum": 1.0}
+ "colorX": {"minimum": 0.0, "maximum": 1.0},
+ "colorY": {"minimum": 0.0, "maximum": 1.0}
}
},
"colorCapBlue": {
"properties": {
- "colorX": {"type": "number", "minimum": 0.0, "maximum": 1.0},
- "colorY": {"type": "number", "minimum": 0.0, "maximum": 1.0}
+ "colorX": {"minimum": 0.0, "maximum": 1.0},
+ "colorY": {"minimum": 0.0, "maximum": 1.0}
}
}
}
@@ -62,23 +62,21 @@
device->AddCommandDefinitionsFromJson(R"({
"onOff": {
- "setConfig":{
- "minimalRole": "user",
- "parameters": {
- "state": {"type": "string", "enum": ["on", "standby"]}
- }
- }
- },
- "brightness": {
- "setConfig":{
- "minimalRole": "user",
- "parameters": {
- "brightness": {
- "type": "integer",
- "minimum": 0,
- "maximum": 100
- }
- }
+ "setConfig":{
+ "parameters": {
+ "state": ["on", "standby"]
+ }
+ }
+ },
+ "brightness": {
+ "setConfig":{
+ "parameters": {
+ "brightness": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 100
+ }
+ }
}
},
"_colorXY": {
@@ -90,16 +88,15 @@
"properties": {
"_colorX": {
"type": "number",
- "minimum": 0.0,
- "maximum": 1.0
+ "minimum": 0,
+ "maximum": 1
},
"_colorY": {
"type": "number",
- "minimum": 0.0,
- "maximum": 1.0
+ "minimum": 0,
+ "maximum": 1
}
- },
- "additionalProperties": false
+ }
}
}
}
@@ -122,9 +119,8 @@
if (!cmd)
return;
LOG(INFO) << "received command: " << cmd->GetName();
- const auto& params = cmd->GetParameters();
int32_t brightness_value = 0;
- if (params.GetInteger("brightness", &brightness_value)) {
+ if (cmd->GetParameters()->GetInteger("brightness", &brightness_value)) {
// Display this command in terminal.
LOG(INFO) << cmd->GetName() << " brightness: " << brightness_value;
@@ -146,9 +142,8 @@
if (!cmd)
return;
LOG(INFO) << "received command: " << cmd->GetName();
- const auto& params = cmd->GetParameters();
std::string requested_state;
- if (params.GetString("state", &requested_state)) {
+ if (cmd->GetParameters()->GetString("state", &requested_state)) {
LOG(INFO) << cmd->GetName() << " state: " << requested_state;
bool new_light_status = requested_state == "on";
@@ -172,9 +167,9 @@
if (!cmd)
return;
LOG(INFO) << "received command: " << cmd->GetName();
- const auto& params = cmd->GetParameters();
- const base::DictionaryValue* colorXY = nullptr;
- if (params.GetDictionary("_colorSetting", &colorXY)) {
+ auto params = cmd->GetParameters();
+ base::DictionaryValue* colorXY = nullptr;
+ if (params->GetDictionary("_colorSetting", &colorXY)) {
bool updateState = false;
double X = 0.0;
double Y = 0.0;
diff --git a/examples/daemon/lock/lock.cc b/examples/daemon/lock/lock.cc
index 10bea00..3014fb1 100644
--- a/examples/daemon/lock/lock.cc
+++ b/examples/daemon/lock/lock.cc
@@ -35,12 +35,8 @@
device->AddStateDefinitionsFromJson(R"({
"lock": {
- "lockedState": {
- "type": "string",
- "enum": ["locked", "unlocked", "partiallyLocked"]
- },
- "isLockingSupported": {"type": "boolean"}
- }
+ "lockedState": ["locked", "unlocked", "partiallyLocked"],
+ "isLockingSupported": "boolean"}
})");
device->SetStatePropertiesFromJson(R"({
@@ -52,14 +48,13 @@
nullptr);
device->AddCommandDefinitionsFromJson(R"({
- "lock": {
- "setConfig":{
- "minimalRole": "user",
- "parameters": {
- "lockedState": {"type": "string", "enum":["locked", "unlocked"]}
+ "lock": {
+ "setConfig":{
+ "parameters": {
+ "lockedState": ["locked", "unlocked"]
+ }
}
}
- }
})");
device->AddCommandHandler("lock.setConfig",
base::Bind(&LockHandler::OnLockSetConfig,
@@ -72,9 +67,8 @@
if (!cmd)
return;
LOG(INFO) << "received command: " << cmd->GetName();
- const auto& params = cmd->GetParameters();
std::string requested_state;
- if (params.GetString("lockedState", &requested_state)) {
+ if (cmd->GetParameters()->GetString("lockedState", &requested_state)) {
LOG(INFO) << cmd->GetName() << " state: " << requested_state;
weave::lockstate::LockState new_lock_status;
diff --git a/examples/daemon/sample/sample.cc b/examples/daemon/sample/sample.cc
index 811e9fb..905a977 100644
--- a/examples/daemon/sample/sample.cc
+++ b/examples/daemon/sample/sample.cc
@@ -27,23 +27,27 @@
"_hello": {
"minimalRole": "user",
"parameters": {
- "_name": {"type": "string"}
- }
+ "_name": "string"
+ },
+ "results": { "_reply": "string" }
},
"_ping": {
- "minimalRole": "user"
+ "minimalRole": "user",
+ "results": {}
},
"_countdown": {
"minimalRole": "user",
"parameters": {
- "_seconds": {"type": "integer", "minimum": 1, "maximum": 25}
- }
+ "_seconds": {"minimum": 1, "maximum": 25}
+ },
+ "progress": { "_seconds_left": "integer"},
+ "results": {}
}
}
})");
device->AddStateDefinitionsFromJson(R"({
- "_sample": {"_ping_count": {"type": "integer"}}
+ "_sample": {"_ping_count":"integer"}
})");
device->SetStatePropertiesFromJson(R"({
@@ -69,9 +73,8 @@
return;
LOG(INFO) << "received command: " << cmd->GetName();
- const auto& params = cmd->GetParameters();
std::string name;
- if (!params.GetString("_name", &name)) {
+ if (!cmd->GetParameters()->GetString("_name", &name)) {
weave::ErrorPtr error;
weave::Error::AddTo(&error, FROM_HERE, "example",
"invalid_parameter_value", "Name is missing");
@@ -94,7 +97,7 @@
base::DictionaryValue state;
state.SetInteger("_sample._ping_count", ++ping_count_);
device_->SetStateProperties(state, nullptr);
- LOG(INFO) << "New state: " << device_->GetState();
+ LOG(INFO) << "New state: " << *device_->GetState();
base::DictionaryValue result;
cmd->Complete(result, nullptr);
@@ -108,9 +111,8 @@
return;
LOG(INFO) << "received command: " << cmd->GetName();
- const auto& params = cmd->GetParameters();
int seconds;
- if (!params.GetInteger("_seconds", &seconds))
+ if (!cmd->GetParameters()->GetInteger("_seconds", &seconds))
seconds = 10;
LOG(INFO) << "starting countdown";
@@ -123,9 +125,8 @@
return;
if (seconds > 0) {
- const auto& params = cmd->GetParameters();
std::string todo;
- params.GetString("_todo", &todo);
+ cmd->GetParameters()->GetString("_todo", &todo);
LOG(INFO) << "countdown tick: " << seconds << " seconds left";
base::DictionaryValue progress;
diff --git a/examples/daemon/speaker/speaker.cc b/examples/daemon/speaker/speaker.cc
index cd7d62f..32591f9 100644
--- a/examples/daemon/speaker/speaker.cc
+++ b/examples/daemon/speaker/speaker.cc
@@ -18,10 +18,10 @@
device_ = device;
device->AddStateDefinitionsFromJson(R"({
- "onOff": {"state": {"type": "string", "enum": ["on", "standby"]}},
+ "onOff": {"state": ["on", "standby"]},
"volume": {
- "volume": {"type": "integer"},
- "isMuted": {"type": "boolean"}
+ "volume": "integer",
+ "isMuted": "boolean"
}
})");
@@ -36,24 +36,22 @@
device->AddCommandDefinitionsFromJson(R"({
"onOff": {
- "setConfig":{
- "minimalRole": "user",
- "parameters": {
- "state": {"type": "string", "enum": ["on", "standby"]}
- }
- }
- },
- "volume": {
- "setConfig":{
- "minimalRole": "user",
- "parameters": {
- "volume": {
- "type": "integer",
- "minimum": 0,
- "maximum": 100
- },
- "isMuted": {"type": "boolean"}
- }
+ "setConfig":{
+ "parameters": {
+ "state": ["on", "standby"]
+ }
+ }
+ },
+ "volume": {
+ "setConfig":{
+ "parameters": {
+ "volume": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 100
+ },
+ "isMuted": "boolean"
+ }
}
}
})");
@@ -72,10 +70,9 @@
return;
LOG(INFO) << "received command: " << cmd->GetName();
- const auto& params = cmd->GetParameters();
// Handle volume parameter
int32_t volume_value = 0;
- if (params.GetInteger("volume", &volume_value)) {
+ if (cmd->GetParameters()->GetInteger("volume", &volume_value)) {
// Display this command in terminal.
LOG(INFO) << cmd->GetName() << " volume: " << volume_value;
@@ -89,7 +86,7 @@
// Handle isMuted parameter
bool isMuted_status = false;
- if (params.GetBoolean("isMuted", &isMuted_status)) {
+ if (cmd->GetParameters()->GetBoolean("isMuted", &isMuted_status)) {
// Display this command in terminal.
LOG(INFO) << cmd->GetName() << " is "
<< (isMuted_status ? "muted" : "not muted");
@@ -111,9 +108,8 @@
if (!cmd)
return;
LOG(INFO) << "received command: " << cmd->GetName();
- const auto& params = cmd->GetParameters();
std::string requested_state;
- if (params.GetString("state", &requested_state)) {
+ if (cmd->GetParameters()->GetString("state", &requested_state)) {
LOG(INFO) << cmd->GetName() << " state: " << requested_state;
bool new_speaker_status = requested_state == "on";
diff --git a/examples/prerequisites.sh b/examples/prerequisites.sh
index 1b27806..7358b71 100755
--- a/examples/prerequisites.sh
+++ b/examples/prerequisites.sh
@@ -20,6 +20,7 @@
libnl-route-3-dev \
libssl-dev \
libtool \
+ ninja-build \
|| exit 1
mkdir -p $ROOT_DIR/third_party/lib $ROOT_DIR/third_party/include 2> /dev/null
diff --git a/examples/provider/avahi_client.h b/examples/provider/avahi_client.h
index 7d9b932..0ca28db 100644
--- a/examples/provider/avahi_client.h
+++ b/examples/provider/avahi_client.h
@@ -34,7 +34,7 @@
std::unique_ptr<AvahiThreadedPoll, decltype(&avahi_threaded_poll_free)>
thread_pool_{nullptr, &avahi_threaded_poll_free};
- std::unique_ptr< ::AvahiClient, decltype(&avahi_client_free)> client_{
+ std::unique_ptr<::AvahiClient, decltype(&avahi_client_free)> client_{
nullptr, &avahi_client_free};
std::unique_ptr<AvahiEntryGroup, decltype(&avahi_entry_group_free)> group_{
diff --git a/examples/provider/event_http_client.cc b/examples/provider/event_http_client.cc
index 1931547..03da97f 100644
--- a/examples/provider/event_http_client.cc
+++ b/examples/provider/event_http_client.cc
@@ -91,7 +91,7 @@
const Headers& headers,
const std::string& data,
const SendRequestCallback& callback) {
- evhttp_cmd_type method_id = EVHTTP_REQ_GET;
+ evhttp_cmd_type method_id;
CHECK(weave::StringToEnum(weave::EnumToString(method), &method_id));
EventPtr<evhttp_uri> http_uri{evhttp_uri_parse(url.c_str())};
CHECK(http_uri);
diff --git a/examples/provider/event_http_server.cc b/examples/provider/event_http_server.cc
index ae8bbec..e0ecea6 100644
--- a/examples/provider/event_http_server.cc
+++ b/examples/provider/event_http_server.cc
@@ -164,14 +164,14 @@
void HttpServerImpl::AddHttpRequestHandler(
const std::string& path,
const RequestHandlerCallback& callback) {
- handlers_.insert(std::make_pair(path, callback));
+ handlers_.emplace(path, callback);
evhttp_set_cb(httpd_.get(), path.c_str(), &ProcessRequestCallback, this);
}
void HttpServerImpl::AddHttpsRequestHandler(
const std::string& path,
const RequestHandlerCallback& callback) {
- handlers_.insert(std::make_pair(path, callback));
+ handlers_.emplace(path, callback);
evhttp_set_cb(httpsd_.get(), path.c_str(), &ProcessRequestCallback, this);
}
diff --git a/examples/provider/event_task_runner.cc b/examples/provider/event_task_runner.cc
index 1d94612..c07e912 100644
--- a/examples/provider/event_task_runner.cc
+++ b/examples/provider/event_task_runner.cc
@@ -34,8 +34,7 @@
flags |= (what & kClosed) ? EV_CLOSED : 0;
event* ioevent = event_new(base_.get(), fd, flags, FdEventHandler, this);
EventPtr<event> ioeventPtr{ioevent};
- fd_task_map_.insert(
- std::make_pair(fd, std::make_pair(std::move(ioeventPtr), task)));
+ fd_task_map_.emplace(fd, std::make_pair(std::move(ioeventPtr), task));
event_add(ioevent, nullptr);
}
diff --git a/include/weave/command.h b/include/weave/command.h
index 0a7d545..59a9305 100644
--- a/include/weave/command.h
+++ b/include/weave/command.h
@@ -40,13 +40,13 @@
virtual Command::Origin GetOrigin() const = 0;
// Returns the command parameters.
- virtual const base::DictionaryValue& GetParameters() const = 0;
+ virtual std::unique_ptr<base::DictionaryValue> GetParameters() const = 0;
// Returns the command progress.
- virtual const base::DictionaryValue& GetProgress() const = 0;
+ virtual std::unique_ptr<base::DictionaryValue> GetProgress() const = 0;
// Returns the command results.
- virtual const base::DictionaryValue& GetResults() const = 0;
+ virtual std::unique_ptr<base::DictionaryValue> GetResults() const = 0;
// Returns the command error.
virtual const Error* GetError() const = 0;
@@ -79,7 +79,7 @@
virtual bool Cancel(ErrorPtr* error) = 0;
protected:
- virtual ~Command() {}
+ virtual ~Command() = default;
};
} // namespace weave
diff --git a/include/weave/device.h b/include/weave/device.h
index 5e86dfd..19012b5 100644
--- a/include/weave/device.h
+++ b/include/weave/device.h
@@ -32,7 +32,7 @@
class Device {
public:
- virtual ~Device() {}
+ virtual ~Device() = default;
// Returns reference the current settings.
virtual const Settings& GetSettings() const = 0;
@@ -96,7 +96,7 @@
// Returns value of the single property.
// |name| is full property name, including package name. e.g. "base.network".
- virtual const base::Value* GetStateProperty(
+ virtual std::unique_ptr<base::Value> GetStateProperty(
const std::string& name) const = 0;
// Sets value of the single property.
@@ -106,7 +106,7 @@
ErrorPtr* error) = 0;
// Returns aggregated state properties across all registered packages.
- virtual const base::DictionaryValue& GetState() const = 0;
+ virtual std::unique_ptr<base::DictionaryValue> GetState() const = 0;
// Returns current state of GCD connection.
virtual GcdState GetGcdState() const = 0;
diff --git a/include/weave/provider/bluetooth.h b/include/weave/provider/bluetooth.h
index 6a47e92..e8f3b3c 100644
--- a/include/weave/provider/bluetooth.h
+++ b/include/weave/provider/bluetooth.h
@@ -14,7 +14,7 @@
// TODO(rginda): Add bluetooth interface methods here.
protected:
- virtual ~Bluetooth() {}
+ virtual ~Bluetooth() = default;
};
} // namespace provider
diff --git a/include/weave/provider/config_store.h b/include/weave/provider/config_store.h
index 1b7988f..53c1128 100644
--- a/include/weave/provider/config_store.h
+++ b/include/weave/provider/config_store.h
@@ -75,7 +75,7 @@
virtual void SaveSettings(const std::string& settings) = 0;
protected:
- virtual ~ConfigStore() {}
+ virtual ~ConfigStore() = default;
};
} // namespace provider
diff --git a/include/weave/provider/dns_service_discovery.h b/include/weave/provider/dns_service_discovery.h
index 37bf84b..fa9d50e 100644
--- a/include/weave/provider/dns_service_discovery.h
+++ b/include/weave/provider/dns_service_discovery.h
@@ -91,7 +91,7 @@
virtual void StopPublishing(const std::string& service_type) = 0;
protected:
- virtual ~DnsServiceDiscovery() {}
+ virtual ~DnsServiceDiscovery() = default;
};
} // namespace provider
diff --git a/include/weave/provider/http_client.h b/include/weave/provider/http_client.h
index bf01022..deb127a 100644
--- a/include/weave/provider/http_client.h
+++ b/include/weave/provider/http_client.h
@@ -78,7 +78,7 @@
virtual std::string GetContentType() const = 0;
virtual std::string GetData() const = 0;
- virtual ~Response() {}
+ virtual ~Response() = default;
};
using Headers = std::vector<std::pair<std::string, std::string>>;
@@ -92,7 +92,7 @@
const SendRequestCallback& callback) = 0;
protected:
- virtual ~HttpClient() {}
+ virtual ~HttpClient() = default;
};
} // namespace provider
diff --git a/include/weave/provider/http_server.h b/include/weave/provider/http_server.h
index 1c28d63..622785b 100644
--- a/include/weave/provider/http_server.h
+++ b/include/weave/provider/http_server.h
@@ -109,7 +109,7 @@
public:
class Request {
public:
- virtual ~Request() {}
+ virtual ~Request() = default;
virtual std::string GetPath() const = 0;
virtual std::string GetFirstHeader(const std::string& name) const = 0;
@@ -141,7 +141,7 @@
virtual base::TimeDelta GetRequestTimeout() const = 0;
protected:
- virtual ~HttpServer() {}
+ virtual ~HttpServer() = default;
};
} // namespace provider
diff --git a/include/weave/provider/network.h b/include/weave/provider/network.h
index 0fb147d..651155a 100644
--- a/include/weave/provider/network.h
+++ b/include/weave/provider/network.h
@@ -47,7 +47,7 @@
const OpenSslSocketCallback& callback) = 0;
protected:
- virtual ~Network() {}
+ virtual ~Network() = default;
};
} // namespace provider
diff --git a/include/weave/provider/task_runner.h b/include/weave/provider/task_runner.h
index 095910b..0804a10 100644
--- a/include/weave/provider/task_runner.h
+++ b/include/weave/provider/task_runner.h
@@ -28,7 +28,7 @@
base::TimeDelta delay) = 0;
protected:
- virtual ~TaskRunner() {}
+ virtual ~TaskRunner() = default;
};
} // namespace provider
diff --git a/include/weave/provider/wifi.h b/include/weave/provider/wifi.h
index 48ac651..111bf3c 100644
--- a/include/weave/provider/wifi.h
+++ b/include/weave/provider/wifi.h
@@ -29,7 +29,7 @@
virtual void StopAccessPoint() = 0;
protected:
- virtual ~Wifi() {}
+ virtual ~Wifi() = default;
};
} // namespace provider
diff --git a/include/weave/stream.h b/include/weave/stream.h
index 14cc7f0..19d38a0 100644
--- a/include/weave/stream.h
+++ b/include/weave/stream.h
@@ -15,7 +15,7 @@
// Interface for async input streaming.
class InputStream {
public:
- virtual ~InputStream() {}
+ virtual ~InputStream() = default;
// Callback type for Read.
using ReadCallback = base::Callback<void(size_t size, ErrorPtr error)>;
@@ -31,7 +31,7 @@
// Interface for async input streaming.
class OutputStream {
public:
- virtual ~OutputStream() {}
+ virtual ~OutputStream() = default;
using WriteCallback = base::Callback<void(ErrorPtr error)>;
@@ -47,7 +47,7 @@
// Interface for async bi-directional streaming.
class Stream : public InputStream, public OutputStream {
public:
- ~Stream() override {}
+ ~Stream() override = default;
// Cancels all pending read or write requests. Canceled operations must not
// call any callbacks.
diff --git a/include/weave/test/mock_command.h b/include/weave/test/mock_command.h
index fe1a02a..2b1080e 100644
--- a/include/weave/test/mock_command.h
+++ b/include/weave/test/mock_command.h
@@ -25,9 +25,9 @@
MOCK_CONST_METHOD0(GetCategory, const std::string&());
MOCK_CONST_METHOD0(GetState, Command::State());
MOCK_CONST_METHOD0(GetOrigin, Command::Origin());
- MOCK_CONST_METHOD0(GetParameters, const base::DictionaryValue&());
- MOCK_CONST_METHOD0(GetProgress, const base::DictionaryValue&());
- MOCK_CONST_METHOD0(GetResults, const base::DictionaryValue&());
+ MOCK_CONST_METHOD0(MockGetParameters, const std::string&());
+ MOCK_CONST_METHOD0(MockGetProgress, const std::string&());
+ MOCK_CONST_METHOD0(MockGetResults, const std::string&());
MOCK_CONST_METHOD0(GetError, const Error*());
MOCK_METHOD2(SetProgress, bool(const base::DictionaryValue&, ErrorPtr*));
MOCK_METHOD2(Complete, bool(const base::DictionaryValue&, ErrorPtr*));
@@ -35,6 +35,10 @@
MOCK_METHOD2(SetError, bool(const Error*, ErrorPtr*));
MOCK_METHOD2(Abort, bool(const Error*, ErrorPtr*));
MOCK_METHOD1(Cancel, bool(ErrorPtr*));
+
+ std::unique_ptr<base::DictionaryValue> GetParameters() const override;
+ std::unique_ptr<base::DictionaryValue> GetProgress() const override;
+ std::unique_ptr<base::DictionaryValue> GetResults() const override;
};
} // namespace test
diff --git a/include/weave/test/mock_device.h b/include/weave/test/mock_device.h
index e5063e7..f751f97 100644
--- a/include/weave/test/mock_device.h
+++ b/include/weave/test/mock_device.h
@@ -34,13 +34,13 @@
MOCK_METHOD2(SetStatePropertiesFromJson, bool(const std::string&, ErrorPtr*));
MOCK_METHOD2(SetStateProperties,
bool(const base::DictionaryValue&, ErrorPtr*));
- MOCK_CONST_METHOD1(GetStateProperty,
- const base::Value*(const std::string& name));
+ MOCK_CONST_METHOD1(MockGetStateProperty,
+ base::Value*(const std::string& name));
MOCK_METHOD3(SetStateProperty,
bool(const std::string& name,
const base::Value& value,
ErrorPtr* error));
- MOCK_CONST_METHOD0(GetState, const base::DictionaryValue&());
+ MOCK_CONST_METHOD0(MockGetState, base::DictionaryValue*());
MOCK_CONST_METHOD0(GetGcdState, GcdState());
MOCK_METHOD1(AddGcdStateChangedCallback,
void(const GcdStateChangedCallback& callback));
@@ -50,6 +50,15 @@
MOCK_METHOD2(AddPairingChangedCallbacks,
void(const PairingBeginCallback& begin_callback,
const PairingEndCallback& end_callback));
+
+ // Gmock 1.7.0 does not work with unuque_ptr as return value.
+ std::unique_ptr<base::Value> GetStateProperty(
+ const std::string& name) const override {
+ return std::unique_ptr<base::Value>(MockGetStateProperty(name));
+ }
+ std::unique_ptr<base::DictionaryValue> GetState() const override {
+ return std::unique_ptr<base::DictionaryValue>(MockGetState());
+ }
};
} // namespace test
diff --git a/libweave.gypi b/libweave.gypi
index a02f082..c32e99b 100644
--- a/libweave.gypi
+++ b/libweave.gypi
@@ -12,7 +12,12 @@
'src/commands/command_instance.cc',
'src/commands/command_manager.cc',
'src/commands/command_queue.cc',
+ 'src/commands/object_schema.cc',
+ 'src/commands/prop_constraints.cc',
+ 'src/commands/prop_types.cc',
+ 'src/commands/prop_values.cc',
'src/commands/schema_constants.cc',
+ 'src/commands/schema_utils.cc',
'src/config.cc',
'src/data_encoding.cc',
'src/device_manager.cc',
@@ -54,6 +59,7 @@
'weave_test_sources': [
'src/test/fake_stream.cc',
'src/test/fake_task_runner.cc',
+ 'src/test/mock_command.cc',
'src/test/unittest_utils.cc',
],
'weave_unittest_sources': [
@@ -65,6 +71,8 @@
'src/commands/command_instance_unittest.cc',
'src/commands/command_manager_unittest.cc',
'src/commands/command_queue_unittest.cc',
+ 'src/commands/object_schema_unittest.cc',
+ 'src/commands/schema_utils_unittest.cc',
'src/config_unittest.cc',
'src/data_encoding_unittest.cc',
'src/device_registration_info_unittest.cc',
diff --git a/libweave_common.gypi b/libweave_common.gypi
index 2e1fa10..49f7ff9 100644
--- a/libweave_common.gypi
+++ b/libweave_common.gypi
@@ -17,7 +17,7 @@
'_DEBUG',
],
'cflags': [
- '-O0 ',
+ '-Og',
'-g3',
],
},
@@ -31,7 +31,6 @@
],
'cflags!': ['-fPIE'],
'cflags': [
- '-fno-exceptions',
'-fPIC',
'-fvisibility=hidden',
'-std=c++11',
@@ -48,10 +47,6 @@
'-Wpointer-arith',
'-Wwrite-strings',
],
- 'libraries': [
- # 'library_dirs' does not work as expected with make files
- '-Lthird_party/lib',
- ],
'library_dirs': ['third_party/lib']
},
}
diff --git a/libweave_standalone.gyp b/libweave_standalone.gyp
index d36d208..fd87f16 100644
--- a/libweave_standalone.gyp
+++ b/libweave_standalone.gyp
@@ -8,11 +8,10 @@
'target_defaults': {
'libraries': [
'-lcrypto',
- '-lexpat',
- '-lgmock',
'-lgtest',
+ '-lgmock',
+ '-lexpat',
'-lpthread',
- '-lrt',
],
},
'targets': [
diff --git a/src/backoff_entry.h b/src/backoff_entry.h
index 002fb8d..2df0d8a 100644
--- a/src/backoff_entry.h
+++ b/src/backoff_entry.h
@@ -57,7 +57,7 @@
// Lifetime of policy must enclose lifetime of BackoffEntry. The
// pointer must be valid but is not dereferenced during construction.
explicit BackoffEntry(const Policy* const policy);
- virtual ~BackoffEntry() {}
+ virtual ~BackoffEntry() = default;
// Inform this item that a request for the network resource it is
// tracking was made, and whether it failed or succeeded.
diff --git a/src/base_api_handler.cc b/src/base_api_handler.cc
index 1423dd1..c3aa616 100644
--- a/src/base_api_handler.cc
+++ b/src/base_api_handler.cc
@@ -42,31 +42,20 @@
"updateBaseConfiguration": {
"minimalRole": "manager",
"parameters": {
- "localAnonymousAccessMaxRole": {
- "enum": [ "none", "viewer", "user" ],
- "type": "string"
- },
- "localDiscoveryEnabled": {
- "type": "boolean"
- },
- "localPairingEnabled": {
- "type": "boolean"
- }
- }
+ "localDiscoveryEnabled": "boolean",
+ "localAnonymousAccessMaxRole": [ "none", "viewer", "user" ],
+ "localPairingEnabled": "boolean"
+ },
+ "results": {}
},
"updateDeviceInfo": {
"minimalRole": "manager",
"parameters": {
- "description": {
- "type": "string"
- },
- "location": {
- "type": "string"
- },
- "name": {
- "type": "string"
- }
- }
+ "description": "string",
+ "name": "string",
+ "location": "string"
+ },
+ "results": {}
}
}
})");
@@ -99,10 +88,10 @@
bool discovery_enabled{settings.local_discovery_enabled};
bool pairing_enabled{settings.local_pairing_enabled};
- const auto& parameters = command->GetParameters();
- parameters.GetString("localAnonymousAccessMaxRole", &anonymous_access_role);
- parameters.GetBoolean("localDiscoveryEnabled", &discovery_enabled);
- parameters.GetBoolean("localPairingEnabled", &pairing_enabled);
+ auto parameters = command->GetParameters();
+ parameters->GetString("localAnonymousAccessMaxRole", &anonymous_access_role);
+ parameters->GetBoolean("localDiscoveryEnabled", &discovery_enabled);
+ parameters->GetBoolean("localPairingEnabled", &pairing_enabled);
AuthScope auth_scope{AuthScope::kNone};
if (!StringToEnum(anonymous_access_role, &auth_scope)) {
@@ -144,10 +133,10 @@
std::string description{settings.description};
std::string location{settings.location};
- const auto& parameters = command->GetParameters();
- parameters.GetString("name", &name);
- parameters.GetString("description", &description);
- parameters.GetString("location", &location);
+ auto parameters = command->GetParameters();
+ parameters->GetString("name", &name);
+ parameters->GetString("description", &description);
+ parameters->GetString("location", &location);
device_info_->UpdateDeviceInfo(name, description, location);
command->Complete({}, nullptr);
diff --git a/src/base_api_handler_unittest.cc b/src/base_api_handler_unittest.cc
index 15a575a..a025e44 100644
--- a/src/base_api_handler_unittest.cc
+++ b/src/base_api_handler_unittest.cc
@@ -10,9 +10,9 @@
#include <weave/provider/test/mock_config_store.h>
#include <weave/provider/test/mock_http_client.h>
#include <weave/test/mock_device.h>
-#include <weave/test/unittest_utils.h>
#include "src/commands/command_manager.h"
+#include "src/commands/unittest_utils.h"
#include "src/config.h"
#include "src/device_registration_info.h"
#include "src/states/mock_state_change_queue_interface.h"
@@ -31,7 +31,7 @@
class BaseApiHandlerTest : public ::testing::Test {
protected:
void SetUp() override {
- EXPECT_CALL(mock_state_change_queue_, MockNotifyPropertiesUpdated(_, _))
+ EXPECT_CALL(mock_state_change_queue_, NotifyPropertiesUpdated(_, _))
.WillRepeatedly(Return(true));
command_manager_ = std::make_shared<CommandManager>();
@@ -84,8 +84,7 @@
}
std::unique_ptr<base::DictionaryValue> GetBaseState() {
- std::unique_ptr<base::DictionaryValue> state{
- state_manager_->GetState().DeepCopy()};
+ auto state = state_manager_->GetState();
std::set<std::string> result;
for (base::DictionaryValue::Iterator it{*state}; !it.IsAtEnd();
it.Advance()) {
@@ -108,38 +107,39 @@
TEST_F(BaseApiHandlerTest, Initialization) {
auto command_defs =
- command_manager_->GetCommandDictionary().GetCommandsAsJson(nullptr);
+ command_manager_->GetCommandDictionary().GetCommandsAsJson(
+ [](const CommandDefinition* def) { return true; }, true, nullptr);
auto expected = R"({
"base": {
"updateBaseConfiguration": {
- "minimalRole": "manager",
- "parameters": {
- "localAnonymousAccessMaxRole": {
- "enum": [ "none", "viewer", "user" ],
- "type": "string"
- },
- "localDiscoveryEnabled": {
- "type": "boolean"
- },
- "localPairingEnabled": {
- "type": "boolean"
- }
- }
+ "minimalRole": "manager",
+ "parameters": {
+ "localAnonymousAccessMaxRole": {
+ "enum": [ "none", "viewer", "user" ],
+ "type": "string"
+ },
+ "localDiscoveryEnabled": {
+ "type": "boolean"
+ },
+ "localPairingEnabled": {
+ "type": "boolean"
+ }
+ }
},
"updateDeviceInfo": {
- "minimalRole": "manager",
- "parameters": {
- "description": {
- "type": "string"
- },
- "location": {
- "type": "string"
- },
- "name": {
- "type": "string"
- }
- }
+ "minimalRole": "manager",
+ "parameters": {
+ "description": {
+ "type": "string"
+ },
+ "location": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ }
}
}
})";
diff --git a/src/commands/cloud_command_proxy.cc b/src/commands/cloud_command_proxy.cc
index 3d472c7..9ec3d3e 100644
--- a/src/commands/cloud_command_proxy.cc
+++ b/src/commands/cloud_command_proxy.cc
@@ -9,6 +9,8 @@
#include <weave/provider/task_runner.h>
#include "src/commands/command_instance.h"
+#include "src/commands/prop_constraints.h"
+#include "src/commands/prop_types.h"
#include "src/commands/schema_constants.h"
#include "src/utils.h"
@@ -43,7 +45,7 @@
void CloudCommandProxy::OnResultsChanged() {
std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
patch->Set(commands::attributes::kCommand_Results,
- command_instance_->GetResults().CreateDeepCopy());
+ command_instance_->GetResults().release());
QueueCommandUpdate(std::move(patch));
}
@@ -57,7 +59,7 @@
void CloudCommandProxy::OnProgressChanged() {
std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
patch->Set(commands::attributes::kCommand_Progress,
- command_instance_->GetProgress().CreateDeepCopy());
+ command_instance_->GetProgress().release());
QueueCommandUpdate(std::move(patch));
}
diff --git a/src/commands/cloud_command_proxy_unittest.cc b/src/commands/cloud_command_proxy_unittest.cc
index a65a967..0c04592 100644
--- a/src/commands/cloud_command_proxy_unittest.cc
+++ b/src/commands/cloud_command_proxy_unittest.cc
@@ -10,10 +10,10 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <weave/provider/test/fake_task_runner.h>
-#include <weave/test/unittest_utils.h>
#include "src/commands/command_dictionary.h"
#include "src/commands/command_instance.h"
+#include "src/commands/unittest_utils.h"
#include "src/states/mock_state_change_queue_interface.h"
using testing::_;
@@ -94,7 +94,7 @@
}
})");
CHECK(json.get());
- CHECK(command_dictionary_.LoadCommands(*json, nullptr))
+ CHECK(command_dictionary_.LoadCommands(*json, nullptr, nullptr))
<< "Failed to parse test command dictionary";
CreateCommandInstance();
diff --git a/src/commands/cloud_command_update_interface.h b/src/commands/cloud_command_update_interface.h
index ed3aa7a..9538960 100644
--- a/src/commands/cloud_command_update_interface.h
+++ b/src/commands/cloud_command_update_interface.h
@@ -21,7 +21,7 @@
const DoneCallback& callback) = 0;
protected:
- virtual ~CloudCommandUpdateInterface() {}
+ virtual ~CloudCommandUpdateInterface() = default;
};
} // namespace weave
diff --git a/src/commands/command_definition.cc b/src/commands/command_definition.cc
index dbae630..d7ebc83 100644
--- a/src/commands/command_definition.cc
+++ b/src/commands/command_definition.cc
@@ -28,31 +28,61 @@
LIBWEAVE_EXPORT EnumToStringMap<UserRole>::EnumToStringMap()
: EnumToStringMap(kMap) {}
-CommandDefinition::CommandDefinition(const base::DictionaryValue& definition,
- UserRole minimal_role)
- : minimal_role_{minimal_role} {
- definition_.MergeDictionary(&definition);
+bool CommandDefinition::Visibility::FromString(const std::string& str,
+ ErrorPtr* error) {
+ // This special case is useful for places where we want to make a command
+ // to ALL clients, even if new clients are added in the future.
+ if (str == commands::attributes::kCommand_Visibility_All) {
+ local = true;
+ cloud = true;
+ return true;
+ }
+
+ // Clear any bits first.
+ local = false;
+ cloud = false;
+ if (str == commands::attributes::kCommand_Visibility_None)
+ return true;
+
+ for (const std::string& value : Split(str, ",", true, true)) {
+ if (value == commands::attributes::kCommand_Visibility_Local) {
+ local = true;
+ } else if (value == commands::attributes::kCommand_Visibility_Cloud) {
+ cloud = true;
+ } else {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidPropValue,
+ "Invalid command visibility value '%s'",
+ value.c_str());
+ return false;
+ }
+ }
+ return true;
}
-std::unique_ptr<CommandDefinition> CommandDefinition::FromJson(
- const base::DictionaryValue& dict, ErrorPtr* error) {
- std::unique_ptr<CommandDefinition> definition;
- // Validate the 'minimalRole' value if present. That's the only thing we
- // care about so far.
- std::string value;
- UserRole minimal_role;
- if (dict.GetString(commands::attributes::kCommand_Role, &value)) {
- if (!StringToEnum(value, &minimal_role)) {
- Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
- errors::commands::kInvalidPropValue,
- "Invalid role: '%s'", value.c_str());
- return definition;
- }
- } else {
- minimal_role = UserRole::kUser;
- }
- definition.reset(new CommandDefinition{dict, minimal_role});
- return definition;
+std::string CommandDefinition::Visibility::ToString() const {
+ if (local && cloud)
+ return commands::attributes::kCommand_Visibility_All;
+ if (!local && !cloud)
+ return commands::attributes::kCommand_Visibility_None;
+ if (local)
+ return commands::attributes::kCommand_Visibility_Local;
+ return commands::attributes::kCommand_Visibility_Cloud;
+}
+
+CommandDefinition::CommandDefinition(
+ std::unique_ptr<const ObjectSchema> parameters,
+ std::unique_ptr<const ObjectSchema> progress,
+ std::unique_ptr<const ObjectSchema> results)
+ : parameters_{std::move(parameters)},
+ progress_{std::move(progress)},
+ results_{std::move(results)} {
+ // Set to be available to all clients by default.
+ visibility_ = Visibility::GetAll();
+}
+
+void CommandDefinition::SetVisibility(const Visibility& visibility) {
+ visibility_ = visibility;
}
} // namespace weave
diff --git a/src/commands/command_definition.h b/src/commands/command_definition.h
index da02bf5..3bcc07f 100644
--- a/src/commands/command_definition.h
+++ b/src/commands/command_definition.h
@@ -9,8 +9,8 @@
#include <string>
#include <base/macros.h>
-#include <base/values.h>
-#include <weave/error.h>
+
+#include "src/commands/object_schema.h"
namespace weave {
@@ -25,19 +25,55 @@
// describing the command parameter types and constraints.
class CommandDefinition final {
public:
- // Factory method to construct a command definition from a JSON dictionary.
- static std::unique_ptr<CommandDefinition> FromJson(
- const base::DictionaryValue& dict, ErrorPtr* error);
- const base::DictionaryValue& ToJson() const { return definition_; }
+ struct Visibility {
+ Visibility() = default;
+ Visibility(bool is_local, bool is_cloud)
+ : local{is_local}, cloud{is_cloud} {}
+
+ // Converts a comma-separated string of visibility identifiers into the
+ // Visibility bitset (|str| is a string like "local,cloud").
+ // Special string value "all" is treated as a list of every possible
+ // visibility values and "none" to have all the bits cleared.
+ bool FromString(const std::string& str, ErrorPtr* error);
+
+ // Converts the visibility bitset to a string.
+ std::string ToString() const;
+
+ static Visibility GetAll() { return Visibility{true, true}; }
+ static Visibility GetLocal() { return Visibility{true, false}; }
+ static Visibility GetCloud() { return Visibility{false, true}; }
+ static Visibility GetNone() { return Visibility{false, false}; }
+
+ bool local{false}; // Command is available to local clients.
+ bool cloud{false}; // Command is available to cloud clients.
+ };
+
+ CommandDefinition(std::unique_ptr<const ObjectSchema> parameters,
+ std::unique_ptr<const ObjectSchema> progress,
+ std::unique_ptr<const ObjectSchema> results);
+
+ // Gets the object schema for command parameters.
+ const ObjectSchema* GetParameters() const { return parameters_.get(); }
+ // Gets the object schema for command progress.
+ const ObjectSchema* GetProgress() const { return progress_.get(); }
+ // Gets the object schema for command results.
+ const ObjectSchema* GetResults() const { return results_.get(); }
+ // Returns the command visibility.
+ const Visibility& GetVisibility() const { return visibility_; }
+ // Changes the command visibility.
+ void SetVisibility(const Visibility& visibility);
// Returns the role required to execute command.
UserRole GetMinimalRole() const { return minimal_role_; }
+ // Changes the role required to execute command.
+ void SetMinimalRole(UserRole minimal_role) { minimal_role_ = minimal_role; }
private:
- CommandDefinition(const base::DictionaryValue& definition,
- UserRole minimal_role);
-
- base::DictionaryValue definition_;
- UserRole minimal_role_;
+ std::unique_ptr<const ObjectSchema> parameters_; // Command parameters def.
+ std::unique_ptr<const ObjectSchema> progress_; // Command progress def.
+ std::unique_ptr<const ObjectSchema> results_; // Command results def.
+ Visibility visibility_; // Available to all by default.
+ // Minimal role required to execute command.
+ UserRole minimal_role_{UserRole::kUser};
DISALLOW_COPY_AND_ASSIGN(CommandDefinition);
};
diff --git a/src/commands/command_definition_unittest.cc b/src/commands/command_definition_unittest.cc
index 867d48f..77b2754 100644
--- a/src/commands/command_definition_unittest.cc
+++ b/src/commands/command_definition_unittest.cc
@@ -5,47 +5,88 @@
#include "src/commands/command_definition.h"
#include <gtest/gtest.h>
-#include <weave/test/unittest_utils.h>
namespace weave {
-using test::CreateDictionaryValue;
-
-TEST(CommandDefinition, DefaultRole) {
- auto params = CreateDictionaryValue(R"({
- 'parameters': {
- 'height': 'integer',
- 'jumpType': ['_withAirFlip', '_withSpin', '_withKick']
- },
- 'progress': {'progress': 'integer'},
- 'results': {'testResult': 'integer'}
- })");
- auto def = CommandDefinition::FromJson(*params, nullptr);
- EXPECT_EQ(UserRole::kUser, def->GetMinimalRole());
+TEST(CommandVisibility, DefaultConstructor) {
+ CommandDefinition::Visibility visibility;
+ EXPECT_FALSE(visibility.local);
+ EXPECT_FALSE(visibility.cloud);
}
-TEST(CommandDefinition, SpecifiedRole) {
- auto params = CreateDictionaryValue(R"({
- 'parameters': {},
- 'progress': {},
- 'results': {},
- 'minimalRole': 'owner'
- })");
- auto def = CommandDefinition::FromJson(*params, nullptr);
- EXPECT_EQ(UserRole::kOwner, def->GetMinimalRole());
+TEST(CommandVisibility, InitialState) {
+ auto visibility = CommandDefinition::Visibility::GetAll();
+ EXPECT_TRUE(visibility.local);
+ EXPECT_TRUE(visibility.cloud);
+
+ visibility = CommandDefinition::Visibility::GetLocal();
+ EXPECT_TRUE(visibility.local);
+ EXPECT_FALSE(visibility.cloud);
+
+ visibility = CommandDefinition::Visibility::GetCloud();
+ EXPECT_FALSE(visibility.local);
+ EXPECT_TRUE(visibility.cloud);
+
+ visibility = CommandDefinition::Visibility::GetNone();
+ EXPECT_FALSE(visibility.local);
+ EXPECT_FALSE(visibility.cloud);
}
-TEST(CommandDefinition, IncorrectRole) {
- auto params = CreateDictionaryValue(R"({
- 'parameters': {},
- 'progress': {},
- 'results': {},
- 'minimalRole': 'foo'
- })");
+TEST(CommandVisibility, FromString) {
+ CommandDefinition::Visibility visibility;
+
+ ASSERT_TRUE(visibility.FromString("local", nullptr));
+ EXPECT_TRUE(visibility.local);
+ EXPECT_FALSE(visibility.cloud);
+
+ ASSERT_TRUE(visibility.FromString("cloud", nullptr));
+ EXPECT_FALSE(visibility.local);
+ EXPECT_TRUE(visibility.cloud);
+
+ ASSERT_TRUE(visibility.FromString("cloud,local", nullptr));
+ EXPECT_TRUE(visibility.local);
+ EXPECT_TRUE(visibility.cloud);
+
+ ASSERT_TRUE(visibility.FromString("none", nullptr));
+ EXPECT_FALSE(visibility.local);
+ EXPECT_FALSE(visibility.cloud);
+
+ ASSERT_TRUE(visibility.FromString("all", nullptr));
+ EXPECT_TRUE(visibility.local);
+ EXPECT_TRUE(visibility.cloud);
+
+ ASSERT_TRUE(visibility.FromString("", nullptr));
+ EXPECT_FALSE(visibility.local);
+ EXPECT_FALSE(visibility.cloud);
+
ErrorPtr error;
- auto def = CommandDefinition::FromJson(*params, &error);
- EXPECT_EQ(nullptr, def.get());
+ ASSERT_FALSE(visibility.FromString("cloud,all", &error));
EXPECT_EQ("invalid_parameter_value", error->GetCode());
}
+TEST(CommandVisibility, ToString) {
+ EXPECT_EQ("none", CommandDefinition::Visibility::GetNone().ToString());
+ EXPECT_EQ("local", CommandDefinition::Visibility::GetLocal().ToString());
+ EXPECT_EQ("cloud", CommandDefinition::Visibility::GetCloud().ToString());
+ EXPECT_EQ("all", CommandDefinition::Visibility::GetAll().ToString());
+}
+
+TEST(CommandDefinition, Test) {
+ std::unique_ptr<const ObjectSchema> params{ObjectSchema::Create()};
+ std::unique_ptr<const ObjectSchema> progress{ObjectSchema::Create()};
+ std::unique_ptr<const ObjectSchema> results{ObjectSchema::Create()};
+ const ObjectSchema* param_ptr = params.get();
+ const ObjectSchema* progress_ptr = progress.get();
+ const ObjectSchema* results_ptr = results.get();
+ CommandDefinition def{std::move(params), std::move(progress),
+ std::move(results)};
+ EXPECT_EQ(param_ptr, def.GetParameters());
+ EXPECT_EQ(progress_ptr, def.GetProgress());
+ EXPECT_EQ(results_ptr, def.GetResults());
+ EXPECT_EQ("all", def.GetVisibility().ToString());
+
+ def.SetVisibility(CommandDefinition::Visibility::GetLocal());
+ EXPECT_EQ("local", def.GetVisibility().ToString());
+}
+
} // namespace weave
diff --git a/src/commands/command_dictionary.cc b/src/commands/command_dictionary.cc
index 516961f..053e7aa 100644
--- a/src/commands/command_dictionary.cc
+++ b/src/commands/command_dictionary.cc
@@ -14,6 +14,7 @@
namespace weave {
bool CommandDictionary::LoadCommands(const base::DictionaryValue& json,
+ const CommandDictionary* base_commands,
ErrorPtr* error) {
CommandMap new_defs;
@@ -53,17 +54,89 @@
// Construct the compound command name as "pkg_name.cmd_name".
std::string full_command_name = Join(".", package_name, command_name);
- auto command_def = CommandDefinition::FromJson(*command_def_json, error);
- if (!command_def) {
- Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
- errors::commands::kInvalidMinimalRole,
- "Error parsing command '%s'",
- full_command_name.c_str());
- return false;
+ const ObjectSchema* base_parameters_def = nullptr;
+ const ObjectSchema* base_progress_def = nullptr;
+ const ObjectSchema* base_results_def = nullptr;
+ // By default make it available to all clients.
+ auto visibility = CommandDefinition::Visibility::GetAll();
+ UserRole minimal_role{UserRole::kUser};
+ if (base_commands) {
+ auto cmd = base_commands->FindCommand(full_command_name);
+ if (cmd) {
+ base_parameters_def = cmd->GetParameters();
+ base_progress_def = cmd->GetProgress();
+ base_results_def = cmd->GetResults();
+ visibility = cmd->GetVisibility();
+ minimal_role = cmd->GetMinimalRole();
+ }
+
+ // 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 (!cmd) {
+ if (command_name.front() != '_') {
+ Error::AddToPrintf(error, FROM_HERE, 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;
+ }
+ }
}
- new_defs.insert(
- std::make_pair(full_command_name, std::move(command_def)));
+ auto parameters_schema = BuildObjectSchema(
+ command_def_json, commands::attributes::kCommand_Parameters,
+ base_parameters_def, full_command_name, error);
+ if (!parameters_schema)
+ return false;
+
+ auto progress_schema = BuildObjectSchema(
+ command_def_json, commands::attributes::kCommand_Progress,
+ base_progress_def, full_command_name, error);
+ if (!progress_schema)
+ return false;
+
+ auto results_schema = BuildObjectSchema(
+ command_def_json, commands::attributes::kCommand_Results,
+ base_results_def, full_command_name, error);
+ if (!results_schema)
+ return false;
+
+ std::string value;
+ if (command_def_json->GetString(commands::attributes::kCommand_Visibility,
+ &value)) {
+ if (!visibility.FromString(value, error)) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidCommandVisibility,
+ "Error parsing command '%s'",
+ full_command_name.c_str());
+ return false;
+ }
+ }
+
+ if (command_def_json->GetString(commands::attributes::kCommand_Role,
+ &value)) {
+ if (!StringToEnum(value, &minimal_role)) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidPropValue,
+ "Invalid role: '%s'", value.c_str());
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidMinimalRole,
+ "Error parsing command '%s'",
+ full_command_name.c_str());
+ return false;
+ }
+ }
+
+ std::unique_ptr<CommandDefinition> command_def{new CommandDefinition{
+ std::move(parameters_schema), std::move(progress_schema),
+ std::move(results_schema)}};
+ command_def->SetVisibility(visibility);
+ command_def->SetMinimalRole(minimal_role);
+ new_defs.emplace(full_command_name, std::move(command_def));
+
command_iter.Advance();
}
package_iter.Advance();
@@ -82,14 +155,53 @@
// Insert new definitions into the global map.
for (auto& pair : new_defs)
- definitions_.insert(std::make_pair(pair.first, std::move(pair.second)));
+ definitions_.emplace(pair.first, std::move(pair.second));
return true;
}
+std::unique_ptr<ObjectSchema> CommandDictionary::BuildObjectSchema(
+ const base::DictionaryValue* command_def_json,
+ const char* property_name,
+ const ObjectSchema* base_def,
+ const std::string& command_name,
+ ErrorPtr* error) {
+ auto object_schema = ObjectSchema::Create();
+
+ const base::DictionaryValue* schema_def = nullptr;
+ if (!command_def_json->GetDictionaryWithoutPathExpansion(property_name,
+ &schema_def)) {
+ if (base_def)
+ return base_def->Clone();
+ return object_schema;
+ }
+
+ if (!object_schema->FromJson(schema_def, base_def, error)) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidObjectSchema,
+ "Invalid definition for command '%s'",
+ command_name.c_str());
+ return {};
+ }
+
+ return object_schema;
+}
+
std::unique_ptr<base::DictionaryValue> CommandDictionary::GetCommandsAsJson(
+ const std::function<bool(const CommandDefinition*)>& filter,
+ bool full_schema,
ErrorPtr* error) const {
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
for (const auto& pair : definitions_) {
+ // Check if the command definition has the desired visibility.
+ // If not, then skip it.
+ if (!filter(pair.second.get()))
+ continue;
+
+ std::unique_ptr<base::DictionaryValue> parameters =
+ pair.second->GetParameters()->ToJson(full_schema, true);
+ CHECK(parameters);
+ // Progress and results are not part of public commandDefs.
+
auto parts = SplitAtFirst(pair.first, ".", true);
const std::string& package_name = parts.first;
const std::string& command_name = parts.second;
@@ -101,8 +213,12 @@
package = new base::DictionaryValue;
dict->SetWithoutPathExpansion(package_name, package);
}
- package->SetWithoutPathExpansion(command_name,
- pair.second->ToJson().DeepCopy());
+ base::DictionaryValue* command_def = new base::DictionaryValue;
+ command_def->Set(commands::attributes::kCommand_Parameters,
+ parameters.release());
+ command_def->SetString(commands::attributes::kCommand_Role,
+ EnumToString(pair.second->GetMinimalRole()));
+ package->SetWithoutPathExpansion(command_name, command_def);
}
return dict;
}
diff --git a/src/commands/command_dictionary.h b/src/commands/command_dictionary.h
index 03e080a..8d3d45c 100644
--- a/src/commands/command_dictionary.h
+++ b/src/commands/command_dictionary.h
@@ -23,6 +23,8 @@
namespace weave {
+class ObjectSchema;
+
// CommandDictionary is a wrapper around a map of command name and the
// corresponding command definition schema. The command name (the key in
// the map) is a compound name in a form of "package_name.command_name",
@@ -35,15 +37,25 @@
// Loads command definitions from a JSON object. This is done at the daemon
// startup and whenever a device daemon decides to update its command list.
// |json| is a JSON dictionary that describes the complete commands. Optional
- // Returns false on failure and |error| provides additional error information
- // when provided.
+ // |base_commands| parameter specifies the definition of standard GCD commands
+ // for parameter schema validation. Can be set to nullptr if no validation is
+ // needed. Returns false on failure and |error| provides additional error
+ // information when provided.
bool LoadCommands(const base::DictionaryValue& json,
+ const CommandDictionary* base_commands,
ErrorPtr* error);
// Converts all the command definitions to a JSON object for CDD/Device
// draft.
+ // |filter| is a predicate used to filter out the command definitions to
+ // be returned by this method. Only command definitions for which the
+ // predicate returns true will be included in the resulting JSON.
+ // |full_schema| specifies whether full command definitions must be generated
+ // (true) for CDD or only overrides from the base schema (false).
// Returns empty unique_ptr in case of an error and fills in the additional
// error details in |error|.
std::unique_ptr<base::DictionaryValue> GetCommandsAsJson(
+ const std::function<bool(const CommandDefinition*)>& filter,
+ bool full_schema,
ErrorPtr* error) const;
// Returns the number of command definitions in the dictionary.
size_t GetSize() const { return definitions_.size(); }
@@ -58,6 +70,13 @@
private:
using CommandMap = std::map<std::string, std::unique_ptr<CommandDefinition>>;
+ std::unique_ptr<ObjectSchema> BuildObjectSchema(
+ const base::DictionaryValue* command_def_json,
+ const char* property_name,
+ const ObjectSchema* base_def,
+ const std::string& command_name,
+ ErrorPtr* error);
+
CommandMap definitions_; // List of all available command definitions.
DISALLOW_COPY_AND_ASSIGN(CommandDictionary);
};
diff --git a/src/commands/command_dictionary_unittest.cc b/src/commands/command_dictionary_unittest.cc
index adae4ec..819718f 100644
--- a/src/commands/command_dictionary_unittest.cc
+++ b/src/commands/command_dictionary_unittest.cc
@@ -5,7 +5,8 @@
#include "src/commands/command_dictionary.h"
#include <gtest/gtest.h>
-#include <weave/test/unittest_utils.h>
+
+#include "src/commands/unittest_utils.h"
namespace weave {
@@ -34,8 +35,8 @@
}
})");
CommandDictionary dict;
- EXPECT_TRUE(dict.LoadCommands(*json, nullptr));
- EXPECT_EQ(1u, dict.GetSize());
+ EXPECT_TRUE(dict.LoadCommands(*json, nullptr, nullptr));
+ EXPECT_EQ(1, dict.GetSize());
EXPECT_NE(nullptr, dict.FindCommand("robot.jump"));
json = CreateDictionaryValue(R"({
'base': {
@@ -46,49 +47,185 @@
}
}
})");
- EXPECT_TRUE(dict.LoadCommands(*json, nullptr));
- EXPECT_EQ(3u, dict.GetSize());
+ EXPECT_TRUE(dict.LoadCommands(*json, nullptr, nullptr));
+ EXPECT_EQ(3, dict.GetSize());
EXPECT_NE(nullptr, dict.FindCommand("robot.jump"));
EXPECT_NE(nullptr, dict.FindCommand("base.reboot"));
EXPECT_NE(nullptr, dict.FindCommand("base.shutdown"));
EXPECT_EQ(nullptr, dict.FindCommand("foo.bar"));
}
+TEST(CommandDictionary, LoadWithInheritance) {
+ auto json = CreateDictionaryValue(R"({
+ 'robot': {
+ 'jump': {
+ 'minimalRole': 'viewer',
+ 'visibility':'local',
+ 'parameters': {
+ 'height': 'integer'
+ },
+ 'progress': {
+ 'progress': 'integer'
+ },
+ 'results': {
+ 'success': 'boolean'
+ }
+ }
+ }
+ })");
+ CommandDictionary base_dict;
+ EXPECT_TRUE(base_dict.LoadCommands(*json, nullptr, nullptr));
+ EXPECT_EQ(1, base_dict.GetSize());
+ json = CreateDictionaryValue(R"({'robot': {'jump': {}}})");
+
+ CommandDictionary dict;
+ EXPECT_TRUE(dict.LoadCommands(*json, &base_dict, nullptr));
+ EXPECT_EQ(1, dict.GetSize());
+
+ auto cmd = dict.FindCommand("robot.jump");
+ EXPECT_NE(nullptr, cmd);
+
+ EXPECT_EQ("local", cmd->GetVisibility().ToString());
+ EXPECT_EQ(UserRole::kViewer, cmd->GetMinimalRole());
+
+ EXPECT_JSON_EQ("{'height': {'type': 'integer'}}",
+ *cmd->GetParameters()->ToJson(true, true));
+ EXPECT_JSON_EQ("{'progress': {'type': 'integer'}}",
+ *cmd->GetProgress()->ToJson(true, false));
+ EXPECT_JSON_EQ("{'success': {'type': 'boolean'}}",
+ *cmd->GetResults()->ToJson(true, false));
+}
+
TEST(CommandDictionary, LoadCommands_Failures) {
CommandDictionary dict;
ErrorPtr error;
// Command definition is not an object.
auto json = CreateDictionaryValue("{'robot':{'jump':0}}");
- EXPECT_FALSE(dict.LoadCommands(*json, &error));
+ EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error));
EXPECT_EQ("type_mismatch", error->GetCode());
error.reset();
// Package definition is not an object.
json = CreateDictionaryValue("{'robot':'blah'}");
- EXPECT_FALSE(dict.LoadCommands(*json, &error));
+ EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error));
EXPECT_EQ("type_mismatch", error->GetCode());
error.reset();
+ // Invalid command definition is not an object.
+ json = CreateDictionaryValue(
+ "{'robot':{'jump':{'parameters':{'flip':0},'results':{}}}}");
+ EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error));
+ EXPECT_EQ("invalid_object_schema", error->GetCode());
+ EXPECT_NE(nullptr, error->GetInnerError()); // Must have additional info.
+ error.reset();
+
// Empty command name.
json = CreateDictionaryValue("{'robot':{'':{'parameters':{},'results':{}}}}");
- EXPECT_FALSE(dict.LoadCommands(*json, &error));
+ EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error));
EXPECT_EQ("invalid_command_name", error->GetCode());
error.reset();
}
-TEST(CommandDictionaryDeathTest, LoadCommands_Redefine) {
- // Redefine commands.
+TEST(CommandDictionaryDeathTest, LoadCommands_RedefineInDifferentCategory) {
+ // Redefine commands in different category.
CommandDictionary dict;
ErrorPtr error;
auto json = CreateDictionaryValue("{'robot':{'jump':{}}}");
- dict.LoadCommands(*json, nullptr);
- ASSERT_DEATH(dict.LoadCommands(*json, &error),
+ dict.LoadCommands(*json, nullptr, &error);
+ ASSERT_DEATH(dict.LoadCommands(*json, nullptr, &error),
".*Definition for command 'robot.jump' overrides an "
"earlier definition");
}
+TEST(CommandDictionary, LoadCommands_CustomCommandNaming) {
+ // Custom command must start with '_'.
+ CommandDictionary base_dict;
+ CommandDictionary dict;
+ ErrorPtr error;
+ auto json = CreateDictionaryValue(R"({
+ 'base': {
+ 'reboot': {
+ 'parameters': {'delay': 'integer'},
+ 'results': {}
+ }
+ }
+ })");
+ base_dict.LoadCommands(*json, nullptr, &error);
+ EXPECT_TRUE(dict.LoadCommands(*json, &base_dict, &error));
+ auto json2 =
+ CreateDictionaryValue("{'base':{'jump':{'parameters':{},'results':{}}}}");
+ EXPECT_FALSE(dict.LoadCommands(*json2, &base_dict, &error));
+ EXPECT_EQ("invalid_command_name", error->GetCode());
+ error.reset();
+
+ // If the command starts with "_", then it's Ok.
+ json2 = CreateDictionaryValue(
+ "{'base':{'_jump':{'parameters':{},'results':{}}}}");
+ EXPECT_TRUE(dict.LoadCommands(*json2, &base_dict, nullptr));
+}
+
+TEST(CommandDictionary, LoadCommands_RedefineStdCommand) {
+ // Redefine commands parameter type.
+ CommandDictionary base_dict;
+ CommandDictionary dict;
+ ErrorPtr error;
+ auto json = CreateDictionaryValue(R"({
+ 'base': {
+ 'reboot': {
+ 'parameters': {'delay': 'integer'},
+ 'results': {'version': 'integer'}
+ }
+ }
+ })");
+ base_dict.LoadCommands(*json, nullptr, &error);
+
+ auto json2 = CreateDictionaryValue(R"({
+ 'base': {
+ 'reboot': {
+ 'parameters': {'delay': 'string'},
+ 'results': {'version': 'integer'}
+ }
+ }
+ })");
+ EXPECT_FALSE(dict.LoadCommands(*json2, &base_dict, &error));
+ EXPECT_EQ("invalid_object_schema", error->GetCode());
+ EXPECT_EQ("invalid_parameter_definition", error->GetInnerError()->GetCode());
+ EXPECT_EQ("param_type_changed", error->GetFirstError()->GetCode());
+ error.reset();
+
+ auto json3 = CreateDictionaryValue(R"({
+ 'base': {
+ 'reboot': {
+ 'parameters': {'delay': 'integer'},
+ 'results': {'version': 'string'}
+ }
+ }
+ })");
+ EXPECT_FALSE(dict.LoadCommands(*json3, &base_dict, &error));
+ EXPECT_EQ("invalid_object_schema", error->GetCode());
+ // TODO(antonm): remove parameter from error below and use some generic.
+ EXPECT_EQ("invalid_parameter_definition", error->GetInnerError()->GetCode());
+ EXPECT_EQ("param_type_changed", error->GetFirstError()->GetCode());
+ error.reset();
+}
+
TEST(CommandDictionary, GetCommandsAsJson) {
+ auto json_base = CreateDictionaryValue(R"({
+ 'base': {
+ 'reboot': {
+ 'parameters': {'delay': {'maximum': 100}},
+ 'results': {}
+ },
+ 'shutdown': {
+ 'parameters': {},
+ 'results': {}
+ }
+ }
+ })");
+ CommandDictionary base_dict;
+ base_dict.LoadCommands(*json_base, nullptr, nullptr);
+
auto json = CreateDictionaryValue(R"({
'base': {
'reboot': {
@@ -99,20 +236,21 @@
'robot': {
'_jump': {
'parameters': {'_height': 'integer'},
- 'minimalRole': 'user'
+ 'results': {}
}
}
})");
CommandDictionary dict;
- dict.LoadCommands(*json, nullptr);
+ dict.LoadCommands(*json, &base_dict, nullptr);
- json = dict.GetCommandsAsJson(nullptr);
+ json = dict.GetCommandsAsJson(
+ [](const CommandDefinition* def) { return true; }, false, nullptr);
ASSERT_NE(nullptr, json.get());
auto expected = R"({
'base': {
'reboot': {
'parameters': {'delay': {'minimum': 10}},
- 'results': {}
+ 'minimalRole': 'user'
}
},
'robot': {
@@ -123,6 +261,143 @@
}
})";
EXPECT_JSON_EQ(expected, *json);
+
+ json = dict.GetCommandsAsJson(
+ [](const CommandDefinition* def) { return true; }, true, nullptr);
+ ASSERT_NE(nullptr, json.get());
+ expected = R"({
+ 'base': {
+ 'reboot': {
+ 'parameters': {
+ 'delay': {
+ 'maximum': 100,
+ 'minimum': 10,
+ 'type': 'integer'
+ }
+ },
+ 'minimalRole': 'user'
+ }
+ },
+ 'robot': {
+ '_jump': {
+ 'parameters': {
+ '_height': {
+ 'type': 'integer'
+ }
+ },
+ 'minimalRole': 'user'
+ }
+ }
+ })";
+ EXPECT_JSON_EQ(expected, *json);
+}
+
+TEST(CommandDictionary, GetCommandsAsJsonWithVisibility) {
+ auto json = CreateDictionaryValue(R"({
+ 'test': {
+ 'command1': {
+ 'parameters': {},
+ 'results': {},
+ 'visibility': 'none'
+ },
+ 'command2': {
+ 'parameters': {},
+ 'results': {},
+ 'visibility': 'local'
+ },
+ 'command3': {
+ 'parameters': {},
+ 'results': {},
+ 'visibility': 'cloud'
+ },
+ 'command4': {
+ 'parameters': {},
+ 'results': {},
+ 'visibility': 'all'
+ },
+ 'command5': {
+ 'parameters': {},
+ 'results': {},
+ 'visibility': 'none'
+ },
+ 'command6': {
+ 'parameters': {},
+ 'results': {},
+ 'visibility': 'local'
+ },
+ 'command7': {
+ 'parameters': {},
+ 'results': {},
+ 'visibility': 'cloud'
+ },
+ 'command8': {
+ 'parameters': {},
+ 'results': {},
+ 'visibility': 'all'
+ }
+ }
+ })");
+ CommandDictionary dict;
+ ASSERT_TRUE(dict.LoadCommands(*json, nullptr, nullptr));
+
+ json = dict.GetCommandsAsJson(
+ [](const CommandDefinition* def) { return true; }, false, nullptr);
+ ASSERT_NE(nullptr, json.get());
+ auto expected = R"({
+ 'test': {
+ 'command1': {'parameters': {}, 'minimalRole': 'user'},
+ 'command2': {'parameters': {}, 'minimalRole': 'user'},
+ 'command3': {'parameters': {}, 'minimalRole': 'user'},
+ 'command4': {'parameters': {}, 'minimalRole': 'user'},
+ 'command5': {'parameters': {}, 'minimalRole': 'user'},
+ 'command6': {'parameters': {}, 'minimalRole': 'user'},
+ 'command7': {'parameters': {}, 'minimalRole': 'user'},
+ 'command8': {'parameters': {}, 'minimalRole': 'user'}
+ }
+ })";
+ EXPECT_JSON_EQ(expected, *json);
+
+ json = dict.GetCommandsAsJson(
+ [](const CommandDefinition* def) { return def->GetVisibility().local; },
+ false, nullptr);
+ ASSERT_NE(nullptr, json.get());
+ expected = R"({
+ 'test': {
+ 'command2': {'parameters': {}, 'minimalRole': 'user'},
+ 'command4': {'parameters': {}, 'minimalRole': 'user'},
+ 'command6': {'parameters': {}, 'minimalRole': 'user'},
+ 'command8': {'parameters': {}, 'minimalRole': 'user'}
+ }
+ })";
+ EXPECT_JSON_EQ(expected, *json);
+
+ json = dict.GetCommandsAsJson(
+ [](const CommandDefinition* def) { return def->GetVisibility().cloud; },
+ false, nullptr);
+ ASSERT_NE(nullptr, json.get());
+ expected = R"({
+ 'test': {
+ 'command3': {'parameters': {}, 'minimalRole': 'user'},
+ 'command4': {'parameters': {}, 'minimalRole': 'user'},
+ 'command7': {'parameters': {}, 'minimalRole': 'user'},
+ 'command8': {'parameters': {}, 'minimalRole': 'user'}
+ }
+ })";
+ EXPECT_JSON_EQ(expected, *json);
+
+ json = dict.GetCommandsAsJson(
+ [](const CommandDefinition* def) {
+ return def->GetVisibility().local && def->GetVisibility().cloud;
+ },
+ false, nullptr);
+ ASSERT_NE(nullptr, json.get());
+ expected = R"({
+ 'test': {
+ 'command4': {'parameters': {}, 'minimalRole': 'user'},
+ 'command8': {'parameters': {}, 'minimalRole': 'user'}
+ }
+ })";
+ EXPECT_JSON_EQ(expected, *json);
}
TEST(CommandDictionary, LoadWithPermissions) {
@@ -131,51 +406,161 @@
'base': {
'command1': {
'parameters': {},
- 'results': {}
+ 'results': {},
+ 'visibility':'none'
},
'command2': {
'minimalRole': 'viewer',
'parameters': {},
- 'results': {}
+ 'results': {},
+ 'visibility':'local'
},
'command3': {
'minimalRole': 'user',
'parameters': {},
- 'results': {}
+ 'results': {},
+ 'visibility':'cloud'
},
'command4': {
'minimalRole': 'manager',
'parameters': {},
- 'results': {}
+ 'results': {},
+ 'visibility':'all'
},
'command5': {
'minimalRole': 'owner',
'parameters': {},
+ 'results': {},
+ 'visibility':'local,cloud'
+ }
+ }
+ })");
+ EXPECT_TRUE(base_dict.LoadCommands(*json, nullptr, nullptr));
+
+ auto cmd = base_dict.FindCommand("base.command1");
+ ASSERT_NE(nullptr, cmd);
+ EXPECT_EQ("none", cmd->GetVisibility().ToString());
+ EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole());
+
+ cmd = base_dict.FindCommand("base.command2");
+ ASSERT_NE(nullptr, cmd);
+ EXPECT_EQ("local", cmd->GetVisibility().ToString());
+ EXPECT_EQ(UserRole::kViewer, cmd->GetMinimalRole());
+
+ cmd = base_dict.FindCommand("base.command3");
+ ASSERT_NE(nullptr, cmd);
+ EXPECT_EQ("cloud", cmd->GetVisibility().ToString());
+ EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole());
+
+ cmd = base_dict.FindCommand("base.command4");
+ ASSERT_NE(nullptr, cmd);
+ EXPECT_EQ("all", cmd->GetVisibility().ToString());
+ EXPECT_EQ(UserRole::kManager, cmd->GetMinimalRole());
+
+ cmd = base_dict.FindCommand("base.command5");
+ ASSERT_NE(nullptr, cmd);
+ EXPECT_EQ("all", cmd->GetVisibility().ToString());
+ EXPECT_EQ(UserRole::kOwner, cmd->GetMinimalRole());
+
+ CommandDictionary dict;
+ json = CreateDictionaryValue(R"({
+ 'base': {
+ 'command1': {
+ 'parameters': {},
+ 'results': {}
+ },
+ 'command2': {
+ 'parameters': {},
+ 'results': {}
+ },
+ 'command3': {
+ 'parameters': {},
+ 'results': {}
+ },
+ 'command4': {
+ 'parameters': {},
+ 'results': {}
+ },
+ 'command5': {
+ 'parameters': {},
+ 'results': {}
+ },
+ '_command6': {
+ 'parameters': {},
'results': {}
}
}
})");
- EXPECT_TRUE(base_dict.LoadCommands(*json, nullptr));
+ EXPECT_TRUE(dict.LoadCommands(*json, &base_dict, nullptr));
- auto cmd = base_dict.FindCommand("base.command1");
+ cmd = dict.FindCommand("base.command1");
ASSERT_NE(nullptr, cmd);
+ EXPECT_EQ("none", cmd->GetVisibility().ToString());
EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole());
- cmd = base_dict.FindCommand("base.command2");
+ cmd = dict.FindCommand("base.command2");
ASSERT_NE(nullptr, cmd);
+ EXPECT_EQ("local", cmd->GetVisibility().ToString());
EXPECT_EQ(UserRole::kViewer, cmd->GetMinimalRole());
- cmd = base_dict.FindCommand("base.command3");
+ cmd = dict.FindCommand("base.command3");
ASSERT_NE(nullptr, cmd);
+ EXPECT_EQ("cloud", cmd->GetVisibility().ToString());
EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole());
- cmd = base_dict.FindCommand("base.command4");
+ cmd = dict.FindCommand("base.command4");
ASSERT_NE(nullptr, cmd);
+ EXPECT_EQ("all", cmd->GetVisibility().ToString());
EXPECT_EQ(UserRole::kManager, cmd->GetMinimalRole());
- cmd = base_dict.FindCommand("base.command5");
+ cmd = dict.FindCommand("base.command5");
ASSERT_NE(nullptr, cmd);
+ EXPECT_EQ("all", cmd->GetVisibility().ToString());
EXPECT_EQ(UserRole::kOwner, cmd->GetMinimalRole());
+
+ cmd = dict.FindCommand("base._command6");
+ ASSERT_NE(nullptr, cmd);
+ EXPECT_EQ("all", cmd->GetVisibility().ToString());
+ EXPECT_EQ(UserRole::kUser, cmd->GetMinimalRole());
+}
+
+TEST(CommandDictionary, LoadWithPermissions_InvalidVisibility) {
+ CommandDictionary dict;
+ ErrorPtr error;
+
+ auto json = CreateDictionaryValue(R"({
+ 'base': {
+ 'jump': {
+ 'parameters': {},
+ 'results': {},
+ 'visibility':'foo'
+ }
+ }
+ })");
+ EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error));
+ EXPECT_EQ("invalid_command_visibility", error->GetCode());
+ EXPECT_EQ("invalid_parameter_value", error->GetInnerError()->GetCode());
+ error.reset();
+}
+
+TEST(CommandDictionary, LoadWithPermissions_InvalidRole) {
+ CommandDictionary dict;
+ ErrorPtr error;
+
+ auto json = CreateDictionaryValue(R"({
+ 'base': {
+ 'jump': {
+ 'parameters': {},
+ 'results': {},
+ 'visibility':'local,cloud',
+ 'minimalRole':'foo'
+ }
+ }
+ })");
+ EXPECT_FALSE(dict.LoadCommands(*json, nullptr, &error));
+ EXPECT_EQ("invalid_minimal_role", error->GetCode());
+ EXPECT_EQ("invalid_parameter_value", error->GetInnerError()->GetCode());
+ error.reset();
}
} // namespace weave
diff --git a/src/commands/command_instance.cc b/src/commands/command_instance.cc
index 8328299..f152d82 100644
--- a/src/commands/command_instance.cc
+++ b/src/commands/command_instance.cc
@@ -12,7 +12,9 @@
#include "src/commands/command_definition.h"
#include "src/commands/command_dictionary.h"
#include "src/commands/command_queue.h"
+#include "src/commands/prop_types.h"
#include "src/commands/schema_constants.h"
+#include "src/commands/schema_utils.h"
#include "src/json_error_codes.h"
#include "src/utils.h"
@@ -66,12 +68,12 @@
CommandInstance::CommandInstance(const std::string& name,
Command::Origin origin,
const CommandDefinition* command_definition,
- const base::DictionaryValue& parameters)
+ const ValueMap& parameters)
: name_{name},
origin_{origin},
- command_definition_{command_definition} {
+ command_definition_{command_definition},
+ parameters_{parameters} {
CHECK(command_definition_);
- parameters_.MergeDictionary(¶meters);
}
CommandInstance::~CommandInstance() {
@@ -94,16 +96,16 @@
return origin_;
}
-const base::DictionaryValue& CommandInstance::GetParameters() const {
- return parameters_;
+std::unique_ptr<base::DictionaryValue> CommandInstance::GetParameters() const {
+ return TypedValueToJson(parameters_);
}
-const base::DictionaryValue& CommandInstance::GetProgress() const {
- return progress_;
+std::unique_ptr<base::DictionaryValue> CommandInstance::GetProgress() const {
+ return TypedValueToJson(progress_);
}
-const base::DictionaryValue& CommandInstance::GetResults() const {
- return results_;
+std::unique_ptr<base::DictionaryValue> CommandInstance::GetResults() const {
+ return TypedValueToJson(results_);
}
const Error* CommandInstance::GetError() const {
@@ -112,13 +114,21 @@
bool CommandInstance::SetProgress(const base::DictionaryValue& progress,
ErrorPtr* error) {
+ if (!command_definition_)
+ return ReportDestroyedError(error);
+ ObjectPropType obj_prop_type;
+ obj_prop_type.SetObjectSchema(command_definition_->GetProgress()->Clone());
+
+ ValueMap obj;
+ if (!TypedValueFromJson(&progress, &obj_prop_type, &obj, error))
+ return false;
+
// Change status even if progress unchanged, e.g. 0% -> 0%.
if (!SetStatus(State::kInProgress, error))
return false;
- if (!progress_.Equals(&progress)) {
- progress_.Clear();
- progress_.MergeDictionary(&progress);
+ if (obj != progress_) {
+ progress_ = obj;
FOR_EACH_OBSERVER(Observer, observers_, OnProgressChanged());
}
@@ -127,9 +137,17 @@
bool CommandInstance::Complete(const base::DictionaryValue& results,
ErrorPtr* error) {
- if (!results_.Equals(&results)) {
- results_.Clear();
- results_.MergeDictionary(&results);
+ if (!command_definition_)
+ return ReportDestroyedError(error);
+ ObjectPropType obj_prop_type;
+ obj_prop_type.SetObjectSchema(command_definition_->GetResults()->Clone());
+
+ ValueMap obj;
+ if (!TypedValueFromJson(&results, &obj_prop_type, &obj, error))
+ return false;
+
+ if (obj != results_) {
+ results_ = obj;
FOR_EACH_OBSERVER(Observer, observers_, OnResultsChanged());
}
// Change status even if result is unchanged.
@@ -153,29 +171,36 @@
// On success, returns |true| and the validated parameters and values through
// |parameters|. Otherwise returns |false| and additional error information in
// |error|.
-std::unique_ptr<base::DictionaryValue> GetCommandParameters(
- const base::DictionaryValue* json,
- const CommandDefinition* command_def,
- ErrorPtr* error) {
+bool GetCommandParameters(const base::DictionaryValue* json,
+ const CommandDefinition* command_def,
+ ValueMap* parameters,
+ ErrorPtr* error) {
// Get the command parameters from 'parameters' property.
- std::unique_ptr<base::DictionaryValue> params;
+ base::DictionaryValue no_params; // Placeholder when no params are specified.
+ const base::DictionaryValue* params = nullptr;
const base::Value* params_value = nullptr;
if (json->Get(commands::attributes::kCommand_Parameters, ¶ms_value)) {
// Make sure the "parameters" property is actually an object.
- const base::DictionaryValue* params_dict = nullptr;
- if (!params_value->GetAsDictionary(¶ms_dict)) {
+ if (!params_value->GetAsDictionary(¶ms)) {
Error::AddToPrintf(error, FROM_HERE, errors::json::kDomain,
errors::json::kObjectExpected,
"Property '%s' must be a JSON object",
commands::attributes::kCommand_Parameters);
- return params;
+ return false;
}
- params.reset(params_dict->DeepCopy());
} else {
// "parameters" are not specified. Assume empty param list.
- params.reset(new base::DictionaryValue);
+ params = &no_params;
}
- return params;
+
+ // Now read in the parameters and validate their values against the command
+ // definition schema.
+ ObjectPropType obj_prop_type;
+ obj_prop_type.SetObjectSchema(command_def->GetParameters()->Clone());
+ if (!TypedValueFromJson(params, &obj_prop_type, parameters, error)) {
+ return false;
+ }
+ return true;
}
} // anonymous namespace
@@ -221,8 +246,8 @@
return instance;
}
- auto parameters = GetCommandParameters(json, command_def, error);
- if (!parameters) {
+ ValueMap parameters;
+ if (!GetCommandParameters(json, command_def, ¶meters, error)) {
Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
errors::commands::kCommandFailed,
"Failed to validate command '%s'", command_name.c_str());
@@ -230,7 +255,7 @@
}
instance.reset(
- new CommandInstance{command_name, origin, command_def, *parameters});
+ new CommandInstance{command_name, origin, command_def, parameters});
if (!command_id->empty())
instance->SetID(*command_id);
@@ -243,9 +268,12 @@
json->SetString(commands::attributes::kCommand_Id, id_);
json->SetString(commands::attributes::kCommand_Name, name_);
- json->Set(commands::attributes::kCommand_Parameters, parameters_.DeepCopy());
- json->Set(commands::attributes::kCommand_Progress, progress_.DeepCopy());
- json->Set(commands::attributes::kCommand_Results, results_.DeepCopy());
+ json->Set(commands::attributes::kCommand_Parameters,
+ TypedValueToJson(parameters_).release());
+ json->Set(commands::attributes::kCommand_Progress,
+ TypedValueToJson(progress_).release());
+ json->Set(commands::attributes::kCommand_Results,
+ TypedValueToJson(results_).release());
json->SetString(commands::attributes::kCommand_State, EnumToString(state_));
if (error_) {
json->Set(commands::attributes::kCommand_Error,
diff --git a/src/commands/command_instance.h b/src/commands/command_instance.h
index 32a93a9..5a2ebf7 100644
--- a/src/commands/command_instance.h
+++ b/src/commands/command_instance.h
@@ -15,6 +15,9 @@
#include <weave/error.h>
#include <weave/command.h>
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_utils.h"
+
namespace base {
class Value;
} // namespace base
@@ -37,16 +40,16 @@
virtual void OnStateChanged() = 0;
protected:
- virtual ~Observer() {}
+ virtual ~Observer() = default;
};
// Construct a command instance given the full command |name| which must
- // be in format "<package_name>.<command_name>" and a list of parameters and
- // their values specified in |parameters|.
+ // be in format "<package_name>.<command_name>", a command |category| and
+ // a list of parameters and their values specified in |parameters|.
CommandInstance(const std::string& name,
Command::Origin origin,
const CommandDefinition* command_definition,
- const base::DictionaryValue& parameters);
+ const ValueMap& parameters);
~CommandInstance() override;
// Command overrides.
@@ -54,9 +57,9 @@
const std::string& GetName() const override;
Command::State GetState() const override;
Command::Origin GetOrigin() const override;
- const base::DictionaryValue& GetParameters() const override;
- const base::DictionaryValue& GetProgress() const override;
- const base::DictionaryValue& GetResults() const override;
+ std::unique_ptr<base::DictionaryValue> GetParameters() const override;
+ std::unique_ptr<base::DictionaryValue> GetProgress() const override;
+ std::unique_ptr<base::DictionaryValue> GetResults() const override;
const Error* GetError() const override;
bool SetProgress(const base::DictionaryValue& progress,
ErrorPtr* error) override;
@@ -121,11 +124,11 @@
// Command definition.
const CommandDefinition* command_definition_{nullptr};
// Command parameters and their values.
- base::DictionaryValue parameters_;
+ ValueMap parameters_;
// Current command execution progress.
- base::DictionaryValue progress_;
+ ValueMap progress_;
// Command results.
- base::DictionaryValue results_;
+ ValueMap results_;
// Current command state.
Command::State state_ = Command::State::kQueued;
// Error encountered during execution of the command.
diff --git a/src/commands/command_instance_unittest.cc b/src/commands/command_instance_unittest.cc
index fb8fe84..4e208ed 100644
--- a/src/commands/command_instance_unittest.cc
+++ b/src/commands/command_instance_unittest.cc
@@ -5,9 +5,11 @@
#include "src/commands/command_instance.h"
#include <gtest/gtest.h>
-#include <weave/test/unittest_utils.h>
#include "src/commands/command_dictionary.h"
+#include "src/commands/prop_types.h"
+#include "src/commands/schema_utils.h"
+#include "src/commands/unittest_utils.h"
namespace weave {
@@ -59,7 +61,7 @@
}
}
})");
- CHECK(dict_.LoadCommands(*json, nullptr))
+ CHECK(dict_.LoadCommands(*json, nullptr, nullptr))
<< "Failed to parse test command dictionary";
}
CommandDictionary dict_;
@@ -68,12 +70,14 @@
} // anonymous namespace
TEST_F(CommandInstanceTest, Test) {
- auto params = CreateDictionaryValue(R"({
- 'phrase': 'iPityDaFool',
- 'volume': 5
- })");
+ StringPropType str_prop;
+ IntPropType int_prop;
+ ValueMap params;
+ params["phrase"] =
+ str_prop.CreateValue(base::StringValue{"iPityDaFool"}, nullptr);
+ params["volume"] = int_prop.CreateValue(base::FundamentalValue{5}, nullptr);
CommandInstance instance{"robot.speak", Command::Origin::kCloud,
- dict_.FindCommand("robot.speak"), *params};
+ dict_.FindCommand("robot.speak"), params};
EXPECT_TRUE(
instance.Complete(*CreateDictionaryValue("{'foo': 239}"), nullptr));
@@ -82,8 +86,8 @@
EXPECT_EQ("robot.speak", instance.GetName());
EXPECT_EQ(Command::Origin::kCloud, instance.GetOrigin());
EXPECT_JSON_EQ("{'phrase': 'iPityDaFool', 'volume': 5}",
- instance.GetParameters());
- EXPECT_JSON_EQ("{'foo': 239}", instance.GetResults());
+ *instance.GetParameters());
+ EXPECT_JSON_EQ("{'foo': 239}", *instance.GetResults());
CommandInstance instance2{"base.reboot",
Command::Origin::kLocal,
@@ -118,7 +122,7 @@
EXPECT_EQ("abcd", instance->GetID());
EXPECT_EQ("robot.jump", instance->GetName());
EXPECT_JSON_EQ("{'height': 53, '_jumpType': '_withKick'}",
- instance->GetParameters());
+ *instance->GetParameters());
}
TEST_F(CommandInstanceTest, FromJson_ParamsOmitted) {
@@ -126,7 +130,7 @@
auto instance = CommandInstance::FromJson(json.get(), Command::Origin::kCloud,
dict_, nullptr, nullptr);
EXPECT_EQ("base.reboot", instance->GetName());
- EXPECT_JSON_EQ("{}", instance->GetParameters());
+ EXPECT_JSON_EQ("{}", *instance->GetParameters());
}
TEST_F(CommandInstanceTest, FromJson_NotObject) {
@@ -170,6 +174,25 @@
EXPECT_EQ("command_failed", error->GetCode());
}
+TEST_F(CommandInstanceTest, FromJson_ParamError) {
+ auto json = CreateDictionaryValue(R"({
+ 'name': 'robot.speak',
+ 'parameters': {
+ 'phrase': 'iPityDaFool',
+ 'volume': 20
+ }
+ })");
+ ErrorPtr error;
+ auto instance = CommandInstance::FromJson(json.get(), Command::Origin::kCloud,
+ dict_, nullptr, &error);
+ EXPECT_EQ(nullptr, instance.get());
+ auto first = error->GetFirstError();
+ EXPECT_EQ("out_of_range", first->GetCode());
+ auto inner = error->GetInnerError();
+ EXPECT_EQ("invalid_parameter_value", inner->GetCode());
+ EXPECT_EQ("command_failed", error->GetCode());
+}
+
TEST_F(CommandInstanceTest, ToJson) {
auto json = CreateDictionaryValue(R"({
'name': 'robot.jump',
diff --git a/src/commands/command_manager.cc b/src/commands/command_manager.cc
index a64c8e5..75d8295 100644
--- a/src/commands/command_manager.cc
+++ b/src/commands/command_manager.cc
@@ -28,7 +28,7 @@
bool CommandManager::LoadCommands(const base::DictionaryValue& dict,
ErrorPtr* error) {
- bool result = dictionary_.LoadCommands(dict, error);
+ bool result = dictionary_.LoadCommands(dict, nullptr, error);
for (const auto& cb : on_command_changed_)
cb.Run();
return result;
@@ -83,6 +83,37 @@
return command_queue_.Find(id);
}
+bool CommandManager::SetCommandVisibility(
+ const std::vector<std::string>& command_names,
+ CommandDefinition::Visibility visibility,
+ ErrorPtr* error) {
+ if (command_names.empty())
+ return true;
+
+ std::vector<CommandDefinition*> definitions;
+ definitions.reserve(command_names.size());
+
+ // Find/validate command definitions first.
+ for (const std::string& name : command_names) {
+ CommandDefinition* def = dictionary_.FindCommand(name);
+ if (!def) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidCommandName,
+ "Command '%s' is unknown", name.c_str());
+ return false;
+ }
+ definitions.push_back(def);
+ }
+
+ // Now that we know that all the command names were valid,
+ // update the respective commands' visibility.
+ for (CommandDefinition* def : definitions)
+ def->SetVisibility(visibility);
+ for (const auto& cb : on_command_changed_)
+ cb.Run();
+ return true;
+}
+
void CommandManager::AddCommandAddedCallback(
const CommandQueue::CommandCallback& callback) {
command_queue_.AddCommandAddedCallback(callback);
diff --git a/src/commands/command_manager.h b/src/commands/command_manager.h
index 04f49ee..7cc8907 100644
--- a/src/commands/command_manager.h
+++ b/src/commands/command_manager.h
@@ -60,6 +60,11 @@
// Adds a new command to the command queue.
void AddCommand(std::unique_ptr<CommandInstance> command_instance);
+ // Changes the visibility of commands.
+ bool SetCommandVisibility(const std::vector<std::string>& command_names,
+ CommandDefinition::Visibility visibility,
+ ErrorPtr* error);
+
bool AddCommand(const base::DictionaryValue& command,
UserRole role,
std::string* id,
diff --git a/src/commands/command_manager_unittest.cc b/src/commands/command_manager_unittest.cc
index f0dc95d..0c890a9 100644
--- a/src/commands/command_manager_unittest.cc
+++ b/src/commands/command_manager_unittest.cc
@@ -9,9 +9,9 @@
#include <base/json/json_writer.h>
#include <gtest/gtest.h>
#include <weave/provider/test/mock_config_store.h>
-#include <weave/test/unittest_utils.h>
#include "src/bind_lambda.h"
+#include "src/commands/unittest_utils.h"
using testing::Return;
@@ -75,7 +75,7 @@
}
})";
EXPECT_TRUE(manager.LoadCommands(json_str, nullptr));
- EXPECT_EQ(2u, manager.GetCommandDictionary().GetSize());
+ EXPECT_EQ(2, manager.GetCommandDictionary().GetSize());
EXPECT_NE(nullptr, manager.GetCommandDictionary().FindCommand("base.reboot"));
EXPECT_NE(nullptr, manager.GetCommandDictionary().FindCommand("robot._jump"));
}
@@ -84,11 +84,66 @@
CommandManager manager;
ASSERT_TRUE(manager.LoadCommands(kTestVendorCommands, nullptr));
ASSERT_TRUE(manager.LoadCommands(kTestTestCommands, nullptr));
- EXPECT_EQ(3u, manager.GetCommandDictionary().GetSize());
+ EXPECT_EQ(3, manager.GetCommandDictionary().GetSize());
EXPECT_NE(nullptr, manager.GetCommandDictionary().FindCommand("robot._jump"));
EXPECT_NE(nullptr,
manager.GetCommandDictionary().FindCommand("robot._speak"));
EXPECT_NE(nullptr, manager.GetCommandDictionary().FindCommand("test._yo"));
}
+TEST(CommandManager, UpdateCommandVisibility) {
+ CommandManager manager;
+ int update_count = 0;
+ auto on_command_change = [&update_count]() { update_count++; };
+ manager.AddCommandDefChanged(base::Bind(on_command_change));
+
+ auto json = CreateDictionaryValue(R"({
+ 'foo': {
+ '_baz': {
+ 'parameters': {},
+ 'results': {}
+ },
+ '_bar': {
+ 'parameters': {},
+ 'results': {}
+ }
+ },
+ 'bar': {
+ '_quux': {
+ 'parameters': {},
+ 'results': {},
+ 'visibility': 'none'
+ }
+ }
+ })");
+ ASSERT_TRUE(manager.LoadCommands(*json, nullptr));
+ EXPECT_EQ(2, update_count);
+ const CommandDictionary& dict = manager.GetCommandDictionary();
+ EXPECT_TRUE(manager.SetCommandVisibility(
+ {"foo._baz"}, CommandDefinition::Visibility::GetLocal(), nullptr));
+ EXPECT_EQ(3, update_count);
+ EXPECT_EQ("local", dict.FindCommand("foo._baz")->GetVisibility().ToString());
+ EXPECT_EQ("all", dict.FindCommand("foo._bar")->GetVisibility().ToString());
+ EXPECT_EQ("none", dict.FindCommand("bar._quux")->GetVisibility().ToString());
+
+ ErrorPtr error;
+ ASSERT_FALSE(manager.SetCommandVisibility(
+ {"foo._baz", "foo._bar", "test.cmd"},
+ CommandDefinition::Visibility::GetLocal(), &error));
+ EXPECT_EQ(errors::commands::kInvalidCommandName, error->GetCode());
+ // The visibility state of commands shouldn't have changed.
+ EXPECT_EQ(3, update_count);
+ EXPECT_EQ("local", dict.FindCommand("foo._baz")->GetVisibility().ToString());
+ EXPECT_EQ("all", dict.FindCommand("foo._bar")->GetVisibility().ToString());
+ EXPECT_EQ("none", dict.FindCommand("bar._quux")->GetVisibility().ToString());
+
+ EXPECT_TRUE(manager.SetCommandVisibility(
+ {"foo._baz", "bar._quux"}, CommandDefinition::Visibility::GetCloud(),
+ nullptr));
+ EXPECT_EQ(4, update_count);
+ EXPECT_EQ("cloud", dict.FindCommand("foo._baz")->GetVisibility().ToString());
+ EXPECT_EQ("all", dict.FindCommand("foo._bar")->GetVisibility().ToString());
+ EXPECT_EQ("cloud", dict.FindCommand("bar._quux")->GetVisibility().ToString());
+}
+
} // namespace weave
diff --git a/src/commands/command_queue.cc b/src/commands/command_queue.cc
index 3d2167f..e08527b 100644
--- a/src/commands/command_queue.cc
+++ b/src/commands/command_queue.cc
@@ -38,8 +38,7 @@
}
}
- CHECK(command_callbacks_.insert(std::make_pair(command_name, callback))
- .second)
+ CHECK(command_callbacks_.emplace(command_name, callback).second)
<< command_name << " already has handler";
} else {
diff --git a/src/commands/command_queue_unittest.cc b/src/commands/command_queue_unittest.cc
index dc7290a..5060071 100644
--- a/src/commands/command_queue_unittest.cc
+++ b/src/commands/command_queue_unittest.cc
@@ -13,21 +13,18 @@
#include <gtest/gtest.h>
#include "src/commands/command_definition.h"
+#include "src/commands/object_schema.h"
#include "src/string_utils.h"
namespace weave {
class CommandQueueTest : public testing::Test {
public:
- CommandQueueTest() {
- command_definition_ = CommandDefinition::FromJson({}, nullptr);
- }
-
std::unique_ptr<CommandInstance> CreateDummyCommandInstance(
const std::string& name,
const std::string& id) {
std::unique_ptr<CommandInstance> cmd{new CommandInstance{
- name, Command::Origin::kLocal, command_definition_.get(), {}}};
+ name, Command::Origin::kLocal, &command_definition_, {}}};
cmd->SetID(id);
return cmd;
}
@@ -42,7 +39,8 @@
CommandQueue queue_;
private:
- std::unique_ptr<CommandDefinition> command_definition_;
+ CommandDefinition command_definition_{
+ ObjectSchema::Create(), ObjectSchema::Create(), ObjectSchema::Create()};
};
// Keeps track of commands being added to and removed from the queue_.
@@ -83,14 +81,14 @@
TEST_F(CommandQueueTest, Empty) {
EXPECT_TRUE(queue_.IsEmpty());
- EXPECT_EQ(0u, queue_.GetCount());
+ EXPECT_EQ(0, queue_.GetCount());
}
TEST_F(CommandQueueTest, Add) {
queue_.Add(CreateDummyCommandInstance("base.reboot", "id1"));
queue_.Add(CreateDummyCommandInstance("base.reboot", "id2"));
queue_.Add(CreateDummyCommandInstance("base.reboot", "id3"));
- EXPECT_EQ(3u, queue_.GetCount());
+ EXPECT_EQ(3, queue_.GetCount());
EXPECT_FALSE(queue_.IsEmpty());
}
@@ -101,31 +99,31 @@
queue_.Add(CreateDummyCommandInstance("base.reboot", id2));
EXPECT_FALSE(queue_.IsEmpty());
EXPECT_FALSE(Remove("dummy"));
- EXPECT_EQ(2u, queue_.GetCount());
+ EXPECT_EQ(2, queue_.GetCount());
EXPECT_TRUE(Remove(id1));
- EXPECT_EQ(1u, queue_.GetCount());
+ EXPECT_EQ(1, queue_.GetCount());
EXPECT_FALSE(Remove(id1));
- EXPECT_EQ(1u, queue_.GetCount());
+ EXPECT_EQ(1, queue_.GetCount());
EXPECT_TRUE(Remove(id2));
- EXPECT_EQ(0u, queue_.GetCount());
+ EXPECT_EQ(0, queue_.GetCount());
EXPECT_FALSE(Remove(id2));
- EXPECT_EQ(0u, queue_.GetCount());
+ EXPECT_EQ(0, queue_.GetCount());
EXPECT_TRUE(queue_.IsEmpty());
}
TEST_F(CommandQueueTest, DelayedRemove) {
const std::string id1 = "id1";
queue_.Add(CreateDummyCommandInstance("base.reboot", id1));
- EXPECT_EQ(1u, queue_.GetCount());
+ EXPECT_EQ(1, queue_.GetCount());
queue_.DelayedRemove(id1);
- EXPECT_EQ(1u, queue_.GetCount());
+ EXPECT_EQ(1, queue_.GetCount());
Cleanup(base::TimeDelta::FromMinutes(1));
- EXPECT_EQ(1u, queue_.GetCount());
+ EXPECT_EQ(1, queue_.GetCount());
Cleanup(base::TimeDelta::FromMinutes(15));
- EXPECT_EQ(0u, queue_.GetCount());
+ EXPECT_EQ(0, queue_.GetCount());
}
TEST_F(CommandQueueTest, Dispatch) {
diff --git a/src/commands/object_schema.cc b/src/commands/object_schema.cc
new file mode 100644
index 0000000..b70beff
--- /dev/null
+++ b/src/commands/object_schema.cc
@@ -0,0 +1,378 @@
+// Copyright 2015 The Weave 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 "src/commands/object_schema.h"
+
+#include <algorithm>
+#include <limits>
+
+#include <base/logging.h>
+#include <base/values.h>
+#include <weave/enum_to_string.h>
+
+#include "src/commands/prop_types.h"
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_constants.h"
+#include "src/string_utils.h"
+
+namespace weave {
+
+namespace {
+
+// Helper function for to create a PropType based on type string.
+// Generates an error if the string identifies an unknown type.
+std::unique_ptr<PropType> CreatePropType(const std::string& type_name,
+ ErrorPtr* error) {
+ auto parts = SplitAtFirst(type_name, ".", false);
+ const std::string& primary_type = parts.first;
+ const std::string& array_type = parts.second;
+
+ std::unique_ptr<PropType> prop;
+ ValueType type;
+ if (PropType::GetTypeFromTypeString(primary_type, &type)) {
+ prop = PropType::Create(type);
+ if (prop && type == ValueType::Array && !array_type.empty()) {
+ auto items_type = CreatePropType(array_type, error);
+ if (items_type) {
+ prop->GetArray()->SetItemType(std::move(items_type));
+ } else {
+ prop.reset();
+ return prop;
+ }
+ }
+ }
+ if (!prop) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kUnknownType, "Unknown type %s",
+ type_name.c_str());
+ }
+ return prop;
+}
+
+// Generates "no_type_info" error.
+void ErrorInvalidTypeInfo(ErrorPtr* error) {
+ Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kNoTypeInfo,
+ "Unable to determine parameter type");
+}
+
+// Helper function for PropFromJson to handle the case of parameter being
+// defined as a JSON string like this:
+// "prop":"..."
+std::unique_ptr<PropType> PropFromJsonString(const base::Value& value,
+ const PropType* base_schema,
+ ErrorPtr* error) {
+ std::string type_name;
+ CHECK(value.GetAsString(&type_name)) << "Unable to get string value";
+ std::unique_ptr<PropType> prop = CreatePropType(type_name, error);
+ base::DictionaryValue empty;
+ if (prop && !prop->FromJson(&empty, base_schema, error))
+ prop.reset();
+
+ return prop;
+}
+
+// Detects a type based on JSON array. Inspects the first element of the array
+// to deduce the PropType from. Returns the string name of the type detected
+// or empty string is type detection failed.
+std::string DetectArrayType(const base::ListValue* list,
+ const PropType* base_schema,
+ bool allow_arrays) {
+ std::string type_name;
+ if (base_schema) {
+ type_name = base_schema->GetTypeAsString();
+ } else if (list->GetSize() > 0) {
+ const base::Value* first_element = nullptr;
+ if (list->Get(0, &first_element)) {
+ switch (first_element->GetType()) {
+ case base::Value::TYPE_BOOLEAN:
+ type_name = PropType::GetTypeStringFromType(ValueType::Boolean);
+ break;
+ case base::Value::TYPE_INTEGER:
+ type_name = PropType::GetTypeStringFromType(ValueType::Int);
+ break;
+ case base::Value::TYPE_DOUBLE:
+ type_name = PropType::GetTypeStringFromType(ValueType::Double);
+ break;
+ case base::Value::TYPE_STRING:
+ type_name = PropType::GetTypeStringFromType(ValueType::String);
+ break;
+ case base::Value::TYPE_DICTIONARY:
+ type_name = PropType::GetTypeStringFromType(ValueType::Object);
+ break;
+ case base::Value::TYPE_LIST: {
+ if (allow_arrays) {
+ type_name = PropType::GetTypeStringFromType(ValueType::Array);
+ const base::ListValue* first_element_list = nullptr;
+ if (first_element->GetAsList(&first_element_list)) {
+ // We do not allow arrays of arrays.
+ auto child_type =
+ DetectArrayType(first_element_list, nullptr, false);
+ if (child_type.empty()) {
+ type_name.clear();
+ } else {
+ type_name += '.' + child_type;
+ }
+ }
+ }
+ break;
+ }
+ default:
+ // The rest are unsupported.
+ break;
+ }
+ }
+ }
+ return type_name;
+}
+
+// Helper function for PropFromJson to handle the case of parameter being
+// defined as a JSON array like this:
+// "prop":[...]
+std::unique_ptr<PropType> PropFromJsonArray(const base::Value& value,
+ const PropType* base_schema,
+ ErrorPtr* error) {
+ std::unique_ptr<PropType> prop;
+ const base::ListValue* list = nullptr;
+ CHECK(value.GetAsList(&list)) << "Unable to get array value";
+ std::string type_name = DetectArrayType(list, base_schema, true);
+ if (type_name.empty()) {
+ ErrorInvalidTypeInfo(error);
+ return prop;
+ }
+ base::DictionaryValue array_object;
+ array_object.SetWithoutPathExpansion(commands::attributes::kOneOf_Enum,
+ list->DeepCopy());
+ prop = CreatePropType(type_name, error);
+ if (prop && !prop->FromJson(&array_object, base_schema, error))
+ prop.reset();
+
+ return prop;
+}
+
+// Detects a type based on JSON object definition of type. Looks at various
+// members such as minimum/maximum constraints, default and enum values to
+// try to deduce the underlying type of the element. Returns the string name of
+// the type detected or empty string is type detection failed.
+std::string DetectObjectType(const base::DictionaryValue* dict,
+ const PropType* base_schema) {
+ bool has_min_max = dict->HasKey(commands::attributes::kNumeric_Min) ||
+ dict->HasKey(commands::attributes::kNumeric_Max);
+
+ // Here we are trying to "detect the type and read in the object based on
+ // the deduced type". Later, we'll verify that this detected type matches
+ // the expectation of the base schema, if applicable, to make sure we are not
+ // changing the expected type. This makes the vendor-side (re)definition of
+ // standard and custom commands behave exactly the same.
+ // The only problem with this approach was the double-vs-int types.
+ // If the type is meant to be a double we want to allow its definition as
+ // "min:0, max:0" instead of just forcing it to be only "min:0.0, max:0.0".
+ // If we have "minimum" or "maximum", and we have a Double schema object,
+ // treat this object as a Double (even if both min and max are integers).
+ if (has_min_max && base_schema && base_schema->GetType() == ValueType::Double)
+ return PropType::GetTypeStringFromType(ValueType::Double);
+
+ // If we have at least one "minimum" or "maximum" that is Double,
+ // it's a Double.
+ const base::Value* value = nullptr;
+ if (dict->Get(commands::attributes::kNumeric_Min, &value) &&
+ value->IsType(base::Value::TYPE_DOUBLE))
+ return PropType::GetTypeStringFromType(ValueType::Double);
+ if (dict->Get(commands::attributes::kNumeric_Max, &value) &&
+ value->IsType(base::Value::TYPE_DOUBLE))
+ return PropType::GetTypeStringFromType(ValueType::Double);
+
+ // If we have "minimum" or "maximum", it's an Integer.
+ if (has_min_max)
+ return PropType::GetTypeStringFromType(ValueType::Int);
+
+ // If we have "minLength" or "maxLength", it's a String.
+ if (dict->HasKey(commands::attributes::kString_MinLength) ||
+ dict->HasKey(commands::attributes::kString_MaxLength))
+ return PropType::GetTypeStringFromType(ValueType::String);
+
+ // If we have "properties", it's an object.
+ if (dict->HasKey(commands::attributes::kObject_Properties))
+ return PropType::GetTypeStringFromType(ValueType::Object);
+
+ // If we have "items", it's an array.
+ if (dict->HasKey(commands::attributes::kItems))
+ return PropType::GetTypeStringFromType(ValueType::Array);
+
+ // If we have "enum", it's an array. Detect type from array elements.
+ const base::ListValue* list = nullptr;
+ if (dict->GetListWithoutPathExpansion(commands::attributes::kOneOf_Enum,
+ &list))
+ return DetectArrayType(list, base_schema, true);
+
+ // If we have "default", try to use it for type detection.
+ if (dict->Get(commands::attributes::kDefault, &value)) {
+ if (value->IsType(base::Value::TYPE_DOUBLE))
+ return PropType::GetTypeStringFromType(ValueType::Double);
+ if (value->IsType(base::Value::TYPE_INTEGER))
+ return PropType::GetTypeStringFromType(ValueType::Int);
+ if (value->IsType(base::Value::TYPE_BOOLEAN))
+ return PropType::GetTypeStringFromType(ValueType::Boolean);
+ if (value->IsType(base::Value::TYPE_STRING))
+ return PropType::GetTypeStringFromType(ValueType::String);
+ if (value->IsType(base::Value::TYPE_LIST)) {
+ CHECK(value->GetAsList(&list)) << "List value expected";
+ std::string child_type = DetectArrayType(list, base_schema, false);
+ if (!child_type.empty()) {
+ return PropType::GetTypeStringFromType(ValueType::Array) + '.' +
+ child_type;
+ }
+ }
+ }
+
+ return std::string{};
+}
+
+// Helper function for PropFromJson to handle the case of parameter being
+// defined as a JSON object like this:
+// "prop":{...}
+std::unique_ptr<PropType> PropFromJsonObject(const base::Value& value,
+ const PropType* base_schema,
+ ErrorPtr* error) {
+ std::unique_ptr<PropType> prop;
+ const base::DictionaryValue* dict = nullptr;
+ CHECK(value.GetAsDictionary(&dict)) << "Unable to get dictionary value";
+ std::string type_name;
+ if (dict->HasKey(commands::attributes::kType)) {
+ if (!dict->GetString(commands::attributes::kType, &type_name)) {
+ ErrorInvalidTypeInfo(error);
+ return prop;
+ }
+ } else {
+ type_name = DetectObjectType(dict, base_schema);
+ }
+ if (type_name.empty()) {
+ if (!base_schema) {
+ ErrorInvalidTypeInfo(error);
+ return prop;
+ }
+ type_name = base_schema->GetTypeAsString();
+ }
+ prop = CreatePropType(type_name, error);
+ if (prop && !prop->FromJson(dict, base_schema, error))
+ prop.reset();
+
+ return prop;
+}
+
+const EnumToStringMap<base::Value::Type>::Map kMap[] = {
+ {base::Value::TYPE_NULL, "Null"},
+ {base::Value::TYPE_BOOLEAN, "Boolean"},
+ {base::Value::TYPE_INTEGER, "Integer"},
+ {base::Value::TYPE_DOUBLE, "Double"},
+ {base::Value::TYPE_STRING, "String"},
+ {base::Value::TYPE_BINARY, "Binary"},
+ {base::Value::TYPE_DICTIONARY, "Object"},
+ {base::Value::TYPE_LIST, "Array"},
+};
+
+} // anonymous namespace
+
+template <>
+EnumToStringMap<base::Value::Type>::EnumToStringMap() : EnumToStringMap(kMap) {}
+
+ObjectSchema::ObjectSchema() {}
+ObjectSchema::~ObjectSchema() {}
+
+std::unique_ptr<ObjectSchema> ObjectSchema::Clone() const {
+ std::unique_ptr<ObjectSchema> cloned{new ObjectSchema};
+ for (const auto& pair : properties_) {
+ cloned->properties_.emplace(pair.first, pair.second->Clone());
+ }
+ cloned->extra_properties_allowed_ = extra_properties_allowed_;
+ return cloned;
+}
+
+void ObjectSchema::AddProp(const std::string& name,
+ std::unique_ptr<PropType> prop) {
+ // Not using emplace() here to make sure we override existing properties.
+ properties_[name] = std::move(prop);
+}
+
+const PropType* ObjectSchema::GetProp(const std::string& name) const {
+ auto p = properties_.find(name);
+ return p != properties_.end() ? p->second.get() : nullptr;
+}
+
+bool ObjectSchema::MarkPropRequired(const std::string& name, ErrorPtr* error) {
+ auto p = properties_.find(name);
+ if (p == properties_.end()) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kUnknownProperty,
+ "Unknown property '%s'", name.c_str());
+ return false;
+ }
+ p->second->MakeRequired(true);
+ return true;
+}
+
+std::unique_ptr<base::DictionaryValue> ObjectSchema::ToJson(
+ bool full_schema,
+ bool in_command_def) const {
+ std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
+ for (const auto& pair : properties_) {
+ auto prop_def = pair.second->ToJson(full_schema, in_command_def);
+ CHECK(prop_def);
+ value->SetWithoutPathExpansion(pair.first, prop_def.release());
+ }
+ return value;
+}
+
+bool ObjectSchema::FromJson(const base::DictionaryValue* value,
+ const ObjectSchema* object_schema,
+ ErrorPtr* error) {
+ Properties properties;
+ base::DictionaryValue::Iterator iter(*value);
+ while (!iter.IsAtEnd()) {
+ std::string name = iter.key();
+ const PropType* base_schema =
+ object_schema ? object_schema->GetProp(iter.key()) : nullptr;
+ auto prop_type = PropFromJson(iter.value(), base_schema, error);
+ if (prop_type) {
+ properties.emplace(iter.key(), std::move(prop_type));
+ } else {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidPropDef,
+ "Error in definition of property '%s'",
+ iter.key().c_str());
+ return false;
+ }
+ iter.Advance();
+ }
+ properties_ = std::move(properties);
+ return true;
+}
+
+std::unique_ptr<PropType> ObjectSchema::PropFromJson(
+ const base::Value& value,
+ const PropType* base_schema,
+ ErrorPtr* error) {
+ if (value.IsType(base::Value::TYPE_STRING)) {
+ // A string value is a short-hand object specification and provides
+ // the parameter type.
+ return PropFromJsonString(value, base_schema, error);
+ } else if (value.IsType(base::Value::TYPE_LIST)) {
+ // One of the enumerated types.
+ return PropFromJsonArray(value, base_schema, error);
+ } else if (value.IsType(base::Value::TYPE_DICTIONARY)) {
+ // Full parameter definition.
+ return PropFromJsonObject(value, base_schema, error);
+ }
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kUnknownType,
+ "Unexpected JSON value type: %s",
+ EnumToString(value.GetType()).c_str());
+ return nullptr;
+}
+
+std::unique_ptr<ObjectSchema> ObjectSchema::Create() {
+ return std::unique_ptr<ObjectSchema>{new ObjectSchema};
+}
+
+} // namespace weave
diff --git a/src/commands/object_schema.h b/src/commands/object_schema.h
new file mode 100644
index 0000000..504e1dd
--- /dev/null
+++ b/src/commands/object_schema.h
@@ -0,0 +1,95 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_SRC_COMMANDS_OBJECT_SCHEMA_H_
+#define LIBWEAVE_SRC_COMMANDS_OBJECT_SCHEMA_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <weave/error.h>
+
+namespace base {
+class Value;
+class DictionaryValue;
+} // namespace base
+
+namespace weave {
+
+class PropType;
+
+// ObjectSchema is a class representing an object definition in GCD command
+// schema. This could represent a GCD command definition, but also it can be
+// used when defining custom object types for command properties such as
+// output media type (paper) for print command. The schema definition for
+// these type of object description is the same.
+class ObjectSchema final {
+ public:
+ // Do not inline the constructor/destructor to allow forward-declared type
+ // PropType to be part of |properties_| member.
+ ObjectSchema();
+ ~ObjectSchema();
+
+ // Properties is a string-to-PropType map representing a list of
+ // properties defined for a command/object. The key is the parameter
+ // name and the value is the parameter type definition object.
+ using Properties = std::map<std::string, std::unique_ptr<PropType>>;
+
+ // Makes a full copy of this object.
+ virtual std::unique_ptr<ObjectSchema> Clone() const;
+
+ // Add a new parameter definition.
+ void AddProp(const std::string& name, std::unique_ptr<PropType> prop);
+
+ // Finds parameter type definition by name. Returns nullptr if not found.
+ const PropType* GetProp(const std::string& name) const;
+
+ // Gets the list of all the properties defined.
+ const Properties& GetProps() const { return properties_; }
+
+ // Marks the property with given name as "required". If |name| specifies
+ // an unknown property, false is returned and |error| is set with detailed
+ // error message for the failure.
+ bool MarkPropRequired(const std::string& name, ErrorPtr* error);
+
+ // Specify whether extra properties are allowed on objects described by
+ // this schema. When validating a value of an object type, we can
+ // make sure that the value has only the properties explicitly defined by
+ // the schema and no other (custom) properties are allowed.
+ // This is to support JSON Schema's "additionalProperties" specification.
+ bool GetExtraPropertiesAllowed() const { return extra_properties_allowed_; }
+ void SetExtraPropertiesAllowed(bool allowed) {
+ extra_properties_allowed_ = allowed;
+ }
+
+ // Saves the object schema to JSON. When |full_schema| is set to true,
+ // then all properties and constraints are saved, otherwise, only
+ // the overridden (not inherited) ones are saved.
+ std::unique_ptr<base::DictionaryValue> ToJson(bool full_schema,
+ bool in_command_def) const;
+
+ // Loads the object schema from JSON. If |object_schema| is not nullptr, it is
+ // used as a base schema to inherit omitted properties and constraints from.
+ bool FromJson(const base::DictionaryValue* value,
+ const ObjectSchema* object_schema,
+ ErrorPtr* error);
+
+ // Helper factory method to create a new instance of ObjectSchema object.
+ static std::unique_ptr<ObjectSchema> Create();
+
+ // Helper method to load property type definitions from JSON.
+ static std::unique_ptr<PropType> PropFromJson(const base::Value& value,
+ const PropType* base_schema,
+ ErrorPtr* error);
+
+ private:
+ // Internal parameter type definition map.
+ Properties properties_;
+ bool extra_properties_allowed_{false};
+};
+
+} // namespace weave
+
+#endif // LIBWEAVE_SRC_COMMANDS_OBJECT_SCHEMA_H_
diff --git a/src/commands/object_schema_unittest.cc b/src/commands/object_schema_unittest.cc
new file mode 100644
index 0000000..6417952
--- /dev/null
+++ b/src/commands/object_schema_unittest.cc
@@ -0,0 +1,1697 @@
+// Copyright 2015 The Weave 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 "src/commands/object_schema.h"
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <vector>
+
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/values.h>
+#include <gtest/gtest.h>
+
+#include "src/commands/prop_constraints.h"
+#include "src/commands/prop_types.h"
+#include "src/commands/schema_constants.h"
+#include "src/commands/unittest_utils.h"
+
+namespace weave {
+
+using test::CreateValue;
+using test::CreateDictionaryValue;
+
+namespace {
+
+template <typename T>
+std::vector<T> GetArrayValues(const ValueVector& arr) {
+ std::vector<T> values;
+ values.reserve(arr.size());
+ for (const auto& prop_value : arr) {
+ const auto& value = static_cast<const TypedValueBase<T>&>(*prop_value);
+ values.push_back(value.GetValue());
+ }
+ return values;
+}
+
+template <typename T>
+std::vector<T> GetOneOfValues(const PropType* prop_type) {
+ auto one_of = static_cast<const ConstraintOneOf*>(
+ prop_type->GetConstraint(ConstraintType::OneOf));
+ if (!one_of)
+ return {};
+
+ return GetArrayValues<T>(one_of->set_.value);
+}
+
+bool ValidateValue(const PropType& type,
+ const base::Value& value,
+ ErrorPtr* error) {
+ std::unique_ptr<PropValue> val = type.CreatePropValue(value, error);
+ return val != nullptr;
+}
+
+} // anonymous namespace
+
+TEST(CommandSchema, IntPropType_Empty) {
+ IntPropType prop;
+ EXPECT_TRUE(prop.GetConstraints().empty());
+ EXPECT_FALSE(prop.HasOverriddenAttributes());
+ EXPECT_FALSE(prop.IsBasedOnSchema());
+ EXPECT_EQ(nullptr, prop.GetDefaultValue());
+}
+
+TEST(CommandSchema, IntPropType_Types) {
+ IntPropType prop;
+ EXPECT_EQ(nullptr, prop.GetBoolean());
+ EXPECT_EQ(&prop, prop.GetInt());
+ EXPECT_EQ(nullptr, prop.GetDouble());
+ EXPECT_EQ(nullptr, prop.GetString());
+ EXPECT_EQ(nullptr, prop.GetObject());
+ EXPECT_EQ(nullptr, prop.GetArray());
+}
+
+TEST(CommandSchema, IntPropType_ToJson) {
+ IntPropType prop;
+ EXPECT_JSON_EQ("'integer'", *prop.ToJson(false, false));
+ EXPECT_JSON_EQ("{'type':'integer'}", *prop.ToJson(true, false));
+ IntPropType param2;
+ param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+ EXPECT_JSON_EQ("{}", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'minimum':3}").get(), &prop, nullptr);
+ EXPECT_JSON_EQ("{'minimum':3}", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'maximum':-7}").get(), &prop,
+ nullptr);
+ EXPECT_JSON_EQ("{'maximum':-7}", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':5}").get(),
+ &prop, nullptr);
+ EXPECT_JSON_EQ("{'maximum':5,'minimum':0}", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'enum':[1,2,3]}").get(), &prop,
+ nullptr);
+ EXPECT_JSON_EQ("[1,2,3]", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'default':123}").get(), &prop,
+ nullptr);
+ EXPECT_JSON_EQ("{'default':123}", *param2.ToJson(false, false));
+}
+
+TEST(CommandSchema, IntPropType_FromJson) {
+ IntPropType prop;
+ prop.AddMinMaxConstraint(2, 8);
+ IntPropType param2;
+ param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+ EXPECT_FALSE(param2.HasOverriddenAttributes());
+ EXPECT_TRUE(param2.IsBasedOnSchema());
+ EXPECT_EQ(2, prop.GetMinValue());
+ EXPECT_EQ(8, prop.GetMaxValue());
+ prop.AddMinMaxConstraint(-2, 30);
+ param2.FromJson(CreateDictionaryValue("{'minimum':7}").get(), &prop, nullptr);
+ EXPECT_TRUE(param2.HasOverriddenAttributes());
+ EXPECT_TRUE(param2.IsBasedOnSchema());
+ EXPECT_EQ(7, param2.GetMinValue());
+ EXPECT_EQ(30, param2.GetMaxValue());
+ param2.FromJson(CreateDictionaryValue("{'maximum':17}").get(), &prop,
+ nullptr);
+ EXPECT_TRUE(param2.HasOverriddenAttributes());
+ EXPECT_TRUE(param2.IsBasedOnSchema());
+ EXPECT_EQ(-2, param2.GetMinValue());
+ EXPECT_EQ(17, param2.GetMaxValue());
+
+ ASSERT_TRUE(param2.FromJson(CreateDictionaryValue("{'default':3}").get(),
+ &prop, nullptr));
+ EXPECT_TRUE(param2.HasOverriddenAttributes());
+ ASSERT_NE(nullptr, param2.GetDefaultValue());
+ EXPECT_EQ(3, param2.GetDefaultValue()->GetInt()->GetValue());
+}
+
+TEST(CommandSchema, IntPropType_Validate) {
+ IntPropType prop;
+ prop.AddMinMaxConstraint(2, 4);
+ ErrorPtr error;
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("-1"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("0"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("1"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("2"), &error));
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("3"), &error));
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("4"), &error));
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("5"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("true"), &error));
+ EXPECT_EQ("type_mismatch", error->GetCode());
+ error.reset();
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("3.0"), &error));
+ EXPECT_EQ("type_mismatch", error->GetCode());
+ error.reset();
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("'3'"), &error));
+ EXPECT_EQ("type_mismatch", error->GetCode());
+}
+
+TEST(CommandSchema, IntPropType_CreateValue) {
+ IntPropType prop;
+ ErrorPtr error;
+ auto val = prop.CreateValue(base::FundamentalValue{2}, &error);
+ ASSERT_NE(nullptr, val.get());
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_EQ(2, val->GetValue());
+
+ val = prop.CreateValue(base::StringValue{"blah"}, &error);
+ EXPECT_EQ(nullptr, val.get());
+ ASSERT_NE(nullptr, error.get());
+ EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, BoolPropType_Empty) {
+ BooleanPropType prop;
+ EXPECT_TRUE(prop.GetConstraints().empty());
+ EXPECT_FALSE(prop.HasOverriddenAttributes());
+ EXPECT_FALSE(prop.IsBasedOnSchema());
+ EXPECT_EQ(nullptr, prop.GetDefaultValue());
+ EXPECT_FALSE(prop.IsRequired());
+}
+
+TEST(CommandSchema, BoolPropType_Types) {
+ BooleanPropType prop;
+ EXPECT_EQ(nullptr, prop.GetInt());
+ EXPECT_EQ(&prop, prop.GetBoolean());
+ EXPECT_EQ(nullptr, prop.GetDouble());
+ EXPECT_EQ(nullptr, prop.GetString());
+ EXPECT_EQ(nullptr, prop.GetObject());
+ EXPECT_EQ(nullptr, prop.GetArray());
+}
+
+TEST(CommandSchema, BoolPropType_ToJson) {
+ BooleanPropType prop;
+ EXPECT_JSON_EQ("'boolean'", *prop.ToJson(false, false));
+ EXPECT_JSON_EQ("{'type':'boolean'}", *prop.ToJson(true, false));
+ BooleanPropType param2;
+ param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+ EXPECT_JSON_EQ("{}", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'enum':[true,false]}").get(), &prop,
+ nullptr);
+ EXPECT_JSON_EQ("[true,false]", *param2.ToJson(false, false));
+ EXPECT_JSON_EQ("{'enum':[true,false],'type':'boolean'}",
+ *param2.ToJson(true, false));
+ param2.FromJson(CreateDictionaryValue("{'default':true}").get(), &prop,
+ nullptr);
+ EXPECT_JSON_EQ("{'default':true}", *param2.ToJson(false, false));
+}
+
+TEST(CommandSchema, BoolPropType_FromJson) {
+ BooleanPropType prop;
+ prop.FromJson(CreateDictionaryValue("{'enum':[true]}").get(), &prop, nullptr);
+ BooleanPropType param2;
+ param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+ EXPECT_FALSE(param2.HasOverriddenAttributes());
+ EXPECT_TRUE(param2.IsBasedOnSchema());
+ EXPECT_EQ(std::vector<bool>{true}, GetOneOfValues<bool>(&prop));
+
+ BooleanPropType prop_base;
+ BooleanPropType param3;
+ ASSERT_TRUE(param3.FromJson(CreateDictionaryValue("{'default':false}").get(),
+ &prop_base, nullptr));
+ EXPECT_TRUE(param3.HasOverriddenAttributes());
+ ASSERT_NE(nullptr, param3.GetDefaultValue());
+ EXPECT_FALSE(param3.GetDefaultValue()->GetBoolean()->GetValue());
+}
+
+TEST(CommandSchema, BoolPropType_Validate) {
+ BooleanPropType prop;
+ prop.FromJson(CreateDictionaryValue("{'enum':[true]}").get(), &prop, nullptr);
+ ErrorPtr error;
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("false"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("true"), &error));
+ error.reset();
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("1"), &error));
+ EXPECT_EQ("type_mismatch", error->GetCode());
+ error.reset();
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("3.0"), &error));
+ EXPECT_EQ("type_mismatch", error->GetCode());
+ error.reset();
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("'3'"), &error));
+ EXPECT_EQ("type_mismatch", error->GetCode());
+}
+
+TEST(CommandSchema, BoolPropType_CreateValue) {
+ BooleanPropType prop;
+ ErrorPtr error;
+ auto val = prop.CreateValue(base::FundamentalValue{true}, &error);
+ ASSERT_NE(nullptr, val.get());
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_TRUE(val->GetValue());
+
+ val = prop.CreateValue(base::StringValue{"blah"}, &error);
+ EXPECT_EQ(nullptr, val.get());
+ ASSERT_NE(nullptr, error.get());
+ EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, DoublePropType_Empty) {
+ DoublePropType prop;
+ EXPECT_DOUBLE_EQ(std::numeric_limits<double>::lowest(), prop.GetMinValue());
+ EXPECT_DOUBLE_EQ((std::numeric_limits<double>::max)(), prop.GetMaxValue());
+ EXPECT_FALSE(prop.HasOverriddenAttributes());
+ EXPECT_FALSE(prop.IsBasedOnSchema());
+ EXPECT_EQ(nullptr, prop.GetDefaultValue());
+ EXPECT_FALSE(prop.IsRequired());
+}
+
+TEST(CommandSchema, DoublePropType_Types) {
+ DoublePropType prop;
+ EXPECT_EQ(nullptr, prop.GetInt());
+ EXPECT_EQ(nullptr, prop.GetBoolean());
+ EXPECT_EQ(&prop, prop.GetDouble());
+ EXPECT_EQ(nullptr, prop.GetString());
+ EXPECT_EQ(nullptr, prop.GetObject());
+ EXPECT_EQ(nullptr, prop.GetArray());
+}
+
+TEST(CommandSchema, DoublePropType_ToJson) {
+ DoublePropType prop;
+ EXPECT_JSON_EQ("'number'", *prop.ToJson(false, false));
+ EXPECT_JSON_EQ("{'type':'number'}", *prop.ToJson(true, false));
+ DoublePropType param2;
+ param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+ EXPECT_JSON_EQ("{}", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'minimum':3}").get(), &prop, nullptr);
+ EXPECT_JSON_EQ("{'minimum':3.0}", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'maximum':-7}").get(), &prop,
+ nullptr);
+ EXPECT_JSON_EQ("{'maximum':-7.0}", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':5}").get(),
+ &prop, nullptr);
+ EXPECT_JSON_EQ("{'maximum':5.0,'minimum':0.0}", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'default':12.3}").get(), &prop,
+ nullptr);
+ EXPECT_JSON_EQ("{'default':12.3}", *param2.ToJson(false, false));
+}
+
+TEST(CommandSchema, DoublePropType_FromJson) {
+ DoublePropType prop;
+ prop.AddMinMaxConstraint(2.5, 8.7);
+ DoublePropType param2;
+ param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+ EXPECT_FALSE(param2.HasOverriddenAttributes());
+ EXPECT_TRUE(param2.IsBasedOnSchema());
+ EXPECT_DOUBLE_EQ(2.5, prop.GetMinValue());
+ EXPECT_DOUBLE_EQ(8.7, prop.GetMaxValue());
+ prop.AddMinMaxConstraint(-2.2, 30.4);
+ param2.FromJson(CreateDictionaryValue("{'minimum':7}").get(), &prop, nullptr);
+ EXPECT_TRUE(param2.HasOverriddenAttributes());
+ EXPECT_TRUE(param2.IsBasedOnSchema());
+ EXPECT_DOUBLE_EQ(7.0, param2.GetMinValue());
+ EXPECT_DOUBLE_EQ(30.4, param2.GetMaxValue());
+ param2.FromJson(CreateDictionaryValue("{'maximum':17.2}").get(), &prop,
+ nullptr);
+ EXPECT_TRUE(param2.HasOverriddenAttributes());
+ EXPECT_TRUE(param2.IsBasedOnSchema());
+ EXPECT_DOUBLE_EQ(-2.2, param2.GetMinValue());
+ EXPECT_DOUBLE_EQ(17.2, param2.GetMaxValue());
+ param2.FromJson(CreateDictionaryValue("{'minimum':0,'maximum':6.1}").get(),
+ &prop, nullptr);
+ EXPECT_TRUE(param2.HasOverriddenAttributes());
+ EXPECT_TRUE(param2.IsBasedOnSchema());
+ EXPECT_DOUBLE_EQ(0.0, param2.GetMinValue());
+ EXPECT_DOUBLE_EQ(6.1, param2.GetMaxValue());
+
+ ASSERT_TRUE(param2.FromJson(CreateDictionaryValue("{'default':-1.234}").get(),
+ &prop, nullptr));
+ EXPECT_TRUE(param2.HasOverriddenAttributes());
+ ASSERT_NE(nullptr, param2.GetDefaultValue());
+ EXPECT_DOUBLE_EQ(-1.234, param2.GetDefaultValue()->GetDouble()->GetValue());
+}
+
+TEST(CommandSchema, DoublePropType_Validate) {
+ DoublePropType prop;
+ prop.AddMinMaxConstraint(-1.2, 1.3);
+ ErrorPtr error;
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("-2"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("-1.3"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("-1.2"), &error));
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("0.0"), &error));
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("1.3"), &error));
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("1.31"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("true"), &error));
+ EXPECT_EQ("type_mismatch", error->GetCode());
+ error.reset();
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("'0.0'"), &error));
+ EXPECT_EQ("type_mismatch", error->GetCode());
+}
+
+TEST(CommandSchema, DoublePropType_CreateValue) {
+ DoublePropType prop;
+ ErrorPtr error;
+ auto val = prop.CreateValue(base::FundamentalValue{2.0}, &error);
+ ASSERT_NE(nullptr, val.get());
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_DOUBLE_EQ(2.0, val->GetValue());
+
+ val = prop.CreateValue(base::StringValue{"blah"}, &error);
+ EXPECT_EQ(nullptr, val.get());
+ ASSERT_NE(nullptr, error.get());
+ EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, StringPropType_Empty) {
+ StringPropType prop;
+ EXPECT_EQ(0, prop.GetMinLength());
+ EXPECT_EQ((std::numeric_limits<int>::max)(), prop.GetMaxLength());
+ EXPECT_FALSE(prop.HasOverriddenAttributes());
+ EXPECT_FALSE(prop.IsBasedOnSchema());
+ EXPECT_EQ(nullptr, prop.GetDefaultValue());
+ EXPECT_FALSE(prop.IsRequired());
+}
+
+TEST(CommandSchema, StringPropType_Types) {
+ StringPropType prop;
+ EXPECT_EQ(nullptr, prop.GetInt());
+ EXPECT_EQ(nullptr, prop.GetBoolean());
+ EXPECT_EQ(nullptr, prop.GetDouble());
+ EXPECT_EQ(&prop, prop.GetString());
+ EXPECT_EQ(nullptr, prop.GetObject());
+ EXPECT_EQ(nullptr, prop.GetArray());
+}
+
+TEST(CommandSchema, StringPropType_ToJson) {
+ StringPropType prop;
+ EXPECT_JSON_EQ("'string'", *prop.ToJson(false, false));
+ EXPECT_JSON_EQ("{'type':'string'}", *prop.ToJson(true, false));
+ StringPropType param2;
+ param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+ EXPECT_JSON_EQ("{}", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'minLength':3}").get(), &prop,
+ nullptr);
+ EXPECT_JSON_EQ("{'minLength':3}", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'maxLength':7}").get(), &prop,
+ nullptr);
+ EXPECT_JSON_EQ("{'maxLength':7}", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'minLength':0,'maxLength':5}").get(),
+ &prop, nullptr);
+ EXPECT_JSON_EQ("{'maxLength':5,'minLength':0}", *param2.ToJson(false, false));
+ param2.FromJson(CreateDictionaryValue("{'default':'abcd'}").get(), &prop,
+ nullptr);
+ EXPECT_JSON_EQ("{'default':'abcd'}", *param2.ToJson(false, false));
+}
+
+TEST(CommandSchema, StringPropType_FromJson) {
+ StringPropType prop;
+ prop.AddLengthConstraint(2, 8);
+ StringPropType param2;
+ param2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+ EXPECT_FALSE(param2.HasOverriddenAttributes());
+ EXPECT_TRUE(param2.IsBasedOnSchema());
+ EXPECT_EQ(2, prop.GetMinLength());
+ EXPECT_EQ(8, prop.GetMaxLength());
+ prop.AddLengthConstraint(3, 5);
+ param2.FromJson(CreateDictionaryValue("{'minLength':4}").get(), &prop,
+ nullptr);
+ EXPECT_TRUE(param2.HasOverriddenAttributes());
+ EXPECT_TRUE(param2.IsBasedOnSchema());
+ EXPECT_EQ(4, param2.GetMinLength());
+ EXPECT_EQ(5, param2.GetMaxLength());
+ param2.FromJson(CreateDictionaryValue("{'maxLength':8}").get(), &prop,
+ nullptr);
+ EXPECT_TRUE(param2.HasOverriddenAttributes());
+ EXPECT_TRUE(param2.IsBasedOnSchema());
+ EXPECT_EQ(3, param2.GetMinLength());
+ EXPECT_EQ(8, param2.GetMaxLength());
+ param2.FromJson(CreateDictionaryValue("{'minLength':1,'maxLength':7}").get(),
+ &prop, nullptr);
+ EXPECT_TRUE(param2.HasOverriddenAttributes());
+ EXPECT_TRUE(param2.IsBasedOnSchema());
+ EXPECT_EQ(1, param2.GetMinLength());
+ EXPECT_EQ(7, param2.GetMaxLength());
+
+ ASSERT_TRUE(param2.FromJson(CreateDictionaryValue("{'default':'foo'}").get(),
+ &prop, nullptr));
+ EXPECT_TRUE(param2.HasOverriddenAttributes());
+ ASSERT_NE(nullptr, param2.GetDefaultValue());
+ EXPECT_EQ("foo", param2.GetDefaultValue()->GetString()->GetValue());
+}
+
+TEST(CommandSchema, StringPropType_Validate) {
+ StringPropType prop;
+ prop.AddLengthConstraint(1, 3);
+ ErrorPtr error;
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("''"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+ prop.AddLengthConstraint(2, 3);
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("''"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("'a'"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("'ab'"), &error));
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("'abc'"), &error));
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("'abcd'"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+
+ prop.FromJson(CreateDictionaryValue("{'enum':['abc','def','xyz!!']}").get(),
+ nullptr, &error);
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("'abc'"), &error));
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("'def'"), &error));
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("'xyz!!'"), &error));
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("'xyz'"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+}
+
+TEST(CommandSchema, StringPropType_CreateValue) {
+ StringPropType prop;
+ ErrorPtr error;
+ auto val = prop.CreateValue(base::StringValue{"blah"}, &error);
+ ASSERT_NE(nullptr, val.get());
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_EQ("blah", val->GetValue());
+
+ val = prop.CreateValue(base::FundamentalValue{4}, &error);
+ EXPECT_EQ(nullptr, val.get());
+ ASSERT_NE(nullptr, error.get());
+ EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, ObjectPropType_Empty) {
+ ObjectPropType prop;
+ EXPECT_TRUE(prop.HasOverriddenAttributes());
+ EXPECT_FALSE(prop.IsBasedOnSchema());
+ EXPECT_EQ(nullptr, prop.GetDefaultValue());
+ EXPECT_FALSE(prop.IsRequired());
+}
+
+TEST(CommandSchema, ObjectPropType_Types) {
+ ObjectPropType prop;
+ EXPECT_EQ(nullptr, prop.GetInt());
+ EXPECT_EQ(nullptr, prop.GetBoolean());
+ EXPECT_EQ(nullptr, prop.GetDouble());
+ EXPECT_EQ(nullptr, prop.GetString());
+ EXPECT_EQ(&prop, prop.GetObject());
+ EXPECT_EQ(nullptr, prop.GetArray());
+}
+
+TEST(CommandSchema, ObjectPropType_ToJson) {
+ ObjectPropType prop;
+ EXPECT_JSON_EQ("{'additionalProperties':false,'properties':{}}",
+ *prop.ToJson(false, false));
+ EXPECT_JSON_EQ(
+ "{'additionalProperties':false,'properties':{},'type':'object'}",
+ *prop.ToJson(true, false));
+ EXPECT_FALSE(prop.IsBasedOnSchema());
+ ObjectPropType prop2;
+ prop2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+ EXPECT_JSON_EQ("{}", *prop2.ToJson(false, false));
+ EXPECT_TRUE(prop2.IsBasedOnSchema());
+
+ auto schema = ObjectSchema::Create();
+ schema->AddProp("expires", PropType::Create(ValueType::Int));
+ auto pw = PropType::Create(ValueType::String);
+ pw->GetString()->AddLengthConstraint(6, 100);
+ schema->AddProp("password", std::move(pw));
+ prop2.SetObjectSchema(std::move(schema));
+ auto expected = R"({
+ 'additionalProperties': false,
+ 'properties': {
+ 'expires': 'integer',
+ 'password': {
+ 'maxLength': 100,
+ 'minLength': 6
+ }
+ }
+ })";
+ EXPECT_JSON_EQ(expected, *prop2.ToJson(false, false));
+
+ expected = R"({
+ 'additionalProperties': false,
+ 'properties': {
+ 'expires': {
+ 'type': 'integer'
+ },
+ 'password': {
+ 'maxLength': 100,
+ 'minLength': 6,
+ 'type': 'string'
+ }
+ },
+ 'type': 'object'
+ })";
+ EXPECT_JSON_EQ(expected, *prop2.ToJson(true, false));
+
+ ObjectPropType prop3;
+ ASSERT_TRUE(
+ prop3.FromJson(CreateDictionaryValue(
+ "{'default':{'expires':3,'password':'abracadabra'}}")
+ .get(),
+ &prop2, nullptr));
+ expected = R"({
+ 'default': {
+ 'expires': 3,
+ 'password': 'abracadabra'
+ }
+ })";
+ EXPECT_JSON_EQ(expected, *prop3.ToJson(false, false));
+
+ expected = R"({
+ 'additionalProperties': false,
+ 'default': {
+ 'expires': 3,
+ 'password': 'abracadabra'
+ },
+ 'properties': {
+ 'expires': {
+ 'type': 'integer'
+ },
+ 'password': {
+ 'maxLength': 100,
+ 'minLength': 6,
+ 'type': 'string'
+ }
+ },
+ 'type': 'object'
+ })";
+ EXPECT_JSON_EQ(expected, *prop3.ToJson(true, false));
+
+ ObjectPropType prop4;
+ ASSERT_TRUE(prop4.FromJson(
+ CreateDictionaryValue("{'additionalProperties':true,"
+ "'default':{'expires':3,'password':'abracadabra'}}")
+ .get(),
+ &prop2, nullptr));
+ expected = R"({
+ 'additionalProperties': true,
+ 'default': {
+ 'expires': 3,
+ 'password': 'abracadabra'
+ },
+ 'properties': {
+ 'expires': 'integer',
+ 'password': {
+ 'maxLength': 100,
+ 'minLength': 6
+ }
+ }
+ })";
+ EXPECT_JSON_EQ(expected, *prop4.ToJson(false, false));
+
+ expected = R"({
+ 'additionalProperties': true,
+ 'default': {
+ 'expires': 3,
+ 'password': 'abracadabra'
+ },
+ 'properties': {
+ 'expires': {
+ 'type': 'integer'
+ },
+ 'password': {
+ 'maxLength': 100,
+ 'minLength': 6,
+ 'type': 'string'
+ }
+ },
+ 'type': 'object'
+ })";
+ EXPECT_JSON_EQ(expected, *prop4.ToJson(true, false));
+}
+
+TEST(CommandSchema, ObjectPropType_FromJson) {
+ ObjectPropType base_prop;
+ EXPECT_TRUE(base_prop.FromJson(
+ CreateDictionaryValue("{'properties':{'name':'string','age':'integer'}}")
+ .get(),
+ nullptr, nullptr));
+ auto schema = base_prop.GetObject()->GetObjectSchemaPtr();
+ const PropType* prop = schema->GetProp("name");
+ EXPECT_EQ(ValueType::String, prop->GetType());
+ prop = schema->GetProp("age");
+ EXPECT_EQ(ValueType::Int, prop->GetType());
+
+ ObjectPropType prop2;
+ ASSERT_TRUE(prop2.FromJson(
+ CreateDictionaryValue("{'properties':{'name':'string','age':'integer'},"
+ "'default':{'name':'Bob','age':33}}")
+ .get(),
+ nullptr, nullptr));
+ ASSERT_NE(nullptr, prop2.GetDefaultValue());
+ const ObjectValue* defval = prop2.GetDefaultValue()->GetObject();
+ ASSERT_NE(nullptr, defval);
+ ValueMap objval = defval->GetValue();
+ EXPECT_EQ("Bob", objval["name"]->GetString()->GetValue());
+ EXPECT_EQ(33, objval["age"]->GetInt()->GetValue());
+}
+
+TEST(CommandSchema, ObjectPropType_Validate) {
+ ObjectPropType prop;
+ prop.FromJson(
+ CreateDictionaryValue("{'properties':{'expires':'integer',"
+ "'password':{'maxLength':100,'minLength':6}},"
+ "'required':['expires','password']}")
+ .get(),
+ nullptr, nullptr);
+ ErrorPtr error;
+ EXPECT_TRUE(ValidateValue(
+ prop, *CreateValue("{'expires':10,'password':'abcdef'}"), &error));
+ error.reset();
+
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("{'expires':10}"), &error));
+ EXPECT_EQ("parameter_missing", error->GetCode());
+ error.reset();
+
+ EXPECT_FALSE(
+ ValidateValue(prop, *CreateValue("{'password':'abcdef'}"), &error));
+ EXPECT_EQ("parameter_missing", error->GetCode());
+ error.reset();
+
+ EXPECT_FALSE(ValidateValue(
+ prop, *CreateValue("{'expires':10,'password':'abcde'}"), &error));
+ EXPECT_EQ("out_of_range", error->GetFirstError()->GetCode());
+ error.reset();
+
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("2"), &error));
+ EXPECT_EQ("type_mismatch", error->GetCode());
+ error.reset();
+
+ EXPECT_FALSE(ValidateValue(
+ prop, *CreateValue("{'expires':10,'password':'abcdef','retry':true}"),
+ &error));
+ EXPECT_EQ("unexpected_parameter", error->GetCode());
+ error.reset();
+}
+
+TEST(CommandSchema, ObjectPropType_Validate_Enum) {
+ ObjectPropType prop;
+ EXPECT_TRUE(prop.FromJson(
+ CreateDictionaryValue(
+ "{'properties':{'width':'integer','height':'integer'},"
+ "'enum':[{'width':10,'height':20},{'width':100,'height':200}]}")
+ .get(),
+ nullptr, nullptr));
+ ErrorPtr error;
+ EXPECT_TRUE(
+ ValidateValue(prop, *CreateValue("{'height':20,'width':10}"), &error));
+ error.reset();
+
+ EXPECT_TRUE(
+ ValidateValue(prop, *CreateValue("{'height':200,'width':100}"), &error));
+ error.reset();
+
+ EXPECT_FALSE(
+ ValidateValue(prop, *CreateValue("{'height':12,'width':10}"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+}
+
+TEST(CommandSchema, ObjectPropType_CreateValue) {
+ ObjectPropType prop;
+ IntPropType int_type;
+ ASSERT_TRUE(prop.FromJson(
+ CreateDictionaryValue(
+ "{'properties':{'width':'integer','height':'integer'},"
+ "'enum':[{'width':10,'height':20},{'width':100,'height':200}]}")
+ .get(),
+ nullptr, nullptr));
+ ValueMap obj{
+ {"width", int_type.CreateValue(base::FundamentalValue{10}, nullptr)},
+ {"height", int_type.CreateValue(base::FundamentalValue{20}, nullptr)},
+ };
+
+ ErrorPtr error;
+ auto val = prop.CreateValue(
+ *CreateDictionaryValue("{'width': 10, 'height': 20}"), &error);
+ ASSERT_NE(nullptr, val.get());
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_EQ(obj, val->GetValue());
+
+ val = prop.CreateValue(base::StringValue{"blah"}, &error);
+ EXPECT_EQ(nullptr, val.get());
+ ASSERT_NE(nullptr, error.get());
+ EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, ArrayPropType_Empty) {
+ ArrayPropType prop;
+ EXPECT_FALSE(prop.HasOverriddenAttributes());
+ EXPECT_FALSE(prop.IsBasedOnSchema());
+ EXPECT_EQ(nullptr, prop.GetDefaultValue());
+ EXPECT_EQ(nullptr, prop.GetItemTypePtr());
+ prop.SetItemType(PropType::Create(ValueType::Int));
+ EXPECT_TRUE(prop.HasOverriddenAttributes());
+ EXPECT_FALSE(prop.IsBasedOnSchema());
+ EXPECT_NE(nullptr, prop.GetItemTypePtr());
+ EXPECT_FALSE(prop.IsRequired());
+}
+
+TEST(CommandSchema, ArrayPropType_Types) {
+ ArrayPropType prop;
+ EXPECT_EQ(nullptr, prop.GetInt());
+ EXPECT_EQ(nullptr, prop.GetBoolean());
+ EXPECT_EQ(nullptr, prop.GetDouble());
+ EXPECT_EQ(nullptr, prop.GetString());
+ EXPECT_EQ(nullptr, prop.GetObject());
+ EXPECT_EQ(&prop, prop.GetArray());
+}
+
+TEST(CommandSchema, ArrayPropType_ToJson) {
+ ArrayPropType prop;
+ prop.SetItemType(PropType::Create(ValueType::Int));
+ EXPECT_JSON_EQ("{'items':'integer'}", *prop.ToJson(false, false));
+ EXPECT_JSON_EQ("{'items':{'type':'integer'},'type':'array'}",
+ *prop.ToJson(true, false));
+ EXPECT_FALSE(prop.IsBasedOnSchema());
+ ArrayPropType prop2;
+ prop2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr);
+ EXPECT_JSON_EQ("{}", *prop2.ToJson(false, false));
+ EXPECT_TRUE(prop2.IsBasedOnSchema());
+ prop2.FromJson(CreateDictionaryValue("{'default':[1,2,3]}").get(), &prop,
+ nullptr);
+ EXPECT_JSON_EQ("{'default':[1,2,3]}", *prop2.ToJson(false, false));
+ EXPECT_JSON_EQ(
+ "{'default':[1,2,3],'items':{'type':'integer'},'type':'array'}",
+ *prop2.ToJson(true, false));
+}
+
+TEST(CommandSchema, ArrayPropType_FromJson) {
+ ArrayPropType prop;
+ EXPECT_TRUE(prop.FromJson(CreateDictionaryValue("{'items':'integer'}").get(),
+ nullptr, nullptr));
+ EXPECT_EQ(ValueType::Int, prop.GetItemTypePtr()->GetType());
+
+ ArrayPropType prop2;
+ ASSERT_TRUE(
+ prop2.FromJson(CreateDictionaryValue(
+ "{'items':'string','default':['foo', 'bar', 'baz']}")
+ .get(),
+ nullptr, nullptr));
+ ASSERT_NE(nullptr, prop2.GetDefaultValue());
+ const ArrayValue* defval = prop2.GetDefaultValue()->GetArray();
+ ASSERT_NE(nullptr, defval);
+ EXPECT_EQ((std::vector<std::string>{"foo", "bar", "baz"}),
+ GetArrayValues<std::string>(defval->GetValue()));
+}
+
+TEST(CommandSchema, ArrayPropType_Validate) {
+ ArrayPropType prop;
+ prop.FromJson(
+ CreateDictionaryValue("{'items':{'minimum':2.3, 'maximum':10.5}}").get(),
+ nullptr, nullptr);
+
+ ErrorPtr error;
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("[3,4,10.5]"), &error));
+ error.reset();
+
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("[2]"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("[4, 5, 20]"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+}
+
+TEST(CommandSchema, ArrayPropType_Validate_Enum) {
+ ArrayPropType prop;
+ prop.FromJson(
+ CreateDictionaryValue("{'items':'integer', 'enum':[[1], [2,3], [4,5,6]]}")
+ .get(),
+ nullptr, nullptr);
+
+ ErrorPtr error;
+ EXPECT_TRUE(ValidateValue(prop, *CreateValue("[2,3]"), &error));
+ error.reset();
+
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("[2]"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+
+ EXPECT_FALSE(ValidateValue(prop, *CreateValue("[2,3,4]"), &error));
+ EXPECT_EQ("out_of_range", error->GetCode());
+ error.reset();
+}
+
+TEST(CommandSchema, ArrayPropType_CreateValue) {
+ ArrayPropType prop;
+ ASSERT_TRUE(prop.FromJson(
+ CreateDictionaryValue(
+ "{'items':{'properties':{'width':'integer','height':'integer'}}}")
+ .get(),
+ nullptr, nullptr));
+
+ ErrorPtr error;
+ ValueVector arr;
+
+ auto val = prop.CreateValue(base::ListValue{}, &error);
+ ASSERT_NE(nullptr, val.get());
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_EQ(arr, val->GetValue());
+ EXPECT_JSON_EQ("[]", *val->ToJson());
+
+ val = prop.CreateValue(
+ *CreateValue("[{'height':20,'width':10},{'width':17, 'height':18}]"),
+ &error);
+ ASSERT_NE(nullptr, val.get());
+ EXPECT_EQ(nullptr, error.get());
+ EXPECT_JSON_EQ("[{'height':20,'width':10},{'height':18,'width':17}]",
+ *val->ToJson());
+
+ val = prop.CreateValue(base::StringValue{"blah"}, &error);
+ EXPECT_EQ(nullptr, val.get());
+ ASSERT_NE(nullptr, error.get());
+ EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+TEST(CommandSchema, ArrayPropType_NestedArrays_NotSupported) {
+ ArrayPropType prop;
+ ErrorPtr error;
+ EXPECT_FALSE(prop.FromJson(
+ CreateDictionaryValue("{'items':{'items':'integer'}}").get(), nullptr,
+ &error));
+ EXPECT_EQ(errors::commands::kInvalidObjectSchema, error->GetCode());
+ error.reset();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeName) {
+ ObjectSchema schema;
+ const char* schema_str =
+ "{"
+ "'param1':'integer',"
+ "'param2':'number',"
+ "'param3':'string'"
+ "}";
+ EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ nullptr));
+ EXPECT_EQ(ValueType::Int, schema.GetProp("param1")->GetType());
+ EXPECT_EQ(ValueType::Double, schema.GetProp("param2")->GetType());
+ EXPECT_EQ(ValueType::String, schema.GetProp("param3")->GetType());
+ EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+ EXPECT_EQ("number", schema.GetProp("param2")->GetTypeAsString());
+ EXPECT_EQ("string", schema.GetProp("param3")->GetTypeAsString());
+ EXPECT_EQ(nullptr, schema.GetProp("param4"));
+
+ int min_int = (std::numeric_limits<int>::min)();
+ int max_int = (std::numeric_limits<int>::max)();
+ double min_dbl = (std::numeric_limits<double>::lowest)();
+ double max_dbl = (std::numeric_limits<double>::max)();
+ EXPECT_EQ(min_int, schema.GetProp("param1")->GetInt()->GetMinValue());
+ EXPECT_EQ(max_int, schema.GetProp("param1")->GetInt()->GetMaxValue());
+ EXPECT_EQ(min_dbl, schema.GetProp("param2")->GetDouble()->GetMinValue());
+ EXPECT_EQ(max_dbl, schema.GetProp("param2")->GetDouble()->GetMaxValue());
+ EXPECT_EQ(0, schema.GetProp("param3")->GetString()->GetMinLength());
+ EXPECT_EQ(max_int, schema.GetProp("param3")->GetString()->GetMaxLength());
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_Full_TypeName) {
+ ObjectSchema schema;
+ const char* schema_str =
+ "{"
+ "'param1':{'type':'integer'},"
+ "'param2':{'type':'number'},"
+ "'param3':{'type':'string'},"
+ "'param4':{'type':'array', 'items':'integer'},"
+ "'param5':{'type':'object', 'properties':{'p1':'integer'}}"
+ "}";
+ EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ nullptr));
+ EXPECT_EQ(ValueType::Int, schema.GetProp("param1")->GetType());
+ EXPECT_EQ(ValueType::Double, schema.GetProp("param2")->GetType());
+ EXPECT_EQ(ValueType::String, schema.GetProp("param3")->GetType());
+ EXPECT_EQ(ValueType::Array, schema.GetProp("param4")->GetType());
+ EXPECT_EQ(ValueType::Object, schema.GetProp("param5")->GetType());
+ EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+ EXPECT_EQ("number", schema.GetProp("param2")->GetTypeAsString());
+ EXPECT_EQ("string", schema.GetProp("param3")->GetTypeAsString());
+ EXPECT_EQ("array", schema.GetProp("param4")->GetTypeAsString());
+ EXPECT_EQ("object", schema.GetProp("param5")->GetTypeAsString());
+ EXPECT_EQ(nullptr, schema.GetProp("param77"));
+
+ int min_int = (std::numeric_limits<int>::min)();
+ int max_int = (std::numeric_limits<int>::max)();
+ double min_dbl = (std::numeric_limits<double>::lowest)();
+ double max_dbl = (std::numeric_limits<double>::max)();
+ EXPECT_EQ(min_int, schema.GetProp("param1")->GetInt()->GetMinValue());
+ EXPECT_EQ(max_int, schema.GetProp("param1")->GetInt()->GetMaxValue());
+ EXPECT_EQ(min_dbl, schema.GetProp("param2")->GetDouble()->GetMinValue());
+ EXPECT_EQ(max_dbl, schema.GetProp("param2")->GetDouble()->GetMaxValue());
+ EXPECT_EQ(0, schema.GetProp("param3")->GetString()->GetMinLength());
+ EXPECT_EQ(max_int, schema.GetProp("param3")->GetString()->GetMaxLength());
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeDeduction_Scalar) {
+ ObjectSchema schema;
+ const char* schema_str =
+ "{"
+ "'param1' :{'minimum':2},"
+ "'param2' :{'maximum':10},"
+ "'param3' :{'maximum':8, 'minimum':2},"
+ "'param4' :{'minimum':2.1},"
+ "'param5' :{'maximum':10.1},"
+ "'param6' :{'maximum':8.1, 'minimum':3.1},"
+ "'param7' :{'maximum':8, 'minimum':3.1},"
+ "'param8' :{'maximum':8.1, 'minimum':3},"
+ "'param9' :{'minLength':2},"
+ "'param10':{'maxLength':10},"
+ "'param11':{'maxLength':8, 'minLength':3},"
+ "'param12':{'default':12},"
+ "'param13':{'default':13.5},"
+ "'param14':{'default':true},"
+ "'param15':{'default':false},"
+ "'param16':{'default':'foobar'},"
+ "'param17':{'default':[1,2,3]},"
+ "'param18':{'items':'number', 'default':[]}"
+ "}";
+ EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ nullptr));
+ EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+ EXPECT_EQ("integer", schema.GetProp("param2")->GetTypeAsString());
+ EXPECT_EQ("integer", schema.GetProp("param3")->GetTypeAsString());
+ EXPECT_EQ("number", schema.GetProp("param4")->GetTypeAsString());
+ EXPECT_EQ("number", schema.GetProp("param5")->GetTypeAsString());
+ EXPECT_EQ("number", schema.GetProp("param6")->GetTypeAsString());
+ EXPECT_EQ("number", schema.GetProp("param7")->GetTypeAsString());
+ EXPECT_EQ("number", schema.GetProp("param8")->GetTypeAsString());
+ EXPECT_EQ("string", schema.GetProp("param9")->GetTypeAsString());
+ EXPECT_EQ("string", schema.GetProp("param10")->GetTypeAsString());
+ EXPECT_EQ("string", schema.GetProp("param11")->GetTypeAsString());
+ EXPECT_EQ("integer", schema.GetProp("param12")->GetTypeAsString());
+ EXPECT_EQ("number", schema.GetProp("param13")->GetTypeAsString());
+ EXPECT_EQ("boolean", schema.GetProp("param14")->GetTypeAsString());
+ EXPECT_EQ("boolean", schema.GetProp("param15")->GetTypeAsString());
+ EXPECT_EQ("string", schema.GetProp("param16")->GetTypeAsString());
+ EXPECT_EQ("array", schema.GetProp("param17")->GetTypeAsString());
+ auto prop17 = schema.GetProp("param17");
+ EXPECT_EQ("integer", prop17->GetArray()->GetItemTypePtr()->GetTypeAsString());
+ EXPECT_EQ("array", schema.GetProp("param18")->GetTypeAsString());
+ auto prop18 = schema.GetProp("param18");
+ EXPECT_EQ("number", prop18->GetArray()->GetItemTypePtr()->GetTypeAsString());
+
+ int min_int = (std::numeric_limits<int>::min)();
+ int max_int = (std::numeric_limits<int>::max)();
+ double min_dbl = (std::numeric_limits<double>::lowest)();
+ double max_dbl = (std::numeric_limits<double>::max)();
+ EXPECT_EQ(2, schema.GetProp("param1")->GetInt()->GetMinValue());
+ EXPECT_EQ(max_int, schema.GetProp("param1")->GetInt()->GetMaxValue());
+ EXPECT_EQ(min_int, schema.GetProp("param2")->GetInt()->GetMinValue());
+ EXPECT_EQ(10, schema.GetProp("param2")->GetInt()->GetMaxValue());
+ EXPECT_EQ(2, schema.GetProp("param3")->GetInt()->GetMinValue());
+ EXPECT_EQ(8, schema.GetProp("param3")->GetInt()->GetMaxValue());
+ EXPECT_DOUBLE_EQ(2.1, schema.GetProp("param4")->GetDouble()->GetMinValue());
+ EXPECT_DOUBLE_EQ(max_dbl,
+ schema.GetProp("param4")->GetDouble()->GetMaxValue());
+ EXPECT_DOUBLE_EQ(min_dbl,
+ schema.GetProp("param5")->GetDouble()->GetMinValue());
+ EXPECT_DOUBLE_EQ(10.1, schema.GetProp("param5")->GetDouble()->GetMaxValue());
+ EXPECT_DOUBLE_EQ(3.1, schema.GetProp("param6")->GetDouble()->GetMinValue());
+ EXPECT_DOUBLE_EQ(8.1, schema.GetProp("param6")->GetDouble()->GetMaxValue());
+ EXPECT_DOUBLE_EQ(3.1, schema.GetProp("param7")->GetDouble()->GetMinValue());
+ EXPECT_DOUBLE_EQ(8.0, schema.GetProp("param7")->GetDouble()->GetMaxValue());
+ EXPECT_DOUBLE_EQ(3.0, schema.GetProp("param8")->GetDouble()->GetMinValue());
+ EXPECT_DOUBLE_EQ(8.1, schema.GetProp("param8")->GetDouble()->GetMaxValue());
+ EXPECT_EQ(2, schema.GetProp("param9")->GetString()->GetMinLength());
+ EXPECT_EQ(max_int, schema.GetProp("param9")->GetString()->GetMaxLength());
+ EXPECT_EQ(0, schema.GetProp("param10")->GetString()->GetMinLength());
+ EXPECT_EQ(10, schema.GetProp("param10")->GetString()->GetMaxLength());
+ EXPECT_EQ(3, schema.GetProp("param11")->GetString()->GetMinLength());
+ EXPECT_EQ(8, schema.GetProp("param11")->GetString()->GetMaxLength());
+ const PropValue* val = schema.GetProp("param12")->GetDefaultValue();
+ EXPECT_EQ(12, val->GetInt()->GetValue());
+ val = schema.GetProp("param13")->GetDefaultValue();
+ EXPECT_DOUBLE_EQ(13.5, val->GetDouble()->GetValue());
+ val = schema.GetProp("param14")->GetDefaultValue();
+ EXPECT_TRUE(val->GetBoolean()->GetValue());
+ val = schema.GetProp("param15")->GetDefaultValue();
+ EXPECT_FALSE(val->GetBoolean()->GetValue());
+ val = schema.GetProp("param16")->GetDefaultValue();
+ EXPECT_EQ("foobar", val->GetString()->GetValue());
+ val = schema.GetProp("param17")->GetDefaultValue();
+ EXPECT_EQ((std::vector<int>{1, 2, 3}),
+ GetArrayValues<int>(val->GetArray()->GetValue()));
+ val = schema.GetProp("param18")->GetDefaultValue();
+ EXPECT_TRUE(val->GetArray()->GetValue().empty());
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_Shorthand_TypeDeduction_Array) {
+ ObjectSchema schema;
+ const char* schema_str =
+ "{"
+ "'param1' :[0,1,2,3],"
+ "'param2' :[0.0,1.1,2.2],"
+ "'param3' :['id1', 'id2'],"
+ "'param4' :{'enum':[1,2,3]},"
+ "'param5' :{'enum':[-1.1,2.2,3]},"
+ "'param6' :{'enum':['id0', 'id1']},"
+ "'param7' :{'type':'integer', 'enum':[1,2,3]},"
+ "'param8' :{'type':'number', 'enum':[1,2,3]},"
+ "'param9' :{'type':'number', 'enum':[]},"
+ "'param10':{'type':'integer', 'enum':[]},"
+ "'param11':[[0,1],[2,3]],"
+ "'param12':[['foo','bar']],"
+ "'param13':{'enum':[['id0', 'id1']]}"
+ "}";
+ EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ nullptr));
+ EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+ EXPECT_EQ("number", schema.GetProp("param2")->GetTypeAsString());
+ EXPECT_EQ("string", schema.GetProp("param3")->GetTypeAsString());
+ EXPECT_EQ("integer", schema.GetProp("param4")->GetTypeAsString());
+ EXPECT_EQ("number", schema.GetProp("param5")->GetTypeAsString());
+ EXPECT_EQ("string", schema.GetProp("param6")->GetTypeAsString());
+ EXPECT_EQ("integer", schema.GetProp("param7")->GetTypeAsString());
+ EXPECT_EQ("number", schema.GetProp("param8")->GetTypeAsString());
+ EXPECT_EQ("number", schema.GetProp("param9")->GetTypeAsString());
+ EXPECT_EQ("integer", schema.GetProp("param10")->GetTypeAsString());
+
+ auto prop_type11 = schema.GetProp("param11");
+ EXPECT_EQ("array", prop_type11->GetTypeAsString());
+ EXPECT_EQ("integer",
+ prop_type11->GetArray()->GetItemTypePtr()->GetTypeAsString());
+
+ auto prop_type12 = schema.GetProp("param12");
+ EXPECT_EQ("array", prop_type12->GetTypeAsString());
+ EXPECT_EQ("string",
+ prop_type12->GetArray()->GetItemTypePtr()->GetTypeAsString());
+
+ auto prop_type13 = schema.GetProp("param13");
+ EXPECT_EQ("array", prop_type12->GetTypeAsString());
+ EXPECT_EQ("string",
+ prop_type13->GetArray()->GetItemTypePtr()->GetTypeAsString());
+
+ EXPECT_EQ((std::vector<int>{0, 1, 2, 3}),
+ GetOneOfValues<int>(schema.GetProp("param1")));
+ EXPECT_EQ((std::vector<double>{0.0, 1.1, 2.2}),
+ GetOneOfValues<double>(schema.GetProp("param2")));
+ EXPECT_EQ((std::vector<std::string>{"id1", "id2"}),
+ GetOneOfValues<std::string>(schema.GetProp("param3")));
+
+ EXPECT_EQ((std::vector<int>{1, 2, 3}),
+ GetOneOfValues<int>(schema.GetProp("param4")));
+ EXPECT_EQ((std::vector<double>{-1.1, 2.2, 3.0}),
+ GetOneOfValues<double>(schema.GetProp("param5")));
+ EXPECT_EQ((std::vector<std::string>{"id0", "id1"}),
+ GetOneOfValues<std::string>(schema.GetProp("param6")));
+ EXPECT_EQ((std::vector<int>{1, 2, 3}),
+ GetOneOfValues<int>(schema.GetProp("param7")));
+ EXPECT_EQ((std::vector<double>{1.0, 2.0, 3.0}),
+ GetOneOfValues<double>(schema.GetProp("param8")));
+ EXPECT_TRUE(GetOneOfValues<double>(schema.GetProp("param9")).empty());
+ EXPECT_TRUE(GetOneOfValues<int>(schema.GetProp("param10")).empty());
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_Inheritance) {
+ const char* base_schema_str =
+ "{"
+ "'param0' :{'minimum':1, 'maximum':5},"
+ "'param1' :{'minimum':1, 'maximum':5},"
+ "'param2' :{'minimum':1, 'maximum':5},"
+ "'param3' :{'minimum':1, 'maximum':5},"
+ "'param4' :{'minimum':1, 'maximum':5},"
+ "'param5' :{'minimum':1.1, 'maximum':5.5},"
+ "'param6' :{'minimum':1.1, 'maximum':5.5},"
+ "'param7' :{'minimum':1.1, 'maximum':5.5},"
+ "'param8' :{'minimum':1.1, 'maximum':5.5},"
+ "'param9' :{'minLength':1, 'maxLength':5},"
+ "'param10':{'minLength':1, 'maxLength':5},"
+ "'param11':{'minLength':1, 'maxLength':5},"
+ "'param12':{'minLength':1, 'maxLength':5},"
+ "'param13':[1,2,3],"
+ "'param14':[1,2,3],"
+ "'param15':[1.1,2.2,3.3],"
+ "'param16':[1.1,2.2,3.3],"
+ "'param17':['id1', 'id2'],"
+ "'param18':['id1', 'id2'],"
+ "'param19':{'minimum':1, 'maximum':5},"
+ "'param20':{'default':49},"
+ "'param21':{'default':49},"
+ "'param22':'integer'"
+ "}";
+ ObjectSchema base_schema;
+ EXPECT_TRUE(base_schema.FromJson(CreateDictionaryValue(base_schema_str).get(),
+ nullptr, nullptr));
+ const char* schema_str =
+ "{"
+ "'param1' :{},"
+ "'param2' :{'minimum':2},"
+ "'param3' :{'maximum':9},"
+ "'param4' :{'minimum':2, 'maximum':9},"
+ "'param5' :{},"
+ "'param6' :{'minimum':2.2},"
+ "'param7' :{'maximum':9.9},"
+ "'param8' :{'minimum':2.2, 'maximum':9.9},"
+ "'param9' :{},"
+ "'param10':{'minLength':3},"
+ "'param11':{'maxLength':8},"
+ "'param12':{'minLength':3, 'maxLength':8},"
+ "'param13':{},"
+ "'param14':[1,2,3,4],"
+ "'param15':{},"
+ "'param16':[1.1,2.2,3.3,4.4],"
+ "'param17':{},"
+ "'param18':['id1', 'id3'],"
+ "'param19':{},"
+ "'param20':{},"
+ "'param21':{'default':8},"
+ "'param22':{'default':123}"
+ "}";
+ ObjectSchema schema;
+ EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(),
+ &base_schema, nullptr));
+ EXPECT_EQ(nullptr, schema.GetProp("param0"));
+ EXPECT_NE(nullptr, schema.GetProp("param1"));
+ EXPECT_EQ("integer", schema.GetProp("param1")->GetTypeAsString());
+ EXPECT_EQ(1, schema.GetProp("param1")->GetInt()->GetMinValue());
+ EXPECT_EQ(5, schema.GetProp("param1")->GetInt()->GetMaxValue());
+ EXPECT_EQ("integer", schema.GetProp("param2")->GetTypeAsString());
+ EXPECT_EQ(2, schema.GetProp("param2")->GetInt()->GetMinValue());
+ EXPECT_EQ(5, schema.GetProp("param2")->GetInt()->GetMaxValue());
+ EXPECT_EQ("integer", schema.GetProp("param3")->GetTypeAsString());
+ EXPECT_EQ(1, schema.GetProp("param3")->GetInt()->GetMinValue());
+ EXPECT_EQ(9, schema.GetProp("param3")->GetInt()->GetMaxValue());
+ EXPECT_EQ("integer", schema.GetProp("param4")->GetTypeAsString());
+ EXPECT_EQ(2, schema.GetProp("param4")->GetInt()->GetMinValue());
+ EXPECT_EQ(9, schema.GetProp("param4")->GetInt()->GetMaxValue());
+ EXPECT_EQ("number", schema.GetProp("param5")->GetTypeAsString());
+ EXPECT_EQ(1.1, schema.GetProp("param5")->GetDouble()->GetMinValue());
+ EXPECT_EQ(5.5, schema.GetProp("param5")->GetDouble()->GetMaxValue());
+ EXPECT_EQ("number", schema.GetProp("param6")->GetTypeAsString());
+ EXPECT_EQ(2.2, schema.GetProp("param6")->GetDouble()->GetMinValue());
+ EXPECT_EQ(5.5, schema.GetProp("param6")->GetDouble()->GetMaxValue());
+ EXPECT_EQ("number", schema.GetProp("param7")->GetTypeAsString());
+ EXPECT_EQ(1.1, schema.GetProp("param7")->GetDouble()->GetMinValue());
+ EXPECT_EQ(9.9, schema.GetProp("param7")->GetDouble()->GetMaxValue());
+ EXPECT_EQ("number", schema.GetProp("param8")->GetTypeAsString());
+ EXPECT_EQ(2.2, schema.GetProp("param8")->GetDouble()->GetMinValue());
+ EXPECT_EQ(9.9, schema.GetProp("param8")->GetDouble()->GetMaxValue());
+ EXPECT_EQ("string", schema.GetProp("param9")->GetTypeAsString());
+ EXPECT_EQ(1, schema.GetProp("param9")->GetString()->GetMinLength());
+ EXPECT_EQ(5, schema.GetProp("param9")->GetString()->GetMaxLength());
+ EXPECT_EQ("string", schema.GetProp("param10")->GetTypeAsString());
+ EXPECT_EQ(3, schema.GetProp("param10")->GetString()->GetMinLength());
+ EXPECT_EQ(5, schema.GetProp("param10")->GetString()->GetMaxLength());
+ EXPECT_EQ("string", schema.GetProp("param11")->GetTypeAsString());
+ EXPECT_EQ(1, schema.GetProp("param11")->GetString()->GetMinLength());
+ EXPECT_EQ(8, schema.GetProp("param11")->GetString()->GetMaxLength());
+ EXPECT_EQ("string", schema.GetProp("param12")->GetTypeAsString());
+ EXPECT_EQ(3, schema.GetProp("param12")->GetString()->GetMinLength());
+ EXPECT_EQ(8, schema.GetProp("param12")->GetString()->GetMaxLength());
+ EXPECT_EQ("integer", schema.GetProp("param13")->GetTypeAsString());
+ EXPECT_EQ((std::vector<int>{1, 2, 3}),
+ GetOneOfValues<int>(schema.GetProp("param13")));
+ EXPECT_EQ("integer", schema.GetProp("param14")->GetTypeAsString());
+ EXPECT_EQ((std::vector<int>{1, 2, 3, 4}),
+ GetOneOfValues<int>(schema.GetProp("param14")));
+ EXPECT_EQ("number", schema.GetProp("param15")->GetTypeAsString());
+ EXPECT_EQ((std::vector<double>{1.1, 2.2, 3.3}),
+ GetOneOfValues<double>(schema.GetProp("param15")));
+ EXPECT_EQ("number", schema.GetProp("param16")->GetTypeAsString());
+ EXPECT_EQ((std::vector<double>{1.1, 2.2, 3.3, 4.4}),
+ GetOneOfValues<double>(schema.GetProp("param16")));
+ EXPECT_EQ("string", schema.GetProp("param17")->GetTypeAsString());
+ EXPECT_EQ((std::vector<std::string>{"id1", "id2"}),
+ GetOneOfValues<std::string>(schema.GetProp("param17")));
+ EXPECT_EQ("string", schema.GetProp("param18")->GetTypeAsString());
+ EXPECT_EQ((std::vector<std::string>{"id1", "id3"}),
+ GetOneOfValues<std::string>(schema.GetProp("param18")));
+ EXPECT_EQ("integer", schema.GetProp("param19")->GetTypeAsString());
+ EXPECT_EQ(1, schema.GetProp("param19")->GetInt()->GetMinValue());
+ EXPECT_EQ(5, schema.GetProp("param19")->GetInt()->GetMaxValue());
+ EXPECT_EQ(49,
+ schema.GetProp("param20")->GetDefaultValue()->GetInt()->GetValue());
+ EXPECT_EQ(8,
+ schema.GetProp("param21")->GetDefaultValue()->GetInt()->GetValue());
+ EXPECT_EQ(123,
+ schema.GetProp("param22")->GetDefaultValue()->GetInt()->GetValue());
+}
+
+TEST(CommandSchema, ObjectSchema_UseDefaults) {
+ ObjectPropType prop;
+ const char* schema_str =
+ "{'properties':{"
+ "'param1':{'default':true},"
+ "'param2':{'default':2},"
+ "'param3':{'default':3.3},"
+ "'param4':{'default':'four'},"
+ "'param5':{'default':{'x':5,'y':6},"
+ "'properties':{'x':'integer','y':'integer'}},"
+ "'param6':{'default':[1,2,3]}"
+ "}}";
+ ASSERT_TRUE(
+ prop.FromJson(CreateDictionaryValue(schema_str).get(), nullptr, nullptr));
+
+ // Omit all.
+ auto value =
+ prop.CreatePropValue(*CreateDictionaryValue("{}").get(), nullptr);
+ ASSERT_NE(nullptr, value);
+ ValueMap obj = value->GetObject()->GetValue();
+ EXPECT_TRUE(obj["param1"]->GetBoolean()->GetValue());
+ EXPECT_EQ(2, obj["param2"]->GetInt()->GetValue());
+ EXPECT_DOUBLE_EQ(3.3, obj["param3"]->GetDouble()->GetValue());
+ EXPECT_EQ("four", obj["param4"]->GetString()->GetValue());
+ ValueMap param5 = obj["param5"]->GetObject()->GetValue();
+ EXPECT_EQ(5, param5["x"]->GetInt()->GetValue());
+ EXPECT_EQ(6, param5["y"]->GetInt()->GetValue());
+ ValueVector param6 = obj["param6"]->GetArray()->GetValue();
+ EXPECT_EQ((std::vector<int>{1, 2, 3}), GetArrayValues<int>(param6));
+
+ // Specify some.
+ const char* val_json =
+ "{"
+ "'param1':false,"
+ "'param3':33.3,"
+ "'param5':{'x':-5,'y':-6}"
+ "}";
+ value = prop.CreatePropValue(*CreateDictionaryValue(val_json).get(), nullptr);
+ ASSERT_NE(nullptr, value);
+ obj = value->GetObject()->GetValue();
+ EXPECT_FALSE(obj["param1"]->GetBoolean()->GetValue());
+ EXPECT_EQ(2, obj["param2"]->GetInt()->GetValue());
+ EXPECT_DOUBLE_EQ(33.3, obj["param3"]->GetDouble()->GetValue());
+ EXPECT_EQ("four", obj["param4"]->GetString()->GetValue());
+ param5 = obj["param5"]->GetObject()->GetValue();
+ EXPECT_EQ(-5, param5["x"]->GetInt()->GetValue());
+ EXPECT_EQ(-6, param5["y"]->GetInt()->GetValue());
+ param6 = obj["param6"]->GetArray()->GetValue();
+ EXPECT_EQ((std::vector<int>{1, 2, 3}), GetArrayValues<int>(param6));
+
+ // Specify all.
+ val_json =
+ "{"
+ "'param1':false,"
+ "'param2':22,"
+ "'param3':333.3,"
+ "'param4':'FOUR',"
+ "'param5':{'x':-55,'y':66},"
+ "'param6':[-1, 0]"
+ "}";
+ value = prop.CreatePropValue(*CreateDictionaryValue(val_json).get(), nullptr);
+ ASSERT_NE(nullptr, value);
+ obj = value->GetObject()->GetValue();
+ EXPECT_FALSE(obj["param1"]->GetBoolean()->GetValue());
+ EXPECT_EQ(22, obj["param2"]->GetInt()->GetValue());
+ EXPECT_DOUBLE_EQ(333.3, obj["param3"]->GetDouble()->GetValue());
+ EXPECT_EQ("FOUR", obj["param4"]->GetString()->GetValue());
+ param5 = obj["param5"]->GetObject()->GetValue();
+ EXPECT_EQ(-55, param5["x"]->GetInt()->GetValue());
+ EXPECT_EQ(66, param5["y"]->GetInt()->GetValue());
+ param6 = obj["param6"]->GetArray()->GetValue();
+ EXPECT_EQ((std::vector<int>{-1, 0}), GetArrayValues<int>(param6));
+}
+
+TEST(CommandSchema, ObjectSchema_FromJson_BaseSchema_Failures) {
+ ObjectSchema schema;
+ ErrorPtr error;
+ const char* schema_str =
+ "{"
+ "'param1':{}"
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
+ error.reset();
+
+ schema_str =
+ "{"
+ "'param1':{'type':'foo'}"
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("unknown_type", error->GetFirstError()->GetCode());
+ error.reset();
+
+ schema_str =
+ "{"
+ "'param1':[]"
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
+ error.reset();
+
+ schema_str =
+ "{"
+ "'param1':{'minimum':'foo'}"
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("type_mismatch", error->GetFirstError()->GetCode());
+ error.reset();
+
+ schema_str =
+ "{"
+ "'param1':[1,2.2]"
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("type_mismatch", error->GetFirstError()->GetCode());
+ error.reset();
+
+ schema_str =
+ "{"
+ "'param1':{'minimum':1, 'enum':[1,2,3]}" // can't have min/max & enum.
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("unexpected_parameter", error->GetFirstError()->GetCode());
+ error.reset();
+
+ schema_str =
+ "{"
+ "'param1':{'maximum':1, 'blah':2}" // 'blah' is unexpected.
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("unexpected_parameter", error->GetFirstError()->GetCode());
+ error.reset();
+
+ schema_str =
+ "{"
+ "'param1':{'enum':[1,2,3],'default':5}" // 'default' must be 1, 2, or 3.
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("out_of_range", error->GetFirstError()->GetCode());
+ error.reset();
+
+ schema_str =
+ "{"
+ "'param1':[[1,2.3]]"
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("type_mismatch", error->GetFirstError()->GetCode());
+ error.reset();
+
+ schema_str =
+ "{"
+ "'param1':[[1,2],[3,4],['blah']]"
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("type_mismatch", error->GetFirstError()->GetCode());
+ error.reset();
+
+ schema_str =
+ "{"
+ "'param1':{'default':[]}"
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
+ error.reset();
+
+ schema_str =
+ "{"
+ "'param1':[[[1]],[[2]]]"
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
+ error.reset();
+
+ schema_str =
+ "{"
+ "'param1':{'enum':[[['foo']]]}"
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
+ error.reset();
+
+ schema_str =
+ "{"
+ "'param1':{'default':[[1],[2]]}"
+ "}";
+ EXPECT_FALSE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ &error));
+ EXPECT_EQ("no_type_info", error->GetFirstError()->GetCode());
+ error.reset();
+}
+
+TEST(CommandSchema, RequiredProperties_Integral) {
+ IntPropType prop;
+
+ prop.MakeRequired(false);
+ EXPECT_JSON_EQ("{'type':'integer'}", *prop.ToJson(true, false));
+ EXPECT_JSON_EQ("{'isRequired':false,'type':'integer'}",
+ *prop.ToJson(true, true));
+
+ prop.MakeRequired(true);
+ EXPECT_JSON_EQ("{'type':'integer'}", *prop.ToJson(true, false));
+ EXPECT_JSON_EQ("{'isRequired':true,'type':'integer'}",
+ *prop.ToJson(true, true));
+
+ IntPropType prop2;
+ EXPECT_TRUE(
+ prop2.FromJson(CreateDictionaryValue("{}").get(), &prop, nullptr));
+ EXPECT_TRUE(prop2.IsRequired());
+
+ EXPECT_TRUE(prop2.FromJson(
+ CreateDictionaryValue("{'isRequired': false}").get(), &prop, nullptr));
+ EXPECT_FALSE(prop2.IsRequired());
+
+ EXPECT_JSON_EQ("{'type':'integer'}", *prop2.ToJson(true, false));
+ EXPECT_JSON_EQ("{'isRequired':false,'type':'integer'}",
+ *prop2.ToJson(true, true));
+}
+
+TEST(CommandSchema, RequiredProperties_Object) {
+ ObjectPropType obj_type;
+ auto schema = ObjectSchema::Create();
+ auto type = PropType::Create(ValueType::Int);
+ type->MakeRequired(true);
+ schema->AddProp("prop1", std::move(type));
+ type = PropType::Create(ValueType::String);
+ type->MakeRequired(false);
+ schema->AddProp("prop2", std::move(type));
+ type = PropType::Create(ValueType::Boolean);
+ type->MakeRequired(true);
+ schema->AddProp("prop3", std::move(type));
+ type = PropType::Create(ValueType::Array);
+ type->GetArray()->SetItemType(PropType::Create(ValueType::String));
+ schema->AddProp("prop4", std::move(type));
+ auto expected1 = R"({
+ 'prop1': 'integer',
+ 'prop2': 'string',
+ 'prop3': 'boolean',
+ 'prop4': {'items': 'string'}
+ })";
+ EXPECT_JSON_EQ(expected1, *schema->ToJson(false, false));
+ auto expected2 = R"({
+ 'prop1': {'type':'integer','isRequired': true},
+ 'prop2': {'type':'string','isRequired': false},
+ 'prop3': {'type':'boolean','isRequired': true},
+ 'prop4': {'items': 'string'}
+ })";
+ EXPECT_JSON_EQ(expected2, *schema->ToJson(false, true));
+
+ obj_type.SetObjectSchema(std::move(schema));
+ auto expected3 = R"({
+ 'additionalProperties': false,
+ 'properties': {
+ 'prop1': 'integer',
+ 'prop2': 'string',
+ 'prop3': 'boolean',
+ 'prop4': {'items': 'string'}
+ },
+ 'required': ['prop1','prop3']
+ })";
+ EXPECT_JSON_EQ(expected3, *obj_type.ToJson(false, false));
+ EXPECT_JSON_EQ(expected3, *obj_type.ToJson(false, true));
+}
+
+TEST(CommandSchema, RequiredProperties_Schema_FromJson) {
+ ObjectSchema schema;
+ auto schema_str = R"({
+ 'prop1': {'type':'integer','isRequired': true},
+ 'prop2': {'type':'string','isRequired': false},
+ 'prop3': 'boolean'
+ })";
+ EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(), nullptr,
+ nullptr));
+ EXPECT_TRUE(schema.GetProp("prop1")->IsRequired());
+ EXPECT_FALSE(schema.GetProp("prop2")->IsRequired());
+ EXPECT_FALSE(schema.GetProp("prop3")->IsRequired());
+ EXPECT_JSON_EQ(schema_str, *schema.ToJson(false, true));
+}
+
+TEST(CommandSchema, RequiredProperties_Schema_FromJson_Inherit) {
+ ObjectSchema base_schema;
+ auto base_schema_str = R"({
+ 'prop1': {'type':'integer','isRequired': true},
+ 'prop2': {'type':'integer','isRequired': false},
+ 'prop3': {'type':'integer','isRequired': true},
+ 'prop4': {'type':'integer','isRequired': false}
+ })";
+ EXPECT_TRUE(base_schema.FromJson(CreateDictionaryValue(base_schema_str).get(),
+ nullptr, nullptr));
+ ObjectSchema schema;
+ auto schema_str = R"({
+ 'prop1': {'isRequired': false},
+ 'prop2': {'isRequired': true},
+ 'prop3': {},
+ 'prop4': 'integer'
+ })";
+ EXPECT_TRUE(schema.FromJson(CreateDictionaryValue(schema_str).get(),
+ &base_schema, nullptr));
+ EXPECT_FALSE(schema.GetProp("prop1")->IsRequired());
+ EXPECT_TRUE(schema.GetProp("prop2")->IsRequired());
+ EXPECT_TRUE(schema.GetProp("prop3")->IsRequired());
+ EXPECT_FALSE(schema.GetProp("prop4")->IsRequired());
+ auto expected = R"({
+ 'prop1': {'type':'integer','isRequired': false},
+ 'prop2': {'type':'integer','isRequired': true},
+ 'prop3': {},
+ 'prop4': {}
+ })";
+ EXPECT_JSON_EQ(expected, *schema.ToJson(false, true));
+}
+
+TEST(CommandSchema, RequiredProperties_ObjectPropType_FromJson) {
+ ObjectPropType obj_type;
+ auto type_str = R"({
+ 'properties': {
+ 'prop1': 'integer',
+ 'prop2': 'string',
+ 'prop3': {'type':'boolean','isRequired':true},
+ 'prop4': {'items': 'string','isRequired':false},
+ 'prop5': {'type':'number','isRequired':true}
+ },
+ 'required': ['prop1','prop3','prop4','prop5']
+ })";
+ EXPECT_TRUE(obj_type.FromJson(CreateDictionaryValue(type_str).get(), nullptr,
+ nullptr));
+ EXPECT_TRUE(obj_type.GetObjectSchemaPtr()->GetProp("prop1")->IsRequired());
+ EXPECT_FALSE(obj_type.GetObjectSchemaPtr()->GetProp("prop2")->IsRequired());
+ EXPECT_TRUE(obj_type.GetObjectSchemaPtr()->GetProp("prop3")->IsRequired());
+ // 'required' takes precedence over 'isRequired'.
+ EXPECT_TRUE(obj_type.GetObjectSchemaPtr()->GetProp("prop4")->IsRequired());
+ EXPECT_TRUE(obj_type.GetObjectSchemaPtr()->GetProp("prop5")->IsRequired());
+}
+
+TEST(CommandSchema, RequiredProperties_Failures) {
+ ObjectPropType obj_type;
+ ErrorPtr error;
+
+ auto type_str = R"({
+ 'properties': {
+ 'prop1': 'integer',
+ 'prop2': 'string'
+ },
+ 'required': ['prop1','prop3','prop4']
+ })";
+ EXPECT_FALSE(obj_type.FromJson(CreateDictionaryValue(type_str).get(), nullptr,
+ &error));
+ EXPECT_EQ(errors::commands::kUnknownProperty, error->GetCode());
+ error.reset();
+
+ type_str = R"({
+ 'properties': {
+ 'prop1': 'integer',
+ 'prop2': 'string'
+ },
+ 'required': 'prop1'
+ })";
+ EXPECT_FALSE(obj_type.FromJson(CreateDictionaryValue(type_str).get(), nullptr,
+ &error));
+ EXPECT_EQ(errors::commands::kInvalidObjectSchema, error->GetCode());
+ error.reset();
+}
+
+TEST(CommandSchema, ObjectSchema_UseRequired) {
+ ObjectPropType prop;
+ auto schema_str = R"({
+ 'properties':{
+ 'param1':'integer',
+ 'param2':'integer',
+ 'param3':{'default':3},
+ 'param4':{'default':4}
+ },
+ 'required':['param1','param3']
+ })";
+ ASSERT_TRUE(
+ prop.FromJson(CreateDictionaryValue(schema_str).get(), nullptr, nullptr));
+
+ auto val_json = R"({
+ 'param1':10,
+ 'param2':20,
+ 'param3':30,
+ 'param4':40
+ })";
+ auto value =
+ prop.CreatePropValue(*CreateDictionaryValue(val_json).get(), nullptr);
+ ASSERT_NE(nullptr, value);
+ ValueMap obj = value->GetObject()->GetValue();
+ EXPECT_EQ(10, obj["param1"]->GetInt()->GetValue());
+ EXPECT_EQ(20, obj["param2"]->GetInt()->GetValue());
+ EXPECT_EQ(30, obj["param3"]->GetInt()->GetValue());
+ EXPECT_EQ(40, obj["param4"]->GetInt()->GetValue());
+
+ val_json = "{'param1':100}";
+ value = prop.CreatePropValue(*CreateDictionaryValue(val_json).get(), nullptr);
+ ASSERT_NE(nullptr, value);
+ obj = value->GetObject()->GetValue();
+ EXPECT_EQ(3, obj.size());
+
+ EXPECT_EQ(100, obj["param1"]->GetInt()->GetValue());
+ EXPECT_EQ(obj.end(), obj.find("param2"));
+ EXPECT_EQ(3, obj["param3"]->GetInt()->GetValue());
+ EXPECT_EQ(4, obj["param4"]->GetInt()->GetValue());
+}
+
+TEST(CommandSchema, ObjectSchema_UseRequired_Failure) {
+ ObjectPropType prop;
+ auto schema_str = R"({
+ 'properties':{
+ 'param1':'integer',
+ 'param2':'integer',
+ 'param3':{'default':3},
+ 'param4':{'default':4}
+ },
+ 'required':['param1','param3']
+ })";
+ ASSERT_TRUE(
+ prop.FromJson(CreateDictionaryValue(schema_str).get(), nullptr, nullptr));
+
+ auto val_json = "{'param2':20}";
+ ErrorPtr error;
+ auto value =
+ prop.CreatePropValue(*CreateDictionaryValue(val_json).get(), &error);
+ ASSERT_EQ(nullptr, value);
+ EXPECT_EQ(errors::commands::kPropertyMissing, error->GetCode());
+}
+
+} // namespace weave
diff --git a/src/commands/prop_constraints.cc b/src/commands/prop_constraints.cc
new file mode 100644
index 0000000..b7e9cf6
--- /dev/null
+++ b/src/commands/prop_constraints.cc
@@ -0,0 +1,202 @@
+// Copyright 2015 The Weave 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 "src/commands/prop_constraints.h"
+
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_constants.h"
+#include "src/string_utils.h"
+
+namespace weave {
+
+namespace {
+
+// Helper function to convert a property value to string, which is used for
+// error reporting.
+std::string PropValueToString(const PropValue& value) {
+ std::string result;
+ auto json = value.ToJson();
+ CHECK(json);
+ base::JSONWriter::Write(*json, &result);
+ return result;
+}
+
+} // anonymous namespace
+
+// Constraint ----------------------------------------------------------------
+Constraint::~Constraint() {}
+
+bool Constraint::ReportErrorLessThan(ErrorPtr* error,
+ const std::string& val,
+ const std::string& limit) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kOutOfRange,
+ "Value %s is out of range. It must not be less than %s",
+ val.c_str(), limit.c_str());
+ return false;
+}
+
+bool Constraint::ReportErrorGreaterThan(ErrorPtr* error,
+ const std::string& val,
+ const std::string& limit) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kOutOfRange,
+ "Value %s is out of range. It must not be greater than %s",
+ val.c_str(), limit.c_str());
+ return false;
+}
+
+bool Constraint::ReportErrorNotOneOf(ErrorPtr* error,
+ const std::string& val,
+ const std::vector<std::string>& values) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kOutOfRange,
+ "Value %s is invalid. Expected one of [%s]", val.c_str(),
+ Join(",", values).c_str());
+ return false;
+}
+
+void Constraint::AddToJsonDict(base::DictionaryValue* dict,
+ bool overridden_only) const {
+ if (!overridden_only || HasOverriddenAttributes()) {
+ auto value = ToJson();
+ CHECK(value);
+ dict->SetWithoutPathExpansion(GetDictKey(), value.release());
+ }
+}
+
+// ConstraintStringLength -----------------------------------------------------
+ConstraintStringLength::ConstraintStringLength(
+ const InheritableAttribute<int>& limit)
+ : limit_(limit) {}
+ConstraintStringLength::ConstraintStringLength(int limit) : limit_(limit) {}
+
+bool ConstraintStringLength::HasOverriddenAttributes() const {
+ return !limit_.is_inherited;
+}
+
+std::unique_ptr<base::Value> ConstraintStringLength::ToJson() const {
+ return TypedValueToJson(limit_.value);
+}
+
+// ConstraintStringLengthMin --------------------------------------------------
+ConstraintStringLengthMin::ConstraintStringLengthMin(
+ const InheritableAttribute<int>& limit)
+ : ConstraintStringLength(limit) {}
+ConstraintStringLengthMin::ConstraintStringLengthMin(int limit)
+ : ConstraintStringLength(limit) {}
+
+bool ConstraintStringLengthMin::Validate(const PropValue& value,
+ ErrorPtr* error) const {
+ CHECK(value.GetString()) << "Expecting a string value for this constraint";
+ const std::string& str = value.GetString()->GetValue();
+ int length = static_cast<int>(str.size());
+ if (length < limit_.value) {
+ if (limit_.value == 1) {
+ Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kOutOfRange, "String must not be empty");
+ } else {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kOutOfRange,
+ "String must be at least %d characters long,"
+ " actual length of string '%s' is %d",
+ limit_.value, str.c_str(), length);
+ }
+ return false;
+ }
+ return true;
+}
+
+std::unique_ptr<Constraint> ConstraintStringLengthMin::Clone() const {
+ return std::unique_ptr<Constraint>{new ConstraintStringLengthMin{limit_}};
+}
+
+std::unique_ptr<Constraint> ConstraintStringLengthMin::CloneAsInherited()
+ const {
+ return std::unique_ptr<Constraint>{
+ new ConstraintStringLengthMin{limit_.value}};
+}
+
+// ConstraintStringLengthMax --------------------------------------------------
+ConstraintStringLengthMax::ConstraintStringLengthMax(
+ const InheritableAttribute<int>& limit)
+ : ConstraintStringLength(limit) {}
+ConstraintStringLengthMax::ConstraintStringLengthMax(int limit)
+ : ConstraintStringLength(limit) {}
+
+bool ConstraintStringLengthMax::Validate(const PropValue& value,
+ ErrorPtr* error) const {
+ CHECK(value.GetString()) << "Expecting a string value for this constraint";
+ const std::string& str = value.GetString()->GetValue();
+ int length = static_cast<int>(str.size());
+ if (length > limit_.value) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kOutOfRange,
+ "String must be no more than %d character(s) "
+ "long, actual length of string '%s' is %d",
+ limit_.value, str.c_str(), length);
+ return false;
+ }
+ return true;
+}
+
+std::unique_ptr<Constraint> ConstraintStringLengthMax::Clone() const {
+ return std::unique_ptr<Constraint>{new ConstraintStringLengthMax{limit_}};
+}
+
+std::unique_ptr<Constraint> ConstraintStringLengthMax::CloneAsInherited()
+ const {
+ return std::unique_ptr<Constraint>{
+ new ConstraintStringLengthMax{limit_.value}};
+}
+
+// ConstraintOneOf --------------------------------------------------
+ConstraintOneOf::ConstraintOneOf(InheritableAttribute<ValueVector> set)
+ : set_(std::move(set)) {}
+ConstraintOneOf::ConstraintOneOf(ValueVector set) : set_(std::move(set)) {}
+
+bool ConstraintOneOf::Validate(const PropValue& value, ErrorPtr* error) const {
+ for (const auto& item : set_.value) {
+ if (value.IsEqual(item.get()))
+ return true;
+ }
+ std::vector<std::string> choice_list;
+ choice_list.reserve(set_.value.size());
+ for (const auto& item : set_.value) {
+ choice_list.push_back(PropValueToString(*item));
+ }
+ return ReportErrorNotOneOf(error, PropValueToString(value), choice_list);
+}
+
+std::unique_ptr<Constraint> ConstraintOneOf::Clone() const {
+ InheritableAttribute<ValueVector> attr;
+ attr.is_inherited = set_.is_inherited;
+ attr.value.reserve(set_.value.size());
+ for (const auto& prop_value : set_.value) {
+ attr.value.push_back(prop_value->Clone());
+ }
+ return std::unique_ptr<Constraint>{new ConstraintOneOf{std::move(attr)}};
+}
+
+std::unique_ptr<Constraint> ConstraintOneOf::CloneAsInherited() const {
+ ValueVector cloned;
+ cloned.reserve(set_.value.size());
+ for (const auto& prop_value : set_.value) {
+ cloned.push_back(prop_value->Clone());
+ }
+ return std::unique_ptr<Constraint>{new ConstraintOneOf{std::move(cloned)}};
+}
+
+std::unique_ptr<base::Value> ConstraintOneOf::ToJson() const {
+ return TypedValueToJson(set_.value);
+}
+
+const char* ConstraintOneOf::GetDictKey() const {
+ return commands::attributes::kOneOf_Enum;
+}
+
+} // namespace weave
diff --git a/src/commands/prop_constraints.h b/src/commands/prop_constraints.h
new file mode 100644
index 0000000..53a4d93
--- /dev/null
+++ b/src/commands/prop_constraints.h
@@ -0,0 +1,315 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_SRC_COMMANDS_PROP_CONSTRAINTS_H_
+#define LIBWEAVE_SRC_COMMANDS_PROP_CONSTRAINTS_H_
+
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <base/macros.h>
+#include <base/values.h>
+#include <weave/error.h>
+
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_constants.h"
+#include "src/commands/schema_utils.h"
+#include "src/string_utils.h"
+
+namespace weave {
+
+enum class ConstraintType { Min, Max, StringLengthMin, StringLengthMax, OneOf };
+
+// Abstract base class for all parameter constraints. Many constraints are
+// type-dependent. Thus, a numeric parameter could have "minimum" and/or
+// "maximum" constraints specified. Some constraints, such as "OneOf" apply to
+// any data type.
+class Constraint {
+ public:
+ Constraint() = default;
+ virtual ~Constraint();
+
+ // Gets the constraint type.
+ virtual ConstraintType GetType() const = 0;
+
+ // Checks if any of the constraint properties/attributes are overridden
+ // from their base schema definition. If the constraint is inherited, then
+ // it will not be written to JSON when saving partial schema.
+ virtual bool HasOverriddenAttributes() const = 0;
+
+ // Validates a parameter against the constraint. Returns true if parameter
+ // value satisfies the constraint, otherwise fills the optional |error| with
+ // the details for the failure.
+ virtual bool Validate(const PropValue& value, ErrorPtr* error) const = 0;
+
+ // Makes a full copy of this Constraint instance.
+ virtual std::unique_ptr<Constraint> Clone() const = 0;
+
+ // Makes a copy of the constraint object, marking all the attributes
+ // as inherited from the original definition.
+ virtual std::unique_ptr<Constraint> CloneAsInherited() const = 0;
+
+ // Saves the constraint into the specified JSON |dict| object, representing
+ // the object schema. If |overridden_only| is set to true, then the
+ // inherited constraints will not be added to the schema object.
+ virtual void AddToJsonDict(base::DictionaryValue* dict,
+ bool overridden_only) const;
+
+ // Saves the value of constraint to JSON value. E.g., if the numeric
+ // constraint was defined as {"minimum":20} this will create a JSON value
+ // of 20. The current design implies that each constraint has one value
+ // only. If this assumption changes, this interface needs to be updated
+ // accordingly.
+ virtual std::unique_ptr<base::Value> ToJson() const = 0;
+
+ // Overloaded by the concrete class implementation, it should return the
+ // JSON object property name to store the constraint's value as.
+ // E.g., if the numeric constraint was defined as {"minimum":20} this
+ // method should return "minimum".
+ virtual const char* GetDictKey() const = 0;
+
+ protected:
+ // Static helper methods to format common constraint validation errors.
+ // They fill the |error| object with specific error message.
+ // Since these functions could be used by constraint objects for various
+ // data types, the values used in validation are expected to be
+ // send as strings already.
+ static bool ReportErrorLessThan(ErrorPtr* error,
+ const std::string& val,
+ const std::string& limit);
+ static bool ReportErrorGreaterThan(ErrorPtr* error,
+ const std::string& val,
+ const std::string& limit);
+
+ static bool ReportErrorNotOneOf(ErrorPtr* error,
+ const std::string& val,
+ const std::vector<std::string>& values);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Constraint);
+};
+
+// ConstraintMinMaxBase is a base class for numeric Minimum and Maximum
+// constraints.
+template <typename T>
+class ConstraintMinMaxBase : public Constraint {
+ public:
+ explicit ConstraintMinMaxBase(const InheritableAttribute<T>& limit)
+ : limit_(limit) {}
+ explicit ConstraintMinMaxBase(const T& limit) : limit_(limit) {}
+
+ // Implementation of Constraint::HasOverriddenAttributes().
+ bool HasOverriddenAttributes() const override { return !limit_.is_inherited; }
+
+ // Implementation of Constraint::ToJson().
+ std::unique_ptr<base::Value> ToJson() const override {
+ return TypedValueToJson(limit_.value);
+ }
+
+ // Stores the upper/lower value limit for maximum/minimum constraint.
+ // |limit_.is_inherited| indicates whether the constraint is inherited
+ // from base schema or overridden.
+ InheritableAttribute<T> limit_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConstraintMinMaxBase);
+};
+
+// Implementation of Minimum value constraint for Integer/Double types.
+template <typename T>
+class ConstraintMin : public ConstraintMinMaxBase<T> {
+ public:
+ explicit ConstraintMin(const InheritableAttribute<T>& limit)
+ : ConstraintMinMaxBase<T>(limit) {}
+ explicit ConstraintMin(const T& limit) : ConstraintMinMaxBase<T>(limit) {}
+
+ // Implementation of Constraint::GetType().
+ ConstraintType GetType() const override { return ConstraintType::Min; }
+
+ // Implementation of Constraint::Validate().
+ bool Validate(const PropValue& value, ErrorPtr* error) const override {
+ const T& v = static_cast<const TypedValueBase<T>&>(value).GetValue();
+ if (v < this->limit_.value) {
+ return this->ReportErrorLessThan(error, std::to_string(v),
+ std::to_string(this->limit_.value));
+ }
+ return true;
+ }
+
+ // Implementation of Constraint::Clone().
+ std::unique_ptr<Constraint> Clone() const override {
+ return std::unique_ptr<Constraint>{new ConstraintMin{this->limit_}};
+ }
+
+ // Implementation of Constraint::CloneAsInherited().
+ std::unique_ptr<Constraint> CloneAsInherited() const override {
+ return std::unique_ptr<Constraint>{new ConstraintMin{this->limit_.value}};
+ }
+
+ // Implementation of Constraint::GetDictKey().
+ const char* GetDictKey() const override {
+ return commands::attributes::kNumeric_Min;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConstraintMin);
+};
+
+// Implementation of Maximum value constraint for Integer/Double types.
+template <typename T>
+class ConstraintMax : public ConstraintMinMaxBase<T> {
+ public:
+ explicit ConstraintMax(const InheritableAttribute<T>& limit)
+ : ConstraintMinMaxBase<T>(limit) {}
+ explicit ConstraintMax(const T& limit) : ConstraintMinMaxBase<T>(limit) {}
+
+ // Implementation of Constraint::GetType().
+ ConstraintType GetType() const override { return ConstraintType::Max; }
+
+ // Implementation of Constraint::Validate().
+ bool Validate(const PropValue& value, ErrorPtr* error) const override {
+ const T& v = static_cast<const TypedValueBase<T>&>(value).GetValue();
+ if (v > this->limit_.value)
+ return this->ReportErrorGreaterThan(error, std::to_string(v),
+ std::to_string(this->limit_.value));
+ return true;
+ }
+
+ // Implementation of Constraint::Clone().
+ std::unique_ptr<Constraint> Clone() const override {
+ return std::unique_ptr<Constraint>{new ConstraintMax{this->limit_}};
+ }
+
+ // Implementation of Constraint::CloneAsInherited().
+ std::unique_ptr<Constraint> CloneAsInherited() const override {
+ return std::unique_ptr<Constraint>{new ConstraintMax{this->limit_.value}};
+ }
+
+ // Implementation of Constraint::GetDictKey().
+ const char* GetDictKey() const override {
+ return commands::attributes::kNumeric_Max;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConstraintMax);
+};
+
+// ConstraintStringLength is a base class for Minimum/Maximum string length
+// constraints, similar to ConstraintMinMaxBase of numeric types.
+class ConstraintStringLength : public Constraint {
+ public:
+ explicit ConstraintStringLength(const InheritableAttribute<int>& limit);
+ explicit ConstraintStringLength(int limit);
+
+ // Implementation of Constraint::HasOverriddenAttributes().
+ bool HasOverriddenAttributes() const override;
+ // Implementation of Constraint::ToJson().
+ std::unique_ptr<base::Value> ToJson() const override;
+
+ // Stores the upper/lower value limit for string length constraint.
+ // |limit_.is_inherited| indicates whether the constraint is inherited
+ // from base schema or overridden.
+ InheritableAttribute<int> limit_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConstraintStringLength);
+};
+
+// Implementation of Minimum string length constraint.
+class ConstraintStringLengthMin : public ConstraintStringLength {
+ public:
+ explicit ConstraintStringLengthMin(const InheritableAttribute<int>& limit);
+ explicit ConstraintStringLengthMin(int limit);
+
+ // Implementation of Constraint::GetType().
+ ConstraintType GetType() const override {
+ return ConstraintType::StringLengthMin;
+ }
+
+ // Implementation of Constraint::Validate().
+ bool Validate(const PropValue& value, ErrorPtr* error) const override;
+
+ // Implementation of Constraint::Clone().
+ std::unique_ptr<Constraint> Clone() const override;
+
+ // Implementation of Constraint::CloneAsInherited().
+ std::unique_ptr<Constraint> CloneAsInherited() const override;
+ // Implementation of Constraint::GetDictKey().
+ const char* GetDictKey() const override {
+ return commands::attributes::kString_MinLength;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConstraintStringLengthMin);
+};
+
+// Implementation of Maximum string length constraint.
+class ConstraintStringLengthMax : public ConstraintStringLength {
+ public:
+ explicit ConstraintStringLengthMax(const InheritableAttribute<int>& limit);
+ explicit ConstraintStringLengthMax(int limit);
+
+ // Implementation of Constraint::GetType().
+ ConstraintType GetType() const override {
+ return ConstraintType::StringLengthMax;
+ }
+
+ // Implementation of Constraint::Validate().
+ bool Validate(const PropValue& value, ErrorPtr* error) const override;
+
+ // Implementation of Constraint::Clone().
+ std::unique_ptr<Constraint> Clone() const override;
+
+ // Implementation of Constraint::CloneAsInherited().
+ std::unique_ptr<Constraint> CloneAsInherited() const override;
+
+ // Implementation of Constraint::GetDictKey().
+ const char* GetDictKey() const override {
+ return commands::attributes::kString_MaxLength;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConstraintStringLengthMax);
+};
+
+// Implementation of OneOf constraint for different data types.
+class ConstraintOneOf : public Constraint {
+ public:
+ explicit ConstraintOneOf(InheritableAttribute<ValueVector> set);
+ explicit ConstraintOneOf(ValueVector set);
+
+ // Implementation of Constraint::GetType().
+ ConstraintType GetType() const override { return ConstraintType::OneOf; }
+
+ // Implementation of Constraint::HasOverriddenAttributes().
+ bool HasOverriddenAttributes() const override { return !set_.is_inherited; }
+
+ // Implementation of Constraint::Validate().
+ bool Validate(const PropValue& value, ErrorPtr* error) const override;
+
+ // Implementation of Constraint::Clone().
+ std::unique_ptr<Constraint> Clone() const override;
+
+ // Implementation of Constraint::CloneAsInherited().
+ std::unique_ptr<Constraint> CloneAsInherited() const override;
+
+ // Implementation of Constraint::ToJson().
+ std::unique_ptr<base::Value> ToJson() const override;
+
+ // Implementation of Constraint::GetDictKey().
+ const char* GetDictKey() const override;
+
+ // Stores the list of acceptable values for the parameter.
+ // |set_.is_inherited| indicates whether the constraint is inherited
+ // from base schema or overridden.
+ InheritableAttribute<ValueVector> set_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConstraintOneOf);
+};
+
+} // namespace weave
+
+#endif // LIBWEAVE_SRC_COMMANDS_PROP_CONSTRAINTS_H_
diff --git a/src/commands/prop_types.cc b/src/commands/prop_types.cc
new file mode 100644
index 0000000..88a53bd
--- /dev/null
+++ b/src/commands/prop_types.cc
@@ -0,0 +1,653 @@
+// Copyright 2015 The Weave 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 "src/commands/prop_types.h"
+
+#include <algorithm>
+#include <limits>
+#include <set>
+
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+#include <base/values.h>
+
+#include "src/commands/object_schema.h"
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_constants.h"
+
+namespace weave {
+
+// PropType -------------------------------------------------------------------
+PropType::PropType() {}
+
+PropType::~PropType() {}
+
+std::string PropType::GetTypeAsString() const {
+ return GetTypeStringFromType(GetType());
+}
+
+bool PropType::HasOverriddenAttributes() const {
+ if (default_.value && !default_.is_inherited)
+ return true;
+
+ for (const auto& pair : constraints_) {
+ if (pair.second->HasOverriddenAttributes())
+ return true;
+ }
+ return false;
+}
+
+bool PropType::IsRequired() const {
+ return required_.value;
+}
+
+void PropType::MakeRequired(bool required) {
+ required_.value = required;
+ required_.is_inherited = false;
+}
+
+std::unique_ptr<base::Value> PropType::ToJson(bool full_schema,
+ bool in_command_def) const {
+ // Determine if we need to output "isRequired" attribute.
+ const bool include_required = in_command_def && !required_.is_inherited;
+
+ // If we must include "isRequired" attribute, then treat this as "full schema"
+ // request because there could be cases where we have just this attribute and
+ // won't be able to infer the type from the constraints only.
+ if (include_required)
+ full_schema = true;
+
+ if (!full_schema && !HasOverriddenAttributes()) {
+ if (based_on_schema_)
+ return std::unique_ptr<base::Value>(new base::DictionaryValue);
+ return TypedValueToJson(GetTypeAsString());
+ }
+
+ std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+ if (full_schema) {
+ // If we are asked for full_schema definition, then we need to output every
+ // parameter property, including the "type", and any constraints.
+ // So, we write the "type" only if asked for full schema.
+ // Otherwise we will be able to infer the parameter type based on
+ // the constraints and their types.
+ // That is, the following JSONs could possibly define a parameter:
+ // {'type':'integer'} -> explicit "integer" with no constraints
+ // {'minimum':10} -> no type specified, but since we have "minimum"
+ // and 10 is integer, than this is an integer
+ // parameter with min constraint.
+ // {'enum':[1,2,3]} -> integer with OneOf constraint.
+ // And so is this: [1,2,3] -> an array of ints assume it's an "int" enum.
+ dict->SetString(commands::attributes::kType, GetTypeAsString());
+ }
+
+ if (!full_schema && constraints_.size() == 1) {
+ // If we are not asked for full schema, and we have only one constraint
+ // which is OneOf, we short-circuit the whole thing and return just
+ // the array [1,2,3] instead of an object with "enum" property like:
+ // {'enum':[1,2,3]}
+ auto p = constraints_.find(ConstraintType::OneOf);
+ if (p != constraints_.end()) {
+ return p->second->ToJson();
+ }
+ }
+
+ for (const auto& pair : constraints_)
+ pair.second->AddToJsonDict(dict.get(), !full_schema);
+
+ if (default_.value && (full_schema || !default_.is_inherited)) {
+ auto def_val = default_.value->ToJson();
+ CHECK(def_val);
+ dict->Set(commands::attributes::kDefault, def_val.release());
+ }
+
+ if (include_required)
+ dict->SetBoolean(commands::attributes::kIsRequired, required_.value);
+ return std::unique_ptr<base::Value>(dict.release());
+}
+
+std::unique_ptr<PropType> PropType::Clone() const {
+ auto cloned = PropType::Create(GetType());
+ cloned->based_on_schema_ = based_on_schema_;
+ for (const auto& pair : constraints_) {
+ cloned->constraints_.emplace(pair.first, pair.second->Clone());
+ }
+ cloned->default_.is_inherited = default_.is_inherited;
+ if (default_.value)
+ cloned->default_.value = default_.value->Clone();
+ cloned->required_ = required_;
+ return cloned;
+}
+
+bool PropType::FromJson(const base::DictionaryValue* value,
+ const PropType* base_schema,
+ ErrorPtr* error) {
+ if (base_schema && base_schema->GetType() != GetType()) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kPropTypeChanged,
+ "Redefining a property of type %s as %s",
+ base_schema->GetTypeAsString().c_str(),
+ GetTypeAsString().c_str());
+ return false;
+ }
+ based_on_schema_ = (base_schema != nullptr);
+ constraints_.clear();
+ // Add the well-known object properties first (like "type", "displayName",
+ // "default") to the list of "processed" keys so we do not complain about them
+ // when we check for unknown/unexpected keys below.
+ std::set<std::string> processed_keys{
+ commands::attributes::kType, commands::attributes::kDisplayName,
+ commands::attributes::kDefault, commands::attributes::kIsRequired,
+ };
+ if (!ObjectSchemaFromJson(value, base_schema, &processed_keys, error))
+ return false;
+ if (base_schema) {
+ for (const auto& pair : base_schema->GetConstraints()) {
+ constraints_.emplace(pair.first, pair.second->CloneAsInherited());
+ }
+ }
+ if (!ConstraintsFromJson(value, &processed_keys, error))
+ return false;
+
+ // Now make sure there are no unexpected/unknown keys in the property schema
+ // definition object.
+ base::DictionaryValue::Iterator iter(*value);
+ while (!iter.IsAtEnd()) {
+ std::string key = iter.key();
+ if (processed_keys.find(key) == processed_keys.end()) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kUnknownProperty,
+ "Unexpected property '%s'", key.c_str());
+ return false;
+ }
+ iter.Advance();
+ }
+
+ // Read the "isRequired" attribute, if specified.
+ bool required = false;
+ if (value->GetBoolean(commands::attributes::kIsRequired, &required)) {
+ required_.value = required;
+ required_.is_inherited = false;
+ } else if (base_schema) {
+ // If we have the base schema, inherit the type's required value from it.
+ if (base_schema->required_.value)
+ required_.value = base_schema->required_.value;
+ required_.is_inherited = true;
+ }
+
+ // Read the default value, if specified.
+ // We need to do this last since the current type definition must be complete,
+ // so we can parse and validate the value of the default.
+ const base::Value* defval = nullptr; // Owned by value
+ if (value->GetWithoutPathExpansion(commands::attributes::kDefault, &defval)) {
+ std::unique_ptr<PropValue> prop_value = CreatePropValue(*defval, error);
+ if (!prop_value) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidPropValue,
+ "Invalid value for property '%s'",
+ commands::attributes::kDefault);
+ return false;
+ }
+ default_.value = std::move(prop_value);
+ default_.is_inherited = false;
+ } else if (base_schema) {
+ // If we have the base schema, inherit the type's default value from it.
+ // It doesn't matter if the base schema actually has a default value
+ // specified or not. If it doesn't, then the current type definition will
+ // have no default value set either (|default_.value| is a unique_ptr to
+ // PropValue, which can be set to nullptr).
+ if (base_schema->default_.value)
+ default_.value = base_schema->default_.value->Clone();
+ default_.is_inherited = true;
+ }
+ return true;
+}
+
+void PropType::AddConstraint(std::unique_ptr<Constraint> constraint) {
+ constraints_[constraint->GetType()] = std::move(constraint);
+}
+
+void PropType::RemoveConstraint(ConstraintType constraint_type) {
+ constraints_.erase(constraint_type);
+}
+
+void PropType::RemoveAllConstraints() {
+ constraints_.clear();
+}
+
+const Constraint* PropType::GetConstraint(
+ ConstraintType constraint_type) const {
+ auto p = constraints_.find(constraint_type);
+ return p != constraints_.end() ? p->second.get() : nullptr;
+}
+
+Constraint* PropType::GetConstraint(ConstraintType constraint_type) {
+ auto p = constraints_.find(constraint_type);
+ return p != constraints_.end() ? p->second.get() : nullptr;
+}
+
+bool PropType::ValidateConstraints(const PropValue& value,
+ ErrorPtr* error) const {
+ for (const auto& pair : constraints_) {
+ if (!pair.second->Validate(value, error))
+ return false;
+ }
+ return true;
+}
+
+const PropType::TypeMap& PropType::GetTypeMap() {
+ static TypeMap map = {
+ {ValueType::Int, "integer"}, {ValueType::Double, "number"},
+ {ValueType::String, "string"}, {ValueType::Boolean, "boolean"},
+ {ValueType::Object, "object"}, {ValueType::Array, "array"},
+ };
+ return map;
+}
+
+std::string PropType::GetTypeStringFromType(ValueType type) {
+ for (const auto& pair : GetTypeMap()) {
+ if (pair.first == type)
+ return pair.second;
+ }
+ LOG(FATAL) << "Type map is missing a type";
+ return std::string();
+}
+
+bool PropType::GetTypeFromTypeString(const std::string& name, ValueType* type) {
+ for (const auto& pair : GetTypeMap()) {
+ if (pair.second == name) {
+ *type = pair.first;
+ return true;
+ }
+ }
+ return false;
+}
+
+std::unique_ptr<PropType> PropType::Create(ValueType type) {
+ PropType* prop = nullptr;
+ switch (type) {
+ case ValueType::Int:
+ prop = new IntPropType;
+ break;
+ case ValueType::Double:
+ prop = new DoublePropType;
+ break;
+ case ValueType::String:
+ prop = new StringPropType;
+ break;
+ case ValueType::Boolean:
+ prop = new BooleanPropType;
+ break;
+ case ValueType::Object:
+ prop = new ObjectPropType;
+ break;
+ case ValueType::Array:
+ prop = new ArrayPropType;
+ break;
+ }
+ return std::unique_ptr<PropType>(prop);
+}
+
+template <typename T>
+static std::unique_ptr<Constraint> LoadOneOfConstraint(
+ const base::DictionaryValue* value,
+ const PropType* prop_type,
+ ErrorPtr* error) {
+ std::unique_ptr<Constraint> constraint;
+ const base::Value* list = nullptr; // Owned by |value|
+ CHECK(value->Get(commands::attributes::kOneOf_Enum, &list))
+ << "'enum' property missing in JSON dictionary";
+ ValueVector choice_list;
+ ArrayPropType array_type;
+ array_type.SetItemType(prop_type->Clone());
+ if (!TypedValueFromJson(list, &array_type, &choice_list, error))
+ return constraint;
+ InheritableAttribute<ValueVector> val(std::move(choice_list), false);
+ constraint.reset(new ConstraintOneOf{std::move(val)});
+ return constraint;
+}
+
+template <class ConstraintClass, typename T>
+static std::unique_ptr<Constraint> LoadMinMaxConstraint(
+ const char* dict_key,
+ const base::DictionaryValue* value,
+ ErrorPtr* error) {
+ std::unique_ptr<Constraint> constraint;
+ InheritableAttribute<T> limit;
+
+ const base::Value* src_val = nullptr;
+ CHECK(value->Get(dict_key, &src_val)) << "Unable to get min/max constraints";
+ if (!TypedValueFromJson(src_val, nullptr, &limit.value, error))
+ return constraint;
+ limit.is_inherited = false;
+
+ constraint.reset(new ConstraintClass{limit});
+ return constraint;
+}
+
+// PropTypeBase ----------------------------------------------------------------
+
+template <class Derived, class Value, typename T>
+bool PropTypeBase<Derived, Value, T>::ConstraintsFromJson(
+ const base::DictionaryValue* value,
+ std::set<std::string>* processed_keys,
+ ErrorPtr* error) {
+ if (!PropType::ConstraintsFromJson(value, processed_keys, error))
+ return false;
+
+ if (value->HasKey(commands::attributes::kOneOf_Enum)) {
+ auto type = Clone();
+ type->RemoveAllConstraints();
+ auto constraint = LoadOneOfConstraint<T>(value, type.get(), error);
+ if (!constraint)
+ return false;
+ this->AddConstraint(std::move(constraint));
+ this->RemoveConstraint(ConstraintType::Min);
+ this->RemoveConstraint(ConstraintType::Max);
+ processed_keys->insert(commands::attributes::kOneOf_Enum);
+ }
+
+ return true;
+}
+
+// NumericPropTypeBase ---------------------------------------------------------
+
+template <class Derived, class Value, typename T>
+bool NumericPropTypeBase<Derived, Value, T>::ConstraintsFromJson(
+ const base::DictionaryValue* value,
+ std::set<std::string>* processed_keys,
+ ErrorPtr* error) {
+ if (!Base::ConstraintsFromJson(value, processed_keys, error))
+ return false;
+
+ if (processed_keys->find(commands::attributes::kOneOf_Enum) ==
+ processed_keys->end()) {
+ // Process min/max constraints only if "enum" constraint wasn't already
+ // specified.
+ if (value->HasKey(commands::attributes::kNumeric_Min)) {
+ auto constraint = LoadMinMaxConstraint<ConstraintMin<T>, T>(
+ commands::attributes::kNumeric_Min, value, error);
+ if (!constraint)
+ return false;
+ this->AddConstraint(std::move(constraint));
+ this->RemoveConstraint(ConstraintType::OneOf);
+ processed_keys->insert(commands::attributes::kNumeric_Min);
+ }
+ if (value->HasKey(commands::attributes::kNumeric_Max)) {
+ auto constraint = LoadMinMaxConstraint<ConstraintMax<T>, T>(
+ commands::attributes::kNumeric_Max, value, error);
+ if (!constraint)
+ return false;
+ this->AddConstraint(std::move(constraint));
+ this->RemoveConstraint(ConstraintType::OneOf);
+ processed_keys->insert(commands::attributes::kNumeric_Max);
+ }
+ }
+
+ return true;
+}
+
+// StringPropType -------------------------------------------------------------
+
+bool StringPropType::ConstraintsFromJson(const base::DictionaryValue* value,
+ std::set<std::string>* processed_keys,
+ ErrorPtr* error) {
+ if (!Base::ConstraintsFromJson(value, processed_keys, error))
+ return false;
+
+ if (processed_keys->find(commands::attributes::kOneOf_Enum) ==
+ processed_keys->end()) {
+ // Process min/max constraints only if "enum" constraint wasn't already
+ // specified.
+ if (value->HasKey(commands::attributes::kString_MinLength)) {
+ auto constraint = LoadMinMaxConstraint<ConstraintStringLengthMin, int>(
+ commands::attributes::kString_MinLength, value, error);
+ if (!constraint)
+ return false;
+ AddConstraint(std::move(constraint));
+ RemoveConstraint(ConstraintType::OneOf);
+ processed_keys->insert(commands::attributes::kString_MinLength);
+ }
+ if (value->HasKey(commands::attributes::kString_MaxLength)) {
+ auto constraint = LoadMinMaxConstraint<ConstraintStringLengthMax, int>(
+ commands::attributes::kString_MaxLength, value, error);
+ if (!constraint)
+ return false;
+ AddConstraint(std::move(constraint));
+ RemoveConstraint(ConstraintType::OneOf);
+ processed_keys->insert(commands::attributes::kString_MaxLength);
+ }
+ }
+ return true;
+}
+
+void StringPropType::AddLengthConstraint(int min_len, int max_len) {
+ InheritableAttribute<int> min_attr(min_len, false);
+ InheritableAttribute<int> max_attr(max_len, false);
+ AddConstraint(std::unique_ptr<ConstraintStringLengthMin>{
+ new ConstraintStringLengthMin{min_attr}});
+ AddConstraint(std::unique_ptr<ConstraintStringLengthMax>{
+ new ConstraintStringLengthMax{max_attr}});
+}
+
+int StringPropType::GetMinLength() const {
+ auto slc = static_cast<const ConstraintStringLength*>(
+ GetConstraint(ConstraintType::StringLengthMin));
+ return slc ? slc->limit_.value : 0;
+}
+
+int StringPropType::GetMaxLength() const {
+ auto slc = static_cast<const ConstraintStringLength*>(
+ GetConstraint(ConstraintType::StringLengthMax));
+ return slc ? slc->limit_.value : std::numeric_limits<int>::max();
+}
+
+// ObjectPropType -------------------------------------------------------------
+
+ObjectPropType::ObjectPropType()
+ : object_schema_{ObjectSchema::Create(), false} {}
+
+bool ObjectPropType::HasOverriddenAttributes() const {
+ return PropType::HasOverriddenAttributes() || !object_schema_.is_inherited;
+}
+
+std::unique_ptr<PropType> ObjectPropType::Clone() const {
+ auto cloned = Base::Clone();
+
+ cloned->GetObject()->object_schema_.is_inherited =
+ object_schema_.is_inherited;
+ cloned->GetObject()->object_schema_.value = object_schema_.value->Clone();
+ return cloned;
+}
+
+std::unique_ptr<base::Value> ObjectPropType::ToJson(bool full_schema,
+ bool in_command_def) const {
+ std::unique_ptr<base::Value> value =
+ PropType::ToJson(full_schema, in_command_def);
+ CHECK(value);
+ base::DictionaryValue* dict = nullptr;
+ CHECK(value->GetAsDictionary(&dict)) << "Expecting a JSON object";
+ if (!object_schema_.is_inherited || full_schema) {
+ auto object_schema = object_schema_.value->ToJson(full_schema, false);
+ CHECK(object_schema);
+
+ dict->SetWithoutPathExpansion(commands::attributes::kObject_Properties,
+ object_schema.release());
+ dict->SetBooleanWithoutPathExpansion(
+ commands::attributes::kObject_AdditionalProperties,
+ object_schema_.value->GetExtraPropertiesAllowed());
+ std::unique_ptr<base::ListValue> required{new base::ListValue};
+ for (const auto& pair : object_schema_.value->GetProps()) {
+ if (pair.second->IsRequired())
+ required->AppendString(pair.first);
+ }
+ if (required->GetSize() > 0) {
+ dict->Set(commands::attributes::kObject_Required, required.release());
+ }
+ }
+ return value;
+}
+
+bool ObjectPropType::ObjectSchemaFromJson(const base::DictionaryValue* value,
+ const PropType* base_schema,
+ std::set<std::string>* processed_keys,
+ ErrorPtr* error) {
+ if (!Base::ObjectSchemaFromJson(value, base_schema, processed_keys, error))
+ return false;
+
+ using commands::attributes::kObject_Properties;
+ using commands::attributes::kObject_AdditionalProperties;
+
+ const ObjectSchema* base_object_schema = nullptr;
+ if (base_schema)
+ base_object_schema = base_schema->GetObject()->GetObjectSchemaPtr();
+
+ const base::DictionaryValue* props = nullptr;
+ std::unique_ptr<ObjectSchema> object_schema;
+ bool inherited = false;
+ if (value->GetDictionaryWithoutPathExpansion(kObject_Properties, &props)) {
+ processed_keys->insert(kObject_Properties);
+ object_schema.reset(new ObjectSchema);
+ if (!object_schema->FromJson(props, base_object_schema, error)) {
+ Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidObjectSchema,
+ "Error parsing object property schema");
+ return false;
+ }
+ } else if (base_object_schema) {
+ object_schema = base_object_schema->Clone();
+ inherited = true;
+ } else {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidObjectSchema,
+ "Object type definition must include the "
+ "object schema ('%s' field not found)",
+ kObject_Properties);
+ return false;
+ }
+ bool extra_properties_allowed = false;
+ if (value->GetBooleanWithoutPathExpansion(kObject_AdditionalProperties,
+ &extra_properties_allowed)) {
+ processed_keys->insert(kObject_AdditionalProperties);
+ object_schema->SetExtraPropertiesAllowed(extra_properties_allowed);
+ inherited = false;
+ }
+ const base::Value* required = nullptr;
+ if (value->Get(commands::attributes::kObject_Required, &required)) {
+ processed_keys->insert(commands::attributes::kObject_Required);
+ const base::ListValue* required_list = nullptr;
+ if (!required->GetAsList(&required_list)) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidObjectSchema,
+ "Property '%s' must be an array",
+ commands::attributes::kObject_Required);
+ return false;
+ }
+ for (const base::Value* value : *required_list) {
+ std::string name;
+ if (!value->GetAsString(&name)) {
+ std::string json_value;
+ CHECK(base::JSONWriter::Write(*value, &json_value));
+ Error::AddToPrintf(
+ error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidObjectSchema,
+ "Property '%s' contains invalid element (%s). String expected",
+ commands::attributes::kObject_Required, json_value.c_str());
+ return false;
+ }
+ if (!object_schema->MarkPropRequired(name, error))
+ return false;
+ inherited = false;
+ }
+ }
+ object_schema_.value = std::move(object_schema);
+ object_schema_.is_inherited = inherited;
+
+ return true;
+}
+
+void ObjectPropType::SetObjectSchema(
+ std::unique_ptr<const ObjectSchema> schema) {
+ object_schema_.value = std::move(schema);
+ object_schema_.is_inherited = false;
+}
+
+// ArrayPropType -------------------------------------------------------------
+
+ArrayPropType::ArrayPropType() {}
+
+bool ArrayPropType::HasOverriddenAttributes() const {
+ return PropType::HasOverriddenAttributes() || !item_type_.is_inherited;
+}
+
+std::unique_ptr<PropType> ArrayPropType::Clone() const {
+ auto cloned = Base::Clone();
+
+ cloned->GetArray()->item_type_.is_inherited = item_type_.is_inherited;
+ cloned->GetArray()->item_type_.value = item_type_.value->Clone();
+ return cloned;
+}
+
+std::unique_ptr<base::Value> ArrayPropType::ToJson(bool full_schema,
+ bool in_command_def) const {
+ std::unique_ptr<base::Value> value =
+ PropType::ToJson(full_schema, in_command_def);
+ CHECK(value);
+ if (!item_type_.is_inherited || full_schema) {
+ base::DictionaryValue* dict = nullptr;
+ CHECK(value->GetAsDictionary(&dict)) << "Expecting a JSON object";
+ auto type = item_type_.value->ToJson(full_schema, false);
+ CHECK(type);
+ dict->SetWithoutPathExpansion(commands::attributes::kItems, type.release());
+ }
+ return value;
+}
+
+bool ArrayPropType::ObjectSchemaFromJson(const base::DictionaryValue* value,
+ const PropType* base_schema,
+ std::set<std::string>* processed_keys,
+ ErrorPtr* error) {
+ if (!Base::ObjectSchemaFromJson(value, base_schema, processed_keys, error))
+ return false;
+
+ using commands::attributes::kItems;
+
+ const PropType* base_type = nullptr;
+ if (base_schema)
+ base_type = base_schema->GetArray()->GetItemTypePtr();
+
+ const base::Value* type_value = nullptr;
+ if (value->GetWithoutPathExpansion(kItems, &type_value)) {
+ processed_keys->insert(kItems);
+ auto item_type = ObjectSchema::PropFromJson(*type_value, base_type, error);
+ if (!item_type)
+ return false;
+ if (item_type->GetType() == ValueType::Array) {
+ Error::AddTo(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidObjectSchema,
+ "Arrays of arrays are not supported");
+ return false;
+ }
+ SetItemType(std::move(item_type));
+ } else if (!item_type_.value) {
+ if (base_type) {
+ item_type_.value = base_type->Clone();
+ item_type_.is_inherited = true;
+ } else {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidObjectSchema,
+ "Array type definition must include the "
+ "array item type ('%s' field not found)",
+ kItems);
+ return false;
+ }
+ }
+ return true;
+}
+
+void ArrayPropType::SetItemType(std::unique_ptr<const PropType> item_type) {
+ item_type_.value = std::move(item_type);
+ item_type_.is_inherited = false;
+}
+
+} // namespace weave
diff --git a/src/commands/prop_types.h b/src/commands/prop_types.h
new file mode 100644
index 0000000..f784eb6
--- /dev/null
+++ b/src/commands/prop_types.h
@@ -0,0 +1,352 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_SRC_COMMANDS_PROP_TYPES_H_
+#define LIBWEAVE_SRC_COMMANDS_PROP_TYPES_H_
+
+#include <limits>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <weave/error.h>
+
+#include "src/commands/prop_constraints.h"
+#include "src/commands/prop_values.h"
+
+namespace weave {
+
+class IntPropType;
+class DoublePropType;
+class StringPropType;
+class BooleanPropType;
+class ObjectPropType;
+class ArrayPropType;
+
+// PropType is a base class for all parameter type definition objects.
+// Property definitions of a particular type will derive from this class and
+// provide type-specific implementations.
+class PropType {
+ public:
+ // ConstraintMap is a type alias for a map containing parameter
+ // constraints. It is implemented as a map for fast look-ups of constraints
+ // of particular type. Also it is expected to have at most one constraint
+ // of each type (e.g. it makes no sense to impose two "minimum" constraints
+ // onto a numeric parameter).
+ using ConstraintMap = std::map<ConstraintType, std::unique_ptr<Constraint>>;
+
+ PropType();
+ virtual ~PropType();
+
+ // Gets the parameter type as an enumeration.
+ virtual ValueType GetType() const = 0;
+ // Gets the parameter type as a string.
+ std::string GetTypeAsString() const;
+ // Returns true if this parameter definition inherits a type
+ // definition from a base object schema.
+ bool IsBasedOnSchema() const { return based_on_schema_; }
+ // Returns a default value specified for the type, or nullptr if no default
+ // is available.
+ const PropValue* GetDefaultValue() const { return default_.value.get(); }
+ // Gets the constraints specified for the parameter, if any.
+ const ConstraintMap& GetConstraints() const { return constraints_; }
+ // Returns true if this value is required. Properties are marked as required
+ // by using "isRequired" attribute or listed in "required" array.
+ bool IsRequired() const;
+ // Sets the required attribute to the value of |required| and marks it as
+ // overridden (not-inherited).
+ void MakeRequired(bool required);
+ // Checks if any of the type attributes were overridden from the base
+ // schema definition. If this type does not inherit from a base schema,
+ // this method returns true.
+ // An attribute could be the value of any of the constraints, default
+ // value of a parameter or any other data that may be specified in
+ // parameter type definition in and can be inherited from the base schema.
+ virtual bool HasOverriddenAttributes() const;
+
+ // Type conversion methods. Used in lieu of RTTI and dynamic_cast<>.
+ virtual IntPropType* GetInt() { return nullptr; }
+ virtual IntPropType const* GetInt() const { return nullptr; }
+ virtual DoublePropType* GetDouble() { return nullptr; }
+ virtual DoublePropType const* GetDouble() const { return nullptr; }
+ virtual StringPropType* GetString() { return nullptr; }
+ virtual StringPropType const* GetString() const { return nullptr; }
+ virtual BooleanPropType* GetBoolean() { return nullptr; }
+ virtual BooleanPropType const* GetBoolean() const { return nullptr; }
+ virtual ObjectPropType* GetObject() { return nullptr; }
+ virtual ObjectPropType const* GetObject() const { return nullptr; }
+ virtual ArrayPropType* GetArray() { return nullptr; }
+ virtual ArrayPropType const* GetArray() const { return nullptr; }
+
+ // Makes a full copy of this type definition.
+ virtual std::unique_ptr<PropType> Clone() const;
+
+ // Creates an instance of associated value object.
+ virtual std::unique_ptr<PropValue> CreatePropValue(const base::Value& value,
+ ErrorPtr* error) const = 0;
+
+ // Saves the parameter type definition as a JSON object.
+ // If |full_schema| is set to true, the full type definition is saved,
+ // otherwise only the overridden properties and attributes from the base
+ // schema is saved. That is, inherited properties and attributes are not
+ // saved.
+ // If it fails, returns "nullptr" and fills in the |error| with additional
+ // error information.
+ // |in_command_def| is set to true if the property type describes a
+ // GCD command parameter, otherwise it is for an object property.
+ // Command definitions handle required parameters differently (using
+ // "isRequired" property as opposed to "required" list for object properties).
+ virtual std::unique_ptr<base::Value> ToJson(bool full_schema,
+ bool in_command_def) const;
+ // Parses an JSON parameter type definition. Optional |base_schema| may
+ // specify the base schema type definition this type should be based upon.
+ // If not specified (nullptr), the parameter type is assumed to be a full
+ // definition and any omitted required properties are treated as an error.
+ // Returns true on success, otherwise fills in the |error| with additional
+ // error information.
+ virtual bool FromJson(const base::DictionaryValue* value,
+ const PropType* base_schema,
+ ErrorPtr* error);
+ // Helper function to load object schema from JSON.
+ virtual bool ObjectSchemaFromJson(const base::DictionaryValue* value,
+ const PropType* base_schema,
+ std::set<std::string>* processed_keys,
+ ErrorPtr* error) {
+ return true;
+ }
+ // Helper function to load type-specific constraints from JSON.
+ virtual bool ConstraintsFromJson(const base::DictionaryValue* value,
+ std::set<std::string>* processed_keys,
+ ErrorPtr* error) {
+ return true;
+ }
+
+ // Additional helper static methods to help with converting a type enum
+ // value into a string and back.
+ using TypeMap = std::vector<std::pair<ValueType, std::string>>;
+ // Returns a list of value types and corresponding type names.
+ static const TypeMap& GetTypeMap();
+ // Gets the type name string for the given type.
+ static std::string GetTypeStringFromType(ValueType type);
+ // Finds the type for the given type name. Returns true on success.
+ static bool GetTypeFromTypeString(const std::string& name, ValueType* type);
+
+ // Creates an instance of PropType-derived class for the specified
+ // parameter type.
+ static std::unique_ptr<PropType> Create(ValueType type);
+
+ // Adds a constraint to the type definition.
+ void AddConstraint(std::unique_ptr<Constraint> constraint);
+ // Removes a constraint of given type, if it exists.
+ void RemoveConstraint(ConstraintType constraint_type);
+ // Removes all constraints.
+ void RemoveAllConstraints();
+
+ // Finds a constraint of given type. Returns nullptr if not found.
+ const Constraint* GetConstraint(ConstraintType constraint_type) const;
+ Constraint* GetConstraint(ConstraintType constraint_type);
+
+ // Validates the given value against all the constraints.
+ bool ValidateConstraints(const PropValue& value, ErrorPtr* error) const;
+
+ protected:
+ friend class StatePackage;
+ virtual std::unique_ptr<PropValue> CreateDefaultValue() const = 0;
+
+ // Specifies if this parameter definition is derived from a base
+ // object schema.
+ bool based_on_schema_ = false;
+ // A list of constraints specified for the parameter.
+ ConstraintMap constraints_;
+ // The default value specified for the parameter, if any. If the default
+ // value is present, the parameter is treated as optional and the default
+ // value is used if the parameter value is omitted when sending a command.
+ // Otherwise the parameter is treated as required and, if it is omitted,
+ // this is treated as an error.
+ InheritableAttribute<std::unique_ptr<PropValue>> default_;
+ // Specifies whether the parameter/property is required and must be specified
+ // (either directly, or by the default value being provided in the schema).
+ // Non-required parameters can be omitted completely and their values will not
+ // be present in the object instance.
+ InheritableAttribute<bool> required_;
+};
+
+// Base class for all the derived concrete implementations of property
+// type classes. Provides implementations for common methods of PropType base.
+template <class Derived, class Value, typename T>
+class PropTypeBase : public PropType {
+ public:
+ // Overrides from PropType.
+ ValueType GetType() const override { return GetValueType<T>(); }
+
+ std::unique_ptr<PropValue> CreatePropValue(const base::Value& value,
+ ErrorPtr* error) const override {
+ return CreateValue(value, error);
+ }
+
+ std::unique_ptr<Value> CreateValue(const base::Value& value,
+ ErrorPtr* error) const {
+ return Value::CreateFromJson(value, *this, error);
+ }
+
+ bool ConstraintsFromJson(const base::DictionaryValue* value,
+ std::set<std::string>* processed_keys,
+ ErrorPtr* error) override;
+
+ protected:
+ std::unique_ptr<PropValue> CreateDefaultValue() const override {
+ if (GetDefaultValue())
+ return GetDefaultValue()->Clone();
+ return std::unique_ptr<PropValue>{new Value{*this}};
+ }
+};
+
+// Helper base class for Int and Double parameter types.
+template <class Derived, class Value, typename T>
+class NumericPropTypeBase : public PropTypeBase<Derived, Value, T> {
+ public:
+ using Base = PropTypeBase<Derived, Value, T>;
+ bool ConstraintsFromJson(const base::DictionaryValue* value,
+ std::set<std::string>* processed_keys,
+ ErrorPtr* error) override;
+
+ // Helper method to set and obtain a min/max constraint values.
+ // Used mostly for unit testing.
+ void AddMinMaxConstraint(T min_value, T max_value) {
+ InheritableAttribute<T> min_attr(min_value, false);
+ InheritableAttribute<T> max_attr(max_value, false);
+ this->AddConstraint(
+ std::unique_ptr<ConstraintMin<T>>{new ConstraintMin<T>{min_attr}});
+ this->AddConstraint(
+ std::unique_ptr<ConstraintMax<T>>{new ConstraintMax<T>{max_attr}});
+ }
+ T GetMinValue() const {
+ auto mmc = static_cast<const ConstraintMin<T>*>(
+ this->GetConstraint(ConstraintType::Min));
+ return mmc ? mmc->limit_.value : std::numeric_limits<T>::lowest();
+ }
+ T GetMaxValue() const {
+ auto mmc = static_cast<const ConstraintMax<T>*>(
+ this->GetConstraint(ConstraintType::Max));
+ return mmc ? mmc->limit_.value : (std::numeric_limits<T>::max)();
+ }
+};
+
+// Property definition of Integer type.
+class IntPropType : public NumericPropTypeBase<IntPropType, IntValue, int> {
+ public:
+ // Overrides from the PropType base class.
+ IntPropType* GetInt() override { return this; }
+ IntPropType const* GetInt() const override { return this; }
+};
+
+// Property definition of Number type.
+class DoublePropType
+ : public NumericPropTypeBase<DoublePropType, DoubleValue, double> {
+ public:
+ // Overrides from the PropType base class.
+ DoublePropType* GetDouble() override { return this; }
+ DoublePropType const* GetDouble() const override { return this; }
+};
+
+// Property definition of String type.
+class StringPropType
+ : public PropTypeBase<StringPropType, StringValue, std::string> {
+ public:
+ using Base = PropTypeBase<StringPropType, StringValue, std::string>;
+ // Overrides from the PropType base class.
+ StringPropType* GetString() override { return this; }
+ StringPropType const* GetString() const override { return this; }
+
+ bool ConstraintsFromJson(const base::DictionaryValue* value,
+ std::set<std::string>* processed_keys,
+ ErrorPtr* error) override;
+
+ // Helper methods to add and inspect simple constraints.
+ // Used mostly for unit testing.
+ void AddLengthConstraint(int min_len, int max_len);
+ int GetMinLength() const;
+ int GetMaxLength() const;
+};
+
+// Property definition of Boolean type.
+class BooleanPropType
+ : public PropTypeBase<BooleanPropType, BooleanValue, bool> {
+ public:
+ // Overrides from the PropType base class.
+ BooleanPropType* GetBoolean() override { return this; }
+ BooleanPropType const* GetBoolean() const override { return this; }
+};
+
+// Parameter definition of Object type.
+class ObjectPropType
+ : public PropTypeBase<ObjectPropType, ObjectValue, ValueMap> {
+ public:
+ using Base = PropTypeBase<ObjectPropType, ObjectValue, ValueMap>;
+ ObjectPropType();
+
+ // Overrides from the ParamType base class.
+ bool HasOverriddenAttributes() const override;
+
+ ObjectPropType* GetObject() override { return this; }
+ ObjectPropType const* GetObject() const override { return this; }
+
+ std::unique_ptr<PropType> Clone() const override;
+
+ std::unique_ptr<base::Value> ToJson(bool full_schema,
+ bool in_command_def) const override;
+ bool ObjectSchemaFromJson(const base::DictionaryValue* value,
+ const PropType* base_schema,
+ std::set<std::string>* processed_keys,
+ ErrorPtr* error) override;
+
+ // Returns a schema for Object-type parameter.
+ inline const ObjectSchema* GetObjectSchemaPtr() const {
+ return object_schema_.value.get();
+ }
+ void SetObjectSchema(std::unique_ptr<const ObjectSchema> schema);
+
+ private:
+ InheritableAttribute<std::unique_ptr<const ObjectSchema>> object_schema_;
+};
+
+// Parameter definition of Array type.
+class ArrayPropType
+ : public PropTypeBase<ArrayPropType, ArrayValue, ValueVector> {
+ public:
+ using Base = PropTypeBase<ArrayPropType, ArrayValue, ValueVector>;
+ ArrayPropType();
+
+ // Overrides from the ParamType base class.
+ bool HasOverriddenAttributes() const override;
+
+ ArrayPropType* GetArray() override { return this; }
+ ArrayPropType const* GetArray() const override { return this; }
+
+ std::unique_ptr<PropType> Clone() const override;
+
+ std::unique_ptr<base::Value> ToJson(bool full_schema,
+ bool in_command_def) const override;
+
+ bool ObjectSchemaFromJson(const base::DictionaryValue* value,
+ const PropType* base_schema,
+ std::set<std::string>* processed_keys,
+ ErrorPtr* error) override;
+
+ // Returns a type for Array elements.
+ inline const PropType* GetItemTypePtr() const {
+ return item_type_.value.get();
+ }
+ void SetItemType(std::unique_ptr<const PropType> item_type);
+
+ private:
+ InheritableAttribute<std::unique_ptr<const PropType>> item_type_;
+};
+
+} // namespace weave
+
+#endif // LIBWEAVE_SRC_COMMANDS_PROP_TYPES_H_
diff --git a/src/commands/prop_values.cc b/src/commands/prop_values.cc
new file mode 100644
index 0000000..6f30bee
--- /dev/null
+++ b/src/commands/prop_values.cc
@@ -0,0 +1,17 @@
+// Copyright 2015 The Weave 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 "src/commands/prop_values.h"
+
+#include "src/commands/prop_types.h"
+
+namespace weave {
+
+PropValue::PropValue(const PropType& type) : type_{type.Clone()} {}
+
+PropValue::PropValue(const PropValue& other) : PropValue{*other.type_} {}
+
+PropValue::~PropValue() {}
+
+} // namespace weave
diff --git a/src/commands/prop_values.h b/src/commands/prop_values.h
new file mode 100644
index 0000000..f0a401e
--- /dev/null
+++ b/src/commands/prop_values.h
@@ -0,0 +1,250 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_SRC_COMMANDS_PROP_VALUES_H_
+#define LIBWEAVE_SRC_COMMANDS_PROP_VALUES_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <weave/error.h>
+
+#include "src/commands/schema_utils.h"
+
+namespace base {
+class Value;
+class DictionaryValue;
+} // namespace base
+
+namespace weave {
+
+// Enumeration to indicate supported command parameter types.
+enum class ValueType {
+ Int,
+ Double,
+ String,
+ Boolean,
+ Object,
+ Array,
+};
+
+class PropValue;
+class IntValue;
+class DoubleValue;
+class StringValue;
+class BooleanValue;
+class ObjectValue;
+class ArrayValue;
+
+class PropType;
+
+// Helper methods to get the parameter type enum value for the given
+// native C++ data representation.
+// The generic GetValueType<T>() is undefined, however particular
+// type specializations return the appropriate ValueType.
+template <typename T>
+ValueType GetValueType(); // Undefined.
+template <>
+inline ValueType GetValueType<int>() {
+ return ValueType::Int;
+}
+template <>
+inline ValueType GetValueType<double>() {
+ return ValueType::Double;
+}
+template <>
+inline ValueType GetValueType<std::string>() {
+ return ValueType::String;
+}
+template <>
+inline ValueType GetValueType<bool>() {
+ return ValueType::Boolean;
+}
+template <>
+inline ValueType GetValueType<ValueMap>() {
+ return ValueType::Object;
+}
+template <>
+inline ValueType GetValueType<ValueVector>() {
+ return ValueType::Array;
+}
+
+// The base class for property values.
+// Concrete value classes of various types will be derived from this base.
+// A property value is the actual command parameter value (or a concrete value
+// that can be used in constraints and presets). The PropValue is mostly
+// just parsed content of base::Value when a command is dispatched, however
+// it does have some additional functionality:
+// - it has a reference to the type definition (PropType) which is used
+// when validating the value, especially for "object" types.
+// - it can be compared with another instances of values of the same type.
+// This is used to validate the values against "enum"/"one of" constraints.
+class PropValue {
+ public:
+ // Only CreateDefaultValue should use this constructor.
+ explicit PropValue(const PropType& type);
+ virtual ~PropValue();
+
+ // Gets the type of the value.
+ virtual ValueType GetType() const = 0;
+
+ // Type conversion methods. Used in lieu of RTTI and dynamic_cast<>.
+ virtual IntValue* GetInt() { return nullptr; }
+ virtual IntValue const* GetInt() const { return nullptr; }
+ virtual DoubleValue* GetDouble() { return nullptr; }
+ virtual DoubleValue const* GetDouble() const { return nullptr; }
+ virtual StringValue* GetString() { return nullptr; }
+ virtual StringValue const* GetString() const { return nullptr; }
+ virtual BooleanValue* GetBoolean() { return nullptr; }
+ virtual BooleanValue const* GetBoolean() const { return nullptr; }
+ virtual ObjectValue* GetObject() { return nullptr; }
+ virtual ObjectValue const* GetObject() const { return nullptr; }
+ virtual ArrayValue* GetArray() { return nullptr; }
+ virtual ArrayValue const* GetArray() const { return nullptr; }
+
+ // Makes a full copy of this value class.
+ virtual std::unique_ptr<PropValue> Clone() const = 0;
+
+ // Saves the value as a JSON object. Never fails.
+ virtual std::unique_ptr<base::Value> ToJson() const = 0;
+
+ // Return the type definition of this value.
+ const PropType* GetPropType() const { return type_.get(); }
+ // Compares two values and returns true if they are equal.
+ virtual bool IsEqual(const PropValue* value) const = 0;
+
+ protected:
+ // Special out-of-line constructor to help implement PropValue::Clone().
+ // That method needs to clone the underlying type but can't do this in this
+ // header file since PropType is just forward-declared (it needs PropValue
+ // fully defined in its own inner workings).
+ explicit PropValue(const PropValue& other);
+
+ private:
+ std::unique_ptr<const PropType> type_;
+};
+
+// A helper template base class for implementing value classes.
+template <typename T>
+class TypedValueBase : public PropValue {
+ public:
+ using PropValue::PropValue;
+
+ // Overrides from PropValue base class.
+ ValueType GetType() const override { return GetValueType<T>(); }
+
+ std::unique_ptr<base::Value> ToJson() const override {
+ return TypedValueToJson(value_);
+ }
+
+ bool IsEqual(const PropValue* value) const override {
+ if (GetType() != value->GetType())
+ return false;
+ return CompareValue(GetValue(),
+ static_cast<const TypedValueBase*>(value)->GetValue());
+ }
+
+ // Helper methods to get and set the C++ representation of the value.
+ const T& GetValue() const { return value_; }
+
+ protected:
+ explicit TypedValueBase(const TypedValueBase& other)
+ : PropValue(other), value_(other.value_) {}
+
+ TypedValueBase(const PropType& type, T value)
+ : PropValue(type), value_(value) {}
+
+ private:
+ T value_{}; // The value of the parameter in C++ data representation.
+};
+
+// A helper template base class for implementing value classes.
+template <typename Derived, typename T>
+class TypedValueWithClone : public TypedValueBase<T> {
+ public:
+ using Base = TypedValueWithClone<Derived, T>;
+
+ // Expose the custom constructor of the base class.
+ using TypedValueBase<T>::TypedValueBase;
+ using PropValue::GetPropType;
+
+ std::unique_ptr<PropValue> Clone() const override {
+ return std::unique_ptr<PropValue>{
+ new Derived{*static_cast<const Derived*>(this)}};
+ }
+
+ static std::unique_ptr<Derived> CreateFromJson(const base::Value& value,
+ const PropType& type,
+ ErrorPtr* error) {
+ T tmp_value;
+ if (!TypedValueFromJson(&value, &type, &tmp_value, error))
+ return nullptr;
+
+ // Only place where invalid value can exist.
+ std::unique_ptr<Derived> result{new Derived{type, tmp_value}};
+ if (!result->GetPropType()->ValidateConstraints(*result, error))
+ return nullptr;
+
+ return result;
+ }
+};
+
+// Value of type Integer.
+class IntValue final : public TypedValueWithClone<IntValue, int> {
+ public:
+ using Base::Base;
+ friend class TypedValueWithClone<IntValue, int>;
+ IntValue* GetInt() override { return this; }
+ IntValue const* GetInt() const override { return this; }
+};
+
+// Value of type Number.
+class DoubleValue final : public TypedValueWithClone<DoubleValue, double> {
+ public:
+ using Base::Base;
+ friend class TypedValueWithClone<DoubleValue, double>;
+ DoubleValue* GetDouble() override { return this; }
+ DoubleValue const* GetDouble() const override { return this; }
+};
+
+// Value of type String.
+class StringValue final : public TypedValueWithClone<StringValue, std::string> {
+ public:
+ using Base::Base;
+ friend class TypedValueWithClone<StringValue, std::string>;
+ StringValue* GetString() override { return this; }
+ StringValue const* GetString() const override { return this; }
+};
+
+// Value of type Boolean.
+class BooleanValue final : public TypedValueWithClone<BooleanValue, bool> {
+ public:
+ using Base::Base;
+ friend class TypedValueWithClone<BooleanValue, bool>;
+ BooleanValue* GetBoolean() override { return this; }
+ BooleanValue const* GetBoolean() const override { return this; }
+};
+
+// Value of type Object.
+class ObjectValue final : public TypedValueWithClone<ObjectValue, ValueMap> {
+ public:
+ using Base::Base;
+ friend class TypedValueWithClone<ObjectValue, ValueMap>;
+ ObjectValue* GetObject() override { return this; }
+ ObjectValue const* GetObject() const override { return this; }
+};
+
+// Value of type Array.
+class ArrayValue final : public TypedValueWithClone<ArrayValue, ValueVector> {
+ public:
+ using Base::Base;
+ friend class TypedValueWithClone<ArrayValue, ValueVector>;
+ ArrayValue* GetArray() override { return this; }
+ ArrayValue const* GetArray() const override { return this; }
+};
+
+} // namespace weave
+
+#endif // LIBWEAVE_SRC_COMMANDS_PROP_VALUES_H_
diff --git a/src/commands/schema_constants.cc b/src/commands/schema_constants.cc
index cd8bf84..c99536b 100644
--- a/src/commands/schema_constants.cc
+++ b/src/commands/schema_constants.cc
@@ -10,11 +10,20 @@
namespace commands {
const char kDomain[] = "command_schema";
+const char kOutOfRange[] = "out_of_range";
const char kTypeMismatch[] = "type_mismatch";
+const char kPropTypeChanged[] = "param_type_changed";
+const char kUnknownType[] = "unknown_type";
+const char kInvalidPropDef[] = "invalid_parameter_definition";
const char kInvalidPropValue[] = "invalid_parameter_value";
+const char kNoTypeInfo[] = "no_type_info";
const char kPropertyMissing[] = "parameter_missing";
+const char kUnknownProperty[] = "unexpected_parameter";
+const char kInvalidObjectSchema[] = "invalid_object_schema";
+const char kDuplicateCommandDef[] = "duplicate_command_definition";
const char kInvalidCommandName[] = "invalid_command_name";
const char kCommandFailed[] = "command_failed";
+const char kInvalidCommandVisibility[] = "invalid_command_visibility";
const char kInvalidMinimalRole[] = "invalid_minimal_role";
const char kCommandDestroyed[] = "command_destroyed";
const char kInvalidState[] = "invalid_state";
@@ -24,6 +33,25 @@
namespace commands {
namespace attributes {
+const char kType[] = "type";
+const char kDisplayName[] = "displayName";
+const char kDefault[] = "default";
+const char kItems[] = "items";
+const char kIsRequired[] = "isRequired";
+
+const char kNumeric_Min[] = "minimum";
+const char kNumeric_Max[] = "maximum";
+
+const char kString_MinLength[] = "minLength";
+const char kString_MaxLength[] = "maxLength";
+
+const char kOneOf_Enum[] = "enum";
+const char kOneOf_Metadata[] = "metadata";
+
+const char kObject_Properties[] = "properties";
+const char kObject_AdditionalProperties[] = "additionalProperties";
+const char kObject_Required[] = "required";
+
const char kCommand_Id[] = "id";
const char kCommand_Name[] = "name";
const char kCommand_Parameters[] = "parameters";
@@ -38,6 +66,12 @@
const char kCommand_Role_User[] = "user";
const char kCommand_Role_Viewer[] = "viewer";
+const char kCommand_Visibility[] = "visibility";
+const char kCommand_Visibility_None[] = "none";
+const char kCommand_Visibility_Local[] = "local";
+const char kCommand_Visibility_Cloud[] = "cloud";
+const char kCommand_Visibility_All[] = "all";
+
} // namespace attributes
} // namespace commands
diff --git a/src/commands/schema_constants.h b/src/commands/schema_constants.h
index 57766e6..742245f 100644
--- a/src/commands/schema_constants.h
+++ b/src/commands/schema_constants.h
@@ -13,11 +13,20 @@
extern const char kDomain[];
// Common command definition error codes.
+extern const char kOutOfRange[];
extern const char kTypeMismatch[];
+extern const char kPropTypeChanged[];
+extern const char kUnknownType[];
+extern const char kInvalidPropDef[];
extern const char kInvalidPropValue[];
+extern const char kNoTypeInfo[];
extern const char kPropertyMissing[];
+extern const char kUnknownProperty[];
+extern const char kInvalidObjectSchema[];
+extern const char kDuplicateCommandDef[];
extern const char kInvalidCommandName[];
extern const char kCommandFailed[];
+extern const char kInvalidCommandVisibility[];
extern const char kInvalidMinimalRole[];
extern const char kCommandDestroyed[];
extern const char kInvalidState[];
@@ -27,6 +36,25 @@
namespace commands {
namespace attributes {
// Command description JSON schema attributes.
+extern const char kType[];
+extern const char kDisplayName[];
+extern const char kDefault[];
+extern const char kItems[];
+extern const char kIsRequired[];
+
+extern const char kNumeric_Min[];
+extern const char kNumeric_Max[];
+
+extern const char kString_MinLength[];
+extern const char kString_MaxLength[];
+
+extern const char kOneOf_Enum[];
+extern const char kOneOf_Metadata[];
+
+extern const char kObject_Properties[];
+extern const char kObject_AdditionalProperties[];
+extern const char kObject_Required[];
+
extern const char kCommand_Id[];
extern const char kCommand_Name[];
extern const char kCommand_Parameters[];
@@ -41,6 +69,12 @@
extern const char kCommand_Role_User[];
extern const char kCommand_Role_Viewer[];
+extern const char kCommand_Visibility[];
+extern const char kCommand_Visibility_None[];
+extern const char kCommand_Visibility_Local[];
+extern const char kCommand_Visibility_Cloud[];
+extern const char kCommand_Visibility_All[];
+
} // namespace attributes
} // namespace commands
diff --git a/src/commands/schema_utils.cc b/src/commands/schema_utils.cc
new file mode 100644
index 0000000..3c3e949
--- /dev/null
+++ b/src/commands/schema_utils.cc
@@ -0,0 +1,266 @@
+// Copyright 2015 The Weave 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 "src/commands/schema_utils.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+
+#include "src/commands/object_schema.h"
+#include "src/commands/prop_types.h"
+#include "src/commands/prop_values.h"
+
+namespace weave {
+namespace {
+// Helper function to report "type mismatch" errors when parsing JSON.
+void ReportJsonTypeMismatch(const tracked_objects::Location& location,
+ const base::Value* value_in,
+ const std::string& expected_type,
+ ErrorPtr* error) {
+ std::string value_as_string;
+ base::JSONWriter::Write(*value_in, &value_as_string);
+ Error::AddToPrintf(error, location, errors::commands::kDomain,
+ errors::commands::kTypeMismatch,
+ "Unable to convert value %s into %s",
+ value_as_string.c_str(), expected_type.c_str());
+}
+
+// Template version of ReportJsonTypeMismatch that deduces the type of expected
+// data from the value_out parameter passed to particular overload of
+// TypedValueFromJson() function. Always returns false.
+template <typename T>
+bool ReportUnexpectedJson(const tracked_objects::Location& location,
+ const base::Value* value_in,
+ T*,
+ ErrorPtr* error) {
+ ReportJsonTypeMismatch(location, value_in,
+ PropType::GetTypeStringFromType(GetValueType<T>()),
+ error);
+ return false;
+}
+
+bool ErrorMissingProperty(ErrorPtr* error,
+ const tracked_objects::Location& location,
+ const char* param_name) {
+ Error::AddToPrintf(error, location, errors::commands::kDomain,
+ errors::commands::kPropertyMissing,
+ "Required parameter missing: %s", param_name);
+ return false;
+}
+
+} // namespace
+
+// Specializations of TypedValueToJson<T>() for supported C++ types.
+std::unique_ptr<base::FundamentalValue> TypedValueToJson(bool value) {
+ return std::unique_ptr<base::FundamentalValue>(
+ new base::FundamentalValue(value));
+}
+
+std::unique_ptr<base::FundamentalValue> TypedValueToJson(int value) {
+ return std::unique_ptr<base::FundamentalValue>(
+ new base::FundamentalValue(value));
+}
+
+std::unique_ptr<base::FundamentalValue> TypedValueToJson(double value) {
+ return std::unique_ptr<base::FundamentalValue>(
+ new base::FundamentalValue(value));
+}
+
+std::unique_ptr<base::StringValue> TypedValueToJson(const std::string& value) {
+ return std::unique_ptr<base::StringValue>(new base::StringValue(value));
+}
+
+std::unique_ptr<base::DictionaryValue> TypedValueToJson(const ValueMap& value) {
+ std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+ for (const auto& pair : value) {
+ auto prop_value = pair.second->ToJson();
+ CHECK(prop_value);
+ dict->SetWithoutPathExpansion(pair.first, prop_value.release());
+ }
+ return dict;
+}
+
+std::unique_ptr<base::ListValue> TypedValueToJson(const ValueVector& value) {
+ std::unique_ptr<base::ListValue> list(new base::ListValue);
+ for (const auto& item : value) {
+ auto json = item->ToJson();
+ CHECK(json);
+ list->Append(json.release());
+ }
+ return list;
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+ const PropType* type,
+ bool* value_out,
+ ErrorPtr* error) {
+ return value_in->GetAsBoolean(value_out) ||
+ ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+ const PropType* type,
+ int* value_out,
+ ErrorPtr* error) {
+ return value_in->GetAsInteger(value_out) ||
+ ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+ const PropType* type,
+ double* value_out,
+ ErrorPtr* error) {
+ return value_in->GetAsDouble(value_out) ||
+ ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+ const PropType* type,
+ std::string* value_out,
+ ErrorPtr* error) {
+ return value_in->GetAsString(value_out) ||
+ ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+ const PropType* type,
+ ValueMap* value_out,
+ ErrorPtr* error) {
+ const base::DictionaryValue* dict = nullptr;
+ if (!value_in->GetAsDictionary(&dict))
+ return ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
+
+ CHECK(type) << "Object definition must be provided";
+ CHECK(ValueType::Object == type->GetType()) << "Type must be Object";
+
+ const ObjectSchema* object_schema = type->GetObject()->GetObjectSchemaPtr();
+ std::set<std::string> keys_processed;
+ value_out->clear(); // Clear possible default values already in |value_out|.
+ for (const auto& pair : object_schema->GetProps()) {
+ const PropValue* def_value = pair.second->GetDefaultValue();
+ if (dict->HasKey(pair.first)) {
+ const base::Value* param_value = nullptr;
+ CHECK(dict->GetWithoutPathExpansion(pair.first, ¶m_value))
+ << "Unable to get parameter";
+ auto value = pair.second->CreatePropValue(*param_value, error);
+ if (!value) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidPropValue,
+ "Invalid value for property '%s'",
+ pair.first.c_str());
+ return false;
+ }
+ value_out->emplace_hint(value_out->end(), pair.first, std::move(value));
+ keys_processed.insert(pair.first);
+ } else if (def_value) {
+ value_out->emplace_hint(value_out->end(), pair.first, def_value->Clone());
+ keys_processed.insert(pair.first);
+ } else if (pair.second->IsRequired()) {
+ return ErrorMissingProperty(error, FROM_HERE, pair.first.c_str());
+ }
+ }
+
+ // Just for sanity, make sure that we processed all the necessary properties
+ // and there weren't any extra (unknown) ones specified. If so, ignore
+ // them, but log as warnings...
+ base::DictionaryValue::Iterator iter(*dict);
+ while (!iter.IsAtEnd()) {
+ std::string key = iter.key();
+ if (keys_processed.find(key) == keys_processed.end() &&
+ !object_schema->GetExtraPropertiesAllowed()) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kUnknownProperty,
+ "Unrecognized parameter '%s'", key.c_str());
+ return false;
+ }
+ iter.Advance();
+ }
+
+ // Now go over all property values and validate them.
+ for (const auto& pair : *value_out) {
+ const PropType* prop_type = pair.second->GetPropType();
+ CHECK(prop_type) << "Value property type must be available";
+ if (!prop_type->ValidateConstraints(*pair.second, error)) {
+ Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+ errors::commands::kInvalidPropValue,
+ "Invalid value for property '%s'", pair.first.c_str());
+ return false;
+ }
+ }
+ return true;
+}
+
+bool TypedValueFromJson(const base::Value* value_in,
+ const PropType* type,
+ ValueVector* value_out,
+ ErrorPtr* error) {
+ const base::ListValue* list = nullptr;
+ if (!value_in->GetAsList(&list))
+ return ReportUnexpectedJson(FROM_HERE, value_in, value_out, error);
+
+ CHECK(type) << "Array type definition must be provided";
+ CHECK(ValueType::Array == type->GetType()) << "Type must be Array";
+ const PropType* item_type = type->GetArray()->GetItemTypePtr();
+ CHECK(item_type) << "Incomplete array type definition";
+
+ // This value might already contain values from the type defaults.
+ // Clear them first before proceeding.
+ value_out->clear();
+ value_out->reserve(list->GetSize());
+ for (const base::Value* item : *list) {
+ std::unique_ptr<PropValue> prop_value =
+ item_type->CreatePropValue(*item, error);
+ if (!prop_value)
+ return false;
+ value_out->push_back(std::move(prop_value));
+ }
+ return true;
+}
+
+// Compares two sets of key-value pairs from two Objects.
+static bool obj_cmp(const ValueMap::value_type& v1,
+ const ValueMap::value_type& v2) {
+ return (v1.first == v2.first) && v1.second->IsEqual(v2.second.get());
+}
+
+bool operator==(const ValueMap& obj1, const ValueMap& obj2) {
+ if (obj1.size() != obj2.size())
+ return false;
+
+ auto pair = std::mismatch(obj1.begin(), obj1.end(), obj2.begin(), obj_cmp);
+ return pair == std::make_pair(obj1.end(), obj2.end());
+}
+
+bool operator==(const ValueVector& arr1, const ValueVector& arr2) {
+ if (arr1.size() != arr2.size())
+ return false;
+
+ using Type = const ValueVector::value_type;
+ // Compare two array items.
+ auto arr_cmp = [](const Type& v1, const Type& v2) {
+ return v1->IsEqual(v2.get());
+ };
+ auto pair = std::mismatch(arr1.begin(), arr1.end(), arr2.begin(), arr_cmp);
+ return pair == std::make_pair(arr1.end(), arr2.end());
+}
+
+std::string ToString(const ValueMap& obj) {
+ auto val = TypedValueToJson(obj);
+ std::string str;
+ base::JSONWriter::Write(*val, &str);
+ return str;
+}
+
+std::string ToString(const ValueVector& arr) {
+ auto val = TypedValueToJson(arr);
+ std::string str;
+ base::JSONWriter::Write(*val, &str);
+ return str;
+}
+
+} // namespace weave
diff --git a/src/commands/schema_utils.h b/src/commands/schema_utils.h
new file mode 100644
index 0000000..0c1d1b3
--- /dev/null
+++ b/src/commands/schema_utils.h
@@ -0,0 +1,136 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_SRC_COMMANDS_SCHEMA_UTILS_H_
+#define LIBWEAVE_SRC_COMMANDS_SCHEMA_UTILS_H_
+
+#include <cmath>
+#include <limits>
+#include <map>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/values.h>
+#include <weave/error.h>
+
+namespace weave {
+
+class PropType;
+class PropValue;
+class ObjectSchema;
+class ObjectValue;
+
+// C++ representation of object values.
+using ValueMap = std::map<std::string, std::shared_ptr<const PropValue>>;
+
+// C++ representation of array of values.
+using ValueVector = std::vector<std::shared_ptr<const PropValue>>;
+
+// Converts an object to string.
+std::string ToString(const ValueMap& obj);
+
+// Converts an array to string.
+std::string ToString(const ValueVector& arr);
+
+// InheritableAttribute class is used for specifying various command parameter
+// attributes that can be inherited from a base (parent) schema.
+// The |value| still specifies the actual attribute values, whether it
+// is inherited or overridden, while |is_inherited| can be used to identify
+// if the attribute was inherited (true) or overridden (false).
+template <typename T>
+class InheritableAttribute final {
+ public:
+ InheritableAttribute() = default;
+ explicit InheritableAttribute(T val)
+ : value(std::move(val)), is_inherited(true) {}
+ InheritableAttribute(T val, bool inherited)
+ : value(std::move(val)), is_inherited(inherited) {}
+ T value{};
+ bool is_inherited{true};
+};
+
+// A bunch of helper function to create base::Value for specific C++ classes,
+// including vectors of types. These are used in template classes below
+// to simplify specialization logic.
+std::unique_ptr<base::FundamentalValue> TypedValueToJson(bool value);
+std::unique_ptr<base::FundamentalValue> TypedValueToJson(int value);
+std::unique_ptr<base::FundamentalValue> TypedValueToJson(double value);
+std::unique_ptr<base::StringValue> TypedValueToJson(const std::string& value);
+std::unique_ptr<base::DictionaryValue> TypedValueToJson(const ValueMap& value);
+std::unique_ptr<base::ListValue> TypedValueToJson(const ValueVector& value);
+template <typename T>
+std::unique_ptr<base::ListValue> TypedValueToJson(
+ const std::vector<T>& values) {
+ std::unique_ptr<base::ListValue> list(new base::ListValue);
+ for (const auto& v : values) {
+ auto json = TypedValueToJson(v);
+ CHECK(json);
+ list->Append(json.release());
+ }
+ return list;
+}
+
+// Similarly to TypedValueToJson() function above, the following overloaded
+// helper methods allow to extract specific C++ data types from base::Value.
+// Also used in template classes below to simplify specialization logic.
+// TODO(vitalybuka): Fix this. Interface is misleading. Seeing PropType internal
+// type validation is expected. In reality only ValueMap and ValueVector do
+// validation.
+bool TypedValueFromJson(const base::Value* value_in,
+ const PropType* type,
+ bool* value_out,
+ ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+ const PropType* type,
+ int* value_out,
+ ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+ const PropType* type,
+ double* value_out,
+ ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+ const PropType* type,
+ std::string* value_out,
+ ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+ const PropType* type,
+ ValueMap* value_out,
+ ErrorPtr* error);
+bool TypedValueFromJson(const base::Value* value_in,
+ const PropType* type,
+ ValueVector* value_out,
+ ErrorPtr* error);
+
+bool operator==(const ValueMap& obj1, const ValueMap& obj2);
+bool operator==(const ValueVector& arr1, const ValueVector& arr2);
+
+// CompareValue is a helper function to help with implementing EqualsTo operator
+// for various data types. For most scalar types it is using operator==(),
+// however, for floating point values, rounding errors in binary representation
+// of IEEE floats/doubles can cause straight == comparison to fail for seemingly
+// equivalent values. For these, use approximate comparison with the error
+// margin equal to the epsilon value defined for the corresponding data type.
+// This is used when looking up values for implementation of OneOf constraints
+// which should work reliably for floating points also ("number" type).
+
+// Compare exact types using ==.
+template <typename T>
+inline typename std::enable_if<!std::is_floating_point<T>::value, bool>::type
+CompareValue(const T& v1, const T& v2) {
+ return v1 == v2;
+}
+
+// Compare non-exact types (such as double) using precision margin (epsilon).
+template <typename T>
+inline typename std::enable_if<std::is_floating_point<T>::value, bool>::type
+CompareValue(const T& v1, const T& v2) {
+ return std::abs(v1 - v2) <= std::numeric_limits<T>::epsilon();
+}
+
+} // namespace weave
+
+#endif // LIBWEAVE_SRC_COMMANDS_SCHEMA_UTILS_H_
diff --git a/src/commands/schema_utils_unittest.cc b/src/commands/schema_utils_unittest.cc
new file mode 100644
index 0000000..1ea4c90
--- /dev/null
+++ b/src/commands/schema_utils_unittest.cc
@@ -0,0 +1,212 @@
+// Copyright 2015 The Weave 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 "src/commands/schema_utils.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/values.h>
+#include <gtest/gtest.h>
+
+#include "src/commands/object_schema.h"
+#include "src/commands/prop_types.h"
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_constants.h"
+#include "src/commands/unittest_utils.h"
+
+namespace weave {
+
+using test::CreateDictionaryValue;
+using test::CreateValue;
+
+TEST(CommandSchemaUtils, TypedValueToJson_Scalar) {
+ EXPECT_JSON_EQ("true", *TypedValueToJson(true));
+ EXPECT_JSON_EQ("false", *TypedValueToJson(false));
+
+ EXPECT_JSON_EQ("0", *TypedValueToJson(0));
+ EXPECT_JSON_EQ("-10", *TypedValueToJson(-10));
+ EXPECT_JSON_EQ("20", *TypedValueToJson(20));
+
+ EXPECT_JSON_EQ("0.0", *TypedValueToJson(0.0));
+ EXPECT_JSON_EQ("1.2", *TypedValueToJson(1.2));
+
+ EXPECT_JSON_EQ("'abc'", *TypedValueToJson(std::string("abc")));
+
+ std::vector<bool> bool_array{true, false};
+ EXPECT_JSON_EQ("[true,false]", *TypedValueToJson(bool_array));
+
+ std::vector<int> int_array{1, 2, 5};
+ EXPECT_JSON_EQ("[1,2,5]", *TypedValueToJson(int_array));
+
+ std::vector<double> dbl_array{1.1, 2.2};
+ EXPECT_JSON_EQ("[1.1,2.2]", *TypedValueToJson(dbl_array));
+
+ std::vector<std::string> str_array{"a", "bc"};
+ EXPECT_JSON_EQ("['a','bc']", *TypedValueToJson(str_array));
+}
+
+TEST(CommandSchemaUtils, TypedValueToJson_Object) {
+ IntPropType int_type;
+ ValueMap object;
+
+ object.insert(std::make_pair(
+ "width", int_type.CreateValue(base::FundamentalValue{640}, nullptr)));
+ object.insert(std::make_pair(
+ "height", int_type.CreateValue(base::FundamentalValue{480}, nullptr)));
+ EXPECT_JSON_EQ("{'height':480,'width':640}", *TypedValueToJson(object));
+}
+
+TEST(CommandSchemaUtils, TypedValueToJson_Array) {
+ IntPropType int_type;
+ ValueVector arr;
+
+ arr.push_back(int_type.CreateValue(base::FundamentalValue{640}, nullptr));
+ arr.push_back(int_type.CreateValue(base::FundamentalValue{480}, nullptr));
+ EXPECT_JSON_EQ("[640,480]", *TypedValueToJson(arr));
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Bool) {
+ bool value;
+
+ EXPECT_TRUE(
+ TypedValueFromJson(CreateValue("true").get(), nullptr, &value, nullptr));
+ EXPECT_TRUE(value);
+
+ EXPECT_TRUE(
+ TypedValueFromJson(CreateValue("false").get(), nullptr, &value, nullptr));
+ EXPECT_FALSE(value);
+
+ ErrorPtr error;
+ EXPECT_FALSE(
+ TypedValueFromJson(CreateValue("0").get(), nullptr, &value, &error));
+ EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+ error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Int) {
+ int value;
+
+ EXPECT_TRUE(
+ TypedValueFromJson(CreateValue("0").get(), nullptr, &value, nullptr));
+ EXPECT_EQ(0, value);
+
+ EXPECT_TRUE(
+ TypedValueFromJson(CreateValue("23").get(), nullptr, &value, nullptr));
+ EXPECT_EQ(23, value);
+
+ EXPECT_TRUE(
+ TypedValueFromJson(CreateValue("-1234").get(), nullptr, &value, nullptr));
+ EXPECT_EQ(-1234, value);
+
+ ErrorPtr error;
+ EXPECT_FALSE(
+ TypedValueFromJson(CreateValue("'abc'").get(), nullptr, &value, &error));
+ EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+ error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Double) {
+ double value;
+
+ EXPECT_TRUE(
+ TypedValueFromJson(CreateValue("0").get(), nullptr, &value, nullptr));
+ EXPECT_DOUBLE_EQ(0.0, value);
+ EXPECT_TRUE(
+ TypedValueFromJson(CreateValue("0.0").get(), nullptr, &value, nullptr));
+ EXPECT_DOUBLE_EQ(0.0, value);
+
+ EXPECT_TRUE(
+ TypedValueFromJson(CreateValue("23").get(), nullptr, &value, nullptr));
+ EXPECT_EQ(23.0, value);
+ EXPECT_TRUE(
+ TypedValueFromJson(CreateValue("23.1").get(), nullptr, &value, nullptr));
+ EXPECT_EQ(23.1, value);
+
+ EXPECT_TRUE(TypedValueFromJson(CreateValue("-1.23E+02").get(), nullptr,
+ &value, nullptr));
+ EXPECT_EQ(-123.0, value);
+
+ ErrorPtr error;
+ EXPECT_FALSE(
+ TypedValueFromJson(CreateValue("'abc'").get(), nullptr, &value, &error));
+ EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+ error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_String) {
+ std::string value;
+
+ EXPECT_TRUE(
+ TypedValueFromJson(CreateValue("''").get(), nullptr, &value, nullptr));
+ EXPECT_EQ("", value);
+
+ EXPECT_TRUE(
+ TypedValueFromJson(CreateValue("'23'").get(), nullptr, &value, nullptr));
+ EXPECT_EQ("23", value);
+
+ EXPECT_TRUE(
+ TypedValueFromJson(CreateValue("'abc'").get(), nullptr, &value, nullptr));
+ EXPECT_EQ("abc", value);
+
+ ErrorPtr error;
+ EXPECT_FALSE(
+ TypedValueFromJson(CreateValue("12").get(), nullptr, &value, &error));
+ EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+ error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Object) {
+ ValueMap value;
+ std::unique_ptr<ObjectSchema> schema{new ObjectSchema};
+
+ IntPropType age_prop;
+ age_prop.AddMinMaxConstraint(0, 150);
+ schema->AddProp("age", age_prop.Clone());
+
+ StringPropType name_prop;
+ name_prop.AddLengthConstraint(1, 30);
+ schema->AddProp("name", name_prop.Clone());
+
+ ObjectPropType type;
+ type.SetObjectSchema(std::move(schema));
+ EXPECT_TRUE(TypedValueFromJson(CreateValue("{'age':20,'name':'Bob'}").get(),
+ &type, &value, nullptr));
+ ValueMap value2;
+ value2.insert(std::make_pair(
+ "age", age_prop.CreateValue(base::FundamentalValue{20}, nullptr)));
+ value2.insert(std::make_pair(
+ "name", name_prop.CreateValue(base::StringValue("Bob"), nullptr)));
+ EXPECT_EQ(value2, value);
+
+ ErrorPtr error;
+ EXPECT_FALSE(
+ TypedValueFromJson(CreateValue("'abc'").get(), nullptr, &value, &error));
+ EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+ error.reset();
+}
+
+TEST(CommandSchemaUtils, TypedValueFromJson_Array) {
+ ValueVector arr;
+ StringPropType str_type;
+ str_type.AddLengthConstraint(3, 100);
+ ArrayPropType type;
+ type.SetItemType(str_type.Clone());
+
+ EXPECT_TRUE(TypedValueFromJson(CreateValue("['foo', 'bar']").get(), &type,
+ &arr, nullptr));
+ ValueVector arr2;
+ arr2.push_back(str_type.CreateValue(base::StringValue{"foo"}, nullptr));
+ arr2.push_back(str_type.CreateValue(base::StringValue{"bar"}, nullptr));
+ EXPECT_EQ(arr2, arr);
+
+ ErrorPtr error;
+ EXPECT_FALSE(TypedValueFromJson(CreateValue("['baz', 'ab']").get(), &type,
+ &arr, &error));
+ EXPECT_EQ(errors::commands::kOutOfRange, error->GetCode());
+ error.reset();
+}
+
+} // namespace weave
diff --git a/src/commands/unittest_utils.h b/src/commands/unittest_utils.h
new file mode 100644
index 0000000..25392ee
--- /dev/null
+++ b/src/commands/unittest_utils.h
@@ -0,0 +1,47 @@
+// Copyright 2015 The Weave Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_SRC_COMMANDS_UNITTEST_UTILS_H_
+#define LIBWEAVE_SRC_COMMANDS_UNITTEST_UTILS_H_
+
+#include <memory>
+#include <string>
+
+#include <base/values.h>
+#include <gtest/gtest.h>
+#include <weave/test/unittest_utils.h>
+
+#include "src/commands/prop_types.h"
+#include "src/commands/prop_values.h"
+
+namespace weave {
+namespace test {
+
+template <typename T>
+std::unique_ptr<const PropValue> make_prop_value(const base::Value& value) {
+ auto prop_type = PropType::Create(GetValueType<T>());
+ return prop_type->CreatePropValue(value, nullptr);
+}
+
+inline std::unique_ptr<const PropValue> make_int_prop_value(int value) {
+ return make_prop_value<int>(base::FundamentalValue{value});
+}
+
+inline std::unique_ptr<const PropValue> make_double_prop_value(double value) {
+ return make_prop_value<double>(base::FundamentalValue{value});
+}
+
+inline std::unique_ptr<const PropValue> make_bool_prop_value(bool value) {
+ return make_prop_value<bool>(base::FundamentalValue{value});
+}
+
+inline std::unique_ptr<const PropValue> make_string_prop_value(
+ const std::string& value) {
+ return make_prop_value<std::string>(base::StringValue{value});
+}
+
+} // namespace test
+} // namespace weave
+
+#endif // LIBWEAVE_SRC_COMMANDS_UNITTEST_UTILS_H_
diff --git a/src/config.cc b/src/config.cc
index fe50e3b..52f5ea1 100644
--- a/src/config.cc
+++ b/src/config.cc
@@ -65,7 +65,7 @@
result.oauth_url = "https://accounts.google.com/o/oauth2/";
result.service_url = kWeaveUrl;
result.local_anonymous_access_role = AuthScope::kViewer;
- result.pairing_modes.insert(PairingType::kPinCode);
+ result.pairing_modes.emplace(PairingType::kPinCode);
result.device_id = base::GenerateGUID();
return result;
}
diff --git a/src/config_unittest.cc b/src/config_unittest.cc
index c36bb25..10ed07e 100644
--- a/src/config_unittest.cc
+++ b/src/config_unittest.cc
@@ -10,7 +10,8 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <weave/provider/test/mock_config_store.h>
-#include <weave/test/unittest_utils.h>
+
+#include "src/commands/unittest_utils.h"
using testing::_;
using testing::Invoke;
diff --git a/src/data_encoding_unittest.cc b/src/data_encoding_unittest.cc
index 53dc534..7e3bfe4 100644
--- a/src/data_encoding_unittest.cc
+++ b/src/data_encoding_unittest.cc
@@ -30,7 +30,7 @@
EXPECT_EQ("q=test&path=%2Fusr%2Fbin&%23=%25", encoded);
auto params = WebParamsDecode(encoded);
- EXPECT_EQ(3u, params.size());
+ EXPECT_EQ(3, params.size());
EXPECT_EQ("q", params[0].first);
EXPECT_EQ("test", params[0].second);
EXPECT_EQ("path", params[1].first);
diff --git a/src/device_manager.cc b/src/device_manager.cc
index 52b2882..c0b8e9a 100644
--- a/src/device_manager.cc
+++ b/src/device_manager.cc
@@ -134,7 +134,7 @@
return state_manager_->SetProperties(dict, error);
}
-const base::Value* DeviceManager::GetStateProperty(
+std::unique_ptr<base::Value> DeviceManager::GetStateProperty(
const std::string& name) const {
return state_manager_->GetProperty(name);
}
@@ -145,7 +145,7 @@
return state_manager_->SetProperty(name, value, error);
}
-const base::DictionaryValue& DeviceManager::GetState() const {
+std::unique_ptr<base::DictionaryValue> DeviceManager::GetState() const {
return state_manager_->GetState();
}
diff --git a/src/device_manager.h b/src/device_manager.h
index 6c3df05..ccf8778 100644
--- a/src/device_manager.h
+++ b/src/device_manager.h
@@ -52,11 +52,12 @@
ErrorPtr* error) override;
bool SetStateProperties(const base::DictionaryValue& dict,
ErrorPtr* error) override;
- const base::Value* GetStateProperty(const std::string& name) const override;
+ std::unique_ptr<base::Value> GetStateProperty(
+ const std::string& name) const override;
bool SetStateProperty(const std::string& name,
const base::Value& value,
ErrorPtr* error) override;
- const base::DictionaryValue& GetState() const override;
+ std::unique_ptr<base::DictionaryValue> GetState() const override;
void Register(const std::string& ticket_id,
const DoneCallback& callback) override;
GcdState GetGcdState() const override;
diff --git a/src/device_registration_info.cc b/src/device_registration_info.cc
index 0c914b7..751e530 100644
--- a/src/device_registration_info.cc
+++ b/src/device_registration_info.cc
@@ -480,12 +480,14 @@
std::unique_ptr<base::DictionaryValue>
DeviceRegistrationInfo::BuildDeviceResource(ErrorPtr* error) {
// Limit only to commands that are visible to the cloud.
- auto commands =
- command_manager_->GetCommandDictionary().GetCommandsAsJson(error);
+ auto commands = command_manager_->GetCommandDictionary().GetCommandsAsJson(
+ [](const CommandDefinition* def) { return def->GetVisibility().cloud; },
+ true, error);
if (!commands)
return nullptr;
- const base::DictionaryValue& state = state_manager_->GetState();
+ std::unique_ptr<base::DictionaryValue> state = state_manager_->GetState();
+ CHECK(state);
std::unique_ptr<base::DictionaryValue> resource{new base::DictionaryValue};
if (!GetSettings().cloud_id.empty())
@@ -506,7 +508,7 @@
}
resource->Set("channel", channel.release());
resource->Set("commandDefs", commands.release());
- resource->Set("state", state.DeepCopy());
+ resource->Set("state", state.release());
return resource;
}
@@ -1119,11 +1121,23 @@
return;
std::unique_ptr<base::ListValue> patches{new base::ListValue};
- for (auto& state_change : state_changes) {
+ for (const auto& state_change : state_changes) {
std::unique_ptr<base::DictionaryValue> patch{new base::DictionaryValue};
patch->SetString("timeMs",
std::to_string(state_change.timestamp.ToJavaTime()));
- patch->Set("patch", state_change.changed_properties.release());
+
+ std::unique_ptr<base::DictionaryValue> changes{new base::DictionaryValue};
+ for (const auto& pair : state_change.changed_properties) {
+ auto value = pair.second->ToJson();
+ CHECK(value);
+ // The key in |pair.first| is the full property name in format
+ // "package.property_name", so must use DictionaryValue::Set() instead of
+ // DictionaryValue::SetWithoutPathExpansion to recreate the JSON
+ // property tree properly.
+ changes->Set(pair.first, value.release());
+ }
+ patch->Set("patch", changes.release());
+
patches->Append(patch.release());
}
diff --git a/src/device_registration_info_unittest.cc b/src/device_registration_info_unittest.cc
index f2fe4c4..df4a438 100644
--- a/src/device_registration_info_unittest.cc
+++ b/src/device_registration_info_unittest.cc
@@ -11,10 +11,10 @@
#include <weave/provider/test/fake_task_runner.h>
#include <weave/provider/test/mock_config_store.h>
#include <weave/provider/test/mock_http_client.h>
-#include <weave/test/unittest_utils.h>
#include "src/bind_lambda.h"
#include "src/commands/command_manager.h"
+#include "src/commands/unittest_utils.h"
#include "src/http_constants.h"
#include "src/states/mock_state_change_queue_interface.h"
#include "src/states/state_manager.h"
@@ -355,14 +355,16 @@
auto json_cmds = CreateDictionaryValue(R"({
'base': {
'reboot': {
- 'parameters': {'delay': {'minimum': 10, 'type': 'integer'}},
- 'minimalRole': 'user'
+ 'parameters': {'delay': {'minimum': 10}},
+ 'minimalRole': 'user',
+ 'results': {}
}
},
'robot': {
'_jump': {
- 'parameters': {'_height': {'type': 'integer'}},
- 'minimalRole': 'user'
+ 'parameters': {'_height': 'integer'},
+ 'minimalRole': 'user',
+ 'results': {}
}
}
})");
diff --git a/src/notification/notification_channel.h b/src/notification/notification_channel.h
index ef152a8..5fb7993 100644
--- a/src/notification/notification_channel.h
+++ b/src/notification/notification_channel.h
@@ -17,7 +17,7 @@
class NotificationChannel {
public:
- virtual ~NotificationChannel() {}
+ virtual ~NotificationChannel() = default;
virtual std::string GetName() const = 0;
virtual bool IsConnected() const = 0;
diff --git a/src/notification/notification_delegate.h b/src/notification/notification_delegate.h
index 263c2f3..719d76d 100644
--- a/src/notification/notification_delegate.h
+++ b/src/notification/notification_delegate.h
@@ -24,7 +24,7 @@
virtual void OnDeviceDeleted(const std::string& cloud_id) = 0;
protected:
- virtual ~NotificationDelegate() {}
+ virtual ~NotificationDelegate() = default;
};
} // namespace weave
diff --git a/src/notification/notification_parser.cc b/src/notification/notification_parser.cc
index d7c4f48..0a27f1c 100644
--- a/src/notification/notification_parser.cc
+++ b/src/notification/notification_parser.cc
@@ -46,9 +46,9 @@
std::string kind;
if (!notification.GetString("kind", &kind) ||
- kind != "weave#notification") {
+ kind != "clouddevices#notification") {
LOG(WARNING) << "Push notification should have 'kind' property set to "
- "weave#notification";
+ "clouddevices#notification";
return false;
}
diff --git a/src/notification/notification_parser_unittest.cc b/src/notification/notification_parser_unittest.cc
index c69f9dd..d0b43fe 100644
--- a/src/notification/notification_parser_unittest.cc
+++ b/src/notification/notification_parser_unittest.cc
@@ -6,7 +6,8 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
-#include <weave/test/unittest_utils.h>
+
+#include "src/commands/unittest_utils.h"
using testing::SaveArg;
using testing::Invoke;
@@ -37,11 +38,11 @@
TEST_F(NotificationParserTest, CommandCreated) {
auto json = CreateDictionaryValue(R"({
- "kind": "weave#notification",
+ "kind": "clouddevices#notification",
"type": "COMMAND_CREATED",
"deviceId": "device_id",
"command": {
- "kind": "weave#command",
+ "kind": "clouddevices#command",
"deviceId": "device_id",
"state": "queued",
"name": "storage.list",
@@ -56,7 +57,7 @@
})");
const char expected_json[] = R"({
- "kind": "weave#command",
+ "kind": "clouddevices#command",
"deviceId": "device_id",
"state": "queued",
"name": "storage.list",
@@ -75,7 +76,7 @@
TEST_F(NotificationParserTest, DeviceDeleted) {
auto json = CreateDictionaryValue(R"({
- "kind":"weave#notification",
+ "kind":"clouddevices#notification",
"type":"DEVICE_DELETED",
"deviceId":"some_device_id"
})");
@@ -91,7 +92,7 @@
"type": "COMMAND_CREATED",
"deviceId": "device_id",
"command": {
- "kind": "weave#command",
+ "kind": "clouddevices#command",
"deviceId": "device_id",
"state": "queued",
"name": "storage.list",
@@ -110,10 +111,10 @@
TEST_F(NotificationParserTest, Failure_NoType) {
auto json = CreateDictionaryValue(R"({
- "kind": "weave#notification",
+ "kind": "clouddevices#notification",
"deviceId": "device_id",
"command": {
- "kind": "weave#command",
+ "kind": "clouddevices#command",
"deviceId": "device_id",
"state": "queued",
"name": "storage.list",
@@ -132,11 +133,11 @@
TEST_F(NotificationParserTest, IgnoredNotificationType) {
auto json = CreateDictionaryValue(R"({
- "kind": "weave#notification",
+ "kind": "clouddevices#notification",
"type": "COMMAND_EXPIRED",
"deviceId": "device_id",
"command": {
- "kind": "weave#command",
+ "kind": "clouddevices#command",
"deviceId": "device_id",
"state": "queued",
"name": "storage.list",
diff --git a/src/notification/xmpp_channel.h b/src/notification/xmpp_channel.h
index 50e84d2..e6185d9 100644
--- a/src/notification/xmpp_channel.h
+++ b/src/notification/xmpp_channel.h
@@ -33,7 +33,7 @@
virtual void SendMessage(const std::string& message) = 0;
protected:
- virtual ~XmppChannelInterface() {}
+ virtual ~XmppChannelInterface() = default;
};
class XmppChannel : public NotificationChannel,
diff --git a/src/notification/xmpp_iq_stanza_handler.cc b/src/notification/xmpp_iq_stanza_handler.cc
index 9ac4223..8753030 100644
--- a/src/notification/xmpp_iq_stanza_handler.cc
+++ b/src/notification/xmpp_iq_stanza_handler.cc
@@ -72,7 +72,7 @@
const ResponseCallback& response_callback,
const TimeoutCallback& timeout_callback) {
// Remember the response callback to call later.
- requests_.insert(std::make_pair(++last_request_id_, response_callback));
+ requests_.emplace(++last_request_id_, response_callback);
// Schedule a time-out callback for this request.
if (timeout < base::TimeDelta::Max()) {
task_runner_->PostDelayedTask(
diff --git a/src/notification/xmpp_stream_parser.cc b/src/notification/xmpp_stream_parser.cc
index 9efaeb5..de3b8f1 100644
--- a/src/notification/xmpp_stream_parser.cc
+++ b/src/notification/xmpp_stream_parser.cc
@@ -36,7 +36,7 @@
std::map<std::string, std::string> attributes;
if (attr != nullptr) {
for (size_t n = 0; attr[n] != nullptr && attr[n + 1] != nullptr; n += 2) {
- attributes.insert(std::make_pair(attr[n], attr[n + 1]));
+ attributes.emplace(attr[n], attr[n + 1]);
}
}
self->OnOpenElement(element, std::move(attributes));
diff --git a/src/notification/xmpp_stream_parser.h b/src/notification/xmpp_stream_parser.h
index b8f5723..41faaff 100644
--- a/src/notification/xmpp_stream_parser.h
+++ b/src/notification/xmpp_stream_parser.h
@@ -49,7 +49,7 @@
virtual void OnStanza(std::unique_ptr<XmlNode> stanza) = 0;
protected:
- virtual ~Delegate() {}
+ virtual ~Delegate() = default;
};
explicit XmppStreamParser(Delegate* delegate);
diff --git a/src/privet/cloud_delegate.cc b/src/privet/cloud_delegate.cc
index c771366..fddc8e1 100644
--- a/src/privet/cloud_delegate.cc
+++ b/src/privet/cloud_delegate.cc
@@ -134,9 +134,7 @@
}
std::string GetCloudId() const override {
- return connection_state_.status() > ConnectionState::kUnconfigured
- ? device_->GetSettings().cloud_id
- : "";
+ return device_->GetSettings().cloud_id;
}
const base::DictionaryValue& GetState() const override { return state_; }
@@ -213,7 +211,7 @@
private:
void OnCommandAdded(Command* command) {
// Set to 0 for any new unknown command.
- command_owners_.insert(std::make_pair(command->GetID(), 0));
+ command_owners_.emplace(command->GetID(), 0);
}
void OnCommandRemoved(Command* command) {
@@ -243,15 +241,17 @@
void OnStateChanged() {
state_.Clear();
- const auto& state = state_manager_->GetState();
- state_.MergeDictionary(&state);
+ auto state = state_manager_->GetState();
+ CHECK(state);
+ state_.MergeDictionary(state.get());
NotifyOnStateChanged();
}
void OnCommandDefChanged() {
command_defs_.Clear();
- auto commands =
- command_manager_->GetCommandDictionary().GetCommandsAsJson(nullptr);
+ auto commands = command_manager_->GetCommandDictionary().GetCommandsAsJson(
+ [](const CommandDefinition* def) { return def->GetVisibility().local; },
+ true, nullptr);
CHECK(commands);
command_defs_.MergeDictionary(commands.get());
NotifyOnCommandDefsChanged();
diff --git a/src/privet/cloud_delegate.h b/src/privet/cloud_delegate.h
index 6396519..8763fbe 100644
--- a/src/privet/cloud_delegate.h
+++ b/src/privet/cloud_delegate.h
@@ -45,7 +45,7 @@
class Observer {
public:
- virtual ~Observer() {}
+ virtual ~Observer() = default;
virtual void OnDeviceInfoChanged() {}
virtual void OnCommandDefsChanged() {}
diff --git a/src/privet/privet_handler.cc b/src/privet/privet_handler.cc
index 157feed..157c982 100644
--- a/src/privet/privet_handler.cc
+++ b/src/privet/privet_handler.cc
@@ -469,7 +469,7 @@
params.handler = handler;
params.scope = scope;
params.https_only = false;
- CHECK(handlers_.insert(std::make_pair(path, params)).second);
+ CHECK(handlers_.emplace(path, params).second);
}
void PrivetHandler::AddSecureHandler(const std::string& path,
@@ -479,7 +479,7 @@
params.handler = handler;
params.scope = scope;
params.https_only = true;
- CHECK(handlers_.insert(std::make_pair(path, params)).second);
+ CHECK(handlers_.emplace(path, params).second);
}
void PrivetHandler::HandleInfo(const base::DictionaryValue&,
diff --git a/src/privet/security_delegate.h b/src/privet/security_delegate.h
index 40f297f..1d28ba3 100644
--- a/src/privet/security_delegate.h
+++ b/src/privet/security_delegate.h
@@ -19,7 +19,7 @@
// Interface to provide Security related logic for |PrivetHandler|.
class SecurityDelegate {
public:
- virtual ~SecurityDelegate() {}
+ virtual ~SecurityDelegate() = default;
// Creates access token for the given scope, user id and |time|.
virtual std::string CreateAccessToken(const UserInfo& user_info,
diff --git a/src/privet/security_manager.cc b/src/privet/security_manager.cc
index 7e35e79..1b4e3c5 100644
--- a/src/privet/security_manager.cc
+++ b/src/privet/security_manager.cc
@@ -24,6 +24,10 @@
#include "src/string_utils.h"
#include "third_party/chromium/crypto/p224_spake.h"
+#if !defined(ARCH_CPU_LITTLE_ENDIAN)
+#error "Big-endian is not supported. See b/25017606"
+#endif
+
namespace weave {
namespace privet {
@@ -254,7 +258,7 @@
} while (confirmed_sessions_.find(session) != confirmed_sessions_.end() ||
pending_sessions_.find(session) != pending_sessions_.end());
std::string commitment = spake->GetMessage();
- pending_sessions_.insert(std::make_pair(session, std::move(spake)));
+ pending_sessions_.emplace(session, std::move(spake));
task_runner_->PostDelayedTask(
FROM_HERE,
@@ -313,8 +317,7 @@
std::vector<uint8_t> cert_hmac = HmacSha256(
std::vector<uint8_t>(key.begin(), key.end()), certificate_fingerprint_);
*signature = Base64Encode(cert_hmac);
- confirmed_sessions_.insert(
- std::make_pair(session->first, std::move(session->second)));
+ confirmed_sessions_.emplace(session->first, std::move(session->second));
task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(base::IgnoreResult(&SecurityManager::CloseConfirmedSession),
diff --git a/src/privet/security_manager.h b/src/privet/security_manager.h
index bb513e6..c99201b 100644
--- a/src/privet/security_manager.h
+++ b/src/privet/security_manager.h
@@ -41,7 +41,7 @@
class KeyExchanger {
public:
- virtual ~KeyExchanger() {}
+ virtual ~KeyExchanger() = default;
virtual const std::string& GetMessage() = 0;
virtual bool ProcessMessage(const std::string& message,
diff --git a/src/privet/security_manager_unittest.cc b/src/privet/security_manager_unittest.cc
index 0880b14..7229710 100644
--- a/src/privet/security_manager_unittest.cc
+++ b/src/privet/security_manager_unittest.cc
@@ -109,7 +109,7 @@
};
TEST_F(SecurityManagerTest, RandomSecret) {
- EXPECT_GE(security_.GetSecret().size(), 32u);
+ EXPECT_GE(security_.GetSecret().size(), 32);
EXPECT_TRUE(IsBase64(security_.GetSecret()));
}
diff --git a/src/privet/wifi_delegate.h b/src/privet/wifi_delegate.h
index 9bd5157..ae5e520 100644
--- a/src/privet/wifi_delegate.h
+++ b/src/privet/wifi_delegate.h
@@ -18,7 +18,7 @@
class WifiDelegate {
public:
WifiDelegate() = default;
- virtual ~WifiDelegate() {}
+ virtual ~WifiDelegate() = default;
// Returns status of the WiFi connection.
virtual const ConnectionState& GetConnectionState() const = 0;
diff --git a/src/privet/wifi_ssid_generator_unittest.cc b/src/privet/wifi_ssid_generator_unittest.cc
index 10680c8..398e105 100644
--- a/src/privet/wifi_ssid_generator_unittest.cc
+++ b/src/privet/wifi_ssid_generator_unittest.cc
@@ -23,7 +23,7 @@
};
TEST_F(WifiSsidGeneratorTest, GenerateFlagsNoHostedAp) {
- EXPECT_EQ(ssid_generator_.GenerateFlags().size(), 2u);
+ EXPECT_EQ(ssid_generator_.GenerateFlags().size(), 2);
wifi_.connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
gcd_.connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
@@ -42,7 +42,7 @@
TEST_F(WifiSsidGeneratorTest, GenerateFlagsWithHostedAp) {
EXPECT_CALL(wifi_, GetHostedSsid())
.WillRepeatedly(Return(ssid_generator_.GenerateSsid()));
- EXPECT_EQ(ssid_generator_.GenerateFlags().size(), 2u);
+ EXPECT_EQ(ssid_generator_.GenerateFlags().size(), 2);
wifi_.connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
gcd_.connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
@@ -59,7 +59,7 @@
}
TEST_F(WifiSsidGeneratorTest, GenerateSsid31orLess) {
- EXPECT_LE(ssid_generator_.GenerateSsid().size(), 31u);
+ EXPECT_LE(ssid_generator_.GenerateSsid().size(), 31);
}
TEST_F(WifiSsidGeneratorTest, GenerateSsidValue) {
diff --git a/src/states/mock_state_change_queue_interface.h b/src/states/mock_state_change_queue_interface.h
index 78c6c82..c3f50c0 100644
--- a/src/states/mock_state_change_queue_interface.h
+++ b/src/states/mock_state_change_queue_interface.h
@@ -16,11 +16,9 @@
class MockStateChangeQueueInterface : public StateChangeQueueInterface {
public:
MOCK_CONST_METHOD0(IsEmpty, bool());
- MOCK_METHOD2(MockNotifyPropertiesUpdated,
- bool(base::Time timestamp,
- const base::DictionaryValue& changed_properties));
- MOCK_METHOD0(MockGetAndClearRecordedStateChanges,
- std::vector<StateChange>&());
+ MOCK_METHOD2(NotifyPropertiesUpdated,
+ bool(base::Time timestamp, ValueMap changed_properties));
+ MOCK_METHOD0(GetAndClearRecordedStateChanges, std::vector<StateChange>());
MOCK_CONST_METHOD0(GetLastStateChangeId, UpdateID());
MOCK_METHOD1(MockAddOnStateUpdatedCallback,
base::CallbackList<void(UpdateID)>::Subscription*(
@@ -28,15 +26,6 @@
MOCK_METHOD1(NotifyStateUpdatedOnServer, void(UpdateID));
private:
- bool NotifyPropertiesUpdated(
- base::Time timestamp,
- std::unique_ptr<base::DictionaryValue> changed_properties) override {
- return MockNotifyPropertiesUpdated(timestamp, *changed_properties);
- }
- std::vector<StateChange> GetAndClearRecordedStateChanges() override {
- return std::move(MockGetAndClearRecordedStateChanges());
- }
-
Token AddOnStateUpdatedCallback(
const base::Callback<void(UpdateID)>& callback) override {
return Token{MockAddOnStateUpdatedCallback(callback)};
diff --git a/src/states/state_change_queue.cc b/src/states/state_change_queue.cc
index ba7bf65..288dadc 100644
--- a/src/states/state_change_queue.cc
+++ b/src/states/state_change_queue.cc
@@ -5,7 +5,6 @@
#include "src/states/state_change_queue.h"
#include <base/logging.h>
-#include <base/values.h>
namespace weave {
@@ -14,15 +13,12 @@
CHECK_GT(max_queue_size_, 0U) << "Max queue size must not be zero";
}
-bool StateChangeQueue::NotifyPropertiesUpdated(
- base::Time timestamp,
- std::unique_ptr<base::DictionaryValue> changed_properties) {
+bool StateChangeQueue::NotifyPropertiesUpdated(base::Time timestamp,
+ ValueMap changed_properties) {
auto& stored_changes = state_changes_[timestamp];
// Merge the old property set.
- if (stored_changes)
- stored_changes->MergeDictionary(changed_properties.get());
- else
- stored_changes = std::move(changed_properties);
+ changed_properties.insert(stored_changes.begin(), stored_changes.end());
+ stored_changes = std::move(changed_properties);
while (state_changes_.size() > max_queue_size_) {
// Queue is full.
@@ -34,8 +30,8 @@
auto element_old = state_changes_.begin();
auto element_new = std::next(element_old);
// This will skip elements that exist in both [old] and [new].
- element_old->second->MergeDictionary(element_new->second.get());
- std::swap(element_old->second, element_new->second);
+ element_new->second.insert(element_old->second.begin(),
+ element_old->second.end());
state_changes_.erase(element_old);
}
++last_change_id_;
@@ -45,8 +41,8 @@
std::vector<StateChange> StateChangeQueue::GetAndClearRecordedStateChanges() {
std::vector<StateChange> changes;
changes.reserve(state_changes_.size());
- for (auto& pair : state_changes_) {
- changes.push_back(StateChange{pair.first, std::move(pair.second)});
+ for (const auto& pair : state_changes_) {
+ changes.emplace_back(pair.first, std::move(pair.second));
}
state_changes_.clear();
return changes;
diff --git a/src/states/state_change_queue.h b/src/states/state_change_queue.h
index 314f746..00b827f 100644
--- a/src/states/state_change_queue.h
+++ b/src/states/state_change_queue.h
@@ -21,9 +21,8 @@
// Overrides from StateChangeQueueInterface.
bool IsEmpty() const override { return state_changes_.empty(); }
- bool NotifyPropertiesUpdated(
- base::Time timestamp,
- std::unique_ptr<base::DictionaryValue> changed_properties) override;
+ bool NotifyPropertiesUpdated(base::Time timestamp,
+ ValueMap changed_properties) override;
std::vector<StateChange> GetAndClearRecordedStateChanges() override;
UpdateID GetLastStateChangeId() const override { return last_change_id_; }
Token AddOnStateUpdatedCallback(
@@ -36,7 +35,7 @@
const size_t max_queue_size_;
// Accumulated list of device state change notifications.
- std::map<base::Time, std::unique_ptr<base::DictionaryValue>> state_changes_;
+ std::map<base::Time, ValueMap> state_changes_;
// An ID of last state change update. Each NotifyPropertiesUpdated()
// invocation increments this value by 1.
diff --git a/src/states/state_change_queue_interface.h b/src/states/state_change_queue_interface.h
index 631a127..e3b3650 100644
--- a/src/states/state_change_queue_interface.h
+++ b/src/states/state_change_queue_interface.h
@@ -10,9 +10,7 @@
#include <base/callback_list.h>
#include <base/time/time.h>
-namespace base {
-class DictionaryValue;
-} // namespace base
+#include "src/commands/schema_utils.h"
namespace weave {
@@ -21,11 +19,10 @@
// |changed_properties| contains a property set with the new property values
// which were updated at the time the event was recorded.
struct StateChange {
- StateChange(base::Time time,
- std::unique_ptr<base::DictionaryValue> properties)
+ StateChange(base::Time time, ValueMap properties)
: timestamp{time}, changed_properties{std::move(properties)} {}
base::Time timestamp;
- std::unique_ptr<base::DictionaryValue> changed_properties;
+ ValueMap changed_properties;
};
// An abstract interface to StateChangeQueue to record and retrieve state
@@ -40,9 +37,8 @@
virtual bool IsEmpty() const = 0;
// Called by StateManager when device state properties are updated.
- virtual bool NotifyPropertiesUpdated(
- base::Time timestamp,
- std::unique_ptr<base::DictionaryValue> changed_properties) = 0;
+ virtual bool NotifyPropertiesUpdated(base::Time timestamp,
+ ValueMap changed_properties) = 0;
// Returns the recorded state changes since last time this method was called.
virtual std::vector<StateChange> GetAndClearRecordedStateChanges() = 0;
diff --git a/src/states/state_change_queue_unittest.cc b/src/states/state_change_queue_unittest.cc
index bb2947a..9f26071 100644
--- a/src/states/state_change_queue_unittest.cc
+++ b/src/states/state_change_queue_unittest.cc
@@ -5,14 +5,12 @@
#include "src/states/state_change_queue.h"
#include <gtest/gtest.h>
-#include <weave/test/unittest_utils.h>
#include "src/bind_lambda.h"
+#include "src/commands/unittest_utils.h"
namespace weave {
-using test::CreateDictionaryValue;
-
class StateChangeQueueTest : public ::testing::Test {
public:
void SetUp() override { queue_.reset(new StateChangeQueue(100)); }
@@ -24,44 +22,48 @@
TEST_F(StateChangeQueueTest, Empty) {
EXPECT_TRUE(queue_->IsEmpty());
- EXPECT_EQ(0u, queue_->GetLastStateChangeId());
+ EXPECT_EQ(0, queue_->GetLastStateChangeId());
EXPECT_TRUE(queue_->GetAndClearRecordedStateChanges().empty());
}
TEST_F(StateChangeQueueTest, UpdateOne) {
- auto timestamp = base::Time::Now();
- ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
- timestamp, CreateDictionaryValue("{'prop': {'name': 23}}")));
+ StateChange change{base::Time::Now(),
+ ValueMap{{"prop.name", test::make_int_prop_value(23)}}};
+ ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change.timestamp,
+ change.changed_properties));
EXPECT_FALSE(queue_->IsEmpty());
- EXPECT_EQ(1u, queue_->GetLastStateChangeId());
+ EXPECT_EQ(1, queue_->GetLastStateChangeId());
auto changes = queue_->GetAndClearRecordedStateChanges();
- EXPECT_EQ(1u, queue_->GetLastStateChangeId());
- ASSERT_EQ(1u, changes.size());
- EXPECT_EQ(timestamp, changes.front().timestamp);
- EXPECT_JSON_EQ("{'prop':{'name': 23}}", *changes.front().changed_properties);
+ EXPECT_EQ(1, queue_->GetLastStateChangeId());
+ ASSERT_EQ(1, changes.size());
+ EXPECT_EQ(change.timestamp, changes.front().timestamp);
+ EXPECT_EQ(change.changed_properties, changes.front().changed_properties);
EXPECT_TRUE(queue_->IsEmpty());
EXPECT_TRUE(queue_->GetAndClearRecordedStateChanges().empty());
}
-TEST_F(StateChangeQueueTest, UpdateMany) {
- auto timestamp1 = base::Time::Now();
- const std::string state1 = "{'prop': {'name1': 23}}";
- auto timestamp2 = timestamp1 + base::TimeDelta::FromSeconds(1);
- const std::string state2 =
- "{'prop': {'name1': 17, 'name2': 1.0, 'name3': false}}";
- ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
- timestamp1, CreateDictionaryValue(state1)));
- ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
- timestamp2, CreateDictionaryValue(state2)));
-
- EXPECT_EQ(2u, queue_->GetLastStateChangeId());
+// TODO(vitalybuka): Fix flakiness.
+TEST_F(StateChangeQueueTest, DISABLED_UpdateMany) {
+ StateChange change1{base::Time::Now(),
+ ValueMap{{"prop.name1", test::make_int_prop_value(23)}}};
+ ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change1.timestamp,
+ change1.changed_properties));
+ StateChange change2{base::Time::Now(),
+ ValueMap{
+ {"prop.name1", test::make_int_prop_value(17)},
+ {"prop.name2", test::make_double_prop_value(1.0)},
+ {"prop.name3", test::make_bool_prop_value(false)},
+ }};
+ ASSERT_TRUE(queue_->NotifyPropertiesUpdated(change2.timestamp,
+ change2.changed_properties));
+ EXPECT_EQ(2, queue_->GetLastStateChangeId());
EXPECT_FALSE(queue_->IsEmpty());
auto changes = queue_->GetAndClearRecordedStateChanges();
- ASSERT_EQ(2u, changes.size());
- EXPECT_EQ(timestamp1, changes[0].timestamp);
- EXPECT_JSON_EQ(state1, *changes[0].changed_properties);
- EXPECT_EQ(timestamp2, changes[1].timestamp);
- EXPECT_JSON_EQ(state2, *changes[1].changed_properties);
+ ASSERT_EQ(2, changes.size());
+ EXPECT_EQ(change1.timestamp, changes[0].timestamp);
+ EXPECT_EQ(change1.changed_properties, changes[0].changed_properties);
+ EXPECT_EQ(change2.timestamp, changes[1].timestamp);
+ EXPECT_EQ(change2.changed_properties, changes[1].changed_properties);
EXPECT_TRUE(queue_->IsEmpty());
EXPECT_TRUE(queue_->GetAndClearRecordedStateChanges().empty());
}
@@ -71,27 +73,33 @@
base::TimeDelta time_delta = base::TimeDelta::FromMinutes(1);
ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
- timestamp, CreateDictionaryValue("{'prop': {'name1': 1}}")));
+ timestamp, ValueMap{{"prop.name1", test::make_int_prop_value(1)}}));
ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
- timestamp, CreateDictionaryValue("{'prop': {'name2': 2}}")));
+ timestamp, ValueMap{{"prop.name2", test::make_int_prop_value(2)}}));
ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
- timestamp, CreateDictionaryValue("{'prop': {'name1': 3}}")));
+ timestamp, ValueMap{{"prop.name1", test::make_int_prop_value(3)}}));
ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
- timestamp + time_delta, CreateDictionaryValue("{'prop': {'name1': 4}}")));
+ timestamp + time_delta,
+ ValueMap{{"prop.name1", test::make_int_prop_value(4)}}));
auto changes = queue_->GetAndClearRecordedStateChanges();
- EXPECT_EQ(4u, queue_->GetLastStateChangeId());
- ASSERT_EQ(2u, changes.size());
+ EXPECT_EQ(4, queue_->GetLastStateChangeId());
+ ASSERT_EQ(2, changes.size());
- const std::string expected1 = "{'prop': {'name1': 3, 'name2': 2}}";
- const std::string expected2 = "{'prop': {'name1': 4}}";
+ ValueMap expected1{
+ {"prop.name1", test::make_int_prop_value(3)},
+ {"prop.name2", test::make_int_prop_value(2)},
+ };
+ ValueMap expected2{
+ {"prop.name1", test::make_int_prop_value(4)},
+ };
EXPECT_EQ(timestamp, changes[0].timestamp);
- EXPECT_JSON_EQ(expected1, *changes[0].changed_properties);
+ EXPECT_EQ(expected1, changes[0].changed_properties);
EXPECT_EQ(timestamp + time_delta, changes[1].timestamp);
- EXPECT_JSON_EQ(expected2, *changes[1].changed_properties);
+ EXPECT_EQ(expected2, changes[1].changed_properties);
}
TEST_F(StateChangeQueueTest, MaxQueueSize) {
@@ -101,29 +109,43 @@
base::TimeDelta time_delta2 = base::TimeDelta::FromMinutes(3);
ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
- start_time, CreateDictionaryValue("{'prop': {'name1': 1, 'name2': 2}}")));
+ start_time, ValueMap{
+ {"prop.name1", test::make_int_prop_value(1)},
+ {"prop.name2", test::make_int_prop_value(2)},
+ }));
ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
start_time + time_delta1,
- CreateDictionaryValue("{'prop': {'name1': 3, 'name3': 4}}")));
+ ValueMap{
+ {"prop.name1", test::make_int_prop_value(3)},
+ {"prop.name3", test::make_int_prop_value(4)},
+ }));
ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
start_time + time_delta2,
- CreateDictionaryValue("{'prop': {'name10': 10, 'name11': 11}}")));
+ ValueMap{
+ {"prop.name10", test::make_int_prop_value(10)},
+ {"prop.name11", test::make_int_prop_value(11)},
+ }));
- EXPECT_EQ(3u, queue_->GetLastStateChangeId());
+ EXPECT_EQ(3, queue_->GetLastStateChangeId());
auto changes = queue_->GetAndClearRecordedStateChanges();
- ASSERT_EQ(2u, changes.size());
+ ASSERT_EQ(2, changes.size());
- const std::string expected1 =
- "{'prop': {'name1': 3, 'name2': 2, 'name3': 4}}";
+ ValueMap expected1{
+ {"prop.name1", test::make_int_prop_value(3)},
+ {"prop.name2", test::make_int_prop_value(2)},
+ {"prop.name3", test::make_int_prop_value(4)},
+ };
EXPECT_EQ(start_time + time_delta1, changes[0].timestamp);
- EXPECT_JSON_EQ(expected1, *changes[0].changed_properties);
+ EXPECT_EQ(expected1, changes[0].changed_properties);
- const std::string expected2 =
- "{'prop': {'name10': 10, 'name11': 11}}";
+ ValueMap expected2{
+ {"prop.name10", test::make_int_prop_value(10)},
+ {"prop.name11", test::make_int_prop_value(11)},
+ };
EXPECT_EQ(start_time + time_delta2, changes[1].timestamp);
- EXPECT_JSON_EQ(expected2, *changes[1].changed_properties);
+ EXPECT_EQ(expected2, changes[1].changed_properties);
}
TEST_F(StateChangeQueueTest, ImmediateStateChangeNotification) {
@@ -139,8 +161,10 @@
TEST_F(StateChangeQueueTest, DelayedStateChangeNotification) {
// When queue is not empty, registering a new callback will not trigger it.
ASSERT_TRUE(queue_->NotifyPropertiesUpdated(
- base::Time::Now(),
- CreateDictionaryValue("{'prop': {'name1': 1, 'name3': 2}}")));
+ base::Time::Now(), ValueMap{
+ {"prop.name1", test::make_int_prop_value(1)},
+ {"prop.name2", test::make_int_prop_value(2)},
+ }));
auto callback = [](StateChangeQueueInterface::UpdateID id) {
FAIL() << "This should not be called";
diff --git a/src/states/state_manager.cc b/src/states/state_manager.cc
index b170b86..fe6e669 100644
--- a/src/states/state_manager.cc
+++ b/src/states/state_manager.cc
@@ -27,17 +27,14 @@
callback.Run(); // Force to read current state.
}
-const base::DictionaryValue& StateManager::GetState() const {
- if (!cached_dict_valid_) {
- cached_dict_.Clear();
- for (const auto& pair : packages_) {
- cached_dict_.SetWithoutPathExpansion(
- pair.first, pair.second->GetValuesAsJson().DeepCopy());
- }
- cached_dict_valid_ = true;
+std::unique_ptr<base::DictionaryValue> StateManager::GetState() const {
+ std::unique_ptr<base::DictionaryValue> dict{new base::DictionaryValue};
+ for (const auto& pair : packages_) {
+ auto pkg_value = pair.second->GetValuesAsJson();
+ CHECK(pkg_value);
+ dict->SetWithoutPathExpansion(pair.first, pkg_value.release());
}
-
- return cached_dict_;
+ return dict;
}
bool StateManager::SetProperty(const std::string& name,
@@ -49,7 +46,8 @@
return result;
}
-const base::Value* StateManager::GetProperty(const std::string& name) const {
+std::unique_ptr<base::Value> StateManager::GetProperty(
+ const std::string& name) const {
auto parts = SplitAtFirst(name, ".", true);
const std::string& package_name = parts.first;
const std::string& property_name = parts.second;
@@ -95,11 +93,8 @@
if (!package->SetPropertyValue(property_name, value, error))
return false;
- cached_dict_valid_ = false;
-
- std::unique_ptr<base::DictionaryValue> prop_set{new base::DictionaryValue};
- prop_set->Set(full_property_name, value.DeepCopy());
- state_change_queue_->NotifyPropertiesUpdated(timestamp, std::move(prop_set));
+ ValueMap prop_set{{full_property_name, package->GetProperty(property_name)}};
+ state_change_queue_->NotifyPropertiesUpdated(timestamp, prop_set);
return true;
}
@@ -222,9 +217,8 @@
StatePackage* package = FindPackage(package_name);
if (package == nullptr) {
std::unique_ptr<StatePackage> new_package{new StatePackage(package_name)};
- package =
- packages_.insert(std::make_pair(package_name, std::move(new_package)))
- .first->second.get();
+ package = packages_.emplace(package_name, std::move(new_package))
+ .first->second.get();
}
return package;
}
diff --git a/src/states/state_manager.h b/src/states/state_manager.h
index c48a57c..55faf76 100644
--- a/src/states/state_manager.h
+++ b/src/states/state_manager.h
@@ -39,11 +39,11 @@
bool LoadStateDefinitionFromJson(const std::string& json, ErrorPtr* error);
bool SetProperties(const base::DictionaryValue& dict, ErrorPtr* error);
bool SetPropertiesFromJson(const std::string& json, ErrorPtr* error);
- const base::Value* GetProperty(const std::string& name) const;
+ std::unique_ptr<base::Value> GetProperty(const std::string& name) const;
bool SetProperty(const std::string& name,
const base::Value& value,
ErrorPtr* error);
- const base::DictionaryValue& GetState() const;
+ std::unique_ptr<base::DictionaryValue> GetState() const;
// Returns the recorded state changes since last time this method has been
// called.
@@ -80,9 +80,6 @@
std::vector<base::Closure> on_changed_;
- mutable base::DictionaryValue cached_dict_;
- mutable bool cached_dict_valid_ = false;
-
DISALLOW_COPY_AND_ASSIGN(StateManager);
};
diff --git a/src/states/state_manager_unittest.cc b/src/states/state_manager_unittest.cc
index 6899646..3f854fe 100644
--- a/src/states/state_manager_unittest.cc
+++ b/src/states/state_manager_unittest.cc
@@ -12,9 +12,9 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <weave/provider/test/mock_config_store.h>
-#include <weave/test/unittest_utils.h>
#include "src/commands/schema_constants.h"
+#include "src/commands/unittest_utils.h"
#include "src/states/error_codes.h"
#include "src/states/mock_state_change_queue_interface.h"
@@ -22,18 +22,17 @@
using testing::_;
using testing::Return;
-using testing::ReturnRef;
using test::CreateDictionaryValue;
namespace {
const char kBaseDefinition[] = R"({
"base": {
- "manufacturer":{"type":"string"},
- "serialNumber":{"type":"string"}
+ "manufacturer":"string",
+ "serialNumber":"string"
},
"device": {
- "state_property":{"type":"string"}
+ "state_property":"string"
}
})";
@@ -52,10 +51,6 @@
return CreateDictionaryValue(kBaseDefaults);
}
-MATCHER_P(IsState, str, "") {
- return arg.Equals(CreateDictionaryValue(str).get());
-}
-
} // anonymous namespace
class StateManagerTest : public ::testing::Test {
@@ -63,9 +58,9 @@
void SetUp() override {
// Initial expectations.
EXPECT_CALL(mock_state_change_queue_, IsEmpty()).Times(0);
- EXPECT_CALL(mock_state_change_queue_, MockNotifyPropertiesUpdated(_, _))
+ EXPECT_CALL(mock_state_change_queue_, NotifyPropertiesUpdated(_, _))
.WillRepeatedly(Return(true));
- EXPECT_CALL(mock_state_change_queue_, MockGetAndClearRecordedStateChanges())
+ EXPECT_CALL(mock_state_change_queue_, GetAndClearRecordedStateChanges())
.Times(0);
mgr_.reset(new StateManager(&mock_state_change_queue_));
@@ -107,9 +102,11 @@
'manufacturer': 'Test Factory',
'serialNumber': 'Test Model'
},
- 'device': {}
+ 'device': {
+ 'state_property': ''
+ }
})";
- EXPECT_JSON_EQ(expected, mgr_->GetState());
+ EXPECT_JSON_EQ(expected, *mgr_->GetState());
}
TEST_F(StateManagerTest, LoadStateDefinition) {
@@ -125,10 +122,14 @@
'manufacturer': 'Test Factory',
'serialNumber': 'Test Model'
},
- 'power': {},
- 'device': {}
+ 'power': {
+ 'battery_level': 0
+ },
+ 'device': {
+ 'state_property': ''
+ }
})";
- EXPECT_JSON_EQ(expected, mgr_->GetState());
+ EXPECT_JSON_EQ(expected, *mgr_->GetState());
}
TEST_F(StateManagerTest, Startup) {
@@ -136,15 +137,12 @@
auto state_definition = R"({
"base": {
- "firmwareVersion": {"type":"string"},
- "localDiscoveryEnabled": {"type":"boolean"},
- "localAnonymousAccessMaxRole": {
- "type": "string",
- "enum": ["none", "viewer", "user"]
- },
- "localPairingEnabled": {"type":"boolean"}
+ "firmwareVersion": "string",
+ "localDiscoveryEnabled": "boolean",
+ "localAnonymousAccessMaxRole": [ "none", "viewer", "user" ],
+ "localPairingEnabled": "boolean"
},
- "power": {"battery_level":{"type":"integer"}}
+ "power": {"battery_level":"integer"}
})";
ASSERT_TRUE(manager.LoadStateDefinitionFromJson(state_definition, nullptr));
@@ -170,13 +168,15 @@
'battery_level': 44
}
})";
- EXPECT_JSON_EQ(expected, manager.GetState());
+ EXPECT_JSON_EQ(expected, *manager.GetState());
}
TEST_F(StateManagerTest, SetPropertyValue) {
- const std::string state = "{'device': {'state_property': 'Test Value'}}";
+ ValueMap expected_prop_set{
+ {"device.state_property", test::make_string_prop_value("Test Value")},
+ };
EXPECT_CALL(mock_state_change_queue_,
- MockNotifyPropertiesUpdated(timestamp_, IsState(state)))
+ NotifyPropertiesUpdated(timestamp_, expected_prop_set))
.WillOnce(Return(true));
ASSERT_TRUE(SetPropertyValue("device.state_property",
base::StringValue{"Test Value"}, nullptr));
@@ -189,7 +189,7 @@
'state_property': 'Test Value'
}
})";
- EXPECT_JSON_EQ(expected, mgr_->GetState());
+ EXPECT_JSON_EQ(expected, *mgr_->GetState());
}
TEST_F(StateManagerTest, SetPropertyValue_Error_NoName) {
@@ -216,36 +216,39 @@
}
TEST_F(StateManagerTest, SetPropertyValue_Error_UnknownProperty) {
- ASSERT_TRUE(
- SetPropertyValue("base.level", base::FundamentalValue{0}, nullptr));
+ ErrorPtr error;
+ ASSERT_FALSE(
+ SetPropertyValue("base.level", base::FundamentalValue{0}, &error));
+ EXPECT_EQ(errors::state::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode());
}
TEST_F(StateManagerTest, GetAndClearRecordedStateChanges) {
- EXPECT_CALL(mock_state_change_queue_,
- MockNotifyPropertiesUpdated(timestamp_, _))
+ EXPECT_CALL(mock_state_change_queue_, NotifyPropertiesUpdated(timestamp_, _))
.WillOnce(Return(true));
ASSERT_TRUE(SetPropertyValue("device.state_property",
base::StringValue{"Test Value"}, nullptr));
- std::vector<StateChange> expected_state;
- const std::string expected_val =
- "{'device': {'state_property': 'Test Value'}}";
- expected_state.emplace_back(
- timestamp_,
- CreateDictionaryValue(expected_val));
- EXPECT_CALL(mock_state_change_queue_, MockGetAndClearRecordedStateChanges())
- .WillOnce(ReturnRef(expected_state));
+ std::vector<StateChange> expected_val;
+ expected_val.emplace_back(
+ timestamp_, ValueMap{{"device.state_property",
+ test::make_string_prop_value("Test Value")}});
+ EXPECT_CALL(mock_state_change_queue_, GetAndClearRecordedStateChanges())
+ .WillOnce(Return(expected_val));
EXPECT_CALL(mock_state_change_queue_, GetLastStateChangeId())
.WillOnce(Return(0));
auto changes = mgr_->GetAndClearRecordedStateChanges();
- ASSERT_EQ(1u, changes.second.size());
- EXPECT_EQ(timestamp_, changes.second.back().timestamp);
- EXPECT_JSON_EQ(expected_val, *changes.second.back().changed_properties);
+ ASSERT_EQ(1, changes.second.size());
+ EXPECT_EQ(expected_val.back().timestamp, changes.second.back().timestamp);
+ EXPECT_EQ(expected_val.back().changed_properties,
+ changes.second.back().changed_properties);
}
TEST_F(StateManagerTest, SetProperties) {
- const std::string state = "{'base': {'manufacturer': 'No Name'}}";
+ ValueMap expected_prop_set{
+ {"base.manufacturer", test::make_string_prop_value("No Name")},
+ };
EXPECT_CALL(mock_state_change_queue_,
- MockNotifyPropertiesUpdated(_, IsState(state)))
+ NotifyPropertiesUpdated(_, expected_prop_set))
.WillOnce(Return(true));
EXPECT_CALL(*this, OnStateChanged()).Times(1);
@@ -257,14 +260,16 @@
'manufacturer': 'No Name',
'serialNumber': 'Test Model'
},
- 'device': {}
+ 'device': {
+ 'state_property': ''
+ }
})";
- EXPECT_JSON_EQ(expected, mgr_->GetState());
+ EXPECT_JSON_EQ(expected, *mgr_->GetState());
}
TEST_F(StateManagerTest, GetProperty) {
EXPECT_JSON_EQ("'Test Model'", *mgr_->GetProperty("base.serialNumber"));
- EXPECT_EQ(nullptr, mgr_->GetProperty("device.state_property"));
+ EXPECT_JSON_EQ("''", *mgr_->GetProperty("device.state_property"));
EXPECT_EQ(nullptr, mgr_->GetProperty("device.unknown"));
EXPECT_EQ(nullptr, mgr_->GetProperty("unknown.state_property"));
}
diff --git a/src/states/state_package.cc b/src/states/state_package.cc
index b0ea199..0b8e219 100644
--- a/src/states/state_package.cc
+++ b/src/states/state_package.cc
@@ -7,6 +7,9 @@
#include <base/logging.h>
#include <base/values.h>
+#include "src/commands/prop_types.h"
+#include "src/commands/prop_values.h"
+#include "src/commands/schema_utils.h"
#include "src/states/error_codes.h"
namespace weave {
@@ -15,18 +18,28 @@
bool StatePackage::AddSchemaFromJson(const base::DictionaryValue* json,
ErrorPtr* error) {
+ ObjectSchema schema;
+ if (!schema.FromJson(json, nullptr, error))
+ return false;
+
// Scan first to make sure we have no property redefinitions.
- for (base::DictionaryValue::Iterator it(*json); !it.IsAtEnd(); it.Advance()) {
- if (types_.HasKey(it.key())) {
+ for (const auto& pair : schema.GetProps()) {
+ if (types_.GetProp(pair.first)) {
Error::AddToPrintf(error, FROM_HERE, errors::state::kDomain,
errors::state::kPropertyRedefinition,
"State property '%s.%s' is already defined",
- name_.c_str(), it.key().c_str());
+ name_.c_str(), pair.first.c_str());
return false;
}
}
- types_.MergeDictionary(json);
+ // Now move all the properties to |types_| object.
+ for (const auto& pair : schema.GetProps()) {
+ types_.AddProp(pair.first, pair.second->Clone());
+ // Create default value for this state property.
+ values_.emplace(pair.first, pair.second->CreateDefaultValue());
+ }
+
return true;
}
@@ -39,15 +52,21 @@
return true;
}
-const base::DictionaryValue& StatePackage::GetValuesAsJson() const {
- return values_;
+std::unique_ptr<base::DictionaryValue> StatePackage::GetValuesAsJson() const {
+ std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+ for (const auto& pair : values_) {
+ auto value = pair.second->ToJson();
+ CHECK(value);
+ dict->SetWithoutPathExpansion(pair.first, value.release());
+ }
+ return dict;
}
-const base::Value* StatePackage::GetPropertyValue(
+std::unique_ptr<base::Value> StatePackage::GetPropertyValue(
const std::string& property_name,
ErrorPtr* error) const {
- const base::Value* value = nullptr;
- if (!values_.Get(property_name, &value)) {
+ auto it = values_.find(property_name);
+ if (it == values_.end()) {
Error::AddToPrintf(error, FROM_HERE, errors::state::kDomain,
errors::state::kPropertyNotDefined,
"State property '%s.%s' is not defined", name_.c_str(),
@@ -55,13 +74,24 @@
return nullptr;
}
- return value;
+ return it->second->ToJson();
}
bool StatePackage::SetPropertyValue(const std::string& property_name,
const base::Value& value,
ErrorPtr* error) {
- values_.Set(property_name, value.DeepCopy());
+ auto it = values_.find(property_name);
+ if (it == values_.end()) {
+ Error::AddToPrintf(error, FROM_HERE, errors::state::kDomain,
+ errors::state::kPropertyNotDefined,
+ "State property '%s.%s' is not defined", name_.c_str(),
+ property_name.c_str());
+ return false;
+ }
+ auto new_value = it->second->GetPropType()->CreatePropValue(value, error);
+ if (!new_value)
+ return false;
+ it->second = std::move(new_value);
return true;
}
diff --git a/src/states/state_package.h b/src/states/state_package.h
index 4092948..e8e3964 100644
--- a/src/states/state_package.h
+++ b/src/states/state_package.h
@@ -10,9 +10,15 @@
#include <string>
#include <base/macros.h>
-#include <base/values.h>
#include <weave/error.h>
+#include "src/commands/object_schema.h"
+#include "src/commands/prop_values.h"
+
+namespace base {
+class DictionaryValue;
+} // namespace base
+
namespace weave {
// A package is a set of related state properties. GCD specification defines
@@ -43,25 +49,33 @@
// "message": "Printer low on cyan ink"
// }
// }
- const base::DictionaryValue& GetValuesAsJson() const;
+ std::unique_ptr<base::DictionaryValue> GetValuesAsJson() const;
// Gets the value for a specific state property. |property_name| must not
// include the package name as part of the property name.
- const base::Value* GetPropertyValue(const std::string& property_name,
- ErrorPtr* error) const;
+ std::unique_ptr<base::Value> GetPropertyValue(
+ const std::string& property_name,
+ ErrorPtr* error) const;
// Sets the value for a specific state property. |property_name| must not
// include the package name as part of the property name.
bool SetPropertyValue(const std::string& property_name,
const base::Value& value,
ErrorPtr* error);
+ std::shared_ptr<const PropValue> GetProperty(
+ const std::string& property_name) const {
+ auto it = values_.find(property_name);
+ return it != values_.end() ? it->second
+ : std::shared_ptr<const PropValue>{};
+ }
+
// Returns the name of the this package.
const std::string& GetName() const { return name_; }
private:
std::string name_;
- base::DictionaryValue types_;
- base::DictionaryValue values_;
+ ObjectSchema types_;
+ ValueMap values_;
friend class StatePackageTestHelper;
DISALLOW_COPY_AND_ASSIGN(StatePackage);
diff --git a/src/states/state_package_unittest.cc b/src/states/state_package_unittest.cc
index d09625a..f958bf5 100644
--- a/src/states/state_package_unittest.cc
+++ b/src/states/state_package_unittest.cc
@@ -9,9 +9,9 @@
#include <base/values.h>
#include <gtest/gtest.h>
-#include <weave/test/unittest_utils.h>
#include "src/commands/schema_constants.h"
+#include "src/commands/unittest_utils.h"
#include "src/states/error_codes.h"
namespace weave {
@@ -21,11 +21,11 @@
class StatePackageTestHelper {
public:
// Returns the state property definitions (types/constraints/etc).
- static const base::DictionaryValue& GetTypes(const StatePackage& package) {
+ static const ObjectSchema& GetTypes(const StatePackage& package) {
return package.types_;
}
// Returns the all state property values in this package.
- static const base::DictionaryValue& GetValues(const StatePackage& package) {
+ static const ValueMap& GetValues(const StatePackage& package) {
return package.values_;
}
};
@@ -33,30 +33,15 @@
namespace {
std::unique_ptr<base::DictionaryValue> GetTestSchema() {
return CreateDictionaryValue(R"({
- 'color': {
- 'type': 'string'
+ 'light': 'boolean',
+ 'color': 'string',
+ 'direction':{
+ 'properties':{
+ 'azimuth': {'type': 'number', 'isRequired': true},
+ 'altitude': {'maximum': 90.0}
+ }
},
- 'direction': {
- 'additionalProperties': false,
- 'properties': {
- 'altitude': {
- 'maximum': 90.0,
- 'type': 'number'
- },
- 'azimuth': {
- 'type': 'number'
- }
- },
- 'type': 'object',
- 'required': [ 'azimuth' ]
- },
- 'iso': {
- 'enum': [50, 100, 200, 400],
- 'type': 'integer'
- },
- 'light': {
- 'type': 'boolean'
- }
+ 'iso': [50, 100, 200, 400]
})");
}
@@ -69,11 +54,11 @@
})");
}
-inline const base::DictionaryValue& GetTypes(const StatePackage& package) {
+inline const ObjectSchema& GetTypes(const StatePackage& package) {
return StatePackageTestHelper::GetTypes(package);
}
// Returns the all state property values in this package.
-inline const base::DictionaryValue& GetValues(const StatePackage& package) {
+inline const ValueMap& GetValues(const StatePackage& package) {
return StatePackageTestHelper::GetValues(package);
}
@@ -93,15 +78,15 @@
TEST(StatePackage, Empty) {
StatePackage package("test");
EXPECT_EQ("test", package.GetName());
- EXPECT_TRUE(GetTypes(package).empty());
+ EXPECT_TRUE(GetTypes(package).GetProps().empty());
EXPECT_TRUE(GetValues(package).empty());
}
TEST(StatePackage, AddSchemaFromJson_OnEmpty) {
StatePackage package("test");
ASSERT_TRUE(package.AddSchemaFromJson(GetTestSchema().get(), nullptr));
- EXPECT_EQ(4u, GetTypes(package).size());
- EXPECT_EQ(0u, GetValues(package).size());
+ EXPECT_EQ(4, GetTypes(package).GetProps().size());
+ EXPECT_EQ(4, GetValues(package).size());
auto expected = R"({
'color': {
@@ -129,16 +114,22 @@
'type': 'boolean'
}
})";
- EXPECT_JSON_EQ(expected, GetTypes(package));
+ EXPECT_JSON_EQ(expected, *GetTypes(package).ToJson(true, false));
- EXPECT_JSON_EQ("{}", package.GetValuesAsJson());
+ expected = R"({
+ 'color': '',
+ 'direction': {},
+ 'iso': 0,
+ 'light': false
+ })";
+ EXPECT_JSON_EQ(expected, *package.GetValuesAsJson());
}
TEST(StatePackage, AddValuesFromJson_OnEmpty) {
StatePackage package("test");
ASSERT_TRUE(package.AddSchemaFromJson(GetTestSchema().get(), nullptr));
ASSERT_TRUE(package.AddValuesFromJson(GetTestValues().get(), nullptr));
- EXPECT_EQ(4u, GetValues(package).size());
+ EXPECT_EQ(4, GetValues(package).size());
auto expected = R"({
'color': 'white',
'direction': {
@@ -148,17 +139,14 @@
'iso': 200,
'light': true
})";
- EXPECT_JSON_EQ(expected, package.GetValuesAsJson());
+ EXPECT_JSON_EQ(expected, *package.GetValuesAsJson());
}
TEST_F(StatePackageTest, AddSchemaFromJson_AddMore) {
- auto dict = CreateDictionaryValue(R"({'brightness':{
- 'enum': ['low', 'medium', 'high'],
- 'type': 'string'
- }})");
+ auto dict = CreateDictionaryValue("{'brightness':['low', 'medium', 'high']}");
ASSERT_TRUE(package_->AddSchemaFromJson(dict.get(), nullptr));
- EXPECT_EQ(5u, GetTypes(*package_).size());
- EXPECT_EQ(4u, GetValues(*package_).size());
+ EXPECT_EQ(5, GetTypes(*package_).GetProps().size());
+ EXPECT_EQ(5, GetValues(*package_).size());
auto expected = R"({
'brightness': {
'enum': ['low', 'medium', 'high'],
@@ -189,9 +177,10 @@
'type': 'boolean'
}
})";
- EXPECT_JSON_EQ(expected, GetTypes(*package_));
+ EXPECT_JSON_EQ(expected, *GetTypes(*package_).ToJson(true, false));
expected = R"({
+ 'brightness': '',
'color': 'white',
'direction': {
'altitude': 89.9,
@@ -200,18 +189,15 @@
'iso': 200,
'light': true
})";
- EXPECT_JSON_EQ(expected, package_->GetValuesAsJson());
+ EXPECT_JSON_EQ(expected, *package_->GetValuesAsJson());
}
TEST_F(StatePackageTest, AddValuesFromJson_AddMore) {
- auto dict = CreateDictionaryValue(R"({'brightness':{
- 'enum': ['low', 'medium', 'high'],
- 'type': 'string'
- }})");
+ auto dict = CreateDictionaryValue("{'brightness':['low', 'medium', 'high']}");
ASSERT_TRUE(package_->AddSchemaFromJson(dict.get(), nullptr));
dict = CreateDictionaryValue("{'brightness':'medium'}");
ASSERT_TRUE(package_->AddValuesFromJson(dict.get(), nullptr));
- EXPECT_EQ(5u, GetValues(*package_).size());
+ EXPECT_EQ(5, GetValues(*package_).size());
auto expected = R"({
'brightness': 'medium',
'color': 'white',
@@ -222,12 +208,11 @@
'iso': 200,
'light': true
})";
- EXPECT_JSON_EQ(expected, package_->GetValuesAsJson());
+ EXPECT_JSON_EQ(expected, *package_->GetValuesAsJson());
}
TEST_F(StatePackageTest, AddSchemaFromJson_Error_Redefined) {
- auto dict = CreateDictionaryValue(R"({'color':
- {'type':'string', 'enum':['white', 'blue', 'red']}})");
+ auto dict = CreateDictionaryValue("{'color':['white', 'blue', 'red']}");
ErrorPtr error;
EXPECT_FALSE(package_->AddSchemaFromJson(dict.get(), &error));
EXPECT_EQ(errors::state::kDomain, error->GetDomain());
@@ -236,7 +221,10 @@
TEST_F(StatePackageTest, AddValuesFromJson_Error_Undefined) {
auto dict = CreateDictionaryValue("{'brightness':'medium'}");
- EXPECT_TRUE(package_->AddValuesFromJson(dict.get(), nullptr));
+ ErrorPtr error;
+ EXPECT_FALSE(package_->AddValuesFromJson(dict.get(), &error));
+ EXPECT_EQ(errors::state::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode());
}
TEST_F(StatePackageTest, GetPropertyValue) {
@@ -283,7 +271,88 @@
'iso': 200,
'light': true
})";
- EXPECT_JSON_EQ(expected, package_->GetValuesAsJson());
+ EXPECT_JSON_EQ(expected, *package_->GetValuesAsJson());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_TypeMismatch) {
+ ErrorPtr error;
+ ASSERT_FALSE(
+ package_->SetPropertyValue("color", base::FundamentalValue{12}, &error));
+ EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+ error.reset();
+
+ ASSERT_FALSE(
+ package_->SetPropertyValue("iso", base::FundamentalValue{false}, &error));
+ EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_OutOfRange) {
+ ErrorPtr error;
+ ASSERT_FALSE(
+ package_->SetPropertyValue("iso", base::FundamentalValue{150}, &error));
+ EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::commands::kOutOfRange, error->GetCode());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_Object_TypeMismatch) {
+ ErrorPtr error;
+ ASSERT_FALSE(package_->SetPropertyValue(
+ "direction",
+ *CreateDictionaryValue("{'altitude': 45.0, 'azimuth': '15'}"), &error));
+ EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::commands::kInvalidPropValue, error->GetCode());
+ const Error* inner = error->GetInnerError();
+ EXPECT_EQ(errors::commands::kDomain, inner->GetDomain());
+ EXPECT_EQ(errors::commands::kTypeMismatch, inner->GetCode());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_Object_OutOfRange) {
+ ErrorPtr error;
+ ASSERT_FALSE(package_->SetPropertyValue(
+ "direction",
+ *CreateDictionaryValue("{'altitude': 100.0, 'azimuth': 290.0}"), &error));
+ EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::commands::kInvalidPropValue, error->GetCode());
+ const Error* inner = error->GetInnerError();
+ EXPECT_EQ(errors::commands::kDomain, inner->GetDomain());
+ EXPECT_EQ(errors::commands::kOutOfRange, inner->GetCode());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_Object_UnknownProperty) {
+ ErrorPtr error;
+ ASSERT_FALSE(package_->SetPropertyValue(
+ "direction", *CreateDictionaryValue(
+ "{'altitude': 10.0, 'azimuth': 20.0, 'spin': 30.0}"),
+ &error));
+ EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::commands::kUnknownProperty, error->GetCode());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Object_OptionalProperty) {
+ EXPECT_JSON_EQ("{'altitude': 89.9, 'azimuth': 57.2957795}",
+ *package_->GetProperty("direction")->ToJson());
+ ASSERT_TRUE(package_->SetPropertyValue(
+ "direction", *CreateDictionaryValue("{'azimuth': 10.0}"), nullptr));
+ EXPECT_JSON_EQ("{'azimuth': 10.0}",
+ *package_->GetProperty("direction")->ToJson());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_Object_MissingProperty) {
+ ErrorPtr error;
+ ASSERT_FALSE(package_->SetPropertyValue(
+ "direction", *CreateDictionaryValue("{'altitude': 10.0}"), &error));
+ EXPECT_EQ(errors::commands::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::commands::kPropertyMissing, error->GetCode());
+}
+
+TEST_F(StatePackageTest, SetPropertyValue_Error_Unknown) {
+ ErrorPtr error;
+ ASSERT_FALSE(package_->SetPropertyValue("volume", base::FundamentalValue{100},
+ &error));
+ EXPECT_EQ(errors::state::kDomain, error->GetDomain());
+ EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode());
}
} // namespace weave
diff --git a/src/string_utils_unittest.cc b/src/string_utils_unittest.cc
index 3966921..c26d7d8 100644
--- a/src/string_utils_unittest.cc
+++ b/src/string_utils_unittest.cc
@@ -17,21 +17,21 @@
std::vector<std::string> parts;
parts = Split("", ",", false, false);
- EXPECT_EQ(0u, parts.size());
+ EXPECT_EQ(0, parts.size());
parts = Split("abc", ",", false, false);
- EXPECT_EQ(1u, parts.size());
+ EXPECT_EQ(1, parts.size());
EXPECT_EQ("abc", parts[0]);
parts = Split(",a,bc , d, ,e, ", ",", true, true);
- EXPECT_EQ(4u, parts.size());
+ EXPECT_EQ(4, parts.size());
EXPECT_EQ("a", parts[0]);
EXPECT_EQ("bc", parts[1]);
EXPECT_EQ("d", parts[2]);
EXPECT_EQ("e", parts[3]);
parts = Split(",a,bc , d, ,e, ", ",", false, true);
- EXPECT_EQ(6u, parts.size());
+ EXPECT_EQ(6, parts.size());
EXPECT_EQ("a", parts[0]);
EXPECT_EQ("bc ", parts[1]);
EXPECT_EQ(" d", parts[2]);
@@ -40,7 +40,7 @@
EXPECT_EQ(" ", parts[5]);
parts = Split(",a,bc , d, ,e, ", ",", true, false);
- EXPECT_EQ(7u, parts.size());
+ EXPECT_EQ(7, parts.size());
EXPECT_EQ("", parts[0]);
EXPECT_EQ("a", parts[1]);
EXPECT_EQ("bc", parts[2]);
@@ -50,7 +50,7 @@
EXPECT_EQ("", parts[6]);
parts = Split(",a,bc , d, ,e, ", ",", false, false);
- EXPECT_EQ(7u, parts.size());
+ EXPECT_EQ(7, parts.size());
EXPECT_EQ("", parts[0]);
EXPECT_EQ("a", parts[1]);
EXPECT_EQ("bc ", parts[2]);
@@ -60,12 +60,12 @@
EXPECT_EQ(" ", parts[6]);
parts = Split("abc:=xyz", ":=", false, false);
- EXPECT_EQ(2u, parts.size());
+ EXPECT_EQ(2, parts.size());
EXPECT_EQ("abc", parts[0]);
EXPECT_EQ("xyz", parts[1]);
parts = Split("abc", "", false, false);
- EXPECT_EQ(3u, parts.size());
+ EXPECT_EQ(3, parts.size());
EXPECT_EQ("a", parts[0]);
EXPECT_EQ("b", parts[1]);
EXPECT_EQ("c", parts[2]);
diff --git a/src/test/mock_command.cc b/src/test/mock_command.cc
new file mode 100644
index 0000000..a8564c4
--- /dev/null
+++ b/src/test/mock_command.cc
@@ -0,0 +1,30 @@
+// Copyright 2015 The Weave 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 <weave/test/mock_command.h>
+
+#include <memory>
+#include <string>
+
+#include <base/values.h>
+
+#include "src/commands/unittest_utils.h"
+
+namespace weave {
+namespace test {
+
+std::unique_ptr<base::DictionaryValue> MockCommand::GetParameters() const {
+ return CreateDictionaryValue(MockGetParameters());
+}
+
+std::unique_ptr<base::DictionaryValue> MockCommand::GetProgress() const {
+ return CreateDictionaryValue(MockGetProgress());
+}
+
+std::unique_ptr<base::DictionaryValue> MockCommand::GetResults() const {
+ return CreateDictionaryValue(MockGetResults());
+}
+
+} // namespace test
+} // namespace weave
diff --git a/src/test/unittest_utils.cc b/src/test/unittest_utils.cc
index df8ccc0..b9858e4 100644
--- a/src/test/unittest_utils.cc
+++ b/src/test/unittest_utils.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include <weave/test/unittest_utils.h>
+#include "src/commands/unittest_utils.h"
#include <base/json/json_reader.h>
#include <base/json/json_writer.h>
diff --git a/src/weave_unittest.cc b/src/weave_unittest.cc
index 2e41852..c2f1e8f 100644
--- a/src/weave_unittest.cc
+++ b/src/weave_unittest.cc
@@ -72,7 +72,7 @@
"base": {
"reboot": {
"minimalRole": "user",
- "parameters": {"delay": {"type": "integer"}},
+ "parameters": {"delay": "integer"},
"results": {}
},
"shutdown": {
diff --git a/third_party/chromium/base/build/build_config.h b/third_party/chromium/base/build/build_config.h
index 347a636..904965a 100644
--- a/third_party/chromium/base/build/build_config.h
+++ b/third_party/chromium/base/build/build_config.h
@@ -147,16 +147,6 @@
#define ARCH_CPU_32_BITS 1
#define ARCH_CPU_LITTLE_ENDIAN 1
#endif
-#elif defined(__mips__)
-#if defined(__LP64__)
-#define ARCH_CPU_MIPS64_FAMILY 1
-#define ARCH_CPU_64_BITS 1
-#define ARCH_CPU_BIG_ENDIAN 1
-#else
-#define ARCH_CPU_MIPS_FAMILY 1
-#define ARCH_CPU_32_BITS 1
-#define ARCH_CPU_BIG_ENDIAN 1
-#endif
#else
#error Please add support for your architecture in build/build_config.h
#endif
diff --git a/third_party/chromium/base/time/time_posix.cc b/third_party/chromium/base/time/time_posix.cc
index 2825ce5..b625af6 100644
--- a/third_party/chromium/base/time/time_posix.cc
+++ b/third_party/chromium/base/time/time_posix.cc
@@ -117,7 +117,7 @@
// => Thu Jan 01 00:00:00 UTC 1970
// irb(main):011:0> Time.at(-11644473600).getutc()
// => Mon Jan 01 00:00:00 UTC 1601
-static const int64 kWindowsEpochDeltaSeconds = 11644473600ll;
+static const int64 kWindowsEpochDeltaSeconds = INT64_C(11644473600);
// static
const int64 Time::kWindowsEpochDeltaMicroseconds =
diff --git a/third_party/chromium/base/time/time_unittest.cc b/third_party/chromium/base/time/time_unittest.cc
index 508244a..43373e7 100644
--- a/third_party/chromium/base/time/time_unittest.cc
+++ b/third_party/chromium/base/time/time_unittest.cc
@@ -584,7 +584,7 @@
exploded.millisecond = 0;
Time t = Time::FromUTCExploded(exploded);
// Unix 1970 epoch.
- EXPECT_EQ(11644473600000000ll, t.ToInternalValue());
+ EXPECT_EQ(INT64_C(11644473600000000), t.ToInternalValue());
// We can't test 1601 epoch, since the system time functions on Linux
// only compute years starting from 1900.
diff --git a/third_party/modp_b64/modp_b64.cc b/third_party/modp_b64/modp_b64.cc
index 8db2ed1..fdb8a40 100644
--- a/third_party/modp_b64/modp_b64.cc
+++ b/third_party/modp_b64/modp_b64.cc
@@ -45,12 +45,16 @@
/* public header */
#include "modp_b64.h"
-#include <base/build/build_config.h>
+/*
+ * If you are ripping this out of the library, comment out the next
+ * line and uncomment the next lines as approrpiate
+ */
+//#include "config.h"
-#undef WORDS_BIGENDIAN
-#if !defined(ARCH_CPU_LITTLE_ENDIAN)
-#define WORDS_BIGENDIAN 1
-#endif
+/* if on motoral, sun, ibm; uncomment this */
+/* #define WORDS_BIGENDIAN 1 */
+/* else for Intel, Amd; uncomment this */
+/* #undef WORDS_BIGENDIAN */
#include "modp_b64_data.h"
@@ -114,7 +118,7 @@
}
#ifdef WORDS_BIGENDIAN /* BIG ENDIAN -- SUN / IBM / MOTOROLA */
-size_t modp_b64_decode(char* dest, const char* src, size_t len)
+int modp_b64_decode(char* dest, const char* src, int len)
{
if (len == 0) return 0;