Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support annotating fields with field extensions #94

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
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
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
2 changes: 2 additions & 0 deletions lib/protobuf/extension.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)
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
6 changes: 4 additions & 2 deletions lib/protobuf/field_props.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -34,5 +35,6 @@ defmodule Protobuf.FieldProps do
packed?: nil,
map?: false,
deprecated?: false,
encoded_fnum: nil
encoded_fnum: nil,
options: nil
end
2 changes: 1 addition & 1 deletion lib/protobuf/message_props.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
5 changes: 5 additions & 0 deletions lib/protobuf/protoc/cli.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion lib/protobuf/protoc/context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 || ""}
Expand Down
9 changes: 8 additions & 1 deletion lib/protobuf/protoc/generator/enum.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/protobuf/protoc/generator/extension.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 19 additions & 2 deletions lib/protobuf/protoc/generator/message.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions lib/protobuf/protoc/generator/util.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
22 changes: 19 additions & 3 deletions lib/protobuf/protoc/template.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,33 @@ 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
)

EEx.function_from_file(:def, :service, @svc_tmpl, [:mod_name, :name, :methods, :desc],
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
4 changes: 2 additions & 2 deletions priv/templates/enum.ex.eex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule <%= name %> do
@moduledoc false
use Protobuf<%= options %>
use <%= using_module %><%= options %>

<%= type %>

<%= if not is_nil(desc) do %>
Expand Down
2 changes: 1 addition & 1 deletion priv/templates/extension.ex.eex
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
defmodule <%= name %> do
@moduledoc false
use Protobuf<%= options %>
use <%= using_module %><%= options %>

<%= for ext <- extends do %> extend <%= ext %>
<% end %>
Expand Down
2 changes: 1 addition & 1 deletion priv/templates/message.ex.eex
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
defmodule <%= name %> do
@moduledoc false
use Protobuf<%= options %>
use <%= using_module %><%= options %>

<%= typespec %>
defstruct [<%= struct_fields %>]
Expand Down
22 changes: 22 additions & 0 deletions src/brex_elixirpb.proto
Original file line number Diff line number Diff line change
@@ -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;
}
5 changes: 5 additions & 0 deletions test/protobuf/dsl_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
37 changes: 36 additions & 1 deletion test/protobuf/protoc/generator/message_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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: %{
Expand Down
12 changes: 12 additions & 0 deletions test/support/test_msg.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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