source/JsonValidation.cpp (253 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include <sstream> #include <boost/algorithm/string/trim.hpp> #include <boost/filesystem/fstream.hpp> #include <fmt/format.h> #include <mariana-trench/Assert.h> #include <mariana-trench/JsonValidation.h> #include <mariana-trench/Log.h> #include <mariana-trench/Redex.h> namespace marianatrench { namespace { std::string invalid_argument_message( const Json::Value& value, const std::optional<std::string>& field, const std::string& expected) { auto field_information = field ? fmt::format(" for field `{}`", *field) : ""; return fmt::format( "Error validating `{}`. Expected {}{}.", boost::algorithm::trim_copy(JsonValidation::to_styled_string(value)), expected, field_information); } } // namespace JsonValidationError::JsonValidationError( const Json::Value& value, const std::optional<std::string>& field, const std::string& expected) : std::invalid_argument(invalid_argument_message(value, field, expected)) {} void JsonValidation::validate_object(const Json::Value& value) { if (value.isNull() || !value.isObject()) { throw JsonValidationError( value, /* field */ std::nullopt, /* expected */ "non-null object"); } } const Json::Value& JsonValidation::object( const Json::Value& value, const std::string& field) { validate_object(value); const auto& attribute = value[field]; if (attribute.isNull() || !attribute.isObject()) { throw JsonValidationError(value, field, /* expected */ "non-null object"); } return attribute; } std::string JsonValidation::string(const Json::Value& value) { if (value.isNull() || !value.isString()) { throw JsonValidationError( value, /* field */ std::nullopt, /* expected */ "string"); } return value.asString(); } std::string JsonValidation::string( const Json::Value& value, const std::string& field) { validate_object(value); const auto& string = value[field]; if (string.isNull() || !string.isString()) { throw JsonValidationError(value, field, /* expected */ "string"); } return string.asString(); } int JsonValidation::integer(const Json::Value& value) { if (value.isNull() || !value.isInt()) { throw JsonValidationError( value, /* field */ std::nullopt, /* expected */ "integer"); } return value.asInt(); } int JsonValidation::integer( const Json::Value& value, const std::string& field) { validate_object(value); const auto& integer = value[field]; if (integer.isNull() || !integer.isInt()) { throw JsonValidationError(value, field, /* expected */ "integer"); } return integer.asInt(); } bool JsonValidation::boolean(const Json::Value& value) { if (value.isNull() || !value.isBool()) { throw JsonValidationError( value, /* field */ std::nullopt, /* expected */ "boolean"); } return value.asBool(); } bool JsonValidation::boolean( const Json::Value& value, const std::string& field) { validate_object(value); const auto& boolean = value[field]; if (boolean.isNull() || !boolean.isBool()) { throw JsonValidationError(value, field, /* expected */ "boolean"); } return boolean.asBool(); } const Json::Value& JsonValidation::null_or_array(const Json::Value& value) { if (!value.isNull() && !value.isArray()) { throw JsonValidationError( value, /* field */ std::nullopt, /* expected */ "null or array"); } return value; } const Json::Value& JsonValidation::null_or_array( const Json::Value& value, const std::string& field) { validate_object(value); const auto& null_or_array = value[field]; if (!null_or_array.isNull() && !null_or_array.isArray()) { throw JsonValidationError(value, field, /* expected */ "null or array"); } return null_or_array; } const Json::Value& JsonValidation::nonempty_array( const Json::Value& value, const std::string& field) { validate_object(value); const auto& nonempty_array = value[field]; if (nonempty_array.isNull() || !nonempty_array.isArray() || nonempty_array.empty()) { throw JsonValidationError(value, field, /* expected */ "non-empty array"); } return nonempty_array; } const Json::Value& JsonValidation::object_or_string( const Json::Value& value, const std::string& field) { validate_object(value); const auto& attribute = value[field]; if (attribute.isNull() || (!attribute.isObject() && !attribute.isString())) { throw JsonValidationError(value, field, /* expected */ "object or string"); } return attribute; } DexType* JsonValidation::dex_type(const Json::Value& value) { auto type_name = JsonValidation::string(value); auto* type = redex::get_type(type_name); if (!type) { throw JsonValidationError( value, /* field */ std::nullopt, /* expected */ "existing type name"); } return type; } DexType* JsonValidation::dex_type( const Json::Value& value, const std::string& field) { auto type_name = JsonValidation::string(value, field); auto* type = redex::get_type(type_name); if (!type) { throw JsonValidationError( value, field, /* expected */ "existing type name"); } return type; } DexFieldRef* JsonValidation::dex_field(const Json::Value& value) { auto field_name = JsonValidation::string(value); auto* dex_field = redex::get_field(field_name); if (!dex_field) { throw JsonValidationError( value, /* field */ std::nullopt, /* expected */ "existing field name"); } return dex_field; } DexFieldRef* JsonValidation::dex_field( const Json::Value& value, const std::string& field) { auto field_name = JsonValidation::string(value, field); auto* dex_field = redex::get_field(field_name); if (!dex_field) { throw JsonValidationError( value, field, /* expected */ "existing field name"); } return dex_field; } Json::Value JsonValidation::parse_json(std::string string) { std::istringstream stream(std::move(string)); static const auto reader = Json::CharReaderBuilder(); std::string errors; Json::Value json; if (!Json::parseFromStream(reader, stream, &json, &errors)) { throw std::invalid_argument(fmt::format("Invalid json: {}", errors)); } return json; } Json::Value JsonValidation::parse_json_file( const boost::filesystem::path& path) { boost::filesystem::ifstream file; file.exceptions(std::ifstream::failbit | std::ifstream::badbit); try { file.open(path, std::ios_base::binary); } catch (const std::ifstream::failure&) { ERROR(1, "Could not open json file: `{}`.", path.string()); throw; } static const auto reader = Json::CharReaderBuilder(); std::string errors; Json::Value json; if (!Json::parseFromStream(reader, file, &json, &errors)) { throw std::invalid_argument( fmt::format("File `{}` is not valid json: {}", path.string(), errors)); } return json; } Json::Value JsonValidation::parse_json_file(const std::string& path) { return parse_json_file(boost::filesystem::path(path)); } namespace { Json::StreamWriterBuilder compact_writer_builder() { Json::StreamWriterBuilder writer; writer["indentation"] = ""; return writer; } Json::StreamWriterBuilder styled_writer_builder() { Json::StreamWriterBuilder writer; writer["indentation"] = " "; return writer; } } // namespace std::unique_ptr<Json::StreamWriter> JsonValidation::compact_writer() { static const auto writer_builder = compact_writer_builder(); return std::unique_ptr<Json::StreamWriter>(writer_builder.newStreamWriter()); } std::unique_ptr<Json::StreamWriter> JsonValidation::styled_writer() { static const auto writer_builder = styled_writer_builder(); return std::unique_ptr<Json::StreamWriter>(writer_builder.newStreamWriter()); } void JsonValidation::write_json_file( const boost::filesystem::path& path, const Json::Value& value) { boost::filesystem::ofstream file; file.exceptions(std::ofstream::failbit | std::ofstream::badbit); file.open(path, std::ios_base::binary); compact_writer()->write(value, &file); file << "\n"; file.close(); } std::string JsonValidation::to_styled_string(const Json::Value& value) { std::ostringstream string; styled_writer()->write(value, &string); return string.str(); } void JsonValidation::update_object( Json::Value& left, const Json::Value& right) { mt_assert(left.isObject()); mt_assert(right.isObject()); for (const auto& key : right.getMemberNames()) { left[key] = right[key]; } } } // namespace marianatrench