diff options
author | Shaun Verch <shaun.verch@10gen.com> | 2013-07-13 12:59:25 -0400 |
---|---|---|
committer | Shaun Verch <shaun.verch@10gen.com> | 2013-08-09 12:19:55 -0400 |
commit | ee0b4a412fbcd8bb60afd6321c2c934387f40d46 (patch) | |
tree | 706cd2b9ed90b4b692a7292502808be45348a166 /src/mongo | |
parent | 7ef3ff6668c05a44745e62d02c40b6041df80bf3 (diff) | |
download | mongo-ee0b4a412fbcd8bb60afd6321c2c934387f40d46.tar.gz |
SERVER-8510 Added command line and config file parser to source tree with unit tests
Diffstat (limited to 'src/mongo')
20 files changed, 3000 insertions, 2 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript index db8eefcda55..fd108dd4906 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -26,7 +26,8 @@ env.SConscript(['base/SConscript', 'platform/SConscript', 's/SConscript', 'unittest/SConscript', - 'util/concurrency/SConscript']) + 'util/concurrency/SConscript', + 'util/options_parser/SConscript']) def add_exe( v ): return "${PROGPREFIX}%s${PROGSUFFIX}" % v diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err index 6465bed8e83..8a51b1ec267 100644 --- a/src/mongo/base/error_codes.err +++ b/src/mongo/base/error_codes.err @@ -44,5 +44,6 @@ error_code("ConflictingUpdateOperators", 40) error_code("FileAlreadyOpen", 41) error_code("LogWriteFailed", 42) error_code("CursorNotFound", 43) +error_code("KeyNotFound", 44) error_class("NetworkError", ["HostUnreachable", "HostNotFound"]) diff --git a/src/mongo/db/json.cpp b/src/mongo/db/json.cpp index 3f7135f8095..9992ced57a1 100644 --- a/src/mongo/db/json.cpp +++ b/src/mongo/db/json.cpp @@ -987,7 +987,16 @@ namespace mongo { } JParse jparse(jsonString); BSONObjBuilder builder; - Status ret = jparse.object("UNUSED", builder, false); + Status ret = Status::OK(); + try { + ret = jparse.object("UNUSED", builder, false); + } + catch(std::exception& e) { + std::ostringstream message; + message << "caught exception from within JSON parser: " << e.what(); + throw MsgAssertionException(17031, message.str()); + } + if (ret != Status::OK()) { ostringstream message; message << "code " << ret.code() << ": " << ret.codeString() << ": " << ret.reason(); diff --git a/src/mongo/util/options_parser/SConscript b/src/mongo/util/options_parser/SConscript new file mode 100644 index 00000000000..ee574dff8bf --- /dev/null +++ b/src/mongo/util/options_parser/SConscript @@ -0,0 +1,19 @@ +# -*- mode: python -*- + +Import("env") + +env.StaticLibrary('options_parser', ['environment.cpp', + 'value.cpp', + 'constraints.cpp', + 'option_section.cpp', + 'options_parser.cpp', + ], + LIBDEPS=['$BUILD_DIR/mongo/bson']) + +env.CppUnitTest('options_parser_test', + 'options_parser_test.cpp', + LIBDEPS=['options_parser', '$BUILD_DIR/mongo/unittest/unittest']) + +env.CppUnitTest('environment_test', + 'environment_test.cpp', + LIBDEPS=['options_parser', '$BUILD_DIR/mongo/unittest/unittest']) diff --git a/src/mongo/util/options_parser/constraints.cpp b/src/mongo/util/options_parser/constraints.cpp new file mode 100644 index 00000000000..2186c8c4fb5 --- /dev/null +++ b/src/mongo/util/options_parser/constraints.cpp @@ -0,0 +1,74 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mongo/util/options_parser/constraints.h" + +#include "mongo/base/status.h" +#include "mongo/bson/util/builder.h" + +namespace mongo { +namespace optionenvironment { + + Status NumericKeyConstraint::check(const Environment& env) { + Value val; + Status s = env.get(_key, &val); + + if (s == ErrorCodes::KeyNotFound) { + // Key not set, skipping numeric constraint check + return Status::OK(); + } + + // The code that controls whether a type is "compatible" is contained in the Value + // class, so if that handles compatibility between numeric types then this will too. + long intVal; + if (val.get(&intVal).isOK()) { + if (intVal < _min || intVal > _max) { + StringBuilder sb; + sb << "Error: Attempting to set " << _key << " to value: " << + intVal << " which is out of range: (" << + _min << "," << _max << ")"; + return Status(ErrorCodes::InternalError, sb.str()); + } + } + else { + StringBuilder sb; + sb << "Error: " << _key << " is of type: " << val.typeToString() << + " but must be of a numeric type."; + return Status(ErrorCodes::InternalError, sb.str()); + } + return Status::OK(); + } + + Status ImmutableKeyConstraint::check(const Environment& env) { + Value env_value; + Status ret = env.get(_key, &env_value); + if (ret.isOK()) { + if (_value.isEmpty()) { + _value = env_value; + } + else { + if (!_value.equal(env_value)) { + StringBuilder sb; + sb << "Error: " << _key << " is immutable once set"; + return Status(ErrorCodes::InternalError, sb.str()); + } + } + } + + return ret; + } + +} // namespace optionenvironment +} // namespace mongo diff --git a/src/mongo/util/options_parser/constraints.h b/src/mongo/util/options_parser/constraints.h new file mode 100644 index 00000000000..9d111c340ef --- /dev/null +++ b/src/mongo/util/options_parser/constraints.h @@ -0,0 +1,124 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "mongo/base/status.h" +#include "mongo/bson/util/builder.h" +#include "mongo/util/options_parser/environment.h" + +namespace mongo { +namespace optionenvironment { + + /** A Constraint validates an Environment. It has one function, which takes an Environment as + * an argument and returns either a success or failure Status depending on whether the + * Environment was valid according to this constraint + * + * These are meant to be registered with an Environment to define what it means for that + * Environment to be "valid" and run as part of its validation process + */ + class Constraint { + public: + // Interface + Status operator()(const Environment& env) { return check(env); } + virtual ~Constraint() {} + private: + // Implementation + virtual Status check(const Environment&) = 0; + }; + + /** A KeyConstraint is a Constraint on a specific Key. Currently this is not handled any + * differently than a Constraint, and is only here as a way to help document whether a + * Constraint applies to a single Key or an Environment as a whole + */ + class KeyConstraint : public Constraint { + public: + KeyConstraint(const Key& key) : + _key(key) + { } + virtual ~KeyConstraint() {} + protected: + Key _key; + }; + + /** Implementation of a Constraint on the range of a numeric Value. Fails if the Value is not a + * number, or if it is a number but outside the given range + */ + class NumericKeyConstraint : public KeyConstraint { + public: + NumericKeyConstraint(const Key& k, long min, long max) : + KeyConstraint(k), + _min(min), + _max(max) + { } + virtual ~NumericKeyConstraint() {} + + private: + virtual Status check(const Environment& env); + long _min; + long _max; + }; + + /** Implementation of a Constraint that makes a Value immutable. Fails if the Value has already + * been set and we are attempting to set it to a different Value. Note that setting it to the + * same value is allowed in this implementation + */ + class ImmutableKeyConstraint : public KeyConstraint { + public: + ImmutableKeyConstraint(const Key& k) : KeyConstraint(k) + { } + virtual ~ImmutableKeyConstraint() {} + + private: + virtual Status check(const Environment& env); + Value _value; + }; + + /** Implementation of a Constraint on the type of a Value. Fails if we cannot extract the given + * type from our Value, which means the implementation of the access functions of Value + * controls which types are "compatible" + */ + template <typename T> + class TypeKeyConstraint : public KeyConstraint { + public: + TypeKeyConstraint(const Key& k) : + KeyConstraint(k) + { } + virtual ~TypeKeyConstraint() {} + + private: + virtual Status check(const Environment& env) { + Value val; + Status s = env.get(_key, &val); + if (!s.isOK()) { + // Key not set, skipping type constraint check + return Status::OK(); + } + + // The code that controls whether a type is "compatible" is contained in the Value + // class, so if that handles compatibility between numeric types then this will too. + T typedVal; + if (!val.get(&typedVal).isOK()) { + StringBuilder sb; + sb << "Error: value for key: " << _key << " was found as type: " + << val.typeToString() << " but is required to be type: " << typeid(typedVal).name(); + return Status(ErrorCodes::InternalError, sb.str()); + } + return Status::OK(); + } + }; + +} // namespace optionenvironment +} // namespace mongo diff --git a/src/mongo/util/options_parser/environment.cpp b/src/mongo/util/options_parser/environment.cpp new file mode 100644 index 00000000000..0db43abef4a --- /dev/null +++ b/src/mongo/util/options_parser/environment.cpp @@ -0,0 +1,151 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mongo/util/options_parser/environment.h" + +#include <boost/shared_ptr.hpp> +#include <iostream> + +#include "mongo/bson/util/builder.h" +#include "mongo/util/options_parser/constraints.h" + +namespace mongo { +namespace optionenvironment { + + // Environment implementation + + Status Environment::addKeyConstraint(KeyConstraint* c) { + keyConstraints.push_back(boost::shared_ptr<KeyConstraint>(c)); + return Status::OK(); + } + Status Environment::addConstraint(Constraint* c) { + constraints.push_back(boost::shared_ptr<Constraint>(c)); + return Status::OK(); + } + + /** Get the value at Key. Note that we should not be able to add empty values to the + * environment, so we don't check for that here */ + Status Environment::get(const Key& get_key, Value* get_value) const { + typedef std::map<Key, Value>::const_iterator it_type; + it_type value = values.find(get_key); + if (value == values.end()) { + StringBuilder sb; + sb << "Value not found for key: " << get_key; + return Status(ErrorCodes::KeyNotFound, sb.str()); + } + else { + *get_value = value->second; + return Status::OK(); + } + } + + /** Set the Value in our Environment. Always disallow empty values */ + Status Environment::set(const Key& add_key, const Value& add_value) { + + // 1. Make sure value is not empty + if (add_value.isEmpty()) { + return Status(ErrorCodes::InternalError, "Attempted to add an empty value"); + } + + // 2. Save old values + std::map <Key, Value> old_values = values; + + // 3. Add value to be added + values[add_key] = add_value; + + // 4. Validate only if our environment is already valid + if (valid) { + Status ret = validate(); + if (!ret.isOK()) { + // 5. Revert to old values if this was invalid + values = old_values; + return ret; + } + } + + return Status::OK(); + } + + /** Set all the Values from the source Environment in our Environment. Does not check for empty + * values as the source Environment should not have been allowed to have any */ + Status Environment::setAll(const Environment& add_environment) { + + // 1. Save old values + std::map <Key, Value> old_values = values; + + // 2. Add values to be added + std::map <Key, Value> add_values = add_environment.values; + for(std::map<Key, Value>::const_iterator iterator = add_values.begin(); + iterator != add_values.end(); iterator++) { + values[iterator->first] = iterator->second; + } + + // 3. Validate only if our environment is already valid + if (valid) { + Status ret = validate(); + if (!ret.isOK()) { + // 4. Revert to old values if this was invalid + values = old_values; + return ret; + } + } + + return Status::OK(); + } + + /** Validate the Environment by iterating over all our constraints and calling them on our + * Environment + */ + Status Environment::validate() { + + // 1. Iterate and check all KeyConstraints + typedef std::vector<boost::shared_ptr<KeyConstraint> >::iterator it_keyConstraint; + for(it_keyConstraint iterator = keyConstraints.begin(); + iterator != keyConstraints.end(); iterator++) { + Status ret = (*(*iterator).get())(*this); + if (!ret.isOK()) { + return ret; + } + } + + // 2. Iterate and check all Constraints + typedef std::vector<boost::shared_ptr<Constraint> >::iterator it_constraint; + for(it_constraint iterator = constraints.begin(); + iterator != constraints.end(); iterator++) { + Status ret = (*(*iterator).get())(*this); + if (!ret.isOK()) { + return ret; + } + } + + // 3. Our Environment is now valid. Record this and return success + valid = true; + return Status::OK(); + } + + /* Debugging */ + void Environment::dump() { + std::map<Key, Value>::iterator iter; + for (iter = values.begin(); iter != values.end(); ++iter) { + std::cout << "Key: '" + << iter->first + << "', Value: '" + << iter->second.toString() + << "'" << std::endl; + } + } + +} // namespace optionenvironment +} // namespace mongo diff --git a/src/mongo/util/options_parser/environment.h b/src/mongo/util/options_parser/environment.h new file mode 100644 index 00000000000..15725d08814 --- /dev/null +++ b/src/mongo/util/options_parser/environment.h @@ -0,0 +1,165 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <map> +#include <vector> + +#include "mongo/base/status.h" +#include "mongo/util/options_parser/value.h" + +namespace mongo { +namespace optionenvironment { + + class Constraint; + class KeyConstraint; + + typedef std::string Key; + + /** An Environment is a map of values that can be validated according to a set of registered + * constraints + * + * Usage overview: + * + * 1. Create an empty Environment + * 2. Add Constraints + * 3. Set Key/Value pairs (will not cause constraints to be triggered) + * 4. Validate (will run all constraints) + * 5. Access + * 6. Set/Modify Key/Value pairs (will run all constraints and reject invalid modifications) + * 7. Access + * + * Since the constraints are run whenever we try to set or modify Key/Value pairs after we + * validate, we have the invariant that the Environment is always valid according to its + * Constraints after validation. Adding new constraints is disallowed after validation. + * + * Usage example: + * + * // Create an empty Environment + * Environment environment; + * + * // Initialize our first Key and Value + * Key key1("key1"); + * Value value1(1); + * + * // Add a Constraint on "key1" + * Status ret = environment.addConstraint(new ImmutableKeyConstraint("key1")); + * if (!ret.isOK()) { + * return ret; + * } + * + * // Set our first Key and Value to the Environment + * ret = environment.set(key1, value1); + * if (!ret.isOK()) { + * return ret; + * } + * + * // Attempt to mutate should be successful, since validate has not been called + * ret = environment.set(key1, Value(2)); + * if (!ret.isOK()) { + * return ret; + * } + * + * // Validate our Environment + * ret = environment.validate(); + * if (!ret.isOK()) { + * return ret; + * } + * + * // Access our Environment + * int intvalue1; + * ret = environment.get(key1, &intvalue1); + * if (!ret.isOK()) { + * return ret; + * } + * + * // Attempt to mutate should fail, since validate has been called + * ret = environment.set(key1, Value(3)); + * if (!ret.isOK()) { + * return ret; + * } + */ + + class Environment { + public: + Environment() : valid(false) { } + ~Environment() { } + + /** These functions are to add Constraints and KeyConstraints which will be run against + * this environment in the following situations: + * 1. in the "validate" function + * 2. in the "set" function after validate has been called successfully + * + * It is an error to call these functions after "validate" has been called + * + * WARNING: These take ownership of the pointer passed in + */ + Status addKeyConstraint(KeyConstraint* keyConstraint); + Status addConstraint(Constraint* constraint); + + /** Add the Value to this Environment with the given Key. If "validate" has already + * been called on this Environment, runs all Constraints on the new Environment. If + * any of the Constraints fail, reverts to the old Environment and returns an error + */ + Status set(const Key& key, const Value& value); + + /** Populate the given Value with the Value stored for the given Key. Return a success + * status if the value was found, or an error status if the value was not found. + * Leaves the Value unchanged on error. + */ + Status get(const Key& key, Value* value) const; + + /** Same as the above get interface, but supports directly getting C++ types without the + * intermediate Value and has the added failure case of the value being the wrong type + */ + template <typename T> + Status get(const Key& key, T* value_contents) const; + + /** Runs all registered Constraints and returns the result. On success, marks this as a + * valid Environment so that any modifications will re run all Constraints + */ + Status validate(); + + /** Sets all variables in the given Environment in this Environment. Does not add + * Constraints + */ + Status setAll(const Environment& other); + + /* Debugging */ + void dump(); + + protected: + std::vector<boost::shared_ptr<Constraint> > constraints; + std::vector<boost::shared_ptr<KeyConstraint> > keyConstraints; + std::map <Key, Value> values; + bool valid; + }; + + template <typename T> + Status Environment::get(const Key& get_key, T* get_value) const { + typedef std::map<Key, Value>::const_iterator it_type; + it_type value = values.find(get_key); + if (value == values.end()) { + return Status(ErrorCodes::InternalError, "Value not found!"); + } + else { + return value->second.get(get_value); + } + } + +} // namespace optionenvironment +} // namespace mongo diff --git a/src/mongo/util/options_parser/environment_test.cpp b/src/mongo/util/options_parser/environment_test.cpp new file mode 100644 index 00000000000..d92a9f682ff --- /dev/null +++ b/src/mongo/util/options_parser/environment_test.cpp @@ -0,0 +1,81 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mongo/bson/util/builder.h" + +#include "mongo/util/options_parser/constraints.h" +#include "mongo/util/options_parser/environment.h" +#include "mongo/unittest/unittest.h" + +namespace { + + using mongo::ErrorCodes; + using mongo::Status; + + namespace moe = mongo::optionenvironment; + + TEST(Environment, EmptyValue) { + moe::Environment environment; + ASSERT_NOT_OK(environment.set(moe::Key("empty"), moe::Value())); + } + + TEST(Environment, Immutable) { + moe::Environment environment; + environment.addKeyConstraint(new moe::ImmutableKeyConstraint(moe::Key("port"))); + ASSERT_OK(environment.set(moe::Key("port"), moe::Value(5))); + ASSERT_OK(environment.validate()); + ASSERT_NOT_OK(environment.set(moe::Key("port"), moe::Value(0))); + } + + TEST(Environment, OutOfRange) { + moe::Environment environment; + environment.addKeyConstraint(new moe::NumericKeyConstraint(moe::Key("port"), 1000, 65535)); + ASSERT_OK(environment.validate()); + ASSERT_NOT_OK(environment.set(moe::Key("port"), moe::Value(0))); + } + + TEST(Environment, NonNumericRangeConstraint) { + moe::Environment environment; + environment.addKeyConstraint(new moe::NumericKeyConstraint(moe::Key("port"), 1000, 65535)); + ASSERT_OK(environment.validate()); + ASSERT_NOT_OK(environment.set(moe::Key("port"), moe::Value("string"))); + } + + TEST(Environment, BadType) { + moe::Environment environment; + environment.addKeyConstraint(new moe::TypeKeyConstraint<int>(moe::Key("port"))); + ASSERT_OK(environment.set(moe::Key("port"), moe::Value("string"))); + ASSERT_NOT_OK(environment.validate()); + } + + TEST(Environment, AllowNumeric) { + moe::Environment environment; + environment.addKeyConstraint(new moe::TypeKeyConstraint<long>(moe::Key("port"))); + environment.addKeyConstraint(new moe::TypeKeyConstraint<int>(moe::Key("port"))); + ASSERT_OK(environment.set(moe::Key("port"), moe::Value(1))); + ASSERT_OK(environment.validate()); + } + + TEST(Environment, DirectTypeAccess) { + moe::Environment environment; + ASSERT_OK(environment.set(moe::Key("number"), moe::Value(5))); + std::string notNumber; + ASSERT_NOT_OK(environment.get(moe::Key("number"), ¬Number)); + int number; + ASSERT_OK(environment.get(moe::Key("number"), &number)); + ASSERT_EQUALS(number, 5); + } + +} // unnamed namespace diff --git a/src/mongo/util/options_parser/option_description.h b/src/mongo/util/options_parser/option_description.h new file mode 100644 index 00000000000..08d99122243 --- /dev/null +++ b/src/mongo/util/options_parser/option_description.h @@ -0,0 +1,80 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <iostream> + +#include "mongo/base/status.h" + +namespace mongo { +namespace optionenvironment { + + /** + * An OptionType is an enum of all the types we support in the OptionsParser + */ + enum OptionType { + StringVector, // po::value< std::vector<std::string> > + Bool, // po::value<bool> + Double, // po::value<double> + Int, // po::value<int> + Long, // po::value<long> + String, // po::value<std::string> + UnsignedLongLong, // po::value<unsigned long long> + Unsigned, // po::value<unsigned> + Switch // po::bool_switch + }; + + /** + * The OptionDescription and PositionalOptionDescription classes are containers for information + * about the options we are expecting either on the command line or in config files. These + * should be registered in an OptionSection instance and passed to an OptionsParser. + */ + class OptionDescription { + public: + OptionDescription(const std::string& dottedName, // Used for JSON config and in Environment + const std::string& singleName, // Used for boost command line and INI + const OptionType type, + const std::string& description, + const bool isVisible = true) + : _dottedName(dottedName), + _singleName(singleName), + _type(type), + _description(description), + _isVisible(isVisible) { } + + std::string _dottedName; + std::string _singleName; + OptionType _type; + std::string _description; + bool _isVisible; + }; + + class PositionalOptionDescription { + public: + PositionalOptionDescription(const std::string& name, + const OptionType type, + int count = 1) + : _name(name), + _type(type), + _count(count) { } + + std::string _name; + OptionType _type; + int _count; // Max number of times this option could be specified. -1 = unlimited + }; + +} // namespace optionenvironment +} // namespace mongo diff --git a/src/mongo/util/options_parser/option_section.cpp b/src/mongo/util/options_parser/option_section.cpp new file mode 100644 index 00000000000..acd381c4bb5 --- /dev/null +++ b/src/mongo/util/options_parser/option_section.cpp @@ -0,0 +1,240 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mongo/util/options_parser/option_section.h" + +#include <sstream> + +#include "mongo/bson/util/builder.h" + +namespace mongo { +namespace optionenvironment { + + // Registration interface + + Status OptionSection::addSection(const OptionSection& subSection) { + if (!subSection._positionalOptions.empty()) { + return Status(ErrorCodes::InternalError, + "Attempted to add subsection with positional options"); + } + _subSections.push_back(subSection); + return Status::OK(); + } + + Status OptionSection::addOption(const OptionDescription& option) { + // Verify that neither the single name nor the dotted name for this option conflicts with + // the names for any options we have already registered + std::vector<OptionDescription>::const_iterator oditerator; + for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) { + if (option._dottedName == oditerator->_dottedName) { + StringBuilder sb; + sb << "Attempted to register option with duplicate dottedName: " + << option._dottedName; + return Status(ErrorCodes::InternalError, sb.str()); + } + if (option._singleName == oditerator->_singleName) { + StringBuilder sb; + sb << "Attempted to register option with duplicate singleName: " + << option._singleName; + return Status(ErrorCodes::InternalError, sb.str()); + } + } + _options.push_back(option); + return Status::OK(); + } + + Status OptionSection::addPositionalOption(const PositionalOptionDescription& positionalOption) { + // Verify that the name for this positional option does not conflict with the name for any + // positional option we have already registered + std::vector<PositionalOptionDescription>::const_iterator poditerator; + for (poditerator = _positionalOptions.begin(); + poditerator != _positionalOptions.end(); poditerator++) { + if (positionalOption._name == poditerator->_name) { + StringBuilder sb; + sb << "Attempted to register duplicate positional option: " + << positionalOption._name; + return Status(ErrorCodes::InternalError, sb.str()); + } + } + Status ret = addOption(OptionDescription(positionalOption._name, + positionalOption._name, + positionalOption._type, + "hidden description", + false/*hidden*/)); + if (!ret.isOK()) { + return ret; + } + _positionalOptions.push_back(positionalOption); + return Status::OK(); + } + + // Stuff for dealing with Boost + + namespace { + /** Helper function to convert the values of our OptionType enum into the classes that + * boost::program_option uses to pass around this information + */ + po::value_semantic* typeToBoostType(OptionType type) { + switch (type) { + case StringVector: return po::value< std::vector<std::string> >(); + case Bool: return po::value<bool>(); + case Double: return po::value<double>(); + case Int: return po::value<int>(); + case Long: return po::value<long>(); + case String: return po::value<std::string>(); + case UnsignedLongLong: return po::value<unsigned long long>(); + case Unsigned: return po::value<unsigned>(); + case Switch: return po::bool_switch(); + default: return NULL; /* XXX: should not get here */ + } + } + } // namespace + + Status OptionSection::getBoostOptions(po::options_description* boostOptions, + bool visibleOnly) const { + + std::vector<OptionDescription>::const_iterator oditerator; + for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) { + if (!visibleOnly || (oditerator->_isVisible)) { + boostOptions->add_options()(oditerator->_singleName.c_str(), + typeToBoostType(oditerator->_type), + oditerator->_description.c_str()); + } + } + + std::vector<OptionSection>::const_iterator ositerator; + for (ositerator = _subSections.begin(); ositerator != _subSections.end(); ositerator++) { + po::options_description subGroup = ositerator->_name.empty() + ? po::options_description() + : po::options_description(ositerator->_name.c_str()); + ositerator->getBoostOptions(&subGroup, visibleOnly); + boostOptions->add(subGroup); + } + + return Status::OK(); + } + + Status OptionSection::getBoostPositionalOptions( + po::positional_options_description* boostPositionalOptions) const { + + std::vector<PositionalOptionDescription>::const_iterator poditerator; + for (poditerator = _positionalOptions.begin(); + poditerator != _positionalOptions.end(); poditerator++) { + boostPositionalOptions->add(poditerator->_name.c_str(), poditerator->_count); + } + + return Status::OK(); + } + + // Get options for iterating + // TODO: should I make this an iterator? + + Status OptionSection::getAllOptions(std::vector<OptionDescription>* options) const { + + std::vector<OptionDescription>::const_iterator oditerator; + for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) { + options->push_back(*oditerator); + } + + std::vector<OptionSection>::const_iterator ositerator; + for (ositerator = _subSections.begin(); ositerator != _subSections.end(); ositerator++) { + ositerator->getAllOptions(options); + } + + return Status::OK(); + } + + std::string OptionSection::positionalHelpString(const std::string& execName) const { + + po::positional_options_description boostPositionalOptions; + Status ret = getBoostPositionalOptions(&boostPositionalOptions); + if (!ret.isOK()) { + StringBuilder sb; + sb << "Error constructing help string: " << ret.toString(); + return sb.str(); + } + + StringBuilder posHelpStringBuilder; + posHelpStringBuilder << execName; + + // If we can have unlimited positional options, this returns + // std::numeric_limits<unsigned>::max(). Check here for that case and record what name to + // look for. + unsigned int numPositional = boostPositionalOptions.max_total_count(); + std::string trailingPositionName; + if (numPositional == std::numeric_limits<unsigned>::max()) { + trailingPositionName = boostPositionalOptions.name_for_position(numPositional - 1); + } + + unsigned int position; + std::string positionName; + for (position = 0; position < numPositional; position++) { + positionName = boostPositionalOptions.name_for_position(position); + if (!trailingPositionName.empty() && trailingPositionName == positionName) { + // If we have a trailing position, we break when we see it the first time. + posHelpStringBuilder << " [" << trailingPositionName << " ... ]"; + break; + } + posHelpStringBuilder << " [" << positionName << "]"; + } + + return posHelpStringBuilder.str(); + } + + std::string OptionSection::helpString() const { + + po::options_description boostOptions; + Status ret = getBoostOptions(&boostOptions); + if (!ret.isOK()) { + StringBuilder sb; + sb << "Error constructing help string: " << ret.toString(); + return sb.str(); + } + + // Can't use a StringBuilder here because boost::program_options only has functions that + // output to std::ostream + std::ostringstream os; + os << boostOptions; + return os.str(); + } + + /* Debugging */ + void OptionSection::dump() const { + std::vector<PositionalOptionDescription>::const_iterator poditerator; + for (poditerator = _positionalOptions.begin(); + poditerator != _positionalOptions.end(); poditerator++) { + std::cout << " _name: " << poditerator->_name + << " _type: " << poditerator->_type + << " _count: " << poditerator->_count << std::endl; + } + + std::vector<OptionDescription>::const_iterator oditerator; + for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) { + std::cout << " _dottedName: " << oditerator->_dottedName + << " _singleName: " << oditerator->_singleName + << " _type: " << oditerator->_type + << " _description: " << oditerator->_description + << " _isVisible: " << oditerator->_isVisible << std::endl; + } + + std::vector<OptionSection>::const_iterator ositerator; + for (ositerator = _subSections.begin(); ositerator != _subSections.end(); ositerator++) { + std::cout << "Section Name: " << ositerator->_name << std::endl; + ositerator->dump(); + } + } + +} // namespace optionenvironment +} // namespace mongo diff --git a/src/mongo/util/options_parser/option_section.h b/src/mongo/util/options_parser/option_section.h new file mode 100644 index 00000000000..86e0a9d2e54 --- /dev/null +++ b/src/mongo/util/options_parser/option_section.h @@ -0,0 +1,110 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include "mongo/util/options_parser/option_description.h" + +#include <boost/program_options.hpp> +#include <iostream> + +#include "mongo/base/status.h" + +namespace mongo { +namespace optionenvironment { + + namespace po = boost::program_options; + + /** A container for OptionDescription instances and PositionalOptionDescription instances as + * well as other OptionSection instances. Provides a description of all options that are + * supported to be passed in to an OptionsParser. Has utility functions to support the various + * formats needed by the parsing process + * + * Usage: + * + * namespace moe = mongo::optionenvironment; + * + * moe::OptionsParser parser; + * moe::Environment environment; + * moe::OptionSection options; + * moe::OptionSection subSection("Section Name"); + * + * // Register our allowed option flags with our OptionSection + * options.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display Help")); + * + * // Register our positional options with our OptionSection + * options.addPositionalOption(moe::PositionalOptionDescription("command", moe::String)); + * + * // Add a subsection + * subSection.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + * options.addSection(subSection); + * + * // Run the parser + * Status ret = parser.run(options, argc, argv, envp, &environment); + * if (!ret.isOK()) { + * cerr << options.helpString() << endl; + * exit(EXIT_FAILURE); + * } + */ + + class OptionSection { + public: + OptionSection(const std::string& name) : _name(name) { } + OptionSection() { } + + // Construction interface + + /** + * Add a sub section to this section. Used mainly to keep track of section headers for when + * we need generate the help string for the command line + */ + Status addSection(const OptionSection& subSection); + /** + * Add an option to this section + */ + Status addOption(const OptionDescription& option); + /** + * Add a positional option to this section. Also adds a normal hidden option with the same + * name as the PositionalOptionDescription because that is the mechanism boost program + * options uses. Unfortunately this means that positional options can also be accessed by + * name in the config files and via command line flags + */ + Status addPositionalOption(const PositionalOptionDescription& positionalOption); + + // These functions are used by the OptionsParser to make calls into boost::program_options + Status getBoostOptions(po::options_description* boostOptions, + bool visibleOnly = false) const; + Status getBoostPositionalOptions( + po::positional_options_description* boostPositionalOptions) const; + + // This is needed so that the parser can iterate over all registered options to get the + // correct names when populating the Environment, as well as check that a parameter that was + // found has been registered and has the correct type + Status getAllOptions(std::vector<OptionDescription>* options) const; + + std::string positionalHelpString(const std::string& execName) const; + std::string helpString() const; + + // Debugging + void dump() const; + + private: + std::string _name; + std::vector<OptionSection> _subSections; + std::vector<OptionDescription> _options; + std::vector<PositionalOptionDescription> _positionalOptions; + }; + +} // namespace optionenvironment +} // namespace mongo diff --git a/src/mongo/util/options_parser/options_parser.cpp b/src/mongo/util/options_parser/options_parser.cpp new file mode 100644 index 00000000000..9e0d007b46c --- /dev/null +++ b/src/mongo/util/options_parser/options_parser.cpp @@ -0,0 +1,611 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mongo/util/options_parser/options_parser.h" + +#include <boost/program_options.hpp> +#include <boost/shared_ptr.hpp> +#include <cerrno> +#include <fstream> +#include <stdio.h> + +#include "mongo/base/status.h" +#include "mongo/bson/bsonobjiterator.h" +#include "mongo/db/json.h" +#include "mongo/util/options_parser/environment.h" +#include "mongo/util/options_parser/constraints.h" +#include "mongo/util/options_parser/option_description.h" +#include "mongo/util/options_parser/option_section.h" + +namespace mongo { +namespace optionenvironment { + + using namespace std; + + namespace po = boost::program_options; + + namespace { + + // The following section contains utility functions that convert between the various objects + // we need to deal with while parsing command line options. + // + // These conversions are different depending on the data source because our current + // implementation uses boost::program_options for the command line and INI files and the + // mongo JSON parser for JSON config files. Our destination storage in both cases is an + // Environment which stores Value objects. + // + // 1. JSON Config Files + // The mongo JSON parser parses a JSON string into a BSON object. Therefore, we need: + // a. A function to convert a BSONElement to a Value (BSONElementToValue) + // b. A function to iterate a BSONObj, convert the BSONElements to Values, and add + // them to our Environment (addBSONElementsToEnvironment) + // + // 2. INI Config Files and command line + // The boost::program_options parsers store their output in a + // boost::program_options::variables_map. Therefore, we need: + // a. A function to convert a boost::any to a Value (boostAnyToValue) + // b. A function to iterate a variables_map, convert the boost::any elements to + // Values, and add them to our Environment (addBoostVariablesToEnvironment) + + // Convert a boost::any to a Value. See comments at the beginning of this section. + Status boostAnyToValue(const boost::any& anyValue, Value* value) { + try { + if (anyValue.type() == typeid(std::vector<std::string>)) { + *value = Value(boost::any_cast<std::vector<std::string> >(anyValue)); + } + else if (anyValue.type() == typeid(bool)) { + *value = Value(boost::any_cast<bool>(anyValue)); + } + else if (anyValue.type() == typeid(double)) { + *value = Value(boost::any_cast<double>(anyValue)); + } + else if (anyValue.type() == typeid(int)) { + *value = Value(boost::any_cast<int>(anyValue)); + } + else if (anyValue.type() == typeid(long)) { + *value = Value(boost::any_cast<long>(anyValue)); + } + else if (anyValue.type() == typeid(std::string)) { + *value = Value(boost::any_cast<std::string>(anyValue)); + } + else if (anyValue.type() == typeid(unsigned long long)) { + *value = Value(boost::any_cast<unsigned long long>(anyValue)); + } + else if (anyValue.type() == typeid(unsigned)) { + *value = Value(boost::any_cast<unsigned>(anyValue)); + } + else { + StringBuilder sb; + sb << "Unrecognized type: " << anyValue.type().name() << + " in any to Value conversion"; + return Status(ErrorCodes::InternalError, sb.str()); + } + } + catch(const boost::bad_any_cast& e) { + StringBuilder sb; + // We already checked the type, so this is just a sanity check + sb << "boost::any_cast threw exception: " << e.what(); + return Status(ErrorCodes::InternalError, sb.str()); + } + return Status::OK(); + } + + // Add all the values in the given variables_map to our environment. See comments at the + // beginning of this section. + Status addBoostVariablesToEnvironment(const po::variables_map& vm, + const OptionSection& options, + Environment* environment) { + + std::vector<OptionDescription> options_vector; + Status ret = options.getAllOptions(&options_vector); + if (!ret.isOK()) { + return ret; + } + + for(std::vector<OptionDescription>::const_iterator iterator = options_vector.begin(); + iterator != options_vector.end(); iterator++) { + if (vm.count(iterator->_singleName)) { + Value optionValue; + Status ret = boostAnyToValue(vm[iterator->_singleName].value(), &optionValue); + if (!ret.isOK()) { + return ret; + } + environment->set(iterator->_dottedName, optionValue); + } + } + return Status::OK(); + } + + // Check if the given key is registered in our OptionDescription. This is needed for JSON + // Config File handling since the JSON parser just reads in whatever fields and values it + // sees without taking a description of what to look for. + Status isRegistered(const std::vector<OptionDescription>& options_vector, const Key& key) { + + for(std::vector<OptionDescription>::const_iterator iterator = options_vector.begin(); + iterator != options_vector.end(); iterator++) { + if (key == iterator->_dottedName) { + return Status::OK(); + } + } + StringBuilder sb; + sb << "Unrecognized option: " << key; + return Status(ErrorCodes::InternalError, sb.str()); + } + + // Convert a BSONElement to a Value. See comments at the beginning of this section. + Status BSONElementToValue(const BSONElement& element, Value* value) { + + std::vector<BSONElement> elements; + std::vector<std::string> valueStrings; + try { + switch (element.type()) { + case ::mongo::NumberInt: + *value = Value(element.Int()); + return Status::OK(); + case ::mongo::NumberDouble: + *value = Value(element.Double()); + return Status::OK(); + case ::mongo::NumberLong: + // FIXME: Figure out how to stop this, or detect overflow + *value = Value(static_cast<unsigned long long>(element.Long())); + return Status::OK(); + case ::mongo::String: + *value = Value(element.String()); + return Status::OK(); + case ::mongo::Array: + elements = element.Array(); + for(std::vector<BSONElement>::const_iterator iterator = elements.begin(); + iterator != elements.end(); iterator++) { + if (iterator->type() == ::mongo::String) { + valueStrings.push_back(iterator->String()); + } + else { + StringBuilder sb; + sb << "Arrays can only contain strings in JSON Config File"; + return Status(ErrorCodes::InternalError, sb.str()); + } + } + *value = Value(valueStrings); + return Status::OK(); + case ::mongo::Bool: + *value = Value(element.Bool()); + return Status::OK(); + case ::mongo::EOO: + return Status(ErrorCodes::InternalError, + "Error converting BSONElement to value; BSONElement empty"); + default: + StringBuilder sb; + sb << "Conversion from BSONElement type: " << + element.type() << " not supported."; + return Status(ErrorCodes::TypeMismatch, sb.str()); + } + } + catch ( std::exception &e ) { + StringBuilder sb; + sb << "Exception thrown by BSON conversion: " << e.what(); + return Status(ErrorCodes::InternalError, sb.str()); + } + } + + // Add all the values in the given BSONObj to our environment. See comments at the + // beginning of this section. + Status addBSONElementsToEnvironment(const BSONObj& obj, + const OptionSection& options, + const std::string parentPath, + Environment* environment) { + + std::vector<OptionDescription> options_vector; + Status ret = options.getAllOptions(&options_vector); + if (!ret.isOK()) { + return ret; + } + + BSONObjIterator iterator(obj); + while (iterator.more()) { + BSONElement elem = iterator.next(); + string fieldName= elem.fieldName(); + + std::string dottedName = ( parentPath.empty() ? fieldName + : parentPath+'.'+fieldName ); + + if (elem.type() == ::mongo::Object) { + addBSONElementsToEnvironment( elem.Obj(), options, dottedName, environment ); + } + else { + Value optionValue; + Status ret = BSONElementToValue(elem, &optionValue); + if (!ret.isOK()) { + return ret; + } + + ret = isRegistered(options_vector, dottedName); + if (!ret.isOK()) { + return ret; + } + + Value dummyVal; + if (environment->get(dottedName, &dummyVal).isOK()) { + StringBuilder sb; + sb << "Error parsing JSON config: duplcate key: " << dottedName; + return Status(ErrorCodes::InternalError, sb.str()); + } + + ret = environment->set(dottedName, optionValue); + if (!ret.isOK()) { + return ret; + } + } + } + + return Status::OK(); + } + + // Iterate through our options and add type constraints to our environment based on what + // types the options were registered with. This is needed for the JSON config file + // handling, since the JSON parser just reads the types without checking them. Currently, + // the boost parsers check the types for us. + Status addTypeConstraints(const OptionSection& options, Environment* environment) { + + std::vector<OptionDescription> options_vector; + Status ret = options.getAllOptions(&options_vector); + if (!ret.isOK()) { + return ret; + } + + for(std::vector<OptionDescription>::const_iterator iterator = options_vector.begin(); + iterator != options_vector.end(); iterator++) { + switch (iterator->_type) { + case StringVector: + environment->addKeyConstraint( + new TypeKeyConstraint<std::vector<std::string> >(iterator->_dottedName)); + break; + case Bool: + environment->addKeyConstraint( + new TypeKeyConstraint<bool>(iterator->_dottedName)); + break; + case Double: + environment->addKeyConstraint( + new TypeKeyConstraint<double>(iterator->_dottedName)); + break; + case Int: + environment->addKeyConstraint( + new TypeKeyConstraint<int>(iterator->_dottedName)); + break; + case Long: + environment->addKeyConstraint( + new TypeKeyConstraint<long>(iterator->_dottedName)); + break; + case String: + environment->addKeyConstraint( + new TypeKeyConstraint<std::string>(iterator->_dottedName)); + break; + case UnsignedLongLong: + environment->addKeyConstraint( + new TypeKeyConstraint<unsigned long long>(iterator->_dottedName)); + break; + case Unsigned: + environment->addKeyConstraint( + new TypeKeyConstraint<unsigned>(iterator->_dottedName)); + break; + case Switch: + environment->addKeyConstraint( + new TypeKeyConstraint<bool>(iterator->_dottedName)); + break; + default: /* XXX: should not get here */ + return Status(ErrorCodes::InternalError, "Unrecognized option type"); + } + } + return Status::OK(); + } + + } // namespace + + /** + * This function delegates the command line parsing to boost program_options. + * + * 1. Extract the boost readable option descriptions and positional option descriptions from the + * OptionSection + * 2. Passes them to the boost command line parser + * 3. Copy everything from the variables map returned by boost into the Environment + */ + Status OptionsParser::parseCommandLine(const OptionSection& options, + const std::vector<std::string>& argv, + Environment* environment) { + po::options_description boostOptions; + po::positional_options_description boostPositionalOptions; + po::variables_map vm; + + // Need to convert to an argc and a vector of char* since that is what + // boost::program_options expects as input to its command line parser + int argc = 0; + std::vector<const char*> argv_buffer; + for (std::vector<std::string>::const_iterator iterator = argv.begin(); + iterator != argv.end(); iterator++) { + argv_buffer.push_back(iterator->c_str()); + argc++; + } + + /** + * Style options for boost command line parser + * + * unix_style is an alias for a group of basic style options. We are deviating from that + * base style in the following ways: + * + * 1. Don't allow guessing - '--dbpat' != '--dbpath' + * 2. Don't allow sticky - '-hf' != '-h -f' + * 3. Allow long disguises - '--dbpath' == '-dbpath' + * + * In some executables, we are using multiple 'v' options to set the verbosity (e.g. '-vvv') + * To make this work, we need to allow long disguises and disallow guessing. + */ + int style = (((po::command_line_style::unix_style ^ + po::command_line_style::allow_guessing) | + po::command_line_style::allow_long_disguise) ^ + po::command_line_style::allow_sticky); + + Status ret = options.getBoostOptions(&boostOptions); + if (!ret.isOK()) { + return ret; + } + + ret = options.getBoostPositionalOptions(&boostPositionalOptions); + if (!ret.isOK()) { + return ret; + } + + try { + po::store(po::command_line_parser(argc, &argv_buffer[0]). + options(boostOptions). + positional(boostPositionalOptions). + style(style). + run(), vm); + + ret = addBoostVariablesToEnvironment(vm, options, environment); + if (!ret.isOK()) { + return ret; + } + } + catch (po::error& e) { + StringBuilder sb; + sb << "Error parsing command line: " << e.what(); + return Status(ErrorCodes::InternalError, sb.str()); + } + return Status::OK(); + } + + /** + * This function delegates the INI config parsing to boost program_options. + * + * 1. Extract the boost readable option descriptions from the OptionSection + * 2. Passes them to the boost config file parser + * 3. Copy everything from the variables map returned by boost into the Environment + */ + Status OptionsParser::parseINIConfigFile(const OptionSection& options, + const std::string& config, + Environment* environment) { + po::options_description boostOptions; + po::variables_map vm; + + Status ret = options.getBoostOptions(&boostOptions); + if (!ret.isOK()) { + return ret; + } + + std::istringstream is(config); + try { + po::store(po::parse_config_file(is, boostOptions), vm); + ret = addBoostVariablesToEnvironment(vm, options, environment); + if (!ret.isOK()) { + return ret; + } + } + catch (po::error& e) { + StringBuilder sb; + sb << "Error parsing INI config file: " << e.what(); + return Status(ErrorCodes::InternalError, sb.str()); + } + return Status::OK(); + } + + /** + * This function delegates the JSON config parsing to the MongoDB JSON parser. + * + * 1. Parse JSON + * 2. Add all elements from the resulting BSONObj to the Environment + * + * This function checks for duplicates and unregistered options, but the caller is responsible + * for checking that the options are the correct types + * + * Also note that the size of our JSON config file is limited in size. The equivalent BSON + * object can only be 16MB. We catch the exception that is thrown in this case and return an + * error Status from this function + */ + Status OptionsParser::parseJSONConfigFile(const OptionSection& options, + const std::string& config, + Environment* environment) { + BSONObj BSONConfig; + try { + BSONConfig = fromjson(config); + Status ret = addBSONElementsToEnvironment(BSONConfig, options, "", environment); + if (!ret.isOK()) { + return ret; + } + } catch ( MsgAssertionException& e ) { + StringBuilder sb; + sb << "Error parsing JSON config file: " << e.what(); + return Status(ErrorCodes::InternalError, sb.str()); + } + return Status::OK(); + } + + /** + * Reads the entire config file into the output string. This is done this way because the JSON + * parser only takes complete strings + */ + Status OptionsParser::readConfigFile(const std::string& filename, std::string* contents) { + + FILE* config; + config = fopen(filename.c_str(), "r"); + if (config == NULL) { + const int current_errno = errno; + StringBuilder sb; + sb << "Error reading config file: " << strerror(current_errno); + return Status(ErrorCodes::InternalError, sb.str()); + } + + // Get length of config file by seeking to the end and getting the cursor position + if (fseek(config, 0L, SEEK_END) != 0) { + const int current_errno = errno; + // TODO: Make sure errno is the correct way to do this + // Confirmed that errno gets set in Mac OSX, but not documented + StringBuilder sb; + sb << "Error seeking in config file: " << strerror(current_errno); + return Status(ErrorCodes::InternalError, sb.str()); + } + long configSize = ftell(config); + + // Seek back to the beginning of the file for reading + if (fseek(config, 0L, SEEK_SET) != 0) { + const int current_errno = errno; + // TODO: Make sure errno is the correct way to do this + // Confirmed that errno gets set in Mac OSX, but not documented + StringBuilder sb; + sb << "Error seeking in config file: " << strerror(current_errno); + return Status(ErrorCodes::InternalError, sb.str()); + } + + // Read into a vector first since it's guaranteed to have contiguous storage + std::vector<char> configVector; + configVector.resize(configSize); + long nread = fread(&configVector[0], sizeof(char), configSize, config); + if (nread != configSize) { + const int current_errno = errno; + // TODO: Make sure errno is the correct way to do this + StringBuilder sb; + sb << "Error reading in config file: " << strerror(current_errno); + return Status(ErrorCodes::InternalError, sb.str()); + } + + // Copy the vector contents into our result string + *contents = std::string(configVector.begin(), configVector.end()); + fclose(config); + + return Status::OK(); + } + + bool OptionsParser::isJSONConfig(const std::string& config) { + for (std::string::const_iterator curChar = config.begin(); + curChar < config.end(); curChar++) { + if (isspace(*curChar)) { + // Skip whitespace + } + else if (*curChar == '{') { + // If first non whitespace character is '{', then this is a JSON config file + return true; + } + else { + // Otherwise, this is a legacy INI config file + return false; + } + } + // Treat the empty config file as INI + return false; + } + + /** + * Run the OptionsParser + * + * Overview: + * + * 1. Parse argc and argv using the given OptionSection as a description of expected options + * 2. Check for a "config" argument + * 3. If "config" found, read config file + * 4. Detect config file type (JSON or INI) + * 5. Parse config file using the given OptionSection as a description of expected options + * 6. Add the results to the output Environment in the proper order to ensure correct precedence + */ + Status OptionsParser::run(const OptionSection& options, + const std::vector<std::string>& argv, + const std::map<std::string, std::string>& env, // XXX: Currently unused + Environment* environment) { + + Environment commandLineEnvironment; + Environment configEnvironment; + + Status ret = parseCommandLine(options, argv, &commandLineEnvironment); + if (!ret.isOK()) { + return ret; + } + + Value config_value; + ret = commandLineEnvironment.get(Key("config"), &config_value); + // We had an error other than "config" not existing in our environment + if (!ret.isOK() && ret != ErrorCodes::KeyNotFound) { + return ret; + } + // "config" exists in our environment + else if (ret.isOK()) { + + // Environment::get returns a bad status if config was not set + std::string config_filename; + ret = config_value.get(&config_filename); + if (!ret.isOK()) { + return ret; + } + + std::string config_file; + ret = readConfigFile(config_filename, &config_file); + if (!ret.isOK()) { + return ret; + } + + if (isJSONConfig(config_file)) { + ret = parseJSONConfigFile(options, config_file, &configEnvironment); + if (!ret.isOK()) { + return ret; + } + ret = addTypeConstraints(options, &configEnvironment); + if (!ret.isOK()) { + return ret; + } + ret = configEnvironment.validate(); + if (!ret.isOK()) { + return ret; + } + } + else { + ret = parseINIConfigFile(options, config_file, &configEnvironment); + if (!ret.isOK()) { + return ret; + } + } + } + + // Add the values to our result in the order of override + // NOTE: This should not fail validation as we haven't called environment->validate() yet + ret = environment->setAll(configEnvironment); + if (!ret.isOK()) { + return ret; + } + ret = environment->setAll(commandLineEnvironment); + if (!ret.isOK()) { + return ret; + } + + return Status::OK(); + } + +} // namespace optionenvironment +} // namespace mongo diff --git a/src/mongo/util/options_parser/options_parser.h b/src/mongo/util/options_parser/options_parser.h new file mode 100644 index 00000000000..dd401b04f5e --- /dev/null +++ b/src/mongo/util/options_parser/options_parser.h @@ -0,0 +1,115 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <map> +#include <string> +#include <vector> + +#include "mongo/base/status.h" + +class Status; + +namespace mongo { +namespace optionenvironment { + + class Environment; + class OptionSection; + class Value; + + /** Handles parsing of the command line as well as JSON and INI config files. Takes an + * OptionSection instance that describes the allowed options, parses argv (env not yet + * supported), and populates an Environment with the results. + * + * Usage: + * + * namespace moe = mongo::optionenvironment; + * + * moe::OptionsParser parser; + * moe::Environment environment; + * moe::OptionSection options; + * + * // Register our allowed options with our OptionSection + * options.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display Help")); + * options.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + * + * // Run the parser + * Status ret = parser.run(options, argv, env, &environment); + * if (!ret.isOK()) { + * cerr << options.helpString() << endl; + * exit(EXIT_FAILURE); + * } + * + * bool displayHelp; + * ret = environment.get(moe::Key("help"), &displayHelp); + * if (!ret.isOK()) { + * // Help is a switch, so it should always be set + * cout << "Should not get here" << endl; + * exit(EXIT_FAILURE); + * } + * if (displayHelp) { + * cout << options.helpString() << endl; + * exit(EXIT_SUCCESS); + * } + * + * // Get the value of port from the environment + * int port = 27017; + * ret = environment.get(moe::Key("port"), &port); + * if (ret.isOK()) { + * // We have overridden port here, otherwise it stays as the default. + * } + */ + class OptionsParser { + public: + OptionsParser() { } + virtual ~OptionsParser() { } + + /** Handles parsing of the command line as well as JSON and INI config files. The + * OptionSection be a description of the allowed options. This function populates the + * given Environment with the results of parsing the command line and or config files but + * does not call validate on the Environment. + * + * The only special option is the "config" option. This function will check if the + * "config" option was set on the command line and if so attempt to read the given config + * file. For binaries that do not support config files, the "config" option should not be + * registered in the OptionSection. + */ + Status run(const OptionSection&, + const std::vector<std::string>& argv, + const std::map<std::string, std::string>& env, + Environment*); + + private: + /** Handles parsing of the command line and adds the results to the given Environment */ + Status parseCommandLine(const OptionSection&, + const std::vector<std::string>& argv, Environment*); + + /** Handles parsing of an INI config string and adds the results to the given Environment */ + Status parseINIConfigFile(const OptionSection&, const std::string& config, Environment*); + + /** Handles parsing of a JSON config string and adds the results to the given Environment */ + Status parseJSONConfigFile(const OptionSection&, const std::string& config, Environment*); + + /** Detects whether the given string represents a JSON config file or an INI config file */ + bool isJSONConfig(const std::string& config); + + /** Reads the given config file into the output string. This function is virtual for + * testing purposes only. */ + virtual Status readConfigFile(const std::string& filename, std::string*); + }; + +} // namespace optionenvironment +} // namespace mongo diff --git a/src/mongo/util/options_parser/options_parser_test.cpp b/src/mongo/util/options_parser/options_parser_test.cpp new file mode 100644 index 00000000000..d539279aca2 --- /dev/null +++ b/src/mongo/util/options_parser/options_parser_test.cpp @@ -0,0 +1,930 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <map> +#include <sstream> + +#include "mongo/bson/util/builder.h" +#include "mongo/unittest/unittest.h" +#include "mongo/util/options_parser/environment.h" +#include "mongo/util/options_parser/option_description.h" +#include "mongo/util/options_parser/option_section.h" +#include "mongo/util/options_parser/options_parser.h" + +namespace { + + using mongo::ErrorCodes; + using mongo::Status; + + namespace moe = mongo::optionenvironment; + +#define TEST_CONFIG_PATH(x) "src/mongo/util/options_parser/test_config_files/" x + + class OptionsParserTester : public moe::OptionsParser { + public: + Status readConfigFile(const std::string& filename, std::string* config) { + if (filename != _filename) { + ::mongo::StringBuilder sb; + sb << "Parser using filename: " << filename << + " which does not match expected filename: " << _filename; + return Status(ErrorCodes::InternalError, sb.str()); + } + *config = _config; + return Status::OK(); + } + void setConfig(const std::string& filename, const std::string& config) { + _filename = filename; + _config = config; + } + private: + std::string _filename; + std::string _config; + }; + + TEST(Registration, DuplicateSingleName) { + moe::OptionSection testOpts; + ASSERT_OK(testOpts.addOption(moe::OptionDescription("dup", "dup", moe::Switch, "dup"))); + ASSERT_NOT_OK(testOpts.addOption(moe::OptionDescription("new", "dup", moe::Switch, "dup"))); + } + + TEST(Registration, DuplicateDottedName) { + moe::OptionSection testOpts; + ASSERT_OK(testOpts.addOption(moe::OptionDescription("dup", "dup", moe::Switch, "dup"))); + ASSERT_NOT_OK(testOpts.addOption(moe::OptionDescription("dup", "new", moe::Switch, "dup"))); + } + + TEST(Registration, DuplicatePositional) { + moe::OptionSection testOpts; + ASSERT_OK(testOpts.addPositionalOption(moe::PositionalOptionDescription("positional", + moe::Int))); + ASSERT_NOT_OK(testOpts.addPositionalOption(moe::PositionalOptionDescription("positional", + moe::Int))); + } + + TEST(Parsing, Good) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display help")); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--port"); + argv.push_back("5"); + argv.push_back("--help"); + std::map<std::string, std::string> env_map; + + moe::Value value; + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + ASSERT_OK(environment.get(moe::Key("help"), &value)); + ASSERT_OK(environment.get(moe::Key("port"), &value)); + int port; + ASSERT_OK(value.get(&port)); + ASSERT_EQUALS(port, 5); + } + + TEST(Parsing, SubSection) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + moe::OptionSection subSection("Section Name"); + + subSection.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + testOpts.addSection(subSection); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--port"); + argv.push_back("5"); + std::map<std::string, std::string> env_map; + + moe::Value value; + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + ASSERT_OK(environment.get(moe::Key("port"), &value)); + int port; + ASSERT_OK(value.get(&port)); + ASSERT_EQUALS(port, 5); + } + + TEST(Parsing, StringVector) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("multival", "multival", moe::StringVector, + "Multiple Values")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--multival"); + argv.push_back("val1"); + argv.push_back("--multival"); + argv.push_back("val2"); + std::map<std::string, std::string> env_map; + + moe::Value value; + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + ASSERT_OK(environment.get(moe::Key("multival"), &value)); + std::vector<std::string> multival; + std::vector<std::string>::iterator multivalit; + ASSERT_OK(value.get(&multival)); + multivalit = multival.begin(); + ASSERT_EQUALS(*multivalit, "val1"); + multivalit++; + ASSERT_EQUALS(*multivalit, "val2"); + } + + TEST(Parsing, Positional) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addPositionalOption(moe::PositionalOptionDescription("positional", moe::String)); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("positional"); + std::map<std::string, std::string> env_map; + + moe::Value value; + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + ASSERT_OK(environment.get(moe::Key("positional"), &value)); + std::string positional; + ASSERT_OK(value.get(&positional)); + ASSERT_EQUALS(positional, "positional"); + } + + TEST(Parsing, PositionalTooMany) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addPositionalOption(moe::PositionalOptionDescription("positional", moe::String)); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("positional"); + argv.push_back("extrapositional"); + std::map<std::string, std::string> env_map; + + moe::Value value; + ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(Parsing, PositionalAndFlag) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addPositionalOption(moe::PositionalOptionDescription("positional", moe::String)); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("positional"); + argv.push_back("--port"); + argv.push_back("5"); + std::map<std::string, std::string> env_map; + + moe::Value value; + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + ASSERT_OK(environment.get(moe::Key("positional"), &value)); + std::string positional; + ASSERT_OK(value.get(&positional)); + ASSERT_EQUALS(positional, "positional"); + ASSERT_OK(environment.get(moe::Key("port"), &value)); + int port; + ASSERT_OK(value.get(&port)); + ASSERT_EQUALS(port, 5); + } + + TEST(Parsing, PositionalMultiple) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addPositionalOption(moe::PositionalOptionDescription("positional", + moe::StringVector, 2)); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("positional1"); + argv.push_back("positional2"); + std::map<std::string, std::string> env_map; + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + moe::Value value; + ASSERT_OK(environment.get(moe::Key("positional"), &value)); + std::vector<std::string> positional; + ASSERT_OK(value.get(&positional)); + std::vector<std::string>::iterator positionalit = positional.begin(); + ASSERT_EQUALS(*positionalit, "positional1"); + positionalit++; + ASSERT_EQUALS(*positionalit, "positional2"); + } + + TEST(Parsing, PositionalMultipleExtra) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addPositionalOption(moe::PositionalOptionDescription("positional", + moe::StringVector, 2)); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("positional1"); + argv.push_back("positional2"); + argv.push_back("positional2"); + std::map<std::string, std::string> env_map; + + ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(Parsing, PositionalMultipleUnlimited) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addPositionalOption(moe::PositionalOptionDescription("positional", + moe::StringVector, -1)); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("positional1"); + argv.push_back("positional2"); + argv.push_back("positional3"); + argv.push_back("positional4"); + argv.push_back("positional5"); + std::map<std::string, std::string> env_map; + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + moe::Value value; + ASSERT_OK(environment.get(moe::Key("positional"), &value)); + std::vector<std::string> positional; + ASSERT_OK(value.get(&positional)); + std::vector<std::string>::iterator positionalit = positional.begin(); + ASSERT_EQUALS(*positionalit, "positional1"); + positionalit++; + ASSERT_EQUALS(*positionalit, "positional2"); + positionalit++; + ASSERT_EQUALS(*positionalit, "positional3"); + positionalit++; + ASSERT_EQUALS(*positionalit, "positional4"); + positionalit++; + ASSERT_EQUALS(*positionalit, "positional5"); + } + + TEST(Parsing, PositionalMultipleAndFlag) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addPositionalOption(moe::PositionalOptionDescription("positional", + moe::StringVector, 2)); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("positional1"); + argv.push_back("--port"); + argv.push_back("5"); + argv.push_back("positional2"); + std::map<std::string, std::string> env_map; + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + moe::Value value; + ASSERT_OK(environment.get(moe::Key("positional"), &value)); + std::vector<std::string> positional; + ASSERT_OK(value.get(&positional)); + std::vector<std::string>::iterator positionalit = positional.begin(); + ASSERT_EQUALS(*positionalit, "positional1"); + positionalit++; + ASSERT_EQUALS(*positionalit, "positional2"); + ASSERT_OK(environment.get(moe::Key("port"), &value)); + int port; + ASSERT_OK(value.get(&port)); + ASSERT_EQUALS(port, 5); + } + + TEST(Parsing, NeedArg) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display help")); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--port"); + std::map<std::string, std::string> env_map; + + ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(Parsing, BadArg) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display help")); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--port"); + argv.push_back("string"); + std::map<std::string, std::string> env_map; + + ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(Parsing, ExtraArg) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display help")); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--help"); + argv.push_back("string"); + std::map<std::string, std::string> env_map; + + ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(Style, NoSticky) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("opt", "opt,o", moe::Switch, "first opt")); + testOpts.addOption(moe::OptionDescription("arg", "arg,a", moe::Switch, "first arg")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("-oa"); + std::map<std::string, std::string> env_map; + + ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(Style, NoGuessing) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display help")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--hel"); + std::map<std::string, std::string> env_map; + + ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(Style, LongDisguises) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display help")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("-help"); + std::map<std::string, std::string> env_map; + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + moe::Value value; + ASSERT_OK(environment.get(moe::Key("help"), &value)); + bool help; + ASSERT_OK(value.get(&help)); + ASSERT_EQUALS(help, true); + } + + TEST(Style, Verbosity) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + ASSERT_OK(testOpts.addOption(moe::OptionDescription("v", "verbose,v", moe::Switch, + "be more verbose (include multiple times for more verbosity e.g. -vvvvv)"))); + + /* support for -vv -vvvv etc. */ + for (std::string s = "vv"; s.length() <= 12; s.append("v")) { + ASSERT_OK(testOpts.addOption(moe::OptionDescription(s.c_str(), s.c_str(), moe::Switch, + "higher verbosity levels (hidden)", false))); + } + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("-vvvvvv"); + std::map<std::string, std::string> env_map; + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + + moe::Value value; + for (std::string s = "vv"; s.length() <= 12; s.append("v")) { + if (s.length() == 6) { + ASSERT_OK(environment.get(moe::Key(s), &value)); + bool verbose; + ASSERT_OK(value.get(&verbose)); + ASSERT_EQUALS(verbose, true); + } + else { + // TODO: Figure out if these are the semantics we want for switches. Right now they + // will always set a boolean value in the environment even if they aren't present. + ASSERT_OK(environment.get(moe::Key(s), &value)); + bool verbose; + ASSERT_OK(value.get(&verbose)); + ASSERT_EQUALS(verbose, false); + } + } + } + + TEST(INIConfigFile, Basic) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("default.conf"); + std::map<std::string, std::string> env_map; + + parser.setConfig("default.conf", "port=5"); + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + moe::Value value; + ASSERT_OK(environment.get(moe::Key("port"), &value)); + int port; + ASSERT_OK(value.get(&port)); + ASSERT_EQUALS(port, 5); + } + + TEST(INIConfigFile, Empty) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("default.conf"); + std::map<std::string, std::string> env_map; + + parser.setConfig("default.conf", ""); + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(INIConfigFile, Override) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("default.conf"); + argv.push_back("--port"); + argv.push_back("6"); + std::map<std::string, std::string> env_map; + + parser.setConfig("default.conf", "port=5"); + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + moe::Value value; + ASSERT_OK(environment.get(moe::Key("port"), &value)); + int port; + ASSERT_OK(value.get(&port)); + ASSERT_EQUALS(port, 6); + } + + TEST(INIConfigFile, Comments) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + testOpts.addOption(moe::OptionDescription("str", "str", moe::String, "String")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("default.conf"); + std::map<std::string, std::string> env_map; + + parser.setConfig("default.conf", "# port=5\nstr=NotCommented"); + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + moe::Value value; + ASSERT_NOT_OK(environment.get(moe::Key("port"), &value)); + ASSERT_OK(environment.get(moe::Key("str"), &value)); + std::string str; + ASSERT_OK(value.get(&str)); + ASSERT_EQUALS(str, "NotCommented"); + } + + TEST(JSONConfigFile, Basic) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("config.json"); + std::map<std::string, std::string> env_map; + + parser.setConfig("config.json", "{ port : 5 }"); + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + moe::Value value; + ASSERT_OK(environment.get(moe::Key("port"), &value)); + int port; + ASSERT_OK(value.get(&port)); + ASSERT_EQUALS(port, 5); + } + + TEST(JSONConfigFile, Empty) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("config.json"); + std::map<std::string, std::string> env_map; + + parser.setConfig("config.json", ""); + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(JSONConfigFile, EmptyObject) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("config.json"); + std::map<std::string, std::string> env_map; + + parser.setConfig("config.json", "{}"); + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(JSONConfigFile, Override) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("config.json"); + argv.push_back("--port"); + argv.push_back("6"); + std::map<std::string, std::string> env_map; + + + parser.setConfig("config.json", "{ port : 5 }"); + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + moe::Value value; + ASSERT_OK(environment.get(moe::Key("port"), &value)); + int port; + ASSERT_OK(value.get(&port)); + ASSERT_EQUALS(port, 6); + } + + TEST(JSONConfigFile, UnregisteredOption) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("config.json"); + std::map<std::string, std::string> env_map; + + parser.setConfig("config.json", "{ port : 5 }"); + + ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(JSONConfigFile, DuplicateOption) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("config.json"); + std::map<std::string, std::string> env_map; + + parser.setConfig("config.json", "{ port : 5, port : 5 }"); + + ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(JSONConfigFile, BadType) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("config.json"); + std::map<std::string, std::string> env_map; + + parser.setConfig("config.json", "{ port : \"string\" }"); + + ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(JSONConfigFile, Nested) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + testOpts.addOption(moe::OptionDescription("nested.port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("config.json"); + std::map<std::string, std::string> env_map; + + parser.setConfig("config.json", "{ nested : { port : 5 } }"); + + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + moe::Value value; + ASSERT_OK(environment.get(moe::Key("nested.port"), &value)); + int port; + ASSERT_OK(value.get(&port)); + ASSERT_EQUALS(port, 5); + } + + TEST(JSONConfigFile, StringVector) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + testOpts.addOption(moe::OptionDescription("multival", "multival", moe::StringVector, + "Multiple Values")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("config.json"); + std::map<std::string, std::string> env_map; + + parser.setConfig("config.json", "{ multival : [ \"val1\", \"val2\" ] }"); + + moe::Value value; + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + ASSERT_OK(environment.get(moe::Key("multival"), &value)); + std::vector<std::string> multival; + std::vector<std::string>::iterator multivalit; + ASSERT_OK(value.get(&multival)); + multivalit = multival.begin(); + ASSERT_EQUALS(*multivalit, "val1"); + multivalit++; + ASSERT_EQUALS(*multivalit, "val2"); + } + + TEST(JSONConfigFile, StringVectorNonString) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + testOpts.addOption(moe::OptionDescription("multival", "multival", moe::StringVector, + "Multiple Values")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("config.json"); + std::map<std::string, std::string> env_map; + + parser.setConfig("config.json", "{ multival : [ 1 ] }"); + + ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(JSONConfigFile, Over16Megabytes) { + // Test to make sure that we fail gracefully when we try to parse a JSON config file that + // results in a BSON object larger than the current limit of 16MB + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("config.json"); + std::map<std::string, std::string> env_map; + + // 1024 characters = 64 * 16 + const std::string largeString = + "\"" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "\""; + + std::string largeConfigString; + + largeConfigString.append("{ \"largeArray\" : [ "); + + // 16mb = 16 * 1024kb = 16384 + for (int i = 0; i < 16383; i++) { + largeConfigString.append(largeString); + largeConfigString.append(","); + } + largeConfigString.append(largeString); + largeConfigString.append(" ] }"); + + parser.setConfig("config.json", largeConfigString); + + moe::Value value; + ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(Parsing, BadConfigFileOption) { + OptionsParserTester parser; + moe::Environment environment; + + moe::OptionSection testOpts; + + // TODO: Should the error be in here? + testOpts.addOption(moe::OptionDescription("config", "config", + moe::Int, "Config file to parse")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back("1"); + std::map<std::string, std::string> env_map; + + parser.setConfig("default.conf", ""); + + ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + + TEST(ConfigFromFilesystem, JSONGood) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back(TEST_CONFIG_PATH("good.json")); + std::map<std::string, std::string> env_map; + + moe::Value value; + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + ASSERT_OK(environment.get(moe::Key("port"), &value)); + int port; + ASSERT_OK(value.get(&port)); + ASSERT_EQUALS(port, 5); + } + + TEST(ConfigFromFilesystem, INIGood) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back(TEST_CONFIG_PATH("good.conf")); + std::map<std::string, std::string> env_map; + + moe::Value value; + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + ASSERT_OK(environment.get(moe::Key("port"), &value)); + int port; + ASSERT_OK(value.get(&port)); + ASSERT_EQUALS(port, 5); + } + + TEST(ConfigFromFilesystem, Empty) { + moe::OptionsParser parser; + moe::Environment environment; + + moe::OptionSection testOpts; + testOpts.addOption(moe::OptionDescription("config", "config", + moe::String, "Config file to parse")); + + std::vector<std::string> argv; + argv.push_back("binaryname"); + argv.push_back("--config"); + argv.push_back(TEST_CONFIG_PATH("empty.json")); + std::map<std::string, std::string> env_map; + + moe::Value value; + ASSERT_OK(parser.run(testOpts, argv, env_map, &environment)); + } + +} // unnamed namespace diff --git a/src/mongo/util/options_parser/test_config_files/empty.json b/src/mongo/util/options_parser/test_config_files/empty.json new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/src/mongo/util/options_parser/test_config_files/empty.json diff --git a/src/mongo/util/options_parser/test_config_files/good.conf b/src/mongo/util/options_parser/test_config_files/good.conf new file mode 100644 index 00000000000..75bcd785281 --- /dev/null +++ b/src/mongo/util/options_parser/test_config_files/good.conf @@ -0,0 +1 @@ +port=5 diff --git a/src/mongo/util/options_parser/test_config_files/good.json b/src/mongo/util/options_parser/test_config_files/good.json new file mode 100644 index 00000000000..83ba69eef58 --- /dev/null +++ b/src/mongo/util/options_parser/test_config_files/good.json @@ -0,0 +1,3 @@ +{ + "port" : 5 +} diff --git a/src/mongo/util/options_parser/value.cpp b/src/mongo/util/options_parser/value.cpp new file mode 100644 index 00000000000..89ad7388a41 --- /dev/null +++ b/src/mongo/util/options_parser/value.cpp @@ -0,0 +1,158 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mongo/util/options_parser/value.h" + +#include "mongo/bson/util/builder.h" + +namespace mongo { +namespace optionenvironment { + + // Value implementation + + // Value access functions + + Status Value::get(std::vector<std::string>* val) const { + if (type != StringVector) { + return Status(ErrorCodes::TypeMismatch, "Value not of type: stringVector"); + } + *val = stringVectorVal; + return Status::OK(); + } + Status Value::get(bool* val) const { + if (type != Bool) { + return Status(ErrorCodes::TypeMismatch, "Value not of type: bool"); + } + *val = boolVal; + return Status::OK(); + } + Status Value::get(double* val) const { + if (type != Double) { + return Status(ErrorCodes::TypeMismatch, "Value not of type: double"); + } + *val = doubleVal; + return Status::OK(); + } + Status Value::get(int* val) const { + if (type != Int) { + return Status(ErrorCodes::TypeMismatch, "Value not of type: int"); + } + *val = intVal; + return Status::OK(); + } + Status Value::get(long* val) const { + if (type == Long) { + *val = longVal; + return Status::OK(); + } + else if (type == Int) { + *val = intVal; + return Status::OK(); + } + return Status(ErrorCodes::TypeMismatch, "Value not convertible to type: long"); + } + Status Value::get(std::string* val) const { + if (type != String) { + return Status(ErrorCodes::TypeMismatch, "Value not of type: string"); + } + *val = stringVal; + return Status::OK(); + } + Status Value::get(unsigned long long* val) const { + if (type == UnsignedLongLong) { + *val = unsignedLongLongVal; + return Status::OK(); + } + else if (type == Unsigned) { + *val = unsignedVal; + return Status::OK(); + } + return Status(ErrorCodes::TypeMismatch, "Value not convertible to type: unsignedlonglong"); + } + Status Value::get(unsigned* val) const { + if (type != Unsigned) { + return Status(ErrorCodes::TypeMismatch, "Value not of type: unsigned"); + } + *val = unsignedVal; + return Status::OK(); + } + + // Value utility functions + + std::string Value::typeToString() const { + switch (type) { + case StringVector: return "StringVector"; + case Bool: return "Bool"; + case Double: return "Double"; + case Int: return "Int"; + case Long: return "Long"; + case String: return "String"; + case UnsignedLongLong: return "UnsignedLongLong"; + case Unsigned: return "Unsigned"; + case None: return "None"; + default: return "Unknown"; + } + } + bool Value::isEmpty() const { + return type == None; + } + bool Value::equal(Value& otherVal) const { + if (type != otherVal.type) { + return false; + } + switch (type) { + case StringVector: return stringVectorVal == otherVal.stringVectorVal; + case Bool: return boolVal == otherVal.boolVal; + case Double: return doubleVal == otherVal.doubleVal; + case Int: return intVal == otherVal.intVal; + case Long: return longVal == otherVal.longVal; + case String: return stringVal == otherVal.stringVal; + case UnsignedLongLong: return unsignedLongLongVal == otherVal.unsignedLongLongVal; + case Unsigned: return unsignedVal == otherVal.unsignedVal; + case None: return true; + default: return false; /* Undefined */ + } + } + std::string Value::toString() const { + StringBuilder sb; + switch (type) { + case StringVector: + if (!stringVectorVal.empty()) + { + // Convert all but the last element to avoid a trailing "," + for(std::vector<std::string>::const_iterator iterator = stringVectorVal.begin(); + iterator != stringVectorVal.end() - 1; iterator++) { + sb << *iterator; + } + + // Now add the last element with no delimiter + sb << stringVectorVal.back(); + } + break; + case Bool: sb << boolVal; break; + case Double: sb << doubleVal; break; + case Int: sb << intVal; break; + case Long: sb << longVal; break; + case String: sb << stringVal; break; + case UnsignedLongLong: sb << unsignedLongLongVal; break; + case Unsigned: sb << unsignedVal; break; + case None: sb << "(not set)"; break; + default: sb << "(undefined)"; break; + } + return sb.str(); + } + +} // namespace optionenvironment +} // namespace mongo diff --git a/src/mongo/util/options_parser/value.h b/src/mongo/util/options_parser/value.h new file mode 100644 index 00000000000..5b0ab751e9a --- /dev/null +++ b/src/mongo/util/options_parser/value.h @@ -0,0 +1,125 @@ +/* Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <vector> + +#include "mongo/base/status.h" + +namespace mongo { +namespace optionenvironment { + + class Constraint; + class KeyConstraint; + + typedef std::string Key; + + /** A simple container interface for storing various C++ values. + * + * Usage: + * + * Value intVal(2); + * Value stringVal("string"); + * + * int intContents = 1; + * Status ret = stringVal.get(&intContents); + * // ret != Status::OK() + * // intContents is still 1 + * + * ret = intVal.get(&intContents); + * // ret == Status::OK() + * // intContents is now 2 + */ + class Value { + public: + + // Constructors + + explicit Value() : type(None) { } + explicit Value(std::vector<std::string> val) : stringVectorVal(val), type(StringVector) { } + explicit Value(bool val) : boolVal(val), type(Bool) { } + explicit Value(double val) : doubleVal(val), type(Double) { } + explicit Value(int val) : intVal(val), type(Int) { } + explicit Value(long val) : longVal(val), type(Long) { } + explicit Value(std::string val) : stringVal(val), type(String) { } + explicit Value(unsigned long long val) : unsignedLongLongVal(val), type(UnsignedLongLong) {} + explicit Value(unsigned val) : unsignedVal(val), type(Unsigned) { } + + // Access interface + + Status get(std::vector<std::string>* val) const; + Status get(bool* val) const; + Status get(double* val) const; + Status get(int* val) const; + Status get(long* val) const; + Status get(string* val) const; + Status get(unsigned long long* val) const; + Status get(unsigned* val) const; + + // Utility functions + + /** + * Return the value's type as a string + */ + std::string typeToString() const; + + /** + * Return true if the value was created with the no argument constructor + */ + bool isEmpty() const; + + /** + * Return true if the other Value equals this value, both in type and in contents + * + * Two empty values are equal + */ + bool equal(Value&) const; + + /** + * Return the string representation of this Value + */ + std::string toString() const; + + private: + std::vector<std::string> stringVectorVal; + std::string stringVal; + union { + bool boolVal; + double doubleVal; + int intVal; + long longVal; + unsigned long long unsignedLongLongVal; + unsigned unsignedVal; + }; + + // Types currently supported by Value + enum Type { + StringVector, // std::vector<std::string> + Bool, // bool + Double, // double + Int, // int + Long, // long + String, // std::string + UnsignedLongLong, // unsigned long long + Unsigned, // unsigned + None, // (not set) + }; + + Type type; + }; + +} // namespace optionenvironment +} // namespace mongo |