Giter Site home page Giter Site logo

exprotobuf's Introduction

Protocol Buffers for Elixir

exprotobuf works by building module/struct definitions from a Google Protocol Buffer schema. This allows you to work with protocol buffers natively in Elixir, with easy decoding/encoding for transport across the wire.

Build Status Hex.pm Version

Features

  • Load protobuf from file or string
  • Respects the namespace of messages
  • Allows you to specify which modules should be loaded in the definition of records
  • Currently uses gpb for protobuf schema parsing

TODO:

  • Clean up code/tests

Breaking Changes

The 1.0 release removed the feature of handling import "..."; statements. Please see the imports upgrade guide for details if you were using this feature.

Getting Started

Add exprotobuf as a dependency to your project:

defp deps do
  [{:exprotobuf, "~> x.x.x"}]
end

Then run mix deps.get to fetch.

Add exprotobuf to applications list:

def application do
  [applications: [:exprotobuf]]
end

Usage

Usage of exprotobuf boils down to a single use statement within one or more modules in your project.

Let's start with the most basic of usages:

Define from a string

defmodule Messages do
  use Protobuf, """
    message Msg {
      message SubMsg {
        required uint32 value = 1;
      }

      enum Version {
        V1 = 1;
        V2 = 2;
      }

      required Version version = 2;
      optional SubMsg sub = 1;
    }
  """
end
iex> msg = Messages.Msg.new(version: :'V2')
%Messages.Msg{version: :V2, sub: nil}
iex> encoded = Messages.Msg.encode(msg)
<<16, 2>>
iex> Messages.Msg.decode(encoded)
%Messages.Msg{version: :V2, sub: nil}

The above code takes the provided protobuf schema as a string, and generates modules/structs for the types it defines. In this case, there would be a Msg module, containing a SubMsg and Version module. The properties defined for those values are keys in the struct belonging to each. Enums do not generate structs, but a specialized module with two functions: atom(x) and value(x). These will get either the name of the enum value, or it's associated value.

Values defined in the schema using the oneof construct are represented with tuples:

defmodule Messages do
  use Protobuf, """
    message Msg {
      oneof choice {
        string first = 1;
        int32 second = 2;
      }
    }
  """
end
iex> msg = Messages.Msg.new(choice: {:second, 42})
%Messages.Msg{choice: {:second, 42}}
iex> encoded = Messages.Msg.encode(msg)
<<16, 42>>

Define from a file

defmodule Messages do
  use Protobuf, from: Path.expand("../proto/messages.proto", __DIR__)
end

This is equivalent to the above, if you assume that messages.proto contains the same schema as in the string of the first example.

Loading all definitions from a set of files

defmodule Protobufs do
  use Protobuf, from: Path.wildcard(Path.expand("../definitions/**/*.proto", __DIR__))
end
iex> Protobufs.Msg.new(v: :V1)
%Protobufs.Msg{v: :V1}
iex> %Protobufs.OtherMessage{middle_name: "Danger"}
%Protobufs.OtherMessage{middle_name: "Danger"}

This will load all the various definitions in your .proto files and allow them to share definitions like enums or messages between them.

Customizing Generated Module Names

In some cases your library of protobuf definitions might already contain some namespaces that you would like to keep. In this case you will probably want to pass the use_package_names: true option. Let's say you had a file called protobufs/example.proto that contained:

package world;
message Example {
  enum Continent {
    ANTARCTICA = 0;
    EUROPE = 1;
  }

  optional Continent continent = 1;
  optional uint32 id = 2;
}

You could load that file (and everything else in the protobufs directory) by doing:

defmodule Definitions do
  use Protobuf, from: Path.wildcard("protobufs/*.proto"), use_package_names: true
end
iex> Definitions.World.Example.new(continent: :EUROPE)
%Definitions.World.Example{continent: :EUROPE}

You might also want to define all of these modules in the top-level namespace. You can do this by passing an explicit namespace: :"Elixir" option.

defmodule Definitions do
  use Protobuf, from: Path.wildcard("protobufs/*.proto"),
                use_package_names: true,
                namespace: :"Elixir"
end
iex> World.Example.new(continent: :EUROPE)
%World.Example{continent: :EUROPE}

Now you can use just the package names and message names that your team is already familiar with.

Inject a definition into an existing module

This is useful when you only have a single type, or if you want to pull the module definition into the current module instead of generating a new one.

defmodule Msg do
  use Protobuf, from: Path.expand("../proto/messages.proto", __DIR__), inject: true

  def update(msg, key, value), do: Map.put(msg, key, value)
end
iex> %Msg{}
%Msg{v: :V1}
iex> Msg.update(%Msg{}, :v, :V2)
%Msg{v: :V2}

As you can see, Msg is no longer created as a nested module, but is injected right at the top level. I find this approach to be a lot cleaner than use_in, but may not work in all use cases.

Inject a specific type from a larger subset of types

When you have a large schema, but perhaps only care about a small subset of those types, you can use :only:

defmodule Messages do
  use Protobuf, from: Path.expand("../proto/messages.proto", __DIR__),
only: [:TypeA, :TypeB]
end

Assuming that the provided .proto file contains multiple type definitions, the above code would extract only TypeA and TypeB as nested modules. Keep in mind your dependencies, if you select a child type which depends on a parent, or another top-level type, exprotobuf may fail, or your code may fail at runtime.

You may only combine :only with :inject when :only is a single type, or a list containing a single type. This is due to the restriction of one struct per module. Theoretically you should be able to pass :only with multiple types, as long all but one of the types is an enum, since enums are just generated as modules, this does not currently work though.

Extend generated modules via use_in

If you need to add behavior to one of the generated modules, use_in will help you. The tricky part is that the struct for the module you use_in will not be defined yet, so you can't rely on it in your functions. You can still work with the structs via the normal Maps API, but you lose compile-time guarantees. I would recommend favoring :inject over this when possible, as it's a much cleaner solution.

defmodule Messages do
  use Protobuf, "
    message Msg {
      enum Version {
        V1 = 1;
        V2 = 1;
      }
      required Version v = 1;
    }
  "

  defmodule MsgHelpers do
    defmacro __using__(_opts) do
      quote do
        def convert_to_record(msg) do
          msg
          |> Map.to_list
          |> Enum.reduce([], fn {_key, value}, acc -> [value | acc] end)
          |> Enum.reverse
          |> list_to_tuple
        end
      end
    end
  end

  use_in "Msg", MsgHelpers
end
iex> Messages.Msg.new |> Messages.Msg.convert_to_record
{Messages.Msg, :V1}

Attribution/License

exprotobuf is a fork of the azukiaapp/elixir-protobuf project, both of which are released under Apache 2 License.

Check LICENSE files for more information.

exprotobuf's People

Contributors

ahamez avatar amatalai avatar apelsinka223 avatar bitwalker avatar efcasado avatar koudelka avatar kwojtaszek avatar lowks avatar mbilokonsky avatar mehonoshin avatar minhajuddin avatar mmmries avatar moperacz avatar mr-bt avatar osdrv avatar pallix avatar paulswartz avatar pcewing avatar peburrows avatar rradz avatar tim2cf avatar tony612 avatar trevors avatar wildstrings avatar xinz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

exprotobuf's Issues

Proto3 support

Hey, I think exprotobuf does not support proto3, but I might be wrong. In any case, noting the supported version that in the README would be great!

Thanks!

PS: I guess I should mention that I'd like proto 3 support :)

Can't parse `syntax` field (see thread for more details)

I'm not sure where this bug lives, exactly, but it looks like maybe :gpb is throwing up when attempting to parse this file?

https://developers.google.com/transit/gtfs-realtime/gtfs-realtime-proto

I pulled down that file, stuck it into /priv, and wrote the following module definition:

defmodule GTFS do use Protobuf, from: "priv/gtfs-realtime.proto" end

But when I try to compile my code, I get this error:

== Compilation error on file lib/tap/protobuf/GTFS.ex ==
** (ArgumentError) argument error
    :erlang.byte_size({25, :gpb_parse, ['syntax error before: ', 'syntax']})
    (elixir) lib/exception.ex:106: Exception.format_banner/3
    (elixir) lib/exception.ex:141: Exception.format/3
    (elixir) lib/kernel/parallel_compiler.ex:216: Kernel.ParallelCompiler.print_failure/2
    (elixir) lib/kernel/parallel_compiler.ex:189: Kernel.ParallelCompiler.handle_failure/5
    (elixir) lib/kernel/parallel_compiler.ex:182: Kernel.ParallelCompiler.wait_for_messages/8
    (elixir) lib/kernel/parallel_compiler.ex:55: Kernel.ParallelCompiler.spawn_compilers/3
    (mix) lib/mix/compilers/elixir.ex:112: Mix.Compilers.Elixir.compile_manifest/5

Frustratingly, when I call protoc on that file from the command line to generate a .desc file it works just fine - so I know it's not a malformed input.proto file.

Any idea what could be causing this?

nested package support?

I'm trying to test out exprotobuf with an existing large schema that has packages such as:

package my_package.pb.person;

message Employee {
...
}

I can't seem to figure out how you'd call new on a fully qualified Employee object. I can call Employee.new and that seems to work but after looking into normalize_name and running some local tests it appears to output (for the above package example)
my_package == My_package
pb == Pb
person == Person

So I thought I could use: My_package.Pb.Person.Employee.new but it doesn't work. This could be a misunderstanding of Elixir though as I'm new to it as well.

Handling imports

We have defined a base.proto file:

package chat;

option java_package = "com.appunite.chat";

import "authorization.proto";

// From server to client
message WebsocketServerContainer {
      required authorization.AuthorizationServerMessage authorization = 1;
}

also additional file authorization.proto file:

package authorization;

option java_package = "com.appunite.chat";

// Message that is sent via server if authorization fail (sent via HTTP)
message WrongAuthorizationHttpMessage {
    required string reason = 1; // reason why authorization fail
}

// Message that is sent via server if authorization fail
message AuthorizationServerMessage {
    required string next_synchronization_token = 1; // token that should be sent to server next time synchronization occure
}

It looks like exprotobuf doesn't support for imports at all, because on compilation time it returns:

** (ArgumentError) argument error
    :erlang.byte_size([ref_to_undefined_msg_or_enum: {{[:., :WebsocketServerContainer], :authorization}, [:authorization, :., :AuthorizationServerMessage]}])
    (elixir) lib/exception.ex:106: Exception.format_banner/3
    (elixir) lib/exception.ex:141: Exception.format/3
    (elixir) lib/kernel/parallel_compiler.ex:216: Kernel.ParallelCompiler.print_failure/2
    (elixir) lib/kernel/parallel_compiler.ex:189: Kernel.ParallelCompiler.handle_failure/5
    (elixir) lib/kernel/parallel_compiler.ex:182: Kernel.ParallelCompiler.wait_for_messages/8
    (elixir) lib/kernel/parallel_compiler.ex:55: Kernel.ParallelCompiler.spawn_compilers/3
    (mix) lib/mix/compilers/elixir.ex:112: Mix.Compilers.Elixir.compile_manifest/5

Representation of oneof with tuples

The current representation of oneof field use tuples. This is problematic when using the Poison library to serialize a Protobuf structure to JSON: tuples cannot be converted to JSON (only arrays, maps etc.).

Would it make sense and would it be possible to use an array (possibly optionally if it breaks code) to represent oneof fields? This would allow a great interoperability between exprotobuf and JSON.

Can't build as a dependency in another project.

When I add exprotobuf and gpb as sole dependencies to a project and try to compile the dependencies, gpb compiles fine while exprotobuf fails to compile with the following error.

trevor.riles@cake:~/code/crash_bot/ > mix deps.compile
==> gpb (compile)
Compiled src/gpb.erl
==> exprotobuf
Compiled lib/exprotobuf/define_enum.ex

== Compilation error on file lib/exprotobuf/field.ex ==
** (ArgumentError) no record field found at /Users/trevor.riles/code/crash_bot/deps/gpb/include/gpb.hrl
    (elixir) lib/record/extractor.ex:38: Record.Extractor.extract_record/2
    lib/exprotobuf/field.ex:2: (module)
    (stdlib) erl_eval.erl:657: :erl_eval.do_apply/6

could not compile dependency exprotobuf, mix compile failed. You can recompile this dependency with `mix deps.compile exprotobuf` or update it with `mix deps.update exprotobuf`

Here is my mix.exs file for reference:

defmodule CrashBot.Mixfile do
  use Mix.Project

  def project do
    [app: :crash_bot,
     version: "0.0.1",
     elixir: "~> 1.0",
     deps: deps]
  end

  # Configuration for the OTP application
  #
  # Type `mix help compile.app` for more information
  def application do
    [applications: [:logger]]
  end

  # Dependencies can be Hex packages:
  #
  #   {:mydep, "~> 0.3.0"}
  #
  # Or git/path repositories:
  #
  #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
  #
  # Type `mix help deps` for more examples and options
  defp deps do
    [{:exprotobuf, github: "bitwalker/exprotobuf"},
     {:gpb, github: "tomas-abrahamsson/gpb"}]
  end
end

package not included in module namespace

I'm not sure if this is a bug or feature request or if I'm just misunderstanding how this is supposed to work but in testing it seems like this library doesn't include the package name in the module namespace. I have the following test to explain what I mean:

defmodule PackageNamespaceTest do
  use ExUnit.Case

  defmodule Proto do
    use Protobuf, """
      package TopLevel;
      message Msg {
        optional string name = 1;
      }
    """
  end

  test "it includes the package in the module name" do
    msg = Proto.TopLevel.Msg.new(name: "Ron")
    assert msg == %Proto.TopLevel.Msg{name: "Ron"}
  end
end

As an aside if this is not easy to make it work this way is there any underlying erlang code or something I can make use of for encoding/decoding based on the name TopLevel.Msg?

Using inject breaks enumerations with proto3

When using inject alongside enumerations, the namespace of the enumeration modules is inconsistent, causing the call to get_default to error with a no match, coming from here.

Reference protobuf

syntax = "proto3";

message Message {
  enum Status {
    STATE_1 = 0;
    STATE_2 = 1;
  }

  required int64 id = 1;
  optional Status status = 2;
}

Example

Module

defmodule MessageTest do
  use Protobuf, from: Path.expand("priv/protos/message.proto"), inject: true, only: :Message
end

Defs

iex(1)> MessageTest.defs()
[{{:enum, :"Message.Status"}, [STATE_1: 0, STATE_2: 1]},
 {{:msg, :Message},
  [%Protobuf.Field{fnum: 1, name: :id, occurrence: :required, opts: [], rnum: 2,
    type: :int64},
   %Protobuf.Field{fnum: 2, name: :status, occurrence: :optional, opts: [],
    rnum: 3, type: {:enum, MessageTest.Message.Status}}]}]

Because of mismatch of the naming (MessageTest.Message.Status in the field vs :enum, :"Message.Status" in the key), when the field is looked up in proto3_type_default, lists:keyfind/3 returns false, rather than a match.

I was able to circumvent this by adding use_module_namespace: true, unfortunately this has the side effect of ignoring the inject and only option, as there is no function clause that takes it into account

When not using inject, the field defs are generated as expected

Example

Module

defmodule MessageTest do
  use Protobuf, from: Path.expand("priv/protos/message.proto")
end

Defs

iex(1)> MessageTest.defs()
[{{:enum, MessageTest.Message.Status}, [STATE_1: 0, STATE_2: 1]},
 {{:msg, MessageTest.Message},
  [%Protobuf.Field{fnum: 1, name: :id, occurrence: :required, opts: [], rnum: 2,
    type: :int64},
   %Protobuf.Field{fnum: 2, name: :status, occurrence: :optional, opts: [],
    rnum: 3, type: {:enum, MessageTest.Message.Status}}]}]

I'm happy to make a pr solving this issue, but would like to get some feedback on the direction to take with this before making a pr.

"warning: this clause cannot match" with elixir 1.4.4

Hi,

warning: this clause cannot match because a previous clause at line 1 always matches lib/messages.ex:1 appears when compiling with Elixir 1.4.4. With 1.4.2 there's no such message.

lib/messages.ex contains only:

defmodule Extreme.Msg do
  use Protobuf, from: Path.expand("../include/event_store.proto", __DIR__)
end

and event_store.proto file is: https://github.com/exponentially/extreme/blob/master/include/event_store.proto

It looks as enum definition causes this warning. (there are 11 of them for this file). Do you have any idea what's wrong?

Thanks

Oneof string decoding into character list

I have defined the structure as:

message Msg {      
        oneof x_present {
                                       string x = 2;
        }
}

When i create new message for this structure as a string the decoding is returning the character list

iex(3)> msg = Messages.Msg.new(x_present: {:x, “aaa”})
%ProtoExample.Messages.Msg{x_present: {:x, “aaa”}}

iex(5)> msg |> Msg.encode |> Msg.decode
%ProtoExample.Messages.Msg{x_present: {:x, ‘aaa’}}

Is this right behavior or a bug?

got a incorrect encode result

For example:
message Test1 {
required int32 a = 1;
}
Expect to got <08 96 01>, but got<<8, 150, 1> instead. Did I missed something?

undefined function: :gpb_parse.post_process/2

Hi, i'm having this issue with exprotobuf 0.8.5 and the mumble proto:

== Compilation error on file lib/opnd/mumble/protocol.ex ==
** (UndefinedFunctionError) undefined function: :gpb_parse.post_process/2
    :gpb_parse.post_process([{:package, [:MumbleProto]}, {:option, [:optimize_for], :SPEED}, {{:msg, :Version}, [{:field, :version, 1, :undefined, :uint32, :optional, []}, {:field, :release, 2, :undefined, :string, :optional, []}, {:field, :os, 3, :undefined, :string, :optional, []}, {:field, :os_version, 4, :undefined, :string, :optional, []}]}, {{:msg, :UDPTunnel}, [{:field, :packet, 1, :undefined, :bytes, :required, []}]}, {{:msg, :Authenticate}, [{:field, :username, 1, :undefined, :string, :optional, []}, {:field, :password, 2, :undefined, :string, :optional, []}, {:field, :tokens, 3, :undefined, :string, :repeated, []}, {:field, :celt_versions, 4, :undefined, :int32, :repeated, []}, {:field, :opus, 5, :undefined, :bool, :optional, [default: false]}]}, {{:msg, :Ping}, [{:field, :timestamp, 1, :undefined, :uint64, :optional, []}, {:field, :good, 2, :undefined, :uint32, :optional, []}, {:field, :late, 3, :undefined, :uint32, :optional, []}, {:field, :lost, 4, :undefined, :uint32, :optional, []}, {:field, :resync, 5, :undefined, :uint32, :optional, []}, {:field, :udp_packets, 6, :undefined, :uint32, :optional, []}, {:field, :tcp_packets, 7, :undefined, :uint32, :optional, []}, {:field, :udp_ping_avg, 8, :undefined, :float, :optional, []}, {:field, :udp_ping_var, 9, :undefined, :float, :optional, []}, {:field, :tcp_ping_avg, 10, :undefined, :float, :optional, []}, {:field, :tcp_ping_var, 11, :undefined, :float, :optional, []}]}, {{:msg, :Reject}, [{{:enum, :RejectType}, [None: 0, WrongVersion: 1, InvalidUsername: 2, WrongUserPW: 3, WrongServerPW: 4, UsernameInUse: 5, ServerFull: 6, NoCertificate: 7, AuthenticatorFail: 8]}, {:field, :type, 1, :undefined, {:ref, [:RejectType]}, :optional, []}, {:field, :reason, 2, :undefined, :string, :optional, []}]}, {{:msg, :ServerSync}, [{:field, :session, 1, :undefined, :uint32, :optional, []}, {:field, :max_bandwidth, 2, :undefined, :uint32, :optional, []}, {:field, :welcome_text, 3, :undefined, :string, :optional, []}, {:field, :permissions, 4, :undefined, :uint64, :optional, []}]}, {{:msg, :ChannelRemove}, [{:field, :channel_id, 1, :undefined, :uint32, :required, []}]}, {{:msg, :ChannelState}, [{:field, :channel_id, 1, :undefined, :uint32, :optional, []}, {:field, :parent, 2, :undefined, :uint32, :optional, []}, {:field, :name, 3, :undefined, :string, :optional, []}, {:field, :links, 4, :undefined, :uint32, :repeated, []}, {:field, :description, 5, :undefined, :string, :optional, []}, {:field, :links_add, 6, :undefined, :uint32, :repeated, []}, {:field, :links_remove, 7, :undefined, :uint32, :repeated, []}, {:field, :temporary, 8, :undefined, :bool, :optional, [default: false]}, {:field, :position, 9, :undefined, :int32, :optional, [default: 0]}, {:field, :description_hash, 10, :undefined, :bytes, :optional, []}]}, {{:msg, :UserRemove}, [{:field, :session, 1, :undefined, :uint32, :required, []}, {:field, :actor, 2, :undefined, :uint32, :optional, []}, {:field, :reason, 3, :undefined, :string, :optional, []}, {:field, :ban, 4, :undefined, :bool, :optional, []}]}, {{:msg, :UserState}, [{:field, :session, 1, :undefined, :uint32, :optional, []}, {:field, :actor, 2, :undefined, :uint32, :optional, []}, {:field, :name, 3, :undefined, :string, :optional, []}, {:field, :user_id, 4, :undefined, :uint32, :optional, []}, {:field, :channel_id, 5, :undefined, :uint32, :optional, []}, {:field, :mute, 6, :undefined, :bool, :optional, []}, {:field, :deaf, 7, :undefined, :bool, :optional, []}, {:field, :suppress, 8, :undefined, :bool, :optional, []}, {:field, :self_mute, 9, :undefined, :bool, :optional, []}, {:field, :self_deaf, 10, :undefined, :bool, :optional, []}, {:field, :texture, 11, :undefined, :bytes, :optional, []}, {:field, :plugin_context, 12, :undefined, :bytes, :optional, []}, {:field, :plugin_identity, 13, :undefined, :string, :optional, []}, {:field, :comment, 14, :undefined, :string, :optional, []}, {:field, :hash, 15, :undefined, :string, :optional, []}, {:field, :comment_hash, 16, :undefined, :bytes, :optional, []}, {:field, :texture_hash, 17, :undefined, :bytes, :optional, []}, {:field, :priority_speaker, 18, :undefined, :bool, :optional, []}, {:field, :recording, 19, :undefined, :bool, :optional, []}]}, {{:msg, :BanList}, [{{:msg, :BanEntry}, [{:field, :address, 1, :undefined, :bytes, :required, []}, {:field, :mask, 2, :undefined, :uint32, :required, []}, {:field, :name, 3, :undefined, :string, :optional, []}, {:field, :hash, 4, :undefined, :string, :optional, []}, {:field, :reason, 5, :undefined, :string, :optional, []}, {:field, :start, 6, :undefined, :string, :optional, []}, {:field, :duration, 7, :undefined, :uint32, :optional, []}]}, {:field, :bans, 1, :undefined, {:ref, [:BanEntry]}, :repeated, []}, {:field, :query, 2, :undefined, :bool, :optional, [default: false]}]}, {{:msg, :TextMessage}, [{:field, :actor, 1, :undefined, :uint32, :optional, []}, {:field, :session, 2, :undefined, :uint32, :repeated, []}, {:field, :channel_id, 3, :undefined, :uint32, :repeated, []}, {:field, :tree_id, 4, :undefined, :uint32, :repeated, []}, {:field, :message, 5, :undefined, :string, :required, []}]}, {{:msg, :PermissionDenied}, [{{:enum, :DenyType}, [Text: 0, Permission: 1, SuperUser: 2, ChannelName: 3, TextTooLong: 4, H9K: 5, TemporaryChannel: 6, MissingCertificate: 7, UserName: 8, ChannelFull: 9, NestingLimit: 10]}, {:field, :permission, 1, :undefined, :uint32, :optional, []}, {:field, :channel_id, 2, :undefined, :uint32, :optional, []}, {:field, :session, 3, :undefined, :uint32, :optional, []}, {:field, :reason, 4, :undefined, :string, :optional, []}, {:field, :type, 5, :undefined, {:ref, [:DenyType]}, :optional, []}, {:field, :name, 6, :undefined, :string, :optional, []}]}, {{:msg, :ACL}, [{{:msg, :ChanGroup}, [{:field, :name, 1, :undefined, :string, :required, []}, {:field, :inherited, 2, :undefined, :bool, :optional, [default: true]}, {:field, :inherit, 3, :undefined, :bool, :optional, [default: true]}, {:field, :inheritable, 4, :undefined, :bool, :optional, [default: true]}, {:field, :add, 5, :undefined, :uint32, :repeated, []}, {:field, :remove, 6, :undefined, :uint32, :repeated, []}, {:field, :inherited_members, 7, :undefined, :uint32, :repeated, []}]}, {{:msg, :ChanACL}, [{:field, :apply_here, 1, :undefined, :bool, :optional, [default: true]}, {:field, :apply_subs, 2, :undefined, :bool, :optional, [default: true]}, {:field, :inherited, 3, :undefined, :bool, :optional, [default: true]}, {:field, :user_id, 4, :undefined, :uint32, :optional, []}, {:field, :group, 5, :undefined, :string, :optional, []}, {:field, :grant, 6, :undefined, :uint32, :optional, []}, {:field, :deny, 7, :undefined, :uint32, :optional, []}]}, {:field, :channel_id, 1, :undefined, :uint32, :required, []}, {:field, :inherit_acls, 2, :undefined, :bool, :optional, [default: true]}, {:field, :groups, 3, :undefined, {:ref, [:ChanGroup]}, :repeated, []}, {:field, :acls, 4, :undefined, {:ref, [:ChanACL]}, :repeated, []}, {:field, :query, 5, :undefined, :bool, :optional, [default: false]}]}, {{:msg, :QueryUsers}, [{:field, :ids, 1, :undefined, :uint32, :repeated, []}, {:field, :names, 2, :undefined, :string, :repeated, []}]}, {{:msg, :CryptSetup}, [{:field, :key, 1, :undefined, :bytes, :optional, []}, {:field, :client_nonce, 2, :undefined, :bytes, :optional, []}, {:field, :server_nonce, 3, :undefined, :bytes, :optional, []}]}, {{:msg, :ContextActionModify}, [{{:enum, :Context}, [Server: 1, Channel: 2, User: 4]}, {{:enum, :Operation}, [Add: 0, Remove: 1]}, {:field, :action, 1, :undefined, :string, :required, []}, {:field, :text, 2, :undefined, :string, :optional, []}, {:field, :context, 3, :undefined, :uint32, :optional, []}, {:field, :operation, 4, :undefined, {:ref, [:Operation]}, :optional, []}]}, {{:msg, :ContextAction}, [{:field, :session, 1, :undefined, :uint32, :optional, []}, {:field, :channel_id, 2, :undefined, :uint32, :optional, []}, {:field, :action, 3, :undefined, :string, :required, []}]}, {{:msg, :UserList}, [{{:msg, :User}, [{:field, :user_id, 1, :undefined, :uint32, :required, []}, {:field, :name, 2, :undefined, :string, :optional, []}, {:field, :last_seen, 3, :undefined, :string, :optional, []}, {:field, :last_channel, 4, :undefined, :uint32, :optional, []}]}, {:field, :users, 1, :undefined, {:ref, [:User]}, :repeated, []}]}, {{:msg, :VoiceTarget}, [{{:msg, :Target}, [{:field, :session, 1, :undefined, :uint32, :repeated, []}, {:field, :channel_id, 2, :undefined, :uint32, :optional, []}, {:field, :group, 3, :undefined, :string, :optional, []}, {:field, :links, 4, :undefined, :bool, :optional, [default: false]}, {:field, :children, 5, :undefined, :bool, :optional, [default: false]}]}, {:field, :id, 1, :undefined, :uint32, :optional, []}, {:field, :targets, 2, :undefined, {:ref, [:Target]}, :repeated, []}]}, {{:msg, :PermissionQuery}, [{:field, :channel_id, 1, :undefined, :uint32, :optional, []}, {:field, :permissions, 2, :undefined, :uint32, :optional, []}, {:field, :flush, 3, :undefined, :bool, :optional, [default: false]}]}, {{:msg, :CodecVersion}, [{:field, :alpha, 1, :undefined, :int32, :required, []}, {:field, :beta, 2, :undefined, :int32, :required, []}, {:field, :prefer_alpha, 3, :undefined, :bool, :required, [default: true]}, {:field, :opus, 4, :undefined, :bool, :optional, [default: false]}]}, {{:msg, :UserStats}, [{{:msg, :Stats}, [{:field, :good, 1, :undefined, :uint32, :optional, []}, {:field, :late, 2, :undefined, :uint32, :optional, []}, {:field, :lost, 3, :undefined, :uint32, :optional, []}, {:field, :resync, 4, :undefined, :uint32, :optional, []}]}, {:field, :session, 1, :undefined, :uint32, :optional, []}, {:field, :stats_only, 2, :undefined, :bool, :optional, [default: false]}, {:field, :certificates, 3, :undefined, :bytes, :repeated, []}, {:field, :from_client, 4, :undefined, {:ref, [:Stats]}, :optional, []}, {:field, :from_server, 5, :undefined, {:ref, [:Stats]}, :optional, []}, {:field, :udp_packets, 6, :undefined, :uint32, :optional, []}, {:field, :tcp_packets, 7, :undefined, :uint32, :optional, []}, {:field, :udp_ping_avg, 8, :undefined, :float, :optional, []}, {:field, :udp_ping_var, 9, :undefined, :float, :optional, []}, {:field, :tcp_ping_avg, 10, :undefined, :float, :optional, []}, {:field, :tcp_ping_var, 11, :undefined, :float, :optional, []}, {:field, :version, 12, :undefined, {:ref, [:Version]}, :optional, []}, {:field, :celt_versions, 13, :undefined, :int32, :repeated, []}, {:field, :address, 14, :undefined, :bytes, :optional, []}, {:field, :bandwidth, 15, :undefined, :uint32, :optional, []}, {:field, :onlinesecs, 16, :undefined, :uint32, :optional, []}, {:field, :idlesecs, 17, :undefined, :uint32, :optional, ...}, {:field, :strong_certificate, 18, :undefined, :bool, ...}, {:field, :opus, 19, :undefined, :bool, ...}]}, {{:msg, :RequestBlob}, [{:field, :session_texture, 1, :undefined, :uint32, :repeated, []}, {:field, :session_comment, 2, :undefined, :uint32, :repeated, []}, {:field, :channel_description, 3, :undefined, :uint32, :repeated, []}]}, {{:msg, :ServerConfig}, [{:field, :max_bandwidth, 1, :undefined, :uint32, :optional, []}, {:field, :welcome_text, 2, :undefined, :string, :optional, []}, {:field, :allow_html, 3, :undefined, :bool, :optional, []}, {:field, :message_length, 4, :undefined, :uint32, :optional, []}, {:field, :image_message_length, 5, :undefined, :uint32, :optional, []}]}, {{:msg, :SuggestConfig}, [{:field, :version, 1, :undefined, :uint32, :optional, []}, {:field, :positional, 2, :undefined, :bool, :optional, []}, {:field, :push_to_talk, 3, :undefined, :bool, :optional, []}]}], [])
    lib/exprotobuf/parser.ex:25: Protobuf.Parser.parse!/2
    lib/exprotobuf.ex:55: Protobuf.parse/1
    expanding macro: Protobuf.__using__/1
    lib/opnd/mumble/protocol.ex:2: OPNd.Mumble.Protocol (module)
    (elixir) expanding macro: Kernel.use/2
    lib/opnd/mumble/protocol.ex:2: OPNd.Mumble.Protocol (module)

lib/opnd/mumble/protocol.ex:

defmodule OPNd.Mumble.Protocol do
  use Protobuf, from: Path.expand("./Mumble.proto", __DIR__)
end

Improve error handling

When an invalid binary message is passed to the decoder, at least three different errors are thrown by the library (depending of the message):

  • MatchError
  • FunctionClauseError
  • CaseClauseError

They all leak internal details of the library and should be catch by the lib. Only one error should be necessary, specific to the lib, for example "DecodeError".

encode and decode are no longer completely reversible

When relying on default fields decoding may produce different values than the structures created with new().

I have tests which confirm that encode/decode are reversible which worked fine with 1.2.1, but now fails with 1.2.4. The reason being, that the decoded structure has default values (0 or int, false for boolean) whereas the unspecified values for newly created structures all have values of nil.

  @decoded MyProtobuf.new()

  test "can encode and decode my protobuf" do
    encoded = @decoded
    |> MyProtobuf.encode
    |> Base.encode64

    decoded = encoded
    |> Base.decode64!
    |> MyProtobuf.decode

    assert decoded == @decoded
  end
  1) test can encode and decode my protobuf (MyProtobuf)
     test/my_protobuf_test.exs:44
     Assertion with == failed
     code:  decoded == @decoded
     left:  %MyProtobuf{timestamp: 0, active: false}
     right: %MyProtobuf{timestamp: nil, active: nil}
     stacktrace:
       test/my_protobuf_test.exs:53: (test)

Cannot compile exprotobuff as dependency for Elixir 1.2 on Win10

When trying to compile project I get the following output:

G:\projects\elixir\test>mix compile
==> gpb (compile)
Compiled src/gpb_scan.xrl
Compiled src/gpb_parse.yrl
Compiled src/gpb_scan.erl
Compiled src/gpb_parse.erl
Compiled src/gpb_codegen.erl
Compiled src/gpb.erl
Compiled src/gpb_compile.erl
'sh' is not recognized as an internal or external command,
operable program or batch file.
ERROR: Command [compile] failed!
** (Mix) Could not compile dependency :gpb, "escript.exe "c:/Users/Денис/.mix/rebar" compile skip_deps=true deps_dir="g:/projects/elixir/test/_build/dev/lib"" command failed. You can recompile this dependency with "mix deps.compile gpb", update it with "mix deps.update gpb" or clean it with "mix deps.clean gpb"

mix.exs file contents:

defmodule Test.Mixfile do
  use Mix.Project

  def project do
    [app: :test,
     version: "0.0.1",
     elixir: "~> 1.2",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps]
  end

  # Configuration for the OTP application
  #
  # Type "mix help compile.app" for more information
  def application do
    [applications: [:logger]]
  end

  # Dependencies can be Hex packages:
  #
  #   {:mydep, "~> 0.3.0"}
  #
  # Or git/path repositories:
  #
  #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
  #
  # Type "mix help deps" for more examples and options
  defp deps do
    [
        {:exprotobuf, "~> 1.0.0-rc1"}
    ]
  end
end

mix.lock contents:

%{"exprotobuf": {:hex, :exprotobuf, "1.0.0-rc1"},
  "gpb": {:hex, :gpb, "3.18.10"}}

rebar --version

rebar 2.3.1 R14B04 20140521_211707 git 2.3.1

hex.pm update

Hello

Could you please update a package on hex.pm with commit 6c9b76d?

Thanks a lot!

Protobuf.Encoder not available on production

We included exprotobuf in our production application. After deploying release to staging, encoding does not work in staging environment. We get following error:

** (UndefinedFunctionError) undefined function: Protobuf.Encoder.encode/2 (module Protobuf.Encoder is not available)

Exprotobuf has been added as an application in mix. Currently we inverstigate the issue.

UndefinedFunctionError in decoding

Hi guys,
First great job with the library it is nice and easy to use.

Now to the issue, a few days ago I have noticed that one of the parsers in my code keeps getting some strange errors:

** (UndefinedFunctionError) function :core_power_state.__struct__/0 is undefined (module :core_power_state is not available)
    :core_power_state.__struct__()
    (exprotobuf) lib/exprotobuf/utils.ex:28: Protobuf.Utils.convert_from_record/2
    (exprotobuf) lib/exprotobuf/decoder.ex:94: Protobuf.Decoder.convert_value/2
    (exprotobuf) lib/exprotobuf/decoder.ex:71: Protobuf.Decoder.convert_field/3
    (elixir) lib/enum.ex:1826: Enum."-reduce/3-lists^foldl/2-0-"/3

first, I just thought, there was something wrong with the encoding that I was, but as it turns out, I could encode the messages just fine, but when I try to decode same message I keep getting error above.

So I did a little bit of debugging and on line 39 in lib/exprotobuf/decoder.ex found the error:
calling module.defs(:field, :gps_course_present) the module is returning wrong field:

%Protobuf.Field{fnum: 9, name: :gps_dop, occurrence: :optional, opts: [], rnum: 10, type: {:msg, Core.ProtofileFunctions.Values.Value.GpsDop}}

instead of:

%Protobuf.OneOfField{fields: [%Protobuf.Field{fnum: 10, name: :gps_course,
   occurrence: :optional, opts: [], rnum: 11, type: :uint32}],
 name: :gps_course_present, rnum: 11}

Is there something wrong with my syntax in the Protofile, or is this an issue in the builder of the Protofile?

PS: the file was created from the C++ side since I encode the messages on the device and decode them in the elixir app. On C++ side I can decode message.

Incorrect encoding

I am have this schema

message Promo {
  string image = 1;
  string adUrl = 2;
  bool isAd = 3;
}

Used this data

promos=[Promo{image=https://djloboapp.com/main_images/djlobopromo_Android.jpg, isAd=false}, Promo{image=https://djloboapp.com/main_images/followDJLobo2_Android.jpg, adUrl=http://bit.ly/djloboinstagram, isAd=true}, Promo{image=https://djloboapp.com/main_images/djlobobooking_Android.jpg, isAd=false}]

Encoding this using exprotobuf produces

"=
9https://djloboapp.com/main_images/djlobopromo_Android.jpg"^
;https://djloboapp.com/main_images/followDJLobo2_Android.jpghttp://bit.ly/djloboinstagram"?
;https://djloboapp.com/main_images/djlobobooking_Android.jpg%

Any other implementation (I used google's implementation in Python & Golang) gives the following

";
9https://djloboapp.com/main_images/djlobopromo_Android.jpg"^
;https://djloboapp.com/main_images/followDJLobo2_Android.jpghttp://bit.ly/djloboinstagram"=
;https://djloboapp.com/main_images/djlobobooking_Android.jpg%

What is going wrong because of this:
I had no issues because of this until I decided to rewrite one part of the API in Go. The client was using Wire to decode response. Since Wire uses Boolean instead of boolean the response from any other library other than exprotobuf was making it null instead of false. Even though I wish other implementations follow what exprotobuf does, in current state, it does not follow the specification

Encoding and Decoding messages that specify their extensions range

I'm experimenting with the possibility of using Elixir in an existing system that has a bunch of protobuf rpc messages defined. I've been really impressed so far with gpb and exprotobuf ❤️

One issue I just ran into is that if my message defines its valid range for extension fields I get an encoding/decoding error. Here is a minimal reproducible (as minimal as I knew how to make it at least)

test/encoding_messages_with_extensions_test.rb

defmodule ExtensionFieldTest do
  use ExUnit.Case

  defmodule Proto do
    use Protobuf, """
      message Msg {
        extensions 200 to max;
        optional string name = 1;
      }
      extend Msg {
        optional string pseudonym = 200;
      }
    """
  end

  test "it can create an extended message" do
    msg = Proto.Msg.new(name: "Ron", pseudonym: "Duke Silver")
    assert msg == %Proto.Msg{name: "Ron", pseudonym: "Duke Silver"}
  end

  test "it can encode an extended message" do
    msg = Proto.Msg.new(name: "Ron", pseudonym: "Duke Silver")
    assert Proto.Msg.encode(msg) == <<10, 3, 82, 111, 110, 194, 12, 11, 68, 117, 107, 101, 32, 83, 105, 108, 118, 101, 114>>
  end

  test "it can decode an extended message" do
    encoded = <<10, 3, 82, 111, 110, 194, 12, 11, 68, 117, 107, 101, 32, 83, 105, 108, 118, 101, 114>>
    assert Proto.Msg.decode(encoded) == %Proto.Msg{name: "Ron", pseudonym: "Duke Silver"}
  end
end

When I run this test I get the following result on Elixir 1.0.5 (erlang 17.5)

  1) test it can encode an extended message (ExtensionFieldTest)
     test/extension_field_test.exs:21
     ** (CaseClauseError) no case clause matching: :extensions
     stacktrace:
       lib/exprotobuf/encoder.ex:7: anonymous fn/2 in Protobuf.Encoder.encode/2
       (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3
       lib/exprotobuf/encoder.ex:6: Protobuf.Encoder.encode/2
       test/extension_field_test.exs:23



  2) test it can decode an extended message (ExtensionFieldTest)
     test/extension_field_test.exs:26
     ** (CaseClauseError) no case clause matching: :extensions
     stacktrace:
       lib/exprotobuf/decoder.ex:9: anonymous fn/2 in Protobuf.Decoder.decode/2
       (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3
       lib/exprotobuf/decoder.ex:8: Protobuf.Decoder.decode/2
       test/extension_field_test.exs:28

.

Finished in 0.1 seconds (0.1s on load, 0.00s on tests)
3 tests, 2 failures

Randomized with seed 2218

I'm not sure if this is an issue with gpb or exprotobuf, but I'm happy to keep diving in further if you can point me in the general direction of how to fix it. Again much ❤️ for your awesome library.

Encode failing with Enums

I've tried to dig a bit into the issue, but I'm having a hard time understanding it.

I've created a test case in my fork for it:

cloud8421@d763718

Have you got any idea on what's happening?

Thanks!

Are delimiters supported?

I'm using protobuf for streamed data so need a delimiter for each message sent. This seems to be implemented in most libs for other languages, e.g. Java. Does exprotobuf support this?

Infinite loop upon encode/1 with inject: true.

When using the inject option, it looks like the defs function (https://github.com/bitwalker/exprotobuf/blob/master/lib/exprotobuf/define_message.ex#L87 ) loops infinitely. encode/1 calls defs (as an argument to Encoder.encode/2), defs(_ \\ nil) responds and calls @root.defs (itself)

Should injected modules also get the global defs helper from https://github.com/bitwalker/exprotobuf/blob/master/lib/exprotobuf/builder.ex#L77?

Test case below:

defmodule Basic do
  use Protobuf, from: Path.expand("../test/proto/basic.proto", __DIR__), only: :Basic, inject: true
end
Interactive Elixir (1.0.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Basic.new
%Basic{args: nil, f1: nil, f2: nil, type: nil}
iex(2)> Basic.new |> Basic.encode

BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
^C%

protodefs are not recompiled if changed

nevermind this seems an issue on my end. Looking into further.

Issue was I did not realize that the lib was not recompiling the defs when I changed the file. There should be a check I think perhaps you can include the file + a checksum.

Everytime use Protobuf executes, check the protofiles MD5, if it does not match the md5 you wrote, recompile the defs?

** (Protobuf.Parser.ParserError) ('syntax error before: ', '\'{\'')

Hi, this is my first time working with Protobuf in general, and I'm trying to read a proto file I have, but get this error:

** (Protobuf.Parser.ParserError) 92('syntax error before: ', '\'{\'')
    (exprotobuf) lib/exprotobuf/parser.ex:68: Protobuf.Parser.parse!/2
    (exprotobuf) lib/exprotobuf/parser.ex:10: anonymous fn/3 in Protobuf.Parser.parse_files!/2
        (elixir) lib/enum.ex:1623: Enum."-reduce/3-lists^foldl/2-0-"/3
    (exprotobuf) lib/exprotobuf/parser.ex:8: Protobuf.Parser.parse_files!/2
    (exprotobuf) lib/exprotobuf.ex:68: Protobuf.parse/2
    (exprotobuf) expanding macro: Protobuf.__using__/1
                 iex:14: GP (module)
        (elixir) expanding macro: Kernel.use/2

I get the error on the Identifier line:

message BookDetails {
    repeated BookSubject subject = 3;
    optional string publisher = 4;
    optional string publicationDate = 5;
    optional string isbn = 6;
    optional int32 numberOfPages = 7;
    optional string subtitle = 8;
    repeated BookAuthor author = 9;
    optional string readerUrl = 10;
    optional string downloadEpubUrl = 11;
    optional string downloadPdfUrl = 12;
    optional string acsEpubTokenUrl = 13;
    optional string acsPdfTokenUrl = 14;
    optional bool epubAvailable = 15;
    optional bool pdfAvailable = 16;
    optional string aboutTheAuthor = 17;
    repeated group Identifier = 18 {
        optional int32 type = 19;
        optional string identifier = 20;
    }
    optional bool fixedLayoutContent = 21;
    optional bool audioVideoContent = 22;
    optional bool isAgencyBook = 23;
}

I have absolutely no idea what to do or how to fix this. Commenting out repeated group Identifier = 18 { block fixes this but I can't do that, because my entire proto file is full of such blocks. Is this a protobuf syntax or version issue? What can I do to get this to work?

Why not transform package name to CamelCase?

Currently, a package my.under_scored.package will be translated to My.Under_scored.Package in Elixir by exprotobuf.

The protobuf's official documents shows that the package name should follow underscore style while it's recommend to use CamelCase in Elixir module name.

So I think maybe my.under_scored.package should be translated to My.UnderScored.Package in Elixir.

"oneof message {}" Causes parsing failure.

The following proto definition appears to break the parser:

message Envelope  
{  
  oneof message {  
    ContentA content_a = 1;  
    ContentB content_b = 2;  
  }  
}  

message ContentA  
{  
  optional string buffer = 1;  
}  

message ContentB  
{  
  optional int32 num = 1;  
}  

The error:

== Compilation error on file lib/proto.ex ==  
** (Protobuf.Parser.ParserError) 7('syntax error before: ', 'message')  
    lib/exprotobuf/parser.ex:68: Protobuf.Parser.parse!/2  
    lib/exprotobuf/parser.ex:10: anonymous fn/3 in Protobuf.Parser.parse_files!/2  
    (elixir) lib/enum.ex:1473: Enum."-reduce/3-lists^foldl/2-0-"/3  
    lib/exprotobuf/parser.ex:8: Protobuf.Parser.parse_files!/2  
    lib/exprotobuf.ex:65: Protobuf.parse/2  
    expanding macro: Protobuf.__using__/1  
    lib/spokes_proto.ex:2: SpokesProto (module)  
    (elixir) expanding macro: Kernel.use/2  

The culprit seems to be the "oneof message" line and my guess is that the parser expects message to be a keyword; however, I haven't had a chance to dig into what it does yet. Protoc doesn't complain about this so I am assuming it's a valid proto definition.

ProtoBuf version 3 not supported

Version 3 of Protocol Buffers is not supported. At least it gives me an error on proto files starting with

syntax = "proto3";

Any plans to support Protocol Buffers v3? Otherwise if you give me some directions I may be able to submit a PR

ParseError when reading proto file

When compiling my mix project I get the following error:

== Compilation error on file lib/csgo_parse.ex ==
** (Protobuf.Parser.ParserError) 6('syntax error before: ', '\'.\'')
    lib/exprotobuf/parser.ex:46: Protobuf.Parser.parse!/2
    lib/exprotobuf.ex:67: Protobuf.parse/2
    expanding macro: Protobuf.__using__/1
    lib/csgo_parse.ex:2: CsgoParse (module)
    (elixir) expanding macro: Kernel.use/2
    lib/csgo_parse.ex:2: CsgoParse (module)

I’m importing .proto files, I’m not sure what’s wrong and/or where to go next to continue debugging it.

My repo lives here, https://github.com/ajrob27/csgo_parse. I've created another branch where I remove most everything from the .proto file I am trying to read, https://github.com/ajrob27/csgo_parse/tree/bugfix/protobuf.

In the bugfix/protobuf branch, the project still does not compile, but if you removed lines 4-6 in steammessages.proto, the project does compile. Is there something wrong with my .proto files? Other project successfully use these .proto files, I'm not sure where I am going wrong.

Thanks!

Imported proto definitions?

Hey -- I'm trying to import a proto definition, and what I'm trying to do isn't covered by an existing test case, so I couldn't model it off of that.

Basically I want to import the struct definition from google.protobuf

// have tried "google.protobuf", "google.protobuf.struct",  "google.protobuf.struct.proto"
// "google/protobuf/struct" to no avail
import "google.protobuf";

message Response {
    enum StatusCode {
        SUCCESS = 0;
        FAILURE = 1;
        CANCELLED = 2;
    }

    StatusCode status = 1;
    // I've tried permutations of `Struct`, `google.protobuf.Struct` to no avail
    repeated Struct data = 2; 
}

But when I call mix grpc.gen I get Reference to undefined message Struct... I would expect the import
to throw an exception if it was invalid, so I'm having trouble figuring out if my import is wrong or my proto3 type definition is wrong.

similar to this test case, except I want to use WrongAuthorizationHttpMessage inside of the WebsocketServerContainer message, for example.

Any thoughts or guidance?

invalid encoding starting at

I'm getting the following error when loading the proto file attached:

** (UnicodeConversionError) invalid encoding starting at <<154, 179, 139, 210, 5, 18, 200, 5, 10, 5, 84, 49, 48, 48, 49, 16, 0, 26, 188, 5, 10, 32, 10, 3, 49, 50, 49, 18, 8, 49, 51, 58, 53, 56, 58, 48, 48, 26, 8, 50, 48, 49, 55, 49, 50, 50, 54, 32, 0, 42, ...>> (elixir) lib/string.ex:1840: String.to_charlist/1 lib/exprotobuf/parser.ex:41: Protobuf.Parser.parse/2 lib/exprotobuf/parser.ex:55: Protobuf.Parser.parse!/2 lib/exprotobuf/parser.ex:10: anonymous fn/3 in Protobuf.Parser.parse_files!/2 (elixir) lib/enum.ex:1826: Enum."-reduce/3-lists^foldl/2-0-"/3 lib/exprotobuf/parser.ex:8: Protobuf.Parser.parse_files!/2 lib/exprotobuf.ex:80: Protobuf.parse/2 expanding macro: Protobuf.__using__/1...

I was able to load the proto file in a sample java protobuf reader to ensure that the file is in the right format. Not sure if I'm missing a config parameter.

GTFSDATAFILE_1514312552253.proto.zip

inject does not work without only

I wrote a .proto file containing a single message:

message Message {
  required string field = 1;
}

I tried to inject this definition into a module as shown in README.md:

defmodule Message do
  use Protobuf, from: Path.expand("../proto/message.proto", __DIR__), inject: true
end

Creating the message fails:

iex(1)> %Message{}
** (CompileError) iex:1: Message.__struct__/1 is undefined, cannot expand struct Message

iex(1)> Message.new
** (UndefinedFunctionError) function Message.new/0 is undefined or private
    (example) Message.new()

However, it started to work after after adding only option:

defmodule Message do
  use Protobuf, from: Path.expand("../proto/message.proto", __DIR__), only: :Message, inject: true
end

Creating the message succeeds:

iex(1)> %Message{}
%Message{field: nil}
iex(2)> Message.new
%Message{field: nil}

Missing optional values not decoded correctly

When creating instances of messages, the optional values are correctly set to nil. But when decoding messages with optional values, the resulting struct-instance has the optional values to type-defaults, instead.

in lib:

defmodule Exprotobufbug do
  use Protobuf, """
    message Msg {
      optional bytes timestamp = 1;
    }
  """
end

in test:

defmodule ExprotobufbugTest do
  use ExUnit.Case
  doctest Exprotobufbug

  test "decode optional values as nil" do
    msg = Exprotobufbug.Msg.new()
    bin = Exprotobufbug.Msg.encode(msg)
    res = Exprotobufbug.Msg.decode(bin)
    assert ^res = msg
  end
end

result of executing the test:

  1) test decode optional values as nil (ExprotobufbugTest)
     test/exprotobufbug_test.exs:5
     match (=) failed
     The following variables were pinned:
       res = %Exprotobufbug.Msg{timestamp: ""}
     code:  ^res = msg
     right: %Exprotobufbug.Msg{timestamp: nil}
     stacktrace:
       test/exprotobufbug_test.exs:9: (test)
 
 
 
Finished in 0.02 seconds
1 test, 1 failure

As expected, the timestamp was set to nil by the constructor. But decoding the binary-version gives a default value for the type binary instead.

OS: MacOS Sierra 10.12.3
Mix: 1.4.2
Elixir: 1.4.2
Erlang: 19
exprotobuf: 1.2.5

Support for :mapfields_as_maps

Hey there!

Recently I've opened an issue on grpc-elixir trying to figure out a way to parse proto's maps as Elixir's maps rather than a list of tuples (default).

Taking a look at exprotobuf and gpb, I saw that the second accepts an option for such kind of map parsing. Then I tried to pass both options [:mapfields_as_maps] and [{:maps, :mapfields_as_maps}] to the parse functions but it didn't work as expected.

I couldn't find any test/spec covering this case so I'm just wondering how should I proceed to make use of this option and be able to use Elixir maps on my map fields.

Is there a polymorphic way to encode/decode ?

Sorry I am fairly new to elixir.
Is there something like a protocol implemented by all protobuf modules?

It would be awesome to be able to do:

Protobuf.encode(MyModule.Msg.new())

I tried something like:

defmodule Objects do
  use Protobuf, from: 'file.proto'
  defprotocol Serializable do
    def serialize(object)
    def deserialize(binary)
  end

  defimpl Serializable, for: Msg do
    def serialize(object) do
      Msg.encode(object)
    end

    def deserialize(binary) do
      Msg.decode(binary)
    end
  end

end

But of course it fails because it does not know Msg.

Could this be achieve with macros?

Proto 3 support ?

Are there plans to support Proto 3 any time in the near future ?

Thanks!
--Nick

Oneof support

It looks like oneof feature of protobuf isn't supported by exprotobuf and causes compilation error.

Here's example proto file:

package chat;

option java_package = "com.appunite.chat";

// From server to client
message WebsocketServerContainer {
  oneof response {
      int32 create_conversation = 1;
      int32 create_single_conversation_confirmation = 2;
      int32 create_group_conversation_confirmation = 3;
    }
}

that cases problem during compilation:

== Compilation error on file web/models/chat_protocol.ex ==
** (ArgumentError) argument error
    lib/exprotobuf/utils.ex:32: anonymous fn/3 in Protobuf.Utils.convert_from_record/2
    (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3
    lib/exprotobuf.ex:78: Protobuf.namespace_fields/2
    (elixir) lib/enum.ex:977: anonymous fn/3 in Enum.map/2
    (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3
    (elixir) lib/enum.ex:977: Enum.map/2
    lib/exprotobuf.ex:69: anonymous fn/4 in Protobuf.namespace_types/3
    (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3

Support "reserved field"

Hello, I found that when the proto file contains a "reserved field" will cause the encode and decode fucntion fail.

This this the demo project I created.

demo

Thanks

Encoding and Decoding messages that include service definitions

Similar to #15 if I try to define a message that includes a service definition I get an CaseClauseError.

Reproducible test:

defmodule ServiceDefinitionsTest do
  use ExUnit.Case

  defmodule Proto do
    use Protobuf, """
      message Msg {
        optional string name = 1;
      }
      service MsgService {
        rpc Create (Msg) returns (Msg);
      }
    """
  end

  test "it can create an extended message" do
    msg = Proto.Msg.new(name: "Ron")
    assert msg == %Proto.Msg{name: "Ron"}
  end

  test "it can encode an extended message" do
    msg = Proto.Msg.new(name: "Ron")
    assert Proto.Msg.encode(msg) == <<10, 3, 82, 111, 110>>
  end

  test "it can decode an extended message" do
    encoded = <<10, 3, 82, 111, 110>>
    assert Proto.Msg.decode(encoded) == %Proto.Msg{name: "Ron"}
  end
end

I get these results

.

  1) test it can decode an extended message (ExtensionFieldTest)
     test/rpc_definitions_test.exs:25
     ** (CaseClauseError) no case clause matching: :service
     stacktrace:
       lib/exprotobuf/decoder.ex:9: anonymous fn/2 in Protobuf.Decoder.decode/2
       (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3
       lib/exprotobuf/decoder.ex:8: Protobuf.Decoder.decode/2
       test/rpc_definitions_test.exs:27



  2) test it can encode an extended message (ExtensionFieldTest)
     test/rpc_definitions_test.exs:20
     ** (CaseClauseError) no case clause matching: :service
     stacktrace:
       lib/exprotobuf/encoder.ex:7: anonymous fn/2 in Protobuf.Encoder.encode/2
       (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3
       lib/exprotobuf/encoder.ex:6: Protobuf.Encoder.encode/2
       test/rpc_definitions_test.exs:22



Finished in 0.1 seconds (0.1s on load, 0.00s on tests)
3 tests, 2 failures

Randomized with seed 298949

Support in .new for composite messages

The library does not appear to instantiate children messages for a message which has other messages declared as an attribute. For example, using the Messages.Msg definition in the Readme, calling

Messages.Msg.new(version: :'V2', sub: [value: 2])

results in the following

iex(8)> msg = Messages.Msg.new(version: :'V2', sub: [value: 2])
%Messages.Msg{sub: [value: 2], version: :V2}

It appears that the value for "sub" is just taken at face value and assigned rather than interpreted as another set of values for a different type of message. I would expect that it could recurse through the embeded layers and construct the sub-message(s) as long as they are supplying values of the correct type.

Is there a better way to deal with this?

Thanks

MatchError while decoding

Using Elixir 0.14.1
exprotobuf from github master
gpb from github master

iex(1)> defmodule Messages do
...(1)>   use Protobuf, """
...(1)>     message Msg {
...(1)>       message SubMsg {
...(1)>         required uint32 value = 1;
...(1)>       }
...(1)> 
...(1)>       enum Version {
...(1)>         V1 = 1;
...(1)>         V2 = 2;
...(1)>       }
...(1)> 
...(1)>       required Version version = 2;
...(1)>       optional SubMsg sub = 1;
...(1)>     }
...(1)>   """
...(1)> end
{:module, Messages,
 <<70, 79, 82, 49, 0, 0, 5, 220, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 93, 131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115, 95, 118, 49, 108, 0, 0, 0, 2, 104, 2, 100, 0, 4, ...>>,
 true}
iex(2)> msg = Messages.Msg.new(version: :'V2')
%Messages.Msg{sub: nil, version: :V2}
iex(3)> encoded = Messages.Msg.encode(msg)
<<16, 2>>
iex(4)>  Messages.Msg.decode(encoded)
** (MatchError) no match of right hand side value: false
    src/gpb.erl:256: :gpb.decode_type/3
    src/gpb.erl:170: :gpb.decode_field/4
    lib/exprotobuf/decoder.ex:11: Protobuf.Decoder.decode/2

Cannot build as a dependency in Elixir 1.1

The error I am getting when trying to compile the project is

Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:2:2] [async-threads:10] [kernel-poll:false]

==> gpb (compile)
Compiling src/gpb_scan.xrl failed:
ERROR: compile failed while processing /home/sarat/projects/protobuf_elixir/deps/gpb: rebar_abort
** (Mix) Could not compile dependency :gpb, "/home/sarat/.mix/rebar" command failed. You can recompile this dependency with "mix deps.compile gpb", update it with "mix deps.update gpb" or clean it with "mix deps.clean gpb"

I am using Elixir 1.1 and Erlang 18(erts-7.1)

Here is my mix.exs file

defmodule ProtobufElixir.Mixfile do
  use Mix.Project

  def project do
    [app: :protobuf_elixir,
     version: "0.0.1",
     elixir: "~> 1.1",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps]
  end

  # Configuration for the OTP application
  #
  # Type "mix help compile.app" for more information
  def application do
    [applications: [:logger]]
  end

  # Dependencies can be Hex packages:
  #
  #   {:mydep, "~> 0.3.0"}
  #
  # Or git/path repositories:
  #
  #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
  #
  # Type "mix help deps" for more examples and options
  defp deps do
    [{:exprotobuf, "~>0.11"}]
  end
end

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.