Feb 12, 2012

Protocol Buffers for Erlang

Protoc-gen-erlang is a plugin for Google’s protoc to generate Protocol Buffers implementations in Erlang. It has a number of advantages:

  • It uses the protoc parser, meaning that it will parse and accept or reject .proto files exactly as the “spec” defines them.
  • Supports enums, nested messages, and default values.
  • Generates Triq tests and domains for messages.

Installation

Download and install the protoc development libraries. Compiling from source and the Ubuntu protoc-dev libs are known to work.

Download https://github.com/TensorWrench/protoc-gen-erlang and make. Put bin/protoc-gen-erlang on your path, somewhere.

Command line options

If protoc-gen-erlang is in the path, protoc should respond to the option –erlang-out=(parameters):(dir). Dir is the base directory of an OTP standard layout where files will be put into include, src, and test. Parameters are optional, and the colon is not necessary if there are no parameters. The “triq_tests” parameter generates triq-based tests for each parser and domains to generate random messages in the “test” directory. The “strict_naming” message names all code exactly as listed in the .pb file. The default behavior is to munge the names to an erlang style.

Project

Add https://github.com/TensorWrench/libprotobuf to your Erlang project dependencies.  For rebar:

{pre_hooks,  [
  { compile, "protoc --erlang_out=. protocol_src/*.proto"},
  { eunit, "protoc --erlang_out=. protocol_src/*.proto"},
  { clean, "rm src/*_pb.erl include/*_pb.hrl test/*_pb_tests.erl"}
]}.
{deps,[
  { libprotobuf, ".*", {git, "https://github.com/TensorWrench/libprotobuf", "HEAD"}}
]}.

Generated Code

By default, the generator will transform the CamelCase naming that’s standard for Protocol Buffers to underscore_name style used by Erlang. Namespacing is seperated by a double underscore “__”.

message nested_message_outer {
  message nested_message {
    required int32 a=1;
    required int32 b=2;
  };
  required nested_message nested=1;
}

will create a hrl:

%% @type nested_message_outer__nested_message_record() = #nested_message_outer__nested_message_record{
%%   a() = integer(),
%%   b() = integer()
%% }.
-record(nested_message_outer__nested_message,{
  a :: integer(),
  b :: integer()}).

%% @type nested_message_outer_record() = #nested_message_outer_record{
%%   nested() = #nested_message_outer__nested_message{}
%% }.
-record(nested_message_outer,{
  nested :: #nested_message_outer__nested_message{}}).

and functions

-export([
  encode_nested_message_outer__nested_message/1,
  decode_nested_message_outer__nested_message/1,
  encode_nested_message_outer/1,
  decode_nested_message_outer/1]).

Usage

Usage is fairly straightforward. The encode functions take a record of the appropriate type and returns an iolist. The decode function takes a binary and returns a record of the appropriate type.

3 Comments

Leave a comment

You must be logged in to post a comment.