summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/src/api.cpp155
-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.cppm142
-rw-r--r--server/src/geo.cppm34
-rw-r--r--server/src/gpx.cpp66
-rw-r--r--server/src/http_client.cppm6
-rw-r--r--server/src/http_server.cppm390
-rw-r--r--server/src/locale.cppm39
-rw-r--r--server/src/log.cppm14
-rw-r--r--server/src/main.cpp36
-rw-r--r--server/src/problem.cppm10
-rw-r--r--server/src/req_ctx.cppm8
-rw-r--r--server/src/rwgps.cppm50
-rw-r--r--server/src/sqlite3.cppm86
-rw-r--r--server/src/srv.cppm94
-rw-r--r--server/src/time.cppm4
-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.cppm144
22 files changed, 748 insertions, 770 deletions
diff --git a/server/src/api.cpp b/server/src/api.cpp
index a6eef23..73dee32 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,59 +48,56 @@ 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)},
56 }; 56 };
57} 57}
58 58
59auto tag_invoke( 59auto tag_invoke(
60 json::value_from_tag, json::value& jv, relevant_situation const& sit) 60 json::value_from_tag, json::value& jv, relevant_situation const& sit) -> void
61 -> void
62{ 61{
63 jv = json::object{ 62 jv = json::object{
64 {"id", json::value_from(sit.id)}, 63 {"id", json::value_from(sit.id)},
65 {"location", 64 {"location", sit.location ? json_value_from_point(*sit.location) : nullptr},
66 sit.location ? json_value_from_point(*sit.location) : nullptr}, 65 {"comments", json::value_from(sit.comments)},
67 {"comments", json::value_from(sit.comments)}, 66 {"relevant_road_closures", json::value_from(sit.relevant_road_closures)},
68 {"relevant_road_closures", json::value_from(sit.relevant_road_closures)},
69 }; 67 };
70} 68}
71 69
72auto tag_invoke(json::value_from_tag, json::value& jv, track_segment const& seg) 70auto tag_invoke(json::value_from_tag, json::value& jv, track_segment const& seg)
73 -> void 71 -> void
74{ 72{
75 jv = json::object{ 73 jv = json::object{
76 {"points", json_value_from_linestring(seg.points)}, 74 {"points", json_value_from_linestring(seg.points)},
77 }; 75 };
78} 76}
79 77
80auto tag_invoke(json::value_from_tag, json::value& jv, track const& track) 78auto tag_invoke(json::value_from_tag, json::value& jv, track const& track)
81 -> void 79 -> void
82{ 80{
83 jv = json::object{ 81 jv = json::object{
84 {"segments", json::value_from(track.segments)}, 82 {"segments", json::value_from(track.segments)},
85 }; 83 };
86} 84}
87 85
88auto tag_invoke( 86auto tag_invoke(
89 json::value_from_tag, json::value& jv, process_gpx_result const& res) 87 json::value_from_tag, json::value& jv, process_gpx_result const& res) -> void
90 -> void
91{ 88{
92 jv = json::object{ 89 jv = json::object{
93 {"tracks", json::value_from(res.tracks)}, 90 {"tracks", json::value_from(res.tracks)},
94 {"relevant_situations", json::value_from(res.relevant_situations)}, 91 {"relevant_situations", json::value_from(res.relevant_situations)},
95 }; 92 };
96} 93}
97 94
98auto tag_invoke(json::value_from_tag, json::value& jv, sysinfo const& info) 95auto tag_invoke(json::value_from_tag, json::value& jv, sysinfo const& info)
99 -> void 96 -> void
100{ 97{
101 jv = json::object{ 98 jv = json::object{
102 {"using_publication_of", 99 {"using_publication_of",
103 std::format("{:%FT%TZ}", info.using_publication_of)}, 100 std::format("{:%FT%TZ}", info.using_publication_of)},
104 }; 101 };
105} 102}
106 103
@@ -127,18 +124,18 @@ handler::handler(log::logger const& l, datex2::situation_publication pub)
127 } 124 }
128 auto const after_build = chrono::steady_clock::now(); 125 auto const after_build = chrono::steady_clock::now();
129 auto const dur_build = 126 auto const dur_build =
130 chrono::duration_cast<chrono::milliseconds>(after_build - before_build); 127 chrono::duration_cast<chrono::milliseconds>(after_build - before_build);
131 l_.info("Indices built in {}", dur_build); 128 l_.info("Indices built in {}", dur_build);
132 l_.info("LSE index size: {}", lse_index_.size()); 129 l_.info("LSE index size: {}", lse_index_.size());
133 l_.info("Point index size: {}", p_index_.size()); 130 l_.info("Point index size: {}", p_index_.size());
134} 131}
135 132
136auto handler::process_gpx(gpx::file&& gpx_file) 133auto handler::process_gpx(gpx::file&& gpx_file)
137 -> std::optional<process_gpx_result> 134 -> std::optional<process_gpx_result>
138{ 135{
139 auto const now = chrono::utc_clock::now(); 136 auto const now = chrono::utc_clock::now();
140 auto const relevant = std::initializer_list<time::period>{ 137 auto const relevant = std::initializer_list<time::period>{
141 time::period{now - chrono::days(7), now + chrono::days(7)} 138 time::period{now - chrono::days(7), now + chrono::days(7)}
142 }; 139 };
143 auto const check_periods = time::period_seq{relevant.begin(), relevant.end()}; 140 auto const check_periods = time::period_seq{relevant.begin(), relevant.end()};
144 141
@@ -146,14 +143,14 @@ auto handler::process_gpx(gpx::file&& gpx_file)
146 for (auto const& track : gpx_file.tracks) 143 for (auto const& track : gpx_file.tracks)
147 for (auto const& seg : track.segments) 144 for (auto const& seg : track.segments)
148 geo::split_linestring_with_overlap_segments( 145 geo::split_linestring_with_overlap_segments(
149 seg.waypoints, 146 seg.waypoints,
150 5000 /* meters max total dist until a new split is forced */, 147 5000 /* meters max total dist until a new split is forced */,
151 splits_with_overlap_segments); 148 splits_with_overlap_segments);
152 auto const before_query = chrono::steady_clock::now(); 149 auto const before_query = chrono::steady_clock::now();
153 150
154 l_.debug("Querying for relevant situations"); 151 l_.debug("Querying for relevant situations");
155 auto relevant_road_closures = 152 auto relevant_road_closures =
156 std::unordered_set<std::shared_ptr<datex2::road_closure>>{}; 153 std::unordered_set<std::shared_ptr<datex2::road_closure>>{};
157 auto ls_checked = 0uz; 154 auto ls_checked = 0uz;
158 auto p_checked = 0uz; 155 auto p_checked = 0uz;
159 auto i = 0; 156 auto i = 0;
@@ -200,70 +197,66 @@ auto handler::process_gpx(gpx::file&& gpx_file)
200 197
201 auto const after_query = chrono::steady_clock::now(); 198 auto const after_query = chrono::steady_clock::now();
202 l_.debug( 199 l_.debug(
203 "Done (checked {} line string(s) and {} point(s)) in {}", ls_checked, 200 "Done (checked {} line string(s) and {} point(s)) in {}", ls_checked,
204 p_checked, 201 p_checked,
205 chrono::duration_cast<chrono::milliseconds>(after_query - before_query)); 202 chrono::duration_cast<chrono::milliseconds>(after_query - before_query));
206 203
207 auto relevant_situations = 204 auto relevant_situations =
208 std::unordered_set<std::shared_ptr<datex2::situation>>{}; 205 std::unordered_set<std::shared_ptr<datex2::situation>>{};
209 for (auto const& rc : relevant_road_closures) 206 for (auto const& rc : relevant_road_closures)
210 relevant_situations.emplace(rc->parent); 207 relevant_situations.emplace(rc->parent);
211 208
212 l_.debug( 209 l_.debug(
213 "Identified {} relevant road closure(s), part of {} unique " 210 "Identified {} relevant road closure(s), part of {} unique "
214 "situation(s)", 211 "situation(s)",
215 relevant_road_closures.size(), relevant_situations.size()); 212 relevant_road_closures.size(), relevant_situations.size());
216 for (auto const& sit : relevant_situations) 213 for (auto const& sit : relevant_situations)
217 l_.debug("Relevant situation: {}", sit->id); 214 l_.debug("Relevant situation: {}", sit->id);
218 215
219 return process_gpx_result{ 216 return process_gpx_result{
220 .tracks = gpx_file.tracks 217 .tracks = gpx_file.tracks
221 | views::transform( 218 | views::transform(
222 [](auto const& trk) -> track 219 [](auto const& trk) -> track
223 { 220 {
224 return { 221 return {
225 .segments = 222 .segments = trk.segments
226 trk.segments 223 | views::transform(
227 | views::transform(
228 [](auto const& seg) -> track_segment 224 [](auto const& seg) -> track_segment
229 { return {.points = seg.waypoints}; }) 225 { return {.points = seg.waypoints}; })
230 | std::ranges::to<std::vector<track_segment>>(), 226 | std::ranges::to<std::vector<track_segment>>(),
231 }; 227 };
232 }) 228 })
233 | std::ranges::to<std::vector<track>>(), 229 | std::ranges::to<std::vector<track>>(),
234 .relevant_situations = 230 .relevant_situations =
235 relevant_situations 231 relevant_situations
236 | views::transform( 232 | views::transform(
237 [&](std::shared_ptr<datex2::situation> sit) -> relevant_situation 233 [&](std::shared_ptr<datex2::situation> sit) -> relevant_situation
238 { 234 {
239 return { 235 return {
240 .id = sit->id, 236 .id = sit->id,
241 .location = sit->location, 237 .location = sit->location,
242 .comments = sit->comments, 238 .comments = sit->comments,
243 .relevant_road_closures = 239 .relevant_road_closures =
244 relevant_road_closures 240 relevant_road_closures
245 | views::filter( 241 | views::filter(
246 [&](std::shared_ptr<datex2::road_closure> const& rc) 242 [&](std::shared_ptr<datex2::road_closure> const& rc) -> bool
247 -> bool 243 { return std::shared_ptr{rc->parent} == sit; })
248 { return std::shared_ptr{rc->parent} == sit; }) 244 | views::transform(
249 | views::transform( 245 [](std::shared_ptr<datex2::road_closure> const& rc)
250 [](std::shared_ptr<datex2::road_closure> const& rc) 246 -> relevant_road_closure
251 -> relevant_road_closure 247 {
252 { 248 return {
253 return { 249 .relevant_lss =
254 .relevant_lss = 250 rc->relevant_line_strings
255 rc->relevant_line_strings 251 | views::transform(
256 | views::transform( 252 [](auto const& lsp) -> geo::linestring { return *lsp; })
257 [](auto const& lsp) -> geo::linestring 253 | std::ranges::to<std::vector<geo::linestring>>(),
258 { return *lsp; }) 254 };
259 | std::ranges::to< 255 })
260 std::vector<geo::linestring>>(), 256 | std::ranges::to<std::vector<relevant_road_closure>>(),
261 }; 257 };
262 }) 258 })
263 | std::ranges::to<std::vector<relevant_road_closure>>(), 259 | std::ranges::to<std::vector<relevant_situation>>(),
264 };
265 })
266 | std::ranges::to<std::vector<relevant_situation>>(),
267 }; 260 };
268} 261}
269 262
diff --git a/server/src/api.cppm b/server/src/api.cppm
index 66e8f45..c724a45 100644
--- a/server/src/api.cppm
+++ b/server/src/api.cppm
@@ -58,30 +58,28 @@ 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) 64 json::value_from_tag, json::value& jv, relevant_situation const& sit) -> void;
65 -> void;
66auto tag_invoke(json::value_from_tag, json::value& jv, track_segment const& seg) 65auto tag_invoke(json::value_from_tag, json::value& jv, track_segment const& seg)
67 -> void; 66 -> void;
68auto tag_invoke(json::value_from_tag, json::value& jv, track const& track) 67auto tag_invoke(json::value_from_tag, json::value& jv, track const& track)
69 -> void; 68 -> void;
70auto tag_invoke( 69auto tag_invoke(
71 json::value_from_tag, json::value& jv, process_gpx_result const& res) 70 json::value_from_tag, json::value& jv, process_gpx_result const& res) -> void;
72 -> void;
73auto tag_invoke(json::value_from_tag, json::value& jv, sysinfo const& info) 71auto tag_invoke(json::value_from_tag, json::value& jv, sysinfo const& info)
74 -> void; 72 -> void;
75 73
76class handler 74class handler
77{ 75{
78 using lse_index_value = std::tuple< 76 using lse_index_value = std::tuple<
79 geo::box, std::shared_ptr<geo::linestring>, 77 geo::box, std::shared_ptr<geo::linestring>,
80 std::shared_ptr<datex2::road_closure>>; 78 std::shared_ptr<datex2::road_closure>>;
81 using p_index_value = 79 using p_index_value =
82 std::pair<geo::point, std::shared_ptr<datex2::road_closure>>; 80 std::pair<geo::point, std::shared_ptr<datex2::road_closure>>;
83 using lse_index = 81 using lse_index =
84 bgeo::index::rtree<lse_index_value, bgeo::index::quadratic<16>>; 82 bgeo::index::rtree<lse_index_value, bgeo::index::quadratic<16>>;
85 using p_index = bgeo::index::rtree<p_index_value, bgeo::index::quadratic<16>>; 83 using p_index = bgeo::index::rtree<p_index_value, bgeo::index::quadratic<16>>;
86 84
87 log::logger l_; 85 log::logger l_;
diff --git a/server/src/config.cpp b/server/src/config.cpp
index 84c436a..e9a728b 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,110 +116,109 @@ 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, 119 json::value_to_tag<rwgps> const&, json::value const& jv, location const& loc)
120 location const& loc) -> rwgps 120 -> 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, 176 json::value_to_tag<logger> const&, json::value const& jv, location const& loc)
177 location const& loc) -> logger 177 -> 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 = std::ifstream{ 220 auto f =
221 filename 221 std::ifstream{filename}; // TODO: ensure that we are opening in binary mode?
222 }; // TODO: ensure that we are opening in binary mode?
223 if (!f.is_open()) 222 if (!f.is_open())
224 throw std::runtime_error{std::format("failed to open {}", filename)}; 223 throw std::runtime_error{std::format("failed to open {}", filename)};
225 auto jv = json::value{}; 224 auto jv = json::value{};
@@ -230,7 +229,7 @@ auto load_file(std::string const& filename) -> app
230 catch (boost::system::system_error const& e) 229 catch (boost::system::system_error const& e)
231 { 230 {
232 throw std::runtime_error{std::format( 231 throw std::runtime_error{std::format(
233 "failed to parse: {}", e.code().message())}; 232 "failed to parse: {}", e.code().message())};
234 } 233 }
235 return json_value_to_app(jv); 234 return json_value_to_app(jv);
236} 235}
diff --git a/server/src/database.cppm b/server/src/database.cppm
index 86ed0d7..c31d610 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 b4c795f..7c20347 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 = parse_timestamp( 212 auto const overall_start_time =
213 validity_spec_xml.child_value("com:overallStartTime")); 213 parse_timestamp(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 = parse_timestamp( 224 auto const start_of_period =
225 valid_period_xml.child_value("com:startOfPeriod")); 225 parse_timestamp(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,10 +281,10 @@ 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"
288 }; 288 };
289 289
290 auto situations = std::vector<std::shared_ptr<situation>>{}; 290 auto situations = std::vector<std::shared_ptr<situation>>{};
@@ -302,26 +302,26 @@ 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",
309 /* | */ "sit:ConstructionWorks", 309 /* | */ "sit:ConstructionWorks",
310 /* } */ 310 /* } */
311 "sit:Obstruction", 311 "sit:Obstruction",
312 /* { */ "sit:EnvironmentalObstruction", 312 /* { */ "sit:EnvironmentalObstruction",
313 /* | */ "sit:GeneralObstruction", 313 /* | */ "sit:GeneralObstruction",
314 /* | */ "sit:InfrastructureDamageObstruction", 314 /* | */ "sit:InfrastructureDamageObstruction",
315 /* } */ 315 /* } */
316 "sit:Activity", 316 "sit:Activity",
317 /* { */ "sit:PublicEvent", 317 /* { */ "sit:PublicEvent",
318 /* } */ 318 /* } */
319 }; 319 };
320 320
321 if (record_type == "sit:RoadOrCarriagewayOrLaneManagement") 321 if (record_type == "sit:RoadOrCarriagewayOrLaneManagement")
322 { 322 {
323 if (auto rc = handle_road_or_carriageway_or_lane_management( 323 if (auto rc =
324 record_xml, sit)) 324 handle_road_or_carriageway_or_lane_management(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
@@ -404,8 +404,8 @@ public:
404 } 404 }
405 405
406 return { 406 return {
407 .publication_time = *mpublication_time, 407 .publication_time = *mpublication_time,
408 .situations = situations, 408 .situations = situations,
409 }; 409 };
410 } 410 }
411 411
diff --git a/server/src/geo.cppm b/server/src/geo.cppm
index b599ad4..9101af8 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)
31 { 34 {
32 bgeo::append(current_ls, p); 35 auto d = bgeo::distance(*previous, p, vincenty_strategy());
33 if (previous) 36 current_ls_length += d;
37 if (current_ls_length > max_split_distance_m)
34 { 38 {
35 auto d = bgeo::distance(*previous, p, vincenty_strategy()); 39 append_to.push_back(std::move(current_ls));
36 current_ls_length += d; 40 current_ls = linestring{*previous, p};
37 if (current_ls_length > max_split_distance_m) 41 current_ls_length = d;
38 {
39 append_to.push_back(std::move(current_ls));
40 current_ls = linestring{*previous, p};
41 current_ls_length = d;
42 }
43 } 42 }
44 previous = p; 43 }
45 }); 44 previous = p;
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 44a6bb2..f70c133 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); };
@@ -41,18 +41,18 @@ auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs)
41 } 41 }
42 if (!mlat || !mlon) 42 if (!mlat || !mlon)
43 throw std::runtime_error{ 43 throw std::runtime_error{
44 "expected valid latitude and longitude for waypoint" 44 "expected valid latitude and longitude for waypoint"
45 }; 45 };
46 co_await xml::ignore_contents(e); 46 co_await xml::ignore_contents(e);
47 co_return geo::point{*mlon, *mlat}; 47 co_return geo::point{*mlon, *mlat};
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 = co_await allow_element( 54 while (auto mwpt =
55 e, qname("trkpt"), xml::hohalo<parse_wpt>())) 55 co_await allow_element(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); };
@@ -140,21 +140,21 @@ auto parse_wpt(xml::executor_ref e, xml::attribute_view attrs)
140 } 140 }
141 if (!mlat || !mlon) 141 if (!mlat || !mlon)
142 throw std::runtime_error{ 142 throw std::runtime_error{
143 "expected valid latitude and longitude for waypoint" 143 "expected valid latitude and longitude for waypoint"
144 }; 144 };
145 co_await xml::ignore_contents(e); 145 co_await xml::ignore_contents(e);
146 co_return geo::point{*mlon, *mlat}; 146 co_return geo::point{*mlon, *mlat};
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 = co_await allow_element( 153 while (auto mwpt =
154 e, qname("trkpt"), xml::hohalo<parse_wpt>())) 154 co_await allow_element(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 bea5384..13dd60a 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 0f7fda7..366037f 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,18 +244,17 @@ 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, 257 bhttp::field::access_control_allow_methods, res.allow_methods.to_string());
258 res.allow_methods.to_string());
259 rsp.set(bhttp::field::access_control_allow_headers, allow_headers_str); 258 rsp.set(bhttp::field::access_control_allow_headers, allow_headers_str);
260 rsp.prepare_payload(); 259 rsp.prepare_payload();
261 return rsp; 260 return rsp;
@@ -266,7 +265,7 @@ auto make_preflight_rsp(preflight_response res, keep_alive ka)
266// anyway, but all context types should derive from base_ctx). 265// anyway, but all context types should derive from base_ctx).
267template <bhttp::concepts::body_reader Body> 266template <bhttp::concepts::body_reader Body>
268auto read_request(base_ctx const& ctx, readable_request&& r) 267auto read_request(base_ctx const& ctx, readable_request&& r)
269 -> net::awaitable<std::expected<bhttp::request<Body>, presponse>> 268 -> net::awaitable<std::expected<bhttp::request<Body>, presponse>>
270{ 269{
271 std::ignore = ctx; 270 std::ignore = ctx;
272 auto p = bhttp::request_parser<Body>{std::move(*r.p)}; 271 auto p = bhttp::request_parser<Body>{std::move(*r.p)};
@@ -276,20 +275,19 @@ auto read_request(base_ctx const& ctx, readable_request&& r)
276 275
277template <> 276template <>
278auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r) 277auto read_request<bhttp::empty_body>(base_ctx const& ctx, readable_request&& r)
279 -> net::awaitable< 278 -> net::awaitable<std::expected<bhttp::request<bhttp::empty_body>, presponse>>
280 std::expected<bhttp::request<bhttp::empty_body>, presponse>>
281{ 279{
282 auto [ec, _] = 280 auto [ec, _] =
283 co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple); 281 co_await bhttp::async_read(*r.strm, *r.buf, *r.p, net::as_tuple);
284 if (ec == bhttp::error::unexpected_body) 282 if (ec == bhttp::error::unexpected_body)
285 { 283 {
286 auto tpl = problem::tpl{ 284 auto tpl = problem::tpl{
287 .status = bhttp::status::bad_request, 285 .status = bhttp::status::bad_request,
288 .title = translate("No body expected for this request"), 286 .title = translate("No body expected for this request"),
289 .type_uri = "https://routemon.fautchen.eu/problems/unexpected-body", 287 .type_uri = "https://routemon.fautchen.eu/problems/unexpected-body",
290 }; 288 };
291 co_return std::unexpected{problem_rsp( 289 co_return std::unexpected{problem_rsp(
292 ctx, tpl.instantiate(), keep_alive{false})}; 290 ctx, tpl.instantiate(), keep_alive{false})};
293 } 291 }
294 else if (ec) 292 else if (ec)
295 { 293 {
@@ -310,8 +308,8 @@ template <class Ctx>
310 []<class InnerCtx>(routed_ctx<InnerCtx> const&) {}(ctx); 308 []<class InnerCtx>(routed_ctx<InnerCtx> const&) {}(ctx);
311 } 309 }
312auto default_options_handler( 310auto default_options_handler(
313 Ctx const& ctx, readable_request r, std::vector<std::string> const&) 311 Ctx const& ctx, readable_request r, std::vector<std::string> const&)
314 -> net::awaitable<presponse> 312 -> net::awaitable<presponse>
315{ 313{
316 auto mreq = co_await read_request<bhttp::empty_body>(ctx, std::move(r)); 314 auto mreq = co_await read_request<bhttp::empty_body>(ctx, std::move(r));
317 if (!mreq) 315 if (!mreq)
@@ -321,18 +319,18 @@ auto default_options_handler(
321 { 319 {
322 // CORS preflight request 320 // CORS preflight request
323 co_return make_preflight_rsp( 321 co_return make_preflight_rsp(
324 preflight_response{ 322 preflight_response{
325 // TODO: should access-control-allow-methods contain OPTIONS? 323 // TODO: should access-control-allow-methods contain OPTIONS?
326 .allow_methods = ctx.route_methods, 324 .allow_methods = ctx.route_methods,
327 .allow_headers = {bhttp::field::content_type}, 325 .allow_headers = {bhttp::field::content_type},
328 }, 326 },
329 keep_alive{mreq->keep_alive()}); 327 keep_alive{mreq->keep_alive()});
330 } 328 }
331 else 329 else
332 { 330 {
333 // Normal OPTIONS request 331 // Normal OPTIONS request
334 auto rsp = make_rsp<bhttp::empty_body>( 332 auto rsp = make_rsp<bhttp::empty_body>(
335 bhttp::status::no_content, keep_alive{mreq->keep_alive()}); 333 bhttp::status::no_content, keep_alive{mreq->keep_alive()});
336 rsp.set(bhttp::field::allow, ctx.route_methods.to_string()); 334 rsp.set(bhttp::field::allow, ctx.route_methods.to_string());
337 rsp.prepare_payload(); 335 rsp.prepare_payload();
338 co_return std::move(rsp); 336 co_return std::move(rsp);
@@ -340,7 +338,7 @@ auto default_options_handler(
340} 338}
341 339
342auto global_options_handler(base_ctx const& ctx, readable_request r) 340auto global_options_handler(base_ctx const& ctx, readable_request r)
343 -> net::awaitable<presponse> 341 -> net::awaitable<presponse>
344{ 342{
345 // TODO: switch to "small (4KB) discarded" body type, similar to what Go 343 // TODO: switch to "small (4KB) discarded" body type, similar to what Go
346 // does? Same goes for default_options_handler? Not sure. 344 // does? Same goes for default_options_handler? Not sure.
@@ -349,46 +347,45 @@ auto global_options_handler(base_ctx const& ctx, readable_request r)
349 co_return std::move(res.error()); 347 co_return std::move(res.error());
350 auto req = r.p->release(); 348 auto req = r.p->release();
351 auto rsp = make_rsp<bhttp::empty_body>( 349 auto rsp = make_rsp<bhttp::empty_body>(
352 bhttp::status::no_content, keep_alive{req.keep_alive()}); 350 bhttp::status::no_content, keep_alive{req.keep_alive()});
353 rsp.prepare_payload(); 351 rsp.prepare_payload();
354 co_return std::move(rsp); 352 co_return std::move(rsp);
355} 353}
356 354
357template <class Ctx> 355template <class Ctx>
358auto id_middleware( 356auto id_middleware(
359 Ctx ctx, bhttp::request_header<bhttp::fields>&, next_handler_t<Ctx> next) 357 Ctx ctx, bhttp::request_header<bhttp::fields>&, next_handler_t<Ctx> next)
360 -> net::awaitable<presponse> 358 -> net::awaitable<presponse>
361{ 359{
362 co_return co_await next(std::move(ctx)); 360 co_return co_await next(std::move(ctx));
363} 361}
364 362
365template <class A, class B, class C> 363template <class A, class B, class C>
366auto middleware_compose(middleware_t<A, B> ab, middleware_t<B, C> bc) 364auto middleware_compose(middleware_t<A, B> ab, middleware_t<B, C> bc)
367 -> middleware_t<A, C> 365 -> middleware_t<A, C>
368{ 366{
369 return [ab = std::move(ab), bc = std::move(bc)]( 367 return [ab = std::move(ab), bc = std::move(bc)](
370 A a, bhttp::request_header<bhttp::fields>& header, 368 A a, bhttp::request_header<bhttp::fields>& header,
371 next_handler_t<C> next) -> net::awaitable<presponse> 369 next_handler_t<C> next) -> net::awaitable<presponse>
372 { 370 {
373 co_return co_await ab( 371 co_return co_await ab(
374 std::move(a), header, [&](B b) -> net::awaitable<presponse> 372 std::move(a), header, [&](B b) -> net::awaitable<presponse>
375 { co_return co_await bc(std::move(b), header, next); }); 373 { co_return co_await bc(std::move(b), header, next); });
376 }; 374 };
377} 375}
378 376
379template <class A, class B> 377template <class A, class B>
380auto middleware_wrap_fn(middleware_t<A, B> ab, basic_route_handler_fn_t<B> fn) 378auto middleware_wrap_fn(middleware_t<A, B> ab, basic_route_handler_fn_t<B> fn)
381 -> basic_route_handler_fn_t<A> 379 -> basic_route_handler_fn_t<A>
382{ 380{
383 return 381 return [ab = std::move(ab), fn = std::move(fn)](
384 [ab = std::move(ab), fn = std::move(fn)]( 382 A a_ctx, readable_request r,
385 A a_ctx, readable_request r, 383 std::vector<std::string> const& matches) -> net::awaitable<presponse>
386 std::vector<std::string> const& matches) -> net::awaitable<presponse>
387 { 384 {
388 co_return co_await ab( 385 co_return co_await ab(
389 std::move(a_ctx), r.p->get().base(), 386 std::move(a_ctx), r.p->get().base(),
390 [&](B b_ctx) -> net::awaitable<presponse> 387 [&](B b_ctx) -> net::awaitable<presponse>
391 { co_return co_await fn(std::move(b_ctx), r, matches); }); 388 { co_return co_await fn(std::move(b_ctx), r, matches); });
392 }; 389 };
393} 390}
394 391
@@ -448,15 +445,15 @@ struct handler_map
448 template <std::default_initializable U> 445 template <std::default_initializable U>
449 auto map(std::invocable<V const&> auto f) const -> handler_map<U> 446 auto map(std::invocable<V const&> auto f) const -> handler_map<U>
450 requires std::assignable_from< 447 requires std::assignable_from<
451 U&, std::invoke_result_t<decltype(f), V const&>> 448 U&, std::invoke_result_t<decltype(f), V const&>>
452 { 449 {
453 return { 450 return {
454 .options = static_cast<bool>(options) ? f(options) : U{}, 451 .options = static_cast<bool>(options) ? f(options) : U{},
455 .delete_ = static_cast<bool>(delete_) ? f(delete_) : U{}, 452 .delete_ = static_cast<bool>(delete_) ? f(delete_) : U{},
456 .get = static_cast<bool>(get) ? f(get) : U{}, 453 .get = static_cast<bool>(get) ? f(get) : U{},
457 .head = static_cast<bool>(head) ? f(head) : U{}, 454 .head = static_cast<bool>(head) ? f(head) : U{},
458 .post = static_cast<bool>(post) ? f(post) : U{}, 455 .post = static_cast<bool>(post) ? f(post) : U{},
459 .put = static_cast<bool>(put) ? f(put) : U{}, 456 .put = static_cast<bool>(put) ? f(put) : U{},
460 }; 457 };
461 } 458 }
462}; 459};
@@ -478,29 +475,29 @@ struct route_tree
478 475
479template <class OuterCtx, class InnerCtx> 476template <class OuterCtx, class InnerCtx>
480auto middleware_wrap_tree( 477auto middleware_wrap_tree(
481 middleware_t<OuterCtx, InnerCtx> mw, route_tree<InnerCtx> const& tree) 478 middleware_t<OuterCtx, InnerCtx> mw, route_tree<InnerCtx> const& tree)
482 -> route_tree<OuterCtx> 479 -> route_tree<OuterCtx>
483{ 480{
484 auto new_leaves = tree.here.template map<basic_route_handler_fn_t<OuterCtx>>( 481 auto new_leaves = tree.here.template map<basic_route_handler_fn_t<OuterCtx>>(
485 std::bind_front(middleware_wrap_fn<OuterCtx, InnerCtx>, mw)); 482 std::bind_front(middleware_wrap_fn<OuterCtx, InnerCtx>, mw));
486 auto new_sub = tree.sub.visit( 483 auto new_sub = tree.sub.visit(
487 util::overloaded{ 484 util::overloaded{
488 [&mw](route_tree<InnerCtx>::named_subtrees const& subtrees) 485 [&mw](route_tree<InnerCtx>::named_subtrees const& subtrees)
489 -> decltype(route_tree<OuterCtx>::sub) 486 -> decltype(route_tree<OuterCtx>::sub)
490 { 487 {
491 auto new_subtrees = typename route_tree<OuterCtx>::named_subtrees{}; 488 auto new_subtrees = typename route_tree<OuterCtx>::named_subtrees{};
492 for (auto [seg, subtree] : subtrees) 489 for (auto [seg, subtree] : subtrees)
493 new_subtrees[seg] = middleware_wrap_tree(mw, subtree); 490 new_subtrees[seg] = middleware_wrap_tree(mw, subtree);
494 return new_subtrees; 491 return new_subtrees;
495 }, 492 },
496 [&mw](route_tree<InnerCtx>::wildcard_subtree const& subtree) 493 [&mw](route_tree<InnerCtx>::wildcard_subtree const& subtree)
497 -> decltype(route_tree<OuterCtx>::sub) 494 -> decltype(route_tree<OuterCtx>::sub)
498 { 495 {
499 return typename route_tree<OuterCtx>::wildcard_subtree{ 496 return typename route_tree<OuterCtx>::wildcard_subtree{
500 middleware_wrap_tree(mw, *subtree) 497 middleware_wrap_tree(mw, *subtree)
501 }; 498 };
502 }, 499 },
503 }); 500 });
504 return {.here = new_leaves, .sub = new_sub}; 501 return {.here = new_leaves, .sub = new_sub};
505} 502}
506 503
@@ -509,23 +506,21 @@ concept match_arg = std::constructible_from<T, std::string const&>;
509 506
510template <class Ctx, match_arg... MatchArgs> 507template <class Ctx, match_arg... MatchArgs>
511using route_handler_fn_t = std::function< 508using route_handler_fn_t = std::function<
512 auto(Ctx, readable_request, MatchArgs...)->net::awaitable<presponse>>; 509 auto(Ctx, readable_request, MatchArgs...)->net::awaitable<presponse>>;
513 510
514template <class Ctx, match_arg... MatchArgs> 511template <class Ctx, match_arg... MatchArgs>
515auto degen_route_handler(route_handler_fn_t<Ctx, MatchArgs...> fn) 512auto degen_route_handler(route_handler_fn_t<Ctx, MatchArgs...> fn)
516 -> basic_route_handler_fn_t<Ctx> 513 -> basic_route_handler_fn_t<Ctx>
517{ 514{
518 return 515 return [fn = std::move(fn)](
519 [fn = std::move(fn)]( 516 Ctx ctx, readable_request r,
520 Ctx ctx, readable_request r, 517 std::vector<std::string> const& matches) -> net::awaitable<presponse>
521 std::vector<std::string> const& matches) -> net::awaitable<presponse>
522 { 518 {
523 if (sizeof...(MatchArgs) != matches.size()) 519 if (sizeof...(MatchArgs) != matches.size())
524 throw std::runtime_error{"got unexpected amount of matches"}; 520 throw std::runtime_error{"got unexpected amount of matches"};
525 auto it = matches.begin(); 521 auto it = matches.begin();
526 co_return co_await fn( 522 co_return co_await fn(
527 std::move(ctx), r, 523 std::move(ctx), r, MatchArgs{static_cast<std::string const&>(*it++)}...);
528 MatchArgs{static_cast<std::string const&>(*it++)}...);
529 }; 524 };
530} 525}
531 526
@@ -534,7 +529,7 @@ struct ctree : route_tree<Ctx>
534{ 529{
535 template <class OuterCtx> 530 template <class OuterCtx>
536 auto wrap(middleware_t<OuterCtx, Ctx> mw) const 531 auto wrap(middleware_t<OuterCtx, Ctx> mw) const
537 -> ctree<OuterCtx, MatchArgs...> 532 -> ctree<OuterCtx, MatchArgs...>
538 { 533 {
539 return {middleware_wrap_tree(std::move(mw), *this)}; 534 return {middleware_wrap_tree(std::move(mw), *this)};
540 } 535 }
@@ -546,25 +541,24 @@ struct dtree : handler_map<route_handler_fn_t<Ctx, MatchArgs...>>
546 [[nodiscard]] auto to_leaves() const -> typename route_tree<Ctx>::leaves 541 [[nodiscard]] auto to_leaves() const -> typename route_tree<Ctx>::leaves
547 { 542 {
548 auto here = this->template map<basic_route_handler_fn_t<Ctx>>( 543 auto here = this->template map<basic_route_handler_fn_t<Ctx>>(
549 degen_route_handler<Ctx, MatchArgs...>); 544 degen_route_handler<Ctx, MatchArgs...>);
550 if (!here.verbs().empty() && !static_cast<bool>(this->options)) 545 if (!here.verbs().empty() && !static_cast<bool>(this->options))
551 here.options = default_options_handler<Ctx>; 546 here.options = default_options_handler<Ctx>;
552 return here; 547 return here;
553 } 548 }
554 549
555 [[nodiscard]] auto named_subtrees( 550 [[nodiscard]] auto named_subtrees(
556 std::initializer_list<std::pair<std::string, ctree<Ctx, MatchArgs...>>> 551 std::initializer_list<std::pair<std::string, ctree<Ctx, MatchArgs...>>>
557 subtrees) const -> ctree<Ctx, MatchArgs...> 552 subtrees) const -> ctree<Ctx, MatchArgs...>
558 { 553 {
559 auto sub = typename route_tree<Ctx>::named_subtrees{ 554 auto sub = typename route_tree<Ctx>::named_subtrees{
560 std::from_range, subtrees 555 std::from_range, subtrees
561 | std::views::transform( 556 | std::views::transform(
562 [](auto const& p) 557 [](auto const& p)
563 { 558 {
564 return std::make_pair( 559 return std::make_pair(
565 p.first, 560 p.first, static_cast<route_tree<Ctx>>(p.second));
566 static_cast<route_tree<Ctx>>(p.second)); 561 })
567 })
568 }; 562 };
569 return {route_tree<Ctx>{.here = to_leaves(), .sub = sub}}; 563 return {route_tree<Ctx>{.here = to_leaves(), .sub = sub}};
570 } 564 }
@@ -572,13 +566,13 @@ struct dtree : handler_map<route_handler_fn_t<Ctx, MatchArgs...>>
572 template <match_arg MatchArg> 566 template <match_arg MatchArg>
573 [[nodiscard]] auto 567 [[nodiscard]] auto
574 wildcard_subtree(ctree<Ctx, MatchArgs..., MatchArg> subtree) 568 wildcard_subtree(ctree<Ctx, MatchArgs..., MatchArg> subtree)
575 -> ctree<Ctx, MatchArgs...> 569 -> ctree<Ctx, MatchArgs...>
576 { 570 {
577 return {route_tree<Ctx>{ 571 return {route_tree<Ctx>{
578 .here = to_leaves(), 572 .here = to_leaves(),
579 .sub = typename route_tree<Ctx>::wildcard_subtree{ 573 .sub = typename route_tree<Ctx>::wildcard_subtree{
580 static_cast<route_tree<Ctx>>(subtree) 574 static_cast<route_tree<Ctx>>(subtree)
581 } 575 }
582 }}; 576 }};
583 } 577 }
584 578
@@ -598,9 +592,9 @@ class server
598 592
599public: 593public:
600 explicit server( 594 explicit server(
601 log::logger const& l, locale::selector&& lsel, 595 log::logger const& l, locale::selector&& lsel,
602 middleware_t<base_ctx, PreRouteCtx> global_middleware, 596 middleware_t<base_ctx, PreRouteCtx> global_middleware,
603 route_tree<routed_ctx<PreRouteCtx>> routes) 597 route_tree<routed_ctx<PreRouteCtx>> routes)
604 : l_{l.sub("http_server")}, lsel_{std::move(lsel)}, 598 : l_{l.sub("http_server")}, lsel_{std::move(lsel)},
605 global_middleware_{std::move(global_middleware)}, 599 global_middleware_{std::move(global_middleware)},
606 routes_{std::move(routes)} 600 routes_{std::move(routes)}
@@ -610,55 +604,56 @@ public:
610 struct match_result 604 struct match_result
611 { 605 {
612 util::not_null< 606 util::not_null<
613 handler_map<basic_route_handler_fn_t<routed_ctx<PreRouteCtx>>> const*> 607 handler_map<basic_route_handler_fn_t<routed_ctx<PreRouteCtx>>> const*>
614 route_handlers; 608 route_handlers;
615 std::vector<std::string> wildcard_matches; 609 std::vector<std::string> wildcard_matches;
616 610
617 auto allowed_methods() const -> verb_set { return route_handlers->verbs(); } 611 auto allowed_methods() const -> verb_set { return route_handlers->verbs(); }
618 }; 612 };
619 613
620 auto match(boost::urls::segments_view segments) const 614 auto match(boost::urls::segments_view segments) const
621 -> std::optional<match_result> 615 -> std::optional<match_result>
622 { 616 {
623 auto const* tree = &routes_; 617 auto const* tree = &routes_;
624 auto wildcard_matches = std::vector<std::string>{}; 618 auto wildcard_matches = std::vector<std::string>{};
625 for (auto const& seg : segments) 619 for (auto const& seg : segments)
626 { 620 {
627 tree->sub.visit( 621 tree->sub.visit(
628 util::overloaded{ 622 util::overloaded{
629 [&](route_tree<routed_ctx<PreRouteCtx>>::named_subtrees const& 623 [&](
630 subtrees) 624 route_tree<routed_ctx<PreRouteCtx>>::named_subtrees const& subtrees)
631 { 625 {
632 auto it = subtrees.find(seg); 626 auto it = subtrees.find(seg);
633 tree = it == subtrees.end() ? nullptr : &it->second; 627 tree = it == subtrees.end() ? nullptr : &it->second;
634 }, 628 },
635 [&](route_tree<routed_ctx<PreRouteCtx>>::wildcard_subtree const& 629 [&](
636 wildcard_subtree) 630 route_tree<routed_ctx<PreRouteCtx>>::wildcard_subtree const&
637 { 631 wildcard_subtree)
638 wildcard_matches.push_back(seg); 632 {
639 tree = &*wildcard_subtree; 633 wildcard_matches.push_back(seg);
640 }, 634 tree = &*wildcard_subtree;
641 }); 635 },
636 });
642 if (!tree) 637 if (!tree)
643 return std::nullopt; 638 return std::nullopt;
644 } 639 }
645 if (tree->here.empty()) 640 if (tree->here.empty())
646 return std::nullopt; 641 return std::nullopt;
647 return match_result{ 642 return match_result{
648 .route_handlers = util::not_null{&tree->here}, 643 .route_handlers = util::not_null{&tree->here},
649 .wildcard_matches = wildcard_matches, 644 .wildcard_matches = wildcard_matches,
650 }; 645 };
651 } 646 }
652 647
653 auto route_request(PreRouteCtx ctx, readable_request r) const 648 auto route_request(PreRouteCtx ctx, readable_request r) const
654 -> net::awaitable<presponse> 649 -> net::awaitable<presponse>
655 { 650 {
656 auto req_base = r.p->get().base(); 651 auto req_base = r.p->get().base();
657 652
658 auto const bad_request_tpl = problem::tpl{ 653 auto const bad_request_tpl = problem::tpl{
659 .status = bhttp::status::bad_request, 654 .status = bhttp::status::bad_request,
660 .title = translate("Bad request"), 655 .title = translate("Bad request"),
661 .type_uri = "https://routemon.fautchen.eu/problems/bad-request", 656 .type_uri = "https://routemon.fautchen.eu/problems/bad-request",
662 }; 657 };
663 658
664 if (req_base.target() == "*") 659 if (req_base.target() == "*")
@@ -669,10 +664,10 @@ public:
669 if (req_base.method() != bhttp::verb::options) 664 if (req_base.method() != bhttp::verb::options)
670 { 665 {
671 auto tpl = problem::tpl{ 666 auto tpl = problem::tpl{
672 .status = bhttp::status::method_not_allowed, 667 .status = bhttp::status::method_not_allowed,
673 .title = translate("Method not allowed"), 668 .title = translate("Method not allowed"),
674 .type_uri = "https://routemon.fautchen.eu/problems/" 669 .type_uri = "https://routemon.fautchen.eu/problems/"
675 "method-not-allowed", 670 "method-not-allowed",
676 }; 671 };
677 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 672 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
678 } 673 }
@@ -690,10 +685,10 @@ public:
690 if (!req_url.is_path_absolute()) 685 if (!req_url.is_path_absolute())
691 { 686 {
692 auto problem = bad_request_tpl.instantiate().set_detail(translate( 687 auto problem = bad_request_tpl.instantiate().set_detail(translate(
693 "Path of normalized (RFC 3986, § 6) " 688 "Path of normalized (RFC 3986, § 6) "
694 "origin-form request-target (RFC " 689 "origin-form request-target (RFC "
695 "9112, § 3.2.1) should be " 690 "9112, § 3.2.1) should be "
696 "absolute")); 691 "absolute"));
697 co_return problem_rsp(ctx, problem, keep_alive{false}); 692 co_return problem_rsp(ctx, problem, keep_alive{false});
698 } 693 }
699 694
@@ -701,9 +696,9 @@ public:
701 if (!mres) 696 if (!mres)
702 { 697 {
703 auto tpl = problem::tpl{ 698 auto tpl = problem::tpl{
704 .status = bhttp::status::not_found, 699 .status = bhttp::status::not_found,
705 .title = translate("Not found"), 700 .title = translate("Not found"),
706 .type_uri = "https://routemon.fautchen.eu/problems/not-found", 701 .type_uri = "https://routemon.fautchen.eu/problems/not-found",
707 }; 702 };
708 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 703 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
709 } 704 }
@@ -713,10 +708,10 @@ public:
713 { 708 {
714 // Method not implemented. 709 // Method not implemented.
715 auto tpl = problem::tpl{ 710 auto tpl = problem::tpl{
716 .status = bhttp::status::not_implemented, 711 .status = bhttp::status::not_implemented,
717 .title = translate("Method not implemented"), 712 .title = translate("Method not implemented"),
718 .type_uri = "https://routemon.fautchen.eu/problems/" 713 .type_uri = "https://routemon.fautchen.eu/problems/"
719 "method-not-implemented", 714 "method-not-implemented",
720 }; 715 };
721 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 716 co_return problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
722 } 717 }
@@ -724,21 +719,21 @@ public:
724 if (auto mhdl = mres->route_handlers->lookup(*mverb)) 719 if (auto mhdl = mres->route_handlers->lookup(*mverb))
725 { 720 {
726 auto new_ctx = 721 auto new_ctx =
727 routed_ctx<PreRouteCtx>{std::move(ctx), mres->allowed_methods()}; 722 routed_ctx<PreRouteCtx>{std::move(ctx), mres->allowed_methods()};
728 co_return co_await mhdl(std::move(new_ctx), r, mres->wildcard_matches); 723 co_return co_await mhdl(std::move(new_ctx), r, mres->wildcard_matches);
729 } 724 }
730 else 725 else
731 { 726 {
732 // Path recognized, but method not allowed. 727 // Path recognized, but method not allowed.
733 auto tpl = problem::tpl{ 728 auto tpl = problem::tpl{
734 .status = bhttp::status::method_not_allowed, 729 .status = bhttp::status::method_not_allowed,
735 .title = translate("Method not allowed"), 730 .title = translate("Method not allowed"),
736 .type_uri = "https://routemon.fautchen.eu/problems/" 731 .type_uri = "https://routemon.fautchen.eu/problems/"
737 "method-not-allowed", 732 "method-not-allowed",
738 }; 733 };
739 auto rsp = problem_rsp(ctx, tpl.instantiate(), keep_alive{false}); 734 auto rsp = problem_rsp(ctx, tpl.instantiate(), keep_alive{false});
740 rsp.header().set( 735 rsp.header().set(
741 bhttp::field::allow, mres->allowed_methods().to_string()); 736 bhttp::field::allow, mres->allowed_methods().to_string());
742 co_return std::move(rsp); 737 co_return std::move(rsp);
743 } 738 }
744 } 739 }
@@ -747,9 +742,9 @@ public:
747 // We do not accept any other request-target forms. 742 // We do not accept any other request-target forms.
748 743
749 auto problem = bad_request_tpl.instantiate().set_detail(translate( 744 auto problem = bad_request_tpl.instantiate().set_detail(translate(
750 "Invalid request-target, expected " 745 "Invalid request-target, expected "
751 "asterisk-form or origin-form " 746 "asterisk-form or origin-form "
752 "(see RFC 9112, § 3.2)")); 747 "(see RFC 9112, § 3.2)"));
753 co_return problem_rsp(ctx, problem, keep_alive{false}); 748 co_return problem_rsp(ctx, problem, keep_alive{false});
754 } 749 }
755 } 750 }
@@ -761,9 +756,8 @@ public:
761 auto ctx0 = base_ctx{.locale = locale}; 756 auto ctx0 = base_ctx{.locale = locale};
762 757
763 co_return co_await global_middleware_( 758 co_return co_await global_middleware_(
764 std::move(ctx0), header, 759 std::move(ctx0), header, [&](PreRouteCtx ctx) -> net::awaitable<presponse>
765 [&](PreRouteCtx ctx) -> net::awaitable<presponse> 760 { co_return co_await route_request(std::move(ctx), std::move(r)); });
766 { co_return co_await route_request(std::move(ctx), std::move(r)); });
767 } 761 }
768 762
769 auto do_session(beast::tcp_stream strm) -> net::awaitable<void> 763 auto do_session(beast::tcp_stream strm) -> net::awaitable<void>
@@ -775,7 +769,7 @@ public:
775 auto p0 = bhttp::request_parser<bhttp::empty_body>{}; 769 auto p0 = bhttp::request_parser<bhttp::empty_body>{};
776 p0.body_limit(boost::none); 770 p0.body_limit(boost::none);
777 auto [ec, _] = 771 auto [ec, _] =
778 co_await bhttp::async_read_header(strm, buf, p0, net::as_tuple); 772 co_await bhttp::async_read_header(strm, buf, p0, net::as_tuple);
779 if (ec == bhttp::error::end_of_stream) 773 if (ec == bhttp::error::end_of_stream)
780 break; 774 break;
781 else if (ec) 775 else if (ec)
@@ -783,11 +777,11 @@ public:
783 777
784 auto http_version = p0.get().version(); 778 auto http_version = p0.get().version();
785 auto&& rsp = co_await handle_request( 779 auto&& rsp = co_await handle_request(
786 readable_request{ 780 readable_request{
787 .p = util::not_null{&p0}, 781 .p = util::not_null{&p0},
788 .strm = util::not_null{&strm}, 782 .strm = util::not_null{&strm},
789 .buf = util::not_null{&buf}, 783 .buf = util::not_null{&buf},
790 }); 784 });
791 rsp.header().version(http_version); 785 rsp.header().version(http_version);
792 bool keep_alive = rsp.keep_alive(); 786 bool keep_alive = rsp.keep_alive();
793 co_await beast::async_write(strm, std::move(rsp)); 787 co_await beast::async_write(strm, std::move(rsp));
@@ -806,27 +800,27 @@ public:
806 auto acceptor = tcp::acceptor{executor, endpoint}; 800 auto acceptor = tcp::acceptor{executor, endpoint};
807 801
808 l_.with("endpoint", endpoint.address().to_string()) 802 l_.with("endpoint", endpoint.address().to_string())
809 .with("port", std::to_string(endpoint.port())) 803 .with("port", std::to_string(endpoint.port()))
810 .info("Serving"); 804 .info("Serving");
811 while (true) 805 while (true)
812 { 806 {
813 net::co_spawn( 807 net::co_spawn(
814 executor, 808 executor,
815 do_session(beast::tcp_stream{co_await acceptor.async_accept()}), 809 do_session(beast::tcp_stream{co_await acceptor.async_accept()}),
816 [this](std::exception_ptr e) 810 [this](std::exception_ptr e)
811 {
812 if (e)
817 { 813 {
818 if (e) 814 try
815 {
816 std::rethrow_exception(e);
817 }
818 catch (std::exception const& e)
819 { 819 {
820 try 820 l_.error("Error in session: {}", e.what());
821 {
822 std::rethrow_exception(e);
823 }
824 catch (std::exception const& e)
825 {
826 l_.error("Error in session: {}", e.what());
827 }
828 } 821 }
829 }); 822 }
823 });
830 } 824 }
831 } 825 }
832 826
@@ -837,21 +831,21 @@ public:
837 831
838 // TODO: make exception handling as nice as in srv.cpp 832 // TODO: make exception handling as nice as in srv.cpp
839 net::co_spawn( 833 net::co_spawn(
840 ioc, do_listen(endpoint), 834 ioc, do_listen(endpoint),
841 [this](std::exception_ptr e) 835 [this](std::exception_ptr e)
836 {
837 if (e)
842 { 838 {
843 if (e) 839 try
844 { 840 {
845 try 841 std::rethrow_exception(e);
846 {
847 std::rethrow_exception(e);
848 }
849 catch (std::exception const& e)
850 {
851 l_.error("Error: {}", e.what());
852 }
853 } 842 }
854 }); 843 catch (std::exception const& e)
844 {
845 l_.error("Error: {}", e.what());
846 }
847 }
848 });
855 } 849 }
856}; 850};
857 851
diff --git a/server/src/locale.cppm b/server/src/locale.cppm
index 8fd867b..06607f0 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,9 +80,8 @@ 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< 84 && std::same_as<std::locale const&, std::ranges::range_const_reference_t<T>>;
85 std::locale const&, std::ranges::range_const_reference_t<T>>;
86 85
87// Helps select a locale based on the Accept-Language header in an 86// Helps select a locale based on the Accept-Language header in an
88// HTTP request. 87// HTTP request.
@@ -93,23 +92,23 @@ export class selector
93 std::shared_ptr<blocale::generator const> lgen_; 92 std::shared_ptr<blocale::generator const> lgen_;
94 93
95 auto make_matcher( 94 auto make_matcher(
96 locale_input_range auto supported_locales, std::locale default_locale) 95 locale_input_range auto supported_locales, std::locale default_locale)
97 { 96 {
98 auto builder = icu::LocaleMatcher::Builder{}; 97 auto builder = icu::LocaleMatcher::Builder{};
99 for (auto const& supported_locale : supported_locales) 98 for (auto const& supported_locale : supported_locales)
100 { 99 {
101 auto const& supported_locale_info = 100 auto const& supported_locale_info =
102 std::use_facet<blocale::info>(supported_locale); 101 std::use_facet<blocale::info>(supported_locale);
103 auto supported_icu_locale = 102 auto supported_icu_locale =
104 icu::Locale{supported_locale_info.name().c_str()}; 103 icu::Locale{supported_locale_info.name().c_str()};
105 if (supported_icu_locale.isBogus()) 104 if (supported_icu_locale.isBogus())
106 throw std::runtime_error{ 105 throw std::runtime_error{
107 "supported locale gives rise to bogus ICU locale" 106 "supported locale gives rise to bogus ICU locale"
108 }; 107 };
109 builder.addSupportedLocale(supported_icu_locale); 108 builder.addSupportedLocale(supported_icu_locale);
110 } 109 }
111 auto const& default_locale_info = 110 auto const& default_locale_info =
112 std::use_facet<blocale::info>(default_locale); 111 std::use_facet<blocale::info>(default_locale);
113 auto default_icu_locale = icu::Locale{default_locale_info.name().c_str()}; 112 auto default_icu_locale = icu::Locale{default_locale_info.name().c_str()};
114 if (default_icu_locale.isBogus()) 113 if (default_icu_locale.isBogus())
115 throw std::runtime_error{"default locale gives rise to bogus ICU locale"}; 114 throw std::runtime_error{"default locale gives rise to bogus ICU locale"};
@@ -171,7 +170,7 @@ export class selector
171 while (kw = keywords->next(&kw_len, ec), !U_FAILURE(ec) && kw) 170 while (kw = keywords->next(&kw_len, ec), !U_FAILURE(ec) && kw)
172 { 171 {
173 auto value = 172 auto value =
174 l.getKeywordValue<std::string>(icu::StringPiece(kw, kw_len), ec); 173 l.getKeywordValue<std::string>(icu::StringPiece(kw, kw_len), ec);
175 if (!added_at) 174 if (!added_at)
176 { 175 {
177 posix_name += "@"; 176 posix_name += "@";
@@ -196,8 +195,8 @@ public:
196 // Note: lgen must live at least as long as the selector constructed here! 195 // Note: lgen must live at least as long as the selector constructed here!
197 // It is unfortunately not possible to copy/move a blocale::generator. 196 // It is unfortunately not possible to copy/move a blocale::generator.
198 explicit selector( 197 explicit selector(
199 locale_input_range auto locales, std::locale default_, 198 locale_input_range auto locales, std::locale default_,
200 std::shared_ptr<blocale::generator const> lgen) 199 std::shared_ptr<blocale::generator const> lgen)
201 : default_{default_}, matcher_{make_matcher(locales, default_)}, lgen_{lgen} 200 : default_{default_}, matcher_{make_matcher(locales, default_)}, lgen_{lgen}
202 { 201 {
203 } 202 }
@@ -212,7 +211,7 @@ public:
212 accept_language | std::views::split(","sv) | std::views::enumerate) 211 accept_language | std::views::split(","sv) | std::views::enumerate)
213 { 212 {
214 auto [lang_range_ut, mweight_ut] = 213 auto [lang_range_ut, mweight_ut] =
215 util::split_on(std::string_view{lang_prio}, ';'); 214 util::split_on(std::string_view{lang_prio}, ';');
216 auto lang_range_str = trim_ows(lang_range_ut); 215 auto lang_range_str = trim_ows(lang_range_ut);
217 auto mweight_str = mweight_ut.transform(trim_ows); 216 auto mweight_str = mweight_ut.transform(trim_ows);
218 if (lang_range_str == "*") 217 if (lang_range_str == "*")
@@ -228,7 +227,7 @@ public:
228 { 227 {
229 auto weight_str = mweight_str->substr(2, 4); 228 auto weight_str = mweight_str->substr(2, 4);
230 if (auto mweight = 229 if (auto mweight =
231 util::parse_float(weight_str, std::chars_format::fixed); 230 util::parse_float(weight_str, std::chars_format::fixed);
232 mweight && 0.0f < *mweight && *mweight < 1.0f) 231 mweight && 0.0f < *mweight && *mweight < 1.0f)
233 { 232 {
234 weight = *mweight; 233 weight = *mweight;
@@ -238,8 +237,8 @@ public:
238 if (weight > 0.0f) 237 if (weight > 0.0f)
239 { 238 {
240 dlpm[icu_locale] = { 239 dlpm[icu_locale] = {
241 .weight = weight, 240 .weight = weight,
242 .original_index = static_cast<std::size_t>(i), 241 .original_index = static_cast<std::size_t>(i),
243 }; 242 };
244 } 243 }
245 else 244 else
@@ -249,7 +248,7 @@ public:
249 } 248 }
250 249
251 auto desired_locales = 250 auto desired_locales =
252 std::vector<icu_priority_locale>{dlpm.begin(), dlpm.end()}; 251 std::vector<icu_priority_locale>{dlpm.begin(), dlpm.end()};
253 std::sort(desired_locales.begin(), desired_locales.end()); 252 std::sort(desired_locales.begin(), desired_locales.end());
254 auto it = icu_priority_locale_vec_iterator{std::move(desired_locales)}; 253 auto it = icu_priority_locale_vec_iterator{std::move(desired_locales)};
255 auto ec = UErrorCode::U_ZERO_ERROR; 254 auto ec = UErrorCode::U_ZERO_ERROR;
@@ -268,7 +267,7 @@ export auto to_bcp47_lang_tag(std::locale locale) -> std::optional<std::string>
268 auto const& locale_info = std::use_facet<blocale::info>(locale); 267 auto const& locale_info = std::use_facet<blocale::info>(locale);
269 auto ec = UErrorCode::U_ZERO_ERROR; 268 auto ec = UErrorCode::U_ZERO_ERROR;
270 auto bcp47_lang_tag = 269 auto bcp47_lang_tag =
271 icu::Locale{locale_info.name().c_str()}.toLanguageTag<std::string>(ec); 270 icu::Locale{locale_info.name().c_str()}.toLanguageTag<std::string>(ec);
272 if (U_FAILURE(ec)) 271 if (U_FAILURE(ec))
273 return std::nullopt; 272 return std::nullopt;
274 return bcp47_lang_tag; 273 return bcp47_lang_tag;
diff --git a/server/src/log.cppm b/server/src/log.cppm
index 0bfdf7d..7c42144 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 0a7a784..2957808 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];
@@ -31,10 +31,10 @@ auto real_main(std::span<char const*> args) -> exit_status
31 auto lgen = routemon::locale::make_generator(); 31 auto lgen = routemon::locale::make_generator();
32 auto default_locale = lgen->generate("en_US.UTF-8"); 32 auto default_locale = lgen->generate("en_US.UTF-8");
33 auto locales = { 33 auto locales = {
34 default_locale, 34 default_locale,
35 lgen->generate("nl_NL.UTF-8"), 35 lgen->generate("nl_NL.UTF-8"),
36 lgen->generate("de_DE.UTF-8"), 36 lgen->generate("de_DE.UTF-8"),
37 lgen->generate("en_GB.UTF-8"), 37 lgen->generate("en_GB.UTF-8"),
38 }; 38 };
39 auto lsel = routemon::locale::selector{locales, default_locale, lgen}; 39 auto lsel = routemon::locale::selector{locales, default_locale, lgen};
40 40
@@ -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,9 +129,7 @@ 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{ 132 std::span{const_cast<char const**>(argv), static_cast<std::size_t>(argc)});
133 const_cast<char const**>(argv), static_cast<std::size_t>(argc)
134 });
135 switch (res) 133 switch (res)
136 { 134 {
137 case exit_status::failure: 135 case exit_status::failure:
diff --git a/server/src/problem.cppm b/server/src/problem.cppm
index a124312..a72379a 100644
--- a/server/src/problem.cppm
+++ b/server/src/problem.cppm
@@ -41,13 +41,13 @@ 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},
49 {"title", details.title.str(locale)}, 49 {"title", details.title.str(locale)},
50 {"status", static_cast<unsigned>(details.status)}, 50 {"status", static_cast<unsigned>(details.status)},
51 }; 51 };
52 if (details.detail) 52 if (details.detail)
53 obj["detail"] = details.detail->str(locale); 53 obj["detail"] = details.detail->str(locale);
diff --git a/server/src/req_ctx.cppm b/server/src/req_ctx.cppm
index 4a64d7d..464f2ff 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 8269ee8..f2f1681 100644
--- a/server/src/rwgps.cppm
+++ b/server/src/rwgps.cppm
@@ -46,50 +46,50 @@ 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")),
53 .user_id = json::value_to<std::int64_t>(jv.at("user_id")), 53 .user_id = json::value_to<std::int64_t>(jv.at("user_id")),
54 .url = json::value_to<std::string>(jv.at("url")), 54 .url = json::value_to<std::string>(jv.at("url")),
55 .name = json::value_to<std::string>(jv.at("name")), 55 .name = json::value_to<std::string>(jv.at("name")),
56 .description = json::value_to<std::string>(jv.at("description")), 56 .description = json::value_to<std::string>(jv.at("description")),
57 }; 57 };
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")),
78 }; 78 };
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")),
87 .meta = json::value_to<get_routes_meta>(jv.at("meta")), 87 .meta = json::value_to<get_routes_meta>(jv.at("meta")),
88 }; 88 };
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}
@@ -107,9 +107,9 @@ export class client
107 auto get_routes_page(std::size_t page) -> get_routes_response 107 auto get_routes_page(std::size_t page) -> get_routes_response
108 { 108 {
109 auto req = bhttp::request<bhttp::string_body>{ 109 auto req = bhttp::request<bhttp::string_body>{
110 bhttp::verb::get, 110 bhttp::verb::get,
111 std::format("/api/v1/routes.json?page_size=200?page={}", page), 111 std::format("/api/v1/routes.json?page_size=200?page={}", page),
112 11, // HTTP 1.1 112 11, // HTTP 1.1
113 }; 113 };
114 req.set(bhttp::field::host, host); 114 req.set(bhttp::field::host, host);
115 req.set("x-rwgps-api-key", api_key_); 115 req.set("x-rwgps-api-key", api_key_);
@@ -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 70f58c0..45366b8 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
@@ -162,7 +162,7 @@ public:
162 throw std::logic_error{"got unexpected negative amount of columns"}; 162 throw std::logic_error{"got unexpected negative amount of columns"};
163 if (sizeof...(args) > *ncols) 163 if (sizeof...(args) > *ncols)
164 throw std::invalid_argument{ 164 throw std::invalid_argument{
165 "more scanning arguments provided than columns in result set" 165 "more scanning arguments provided than columns in result set"
166 }; 166 };
167 auto col = 0; 167 auto col = 0;
168 (..., scan(col++, args)); 168 (..., scan(col++, args));
@@ -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,31 +242,31 @@ 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)
247 { 251 {
248 if (auto s = ::sqlite3_prepare_v2( 252 if (pstmt != nullptr)
249 dbc_, sql.data(), *sql_size + 1, &pstmt, &sql_tail);
250 s != SQLITE_OK)
251 { 253 {
252 if (pstmt != nullptr) 254 // Use contract_assert when having a compiler with
253 { 255 // contracts available
254 // Use contract_assert when having a compiler with 256 ::sqlite3_finalize(pstmt);
255 // contracts available 257 throw std::logic_error{
256 ::sqlite3_finalize(pstmt); 258 "expected stmt to be null after failed preparation"
257 throw std::logic_error{ 259 };
258 "expected stmt to be null after failed preparation"
259 };
260 }
261 throw error{guard, s, dbc_};
262 } 260 }
263 }); 261 throw error{guard, s, dbc_};
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};
267 if (sql_tail && std::strlen(sql_tail) > 0) 267 if (sql_tail && std::strlen(sql_tail) > 0)
268 throw std::invalid_argument{ 268 throw std::invalid_argument{
269 "provided input text contains more than one SQL statement" 269 "provided input text contains more than one SQL statement"
270 }; 270 };
271 auto b = binder{stmt}; 271 auto b = binder{stmt};
272 bf(b); 272 bf(b);
@@ -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 d9ff880..5763bc5 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); },
75 }, 73 },
76 std::move(res_)); 74 [](gpx::file&& f) -> gpx::file&& { return std::move(f); },
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 117 auto put(
118 put(beast::concepts::const_buffer_sequence auto b, 118 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,9 +125,7 @@ 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{ 128 std::string_view{static_cast<char const*>(it->data()), it->size()});
129 static_cast<char const*>(it->data()), it->size()
130 });
131 total += it->size(); 129 total += it->size();
132 } 130 }
133 ec = {}; 131 ec = {};
@@ -168,25 +166,25 @@ public:
168 166
169private: 167private:
170 auto handle_process_gpx(l0_ctx ctx, http::readable_request r) 168 auto handle_process_gpx(l0_ctx ctx, http::readable_request r)
171 -> net::awaitable<http::presponse> 169 -> net::awaitable<http::presponse>
172 { 170 {
173 auto gpx_file = gpx::file{}; 171 auto gpx_file = gpx::file{};
174 try 172 try
175 { 173 {
176 auto req = 174 auto req =
177 co_await http::read_request<readable_gpx_body>(ctx, std::move(r)); 175 co_await http::read_request<readable_gpx_body>(ctx, std::move(r));
178 gpx_file = std::move(req->body().unwrap()); 176 gpx_file = std::move(req->body().unwrap());
179 } 177 }
180 catch (std::exception& ex) 178 catch (std::exception& ex)
181 { 179 {
182 // TODO: more detailed problem reporting 180 // TODO: more detailed problem reporting
183 auto tpl = problem::tpl{ 181 auto tpl = problem::tpl{
184 .status = bhttp::status::bad_request, 182 .status = bhttp::status::bad_request,
185 .title = translate("Failed to parse GPX file"), 183 .title = translate("Failed to parse GPX file"),
186 .type_uri = "https://routemon.fautchen.eu/problems/gpx-parse-failed", 184 .type_uri = "https://routemon.fautchen.eu/problems/gpx-parse-failed",
187 }; 185 };
188 co_return http::problem_rsp( 186 co_return http::problem_rsp(
189 ctx, tpl.instantiate(), http::keep_alive{false}); 187 ctx, tpl.instantiate(), http::keep_alive{false});
190 } 188 }
191 189
192 // TODO: catch handler exceptions and return 500 when raised? 190 // TODO: catch handler exceptions and return 500 when raised?
@@ -195,17 +193,17 @@ private:
195 if (!mres) 193 if (!mres)
196 { 194 {
197 auto tpl = problem::tpl{ 195 auto tpl = problem::tpl{
198 .status = bhttp::status::internal_server_error, 196 .status = bhttp::status::internal_server_error,
199 .title = translate("Internal server error"), 197 .title = translate("Internal server error"),
200 .type_uri = "https://routemon.fautchen.eu/problems/" 198 .type_uri = "https://routemon.fautchen.eu/problems/"
201 "internal-server-error", 199 "internal-server-error",
202 }; 200 };
203 co_return http::problem_rsp( 201 co_return http::problem_rsp(
204 ctx, tpl.instantiate(), http::keep_alive{true}); 202 ctx, tpl.instantiate(), http::keep_alive{true});
205 } 203 }
206 204
207 auto rsp = http::make_rsp<bhttp::string_body>( 205 auto rsp = http::make_rsp<bhttp::string_body>(
208 bhttp::status::ok, http::keep_alive{true}); 206 bhttp::status::ok, http::keep_alive{true});
209 rsp.set(bhttp::field::content_type, "application/json"); 207 rsp.set(bhttp::field::content_type, "application/json");
210 rsp.body() = json::serialize(json::value_from(*mres)); 208 rsp.body() = json::serialize(json::value_from(*mres));
211 rsp.prepare_payload(); 209 rsp.prepare_payload();
@@ -213,14 +211,14 @@ private:
213 } 211 }
214 212
215 auto handle_sysinfo(l0_ctx ctx, http::readable_request r) 213 auto handle_sysinfo(l0_ctx ctx, http::readable_request r)
216 -> net::awaitable<http::presponse> 214 -> net::awaitable<http::presponse>
217 { 215 {
218 auto req = 216 auto req =
219 co_await http::read_request<bhttp::empty_body>(ctx, std::move(r)); 217 co_await http::read_request<bhttp::empty_body>(ctx, std::move(r));
220 auto info = inner_.sysinfo(); 218 auto info = inner_.sysinfo();
221 219
222 auto rsp = http::make_rsp<bhttp::string_body>( 220 auto rsp = http::make_rsp<bhttp::string_body>(
223 bhttp::status::ok, http::keep_alive{true}); 221 bhttp::status::ok, http::keep_alive{true});
224 rsp.set(bhttp::field::content_type, "application/json"); 222 rsp.set(bhttp::field::content_type, "application/json");
225 rsp.body() = json::serialize(json::value_from(info)); 223 rsp.body() = json::serialize(json::value_from(info));
226 rsp.prepare_payload(); 224 rsp.prepare_payload();
@@ -236,17 +234,17 @@ public:
236 { return std::bind_front(member, this); }; 234 { return std::bind_front(member, this); };
237 235
238 return http::dtree<http::routed_ctx<outer_ctx>>{}.named_subtrees({ 236 return http::dtree<http::routed_ctx<outer_ctx>>{}.named_subtrees({
239 {"gpx", 237 {"gpx",
240 http::dtree<l0_ctx>{ 238 http::dtree<l0_ctx>{
241 { 239 {
242 .post = handler(&handler::handle_process_gpx), 240 .post = handler(&handler::handle_process_gpx),
243 } 241 }
244 }.no_subtrees()}, 242 }.no_subtrees()},
245 {"sysinfo", http::dtree<l0_ctx>{ 243 {"sysinfo", http::dtree<l0_ctx>{
246 { 244 {
247 .get = handler(&handler::handle_sysinfo), 245 .get = handler(&handler::handle_sysinfo),
248 } 246 }
249 }.no_subtrees()}, 247 }.no_subtrees()},
250 }); 248 });
251 } 249 }
252}; 250};
@@ -257,13 +255,13 @@ export class server
257 http::server<handler::outer_ctx> srv_; 255 http::server<handler::outer_ctx> srv_;
258 256
259 static auto make_global_middleware() 257 static auto make_global_middleware()
260 -> http::middleware_t<http::base_ctx, handler::outer_ctx> 258 -> http::middleware_t<http::base_ctx, handler::outer_ctx>
261 { 259 {
262 return http::middleware_compose< 260 return http::middleware_compose<
263 http::base_ctx, http::trace_id_ctx<http::base_ctx>, 261 http::base_ctx, http::trace_id_ctx<http::base_ctx>,
264 http::trace_id_ctx<http::base_ctx>>( 262 http::trace_id_ctx<http::base_ctx>>(
265 http::trace_id_middleware<http::base_ctx>, 263 http::trace_id_middleware<http::base_ctx>,
266 http::lax_cors_middleware<http::trace_id_ctx<http::base_ctx>>); 264 http::lax_cors_middleware<http::trace_id_ctx<http::base_ctx>>);
267 } 265 }
268 266
269public: 267public:
diff --git a/server/src/time.cppm b/server/src/time.cppm
index 907cec4..d77a30b 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();
@@ -84,7 +84,7 @@ class period_seq
84 84
85 if (preds.size() < 2) 85 if (preds.size() < 2)
86 throw std::logic_error{ 86 throw std::logic_error{
87 "period_seq::consolidate: amount of predicates should be >= 2" 87 "period_seq::consolidate: amount of predicates should be >= 2"
88 }; 88 };
89 if (preds.front().second != ge) 89 if (preds.front().second != ge)
90 throw std::logic_error{"period_seq::consolidate: first element of preds " 90 throw std::logic_error{"period_seq::consolidate: first element of preds "
diff --git a/server/src/trace.cppm b/server/src/trace.cppm
index 35d32f3..3dbed12 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 bdccf10..2e20e8b 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,8 +155,7 @@ 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), 158 rhs.length_.load(std::memory_order_acquire), std::memory_order_release);
159 std::memory_order_release);
160 } 159 }
161 return *this; 160 return *this;
162 } 161 }
@@ -203,7 +202,7 @@ public:
203}; 202};
204 203
205constexpr auto operator""_zsv(char const* s, std::size_t length) noexcept 204constexpr auto operator""_zsv(char const* s, std::size_t length) noexcept
206 -> zstring_view 205 -> zstring_view
207{ 206{
208 return zstring_view{s, length}; 207 return zstring_view{s, length};
209} 208}
@@ -214,7 +213,7 @@ auto operator==(zstring_view lhs, zstring_view rhs) -> bool
214} 213}
215 214
216export constexpr auto split_on(std::string_view s, char c) 215export constexpr auto split_on(std::string_view s, char c)
217 -> std::pair<std::string_view, std::optional<std::string_view>> 216 -> std::pair<std::string_view, std::optional<std::string_view>>
218{ 217{
219 if (auto i = s.find(c); i != std::string_view::npos) 218 if (auto i = s.find(c); i != std::string_view::npos)
220 return std::make_pair(s.substr(0, i), s.substr(i + 1)); 219 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 c1b4358..7cf4415 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 ff27c5c..0c319e6 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 = standalone < 0 ? std::nullopt 388 .standalone =
389 : std::make_optional(standalone > 0), 389 standalone < 0 ? std::nullopt : 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())
@@ -442,8 +442,8 @@ public:
442 [[nodiscard]] inline auto position() -> position 442 [[nodiscard]] inline auto position() -> position
443 { 443 {
444 return { 444 return {
445 .line = XML_GetCurrentLineNumber(p_), 445 .line = XML_GetCurrentLineNumber(p_),
446 .col = XML_GetCurrentColumnNumber(p_), 446 .col = XML_GetCurrentColumnNumber(p_),
447 }; 447 };
448 } 448 }
449 449
@@ -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