Packages
protox
1.7.4
2.0.9
2.0.8
2.0.7
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-dev
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
retired
1.7.3
1.7.2
1.7.1
1.7.0
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.1
1.1.0
1.0.0
0.25.0
0.24.0
0.23.1
0.23.0
0.22.0
0.21.0
0.20.0
0.19.1
0.19.0
0.18.0
0.17.0
0.16.2
0.16.1
0.16.0
0.15.2
0.15.1
0.15.0
0.14.0
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.1
0.7.0
A fast, easy to use and 100% conformant Elixir library for Google Protocol Buffers (aka protobuf)
Retired package: Release invalid - Deps reference a non-existing repository
Current section
Files
Jump to
Current section
Files
lib/protox.ex
defmodule Protox do
@moduledoc ~S'''
Use this module to generate the Elixir structs corresponding to a set of protobuf definitions
and to encode/decode instances of these structures.
## Elixit structs generation examples
From a set of files:
defmodule Dummy do
use Protox,
files: [
"./defs/foo.proto",
"./defs/bar.proto",
"./defs/baz/fiz.proto",
]
end
From a string:
defmodule Dummy do
use Protox,
schema: """
syntax = "proto3";
package fiz;
message Baz {
}
message Foo {
map<int32, Baz> b = 2;
}
"""
end
The generated modules respect the package declaration. For instance, in the above example,
both the `Fiz.Baz` and `Fiz.Foo` modules will be generated.
## Encoding/decoding
For the rest of this module documentation, we suppose the following protobuf messages are defined:
defmodule Dummy do
use Protox,
schema: """
syntax = "proto3";
package fiz;
message Baz {
}
enum Enum {
FOO = 0;
BAR = 1;
}
message Foo {
Enum a = 1;
map<int32, Baz> b = 2;
}
""",
namespace: Namespace
use Protox,
schema: """
syntax = "proto3";
message Msg {
map<int32, string> msg_k = 8;
}
"""
use Protox,
schema: """
syntax = "proto3";
message Sub {
int32 a = 1;
}
"""
end
See each function documentation to see how they are used to encode and decode protobuf messages.
'''
defmacro __using__(opts) do
{opts, _} = Code.eval_quoted(opts)
{paths, opts} = get_paths(opts)
{files, opts} = get_files(opts)
{:ok, file_descriptor_set} = Protox.Protoc.run(files, paths)
%{enums: enums, messages: messages} = Protox.Parse.parse(file_descriptor_set, opts)
quote do
unquote(make_external_resources(files))
unquote(Protox.Define.define(enums, messages, opts))
end
end
@doc """
Throwing version of `decode/2`.
"""
@doc since: "1.6.0"
@spec decode!(binary(), atom()) :: struct() | no_return()
def decode!(binary, msg_module) do
msg_module.decode!(binary)
end
@doc """
Decode a binary into a protobuf message.
## Examples
iex> binary = <<66, 7, 8, 1, 18, 3, 102, 111, 111, 66, 7, 8, 2, 18, 3, 98, 97, 114>>
iex> {:ok, msg} = Protox.decode(binary, Msg)
iex> msg
%Msg{msg_k: %{1 => "foo", 2 => "bar"}}
iex> binary = <<66, 7, 8, 1, 18, 3, 102, 111, 66, 7, 8, 2, 18, 3, 98, 97, 114>>
iex> {:error, reason} = Protox.decode(binary, Msg)
iex> reason
%Protox.IllegalTagError{message: "Field with illegal tag 0"}
"""
@doc since: "1.6.0"
@spec decode(binary(), atom()) :: {:ok, struct()} | {:error, any()}
def decode(binary, msg_module) do
msg_module.decode(binary)
end
@doc """
Throwing version of `encode/1`.
"""
@doc since: "1.6.0"
@spec encode!(struct()) :: iodata() | no_return()
def encode!(msg) do
msg.__struct__.encode!(msg)
end
@doc """
Encode a protobuf message into IO data.
## Examples
iex> msg = %Namespace.Fiz.Foo{a: 3, b: %{1 => %Namespace.Fiz.Baz{}}}
iex> {:ok, iodata} = Protox.encode(msg)
iex> :binary.list_to_bin(iodata)
<<8, 3, 18, 4, 8, 1, 18, 0>>
iex> msg = %Namespace.Fiz.Foo{a: "should not be a string"}
iex> {:error, reason} = Protox.encode(msg)
iex> reason
%Protox.EncodingError{field: :a, message: "Could not encode field :a (invalid field value)"}
"""
@doc since: "1.6.0"
@spec encode(struct()) :: {:ok, iodata()} | {:error, any()}
def encode(msg) do
msg.__struct__.encode(msg)
end
@doc """
## Errors
This function returns a tuple `{:error, reason}` if:
- `input` could not be decoded to JSON; `reason` is a `Protox.JsonDecodingError` error
## JSON library configuration
The default library to decode JSON is [`Jason`](https://github.com/michalmuskala/jason).
However, you can chose to use [`Poison`](https://github.com/devinus/poison):
iex> Protox.json_decode("{\\"a\\":\\"BAR\\"}", Namespace.Fiz.Foo, json_decoder: Poison)
{:ok, %Namespace.Fiz.Foo{__uf__: [], a: :BAR, b: %{}}}
You can also use another library as long as it exports an `decode!` function. You can easily
create a module to wrap a library that would not have this interface (like [`jiffy`](https://github.com/davisp/jiffy)):
defmodule Jiffy do
def decode!(input) do
:jiffy.decode(input, [:return_maps, :use_nil])
end
end
"""
@doc since: "1.6.0"
@spec json_decode(iodata(), atom(), keyword()) :: {:ok, struct()} | {:error, any()}
def json_decode(input, message_module, opts \\ []) do
message_module.json_decode(input, opts)
end
@doc """
Throwing version of `json_decode/2`.
"""
@doc since: "1.6.0"
@spec json_decode!(iodata(), atom(), keyword()) :: iodata() | no_return()
def json_decode!(input, message_module, opts \\ []) do
message_module.json_decode!(input, opts)
end
@doc """
Export a proto3 message to JSON as IO data.
## Errors
This function returns a tuple `{:error, reason}` if:
- `msg` could not be encoded to JSON; `reason` is a `Protox.JsonEncodingError` error
## Examples
iex> msg = %Namespace.Fiz.Foo{a: :BAR}
iex> {:ok, iodata} = Protox.json_encode(msg)
iex> iodata
["{", ["\\"a\\"", ":", "\\"BAR\\""], "}"]
iex> msg = %Sub{a: 42}
iex> {:ok, iodata} = Protox.json_encode(msg)
iex> iodata
["{", ["\\"a\\"", ":", "42"], "}"]
iex> msg = %Msg{msg_k: %{1 => "foo", 2 => "bar"}}
iex> {:ok, iodata} = msg |> Protox.json_encode()
iex> :binary.list_to_bin(iodata)
"{\\"msgK\\":{\\"2\\":\\"bar\\",\\"1\\":\\"foo\\"}}"
## JSON library configuration
The default library to encode values (i.e. mostly to escape strings) to JSON is [`Jason`](https://github.com/michalmuskala/jason).
However, you can chose to use [`Poison`](https://github.com/devinus/poison):
iex> msg = %Namespace.Fiz.Foo{a: :BAR}
iex> Protox.json_encode(msg, json_encoder: Poison)
{:ok, ["{", ["\\"a\\"", ":", "\\"BAR\\""], "}"]}
You can also use another library as long as it exports an `encode!` function, which is expected to return objects as maps and `nil`
to represent `null`.
You can easily create a module to wrap a library that would not have this interface (like [`jiffy`](https://github.com/davisp/jiffy)):
defmodule Jiffy do
defdelegate encode!(msg), to: :jiffy, as: :encode
end
## Encoding specifications
See https://developers.google.com/protocol-buffers/docs/proto3#json for the specifications
of the encoding.
"""
@doc since: "1.6.0"
@spec json_encode(struct(), keyword()) :: {:ok, iodata()} | {:error, any()}
def json_encode(msg, opts \\ []) do
msg.__struct__.json_encode(msg, opts)
end
@doc """
Throwing version of `json_encode/1`.
"""
@doc since: "1.6.0"
@spec json_encode!(struct(), keyword()) :: iodata() | no_return()
def json_encode!(msg, opts \\ []) do
msg.__struct__.json_encode!(msg, opts)
end
# -- Private
defp get_paths(opts) do
case Keyword.pop(opts, :paths) do
{nil, opts} -> get_path(opts)
{ps, opts} -> {Enum.map(ps, &Path.expand/1), opts}
end
end
defp get_path(opts) do
case Keyword.pop(opts, :path) do
{nil, opts} -> {nil, opts}
{p, opts} -> {[Path.expand(p)], opts}
end
end
defp get_files(opts) do
case Keyword.pop(opts, :schema) do
{<<text::binary>>, opts} ->
filename = "#{Base.encode16(:crypto.hash(:sha, text))}.proto"
filepath = [Mix.Project.build_path(), filename] |> Path.join() |> Path.expand()
File.write!(filepath, text)
{[filepath], opts}
{nil, opts} ->
{files, opts} = Keyword.pop(opts, :files)
{Enum.map(files, &Path.expand/1), opts}
end
end
defp make_external_resources(files) do
Enum.map(files, fn file -> quote(do: @external_resource(unquote(file))) end)
end
end