Skip to content

Commit 47d2daf

Browse files
committed
JSON support for changeset upload
1 parent 71d40b8 commit 47d2daf

18 files changed

+1391
-30
lines changed

contrib/sjparser/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cmake_minimum_required(VERSION 3.8)
1+
cmake_minimum_required(VERSION 3.14)
22

33
project(sjparser)
44

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

0 commit comments

Comments
 (0)