diff --git a/doc/src/content/docs/reference/languages/elixir.mdx b/doc/src/content/docs/reference/languages/elixir.mdx
new file mode 100644
index 0000000..5e3cd2b
--- /dev/null
+++ b/doc/src/content/docs/reference/languages/elixir.mdx
@@ -0,0 +1,226 @@
+---
+title: Elixir Language Support
+description: Protocol Buffer and gRPC support for Elixir applications, enabling distributed systems and real-time services.
+---
+
+import { Tabs, TabItem } from "@astrojs/starlight/components";
+import { Code } from "astro:components";
+import basicConfig from "./elixir.x-basic-configuration.nix?raw";
+
+# Elixir Language Support
+
+**Status**: ✅ Full Support
+**Examples**:
+- [`examples/elixir-basic/`](https://github.com/conneroisu/bufr.nix/tree/main/examples/elixir-basic) - Basic protobuf messages
+- [`examples/elixir-grpc/`](https://github.com/conneroisu/bufr.nix/tree/main/examples/elixir-grpc) - gRPC services
+
+Elixir support provides Protocol Buffer and gRPC integration for building distributed systems, real-time applications, and microservices.
+
+## Available Plugins
+
+| Plugin | Description | Generated Files |
+| ----------------------- | --------------------- | ------------------------ |
+| **`protoc-gen-elixir`** | Base messages & gRPC | `*.pb.ex` |
+
+## Configuration
+
+### Basic Configuration
+
+```nix
+languages.elixir = {
+ enable = true;
+ outputPath = "lib/proto";
+};
+```
+
+### Full Configuration
+
+
+
+## Features
+
+### Message Generation
+
+Generates Elixir modules for all protobuf messages with:
+- Full type safety using structs
+- Binary encoding/decoding
+- JSON serialization support
+- Default values and field presence tracking
+
+### gRPC Support
+
+When enabled, generates:
+- Service modules with server behavior
+- Client stubs for service calls
+- Support for all RPC types (unary, streaming, bidirectional)
+- Error handling with proper gRPC status codes
+
+### Validation Support
+
+Provides hooks for integrating with validation libraries:
+- Field validation rules
+- Custom validation functions
+- Integration with Ecto changesets
+
+## Usage Example
+
+### Basic Message Usage
+
+```elixir
+# Create a message
+message = %MyApp.Proto.User{
+ id: 1,
+ name: "Alice",
+ email: "alice@example.com",
+ roles: ["admin", "user"]
+}
+
+# Encode to binary
+binary = MyApp.Proto.User.encode(message)
+
+# Decode from binary
+{:ok, decoded} = MyApp.Proto.User.decode(binary)
+```
+
+### gRPC Server Implementation
+
+```elixir
+defmodule MyApp.UserService.Server do
+ use GRPC.Server, service: MyApp.Proto.UserService.Service
+
+ def get_user(request, _stream) do
+ user = MyApp.Users.find(request.id)
+ %MyApp.Proto.GetUserResponse{user: user}
+ end
+
+ def list_users(request, stream) do
+ MyApp.Users.list()
+ |> Stream.map(&%MyApp.Proto.User{&1})
+ |> Enum.each(&GRPC.Server.send_reply(stream, &1))
+ end
+end
+```
+
+### gRPC Client Usage
+
+```elixir
+# Connect to server
+{:ok, channel} = GRPC.Stub.connect("localhost:50051")
+
+# Make RPC call
+request = %MyApp.Proto.GetUserRequest{id: 123}
+{:ok, response} = MyApp.Proto.UserService.Stub.get_user(channel, request)
+```
+
+## Integration with Phoenix
+
+Elixir protobuf integrates well with Phoenix applications:
+
+```elixir
+defmodule MyAppWeb.ProtoController do
+ use MyAppWeb, :controller
+
+ def show(conn, %{"id" => id}) do
+ user = MyApp.Users.get(id)
+ proto = %MyApp.Proto.User{user}
+
+ conn
+ |> put_resp_content_type("application/x-protobuf")
+ |> send_resp(200, MyApp.Proto.User.encode(proto))
+ end
+end
+```
+
+## Mix.exs Dependencies
+
+Add these dependencies to your `mix.exs`:
+
+```elixir
+defp deps do
+ [
+ {:protobuf, "~> 0.12.0"},
+ {:grpc, "~> 0.7.0"}, # If using gRPC
+ {:jason, "~> 1.4"} # For JSON support
+ ]
+end
+```
+
+## Configuration Options
+
+### `languages.elixir`
+
+| Option | Type | Default | Description |
+| ---------------- | ------------------- | ------------- | ---------------------------------------------- |
+| `enable` | `bool` | `false` | Enable Elixir code generation |
+| `package` | `package` | `protoc-gen-elixir` | The protoc plugin package |
+| `outputPath` | `string \| [string]`| `"lib"` | Output directory for generated code |
+| `options` | `[string]` | `[]` | Options to pass to protoc-gen-elixir |
+| `namespace` | `string` | `""` | Module namespace prefix (e.g., "MyApp.Proto") |
+| `files` | `[string] \| null` | `null` | Specific proto files for this language |
+| `additionalFiles`| `[string]` | `[]` | Additional proto files to compile |
+
+### `languages.elixir.grpc`
+
+| Option | Type | Default | Description |
+| --------- | ---------- | -------------------- | -------------------------------- |
+| `enable` | `bool` | `false` | Enable gRPC code generation |
+| `package` | `package` | `protoc-gen-elixir` | The protoc plugin package |
+| `options` | `[string]` | `[]` | Options for gRPC generation |
+
+### `languages.elixir.validate`
+
+| Option | Type | Default | Description |
+| --------- | ---------- | ------- | -------------------------------------- |
+| `enable` | `bool` | `false` | Enable validation support |
+| `package` | `package` | `null` | Validation package (if any) |
+| `options` | `[string]` | `[]` | Options for validation generation |
+
+## Tips and Best Practices
+
+1. **Module Organization**: Use the `namespace` option to organize generated modules under your application's namespace.
+
+2. **OTP Integration**: Generated gRPC servers integrate with OTP supervision trees:
+ ```elixir
+ children = [
+ {GRPC.Server.Supervisor, endpoint: MyApp.Endpoint, port: 50051}
+ ]
+ ```
+
+3. **Error Handling**: Use proper gRPC status codes:
+ ```elixir
+ raise GRPC.RPCError, status: :not_found, message: "User not found"
+ ```
+
+4. **Testing**: Use the generated modules in tests:
+ ```elixir
+ test "encodes and decodes messages" do
+ original = %MyApp.Proto.User{id: 1, name: "Test"}
+ binary = MyApp.Proto.User.encode(original)
+ {:ok, decoded} = MyApp.Proto.User.decode(binary)
+ assert decoded == original
+ end
+ ```
+
+5. **Performance**: For high-performance scenarios, consider using binary pattern matching directly on encoded messages.
+
+## Common Issues
+
+### Module Not Found
+
+If you get "module not found" errors after generation:
+1. Ensure the `outputPath` is in your Elixir project's lib path
+2. Run `mix compile` to compile the generated modules
+3. Check that the namespace matches your project structure
+
+### gRPC Connection Issues
+
+For gRPC connection problems:
+1. Verify the server is running on the correct port
+2. Check firewall settings
+3. Ensure both client and server use the same proto definitions
+
+## See Also
+
+- [Elixir Protobuf Library](https://github.com/elixir-protobuf/protobuf)
+- [Elixir gRPC](https://github.com/elixir-grpc/grpc)
+- [Phoenix Framework Integration](https://hexdocs.pm/phoenix)
\ No newline at end of file
diff --git a/doc/src/content/docs/reference/languages/elixir.x-basic-configuration.nix b/doc/src/content/docs/reference/languages/elixir.x-basic-configuration.nix
new file mode 100644
index 0000000..229b3d9
--- /dev/null
+++ b/doc/src/content/docs/reference/languages/elixir.x-basic-configuration.nix
@@ -0,0 +1,29 @@
+languages.elixir = {
+ enable = true;
+ outputPath = "lib/proto";
+ namespace = "MyApp.Proto";
+ options = [];
+
+ # Enable gRPC service generation
+ grpc = {
+ enable = true;
+ options = [];
+ };
+
+ # Enable validation support
+ validate = {
+ enable = true;
+ options = [];
+ };
+
+ # Compile specific proto files for Elixir
+ files = [
+ "./proto/services/v1/user_service.proto"
+ "./proto/messages/v1/common.proto"
+ ];
+
+ # Additional proto files beyond the global list
+ additionalFiles = [
+ "./proto/internal/v1/admin.proto"
+ ];
+};
\ No newline at end of file
diff --git a/doc/src/content/docs/reference/languages/index.mdx b/doc/src/content/docs/reference/languages/index.mdx
index ae2760d..2d94f0f 100644
--- a/doc/src/content/docs/reference/languages/index.mdx
+++ b/doc/src/content/docs/reference/languages/index.mdx
@@ -32,6 +32,11 @@ All examples shown are fully functional and can be found in the [`examples/`](ht
Documentation →](./dart)
+
+ Protocol Buffer and gRPC support for distributed systems and real-time applications. [View Elixir
+ Documentation →](./elixir)
+
+
Multiple generation options including betterproto, mypy stubs, and gRPC
support. [View Python Documentation →](./python)
diff --git a/examples/elixir-basic/README.md b/examples/elixir-basic/README.md
new file mode 100644
index 0000000..20eac3c
--- /dev/null
+++ b/examples/elixir-basic/README.md
@@ -0,0 +1,91 @@
+# Elixir Basic Example
+
+This example demonstrates basic Protocol Buffer code generation for Elixir using Bufrnix.
+
+## Features
+
+- Basic protobuf message generation
+- Nested messages
+- Enums
+- Maps
+- Oneof fields
+- Repeated fields
+
+## Prerequisites
+
+- Nix with flakes enabled
+- Elixir development environment (provided by the flake)
+
+## Usage
+
+### Generate Protobuf Code
+
+```bash
+# Generate the protobuf code
+nix run
+
+# Or enter the development shell and generate
+nix develop
+nix run
+```
+
+### Use in Elixir Project
+
+After generation, the protobuf modules will be available in `lib/proto/`. You can use them in your Elixir code:
+
+```elixir
+# Create a message
+message = %Proto.Example.V1.ExampleMessage{
+ id: 1,
+ name: "Alice",
+ email: "alice@example.com",
+ tags: ["elixir", "protobuf"],
+ created_at: %Proto.Example.V1.TimestampMessage{
+ seconds: System.system_time(:second),
+ nanos: 0
+ }
+}
+
+# Encode to binary
+binary = Proto.Example.V1.ExampleMessage.encode(message)
+
+# Decode from binary
+decoded = Proto.Example.V1.ExampleMessage.decode(binary)
+```
+
+### Run the Example
+
+```bash
+# Enter the development shell
+nix develop
+
+# Install dependencies
+mix deps.get
+
+# Generate protobuf code
+nix run
+
+# Run the example
+iex -S mix
+iex> ExampleUsage.demo()
+```
+
+## Generated Files
+
+After running `nix run`, you'll find the generated Elixir code in:
+
+- `lib/proto/example/v1/example.pb.ex` - Generated protobuf modules
+
+## Project Structure
+
+```
+.
+├── flake.nix # Nix flake configuration
+├── mix.exs # Elixir project file
+├── proto/ # Protocol buffer definitions
+│ └── example/v1/
+│ └── example.proto
+└── lib/ # Elixir source code
+ ├── proto/ # Generated protobuf code (after running nix run)
+ └── example_usage.ex # Example usage code
+```
\ No newline at end of file
diff --git a/examples/elixir-basic/flake.lock b/examples/elixir-basic/flake.lock
new file mode 100644
index 0000000..34adfb8
--- /dev/null
+++ b/examples/elixir-basic/flake.lock
@@ -0,0 +1,113 @@
+{
+ "nodes": {
+ "bufrnix": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ],
+ "treefmt-nix": "treefmt-nix"
+ },
+ "locked": {
+ "path": "../..",
+ "type": "path"
+ },
+ "original": {
+ "path": "../..",
+ "type": "path"
+ },
+ "parent": []
+ },
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1747958103,
+ "narHash": "sha256-qmmFCrfBwSHoWw7cVK4Aj+fns+c54EBP8cGqp/yK410=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "fe51d34885f7b5e3e7b59572796e1bcb427eccb1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_2": {
+ "locked": {
+ "lastModified": 1757487488,
+ "narHash": "sha256-zwE/e7CuPJUWKdvvTCB7iunV4E/+G0lKfv4kk/5Izdg=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "ab0f3607a6c7486ea22229b92ed2d355f1482ee0",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "bufrnix": "bufrnix",
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs_2"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "treefmt-nix": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ },
+ "locked": {
+ "lastModified": 1749194973,
+ "narHash": "sha256-eEy8cuS0mZ2j/r/FE0/LYBSBcIs/MKOIVakwHVuqTfk=",
+ "owner": "numtide",
+ "repo": "treefmt-nix",
+ "rev": "a05be418a1af1198ca0f63facb13c985db4cb3c5",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "treefmt-nix",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/examples/elixir-basic/flake.nix b/examples/elixir-basic/flake.nix
new file mode 100644
index 0000000..8768b2f
--- /dev/null
+++ b/examples/elixir-basic/flake.nix
@@ -0,0 +1,49 @@
+{
+ description = "Basic Elixir example for bufrnix";
+
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
+ flake-utils.url = "github:numtide/flake-utils";
+ # bufrnix.url = "github:conneroisu/bufrnix";
+ bufrnix.url = "path:../..";
+ bufrnix.inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ outputs = {
+ self,
+ nixpkgs,
+ flake-utils,
+ bufrnix,
+ }:
+ flake-utils.lib.eachDefaultSystem (system: let
+ pkgs = nixpkgs.legacyPackages.${system};
+ in {
+ devShells.default = pkgs.mkShell {
+ packages = with pkgs; [
+ elixir
+ erlang
+ protobuf
+ ];
+ };
+ packages = {
+ default = bufrnix.lib.mkBufrnixPackage {
+ inherit pkgs;
+ config = {
+ root = ./.;
+ protoc = {
+ sourceDirectories = ["./proto"];
+ includeDirectories = ["./proto"];
+ files = ["./proto/example/v1/example.proto"];
+ };
+ languages.elixir = {
+ enable = true;
+ package = pkgs.protoc-gen-elixir;
+ outputPath = "lib/proto";
+ namespace = "Proto";
+ options = [];
+ };
+ };
+ };
+ };
+ });
+}
\ No newline at end of file
diff --git a/examples/elixir-basic/lib/example_usage.ex b/examples/elixir-basic/lib/example_usage.ex
new file mode 100644
index 0000000..1f6e66e
--- /dev/null
+++ b/examples/elixir-basic/lib/example_usage.ex
@@ -0,0 +1,65 @@
+defmodule ExampleUsage do
+ @moduledoc """
+ Example usage of generated Protobuf modules.
+
+ This module demonstrates how to use the Protobuf code generated by Bufrnix.
+ """
+
+ # Import the generated modules (these will exist after running bufrnix)
+ # alias Proto.Example.V1.ExampleMessage
+ # alias Proto.Example.V1.TimestampMessage
+ # alias Proto.Example.V1.StatusMessage
+ # alias Proto.Example.V1.ConfigMessage
+ # alias Proto.Example.V1.NotificationMessage
+
+ def demo do
+ IO.puts("Bufrnix Elixir Example")
+ IO.puts("======================")
+ IO.puts("")
+ IO.puts("This example demonstrates Protocol Buffer code generation for Elixir.")
+ IO.puts("")
+ IO.puts("To generate the protobuf code:")
+ IO.puts(" 1. Run: nix run")
+ IO.puts(" 2. The generated code will be in lib/proto/")
+ IO.puts("")
+ IO.puts("Once generated, you can use the modules like:")
+ IO.puts(" message = %Proto.Example.V1.ExampleMessage{")
+ IO.puts(" id: 1,")
+ IO.puts(" name: \"Test User\",")
+ IO.puts(" email: \"test@example.com\",")
+ IO.puts(" tags: [\"elixir\", \"protobuf\"]")
+ IO.puts(" }")
+ IO.puts("")
+ IO.puts(" # Encode to binary")
+ IO.puts(" binary = Proto.Example.V1.ExampleMessage.encode(message)")
+ IO.puts("")
+ IO.puts(" # Decode from binary")
+ IO.puts(" decoded = Proto.Example.V1.ExampleMessage.decode(binary)")
+ end
+
+ # Example functions that will work after code generation
+ def create_example_message do
+ # Uncomment after generation:
+ # %Proto.Example.V1.ExampleMessage{
+ # id: 1,
+ # name: "Alice",
+ # email: "alice@example.com",
+ # tags: ["developer", "elixir"],
+ # description: "An example user",
+ # created_at: %Proto.Example.V1.TimestampMessage{
+ # seconds: System.system_time(:second),
+ # nanos: 0
+ # }
+ # }
+ {:ok, "Example message will be created after protobuf generation"}
+ end
+
+ def encode_decode_example do
+ # Uncomment after generation:
+ # message = create_example_message()
+ # binary = Proto.Example.V1.ExampleMessage.encode(message)
+ # decoded = Proto.Example.V1.ExampleMessage.decode(binary)
+ # IO.inspect(decoded, label: "Decoded message")
+ {:ok, "Encode/decode will work after protobuf generation"}
+ end
+end
\ No newline at end of file
diff --git a/examples/elixir-basic/lib/proto/.formatter.exs b/examples/elixir-basic/lib/proto/.formatter.exs
new file mode 100644
index 0000000..3a43859
--- /dev/null
+++ b/examples/elixir-basic/lib/proto/.formatter.exs
@@ -0,0 +1,4 @@
+[
+ inputs: ["*.{ex,exs}", "{lib,test}/**/*.{ex,exs}"],
+ line_length: 120
+]
diff --git a/examples/elixir-basic/lib/proto/example/v1/example.pb.ex b/examples/elixir-basic/lib/proto/example/v1/example.pb.ex
new file mode 100644
index 0000000..e105bb6
--- /dev/null
+++ b/examples/elixir-basic/lib/proto/example/v1/example.pb.ex
@@ -0,0 +1,89 @@
+defmodule Example.V1.Status do
+ @moduledoc false
+
+ use Protobuf, enum: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :STATUS_UNSPECIFIED, 0
+ field :STATUS_ACTIVE, 1
+ field :STATUS_INACTIVE, 2
+ field :STATUS_PENDING, 3
+ field :STATUS_ARCHIVED, 4
+end
+
+defmodule Example.V1.ExampleMessage do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :id, 1, type: :int32
+ field :name, 2, type: :string
+ field :email, 3, type: :string
+ field :tags, 4, repeated: true, type: :string
+ field :description, 5, proto3_optional: true, type: :string
+ field :created_at, 6, type: Example.V1.TimestampMessage, json_name: "createdAt"
+end
+
+defmodule Example.V1.TimestampMessage do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :seconds, 1, type: :int64
+ field :nanos, 2, type: :int32
+end
+
+defmodule Example.V1.StatusMessage do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :id, 1, type: :int32
+ field :status, 2, type: Example.V1.Status, enum: true
+ field :message, 3, type: :string
+end
+
+defmodule Example.V1.ConfigMessage.SettingsEntry do
+ @moduledoc false
+
+ use Protobuf, map: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :key, 1, type: :string
+ field :value, 2, type: :string
+end
+
+defmodule Example.V1.ConfigMessage.ExamplesByIdEntry do
+ @moduledoc false
+
+ use Protobuf, map: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :key, 1, type: :int32
+ field :value, 2, type: Example.V1.ExampleMessage
+end
+
+defmodule Example.V1.ConfigMessage do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :settings, 1, repeated: true, type: Example.V1.ConfigMessage.SettingsEntry, map: true
+
+ field :examples_by_id, 2,
+ repeated: true,
+ type: Example.V1.ConfigMessage.ExamplesByIdEntry,
+ json_name: "examplesById",
+ map: true
+end
+
+defmodule Example.V1.NotificationMessage do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ oneof :notification, 0
+
+ field :email, 1, type: :string, oneof: 0
+ field :sms, 2, type: :string, oneof: 0
+ field :push, 3, type: :string, oneof: 0
+ field :content, 4, type: :string
+ field :sent_at, 5, type: Example.V1.TimestampMessage, json_name: "sentAt"
+end
diff --git a/examples/elixir-basic/mix.exs b/examples/elixir-basic/mix.exs
new file mode 100644
index 0000000..a83ab91
--- /dev/null
+++ b/examples/elixir-basic/mix.exs
@@ -0,0 +1,28 @@
+defmodule ElixirBasicExample.MixProject do
+ use Mix.Project
+
+ def project do
+ [
+ app: :elixir_basic_example,
+ version: "0.1.0",
+ elixir: "~> 1.14",
+ start_permanent: Mix.env() == :prod,
+ deps: deps()
+ ]
+ end
+
+ # Run "mix help compile.app" to learn about applications.
+ def application do
+ [
+ extra_applications: [:logger]
+ ]
+ end
+
+ # Run "mix help deps" to learn about dependencies.
+ defp deps do
+ [
+ {:protobuf, "~> 0.12.0"},
+ {:jason, "~> 1.4"}
+ ]
+ end
+end
\ No newline at end of file
diff --git a/examples/elixir-basic/proto/example/v1/example.proto b/examples/elixir-basic/proto/example/v1/example.proto
new file mode 100644
index 0000000..ff2b224
--- /dev/null
+++ b/examples/elixir-basic/proto/example/v1/example.proto
@@ -0,0 +1,52 @@
+syntax = "proto3";
+
+package example.v1;
+
+// Example message for demonstrating Elixir protobuf generation
+message ExampleMessage {
+ int32 id = 1;
+ string name = 2;
+ string email = 3;
+ repeated string tags = 4;
+ optional string description = 5;
+ TimestampMessage created_at = 6;
+}
+
+// Nested message to demonstrate complex types
+message TimestampMessage {
+ int64 seconds = 1;
+ int32 nanos = 2;
+}
+
+// Enum to demonstrate enum generation
+enum Status {
+ STATUS_UNSPECIFIED = 0;
+ STATUS_ACTIVE = 1;
+ STATUS_INACTIVE = 2;
+ STATUS_PENDING = 3;
+ STATUS_ARCHIVED = 4;
+}
+
+// Message with enum field
+message StatusMessage {
+ int32 id = 1;
+ Status status = 2;
+ string message = 3;
+}
+
+// Message with map field
+message ConfigMessage {
+ map settings = 1;
+ map examples_by_id = 2;
+}
+
+// Message with oneof field
+message NotificationMessage {
+ oneof notification {
+ string email = 1;
+ string sms = 2;
+ string push = 3;
+ }
+ string content = 4;
+ TimestampMessage sent_at = 5;
+}
\ No newline at end of file
diff --git a/examples/elixir-grpc/README.md b/examples/elixir-grpc/README.md
new file mode 100644
index 0000000..f239c8a
--- /dev/null
+++ b/examples/elixir-grpc/README.md
@@ -0,0 +1,135 @@
+# Elixir gRPC Example
+
+This example demonstrates gRPC service generation for Elixir using Bufrnix.
+
+## Features
+
+- gRPC service definition and implementation
+- Server and client code examples
+- Unary RPC methods
+- Server streaming RPC
+- Bidirectional streaming RPC
+- Error handling with gRPC status codes
+
+## Prerequisites
+
+- Nix with flakes enabled
+- Elixir development environment (provided by the flake)
+
+## Usage
+
+### Generate gRPC Code
+
+```bash
+# Generate the protobuf and gRPC code
+nix run
+
+# Or enter the development shell and generate
+nix develop
+nix run
+```
+
+### Run the gRPC Server
+
+```bash
+# Enter the development shell
+nix develop
+
+# Install dependencies
+mix deps.get
+
+# Generate protobuf code
+nix run
+
+# Start the gRPC server (runs on port 50051)
+iex -S mix
+```
+
+### Test with Client
+
+In another terminal:
+
+```bash
+# Enter the development shell
+nix develop
+
+# Start an IEx session
+iex -S mix
+
+# Connect to the server
+iex> {:ok, channel} = GRPC.Stub.connect("localhost:50051")
+
+# Create a user (after uncommenting the generated code)
+iex> request = %Proto.Example.V1.CreateUserRequest{
+...> username: "john",
+...> email: "john@example.com",
+...> full_name: "John Doe"
+...> }
+iex> {:ok, response} = Proto.Example.V1.UserService.Stub.create_user(channel, request)
+
+# List users
+iex> request = %Proto.Example.V1.ListUsersRequest{page_size: 10}
+iex> {:ok, response} = Proto.Example.V1.UserService.Stub.list_users(channel, request)
+```
+
+## Generated Files
+
+After running `nix run`, you'll find the generated code in:
+
+- `lib/proto/example/v1/service.pb.ex` - Protobuf message definitions
+- `lib/proto/example/v1/service_grpc.pb.ex` - gRPC service and stub definitions
+
+## Project Structure
+
+```
+.
+├── flake.nix # Nix flake configuration
+├── mix.exs # Elixir project file
+├── proto/ # Protocol buffer definitions
+│ └── example/v1/
+│ └── service.proto # gRPC service definition
+└── lib/
+ ├── proto/ # Generated code (after nix run)
+ └── elixir_grpc_example/
+ ├── application.ex # OTP application
+ ├── endpoint.ex # gRPC endpoint configuration
+ ├── user_service_server.ex # Server implementation
+ └── client.ex # Client example code
+```
+
+## Implementation Notes
+
+1. After running `nix run`, you'll need to uncomment the code in:
+ - `user_service_server.ex` - Server implementation
+ - `client.ex` - Client code
+ - `endpoint.ex` - Add the service to the endpoint
+
+2. The example includes a simple in-memory storage for demonstration purposes.
+
+3. Error handling is demonstrated using `GRPC.RPCError` for proper gRPC status codes.
+
+## Advanced Features
+
+### Streaming
+
+The example includes streaming RPCs:
+- `WatchUsers` - Server streaming for real-time updates
+- `BatchProcess` - Bidirectional streaming for batch operations
+
+### Authentication
+
+For production use, you would typically add authentication interceptors:
+
+```elixir
+defmodule MyApp.AuthInterceptor do
+ use GRPC.Server.Interceptor
+
+ def call(req, stream, next, _opts) do
+ with {:ok, _claims} <- verify_token(stream) do
+ next.(req, stream)
+ else
+ _ -> raise GRPC.RPCError, status: :unauthenticated
+ end
+ end
+end
+```
\ No newline at end of file
diff --git a/examples/elixir-grpc/flake.lock b/examples/elixir-grpc/flake.lock
new file mode 100644
index 0000000..34adfb8
--- /dev/null
+++ b/examples/elixir-grpc/flake.lock
@@ -0,0 +1,113 @@
+{
+ "nodes": {
+ "bufrnix": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ],
+ "treefmt-nix": "treefmt-nix"
+ },
+ "locked": {
+ "path": "../..",
+ "type": "path"
+ },
+ "original": {
+ "path": "../..",
+ "type": "path"
+ },
+ "parent": []
+ },
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1747958103,
+ "narHash": "sha256-qmmFCrfBwSHoWw7cVK4Aj+fns+c54EBP8cGqp/yK410=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "fe51d34885f7b5e3e7b59572796e1bcb427eccb1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_2": {
+ "locked": {
+ "lastModified": 1757487488,
+ "narHash": "sha256-zwE/e7CuPJUWKdvvTCB7iunV4E/+G0lKfv4kk/5Izdg=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "ab0f3607a6c7486ea22229b92ed2d355f1482ee0",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "bufrnix": "bufrnix",
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs_2"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "treefmt-nix": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ },
+ "locked": {
+ "lastModified": 1749194973,
+ "narHash": "sha256-eEy8cuS0mZ2j/r/FE0/LYBSBcIs/MKOIVakwHVuqTfk=",
+ "owner": "numtide",
+ "repo": "treefmt-nix",
+ "rev": "a05be418a1af1198ca0f63facb13c985db4cb3c5",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "treefmt-nix",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/examples/elixir-grpc/flake.nix b/examples/elixir-grpc/flake.nix
new file mode 100644
index 0000000..ad38cba
--- /dev/null
+++ b/examples/elixir-grpc/flake.nix
@@ -0,0 +1,53 @@
+{
+ description = "Elixir gRPC example for bufrnix";
+
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
+ flake-utils.url = "github:numtide/flake-utils";
+ # bufrnix.url = "github:conneroisu/bufrnix";
+ bufrnix.url = "path:../..";
+ bufrnix.inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ outputs = {
+ self,
+ nixpkgs,
+ flake-utils,
+ bufrnix,
+ }:
+ flake-utils.lib.eachDefaultSystem (system: let
+ pkgs = nixpkgs.legacyPackages.${system};
+ in {
+ devShells.default = pkgs.mkShell {
+ packages = with pkgs; [
+ elixir
+ erlang
+ protobuf
+ ];
+ };
+ packages = {
+ default = bufrnix.lib.mkBufrnixPackage {
+ inherit pkgs;
+ config = {
+ root = ./.;
+ protoc = {
+ sourceDirectories = ["./proto"];
+ includeDirectories = ["./proto"];
+ files = ["./proto/example/v1/service.proto"];
+ };
+ languages.elixir = {
+ enable = true;
+ package = pkgs.protoc-gen-elixir;
+ outputPath = "lib/proto";
+ namespace = "Proto";
+ options = [];
+ grpc = {
+ enable = true;
+ options = [];
+ };
+ };
+ };
+ };
+ };
+ });
+}
\ No newline at end of file
diff --git a/examples/elixir-grpc/lib/elixir_grpc_example/application.ex b/examples/elixir-grpc/lib/elixir_grpc_example/application.ex
new file mode 100644
index 0000000..9057048
--- /dev/null
+++ b/examples/elixir-grpc/lib/elixir_grpc_example/application.ex
@@ -0,0 +1,16 @@
+defmodule ElixirGrpcExample.Application do
+ @moduledoc false
+
+ use Application
+
+ @impl true
+ def start(_type, _args) do
+ children = [
+ # Start the gRPC server
+ {GRPC.Server.Supervisor, endpoint: ElixirGrpcExample.Endpoint, port: 50051, start_server: true}
+ ]
+
+ opts = [strategy: :one_for_one, name: ElixirGrpcExample.Supervisor]
+ Supervisor.start_link(children, opts)
+ end
+end
\ No newline at end of file
diff --git a/examples/elixir-grpc/lib/elixir_grpc_example/client.ex b/examples/elixir-grpc/lib/elixir_grpc_example/client.ex
new file mode 100644
index 0000000..7dece45
--- /dev/null
+++ b/examples/elixir-grpc/lib/elixir_grpc_example/client.ex
@@ -0,0 +1,57 @@
+defmodule ElixirGrpcExample.Client do
+ @moduledoc """
+ gRPC client for testing the UserService.
+
+ This module demonstrates how to use the generated gRPC client code.
+ """
+
+ def connect(host \\ "localhost", port \\ 50051) do
+ # After generation, uncomment:
+ # {:ok, channel} = GRPC.Stub.connect("#{host}:#{port}")
+ # channel
+ {:ok, "Client will connect after protobuf generation"}
+ end
+
+ def create_user(channel, username, email, full_name) do
+ # After generation, uncomment:
+ # request = %Proto.Example.V1.CreateUserRequest{
+ # username: username,
+ # email: email,
+ # full_name: full_name,
+ # password: "temporary"
+ # }
+ # Proto.Example.V1.UserService.Stub.create_user(channel, request)
+ {:ok, "create_user will work after protobuf generation"}
+ end
+
+ def get_user(channel, id) do
+ # After generation, uncomment:
+ # request = %Proto.Example.V1.GetUserRequest{id: id}
+ # Proto.Example.V1.UserService.Stub.get_user(channel, request)
+ {:ok, "get_user will work after protobuf generation"}
+ end
+
+ def list_users(channel, page_size \\ 10) do
+ # After generation, uncomment:
+ # request = %Proto.Example.V1.ListUsersRequest{
+ # page_size: page_size,
+ # page: 1
+ # }
+ # Proto.Example.V1.UserService.Stub.list_users(channel, request)
+ {:ok, "list_users will work after protobuf generation"}
+ end
+
+ def demo do
+ IO.puts("Elixir gRPC Client Demo")
+ IO.puts("========================")
+ IO.puts("")
+ IO.puts("After running 'nix run' to generate the protobuf code:")
+ IO.puts(" 1. Uncomment the code in this module")
+ IO.puts(" 2. Start the server: iex -S mix")
+ IO.puts(" 3. In another terminal, connect a client:")
+ IO.puts("")
+ IO.puts(" iex> {:ok, channel} = ElixirGrpcExample.Client.connect()")
+ IO.puts(" iex> ElixirGrpcExample.Client.list_users(channel)")
+ IO.puts("")
+ end
+end
\ No newline at end of file
diff --git a/examples/elixir-grpc/lib/elixir_grpc_example/endpoint.ex b/examples/elixir-grpc/lib/elixir_grpc_example/endpoint.ex
new file mode 100644
index 0000000..f694027
--- /dev/null
+++ b/examples/elixir-grpc/lib/elixir_grpc_example/endpoint.ex
@@ -0,0 +1,7 @@
+defmodule ElixirGrpcExample.Endpoint do
+ use GRPC.Endpoint
+
+ # This will be updated after protobuf generation to include the actual service
+ # intercept GRPC.Server.Interceptors.Logger
+ # run ElixirGrpcExample.UserService.Server
+end
\ No newline at end of file
diff --git a/examples/elixir-grpc/lib/elixir_grpc_example/user_service_server.ex b/examples/elixir-grpc/lib/elixir_grpc_example/user_service_server.ex
new file mode 100644
index 0000000..5b60d28
--- /dev/null
+++ b/examples/elixir-grpc/lib/elixir_grpc_example/user_service_server.ex
@@ -0,0 +1,90 @@
+defmodule ElixirGrpcExample.UserServiceServer do
+ @moduledoc """
+ gRPC server implementation for UserService.
+
+ This module will use the generated protobuf modules after running `nix run`.
+ """
+
+ # After generation, uncomment:
+ # use GRPC.Server, service: Proto.Example.V1.UserService.Service
+
+ # Sample in-memory storage for demonstration
+ @initial_users %{
+ 1 => %{
+ id: 1,
+ username: "alice",
+ email: "alice@example.com",
+ full_name: "Alice Smith",
+ roles: ["user", "admin"],
+ created_at: 1234567890,
+ updated_at: 1234567890
+ },
+ 2 => %{
+ id: 2,
+ username: "bob",
+ email: "bob@example.com",
+ full_name: "Bob Johnson",
+ roles: ["user"],
+ created_at: 1234567891,
+ updated_at: 1234567891
+ }
+ }
+
+ def init(_args) do
+ {:ok, %{users: @initial_users, next_id: 3}}
+ end
+
+ # Uncomment after protobuf generation:
+
+ # @spec create_user(Proto.Example.V1.CreateUserRequest.t(), GRPC.Server.Stream.t()) ::
+ # Proto.Example.V1.CreateUserResponse.t()
+ # def create_user(request, _stream) do
+ # # Implementation would go here
+ # user = %Proto.Example.V1.User{
+ # id: 3,
+ # username: request.username,
+ # email: request.email,
+ # full_name: request.full_name,
+ # roles: ["user"],
+ # created_at: System.system_time(:second),
+ # updated_at: System.system_time(:second)
+ # }
+ #
+ # %Proto.Example.V1.CreateUserResponse{
+ # user: user,
+ # message: "User created successfully"
+ # }
+ # end
+
+ # @spec get_user(Proto.Example.V1.GetUserRequest.t(), GRPC.Server.Stream.t()) ::
+ # Proto.Example.V1.GetUserResponse.t()
+ # def get_user(request, _stream) do
+ # case @initial_users[request.id] do
+ # nil ->
+ # raise GRPC.RPCError, status: :not_found, message: "User not found"
+ #
+ # user_data ->
+ # user = struct(Proto.Example.V1.User, user_data)
+ # %Proto.Example.V1.GetUserResponse{user: user}
+ # end
+ # end
+
+ # @spec list_users(Proto.Example.V1.ListUsersRequest.t(), GRPC.Server.Stream.t()) ::
+ # Proto.Example.V1.ListUsersResponse.t()
+ # def list_users(request, _stream) do
+ # users =
+ # @initial_users
+ # |> Map.values()
+ # |> Enum.map(&struct(Proto.Example.V1.User, &1))
+ # |> Enum.take(request.page_size || 10)
+ #
+ # %Proto.Example.V1.ListUsersResponse{
+ # users: users,
+ # total: length(users),
+ # page: request.page || 1,
+ # page_size: request.page_size || 10
+ # }
+ # end
+
+ # Additional method implementations would go here...
+end
\ No newline at end of file
diff --git a/examples/elixir-grpc/lib/proto/.formatter.exs b/examples/elixir-grpc/lib/proto/.formatter.exs
new file mode 100644
index 0000000..3a43859
--- /dev/null
+++ b/examples/elixir-grpc/lib/proto/.formatter.exs
@@ -0,0 +1,4 @@
+[
+ inputs: ["*.{ex,exs}", "{lib,test}/**/*.{ex,exs}"],
+ line_length: 120
+]
diff --git a/examples/elixir-grpc/lib/proto/example/v1/service.pb.ex b/examples/elixir-grpc/lib/proto/example/v1/service.pb.ex
new file mode 100644
index 0000000..e357511
--- /dev/null
+++ b/examples/elixir-grpc/lib/proto/example/v1/service.pb.ex
@@ -0,0 +1,162 @@
+defmodule Example.V1.UserEvent.EventType do
+ @moduledoc false
+
+ use Protobuf, enum: true, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :EVENT_TYPE_UNSPECIFIED, 0
+ field :EVENT_TYPE_CREATED, 1
+ field :EVENT_TYPE_UPDATED, 2
+ field :EVENT_TYPE_DELETED, 3
+end
+
+defmodule Example.V1.User do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :id, 1, type: :int32
+ field :username, 2, type: :string
+ field :email, 3, type: :string
+ field :full_name, 4, type: :string, json_name: "fullName"
+ field :roles, 5, repeated: true, type: :string
+ field :created_at, 6, type: :int64, json_name: "createdAt"
+ field :updated_at, 7, type: :int64, json_name: "updatedAt"
+end
+
+defmodule Example.V1.CreateUserRequest do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :username, 1, type: :string
+ field :email, 2, type: :string
+ field :full_name, 3, type: :string, json_name: "fullName"
+ field :password, 4, type: :string
+end
+
+defmodule Example.V1.CreateUserResponse do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :user, 1, type: Example.V1.User
+ field :message, 2, type: :string
+end
+
+defmodule Example.V1.GetUserRequest do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :id, 1, type: :int32
+end
+
+defmodule Example.V1.GetUserResponse do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :user, 1, type: Example.V1.User
+end
+
+defmodule Example.V1.ListUsersRequest do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :page_size, 1, type: :int32, json_name: "pageSize"
+ field :page, 2, type: :int32
+ field :search, 3, type: :string
+end
+
+defmodule Example.V1.ListUsersResponse do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :users, 1, repeated: true, type: Example.V1.User
+ field :total, 2, type: :int32
+ field :page, 3, type: :int32
+ field :page_size, 4, type: :int32, json_name: "pageSize"
+end
+
+defmodule Example.V1.UpdateUserRequest do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :id, 1, type: :int32
+ field :email, 2, type: :string
+ field :full_name, 3, type: :string, json_name: "fullName"
+ field :roles, 4, repeated: true, type: :string
+end
+
+defmodule Example.V1.UpdateUserResponse do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :user, 1, type: Example.V1.User
+ field :message, 2, type: :string
+end
+
+defmodule Example.V1.DeleteUserRequest do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :id, 1, type: :int32
+end
+
+defmodule Example.V1.DeleteUserResponse do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :success, 1, type: :bool
+ field :message, 2, type: :string
+end
+
+defmodule Example.V1.UserEvent do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :event_type, 1, type: Example.V1.UserEvent.EventType, json_name: "eventType", enum: true
+ field :user, 2, type: Example.V1.User
+ field :timestamp, 3, type: :int64
+end
+
+defmodule Example.V1.WatchUsersRequest do
+ @moduledoc false
+
+ use Protobuf, protoc_gen_elixir_version: "0.15.0", syntax: :proto3
+
+ field :user_ids, 1, repeated: true, type: :int32, json_name: "userIds"
+end
+
+defmodule Example.V1.UserService.Service do
+ @moduledoc false
+
+ use GRPC.Service, name: "example.v1.UserService", protoc_gen_elixir_version: "0.15.0"
+
+ rpc :CreateUser, Example.V1.CreateUserRequest, Example.V1.CreateUserResponse
+
+ rpc :GetUser, Example.V1.GetUserRequest, Example.V1.GetUserResponse
+
+ rpc :ListUsers, Example.V1.ListUsersRequest, Example.V1.ListUsersResponse
+
+ rpc :UpdateUser, Example.V1.UpdateUserRequest, Example.V1.UpdateUserResponse
+
+ rpc :DeleteUser, Example.V1.DeleteUserRequest, Example.V1.DeleteUserResponse
+
+ rpc :WatchUsers, Example.V1.WatchUsersRequest, stream(Example.V1.UserEvent)
+
+ rpc :BatchProcess, stream(Example.V1.UpdateUserRequest), stream(Example.V1.UpdateUserResponse)
+end
+
+defmodule Example.V1.UserService.Stub do
+ @moduledoc false
+
+ use GRPC.Stub, service: Example.V1.UserService.Service
+end
diff --git a/examples/elixir-grpc/mix.exs b/examples/elixir-grpc/mix.exs
new file mode 100644
index 0000000..77ad286
--- /dev/null
+++ b/examples/elixir-grpc/mix.exs
@@ -0,0 +1,31 @@
+defmodule ElixirGrpcExample.MixProject do
+ use Mix.Project
+
+ def project do
+ [
+ app: :elixir_grpc_example,
+ version: "0.1.0",
+ elixir: "~> 1.14",
+ start_permanent: Mix.env() == :prod,
+ deps: deps()
+ ]
+ end
+
+ # Run "mix help compile.app" to learn about applications.
+ def application do
+ [
+ extra_applications: [:logger],
+ mod: {ElixirGrpcExample.Application, []}
+ ]
+ end
+
+ # Run "mix help deps" to learn about dependencies.
+ defp deps do
+ [
+ {:grpc, "~> 0.7.0"},
+ {:protobuf, "~> 0.12.0"},
+ {:jason, "~> 1.4"},
+ {:gun, "~> 2.0"}
+ ]
+ end
+end
\ No newline at end of file
diff --git a/examples/elixir-grpc/proto/example/v1/service.proto b/examples/elixir-grpc/proto/example/v1/service.proto
new file mode 100644
index 0000000..a64ba16
--- /dev/null
+++ b/examples/elixir-grpc/proto/example/v1/service.proto
@@ -0,0 +1,111 @@
+syntax = "proto3";
+
+package example.v1;
+
+// User message
+message User {
+ int32 id = 1;
+ string username = 2;
+ string email = 3;
+ string full_name = 4;
+ repeated string roles = 5;
+ int64 created_at = 6;
+ int64 updated_at = 7;
+}
+
+// Request/Response messages for User service
+message CreateUserRequest {
+ string username = 1;
+ string email = 2;
+ string full_name = 3;
+ string password = 4;
+}
+
+message CreateUserResponse {
+ User user = 1;
+ string message = 2;
+}
+
+message GetUserRequest {
+ int32 id = 1;
+}
+
+message GetUserResponse {
+ User user = 1;
+}
+
+message ListUsersRequest {
+ int32 page_size = 1;
+ int32 page = 2;
+ string search = 3;
+}
+
+message ListUsersResponse {
+ repeated User users = 1;
+ int32 total = 2;
+ int32 page = 3;
+ int32 page_size = 4;
+}
+
+message UpdateUserRequest {
+ int32 id = 1;
+ string email = 2;
+ string full_name = 3;
+ repeated string roles = 4;
+}
+
+message UpdateUserResponse {
+ User user = 1;
+ string message = 2;
+}
+
+message DeleteUserRequest {
+ int32 id = 1;
+}
+
+message DeleteUserResponse {
+ bool success = 1;
+ string message = 2;
+}
+
+// Streaming messages
+message UserEvent {
+ enum EventType {
+ EVENT_TYPE_UNSPECIFIED = 0;
+ EVENT_TYPE_CREATED = 1;
+ EVENT_TYPE_UPDATED = 2;
+ EVENT_TYPE_DELETED = 3;
+ }
+
+ EventType event_type = 1;
+ User user = 2;
+ int64 timestamp = 3;
+}
+
+message WatchUsersRequest {
+ repeated int32 user_ids = 1;
+}
+
+// User management service
+service UserService {
+ // Create a new user
+ rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
+
+ // Get a user by ID
+ rpc GetUser(GetUserRequest) returns (GetUserResponse);
+
+ // List users with pagination
+ rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
+
+ // Update user information
+ rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
+
+ // Delete a user
+ rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
+
+ // Stream user events
+ rpc WatchUsers(WatchUsersRequest) returns (stream UserEvent);
+
+ // Bidirectional streaming for batch operations
+ rpc BatchProcess(stream UpdateUserRequest) returns (stream UpdateUserResponse);
+}
\ No newline at end of file
diff --git a/src/languages/elixir/default.nix b/src/languages/elixir/default.nix
new file mode 100644
index 0000000..09236e9
--- /dev/null
+++ b/src/languages/elixir/default.nix
@@ -0,0 +1,92 @@
+{
+ pkgs,
+ config,
+ lib,
+ cfg ? config.languages.elixir,
+ ...
+}:
+with lib; let
+ # Define output path and options
+ outputPath = cfg.outputPath;
+ elixirOptions = cfg.options;
+
+ # Import Elixir-specific sub-modules
+ grpcModule = import ./grpc.nix {
+ inherit pkgs lib;
+ cfg =
+ (cfg.grpc or {enable = false;})
+ // {
+ outputPath = outputPath;
+ };
+ };
+
+ validateModule = import ./validate.nix {
+ inherit pkgs lib;
+ cfg =
+ (cfg.validate or {enable = false;})
+ // {
+ outputPath = outputPath;
+ };
+ };
+
+ # Combine all sub-modules
+ combineModuleAttrs = attr:
+ concatLists (catAttrs attr [
+ grpcModule
+ validateModule
+ ]);
+in {
+ # Runtime dependencies for Elixir code generation
+ runtimeInputs =
+ [
+ cfg.package
+ ]
+ ++ (combineModuleAttrs "runtimeInputs");
+
+ # Protoc plugin configuration for Elixir
+ protocPlugins =
+ # Only add the base plugin if gRPC is not handling it
+ (if (cfg.grpc.enable or false) then [] else [
+ "--elixir_out=${outputPath}"
+ ])
+ ++ (optionals (elixirOptions != []) [
+ "--elixir_opt=${concatStringsSep " --elixir_opt=" elixirOptions}"
+ ])
+ ++ (combineModuleAttrs "protocPlugins");
+
+ # Initialization hook for Elixir
+ initHooks =
+ ''
+ # Create elixir-specific directories
+ mkdir -p "${outputPath}"
+ ${optionalString (cfg.namespace != "") ''
+ echo "Creating Elixir modules with namespace: ${cfg.namespace}"
+ ''}
+ ''
+ + concatStrings (catAttrs "initHooks" [
+ grpcModule
+ validateModule
+ ]);
+
+ # Code generation hook for Elixir
+ generateHooks =
+ ''
+ # Elixir-specific code generation steps
+ echo "Generating Elixir code..."
+ mkdir -p ${outputPath}
+
+ # Add .formatter.exs if it doesn't exist
+ if [ ! -f "${outputPath}/.formatter.exs" ]; then
+ cat > "${outputPath}/.formatter.exs" << 'EOF'
+[
+ inputs: ["*.{ex,exs}", "{lib,test}/**/*.{ex,exs}"],
+ line_length: 120
+]
+EOF
+ fi
+ ''
+ + concatStrings (catAttrs "generateHooks" [
+ grpcModule
+ validateModule
+ ]);
+}
\ No newline at end of file
diff --git a/src/languages/elixir/grpc.nix b/src/languages/elixir/grpc.nix
new file mode 100644
index 0000000..dbd6e0c
--- /dev/null
+++ b/src/languages/elixir/grpc.nix
@@ -0,0 +1,40 @@
+{
+ pkgs,
+ lib,
+ cfg,
+ ...
+}:
+with lib; let
+ outputPath = cfg.outputPath;
+ grpcOptions = cfg.options or [];
+in
+ if (cfg.enable or false) then {
+ # Runtime dependencies for Elixir gRPC
+ runtimeInputs = [
+ cfg.package or pkgs.protoc-gen-elixir
+ ];
+
+ # Protoc plugin configuration for Elixir gRPC
+ protocPlugins =
+ [
+ "--elixir_out=plugins=grpc:${outputPath}"
+ ]
+ ++ (optionals (grpcOptions != []) [
+ "--elixir_opt=${concatStringsSep " --elixir_opt=" grpcOptions}"
+ ]);
+
+ # Initialization hook for Elixir gRPC
+ initHooks = ''
+ echo "Enabling Elixir gRPC generation..."
+ '';
+
+ # Code generation hook for Elixir gRPC
+ generateHooks = ''
+ echo "Generated Elixir gRPC services in ${outputPath}"
+ '';
+ } else {
+ runtimeInputs = [];
+ protocPlugins = [];
+ initHooks = "";
+ generateHooks = "";
+ }
\ No newline at end of file
diff --git a/src/languages/elixir/validate.nix b/src/languages/elixir/validate.nix
new file mode 100644
index 0000000..47f0e9a
--- /dev/null
+++ b/src/languages/elixir/validate.nix
@@ -0,0 +1,32 @@
+{
+ pkgs,
+ lib,
+ cfg,
+ ...
+}:
+with lib; let
+ outputPath = cfg.outputPath;
+in
+ if (cfg.enable or false) then {
+ # Runtime dependencies for Elixir validation
+ runtimeInputs = [];
+
+ # Protoc plugin configuration for Elixir validation
+ protocPlugins = [];
+
+ # Initialization hook for Elixir validation
+ initHooks = ''
+ echo "Preparing Elixir validation support..."
+ '';
+
+ # Code generation hook for Elixir validation
+ generateHooks = ''
+ # Add validation support hints
+ echo "Note: To use validation in Elixir, add protoc_validate to your mix.exs dependencies"
+ '';
+ } else {
+ runtimeInputs = [];
+ protocPlugins = [];
+ initHooks = "";
+ generateHooks = "";
+ }
\ No newline at end of file
diff --git a/src/lib/bufrnix-options.nix b/src/lib/bufrnix-options.nix
index ee90482..c68c190 100644
--- a/src/lib/bufrnix-options.nix
+++ b/src/lib/bufrnix-options.nix
@@ -1256,6 +1256,106 @@ with lib; {
};
};
+ # Elixir language options
+ elixir = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable Elixir code generation";
+ };
+
+ package = mkOption {
+ type = types.package;
+ defaultText = literalExpression "pkgs.protoc-gen-elixir";
+ description = "The protoc-gen-elixir package to use";
+ };
+
+ outputPath = mkOption {
+ type = types.either types.str (types.listOf types.str);
+ default = "lib";
+ description = "Output directory(ies) for generated Elixir code";
+ example = literalExpression ''
+ [
+ "lib/proto"
+ "lib/generated"
+ ]
+ '';
+ };
+
+ options = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Options to pass to protoc-gen-elixir";
+ };
+
+ files = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ description = "Proto files to compile for Elixir only. Overrides global protoc.files. If null, uses global protoc.files.";
+ example = [
+ "./proto/services/v1/user_service.proto"
+ "./proto/common/v1/types.proto"
+ ];
+ };
+
+ additionalFiles = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Additional proto files to compile for Elixir. Extends global protoc.files.";
+ example = [
+ "./proto/elixir/v1/elixir_extensions.proto"
+ "./proto/services/v1/notification_service.proto"
+ ];
+ };
+
+ namespace = mkOption {
+ type = types.str;
+ default = "";
+ description = "Elixir module namespace prefix for generated code";
+ example = "MyApp.Proto";
+ };
+
+ grpc = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable gRPC code generation for Elixir";
+ };
+
+ package = mkOption {
+ type = types.package;
+ defaultText = literalExpression "pkgs.protoc-gen-elixir";
+ description = "The protoc-gen-elixir package to use (same as parent for Elixir)";
+ };
+
+ options = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Options to pass to protoc-gen-elixir for gRPC";
+ };
+ };
+
+ validate = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable validation support for Elixir protobuf messages";
+ };
+
+ package = mkOption {
+ type = types.package;
+ defaultText = literalExpression "null";
+ description = "The validation package to use (if any)";
+ };
+
+ options = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "Options for Elixir validation";
+ };
+ };
+ };
+
# Documentation language options
doc = {
enable = mkOption {
diff --git a/src/lib/mkBufrnix.nix b/src/lib/mkBufrnix.nix
index 535ad7e..fc05fdf 100644
--- a/src/lib/mkBufrnix.nix
+++ b/src/lib/mkBufrnix.nix
@@ -97,6 +97,11 @@ with pkgs.lib; let
package = pkgs.protoc-gen-dart;
grpc.package = pkgs.protoc-gen-dart;
};
+ elixir = {
+ package = pkgs.protoc-gen-elixir;
+ grpc.package = pkgs.protoc-gen-elixir;
+ validate.package = null; # Not yet in nixpkgs
+ };
doc = {
package = pkgs.protoc-gen-doc;
};
diff --git a/test-examples.sh b/test-examples.sh
index 0fe59ee..ebafda6 100755
--- a/test-examples.sh
+++ b/test-examples.sh
@@ -427,6 +427,15 @@ test_example "java-protovalidate" \
"gen/java/build.gradle" \
"gen/java/pom.xml"
+# Test Elixir examples
+test_example "elixir-basic" \
+ "lib/proto/example/v1/example.pb.ex" \
+ "lib/proto/.formatter.exs"
+
+test_example "elixir-grpc" \
+ "lib/proto/example/v1/service.pb.ex" \
+ "lib/proto/.formatter.exs"
+
# Summary
echo -e "\n${YELLOW}Test Summary:${NC}"
echo -e "${GREEN}Passed: ${#PASSED_TESTS[@]}${NC}"