Add support for getting config from objects

This commit is contained in:
Mike Bierlee 2022-09-24 02:55:44 +03:00
parent e3673755de
commit a4b0e7982e

View file

@ -10,7 +10,7 @@
module poodinis.config.dictionary; module poodinis.config.dictionary;
import std.exception : enforce; import std.exception : enforce;
import std.string : split, startsWith, endsWith; import std.string : split, startsWith, endsWith, join;
import std.conv : to, ConvException; import std.conv : to, ConvException;
class ConfigReadException : Exception { class ConfigReadException : Exception {
@ -27,6 +27,7 @@ class PathParseException : Exception {
} }
interface ConfigNode { interface ConfigNode {
string nodeType();
} }
class ValueNode : ConfigNode { class ValueNode : ConfigNode {
@ -38,6 +39,10 @@ class ValueNode : ConfigNode {
this(string value) { this(string value) {
this.value = value; this.value = value;
} }
string nodeType() {
return "value";
}
} }
class ObjectNode : ConfigNode { class ObjectNode : ConfigNode {
@ -55,6 +60,10 @@ class ObjectNode : ConfigNode {
children[key] = new ValueNode(value); children[key] = new ValueNode(value);
} }
} }
string nodeType() {
return "object";
}
} }
class ArrayNode : ConfigNode { class ArrayNode : ConfigNode {
@ -72,9 +81,13 @@ class ArrayNode : ConfigNode {
children ~= new ValueNode(value); children ~= new ValueNode(value);
} }
} }
string nodeType() {
return "array";
}
} }
class PathSegment { interface PathSegment {
} }
class ArrayPathSegment : PathSegment { class ArrayPathSegment : PathSegment {
@ -95,6 +108,7 @@ class PropertyPathSegment : PathSegment {
class ConfigPath { class ConfigPath {
private const string path; private const string path;
private string[] previousSegments;
private string[] segments; private string[] segments;
this(const string path) { this(const string path) {
@ -108,7 +122,8 @@ class ConfigPath {
} }
PathSegment ret(PathSegment segment) { PathSegment ret(PathSegment segment) {
segments = segments.length > 1 ? segments[1 .. $] : []; previousSegments ~= segments[0];
segments = segments[1 .. $];
return segment; return segment;
} }
@ -130,6 +145,10 @@ class ConfigPath {
return ret(new PropertyPathSegment(segment)); return ret(new PropertyPathSegment(segment));
} }
string getCurrentPath() {
return previousSegments.join(".");
}
} }
class ConfigDictionary { class ConfigDictionary {
@ -151,13 +170,29 @@ class ConfigDictionary {
auto path = new ConfigPath(configPath); auto path = new ConfigPath(configPath);
auto currentNode = rootNode; auto currentNode = rootNode;
PathSegment currentPathSegment = path.getNextSegment(); PathSegment currentPathSegment = path.getNextSegment();
string createExceptionPath() {
return "'" ~ configPath ~ "' (at '" ~ path.getCurrentPath() ~ "')";
}
while (currentPathSegment !is null) { while (currentPathSegment !is null) {
if (currentNode is null) {
throw new ConfigReadException(
"Path does not exist: " ~ createExceptionPath());
}
auto valueNode = cast(ValueNode) currentNode;
if (valueNode) {
throw new ConfigReadException(
"Path does not exist: " ~ createExceptionPath());
}
auto arrayPath = cast(ArrayPathSegment) currentPathSegment; auto arrayPath = cast(ArrayPathSegment) currentPathSegment;
if (arrayPath) { if (arrayPath) {
auto arrayNode = cast(ArrayNode) currentNode; auto arrayNode = cast(ArrayNode) currentNode;
if (arrayNode) { if (arrayNode) {
if (arrayNode.children.length < arrayPath.index) { if (arrayNode.children.length < arrayPath.index) {
throw new ConfigReadException("Array index out of bounds: " ~ configPath); throw new ConfigReadException(
"Array index out of bounds: " ~ createExceptionPath());
} }
currentNode = arrayNode.children[arrayPath.index]; currentNode = arrayNode.children[arrayPath.index];
@ -171,6 +206,9 @@ class ConfigDictionary {
auto propertyNode = propertyPath.propertyName in objectNode.children; auto propertyNode = propertyPath.propertyName in objectNode.children;
if (propertyNode) { if (propertyNode) {
currentNode = *propertyNode; currentNode = *propertyNode;
} else {
throw new ConfigReadException(
"Path does not exist: " ~ createExceptionPath());
} }
} }
} }
@ -183,7 +221,7 @@ class ConfigDictionary {
return value.value; return value.value;
} else { } else {
throw new ConfigReadException( throw new ConfigReadException(
"The configuration at the given path is not a value: " ~ configPath); "Value expected but " ~ currentNode.nodeType ~ " found at path: " ~ createExceptionPath());
} }
} }
} }
@ -256,4 +294,67 @@ version (unittest) {
assert(dictionary.get("noot") == "nut"); assert(dictionary.get("noot") == "nut");
assert(dictionary.get("mies") == "mies"); assert(dictionary.get("mies") == "mies");
} }
@("Get value from object in object")
unittest {
auto dictionary = new ConfigDictionary();
dictionary.rootNode = new ObjectNode([
"server": new ObjectNode([
"port": "8080"
])
]);
assert(dictionary.get("server.port") == "8080");
}
@("Get value from array in object")
unittest {
auto dictionary = new ConfigDictionary();
dictionary.rootNode = new ObjectNode([
"hostname": new ArrayNode(["google.com", "dlang.org"])
]);
assert(dictionary.get("hostname.[1]") == "dlang.org");
}
@("Exception is thrown when array out of bounds when fetching from root")
unittest {
auto dictionary = new ConfigDictionary();
dictionary.rootNode = new ArrayNode(["google.com", "dlang.org"]);
assertThrown!ConfigReadException(dictionary.get("[5]"));
}
@("Exception is thrown when array out of bounds when fetching from object")
unittest {
auto dictionary = new ConfigDictionary();
dictionary.rootNode = new ObjectNode([
"hostname": new ArrayNode(["google.com", "dlang.org"])
]);
assertThrown!ConfigReadException(dictionary.get("hostname.[5]"));
}
@("Exception is thrown when path does not exist")
unittest {
auto dictionary = new ConfigDictionary();
dictionary.rootNode = new ObjectNode(
[
"hostname": new ObjectNode(["cluster": new ValueNode("")])
]);
assertThrown!ConfigReadException(dictionary.get("hostname.cluster.spacey"));
}
@("Exception is thrown when given path terminates too early")
unittest {
auto dictionary = new ConfigDictionary();
dictionary.rootNode = new ObjectNode(
[
"hostname": new ObjectNode(["cluster": new ValueNode(null)])
]);
assertThrown!ConfigReadException(dictionary.get("hostname"));
}
} }