|
|
|
#include <functional>
|
|
|
|
#include <iostream>
|
|
|
|
#include <map>
|
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include <cm/optional>
|
|
|
|
#include <cmext/string_view>
|
|
|
|
|
|
|
|
#include <cm3p/json/value.h>
|
|
|
|
|
|
|
|
#include "cmJSONHelpers.h"
|
|
|
|
#include "cmJSONState.h"
|
|
|
|
|
|
|
|
#define ASSERT_TRUE(x) \
|
|
|
|
do { \
|
|
|
|
if (!(x)) { \
|
|
|
|
std::cout << "ASSERT_TRUE(" #x ") failed on line " << __LINE__ << "\n"; \
|
|
|
|
return false; \
|
|
|
|
} \
|
|
|
|
} while (false)
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
struct ObjectStruct
|
|
|
|
{
|
|
|
|
std::string Field1;
|
|
|
|
int Field2;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct InheritedStruct : public ObjectStruct
|
|
|
|
{
|
|
|
|
std::string Field3;
|
|
|
|
};
|
|
|
|
|
|
|
|
namespace ErrorMessages {
|
|
|
|
using ErrorGenerator =
|
|
|
|
std::function<void(const Json::Value*, cmJSONState* state)>;
|
|
|
|
ErrorGenerator ErrorGeneratorBuilder(std::string errorMessage)
|
|
|
|
{
|
|
|
|
return [errorMessage](const Json::Value* value, cmJSONState* state) -> void {
|
|
|
|
state->AddErrorAtValue(errorMessage, value);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
ErrorGenerator InvalidArray = ErrorGeneratorBuilder("Invalid Array");
|
|
|
|
ErrorGenerator MissingRequired = ErrorGeneratorBuilder("Missing Required");
|
|
|
|
ErrorGenerator InvalidMap = ErrorGeneratorBuilder("Invalid Map");
|
|
|
|
ErrorGenerator InvalidObject(JsonErrors::ObjectError /*errorType*/,
|
|
|
|
const Json::Value::Members& extraFields)
|
|
|
|
{
|
|
|
|
return [extraFields](const Json::Value* value, cmJSONState* state) -> void {
|
|
|
|
state->AddErrorAtValue("Invalid Object", value);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
using JSONHelperBuilder = cmJSONHelperBuilder;
|
|
|
|
|
|
|
|
auto const IntHelper = JSONHelperBuilder::Int(1);
|
|
|
|
auto const RequiredIntHelper =
|
|
|
|
JSONHelperBuilder::Required<int>(ErrorMessages::MissingRequired, IntHelper);
|
|
|
|
auto const UIntHelper = JSONHelperBuilder::UInt(1);
|
|
|
|
auto const BoolHelper = JSONHelperBuilder::Bool(false);
|
|
|
|
auto const StringHelper = JSONHelperBuilder::String("default");
|
|
|
|
auto const RequiredStringHelper = JSONHelperBuilder::Required<std::string>(
|
|
|
|
ErrorMessages::MissingRequired, StringHelper);
|
|
|
|
auto const StringVectorHelper = JSONHelperBuilder::Vector<std::string>(
|
|
|
|
ErrorMessages::InvalidArray, StringHelper);
|
|
|
|
auto const StringVectorFilterHelper =
|
|
|
|
JSONHelperBuilder::VectorFilter<std::string>(
|
|
|
|
ErrorMessages::InvalidArray, StringHelper,
|
|
|
|
[](const std::string& value) { return value != "ignore"; });
|
|
|
|
auto const StringMapHelper =
|
|
|
|
JSONHelperBuilder::Map<std::string>(ErrorMessages::InvalidMap, StringHelper);
|
|
|
|
auto const StringMapFilterHelper = JSONHelperBuilder::MapFilter<std::string>(
|
|
|
|
ErrorMessages::InvalidMap, StringHelper,
|
|
|
|
[](const std::string& key) { return key != "ignore"; });
|
|
|
|
auto const OptionalStringHelper =
|
|
|
|
JSONHelperBuilder::Optional<std::string>(StringHelper);
|
|
|
|
|
|
|
|
bool testInt()
|
|
|
|
{
|
|
|
|
Json::Value v(2);
|
|
|
|
cmJSONState state;
|
|
|
|
int i = 0;
|
|
|
|
ASSERT_TRUE(IntHelper(i, &v, &state));
|
|
|
|
ASSERT_TRUE(i == 2);
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
v = Json::nullValue;
|
|
|
|
ASSERT_TRUE(!IntHelper(i, &v, &state));
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
ASSERT_TRUE(IntHelper(i, nullptr, &state));
|
|
|
|
ASSERT_TRUE(i == 1);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool testUInt()
|
|
|
|
{
|
|
|
|
Json::Value v(2);
|
|
|
|
cmJSONState state;
|
|
|
|
unsigned int i = 0;
|
|
|
|
ASSERT_TRUE(UIntHelper(i, &v, &state));
|
|
|
|
ASSERT_TRUE(i == 2);
|
|
|
|
i = 0;
|
|
|
|
v = Json::nullValue;
|
|
|
|
ASSERT_TRUE(!UIntHelper(i, &v, &state));
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
ASSERT_TRUE(UIntHelper(i, nullptr, &state));
|
|
|
|
ASSERT_TRUE(i == 1);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool testBool()
|
|
|
|
{
|
|
|
|
Json::Value v(true);
|
|
|
|
cmJSONState state;
|
|
|
|
bool b = false;
|
|
|
|
ASSERT_TRUE(BoolHelper(b, &v, &state));
|
|
|
|
ASSERT_TRUE(b);
|
|
|
|
|
|
|
|
b = false;
|
|
|
|
v = false;
|
|
|
|
ASSERT_TRUE(BoolHelper(b, &v, &state));
|
|
|
|
ASSERT_TRUE(!b);
|
|
|
|
|
|
|
|
b = false;
|
|
|
|
v = 4;
|
|
|
|
ASSERT_TRUE(!BoolHelper(b, &v, &state));
|
|
|
|
|
|
|
|
b = true;
|
|
|
|
ASSERT_TRUE(BoolHelper(b, nullptr, &state));
|
|
|
|
ASSERT_TRUE(!b);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool testString()
|
|
|
|
{
|
|
|
|
Json::Value v("str");
|
|
|
|
cmJSONState state;
|
|
|
|
std::string str = "";
|
|
|
|
ASSERT_TRUE(StringHelper(str, &v, &state));
|
|
|
|
ASSERT_TRUE(str == "str");
|
|
|
|
|
|
|
|
str = "";
|
|
|
|
v = Json::nullValue;
|
|
|
|
ASSERT_TRUE(!StringHelper(str, &v, &state));
|
|
|
|
|
|
|
|
str = "";
|
|
|
|
ASSERT_TRUE(StringHelper(str, nullptr, &state));
|
|
|
|
ASSERT_TRUE(str == "default");
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool testObject()
|
|
|
|
{
|
|
|
|
auto const subhelper = JSONHelperBuilder::Object<ObjectStruct>().Bind(
|
|
|
|
"subfield"_s, &ObjectStruct::Field2, IntHelper);
|
|
|
|
auto const helper = JSONHelperBuilder::Object<ObjectStruct>()
|
|
|
|
.Bind("field1"_s, &ObjectStruct::Field1, StringHelper)
|
|
|
|
.Bind("field2"_s, subhelper)
|
|
|
|
.Bind<std::string>("field3"_s, nullptr, StringHelper);
|
|
|
|
|
|
|
|
Json::Value v(Json::objectValue);
|
|
|
|
cmJSONState state;
|
|
|
|
v["field1"] = "Hello";
|
|
|
|
v["field2"] = Json::objectValue;
|
|
|
|
v["field2"]["subfield"] = 2;
|
|
|
|
v["field3"] = "world!";
|
|
|
|
v["extra"] = "extra";
|
|
|
|
|
|
|
|
ObjectStruct s1;
|
|
|
|
ASSERT_TRUE(helper(s1, &v, &state));
|
|
|
|
ASSERT_TRUE(s1.Field1 == "Hello");
|
|
|
|
ASSERT_TRUE(s1.Field2 == 2);
|
|
|
|
|
|
|
|
v["field2"]["subfield"] = "wrong";
|
|
|
|
ObjectStruct s2;
|
|
|
|
ASSERT_TRUE(!helper(s2, &v, &state));
|
|
|
|
|
|
|
|
v["field2"].removeMember("subfield");
|
|
|
|
ObjectStruct s3;
|
|
|
|
ASSERT_TRUE(!helper(s3, &v, &state));
|
|
|
|
|
|
|
|
v.removeMember("field2");
|
|
|
|
ObjectStruct s4;
|
|
|
|
ASSERT_TRUE(!helper(s4, &v, &state));
|
|
|
|
|
|
|
|
v["field2"] = Json::objectValue;
|
|
|
|
v["field2"]["subfield"] = 2;
|
|
|
|
v["field3"] = 3;
|
|
|
|
ObjectStruct s5;
|
|
|
|
ASSERT_TRUE(!helper(s5, &v, &state));
|
|
|
|
|
|
|
|
v.removeMember("field3");
|
|
|
|
ObjectStruct s6;
|
|
|
|
ASSERT_TRUE(!helper(s6, &v, &state));
|
|
|
|
|
|
|
|
v = "Hello";
|
|
|
|
ObjectStruct s7;
|
|
|
|
ASSERT_TRUE(!helper(s7, &v, &state));
|
|
|
|
|
|
|
|
ObjectStruct s8;
|
|
|
|
ASSERT_TRUE(!helper(s8, nullptr, &state));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool testObjectInherited()
|
|
|
|
{
|
|
|
|
auto const helper =
|
|
|
|
JSONHelperBuilder::Object<InheritedStruct>(ErrorMessages::InvalidObject,
|
|
|
|
true)
|
|
|
|
.Bind("field1"_s, &InheritedStruct::Field1, StringHelper)
|
|
|
|
.Bind("field2"_s, &InheritedStruct::Field2, IntHelper)
|
|
|
|
.Bind("field3"_s, &InheritedStruct::Field3, StringHelper);
|
|
|
|
|
|
|
|
Json::Value v(Json::objectValue);
|
|
|
|
cmJSONState state;
|
|
|
|
v["field1"] = "Hello";
|
|
|
|
v["field2"] = 2;
|
|
|
|
v["field3"] = "world!";
|
|
|
|
v["extra"] = "extra";
|
|
|
|
|
|
|
|
InheritedStruct s1;
|
|
|
|
ASSERT_TRUE(helper(s1, &v, &state));
|
|
|
|
ASSERT_TRUE(s1.Field1 == "Hello");
|
|
|
|
ASSERT_TRUE(s1.Field2 == 2);
|
|
|
|
ASSERT_TRUE(s1.Field3 == "world!");
|
|
|
|
|
|
|
|
v["field2"] = "wrong";
|
|
|
|
InheritedStruct s2;
|
|
|
|
ASSERT_TRUE(!helper(s2, &v, &state));
|
|
|
|
|
|
|
|
v.removeMember("field2");
|
|
|
|
InheritedStruct s3;
|
|
|
|
ASSERT_TRUE(!helper(s3, &v, &state));
|
|
|
|
|
|
|
|
v["field2"] = 2;
|
|
|
|
v["field3"] = 3;
|
|
|
|
InheritedStruct s4;
|
|
|
|
ASSERT_TRUE(!helper(s4, &v, &state));
|
|
|
|
|
|
|
|
v.removeMember("field3");
|
|
|
|
InheritedStruct s5;
|
|
|
|
ASSERT_TRUE(!helper(s5, &v, &state));
|
|
|
|
|
|
|
|
v = "Hello";
|
|
|
|
InheritedStruct s6;
|
|
|
|
ASSERT_TRUE(!helper(s6, &v, &state));
|
|
|
|
|
|
|
|
InheritedStruct s7;
|
|
|
|
ASSERT_TRUE(!helper(s7, nullptr, &state));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool testObjectNoExtra()
|
|
|
|
{
|
|
|
|
auto const helper = JSONHelperBuilder::Object<ObjectStruct>(
|
|
|
|
ErrorMessages::InvalidObject, false)
|
|
|
|
.Bind("field1"_s, &ObjectStruct::Field1, StringHelper)
|
|
|
|
.Bind("field2"_s, &ObjectStruct::Field2, IntHelper);
|
|
|
|
|
|
|
|
Json::Value v(Json::objectValue);
|
|
|
|
cmJSONState state;
|
|
|
|
v["field1"] = "Hello";
|
|
|
|
v["field2"] = 2;
|
|
|
|
|
|
|
|
ObjectStruct s1;
|
|
|
|
ASSERT_TRUE(helper(s1, &v, &state));
|
|
|
|
ASSERT_TRUE(s1.Field1 == "Hello");
|
|
|
|
ASSERT_TRUE(s1.Field2 == 2);
|
|
|
|
|
|
|
|
v["extra"] = "world!";
|
|
|
|
ObjectStruct s2;
|
|
|
|
ASSERT_TRUE(!helper(s2, &v, &state));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool testObjectOptional()
|
|
|
|
{
|
|
|
|
auto const helper =
|
|
|
|
JSONHelperBuilder::Object<ObjectStruct>(ErrorMessages::InvalidObject, true)
|
|
|
|
.Bind("field1"_s, &ObjectStruct::Field1, StringHelper, false)
|
|
|
|
.Bind("field2"_s, &ObjectStruct::Field2, IntHelper, false)
|
|
|
|
.Bind<std::string>("field3_s", nullptr, StringHelper, false);
|
|
|
|
|
|
|
|
Json::Value v(Json::objectValue);
|
|
|
|
cmJSONState state;
|
|
|
|
v["field1"] = "Hello";
|
|
|
|
v["field2"] = 2;
|
|
|
|
v["field3"] = "world!";
|
|
|
|
v["extra"] = "extra";
|
|
|
|
|
|
|
|
ObjectStruct s1;
|
|
|
|
ASSERT_TRUE(helper(s1, &v, &state));
|
|
|
|
ASSERT_TRUE(s1.Field1 == "Hello");
|
|
|
|
ASSERT_TRUE(s1.Field2 == 2);
|
|
|
|
|
|
|
|
v = Json::objectValue;
|
|
|
|
ObjectStruct s2;
|
|
|
|
ASSERT_TRUE(helper(s2, &v, &state));
|
|
|
|
ASSERT_TRUE(s2.Field1 == "default");
|
|
|
|
ASSERT_TRUE(s2.Field2 == 1);
|
|
|
|
|
|
|
|
ObjectStruct s3;
|
|
|
|
ASSERT_TRUE(helper(s3, nullptr, &state));
|
|
|
|
ASSERT_TRUE(s3.Field1 == "default");
|
|
|
|
ASSERT_TRUE(s3.Field2 == 1);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool testVector()
|
|
|
|
{
|
|
|
|
Json::Value v(Json::arrayValue);
|
|
|
|
cmJSONState state;
|
|
|
|
v.append("Hello");
|
|
|
|
v.append("world!");
|
|
|
|
v.append("ignore");
|
|
|
|
|
|
|
|
std::vector<std::string> l{ "default" };
|
|
|
|
std::vector<std::string> expected{ "Hello", "world!", "ignore" };
|
|
|
|
ASSERT_TRUE(StringVectorHelper(l, &v, &state));
|
|
|
|
ASSERT_TRUE(l == expected);
|
|
|
|
|
|
|
|
v[1] = 2;
|
|
|
|
l = { "default" };
|
|
|
|
ASSERT_TRUE(!StringVectorHelper(l, &v, &state));
|
|
|
|
|
|
|
|
v = "Hello";
|
|
|
|
l = { "default" };
|
|
|
|
ASSERT_TRUE(!StringVectorHelper(l, &v, &state));
|
|
|
|
|
|
|
|
l = { "default" };
|
|
|
|
ASSERT_TRUE(StringVectorHelper(l, nullptr, &state));
|
|
|
|
ASSERT_TRUE(l.empty());
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool testVectorFilter()
|
|
|
|
{
|
|
|
|
Json::Value v(Json::arrayValue);
|
|
|
|
cmJSONState state;
|
|
|
|
v.append("Hello");
|
|
|
|
v.append("world!");
|
|
|
|
v.append("ignore");
|
|
|
|
|
|
|
|
std::vector<std::string> l{ "default" };
|
|
|
|
std::vector<std::string> expected{
|
|
|
|
"Hello",
|
|
|
|
"world!",
|
|
|
|
};
|
|
|
|
ASSERT_TRUE(StringVectorFilterHelper(l, &v, &state));
|
|
|
|
ASSERT_TRUE(l == expected);
|
|
|
|
|
|
|
|
v[1] = 2;
|
|
|
|
l = { "default" };
|
|
|
|
ASSERT_TRUE(!StringVectorFilterHelper(l, &v, &state));
|
|
|
|
|
|
|
|
v = "Hello";
|
|
|
|
l = { "default" };
|
|
|
|
ASSERT_TRUE(!StringVectorFilterHelper(l, &v, &state));
|
|
|
|
|
|
|
|
l = { "default" };
|
|
|
|
ASSERT_TRUE(StringVectorFilterHelper(l, nullptr, &state));
|
|
|
|
ASSERT_TRUE(l.empty());
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool testMap()
|
|
|
|
{
|
|
|
|
Json::Value v(Json::objectValue);
|
|
|
|
v["field1"] = "Hello";
|
|
|
|
v["field2"] = "world!";
|
|
|
|
v["ignore"] = "ignore";
|
|
|
|
cmJSONState state;
|
|
|
|
|
|
|
|
std::map<std::string, std::string> m{ { "key", "default" } };
|
|
|
|
std::map<std::string, std::string> expected{ { "field1", "Hello" },
|
|
|
|
{ "field2", "world!" },
|
|
|
|
{ "ignore", "ignore" } };
|
|
|
|
ASSERT_TRUE(StringMapHelper(m, &v, &state));
|
|
|
|
ASSERT_TRUE(m == expected);
|
|
|
|
|
|
|
|
v = Json::arrayValue;
|
|
|
|
m = { { "key", "default" } };
|
|
|
|
ASSERT_TRUE(!StringMapHelper(m, &v, &state));
|
|
|
|
|
|
|
|
m = { { "key", "default" } };
|
|
|
|
ASSERT_TRUE(StringMapHelper(m, nullptr, &state));
|
|
|
|
ASSERT_TRUE(m.empty());
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool testMapFilter()
|
|
|
|
{
|
|
|
|
Json::Value v(Json::objectValue);
|
|
|
|
cmJSONState state;
|
|
|
|
v["field1"] = "Hello";
|
|
|
|
v["field2"] = "world!";
|
|
|
|
v["ignore"] = "ignore";
|
|
|
|
|
|
|
|
std::map<std::string, std::string> m{ { "key", "default" } };
|
|
|
|
std::map<std::string, std::string> expected{ { "field1", "Hello" },
|
|
|
|
{ "field2", "world!" } };
|
|
|
|
ASSERT_TRUE(StringMapFilterHelper(m, &v, &state));
|
|
|
|
ASSERT_TRUE(m == expected);
|
|
|
|
|
|
|
|
v = Json::arrayValue;
|
|
|
|
m = { { "key", "default" } };
|
|
|
|
ASSERT_TRUE(!StringMapFilterHelper(m, &v, &state));
|
|
|
|
|
|
|
|
m = { { "key", "default" } };
|
|
|
|
ASSERT_TRUE(StringMapFilterHelper(m, nullptr, &state));
|
|
|
|
ASSERT_TRUE(m.empty());
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool testOptional()
|
|
|
|
{
|
|
|
|
Json::Value v = "Hello";
|
|
|
|
cmJSONState state;
|
|
|
|
|
|
|
|
cm::optional<std::string> str{ "default" };
|
|
|
|
ASSERT_TRUE(OptionalStringHelper(str, &v, &state));
|
|
|
|
ASSERT_TRUE(str == "Hello");
|
|
|
|
|
|
|
|
str.emplace("default");
|
|
|
|
ASSERT_TRUE(OptionalStringHelper(str, nullptr, &state));
|
|
|
|
ASSERT_TRUE(str == cm::nullopt);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool testRequired()
|
|
|
|
{
|
|
|
|
Json::Value v = "Hello";
|
|
|
|
std::string str = "default";
|
|
|
|
int i = 1;
|
|
|
|
cmJSONState state;
|
|
|
|
ASSERT_TRUE(RequiredStringHelper(str, &v, &state));
|
|
|
|
ASSERT_TRUE(str == "Hello");
|
|
|
|
ASSERT_TRUE(!RequiredIntHelper(i, &v, &state));
|
|
|
|
|
|
|
|
v = 2;
|
|
|
|
str = "default";
|
|
|
|
i = 1;
|
|
|
|
ASSERT_TRUE(!RequiredStringHelper(str, &v, &state));
|
|
|
|
ASSERT_TRUE(RequiredIntHelper(i, &v, &state));
|
|
|
|
ASSERT_TRUE(i == 2);
|
|
|
|
|
|
|
|
str = "default";
|
|
|
|
i = 1;
|
|
|
|
ASSERT_TRUE(!RequiredStringHelper(str, nullptr, &state));
|
|
|
|
ASSERT_TRUE(!RequiredIntHelper(i, nullptr, &state));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int testJSONHelpers(int /*unused*/, char* /*unused*/[])
|
|
|
|
{
|
|
|
|
if (!testInt()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!testUInt()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!testBool()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!testString()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!testObject()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!testObjectInherited()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!testObjectNoExtra()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!testObjectOptional()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!testVector()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!testVectorFilter()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!testMap()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!testMapFilter()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!testOptional()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (!testRequired()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|