1 /**
2     Copyright: © 2016 Chris Barnes
3     License: The MIT License, see license file
4     Authors: Chris Barnes
5 */
6 module runtimer.configuration;
7 
8 import std.conv;
9 import std.file;
10 import std.getopt;
11 import std.json;
12 import vibe.data.json;
13 
14 /**
15      Represents application configuration where a struct T maps to some JSON configuration.
16  */
17 class Configuration(T)
18 {
19     /**
20         Creates a new configuration object wihout any command line overrides.
21         Params:
22             path = path to a JSON file containing the default path and configuration filename
23     */
24     this(string path = "configuration.json")
25     {
26         this.path = path;
27     }
28 
29     /**
30         Creates a new configuration object given the command line override args array.
31         Params:
32             path = path to a JSON file containing the default path and configuration filename
33     */
34     this(string[] args, string path = "configuration.json")
35     {
36         getopt(args,
37             "e|environment",  &environmentArgument,
38             "p|path",  &pathArgument
39         );
40         this(path);
41     }
42 
43     /**
44         Reads in the current configuration into this.application
45      */
46     void initialize()
47     {
48         JSONValue[string] configuration;
49         if(this.pathArgument == "")
50         {
51             configuration = parseJSON(to!string(read(this.path))).object;
52         }
53         string defaultEnvironment = this.environmentArgument != "" ? this.environmentArgument : configuration["default-environment"].str;
54         string environmentPath = this.pathArgument != "" ? this.pathArgument : configuration["environment-path"].str;
55         this.application = deserializeJson!T(to!(string)(read(environmentPath ~ "/" ~ defaultEnvironment ~ ".json")));
56     }
57 
58     T application;
59 
60     private:
61         string path;
62         string environmentArgument;
63         string pathArgument;
64 }
65 
66 version(unittest)
67 {
68     struct Host
69     {
70       string name;
71       ushort port;
72     }
73 
74     struct Credentials
75     {
76       string username;
77       string password;
78     }
79 
80     struct Http
81     {
82         Host host;
83     }
84 
85     struct Data
86     {
87         Host host;
88         Credentials credentials;
89         string database;
90     }
91 
92     struct Assets
93     {
94         string directory;
95         string files;
96     }
97 
98     struct Logging
99     {
100         string level;
101         string path;
102     }
103 
104     struct App
105     {
106         string name;
107         Http http;
108         Data data;
109         Assets assets;
110         Logging logging;
111     }
112 }
113 
114 unittest
115 {
116     auto configuration = new Configuration!(App)("fixtures/configuration.json");
117     configuration.initialize();
118     assert(configuration.application.name == "some-name");
119     assert(configuration.application.http.host.port == to!short(3000));
120     assert(configuration.application.http.host.name == "127.0.0.1");
121 
122     assert(configuration.application.data.host.name == "127.0.0.1");
123     assert(configuration.application.data.host.port == to!short(5432));
124     assert(configuration.application.data.database == "PostgreSQL");
125     assert(configuration.application.data.credentials.username == "admin");
126     assert(configuration.application.data.credentials.password == "password");
127 
128     assert(configuration.application.assets.directory == "public");
129     assert(configuration.application.assets.files == "*");
130 
131     assert(configuration.application.logging.level == "debug");
132     assert(configuration.application.logging.path == "log/test.log");
133 }
134 
135 unittest
136 {
137     auto configuration = new Configuration!(App)();
138     assert(configuration.path == "configuration.json"); //don't init, just make sure the default path is in place
139 }
140 
141 version(unittest)
142 {
143     struct AnotherApp
144     {
145         string name;
146     }
147 }
148 
149 unittest
150 {
151     //Make sure the terse switches work
152     auto configuration = new Configuration!(AnotherApp)(["_", "-e", "development", "-p", "fixtures/environments"]);
153     configuration.initialize();
154     assert(configuration.application.name == "development environment");
155 }
156 
157 unittest
158 {
159     //Make sure the verbose switches work
160     auto configuration = new Configuration!(AnotherApp)(["_", "--environment", "development", "--path", "fixtures/environments"]);
161     configuration.initialize();
162     assert(configuration.application.name == "development environment");
163 }