You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
144 lines
4.7 KiB
144 lines
4.7 KiB
4 months ago
|
// License: Apache 2.0. See LICENSE file in root directory.
|
||
|
// Copyright(c) 2023 Intel Corporation. All Rights Reserved.
|
||
|
|
||
|
#include <rsutils/json.h>
|
||
|
#include <rsutils/json-config.h>
|
||
|
#include <rsutils/os/executable-name.h>
|
||
|
|
||
|
#include <fstream>
|
||
|
|
||
|
|
||
|
namespace rsutils {
|
||
|
|
||
|
|
||
|
json const null_json = {};
|
||
|
json const missing_json = json::value_t::discarded;
|
||
|
json const empty_json_string = json::value_t::string;
|
||
|
json const empty_json_object = json::object();
|
||
|
|
||
|
|
||
|
// Recursively patches existing JSON with contents of 'overrides', which must be a JSON object.
|
||
|
// A 'null' value inside erases previous contents. Any other value overrides.
|
||
|
// See: https://json.nlohmann.me/api/basic_json/merge_patch/
|
||
|
// Example below, for load_app_settings.
|
||
|
// Use 'what' to denote what it is we're patching in, if a failure happens. The std::runtime_error will populate with
|
||
|
// it.
|
||
|
//
|
||
|
// NOTE: called 'override' to agree with terminology elsewhere, and to avoid collisions with the json patch(),
|
||
|
// merge_patch(), existing functions.
|
||
|
//
|
||
|
void json_base::override( json_ref patches, std::string const & what )
|
||
|
{
|
||
|
if( ! patches.is_object() )
|
||
|
{
|
||
|
std::string context = what.empty() ? std::string( "patch", 5 ) : what;
|
||
|
throw std::runtime_error( context + ": expecting an object; got " + patches.dump() );
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
static_cast< json * >( this )->merge_patch( patches );
|
||
|
}
|
||
|
catch( std::exception const & e )
|
||
|
{
|
||
|
std::string context = what.empty() ? std::string( "patch", 5 ) : what;
|
||
|
throw std::runtime_error( "failed to merge " + context + ": " + e.what() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Load the contents of a file into a JSON object.
|
||
|
//
|
||
|
// Throws if any errors are encountered with the file or its contents.
|
||
|
// Returns the contents. If the file wasn't there, returns missing_json.
|
||
|
//
|
||
|
json json_config::load_from_file( std::string const & filename )
|
||
|
{
|
||
|
std::ifstream f( filename );
|
||
|
if( f.good() )
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
return json::parse( f );
|
||
|
}
|
||
|
catch( std::exception const & e )
|
||
|
{
|
||
|
throw std::runtime_error( "failed to load configuration file (" + filename
|
||
|
+ "): " + std::string( e.what() ) );
|
||
|
}
|
||
|
}
|
||
|
return missing_json;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// Loads configuration settings from 'global' content (loaded by load_from_file()?).
|
||
|
// E.g., a configuration file may contain:
|
||
|
// {
|
||
|
// "context": {
|
||
|
// "dds": {
|
||
|
// "enabled": false,
|
||
|
// "domain" : 5
|
||
|
// }
|
||
|
// },
|
||
|
// ...
|
||
|
// }
|
||
|
// This function will load a specific key 'context' inside and return it. The result will be a disabling of dds:
|
||
|
// Besides this "global" key, application-specific settings can override the global settings, e.g.:
|
||
|
// {
|
||
|
// "context": {
|
||
|
// "dds": {
|
||
|
// "enabled": false,
|
||
|
// "domain" : 5
|
||
|
// }
|
||
|
// },
|
||
|
// "realsense-viewer": {
|
||
|
// "context": {
|
||
|
// "dds": { "enabled": null }
|
||
|
// }
|
||
|
// },
|
||
|
// ...
|
||
|
// }
|
||
|
// If the current application is 'realsense-viewer', then the global 'context' settings will be patched with the
|
||
|
// application-specific 'context' and returned:
|
||
|
// {
|
||
|
// "dds": {
|
||
|
// "domain" : 5
|
||
|
// }
|
||
|
// }
|
||
|
// See rules for patching in patch().
|
||
|
// The 'application' is usually any single-word executable name (without extension).
|
||
|
// The 'subkey' is mandatory.
|
||
|
// The 'error_context' is used for error reporting, to show what failed. Like application, it should be a single word
|
||
|
// that can be used to denote hierarchy within the global json.
|
||
|
//
|
||
|
json json_config::load_app_settings( json const & global,
|
||
|
std::string const & application,
|
||
|
json_key const & subkey,
|
||
|
std::string const & error_context )
|
||
|
{
|
||
|
// Take the global subkey settings out of the configuration
|
||
|
json settings;
|
||
|
if( auto global_subkey = global.nested( subkey ) )
|
||
|
settings.override( global_subkey, "global " + error_context + '/' + subkey );
|
||
|
|
||
|
// Patch any application-specific subkey settings
|
||
|
if( auto application_subkey = global.nested( application, subkey ) )
|
||
|
settings.override( application_subkey, error_context + '/' + application + '/' + subkey );
|
||
|
|
||
|
return settings;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Same as above, but automatically takes the application name from the executable-name
|
||
|
//
|
||
|
json json_config::load_settings( json const & global,
|
||
|
json_key const & subkey,
|
||
|
std::string const & error_context )
|
||
|
{
|
||
|
return load_app_settings( global, rsutils::os::executable_name(), subkey, error_context );
|
||
|
}
|
||
|
|
||
|
|
||
|
} // namespace rsutils
|