Add return of default values

This commit is contained in:
Mike Bierlee 2022-09-29 00:38:54 +03:00
parent 3ae90783de
commit 3c7918c2e2

View file

@ -30,6 +30,15 @@ class ConfigReadException : Exception {
} }
} }
/**
* Used by ConfigDictionary when the supplied path does not exist.
*/
class ConfigPathNotFoundException : Exception {
this(string msg, string file = __FILE__, size_t line = __LINE__) {
super(msg, file, line);
}
}
/** /**
* Used by ConfigFactory instances when loading or parsing configuration fails. * Used by ConfigFactory instances when loading or parsing configuration fails.
*/ */
@ -246,19 +255,31 @@ class ConfigDictionary {
* Although the path should be universally the same over all types of config files, some might not lend to this structure, * Although the path should be universally the same over all types of config files, some might not lend to this structure,
* and have a more specific way of retrieving data from the config. See the examples and specific config factories for * and have a more specific way of retrieving data from the config. See the examples and specific config factories for
* more details. * more details.
* defaultValue = (Optional) Value to return when the given configPath is invalid. When not supplied a ConfigPathNotFoundException exception is thrown.
*
* Throws: ConfigReadException when something goes wrong reading the config.
* ConfigPathNotFoundException when the given path does not exist in the config.
* *
* Returns: The value at the path in the configuration. To convert it use get!T(). * Returns: The value at the path in the configuration. To convert it use get!T().
*/ */
string get(string configPath) { string get(string configPath, string defaultValue = null) {
auto path = new ConfigPath(configPath); try {
auto node = getNodeAt(path); auto path = new ConfigPath(configPath);
auto value = cast(ValueNode) node; auto node = getNodeAt(path);
if (value) { auto value = cast(ValueNode) node;
return substituteEnvironmentVariables ? substituteEnvVars(value) : value.value; if (value) {
} else { return substituteEnvironmentVariables ? substituteEnvVars(value) : value.value;
throw new ConfigReadException( } else {
"Value expected but " ~ node.nodeType ~ " found at path: " ~ createExceptionPath( throw new ConfigReadException(
path)); "Value expected but " ~ node.nodeType ~ " found at path: " ~ createExceptionPath(
path));
}
} catch (ConfigPathNotFoundException e) {
if (defaultValue !is null) {
return defaultValue;
}
throw e;
} }
} }
@ -267,12 +288,38 @@ class ConfigDictionary {
* *
* Params: * Params:
* configPath = Path to the wanted config value. See get(). * configPath = Path to the wanted config value. See get().
*
* Throws: ConfigReadException when something goes wrong reading the config.
* ConfigPathNotFoundException when the given path does not exist in the config.
*
* Returns: The value at the path in the configuration. * Returns: The value at the path in the configuration.
* See_Also: get
*/ */
ConvertToType get(ConvertToType)(string configPath) { ConvertToType get(ConvertToType)(string configPath) {
return get(configPath).to!ConvertToType; return get(configPath).to!ConvertToType;
} }
/**
* Get values from the configuration and attempts to convert them to the specified type.
*
* Params:
* configPath = Path to the wanted config value. See get().
* defaultValue = (Optional) Value to return when the given configPath is invalid. When not supplied a ConfigPathNotFoundException exception is thrown.
*
* Throws: ConfigReadException when something goes wrong reading the config.
* ConfigPathNotFoundException when the given path does not exist in the config.
*
* Returns: The value at the path in the configuration.
* See_Also: get
*/
ConvertToType get(ConvertToType)(string configPath, ConvertToType defaultValue) {
try {
return get(configPath).to!ConvertToType;
} catch (ConfigPathNotFoundException e) {
return defaultValue;
}
}
/** /**
* Fetch a sub-section of the config as another config. * Fetch a sub-section of the config as another config.
* *
@ -294,20 +341,23 @@ class ConfigDictionary {
} }
private ConfigNode getNodeAt(ConfigPath path) { private ConfigNode getNodeAt(ConfigPath path) {
enforce!ConfigReadException(rootNode !is null, "The config is empty"); void throwPathNotFound() {
throw new ConfigPathNotFoundException(
"Path does not exist: " ~ createExceptionPath(path));
}
if (rootNode is null) {
throwPathNotFound();
}
auto currentNode = rootNode; auto currentNode = rootNode;
PathSegment currentPathSegment = path.getNextSegment(); PathSegment currentPathSegment = path.getNextSegment();
void throwPathNotExists() {
throw new ConfigReadException("Path does not exist: " ~ createExceptionPath(path));
}
void ifNotNullPointer(void* obj, void delegate() fn) { void ifNotNullPointer(void* obj, void delegate() fn) {
if (obj) { if (obj) {
fn(); fn();
} else { } else {
throwPathNotExists(); throwPathNotFound();
} }
} }
@ -315,18 +365,18 @@ class ConfigDictionary {
if (obj) { if (obj) {
fn(); fn();
} else { } else {
throwPathNotExists(); throwPathNotFound();
} }
} }
while (currentPathSegment !is null) { while (currentPathSegment !is null) {
if (currentNode is null) { if (currentNode is null) {
throwPathNotExists(); throwPathNotFound();
} }
auto valueNode = cast(ValueNode) currentNode; auto valueNode = cast(ValueNode) currentNode;
if (valueNode) { if (valueNode) {
throwPathNotExists(); throwPathNotFound();
} }
auto arrayPath = cast(ArrayPathSegment) currentPathSegment; auto arrayPath = cast(ArrayPathSegment) currentPathSegment;
@ -485,7 +535,7 @@ version (unittest) {
unittest { unittest {
auto config = new ConfigDictionary(); auto config = new ConfigDictionary();
assertThrown!ConfigReadException(config.get(".")); assertThrown!ConfigPathNotFoundException(config.get("."));
} }
@("Get value in root with empty path") @("Get value in root with empty path")
@ -586,7 +636,7 @@ version (unittest) {
]) ])
); );
assertThrown!ConfigReadException(config.get("hostname.cluster.spacey")); assertThrown!ConfigPathNotFoundException(config.get("hostname.cluster.spacey"));
} }
@("Exception is thrown when given path terminates too early") @("Exception is thrown when given path terminates too early")
@ -604,7 +654,7 @@ version (unittest) {
unittest { unittest {
auto config = new ConfigDictionary(new ArrayNode()); auto config = new ConfigDictionary(new ArrayNode());
assertThrown!ConfigReadException(config.get("hostname")); assertThrown!ConfigPathNotFoundException(config.get("hostname"));
} }
@("Get value from objects in array") @("Get value from objects in array")
@ -789,8 +839,19 @@ version (unittest) {
assert(config.get("tooMuchWhiteSpace") == "$MIRAGE_CONFIG_TEST_ENV_VAR "); assert(config.get("tooMuchWhiteSpace") == "$MIRAGE_CONFIG_TEST_ENV_VAR ");
assert(config.get("notSet") == "${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR}"); assert(config.get("notSet") == "${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR}");
assert(config.get("withDefault") == "$MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:use default!"); assert(config.get("withDefault") == "$MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:use default!");
assert(config.get("withDefaultAndBrackets") == "${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:use default!}"); assert(config.get(
"withDefaultAndBrackets") == "${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:use default!}");
assert(config.get("megaMix") == "${MIRAGE_CONFIG_TEST_ENV_VAR_TWO} ${MIRAGE_CONFIG_TEST_ENV_VAR} ${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:go}!"); assert(config.get("megaMix") == "${MIRAGE_CONFIG_TEST_ENV_VAR_TWO} ${MIRAGE_CONFIG_TEST_ENV_VAR} ${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:go}!");
assert(config.get("typical") == "${MIRAGE_CONFIG_TEST_HOSTNAME:localhost}:${MIRAGE_CONFIG_TEST_PORT:8080}"); assert(config.get(
"typical") == "${MIRAGE_CONFIG_TEST_HOSTNAME:localhost}:${MIRAGE_CONFIG_TEST_PORT:8080}");
} }
@("Get with default should return default")
unittest {
auto config = new ConfigDictionary();
assert(config.get("la.la.la", "not there") == "not there");
assert(config.get!int("do.re.mi.fa.so", 42) == 42);
}
//TODO: Test null nodes should gracefully fail
} }