Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ __pycache__/
# protocol buffers generated headers
include/osmformat.pb.h
include/vector_tile.pb.h
src/config_schema.h

# downloaded data
coastline
Expand Down
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,15 @@ else()
set(THREAD_LIB pthread)
endif()

file(READ "${CMAKE_CURRENT_SOURCE_DIR}/resources/config-schema.json" TILEMAKER_CONFIG_SCHEMA_JSON)
configure_file(
cmake/config_schema.h.in
"${CMAKE_BINARY_DIR}/config_schema.h"
@ONLY)

file(GLOB tilemaker_src_files
src/attribute_store.cpp
src/config_validator.cpp
src/coordinates.cpp
src/coordinates_geom.cpp
src/external/streamvbyte_decode.c
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ COPY cmake ./cmake
COPY src ./src
COPY include ./include
COPY server ./server
COPY resources/config-schema.json ./resources/config-schema.json

RUN mkdir build && \
cd build && \
Expand Down
17 changes: 16 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ all: tilemaker server

tilemaker: \
src/attribute_store.o \
src/config_validator.o \
src/coordinates_geom.o \
src/coordinates.o \
src/external/streamvbyte_decode.o \
Expand Down Expand Up @@ -140,6 +141,7 @@ tilemaker: \
test: \
test_append_vector \
test_attribute_store \
test_config_validator \
test_deque_map \
test_helpers \
test_options_parser \
Expand All @@ -164,6 +166,19 @@ test_attribute_store: \
test/attribute_store.test.o
$(CXX) $(CXXFLAGS) -o test.attribute_store $^ $(INC) $(LIB) $(LDFLAGS) && ./test.attribute_store

test_config_validator: \
src/config_validator.o \
test/config_validator.test.o
$(CXX) $(CXXFLAGS) -o test.config_validator $^ $(INC) $(LIB) $(LDFLAGS) && ./test.config_validator

src/config_schema.h: resources/config-schema.json
printf '#ifndef _CONFIG_SCHEMA_H\n#define _CONFIG_SCHEMA_H\n\nstatic const char* CONFIG_SCHEMA = R"TMCONFIGSCHEMA(\n' > $@
cat $< >> $@
printf '\n)TMCONFIGSCHEMA";\n\n#endif //_CONFIG_SCHEMA_H\n' >> $@

src/config_validator.o: src/config_validator.cpp include/config_validator.h src/config_schema.h
$(CXX) $(CXXFLAGS) -o $@ -c $< $(INC)

test_deque_map: \
test/deque_map.test.o
$(CXX) $(CXXFLAGS) -o test.deque_map $^ $(INC) $(LIB) $(LDFLAGS) && ./test.deque_map
Expand Down Expand Up @@ -268,6 +283,6 @@ install:
@install docs/man/tilemaker.1 ${DESTDIR}${MANPREFIX}/man1/ || true

clean:
rm -f tilemaker tilemaker-server src/*.o src/external/*.o src/external/libdeflate/lib/*.o src/external/libdeflate/lib/*/*.o include/*.o include/*.pb.h server/*.o test/*.o
rm -f tilemaker tilemaker-server src/*.o src/external/*.o src/external/libdeflate/lib/*.o src/external/libdeflate/lib/*/*.o include/*.o include/*.pb.h server/*.o test/*.o src/config_schema.h

.PHONY: install
8 changes: 8 additions & 0 deletions cmake/config_schema.h.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef _CONFIG_SCHEMA_H
#define _CONFIG_SCHEMA_H

static const char* CONFIG_SCHEMA = R"TMCONFIGSCHEMA(
@TILEMAKER_CONFIG_SCHEMA_JSON@
)TMCONFIGSCHEMA";

#endif //_CONFIG_SCHEMA_H
10 changes: 10 additions & 0 deletions include/config_validator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef _CONFIG_VALIDATOR_H
#define _CONFIG_VALIDATOR_H

#include <string>

#include "rapidjson/document.h"

bool validateConfigJson(const rapidjson::Document &jsonConfig, std::string &error);

#endif //_CONFIG_VALIDATOR_H
90 changes: 90 additions & 0 deletions resources/config-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{
"type": "object",
"required": ["settings", "layers"],
"properties": {
"settings": {
"type": "object",
"required": ["basezoom", "minzoom", "maxzoom", "include_ids", "compress", "name", "version", "description"],
"properties": {
"basezoom": { "type": "integer", "minimum": 0 },
"minzoom": { "type": "integer", "minimum": 0 },
"maxzoom": { "type": "integer", "minimum": 0 },
"include_ids": { "type": "boolean" },
"compress": { "type": "string", "enum": ["gzip", "deflate", "none"] },
"name": { "type": "string" },
"version": { "type": "string" },
"description": { "type": "string" },
"high_resolution": { "type": "boolean" },
"combine_below": { "type": "integer", "minimum": 0 },
"mvt_version": { "type": "integer", "minimum": 1 },
"bounding_box": {
"type": "array",
"minItems": 4,
"maxItems": 4,
"items": { "type": "number" }
},
"default_view": {
"type": "array",
"minItems": 3,
"maxItems": 3,
"items": [
{ "type": "number" },
{ "type": "number" },
{ "type": "integer" }
]
},
"metadata": {
"type": "object",
"properties": {
"attribution": { "type": "string" }
},
"additionalProperties": true
},
"filemetadata": { "type": "object" }
},
"additionalProperties": true
},
"layers": {
"type": "object",
"additionalProperties": {
"type": "object",
"required": ["minzoom", "maxzoom"],
"properties": {
"minzoom": { "type": "integer", "minimum": 0 },
"maxzoom": { "type": "integer", "minimum": 0 },
"write_to": { "type": "string" },
"simplify_below": { "type": "integer", "minimum": 0 },
"simplify_level": { "type": "number" },
"simplify_length": { "type": "number" },
"simplify_ratio": { "type": "number" },
"filter_below": { "type": "integer", "minimum": 0 },
"filter_area": { "type": "number" },
"feature_limit": { "type": "integer", "minimum": 0 },
"feature_limit_below": { "type": "integer", "minimum": 0 },
"combine_points": { "type": "boolean" },
"combine_lines_below": { "type": "integer", "minimum": 0 },
"combine_polygons_below": { "type": "integer", "minimum": 0 },
"z_order_ascending": { "type": "boolean" },
"simplify_algorithm": { "type": "string" },
"source": { "type": "string" },
"source_columns": {
"oneOf": [
{
"type": "boolean",
"enum": [true]
},
{
"type": "array",
"items": { "type": "string" }
}
]
},
"index": { "type": "boolean" },
"index_column": { "type": "string" }
},
"additionalProperties": true
}
}
},
"additionalProperties": true
}
145 changes: 145 additions & 0 deletions src/config_validator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#include "config_validator.h"

#include <config_schema.h>

#include <cstring>
#include <vector>

#include "rapidjson/pointer.h"
#include "rapidjson/schema.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"

namespace {
std::string pointerString(const rapidjson::Pointer &pointer) {
rapidjson::StringBuffer buffer;
pointer.StringifyUriFragment(buffer);
return buffer.GetString();
}

std::string valueToString(const rapidjson::Value &value) {
if (value.IsString()) return std::string("\"") + value.GetString() + "\"";

rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
value.Accept(writer);
return buffer.GetString();
}

std::string joinValues(const std::vector<std::string> &values) {
std::string joined;
for (std::size_t i = 0; i < values.size(); i++) {
if (i > 0) joined += ", ";
joined += values[i];
}
return joined;
}

std::string valueTypeName(const rapidjson::Value &value) {
if (value.IsNull()) return "null";
if (value.IsBool()) return "boolean";
if (value.IsObject()) return "object";
if (value.IsArray()) return "array";
if (value.IsString()) return "string";
if (value.IsNumber()) return "number";
return "unknown";
}

std::string requiredError(const rapidjson::Document &schemaJson,
const rapidjson::Document &jsonConfig,
const rapidjson::Pointer &schemaPointer,
const rapidjson::Pointer &documentPointer,
const std::string &documentPointerString) {
const rapidjson::Value* schemaNode = schemaPointer.Get(schemaJson);
const rapidjson::Value* documentNode = documentPointer.Get(jsonConfig);
if (!schemaNode || !schemaNode->IsObject() || !schemaNode->HasMember("required") ||
!(*schemaNode)["required"].IsArray() || !documentNode || !documentNode->IsObject()) {
return "";
}

std::vector<std::string> missing;
for (rapidjson::Value::ConstValueIterator it = (*schemaNode)["required"].Begin(); it != (*schemaNode)["required"].End(); ++it) {
if (it->IsString() && !documentNode->HasMember(it->GetString())) {
missing.push_back(std::string("\"") + it->GetString() + "\"");
}
}
if (missing.empty()) return "";

return "missing required " + std::string(missing.size() == 1 ? "field " : "fields ") +
joinValues(missing) + " at " + documentPointerString;
}

std::string typeError(const rapidjson::Document &schemaJson,
const rapidjson::Document &jsonConfig,
const rapidjson::Pointer &schemaPointer,
const rapidjson::Pointer &documentPointer,
const std::string &documentPointerString) {
const rapidjson::Value* schemaNode = schemaPointer.Get(schemaJson);
const rapidjson::Value* documentNode = documentPointer.Get(jsonConfig);
if (!schemaNode || !schemaNode->IsObject() || !schemaNode->HasMember("type") || !documentNode) {
return "";
}

return "invalid type at " + documentPointerString + ": expected " +
valueToString((*schemaNode)["type"]) + ", got " + valueTypeName(*documentNode);
}

std::string enumError(const rapidjson::Document &schemaJson,
const rapidjson::Document &jsonConfig,
const rapidjson::Pointer &schemaPointer,
const rapidjson::Pointer &documentPointer,
const std::string &documentPointerString) {
const rapidjson::Value* schemaNode = schemaPointer.Get(schemaJson);
const rapidjson::Value* documentNode = documentPointer.Get(jsonConfig);
if (!schemaNode || !schemaNode->IsObject() || !schemaNode->HasMember("enum") ||
!(*schemaNode)["enum"].IsArray() || !documentNode) {
return "";
}

std::vector<std::string> allowed;
for (rapidjson::Value::ConstValueIterator it = (*schemaNode)["enum"].Begin(); it != (*schemaNode)["enum"].End(); ++it) {
allowed.push_back(valueToString(*it));
}

return "invalid value at " + documentPointerString + ": expected one of " +
joinValues(allowed) + ", got " + valueToString(*documentNode);
}
} // namespace

bool validateConfigJson(const rapidjson::Document &jsonConfig, std::string &error) {
rapidjson::Document schemaJson;
schemaJson.Parse(CONFIG_SCHEMA);
if (schemaJson.HasParseError()) {
error = "Internal config schema is invalid.";
return false;
}

rapidjson::SchemaDocument schema(schemaJson);
rapidjson::SchemaValidator validator(schema);
if (jsonConfig.Accept(validator)) {
return true;
}

std::string documentPointer = pointerString(validator.GetInvalidDocumentPointer());
if (documentPointer.empty()) documentPointer = "#";
std::string schemaPointer = pointerString(validator.GetInvalidSchemaPointer());
if (schemaPointer.empty()) schemaPointer = "#";

const char* keyword = validator.GetInvalidSchemaKeyword();
if (std::strcmp(keyword, "required") == 0) {
error = requiredError(schemaJson, jsonConfig, validator.GetInvalidSchemaPointer(),
validator.GetInvalidDocumentPointer(), documentPointer);
} else if (std::strcmp(keyword, "type") == 0) {
error = typeError(schemaJson, jsonConfig, validator.GetInvalidSchemaPointer(),
validator.GetInvalidDocumentPointer(), documentPointer);
} else if (std::strcmp(keyword, "enum") == 0) {
error = enumError(schemaJson, jsonConfig, validator.GetInvalidSchemaPointer(),
validator.GetInvalidDocumentPointer(), documentPointer);
}

if (error.empty()) {
error = "schema validation failed at " + documentPointer + ": " +
keyword + " (" + schemaPointer + ")";
}
return false;
}
14 changes: 13 additions & 1 deletion src/tilemaker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "rapidjson/stringbuffer.h"
#include "rapidjson/filereadstream.h"
#include "rapidjson/filewritestream.h"
#include "rapidjson/error/en.h"

#ifndef _MSC_VER
#include <sys/resource.h>
Expand All @@ -38,6 +39,7 @@
#include "way_stores.h"

// Tilemaker code
#include "config_validator.h"
#include "helpers.h"
#include "coordinates.h"
#include "coordinates_geom.h"
Expand Down Expand Up @@ -173,8 +175,18 @@ int main(const int argc, const char* argv[]) {
char readBuffer[65536];
rapidjson::FileReadStream is(fp, readBuffer, sizeof(readBuffer));
jsonConfig.ParseStream(is);
if (jsonConfig.HasParseError()) { cerr << "Invalid JSON file." << endl; return -1; }
fclose(fp);
if (jsonConfig.HasParseError()) {
cerr << "Invalid JSON file: " << rapidjson::GetParseError_En(jsonConfig.GetParseError())
<< " at offset " << jsonConfig.GetErrorOffset() << "." << endl;
return -1;
}

string jsonError;
if (!validateConfigJson(jsonConfig, jsonError)) {
cerr << "Invalid JSON file: " << jsonError << "." << endl;
return -1;
}

config.readConfig(jsonConfig, hasClippingBox, clippingBox);
} catch (...) {
Expand Down
Loading