2022-09-24 00:13:08 +02:00
/ * *
2022-09-24 18:31:21 +02:00
* Base utilities for working with configurations .
*
2022-09-24 00:13:08 +02:00
* Authors :
* Mike Bierlee , m . bierlee @lostmoment.com
2023-01-11 00:06:41 +01:00
* Copyright : 2022 - 2023 Mike Bierlee
2022-09-24 00:13:08 +02:00
* 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 .
* /
2022-09-24 15:58:49 +02:00
module mirage.config ;
2022-09-23 22:34:46 +02:00
2022-09-24 00:13:08 +02:00
import std.exception : enforce ;
2022-09-25 18:38:37 +02:00
import std.string : split , startsWith , endsWith , join , lastIndexOf , strip , toLower ;
2022-09-24 00:13:08 +02:00
import std.conv : to , ConvException ;
2022-09-24 17:51:32 +02:00
import std.file : readText ;
2022-09-25 18:38:37 +02:00
import std.path : extension ;
2022-09-28 22:31:52 +02:00
import std.process : environment ;
2022-09-28 22:49:08 +02:00
import std.typecons : Flag ;
2022-09-25 18:38:37 +02:00
import mirage.json : loadJsonConfig ;
2022-10-08 23:12:24 +02:00
import mirage.java : loadJavaProperties ;
2022-10-13 00:56:27 +02:00
import mirage.ini : loadIniConfig ;
2022-09-24 00:13:08 +02:00
2022-09-24 18:31:21 +02:00
/ * *
* Used by the ConfigDictionary when something goes wrong when reading configuration .
* /
2022-09-24 00:13:08 +02:00
class ConfigReadException : Exception {
this ( string msg , string file = __FILE__ , size_t line = __LINE__ ) {
super ( msg , file , line ) ;
}
}
2022-09-28 23:38:54 +02:00
/ * *
* 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 ) ;
}
}
2022-09-24 18:31:21 +02:00
/ * *
* Used by ConfigFactory instances when loading or parsing configuration fails .
* /
2022-09-24 17:05:33 +02:00
class ConfigCreationException : Exception {
this ( string msg , string file = __FILE__ , size_t line = __LINE__ ) {
super ( msg , file , line ) ;
}
}
2022-09-24 18:31:21 +02:00
/ * *
* Used by ConfigDictionary when there is something wrong with the path when calling ConfigDictionary . get ( )
* /
2022-09-24 00:13:08 +02:00
class PathParseException : Exception {
this ( string msg , string path , string file = __FILE__ , size_t line = __LINE__ ) {
string fullMsg = msg ~ " (Path: " ~ path ~ ")" ;
super ( fullMsg , file , line ) ;
}
}
2022-09-24 18:31:21 +02:00
/ * *
* The configuration tree is made up of specific types of ConfigNodes .
* Used as generic type for ConfigFactory and ConfigDictionary .
* /
2022-09-24 17:05:33 +02:00
interface ConfigNode {
2022-09-24 01:55:44 +02:00
string nodeType ( ) ;
2022-09-23 22:34:46 +02:00
}
2022-09-24 18:31:21 +02:00
/ * *
* A configuration item that is any sort of primitive value ( strings , numbers or null ) .
* /
2022-09-24 17:05:33 +02:00
class ValueNode : ConfigNode {
2022-09-23 22:34:46 +02:00
string value ;
this ( ) {
}
this ( string value ) {
this . value = value ;
}
2022-09-24 01:55:44 +02:00
string nodeType ( ) {
return "value" ;
}
2022-09-23 22:34:46 +02:00
}
2022-09-24 18:31:21 +02:00
/ * *
* A configuration item that is an object .
*
* ObjectNodes contain a node dictionary that points to other ConfigNodes .
* /
2022-09-24 17:05:33 +02:00
class ObjectNode : ConfigNode {
2022-09-23 22:34:46 +02:00
ConfigNode [ string ] children ;
this ( ) {
}
this ( ConfigNode [ string ] children ) {
this . children = children ;
}
2022-09-24 01:07:31 +02:00
this ( string [ string ] values ) {
foreach ( key , value ; values ) {
children [ key ] = new ValueNode ( value ) ;
}
}
2022-09-24 01:55:44 +02:00
string nodeType ( ) {
return "object" ;
}
2022-09-23 22:34:46 +02:00
}
2022-09-24 18:31:21 +02:00
/ * *
* A configuration item that is an array .
*
* Contains other ConfigNodes as children .
* /
2022-09-24 17:05:33 +02:00
class ArrayNode : ConfigNode {
2022-09-23 22:34:46 +02:00
ConfigNode [ ] children ;
this ( ) {
}
this ( ConfigNode [ ] children . . . ) {
this . children = children ;
}
2022-09-24 00:13:08 +02:00
this ( string [ ] values . . . ) {
foreach ( string value ; values ) {
2022-09-24 00:15:16 +02:00
children ~ = new ValueNode ( value ) ;
2022-09-24 00:13:08 +02:00
}
}
2022-09-24 01:55:44 +02:00
string nodeType ( ) {
return "array" ;
}
2022-09-24 00:13:08 +02:00
}
2022-09-24 16:05:47 +02:00
private interface PathSegment {
2022-09-24 00:13:08 +02:00
}
2022-09-24 16:05:47 +02:00
private class ArrayPathSegment : PathSegment {
2022-09-24 00:13:08 +02:00
const size_t index ;
this ( const size_t index ) {
this . index = index ;
}
}
2022-09-24 16:05:47 +02:00
private class PropertyPathSegment : PathSegment {
2022-09-24 01:07:31 +02:00
const string propertyName ;
this ( const string propertyName ) {
this . propertyName = propertyName ;
}
}
2022-09-24 16:05:47 +02:00
private class ConfigPath {
2022-09-24 00:13:08 +02:00
private const string path ;
2022-09-24 01:55:44 +02:00
private string [ ] previousSegments ;
2022-09-24 00:13:08 +02:00
private string [ ] segments ;
this ( const string path ) {
this . path = path ;
2022-09-24 02:46:19 +02:00
segmentAndNormalize ( path ) ;
}
2022-09-24 02:32:30 +02:00
2022-09-24 02:46:19 +02:00
private void segmentAndNormalize ( string path ) {
2022-09-24 02:32:30 +02:00
foreach ( segment ; path . split ( "." ) ) {
2022-09-25 18:20:52 +02:00
auto trimmedSegment = segment . strip ;
if ( trimmedSegment . length < = 0 ) {
2022-09-24 02:46:19 +02:00
continue ;
}
2022-09-25 18:20:52 +02:00
if ( trimmedSegment . endsWith ( "]" ) & & ! trimmedSegment . startsWith ( "[" ) ) {
auto openBracketPos = trimmedSegment . lastIndexOf ( "[" ) ;
2022-09-24 02:46:19 +02:00
if ( openBracketPos ! = - 1 ) {
2022-09-25 18:20:52 +02:00
segments ~ = trimmedSegment [ 0 . . openBracketPos ] ;
segments ~ = trimmedSegment [ openBracketPos . . $ ] ;
2022-09-24 02:46:19 +02:00
continue ;
}
2022-09-24 02:32:30 +02:00
}
2022-09-24 02:46:19 +02:00
2022-09-25 18:20:52 +02:00
segments ~ = trimmedSegment ;
2022-09-24 02:32:30 +02:00
}
2022-09-24 00:13:08 +02:00
}
PathSegment getNextSegment ( ) {
if ( segments . length = = 0 ) {
return null ;
}
PathSegment ret ( PathSegment segment ) {
2022-09-24 01:55:44 +02:00
previousSegments ~ = segments [ 0 ] ;
segments = segments [ 1 . . $ ] ;
2022-09-24 00:13:08 +02:00
return segment ;
}
string segment = segments [ 0 ] ;
if ( segment . startsWith ( "[" ) & & segment . endsWith ( "]" ) ) {
if ( segment . length < = 2 ) {
throw new PathParseException ( "Path has array accessor but no index specified" , path ) ;
}
auto indexString = segment [ 1 . . $ - 1 ] ;
try {
auto index = indexString . to ! size_t ;
return ret ( new ArrayPathSegment ( index ) ) ;
} catch ( ConvException e ) {
2022-09-24 17:05:33 +02:00
throw new PathParseException ( "Value '" ~ indexString ~ "' is not acceptable as an array index" , path ) ;
2022-09-24 00:13:08 +02:00
}
}
2022-09-24 01:07:31 +02:00
return ret ( new PropertyPathSegment ( segment ) ) ;
2022-09-24 00:13:08 +02:00
}
2022-09-24 01:55:44 +02:00
string getCurrentPath ( ) {
return previousSegments . join ( "." ) ;
}
2022-09-23 22:34:46 +02:00
}
2022-09-28 22:49:08 +02:00
/ * *
* Used in a ConfigDictionary to enable to disable environment variable substitution .
* /
2022-09-29 00:31:10 +02:00
alias SubstituteEnvironmentVariables = Flag ! "SubstituteEnvironmentVariables" ;
/ * *
* Used in a ConfigDictionary to enable to disable config path substitution .
* /
alias SubstituteConfigVariables = Flag ! "SubstituteConfigVariables" ;
2022-09-28 22:49:08 +02:00
2022-09-24 18:31:21 +02:00
/ * *
* A ConfigDictionary contains the configuration tree and facilities to get values from that tree .
* /
2022-09-23 22:34:46 +02:00
class ConfigDictionary {
ConfigNode rootNode ;
2022-09-28 22:49:08 +02:00
SubstituteEnvironmentVariables substituteEnvironmentVariables = SubstituteEnvironmentVariables
. yes ;
2022-09-29 00:31:10 +02:00
SubstituteConfigVariables substituteConfigVariables = SubstituteConfigVariables . yes ;
2022-09-24 00:13:08 +02:00
2022-09-28 22:49:08 +02:00
this ( SubstituteEnvironmentVariables substituteEnvironmentVariables = SubstituteEnvironmentVariables
2022-09-29 00:31:10 +02:00
. yes , SubstituteConfigVariables substituteConfigVariables = SubstituteConfigVariables
2022-09-28 22:49:08 +02:00
. yes ) {
this . substituteEnvironmentVariables = substituteEnvironmentVariables ;
2022-09-29 00:31:10 +02:00
this . substituteConfigVariables = substituteConfigVariables ;
2022-09-24 17:05:33 +02:00
}
2022-09-28 22:49:08 +02:00
this ( ConfigNode rootNode , SubstituteEnvironmentVariables substituteEnvironmentVariables = SubstituteEnvironmentVariables
2022-09-29 00:31:10 +02:00
. yes , SubstituteConfigVariables substituteConfigVariables = SubstituteConfigVariables
2022-09-28 22:49:08 +02:00
. yes ) {
2022-09-29 00:31:10 +02:00
this ( substituteEnvironmentVariables , substituteConfigVariables ) ;
2022-09-24 17:05:33 +02:00
this . rootNode = rootNode ;
}
2022-09-24 18:31:21 +02:00
/ * *
* Get values from the configuration using config path notation .
*
* Params :
* configPath = Path to the wanted config value . The path is separated by dots , e . g . "server.public.hostname" .
* Values from arrays can be selected by brackets , for example : "server[3].hostname.ports[0]" .
* When the config is just a value , for example just a string , it can be fetched by just specifying "." as path .
* 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
* more details .
2022-09-28 23:38:54 +02:00
* 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 .
2022-09-24 18:31:21 +02:00
*
2022-09-24 18:42:29 +02:00
* Returns : The value at the path in the configuration . To convert it use get ! T ( ) .
2022-09-24 18:31:21 +02:00
* /
2022-09-28 23:38:54 +02:00
string get ( string configPath , string defaultValue = null ) {
try {
auto path = new ConfigPath ( configPath ) ;
auto node = getNodeAt ( path ) ;
auto value = cast ( ValueNode ) node ;
if ( value ) {
2022-09-29 00:31:10 +02:00
if ( substituteEnvironmentVariables | | substituteConfigVariables ) {
return substituteVariables ( value ) ;
} else {
return value . value ;
}
2022-09-28 23:38:54 +02:00
} else {
throw new ConfigReadException (
"Value expected but " ~ node . nodeType ~ " found at path: " ~ createExceptionPath (
path ) ) ;
}
} catch ( ConfigPathNotFoundException e ) {
if ( defaultValue ! is null ) {
return defaultValue ;
}
throw e ;
2022-09-24 19:05:15 +02:00
}
}
/ * *
* Get values from the configuration and attempts to convert them to the specified type .
*
* Params :
2022-09-28 23:38:54 +02:00
* 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 .
*
2022-09-24 19:05:15 +02:00
* Returns : The value at the path in the configuration .
2022-09-28 23:38:54 +02:00
* See_Also : get
2022-09-24 19:05:15 +02:00
* /
ConvertToType get ( ConvertToType ) ( string configPath ) {
return get ( configPath ) . to ! ConvertToType ;
}
2022-09-24 00:13:08 +02:00
2022-09-28 23:38:54 +02:00
/ * *
* 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 ;
}
}
2022-09-24 19:05:15 +02:00
/ * *
* Fetch a sub - section of the config as another config .
*
* Commonly used for example to fetch further configuration from arrays , e . g . : `getConfig("http.servers[3]")`
* which then returns the rest of the config at that path .
*
* Params :
* configPath = Path to the wanted config . See get ( ) .
* Returns : A sub - section of the configuration .
* /
ConfigDictionary getConfig ( string configPath ) {
2022-09-24 00:13:08 +02:00
auto path = new ConfigPath ( configPath ) ;
2022-09-24 19:05:15 +02:00
auto node = getNodeAt ( path ) ;
return new ConfigDictionary ( node ) ;
}
2022-10-06 23:51:17 +02:00
/ * *
* Assign a value at the given path .
*
* Params :
* configPath = Path where to assign the value to . If the path does not exist , it will be created .
* value = Value to set at path .
* /
void set ( string configPath , string value ) {
auto path = new ConfigPath ( configPath ) ;
rootNode = insertAtPath ( rootNode , path , value ) ;
}
private string createExceptionPath ( ConfigPath path ) {
2022-09-24 19:05:15 +02:00
return "'" ~ path . path ~ "' (at '" ~ path . getCurrentPath ( ) ~ "')" ;
}
private ConfigNode getNodeAt ( ConfigPath path ) {
2022-09-28 23:38:54 +02:00
void throwPathNotFound ( ) {
throw new ConfigPathNotFoundException (
"Path does not exist: " ~ createExceptionPath ( path ) ) ;
}
if ( rootNode is null ) {
throwPathNotFound ( ) ;
}
2022-09-24 19:05:15 +02:00
2022-09-24 00:13:08 +02:00
auto currentNode = rootNode ;
PathSegment currentPathSegment = path . getNextSegment ( ) ;
2022-09-24 02:18:49 +02:00
void ifNotNullPointer ( void * obj , void delegate ( ) fn ) {
if ( obj ) {
fn ( ) ;
} else {
2022-09-28 23:38:54 +02:00
throwPathNotFound ( ) ;
2022-09-24 02:18:49 +02:00
}
}
void ifNotNull ( Object obj , void delegate ( ) fn ) {
if ( obj ) {
fn ( ) ;
} else {
2022-09-28 23:38:54 +02:00
throwPathNotFound ( ) ;
2022-09-24 02:18:49 +02:00
}
}
2022-09-24 00:13:08 +02:00
while ( currentPathSegment ! is null ) {
2022-09-24 01:55:44 +02:00
if ( currentNode is null ) {
2022-09-28 23:38:54 +02:00
throwPathNotFound ( ) ;
2022-09-24 01:55:44 +02:00
}
auto valueNode = cast ( ValueNode ) currentNode ;
if ( valueNode ) {
2022-09-28 23:38:54 +02:00
throwPathNotFound ( ) ;
2022-09-24 01:55:44 +02:00
}
2022-09-24 00:13:08 +02:00
auto arrayPath = cast ( ArrayPathSegment ) currentPathSegment ;
if ( arrayPath ) {
2022-09-24 00:15:16 +02:00
auto arrayNode = cast ( ArrayNode ) currentNode ;
2022-09-24 02:18:49 +02:00
ifNotNull ( arrayNode , {
2022-09-24 01:07:31 +02:00
if ( arrayNode . children . length < arrayPath . index ) {
2022-09-24 01:55:44 +02:00
throw new ConfigReadException (
2022-09-24 19:05:15 +02:00
"Array index out of bounds: " ~ createExceptionPath ( path ) ) ;
2022-09-24 01:07:31 +02:00
}
2022-09-24 00:13:08 +02:00
currentNode = arrayNode . children [ arrayPath . index ] ;
2022-09-24 02:18:49 +02:00
} ) ;
2022-09-24 00:13:08 +02:00
}
2022-09-24 01:07:31 +02:00
auto propertyPath = cast ( PropertyPathSegment ) currentPathSegment ;
if ( propertyPath ) {
auto objectNode = cast ( ObjectNode ) currentNode ;
2022-09-24 02:18:49 +02:00
ifNotNull ( objectNode , {
2022-09-24 01:07:31 +02:00
auto propertyNode = propertyPath . propertyName in objectNode . children ;
2022-09-24 02:18:49 +02:00
ifNotNullPointer ( propertyNode , {
2022-09-24 01:07:31 +02:00
currentNode = * propertyNode ;
2022-09-24 02:18:49 +02:00
} ) ;
} ) ;
2022-09-24 01:07:31 +02:00
}
2022-09-24 00:13:08 +02:00
currentPathSegment = path . getNextSegment ( ) ;
}
2022-09-24 19:05:15 +02:00
return currentNode ;
2022-09-24 18:42:29 +02:00
}
2022-09-28 22:31:52 +02:00
2022-09-29 00:31:10 +02:00
private string substituteVariables ( ValueNode valueNode ) {
2022-09-28 22:31:52 +02:00
auto value = valueNode . value ;
if ( value = = null ) {
2022-09-28 22:35:10 +02:00
return value ;
2022-09-28 22:31:52 +02:00
}
auto result = "" ;
auto isParsingEnvVar = false ;
auto isParsingDefault = false ;
2022-09-29 01:04:58 +02:00
auto escapeNextCharacter = false ;
2022-09-29 00:31:10 +02:00
auto varName = "" ;
auto defaultVarValue = "" ;
void addVarValueToResult ( ) {
2022-09-29 01:28:48 +02:00
varName = varName . strip ;
2022-09-29 01:04:58 +02:00
if ( varName . length = = 0 ) {
return ;
}
2022-09-29 00:31:10 +02:00
string [ ] exceptionMessageParts ;
if ( substituteEnvironmentVariables ) {
exceptionMessageParts ~ = "environment variable" ;
auto envVarValue = environment . get ( varName ) ;
if ( envVarValue ! is null ) {
result ~ = envVarValue ;
return ;
}
}
2022-09-28 22:31:52 +02:00
2022-09-29 00:31:10 +02:00
if ( substituteConfigVariables ) {
exceptionMessageParts ~ = "config path" ;
auto configValue = get ( varName , defaultVarValue ) ;
if ( configValue . length > 0 ) {
result ~ = configValue ;
return ;
2022-09-28 22:31:52 +02:00
}
2022-09-29 00:31:10 +02:00
}
2022-09-28 22:31:52 +02:00
2022-09-29 01:28:48 +02:00
if ( isParsingDefault ) {
result ~ = defaultVarValue . strip ;
2022-09-29 00:31:10 +02:00
return ;
2022-09-28 22:31:52 +02:00
}
2022-09-29 00:31:10 +02:00
auto exceptionMessageComponents = exceptionMessageParts . join ( " or " ) ;
throw new ConfigReadException (
"No substitution found for " ~ exceptionMessageComponents ~ ": " ~ varName ) ;
2022-09-28 22:31:52 +02:00
}
foreach ( size_t i , char c ; value ) {
2022-09-29 01:04:58 +02:00
if ( escapeNextCharacter ) {
result ~ = c ;
escapeNextCharacter = false ;
continue ;
}
if ( c = = '\\' & & value . length . to ! int - 1 > i & & value [ i + 1 ] = = '$' ) {
escapeNextCharacter = true ;
continue ;
}
2022-09-28 22:31:52 +02:00
if ( c = = '$' ) {
isParsingEnvVar = true ;
continue ;
}
if ( isParsingEnvVar ) {
if ( c = = '{' ) {
continue ;
}
if ( c = = '}' ) {
2022-09-29 01:28:48 +02:00
addVarValueToResult ( ) ;
2022-09-28 22:31:52 +02:00
isParsingEnvVar = false ;
isParsingDefault = false ;
2022-09-29 00:31:10 +02:00
varName = "" ;
defaultVarValue = "" ;
2022-09-28 22:31:52 +02:00
continue ;
}
if ( isParsingDefault ) {
2022-09-29 00:31:10 +02:00
defaultVarValue ~ = c ;
2022-09-28 22:31:52 +02:00
continue ;
}
if ( c = = ':' ) {
isParsingDefault = true ;
continue ;
}
2022-09-29 00:31:10 +02:00
varName ~ = c ;
2022-09-28 22:31:52 +02:00
continue ;
}
result ~ = c ;
}
2022-09-29 01:04:58 +02:00
if ( isParsingEnvVar ) {
if ( varName . length > 0 ) {
addVarValueToResult ( ) ;
} else {
result ~ = '$' ;
}
2022-09-28 22:31:52 +02:00
}
return result ;
}
2022-10-06 23:51:17 +02:00
private ConfigNode insertAtPath ( ConfigNode node , ConfigPath path , const string value ) {
auto nextSegment = path . getNextSegment ( ) ;
if ( nextSegment is null ) {
return new ValueNode ( value ) ;
}
auto propertySegment = cast ( PropertyPathSegment ) nextSegment ;
if ( propertySegment is null ) {
throw new Exception ( "Not yet implemented: cannot set array" ) ; // todo
}
auto objectNode = node is null ? new ObjectNode ( ) : cast ( ObjectNode ) node ;
if ( objectNode is null ) {
throw new Exception ( "Not yet implemented: cannot set array" ) ; // todo
}
auto childNodePtr = propertySegment . propertyName in objectNode . children ;
ConfigNode childNode = childNodePtr ! is null ? * childNodePtr : new ObjectNode ( ) ;
objectNode . children [ propertySegment . propertyName ] = insertAtPath ( childNode , path , value ) ;
return objectNode ;
}
2022-09-23 22:34:46 +02:00
}
2022-09-24 18:31:21 +02:00
/ * *
* The base class used by configuration factories for specific file types .
* /
2022-09-24 17:51:32 +02:00
abstract class ConfigFactory {
2022-09-24 18:31:21 +02:00
/ * *
* Loads a configuration from the specified path from disk .
*
* Params :
* path = Path to file . OS dependent , but UNIX paths are generally working .
* Returns : The parsed configuration .
* /
2022-09-24 17:51:32 +02:00
ConfigDictionary loadFile ( string path ) {
auto json = readText ( path ) ;
return parseConfig ( json ) ;
}
2022-09-24 18:31:21 +02:00
/ * *
* Parse configuration from the given string .
*
* Params :
* contents = Text contents of the config to be parsed .
* Returns : The parsed configuration .
* /
2022-09-24 17:05:33 +02:00
ConfigDictionary parseConfig ( string contents ) ;
2022-09-24 16:05:47 +02:00
}
2022-09-25 18:38:37 +02:00
ConfigDictionary loadConfig ( const string configPath ) {
auto extension = configPath . extension . toLower ;
if ( extension = = ".json" ) {
return loadJsonConfig ( configPath ) ;
}
2022-10-08 23:12:24 +02:00
if ( extension = = ".properties" ) {
return loadJavaProperties ( configPath ) ;
}
2022-10-13 00:56:27 +02:00
if ( extension = = ".ini" ) {
return loadIniConfig ( configPath ) ;
}
2022-09-25 18:38:37 +02:00
throw new ConfigCreationException (
"File extension '" ~ extension ~ "' is not recognized as a supported config file format. Please use a specific function to load it, such as 'loadJsonConfig()'" ) ;
}
2022-09-23 22:34:46 +02:00
version ( unittest ) {
2022-09-24 00:13:08 +02:00
import std.exception : assertThrown ;
2022-09-24 18:42:29 +02:00
import std.math.operations : isClose ;
2022-09-24 00:13:08 +02:00
2022-09-23 22:34:46 +02:00
@ ( "Dictionary creation" )
unittest {
2022-09-24 00:15:16 +02:00
auto root = new ObjectNode ( [
"english" : new ArrayNode ( [ new ValueNode ( "one" ) , new ValueNode ( "two" ) ] ) ,
"spanish" : new ArrayNode ( new ValueNode ( "uno" ) , new ValueNode ( "dos" ) )
2022-09-23 22:34:46 +02:00
] ) ;
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary ( ) ;
config . rootNode = root ;
2022-09-23 22:34:46 +02:00
}
2022-09-24 00:13:08 +02:00
2022-09-25 18:23:27 +02:00
@ ( "Get value in config with empty root fails" )
2022-09-24 00:13:08 +02:00
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary ( ) ;
2022-09-24 00:13:08 +02:00
2022-09-28 23:38:54 +02:00
assertThrown ! ConfigPathNotFoundException ( config . get ( "." ) ) ;
2022-09-24 00:13:08 +02:00
}
2022-09-24 16:09:20 +02:00
@ ( "Get value in root with empty path" )
2022-09-24 00:13:08 +02:00
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary ( new ValueNode ( "hehehe" ) ) ;
2022-09-24 00:13:08 +02:00
2022-09-25 18:23:27 +02:00
assert ( config . get ( "" ) = = "hehehe" ) ;
2022-09-24 00:13:08 +02:00
}
2022-09-24 16:09:20 +02:00
@ ( "Get value in root with just a dot" )
2022-09-24 00:13:08 +02:00
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary ( new ValueNode ( "yup" ) ) ;
2022-09-24 00:13:08 +02:00
2022-09-25 18:23:27 +02:00
assert ( config . get ( "." ) = = "yup" ) ;
2022-09-24 00:13:08 +02:00
}
@ ( "Get value in root fails when root is not a value" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary ( new ArrayNode ( ) ) ;
2022-09-24 00:13:08 +02:00
2022-09-25 18:23:27 +02:00
assertThrown ! ConfigReadException ( config . get ( "." ) ) ;
2022-09-24 00:13:08 +02:00
}
@ ( "Get array value from root" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary ( new ArrayNode ( "aap" , "noot" , "mies" ) ) ;
2022-09-24 00:13:08 +02:00
2022-09-25 18:23:27 +02:00
assert ( config . get ( "[0]" ) = = "aap" ) ;
assert ( config . get ( "[1]" ) = = "noot" ) ;
assert ( config . get ( "[2]" ) = = "mies" ) ;
2022-09-24 00:13:08 +02:00
}
2022-09-24 01:07:31 +02:00
@ ( "Get value from object at root" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary ( new ObjectNode ( [
2022-09-24 18:49:38 +02:00
"aap" : "monkey" ,
"noot" : "nut" ,
"mies" : "mies" // It's a name!
] )
) ;
2022-09-24 01:07:31 +02:00
2022-09-25 18:23:27 +02:00
assert ( config . get ( "aap" ) = = "monkey" ) ;
assert ( config . get ( "noot" ) = = "nut" ) ;
assert ( config . get ( "mies" ) = = "mies" ) ;
2022-09-24 01:07:31 +02:00
}
2022-09-24 01:55:44 +02:00
@ ( "Get value from object in object" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary (
2022-09-24 18:49:38 +02:00
new ObjectNode ( [
"server" : new ObjectNode ( [
"port" : "8080"
] )
2022-09-24 01:55:44 +02:00
] )
2022-09-24 18:49:38 +02:00
) ;
2022-09-24 01:55:44 +02:00
2022-09-25 18:23:27 +02:00
assert ( config . get ( "server.port" ) = = "8080" ) ;
2022-09-24 01:55:44 +02:00
}
@ ( "Get value from array in object" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary (
2022-09-24 18:49:38 +02:00
new ObjectNode ( [
"hostname" : new ArrayNode ( [ "google.com" , "dlang.org" ] )
] )
) ;
2022-09-24 01:55:44 +02:00
2022-09-25 18:23:27 +02:00
assert ( config . get ( "hostname.[1]" ) = = "dlang.org" ) ;
2022-09-24 01:55:44 +02:00
}
@ ( "Exception is thrown when array out of bounds when fetching from root" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary (
2022-09-24 18:49:38 +02:00
new ArrayNode ( [
"google.com" , "dlang.org"
] )
) ;
2022-09-24 01:55:44 +02:00
2022-09-25 18:23:27 +02:00
assertThrown ! ConfigReadException ( config . get ( "[5]" ) ) ;
2022-09-24 01:55:44 +02:00
}
@ ( "Exception is thrown when array out of bounds when fetching from object" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary (
2022-09-24 18:49:38 +02:00
new ObjectNode ( [
"hostname" : new ArrayNode ( [ "google.com" , "dlang.org" ] )
] )
) ;
2022-09-24 01:55:44 +02:00
2022-09-25 18:23:27 +02:00
assertThrown ! ConfigReadException ( config . get ( "hostname.[5]" ) ) ;
2022-09-24 01:55:44 +02:00
}
@ ( "Exception is thrown when path does not exist" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary ( new ObjectNode (
2022-09-24 18:49:38 +02:00
[
"hostname" : new ObjectNode ( [ "cluster" : new ValueNode ( "" ) ] )
] )
) ;
2022-09-24 01:55:44 +02:00
2022-09-28 23:38:54 +02:00
assertThrown ! ConfigPathNotFoundException ( config . get ( "hostname.cluster.spacey" ) ) ;
2022-09-24 01:55:44 +02:00
}
@ ( "Exception is thrown when given path terminates too early" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary ( new ObjectNode (
2022-09-24 18:49:38 +02:00
[
"hostname" : new ObjectNode ( [ "cluster" : new ValueNode ( null ) ] )
] )
) ;
2022-09-24 01:55:44 +02:00
2022-09-25 18:23:27 +02:00
assertThrown ! ConfigReadException ( config . get ( "hostname" ) ) ;
2022-09-24 01:55:44 +02:00
}
2022-09-24 02:18:49 +02:00
@ ( "Exception is thrown when given path does not exist because config is an array" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary ( new ArrayNode ( ) ) ;
2022-09-24 02:18:49 +02:00
2022-09-28 23:38:54 +02:00
assertThrown ! ConfigPathNotFoundException ( config . get ( "hostname" ) ) ;
2022-09-24 02:18:49 +02:00
}
2022-09-24 02:27:41 +02:00
@ ( "Get value from objects in array" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary ( new ArrayNode (
2022-09-24 18:49:38 +02:00
new ObjectNode ( [ "wrong" : "yes" ] ) ,
new ObjectNode ( [ "wrong" : "no" ] ) ,
new ObjectNode ( [ "wrong" : "very" ] ) ,
) ) ;
2022-09-24 02:27:41 +02:00
2022-09-25 18:23:27 +02:00
assert ( config . get ( "[1].wrong" ) = = "no" ) ;
2022-09-24 02:27:41 +02:00
}
@ ( "Get value from config with mixed types" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary (
2022-09-24 18:49:38 +02:00
new ObjectNode ( [
"uno" : cast ( ConfigNode ) new ValueNode ( "one" ) ,
"dos" : cast ( ConfigNode ) new ArrayNode ( [ "nope" , "two" ] ) ,
"tres" : cast ( ConfigNode ) new ObjectNode ( [ "thisone" : "three" ] )
] )
) ;
2022-09-24 02:27:41 +02:00
2022-09-25 18:23:27 +02:00
assert ( config . get ( "uno" ) = = "one" ) ;
assert ( config . get ( "dos.[1]" ) = = "two" ) ;
assert ( config . get ( "tres.thisone" ) = = "three" ) ;
2022-09-24 02:27:41 +02:00
}
2022-09-24 02:32:30 +02:00
@ ( "Ignore empty segments" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary (
2022-09-24 18:49:38 +02:00
new ObjectNode (
[
2022-09-24 02:32:30 +02:00
"one" : new ObjectNode ( [ "two" : new ObjectNode ( [ "three" : "four" ] ) ] )
2022-09-24 18:49:38 +02:00
] )
) ;
2022-09-24 02:32:30 +02:00
2022-09-25 18:23:27 +02:00
assert ( config . get ( ".one..two...three...." ) = = "four" ) ;
2022-09-24 02:32:30 +02:00
}
2022-09-24 02:46:19 +02:00
@ ( "Support conventional array indexing notation" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary (
2022-09-24 18:49:38 +02:00
new ObjectNode (
[
"one" : new ObjectNode ( [
"two" : new ArrayNode ( [ "dino" , "mino" ] )
] )
] )
) ;
2022-09-24 02:46:19 +02:00
2022-09-25 18:23:27 +02:00
assert ( config . get ( "one.two[1]" ) = = "mino" ) ;
2022-09-24 02:46:19 +02:00
}
2022-09-24 18:42:29 +02:00
@ ( "Get and convert values" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary (
2022-09-24 18:49:38 +02:00
new ObjectNode ( [
"uno" : new ValueNode ( "1223" ) ,
"dos" : new ValueNode ( "true" ) ,
"tres" : new ValueNode ( "Hi you" ) ,
"quatro" : new ValueNode ( "1.3" )
] )
) ;
2022-09-24 18:42:29 +02:00
2022-09-25 18:23:27 +02:00
assert ( config . get ! int ( "uno" ) = = 1223 ) ;
assert ( config . get ! bool ( "dos" ) = = true ) ;
assert ( config . get ! string ( "tres" ) = = "Hi you" ) ;
assert ( isClose ( config . get ! float ( "quatro" ) , 1.3 ) ) ;
2022-09-24 18:42:29 +02:00
}
2022-09-24 19:05:15 +02:00
@ ( "Get config from array" )
unittest {
2022-09-25 18:23:27 +02:00
auto configOne = new ConfigDictionary ( new ObjectNode (
2022-09-24 19:05:15 +02:00
[
"servers" : new ArrayNode ( [
new ObjectNode ( [ "hostname" : "lala.com" ] ) ,
new ObjectNode ( [ "hostname" : "lele.com" ] )
] )
] )
) ;
2022-09-25 18:23:27 +02:00
auto config = configOne . getConfig ( "servers[0]" ) ;
2022-09-24 19:05:15 +02:00
assert ( config . get ( "hostname" ) = = "lala.com" ) ;
}
2022-09-25 18:20:52 +02:00
@ ( "Trim spaces in path segments" )
unittest {
2022-09-25 18:23:27 +02:00
auto config = new ConfigDictionary (
2022-09-25 18:20:52 +02:00
new ObjectNode ( [ "que" : new ObjectNode ( [ "pasa hombre" : "not much" ] ) ] )
) ;
2022-09-25 18:23:27 +02:00
assert ( config . get ( " que. pasa hombre " ) = = "not much" ) ;
2022-09-25 18:20:52 +02:00
}
2022-09-25 18:38:37 +02:00
@ ( "Load configurations using the loadConfig convenience function" )
unittest {
auto jsonConfig = loadConfig ( "testfiles/groot.json" ) ;
assert ( jsonConfig . get ( "name" ) = = "Groot" ) ;
assert ( jsonConfig . get ( "traits[1]" ) = = "tree" ) ;
assert ( jsonConfig . get ( "age" ) = = "8728" ) ;
assert ( jsonConfig . get ( "taxNumber" ) = = null ) ;
2022-10-08 23:12:24 +02:00
auto javaProperties = loadConfig ( "testfiles/groot.properties" ) ;
assert ( javaProperties . get ( "name" ) = = "Groot" ) ;
assert ( javaProperties . get ( "age" ) = = "8728" ) ;
assert ( javaProperties . get ( "taxNumber" ) = = "null" ) ;
2022-10-13 00:56:27 +02:00
auto iniConfig = loadConfig ( "testfiles/groot.ini" ) ;
assert ( iniConfig . get ( "groot.name" ) = = "Groot" ) ;
assert ( iniConfig . get ( "groot.age" ) = = "8728" ) ;
assert ( iniConfig . get ( "groot.taxNumber" ) = = "null" ) ;
2022-09-25 18:38:37 +02:00
}
2022-09-28 22:31:52 +02:00
2022-09-28 22:33:23 +02:00
@ ( "Whitespace is preserved in values" )
unittest {
auto config = new ConfigDictionary ( new ObjectNode ( [
"bla" : " blergh "
] ) ) ;
assert ( config . get ( "bla" ) = = " blergh " ) ;
}
2022-09-28 22:31:52 +02:00
2022-09-28 22:35:10 +02:00
@ ( "Null value stays null, not string" )
unittest {
auto config = new ConfigDictionary ( new ValueNode ( null ) ) ;
assert ( config . get ( "." ) = = null ) ;
}
2022-09-28 22:31:52 +02:00
@ ( "Read value from environment variable" )
unittest {
environment [ "MIRAGE_CONFIG_TEST_ENV_VAR" ] = "is set!" ;
environment [ "MIRAGE_CONFIG_TEST_ENV_VAR_TWO" ] = "is ready!" ;
auto config = new ConfigDictionary (
new ObjectNode (
[
"withBrackets" : new ValueNode ( "${MIRAGE_CONFIG_TEST_ENV_VAR}" ) ,
"withoutBrackets" : new ValueNode ( "$MIRAGE_CONFIG_TEST_ENV_VAR" ) ,
"withWhiteSpace" : new ValueNode ( " ${MIRAGE_CONFIG_TEST_ENV_VAR} " ) ,
"alsoWithWhiteSpace" : new ValueNode ( " $MIRAGE_CONFIG_TEST_ENV_VAR" ) ,
2022-09-29 01:28:48 +02:00
"whitespaceAtEnd" : new ValueNode ( "$MIRAGE_CONFIG_TEST_ENV_VAR " ) ,
2022-09-28 22:31:52 +02:00
"notSet" : new ValueNode ( "${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR}" ) ,
2022-09-29 01:28:48 +02:00
"alsoNotSet" : new ValueNode ( "$MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR" ) ,
2022-09-28 22:31:52 +02:00
"withDefault" : new ValueNode ( "$MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:use default!" ) ,
"withDefaultAndBrackets" : new ValueNode (
"${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:use default!}" ) ,
"megaMix" : new ValueNode ( "${MIRAGE_CONFIG_TEST_ENV_VAR_TWO} ${MIRAGE_CONFIG_TEST_ENV_VAR} ${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:go}!" ) ,
"typical" : new ValueNode ( "${MIRAGE_CONFIG_TEST_HOSTNAME:localhost}:${MIRAGE_CONFIG_TEST_PORT:8080}" ) ,
2022-09-29 01:28:48 +02:00
"whitespaceInVar" : new ValueNode ( "${MIRAGE_CONFIG_TEST_ENV_VAR }" ) ,
"whitespaceInDefault" : new ValueNode ( "${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR: bruh}" ) ,
"emptyDefault" : new ValueNode ( "${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:}" ) ,
"emptyDefaultWhiteSpace" : new ValueNode ( "${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR: }" ) ,
2022-09-29 00:31:10 +02:00
] ) ,
SubstituteEnvironmentVariables . yes ,
SubstituteConfigVariables . no
2022-09-28 22:31:52 +02:00
) ;
assert ( config . get ( "withBrackets" ) = = "is set!" ) ;
assert ( config . get ( "withoutBrackets" ) = = "is set!" ) ;
assert ( config . get ( "withWhiteSpace" ) = = " is set! " ) ;
assert ( config . get ( "alsoWithWhiteSpace" ) = = " is set!" ) ;
2022-09-29 01:28:48 +02:00
assert ( config . get ( "whitespaceAtEnd" ) = = "is set!" ) ;
2022-09-29 00:31:10 +02:00
assertThrown ! ConfigReadException ( config . get ( "notSet" ) ) ; // Environment variable not found
2022-09-29 01:28:48 +02:00
assertThrown ! ConfigReadException ( config . get ( "alsoNotSet" ) ) ; // Environment variable not found
2022-09-28 22:31:52 +02:00
assert ( config . get ( "withDefault" ) = = "use default!" ) ;
assert ( config . get ( "withDefaultAndBrackets" ) = = "use default!" ) ;
assert ( config . get ( "megaMix" ) = = "is ready! is set! go!" ) ;
assert ( config . get ( "typical" ) = = "localhost:8080" ) ;
2022-09-29 01:28:48 +02:00
assert ( config . get ( "whitespaceInVar" ) = = "is set!" ) ;
assert ( config . get ( "whitespaceInDefault" ) = = "bruh" ) ;
assert ( config . get ( "emptyDefault" ) = = "" ) ;
assert ( config . get ( "emptyDefaultWhiteSpace" ) = = "" ) ;
2022-09-28 22:31:52 +02:00
}
2022-09-28 22:49:08 +02:00
@ ( "Don't read value from environment variables when disabled" )
unittest {
environment . remove ( "MIRAGE_CONFIG_TEST_ENV_VAR" ) ;
environment . remove ( "MIRAGE_CONFIG_TEST_ENV_VAR_TWO" ) ;
auto config = new ConfigDictionary (
new ObjectNode (
[
"withBrackets" : new ValueNode ( "${MIRAGE_CONFIG_TEST_ENV_VAR}" ) ,
"withoutBrackets" : new ValueNode ( "$MIRAGE_CONFIG_TEST_ENV_VAR" ) ,
"withWhiteSpace" : new ValueNode ( " ${MIRAGE_CONFIG_TEST_ENV_VAR} " ) ,
"alsoWithWhiteSpace" : new ValueNode ( " $MIRAGE_CONFIG_TEST_ENV_VAR" ) ,
"tooMuchWhiteSpace" : new ValueNode ( "$MIRAGE_CONFIG_TEST_ENV_VAR " ) ,
"notSet" : new ValueNode ( "${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR}" ) ,
"withDefault" : new ValueNode ( "$MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:use default!" ) ,
"withDefaultAndBrackets" : new ValueNode (
"${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:use default!}" ) ,
"megaMix" : new ValueNode ( "${MIRAGE_CONFIG_TEST_ENV_VAR_TWO} ${MIRAGE_CONFIG_TEST_ENV_VAR} ${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:go}!" ) ,
"typical" : new ValueNode ( "${MIRAGE_CONFIG_TEST_HOSTNAME:localhost}:${MIRAGE_CONFIG_TEST_PORT:8080}" ) ,
] ) ,
2022-09-29 00:31:10 +02:00
SubstituteEnvironmentVariables . no ,
SubstituteConfigVariables . no
2022-09-28 22:49:08 +02:00
) ;
assert ( config . get ( "withBrackets" ) = = "${MIRAGE_CONFIG_TEST_ENV_VAR}" ) ;
assert ( config . get ( "withoutBrackets" ) = = "$MIRAGE_CONFIG_TEST_ENV_VAR" ) ;
assert ( config . get ( "withWhiteSpace" ) = = " ${MIRAGE_CONFIG_TEST_ENV_VAR} " ) ;
assert ( config . get ( "alsoWithWhiteSpace" ) = = " $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 ( "withDefault" ) = = "$MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:use default!" ) ;
2022-09-28 23:38:54 +02:00
assert ( config . get (
"withDefaultAndBrackets" ) = = "${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:use default!}" ) ;
2022-09-28 22:49:08 +02:00
assert ( config . get ( "megaMix" ) = = "${MIRAGE_CONFIG_TEST_ENV_VAR_TWO} ${MIRAGE_CONFIG_TEST_ENV_VAR} ${MIRAGE_CONFIG_NOT_SET_TEST_ENV_VAR:go}!" ) ;
2022-09-28 23:38:54 +02:00
assert ( config . get (
"typical" ) = = "${MIRAGE_CONFIG_TEST_HOSTNAME:localhost}:${MIRAGE_CONFIG_TEST_PORT:8080}" ) ;
2022-09-28 22:49:08 +02:00
}
2022-09-28 23:38:54 +02:00
@ ( "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 ) ;
}
2022-09-29 00:31:10 +02:00
@ ( "Substitute values from other config paths" )
unittest {
auto config = new ConfigDictionary (
new ObjectNode ( [
"greet" : cast ( ConfigNode ) new ObjectNode ( [
"env" : "Hi"
] ) ,
"hi" : cast ( ConfigNode ) new ValueNode ( "${greet.env} there!" ) ,
"oi" : cast ( ConfigNode ) new ValueNode ( "${path.does.not.exist}" ) ,
] ) ,
SubstituteEnvironmentVariables . no ,
SubstituteConfigVariables . yes
) ;
assert ( config . get ( "hi" ) = = "Hi there!" ) ;
assertThrown ! ConfigReadException ( config . get ( "oi" ) ) ;
}
@ ( "Do not substitute values from other config paths when disabled" )
unittest {
auto config = new ConfigDictionary (
new ObjectNode ( [
"greet" : cast ( ConfigNode ) new ObjectNode ( [
"env" : "Hi"
] ) ,
"hi" : cast ( ConfigNode ) new ValueNode ( "${greet.env} there!" ) ,
"oi" : cast ( ConfigNode ) new ValueNode ( "${path.does.not.exist}" ) ,
] ) ,
SubstituteEnvironmentVariables . no ,
SubstituteConfigVariables . no
) ;
assert ( config . get ( "greet.env" ) = = "Hi" ) ;
assert ( config . get ( "hi" ) = = "${greet.env} there!" ) ;
assert ( config . get ( "oi" ) = = "${path.does.not.exist}" ) ;
}
@ ( "substitute values from both environment variables and config paths" )
unittest {
environment [ "MIRAGE_CONFIG_TEST_ENV_VAR_THREE" ] = "punch" ;
auto config = new ConfigDictionary (
new ObjectNode ( [
"one" : new ValueNode ( "${MIRAGE_CONFIG_TEST_ENV_VAR_THREE}" ) ,
"two" : new ValueNode ( "${one}" ) ,
] ) ,
SubstituteEnvironmentVariables . yes ,
SubstituteConfigVariables . yes
) ;
assert ( config . get ( "two" ) = = "punch" ) ;
}
2022-09-29 01:04:58 +02:00
@ ( "Escaping avoids variable substitution" )
unittest {
auto config = new ConfigDictionary ( new ObjectNode ( [
"only dollar" : "\\$ESCAPE_WITH_MY_LIFE" ,
"with brackets" : "\\${YOU WON'T GET ME}" ,
] )
) ;
assert ( config . get ( "only dollar" ) = = "$ESCAPE_WITH_MY_LIFE" ) ;
assert ( config . get ( "with brackets" ) = = "${YOU WON'T GET ME}" ) ;
}
@ ( "Wonky values" )
unittest {
auto config = new ConfigDictionary ( new ObjectNode ( [
"just a dollar" : "$" ,
"final escape" : "\\" ,
"escape with money" : "\\$" ,
"brackets" : "{}" ,
"empty bracket money" : "${}" ,
"smackers" : "$$$$$" ,
"weird bash escape thingy" : "$$" ,
"escape room" : "\\\\"
] ) ) ;
assert ( config . get ( "just a dollar" ) = = "$" ) ;
assert ( config . get ( "final escape" ) = = "\\" ) ;
assert ( config . get ( "escape with money" ) = = "$" ) ;
assert ( config . get ( "brackets" ) = = "{}" ) ;
assert ( config . get ( "empty bracket money" ) = = "" ) ;
assert ( config . get ( "smackers" ) = = "$" ) ;
assert ( config . get ( "escape room" ) = = "\\\\" ) ;
}
2022-10-06 23:51:17 +02:00
@ ( "Set value at path when path does not exist" )
unittest {
auto config = new ConfigDictionary ( ) ;
config . set ( "do.re.mi.fa.so" , "buh" ) ;
assert ( config . get ( "do.re.mi.fa.so" ) = = "buh" ) ;
}
@ ( "Set value at path when object at path exists" )
unittest {
auto config = new ConfigDictionary (
new ObjectNode (
[
"one" : new ObjectNode ( [
"two" : new ObjectNode ( [
"mouseSound" : "meep"
] )
] )
] )
) ;
config . set ( "one.two.catSound" , "meow" ) ;
assert ( config . get ( "one.two.mouseSound" ) = = "meep" ) ;
assert ( config . get ( "one.two.catSound" ) = = "meow" ) ;
}
@ ( "Overwrite value at path" )
unittest {
auto config = new ConfigDictionary (
new ObjectNode (
[
"one" : new ObjectNode ( [
"two" : new ObjectNode ( [
"mouseSound" : "meep"
] )
] )
] )
) ;
config . set ( "one.two.mouseSound" , "yo" ) ;
assert ( config . get ( "one.two.mouseSound" ) = = "yo" ) ;
}
@ ( "Diverge path completely" )
unittest {
auto config = new ConfigDictionary (
new ObjectNode (
[
"one" : new ObjectNode ( [
"two" : new ObjectNode ( [
"mouseSound" : "meep"
] )
] )
] )
) ;
config . set ( "I.am" , "baboon" ) ;
assert ( config . get ( "one.two.mouseSound" ) = = "meep" ) ;
assert ( config . get ( "I.am" ) = = "baboon" ) ;
}
2022-10-11 19:18:25 +02:00
@ ( "Setting the same value twice will use latest setting" )
unittest {
auto config = new ConfigDictionary ( ) ;
config . set ( "key" , "value" ) ;
config . set ( "key" , "alsoValue" ) ;
assert ( config . get ( "key" ) = = "alsoValue" ) ;
}
2022-09-23 22:34:46 +02:00
}