proto
proto.cr
📦 Protobuf runtime and protoc plugin for Crystal.
This library was generated using an AI agent.
Installation
dependencies:
proto:
github: kojix2/proto
Build plugin
shards build
Generate .pb.cr
protoc --plugin=protoc-gen-crystal=bin/protoc-gen-crystal \
--crystal_out=. path/to/example.proto
Generated .pb.cr files include:
- message / enum definitions
- RPC metadata for each service
- extension metadata at file and message scope
Client/server stubs are not generated for services themselves. For gRPC stubs, use grpc.cr's protoc-gen-crystal-grpc.
Runtime usage
require "proto"
require "./example.pb.cr"
msg = Example::HelloRequest.new
msg.name = "Alice"
bytes = msg.encode
parsed = Example::HelloRequest.decode(bytes)
puts parsed.name
Generated message types expose two wire-level API layers:
decode/encode: strict APIs that validate required fields across the full message treedecode_partial/encode_partial: explicit partial APIs for working with incomplete messages
Strict validation failures raise Proto::RequiredFieldError, which is a Proto::ValidationError.
begin
parsed = Example::Settings.decode(bytes)
rescue ex : Proto::RequiredFieldError
puts ex.message
end
For proto2-style required fields, generated code keeps the value surface non-nilable where practical and exposes presence separately:
settings = Example::Settings.new
settings.retries = 3
settings.has_retries? # => true
settings.clear_retries
settings.has_retries? # => false
Examples
Minimal self-contained example:
crystal run examples/basic_roundtrip.cr
Example output:
encoded bytes: 9
decoded id: 42
decoded name: Alice
Strict vs partial required-field example:
crystal run examples/required_strict_partial.cr
Example output:
strict encode error: Missing required field: value
partial bytes: 2
strict decode error: Missing required field: value
partial child present?: true
partial child has_value?: false
Example for service metadata / extension metadata generation:
crystal run examples/service_extensions_codegen.cr
This example constructs descriptors inline and shows that the generated code includes output like the following.
module DemoService
METHODS = [
{
name: "Ping",
request_type: ".demo.PingRequest",
response_type: ".demo.PingReply",
client_streaming: false,
server_streaming: false,
path: "/demo.DemoService/Ping",
},
] of NamedTuple(name: String, request_type: String, response_type: String, client_streaming: Bool, server_streaming: Bool, path: String)
end
module Extensions
RPC_REQUIRED = ExtensionDescriptor.new(
name: "rpc_required",
extendee: ".google.protobuf.MethodOptions",
number: 50001,
label: Proto::Bootstrap::FieldLabel::LABEL_OPTIONAL,
type: Proto::Bootstrap::FieldType::TYPE_BOOL,
type_name: "",
)
end
Testing
crystal spec
proto
- 0
- 0
- 0
- 2
- 0
- about 4 hours ago
- April 17, 2026
Mon, 20 Apr 2026 06:03:57 GMT