ahamez / protox

A fast, easy to use and 100% conformant Elixir library for Google Protocol Buffers (aka protobuf)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

feature request: ability to inject definition into existing module

ananthakumaran opened this issue · comments

exprotobuf currently supports this feature via inject & only option. I am currently experimenting with some database library where ability to inject defstruct/encode/decode into existing module would make the api cleaner.

Protox provides the namespace option:

defmodule Bar do
  use Protox, schema: """
    syntax = "proto3";

    enum Enum {
        FOO = 0;
        BAR = 1;
      }
    """,
    namespace: Namespace
end

Isn't it what you're looking for?

no, the namespace will create a nested module

defmodule Bar
  defmodule Namespace do
    defstruct [..]
  end
end

inject directly injects the defstruct into the calling module.

something like

defmodule Enum do
  use Protox, schema: """
    syntax = "proto3";

    enum Enum {
        FOO = 0;
        BAR = 1;
      }
    """,
    inject: true
end

it will inject the defstruct and other methods directly into the calling module without creating nested module using defmodule

Actually, the following code

defmodule Dummy do
  use Protox, schema: """
  syntax = "proto3";

  message Foo {
    int32 a = 1;
  }
  """,
  namespace: Namespace
end

produces

defmodule Namespace do
  defmodule Foo do
    defstruct [...]
  end
end

Thus, Dummy is completely ignored (I should make it clear in the documentation) and Foo is "injected" into Namespace.

If you don't use the namespace option, then the code is directly "injected" in the global namespace and you would end up with:

defmodule Foo do
  defstruct [...]
end

Does it answer your question?

Let me try with an example. For the schema

  message Foo {
    int32 a = 1;
  }

What I want is the ability to add extra code inside the generated Foo module.

defmodule Foo do
  # added by protox
  defstruct [...]
  def encode(.)
  def decode(.)

  # added by user
  def bar() do
  end
end

with the proposed inject future, I would be able to do

defmodule Foo do
  use Protox, schema: "..", inject: true

  # added by user
  def bar() do
  end
end

I would be able to call Foo.bar(). From user perspective, the defstruct is injected by the protox. If protox always generates defmodule Foo, there is no way for a user to add extra functions to the generated module.

OK, I understand now. To be honest, I discarded this possibility right from the beginning as I felt it was a lot of work just to avoid writing a new module:

defmodule Dummy do
  use Protox, schema: """
  syntax = "proto3";

  message Foo {
    int32 a = 1;
  }
  """
end

defmodule WorkWithFoo do
  def bar() do
  end
end

Also, I don't like what it implies in terms of renaming messages names:

defmodule Bar do
  use Protox, schema: """
  syntax = "proto3";
  message Foo {
    int32 a = 1;
  }
  """,
  inject: true
end

Suddenly, Foo becomes Bar just because it has been "injected" into Bar. Someone looking at the protobuf definition files won't find the messages he's looking for.

I realise it's more or less the debate of "inheritance vs composition". I usually strongly favour the latest.

That being said, I'll welcome any PR that brings this functionality :-)

Those are good points, I am also not sure how well it would work with Enums, Nested msgs etc.

But for some use cases, API wise, it would be ideal to add extra methods to the generated module instead of using a different module, otherwise one would have to come up with a new name for the same thing.

defmodule Friends.Person do
  use Ecto.Schema

  schema "people" do
    field :first_name, :string
    field :last_name, :string
    field :age, :integer
  end

  def changeset(person, params \\ %{}) do
    person
    |> Ecto.Changeset.cast(params, ~w(first_name last_name age))
    |> Ecto.Changeset.validate_required([:first_name, :last_name])
  end
end

Here for example, casting/validation logic are kept together