Продолжаю делать пилить свой petproject. Что нового с прошлой публикацией:
-
запись сообщений в кафку;
-
создание/удаление топиков;
-
бинарные сборки для OSX и Windows.
Сейчас подошел к тому ради чего все это затевалось: декодирование protobuf без schema registry и кодогенерации.
![](https://habrastorage.org/getpro/habr/upload_files/f19/1c1/cce/f191c1cce335ff2831b2e5cf939c5bea.png)
Чем же неудобен protobuf?
Если опустить его бинарную природу, то он позволяет писать так
//order.proto syntax = "proto3"; package order; import "enums.proto"; import "google/protobuf/timestamp.proto"; message EventOrderEnrichment { string shipment_uuid = 1; string order_uuid = 2; string place_uuid = 3; enums.ShipmentStatus shipment_status = 4; string shipment_type = 5; uint64 weight = 6; enums.Location client_location = 7; enums.Location place_location = 8; uint64 assembly_time_min = 9; repeated string assembly = 10; repeated string delivery = 11; optional DispatchMeta dispatch_meta = 12; optional Settings settings = 13; } message Settings { uint64 max_order_assign_retry_count = 1; uint64 avg_parking_min_vehicle = 2; uint64 max_current_order_assign_queue = 3; fixed64 order_weight_threshold_to_assign_to_vehicle_gramms = 4; uint64 average_speed_for_straight_distance_to_client_min = 5; uint64 additional_factor_for_straight_distance_to_client_min = 6; uint64 order_transfer_time_from_assembly_to_delivery_min = 7; uint64 avg_to_place_min_external = 8; uint64 avg_to_place_min = 9; bool place_location_center = 10; uint64 search_radius_transport_pedestrian = 11; uint64 search_radius_transport_auto = 12; uint64 search_radius_transport_bike = 13; uint64 last_position_expire = 14; } message DispatchMeta { uint64 dispatch_count = 1; google.protobuf.Timestamp dispatch_start = 2; repeated string dispatch_ids = 3; string dispatch_id = 4; optional Tasks decline_task = 5; optional string decline_performer_uuid = 6; } enum Tasks { DELIVERY = 0; ASSEMBLY = 1; ASSEMBLY_AND_DELIVERY = 2; } message Location { double latitude = 1; double longitude = 2; }
// enums.proto syntax = "proto3"; package enums; enum ShipmentStatus { NEW = 0; POSTPONED = 1; AUTOMATIC_ROUTING = 2; MANUAL_ROUTING = 3; OFFERING = 4; OFFERED = 5; DECLINED = 6; CANCELED = 7; } message Location { double latitude = 1; double longitude = 2; }
Если вы хотите декодировать protobuf, то нужно указать:
-
где найти фай proto файлы(order.proto, enums.proto, timestamp.proto);
-
тип сообщения.
Реализация на C++
Какие классы из C++ API потребуются
![](https://habrastorage.org/getpro/habr/upload_files/f07/2c6/33b/f072c633b89d8c1dadfa12140d5341d7.png)
В Google не используют исключения, поэтому ошибки парсинга proto файла будем ловить наследником от MultiFileErrorCollector
class ProtobufErrorCollector final : public google::protobuf::compiler::MultiFileErrorCollector { public: void AddError(const std::string &filename, int line, int column, const std::string &message) override; void AddWarning(const std::string &filename, int line, int column, const std::string &message) override; QStringList errors() const; bool hasErrors() const; private: QStringList m_messages; }; /// void ProtobufErrorCollector::AddError(const std::string &filename, int line, int column, const std::string &message) { m_messages << QString("error file: %1, line: %2, column: %3 %4") .arg(QString::fromStdString(filename)) .arg(line) .arg(column) .arg(QString::fromStdString(message)); } void ProtobufErrorCollector::AddWarning(const std::string &filename, int line, int column, const std::string &message) { m_messages << QString("warning file: %1, line: %2, column: %3 %4") .arg(QString::fromStdString(filename)) .arg(line) .arg(column) .arg(QString::fromStdString(message)); } QStringList ProtobufErrorCollector::errors() const { return m_messages; } bool ProtobufErrorCollector::hasErrors() const { return !m_messages.isEmpty(); }
SourceTree это абстрактное дерево каталогов. Его наследник DiskSourceTree позволяет нам класть proto файлы в структуру каталогов
dir/ order.proto enums.proto google/ protobuf/ timestamp.proto
Каждый раз раскладывать proto файлы от Google неудобно. Поэтому было принято решение таскать эти файлы в самом бинарнике. Так появился ProtobufSourceTree
class ProtobufSourceTree final : public google::protobuf::compiler::DiskSourceTree { public: google::protobuf::io::ZeroCopyInputStream *Open(const std::string &filename) override; void Add(const QDir &dir); private: static google::protobuf::io::ZeroCopyInputStream *openFromResources(const std::string &filename); }; /// class ByteArrayInputStream final : public google::protobuf::io::ArrayInputStream { public: explicit ByteArrayInputStream(QByteArray &&data) : ArrayInputStream(data.data(), data.size()) , m_data(std::move(data)) {} private: QByteArray m_data; }; google::protobuf::io::ZeroCopyInputStream *ProtobufSourceTree::Open(const std::string &filename) { static QSet<std::string> inResources = {"google/protobuf/any.proto", "google/protobuf/api.proto", "google/protobuf/descriptor.proto", "google/protobuf/duration.proto", "google/protobuf/empty.proto", "google/protobuf/field_mask.proto", "google/protobuf/source_context.proto", "google/protobuf/struct.proto", "google/protobuf/timestamp.proto", "google/protobuf/type.proto", "google/protobuf/wrappers.proto"}; if (inResources.contains(filename)) { return openFromResources(filename); } return DiskSourceTree::Open(filename); } google::protobuf::io::ZeroCopyInputStream *ProtobufSourceTree::openFromResources( const std::string &filename) { using namespace google::protobuf::io; QString path(QString(":/%1").arg(QString::fromStdString(filename))); QFile file(path); if (!file.open(QIODevice::ReadOnly)) { spdlog::error("failed open file {} from resources error {}", path.toStdString(), file.errorString().toStdString()); return nullptr; } auto data = file.readAll(); file.close(); return new ByteArrayInputStream(std::move(data)); } void ProtobufSourceTree::Add(const QDir &dir) { QString path = dir.path(); #ifdef Q_OS_WINDOWS if (path.front() == '/') { path = path.remove(0, 1); } #endif MapPath("", path.toStdString()); }
ByteArrayInputStream откровенный костыль, за то не нужно реализовывать все методы ZeroCopyInputStream. Тут стоит обратить внимание на вызов DiskSourceTree::MapPath. Первый параметр пустой, что заставляет второй параметр интерпретировать как путь к каталогу
void DiskSourceTree::MapPath( const std::string & virtual_path, const std::string & disk_path)
![](https://habrastorage.org/getpro/habr/upload_files/c91/3f4/695/c913f4695105b9eda1911233ccc7561d.png)
Парсим proto файл и получаем список типов, который выведем в UI
using namespace google::protobuf; using namespace google::protobuf::compiler; QFileInfo info(m_file.path()); ProtobufErrorCollector errors; ProtobufSourceTree sources; sources.Add(info.dir()); SourceTreeDescriptorDatabase database(&sources, nullptr); database.RecordErrorsTo(&errors); DescriptorPool pool(&database, database.GetValidationErrorCollector()); pool.EnforceWeakDependencies(true); const auto *const fileDescriptor = pool.FindFileByName(info.fileName().toStdString()); // обработка ошибок beginResetModel(); m_messages.clear(); for (int i = 0; i < fileDescriptor->message_type_count(); i++) { m_messages << QString::fromStdString(fileDescriptor->message_type(i)->name()); } endResetModel();
Собираем Message. Обратите внимание, что имя включает в себя имя пакета
const auto package = fileDescriptor->package(); const auto messageType = package + "." + message.toStdString(); const auto *const typeDescriptor = pool->FindMessageTypeByName(messageType); // обработка ошибок auto factory = std::make_unique<DynamicMessageFactory>(pool.get()); auto *dynamicMessage = factory->GetPrototype(typeDescriptor)->New();
Само преобразование
QByteArray ProtobufConverter::toJSON(QByteArray &&binary) { using namespace google::protobuf::util; m_message->Clear(); if (!m_message->ParseFromArray(binary.data(), binary.size())) { return errParse; } JsonPrintOptions opt; std::string json; MessageToJsonString(*m_message, &json, opt); return QByteArray(json.c_str(), json.size()); }
На этом все. Весь код доступен на GitHub, бинарные сборки на странице релизов
ссылка на оригинал статьи https://habr.com/ru/articles/589105/
Добавить комментарий