2022-09-24 17:05:33 +02:00
|
|
|
/**
|
2022-09-24 18:31:21 +02:00
|
|
|
* Utilities for loading JSON configurations.
|
|
|
|
*
|
2022-09-24 17:05:33 +02:00
|
|
|
* 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;
|
|
|
|
|
2022-09-24 17:43:56 +02:00
|
|
|
import std.json : JSONValue, JSONType, parseJSON;
|
2022-09-24 17:05:33 +02:00
|
|
|
import std.conv : to;
|
|
|
|
|
|
|
|
import mirage.config : ConfigFactory, ConfigDictionary, ConfigNode, ValueNode, ObjectNode, ArrayNode, ConfigCreationException;
|
|
|
|
|
2022-09-24 18:31:21 +02:00
|
|
|
/**
|
|
|
|
* Creates configuration dictionaries from JSONs.
|
|
|
|
*/
|
2022-09-24 17:05:33 +02:00
|
|
|
class JsonConfigFactory : ConfigFactory {
|
2022-09-24 18:31:21 +02:00
|
|
|
|
2022-09-25 18:09:59 +02:00
|
|
|
/**
|
2022-09-24 18:31:21 +02:00
|
|
|
* Parse configuration from the given JSON string.
|
|
|
|
*
|
|
|
|
* Params:
|
|
|
|
* contents = Text contents of the config to be parsed.
|
|
|
|
* Returns: The parsed configuration.
|
|
|
|
*/
|
2022-09-24 17:51:32 +02:00
|
|
|
override ConfigDictionary parseConfig(string contents) {
|
2022-09-24 17:43:56 +02:00
|
|
|
return parseJson(parseJSON(contents));
|
2022-09-24 17:05:33 +02:00
|
|
|
}
|
|
|
|
|
2022-09-24 18:31:21 +02:00
|
|
|
/**
|
|
|
|
* Parse configuration from a JSONValue tree.
|
|
|
|
*
|
|
|
|
* Params:
|
2022-10-09 00:10:05 +02:00
|
|
|
* json = JSONValue config to be parsed.
|
2022-09-24 18:31:21 +02:00
|
|
|
* Returns: The parsed configuration.
|
|
|
|
*/
|
2022-09-24 17:05:33 +02:00
|
|
|
ConfigDictionary parseJson(JSONValue json) {
|
|
|
|
return new ConfigDictionary(convertJValue(json));
|
|
|
|
}
|
|
|
|
|
2022-09-24 18:31:21 +02:00
|
|
|
/**
|
|
|
|
* Alias for parseConfig
|
|
|
|
*
|
|
|
|
* Params:
|
2022-10-09 00:10:05 +02:00
|
|
|
* json = Text contents of the config to be parsed.
|
2022-09-24 18:31:21 +02:00
|
|
|
* Returns: The parsed configuration.
|
|
|
|
* See_Also: parseConfig
|
|
|
|
*/
|
2022-09-24 17:05:33 +02:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-25 18:09:59 +02:00
|
|
|
/**
|
|
|
|
* Parse JSON config from the given JSON string.
|
2022-11-27 16:05:09 +01:00
|
|
|
*
|
2022-09-25 18:09:59 +02:00
|
|
|
* Params:
|
|
|
|
* json = Text contents of the config to be parsed.
|
|
|
|
* Returns: The parsed configuration.
|
|
|
|
*/
|
2022-09-25 18:38:37 +02:00
|
|
|
ConfigDictionary parseJsonConfig(const string json) {
|
2022-09-25 18:09:59 +02:00
|
|
|
return new JsonConfigFactory().parseConfig(json);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse JSON config from the given JSONValue.
|
|
|
|
*
|
|
|
|
* Params:
|
2022-10-09 00:10:05 +02:00
|
|
|
* json = JSONValue config to be parsed.
|
2022-09-25 18:09:59 +02:00
|
|
|
* Returns: The parsed configuration.
|
|
|
|
*/
|
2022-09-25 18:38:37 +02:00
|
|
|
ConfigDictionary parseJsonConfig(const JSONValue json) {
|
2022-09-25 18:09:59 +02:00
|
|
|
return new JsonConfigFactory().parseJson(json);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load a JSON configuration file from disk.
|
|
|
|
*
|
|
|
|
* Params:
|
|
|
|
* filePath = Path to the JSON configuration file.
|
|
|
|
* Returns: The loaded configuration.
|
|
|
|
*/
|
2022-09-25 18:38:37 +02:00
|
|
|
ConfigDictionary loadJsonConfig(const string filePath) {
|
2022-09-25 18:09:59 +02:00
|
|
|
return new JsonConfigFactory().loadFile(filePath);
|
|
|
|
}
|
|
|
|
|
2022-09-24 17:05:33 +02:00
|
|
|
version (unittest) {
|
2022-09-28 22:54:38 +02:00
|
|
|
import std.process : environment;
|
|
|
|
|
2022-09-24 17:43:56 +02:00
|
|
|
@("Parse JSON")
|
|
|
|
unittest {
|
2022-09-24 17:05:33 +02:00
|
|
|
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
|
|
|
|
];
|
|
|
|
|
2022-09-25 18:09:59 +02:00
|
|
|
auto config = parseJsonConfig(jsonConfig);
|
2022-09-24 17:05:33 +02:00
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2022-09-24 17:43:56 +02:00
|
|
|
@("Parse JSON root values")
|
|
|
|
unittest {
|
2022-09-25 18:09:59 +02:00
|
|
|
assert(parseJsonConfig(JSONValue("hi")).get(".") == "hi");
|
|
|
|
assert(parseJsonConfig(JSONValue(1)).get(".") == "1");
|
|
|
|
assert(parseJsonConfig(JSONValue(null)).get(".") == null);
|
|
|
|
assert(parseJsonConfig(JSONValue(1.8)).get(".") == "1.8");
|
|
|
|
assert(parseJsonConfig(JSONValue([1, 2, 3])).get("[2]") == "3");
|
2022-09-24 17:05:33 +02:00
|
|
|
}
|
2022-09-24 17:43:56 +02:00
|
|
|
|
|
|
|
@("Parse JSON string")
|
|
|
|
unittest {
|
|
|
|
string json = "
|
|
|
|
{
|
|
|
|
\"name\": \"Groot\",
|
|
|
|
\"traits\": [\"groot\", \"tree\"],
|
|
|
|
\"age\": 8728,
|
|
|
|
\"taxNumber\": null
|
|
|
|
}
|
|
|
|
";
|
|
|
|
|
2022-09-25 18:09:59 +02:00
|
|
|
auto config = parseJsonConfig(json);
|
2022-09-24 17:43:56 +02:00
|
|
|
|
|
|
|
assert(config.get("name") == "Groot");
|
|
|
|
assert(config.get("traits[1]") == "tree");
|
|
|
|
assert(config.get("age") == "8728");
|
|
|
|
assert(config.get("taxNumber") == null);
|
|
|
|
}
|
2022-09-24 17:51:32 +02:00
|
|
|
|
|
|
|
@("Load JSON file")
|
|
|
|
unittest {
|
2022-09-25 18:09:59 +02:00
|
|
|
auto config = loadJsonConfig("testfiles/groot.json");
|
2022-09-24 17:51:32 +02:00
|
|
|
|
|
|
|
assert(config.get("name") == "Groot");
|
|
|
|
assert(config.get("traits[1]") == "tree");
|
|
|
|
assert(config.get("age") == "8728");
|
|
|
|
assert(config.get("taxNumber") == null);
|
|
|
|
}
|
2022-09-28 22:54:38 +02:00
|
|
|
|
2022-10-08 22:46:34 +02:00
|
|
|
@("Substitute env vars")
|
2022-09-28 22:54:38 +02:00
|
|
|
unittest {
|
|
|
|
environment["MIRAGE_TEST_APP_NAME"] = "Unittest";
|
|
|
|
environment["MIRAGE_TEST_HOSTNAME"] = "wonkeyhost";
|
|
|
|
environment.remove("MIRAGE_TEST_PORT");
|
|
|
|
|
|
|
|
auto config = loadJsonConfig("testfiles/server.json");
|
|
|
|
|
|
|
|
assert(config.get("server.host") == "wonkeyhost");
|
|
|
|
assert(config.get("server.port") == "8118");
|
|
|
|
assert(config.get("app") == "Unittest server - built with love");
|
|
|
|
}
|
2022-10-08 22:46:34 +02:00
|
|
|
|
|
|
|
@("Use value from other key")
|
|
|
|
unittest {
|
|
|
|
string json = "
|
|
|
|
{
|
|
|
|
\"one\": \"Groot\",
|
|
|
|
\"two\": \"${one}\",
|
|
|
|
}
|
|
|
|
";
|
|
|
|
|
|
|
|
auto config = parseJsonConfig(json);
|
|
|
|
|
|
|
|
assert(config.get("two") == "Groot");
|
|
|
|
}
|
2022-09-24 17:05:33 +02:00
|
|
|
}
|