• Stars
    star
    485
  • Rank 90,698 (Top 2 %)
  • Language
    Elixir
  • License
    Apache License 2.0
  • Created over 10 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Protocol Buffers in Elixir made easy!

Protocol Buffers for Elixir

exprotobuf works by building module/struct definitions from a Google Protocol Buffer schema. This allows you to work with protocol buffers natively in Elixir, with easy decoding/encoding for transport across the wire.

Build Status Hex.pm Version

Features

  • Load protobuf from file or string
  • Respects the namespace of messages
  • Allows you to specify which modules should be loaded in the definition of records
  • Currently uses gpb for protobuf schema parsing

TODO:

  • Clean up code/tests

Breaking Changes

The 1.0 release removed the feature of handling import "..."; statements. Please see the imports upgrade guide for details if you were using this feature.

Getting Started

Add exprotobuf as a dependency to your project:

defp deps do
  [{:exprotobuf, "~> x.x.x"}]
end

Then run mix deps.get to fetch.

Add exprotobuf to applications list:

def application do
  [applications: [:exprotobuf]]
end

Usage

Usage of exprotobuf boils down to a single use statement within one or more modules in your project.

Let's start with the most basic of usages:

Define from a string

defmodule Messages do
  use Protobuf, """
    message Msg {
      message SubMsg {
        required uint32 value = 1;
      }

      enum Version {
        V1 = 1;
        V2 = 2;
      }

      required Version version = 2;
      optional SubMsg sub = 1;
    }
  """
end
iex> msg = Messages.Msg.new(version: :'V2')
%Messages.Msg{version: :V2, sub: nil}
iex> encoded = Messages.Msg.encode(msg)
<<16, 2>>
iex> Messages.Msg.decode(encoded)
%Messages.Msg{version: :V2, sub: nil}

The above code takes the provided protobuf schema as a string, and generates modules/structs for the types it defines. In this case, there would be a Msg module, containing a SubMsg and Version module. The properties defined for those values are keys in the struct belonging to each. Enums do not generate structs, but a specialized module with two functions: atom(x) and value(x). These will get either the name of the enum value, or it's associated value.

Values defined in the schema using the oneof construct are represented with tuples:

defmodule Messages do
  use Protobuf, """
    message Msg {
      oneof choice {
        string first = 1;
        int32 second = 2;
      }
    }
  """
end
iex> msg = Messages.Msg.new(choice: {:second, 42})
%Messages.Msg{choice: {:second, 42}}
iex> encoded = Messages.Msg.encode(msg)
<<16, 42>>

Define from a file

defmodule Messages do
  use Protobuf, from: Path.expand("../proto/messages.proto", __DIR__)
end

This is equivalent to the above, if you assume that messages.proto contains the same schema as in the string of the first example.

Loading all definitions from a set of files

defmodule Protobufs do
  use Protobuf, from: Path.wildcard(Path.expand("../definitions/**/*.proto", __DIR__))
end
iex> Protobufs.Msg.new(v: :V1)
%Protobufs.Msg{v: :V1}
iex> %Protobufs.OtherMessage{middle_name: "Danger"}
%Protobufs.OtherMessage{middle_name: "Danger"}

This will load all the various definitions in your .proto files and allow them to share definitions like enums or messages between them.

Customizing Generated Module Names

In some cases your library of protobuf definitions might already contain some namespaces that you would like to keep. In this case you will probably want to pass the use_package_names: true option. Let's say you had a file called protobufs/example.proto that contained:

package world;
message Example {
  enum Continent {
    ANTARCTICA = 0;
    EUROPE = 1;
  }

  optional Continent continent = 1;
  optional uint32 id = 2;
}

You could load that file (and everything else in the protobufs directory) by doing:

defmodule Definitions do
  use Protobuf, from: Path.wildcard("protobufs/*.proto"), use_package_names: true
end
iex> Definitions.World.Example.new(continent: :EUROPE)
%Definitions.World.Example{continent: :EUROPE}

You might also want to define all of these modules in the top-level namespace. You can do this by passing an explicit namespace: :"Elixir" option.

defmodule Definitions do
  use Protobuf, from: Path.wildcard("protobufs/*.proto"),
                use_package_names: true,
                namespace: :"Elixir"
end
iex> World.Example.new(continent: :EUROPE)
%World.Example{continent: :EUROPE}

Now you can use just the package names and message names that your team is already familiar with.

Inject a definition into an existing module

This is useful when you only have a single type, or if you want to pull the module definition into the current module instead of generating a new one.

defmodule Msg do
  use Protobuf, from: Path.expand("../proto/messages.proto", __DIR__), inject: true

  def update(msg, key, value), do: Map.put(msg, key, value)
end
iex> %Msg{}
%Msg{v: :V1}
iex> Msg.update(%Msg{}, :v, :V2)
%Msg{v: :V2}

As you can see, Msg is no longer created as a nested module, but is injected right at the top level. I find this approach to be a lot cleaner than use_in, but may not work in all use cases.

Inject a specific type from a larger subset of types

When you have a large schema, but perhaps only care about a small subset of those types, you can use :only:

defmodule Messages do
  use Protobuf, from: Path.expand("../proto/messages.proto", __DIR__),
only: [:TypeA, :TypeB]
end

Assuming that the provided .proto file contains multiple type definitions, the above code would extract only TypeA and TypeB as nested modules. Keep in mind your dependencies, if you select a child type which depends on a parent, or another top-level type, exprotobuf may fail, or your code may fail at runtime.

You may only combine :only with :inject when :only is a single type, or a list containing a single type. This is due to the restriction of one struct per module. Theoretically you should be able to pass :only with multiple types, as long all but one of the types is an enum, since enums are just generated as modules, this does not currently work though.

Extend generated modules via use_in

If you need to add behavior to one of the generated modules, use_in will help you. The tricky part is that the struct for the module you use_in will not be defined yet, so you can't rely on it in your functions. You can still work with the structs via the normal Maps API, but you lose compile-time guarantees. I would recommend favoring :inject over this when possible, as it's a much cleaner solution.

defmodule Messages do
  use Protobuf, "
    message Msg {
      enum Version {
        V1 = 1;
        V2 = 1;
      }
      required Version v = 1;
    }
  "

  defmodule MsgHelpers do
    defmacro __using__(_opts) do
      quote do
        def convert_to_record(msg) do
          msg
          |> Map.to_list
          |> Enum.reduce([], fn {_key, value}, acc -> [value | acc] end)
          |> Enum.reverse
          |> list_to_tuple
        end
      end
    end
  end

  use_in "Msg", MsgHelpers
end
iex> Messages.Msg.new |> Messages.Msg.convert_to_record
{Messages.Msg, :V1}

Attribution/License

exprotobuf is a fork of the azukiaapp/elixir-protobuf project, both of which are released under Apache 2 License.

Check LICENSE files for more information.

More Repositories

1

distillery

Simplify deployments in Elixir with OTP releases!
Elixir
2,944
star
2

timex

A complete date/time library for Elixir projects.
Elixir
1,732
star
3

swarm

Easy clustering, registration, and distribution of worker processes for Erlang/Elixir
Elixir
1,201
star
4

exrm

Automatically generate a release for your Elixir project!
Elixir
923
star
5

libgraph

A graph data structure library for Elixir projects
Elixir
454
star
6

conform

Easy, powerful, and extendable configuration tooling for releases.
Elixir
378
star
7

keys.js

Easy keybindings for browser applications!
JavaScript
360
star
8

alpine-elixir-phoenix

An Alpine Linux base image containing Elixir, Erlang, Node, Hex, and Rebar. Ready for Phoenix applications!
Makefile
349
star
9

alpine-elixir

A Dockerfile based on my alpine-erlang image for Elixir applications
Makefile
202
star
10

toml-elixir

An implementation of TOML for Elixir projects, compliant with the latest specification
Elixir
196
star
11

combine

A parser combinator library for Elixir projects
Elixir
194
star
12

libring

A fast consistent hash ring implementation in Elixir
Elixir
192
star
13

timex_ecto

An adapter for using Timex DateTimes with Ecto
Elixir
161
star
14

exirc

IRC client adapter for Elixir projects
Elixir
152
star
15

artificery

A toolkit for creating terminal user interfaces in Elixir
Elixir
121
star
16

alpine-erlang

An alpine image with Erlang installed, intended for releases
Dockerfile
82
star
17

strukt

Extends defstruct with schemas, changeset validation, and more
Elixir
71
star
18

ex_unit_clustered_case

An extension for ExUnit for simplifying tests against a clustered application
Elixir
57
star
19

uniq

Provides UUID generation, parsing, and formatting. Supports RFC 4122, and the v6 draft extension
Elixir
52
star
20

distillery-aws-example

An example application to go with the AWS guide in the Distillery documentation
Elixir
50
star
21

distillery-umbrella-test

An example Elixir application for working with umbella apps and Distillery.
Elixir
49
star
22

pluginhost

An example C# application which provides runtime extensibility via plugins
C#
31
star
23

picosat_elixir

Elixir + Erlang bindings for the PicoSAT solver
C
17
star
24

docker-release-toolkit

My personal toolkit for building releases with Docker
Makefile
16
star
25

distillery-test

Elixir application which demonstrates a bare-minimum release-ready app using Distillery.
Elixir
16
star
26

8bit-background

A sweet series of 8-bit backgrounds, which changes based on the time of day.
Shell
15
star
27

libswagger

A Swagger client library for Elixir projects
Elixir
15
star
28

stringex

A string extensions library for node.js
JavaScript
15
star
29

scaladiff

A diff library built in Scala, for Scala projects
Scala
13
star
30

dotfiles

My assorted dotfiles
Shell
10
star
31

resume

The TeX source for my current resume.
TeX
7
star
32

fogbugz-cli

FogBugz Command Line Client
Ruby
7
star
33

aria

An experiment in programming language design
7
star
34

s2i-alpine-base

An S2I base image using Alpine Linux
Python
5
star
35

RPNCalculator

Reverse Polish Notation calculator built in Scala for a impromptu code challenge at work
Scala
5
star
36

uniq_compat

A compatibility shim for ::elixir_uuid when used with :uniq
Elixir
4
star
37

aws-dist-test

Clone of distillery-aws-example to illustrate distribution
Elixir
4
star
38

centos7-elixir

A CentOS7 base image for use with the Distillery AWS guide
Dockerfile
4
star
39

firefly

TBD
Elixir
3
star
40

conform_exrm

Conform plugin for ExRM
Elixir
3
star
41

alpine-toolbox

A toolbox image based on Alpine Linux for when I want to troubleshoot things in my OpenShift cluster
Makefile
3
star
42

SharpTools

A collection of practical C# code to build upon
C#
2
star
43

functools

Functional programming tools for Go
Go
2
star
44

toolbox.js

A general purpose javascript utility library. Contains everything you need, and most anything worth having (other than DOM manipulation).
JavaScript
1
star
45

erl_tar2

A re-implementation of erl_tar to support common tar archive formats
Erlang
1
star
46

s2i-elixir

An S2I image which provides Elixir, Erlang, and Node.js
Shell
1
star
47

blog

My personal blog source
CSS
1
star
48

s2i-erlang

An S2I image which provides Erlang and Node.js
Shell
1
star