From fcbc3729235ea1d788173593700706f619ad010e Mon Sep 17 00:00:00 2001 From: Mike Bierlee Date: Sat, 24 Sep 2022 18:05:33 +0300 Subject: [PATCH] Add JsonConfigFactory --- source/mirage/config.d | 27 +++++++--- source/mirage/json.d | 111 ++++++++++++++++++++++++++++++++++++++++ source/mirage/package.d | 1 + 3 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 source/mirage/json.d diff --git a/source/mirage/config.d b/source/mirage/config.d index 94e3455..24ac843 100644 --- a/source/mirage/config.d +++ b/source/mirage/config.d @@ -19,6 +19,12 @@ class ConfigReadException : Exception { } } +class ConfigCreationException : Exception { + this(string msg, string file = __FILE__, size_t line = __LINE__) { + super(msg, file, line); + } +} + class PathParseException : Exception { this(string msg, string path, string file = __FILE__, size_t line = __LINE__) { string fullMsg = msg ~ " (Path: " ~ path ~ ")"; @@ -26,11 +32,11 @@ class PathParseException : Exception { } } -private interface ConfigNode { +interface ConfigNode { string nodeType(); } -private class ValueNode : ConfigNode { +class ValueNode : ConfigNode { string value; this() { @@ -45,7 +51,7 @@ private class ValueNode : ConfigNode { } } -private class ObjectNode : ConfigNode { +class ObjectNode : ConfigNode { ConfigNode[string] children; this() { @@ -66,7 +72,7 @@ private class ObjectNode : ConfigNode { } } -private class ArrayNode : ConfigNode { +class ArrayNode : ConfigNode { ConfigNode[] children; this() { @@ -158,7 +164,7 @@ private class ConfigPath { auto index = indexString.to!size_t; return ret(new ArrayPathSegment(index)); } catch (ConvException e) { - throw new PathParseException("Array index '" ~ indexString ~ "' is not acceptable as an array number", path); + throw new PathParseException("Value '" ~ indexString ~ "' is not acceptable as an array index", path); } } @@ -173,6 +179,13 @@ private class ConfigPath { class ConfigDictionary { ConfigNode rootNode; + this() { + } + + this(ConfigNode rootNode) { + this.rootNode = rootNode; + } + string get(string configPath) { enforce!ConfigReadException(rootNode !is null, "The config is empty"); // enforce!ConfigReadException(configPath.length > 0, "Supplied config path is empty"); @@ -252,9 +265,9 @@ class ConfigDictionary { } } -interface ConfigLoader { - ConfigDictionary parseConfig(string contents); +interface ConfigFactory { ConfigDictionary loadFile(string path); + ConfigDictionary parseConfig(string contents); } version (unittest) { diff --git a/source/mirage/json.d b/source/mirage/json.d new file mode 100644 index 0000000..5088c56 --- /dev/null +++ b/source/mirage/json.d @@ -0,0 +1,111 @@ +/** + * Authors: + * Mike Bierlee, m.bierlee@lostmoment.com + * Copyright: 2022 Mike Bierlee + * License: + * This software is licensed under the terms of the MIT license. + * The full terms of the license can be found in the LICENSE file. + */ + +module mirage.json; + +import std.json : JSONValue, JSONType; +import std.conv : to; + +import mirage.config : ConfigFactory, ConfigDictionary, ConfigNode, ValueNode, ObjectNode, ArrayNode, ConfigCreationException; + +class JsonConfigFactory : ConfigFactory { + ConfigDictionary loadFile(string path) { + throw new Exception("not yet implemented"); + } + + ConfigDictionary parseConfig(string contents) { + throw new Exception("not yet implemented"); + } + + ConfigDictionary parseJson(JSONValue json) { + return new ConfigDictionary(convertJValue(json)); + } + + ConfigDictionary parseJson(string json) { + return parseConfig(json); + } + + private ConfigNode convertJValue(JSONValue json) { + if (json.type() == JSONType.object) { + auto objectNode = new ObjectNode(); + auto objectJson = json.object(); + foreach (propertyName, jvalue; objectJson) { + objectNode.children[propertyName] = convertJValue(jvalue); + } + + return objectNode; + } + + if (json.type() == JSONType.array) { + auto arrayNode = new ArrayNode(); + auto arrayJson = json.array(); + foreach (jvalue; arrayJson) { + arrayNode.children ~= convertJValue(jvalue); + } + + return arrayNode; + } + + if (json.type() == JSONType.null_) { + return new ValueNode(null); + } + + if (json.type() == JSONType.string) { + return new ValueNode(json.get!string); + } + + if (json.type() == JSONType.integer) { + return new ValueNode(json.integer.to!string); + } + + if (json.type() == JSONType.float_) { + return new ValueNode(json.floating.to!string); + } + + throw new ConfigCreationException("JSONValue is not supported: " ~ json.toString()); + } +} + +version (unittest) { + @("Parse JSON") unittest { + JSONValue serverJson = ["hostname": "hosty.com", "port": "1234"]; + JSONValue nullJson = ["isNull": null]; + JSONValue socketsJson = [ + "/var/sock/one", "/var/sock/two", "/var/sock/three" + ]; + JSONValue numbersJson = [1, 2, 3, 4, -7]; + JSONValue decimalsJson = [1.2, 4.5, 6.7]; + JSONValue jsonConfig = [ + "server": serverJson, "sockets": socketsJson, "nully": nullJson, + "numberos": numbersJson, "decimalas": decimalsJson + ]; + + auto loader = new JsonConfigFactory(); + auto config = loader.parseJson(jsonConfig); + + assert(config.get("server.hostname") == "hosty.com"); + assert(config.get("server.port") == "1234"); + assert(config.get("sockets[2]") == "/var/sock/three"); + assert(config.get("nully.isNull") == null); + assert(config.get("numberos[3]") == "4"); + assert(config.get("numberos[4]") == "-7"); + assert(config.get("decimalas[0]") == "1.2"); + assert(config.get("decimalas[2]") == "6.7"); + } + + @("Parse JSON root values") unittest { + auto loader = new JsonConfigFactory(); + + assert(loader.parseJson(JSONValue("hi")).get(".") == "hi"); + assert(loader.parseJson(JSONValue(1)).get(".") == "1"); + assert(loader.parseJson(JSONValue(null)).get(".") == null); + assert(loader.parseJson(JSONValue(1.8)).get(".") == "1.8"); + assert(loader.parseJson(JSONValue([1, 2, 3])).get("[2]") == "3"); + } +} diff --git a/source/mirage/package.d b/source/mirage/package.d index 7031966..cf145ec 100644 --- a/source/mirage/package.d +++ b/source/mirage/package.d @@ -10,3 +10,4 @@ module mirage; public import mirage.config; +public import mirage.json;