/*
* ConfigProfile.cpp
*
* Copyright (C) 2018 by RStudio, PBC
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
#include
#include
#include
#include
#include
namespace rstudio {
namespace core {
using namespace boost::property_tree;
Error ConfigProfile::validateParam(const std::string& paramName,
const std::string& paramValue) const
{
ValidatorMap::const_iterator iter = validators_.find(paramName);
if (iter == validators_.end())
return Success();
const ValidatorFunc& validator = iter->second;
std::string errorMessage;
if (!validator(paramValue, &errorMessage))
{
return systemError(boost::system::errc::protocol_error,
"Param " + paramName + " is not valid: " + errorMessage,
ERROR_LOCATION);
}
return Success();
}
Error ConfigProfile::load(const FilePath& filePath)
{
std::string contents;
Error error = readStringFromFile(filePath, &contents);
if (error)
return error;
error = parseString(contents);
if (error)
{
std::string description = error.getProperty("description");
description += " in file " + filePath.getAbsolutePath();
error.addOrUpdateProperty("description", description);
}
return error;
}
Error ConfigProfile::parseString(const std::string& profileStr)
{
std::istringstream stream{profileStr};
// parse the profile (ini syntax)
ptree profileTree;
try
{
ini_parser::read_ini(stream, profileTree);
}
catch (const ini_parser_error& error)
{
return systemError(boost::system::errc::protocol_error,
"Could not parse config profile: " + error.message(),
ERROR_LOCATION);
}
// build section overrides
std::vector levels;
for (const ptree::value_type& child : profileTree)
{
boost::optional matchingLevel;
for (const Level& level : sections_)
{
// if the section level starts with the defined section name
// from a prior call to addSections, we have a matching section level
if (boost::algorithm::starts_with(child.first, level.second))
{
matchingLevel = level;
break;
}
}
if (!matchingLevel)
{
// no matching section level - it was not specified and is thus
// an erroneous section
return systemError(boost::system::errc::protocol_error,
"Invalid config section " + child.first,
ERROR_LOCATION);
}
std::map values;
for (const ptree::value_type val : child.second)
{
// check to see if the parameter within the section has been registered
const std::string& paramName = val.first;
DefaultParamValuesMap::const_iterator defaultIter = defaultValues_.find(paramName);
if (defaultIter == defaultValues_.end())
{
// we require every param to have a default value / be registered so error out here
return systemError(boost::system::errc::protocol_error,
"Unknown param " + paramName + " specified",
ERROR_LOCATION);
}
std::string paramValue = val.second.get_value();
Error error = validateParam(paramName, paramValue);
if (error)
return error;
values[paramName] = paramValue;
}
std::string levelValue = child.first.substr(matchingLevel.get().second.size());
levels.push_back({{matchingLevel.get().first, levelValue}, values});
}
// stable sort the levels in ascending level number
// this will preserve the order of same-level section overrides
std::stable_sort(
levels.begin(),
levels.end(),
[](const LevelValues& a, const LevelValues& b) { return a.first.first < b.first.first; });
// assign the temporary variable levels to the class member
// this assures that we can safely call this method multiple times, preserving the last
// good configuration in case we error out above due to invalid configuration
levels_ = levels;
return Success();
}
std::vector ConfigProfile::getLevelNames(uint32_t level) const
{
std::vector levelNames;
for (const LevelValues& value : levels_)
{
if (value.first.first == level)
levelNames.push_back(value.first.second);
}
return levelNames;
}
} // core
} // namespace rstudio