summaryrefslogtreecommitdiffstats
path: root/server/src/config.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/config.cpp')
-rw-r--r--server/src/config.cpp323
1 files changed, 197 insertions, 126 deletions
diff --git a/server/src/config.cpp b/server/src/config.cpp
index 3b17691..84c436a 100644
--- a/server/src/config.cpp
+++ b/server/src/config.cpp
@@ -14,154 +14,225 @@ namespace json = boost::json;
14 14
15namespace routemon::config { 15namespace routemon::config {
16 16
17 class location { 17class location
18 std::optional<std::pair<std::string_view, util::not_null<location const*>>> next_; 18{
19 std::optional<std::pair<std::string_view, util::not_null<location const*>>>
20 next_;
19 21
20 auto append_to(std::string& s) const -> void { 22 auto append_to(std::string& s) const -> void
21 if (next_) { 23 {
22 s += next_->first; 24 if (next_)
23 s += "."; 25 {
24 next_->second->append_to(s); 26 s += next_->first;
25 } 27 s += ".";
28 next_->second->append_to(s);
26 } 29 }
30 }
27 31
28 public: 32public:
29 location() = default; 33 location() = default;
30 34
31 explicit location(location const& next, std::string_view entry) 35 explicit location(location const& next, std::string_view entry)
32 : next_{std::make_pair(entry, util::not_null{&next})} 36 : next_{std::make_pair(entry, util::not_null{&next})}
33 {} 37 {
38 }
34 39
35 [[nodiscard]] auto to_string() const -> std::string { 40 [[nodiscard]] auto to_string() const -> std::string
36 if (!next_) 41 {
37 return ""; 42 if (!next_)
38 auto s = std::string{next_->first}; 43 return "";
39 next_->second->append_to(s); 44 auto s = std::string{next_->first};
40 return s; 45 next_->second->append_to(s);
41 } 46 return s;
47 }
42 48
43 auto sub(std::string_view entry) const& -> location { 49 auto sub(std::string_view entry) const& -> location
44 return location{*this, entry}; 50 {
45 } 51 return location{*this, entry};
46 }; 52 }
53};
47 54
48 class object_reader { 55class object_reader
49 location loc_; 56{
50 json::object const& obj_; 57 location loc_;
51 std::unordered_set<std::string> visited_; 58 json::object const& obj_;
59 std::unordered_set<std::string> visited_;
52 60
53 public: 61public:
54 object_reader(json::value const& jv, location loc) 62 object_reader(json::value const& jv, location loc)
55 try : loc_{std::move(loc)}, obj_{jv.as_object()} 63 try : loc_{std::move(loc)}, obj_{jv.as_object()} {}
56 {} catch (boost::system::system_error const&) { 64 catch (boost::system::system_error const&)
57 throw std::runtime_error{std::format("expected an object at {}", loc.to_string())}; 65 {
58 } 66 throw std::runtime_error{std::format(
67 "expected an object at {}", loc.to_string())};
68 }
59 69
60 auto check_unused() const -> void { 70 auto check_unused() const -> void
61 for (auto const& kv : obj_) { 71 {
62 if (!visited_.contains(std::string{kv.key()})) { 72 for (auto const& kv : obj_)
63 throw std::runtime_error{std::format("unexpected key {}", loc_.sub(kv.key()).to_string())}; 73 {
64 } 74 if (!visited_.contains(std::string{kv.key()}))
75 {
76 throw std::runtime_error{std::format(
77 "unexpected key {}", loc_.sub(kv.key()).to_string())};
65 } 78 }
66 } 79 }
80 }
67 81
68 template<class T> 82 template <class T>
69 auto expect_at(std::string_view key) -> T { 83 auto expect_at(std::string_view key) -> T
70 visited_.emplace(key); 84 {
71 auto const loc = loc_.sub(key); 85 visited_.emplace(key);
72 if (auto const jv = obj_.try_at(key)) { 86 auto const loc = loc_.sub(key);
73 try { 87 if (auto const jv = obj_.try_at(key))
74 return json::value_to<T>(*jv, loc); 88 {
75 } catch (boost::system::system_error const& e) { 89 try
76 throw std::runtime_error{std::format("failed to read {}: {}", loc.to_string(), e.code().message())}; 90 {
77 } 91 return json::value_to<T>(*jv, loc);
78 } else { 92 }
79 throw std::runtime_error{std::format("did not find expected key {}", loc.to_string())}; 93 catch (boost::system::system_error const& e)
94 {
95 throw std::runtime_error{std::format(
96 "failed to read {}: {}", loc.to_string(), e.code().message())};
80 } 97 }
81 } 98 }
82 }; 99 else
83 100 {
84 auto as_checked_object(json::value const& jv, location const& loc, std::invocable<object_reader&> auto const& f) -> decltype(f(std::declval<object_reader&>())) { 101 throw std::runtime_error{std::format(
85 auto r = object_reader{jv, loc}; 102 "did not find expected key {}", loc.to_string())};
86 auto&& v = f(r); 103 }
87 r.check_unused();
88 return std::forward<decltype(f(r))>(v);
89 } 104 }
105};
90 106
91 auto tag_invoke(json::value_to_tag<rwgps> const&, json::value const& jv, location const& loc) -> rwgps { 107auto as_checked_object(
92 return as_checked_object(jv, loc, [](object_reader& r) -> rwgps { 108 json::value const& jv, location const& loc,
93 return { 109 std::invocable<object_reader&> auto const& f)
94 .api_key = r.expect_at<std::string>("api_key"), 110 -> decltype(f(std::declval<object_reader&>()))
95 .auth_token = r.expect_at<std::string>("auth_token"), 111{
96 }; 112 auto r = object_reader{jv, loc};
97 }); 113 auto&& v = f(r);
98 } 114 r.check_unused();
115 return std::forward<decltype(f(r))>(v);
116}
99 117
100 auto tag_invoke(json::value_to_tag<situations> const&, json::value const& jv, location const& loc) -> situations { 118auto tag_invoke(
101 return as_checked_object(jv, loc, [](object_reader& r) -> situations { 119 json::value_to_tag<rwgps> const&, json::value const& jv,
102 return { 120 location const& loc) -> rwgps
103 .datex2_filename = r.expect_at<std::string>("datex2_filename"), 121{
104 }; 122 return as_checked_object(
105 }); 123 jv, loc,
106 } 124 [](object_reader& r) -> rwgps
125 {
126 return {
127 .api_key = r.expect_at<std::string>("api_key"),
128 .auth_token = r.expect_at<std::string>("auth_token"),
129 };
130 });
131}
107 132
108 auto tag_invoke(json::value_to_tag<database> const&, json::value const& jv, location const& loc) -> database { 133auto tag_invoke(
109 return as_checked_object(jv, loc, [](object_reader& r) -> database { 134 json::value_to_tag<situations> const&, json::value const& jv,
110 return { 135 location const& loc) -> situations
111 .sqlite3_filename = r.expect_at<std::string>("sqlite3_filename"), 136{
112 }; 137 return as_checked_object(
113 }); 138 jv, loc,
114 } 139 [](object_reader& r) -> situations
140 {
141 return {
142 .datex2_filename = r.expect_at<std::string>("datex2_filename"),
143 };
144 });
145}
115 146
116 auto tag_invoke(json::value_to_tag<http_server> const&, json::value const& jv, location const& loc) -> http_server { 147auto tag_invoke(
117 return as_checked_object(jv, loc, [](object_reader& r) -> http_server { 148 json::value_to_tag<database> const&, json::value const& jv,
118 return { 149 location const& loc) -> database
119 .lax_cors = r.expect_at<bool>("lax_cors"), 150{
120 }; 151 return as_checked_object(
121 }); 152 jv, loc,
122 } 153 [](object_reader& r) -> database
154 {
155 return {
156 .sqlite3_filename = r.expect_at<std::string>("sqlite3_filename"),
157 };
158 });
159}
123 160
124 auto tag_invoke(json::value_to_tag<logger> const&, json::value const& jv, location const& loc) -> logger { 161auto tag_invoke(
125 return as_checked_object(jv, loc, [obj_loc = loc](object_reader& r) -> logger { 162 json::value_to_tag<http_server> const&, json::value const& jv,
126 auto const level_str = r.expect_at<std::string>("level"); 163 location const& loc) -> http_server
127 auto level = log::level{}; 164{
128 if (level_str == "debug") 165 return as_checked_object(
129 level = log::level::debug; 166 jv, loc,
130 else if (level_str == "info") 167 [](object_reader& r) -> http_server
131 level = log::level::info; 168 {
132 else if (level_str == "warn") 169 return {
133 level = log::level::warn; 170 .lax_cors = r.expect_at<bool>("lax_cors"),
134 else if (level_str == "error") 171 };
135 level = log::level::error; 172 });
136 else 173}
137 throw std::runtime_error{std::format("unable to parse log level {:?} at {}: expected one of {{debug, info, warn, error}}", level_str, obj_loc.sub("level").to_string())};
138 return logger{level};
139 });
140 }
141 174
142 auto json_value_to_app(json::value const& jv) -> app { 175auto tag_invoke(
143 return as_checked_object(jv, location{}, [](object_reader& r) -> app { 176 json::value_to_tag<logger> const&, json::value const& jv,
144 return { 177 location const& loc) -> logger
145 .rwgps = r.expect_at<rwgps>("rwgps"), 178{
146 .situations = r.expect_at<situations>("situations"), 179 return as_checked_object(
147 .database = r.expect_at<database>("database"), 180 jv, loc,
148 .http_server = r.expect_at<http_server>("http_server"), 181 [obj_loc = loc](object_reader& r) -> logger
149 .logger = r.expect_at<logger>("logger"), 182 {
150 }; 183 auto const level_str = r.expect_at<std::string>("level");
151 }); 184 auto level = log::level{};
152 } 185 if (level_str == "debug")
186 level = log::level::debug;
187 else if (level_str == "info")
188 level = log::level::info;
189 else if (level_str == "warn")
190 level = log::level::warn;
191 else if (level_str == "error")
192 level = log::level::error;
193 else
194 throw std::runtime_error{std::format(
195 "unable to parse log level {:?} at {}: expected one "
196 "of {{debug, info, warn, error}}",
197 level_str, obj_loc.sub("level").to_string())};
198 return logger{level};
199 });
200}
153 201
154 auto load_file(std::string const& filename) -> app { 202auto json_value_to_app(json::value const& jv) -> app
155 auto f = std::ifstream{filename}; // TODO: ensure that we are opening in binary mode? 203{
156 if (!f.is_open()) 204 return as_checked_object(
157 throw std::runtime_error{std::format("failed to open {}", filename)}; 205 jv, location{},
158 auto jv = json::value{}; 206 [](object_reader& r) -> app
159 try { 207 {
160 jv = json::parse(f); 208 return {
161 } catch (boost::system::system_error const& e) { 209 .rwgps = r.expect_at<rwgps>("rwgps"),
162 throw std::runtime_error{std::format("failed to parse: {}", e.code().message())}; 210 .situations = r.expect_at<situations>("situations"),
163 } 211 .database = r.expect_at<database>("database"),
164 return json_value_to_app(jv); 212 .http_server = r.expect_at<http_server>("http_server"),
213 .logger = r.expect_at<logger>("logger"),
214 };
215 });
216}
217
218auto load_file(std::string const& filename) -> app
219{
220 auto f = std::ifstream{
221 filename
222 }; // TODO: ensure that we are opening in binary mode?
223 if (!f.is_open())
224 throw std::runtime_error{std::format("failed to open {}", filename)};
225 auto jv = json::value{};
226 try
227 {
228 jv = json::parse(f);
229 }
230 catch (boost::system::system_error const& e)
231 {
232 throw std::runtime_error{std::format(
233 "failed to parse: {}", e.code().message())};
165 } 234 }
235 return json_value_to_app(jv);
236}
166 237
167} // namespace routemon::config 238} // namespace routemon::config