Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ gen-google-protos: protoc-gen-elixir

gen-protos: protoc-gen-elixir
protoc -I src -I test/protobuf/protoc/proto --elixir_out=test/protobuf/protoc/proto_gen --plugin=./protoc-gen-elixir test/protobuf/protoc/proto/*.proto
protoc -I src -I test/protobuf/protoc/proto --elixir_out=custom_field_options=true:test/protobuf/protoc/proto_gen --plugin=./protoc-gen-elixir test/protobuf/protoc/proto/extension.proto
protoc -I src -I test/protobuf/protoc/proto --elixir_out=custom_field_options=true:test/protobuf/protoc/proto_gen --plugin=./protoc-gen-elixir test/protobuf/protoc/proto/extension2.proto
protoc -I src --elixir_out=lib --plugin=./protoc-gen-elixir elixirpb.proto

.PHONY: clean gen_google_proto gen_test_protos
20 changes: 20 additions & 0 deletions lib/brex_elixirpb.pb.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule Brex.Elixirpb.FieldOptions do
@moduledoc false
use Protobuf, syntax: :proto2

@type t :: %__MODULE__{
extype: String.t()
}
defstruct [:extype]

field :extype, 1, optional: true, type: :string
end

defmodule Brex.Elixirpb.PbExtension do
@moduledoc false
use Protobuf, syntax: :proto2

extend Google.Protobuf.FieldOptions, :field, 65007,
optional: true,
type: Brex.Elixirpb.FieldOptions
end
13 changes: 13 additions & 0 deletions lib/google/timestamp.pb.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule Google.Protobuf.Timestamp do
@moduledoc false
use Protobuf, syntax: :proto3

@type t :: %__MODULE__{
seconds: integer,
nanos: integer
}
defstruct [:seconds, :nanos]

field :seconds, 1, type: :int64
field :nanos, 2, type: :int32
end
107 changes: 107 additions & 0 deletions lib/google/wrappers.pb.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
defmodule Google.Protobuf.DoubleValue do
@moduledoc false
use Protobuf, syntax: :proto3

@type t :: %__MODULE__{
value: float
}
defstruct [:value]

field :value, 1, type: :double
end

defmodule Google.Protobuf.FloatValue do
@moduledoc false
use Protobuf, syntax: :proto3

@type t :: %__MODULE__{
value: float
}
defstruct [:value]

field :value, 1, type: :float
end

defmodule Google.Protobuf.Int64Value do
@moduledoc false
use Protobuf, syntax: :proto3

@type t :: %__MODULE__{
value: integer
}
defstruct [:value]

field :value, 1, type: :int64
end

defmodule Google.Protobuf.UInt64Value do
@moduledoc false
use Protobuf, syntax: :proto3

@type t :: %__MODULE__{
value: non_neg_integer
}
defstruct [:value]

field :value, 1, type: :uint64
end

defmodule Google.Protobuf.Int32Value do
@moduledoc false
use Protobuf, syntax: :proto3

@type t :: %__MODULE__{
value: integer
}
defstruct [:value]

field :value, 1, type: :int32
end

defmodule Google.Protobuf.UInt32Value do
@moduledoc false
use Protobuf, syntax: :proto3

@type t :: %__MODULE__{
value: non_neg_integer
}
defstruct [:value]

field :value, 1, type: :uint32
end

defmodule Google.Protobuf.BoolValue do
@moduledoc false
use Protobuf, syntax: :proto3

@type t :: %__MODULE__{
value: boolean
}
defstruct [:value]

field :value, 1, type: :bool
end

defmodule Google.Protobuf.StringValue do
@moduledoc false
use Protobuf, syntax: :proto3

@type t :: %__MODULE__{
value: String.t()
}
defstruct [:value]

field :value, 1, type: :string
end

defmodule Google.Protobuf.BytesValue do
@moduledoc false
use Protobuf, syntax: :proto3

@type t :: %__MODULE__{
value: String.t()
}
defstruct [:value]

field :value, 1, type: :bytes
end
18 changes: 10 additions & 8 deletions lib/protobuf/builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ defmodule Protobuf.Builder do
new_maybe_strict(mod, attrs, _strict? = true)
end

def field_default(_, %{options: options} = props) when not is_nil(options) do
Protobuf.FieldOptionsProcessor.type_default(props.type, options)
end
def field_default(_, %{default: default}) when not is_nil(default), do: default
def field_default(_, %{repeated?: true}), do: []
def field_default(_, %{map?: true}), do: %{}
Expand Down Expand Up @@ -69,14 +72,13 @@ defmodule Protobuf.Builder do
f_props = props.field_props[props.field_tags[k]]

v =
if f_props.embedded? do
if f_props.repeated? do
Enum.map(v, fn i -> f_props.type.new(i) end)
else
f_props.type.new(v)
end
else
v
cond do
not is_nil(f_props.options) -> Protobuf.FieldOptionsProcessor.new(f_props.type, v, f_props.options)
not is_nil(f_props.options) and f_props.repeated? ->
Enum.map(v, fn i -> Protobuf.FieldOptionsProcessor.new(f_props.type, i, f_props.options) end)
f_props.embedded? and f_props.repeated? -> Enum.map(v, fn i -> f_props.type.new(i) end)
f_props.embedded? -> f_props.type.new(v)
true -> v
end

Map.put(acc, k, v)
Expand Down
27 changes: 17 additions & 10 deletions lib/protobuf/decoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -142,21 +142,14 @@ defmodule Protobuf.Decoder do
type: type,
oneof: oneof,
name_atom: name_atom,
embedded?: embedded
embedded?: embedded,
options: options
} = prop
} ->
key = if oneof, do: oneof_field(prop, msg_props), else: name_atom

struct =
if embedded do
embedded_msg = decode(val, type)
val = if is_map, do: %{embedded_msg.key => embedded_msg.value}, else: embedded_msg
val = if oneof, do: {name_atom, val}, else: val

val = merge_embedded_value(struct, key, val, is_repeated)

Map.put(struct, key, val)
else
if is_nil(options) and not embedded do
val = decode_type_m(type, key, val)
val = if oneof, do: {name_atom, val}, else: val

Expand All @@ -167,6 +160,20 @@ defmodule Protobuf.Decoder do
val
end

Map.put(struct, key, val)
else
embedded_msg =
if is_nil(options) do
decode(val, type)
else
Protobuf.FieldOptionsProcessor.decode_type(val, type, options)
end

val = if is_map, do: %{embedded_msg.key => embedded_msg.value}, else: embedded_msg
val = if oneof, do: {name_atom, val}, else: val

val = merge_embedded_value(struct, key, val, is_repeated)

Map.put(struct, key, val)
end

Expand Down
21 changes: 21 additions & 0 deletions lib/protobuf/encoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ defmodule Protobuf.Encoder do
def skip_field?(_, _, _), do: false

@spec encode_field(atom, any, FieldProps.t()) :: iodata
defp encode_field(
:custom,
val,
%{encoded_fnum: fnum, repeated?: is_repeated, map?: is_map, type: type, options: options} =
prop
) do
repeated = is_repeated || is_map

repeated_or_not(val, repeated, fn v ->
v = if is_map, do: struct(prop.type, %{key: elem(v, 0), value: elem(v, 1)}), else: v
encoded = Protobuf.FieldOptionsProcessor.encode_type(type, v, options)
byte_size = byte_size(encoded)
[fnum, encode_varint(byte_size), encoded]
end)
end

defp encode_field(:normal, val, %{encoded_fnum: fnum, type: type, repeated?: is_repeated}) do
repeated_or_not(val, is_repeated, fn v ->
[fnum, encode_type(type, v)]
Expand Down Expand Up @@ -126,6 +142,11 @@ defmodule Protobuf.Encoder do
[fnum, encode_varint(byte_size), encoded]
end

@spec class_field(map) :: atom
defp class_field(%{options: options}) when not is_nil(options) do
:custom
end

@spec class_field(map) :: atom
defp class_field(%{wire_type: wire_delimited(), embedded?: true}) do
:embedded
Expand Down
2 changes: 1 addition & 1 deletion lib/protobuf/extension/props.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Protobuf.Extension.Props do
@moduledoc false
@type t :: %__MODULE__{
extendee: module,
field_props: FieldProps.T
field_props: FieldProps.t
}
defstruct extendee: nil,
field_props: nil
Expand Down
102 changes: 102 additions & 0 deletions lib/protobuf/extype/extype_protocol.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
defprotocol Extype.Protocol do
@moduledoc """
Protocol for defining an elixir type for a protobuf type.
"""
@typedoc """
An elixir type.
"""
@type extype :: String.t()

@typedoc """
The existing type of the field. Often the module name of the struct.
"""
@type type :: atom

@typedoc """
A value with type extype.
"""
@type value :: struct | any

@spec validate_and_to_atom_extype!(type, option :: String.t) :: atom
def validate_and_to_atom_extype!(type, option)

@spec type_default(type, extype) :: any
def type_default(type, extype)

@spec new(type, value, extype) :: value
def new(type, value, extype)

@spec encode_type(type, value, extype) :: binary
def encode_type(type, v, extype)

@spec decode_type(type, val :: binary, extype) :: value
def decode_type(val, type, extype)
end

defmodule Extype do
@moduledoc "Extype"

@type extype :: Extype.Protocol.extype
@type type :: Extype.Protocol.type
@type value :: Extype.Protocol.value

# A serious trick
def get_mod(type) when is_atom(type) do
try do
Extype.Protocol.impl_for!(%{__struct__: type})
rescue
_exception ->
reraise "Sorry #{type} does not support the field option extype", __STACKTRACE__
end
end

@spec type_to_spec(type :: String.t(), repeated :: boolean, extype) :: String.t()
def type_to_spec(_type, repeated, extype) do
extype = pad_parens(extype)
if repeated do
"[#{extype}]"
else
extype <> " | nil"
end
end

@spec type_default(type, extype) :: any
def type_default(type, extype) do
mod = get_mod(type)
extype = pad_parens(extype)
atom_extype = mod.validate_and_to_atom_extype!(type, extype)
mod.type_default(type, atom_extype)
end

@spec new(type, value, extype) :: value
def new(type, value, extype) do
mod = get_mod(type)
extype = pad_parens(extype)
atom_extype = mod.validate_and_to_atom_extype!(type, extype)
mod.new(type, value, atom_extype)
end

@spec encode_type(type, value, extype) :: binary
def encode_type(type, v, extype) do
mod = get_mod(type)
extype = pad_parens(extype)
atom_extype = mod.validate_and_to_atom_extype!(type, extype)
mod.encode_type(type, v, atom_extype)
end

@spec decode_type(val :: binary, type, extype) :: value
def decode_type(val, type, extype) do
mod = get_mod(type)
extype = pad_parens(extype)
atom_extype = mod.validate_and_to_atom_extype!(type, extype)
mod.decode_type(type, val, atom_extype)
end

defp pad_parens(extype) do
if String.ends_with?(extype, ".t") do
extype <> "()"
else
extype
end
end
end
Loading