defmodule Protobuf do
@moduledoc """
`protoc` should always be used to generate code instead of writing the code by hand.
By `use` this module, macros defined in `Protobuf.DSL` will be injected. Most of thee macros
are equal to definition in .proto files.
defmodule Foo do
use Protobuf, syntax: :proto3
defstruct [:a, :b]
field :a, 1, type: :int32
field :b, 2, type: :string
end
Your Protobuf message(module) is just a normal Elixir struct. Some useful functions are also injected,
see "Callbacks" for details. Examples:
foo1 = Foo.new!(%{a: 1})
foo1.b == ""
bin = Foo.encode(foo1)
foo1 == Foo.decode(bin)
Except functions in "Callbacks", some other functions may be defined:
* Extension functions when your Protobuf message use extensions. See `Protobuf.Extension` for details.
* `put_extension(struct, extension_mod, field, value)`
* `get_extension(struct, extension_mod, field, default \\ nil)`
"""
defmacro __using__(opts) do
quote location: :keep do
import Protobuf.DSL, only: [field: 3, field: 2, oneof: 2, extend: 4, extensions: 1]
Module.register_attribute(__MODULE__, :fields, accumulate: true)
Module.register_attribute(__MODULE__, :oneofs, accumulate: true)
Module.register_attribute(__MODULE__, :extends, accumulate: true)
Module.register_attribute(__MODULE__, :extensions, [])
@options unquote(opts)
@before_compile Protobuf.DSL
@behaviour Protobuf
@impl unquote(__MODULE__)
def new() do
Protobuf.Builder.new(__MODULE__)
end
@impl unquote(__MODULE__)
def new(attrs) do
Protobuf.Builder.new(__MODULE__, attrs)
end
@impl unquote(__MODULE__)
def new!(attrs) do
Protobuf.Builder.new!(__MODULE__, attrs)
end
@impl unquote(__MODULE__)
def transform_module() do
nil
end
defoverridable transform_module: 0
@impl unquote(__MODULE__)
def decode(data), do: Protobuf.Decoder.decode(data, __MODULE__)
@impl unquote(__MODULE__)
def encode(struct), do: Protobuf.Encoder.encode(struct)
end
end
@doc """
Builds a blank struct with default values. This and other "new" functions are
preferred than raw building struct method like `%Foo{}`.
In proto3, the zero values are the default values.
"""
@callback new() :: struct()
@doc """
Builds and updates the struct with passed fields.
## Examples
MyMessage.new(field1: "foo")
#=> %MyMessage{field1: "foo", ...}
"""
@callback new(Enum.t()) :: struct
@doc """
Similar to `c:new/1`, but use `struct!/2` to build the struct, so
errors will be raised if unknown keys are passed.
"""
@callback new!(Enum.t()) :: struct()
@doc """
Encodes the given struct into to a Protobuf binary.
Errors may be raised if there's something wrong in the struct.
If you want to encode to iodata instead of to a binary, use `encode_to_iodata/1`.
"""
@callback encode(struct()) :: binary()
@doc """
Decodes a Protobuf binary into a struct.
Errors may be raised if there's something wrong in the binary.
"""
@callback decode(binary()) :: struct()
@doc """
Returns `nil` or a transformer module that implements the `Protobuf.TransformModule`
behaviour.
This function is overridable in your module.
"""
@callback transform_module() :: module() | nil
@doc """
Decodes the given binary data interpreting it as the Protobuf message `module`.
It's preferrable to use the message's `c:decode/1` function. For a message `MyMessage`:
MyMessage.decode(<<...>>)
#=> %MyMessage{...}
This function raises an error if anything goes wrong with decoding.
## Examples
Protobuf.decode(<<...>>, MyMessage)
#=> %MyMessage{...}
Protobuf.decode(<<"bad data">>, MyMessage)
#=> ** (Protobuf.DecodeError) ...
"""
@spec decode(binary(), message) :: %{required(:__struct__) => message} when message: module()
defdelegate decode(data, module), to: Protobuf.Decoder
@doc """
Encodes the given Protobuf struct into a binary.
If you want to encode to iodata instead, see `encode_to_iodata/1`.
## Examples
struct = MyMessage.new()
Protobuf.encode(struct)
#=> <<...>>
"""
@spec encode(struct()) :: binary()
defdelegate encode(struct), to: Protobuf.Encoder
@doc """
Encodes the given Protobuf struct into iodata.
## Examples
struct = MyMessage.new()
Protobuf.encode_to_iodata(struct)
#=> [...]
"""
@spec encode_to_iodata(struct()) :: iodata()
defdelegate encode_to_iodata(struct), to: Protobuf.Encoder
@doc """
Loads extensions modules.
This function should be called in your application's `c:Application.start/2` callback,
as seen in the example below, if you wish to use extensions.
## Example
@impl Application
def start(_type, _args) do
Protobuf.load_extensions()
Supervisor.start_link([], strategy: :one_for_one)
end
"""
@spec load_extensions() :: :ok
def load_extensions() do
Protobuf.Extension.__cal_extensions__()
:ok
end
end