diff --git a/lib/brex_elixirpb.pb.ex b/lib/brex_elixirpb.pb.ex new file mode 100644 index 00000000..7124410e --- /dev/null +++ b/lib/brex_elixirpb.pb.ex @@ -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 diff --git a/lib/protobuf/extension.ex b/lib/protobuf/extension.ex index 3cc8b34e..afd9d8dd 100644 --- a/lib/protobuf/extension.ex +++ b/lib/protobuf/extension.ex @@ -135,6 +135,8 @@ defmodule Protobuf.Extension do raise "Extension #{inspect(ext.extendee)}##{fnum} already exists" end + # IO.inspect(:stderr, {fnum_key, mod}, label: :extensions) + GlobalStore.put(fnum_key, mod) end) end) diff --git a/lib/protobuf/extension/props.ex b/lib/protobuf/extension/props.ex index efef629b..6b9947ac 100644 --- a/lib/protobuf/extension/props.ex +++ b/lib/protobuf/extension/props.ex @@ -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 diff --git a/lib/protobuf/field_props.ex b/lib/protobuf/field_props.ex index 693bed75..88581f22 100644 --- a/lib/protobuf/field_props.ex +++ b/lib/protobuf/field_props.ex @@ -17,7 +17,8 @@ defmodule Protobuf.FieldProps do packed?: boolean, map?: boolean, deprecated?: boolean, - encoded_fnum: iodata + encoded_fnum: iodata, + options: List.t() | nil } defstruct fnum: nil, name: nil, @@ -34,5 +35,6 @@ defmodule Protobuf.FieldProps do packed?: nil, map?: false, deprecated?: false, - encoded_fnum: nil + encoded_fnum: nil, + options: nil end diff --git a/lib/protobuf/message_props.ex b/lib/protobuf/message_props.ex index 5afbbe9d..9fe6166a 100644 --- a/lib/protobuf/message_props.ex +++ b/lib/protobuf/message_props.ex @@ -6,7 +6,7 @@ defmodule Protobuf.MessageProps do @type t :: %__MODULE__{ ordered_tags: [integer], tags_map: %{integer => integer}, - field_props: %{integer => FieldProps.T}, + field_props: %{integer => FieldProps.t()}, field_tags: %{atom => integer}, repeated_fields: [atom], embedded_fields: [atom], diff --git a/lib/protobuf/protoc/cli.ex b/lib/protobuf/protoc/cli.ex index 58a15047..f4a9d3aa 100644 --- a/lib/protobuf/protoc/cli.ex +++ b/lib/protobuf/protoc/cli.ex @@ -66,6 +66,11 @@ defmodule Protobuf.Protoc.CLI do parse_params(ctx, t) end + def parse_params(ctx, ["using_module=" <> using_module | t]) do + ctx = %{ctx | using_module: using_module} + parse_params(ctx, t) + end + def parse_params(ctx, _), do: ctx @doc false diff --git a/lib/protobuf/protoc/context.ex b/lib/protobuf/protoc/context.ex index 9c7dacbf..ce0e4fb2 100644 --- a/lib/protobuf/protoc/context.ex +++ b/lib/protobuf/protoc/context.ex @@ -29,7 +29,10 @@ defmodule Protobuf.Protoc.Context do gen_descriptors?: false, # Elixirpb.FileOptions - custom_file_options: %{} + custom_file_options: %{}, + + # Allow custom code injection + using_module: "Protobuf" def cal_file_options(ctx, nil) do %{ctx | custom_file_options: %{}, module_prefix: ctx.package || ""} diff --git a/lib/protobuf/protoc/generator/enum.ex b/lib/protobuf/protoc/generator/enum.ex index 9c3dd4b6..d258d5f5 100644 --- a/lib/protobuf/protoc/generator/enum.ex +++ b/lib/protobuf/protoc/generator/enum.ex @@ -13,7 +13,14 @@ defmodule Protobuf.Protoc.Generator.Enum do generate_desc = if ctx.gen_descriptors?, do: desc, else: nil type = generate_type(desc.value) - Protobuf.Protoc.Template.enum(msg_name, msg_opts(ctx, desc), fields, type, generate_desc) + Protobuf.Protoc.Template.enum( + msg_name, + msg_opts(ctx, desc), + fields, + type, + generate_desc, + ctx.using_module + ) end def generate_type(fields) do diff --git a/lib/protobuf/protoc/generator/extension.ex b/lib/protobuf/protoc/generator/extension.ex index 8ce1d2b7..e5f19d99 100644 --- a/lib/protobuf/protoc/generator/extension.ex +++ b/lib/protobuf/protoc/generator/extension.ex @@ -24,7 +24,7 @@ defmodule Protobuf.Protoc.Generator.Extension do else name = Util.trans_name(@ext_postfix) msg_name = Util.mod_name(ctx, ns ++ [name]) - Protobuf.Protoc.Template.extension(msg_name, msg_opts(ctx, desc), extends) + Protobuf.Protoc.Template.extension(msg_name, msg_opts(ctx, desc), extends, ctx.using_module) end end diff --git a/lib/protobuf/protoc/generator/message.ex b/lib/protobuf/protoc/generator/message.ex index 33d631a5..1365c28d 100644 --- a/lib/protobuf/protoc/generator/message.ex +++ b/lib/protobuf/protoc/generator/message.ex @@ -34,7 +34,8 @@ defmodule Protobuf.Protoc.Generator.Message do fields: fields, oneofs: oneofs_str(desc.oneof_decl), desc: generate_desc, - extensions: extensions + extensions: extensions, + using_module: ctx.using_module } end @@ -47,7 +48,8 @@ defmodule Protobuf.Protoc.Generator.Message do msg_struct[:oneofs], gen_fields(syntax, msg_struct[:fields]), msg_struct[:desc], - gen_extensions(msg_struct[:extensions]) + gen_extensions(msg_struct[:extensions]), + msg_struct[:using_module] ) end @@ -179,6 +181,7 @@ defmodule Protobuf.Protoc.Generator.Message do def get_field(ctx, f, nested_maps, oneofs) do opts = field_options(f) map = nested_maps[f.type_name] + opts = if map, do: Map.put(opts, :map, true), else: opts opts = @@ -288,8 +291,22 @@ defmodule Protobuf.Protoc.Generator.Message do end defp merge_field_options(opts, f) do + custom_options = + f.options + |> Google.Protobuf.FieldOptions.get_extension(Brex.Elixirpb.PbExtension, :field) + |> case do + nil -> + nil + + elixir_field_options -> + elixir_field_options + |> Map.from_struct() + |> Enum.into([]) + end + opts |> Map.put(:packed, f.options.packed) |> Map.put(:deprecated, f.options.deprecated) + |> Map.put(:options, custom_options) end end diff --git a/lib/protobuf/protoc/generator/util.ex b/lib/protobuf/protoc/generator/util.ex index a9ac87a5..84ddb41e 100644 --- a/lib/protobuf/protoc/generator/util.ex +++ b/lib/protobuf/protoc/generator/util.ex @@ -49,5 +49,6 @@ defmodule Protobuf.Protoc.Generator.Util do end def print(v) when is_atom(v), do: inspect(v) + def print(v) when is_list(v), do: inspect(v) def print(v), do: v end diff --git a/lib/protobuf/protoc/template.ex b/lib/protobuf/protoc/template.ex index 2a07c454..bdbcba9f 100644 --- a/lib/protobuf/protoc/template.ex +++ b/lib/protobuf/protoc/template.ex @@ -11,11 +11,25 @@ defmodule Protobuf.Protoc.Template do :def, :message, @msg_tmpl, - [:name, :options, :struct_fields, :typespec, :oneofs, :fields, :desc, :extensions], + [ + :name, + :options, + :struct_fields, + :typespec, + :oneofs, + :fields, + :desc, + :extensions, + :using_module + ], trim: true ) - EEx.function_from_file(:def, :enum, @enum_tmpl, [:name, :options, :fields, :type, :desc], + EEx.function_from_file( + :def, + :enum, + @enum_tmpl, + [:name, :options, :fields, :type, :desc, :using_module], trim: true ) @@ -23,5 +37,7 @@ defmodule Protobuf.Protoc.Template do trim: true ) - EEx.function_from_file(:def, :extension, @ext_tmpl, [:name, :options, :extends], trim: true) + EEx.function_from_file(:def, :extension, @ext_tmpl, [:name, :options, :extends, :using_module], + trim: true + ) end diff --git a/priv/templates/enum.ex.eex b/priv/templates/enum.ex.eex index 690695ad..d38080f0 100644 --- a/priv/templates/enum.ex.eex +++ b/priv/templates/enum.ex.eex @@ -1,7 +1,7 @@ defmodule <%= name %> do @moduledoc false - use Protobuf<%= options %> - + use <%= using_module %><%= options %> + <%= type %> <%= if not is_nil(desc) do %> diff --git a/priv/templates/extension.ex.eex b/priv/templates/extension.ex.eex index 3c29c173..da77f9bf 100644 --- a/priv/templates/extension.ex.eex +++ b/priv/templates/extension.ex.eex @@ -1,6 +1,6 @@ defmodule <%= name %> do @moduledoc false - use Protobuf<%= options %> + use <%= using_module %><%= options %> <%= for ext <- extends do %> extend <%= ext %> <% end %> diff --git a/priv/templates/message.ex.eex b/priv/templates/message.ex.eex index 9f0aa76f..9e5c970d 100644 --- a/priv/templates/message.ex.eex +++ b/priv/templates/message.ex.eex @@ -1,6 +1,6 @@ defmodule <%= name %> do @moduledoc false - use Protobuf<%= options %> + use <%= using_module %><%= options %> <%= typespec %> defstruct [<%= struct_fields %>] diff --git a/src/brex_elixirpb.proto b/src/brex_elixirpb.proto new file mode 100644 index 00000000..233c5f6a --- /dev/null +++ b/src/brex_elixirpb.proto @@ -0,0 +1,22 @@ +syntax = "proto2"; + +package brex.elixirpb; +import "google/protobuf/descriptor.proto"; + +// Defines an extension to specify the elixir type generated for the given field. + +// For example, +// google.protobuf.StringValue my_string = 1 [(brex.elixirpb.field).extype="String.t"]; + +// To compile +//protoc --plugin=/Users/lizard/Projects/Work/git/brex/protobuf-elixir/protoc-gen-elixir --proto_path=lib --elixir_out=lib brex_elixirpb.proto + +message FieldOptions { + // Specify an elixir type to generate for this field. This will override usual type. + optional string extype = 1; +} + +extend google.protobuf.FieldOptions { + // Note: number to change + optional FieldOptions field = 65007; +} \ No newline at end of file diff --git a/test/protobuf/dsl_test.exs b/test/protobuf/dsl_test.exs index 12b7e254..8b0e7fbb 100644 --- a/test/protobuf/dsl_test.exs +++ b/test/protobuf/dsl_test.exs @@ -223,4 +223,9 @@ defmodule Protobuf.DSLTest do assert msg_props.field_props[4].oneof == 1 refute msg_props.field_props[5].oneof end + + test "Extension use case" do + msg_props = TestMsg.Ext.UseCase.__message_props__() + assert %Protobuf.FieldProps{options: [extype: "String.t"]} = msg_props.field_props[1] + end end diff --git a/test/protobuf/protoc/generator/message_test.exs b/test/protobuf/protoc/generator/message_test.exs index 605b1780..8bb2b9f4 100644 --- a/test/protobuf/protoc/generator/message_test.exs +++ b/test/protobuf/protoc/generator/message_test.exs @@ -173,7 +173,42 @@ defmodule Protobuf.Protoc.Generator.MessageTest do assert msg =~ "field :a, 1, optional: true, type: :int32, deprecated: true\n" end - test "generete/2 supports message type field" do + test "generate/2 supports extensions on field options" do + ctx = %Context{package: ""} + + field_opts = Google.Protobuf.FieldOptions.new() + custom_opts = Brex.Elixirpb.FieldOptions.new(extype: "String.t") + + opts = + Google.Protobuf.FieldOptions.put_extension( + field_opts, + Brex.Elixirpb.PbExtension, + :field, + custom_opts + ) + + desc = + Google.Protobuf.DescriptorProto.new( + name: "Foo", + field: [ + Google.Protobuf.FieldDescriptorProto.new( + name: "my_string", + number: 1, + type: :TYPE_MESSAGE, + # type_name: ".google.protodescbuf.StringValue", + label: :LABEL_OPTIONAL, + options: opts + ) + ] + ) + + {[], [msg]} = Generator.generate(ctx, desc) + + assert msg =~ + "field :my_string, 1, optional: true, type: :message, options: [extype: \"String.t\"]\n" + end + + test "generate/2 supports message type field" do ctx = %Context{ package: "", dep_type_mapping: %{ diff --git a/test/support/test_msg.ex b/test/support/test_msg.ex index 9d60154d..27d8f515 100644 --- a/test/support/test_msg.ex +++ b/test/support/test_msg.ex @@ -212,4 +212,16 @@ defmodule TestMsg do extend Ext.Foo2, :bar, 1047, optional: true, type: :string extend Ext.Foo1, :"Parent.foo", 1048, optional: true, type: Ext.EnumFoo, enum: true end + + defmodule Ext.UseCase do + @moduledoc false + use Protobuf, syntax: :proto3 + + @type t :: %__MODULE__{ + my_string: Google.Protobuf.StringValue.t() | nil + } + defstruct [:my_string] + + field :my_string, 1, type: Google.Protobuf.StringValue, options: [extype: "String.t"] + end end