summaryrefslogtreecommitdiffstats
path: root/server/src
diff options
context:
space:
mode:
authorRutger Broekhoff2026-08-28 22:22:24 +0200
committerRutger Broekhoff2026-08-28 22:22:24 +0200
commitb0d7600970d2fdb1eb6fff813c7336ea34d4e228 (patch)
tree051d6f80c7e332f4b31027025ae7c60c7ac70742 /server/src
parent70643a9e4821bd11c3f363f1708fe420965467d1 (diff)
downloadroutemon-b0d7600970d2fdb1eb6fff813c7336ea34d4e228.tar.gz
routemon-b0d7600970d2fdb1eb6fff813c7336ea34d4e228.zip
Indent tweaks
Diffstat (limited to 'server/src')
-rw-r--r--server/src/api.cpp126
-rw-r--r--server/src/api.cppm24
-rw-r--r--server/src/config.cpp163
-rw-r--r--server/src/database.cppm8
-rw-r--r--server/src/datex2.cppm112
-rw-r--r--server/src/geo.cppm34
-rw-r--r--server/src/gpx.cpp62
-rw-r--r--server/src/http_client.cppm6
-rw-r--r--server/src/http_server.cppm322
-rw-r--r--server/src/locale.cppm33
-rw-r--r--server/src/log.cppm14
-rw-r--r--server/src/main.cpp28
-rw-r--r--server/src/problem.cppm4
-rw-r--r--server/src/req_ctx.cppm8
-rw-r--r--server/src/rwgps.cppm20
-rw-r--r--server/src/sqlite3.cppm82
-rw-r--r--server/src/srv.cppm58
-rw-r--r--server/src/time.cppm2
-rw-r--r--server/src/trace.cppm14
-rw-r--r--server/src/util.cppm25
-rw-r--r--server/src/xml.cpp6
-rw-r--r--server/src/xml.cppm140
22 files changed, 656 insertions, 635 deletions
diff --git a/server/src/api.cpp b/server/src/api.cpp
index 73dee32..9317f1b 100644
--- a/server/src/api.cpp
+++ b/server/src/api.cpp
@@ -39,7 +39,7 @@ auto json_value_from_linestring(geo::linestring const& ls) -> json::value
39} 39}
40 40
41auto json_value_from_linestrings(std::vector<geo::linestring> const& lss) 41auto json_value_from_linestrings(std::vector<geo::linestring> const& lss)
42 -> json::value 42 -> json::value
43{ 43{
44 json::array a; 44 json::array a;
45 for (auto const& ls : lss) 45 for (auto const& ls : lss)
@@ -48,8 +48,8 @@ auto json_value_from_linestrings(std::vector<geo::linestring> const& lss)
48} 48}
49 49
50auto tag_invoke( 50auto tag_invoke(
51 json::value_from_tag, json::value& jv, relevant_road_closure const& clo) 51 json::value_from_tag, json::value& jv, relevant_road_closure const& clo)
52 -> void 52 -> void
53{ 53{
54 jv = json::object{ 54 jv = json::object{
55 {"relevant_lss", json_value_from_linestrings(clo.relevant_lss)}, 55 {"relevant_lss", json_value_from_linestrings(clo.relevant_lss)},
@@ -57,7 +57,8 @@ auto tag_invoke(
57} 57}
58 58
59auto tag_invoke( 59auto tag_invoke(
60 json::value_from_tag, json::value& jv, relevant_situation const& sit) -> void 60 json::value_from_tag, json::value& jv, relevant_situation const& sit)
61 -> void
61{ 62{
62 jv = json::object{ 63 jv = json::object{
63 {"id", json::value_from(sit.id)}, 64 {"id", json::value_from(sit.id)},
@@ -68,7 +69,7 @@ auto tag_invoke(
68} 69}
69 70
70auto tag_invoke(json::value_from_tag, json::value& jv, track_segment const& seg) 71auto tag_invoke(json::value_from_tag, json::value& jv, track_segment const& seg)
71 -> void 72 -> void
72{ 73{
73 jv = json::object{ 74 jv = json::object{
74 {"points", json_value_from_linestring(seg.points)}, 75 {"points", json_value_from_linestring(seg.points)},
@@ -76,7 +77,7 @@ auto tag_invoke(json::value_from_tag, json::value& jv, track_segment const& seg)
76} 77}
77 78
78auto tag_invoke(json::value_from_tag, json::value& jv, track const& track) 79auto tag_invoke(json::value_from_tag, json::value& jv, track const& track)
79 -> void 80 -> void
80{ 81{
81 jv = json::object{ 82 jv = json::object{
82 {"segments", json::value_from(track.segments)}, 83 {"segments", json::value_from(track.segments)},
@@ -84,7 +85,8 @@ auto tag_invoke(json::value_from_tag, json::value& jv, track const& track)
84} 85}
85 86
86auto tag_invoke( 87auto tag_invoke(
87 json::value_from_tag, json::value& jv, process_gpx_result const& res) -> void 88 json::value_from_tag, json::value& jv, process_gpx_result const& res)
89 -> void
88{ 90{
89 jv = json::object{ 91 jv = json::object{
90 {"tracks", json::value_from(res.tracks)}, 92 {"tracks", json::value_from(res.tracks)},
@@ -93,7 +95,7 @@ auto tag_invoke(
93} 95}
94 96
95auto tag_invoke(json::value_from_tag, json::value& jv, sysinfo const& info) 97auto tag_invoke(json::value_from_tag, json::value& jv, sysinfo const& info)
96 -> void 98 -> void
97{ 99{
98 jv = json::object{ 100 jv = json::object{
99 {"using_publication_of", 101 {"using_publication_of",
@@ -124,14 +126,14 @@ handler::handler(log::logger const& l, datex2::situation_publication pub)
124 } 126 }
125 auto const after_build = chrono::steady_clock::now(); 127 auto const after_build = chrono::steady_clock::now();
126 auto const dur_build = 128 auto const dur_build =
127 chrono::duration_cast<chrono::milliseconds>(after_build - before_build); 129 chrono::duration_cast<chrono::milliseconds>(after_build - before_build);
128 l_.info("Indices built in {}", dur_build); 130 l_.info("Indices built in {}", dur_build);
129 l_.info("LSE index size: {}", lse_index_.size()); 131 l_.info("LSE index size: {}", lse_index_.size());
130 l_.info("Point index size: {}", p_index_.size()); 132 l_.info("Point index size: {}", p_index_.size());
131} 133}
132 134
133auto handler::process_gpx(gpx::file&& gpx_file) 135auto handler::process_gpx(gpx::file&& gpx_file)
134 -> std::optional<process_gpx_result> 136 -> std::optional<process_gpx_result>
135{ 137{
136 auto const now = chrono::utc_clock::now(); 138 auto const now = chrono::utc_clock::now();
137 auto const relevant = std::initializer_list<time::period>{ 139 auto const relevant = std::initializer_list<time::period>{
@@ -143,14 +145,14 @@ auto handler::process_gpx(gpx::file&& gpx_file)
143 for (auto const& track : gpx_file.tracks) 145 for (auto const& track : gpx_file.tracks)
144 for (auto const& seg : track.segments) 146 for (auto const& seg : track.segments)
145 geo::split_linestring_with_overlap_segments( 147 geo::split_linestring_with_overlap_segments(
146 seg.waypoints, 148 seg.waypoints,
147 5000 /* meters max total dist until a new split is forced */, 149 5000 /* meters max total dist until a new split is forced */,
148 splits_with_overlap_segments); 150 splits_with_overlap_segments);
149 auto const before_query = chrono::steady_clock::now(); 151 auto const before_query = chrono::steady_clock::now();
150 152
151 l_.debug("Querying for relevant situations"); 153 l_.debug("Querying for relevant situations");
152 auto relevant_road_closures = 154 auto relevant_road_closures =
153 std::unordered_set<std::shared_ptr<datex2::road_closure>>{}; 155 std::unordered_set<std::shared_ptr<datex2::road_closure>>{};
154 auto ls_checked = 0uz; 156 auto ls_checked = 0uz;
155 auto p_checked = 0uz; 157 auto p_checked = 0uz;
156 auto i = 0; 158 auto i = 0;
@@ -197,66 +199,70 @@ auto handler::process_gpx(gpx::file&& gpx_file)
197 199
198 auto const after_query = chrono::steady_clock::now(); 200 auto const after_query = chrono::steady_clock::now();
199 l_.debug( 201 l_.debug(
200 "Done (checked {} line string(s) and {} point(s)) in {}", ls_checked, 202 "Done (checked {} line string(s) and {} point(s)) in {}", ls_checked,
201 p_checked, 203 p_checked,
202 chrono::duration_cast<chrono::milliseconds>(after_query - before_query)); 204 chrono::duration_cast<chrono::milliseconds>(after_query - before_query));
203 205
204 auto relevant_situations = 206 auto relevant_situations =
205 std::unordered_set<std::shared_ptr<datex2::situation>>{}; 207 std::unordered_set<std::shared_ptr<datex2::situation>>{};
206 for (auto const& rc : relevant_road_closures) 208 for (auto const& rc : relevant_road_closures)
207 relevant_situations.emplace(rc->parent); 209 relevant_situations.emplace(rc->parent);
208 210
209 l_.debug( 211 l_.debug(
210 "Identified {} relevant road closure(s), part of {} unique " 212 "Identified {} relevant road closure(s), part of {} unique "
211 "situation(s)", 213 "situation(s)",
212 relevant_road_closures.size(), relevant_situations.size()); 214 relevant_road_closures.size(), relevant_situations.size());
213 for (auto const& sit : relevant_situations) 215 for (auto const& sit : relevant_situations)
214 l_.debug("Relevant situation: {}", sit->id); 216 l_.debug("Relevant situation: {}", sit->id);
215 217
216 return process_gpx_result{ 218 return process_gpx_result{
217 .tracks = gpx_file.tracks 219 .tracks = gpx_file.tracks
218 | views::transform( 220 | views::transform(
219 [](auto const& trk) -> track 221 [](auto const& trk) -> track
220 { 222 {
221 return { 223 return {
222 .segments = trk.segments 224 .segments =
223 | views::transform( 225 trk.segments
224 [](auto const& seg) -> track_segment 226 | views::transform(
225 { return {.points = seg.waypoints}; }) 227 [](auto const& seg) -> track_segment
226 | std::ranges::to<std::vector<track_segment>>(), 228 { return {.points = seg.waypoints}; })
227 }; 229 | std::ranges::to<std::vector<track_segment>>(),
228 }) 230 };
231 })
229 | std::ranges::to<std::vector<track>>(), 232 | std::ranges::to<std::vector<track>>(),
230 .relevant_situations = 233 .relevant_situations =
231 relevant_situations 234 relevant_situations
232 | views::transform( 235 | views::transform(
233 [&](std::shared_ptr<datex2::situation> sit) -> relevant_situation 236 [&](std::shared_ptr<datex2::situation> sit) -> relevant_situation
234 { 237 {
235 return { 238 return {
236 .id = sit->id, 239 .id = sit->id,
237 .location = sit->location, 240 .location = sit->location,
238 .comments = sit->comments, 241 .comments = sit->comments,
239 .relevant_road_closures = 242 .relevant_road_closures =
240 relevant_road_closures 243 relevant_road_closures
241 | views::filter( 244 | views::filter(
242 [&](std::shared_ptr<datex2::road_closure> const& rc) -> bool 245 [&](std::shared_ptr<datex2::road_closure> const& rc)
243 { return std::shared_ptr{rc->parent} == sit; }) 246 -> bool
244 | views::transform( 247 { return std::shared_ptr{rc->parent} == sit; })
245 [](std::shared_ptr<datex2::road_closure> const& rc) 248 | views::transform(
246 -> relevant_road_closure 249 [](std::shared_ptr<datex2::road_closure> const& rc)
247 { 250 -> relevant_road_closure
248 return { 251 {
249 .relevant_lss = 252 return {
250 rc->relevant_line_strings 253 .relevant_lss =
251 | views::transform( 254 rc->relevant_line_strings
252 [](auto const& lsp) -> geo::linestring { return *lsp; }) 255 | views::transform(
253 | std::ranges::to<std::vector<geo::linestring>>(), 256 [](auto const& lsp) -> geo::linestring
254 }; 257 { return *lsp; })
255 }) 258 | std::ranges::to<
256 | std::ranges::to<std::vector<relevant_road_closure>>(), 259 std::vector<geo::linestring>>(),
257 }; 260 };
258 }) 261 })
259 | std::ranges::to<std::vector<relevant_situation>>(), 262 | std::ranges::to<std::vector<relevant_road_closure>>(),
263 };
264 })
265 | std::ranges::to<std::vector<relevant_situation>>(),
260 }; 266 };
261} 267}
262 268
diff --git a/server/src/api.cppm b/server/src/api.cppm
index c724a45..66e8f45 100644
--- a/server/src/api.cppm
+++ b/server/src/api.cppm
@@ -58,28 +58,30 @@ struct sysinfo
58}; 58};
59 59
60auto tag_invoke( 60auto tag_invoke(
61 json::value_from_tag, json::value& jv, relevant_road_closure const& clo) 61 json::value_from_tag, json::value& jv, relevant_road_closure const& clo)
62 -> void; 62 -> void;
63auto tag_invoke( 63auto tag_invoke(
64 json::value_from_tag, json::value& jv, relevant_situation const& sit) -> void; 64 json::value_from_tag, json::value& jv, relevant_situation const& sit)
65 -> void;
65auto tag_invoke(json::value_from_tag, json::value& jv, track_segment const& seg) 66auto tag_invoke(json::value_from_tag, json::value& jv, track_segment const& seg)
66 -> void; 67 -> void;
67auto tag_invoke(json::value_from_tag, json::value& jv, track const& track) 68auto tag_invoke(json::value_from_tag, json::value& jv, track const& track)
68 -> void; 69 -> void;
69auto tag_invoke( 70auto tag_invoke(
70 json::value_from_tag, json::value& jv, process_gpx_result const& res) -> void; 71 json::value_from_tag, json::value& jv, process_gpx_result const& res)
72 -> void;
71auto tag_invoke(json::value_from_tag, json::value& jv, sysinfo const& info) 73auto tag_invoke(json::value_from_tag, json::value& jv, sysinfo const& info)
72 -> void; 74 -> void;
73 75
74class handler 76class handler
75{ 77{
76 using lse_index_value = std::tuple< 78 using lse_index_value = std::tuple<
77 geo::box, std::shared_ptr<geo::linestring>, 79 geo::box, std::shared_ptr<geo::linestring>,
78 std::shared_ptr<datex2::road_closure>>; 80 std::shared_ptr<datex2::road_closure>>;
79 using p_index_value = 81 using p_index_value =
80 std::pair<geo::point, std::shared_ptr<datex2::road_closure>>; 82 std::pair<geo::point, std::shared_ptr<datex2::road_closure>>;
81 using lse_index = 83 using lse_index =
82 bgeo::index::rtree<lse_index_value, bgeo::index::quadratic<16>>; 84 bgeo::index::rtree<lse_index_value, bgeo::index::quadratic<16>>;
83 using p_index = bgeo::index::rtree<p_index_value, bgeo::index::quadratic<16>>; 85 using p_index = bgeo::index::rtree<p_index_value, bgeo::index::quadratic<16>>;
84 86
85 log::logger l_; 87 log::logger l_;
diff --git a/server/src/config.cpp b/server/src/config.cpp
index e9a728b..eab97b5 100644
--- a/server/src/config.cpp
+++ b/server/src/config.cpp
@@ -17,7 +17,7 @@ namespace routemon::config {
17class location 17class location
18{ 18{
19 std::optional<std::pair<std::string_view, util::not_null<location const*>>> 19 std::optional<std::pair<std::string_view, util::not_null<location const*>>>
20 next_; 20 next_;
21 21
22 auto append_to(std::string& s) const -> void 22 auto append_to(std::string& s) const -> void
23 { 23 {
@@ -64,7 +64,7 @@ public:
64 catch (boost::system::system_error const&) 64 catch (boost::system::system_error const&)
65 { 65 {
66 throw std::runtime_error{std::format( 66 throw std::runtime_error{std::format(
67 "expected an object at {}", loc.to_string())}; 67 "expected an object at {}", loc.to_string())};
68 } 68 }
69 69
70 auto check_unused() const -> void 70 auto check_unused() const -> void
@@ -74,7 +74,7 @@ public:
74 if (!visited_.contains(std::string{kv.key()})) 74 if (!visited_.contains(std::string{kv.key()}))
75 { 75 {
76 throw std::runtime_error{std::format( 76 throw std::runtime_error{std::format(
77 "unexpected key {}", loc_.sub(kv.key()).to_string())}; 77 "unexpected key {}", loc_.sub(kv.key()).to_string())};
78 } 78 }
79 } 79 }
80 } 80 }
@@ -93,21 +93,21 @@ public:
93 catch (boost::system::system_error const& e) 93 catch (boost::system::system_error const& e)
94 { 94 {
95 throw std::runtime_error{std::format( 95 throw std::runtime_error{std::format(
96 "failed to read {}: {}", loc.to_string(), e.code().message())}; 96 "failed to read {}: {}", loc.to_string(), e.code().message())};
97 } 97 }
98 } 98 }
99 else 99 else
100 { 100 {
101 throw std::runtime_error{std::format( 101 throw std::runtime_error{std::format(
102 "did not find expected key {}", loc.to_string())}; 102 "did not find expected key {}", loc.to_string())};
103 } 103 }
104 } 104 }
105}; 105};
106 106
107auto as_checked_object( 107auto as_checked_object(
108 json::value const& jv, location const& loc, 108 json::value const& jv, location const& loc,
109 std::invocable<object_reader&> auto const& f) 109 std::invocable<object_reader&> auto const& f)
110 -> decltype(f(std::declval<object_reader&>())) 110 -> decltype(f(std::declval<object_reader&>()))
111{ 111{
112 auto r = object_reader{jv, loc}; 112 auto r = object_reader{jv, loc};
113 auto&& v = f(r); 113 auto&& v = f(r);
@@ -116,109 +116,110 @@ auto as_checked_object(
116} 116}
117 117
118auto tag_invoke( 118auto tag_invoke(
119 json::value_to_tag<rwgps> const&, json::value const& jv, location const& loc) 119 json::value_to_tag<rwgps> const&, json::value const& jv,
120 -> rwgps 120 location const& loc) -> rwgps
121{ 121{
122 return as_checked_object( 122 return as_checked_object(
123 jv, loc, 123 jv, loc,
124 [](object_reader& r) -> rwgps 124 [](object_reader& r) -> rwgps
125 { 125 {
126 return { 126 return {
127 .api_key = r.expect_at<std::string>("api_key"), 127 .api_key = r.expect_at<std::string>("api_key"),
128 .auth_token = r.expect_at<std::string>("auth_token"), 128 .auth_token = r.expect_at<std::string>("auth_token"),
129 }; 129 };
130 }); 130 });
131} 131}
132 132
133auto tag_invoke( 133auto tag_invoke(
134 json::value_to_tag<situations> const&, json::value const& jv, 134 json::value_to_tag<situations> const&, json::value const& jv,
135 location const& loc) -> situations 135 location const& loc) -> situations
136{ 136{
137 return as_checked_object( 137 return as_checked_object(
138 jv, loc, 138 jv, loc,
139 [](object_reader& r) -> situations 139 [](object_reader& r) -> situations
140 { 140 {
141 return { 141 return {
142 .datex2_filename = r.expect_at<std::string>("datex2_filename"), 142 .datex2_filename = r.expect_at<std::string>("datex2_filename"),
143 }; 143 };
144 }); 144 });
145} 145}
146 146
147auto tag_invoke( 147auto tag_invoke(
148 json::value_to_tag<database> const&, json::value const& jv, 148 json::value_to_tag<database> const&, json::value const& jv,
149 location const& loc) -> database 149 location const& loc) -> database
150{ 150{
151 return as_checked_object( 151 return as_checked_object(
152 jv, loc, 152 jv, loc,
153 [](object_reader& r) -> database 153 [](object_reader& r) -> database
154 { 154 {
155 return { 155 return {
156 .sqlite3_filename = r.expect_at<std::string>("sqlite3_filename"), 156 .sqlite3_filename = r.expect_at<std::string>("sqlite3_filename"),
157 }; 157 };
158 }); 158 });
159} 159}
160 160
161auto tag_invoke( 161auto tag_invoke(
162 json::value_to_tag<http_server> const&, json::value const& jv, 162 json::value_to_tag<http_server> const&, json::value const& jv,
163 location const& loc) -> http_server 163 location const& loc) -> http_server
164{ 164{
165 return as_checked_object( 165 return as_checked_object(
166 jv, loc, 166 jv, loc,
167 [](object_reader& r) -> http_server 167 [](object_reader& r) -> http_server
168 { 168 {
169 return { 169 return {
170 .lax_cors = r.expect_at<bool>("lax_cors"), 170 .lax_cors = r.expect_at<bool>("lax_cors"),
171 }; 171 };
172 }); 172 });
173} 173}
174 174
175auto tag_invoke( 175auto tag_invoke(
176 json::value_to_tag<logger> const&, json::value const& jv, location const& loc) 176 json::value_to_tag<logger> const&, json::value const& jv,
177 -> logger 177 location const& loc) -> logger
178{ 178{
179 return as_checked_object( 179 return as_checked_object(
180 jv, loc, 180 jv, loc,
181 [obj_loc = loc](object_reader& r) -> logger 181 [obj_loc = loc](object_reader& r) -> logger
182 { 182 {
183 auto const level_str = r.expect_at<std::string>("level"); 183 auto const level_str = r.expect_at<std::string>("level");
184 auto level = log::level{}; 184 auto level = log::level{};
185 if (level_str == "debug") 185 if (level_str == "debug")
186 level = log::level::debug; 186 level = log::level::debug;
187 else if (level_str == "info") 187 else if (level_str == "info")
188 level = log::level::info; 188 level = log::level::info;
189 else if (level_str == "warn") 189 else if (level_str == "warn")
190 level = log::level::warn; 190 level = log::level::warn;
191 else if (level_str == "error") 191 else if (level_str == "error")
192 level = log::level::error; 192 level = log::level::error;
193 else 193 else
194 throw std::runtime_error{std::format( 194 throw std::runtime_error{std::format(
195 "unable to parse log level {:?} at {}: expected one " 195 "unable to parse log level {:?} at {}: expected one "
196 "of {{debug, info, warn, error}}", 196 "of {{debug, info, warn, error}}",
197 level_str, obj_loc.sub("level").to_string())}; 197 level_str, obj_loc.sub("level").to_string())};
198 return logger{level}; 198 return logger{level};
199 }); 199 });
200} 200}
201 201
202auto json_value_to_app(json::value const& jv) -> app 202auto json_value_to_app(json::value const& jv) -> app
203{ 203{
204 return as_checked_object( 204 return as_checked_object(
205 jv, location{}, 205 jv, location{},
206 [](object_reader& r) -> app 206 [](object_reader& r) -> app
207 { 207 {
208 return { 208 return {
209 .rwgps = r.expect_at<rwgps>("rwgps"), 209 .rwgps = r.expect_at<rwgps>("rwgps"),
210 .situations = r.expect_at<situations>("situations"), 210 .situations = r.expect_at<situations>("situations"),
211 .database = r.expect_at<database>("database"), 211 .database = r.expect_at<database>("database"),
212 .http_server = r.expect_at<http_server>("http_server"), 212 .http_server = r.expect_at<http_server>("http_server"),
213 .logger = r.expect_at<logger>("logger"), 213 .logger = r.expect_at<logger>("logger"),
214 }; 214 };
215 }); 215 });
216} 216}
217 217
218auto load_file(std::string const& filename) -> app 218auto load_file(std::string const& filename) -> app
219{ 219{
220 auto f = 220 auto f = std::ifstream{
221 std::ifstream{filename}; // TODO: ensure that we are opening in binary mode? 221 filename
222 }; // TODO: ensure that we are opening in binary mode?
222 if (!f.is_open()) 223 if (!f.is_open())
223 throw std::runtime_error{std::format("failed to open {}", filename)}; 224 throw std::runtime_error{std::format("failed to open {}", filename)};
224 auto jv = json::value{}; 225 auto jv = json::value{};
@@ -229,7 +230,7 @@ auto load_file(std::string const& filename) -> app
229 catch (boost::system::system_error const& e) 230 catch (boost::system::system_error const& e)
230 { 231 {
231 throw std::runtime_error{std::format( 232 throw std::runtime_error{std::format(
232 "failed to parse: {}", e.code().message())}; 233 "failed to parse: {}", e.code().message())};
233 } 234 }
234 return json_value_to_app(jv); 235 return json_value_to_app(jv);
235} 236}
diff --git a/server/src/database.cppm b/server/src/database.cppm
index c31d610..86ed0d7 100644
--- a/server/src/database.cppm
+++ b/server/src/database.cppm
@@ -31,15 +31,15 @@ export auto open(std::string const& filename) -> std::shared_ptr<connection>
31 if (version != expected_database_version) 31 if (version != expected_database_version)
32 { 32 {
33 throw std::runtime_error{std::format( 33 throw std::runtime_error{std::format(
34 "database migration version ({}) does not match expected " 34 "database migration version ({}) does not match expected "
35 "version ({}), consider running migrations", 35 "version ({}), consider running migrations",
36 *version, expected_database_version)}; 36 *version, expected_database_version)};
37 } 37 }
38 } 38 }
39 catch (std::exception const& e) 39 catch (std::exception const& e)
40 { 40 {
41 throw std::runtime_error{std::format( 41 throw std::runtime_error{std::format(
42 "failed to query database version: {}", e.what())}; 42 "failed to query database version: {}", e.what())};
43 } 43 }
44 return std::shared_ptr<connection>{new connection{std::move(dbc)}}; 44 return std::shared_ptr<connection>{new connection{std::move(dbc)}};
45} 45}
diff --git a/server/src/datex2.cppm b/server/src/datex2.cppm
index 7c20347..d7b4921 100644
--- a/server/src/datex2.cppm
+++ b/server/src/datex2.cppm
@@ -31,7 +31,7 @@ export struct situation
31{ 31{
32 std::string id; 32 std::string id;
33 std::optional<geo::point> location = 33 std::optional<geo::point> location =
34 std::nullopt; // as shown on the map, not used for querying 34 std::nullopt; // as shown on the map, not used for querying
35 std::vector<std::string> comments = {}; 35 std::vector<std::string> comments = {};
36 std::vector<std::shared_ptr<road_closure>> road_closures = {}; 36 std::vector<std::shared_ptr<road_closure>> road_closures = {};
37}; 37};
@@ -54,13 +54,13 @@ export class loader
54{ 54{
55 // ETRS 89 (EPSG:4258) -> WGS 84 (EPSG:4326) 55 // ETRS 89 (EPSG:4258) -> WGS 84 (EPSG:4326)
56 bgeo::srs::transformation< 56 bgeo::srs::transformation<
57 bgeo::srs::static_epsg<4258>, bgeo::srs::static_epsg<4326>> 57 bgeo::srs::static_epsg<4258>, bgeo::srs::static_epsg<4326>>
58 etrs89_to_wgs84_{}; 58 etrs89_to_wgs84_{};
59 59
60 std::multiset<std::string> warnings_; 60 std::multiset<std::string> warnings_;
61 61
62 auto add_location_from_xml(road_closure& rc, pugi::xml_node const& loc_xml) 62 auto add_location_from_xml(road_closure& rc, pugi::xml_node const& loc_xml)
63 -> void 63 -> void
64 { 64 {
65 auto loc_xml_type = std::string_view{loc_xml.attribute("xsi:type").value()}; 65 auto loc_xml_type = std::string_view{loc_xml.attribute("xsi:type").value()};
66 if (loc_xml_type == "loc:ItineraryByIndexedLocations") 66 if (loc_xml_type == "loc:ItineraryByIndexedLocations")
@@ -79,15 +79,15 @@ export class loader
79 return; 79 return;
80 80
81 auto const srs_name = 81 auto const srs_name =
82 std::string_view{loc_gml_xml.attribute("srsName").value()}; 82 std::string_view{loc_gml_xml.attribute("srsName").value()};
83 if (srs_name != "WGS 84"sv) 83 if (srs_name != "WGS 84"sv)
84 { 84 {
85 warnings_.insert( 85 warnings_.insert(
86 std::format("don't now how to handle the CRS {}", srs_name)); 86 std::format("don't now how to handle the CRS {}", srs_name));
87 return; 87 return;
88 } 88 }
89 auto const pos_list_str = 89 auto const pos_list_str =
90 std::string_view{loc_gml_xml.child_value("loc:posList")}; 90 std::string_view{loc_gml_xml.child_value("loc:posList")};
91 // lat1 long1 lat2 long2 ... lat(n-1) long(n-1) latn longn 91 // lat1 long1 lat2 long2 ... lat(n-1) long(n-1) latn longn
92 92
93 auto ls = std::make_shared<geo::linestring>(); 93 auto ls = std::make_shared<geo::linestring>();
@@ -97,13 +97,13 @@ export class loader
97 for (auto const lat_or_long_str : std::views::split(pos_list_str, " "sv)) 97 for (auto const lat_or_long_str : std::views::split(pos_list_str, " "sv))
98 { 98 {
99 auto mlat_or_long = 99 auto mlat_or_long =
100 util::parse_double(std::string_view{lat_or_long_str}); 100 util::parse_double(std::string_view{lat_or_long_str});
101 if (!mlat_or_long) 101 if (!mlat_or_long)
102 { 102 {
103 warnings_.insert( 103 warnings_.insert(
104 std::format( 104 std::format(
105 "failed to parse coordinate {:?}", 105 "failed to parse coordinate {:?}",
106 std::string_view{lat_or_long_str})); 106 std::string_view{lat_or_long_str}));
107 return; 107 return;
108 } 108 }
109 109
@@ -131,7 +131,7 @@ export class loader
131 else if (loc_xml_type == "loc:PointLocation") 131 else if (loc_xml_type == "loc:PointLocation")
132 { 132 {
133 auto const& coords_xml = 133 auto const& coords_xml =
134 loc_xml.child("loc:pointByCoordinates").child("loc:pointCoordinates"); 134 loc_xml.child("loc:pointByCoordinates").child("loc:pointCoordinates");
135 if (!coords_xml) 135 if (!coords_xml)
136 return; 136 return;
137 137
@@ -157,28 +157,28 @@ export class loader
157 else 157 else
158 { 158 {
159 warnings_.insert( 159 warnings_.insert(
160 std::format( 160 std::format(
161 "don't know how to hande location of type {}, ignoring", 161 "don't know how to hande location of type {}, ignoring",
162 loc_xml.attribute("xsi:type").value())); 162 loc_xml.attribute("xsi:type").value()));
163 return; 163 return;
164 } 164 }
165 } 165 }
166 166
167 auto handle_road_or_carriageway_or_lane_management( 167 auto handle_road_or_carriageway_or_lane_management(
168 pugi::xml_node const& record_xml, std::weak_ptr<situation> parent) 168 pugi::xml_node const& record_xml, std::weak_ptr<situation> parent)
169 -> std::optional<std::shared_ptr<road_closure>> 169 -> std::optional<std::shared_ptr<road_closure>>
170 { 170 {
171 auto const type = 171 auto const type =
172 std::string_view{record_xml 172 std::string_view{record_xml
173 .child("sit:roadOrCarriagewayOrLaneManagementType") 173 .child("sit:roadOrCarriagewayOrLaneManagementType")
174 .child_value()}; 174 .child_value()};
175 if (type != "carriagewayClosures" && type != "roadClosed") 175 if (type != "carriagewayClosures" && type != "roadClosed")
176 // TODO: checken of er nog andere types fietsers de doorgang zouden 176 // TODO: checken of er nog andere types fietsers de doorgang zouden
177 // kunnen blokkeren? 177 // kunnen blokkeren?
178 return std::nullopt; 178 return std::nullopt;
179 179
180 auto const& restricted_vehicle_types_xml = 180 auto const& restricted_vehicle_types_xml =
181 record_xml.child("sit:forVehiclesWithCharacteristicsOf"); 181 record_xml.child("sit:forVehiclesWithCharacteristicsOf");
182 bool likely_restriction_for_bikes = restricted_vehicle_types_xml.empty(); 182 bool likely_restriction_for_bikes = restricted_vehicle_types_xml.empty();
183 for (auto const vehicle_type_xml : 183 for (auto const vehicle_type_xml :
184 restricted_vehicle_types_xml.children("com:vehicleType")) 184 restricted_vehicle_types_xml.children("com:vehicleType"))
@@ -199,20 +199,20 @@ export class loader
199 auto const& validity_xml = record_xml.child("sit:validity"); 199 auto const& validity_xml = record_xml.child("sit:validity");
200 if (validity_xml 200 if (validity_xml
201 && validity_xml.child_value("com:validityStatus") 201 && validity_xml.child_value("com:validityStatus")
202 == "definedByValidityTimeSpec"sv) 202 == "definedByValidityTimeSpec"sv)
203 { 203 {
204 auto const& validity_spec_xml = 204 auto const& validity_spec_xml =
205 validity_xml.child("com:validityTimeSpecification"); 205 validity_xml.child("com:validityTimeSpecification");
206 206
207 auto valid_periods = std::vector<time::period>{}; 207 auto valid_periods = std::vector<time::period>{};
208 auto exception_periods = std::vector<time::period>{}; 208 auto exception_periods = std::vector<time::period>{};
209 209
210 // TODO: com:overallEndTime may be missing (according to the DATEX 210 // TODO: com:overallEndTime may be missing (according to the DATEX
211 // II v3 data model) 211 // II v3 data model)
212 auto const overall_start_time = 212 auto const overall_start_time = parse_timestamp(
213 parse_timestamp(validity_spec_xml.child_value("com:overallStartTime")); 213 validity_spec_xml.child_value("com:overallStartTime"));
214 auto const overall_end_time = 214 auto const overall_end_time =
215 parse_timestamp(validity_spec_xml.child_value("com:overallEndTime")); 215 parse_timestamp(validity_spec_xml.child_value("com:overallEndTime"));
216 if (overall_start_time && overall_end_time 216 if (overall_start_time && overall_end_time
217 && *overall_start_time < *overall_end_time) 217 && *overall_start_time < *overall_end_time)
218 { 218 {
@@ -221,10 +221,10 @@ export class loader
221 for (auto const valid_period_xml : 221 for (auto const valid_period_xml :
222 validity_xml.children("com:validPeriod")) 222 validity_xml.children("com:validPeriod"))
223 { 223 {
224 auto const start_of_period = 224 auto const start_of_period = parse_timestamp(
225 parse_timestamp(valid_period_xml.child_value("com:startOfPeriod")); 225 valid_period_xml.child_value("com:startOfPeriod"));
226 auto const end_of_period = 226 auto const end_of_period =
227 parse_timestamp(valid_period_xml.child_value("com:endOfPeriod")); 227 parse_timestamp(valid_period_xml.child_value("com:endOfPeriod"));
228 if (start_of_period && end_of_period 228 if (start_of_period && end_of_period
229 && *start_of_period < *end_of_period) 229 && *start_of_period < *end_of_period)
230 { 230 {
@@ -235,9 +235,9 @@ export class loader
235 validity_xml.children("com:exceptionPeriod")) 235 validity_xml.children("com:exceptionPeriod"))
236 { 236 {
237 auto const start_of_period = parse_timestamp( 237 auto const start_of_period = parse_timestamp(
238 exception_period_xml.child_value("com:startOfPeriod")); 238 exception_period_xml.child_value("com:startOfPeriod"));
239 auto const end_of_period = parse_timestamp( 239 auto const end_of_period = parse_timestamp(
240 exception_period_xml.child_value("com:endOfPeriod")); 240 exception_period_xml.child_value("com:endOfPeriod"));
241 if (start_of_period && end_of_period 241 if (start_of_period && end_of_period
242 && *start_of_period < *end_of_period) 242 && *start_of_period < *end_of_period)
243 { 243 {
@@ -246,19 +246,19 @@ export class loader
246 } 246 }
247 247
248 validity = 248 validity =
249 time::period_seq{valid_periods.begin(), valid_periods.end()}.except( 249 time::period_seq{valid_periods.begin(), valid_periods.end()}.except(
250 time::period_seq{ 250 time::period_seq{
251 exception_periods.begin(), exception_periods.end() 251 exception_periods.begin(), exception_periods.end()
252 }); 252 });
253 } 253 }
254 else 254 else
255 { 255 {
256 warnings_.insert( 256 warnings_.insert(
257 std::format( 257 std::format(
258 "invalid overall start / end time (start time: {}, end " 258 "invalid overall start / end time (start time: {}, end "
259 "time: {})", 259 "time: {})",
260 validity_spec_xml.child_value("com:overallStartTime"), 260 validity_spec_xml.child_value("com:overallStartTime"),
261 validity_spec_xml.child_value("com:overallEndTime"))); 261 validity_spec_xml.child_value("com:overallEndTime")));
262 return std::nullopt; 262 return std::nullopt;
263 } 263 }
264 } 264 }
@@ -272,7 +272,7 @@ export class loader
272 272
273public: 273public:
274 [[nodiscard]] auto load_situation_publication(std::string const& filename) 274 [[nodiscard]] auto load_situation_publication(std::string const& filename)
275 -> situation_publication 275 -> situation_publication
276 { 276 {
277 auto doc = pugi::xml_document{}; 277 auto doc = pugi::xml_document{};
278 if (auto result = doc.load_file(filename.c_str()); !result) 278 if (auto result = doc.load_file(filename.c_str()); !result)
@@ -281,7 +281,7 @@ public:
281 } 281 }
282 auto payload_xml = doc.child("mc:messageContainer").child("mc:payload"); 282 auto payload_xml = doc.child("mc:messageContainer").child("mc:payload");
283 auto mpublication_time = 283 auto mpublication_time =
284 parse_timestamp(payload_xml.child_value("com:publicationTime")); 284 parse_timestamp(payload_xml.child_value("com:publicationTime"));
285 if (!mpublication_time) 285 if (!mpublication_time)
286 throw std::runtime_error{ 286 throw std::runtime_error{
287 "provided publication does not name publication time" 287 "provided publication does not name publication time"
@@ -302,7 +302,7 @@ public:
302 for (auto const record_xml : sit_xml.children("sit:situationRecord")) 302 for (auto const record_xml : sit_xml.children("sit:situationRecord"))
303 { 303 {
304 auto const record_type = 304 auto const record_type =
305 std::string_view{record_xml.attribute("xsi:type").value()}; 305 std::string_view{record_xml.attribute("xsi:type").value()};
306 auto const primary_record_types = std::unordered_set<std::string_view>{ 306 auto const primary_record_types = std::unordered_set<std::string_view>{
307 "sit:Roadworks", 307 "sit:Roadworks",
308 /* { */ "sit:MaintenanceWorks", 308 /* { */ "sit:MaintenanceWorks",
@@ -320,8 +320,8 @@ public:
320 320
321 if (record_type == "sit:RoadOrCarriagewayOrLaneManagement") 321 if (record_type == "sit:RoadOrCarriagewayOrLaneManagement")
322 { 322 {
323 if (auto rc = 323 if (auto rc = handle_road_or_carriageway_or_lane_management(
324 handle_road_or_carriageway_or_lane_management(record_xml, sit)) 324 record_xml, sit))
325 { 325 {
326 sit->road_closures.push_back(*rc); 326 sit->road_closures.push_back(*rc);
327 } 327 }
@@ -335,10 +335,10 @@ public:
335 // (comment_xml.child_value("sit:commentType") 335 // (comment_xml.child_value("sit:commentType")
336 // == "internalNote"sv) { 336 // == "internalNote"sv) {
337 auto candidate = std::optional<std::pair< 337 auto candidate = std::optional<std::pair<
338 std::string_view, std::string_view>>{}; // (text, language) 338 std::string_view, std::string_view>>{}; // (text, language)
339 for (auto const comment_value_xml : comment_xml.child("sit:comment") 339 for (auto const comment_value_xml : comment_xml.child("sit:comment")
340 .child("com:values") 340 .child("com:values")
341 .children("com:value")) 341 .children("com:value"))
342 { 342 {
343 if (!candidate 343 if (!candidate
344 || comment_value_xml.attribute("lang").value() == "nl"sv 344 || comment_value_xml.attribute("lang").value() == "nl"sv
@@ -346,8 +346,8 @@ public:
346 && comment_value_xml.attribute("lang").value() == "nl"sv)) 346 && comment_value_xml.attribute("lang").value() == "nl"sv))
347 { 347 {
348 candidate = std::make_pair( 348 candidate = std::make_pair(
349 comment_value_xml.child_value(), 349 comment_value_xml.child_value(),
350 comment_value_xml.attribute("lang").value()); 350 comment_value_xml.attribute("lang").value());
351 } 351 }
352 } 352 }
353 if (candidate) 353 if (candidate)
@@ -355,7 +355,7 @@ public:
355 auto already_present = false; 355 auto already_present = false;
356 for (auto const& comment : sit->comments) 356 for (auto const& comment : sit->comments)
357 already_present = 357 already_present =
358 already_present || comment == candidate->first; 358 already_present || comment == candidate->first;
359 if (!already_present) 359 if (!already_present)
360 { 360 {
361 sit->comments.emplace_back(candidate->first); 361 sit->comments.emplace_back(candidate->first);
@@ -365,19 +365,19 @@ public:
365 } 365 }
366 366
367 if (auto const location_ref_xml = 367 if (auto const location_ref_xml =
368 record_xml.child("sit:locationReference")) 368 record_xml.child("sit:locationReference"))
369 { 369 {
370 if (location_ref_xml.attribute("xsi:type").value() 370 if (location_ref_xml.attribute("xsi:type").value()
371 == "loc:PointLocation"sv) 371 == "loc:PointLocation"sv)
372 { 372 {
373 if (auto const coords_xml = 373 if (auto const coords_xml =
374 location_ref_xml.child("loc:pointByCoordinates") 374 location_ref_xml.child("loc:pointByCoordinates")
375 .child("loc:pointCoordinates")) 375 .child("loc:pointCoordinates"))
376 { 376 {
377 auto const mlat = 377 auto const mlat =
378 util::parse_double(coords_xml.child_value("loc:latitude")); 378 util::parse_double(coords_xml.child_value("loc:latitude"));
379 auto const mlon = 379 auto const mlon =
380 util::parse_double(coords_xml.child_value("loc:longitude")); 380 util::parse_double(coords_xml.child_value("loc:longitude"));
381 if (mlat && mlon) 381 if (mlat && mlon)
382 { 382 {
383 // Vaag genoeg zegt NDW dat het hier om WGS 383 // Vaag genoeg zegt NDW dat het hier om WGS
diff --git a/server/src/geo.cppm b/server/src/geo.cppm
index 9101af8..b599ad4 100644
--- a/server/src/geo.cppm
+++ b/server/src/geo.cppm
@@ -9,15 +9,15 @@ export namespace bgeo = boost::geometry;
9export namespace routemon::geo { 9export namespace routemon::geo {
10 10
11using point = 11using point =
12 bgeo::model::point<double, 2, bgeo::cs::spherical_equatorial<bgeo::degree>>; 12 bgeo::model::point<double, 2, bgeo::cs::spherical_equatorial<bgeo::degree>>;
13using linestring = bgeo::model::linestring<point>; 13using linestring = bgeo::model::linestring<point>;
14using box = bgeo::model::box<point>; 14using box = bgeo::model::box<point>;
15using stype = bgeo::srs::spheroid<double>; 15using stype = bgeo::srs::spheroid<double>;
16using vincenty_strategy = bgeo::strategy::distance::vincenty<stype>; 16using vincenty_strategy = bgeo::strategy::distance::vincenty<stype>;
17 17
18auto split_linestring_with_overlap_segments( 18auto split_linestring_with_overlap_segments(
19 linestring const& ls, double max_split_distance_m, 19 linestring const& ls, double max_split_distance_m,
20 std::vector<linestring>& append_to) -> void 20 std::vector<linestring>& append_to) -> void
21{ 21{
22 if (bgeo::is_empty(ls)) 22 if (bgeo::is_empty(ls))
23 return; 23 return;
@@ -26,23 +26,23 @@ auto split_linestring_with_overlap_segments(
26 auto current_ls_length = 0.0; 26 auto current_ls_length = 0.0;
27 auto previous = std::optional<point>{}; 27 auto previous = std::optional<point>{};
28 bgeo::for_each_point( 28 bgeo::for_each_point(
29 ls, 29 ls,
30 [&](point p) -> void 30 [&](point p) -> void
31 {
32 bgeo::append(current_ls, p);
33 if (previous)
34 { 31 {
35 auto d = bgeo::distance(*previous, p, vincenty_strategy()); 32 bgeo::append(current_ls, p);
36 current_ls_length += d; 33 if (previous)
37 if (current_ls_length > max_split_distance_m)
38 { 34 {
39 append_to.push_back(std::move(current_ls)); 35 auto d = bgeo::distance(*previous, p, vincenty_strategy());
40 current_ls = linestring{*previous, p}; 36 current_ls_length += d;
41 current_ls_length = d; 37 if (current_ls_length > max_split_distance_m)
38 {
39 append_to.push_back(std::move(current_ls));
40 current_ls = linestring{*previous, p};
41 current_ls_length = d;
42 }
42 } 43 }
43 } 44 previous = p;
44 previous = p; 45 });
45 });
46 append_to.emplace_back(std::move(current_ls)); 46 append_to.emplace_back(std::move(current_ls));
47} 47}
48 48
diff --git a/server/src/gpx.cpp b/server/src/gpx.cpp
index f70c133..078248f 100644
--- a/server/src/gpx.cpp
+++ b/server/src/gpx.cpp
@@ -22,7 +22,7 @@ auto qname(std::string_view local) -> xml::qname_view
22} 22}
23 23
24auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs) 24auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs)
25 -> xml::parser<geo::point> 25 -> xml::parser<geo::point>
26{ 26{
27 auto parse_xml_double = [](std::string_view sv) -> std::optional<double> 27 auto parse_xml_double = [](std::string_view sv) -> std::optional<double>
28 { return util::parse_double(sv, std::chars_format::fixed); }; 28 { return util::parse_double(sv, std::chars_format::fixed); };
@@ -48,11 +48,11 @@ auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs)
48} 48}
49 49
50auto parse_trkseg(xml::executor_ref e, xml::attribute_view) 50auto parse_trkseg(xml::executor_ref e, xml::attribute_view)
51 -> xml::parser<track_segment> 51 -> xml::parser<track_segment>
52{ 52{
53 auto s = track_segment{}; 53 auto s = track_segment{};
54 while (auto mwpt = 54 while (auto mwpt = co_await allow_element(
55 co_await allow_element(e, qname("trkpt"), xml::hohalo<parse_wpt>())) 55 e, qname("trkpt"), xml::hohalo<parse_wpt>()))
56 bgeo::append(s.waypoints, *mwpt); 56 bgeo::append(s.waypoints, *mwpt);
57 co_return std::move(s); 57 co_return std::move(s);
58} 58}
@@ -61,20 +61,20 @@ auto parse_trk(xml::executor_ref e, xml::attribute_view) -> xml::parser<track>
61{ 61{
62 auto t = track{}; 62 auto t = track{};
63 t.name = co_await allow_element( 63 t.name = co_await allow_element(
64 e, qname("name"), xml::hohalo<xml::read_string_contents>()); 64 e, qname("name"), xml::hohalo<xml::read_string_contents>());
65 co_await allow_element( 65 co_await allow_element(
66 e, qname("cmt"), xml::hohalo<xml::ignore_element_contents>()); 66 e, qname("cmt"), xml::hohalo<xml::ignore_element_contents>());
67 t.desc = co_await allow_element( 67 t.desc = co_await allow_element(
68 e, qname("desc"), xml::hohalo<xml::read_string_contents>()); 68 e, qname("desc"), xml::hohalo<xml::read_string_contents>());
69 co_await xml::ignore_contents(e, /* until */ qname("trkseg")); 69 co_await xml::ignore_contents(e, /* until */ qname("trkseg"));
70 while (auto mseg = co_await allow_element( 70 while (auto mseg = co_await allow_element(
71 e, qname("trkseg"), xml::hohalo<parse_trkseg>())) 71 e, qname("trkseg"), xml::hohalo<parse_trkseg>()))
72 t.segments.push_back(std::move(*mseg)); 72 t.segments.push_back(std::move(*mseg));
73 co_return std::move(t); 73 co_return std::move(t);
74} 74}
75 75
76auto parse_gpx(xml::executor_ref e, xml::attribute_view attrs) 76auto parse_gpx(xml::executor_ref e, xml::attribute_view attrs)
77 -> xml::parser<file> 77 -> xml::parser<file>
78{ 78{
79 auto f = file{}; 79 auto f = file{};
80 if (attrs.lookup(qname("version")) != "1.0"sv) 80 if (attrs.lookup(qname("version")) != "1.0"sv)
@@ -85,13 +85,13 @@ auto parse_gpx(xml::executor_ref e, xml::attribute_view attrs)
85 throw std::runtime_error{"expected GPX file to have creator"}; 85 throw std::runtime_error{"expected GPX file to have creator"};
86 86
87 f.meta.name = co_await allow_element( 87 f.meta.name = co_await allow_element(
88 e, qname("name"), xml::hohalo<xml::read_string_contents>()); 88 e, qname("name"), xml::hohalo<xml::read_string_contents>());
89 f.meta.desc = co_await allow_element( 89 f.meta.desc = co_await allow_element(
90 e, qname("desc"), xml::hohalo<xml::read_string_contents>()); 90 e, qname("desc"), xml::hohalo<xml::read_string_contents>());
91 91
92 co_await xml::ignore_contents(e, /* until */ qname("trk")); 92 co_await xml::ignore_contents(e, /* until */ qname("trk"));
93 while (auto mtrk = 93 while (auto mtrk =
94 co_await allow_element(e, qname("trk"), xml::hohalo<parse_trk>())) 94 co_await allow_element(e, qname("trk"), xml::hohalo<parse_trk>()))
95 f.tracks.push_back(std::move(*mtrk)); 95 f.tracks.push_back(std::move(*mtrk));
96 co_await xml::ignore_contents(e); 96 co_await xml::ignore_contents(e);
97 97
@@ -109,19 +109,19 @@ auto qname(std::string_view local) -> xml::qname_view
109} 109}
110 110
111auto parse_metadata(xml::executor_ref e, xml::attribute_view) 111auto parse_metadata(xml::executor_ref e, xml::attribute_view)
112 -> xml::parser<metadata> 112 -> xml::parser<metadata>
113{ 113{
114 auto meta = metadata{}; 114 auto meta = metadata{};
115 meta.name = co_await allow_element( 115 meta.name = co_await allow_element(
116 e, qname("name"), xml::hohalo<xml::read_string_contents>()); 116 e, qname("name"), xml::hohalo<xml::read_string_contents>());
117 meta.desc = co_await allow_element( 117 meta.desc = co_await allow_element(
118 e, qname("desc"), xml::hohalo<xml::read_string_contents>()); 118 e, qname("desc"), xml::hohalo<xml::read_string_contents>());
119 co_await xml::ignore_contents(e); 119 co_await xml::ignore_contents(e);
120 co_return meta; 120 co_return meta;
121} 121}
122 122
123auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs) 123auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs)
124 -> xml::parser<geo::point> 124 -> xml::parser<geo::point>
125{ 125{
126 auto parse_xml_double = [](std::string_view sv) -> std::optional<double> 126 auto parse_xml_double = [](std::string_view sv) -> std::optional<double>
127 { return util::parse_double(sv, std::chars_format::fixed); }; 127 { return util::parse_double(sv, std::chars_format::fixed); };
@@ -147,14 +147,14 @@ auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs)
147} 147}
148 148
149auto parse_trkseg(xml::executor_ref e, xml::attribute_view) 149auto parse_trkseg(xml::executor_ref e, xml::attribute_view)
150 -> xml::parser<track_segment> 150 -> xml::parser<track_segment>
151{ 151{
152 auto s = track_segment{}; 152 auto s = track_segment{};
153 while (auto mwpt = 153 while (auto mwpt = co_await allow_element(
154 co_await allow_element(e, qname("trkpt"), xml::hohalo<parse_wpt>())) 154 e, qname("trkpt"), xml::hohalo<parse_wpt>()))
155 bgeo::append(s.waypoints, *mwpt); 155 bgeo::append(s.waypoints, *mwpt);
156 co_await allow_element( 156 co_await allow_element(
157 e, qname("extensions"), xml::hohalo<xml::ignore_element_contents>()); 157 e, qname("extensions"), xml::hohalo<xml::ignore_element_contents>());
158 co_return std::move(s); 158 co_return std::move(s);
159} 159}
160 160
@@ -162,20 +162,20 @@ auto parse_trk(xml::executor_ref e, xml::attribute_view) -> xml::parser<track>
162{ 162{
163 auto t = track{}; 163 auto t = track{};
164 t.name = co_await allow_element( 164 t.name = co_await allow_element(
165 e, qname("name"), xml::hohalo<xml::read_string_contents>()); 165 e, qname("name"), xml::hohalo<xml::read_string_contents>());
166 co_await allow_element( 166 co_await allow_element(
167 e, qname("cmt"), xml::hohalo<xml::ignore_element_contents>()); 167 e, qname("cmt"), xml::hohalo<xml::ignore_element_contents>());
168 t.desc = co_await allow_element( 168 t.desc = co_await allow_element(
169 e, qname("desc"), xml::hohalo<xml::read_string_contents>()); 169 e, qname("desc"), xml::hohalo<xml::read_string_contents>());
170 co_await xml::ignore_contents(e, /* until */ qname("trkseg")); 170 co_await xml::ignore_contents(e, /* until */ qname("trkseg"));
171 while (auto mseg = co_await allow_element( 171 while (auto mseg = co_await allow_element(
172 e, qname("trkseg"), xml::hohalo<parse_trkseg>())) 172 e, qname("trkseg"), xml::hohalo<parse_trkseg>()))
173 t.segments.push_back(std::move(*mseg)); 173 t.segments.push_back(std::move(*mseg));
174 co_return std::move(t); 174 co_return std::move(t);
175} 175}
176 176
177auto parse_gpx(xml::executor_ref e, xml::attribute_view attrs) 177auto parse_gpx(xml::executor_ref e, xml::attribute_view attrs)
178 -> xml::parser<file> 178 -> xml::parser<file>
179{ 179{
180 auto f = file{}; 180 auto f = file{};
181 if (attrs.lookup(qname("version")) != "1.1"sv) 181 if (attrs.lookup(qname("version")) != "1.1"sv)
@@ -186,14 +186,14 @@ auto parse_gpx(xml::executor_ref e, xml::attribute_view attrs)
186 throw std::runtime_error{"expected GPX file to have creator"}; 186 throw std::runtime_error{"expected GPX file to have creator"};
187 187
188 if (auto mmeta = co_await allow_element( 188 if (auto mmeta = co_await allow_element(
189 e, qname("metadata"), xml::hohalo<parse_metadata>())) 189 e, qname("metadata"), xml::hohalo<parse_metadata>()))
190 f.meta = *mmeta; 190 f.meta = *mmeta;
191 co_await xml::ignore_contents(e, /* until */ qname("trk")); 191 co_await xml::ignore_contents(e, /* until */ qname("trk"));
192 while (auto mtrk = 192 while (auto mtrk =
193 co_await allow_element(e, qname("trk"), xml::hohalo<parse_trk>())) 193 co_await allow_element(e, qname("trk"), xml::hohalo<parse_trk>()))
194 f.tracks.push_back(std::move(*mtrk)); 194 f.tracks.push_back(std::move(*mtrk));
195 co_await allow_element( 195 co_await allow_element(
196 e, qname("extensions"), xml::hohalo<xml::ignore_element_contents>()); 196 e, qname("extensions"), xml::hohalo<xml::ignore_element_contents>());
197 197
198 co_return std::move(f); 198 co_return std::move(f);
199} 199}
@@ -205,14 +205,14 @@ auto parse_file(xml::executor_ref e) -> xml::parser<file>
205 auto decl = co_await expect_event<xml::xml_decl_event>(e); 205 auto decl = co_await expect_event<xml::xml_decl_event>(e);
206 if (decl.version != "1.0"sv) 206 if (decl.version != "1.0"sv)
207 throw std::runtime_error{std::format( 207 throw std::runtime_error{std::format(
208 "unsupported XML version, got {}", std::string_view{decl.version})}; 208 "unsupported XML version, got {}", std::string_view{decl.version})};
209 if (decl.encoding != "UTF-8"sv) 209 if (decl.encoding != "UTF-8"sv)
210 throw std::runtime_error{"unsupported encoding"}; 210 throw std::runtime_error{"unsupported encoding"};
211 if (auto mf = co_await allow_element( 211 if (auto mf = co_await allow_element(
212 e, v10::qname("gpx"), xml::hohalo<v10::parse_gpx>())) 212 e, v10::qname("gpx"), xml::hohalo<v10::parse_gpx>()))
213 co_return std::move(*mf); 213 co_return std::move(*mf);
214 if (auto mf = co_await allow_element( 214 if (auto mf = co_await allow_element(
215 e, v11::qname("gpx"), xml::hohalo<v11::parse_gpx>())) 215 e, v11::qname("gpx"), xml::hohalo<v11::parse_gpx>()))
216 co_return std::move(*mf); 216 co_return std::move(*mf);
217 throw std::runtime_error{"no supported GPX document found"}; 217 throw std::runtime_error{"no supported GPX document found"};
218} 218}
diff --git a/server/src/http_client.cppm b/server/src/http_client.cppm
index 13dd60a..bea5384 100644
--- a/server/src/http_client.cppm
+++ b/server/src/http_client.cppm
@@ -27,12 +27,12 @@ public:
27 { 27 {
28 sslc_.set_default_verify_paths(); 28 sslc_.set_default_verify_paths();
29 sslc_.set_verify_mode( 29 sslc_.set_verify_mode(
30 net::ssl::verify_peer | net::ssl::verify_fail_if_no_peer_cert); 30 net::ssl::verify_peer | net::ssl::verify_fail_if_no_peer_cert);
31 } 31 }
32 32
33 template <class ReqBody> 33 template <class ReqBody>
34 auto do_request(bhttp::request<ReqBody>& req) 34 auto do_request(bhttp::request<ReqBody>& req)
35 -> bhttp::response<bhttp::dynamic_body> 35 -> bhttp::response<bhttp::dynamic_body>
36 { 36 {
37 auto stream = ssl::stream<beast::tcp_stream>{ioc_, sslc_}; 37 auto stream = ssl::stream<beast::tcp_stream>{ioc_, sslc_};
38 38
@@ -40,7 +40,7 @@ public:
40 if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) 40 if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str()))
41 { 41 {
42 throw beast::system_error( 42 throw beast::system_error(
43 static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()); 43 static_cast<int>(::ERR_get_error()), net::error::get_ssl_category());
44 } 44 }
45 stream.set_verify_callback(ssl::host_name_verification(host)); 45 stream.set_verify_callback(ssl::host_name_verification(host));
46 auto const results = resolver_.resolve(host, "443"); 46 auto const results = resolver_.resolve(host, "443");
diff --git a/server/src/http_server.cppm b/server/src/http_server.cppm
index 366037f..f5b4c3e 100644
--- a/server/src/http_server.cppm
+++ b/server/src/http_server.cppm
@@ -41,7 +41,7 @@ private:
41 virtual ~impl_base() = default; 41 virtual ~impl_base() = default;
42 virtual auto header() -> bhttp::response_header<bhttp::fields>& = 0; 42 virtual auto header() -> bhttp::response_header<bhttp::fields>& = 0;
43 virtual auto header() const 43 virtual auto header() const
44 -> bhttp::response_header<bhttp::fields> const& = 0; 44 -> bhttp::response_header<bhttp::fields> const& = 0;
45 virtual auto is_done() const -> bool = 0; 45 virtual auto is_done() const -> bool = 0;
46 virtual auto prepare(beast::error_code&) -> const_buffers_type = 0; 46 virtual auto prepare(beast::error_code&) -> const_buffers_type = 0;
47 virtual auto consume(std::size_t n) -> void = 0; 47 virtual auto consume(std::size_t n) -> void = 0;
@@ -121,12 +121,12 @@ private:
121 auto keep_alive() const noexcept -> bool override 121 auto keep_alive() const noexcept -> bool override
122 { 122 {
123 return state_.visit( 123 return state_.visit(
124 util::overloaded{ 124 util::overloaded{
125 [](bhttp::response<Body> const& rsp) -> bool 125 [](bhttp::response<Body> const& rsp) -> bool
126 { return rsp.keep_alive(); }, 126 { return rsp.keep_alive(); },
127 [](bhttp::message_generator const& gen) -> bool 127 [](bhttp::message_generator const& gen) -> bool
128 { return gen.keep_alive(); }, 128 { return gen.keep_alive(); },
129 }); 129 });
130 } 130 }
131 }; 131 };
132 132
@@ -164,15 +164,15 @@ using next_handler_t = std::function<auto(Ctx)->net::awaitable<presponse>>;
164 164
165template <class OuterCtx, class InnerCtx> 165template <class OuterCtx, class InnerCtx>
166using middleware_t = 166using middleware_t =
167 std::function<auto( 167 std::function<auto(
168 OuterCtx, bhttp::request_header<bhttp::fields>&, 168 OuterCtx, bhttp::request_header<bhttp::fields>&,
169 next_handler_t<InnerCtx>) 169 next_handler_t<InnerCtx>)
170 ->net::awaitable<presponse>>; 170 ->net::awaitable<presponse>>;
171 171
172template <class Ctx> 172template <class Ctx>
173auto lax_cors_middleware( 173auto lax_cors_middleware(
174 Ctx ctx, bhttp::request_header<bhttp::fields>& req_hdr, 174 Ctx ctx, bhttp::request_header<bhttp::fields>& req_hdr,
175 next_handler_t<Ctx> next) -> net::awaitable<presponse> 175 next_handler_t<Ctx> next) -> net::awaitable<presponse>
176{ 176{
177 std::ignore = req_hdr; 177 std::ignore = req_hdr;
178 auto prersp = co_await next(ctx); 178 auto prersp = co_await next(ctx);
@@ -188,16 +188,16 @@ struct trace_id_ctx : InnerCtx
188 188
189template <class OuterCtx> 189template <class OuterCtx>
190auto trace_id_middleware( 190auto trace_id_middleware(
191 OuterCtx ctx0, bhttp::request_header<bhttp::fields>& req_hdr, 191 OuterCtx ctx0, bhttp::request_header<bhttp::fields>& req_hdr,
192 next_handler_t<trace_id_ctx<OuterCtx>> next) -> net::awaitable<presponse> 192 next_handler_t<trace_id_ctx<OuterCtx>> next) -> net::awaitable<presponse>
193{ 193{
194 std::ignore = req_hdr; 194 std::ignore = req_hdr;
195 auto ctx = trace_id_ctx{std::move(ctx0)}; 195 auto ctx = trace_id_ctx{std::move(ctx0)};
196 auto prersp = co_await next(std::move(ctx)); 196 auto prersp = co_await next(std::move(ctx));
197 prersp.header().set( 197 prersp.header().set(
198 "X-Routemon-Trace-Id", std::string_view{ctx.trace_id.as_string()}); 198 "X-Routemon-Trace-Id", std::string_view{ctx.trace_id.as_string()});
199 prersp.header().insert( 199 prersp.header().insert(
200 bhttp::field::access_control_expose_headers, "X-Routemon-Trace-Id"); 200 bhttp::field::access_control_expose_headers, "X-Routemon-Trace-Id");
201 co_return std::move(prersp); 201 co_return std::move(prersp);
202} 202}
203 203
@@ -208,8 +208,8 @@ struct base_ctx
208 208
209template <class Ctx> 209template <class Ctx>
210using basic_route_handler_fn_t = std::function< 210using basic_route_handler_fn_t = std::function<
211 auto(Ctx, readable_request, std::vector<std::string> const& matches) 211 auto(Ctx, readable_request, std::vector<std::string> const& matches)
212 ->net::awaitable<presponse>>; 212 ->net::awaitable<presponse>>;
213 213
214struct keep_alive 214struct keep_alive
215{ 215{
@@ -228,8 +228,8 @@ auto make_rsp(bhttp::status status, keep_alive ka) -> bhttp::response<Body>
228} 228}
229 229
230auto problem_rsp( 230auto problem_rsp(
231 base_ctx const& ctx, problem::details const& problem, keep_alive ka) 231 base_ctx const& ctx, problem::details const& problem, keep_alive ka)
232 -> presponse 232 -> presponse
233{ 233{
234 auto rsp = make_rsp<bhttp::string_body>(problem.status, ka); 234 auto rsp = make_rsp<bhttp::string_body>(problem.status, ka);
235 rsp.set(bhttp::field::content_type, "application/problem+json"); 235 rsp.set(bhttp::field::content_type, "application/problem+json");
@@ -244,17 +244,18 @@ struct preflight_response
244 std::vector<bhttp::field> allow_headers; 244 std::vector<bhttp::field> allow_headers;
245}; 245};
246auto make_preflight_rsp(preflight_response res, keep_alive ka) 246auto make_preflight_rsp(preflight_response res, keep_alive ka)
247 -> bhttp::response<bhttp::empty_body> 247 -> bhttp::response<bhttp::empty_body>
248{ 248{
249 auto rsp = make_rsp<bhttp::empty_body>(bhttp::status::no_content, ka); 249 auto rsp = make_rsp<bhttp::empty_body>(bhttp::status::no_content, ka);
250 auto allow_headers_str = res.allow_headers 250 auto allow_headers_str = res.allow_headers
251 | std::views::transform( 251 | std::views::transform(
252 [](auto const& field) -> std::string_view 252 [](auto const& field) -> std::string_view
253 { return bhttp::to_string(field); }) 253 { return bhttp::to_string(field); })
254 | std::views::join_with(std::string_view{", "}) 254 | std::views::join_with(std::string_view{", "})
255 | std::ranges::to<std::string>(); 255 | std::ranges::to<std::string>();
256 rsp.set( 256 rsp.set(
257 bhttp::field::access_control_allow_methods, res.allow_methods.to_string()); 257 bhttp::field::access_control_allow_methods,
258 res.allow_methods.to_string());
258 rsp.set(bhttp::field::access_control_allow_headers, allow_headers_str); 259 rsp.set(bhttp::field::access_control_allow_headers, allow_headers_str);
259 rsp.prepare_payload(); 260 rsp.prepare_payload();
260 return rsp; 261 return rsp;
@@ -265,7 +266,7 @@ auto make_preflight_rsp(preflight_response res, keep_alive ka)
265// anyway, but all context types should derive from base_ctx). 266// anyway, but all context types should derive from base_ctx).
266template <bhttp::concepts::body_reader Body> 267template <bhttp::concepts::body_reader Body>
267auto read_request(base_ctx const& ctx, readable_request&& r) 268auto read_request(base_ctx const& ctx, readable_request&& r)
268 -> net::awaitable<std::expected<bhttp::request<Body>, presponse>> 269 -> net::awaitable<std::expected<bhttp::request<Body>, presponse>>
269{ 270{
270 std::ignore = ctx; 271 std::ignore = ctx;
271 auto p = bhttp::request_parser<Body>{std::move(*r.p)}; 272 auto p = bhttp::request_parser<Body>{std::move(*r.p)};
@@ -275,10 +276,11 @@ auto read_request(base_ctx const& ctx, readable_request&& r)
275 276
276template <> 277template <>
277auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r) 278auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r)
278 -> net::awaitable<std::expected<bhttp::request<bhttp::empty_body>, presponse>> 279 -> net::awaitable<
280 std::expected<bhttp::request<bhttp::empty_body>, presponse>>
279{ 281{
280 auto [ec, _] = 282 auto [ec, _] =
281 co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple); 283 co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple);
282 if (ec == bhttp::error::unexpected_body) 284 if (ec == bhttp::error::unexpected_body)
283 { 285 {
284 auto tpl = problem::tpl{ 286 auto tpl = problem::tpl{
@@ -287,7 +289,7 @@ auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r)
287 .type_uri = "https://routemon.fautchen.eu/problems/unexpected-body", 289 .type_uri = "https://routemon.fautchen.eu/problems/unexpected-body",
288 }; 290 };
289 co_return std::unexpected{problem_rsp( 291 co_return std::unexpected{problem_rsp(
290 ctx, tpl.instantiate(), keep_alive{false})}; 292 ctx, tpl.instantiate(), keep_alive{false})};
291 } 293 }
292 else if (ec) 294 else if (ec)
293 { 295 {
@@ -308,8 +310,8 @@ template <class Ctx>
308 []<class InnerCtx>(routed_ctx<InnerCtx> const&) {}(ctx); 310 []<class InnerCtx>(routed_ctx<InnerCtx> const&) {}(ctx);
309 } 311 }
310auto default_options_handler( 312auto default_options_handler(
311 Ctx const& ctx, readable_request r, std::vector<std::string> const&) 313 Ctx const& ctx, readable_request r, std::vector<std::string> const&)
312 -> net::awaitable<presponse> 314 -> net::awaitable<presponse>
313{ 315{
314 auto mreq = co_await read_request<bhttp::empty_body>(ctx, std::move(r)); 316 auto mreq = co_await read_request<bhttp::empty_body>(ctx, std::move(r));
315 if (!mreq) 317 if (!mreq)
@@ -319,18 +321,18 @@ auto default_options_handler(
319 { 321 {
320 // CORS preflight request 322 // CORS preflight request
321 co_return make_preflight_rsp( 323 co_return make_preflight_rsp(
322 preflight_response{ 324 preflight_response{
323 // TODO: should access-control-allow-methods contain OPTIONS? 325 // TODO: should access-control-allow-methods contain OPTIONS?
324 .allow_methods = ctx.route_methods, 326 .allow_methods = ctx.route_methods,
325 .allow_headers = {bhttp::field::content_type}, 327 .allow_headers = {bhttp::field::content_type},
326 }, 328 },
327 keep_alive{mreq->keep_alive()}); 329 keep_alive{mreq->keep_alive()});
328 } 330 }
329 else 331 else
330 { 332 {
331 // Normal OPTIONS request 333 // Normal OPTIONS request
332 auto rsp = make_rsp<bhttp::empty_body>( 334 auto rsp = make_rsp<bhttp::empty_body>(
333 bhttp::status::no_content, keep_alive{mreq->keep_alive()}); 335 bhttp::status::no_content, keep_alive{mreq->keep_alive()});
334 rsp.set(bhttp::field::allow, ctx.route_methods.to_string()); 336 rsp.set(bhttp::field::allow, ctx.route_methods.to_string());
335 rsp.prepare_payload(); 337 rsp.prepare_payload();
336 co_return std::move(rsp); 338 co_return std::move(rsp);
@@ -338,7 +340,7 @@ auto default_options_handler(
338} 340}
339 341
340auto global_options_handler(base_ctx const& ctx, readable_request r) 342auto global_options_handler(base_ctx const& ctx, readable_request r)
341 -> net::awaitable<presponse> 343 -> net::awaitable<presponse>
342{ 344{
343 // TODO: switch to "small (4KB) discarded" body type, similar to what Go 345 // TODO: switch to "small (4KB) discarded" body type, similar to what Go
344 // does? Same goes for default_options_handler? Not sure. 346 // does? Same goes for default_options_handler? Not sure.
@@ -347,45 +349,46 @@ auto global_options_handler(base_ctx const& ctx, readable_request r)
347 co_return std::move(res.error()); 349 co_return std::move(res.error());
348 auto req = r.p->release(); 350 auto req = r.p->release();
349 auto rsp = make_rsp<bhttp::empty_body>( 351 auto rsp = make_rsp<bhttp::empty_body>(
350 bhttp::status::no_content, keep_alive{req.keep_alive()}); 352 bhttp::status::no_content, keep_alive{req.keep_alive()});
351 rsp.prepare_payload(); 353 rsp.prepare_payload();
352 co_return std::move(rsp); 354 co_return std::move(rsp);
353} 355}
354 356
355template <class Ctx> 357template <class Ctx>
356auto id_middleware( 358auto id_middleware(
357 Ctx ctx, bhttp::request_header<bhttp::fields>&, next_handler_t<Ctx> next) 359 Ctx ctx, bhttp::request_header<bhttp::fields>&, next_handler_t<Ctx> next)
358 -> net::awaitable<presponse> 360 -> net::awaitable<presponse>
359{ 361{
360 co_return co_await next(std::move(ctx)); 362 co_return co_await next(std::move(ctx));
361} 363}
362 364
363template <class A, class B, class C> 365template <class A, class B, class C>
364auto middleware_compose(middleware_t<A, B> ab, middleware_t<B, C> bc) 366auto middleware_compose(middleware_t<A, B> ab, middleware_t<B, C> bc)
365 -> middleware_t<A, C> 367 -> middleware_t<A, C>
366{ 368{
367 return [ab = std::move(ab), bc = std::move(bc)]( 369 return [ab = std::move(ab), bc = std::move(bc)](
368 A a, bhttp::request_header<bhttp::fields>& header, 370 A a, bhttp::request_header<bhttp::fields>& header,
369 next_handler_t<C> next) -> net::awaitable<presponse> 371 next_handler_t<C> next) -> net::awaitable<presponse>
370 { 372 {
371 co_return co_await ab( 373 co_return co_await ab(
372 std::move(a), header, [&](B b) -> net::awaitable<presponse> 374 std::move(a), header, [&](B b) -> net::awaitable<presponse>
373 { co_return co_await bc(std::move(b), header, next); }); 375 { co_return co_await bc(std::move(b), header, next); });
374 }; 376 };
375} 377}
376 378
377template <class A, class B> 379template <class A, class B>
378auto middleware_wrap_fn(middleware_t<A, B> ab, basic_route_handler_fn_t<B> fn) 380auto middleware_wrap_fn(middleware_t<A, B> ab, basic_route_handler_fn_t<B> fn)
379 -> basic_route_handler_fn_t<A> 381 -> basic_route_handler_fn_t<A>
380{ 382{
381 return [ab = std::move(ab), fn = std::move(fn)]( 383 return
382 A a_ctx, readable_request r, 384 [ab = std::move(ab), fn = std::move(fn)](
383 std::vector<std::string> const& matches) -> net::awaitable<presponse> 385 A a_ctx, readable_request r,
386 std::vector<std::string> const& matches) -> net::awaitable<presponse>
384 { 387 {
385 co_return co_await ab( 388 co_return co_await ab(
386 std::move(a_ctx), r.p->get().base(), 389 std::move(a_ctx), r.p->get().base(),
387 [&](B b_ctx) -> net::awaitable<presponse> 390 [&](B b_ctx) -> net::awaitable<presponse>
388 { co_return co_await fn(std::move(b_ctx), r, matches); }); 391 { co_return co_await fn(std::move(b_ctx), r, matches); });
389 }; 392 };
390} 393}
391 394
@@ -445,7 +448,7 @@ struct handler_map
445 template <std::default_initializable U> 448 template <std::default_initializable U>
446 auto map(std::invocable<V const&> auto f) const -> handler_map<U> 449 auto map(std::invocable<V const&> auto f) const -> handler_map<U>
447 requires std::assignable_from< 450 requires std::assignable_from<
448 U&, std::invoke_result_t<decltype(f), V const&>> 451 U&, std::invoke_result_t<decltype(f), V const&>>
449 { 452 {
450 return { 453 return {
451 .options = static_cast<bool>(options) ? f(options) : U{}, 454 .options = static_cast<bool>(options) ? f(options) : U{},
@@ -475,29 +478,29 @@ struct route_tree
475 478
476template <class OuterCtx, class InnerCtx> 479template <class OuterCtx, class InnerCtx>
477auto middleware_wrap_tree( 480auto middleware_wrap_tree(
478 middleware_t<OuterCtx, InnerCtx> mw, route_tree<InnerCtx> const& tree) 481 middleware_t<OuterCtx, InnerCtx> mw, route_tree<InnerCtx> const& tree)
479 -> route_tree<OuterCtx> 482 -> route_tree<OuterCtx>
480{ 483{
481 auto new_leaves = tree.here.template map<basic_route_handler_fn_t<OuterCtx>>( 484 auto new_leaves = tree.here.template map<basic_route_handler_fn_t<OuterCtx>>(
482 std::bind_front(middleware_wrap_fn<OuterCtx, InnerCtx>, mw)); 485 std::bind_front(middleware_wrap_fn<OuterCtx, InnerCtx>, mw));
483 auto new_sub = tree.sub.visit( 486 auto new_sub = tree.sub.visit(
484 util::overloaded{ 487 util::overloaded{
485 [&mw](route_tree<InnerCtx>::named_subtrees const& subtrees) 488 [&mw](route_tree<InnerCtx>::named_subtrees const& subtrees)
486 -> decltype(route_tree<OuterCtx>::sub) 489 -> decltype(route_tree<OuterCtx>::sub)
487 { 490 {
488 auto new_subtrees = typename route_tree<OuterCtx>::named_subtrees{}; 491 auto new_subtrees = typename route_tree<OuterCtx>::named_subtrees{};
489 for (auto [seg, subtree] : subtrees) 492 for (auto [seg, subtree] : subtrees)
490 new_subtrees[seg] = middleware_wrap_tree(mw, subtree); 493 new_subtrees[seg] = middleware_wrap_tree(mw, subtree);
491 return new_subtrees; 494 return new_subtrees;
492 }, 495 },
493 [&mw](route_tree<InnerCtx>::wildcard_subtree const& subtree) 496 [&mw](route_tree<InnerCtx>::wildcard_subtree const& subtree)
494 -> decltype(route_tree<OuterCtx>::sub) 497 -> decltype(route_tree<OuterCtx>::sub)
495 { 498 {
496 return typename route_tree<OuterCtx>::wildcard_subtree{ 499 return typename route_tree<OuterCtx>::wildcard_subtree{
497 middleware_wrap_tree(mw, *subtree) 500 middleware_wrap_tree(mw, *subtree)
498 }; 501 };
499 }, 502 },
500 }); 503 });
501 return {.here = new_leaves, .sub = new_sub}; 504 return {.here = new_leaves, .sub = new_sub};
502} 505}
503 506
@@ -506,21 +509,23 @@ concept match_arg = std::constructible_from<T, std::string const&>;
506 509
507template <class Ctx, match_arg... MatchArgs> 510template <class Ctx, match_arg... MatchArgs>
508using route_handler_fn_t = std::function< 511using route_handler_fn_t = std::function<
509 auto(Ctx, readable_request, MatchArgs...)->net::awaitable<presponse>>; 512 auto(Ctx, readable_request, MatchArgs...)->net::awaitable<presponse>>;
510 513
511template <class Ctx, match_arg... MatchArgs> 514template <class Ctx, match_arg... MatchArgs>
512auto degen_route_handler(route_handler_fn_t<Ctx, MatchArgs...> fn) 515auto degen_route_handler(route_handler_fn_t<Ctx, MatchArgs...> fn)
513 -> basic_route_handler_fn_t<Ctx> 516 -> basic_route_handler_fn_t<Ctx>
514{ 517{
515 return [fn = std::move(fn)]( 518 return
516 Ctx ctx, readable_request r, 519 [fn = std::move(fn)](
517 std::vector<std::string> const& matches) -> net::awaitable<presponse> 520 Ctx ctx, readable_request r,
521 std::vector<std::string> const& matches) -> net::awaitable<presponse>
518 { 522 {
519 if (sizeof...(MatchArgs) != matches.size()) 523 if (sizeof...(MatchArgs) != matches.size())
520 throw std::runtime_error{"got unexpected amount of matches"}; 524 throw std::runtime_error{"got unexpected amount of matches"};
521 auto it = matches.begin(); 525 auto it = matches.begin();
522 co_return co_await fn( 526 co_return co_await fn(
523 std::move(ctx), r, MatchArgs{static_cast<std::string const&>(*it++)}...); 527 std::move(ctx), r,
528 MatchArgs{static_cast<std::string const&>(*it++)}...);
524 }; 529 };
525} 530}
526 531
@@ -529,7 +534,7 @@ struct ctree : route_tree<Ctx>
529{ 534{
530 template <class OuterCtx> 535 template <class OuterCtx>
531 auto wrap(middleware_t<OuterCtx, Ctx> mw) const 536 auto wrap(middleware_t<OuterCtx, Ctx> mw) const
532 -> ctree<OuterCtx, MatchArgs...> 537 -> ctree<OuterCtx, MatchArgs...>
533 { 538 {
534 return {middleware_wrap_tree(std::move(mw), *this)}; 539 return {middleware_wrap_tree(std::move(mw), *this)};
535 } 540 }
@@ -541,24 +546,25 @@ struct dtree : handler_map<route_handler_fn_t<Ctx, MatchArgs...>>
541 [[nodiscard]] auto to_leaves() const -> typename route_tree<Ctx>::leaves 546 [[nodiscard]] auto to_leaves() const -> typename route_tree<Ctx>::leaves
542 { 547 {
543 auto here = this->template map<basic_route_handler_fn_t<Ctx>>( 548 auto here = this->template map<basic_route_handler_fn_t<Ctx>>(
544 degen_route_handler<Ctx, MatchArgs...>); 549 degen_route_handler<Ctx, MatchArgs...>);
545 if (!here.verbs().empty() && !static_cast<bool>(this->options)) 550 if (!here.verbs().empty() && !static_cast<bool>(this->options))
546 here.options = default_options_handler<Ctx>; 551 here.options = default_options_handler<Ctx>;
547 return here; 552 return here;
548 } 553 }
549 554
550 [[nodiscard]] auto named_subtrees( 555 [[nodiscard]] auto named_subtrees(
551 std::initializer_list<std::pair<std::string, ctree<Ctx, MatchArgs...>>> 556 std::initializer_list<std::pair<std::string, ctree<Ctx, MatchArgs...>>>
552 subtrees) const -> ctree<Ctx, MatchArgs...> 557 subtrees) const -> ctree<Ctx, MatchArgs...>
553 { 558 {
554 auto sub = typename route_tree<Ctx>::named_subtrees{ 559 auto sub = typename route_tree<Ctx>::named_subtrees{
555 std::from_range, subtrees 560 std::from_range, subtrees
556 | std::views::transform( 561 | std::views::transform(
557 [](auto const& p) 562 [](auto const& p)
558 { 563 {
559 return std::make_pair( 564 return std::make_pair(
560 p.first, static_cast<route_tree<Ctx>>(p.second)); 565 p.first,
561 }) 566 static_cast<route_tree<Ctx>>(p.second));
567 })
562 }; 568 };
563 return {route_tree<Ctx>{.here = to_leaves(), .sub = sub}}; 569 return {route_tree<Ctx>{.here = to_leaves(), .sub = sub}};
564 } 570 }
@@ -566,7 +572,7 @@ struct dtree : handler_map<route_handler_fn_t<Ctx, MatchArgs...>>
566 template <match_arg MatchArg> 572 template <match_arg MatchArg>
567 [[nodiscard]] auto 573 [[nodiscard]] auto
568 wildcard_subtree(ctree<Ctx, MatchArgs..., MatchArg> subtree) 574 wildcard_subtree(ctree<Ctx, MatchArgs..., MatchArg> subtree)
569 -> ctree<Ctx, MatchArgs...> 575 -> ctree<Ctx, MatchArgs...>
570 { 576 {
571 return {route_tree<Ctx>{ 577 return {route_tree<Ctx>{
572 .here = to_leaves(), 578 .here = to_leaves(),
@@ -592,9 +598,9 @@ class server
592 598
593public: 599public:
594 explicit server( 600 explicit server(
595 log::logger const& l, locale::selector&& lsel, 601 log::logger const& l, locale::selector&& lsel,
596 middleware_t<base_ctx, PreRouteCtx> global_middleware, 602 middleware_t<base_ctx, PreRouteCtx> global_middleware,
597 route_tree<routed_ctx<PreRouteCtx>> routes) 603 route_tree<routed_ctx<PreRouteCtx>> routes)
598 : l_{l.sub("http_server")}, lsel_{std::move(lsel)}, 604 : l_{l.sub("http_server")}, lsel_{std::move(lsel)},
599 global_middleware_{std::move(global_middleware)}, 605 global_middleware_{std::move(global_middleware)},
600 routes_{std::move(routes)} 606 routes_{std::move(routes)}
@@ -604,36 +610,35 @@ public:
604 struct match_result 610 struct match_result
605 { 611 {
606 util::not_null< 612 util::not_null<
607 handler_map<basic_route_handler_fn_t<routed_ctx<PreRouteCtx>>> const*> 613 handler_map<basic_route_handler_fn_t<routed_ctx<PreRouteCtx>>> const*>
608 route_handlers; 614 route_handlers;
609 std::vector<std::string> wildcard_matches; 615 std::vector<std::string> wildcard_matches;
610 616
611 auto allowed_methods() const -> verb_set { return route_handlers->verbs(); } 617 auto allowed_methods() const -> verb_set { return route_handlers->verbs(); }
612 }; 618 };
613 619
614 auto match(boost::urls::segments_view segments) const 620 auto match(boost::urls::segments_view segments) const
615 -> std::optional<match_result> 621 -> std::optional<match_result>
616 { 622 {
617 auto const* tree = &routes_; 623 auto const* tree = &routes_;
618 auto wildcard_matches = std::vector<std::string>{}; 624 auto wildcard_matches = std::vector<std::string>{};
619 for (auto const& seg : segments) 625 for (auto const& seg : segments)
620 { 626 {
621 tree->sub.visit( 627 tree->sub.visit(
622 util::overloaded{ 628 util::overloaded{
623 [&]( 629 [&](route_tree<routed_ctx<PreRouteCtx>>::named_subtrees const&
624 route_tree<routed_ctx<PreRouteCtx>>::named_subtrees const& subtrees) 630 subtrees)
625 { 631 {
626 auto it = subtrees.find(seg); 632 auto it = subtrees.find(seg);
627 tree = it == subtrees.end() ? nullptr : &it->second; 633 tree = it == subtrees.end() ? nullptr : &it->second;
628 }, 634 },
629 [&]( 635 [&](route_tree<routed_ctx<PreRouteCtx>>::wildcard_subtree const&
630 route_tree<routed_ctx<PreRouteCtx>>::wildcard_subtree const& 636 wildcard_subtree)
631 wildcard_subtree) 637 {
632 { 638 wildcard_matches.push_back(seg);
633 wildcard_matches.push_back(seg); 639 tree = &*wildcard_subtree;
634 tree = &*wildcard_subtree; 640 },
635 }, 641 });
636 });
637 if (!tree) 642 if (!tree)
638 return std::nullopt; 643 return std::nullopt;
639 } 644 }
@@ -646,7 +651,7 @@ public:
646 } 651 }
647 652
648 auto route_request(PreRouteCtx ctx, readable_request r) const 653 auto route_request(PreRouteCtx ctx, readable_request r) const
649 -> net::awaitable<presponse> 654 -> net::awaitable<presponse>
650 { 655 {
651 auto req_base = r.p->get().base(); 656 auto req_base = r.p->get().base();
652 657
@@ -685,10 +690,10 @@ public:
685 if (!req_url.is_path_absolute()) 690 if (!req_url.is_path_absolute())
686 { 691 {
687 auto problem = bad_request_tpl.instantiate().set_detail(translate( 692 auto problem = bad_request_tpl.instantiate().set_detail(translate(
688 "Path of normalized (RFC 3986, § 6) " 693 "Path of normalized (RFC 3986, § 6) "
689 "origin-form request-target (RFC " 694 "origin-form request-target (RFC "
690 "9112, § 3.2.1) should be " 695 "9112, § 3.2.1) should be "
691 "absolute")); 696 "absolute"));
692 co_return problem_rsp(ctx, problem, keep_alive{false}); 697 co_return problem_rsp(ctx, problem, keep_alive{false});
693 } 698 }
694 699
@@ -719,7 +724,7 @@ public:
719 if (auto mhdl = mres->route_handlers->lookup(*mverb)) 724 if (auto mhdl = mres->route_handlers->lookup(*mverb))
720 { 725 {
721 auto new_ctx = 726 auto new_ctx =
722 routed_ctx<PreRouteCtx>{std::move(ctx), mres->allowed_methods()}; 727 routed_ctx<PreRouteCtx>{std::move(ctx), mres->allowed_methods()};
723 co_return co_await mhdl(std::move(new_ctx), r, mres->wildcard_matches); 728 co_return co_await mhdl(std::move(new_ctx), r, mres->wildcard_matches);
724 } 729 }
725 else 730 else
@@ -733,7 +738,7 @@ public:
733 }; 738 };
734 auto rsp = problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 739 auto rsp = problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
735 rsp.header().set( 740 rsp.header().set(
736 bhttp::field::allow, mres->allowed_methods().to_string()); 741 bhttp::field::allow, mres->allowed_methods().to_string());
737 co_return std::move(rsp); 742 co_return std::move(rsp);
738 } 743 }
739 } 744 }
@@ -742,9 +747,9 @@ public:
742 // We do not accept any other request-target forms. 747 // We do not accept any other request-target forms.
743 748
744 auto problem = bad_request_tpl.instantiate().set_detail(translate( 749 auto problem = bad_request_tpl.instantiate().set_detail(translate(
745 "Invalid request-target, expected " 750 "Invalid request-target, expected "
746 "asterisk-form or origin-form " 751 "asterisk-form or origin-form "
747 "(see RFC 9112, § 3.2)")); 752 "(see RFC 9112, § 3.2)"));
748 co_return problem_rsp(ctx, problem, keep_alive{false}); 753 co_return problem_rsp(ctx, problem, keep_alive{false});
749 } 754 }
750 } 755 }
@@ -756,8 +761,9 @@ public:
756 auto ctx0 = base_ctx{.locale = locale}; 761 auto ctx0 = base_ctx{.locale = locale};
757 762
758 co_return co_await global_middleware_( 763 co_return co_await global_middleware_(
759 std::move(ctx0), header, [&](PreRouteCtx ctx) -> net::awaitable<presponse> 764 std::move(ctx0), header,
760 { co_return co_await route_request(std::move(ctx), std::move(r)); }); 765 [&](PreRouteCtx ctx) -> net::awaitable<presponse>
766 { co_return co_await route_request(std::move(ctx), std::move(r)); });
761 } 767 }
762 768
763 auto do_session(beast::tcp_stream strm) -> net::awaitable<void> 769 auto do_session(beast::tcp_stream strm) -> net::awaitable<void>
@@ -769,7 +775,7 @@ public:
769 auto p0 = bhttp::request_parser<bhttp::empty_body>{}; 775 auto p0 = bhttp::request_parser<bhttp::empty_body>{};
770 p0.body_limit(boost::none); 776 p0.body_limit(boost::none);
771 auto [ec, _] = 777 auto [ec, _] =
772 co_await bhttp::async_read_header(strm, buf, p0, net::as_tuple); 778 co_await bhttp::async_read_header(strm, buf, p0, net::as_tuple);
773 if (ec == bhttp::error::end_of_stream) 779 if (ec == bhttp::error::end_of_stream)
774 break; 780 break;
775 else if (ec) 781 else if (ec)
@@ -777,11 +783,11 @@ public:
777 783
778 auto http_version = p0.get().version(); 784 auto http_version = p0.get().version();
779 auto&& rsp = co_await handle_request( 785 auto&& rsp = co_await handle_request(
780 readable_request{ 786 readable_request{
781 .p = util::not_null{&p0}, 787 .p = util::not_null{&p0},
782 .strm = util::not_null{&strm}, 788 .strm = util::not_null{&strm},
783 .buf = util::not_null{&buf}, 789 .buf = util::not_null{&buf},
784 }); 790 });
785 rsp.header().version(http_version); 791 rsp.header().version(http_version);
786 bool keep_alive = rsp.keep_alive(); 792 bool keep_alive = rsp.keep_alive();
787 co_await beast::async_write(strm, std::move(rsp)); 793 co_await beast::async_write(strm, std::move(rsp));
@@ -800,27 +806,27 @@ public:
800 auto acceptor = tcp::acceptor{executor, endpoint}; 806 auto acceptor = tcp::acceptor{executor, endpoint};
801 807
802 l_.with("endpoint", endpoint.address().to_string()) 808 l_.with("endpoint", endpoint.address().to_string())
803 .with("port", std::to_string(endpoint.port())) 809 .with("port", std::to_string(endpoint.port()))
804 .info("Serving"); 810 .info("Serving");
805 while (true) 811 while (true)
806 { 812 {
807 net::co_spawn( 813 net::co_spawn(
808 executor, 814 executor,
809 do_session(beast::tcp_stream{co_await acceptor.async_accept()}), 815 do_session(beast::tcp_stream{co_await acceptor.async_accept()}),
810 [this](std::exception_ptr e) 816 [this](std::exception_ptr e)
811 {
812 if (e)
813 { 817 {
814 try 818 if (e)
815 {
816 std::rethrow_exception(e);
817 }
818 catch (std::exception const& e)
819 { 819 {
820 l_.error("Error in session: {}", e.what()); 820 try
821 {
822 std::rethrow_exception(e);
823 }
824 catch (std::exception const& e)
825 {
826 l_.error("Error in session: {}", e.what());
827 }
821 } 828 }
822 } 829 });
823 });
824 } 830 }
825 } 831 }
826 832
@@ -831,21 +837,21 @@ public:
831 837
832 // TODO: make exception handling as nice as in srv.cpp 838 // TODO: make exception handling as nice as in srv.cpp
833 net::co_spawn( 839 net::co_spawn(
834 ioc, do_listen(endpoint), 840 ioc, do_listen(endpoint),
835 [this](std::exception_ptr e) 841 [this](std::exception_ptr e)
836 {
837 if (e)
838 { 842 {
839 try 843 if (e)
840 {
841 std::rethrow_exception(e);
842 }
843 catch (std::exception const& e)
844 { 844 {
845 l_.error("Error: {}", e.what()); 845 try
846 {
847 std::rethrow_exception(e);
848 }
849 catch (std::exception const& e)
850 {
851 l_.error("Error: {}", e.what());
852 }
846 } 853 }
847 } 854 });
848 });
849 } 855 }
850}; 856};
851 857
diff --git a/server/src/locale.cppm b/server/src/locale.cppm
index 06607f0..da80f76 100644
--- a/server/src/locale.cppm
+++ b/server/src/locale.cppm
@@ -50,11 +50,11 @@ struct icu_locale_hash
50}; 50};
51 51
52using icu_locale_priority_map = 52using icu_locale_priority_map =
53 std::unordered_map<icu::Locale, locale_priority, icu_locale_hash>; 53 std::unordered_map<icu::Locale, locale_priority, icu_locale_hash>;
54using icu_priority_locale = std::pair<icu::Locale, locale_priority>; 54using icu_priority_locale = std::pair<icu::Locale, locale_priority>;
55 55
56auto operator<(icu_priority_locale const& lhs, icu_priority_locale const& rhs) 56auto operator<(icu_priority_locale const& lhs, icu_priority_locale const& rhs)
57 -> bool 57 -> bool
58{ 58{
59 return lhs.second < rhs.second; 59 return lhs.second < rhs.second;
60} 60}
@@ -66,7 +66,7 @@ class icu_priority_locale_vec_iterator : public icu::Locale::Iterator
66 66
67public: 67public:
68 explicit icu_priority_locale_vec_iterator( 68 explicit icu_priority_locale_vec_iterator(
69 std::vector<icu_priority_locale>&& ls) 69 std::vector<icu_priority_locale>&& ls)
70 : ls_(std::move(ls)) 70 : ls_(std::move(ls))
71 { 71 {
72 } 72 }
@@ -80,8 +80,9 @@ public:
80 80
81export template <class T> 81export template <class T>
82concept locale_input_range = 82concept locale_input_range =
83 std::ranges::input_range<T> 83 std::ranges::input_range<T>
84 && std::same_as<std::locale const&, std::ranges::range_const_reference_t<T>>; 84 && std::same_as<
85 std::locale const&, std::ranges::range_const_reference_t<T>>;
85 86
86// Helps select a locale based on the Accept-Language header in an 87// Helps select a locale based on the Accept-Language header in an
87// HTTP request. 88// HTTP request.
@@ -92,15 +93,15 @@ export class selector
92 std::shared_ptr<blocale::generator const> lgen_; 93 std::shared_ptr<blocale::generator const> lgen_;
93 94
94 auto make_matcher( 95 auto make_matcher(
95 locale_input_range auto supported_locales, std::locale default_locale) 96 locale_input_range auto supported_locales, std::locale default_locale)
96 { 97 {
97 auto builder = icu::LocaleMatcher::Builder{}; 98 auto builder = icu::LocaleMatcher::Builder{};
98 for (auto const& supported_locale : supported_locales) 99 for (auto const& supported_locale : supported_locales)
99 { 100 {
100 auto const& supported_locale_info = 101 auto const& supported_locale_info =
101 std::use_facet<blocale::info>(supported_locale); 102 std::use_facet<blocale::info>(supported_locale);
102 auto supported_icu_locale = 103 auto supported_icu_locale =
103 icu::Locale{supported_locale_info.name().c_str()}; 104 icu::Locale{supported_locale_info.name().c_str()};
104 if (supported_icu_locale.isBogus()) 105 if (supported_icu_locale.isBogus())
105 throw std::runtime_error{ 106 throw std::runtime_error{
106 "supported locale gives rise to bogus ICU locale" 107 "supported locale gives rise to bogus ICU locale"
@@ -108,7 +109,7 @@ export class selector
108 builder.addSupportedLocale(supported_icu_locale); 109 builder.addSupportedLocale(supported_icu_locale);
109 } 110 }
110 auto const& default_locale_info = 111 auto const& default_locale_info =
111 std::use_facet<blocale::info>(default_locale); 112 std::use_facet<blocale::info>(default_locale);
112 auto default_icu_locale = icu::Locale{default_locale_info.name().c_str()}; 113 auto default_icu_locale = icu::Locale{default_locale_info.name().c_str()};
113 if (default_icu_locale.isBogus()) 114 if (default_icu_locale.isBogus())
114 throw std::runtime_error{"default locale gives rise to bogus ICU locale"}; 115 throw std::runtime_error{"default locale gives rise to bogus ICU locale"};
@@ -170,7 +171,7 @@ export class selector
170 while (kw = keywords->next(&kw_len, ec), !U_FAILURE(ec) && kw) 171 while (kw = keywords->next(&kw_len, ec), !U_FAILURE(ec) && kw)
171 { 172 {
172 auto value = 173 auto value =
173 l.getKeywordValue<std::string>(icu::StringPiece(kw, kw_len), ec); 174 l.getKeywordValue<std::string>(icu::StringPiece(kw, kw_len), ec);
174 if (!added_at) 175 if (!added_at)
175 { 176 {
176 posix_name += "@"; 177 posix_name += "@";
@@ -195,8 +196,8 @@ public:
195 // Note: lgen must live at least as long as the selector constructed here! 196 // Note: lgen must live at least as long as the selector constructed here!
196 // It is unfortunately not possible to copy/move a blocale::generator. 197 // It is unfortunately not possible to copy/move a blocale::generator.
197 explicit selector( 198 explicit selector(
198 locale_input_range auto locales, std::locale default_, 199 locale_input_range auto locales, std::locale default_,
199 std::shared_ptr<blocale::generator const> lgen) 200 std::shared_ptr<blocale::generator const> lgen)
200 : default_{default_}, matcher_{make_matcher(locales, default_)}, lgen_{lgen} 201 : default_{default_}, matcher_{make_matcher(locales, default_)}, lgen_{lgen}
201 { 202 {
202 } 203 }
@@ -211,7 +212,7 @@ public:
211 accept_language | std::views::split(","sv) | std::views::enumerate) 212 accept_language | std::views::split(","sv) | std::views::enumerate)
212 { 213 {
213 auto [lang_range_ut, mweight_ut] = 214 auto [lang_range_ut, mweight_ut] =
214 util::split_on(std::string_view{lang_prio}, ';'); 215 util::split_on(std::string_view{lang_prio}, ';');
215 auto lang_range_str = trim_ows(lang_range_ut); 216 auto lang_range_str = trim_ows(lang_range_ut);
216 auto mweight_str = mweight_ut.transform(trim_ows); 217 auto mweight_str = mweight_ut.transform(trim_ows);
217 if (lang_range_str == "*") 218 if (lang_range_str == "*")
@@ -227,7 +228,7 @@ public:
227 { 228 {
228 auto weight_str = mweight_str->substr(2, 4); 229 auto weight_str = mweight_str->substr(2, 4);
229 if (auto mweight = 230 if (auto mweight =
230 util::parse_float(weight_str, std::chars_format::fixed); 231 util::parse_float(weight_str, std::chars_format::fixed);
231 mweight && 0.0f < *mweight && *mweight < 1.0f) 232 mweight && 0.0f < *mweight && *mweight < 1.0f)
232 { 233 {
233 weight = *mweight; 234 weight = *mweight;
@@ -248,7 +249,7 @@ public:
248 } 249 }
249 250
250 auto desired_locales = 251 auto desired_locales =
251 std::vector<icu_priority_locale>{dlpm.begin(), dlpm.end()}; 252 std::vector<icu_priority_locale>{dlpm.begin(), dlpm.end()};
252 std::sort(desired_locales.begin(), desired_locales.end()); 253 std::sort(desired_locales.begin(), desired_locales.end());
253 auto it = icu_priority_locale_vec_iterator{std::move(desired_locales)}; 254 auto it = icu_priority_locale_vec_iterator{std::move(desired_locales)};
254 auto ec = UErrorCode::U_ZERO_ERROR; 255 auto ec = UErrorCode::U_ZERO_ERROR;
@@ -267,7 +268,7 @@ export auto to_bcp47_lang_tag(std::locale locale) -> std::optional<std::string>
267 auto const& locale_info = std::use_facet<blocale::info>(locale); 268 auto const& locale_info = std::use_facet<blocale::info>(locale);
268 auto ec = UErrorCode::U_ZERO_ERROR; 269 auto ec = UErrorCode::U_ZERO_ERROR;
269 auto bcp47_lang_tag = 270 auto bcp47_lang_tag =
270 icu::Locale{locale_info.name().c_str()}.toLanguageTag<std::string>(ec); 271 icu::Locale{locale_info.name().c_str()}.toLanguageTag<std::string>(ec);
271 if (U_FAILURE(ec)) 272 if (U_FAILURE(ec))
272 return std::nullopt; 273 return std::nullopt;
273 return bcp47_lang_tag; 274 return bcp47_lang_tag;
diff --git a/server/src/log.cppm b/server/src/log.cppm
index 7c42144..97552b6 100644
--- a/server/src/log.cppm
+++ b/server/src/log.cppm
@@ -97,12 +97,12 @@ export class logger
97 { 97 {
98 if (sink_->level() <= lvl) 98 if (sink_->level() <= lvl)
99 sink_->write( 99 sink_->write(
100 sink::tmp_message{ 100 sink::tmp_message{
101 .lvl = lvl, 101 .lvl = lvl,
102 .component = component_, 102 .component = component_,
103 .txt = std::vformat(fmt, args), 103 .txt = std::vformat(fmt, args),
104 .attrs = attrs_, 104 .attrs = attrs_,
105 }); 105 });
106 return *this; 106 return *this;
107 } 107 }
108 108
@@ -138,7 +138,7 @@ public:
138 } 138 }
139 139
140 [[nodiscard]] auto with(std::string const& k, std::string_view v) const 140 [[nodiscard]] auto with(std::string const& k, std::string_view v) const
141 -> logger 141 -> logger
142 { 142 {
143 return with(k, std::string{v}); 143 return with(k, std::string{v});
144 } 144 }
diff --git a/server/src/main.cpp b/server/src/main.cpp
index 2957808..3f8cb11 100644
--- a/server/src/main.cpp
+++ b/server/src/main.cpp
@@ -21,9 +21,9 @@ auto real_main(std::span<char const*> args) -> exit_status
21 if (args.size() != 2) 21 if (args.size() != 2)
22 { 22 {
23 l.error( 23 l.error(
24 "Fatal: expected exactly one argument (the configuration file " 24 "Fatal: expected exactly one argument (the configuration file "
25 "location), got {}", 25 "location), got {}",
26 args.size() - 1); 26 args.size() - 1);
27 return exit_status::bad_usage; 27 return exit_status::bad_usage;
28 } 28 }
29 auto const* config_filename = args[1]; 29 auto const* config_filename = args[1];
@@ -48,7 +48,7 @@ auto real_main(std::span<char const*> args) -> exit_status
48 catch (std::exception const& e) 48 catch (std::exception const& e)
49 { 49 {
50 l.with("filename", std::string_view{config_filename}) 50 l.with("filename", std::string_view{config_filename})
51 .error("Failed to load configuration: {}", e.what()); 51 .error("Failed to load configuration: {}", e.what());
52 return exit_status::failure; 52 return exit_status::failure;
53 } 53 }
54 sink->set_level(config.logger.level); 54 sink->set_level(config.logger.level);
@@ -68,19 +68,19 @@ auto real_main(std::span<char const*> args) -> exit_status
68 catch (std::exception const& e) 68 catch (std::exception const& e)
69 { 69 {
70 l.with("filename", config.database.sqlite3_filename) 70 l.with("filename", config.database.sqlite3_filename)
71 .error("Failed to open database: {}", e.what()); 71 .error("Failed to open database: {}", e.what());
72 return exit_status::failure; 72 return exit_status::failure;
73 } 73 }
74 74
75 l.with("filename", config.situations.datex2_filename) 75 l.with("filename", config.situations.datex2_filename)
76 .info("Loading situations"); 76 .info("Loading situations");
77 auto const before_load = chrono::steady_clock::now(); 77 auto const before_load = chrono::steady_clock::now();
78 auto d2loader = routemon::datex2::loader{}; 78 auto d2loader = routemon::datex2::loader{};
79 auto pub = routemon::datex2::situation_publication{}; 79 auto pub = routemon::datex2::situation_publication{};
80 try 80 try
81 { 81 {
82 pub = 82 pub =
83 d2loader.load_situation_publication(config.situations.datex2_filename); 83 d2loader.load_situation_publication(config.situations.datex2_filename);
84 } 84 }
85 catch (std::exception const& e) 85 catch (std::exception const& e)
86 { 86 {
@@ -91,9 +91,9 @@ auto real_main(std::span<char const*> args) -> exit_status
91 { 91 {
92 auto const& warns = d2loader.warnings(); 92 auto const& warns = d2loader.warnings();
93 l.warn( 93 l.warn(
94 "Encountered {} unique warnings while loading DATEX II situations " 94 "Encountered {} unique warnings while loading DATEX II situations "
95 "publication", 95 "publication",
96 warns.size()); 96 warns.size());
97 auto i = 0uz; 97 auto i = 0uz;
98 for (auto it = warns.begin(); it != warns.end(); 98 for (auto it = warns.begin(); it != warns.end();
99 it = warns.upper_bound(*it)) 99 it = warns.upper_bound(*it))
@@ -108,12 +108,12 @@ auto real_main(std::span<char const*> args) -> exit_status
108 malloc_trim(0); 108 malloc_trim(0);
109 auto const after_load = chrono::steady_clock::now(); 109 auto const after_load = chrono::steady_clock::now();
110 auto const dur_load = 110 auto const dur_load =
111 chrono::duration_cast<chrono::milliseconds>(after_load - before_load); 111 chrono::duration_cast<chrono::milliseconds>(after_load - before_load);
112 l.info("Loading situations finished in {}", dur_load); 112 l.info("Loading situations finished in {}", dur_load);
113 113
114 auto handler = routemon::api::handler{l, std::move(pub)}; 114 auto handler = routemon::api::handler{l, std::move(pub)};
115 auto http_server = 115 auto http_server =
116 routemon::srv::server{l, std::move(lsel), std::move(handler)}; 116 routemon::srv::server{l, std::move(lsel), std::move(handler)};
117 http_server.spawn(ioc); 117 http_server.spawn(ioc);
118 ioc.run(); 118 ioc.run();
119 119
@@ -129,7 +129,9 @@ auto main(int argc, char* argv[]) -> int
129 return EXIT_FAILURE; 129 return EXIT_FAILURE;
130 } 130 }
131 auto res = real_main( 131 auto res = real_main(
132 std::span{const_cast<char const**>(argv), static_cast<std::size_t>(argc)}); 132 std::span{
133 const_cast<char const**>(argv), static_cast<std::size_t>(argc)
134 });
133 switch (res) 135 switch (res)
134 { 136 {
135 case exit_status::failure: 137 case exit_status::failure:
diff --git a/server/src/problem.cppm b/server/src/problem.cppm
index a72379a..37e6257 100644
--- a/server/src/problem.cppm
+++ b/server/src/problem.cppm
@@ -41,8 +41,8 @@ export struct details
41}; 41};
42 42
43export auto tag_invoke( 43export auto tag_invoke(
44 json::value_from_tag, json::value& jv, details const& details, 44 json::value_from_tag, json::value& jv, details const& details,
45 std::locale locale) -> void 45 std::locale locale) -> void
46{ 46{
47 auto obj = json::object{ 47 auto obj = json::object{
48 {"type", details.type_uri}, 48 {"type", details.type_uri},
diff --git a/server/src/req_ctx.cppm b/server/src/req_ctx.cppm
index 464f2ff..4a64d7d 100644
--- a/server/src/req_ctx.cppm
+++ b/server/src/req_ctx.cppm
@@ -20,8 +20,8 @@ export class req_ctx
20 20
21public: 21public:
22 explicit req_ctx( 22 explicit req_ctx(
23 trace::id tid, std::locale locale, http::verb_set route_verbs, 23 trace::id tid, std::locale locale, http::verb_set route_verbs,
24 bool keep_alive, bhttp::request_header<bhttp::fields> const& req_header) 24 bool keep_alive, bhttp::request_header<bhttp::fields> const& req_header)
25 : tid_{tid}, locale_{locale}, route_verbs_{route_verbs}, 25 : tid_{tid}, locale_{locale}, route_verbs_{route_verbs},
26 keep_alive_{keep_alive}, req_header_{req_header} 26 keep_alive_{keep_alive}, req_header_{req_header}
27 { 27 {
@@ -29,8 +29,8 @@ public:
29 29
30 template <typename ReqBody> 30 template <typename ReqBody>
31 explicit req_ctx( 31 explicit req_ctx(
32 trace::id tid, std::locale locale, http::verb_set route_verbs, 32 trace::id tid, std::locale locale, http::verb_set route_verbs,
33 bhttp::request<ReqBody> const& req) 33 bhttp::request<ReqBody> const& req)
34 : req_ctx{tid, locale, route_verbs, req.keep_alive(), req.base()} 34 : req_ctx{tid, locale, route_verbs, req.keep_alive(), req.base()}
35 { 35 {
36 } 36 }
diff --git a/server/src/rwgps.cppm b/server/src/rwgps.cppm
index f2f1681..3b01e6c 100644
--- a/server/src/rwgps.cppm
+++ b/server/src/rwgps.cppm
@@ -46,7 +46,7 @@ struct get_routes_response
46}; 46};
47 47
48auto tag_invoke(json::value_to_tag<route_summary> const&, json::value const& jv) 48auto tag_invoke(json::value_to_tag<route_summary> const&, json::value const& jv)
49 -> route_summary 49 -> route_summary
50{ 50{
51 return { 51 return {
52 .id = json::value_to<std::int64_t>(jv.at("id")), 52 .id = json::value_to<std::int64_t>(jv.at("id")),
@@ -58,20 +58,20 @@ auto tag_invoke(json::value_to_tag<route_summary> const&, json::value const& jv)
58} 58}
59 59
60auto tag_invoke(json::value_to_tag<pagination> const&, json::value const& jv) 60auto tag_invoke(json::value_to_tag<pagination> const&, json::value const& jv)
61 -> pagination 61 -> pagination
62{ 62{
63 return { 63 return {
64 .record_count = json::value_to<std::size_t>(jv.at("record_count")), 64 .record_count = json::value_to<std::size_t>(jv.at("record_count")),
65 .page_count = json::value_to<std::size_t>(jv.at("page_count")), 65 .page_count = json::value_to<std::size_t>(jv.at("page_count")),
66 .page_size = json::value_to<std::size_t>(jv.at("page_size")), 66 .page_size = json::value_to<std::size_t>(jv.at("page_size")),
67 .next_page_url = 67 .next_page_url =
68 json::value_to<std::optional<std::string>>(jv.at("next_page_url")), 68 json::value_to<std::optional<std::string>>(jv.at("next_page_url")),
69 }; 69 };
70} 70}
71 71
72auto tag_invoke( 72auto tag_invoke(
73 json::value_to_tag<get_routes_meta> const&, json::value const& jv) 73 json::value_to_tag<get_routes_meta> const&, json::value const& jv)
74 -> get_routes_meta 74 -> get_routes_meta
75{ 75{
76 return { 76 return {
77 .pagination = json::value_to<pagination>(jv.at("pagination")), 77 .pagination = json::value_to<pagination>(jv.at("pagination")),
@@ -79,8 +79,8 @@ auto tag_invoke(
79} 79}
80 80
81auto tag_invoke( 81auto tag_invoke(
82 json::value_to_tag<get_routes_response> const&, json::value const& jv) 82 json::value_to_tag<get_routes_response> const&, json::value const& jv)
83 -> get_routes_response 83 -> get_routes_response
84{ 84{
85 return { 85 return {
86 .routes = json::value_to<std::vector<route_summary>>(jv.at("routes")), 86 .routes = json::value_to<std::vector<route_summary>>(jv.at("routes")),
@@ -89,7 +89,7 @@ auto tag_invoke(
89} 89}
90 90
91auto json_value_to_get_routes_response(json::value const& jv) 91auto json_value_to_get_routes_response(json::value const& jv)
92 -> get_routes_response 92 -> get_routes_response
93{ 93{
94 return json::value_to<get_routes_response>(jv); 94 return json::value_to<get_routes_response>(jv);
95} 95}
@@ -127,8 +127,8 @@ export class client
127 127
128public: 128public:
129 explicit client( 129 explicit client(
130 net::io_context& ioc, log::logger const& l, std::string api_key, 130 net::io_context& ioc, log::logger const& l, std::string api_key,
131 std::string auth_token) 131 std::string auth_token)
132 : l_{l.sub("rwgps-client")}, hc_{ioc}, api_key_{std::move(api_key)}, 132 : l_{l.sub("rwgps-client")}, hc_{ioc}, api_key_{std::move(api_key)},
133 auth_token_{std::move(auth_token)} 133 auth_token_{std::move(auth_token)}
134 { 134 {
diff --git a/server/src/sqlite3.cppm b/server/src/sqlite3.cppm
index 45366b8..4623cdc 100644
--- a/server/src/sqlite3.cppm
+++ b/server/src/sqlite3.cppm
@@ -18,7 +18,7 @@ class mutex_guard
18 18
19 friend auto 19 friend auto
20 do_guarded(::sqlite3_mutex* mut, std::invocable<mutex_guard const&> auto f) 20 do_guarded(::sqlite3_mutex* mut, std::invocable<mutex_guard const&> auto f)
21 -> decltype(f(std::declval<mutex_guard const&>())); 21 -> decltype(f(std::declval<mutex_guard const&>()));
22 22
23public: 23public:
24 mutex_guard(mutex_guard const&) = delete; 24 mutex_guard(mutex_guard const&) = delete;
@@ -29,13 +29,13 @@ private:
29}; 29};
30 30
31auto do_guarded(::sqlite3_mutex* mut, std::invocable<mutex_guard const&> auto f) 31auto do_guarded(::sqlite3_mutex* mut, std::invocable<mutex_guard const&> auto f)
32 -> decltype(f(std::declval<mutex_guard const&>())) 32 -> decltype(f(std::declval<mutex_guard const&>()))
33{ 33{
34 return f(mutex_guard{mut}); 34 return f(mutex_guard{mut});
35} 35}
36 36
37auto do_guarded(::sqlite3* dbc, std::invocable<mutex_guard const&> auto f) 37auto do_guarded(::sqlite3* dbc, std::invocable<mutex_guard const&> auto f)
38 -> decltype(f(std::declval<mutex_guard const&>())) 38 -> decltype(f(std::declval<mutex_guard const&>()))
39{ 39{
40 return do_guarded(::sqlite3_db_mutex(dbc), f); 40 return do_guarded(::sqlite3_db_mutex(dbc), f);
41} 41}
@@ -143,16 +143,16 @@ public:
143 { 143 {
144 ::sqlite3* dbc = ::sqlite3_db_handle(stmt_.get()); 144 ::sqlite3* dbc = ::sqlite3_db_handle(stmt_.get());
145 return do_guarded( 145 return do_guarded(
146 dbc, 146 dbc,
147 [&](auto const& guard) -> bool 147 [&](auto const& guard) -> bool
148 { 148 {
149 auto const s = ::sqlite3_step(stmt_.get()); 149 auto const s = ::sqlite3_step(stmt_.get());
150 if (s == SQLITE_ROW) 150 if (s == SQLITE_ROW)
151 return true; 151 return true;
152 if (s == SQLITE_DONE) 152 if (s == SQLITE_DONE)
153 return false; 153 return false;
154 throw error{guard, s, dbc}; 154 throw error{guard, s, dbc};
155 }); 155 });
156 } 156 }
157 157
158 auto scan(scannable auto&... args) -> void 158 auto scan(scannable auto&... args) -> void
@@ -192,15 +192,15 @@ public:
192 auto text(std::string const& param_name, std::string_view str) -> void 192 auto text(std::string const& param_name, std::string_view str) -> void
193 { 193 {
194 int const i = 194 int const i =
195 ::sqlite3_bind_parameter_index(stmt_.get(), param_name.c_str()); 195 ::sqlite3_bind_parameter_index(stmt_.get(), param_name.c_str());
196 if (i == 0) 196 if (i == 0)
197 throw std::invalid_argument{std::format( 197 throw std::invalid_argument{std::format(
198 "bind: no parameter with name {} found", param_name)}; 198 "bind: no parameter with name {} found", param_name)};
199 auto str_size = util::int_from_size(str.size()); 199 auto str_size = util::int_from_size(str.size());
200 if (!str_size.has_value()) 200 if (!str_size.has_value())
201 throw std::invalid_argument{"bind: provided text is too long"}; 201 throw std::invalid_argument{"bind: provided text is too long"};
202 if (auto s = ::sqlite3_bind_text( 202 if (auto s = ::sqlite3_bind_text(
203 stmt_.get(), i, str.data(), *str_size, SQLITE_TRANSIENT); 203 stmt_.get(), i, str.data(), *str_size, SQLITE_TRANSIENT);
204 s != SQLITE_OK) 204 s != SQLITE_OK)
205 { 205 {
206 throw error{s}; 206 throw error{s};
@@ -232,8 +232,8 @@ public:
232 } 232 }
233 233
234 [[nodiscard]] auto query( 234 [[nodiscard]] auto query(
235 std::string const& sql, 235 std::string const& sql,
236 std::function<void(binder&)> const& bf = binder::noop) -> row_reader 236 std::function<void(binder&)> const& bf = binder::noop) -> row_reader
237 { 237 {
238 ::sqlite3_stmt* pstmt = nullptr; 238 ::sqlite3_stmt* pstmt = nullptr;
239 char const* sql_tail = nullptr; 239 char const* sql_tail = nullptr;
@@ -242,25 +242,25 @@ public:
242 || *sql_size >= std::numeric_limits<int>::max() - 1) 242 || *sql_size >= std::numeric_limits<int>::max() - 1)
243 throw std::invalid_argument{"provided input text too large"}; 243 throw std::invalid_argument{"provided input text too large"};
244 do_guarded( 244 do_guarded(
245 mut_, 245 mut_,
246 [&](auto const& guard) -> void 246 [&](auto const& guard) -> void
247 {
248 if (auto s = ::sqlite3_prepare_v2(
249 dbc_, sql.data(), *sql_size + 1, &pstmt, &sql_tail);
250 s != SQLITE_OK)
251 { 247 {
252 if (pstmt != nullptr) 248 if (auto s = ::sqlite3_prepare_v2(
249 dbc_, sql.data(), *sql_size + 1, &pstmt, &sql_tail);
250 s != SQLITE_OK)
253 { 251 {
254 // Use contract_assert when having a compiler with 252 if (pstmt != nullptr)
255 // contracts available 253 {
256 ::sqlite3_finalize(pstmt); 254 // Use contract_assert when having a compiler with
257 throw std::logic_error{ 255 // contracts available
258 "expected stmt to be null after failed preparation" 256 ::sqlite3_finalize(pstmt);
259 }; 257 throw std::logic_error{
258 "expected stmt to be null after failed preparation"
259 };
260 }
261 throw error{guard, s, dbc_};
260 } 262 }
261 throw error{guard, s, dbc_}; 263 });
262 }
263 });
264 if (!pstmt) 264 if (!pstmt)
265 throw std::invalid_argument{"provided input text contains no SQL"}; 265 throw std::invalid_argument{"provided input text contains no SQL"};
266 auto stmt = statement{pstmt}; 266 auto stmt = statement{pstmt};
@@ -274,8 +274,8 @@ public:
274 } 274 }
275 275
276 auto exec( 276 auto exec(
277 std::string const& sql, 277 std::string const& sql,
278 std::function<void(binder&)> const& bf = binder::noop) -> void 278 std::function<void(binder&)> const& bf = binder::noop) -> void
279 { 279 {
280 auto reader = query(sql, bf); 280 auto reader = query(sql, bf);
281 while (reader.next()) 281 while (reader.next())
@@ -289,16 +289,16 @@ export auto open(std::string const& filename) -> connection
289{ 289{
290 ::sqlite3* dbc = nullptr; 290 ::sqlite3* dbc = nullptr;
291 auto s = ::sqlite3_open_v2( 291 auto s = ::sqlite3_open_v2(
292 filename.c_str(), &dbc, 292 filename.c_str(), &dbc,
293 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX 293 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX
294 | SQLITE_OPEN_EXRESCODE, 294 | SQLITE_OPEN_EXRESCODE,
295 nullptr); 295 nullptr);
296 if (s != SQLITE_OK) 296 if (s != SQLITE_OK)
297 { 297 {
298 if (dbc) 298 if (dbc)
299 { 299 {
300 do_guarded( 300 do_guarded(
301 dbc, [&](auto const& guard) -> void { throw error{guard, s, dbc}; }); 301 dbc, [&](auto const& guard) -> void { throw error{guard, s, dbc}; });
302 } 302 }
303 else 303 else
304 { 304 {
diff --git a/server/src/srv.cppm b/server/src/srv.cppm
index 5763bc5..aef28f4 100644
--- a/server/src/srv.cppm
+++ b/server/src/srv.cppm
@@ -63,17 +63,17 @@ public:
63 auto unwrap() -> gpx::file&& 63 auto unwrap() -> gpx::file&&
64 { 64 {
65 return std::visit( 65 return std::visit(
66 util::overloaded{ 66 util::overloaded{
67 [](std::exception_ptr ex) -> gpx::file&& 67 [](std::exception_ptr ex) -> gpx::file&&
68 { 68 {
69 if (ex) 69 if (ex)
70 std::rethrow_exception(ex); 70 std::rethrow_exception(ex);
71 else 71 else
72 throw std::runtime_error{"no GPX file parse result available"}; 72 throw std::runtime_error{"no GPX file parse result available"};
73 },
74 [](gpx::file&& f) -> gpx::file&& { return std::move(f); },
73 }, 75 },
74 [](gpx::file&& f) -> gpx::file&& { return std::move(f); }, 76 std::move(res_));
75 },
76 std::move(res_));
77 } 77 }
78}; 78};
79 79
@@ -100,7 +100,7 @@ struct readable_gpx_body
100 100
101 auto 101 auto
102 init(boost::optional<std::uint64_t> /* n */, beast::error_code& ec) noexcept 102 init(boost::optional<std::uint64_t> /* n */, beast::error_code& ec) noexcept
103 -> void 103 -> void
104 { 104 {
105 try 105 try
106 { 106 {
@@ -114,9 +114,9 @@ struct readable_gpx_body
114 } 114 }
115 } 115 }
116 116
117 auto put( 117 auto
118 beast::concepts::const_buffer_sequence auto b, 118 put(beast::concepts::const_buffer_sequence auto b,
119 beast::error_code& ec) noexcept -> std::size_t 119 beast::error_code& ec) noexcept -> std::size_t
120 { 120 {
121 auto total = 0uz; 121 auto total = 0uz;
122 try 122 try
@@ -125,7 +125,9 @@ struct readable_gpx_body
125 it != net::buffer_sequence_end(b); it++) 125 it != net::buffer_sequence_end(b); it++)
126 { 126 {
127 r_.put( 127 r_.put(
128 std::string_view{static_cast<char const*>(it->data()), it->size()}); 128 std::string_view{
129 static_cast<char const*>(it->data()), it->size()
130 });
129 total += it->size(); 131 total += it->size();
130 } 132 }
131 ec = {}; 133 ec = {};
@@ -166,13 +168,13 @@ public:
166 168
167private: 169private:
168 auto handle_process_gpx(l0_ctx ctx, http::readable_request r) 170 auto handle_process_gpx(l0_ctx ctx, http::readable_request r)
169 -> net::awaitable<http::presponse> 171 -> net::awaitable<http::presponse>
170 { 172 {
171 auto gpx_file = gpx::file{}; 173 auto gpx_file = gpx::file{};
172 try 174 try
173 { 175 {
174 auto req = 176 auto req =
175 co_await http::read_request<readable_gpx_body>(ctx, std::move(r)); 177 co_await http::read_request<readable_gpx_body>(ctx, std::move(r));
176 gpx_file = std::move(req->body().unwrap()); 178 gpx_file = std::move(req->body().unwrap());
177 } 179 }
178 catch (std::exception& ex) 180 catch (std::exception& ex)
@@ -184,7 +186,7 @@ private:
184 .type_uri = "https://routemon.fautchen.eu/problems/gpx-parse-failed", 186 .type_uri = "https://routemon.fautchen.eu/problems/gpx-parse-failed",
185 }; 187 };
186 co_return http::problem_rsp( 188 co_return http::problem_rsp(
187 ctx, tpl.instantiate(), http::keep_alive{false}); 189 ctx, tpl.instantiate(), http::keep_alive{false});
188 } 190 }
189 191
190 // TODO: catch handler exceptions and return 500 when raised? 192 // TODO: catch handler exceptions and return 500 when raised?
@@ -199,11 +201,11 @@ private:
199 "internal-server-error", 201 "internal-server-error",
200 }; 202 };
201 co_return http::problem_rsp( 203 co_return http::problem_rsp(
202 ctx, tpl.instantiate(), http::keep_alive{true}); 204 ctx, tpl.instantiate(), http::keep_alive{true});
203 } 205 }
204 206
205 auto rsp = http::make_rsp<bhttp::string_body>( 207 auto rsp = http::make_rsp<bhttp::string_body>(
206 bhttp::status::ok, http::keep_alive{true}); 208 bhttp::status::ok, http::keep_alive{true});
207 rsp.set(bhttp::field::content_type, "application/json"); 209 rsp.set(bhttp::field::content_type, "application/json");
208 rsp.body() = json::serialize(json::value_from(*mres)); 210 rsp.body() = json::serialize(json::value_from(*mres));
209 rsp.prepare_payload(); 211 rsp.prepare_payload();
@@ -211,14 +213,14 @@ private:
211 } 213 }
212 214
213 auto handle_sysinfo(l0_ctx ctx, http::readable_request r) 215 auto handle_sysinfo(l0_ctx ctx, http::readable_request r)
214 -> net::awaitable<http::presponse> 216 -> net::awaitable<http::presponse>
215 { 217 {
216 auto req = 218 auto req =
217 co_await http::read_request<bhttp::empty_body>(ctx, std::move(r)); 219 co_await http::read_request<bhttp::empty_body>(ctx, std::move(r));
218 auto info = inner_.sysinfo(); 220 auto info = inner_.sysinfo();
219 221
220 auto rsp = http::make_rsp<bhttp::string_body>( 222 auto rsp = http::make_rsp<bhttp::string_body>(
221 bhttp::status::ok, http::keep_alive{true}); 223 bhttp::status::ok, http::keep_alive{true});
222 rsp.set(bhttp::field::content_type, "application/json"); 224 rsp.set(bhttp::field::content_type, "application/json");
223 rsp.body() = json::serialize(json::value_from(info)); 225 rsp.body() = json::serialize(json::value_from(info));
224 rsp.prepare_payload(); 226 rsp.prepare_payload();
@@ -255,13 +257,13 @@ export class server
255 http::server<handler::outer_ctx> srv_; 257 http::server<handler::outer_ctx> srv_;
256 258
257 static auto make_global_middleware() 259 static auto make_global_middleware()
258 -> http::middleware_t<http::base_ctx, handler::outer_ctx> 260 -> http::middleware_t<http::base_ctx, handler::outer_ctx>
259 { 261 {
260 return http::middleware_compose< 262 return http::middleware_compose<
261 http::base_ctx, http::trace_id_ctx<http::base_ctx>, 263 http::base_ctx, http::trace_id_ctx<http::base_ctx>,
262 http::trace_id_ctx<http::base_ctx>>( 264 http::trace_id_ctx<http::base_ctx>>(
263 http::trace_id_middleware<http::base_ctx>, 265 http::trace_id_middleware<http::base_ctx>,
264 http::lax_cors_middleware<http::trace_id_ctx<http::base_ctx>>); 266 http::lax_cors_middleware<http::trace_id_ctx<http::base_ctx>>);
265 } 267 }
266 268
267public: 269public:
diff --git a/server/src/time.cppm b/server/src/time.cppm
index d77a30b..8f53bd9 100644
--- a/server/src/time.cppm
+++ b/server/src/time.cppm
@@ -31,7 +31,7 @@ public:
31 } 31 }
32 32
33 [[nodiscard]] auto except(period other) const 33 [[nodiscard]] auto except(period other) const
34 -> std::pair<std::optional<period>, std::optional<period>> 34 -> std::pair<std::optional<period>, std::optional<period>>
35 { 35 {
36 auto const before_start = start_; 36 auto const before_start = start_;
37 auto const before_end = other.start(); 37 auto const before_end = other.start();
diff --git a/server/src/trace.cppm b/server/src/trace.cppm
index 3dbed12..35d32f3 100644
--- a/server/src/trace.cppm
+++ b/server/src/trace.cppm
@@ -21,9 +21,9 @@ public:
21 { 21 {
22 namespace chrono = std::chrono; 22 namespace chrono = std::chrono;
23 auto const unix_time_ms_signed = static_cast<std::int64_t>( 23 auto const unix_time_ms_signed = static_cast<std::int64_t>(
24 chrono::duration_cast<chrono::milliseconds>( 24 chrono::duration_cast<chrono::milliseconds>(
25 chrono::system_clock::now().time_since_epoch()) 25 chrono::system_clock::now().time_since_epoch())
26 .count()); 26 .count());
27 if (unix_time_ms_signed < 0) 27 if (unix_time_ms_signed < 0)
28 throw std::runtime_error{"system time before UNIX epoch"}; 28 throw std::runtime_error{"system time before UNIX epoch"};
29 auto const unix_time_ms = static_cast<std::uint64_t>(unix_time_ms_signed); 29 auto const unix_time_ms = static_cast<std::uint64_t>(unix_time_ms_signed);
@@ -36,8 +36,8 @@ public:
36 { 36 {
37 unsigned long e = ERR_get_error(); 37 unsigned long e = ERR_get_error();
38 throw std::runtime_error{std::format( 38 throw std::runtime_error{std::format(
39 "failed to generate UUID(v7): {} ({}, code {})", 39 "failed to generate UUID(v7): {} ({}, code {})",
40 ERR_reason_error_string(e), ERR_lib_error_string(e), e)}; 40 ERR_reason_error_string(e), ERR_lib_error_string(e), e)};
41 } 41 }
42 42
43 auto version = std::uint64_t{0b0111}; 43 auto version = std::uint64_t{0b0111};
@@ -67,8 +67,8 @@ public:
67 auto low_low = (low_ & 0x0000'ffff'ffff'ffff) >> 0; 67 auto low_low = (low_ & 0x0000'ffff'ffff'ffff) >> 0;
68 68
69 std::format_to( 69 std::format_to(
70 target.begin(), "{:0>8x}-{:0>4x}-{:0>4x}-{:0>4x}-{:0>12x}", high_high, 70 target.begin(), "{:0>8x}-{:0>4x}-{:0>4x}-{:0>4x}-{:0>12x}", high_high,
71 high_low_high, low_low_high, high_low, low_low); 71 high_low_high, low_low_high, high_low, low_low);
72 target.back() = '\0'; 72 target.back() = '\0';
73 } 73 }
74}; 74};
diff --git a/server/src/util.cppm b/server/src/util.cppm
index 2e20e8b..bdccf10 100644
--- a/server/src/util.cppm
+++ b/server/src/util.cppm
@@ -14,9 +14,9 @@ struct overloaded : Ts...
14}; 14};
15 15
16export constexpr auto parse_double( 16export constexpr auto parse_double(
17 std::string_view s, 17 std::string_view s,
18 std::chars_format fmt = std::chars_format::general) noexcept 18 std::chars_format fmt = std::chars_format::general) noexcept
19 -> std::optional<double> 19 -> std::optional<double>
20{ 20{
21 auto x = 0.0; 21 auto x = 0.0;
22 auto [_, ec] = std::from_chars(s.data(), s.data() + s.size(), x, fmt); 22 auto [_, ec] = std::from_chars(s.data(), s.data() + s.size(), x, fmt);
@@ -31,9 +31,9 @@ export constexpr auto parse_double(
31} 31}
32 32
33export constexpr auto parse_float( 33export constexpr auto parse_float(
34 std::string_view s, 34 std::string_view s,
35 std::chars_format fmt = std::chars_format::general) noexcept 35 std::chars_format fmt = std::chars_format::general) noexcept
36 -> std::optional<float> 36 -> std::optional<float>
37{ 37{
38 auto x = 0.0; 38 auto x = 0.0;
39 auto [_, ec] = std::from_chars(s.data(), s.data() + s.size(), x, fmt); 39 auto [_, ec] = std::from_chars(s.data(), s.data() + s.size(), x, fmt);
@@ -62,7 +62,7 @@ public:
62 static auto nil() -> std::shared_ptr<aolist<T>> { return nullptr; } 62 static auto nil() -> std::shared_ptr<aolist<T>> { return nullptr; }
63 63
64 static auto cons(T v, std::shared_ptr<aolist<T> const> l) 64 static auto cons(T v, std::shared_ptr<aolist<T> const> l)
65 -> std::shared_ptr<aolist<T>> 65 -> std::shared_ptr<aolist<T>>
66 { 66 {
67 return std::shared_ptr<aolist<T>>{new aolist<T>{v, l}}; 67 return std::shared_ptr<aolist<T>>{new aolist<T>{v, l}};
68 } 68 }
@@ -75,8 +75,8 @@ public:
75export constexpr auto size_from_int(int x) -> std::optional<std::size_t> 75export constexpr auto size_from_int(int x) -> std::optional<std::size_t>
76{ 76{
77 static_assert( 77 static_assert(
78 sizeof(int) <= sizeof(std::size_t), 78 sizeof(int) <= sizeof(std::size_t),
79 "cannot cast int to smaller size_t type"); 79 "cannot cast int to smaller size_t type");
80 if (x < 0) 80 if (x < 0)
81 return std::nullopt; 81 return std::nullopt;
82 return static_cast<std::size_t>(x); 82 return static_cast<std::size_t>(x);
@@ -155,7 +155,8 @@ public:
155 { 155 {
156 s_ = rhs.s_; 156 s_ = rhs.s_;
157 length_.store( 157 length_.store(
158 rhs.length_.load(std::memory_order_acquire), std::memory_order_release); 158 rhs.length_.load(std::memory_order_acquire),
159 std::memory_order_release);
159 } 160 }
160 return *this; 161 return *this;
161 } 162 }
@@ -202,7 +203,7 @@ public:
202}; 203};
203 204
204constexpr auto operator""_zsv(char const* s, std::size_t length) noexcept 205constexpr auto operator""_zsv(char const* s, std::size_t length) noexcept
205 -> zstring_view 206 -> zstring_view
206{ 207{
207 return zstring_view{s, length}; 208 return zstring_view{s, length};
208} 209}
@@ -213,7 +214,7 @@ auto operator==(zstring_view lhs, zstring_view rhs) -> bool
213} 214}
214 215
215export constexpr auto split_on(std::string_view s, char c) 216export constexpr auto split_on(std::string_view s, char c)
216 -> std::pair<std::string_view, std::optional<std::string_view>> 217 -> std::pair<std::string_view, std::optional<std::string_view>>
217{ 218{
218 if (auto i = s.find(c); i != std::string_view::npos) 219 if (auto i = s.find(c); i != std::string_view::npos)
219 return std::make_pair(s.substr(0, i), s.substr(i + 1)); 220 return std::make_pair(s.substr(0, i), s.substr(i + 1));
diff --git a/server/src/xml.cpp b/server/src/xml.cpp
index 7cf4415..c1b4358 100644
--- a/server/src/xml.cpp
+++ b/server/src/xml.cpp
@@ -22,7 +22,7 @@ executor::executor() : p_{XML_ParserCreateNS("UTF-8", detail::qname_sep)}
22 XML_SetProcessingInstructionHandler(p_, handle_processing_instructions); 22 XML_SetProcessingInstructionHandler(p_, handle_processing_instructions);
23 XML_SetExternalEntityRefHandler(p_, handle_external_entity_ref); 23 XML_SetExternalEntityRefHandler(p_, handle_external_entity_ref);
24 XML_SetNamespaceDeclHandler( 24 XML_SetNamespaceDeclHandler(
25 p_, handle_start_namespace_decl, handle_end_namespace_decl); 25 p_, handle_start_namespace_decl, handle_end_namespace_decl);
26 XML_SetXmlDeclHandler(p_, handle_xml_decl); 26 XML_SetXmlDeclHandler(p_, handle_xml_decl);
27} 27}
28 28
@@ -41,7 +41,7 @@ auto executor::read(std::string_view xml, bool is_final) -> void
41 throw std::runtime_error{"refusing to restart parser that was thrown in"}; 41 throw std::runtime_error{"refusing to restart parser that was thrown in"};
42 // TODO: narrow_cast 42 // TODO: narrow_cast
43 if (auto s = 43 if (auto s =
44 XML_Parse(p_, xml.data(), static_cast<int>(xml.size()), is_final); 44 XML_Parse(p_, xml.data(), static_cast<int>(xml.size()), is_final);
45 s != XML_STATUS_OK) 45 s != XML_STATUS_OK)
46 { 46 {
47 auto errc = XML_GetErrorCode(p_); 47 auto errc = XML_GetErrorCode(p_);
@@ -53,7 +53,7 @@ auto executor::read(std::string_view xml, bool is_final) -> void
53 else 53 else
54 { 54 {
55 throw std::runtime_error{std::format( 55 throw std::runtime_error{std::format(
56 "failed to parse XML: {}", XML_ErrorString(errc))}; 56 "failed to parse XML: {}", XML_ErrorString(errc))};
57 } 57 }
58 } 58 }
59} 59}
diff --git a/server/src/xml.cppm b/server/src/xml.cppm
index 0c319e6..4648047 100644
--- a/server/src/xml.cppm
+++ b/server/src/xml.cppm
@@ -66,7 +66,7 @@ public:
66 }; 66 };
67 67
68 inline explicit attribute_view_iterator( 68 inline explicit attribute_view_iterator(
69 std::string_view default_ns_uri, char const* const* attrs) 69 std::string_view default_ns_uri, char const* const* attrs)
70 : default_ns_uri_{default_ns_uri}, attrs_{attrs} 70 : default_ns_uri_{default_ns_uri}, attrs_{attrs}
71 { 71 {
72 } 72 }
@@ -105,7 +105,7 @@ class attribute_view : std::ranges::view_base
105 105
106public: 106public:
107 inline explicit attribute_view( 107 inline explicit attribute_view(
108 std::string_view default_ns_uri, char const** attrs) 108 std::string_view default_ns_uri, char const** attrs)
109 : default_ns_uri_{default_ns_uri}, 109 : default_ns_uri_{default_ns_uri},
110 attrs_{const_cast<char const* const*>(attrs)} 110 attrs_{const_cast<char const* const*>(attrs)}
111 { 111 {
@@ -122,7 +122,7 @@ public:
122 } 122 }
123 123
124 inline auto lookup(qname_view want) 124 inline auto lookup(qname_view want)
125 -> std::optional<util::not_null<util::lazy_zstring_view>> 125 -> std::optional<util::not_null<util::lazy_zstring_view>>
126 { 126 {
127 for (auto const& [name, v] : *this) 127 for (auto const& [name, v] : *this)
128 { 128 {
@@ -200,8 +200,8 @@ struct eof_event
200}; 200};
201 201
202using event = std::variant< 202using event = std::variant<
203 start_element_event, end_element_event, character_data_event, 203 start_element_event, end_element_event, character_data_event,
204 processing_instructions_event, xml_decl_event, eof_event>; 204 processing_instructions_event, xml_decl_event, eof_event>;
205 205
206template <class T> 206template <class T>
207concept event_type = requires(event ev) { std::get<T>(ev); }; 207concept event_type = requires(event ev) { std::get<T>(ev); };
@@ -237,7 +237,7 @@ public:
237 } 237 }
238 238
239 [[nodiscard]] inline auto continuation() const 239 [[nodiscard]] inline auto continuation() const
240 -> std::coroutine_handle<promise_base> 240 -> std::coroutine_handle<promise_base>
241 { 241 {
242 return continuation_; 242 return continuation_;
243 } 243 }
@@ -285,11 +285,11 @@ class executor
285 s != XML_STATUS_OK) 285 s != XML_STATUS_OK)
286 { 286 {
287 ex_ = std::make_exception_ptr( 287 ex_ = std::make_exception_ptr(
288 std::runtime_error{"unexpected error when stopping XML parser"}); 288 std::runtime_error{"unexpected error when stopping XML parser"});
289 return; 289 return;
290 } 290 }
291 ex_ = std::make_exception_ptr( 291 ex_ = std::make_exception_ptr(
292 std::runtime_error{"parser did not consume entire XML document"}); 292 std::runtime_error{"parser did not consume entire XML document"});
293 return; 293 return;
294 } 294 }
295 continuation_.resume(); 295 continuation_.resume();
@@ -302,74 +302,74 @@ class executor
302 302
303 static auto 303 static auto
304 handle_start_element(void* ctx, char const* name, char const** attrs) noexcept 304 handle_start_element(void* ctx, char const* name, char const** attrs) noexcept
305 -> void 305 -> void
306 { 306 {
307 auto qname = detail::split_name(name); 307 auto qname = detail::split_name(name);
308 static_cast<executor*>(ctx)->try_handle_event( 308 static_cast<executor*>(ctx)->try_handle_event(
309 start_element_event{ 309 start_element_event{
310 .name = qname, 310 .name = qname,
311 .attrs = attribute_view{qname.ns_uri, attrs}, 311 .attrs = attribute_view{qname.ns_uri, attrs},
312 }); 312 });
313 } 313 }
314 314
315 static auto handle_end_element(void* ctx, char const* name) noexcept -> void 315 static auto handle_end_element(void* ctx, char const* name) noexcept -> void
316 { 316 {
317 static_cast<executor*>(ctx)->try_handle_event( 317 static_cast<executor*>(ctx)->try_handle_event(
318 end_element_event{ 318 end_element_event{
319 .name = detail::split_name(name), 319 .name = detail::split_name(name),
320 }); 320 });
321 } 321 }
322 322
323 static auto handle_character_data(void* ctx, char const* s, int len) noexcept 323 static auto handle_character_data(void* ctx, char const* s, int len) noexcept
324 -> void 324 -> void
325 { 325 {
326 static_cast<executor*>(ctx)->try_handle_event( 326 static_cast<executor*>(ctx)->try_handle_event(
327 character_data_event{ 327 character_data_event{
328 .data = std::string_view{s, static_cast<std::size_t>(len)}, 328 .data = std::string_view{s, static_cast<std::size_t>(len)},
329 }); 329 });
330 } 330 }
331 331
332 static auto handle_processing_instructions( 332 static auto handle_processing_instructions(
333 void* ctx, char const* target, char const* data) noexcept -> void 333 void* ctx, char const* target, char const* data) noexcept -> void
334 { 334 {
335 static_cast<executor*>(ctx)->try_handle_event( 335 static_cast<executor*>(ctx)->try_handle_event(
336 processing_instructions_event{ 336 processing_instructions_event{
337 .target = util::lazy_zstring_view{target}, 337 .target = util::lazy_zstring_view{target},
338 .data = util::lazy_zstring_view{data}, 338 .data = util::lazy_zstring_view{data},
339 }); 339 });
340 } 340 }
341 341
342 static auto handle_external_entity_ref( 342 static auto handle_external_entity_ref(
343 XML_Parser, char const* /* context */, char const* /* base */, 343 XML_Parser, char const* /* context */, char const* /* base */,
344 char const* /* system_id */, char const* /* public_id */) noexcept -> int 344 char const* /* system_id */, char const* /* public_id */) noexcept -> int
345 { 345 {
346 return XML_STATUS_ERROR; 346 return XML_STATUS_ERROR;
347 } 347 }
348 348
349 static auto handle_start_namespace_decl( 349 static auto handle_start_namespace_decl(
350 void* ctx, char const* prefix, char const* uri) noexcept -> void 350 void* ctx, char const* prefix, char const* uri) noexcept -> void
351 { 351 {
352 if (prefix) 352 if (prefix)
353 { 353 {
354 static_cast<executor*>(ctx) 354 static_cast<executor*>(ctx)
355 ->namespaces_[std::string_view{prefix}] 355 ->namespaces_[std::string_view{prefix}]
356 .emplace_back(uri); 356 .emplace_back(uri);
357 } 357 }
358 else 358 else
359 { 359 {
360 static_cast<executor*>(ctx)->default_namespace_.push_back( 360 static_cast<executor*>(ctx)->default_namespace_.push_back(
361 uri ? std::make_optional<std::string>(uri) : std::nullopt); 361 uri ? std::make_optional<std::string>(uri) : std::nullopt);
362 } 362 }
363 } 363 }
364 364
365 static auto handle_end_namespace_decl(void* ctx, char const* prefix) noexcept 365 static auto handle_end_namespace_decl(void* ctx, char const* prefix) noexcept
366 -> void 366 -> void
367 { 367 {
368 if (prefix) 368 if (prefix)
369 { 369 {
370 static_cast<executor*>(ctx) 370 static_cast<executor*>(ctx)
371 ->namespaces_[std::string_view{prefix}] 371 ->namespaces_[std::string_view{prefix}]
372 .pop_back(); 372 .pop_back();
373 } 373 }
374 else 374 else
375 { 375 {
@@ -378,16 +378,16 @@ class executor
378 } 378 }
379 379
380 static auto handle_xml_decl( 380 static auto handle_xml_decl(
381 void* ctx, char const* version, char const* encoding, 381 void* ctx, char const* version, char const* encoding,
382 int standalone) noexcept -> void 382 int standalone) noexcept -> void
383 { 383 {
384 static_cast<executor*>(ctx)->try_handle_event( 384 static_cast<executor*>(ctx)->try_handle_event(
385 xml_decl_event{ 385 xml_decl_event{
386 .version = util::lazy_zstring_view{version}, 386 .version = util::lazy_zstring_view{version},
387 .encoding = util::lazy_zstring_view{encoding}, 387 .encoding = util::lazy_zstring_view{encoding},
388 .standalone = 388 .standalone = standalone < 0 ? std::nullopt
389 standalone < 0 ? std::nullopt : std::make_optional(standalone > 0), 389 : std::make_optional(standalone > 0),
390 }); 390 });
391 } 391 }
392 392
393 inline auto advance_flag() -> bool { return advance_; } 393 inline auto advance_flag() -> bool { return advance_; }
@@ -431,7 +431,7 @@ public:
431 inline auto event() const -> std::optional<event> const& { return ev_; } 431 inline auto event() const -> std::optional<event> const& { return ev_; }
432 432
433 inline auto resolve_namespace(std::string_view prefix) 433 inline auto resolve_namespace(std::string_view prefix)
434 -> std::optional<std::string_view> 434 -> std::optional<std::string_view>
435 { 435 {
436 if (auto it = namespaces_.find(prefix); 436 if (auto it = namespaces_.find(prefix);
437 it != namespaces_.end() && !it->second.empty()) 437 it != namespaces_.end() && !it->second.empty())
@@ -573,7 +573,7 @@ public:
573 return false; 573 return false;
574 } 574 }
575 auto await_suspend(std::coroutine_handle<promise<T>> h) 575 auto await_suspend(std::coroutine_handle<promise<T>> h)
576 -> std::coroutine_handle<> 576 -> std::coroutine_handle<>
577 { 577 {
578 // Passed coroutine handle will be the same as 578 // Passed coroutine handle will be the same as
579 // parser<T>::handle_type::from_promise(*this) 579 // parser<T>::handle_type::from_promise(*this)
@@ -621,13 +621,13 @@ using parser_result_t = T::result_type;
621 621
622template <class T, class... Args> 622template <class T, class... Args>
623concept parser_invocable = 623concept parser_invocable =
624 std::invocable<T, Args...> 624 std::invocable<T, Args...>
625 && parser_of<std::invoke_result_t<T, Args...>, unconstrained>; 625 && parser_of<std::invoke_result_t<T, Args...>, unconstrained>;
626 626
627template <class Fn, class... Args> 627template <class Fn, class... Args>
628 requires parser_invocable<Fn, Args...> 628 requires parser_invocable<Fn, Args...>
629using parser_invoke_result_t = 629using parser_invoke_result_t =
630 parser_result_t<std::invoke_result_t<Fn, Args...>>; 630 parser_result_t<std::invoke_result_t<Fn, Args...>>;
631 631
632template <event_type T> 632template <event_type T>
633auto expect_event(executor_ref e) -> parser<T> 633auto expect_event(executor_ref e) -> parser<T>
@@ -637,28 +637,28 @@ auto expect_event(executor_ref e) -> parser<T>
637 { 637 {
638 auto pos = e->position(); 638 auto pos = e->position();
639 throw std::runtime_error{std::format( 639 throw std::runtime_error{std::format(
640 "at {}:{}: unexpected event type, have {}", pos.line, pos.col, 640 "at {}:{}: unexpected event type, have {}", pos.line, pos.col,
641 ev.index())}; 641 ev.index())};
642 } 642 }
643 e->set_advance_flag(); 643 e->set_advance_flag();
644 co_return std::get<T>(ev); 644 co_return std::get<T>(ev);
645} 645}
646 646
647inline auto expect_start_element(executor_ref e, qname_view want) 647inline auto expect_start_element(executor_ref e, qname_view want)
648 -> parser<attribute_view> 648 -> parser<attribute_view>
649{ 649{
650 auto ev = co_await expect_event<start_element_event>(e); 650 auto ev = co_await expect_event<start_element_event>(e);
651 if (ev.name != want) 651 if (ev.name != want)
652 { 652 {
653 auto pos = e->position(); 653 auto pos = e->position();
654 throw std::runtime_error{std::format( 654 throw std::runtime_error{std::format(
655 "at {}:{}: unexpected element started", pos.line, pos.col)}; 655 "at {}:{}: unexpected element started", pos.line, pos.col)};
656 } 656 }
657 co_return ev.attrs; 657 co_return ev.attrs;
658} 658}
659 659
660inline auto allow_start_element(executor_ref e, qname_view want) 660inline auto allow_start_element(executor_ref e, qname_view want)
661 -> parser<std::optional<attribute_view>> 661 -> parser<std::optional<attribute_view>>
662{ 662{
663 auto ev = co_await current_event(e); 663 auto ev = co_await current_event(e);
664 if (auto const* pev = std::get_if<start_element_event>(&ev)) 664 if (auto const* pev = std::get_if<start_element_event>(&ev))
@@ -707,9 +707,9 @@ inline auto ignore_whitespace(executor_ref e) -> parser<void>
707} 707}
708 708
709auto expect_element( 709auto expect_element(
710 executor_ref e, qname_view want, 710 executor_ref e, qname_view want,
711 parser_invocable<executor_ref, attribute_view> auto p) 711 parser_invocable<executor_ref, attribute_view> auto p)
712 -> parser<parser_invoke_result_t<decltype(p), executor_ref, attribute_view>> 712 -> parser<parser_invoke_result_t<decltype(p), executor_ref, attribute_view>>
713{ 713{
714 co_await ignore_whitespace(e); 714 co_await ignore_whitespace(e);
715 auto attrs = co_await expect_start_element(e, want); 715 auto attrs = co_await expect_start_element(e, want);
@@ -717,14 +717,14 @@ auto expect_element(
717 co_await expect_end_element(e, want); 717 co_await expect_end_element(e, want);
718 co_await ignore_whitespace(e); 718 co_await ignore_whitespace(e);
719 co_return std::forward< 719 co_return std::forward<
720 parser_invoke_result_t<decltype(p), executor_ref, attribute_view>>(res); 720 parser_invoke_result_t<decltype(p), executor_ref, attribute_view>>(res);
721} 721}
722 722
723auto allow_element( 723auto allow_element(
724 executor_ref e, qname_view want, 724 executor_ref e, qname_view want,
725 parser_invocable<executor_ref, attribute_view> auto p) 725 parser_invocable<executor_ref, attribute_view> auto p)
726 -> parser<std::optional< 726 -> parser<std::optional<
727 parser_invoke_result_t<decltype(p), executor_ref, attribute_view>>> 727 parser_invoke_result_t<decltype(p), executor_ref, attribute_view>>>
728{ 728{
729 co_await ignore_whitespace(e); 729 co_await ignore_whitespace(e);
730 if (auto mattrs = co_await allow_start_element(e, want)) 730 if (auto mattrs = co_await allow_start_element(e, want))
@@ -733,18 +733,18 @@ auto allow_element(
733 co_await expect_end_element(e, want); 733 co_await expect_end_element(e, want);
734 co_await ignore_whitespace(e); 734 co_await ignore_whitespace(e);
735 co_return std::make_optional( 735 co_return std::make_optional(
736 std::forward< 736 std::forward<
737 parser_invoke_result_t<decltype(p), executor_ref, attribute_view>>( 737 parser_invoke_result_t<decltype(p), executor_ref, attribute_view>>(
738 res)); 738 res));
739 } 739 }
740 co_return std::nullopt; 740 co_return std::nullopt;
741} 741}
742 742
743auto allow_element( 743auto allow_element(
744 executor_ref e, qname_view want, 744 executor_ref e, qname_view want,
745 parser_invocable<executor_ref, attribute_view> auto p) -> parser<bool> 745 parser_invocable<executor_ref, attribute_view> auto p) -> parser<bool>
746 requires std::is_void_v< 746 requires std::is_void_v<
747 parser_invoke_result_t<decltype(p), executor_ref, attribute_view>> 747 parser_invoke_result_t<decltype(p), executor_ref, attribute_view>>
748{ 748{
749 co_await ignore_whitespace(e); 749 co_await ignore_whitespace(e);
750 if (auto mattrs = co_await allow_start_element(e, want)) 750 if (auto mattrs = co_await allow_start_element(e, want))
@@ -759,7 +759,7 @@ auto allow_element(
759 759
760inline auto 760inline auto
761ignore_contents(executor_ref e, std::optional<qname_view> muntil = std::nullopt) 761ignore_contents(executor_ref e, std::optional<qname_view> muntil = std::nullopt)
762 -> parser<void> 762 -> parser<void>
763{ 763{
764 std::size_t depth = 0; 764 std::size_t depth = 0;
765 while (true) 765 while (true)
@@ -791,7 +791,7 @@ ignore_contents(executor_ref e, std::optional<qname_view> muntil = std::nullopt)
791 } 791 }
792} 792}
793inline auto ignore_element_contents(executor_ref e, attribute_view) 793inline auto ignore_element_contents(executor_ref e, attribute_view)
794 -> parser<void> 794 -> parser<void>
795{ 795{
796 co_await ignore_contents(e); 796 co_await ignore_contents(e);
797} 797}
@@ -814,7 +814,7 @@ inline auto read_string(executor_ref e) -> parser<std::string>
814 } 814 }
815} 815}
816inline auto read_string_contents(executor_ref e, attribute_view) 816inline auto read_string_contents(executor_ref e, attribute_view)
817 -> parser<std::string> 817 -> parser<std::string>
818{ 818{
819 co_return co_await read_string(e); 819 co_return co_await read_string(e);
820} 820}
@@ -823,7 +823,7 @@ template <auto f>
823auto hohalo() 823auto hohalo()
824{ 824{
825 return []<class... Args>( 825 return []<class... Args>(
826 Args&&... args) -> std::invoke_result_t<decltype(f), Args...> 826 Args&&... args) -> std::invoke_result_t<decltype(f), Args...>
827 { co_return co_await f(std::forward<Args>(args)...); }; 827 { co_return co_await f(std::forward<Args>(args)...); };
828} 828}
829 829