Skip to content

Repository files navigation

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 StatusHex.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:

defpdepsdo[{:exprotobuf,"~> x.x.x"}]end

Then run mix deps.get to fetch.

Add exprotobuf to applications list:

defapplicationdo[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

defmoduleMessagesdouseProtobuf,""" 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:

defmoduleMessagesdouseProtobuf,""" 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

defmoduleMessagesdouseProtobuf,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

defmoduleProtobufsdouseProtobuf,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:

packageworld;
messageExample {
enumContinent {
ANTARCTICA=0;
EUROPE=1;
}
optionalContinentcontinent=1;
optionaluint32id=2;
}

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

defmoduleDefinitionsdouseProtobuf,from: Path.wildcard("protobufs/*.proto"),use_package_names: trueend
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.

defmoduleDefinitionsdouseProtobuf,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.

defmoduleMsgdouseProtobuf,from: Path.expand("../proto/messages.proto",__DIR__),inject: truedefupdate(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:

defmoduleMessagesdouseProtobuf,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.

defmoduleMessagesdouseProtobuf," message Msg { enum Version { V1 = 1; V2 = 1; } required Version v = 1; } "defmoduleMsgHelpersdodefmacro__using__(_opts)doquotedodefconvert_to_record(msg)domsg|>Map.to_list|>Enum.reduce([],fn{_key,value},acc->[value|acc]end)|>Enum.reverse|>list_to_tupleendendendenduse_in"Msg",MsgHelpersend
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.

About

Protocol Buffers in Elixir made easy!

Resources

Stars

482 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - bitwalker/exprotobuf: Protocol Buffers in Elixir made easy! · GitHub
Skip to content

Repository files navigation

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 StatusHex.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:

defpdepsdo[{:exprotobuf,"~> x.x.x"}]end

Then run mix deps.get to fetch.

Add exprotobuf to applications list:

defapplicationdo[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

defmoduleMessagesdouseProtobuf,""" 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:

defmoduleMessagesdouseProtobuf,""" 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

defmoduleMessagesdouseProtobuf,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

defmoduleProtobufsdouseProtobuf,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:

packageworld;
messageExample {
enumContinent {
ANTARCTICA=0;
EUROPE=1;
}
optionalContinentcontinent=1;
optionaluint32id=2;
}

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

defmoduleDefinitionsdouseProtobuf,from: Path.wildcard("protobufs/*.proto"),use_package_names: trueend
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.

defmoduleDefinitionsdouseProtobuf,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.

defmoduleMsgdouseProtobuf,from: Path.expand("../proto/messages.proto",__DIR__),inject: truedefupdate(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:

defmoduleMessagesdouseProtobuf,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.

defmoduleMessagesdouseProtobuf," message Msg { enum Version { V1 = 1; V2 = 1; } required Version v = 1; } "defmoduleMsgHelpersdodefmacro__using__(_opts)doquotedodefconvert_to_record(msg)domsg|>Map.to_list|>Enum.reduce([],fn{_key,value},acc->[value|acc]end)|>Enum.reverse|>list_to_tupleendendendenduse_in"Msg",MsgHelpersend
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.

About

Protocol Buffers in Elixir made easy!

Resources

Stars

482 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - bitwalker/exprotobuf: Protocol Buffers in Elixir made easy! · GitHub
Skip to content

Repository files navigation

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 StatusHex.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:

defpdepsdo[{:exprotobuf,"~> x.x.x"}]end

Then run mix deps.get to fetch.

Add exprotobuf to applications list:

defapplicationdo[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

defmoduleMessagesdouseProtobuf,""" 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:

defmoduleMessagesdouseProtobuf,""" 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

defmoduleMessagesdouseProtobuf,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

defmoduleProtobufsdouseProtobuf,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:

packageworld;
messageExample {
enumContinent {
ANTARCTICA=0;
EUROPE=1;
}
optionalContinentcontinent=1;
optionaluint32id=2;
}

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

defmoduleDefinitionsdouseProtobuf,from: Path.wildcard("protobufs/*.proto"),use_package_names: trueend
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.

defmoduleDefinitionsdouseProtobuf,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.

defmoduleMsgdouseProtobuf,from: Path.expand("../proto/messages.proto",__DIR__),inject: truedefupdate(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:

defmoduleMessagesdouseProtobuf,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.

defmoduleMessagesdouseProtobuf," message Msg { enum Version { V1 = 1; V2 = 1; } required Version v = 1; } "defmoduleMsgHelpersdodefmacro__using__(_opts)doquotedodefconvert_to_record(msg)domsg|>Map.to_list|>Enum.reduce([],fn{_key,value},acc->[value|acc]end)|>Enum.reverse|>list_to_tupleendendendenduse_in"Msg",MsgHelpersend
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.

About

Protocol Buffers in Elixir made easy!

Resources

Stars

482 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - bitwalker/exprotobuf: Protocol Buffers in Elixir made easy! · GitHub
Skip to content

Repository files navigation

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 StatusHex.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:

defpdepsdo[{:exprotobuf,"~> x.x.x"}]end

Then run mix deps.get to fetch.

Add exprotobuf to applications list:

defapplicationdo[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

defmoduleMessagesdouseProtobuf,""" 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:

defmoduleMessagesdouseProtobuf,""" 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

defmoduleMessagesdouseProtobuf,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

defmoduleProtobufsdouseProtobuf,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:

packageworld;
messageExample {
enumContinent {
ANTARCTICA=0;
EUROPE=1;
}
optionalContinentcontinent=1;
optionaluint32id=2;
}

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

defmoduleDefinitionsdouseProtobuf,from: Path.wildcard("protobufs/*.proto"),use_package_names: trueend
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.

defmoduleDefinitionsdouseProtobuf,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.

defmoduleMsgdouseProtobuf,from: Path.expand("../proto/messages.proto",__DIR__),inject: truedefupdate(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:

defmoduleMessagesdouseProtobuf,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.

defmoduleMessagesdouseProtobuf," message Msg { enum Version { V1 = 1; V2 = 1; } required Version v = 1; } "defmoduleMsgHelpersdodefmacro__using__(_opts)doquotedodefconvert_to_record(msg)domsg|>Map.to_list|>Enum.reduce([],fn{_key,value},acc->[value|acc]end)|>Enum.reverse|>list_to_tupleendendendenduse_in"Msg",MsgHelpersend
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.

About

Protocol Buffers in Elixir made easy!

Resources

Stars

482 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - bitwalker/exprotobuf: Protocol Buffers in Elixir made easy! · GitHub
Skip to content

Repository files navigation

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 StatusHex.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:

defpdepsdo[{:exprotobuf,"~> x.x.x"}]end

Then run mix deps.get to fetch.

Add exprotobuf to applications list:

defapplicationdo[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

defmoduleMessagesdouseProtobuf,""" 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:

defmoduleMessagesdouseProtobuf,""" 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

defmoduleMessagesdouseProtobuf,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

defmoduleProtobufsdouseProtobuf,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:

packageworld;
messageExample {
enumContinent {
ANTARCTICA=0;
EUROPE=1;
}
optionalContinentcontinent=1;
optionaluint32id=2;
}

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

defmoduleDefinitionsdouseProtobuf,from: Path.wildcard("protobufs/*.proto"),use_package_names: trueend
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.

defmoduleDefinitionsdouseProtobuf,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.

defmoduleMsgdouseProtobuf,from: Path.expand("../proto/messages.proto",__DIR__),inject: truedefupdate(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:

defmoduleMessagesdouseProtobuf,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.

defmoduleMessagesdouseProtobuf," message Msg { enum Version { V1 = 1; V2 = 1; } required Version v = 1; } "defmoduleMsgHelpersdodefmacro__using__(_opts)doquotedodefconvert_to_record(msg)domsg|>Map.to_list|>Enum.reduce([],fn{_key,value},acc->[value|acc]end)|>Enum.reverse|>list_to_tupleendendendenduse_in"Msg",MsgHelpersend
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.

About

Protocol Buffers in Elixir made easy!

Resources

Stars

482 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - bitwalker/exprotobuf: Protocol Buffers in Elixir made easy! · GitHub
Skip to content

Repository files navigation

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 StatusHex.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:

defpdepsdo[{:exprotobuf,"~> x.x.x"}]end

Then run mix deps.get to fetch.

Add exprotobuf to applications list:

defapplicationdo[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

defmoduleMessagesdouseProtobuf,""" 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:

defmoduleMessagesdouseProtobuf,""" 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

defmoduleMessagesdouseProtobuf,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

defmoduleProtobufsdouseProtobuf,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:

packageworld;
messageExample {
enumContinent {
ANTARCTICA=0;
EUROPE=1;
}
optionalContinentcontinent=1;
optionaluint32id=2;
}

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

defmoduleDefinitionsdouseProtobuf,from: Path.wildcard("protobufs/*.proto"),use_package_names: trueend
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.

defmoduleDefinitionsdouseProtobuf,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.

defmoduleMsgdouseProtobuf,from: Path.expand("../proto/messages.proto",__DIR__),inject: truedefupdate(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:

defmoduleMessagesdouseProtobuf,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.

defmoduleMessagesdouseProtobuf," message Msg { enum Version { V1 = 1; V2 = 1; } required Version v = 1; } "defmoduleMsgHelpersdodefmacro__using__(_opts)doquotedodefconvert_to_record(msg)domsg|>Map.to_list|>Enum.reduce([],fn{_key,value},acc->[value|acc]end)|>Enum.reverse|>list_to_tupleendendendenduse_in"Msg",MsgHelpersend
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.

About

Protocol Buffers in Elixir made easy!

Resources

Stars

482 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - bitwalker/exprotobuf: Protocol Buffers in Elixir made easy! · GitHub
Skip to content

Repository files navigation

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 StatusHex.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:

defpdepsdo[{:exprotobuf,"~> x.x.x"}]end

Then run mix deps.get to fetch.

Add exprotobuf to applications list:

defapplicationdo[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

defmoduleMessagesdouseProtobuf,""" 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:

defmoduleMessagesdouseProtobuf,""" 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

defmoduleMessagesdouseProtobuf,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

defmoduleProtobufsdouseProtobuf,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:

packageworld;
messageExample {
enumContinent {
ANTARCTICA=0;
EUROPE=1;
}
optionalContinentcontinent=1;
optionaluint32id=2;
}

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

defmoduleDefinitionsdouseProtobuf,from: Path.wildcard("protobufs/*.proto"),use_package_names: trueend
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.

defmoduleDefinitionsdouseProtobuf,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.

defmoduleMsgdouseProtobuf,from: Path.expand("../proto/messages.proto",__DIR__),inject: truedefupdate(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:

defmoduleMessagesdouseProtobuf,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.

defmoduleMessagesdouseProtobuf," message Msg { enum Version { V1 = 1; V2 = 1; } required Version v = 1; } "defmoduleMsgHelpersdodefmacro__using__(_opts)doquotedodefconvert_to_record(msg)domsg|>Map.to_list|>Enum.reduce([],fn{_key,value},acc->[value|acc]end)|>Enum.reverse|>list_to_tupleendendendenduse_in"Msg",MsgHelpersend
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.

About

Protocol Buffers in Elixir made easy!

Resources

Stars

482 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - bitwalker/exprotobuf: Protocol Buffers in Elixir made easy! · GitHub
Skip to content

Repository files navigation

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 StatusHex.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:

defpdepsdo[{:exprotobuf,"~> x.x.x"}]end

Then run mix deps.get to fetch.

Add exprotobuf to applications list:

defapplicationdo[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

defmoduleMessagesdouseProtobuf,""" 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:

defmoduleMessagesdouseProtobuf,""" 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

defmoduleMessagesdouseProtobuf,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

defmoduleProtobufsdouseProtobuf,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:

packageworld;
messageExample {
enumContinent {
ANTARCTICA=0;
EUROPE=1;
}
optionalContinentcontinent=1;
optionaluint32id=2;
}

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

defmoduleDefinitionsdouseProtobuf,from: Path.wildcard("protobufs/*.proto"),use_package_names: trueend
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.

defmoduleDefinitionsdouseProtobuf,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.

defmoduleMsgdouseProtobuf,from: Path.expand("../proto/messages.proto",__DIR__),inject: truedefupdate(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:

defmoduleMessagesdouseProtobuf,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.

defmoduleMessagesdouseProtobuf," message Msg { enum Version { V1 = 1; V2 = 1; } required Version v = 1; } "defmoduleMsgHelpersdodefmacro__using__(_opts)doquotedodefconvert_to_record(msg)domsg|>Map.to_list|>Enum.reduce([],fn{_key,value},acc->[value|acc]end)|>Enum.reverse|>list_to_tupleendendendenduse_in"Msg",MsgHelpersend
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.

About

Protocol Buffers in Elixir made easy!

Resources

Stars

482 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages