ПРИМЕЧАНИЕ. Этот сайт устарел. Сайт будет отключен после 31 января 2023 года, и трафик будет перенаправлен на новый сайт по адресу https://protobuf.dev . А пока обновления будут производиться только для protobuf.dev.

Сгенерированный дротиком код

Оптимизируйте свои подборки Сохраняйте и классифицируйте контент в соответствии со своими настройками.

На этой странице описывается, какой код Dart генерирует компилятор буфера протокола для любого заданного определения протокола. Любые различия между сгенерированным кодом proto2 и proto3 выделены — обратите внимание, что эти различия заключаются в сгенерированном коде, как описано в этом документе, а не в базовом API, который одинаков в обеих версиях. Вы должны прочитать руководство по языку proto2 и/или руководство по языку proto3, прежде чем читать этот документ.

Вызов компилятора

Компилятору буфера протокола требуется подключаемый модуль для генерации кода Dart . Установка его в соответствии с инструкциями предоставляет protoc-gen-dart , который protoc использует при вызове с флагом командной строки --dart_out . Флаг --dart_out сообщает компилятору, куда записывать исходные файлы Dart. Для ввода файла .proto компилятор создает среди прочего файл .pb.dart .

Имя файла .pb.dart вычисляется путем взятия имени файла .proto и внесения двух изменений:

  • Расширение ( .proto ) заменяется на .pb.dart . Например, файл с именем foo.proto приводит к выходному файлу с именем foo.pb.dart .
  • Прото-путь (указанный флагом командной строки --proto_path или -I ) заменяется выходным путем (указанным флагом --dart_out ).

Например, когда вы вызываете компилятор следующим образом:

protoc --proto_path=src --dart_out=build/gen src/foo.proto src/bar/baz.proto

компилятор прочитает файлы src/foo.proto и src/bar/baz.proto . Он производит: build/gen/foo.pb.dart и build/gen/bar/baz.pb.dart . Компилятор автоматически создает каталог build/gen/bar , если это необходимо, но не создает build или build/gen ; они должны уже существовать.

Сообщения

Учитывая простое объявление сообщения:

message Foo {}

Компилятор буфера протокола генерирует класс с именем Foo , который расширяет класс GeneratedMessage .

Класс GeneratedMessage определяет методы, позволяющие проверять, обрабатывать, читать или записывать все сообщение целиком. В дополнение к этим методам класс Foo определяет следующие методы и конструкторы:

  • Foo() : конструктор по умолчанию. Создает экземпляр, в котором все одиночные поля не заданы, а повторяющиеся поля пусты.
  • Foo.fromBuffer(...) : создает Foo из сериализованных данных буфера протокола, представляющих сообщение.
  • Foo.fromJson(...) : создает Foo из строки JSON, кодирующей сообщение.
  • Foo clone() : создает глубокое клонирование полей в сообщении.
  • Foo copyWith(void Function(Foo) updates) : создает доступную для записи копию этого сообщения, применяет к нему updates и помечает копию как доступную только для чтения перед ее возвратом.
  • static Foo create() : фабричная функция для создания одного Foo .
  • static PbList<Foo> createRepeated() : фабричная функция для создания списка, реализующего изменяемое повторяющееся поле элементов Foo .
  • static Foo getDefault() : возвращает одноэлементный экземпляр Foo , который идентичен вновь созданному экземпляру Foo (поэтому все единичные поля не установлены, а все повторяющиеся поля пусты).

Вложенные типы

Сообщение может быть объявлено внутри другого сообщения. Например:

message Foo {
  message Bar {
  }
}

В этом случае компилятор генерирует два класса: Foo и Foo_Bar .

Поля

В дополнение к методам, описанным в предыдущем разделе, компилятор буфера протокола создает методы доступа для каждого поля, определенного в сообщении в файле .proto .

Обратите внимание, что сгенерированные имена всегда используют верблюжий регистр, даже если имя поля в файле .proto использует нижний регистр с подчеркиванием ( как и должно быть ). Преобразование регистра работает следующим образом:

  1. Для каждого подчеркивания в имени подчеркивание удаляется, а следующая буква делается заглавной.
  2. Если к имени будет прикреплен префикс (например, «имеет»), первая буква будет заглавной. В противном случае это нижний регистр.

Таким образом, для поля foo_bar_baz геттер становится get fooBarBaz , а метод с префиксом has будет hasFooBarBaz .

Сингулярные примитивные поля (proto2)

Для любого из этих определений полей:

optional int32 foo = 1;
required int32 foo = 1;

Компилятор сгенерирует следующие методы доступа в классе сообщения:

  • int get foo : возвращает текущее значение поля. Если поле не задано, возвращает значение по умолчанию.
  • bool hasFoo() : возвращает true , если поле установлено.
  • set foo(int value) : устанавливает значение поля. После вызова hasFoo() вернет true , а get foo вернет value .
  • void clearFoo() : очищает значение поля. После вызова hasFoo() вернет false , а get foo вернет значение по умолчанию.

Для других простых типов полей соответствующий тип Dart выбирается согласно таблице типов скалярных значений . Для типов сообщения и перечисления тип значения заменяется классом сообщения или перечисления.

Сингулярные примитивные поля (proto3)

Для этого определения поля:

int32 foo = 1;

Компилятор сгенерирует следующие методы доступа в классе сообщения:

  • int get foo : возвращает текущее значение поля. Если поле не задано, возвращает значение по умолчанию.
  • set foo(int value) : устанавливает значение поля. После этого get foo вернет value .
  • void clearFoo() : очищает значение поля. После этого get foo вернет значение по умолчанию.

Отдельные поля сообщений

Учитывая тип сообщения:

message Bar {}
Для сообщения с полем Bar :
// proto2
message Baz {
  optional Bar bar = 1;
  // The generated code is the same result if required instead of optional.
}

// proto3
message Baz {
  Bar bar = 1;
}

Компилятор сгенерирует следующие методы доступа в классе сообщения:

  • Bar get bar : возвращает текущее значение поля. Если поле не задано, возвращает значение по умолчанию.
  • set bar(Bar value) : устанавливает значение поля. После этого hasBar() вернет true , а get bar вернет value .
  • bool hasBar() : возвращает true , если поле установлено.
  • void clearBar() : очищает значение поля. После этого hasBar() вернет false , а get bar вернет значение по умолчанию.
  • Bar ensureBar() : Устанавливает bar в пустой экземпляр, если hasBar() возвращает false , а затем возвращает значение bar . После этого hasBar() вернет true .

Повторяющиеся поля

Для этого определения поля:

repeated int32 foo = 1;

Компилятор сгенерирует:

  • List<int> get foo : возвращает список, поддерживающий поле. Если поле не задано, возвращает пустой список. Изменения в списке отражаются в поле.

Поля Int64

Для этого определения поля:

// proto2
optional int64 bar = 1;

// proto3
int64 bar = 1;

Компилятор сгенерирует:

  • Int64 get bar : возвращает объект Int64 , содержащий значение поля.

Обратите внимание, что Int64 не встроен в основные библиотеки Dart. Для работы с этими объектами может потребоваться импорт библиотеки Dart fixnum :

import 'package:fixnum/fixnum.dart';

Поля карты

Учитывая такое определение поля map :

map<int32, int32> map_field = 1;

Компилятор сгенерирует следующий геттер:

  • Map<int, int> get mapField : возвращает карту Dart, поддерживающую поле. Если поле не задано, возвращает пустую карту. Изменения карты отражаются в поле.

Любой

Учитывая Any поле, подобное этому:

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  google.protobuf.Any details = 2;
}

В нашем сгенерированном коде геттер для поля details возвращает экземпляр com.google.protobuf.Any . Это предоставляет следующие специальные методы для упаковки и распаковки значений Any :

    /// Unpacks the message in [value] into [instance].
    ///
    /// Throws a [InvalidProtocolBufferException] if [typeUrl] does not correspond
    /// to the type of [instance].
    ///
    /// A typical usage would be `any.unpackInto(new Message())`.
    ///
    /// Returns [instance].
    T unpackInto<T extends GeneratedMessage>(T instance,
        {ExtensionRegistry extensionRegistry = ExtensionRegistry.EMPTY});

    /// Returns `true` if the encoded message matches the type of [instance].
    ///
    /// Can be used with a default instance:
    /// `any.canUnpackInto(Message.getDefault())`
    bool canUnpackInto(GeneratedMessage instance);

    /// Creates a new [Any] encoding [message].
    ///
    /// The [typeUrl] will be [typeUrlPrefix]/`fullName` where `fullName` is
    /// the fully qualified name of the type of [message].
    static Any pack(GeneratedMessage message,
        {String typeUrlPrefix = 'type.googleapis.com'});

Один из

Учитывая такое определение oneof :

message Foo {
  oneof test {
    string name = 1;
    SubMessage sub_message = 2;
  }
}

Компилятор сгенерирует следующий тип перечисления Dart:

 enum Foo_Test { name, subMessage, notSet }
.

Кроме того, он сгенерирует следующие методы:

  • Foo_Test whichTest() : возвращает перечисление, указывающее, какое поле установлено. Возвращает Foo_Test.notSet , если ни один из них не установлен.
  • void clearTest() : очищает значение поля oneof, которое установлено в данный момент (если оно есть), и устанавливает для случая oneof значение Foo_Test.notSet .

Для каждого поля внутри определения oneof генерируются обычные методы доступа к полям. Например, для name :

  • String get name : возвращает текущее значение поля, если в одном из случаев Foo_Test.name . В противном случае возвращает значение по умолчанию.
  • set name(String value) : Устанавливает значение поля и устанавливает значение oneof для Foo_Test.name . После этого get name вернет value , а whichTest() вернет Foo_Test.name .
  • void clearName() : ничего не изменится, если в одном случае не Foo_Test.name . В противном случае очищает значение поля. После этого get name вернет значение по умолчанию, а whichTest() вернет Foo_Test.notSet .

Перечисления

Учитывая определение перечисления, например:

enum Color {
  RED = 0;
  GREEN = 1;
  BLUE = 2;
}

Компилятор буфера протокола создаст класс с именем Color , который расширяет класс ProtobufEnum . Класс будет включать static const Color для каждого из трех определенных значений, а также static const List<Color> , содержащую все три значения. Он также будет включать следующий метод:

  • static Color valueOf(int value) : возвращает Color , соответствующий заданному числовому значению.

Каждое значение будет иметь следующие свойства:

  • name : имя перечисления, как указано в файле .proto.
  • value : целочисленное значение перечисления, как указано в файле .proto.

Обратите внимание, что язык .proto позволяет нескольким символам перечисления иметь одно и то же числовое значение. Символы с одинаковым числовым значением являются синонимами. Например:

enum Foo {
  BAR = 0;
  BAZ = 0;
}

В этом случае BAZ является синонимом BAR и будет определяться так:

static const Foo BAZ = BAR;
.

Перечисление может быть определено вложенным в тип сообщения. Например, учитывая определение перечисления, например:

message Bar {
  enum Color {
    RED = 0;
    GREEN = 1;
    BLUE = 2;
  }
}

Компилятор буфера протокола сгенерирует класс Bar , который расширяет GeneratedMessage , и класс Bar_Color , который расширяет ProtobufEnum .

Расширения (только proto2)

Учитывая файл foo_test.proto , включающий сообщение с диапазоном расширений и определение расширения верхнего уровня:

message Foo {
  extensions 100 to 199;
}

extend Foo {
  optional int32 bar = 101;
}

Компилятор буфера протокола сгенерирует в дополнение к классу Foo класс Foo_test , который будет содержать static Extension для каждого поля расширения в файле вместе с методом для регистрации всех расширений в ExtensionRegistry :

  • static final Extension bar
  • static void registerAllExtensions(ExtensionRegistry registry) : регистрирует все определенные расширения в данном реестре.

Аксессоры расширения Foo можно использовать следующим образом:

Foo foo = Foo();
foo.setExtension(Foo_test.bar, 1);
assert(foo.hasExtension(Foo_test.bar));
assert(foo.getExtension(Foo_test.bar)) == 1);

Расширения также могут быть объявлены вложенными в другое сообщение:

message Baz {
  extend Foo {
    optional int32 bar = 124;
  }
}
.

В этом случае bar расширения вместо этого объявляется как статический член класса Baz .

При анализе сообщения, которое может иметь расширения, вы должны указать ExtensionRegistry , в котором вы зарегистрировали все расширения, которые хотите анализировать. В противном случае эти расширения будут рассматриваться как неизвестные поля. Например:

ExtensionRegistry registry = ExtensionRegistry();
registry.add(Baz.bar);
Foo foo = Foo.fromBuffer(input, registry);

Если у вас уже есть проанализированное сообщение с неизвестными полями, вы можете использовать reparseMessage в ExtensionRegistry для повторного анализа сообщения. Если набор неизвестных полей содержит расширения, присутствующие в реестре, эти расширения анализируются и удаляются из набора неизвестных полей. Расширения, уже присутствующие в сообщении, сохраняются.

Foo foo = Foo.fromBuffer(input);
ExtensionRegistry registry = ExtensionRegistry();
registry.add(Baz.bar);
Foo reparsed = registry.reparseMessage(foo);
Имейте в виду, что этот метод получения расширений в целом более затратный. По возможности мы рекомендуем использовать ExtensionRegistry со всеми необходимыми расширениями при выполнении GeneratedMessage.fromBuffer .

Услуги

Учитывая определение службы:

service Foo {
  rpc Bar(FooRequest) returns(FooResponse);
}

Компилятор буфера протокола можно вызвать с параметром `grpc` (например --dart_out=grpc:output_folder ), и в этом случае он сгенерирует код для поддержки gRPC . Дополнительные сведения см. в кратком руководстве по gRPC Dart .