d2p_gen

The "d2p_proto" Dart package facilitates the automatic generation of protocol buffer files (.proto) by leveraging Dart code annotations provided by the d2p_annotation package. It streamlines the process of creating .g.proto files, mappers, and associated tests, enhancing the efficiency of working with protocol buffers in Dart projects.

Reason

Protocol Buffers (Proto) provides a compact representation of data, reducing the size of transmitted messages by up to 3-5 times compared to JSON. With more efficient serialization and deserialization, using Proto speeds up data exchange by 30-40%. Proto's data typing and strict schema simplify development and provide more robust data validation, making it the preferred choice for distributed systems.

Installation

To use d2p_gen, you will need your typical build_runner/code-generator setup.
First, install build_runner and d2p_gen by adding them to your pubspec.yaml file:

dependencies:
 d2p_annotation: any
 # other dependencies
dev_dependencies:
  d2p_gen: any

Alternativies for install the package - run conamd folowwing comand:

  • For a Flutter project:
flutter pub add d2p_annotation
flutter pub add dev:build_runner
flutter pub add dev:d2p_gen
  • -For a Dart project:
dart pub add d2p_annotation
dart pub add dev:build_runner
dart pub add dev:d2p_gen

Annotation

Place annotations under the classes that you prefer to receive notifications from.


import 'package:d2p_annotation/d2p_annotation.dart';

@ProtoGen(createMappers: false)
class User {
  final String name;
  final int age;
  const User(this.name, this.age);
}

run the following command:

dart run build_runner build

This package create a new foldeer at the root directory of project proto with single file messages.g.proto with all of your proto messeges. For this class it will look like this:

syntax = "proto3";
package messages;
// -- Some other messages.
/*
  class: User
*/
  message DTOUser {
    // String User.name
    string name = 1;
    // int User.age
    int32 age = 2;
  }

If you use the @ProtoGen annotation with the createMappers option set to true, you will also get a mapper class that can convert the generated Dart code into the model class and vice versa. These mapper classes are always saved in separate files with a .mp.dart extension.

/// Mapper that converts a DTO [DTOUser] object
/// into a Model [User] and back.
abstract class $MapperUser {
  /// Converts the model [User]
  /// to the DTO [DTOUser].
  static User fromDTO(DTOUser model) {
    try {
      return User(
        model.name,
        model.age,
      );
    } on FormatException catch (e, trace) {
      throw FormatException(
        '''Exception
      ${e.source}
      ${e.message}
      $trace''',
      );
    }
  }

  /// Converts the model [User]
  /// to the DTO [DTOUser]
  static DTOUser toDTO(User model) {
    try {
      return DTOUser(
        name: model.name,
        age: model.age,
      );
    } on FormatException catch (e, trace) {
      throw FormatException(
        '''Exception
      ${e.source}
      ${e.message}
      $trace''',
      );
    }
  }
}

In order to ensure that the mapper function operates correctly, the tool also generates a test case for each mapper function.


  group(r'Testing $MapperUser methods', () {
// Test the toDTO method (which returns a DTO class)
    test(r'$MapperUser.toDTO Output class User should be DTOUser', () {
      // Arrange - Setup facts, Put Expected outputs or Initialize
      final model = User(
        'ZymI7ohW2Dq9XeUE',
        49,
      );

      // Act - Call the function that is to be tested
      final dto = $MapperUser.toDTO(model);

      // Assert - Compare the actual result and expected result
      // Check if the output is of the expected type
      expect(
        dto,
        TypeMatcher<DTOUser>(),
        reason: 'The output should be of type DTOUser',
      );
// Check if the output is not null
      expect(
        dto,
        isNotNull,
        reason: 'The output must not be null',
      );
// Check if the output is not an exception
      expect(
        dto,
        isNot(isException),
        reason: 'The output must not be an exception',
      );
    });

// Test the fromDTO method (which returns a dart data class or enum)
    test(r'$MapperUser.fromDTO Output class User should be User', () {
      // Arrange - Setup facts, Put Expected outputs or Initialize
      final dto = DTOUser(
        name: '8O',
        age: 48,
      );

      // Act - Call the function that is to be tested
      final model = $MapperUser.fromDTO(dto);

      // Assert - Compare the actual result and expected result
      // Check if the output is of the expected type
      expect(
        model,
        TypeMatcher<User>(),
        reason: 'The output should be of type User',
      );
// Check if the output is not null
      expect(
        model,
        isNotNull,
        reason: 'The output must not be null',
      );
// Check if the output is not an exception
      expect(
        model,
        isNot(isException),
        reason: 'The output must not be an exception',
      );
    });
  });

Once all relevant files have been created, the testing of the mapper will begin automatically. You will then see a screen similar to the one below.

🎉 2 tests passed.

Table of comparison of Dart types to Proto which is implemented by the package

            | proto3   | dart     |
            |----------|----------|
            | double   | double   |
            | double   | mun      |
            | Int64    | int      |
            | bool     | bool     |
            | string   | String   |
            | string   | Map      |
            | enum     | enum     |
            | repeated | Iterables|

Full comparison of proto types to programming languages on the official website website

Libraries

d2p_gen