Skip to content

Commit 09ea77d

Browse files
committed
JSON support for changeset upload
1 parent dbb6673 commit 09ea77d

17 files changed

+1375
-28
lines changed
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
/**
2+
* SPDX-License-Identifier: GPL-2.0-only
3+
*
4+
* This file is part of openstreetmap-cgimap (https://github.com/zerebubuth/openstreetmap-cgimap/).
5+
*
6+
* Copyright (C) 2009-2023 by the CGImap developer community.
7+
* For a full list of authors see the git log.
8+
*/
9+
10+
#ifndef OSMCHANGE_JSON_INPUT_FORMAT_HPP
11+
#define OSMCHANGE_JSON_INPUT_FORMAT_HPP
12+
13+
#include "cgimap/api06/changeset_upload/node.hpp"
14+
#include "cgimap/api06/changeset_upload/osmobject.hpp"
15+
#include "cgimap/api06/changeset_upload/parser_callback.hpp"
16+
#include "cgimap/api06/changeset_upload/relation.hpp"
17+
#include "cgimap/api06/changeset_upload/way.hpp"
18+
#include "cgimap/types.hpp"
19+
20+
#include "sjparser/sjparser.h"
21+
22+
#include <fmt/core.h>
23+
24+
#include <cassert>
25+
#include <memory>
26+
#include <string>
27+
#include <utility>
28+
#include <vector>
29+
30+
31+
namespace api06 {
32+
33+
using SJParser::Array;
34+
using SJParser::Member;
35+
using SJParser::Object;
36+
using SJParser::Parser;
37+
using SJParser::Presence;
38+
using SJParser::SArray;
39+
using SJParser::SAutoObject;
40+
using SJParser::SMap;
41+
using SJParser::Value;
42+
using SJParser::Reaction;
43+
using SJParser::ObjectOptions;
44+
45+
using std::placeholders::_1;
46+
47+
class OSMChangeJSONParserFormat {
48+
49+
static auto getMemberParser() {
50+
return SAutoObject{std::tuple{Member{"type", Value<std::string>{}},
51+
Member{"ref", Value<int64_t>{}},
52+
Member{"role", Value<std::string>{}, Presence::Optional, ""}},
53+
ObjectOptions{Reaction::Ignore}
54+
};
55+
}
56+
57+
template <typename ElementParserCallback = std::nullptr_t>
58+
static auto getElementsParser(ElementParserCallback element_parser_callback = nullptr) {
59+
return Object{
60+
std::tuple{
61+
Member{"type", Value<std::string>{}},
62+
Member{"action", Value<std::string>{}},
63+
Member{"if-unused", Value<bool>{}, Presence::Optional, false},
64+
Member{"id", Value<int64_t>{}},
65+
Member{"lat", Value<double>{}, Presence::Optional},
66+
Member{"lon", Value<double>{}, Presence::Optional},
67+
Member{"version", Value<int64_t>{}, Presence::Optional},
68+
Member{"changeset", Value<int64_t>{}},
69+
Member{"tags", SMap{Value<std::string>{}}, Presence::Optional},
70+
Member{"nodes", SArray{Value<int64_t>{}}, Presence::Optional},
71+
Member{"members", SArray{getMemberParser()}, Presence::Optional}
72+
},
73+
ObjectOptions{Reaction::Ignore},
74+
element_parser_callback};
75+
}
76+
77+
template <typename ElementParserCallback = std::nullptr_t>
78+
static auto getMainParser(ElementParserCallback element_parser_callback = nullptr) {
79+
return Parser{
80+
Object{
81+
std::tuple{
82+
Member{"version", Value<std::string>{}, Presence::Optional},
83+
Member{"generator", Value<std::string>{}, Presence::Optional},
84+
Member{"osmChange", Array{getElementsParser(element_parser_callback)}}
85+
},ObjectOptions{Reaction::Ignore}}};
86+
}
87+
88+
friend class OSMChangeJSONParser;
89+
};
90+
91+
class OSMChangeJSONParser {
92+
93+
public:
94+
explicit OSMChangeJSONParser(Parser_Callback& callback)
95+
: m_callback(callback) { }
96+
97+
OSMChangeJSONParser(const OSMChangeJSONParser &) = delete;
98+
OSMChangeJSONParser &operator=(const OSMChangeJSONParser &) = delete;
99+
100+
OSMChangeJSONParser(OSMChangeJSONParser &&) = delete;
101+
OSMChangeJSONParser &operator=(OSMChangeJSONParser &&) = delete;
102+
103+
void process_message(const std::string &data) {
104+
105+
try {
106+
m_callback.start_document();
107+
_parser.parse(data);
108+
_parser.finish();
109+
110+
if (_parser.parser().isEmpty()) {
111+
throw payload_error("Empty JSON payload");
112+
}
113+
114+
if (element_count == 0) {
115+
throw payload_error("osmChange array is empty");
116+
}
117+
118+
m_callback.end_document();
119+
} catch (const std::exception& e) {
120+
throw http::bad_request(e.what()); // rethrow JSON parser error as HTTP 400 Bad request
121+
}
122+
}
123+
124+
private:
125+
126+
using ElementsParser = decltype(api06::OSMChangeJSONParserFormat::getElementsParser());
127+
using MainParser = decltype(api06::OSMChangeJSONParserFormat::getMainParser());
128+
129+
MainParser _parser{api06::OSMChangeJSONParserFormat::getMainParser(std::bind(&api06::OSMChangeJSONParser::process_element, this, _1))};
130+
131+
// OSM element callback
132+
bool process_element(ElementsParser &parser) {
133+
134+
element_count++;
135+
136+
// process action
137+
process_action(parser);
138+
139+
// process if-unused flag for delete action
140+
process_if_unused(parser);
141+
142+
// process type (node, way, relation)
143+
process_type(parser);
144+
145+
return true;
146+
}
147+
148+
void process_action(ElementsParser &parser) {
149+
150+
const std::string& action = parser.get<1>();
151+
152+
if (action == "create") {
153+
m_operation = operation::op_create;
154+
} else if (action == "modify") {
155+
m_operation = operation::op_modify;
156+
} else if (action == "delete") {
157+
m_operation = operation::op_delete;
158+
} else {
159+
throw payload_error{fmt::format("Unknown action {}, choices are create, modify, delete", action)};
160+
}
161+
}
162+
163+
void process_if_unused(ElementsParser &parser) {
164+
165+
if (m_operation == operation::op_delete) {
166+
m_if_unused = false;
167+
if (parser.parser<2>().isSet()) {
168+
m_if_unused = parser.get<2>();
169+
}
170+
}
171+
}
172+
173+
void process_type(ElementsParser &parser) {
174+
175+
const std::string& type = parser.get<0>();
176+
177+
if (type == "node") {
178+
process_node(parser);
179+
} else if (type == "way") {
180+
process_way(parser);
181+
} else if (type == "relation") {
182+
process_relation(parser);
183+
} else {
184+
throw payload_error{fmt::format("Unknown element {}, expecting node, way or relation", type)};
185+
}
186+
}
187+
188+
void process_node(ElementsParser& parser) {
189+
190+
Node node;
191+
init_object(node, parser);
192+
193+
if (parser.parser<4>().isSet()) {
194+
node.set_lat(parser.get<4>());
195+
}
196+
197+
if (parser.parser<5>().isSet()) {
198+
node.set_lon(parser.get<5>());
199+
}
200+
201+
process_tags(node, parser);
202+
203+
if (!node.is_valid(m_operation)) {
204+
throw payload_error{fmt::format("{} does not include all mandatory fields", node.to_string())};
205+
}
206+
207+
m_callback.process_node(node, m_operation, m_if_unused);
208+
}
209+
210+
void process_way(ElementsParser& parser) {
211+
212+
Way way;
213+
init_object(way, parser);
214+
215+
// adding way nodes
216+
if (parser.parser<9>().isSet()) {
217+
for (const auto& value : parser.get<9>()) {
218+
way.add_way_node(value);
219+
}
220+
}
221+
222+
process_tags(way, parser);
223+
224+
if (!way.is_valid(m_operation)) {
225+
throw payload_error{fmt::format("{} does not include all mandatory fields", way.to_string())};
226+
}
227+
228+
m_callback.process_way(way, m_operation, m_if_unused);
229+
}
230+
231+
void process_relation(ElementsParser& parser) {
232+
233+
Relation relation;
234+
init_object(relation, parser);
235+
236+
process_relation_members(relation, parser);
237+
238+
process_tags(relation, parser);
239+
240+
if (!relation.is_valid(m_operation)) {
241+
throw payload_error{fmt::format("{} does not include all mandatory fields", relation.to_string())};
242+
}
243+
244+
m_callback.process_relation(relation, m_operation, m_if_unused);
245+
}
246+
247+
void process_relation_members(Relation &relation, ElementsParser& parser) {
248+
249+
if (!parser.parser<10>().isSet()) {
250+
return;
251+
}
252+
253+
for (auto &mbr : parser.get<10>()) {
254+
const auto& [type, ref, role] = mbr;
255+
256+
RelationMember member;
257+
member.set_type(type);
258+
member.set_ref(ref);
259+
member.set_role(role);
260+
261+
if (!member.is_valid()) {
262+
throw payload_error{fmt::format("Missing mandatory field on relation member in {}", relation.to_string()) };
263+
}
264+
relation.add_member(member);
265+
}
266+
}
267+
268+
void process_tags(OSMObject &o, ElementsParser& parser) {
269+
270+
if (parser.parser<8>().isSet()) {
271+
for (const auto &tag : parser.get<8>()) {
272+
o.add_tag(tag.first, tag.second);
273+
}
274+
}
275+
}
276+
277+
void init_object(OSMObject &object, ElementsParser& parser) {
278+
279+
// id
280+
object.set_id(parser.get<3>());
281+
282+
// version
283+
if (parser.parser<6>().isSet()) {
284+
object.set_version(parser.get<6>());
285+
}
286+
287+
// changeset
288+
if (parser.parser<7>().isSet()) {
289+
object.set_changeset(parser.get<7>());
290+
}
291+
292+
// TODO: not needed, handled by sjparser
293+
if (!object.has_id()) {
294+
throw payload_error{ "Mandatory field id missing in object" };
295+
}
296+
297+
if (!object.has_changeset()) {
298+
throw payload_error{fmt::format("Changeset id is missing for {}", object.to_string()) };
299+
}
300+
301+
if (m_operation == operation::op_create) {
302+
// we always override version number for create operations (they are not
303+
// mandatory)
304+
object.set_version(0u);
305+
} else if (m_operation == operation::op_delete ||
306+
m_operation == operation::op_modify) {
307+
// objects for other operations must have a positive version number
308+
if (!object.has_version()) {
309+
throw payload_error{fmt::format("Version is required when updating {}", object.to_string()) };
310+
}
311+
if (object.version() < 1) {
312+
throw payload_error{ fmt::format("Invalid version number {} in {}", object.version(), object.to_string()) };
313+
}
314+
}
315+
}
316+
317+
operation m_operation = operation::op_undefined;
318+
Parser_Callback& m_callback;
319+
bool m_if_unused = false;
320+
int element_count = 0;
321+
};
322+
323+
} // namespace api06
324+
325+
#endif // OSMCHANGE_JSON_INPUT_FORMAT_HPP

include/cgimap/json_formatter.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ class json_formatter : public output_formatter {
4242
void start_changeset(bool) override;
4343
void end_changeset(bool) override;
4444

45+
void start_diffresult() override;
46+
void end_diffresult() override;
4547
void start_action(action_type type) override;
4648
void end_action(action_type type) override;
4749
void error(const std::exception &e) override;
@@ -63,6 +65,7 @@ class json_formatter : public output_formatter {
6365
const osm_nwr_signed_id_t old_id,
6466
const osm_nwr_id_t new_id,
6567
const osm_version_t new_version) override;
68+
6669
void write_diffresult_delete(const element_type elem,
6770
const osm_nwr_signed_id_t old_id) override;
6871

include/cgimap/osm_diffresult_responder.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class osm_diffresult_responder : public osm_responder {
2727

2828
~osm_diffresult_responder() override;
2929

30+
// lists the standard types that OSM format can respond in
31+
std::vector<mime::type> types_available() const override;
3032

3133
void write(output_formatter& f,
3234
const std::string &generator,

include/cgimap/output_formatter.hpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,23 @@ constexpr T element_type_name(element_type elt) noexcept {
5252
return "";
5353
}
5454

55+
template <typename T = const char*>
56+
T action_type_name(action_type action) noexcept {
57+
58+
switch (action) {
59+
case action_type::create:
60+
return "create";
61+
break;
62+
case action_type::modify:
63+
return "modify";
64+
break;
65+
case action_type::del:
66+
return "delete";
67+
break;
68+
}
69+
return "";
70+
}
71+
5572
} // anonymous namespace
5673

5774
struct element_info {
@@ -207,6 +224,11 @@ struct output_formatter {
207224

208225
virtual void end_changeset(bool) = 0;
209226

227+
// marks the beginning of diffResult response processing
228+
virtual void start_diffresult() = 0;
229+
230+
virtual void end_diffresult() = 0;
231+
210232
// TODO: document me.
211233
virtual void start_action(action_type type) = 0;
212234
virtual void end_action(action_type type) = 0;

include/cgimap/text_formatter.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class text_formatter : public output_formatter {
4040
void start_changeset(bool) override;
4141
void end_changeset(bool) override;
4242

43+
void start_diffresult() override;
44+
void end_diffresult() override;
4345
void start_action(action_type type) override;
4446
void end_action(action_type type) override;
4547
void error(const std::exception &e) override;

0 commit comments

Comments
 (0)