• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..03-May-2022-

lib/H30-Aug-2017-807708

test/H30-Aug-2017-1,3771,128

.gitignoreH A D30-Aug-201766 98

.travis.ymlH A D30-Aug-2017163 1413

LICENSEH A D30-Aug-201710 KiB193155

README.mdH A D30-Aug-20177.3 KiB272210

imports_upgrade_guide.mdH A D30-Aug-20171.9 KiB6649

mix.exsH A D03-May-2022859 3933

mix.lockH A D30-Aug-2017415 43

README.md

1# Protocol Buffers for Elixir
2
3exprotobuf works by building module/struct definitions from a [Google Protocol Buffer](https://code.google.com/p/protobuf)
4schema. This allows you to work with protocol buffers natively in Elixir, with easy decoding/encoding for transport across the
5wire.
6
7[![Build Status](https://travis-ci.org/bitwalker/exprotobuf.svg?branch=master)](https://travis-ci.org/bitwalker/exprotobuf)
8[![Hex.pm Version](http://img.shields.io/hexpm/v/exprotobuf.svg?style=flat)](https://hex.pm/packages/exprotobuf)
9
10## Features
11
12* Load protobuf from file or string
13* Respects the namespace of messages
14* Allows you to specify which modules should be loaded in the definition of records
15* Currently uses [gpb](https://github.com/tomas-abrahamsson/gpb) for protobuf schema parsing
16
17TODO:
18
19* Clean up code/tests
20
21## Breaking Changes
22
23The 1.0 release removed the feature of handling `import "...";` statements.
24Please see [the imports upgrade guide](imports_upgrade_guide.md) for details if you were using this feature.
25
26## Getting Started
27
28Add exprotobuf as a dependency to your project:
29
30```elixir
31defp deps do
32  [{:exprotobuf, "~> x.x.x"}]
33end
34```
35
36Then run `mix deps.get` to fetch.
37
38Add exprotobuf to applications list:
39
40```elixir
41def application do
42  [applications: [:exprotobuf]]
43end
44```
45
46## Usage
47
48Usage of exprotobuf boils down to a single `use` statement within one or
49more modules in your project.
50
51Let's start with the most basic of usages:
52
53### Define from a string
54
55```elixir
56defmodule Messages do
57  use Protobuf, """
58    message Msg {
59      message SubMsg {
60        required uint32 value = 1;
61      }
62
63      enum Version {
64        V1 = 1;
65        V2 = 2;
66      }
67
68      required Version version = 2;
69      optional SubMsg sub = 1;
70    }
71  """
72end
73```
74
75```elixir
76iex> msg = Messages.Msg.new(version: :'V2')
77%Messages.Msg{version: :V2, sub: nil}
78iex> encoded = Messages.Msg.encode(msg)
79<<16, 2>>
80iex> Messages.Msg.decode(encoded)
81%Messages.Msg{version: :V2, sub: nil}
82```
83
84The above code takes the provided protobuf schema as a string, and
85generates modules/structs for the types it defines. In this case, there
86would be a Msg module, containing a SubMsg and Version module. The
87properties defined for those values are keys in the struct belonging to
88each. Enums do not generate structs, but a specialized module with two
89functions: `atom(x)` and `value(x)`. These will get either the name of
90the enum value, or it's associated value.
91
92### Define from a file
93
94```elixir
95defmodule Messages do
96  use Protobuf, from: Path.expand("../proto/messages.proto", __DIR__)
97end
98```
99
100This is equivalent to the above, if you assume that `messages.proto`
101contains the same schema as in the string of the first example.
102
103### Loading all definitions from a set of files
104
105```elixir
106defmodule Protobufs do
107  use Protobuf, from: Path.wildcard(Path.expand("../definitions/**/*.proto", __DIR__))
108end
109```
110
111```elixir
112iex> Protobufs.Msg.new(v: :V1)
113%Protobufs.Msg{v: :V1}
114iex> %Protobufs.OtherMessage{middle_name: "Danger"}
115%Protobufs.OtherMessage{middle_name: "Danger"}
116```
117
118This will load all the various definitions in your `.proto` files and
119allow them to share definitions like enums or messages between them.
120
121### Customizing Generated Module Names
122
123In some cases your library of protobuf definitions might already contain some
124namespaces that you would like to keep.
125In this case you will probably want to pass the `use_package_names: true` option.
126Let's say you had a file called `protobufs/example.proto` that contained:
127
128```protobuf
129package world;
130message Example {
131  enum Continent {
132    ANTARCTICA = 0;
133    EUROPE = 1;
134  }
135
136  optional Continent continent = 1;
137  optional uint32 id = 2;
138}
139```
140
141You could load that file (and everything else in the protobufs directory) by doing:
142
143```elixir
144defmodule Definitions do
145  use Protobuf, from: Path.wildcard("protobufs/*.proto"), use_package_names: true
146end
147```
148
149```elixir
150iex> Definitions.World.Example.new(continent: :EUROPE)
151%Definitions.World.Example{continent: :EUROPE}
152```
153
154You might also want to define all of these modules in the top-level namespace. You
155can do this by passing an explicit `namespace: :"Elixir"` option.
156
157```elixir
158defmodule Definitions do
159  use Protobuf, from: Path.wildcard("protobufs/*.proto"),
160                use_package_names: true,
161                namespace: :"Elixir"
162end
163```
164
165```elixir
166iex> World.Example.new(continent: :EUROPE)
167%World.Example{continent: :EUROPE}
168```
169
170Now you can use just the package names and message names that your team is already
171familiar with.
172
173### Inject a definition into an existing module
174
175This is useful when you only have a single type, or if you want to pull
176the module definition into the current module instead of generating a
177new one.
178
179```elixir
180defmodule Msg do
181  use Protobuf, from: Path.expand("../proto/messages.proto", __DIR__), inject: true
182
183  def update(msg, key, value), do: Map.put(msg, key, value)
184end
185```
186
187```elixir
188iex> %Msg{}
189%Msg{v: :V1}
190iex> Msg.update(%Msg{}, :v, :V2)
191%Msg{v: :V2}
192```
193
194As you can see, Msg is no longer created as a nested module, but is
195injected right at the top level. I find this approach to be a lot
196cleaner than `use_in`, but may not work in all use cases.
197
198### Inject a specific type from a larger subset of types
199
200When you have a large schema, but perhaps only care about a small subset
201of those types, you can use `:only`:
202
203```elixir
204defmodule Messages do
205  use Protobuf, from: Path.expand("../proto/messages.proto", __DIR__),
206only: [:TypeA, :TypeB]
207end
208```
209
210Assuming that the provided .proto file contains multiple type
211definitions, the above code would extract only TypeA and TypeB as nested
212modules. Keep in mind your dependencies, if you select a child type
213which depends on a parent, or another top-level type, exprotobuf may
214fail, or your code may fail at runtime.
215
216You may only combine `:only` with `:inject` when `:only` is a single
217type, or a list containing a single type. This is due to the restriction
218of one struct per module. Theoretically you should be able to pass `:only`
219with multiple types, as long all but one of the types is an enum, since
220enums are just generated as modules, this does not currently work
221though.
222
223### Extend generated modules via `use_in`
224
225If you need to add behavior to one of the generated modules, `use_in`
226will help you. The tricky part is that the struct for the module you
227`use_in` will not be defined yet, so you can't rely on it in your
228functions. You can still work with the structs via the normal Maps API,
229but you lose compile-time guarantees. I would recommend favoring
230`:inject` over this when possible, as it's a much cleaner solution.
231
232```elixir
233defmodule Messages do
234  use Protobuf, "
235    message Msg {
236      enum Version {
237        V1 = 1;
238        V2 = 1;
239      }
240      required Version v = 1;
241    }
242  "
243
244  defmodule MsgHelpers do
245    defmacro __using__(_opts) do
246      quote do
247        def convert_to_record(msg) do
248          msg
249          |> Map.to_list
250          |> Enum.reduce([], fn {_key, value}, acc -> [value | acc] end)
251          |> Enum.reverse
252          |> list_to_tuple
253        end
254      end
255    end
256  end
257
258  use_in "Msg", MsgHelpers
259end
260```
261
262```elixir
263iex> Messages.Msg.new |> Messages.Msg.convert_to_record
264{Messages.Msg, :V1}
265```
266
267## Attribution/License
268
269exprotobuf is a fork of the azukiaapp/elixir-protobuf project, both of which are released under Apache 2 License.
270
271Check LICENSE files for more information.
272