diff --git a/CMakeLists.txt b/CMakeLists.txt index 1718e1f36..1c946a28c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -379,6 +379,7 @@ set(TDLIB_SOURCE td/telegram/HashtagHints.cpp td/telegram/InlineQueriesManager.cpp td/telegram/InputMessageText.cpp + td/telegram/JsonValue.cpp td/telegram/LanguagePackManager.cpp td/telegram/Location.cpp td/telegram/Logging.cpp @@ -502,6 +503,7 @@ set(TDLIB_SOURCE td/telegram/HashtagHints.h td/telegram/InlineQueriesManager.h td/telegram/InputMessageText.h + td/telegram/JsonValue.h td/telegram/LanguagePackManager.h td/telegram/Location.h td/telegram/logevent/LogEvent.h diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 6cfe1e6f8..b584039fc 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -1825,19 +1825,43 @@ notificationGroup id:int32 chat_id:int53 total_count:int32 notifications:vector< //@class OptionValue @description Represents the value of an option -//@description Boolean option @value The value of the option +//@description Represents a boolean option @value The value of the option optionValueBoolean value:Bool = OptionValue; -//@description An unknown option or an option which has a default value +//@description Represents an unknown option or an option which has a default value optionValueEmpty = OptionValue; -//@description An integer option @value The value of the option +//@description Represents an integer option @value The value of the option optionValueInteger value:int32 = OptionValue; -//@description A string option @value The value of the option +//@description Represents a string option @value The value of the option optionValueString value:string = OptionValue; +//@description Represents one member of a JSON object @key Member's key @value Member's value +jsonObjectMember key:string value:JsonValue = JsonObjectMember; + +//@class JsonValue @description Represents a JSON value + +//@description Represents a null JSON value +jsonValueNull = JsonValue; + +//@description Represents a boolean JSON value @value The value +jsonValueBoolean value:Bool = JsonValue; + +//@description Represents a numeric JSON value @value The value +jsonValueNumber value:double = JsonValue; + +//@description Represents a string JSON value @value The value +jsonValueString value:string = JsonValue; + +//@description Represents a JSON array @values The list of array elements +jsonValueArray values:vector = JsonValue; + +//@description Represents a JSON object @members The list of object members +jsonValueObject members:vector = JsonValue; + + //@class UserPrivacySettingRule @description Represents a single rule for managing privacy settings //@description A rule to allow all users to do something @@ -2728,6 +2752,12 @@ cleanFileName file_name:string = Text; //@language_pack_database_path Path to the language pack database in which strings are stored @localization_target Localization target to which the language pack belongs @language_pack_id Language pack identifier @key Language pack key of the string to be returned getLanguagePackString language_pack_database_path:string localization_target:string language_pack_id:string key:string = LanguagePackStringValue; +//@description Converts a JSON-serialized string to corresponding JsonValue object. This is an offline method. Can be called before authorization. Can be called synchronously @json The JSON-serialized string +getJsonValue json:string = JsonValue; + +//@description Converts a JsonValue object to corresponding JSON-serialized string. This is an offline method. Can be called before authorization. Can be called synchronously @json_value The JsonValue object +getJsonString json_value:JsonValue = Text; + //@description Sends an inline query to a bot and returns its results. Returns an error with code 502 if the bot fails to answer the query before the query timeout expires @bot_user_id The identifier of the target bot //@chat_id Identifier of the chat, where the query was sent @user_location Location of the user, only if needed @query Text of the query @offset Offset of the first entry to return diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index 769d67d9f..33eaa4fe9 100644 Binary files a/td/generate/scheme/td_api.tlo and b/td/generate/scheme/td_api.tlo differ diff --git a/td/generate/tl_json_converter.cpp b/td/generate/tl_json_converter.cpp index aaf85ffae..ee2c064ff 100644 --- a/td/generate/tl_json_converter.cpp +++ b/td/generate/tl_json_converter.cpp @@ -88,8 +88,8 @@ void gen_from_json_constructor(StringBuilder &sb, const T *constructor, bool is_ for (auto &arg : constructor->args) { sb << " {\n"; sb << " TRY_RESULT(value, get_json_object_field(from, \"" << tl::simple::gen_cpp_name(arg.name) - << "\", JsonValue::Type::Null, true));\n"; - sb << " if (value.type() != JsonValue::Type::Null) {\n"; + << "\", td::JsonValue::Type::Null, true));\n"; + sb << " if (value.type() != td::JsonValue::Type::Null) {\n"; if (arg.type->type == tl::simple::Type::Bytes) { sb << " TRY_STATUS(from_json_bytes(to." << tl::simple::gen_cpp_field_name(arg.name) << ", value));\n"; } else { diff --git a/td/telegram/JsonValue.cpp b/td/telegram/JsonValue.cpp new file mode 100644 index 000000000..226ffc829 --- /dev/null +++ b/td/telegram/JsonValue.cpp @@ -0,0 +1,103 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/telegram/JsonValue.h" + +#include "td/utils/JsonBuilder.h" +#include "td/utils/misc.h" + +namespace td { + +static td_api::object_ptr get_json_value_object(const JsonValue &json_value); + +static td_api::object_ptr get_json_value_member_object( + const std::pair &json_value_member) { + return td_api::make_object(json_value_member.first.str(), + get_json_value_object(json_value_member.second)); +} + +static td_api::object_ptr get_json_value_object(const JsonValue &json_value) { + switch (json_value.type()) { + case JsonValue::Type::Null: + return td_api::make_object(); + case JsonValue::Type::Boolean: + return td_api::make_object(json_value.get_boolean()); + case JsonValue::Type::Number: + return td_api::make_object(to_double(json_value.get_number())); + case JsonValue::Type::String: + return td_api::make_object(json_value.get_string().str()); + case JsonValue::Type::Array: + return td_api::make_object(transform(json_value.get_array(), get_json_value_object)); + case JsonValue::Type::Object: + return td_api::make_object( + transform(json_value.get_object(), get_json_value_member_object)); + default: + UNREACHABLE(); + return nullptr; + } +} + +Result> get_json_value(MutableSlice json) { + TRY_RESULT(json_value, json_decode(json)); + return get_json_value_object(json_value); +} + +namespace { + +class JsonableJsonValue : public Jsonable { + public: + explicit JsonableJsonValue(const td_api::JsonValue *json_value) : json_value_(json_value) { + } + void store(JsonValueScope *scope) const { + if (json_value_ == nullptr) { + *scope << JsonNull(); + return; + } + switch (json_value_->get_id()) { + case td_api::jsonValueNull::ID: + *scope << JsonNull(); + break; + case td_api::jsonValueBoolean::ID: + *scope << static_cast(json_value_)->value_; + break; + case td_api::jsonValueNumber::ID: + *scope << static_cast(json_value_)->value_; + break; + case td_api::jsonValueString::ID: + *scope << static_cast(json_value_)->value_; + break; + case td_api::jsonValueArray::ID: { + auto array = scope->enter_array(); + for (auto &value : static_cast(json_value_)->values_) { + array << JsonableJsonValue(value.get()); + } + break; + } + case td_api::jsonValueObject::ID: { + auto object = scope->enter_object(); + for (auto &member : static_cast(json_value_)->members_) { + if (member != nullptr) { + object << ctie(member->key_, JsonableJsonValue(member->value_.get())); + } + } + break; + } + default: + UNREACHABLE(); + } + } + + private: + const td_api::JsonValue *json_value_; +}; + +} // namespace + +string get_json_string(const td_api::JsonValue *json_value) { + return json_encode(JsonableJsonValue(json_value)); +} + +} // namespace td diff --git a/td/telegram/JsonValue.h b/td/telegram/JsonValue.h new file mode 100644 index 000000000..36099efc8 --- /dev/null +++ b/td/telegram/JsonValue.h @@ -0,0 +1,21 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/td_api.h" + +#include "td/utils/common.h" +#include "td/utils/Slice.h" +#include "td/utils/Status.h" + +namespace td { + +Result> get_json_value(MutableSlice json); + +string get_json_string(const td_api::JsonValue *json_value); + +} // namespace td diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 2e4d036d2..b389c2459 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -38,6 +38,7 @@ #include "td/telegram/Global.h" #include "td/telegram/HashtagHints.h" #include "td/telegram/InlineQueriesManager.h" +#include "td/telegram/JsonValue.h" #include "td/telegram/LanguagePackManager.h" #include "td/telegram/Logging.h" #include "td/telegram/MessageEntity.h" @@ -3119,6 +3120,8 @@ bool Td::is_synchronous_request(int32 id) { case td_api::getFileExtension::ID: case td_api::cleanFileName::ID: case td_api::getLanguagePackString::ID: + case td_api::getJsonValue::ID: + case td_api::getJsonString::ID: case td_api::setLogStream::ID: case td_api::getLogStream::ID: case td_api::setLogVerbosityLevel::ID: @@ -3322,6 +3325,8 @@ td_api::object_ptr Td::static_request(td_api::object_ptr Td::do_static_request(const td_api::getLangua request.language_pack_database_path_, request.localization_target_, request.language_pack_id_, request.key_); } +td_api::object_ptr Td::do_static_request(td_api::getJsonValue &request) { + if (!check_utf8(request.json_)) { + return make_error(400, "JSON has invalid encoding"); + } + auto result = get_json_value(request.json_); + if (result.is_error()) { + return make_error(400, result.error().message()); + } else { + return result.move_as_ok(); + } +} + +td_api::object_ptr Td::do_static_request(const td_api::getJsonString &request) { + return td_api::make_object(get_json_string(request.json_value_.get())); +} + td_api::object_ptr Td::do_static_request(td_api::setLogStream &request) { auto result = Logging::set_current_stream(std::move(request.log_stream_)); if (result.is_ok()) { diff --git a/td/telegram/Td.h b/td/telegram/Td.h index a3e8c24ae..4d0aa0eee 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -910,7 +910,7 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::getTextEntities &request); - void on_request(uint64 id, td_api::parseTextEntities &request); + void on_request(uint64 id, const td_api::parseTextEntities &request); void on_request(uint64 id, const td_api::getFileMimeType &request); @@ -920,6 +920,10 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::getLanguagePackString &request); + void on_request(uint64 id, const td_api::getJsonValue &request); + + void on_request(uint64 id, const td_api::getJsonString &request); + void on_request(uint64 id, const td_api::setLogStream &request); void on_request(uint64 id, const td_api::getLogStream &request); @@ -958,6 +962,8 @@ class Td final : public NetQueryCallback { static td_api::object_ptr do_static_request(const td_api::getFileExtension &request); static td_api::object_ptr do_static_request(const td_api::cleanFileName &request); static td_api::object_ptr do_static_request(const td_api::getLanguagePackString &request); + static td_api::object_ptr do_static_request(td_api::getJsonValue &request); + static td_api::object_ptr do_static_request(const td_api::getJsonString &request); static td_api::object_ptr do_static_request(td_api::setLogStream &request); static td_api::object_ptr do_static_request(const td_api::getLogStream &request); static td_api::object_ptr do_static_request(const td_api::setLogVerbosityLevel &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 09912778c..fadbee26f 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -2382,11 +2382,40 @@ class CliClient final : public Actor { } else if (op == "ptehs") { execute(make_tl_object(args, make_tl_object())); } else if (op == "gfmt") { - send_request(make_tl_object(trim(args))); + execute(make_tl_object(trim(args))); } else if (op == "gfe") { - send_request(make_tl_object(trim(args))); + execute(make_tl_object(trim(args))); } else if (op == "cfn") { - send_request(make_tl_object(args)); + execute(make_tl_object(args)); + } else if (op == "gjv") { + execute(td_api::make_object(args)); + } else if (op == "gjs") { + execute(td_api::make_object()); + execute(td_api::make_object(td_api::make_object())); + execute(td_api::make_object(td_api::make_object(true))); + execute(td_api::make_object(td_api::make_object(123456789123.0))); + execute( + td_api::make_object(td_api::make_object(string("aba\x00" + "caba", + 8)))); + + auto inner_array = td_api::make_object(); + inner_array->values_.push_back(td_api::make_object(false)); + auto array = td_api::make_object(); + array->values_.push_back(nullptr); + array->values_.push_back(std::move(inner_array)); + array->values_.push_back(td_api::make_object()); + array->values_.push_back(td_api::make_object(-1)); + execute(td_api::make_object(std::move(array))); + + auto object = td_api::make_object(); + object->members_.push_back( + td_api::make_object("", td_api::make_object("test"))); + object->members_.push_back(td_api::make_object("a", nullptr)); + object->members_.push_back(nullptr); + object->members_.push_back( + td_api::make_object("a", td_api::make_object())); + execute(td_api::make_object(std::move(object))); } else { op_not_found_count++; } diff --git a/tdutils/td/utils/JsonBuilder.cpp b/tdutils/td/utils/JsonBuilder.cpp index da11b9693..d7b93a043 100644 --- a/tdutils/td/utils/JsonBuilder.cpp +++ b/tdutils/td/utils/JsonBuilder.cpp @@ -344,17 +344,17 @@ Result do_json_decode(Parser &parser, int32 max_depth) { if (parser.skip_start_with("false")) { return JsonValue::create_boolean(false); } - return Status::Error("Starts with 'f' -- false expected"); + return Status::Error("Token starts with 'f' -- false expected"); case 't': if (parser.skip_start_with("true")) { return JsonValue::create_boolean(true); } - return Status::Error("Starts with 't' -- true expected"); + return Status::Error("Token starts with 't' -- true expected"); case 'n': if (parser.skip_start_with("null")) { return JsonValue(); } - return Status::Error("Starts with 'n' -- null expected"); + return Status::Error("Token starts with 'n' -- null expected"); case '"': { TRY_RESULT(slice, json_string_decode(parser)); return JsonValue::create_string(slice); @@ -368,7 +368,7 @@ Result do_json_decode(Parser &parser, int32 max_depth) { } while (true) { if (parser.empty()) { - return Status::Error("Unexpected end"); + return Status::Error("Unexpected string end"); } TRY_RESULT(value, do_json_decode(parser, max_depth - 1)); res.emplace_back(std::move(value)); @@ -381,7 +381,10 @@ Result do_json_decode(Parser &parser, int32 max_depth) { parser.skip_whitespaces(); continue; } - return Status::Error("Unexpected symbol"); + if (parser.empty()) { + return Status::Error("Unexpected string end"); + } + return Status::Error("Unexpected symbol while parsing JSON Array"); } return JsonValue::create_array(std::move(res)); } @@ -394,7 +397,7 @@ Result do_json_decode(Parser &parser, int32 max_depth) { } while (true) { if (parser.empty()) { - return Status::Error("Unexpected end"); + return Status::Error("Unexpected string end"); } TRY_RESULT(key, json_string_decode(parser)); parser.skip_whitespaces(); @@ -412,7 +415,10 @@ Result do_json_decode(Parser &parser, int32 max_depth) { parser.skip_whitespaces(); continue; } - return Status::Error("Unexpected symbol"); + if (parser.empty()) { + return Status::Error("Unexpected string end"); + } + return Status::Error("Unexpected symbol while parsing JSON Object"); } return JsonValue::make_object(std::move(res)); } @@ -434,7 +440,7 @@ Result do_json_decode(Parser &parser, int32 max_depth) { return JsonValue::create_number(num); } case 0: - return Status::Error("Unexpected end"); + return Status::Error("Unexpected string end"); default: { char next = parser.peek_char(); if (0 < next && next < 127) { diff --git a/tdutils/td/utils/JsonBuilder.h b/tdutils/td/utils/JsonBuilder.h index 35fff16c2..b919e97ce 100644 --- a/tdutils/td/utils/JsonBuilder.h +++ b/tdutils/td/utils/JsonBuilder.h @@ -723,8 +723,8 @@ Status json_string_skip(Parser &parser) TD_WARN_UNUSED_RESULT; Result do_json_decode(Parser &parser, int32 max_depth) TD_WARN_UNUSED_RESULT; Status do_json_skip(Parser &parser, int32 max_depth) TD_WARN_UNUSED_RESULT; -inline Result json_decode(MutableSlice from) { - Parser parser(from); +inline Result json_decode(MutableSlice json) { + Parser parser(json); const int32 DEFAULT_MAX_DEPTH = 100; auto result = do_json_decode(parser, DEFAULT_MAX_DEPTH); if (result.is_ok()) {