buffet: Add support for 'additionalProperties' attribute
GCD server requires parameters of type 'object' to include the
'additionalProperties' attribute in their schema. This attribute
has been implemented in buffet internally but never exposed through
JSON, so here we add both serialization and de-serialization code
for 'additionalProperties'.
BUG=brillo:356
TEST=`FEATURES=test emerge-link buffet`
Change-Id: I8db3144e2e572b1835d34603be3677a82734c679
Reviewed-on: https://chromium-review.googlesource.com/263270
Trybot-Ready: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Aaron Kemp <kemp@google.com>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/commands/object_schema_unittest.cc b/buffet/commands/object_schema_unittest.cc
index b0332d2..850f64e 100644
--- a/buffet/commands/object_schema_unittest.cc
+++ b/buffet/commands/object_schema_unittest.cc
@@ -536,9 +536,9 @@
TEST(CommandSchema, ObjectPropType_ToJson) {
buffet::ObjectPropType prop;
- EXPECT_EQ("{'properties':{}}",
+ EXPECT_EQ("{'additionalProperties':false,'properties':{}}",
ValueToString(prop.ToJson(false, nullptr).get()));
- EXPECT_EQ("{'properties':{},'type':'object'}",
+ EXPECT_EQ("{'additionalProperties':false,'properties':{},'type':'object'}",
ValueToString(prop.ToJson(true, nullptr).get()));
EXPECT_FALSE(prop.IsBasedOnSchema());
buffet::ObjectPropType prop2;
@@ -552,10 +552,11 @@
pw->GetString()->AddLengthConstraint(6, 100);
schema->AddProp("password", std::move(pw));
prop2.SetObjectSchema(std::move(schema));
- EXPECT_EQ("{'properties':{'expires':'integer',"
+ EXPECT_EQ("{'additionalProperties':false,'properties':{'expires':'integer',"
"'password':{'maxLength':100,'minLength':6}}}",
ValueToString(prop2.ToJson(false, nullptr).get()));
- EXPECT_EQ("{'properties':{'expires':{'type':'integer'},"
+ EXPECT_EQ("{'additionalProperties':false,'properties':{"
+ "'expires':{'type':'integer'},"
"'password':{'maxLength':100,'minLength':6,'type':'string'}},"
"'type':'object'}",
ValueToString(prop2.ToJson(true, nullptr).get()));
@@ -566,11 +567,29 @@
nullptr));
EXPECT_EQ("{'default':{'expires':3,'password':'abracadabra'}}",
ValueToString(prop3.ToJson(false, nullptr).get()));
- EXPECT_EQ("{'default':{'expires':3,'password':'abracadabra'},"
+ EXPECT_EQ("{'additionalProperties':false,"
+ "'default':{'expires':3,'password':'abracadabra'},"
"'properties':{'expires':{'type':'integer'},"
"'password':{'maxLength':100,'minLength':6,'type':'string'}},"
"'type':'object'}",
ValueToString(prop3.ToJson(true, nullptr).get()));
+
+ buffet::ObjectPropType prop4;
+ ASSERT_TRUE(prop4.FromJson(CreateDictionaryValue(
+ "{'additionalProperties':true,"
+ "'default':{'expires':3,'password':'abracadabra'}}").get(), &prop2,
+ nullptr));
+ EXPECT_EQ("{'additionalProperties':true,"
+ "'default':{'expires':3,'password':'abracadabra'},"
+ "'properties':{'expires':'integer',"
+ "'password':{'maxLength':100,'minLength':6}}}",
+ ValueToString(prop4.ToJson(false, nullptr).get()));
+ EXPECT_EQ("{'additionalProperties':true,"
+ "'default':{'expires':3,'password':'abracadabra'},"
+ "'properties':{'expires':{'type':'integer'},"
+ "'password':{'maxLength':100,'minLength':6,'type':'string'}},"
+ "'type':'object'}",
+ ValueToString(prop4.ToJson(true, nullptr).get()));
}
TEST(CommandSchema, ObjectPropType_FromJson) {
diff --git a/buffet/commands/prop_types.cc b/buffet/commands/prop_types.cc
index 6365dcc..f60f256 100644
--- a/buffet/commands/prop_types.cc
+++ b/buffet/commands/prop_types.cc
@@ -475,6 +475,9 @@
}
dict->SetWithoutPathExpansion(commands::attributes::kObject_Properties,
object_schema.release());
+ dict->SetBooleanWithoutPathExpansion(
+ commands::attributes::kObject_AdditionalProperties,
+ object_schema_.value->GetExtraPropertiesAllowed());
}
}
return value;
@@ -488,6 +491,7 @@
return false;
using commands::attributes::kObject_Properties;
+ using commands::attributes::kObject_AdditionalProperties;
const ObjectSchema* base_object_schema = nullptr;
if (base_schema)
@@ -503,11 +507,26 @@
"Error parsing object property schema");
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);
+ }
object_schema_.value = std::move(object_schema);
object_schema_.is_inherited = false;
} else if (base_object_schema) {
- object_schema_.value = base_object_schema->Clone();
- object_schema_.is_inherited = true;
+ auto cloned_object_schema = base_object_schema->Clone();
+ bool extra_properties_allowed = false;
+ if (value->GetBooleanWithoutPathExpansion(kObject_AdditionalProperties,
+ &extra_properties_allowed)) {
+ processed_keys->insert(kObject_AdditionalProperties);
+ cloned_object_schema->SetExtraPropertiesAllowed(extra_properties_allowed);
+ object_schema_.is_inherited = false;
+ } else {
+ object_schema_.is_inherited = true;
+ }
+ object_schema_.value = std::move(cloned_object_schema);
} else {
chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
errors::commands::kInvalidObjectSchema,
diff --git a/buffet/commands/schema_constants.cc b/buffet/commands/schema_constants.cc
index 3854278..cd06b32 100644
--- a/buffet/commands/schema_constants.cc
+++ b/buffet/commands/schema_constants.cc
@@ -43,6 +43,7 @@
const char kOneOf_Metadata[] = "metadata";
const char kObject_Properties[] = "properties";
+const char kObject_AdditionalProperties[] = "additionalProperties";
const char kCommand_Id[] = "id";
const char kCommand_Name[] = "name";
diff --git a/buffet/commands/schema_constants.h b/buffet/commands/schema_constants.h
index 450ad37..6f5ee71 100644
--- a/buffet/commands/schema_constants.h
+++ b/buffet/commands/schema_constants.h
@@ -47,6 +47,7 @@
extern const char kOneOf_Metadata[];
extern const char kObject_Properties[];
+extern const char kObject_AdditionalProperties[];
extern const char kCommand_Id[];
extern const char kCommand_Name[];
diff --git a/buffet/states/state_package_unittest.cc b/buffet/states/state_package_unittest.cc
index 9d5b0d6..d2d36e4 100644
--- a/buffet/states/state_package_unittest.cc
+++ b/buffet/states/state_package_unittest.cc
@@ -87,7 +87,7 @@
EXPECT_EQ(4, GetTypes(package).GetProps().size());
EXPECT_EQ(4, GetValues(package).size());
EXPECT_EQ("{'color':{'type':'string'},"
- "'direction':{'properties':{"
+ "'direction':{'additionalProperties':false,'properties':{"
"'altitude':{'maximum':90.0,'type':'number'},"
"'azimuth':{'type':'number'}},"
"'type':'object'},"
@@ -117,7 +117,7 @@
EXPECT_EQ(5, GetValues(*package_).size());
EXPECT_EQ("{'brightness':{'enum':['low','medium','high'],'type':'string'},"
"'color':{'type':'string'},"
- "'direction':{'properties':{"
+ "'direction':{'additionalProperties':false,'properties':{"
"'altitude':{'maximum':90.0,'type':'number'},"
"'azimuth':{'type':'number'}},"
"'type':'object'},"