Compare commits

..

66 Commits

Author SHA1 Message Date
openhands de6af24565 Refactor codegen/src/generator.rs into a modular directory structure and fix compilation errors.
Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-25 04:11:57 +00:00
charles 6085ef3923 More generated files 2026-05-20 08:12:38 -07:00
charles f03e1afdbf Regenerate protobuf files 2026-05-20 08:09:11 -07:00
charles 7e43e09f66 Clean up generated code and implement strip_boilerplate
Remove redundant newlines and duplicate headers from generated files
and prevent the creation of empty sub-modules. Implement the
`strip_boilerplate` function to remove generated headers and
attributes, including a comprehensive test suite.
2026-05-19 23:08:05 -07:00
charles 117cbf812b Introduce alloc feature for optional allocation
Wrap heap-allocated types and service generation in the `alloc` feature
flag to support environments without a memory allocator.
2026-05-19 21:55:18 -07:00
charles 6910f11d69 Add no_std test example and update alloc gating
Update codegen to make `bytes` imports conditional on the `alloc`
feature and add a test example for the `thumbv7em-none-eabihf`
target.
2026-05-18 08:43:22 -07:00
charles fa4d8cca83 Support no_std in roto-runtime
Add #![no_std] to the runtime crate and introduce optional std and
alloc features. Update the code generator to be compatible and add a
no_std_test example. Remove the generator binary.
2026-05-17 19:55:44 -07:00
charles 956993d1d0 Split service and proto gen 2026-05-17 18:53:00 -07:00
charles b2c5639338 add: grpc_bench 2026-05-17 16:45:44 -07:00
charles 33f3e58f74 Use original method names for gRPC paths
Stop converting method names to snake_case when generating the gRPC
service paths to maintain compatibility with protobuf definitions.
2026-05-17 10:44:07 -07:00
charles 89455190b1 Many fixes later 2026-05-17 00:43:21 -07:00
charles b11b068345 Update crate references in codegen tests
Update imports to use `roto_tonic` instead of `crate` for BufferPool
and StatusBody. Remove the broad replacement of `crate` with `roto`
and add error output to `test_map_build.rs`.
2026-05-16 19:45:46 -07:00
charles 56fc787f7a Use roto_tonic types in hello_world example
Fix the client's poll_ready implementation to properly propagate
readiness.
2026-05-16 17:24:46 -07:00
charles 43dcfabcdc Include test case and auto generated code 2026-05-16 16:57:12 -07:00
charles 2202548ae5 Support gRPC packages in generated code
Include package names in the `NamedService` identifier and request
paths. Change generated imports to use `crate` and add `tokio` and
`tokio-stream` dependencies to `roto-tonic`.
2026-05-16 16:57:01 -07:00
charles 809a0d844c Update StatusBody to support gRPC trailers
Change StatusBody from a tuple struct to a struct containing both data
and trailers. Update the codegen to use the new StatusBody::new
constructor to specify gRPC status codes.

Also remove the temp_test_project.
2026-05-15 18:57:15 -07:00
charles db89c9842a Add http dependency to helloworld build test 2026-05-15 14:25:16 -07:00
charles 546b74149e Fix code generator and add compilation test
Correct module paths and accessor usage in the generator to ensure
generated code builds. Add a test that verifies this by compiling the
generated code in a temporary project.
2026-05-15 14:13:18 -07:00
charles 372eab94d4 Fix path resolution in codegen build tests
Use absolute paths for temporary project directories and dependencies
to improve reliability. Update the generated Cargo.toml with additional
dependencies and remove the offline flag from cargo build.
2026-05-15 11:00:50 -07:00
charles a3fece24fc Update Orchestrator persona to use subagent tool 2026-05-15 10:52:07 -07:00
charles cc82e990ba Update generated code build test
Use absolute paths for the test project, add roto-tonic and other
dependencies, and capture build output on failure.
2026-05-15 10:49:56 -07:00
charles a9fef01950 Update orchestrator persona instructions
Require the orchestrator to tell subagents which persona they are and
provide the path to the persona file.
2026-05-15 10:30:21 -07:00
charles da7ba47505 Fix generated code and update example integration
Resolve `Result` ambiguity and lifetime issues in generated services.
Use `file_stem` for proto filenames. Make `StatusBody` public in
`roto-tonic` and update the `hello_world` build process.
2026-05-15 10:29:54 -07:00
charles 00b3dcd9a6 Add agent personas for task orchestration
Define roles and workflows for the Orchestrator, Researcher,
Coordinator, and Codemonkey agents, and ignore the artifacts directory.
2026-05-15 09:38:50 -07:00
charles 08be61966c Fix stuff 2026-05-13 23:08:21 -07:00
charles db2bf1bffd Implement buffer pooling in hello_world example
Introduce BufferPool to reuse BytesMut allocations for requests and
responses. Update AGENTS.md to require build and test success.
2026-05-13 09:45:27 -07:00
charles dfdcd8ae46 Add README for hello_world example 2026-05-12 14:16:27 -07:00
charles daa42d2d07 Update dependencies in codegen build tests
Bump bytes version to 1.7 and synchronize dependencies across all
generated Cargo.toml files in codegen tests.
2026-05-12 14:03:28 -07:00
charles 804ff3ead0 Checkpoint for gRPC implementation 2026-05-12 13:44:53 -07:00
charles 02a0b0d908 Initial commit of AI generated slop 2026-05-11 22:31:04 -07:00
charles 17ab0d1670 Update generated protobuf code 2026-05-11 17:43:56 -07:00
charles 792f2d5f5d Ensure we use roto_runtime rather than crate for error types 2026-05-11 17:43:25 -07:00
charles 3280092e08 Update README supported features list
Move oneof, map, and default value support from unsupported to
supported features.
2026-05-08 23:47:41 -07:00
charles 6e045fd808 Add _or_default accessors to generated messages
Update map_type_to_rust_accessor to provide default values for each
type, which are used to generate helper methods that return a default
value when a field is missing.
2026-05-08 23:20:40 -07:00
charles 7e368feddf Add method signature for repeated fields 2026-05-07 21:21:29 -07:00
charles d9186e697e Fix merge conflicts and generated code logic in generator.rs 2026-05-07 20:53:08 -07:00
charles 13625a48c9 Add support for Protobuf oneof fields in generator
Generate `which_<oneof>` methods and corresponding enums to handle
oneof fields in generated messages. Also add `has_<field>` helper
methods for all fields.
2026-05-07 20:15:16 -07:00
charles a20eed7223 Add support for protobuf map fields
Update the generator to detect map fields and use MapFieldIterator.
Implement MapFieldIterator in the runtime to handle key-value pair
extraction and add write_map_entry to ProtoBuilder. Add tests to
verify that map-bearing messages generate and compile correctly.
2026-05-07 20:15:08 -07:00
charles 8395195ac1 Clean tests 2026-05-07 17:59:39 -07:00
charles f76a020b1e Add test to verify generated code builds
Create a temporary Cargo project to ensure that the Rust code generated from
`test_types.desc` compiles successfully.
2026-05-06 16:21:21 -07:00
charles 80f3aa49ba Fix bug in codegen 2026-05-06 16:03:56 -07:00
charles 847c6cdc66 Merge branch 'check-conformance' 2026-05-04 23:38:50 -07:00
charles e5c2479985 Document unsupported Proto3 features 2026-05-04 23:38:11 -07:00
charles d21ff797b0 Add read-update-write benchmark
Update the README with a new benchmark group and performance results.
Include new data files and update the Rust benchmark implementation.
Regenerate UPB bindings and fix a data path in the C benchmark runner.
2026-05-04 23:37:36 -07:00
charles a0c2747583 Add read-update-write benchmarks
Add benchmarks for parsing, updating, and serializing in Rust and C.
Update the fork instructions in AGENTS.md.
2026-05-04 23:13:53 -07:00
charles 40e342f221 Document Protobuf spec validation 2026-05-04 23:12:20 -07:00
charles ea68537f0b Refactor crate into multiple subcrates 2026-05-04 22:48:26 -07:00
charles d71d96c15f Use roto::RawFieldIterator in generated code 2026-05-04 20:28:08 -07:00
charles 3e5544d3d8 Allow unused imports in generated code 2026-05-04 20:19:40 -07:00
charles 04ef952a58 Add Builder::with for updating messages
Allow creating a new message based on an existing one, overriding
specific fields while copying the remaining original fields.
2026-05-04 20:11:54 -07:00
charles 05e4c275bb Add raw field iterator and with builder method
- Implement RawFieldIterator and ProtoAccessor::raw_fields that yield
  (field_number, raw_bytes) pairs for each field
- Extend Builder with per-field _written flags and add a with()
  method to copy unseen fields from a source message
- Add ProtoBuilder::write_raw to copy pre-encoded field bytes
- Add tests for raw-field iteration, verbatim copying, and with()
2026-05-04 19:03:56 -07:00
charles 6821bd1cca Add performance results section to README 2026-05-04 14:53:49 -07:00
charles 4a6a09cff1 Add benchmark 2026-05-04 14:40:11 -07:00
charles b03ec9eba9 fix: tests 2026-05-04 13:46:05 -07:00
charles ca81a2d010 Update README 2026-05-04 13:45:18 -07:00
charles 858c6968d4 Clean warnings 2026-05-04 11:14:57 -07:00
charles 7fbea70860 Migrate to using generated code 2026-05-04 10:45:38 -07:00
charles a4fa72c819 Include generated code 2026-05-04 09:23:51 -07:00
charles 22c4e17e9f Fix code generation 2026-05-04 09:23:01 -07:00
charles 87b111faf5 Code compiles, but tests fail 2026-05-04 09:04:28 -07:00
charles d9be36726f Implement protobuf enum and message generation
Add `write_enum` and `write_message` to generate Rust enums and structs
from protobuf descriptors. Update the generator tests to support the
updated `generate_rust_code` signature.
2026-05-04 08:59:23 -07:00
charles 74d975788b add: files 2026-05-03 21:03:55 -07:00
charles 20e4fb909b Generate modules to make easy importing 2026-05-03 20:44:07 -07:00
charles a2a5c12235 Escape 'type' keyword in generated Rust code
Use raw identifiers for fields named "type" to avoid conflicts with the
Rust keyword. Fix field number indexing in tests.
2026-05-03 14:00:20 -07:00
charles b73cbb3dbc Merge branch 'add-generated-markers' and resolve conflicts in src/generator.rs 2026-05-03 13:45:25 -07:00
charles 38367227ed Cache field offsets in generated accessors
Update the generator to store field offsets and ranges within the
generated accessor structs, avoiding repeated buffer scans.
2026-05-03 13:32:39 -07:00
116 changed files with 41552 additions and 3666 deletions
+7
View File
@@ -1,2 +1,9 @@
/target
test_gen_project
test_types_gen_project
test_map_gen_project
test_grpc_project
artifacts/
temp_test_project/
output_svc/
output_proto/
+3
View File
@@ -0,0 +1,3 @@
[submodule "grpc_bench"]
path = grpc_bench
url = https://github.com/Lesnyrumcajs/grpc_bench.git
+21
View File
@@ -0,0 +1,21 @@
## Ask for clarification
Unless the request is extremely clear, assemble a list of questions up front. After you begin work,
you should be able to work without user assistance.
## Coding
If you are writing code, write tests first. The tests must pass for your work to be complete.
Before considering a task complete, make sure that all target build, and all tests suceed.
## Special instructions
### Fork
If the users asks you to fork off and work on something, this means that you should:
1. Create a temporary directory using `mktemp -d`
2. Create a new branch to work on `git branch xyz`
3. Use git worktree to extract that branch to the temporary directory (i.e., `git worktree add $TMP_DIR -- $BRANCH)
4. Work from that directory (i.e., `cd $TMP_DIR`)
Generated
+1621 -3
View File
File diff suppressed because it is too large Load Diff
+19 -8
View File
@@ -1,9 +1,20 @@
[package]
name = "roto"
version = "0.1.0"
edition = "2024"
[workspace]
members = [
"runtime",
"codegen",
"protos",
"benches",
"roto-tonic",
"examples/hello_world",
"examples/no_std_test",
]
[dependencies]
clap = { version = "4", features = ["derive"] }
log = "0.4"
env_logger = "0.11"
exclude = [
"test_gen_project"
]
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
+297 -44
View File
@@ -1,29 +1,54 @@
# roto
Rust protos without the pointers.
Zero-allocation Rust protobuf reader and writer.
The codegen is different; we don't create data structures.
We mark what where each field is, and only read it when asked.
The binary blob is never decompressed by the library; it is your
job to figure out how to store the data if you need to access it
more than once.
## Overview
And building protos? You use a builder. We don't make some fancy
structure and give you a marshal function, nah. You give us a blob
to write data into, and we write what you tell us, no questions asked.
Instead of deserializing binary protobuf data into Rust structs, roto scans a message _once_ on
construction — recording the byte offset of each field — then reads fields on demand directly from
the original bytes. No heap allocation, no data copying, no full deserialization upfront.
### Design
It also provides a first-class integration with the `tonic` gRPC framework via the `roto-tonic` crate,
enabling zero-allocation request/response processing.
The `protoc` command generates a CodeGeneratorRequest message; `protoc-gen-roto` (from src/bin/protoc-gen-roto.rs)
reads this message from stdin, and generated a CodeGeneratorResponse, which it sends to stdout.
Writing works the same way: you provide a fixed buffer (or a `bytes::BufMut`) and a builder writes
fields directly into it, returning a slice of the bytes written.
The generated files get written to disk by protoc; these should be included in the Rust code being developed to
use the protobuffers in question.
## Design
### Sample usage
`protoc` generates a `CodeGeneratorRequest` message; `protoc-gen-roto` (in
`src/bin/protoc-gen-roto.rs`) reads this from stdin, generates Rust source files, and writes a
`CodeGeneratorResponse` to stdout. `protoc` then writes those `.rs` files to disk. The generated
files are included directly in the crate that uses the protobuffers.
```rust
/*
Sample usage:
```
protoc -Iproto/ proto/hackers.proto --plugin=./target/debug/protoc-gen-roto --roto_out=src/
```
This will generate a file, src/hackers.rs.
## Generated code
For each protobuf message roto generates three types:
- **Reader struct** `MessageName<'a>` — borrows the original byte slice, zero-copy.
- **Builder struct** `MessageNameBuilder<'b>` — writes into a caller-provided `&mut [u8]` or `BufMut`.
- **Owned struct** `OwnedMessageName` — owns the byte buffer and implements `RotoOwned`, providing a bridge to the `Reader`.
For each protobuf service, roto generates:
- **Service Trait** `ServiceName` — a `tonic`-compatible async trait for gRPC service implementations.
Nested message types are placed in a `pub mod message_name { ... }` module (snake_case of the
parent message name) within the same generated file.
## Sample usage
Given this proto definition:
```proto
message Hello {
string hello_world = 1;
message InnerWorld {
@@ -31,42 +56,270 @@ message Hello {
}
InnerWorld inner_world = 2;
}
*/
```
fn parse_proto(data: &[u8]) -> Result<String> {
// Scans the data, marks where each flag is as an offset
// into the proto.
let accessor = HelloProto::new(data)?;
// Load the hello world string; returns bytes, not
// a Rust string.
let hello_world = accessor.hello_world()?;
// Inspect a nested message; accessing inner_world scans it
// for flag locations and returns a similiar access struct
let inner_world = accessor.inner_world()?.thought()?;
### Reading
format!("{} is about {}", hello_world, inner_world)
```rust
fn parse_proto(data: &[u8]) -> roto::Result<String> {
// Scan the data once, recording field offsets
let hello = Hello::new(data)?;
// String fields return &str borrowed from the original bytes (zero-copy)
let hello_world: &str = hello.hello_world()?;
// Nested message fields return &[u8]; construct the nested reader from those bytes
let inner_bytes: &[u8] = hello.inner_world()?;
let inner_world = hello::InnerWorld::new(inner_bytes)?;
let thought: &str = inner_world.thought()?;
Ok(format!("{} is about {}", hello_world, thought))
}
```
### Sample builder usage
Fields absent from the binary data return `Err(roto::RotoError::FieldNotFound)`.
### Writing
Nested messages must be serialized into a scratch buffer first, then embedded as raw bytes in the
outer builder.
```rust
let mut buf = [0u8; 1024];
let mut builder = HelloProto::Builder::new(&mut buf)
.hello_world("some world")
.inner_world() // Returns an HelloProto::InnerWorld::Builder
.thought("some thought")
.done(); // returns the HelloProto::Builder
let bytes_written = builder.finish()?; // returns the number of bytes written to buffer
fn build_proto(buf: &mut [u8]) -> roto::Result<&[u8]> {
// Serialize the inner message first
let mut inner_buf = [0u8; 256];
let inner_bytes = hello::InnerWorldBuilder::builder(&mut inner_buf)
.thought("some thought")?
.finish()?;
// Build the outer message, embedding the serialized inner bytes
HelloBuilder::builder(buf)
.hello_world("some world")?
.inner_world(inner_bytes)?
.finish() // returns Result<&'b mut [u8]> — the written portion of buf
}
```
### High level design
Builder methods consume `self` and return `Result<Self>`, enabling `?`-based chaining.
`finish()` returns `Result<&'b mut [u8]>` — a slice of the portion of the buffer that was written.
The runtime library offers an iterator over the fields in a message, using the protobuf wire format provide
objects of flag and type. Codegen creates a 'wrapper' that iterates over the message, and records the
byte offset of each element. Helper methods in the wrapper give the user access to the name fields,
casted to the appropriate data type.
### Updating messages
### Literature
You can read a message, modify specific fields, and use `.with()` to copy the remaining fields from the original binary.
https://protobuf.dev/programming-guides/encoding/
```rust
fn update_proto(data: &[u8], buf: &mut [u8]) -> roto::Result<&[u8]> {
let msg = Message::new(data)?;
let mut builder = MessageBuilder::builder(buf);
if msg.foo()? == "bar" {
builder = builder.foo("foosbar")?;
}
builder.with(&msg)?.finish()
}
```
### Repeated fields
Repeated fields return a `RepeatedFieldIterator<'a>`. Each item yields `Result<(&[u8], WireType)>`.
```rust
let hello = Hello::new(data)?;
for item in hello.tags() {
let (value_bytes, _wire_type) = item?;
// decode value_bytes according to the expected wire type
}
```
## Runtime API
The core runtime in `src/lib.rs` provides:
- `ProtoAccessor<'a>` — scans a message's fields and reads values at recorded offsets.
- `ProtoBuilder<'a>` — writes fields into a provided `&mut [u8]` buffer.
- `FieldIterator<'a>` / `RepeatedFieldIterator<'a>` — iterators over fields and repeated fields.
- `Tag`, `WireType` — protobuf encoding primitives.
- `read_varint`, `write_varint`, `skip_value` — low-level wire-format helpers.
- `RotoError`, `Result<T>` — error type and alias.
## High-level design
On construction (`MessageName::new(data)`), the generated reader struct iterates the binary once
using `FieldIterator` and records the byte offset of each field's tag. Subsequent field accesses
call `ProtoAccessor::get_value_at(offset)` — no re-scanning. For repeated fields, the start and
end offsets of the field range are recorded to bound iteration efficiently.
## Benchmarks
Two benchmark suites share the same binary data files and the same four
measurement groups:
| Group | What is timed |
| ------------------- | ------------------------------------------------------- |
| `shallow_parse` | Become ready to read any field (one scan / full decode) |
| `deep_parse` | Walk the full tree: Campaign → Operations → Hackers |
| `field_access` | Read individual fields on an already-parsed message |
| `iterate` | Count top-level and nested repeated fields |
| `read_update_write` | Parse, update a field, and serialize back to a buffer |
### 1 — Generate the shared data files (do this once)
Data files are written to `data/bench/`.
```sh
cargo run --release --bin gen_bench_data -- --preset tiny
cargo run --release --bin gen_bench_data -- --preset small
cargo run --release --bin gen_bench_data -- --preset medium
cargo run --release --bin gen_bench_data -- --preset large
```
For even larger inputs use `--preset huge` (~500 MB) or set the knobs
directly:
```sh
# ~50 MB: 500 operations × 100 KB stolen_data each
cargo run --release --bin gen_bench_data -- --ops 500 --stolen-kb 100 --output data/bench/50mb.pb
```
### 2 — Rust benchmark (criterion)
```sh
cargo bench --bench hackers_bench
```
HTML reports are written to `target/criterion/`. Run a single group:
```sh
cargo bench --bench hackers_bench -- shallow_parse
```
### 3 — C / upb benchmark
Requires protobuf ≥ 21 with `protoc-gen-upb` (ships with modern `protoc`).
```sh
cd upb_test
make # compiles hackers_bench from the pre-generated upb files
./hackers_bench
```
To regenerate the upb C files from `proto/hackers.proto`:
```sh
cd upb_test && make regen
```
### 4 — Results
Measured on Linux x86-64 with the four standard presets. Rust times are
criterion medians; C/upb times are the custom runner's mean over ≥ 0.5 s.
#### `shallow_parse` — cost to become ready to read any field
| Size | Bytes | roto (ns) | upb (ns) | roto speedup |
| ------ | ----------: | --------: | -----------: | -----------: |
| tiny | 588 | 32.7 | 606.2 | **18.5×** |
| small | 20,265 | 182.9 | 22,619.2 | **123.7×** |
| medium | 2,071,053 | 16,632.0 | 5,346,977.2 | **321×** |
| large | 102,608,384 | 1,618.6 | 41,132,079.7 | **25,411×** |
> roto's cost is O(number of top-level fields): it records field offsets by
> jumping past nested blobs using their length prefixes. upb fully decodes the
> entire tree — including all nested messages and raw byte payloads — into
> arena-allocated structs.
#### `deep_parse` — parse + walk Campaign → Operations → every Hacker handle
| Size | Bytes | roto (ns) | upb (ns) | roto speedup |
| ------ | --------: | ----------: | ----------: | -----------: |
| tiny | 588 | 385.3 | 596.8 | **1.55×** |
| small | 20,265 | 13,374.0 | 22,321.6 | **1.67×** |
| medium | 2,071,053 | 1,454,400.0 | 4,227,384.3 | **2.91×** |
> roto pays one extra `::new()` scan per nesting level; upb's walk is pure
> pointer-chasing because everything was decoded upfront. roto is still
> faster overall because its per-level scans cost less than upb's full decode.
#### `field_access` — individual field reads on a pre-parsed message (`small` preset)
| Field | roto (ns) | upb (ns) | upb speedup |
| ------------------------------ | --------: | -------: | ----------: |
| `campaign::name` | 14.3 | 1.11 | **12.9×** |
| `campaign::total_bytes_stolen` | 7.1 | 1.74 | **4.1×** |
| `operation::codename` | 13.8 | 1.76 | **7.8×** |
| `operation::timestamp` | 9.7 | 1.40 | **6.9×** |
| `operation::successful` | 7.0 | 1.13 | **6.1×** |
| `hacker::handle` | 14.4 | 1.56 | **9.2×** |
| `hacker::skill_level` (f32) | 7.7 | 1.76 | **4.4×** |
| `hacker::is_elite` (bool) | 7.5 | 1.14 | **6.6×** |
| `worm::polymorphic` (bool) | 7.5 | 1.76 | **4.2×** |
| `worm::payload` (bytes) | 16.6 | 1.75 | **9.5×** |
> After parsing, upb field reads are direct struct-member lookups (~12 ns).
> roto re-decodes the value at its pre-recorded byte offset on every call
> (~717 ns). This is the one area where upb holds a clear advantage.
#### `iterate` — count repeated fields (parse included in every iteration)
| Benchmark | Size | roto (ns) | upb (ns) | roto speedup |
| ------------------ | ------ | --------: | ----------: | -----------: |
| `count_operations` | tiny | 50.0 | 600.2 | **12.0×** |
| `count_operations` | small | 393.7 | 22,702.9 | **57.7×** |
| `count_operations` | medium | 36,628.0 | 4,193,874.0 | **114.5×** |
| `count_all_crew` | tiny | 235.3 | 610.2 | **2.6×** |
| `count_all_crew` | small | 4,369.5 | 23,109.0 | **5.3×** |
| `count_all_crew` | medium | 444,930.0 | 4,151,181.5 | **9.3×** |
> `count_operations` includes parsing; upb's O(1) array-length read is
> dominated by its full-decode cost, so roto wins by the same margin as
> `shallow_parse`. `count_all_crew` also parses each `Operation` sub-message;
> roto's per-level scans remain cheaper than upb's full decode.
#### `read_update_write` — parse, update a field, and serialize back to a buffer
| Size | Bytes | roto (ns) | upb (ns) | roto speedup |
| ------ | --------: | --------: | ----------: | -----------: |
| tiny | 588 | 153.8 | 1,120.3 | **7.3×** |
| small | 20,265 | 1,301.8 | 42,089.6 | **32.3×** |
| medium | 2,071,053 | 302,090.0 | 9,233,397.9 | **30.5×** |
> roto's `with()` method allows copying fields directly from the original binary
> without decoding them, making the update process extremely efficient. upb must
> fully parse the message into structs and then re-serialize the entire tree.
### Interpreting the comparison
The two libraries have fundamentally different models:
- **roto `shallow_parse`** does one linear scan recording byte offsets — no
allocation, no field decoding. Subsequent field reads decode on demand at
the stored offset.
- **upb `Campaign_parse`** fully decodes the entire message tree into
arena-allocated structs upfront. Subsequent field reads are direct struct
member lookups (~1 ns).
The result: roto's parse is faster and allocation-free; upb's field access
after parsing is faster. For workloads that read every field the costs
invert; for workloads that read a handful of fields from large messages roto
wins.
## Protobuf Spec Validation
The goal is to validate roto's implementation against the Proto3 specification.
### Supported Features
- **Scalar Types**: `double`, `float`, `int32`, `int64`, `uint32`, `uint64`, `sint32`, `sint64`, `fixed32`, `fixed64`, `sfixed32`, `sfixed64`, `bool`, `string`, `bytes`.
- **Messages**: Top-level and nested message definitions.
- **Enums**: Enum definitions with `from_i32` conversion.
- **Field Labels**: Singular and `repeated` fields.
- **Field Presence**: No `has_field()` methods are generated to distinguish between a field being absent and a field being set to its default value.
- **`oneof` Fields**: Generates enums that allow checking which field is set.
- **`map` Fields**: Iterator over underlying key/value pairs.
- **Default Values**: There is an option to select the default value for each field.
### Unsupported Features
- **Reserved Fields**: `reserved` statements are ignored.
- **Options**: Field and message options are ignored.
+44
View File
@@ -0,0 +1,44 @@
# Tasks for Tonic Integration
This document outlines the steps required to integrate the `roto` protobuf library with the `tonic` gRPC framework.
## Goals
- Provide a `tonic::codec::Codec` implementation for `roto` messages.
- Enable zero-allocation decoding of gRPC requests and responses.
- Support efficient encoding of gRPC messages using `roto`'s `Builder` pattern.
- Generate `tonic`-compatible service traits and client/server boilerplate via `protoc-gen-roto`.
## 1. Runtime Changes (`roto_runtime`)
- [x] Add `bytes` as a dependency to `roto_runtime`.
- [x] Modify `ProtoBuilder` to support writing to `bytes::BufMut` or provide a specialized `BufMut` based builder to facilitate integration with `tonic::codec::EncodeBuf`.
- [x] Design and implement "Owned" message support:
- [x] Create a mechanism (likely via codegen) to generate structs that hold `bytes::Bytes` and the field offsets (e.g., `OwnedMyMessage`).
- [x] These structs will serve as the owned types required by `tonic`'s `Codec`.
- [x] Add a method to convert an `OwnedMyMessage` into a zero-allocation `Reader` (e.g., `reader(&self) -> MyMessage<'_>`).
## 2. Tonic Codec Implementation
- [x] Implement `tonic::codec::Codec` for `roto` messages.
- [x] Implement `tonic::codec::Decoder` for `roto` messages:
- [x] The decoder should take a `DecodeBuf` and produce an `OwnedMyMessage`.
- [x] It must perform the initial scan of the buffer to populate the field offsets.
- [x] Implement `tonic::codec::Encoder` for `roto` messages:
- [x] The encoder should take an `OwnedMyMessage` and write its internal `bytes::Bytes` to the `EncodeBuf`.
- [x] Since `roto` responses are built using `Builder` directly into a buffer, the `Encoder` will primarily handle copying these pre-built buffers.
## 3. Code Generation Extensions (`protoc-gen-roto`)
- [x] Update the generator to produce `OwnedMyMessage` structs in addition to the `Reader` and `Builder` structs.
- [x] Generate the `OwnedMyMessage::new(data: bytes::Bytes)` method for initial decoding.
- [x] Implement gRPC service generation:
- [x] Generate `tonic`-compatible service traits (using `#[tonic::async_trait]`).
- [x] Generate client and server boilerplate that uses `OwnedMyMessage` as the request and response types.
- [x] Ensure the generated code correctly maps protobuf services to Rust traits.
## 4. Testing and Validation
- [x] Create a test gRPC project using the updated `protoc-gen-roto`.
- [ ] Implement a sample gRPC service with:
- [ ] A unary call.
- [ ] A server-streaming call.
- [ ] A client-streaming call.
- [ ] A bidirectional-streaming call.
- [ ] Verify that the integration is zero-allocation on the reading path.
- [ ] Perform benchmark tests to compare performance with `prost`.
+1
View File
@@ -0,0 +1 @@
data
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "roto-benches"
version = "0.1.0"
edition = "2024"
[dependencies]
roto-runtime = { path = "../runtime" }
clap = { version = "4", features = ["derive"] }
log = "0.4"
env_logger = "0.11"
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "hackers_bench"
harness = false
+245
View File
@@ -0,0 +1,245 @@
//! Benchmark suite for roto — themed after the 1995 film *Hackers*.
//!
//! Proto schema: `proto/hackers.proto`
//! Generated types: `src/hackers.rs` (via `protoc-gen-roto`)
//!
//! # Setup
//!
//! Generate the data files once before running benchmarks:
//!
//! ```sh
//! cargo run --release --bin gen_bench_data -- --preset tiny
//! cargo run --release --bin gen_bench_data -- --preset small
//! cargo run --release --bin gen_bench_data -- --preset medium
//! cargo run --release --bin gen_bench_data -- --preset large
//! ```
//!
//! Then run:
//!
//! ```sh
//! cargo bench --bench hackers_bench
//! ```
//!
//! Benchmark groups:
//! - `shallow_parse` — `Campaign::new(data)`, one scan of the whole blob
//! - `deep_parse` — Campaign → Operations → Hackers (a `::new()` per level)
//! - `field_access` — individual field reads on pre-parsed messages (O(1))
//! - `iterate` — counting repeated fields at different nesting depths
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use roto_benches::hackers::{Campaign, CampaignBuilder, Hacker, Operation, Worm};
use std::hint::black_box;
// =============================================================================
// Data loading
// =============================================================================
/// Load a pre-generated data file from `data/bench/<name>.pb`.
/// Returns `None` (and prints a hint) if the file does not exist.
fn load(name: &str) -> Option<Vec<u8>> {
let path = format!("data/bench/{name}.pb");
match std::fs::read(&path) {
Ok(data) => Some(data),
Err(_) => {
eprintln!(
"[skip] {path} not found — \
run `cargo run --release --bin gen_bench_data -- --preset {name}` first"
);
None
}
}
}
// =============================================================================
// Benchmarks
// =============================================================================
/// `Campaign::new()` — one linear scan to record field offsets, no allocation.
/// Throughput reported in MB/s so different sizes are directly comparable.
fn bench_shallow_parse(c: &mut Criterion) {
let cases = [
("tiny", load("tiny")),
("small", load("small")),
("medium", load("medium")),
("large", load("large")),
];
let mut group = c.benchmark_group("shallow_parse");
for (label, maybe_data) in &cases {
let Some(data) = maybe_data else { continue };
group.throughput(Throughput::Bytes(data.len() as u64));
group.bench_with_input(BenchmarkId::new("Campaign::new", label), data, |b, data| {
b.iter(|| Campaign::new(black_box(data)).unwrap())
});
}
group.finish();
}
/// Walk every level of the tree: Campaign → Operations → Hackers.
/// Each `::new()` is an additional linear scan of that sub-message's bytes.
fn bench_deep_parse(c: &mut Criterion) {
let cases = [
("tiny", load("tiny")),
("small", load("small")),
("medium", load("medium")),
];
let mut group = c.benchmark_group("deep_parse");
for (label, maybe_data) in &cases {
let Some(data) = maybe_data else { continue };
group.throughput(Throughput::Bytes(data.len() as u64));
group.bench_with_input(
BenchmarkId::new("Campaign+Ops+Hackers", label),
data,
|b, data| {
b.iter(|| {
let campaign = Campaign::new(data).unwrap();
let mut hacker_count = 0usize;
for op_res in campaign.operations() {
let (op_bytes, _) = op_res.unwrap();
let op = Operation::new(op_bytes).unwrap();
for crew_res in op.crew() {
let (hacker_bytes, _) = crew_res.unwrap();
let hacker = Hacker::new(hacker_bytes).unwrap();
let _ = black_box(hacker.handle().unwrap());
hacker_count += 1;
}
}
black_box(hacker_count)
})
},
);
}
group.finish();
}
/// O(1) field accesses on pre-parsed messages.
/// Measures only the decode step at a known offset — not the scan.
fn bench_field_access(c: &mut Criterion) {
let Some(data) = load("small") else { return };
let campaign = Campaign::new(&data).unwrap();
let (op_bytes, _) = campaign.operations().next().unwrap().unwrap();
let op = Operation::new(op_bytes).unwrap();
let (hacker_bytes, _) = op.crew().next().unwrap().unwrap();
let hacker = Hacker::new(hacker_bytes).unwrap();
let worm = Worm::new(op.worm().unwrap()).unwrap();
let mut group = c.benchmark_group("field_access");
group.bench_function("campaign::name", |b| {
b.iter(|| black_box(campaign.name().unwrap()))
});
group.bench_function("campaign::total_bytes_stolen", |b| {
b.iter(|| black_box(campaign.total_bytes_stolen().unwrap()))
});
group.bench_function("operation::codename", |b| {
b.iter(|| black_box(op.codename().unwrap()))
});
group.bench_function("operation::timestamp", |b| {
b.iter(|| black_box(op.timestamp().unwrap()))
});
group.bench_function("operation::successful", |b| {
b.iter(|| black_box(op.successful().unwrap()))
});
group.bench_function("hacker::handle", |b| {
b.iter(|| black_box(hacker.handle().unwrap()))
});
group.bench_function("hacker::skill_level (f32)", |b| {
b.iter(|| black_box(hacker.skill_level().unwrap()))
});
group.bench_function("hacker::is_elite (bool)", |b| {
b.iter(|| black_box(hacker.is_elite().unwrap()))
});
group.bench_function("worm::polymorphic (bool)", |b| {
b.iter(|| black_box(worm.polymorphic().unwrap()))
});
group.bench_function("worm::payload (bytes)", |b| {
b.iter(|| black_box(worm.payload().unwrap()))
});
group.finish();
}
/// Iterate repeated fields at different depths.
fn bench_iterate(c: &mut Criterion) {
let cases = [
("tiny", load("tiny")),
("small", load("small")),
("medium", load("medium")),
];
let mut group = c.benchmark_group("iterate");
for (label, maybe_data) in &cases {
let Some(data) = maybe_data else { continue };
// Top-level repeated field — walk Operation blobs, no inner parse.
group.bench_with_input(
BenchmarkId::new("count_operations", label),
data,
|b, data| {
b.iter(|| {
let campaign = Campaign::new(data).unwrap();
black_box(campaign.operations().count())
})
},
);
// Nested repeated field — parse each Operation to reach its crew.
group.bench_with_input(
BenchmarkId::new("count_all_crew", label),
data,
|b, data| {
b.iter(|| {
let campaign = Campaign::new(data).unwrap();
let mut n = 0usize;
for op_res in campaign.operations() {
let (op_bytes, _) = op_res.unwrap();
n += Operation::new(op_bytes).unwrap().crew().count();
}
black_box(n)
})
},
);
}
group.finish();
}
/// Parse, update a field, and serialize back to a buffer.
fn bench_read_update_write(c: &mut Criterion) {
let cases = [
("tiny", load("tiny")),
("small", load("small")),
("medium", load("medium")),
];
let mut group = c.benchmark_group("read_update_write");
for (label, maybe_data) in &cases {
let Some(data) = maybe_data else { continue };
group.throughput(Throughput::Bytes(data.len() as u64));
group.bench_with_input(BenchmarkId::new("Campaign", label), data, |b, data| {
b.iter(|| {
let campaign = Campaign::new(data).unwrap();
let mut buf = vec![0u8; data.len() * 2];
let res = CampaignBuilder::builder(&mut buf)
.name("updated")
.unwrap()
.with(&campaign)
.unwrap()
.finish();
black_box(res.unwrap().len())
})
});
}
group.finish();
}
criterion_group!(
benches,
bench_shallow_parse,
bench_deep_parse,
bench_field_access,
bench_iterate,
bench_read_update_write
);
criterion_main!(benches);
+478
View File
@@ -0,0 +1,478 @@
//! Generates Hackers-themed benchmark proto binaries using the roto builder API.
//!
//! Run this once to create the data files that `hackers_bench` loads:
//!
//! ```sh
//! cargo run --release --bin gen_bench_data -- --preset tiny
//! cargo run --release --bin gen_bench_data -- --preset small
//! cargo run --release --bin gen_bench_data -- --preset medium
//! cargo run --release --bin gen_bench_data -- --preset large
//! cargo run --release --bin gen_bench_data -- --preset huge
//!
//! # Custom: ~50 MB — 500 ops × 100 KB stolen_data each
//! cargo run --release --bin gen_bench_data -- \
//! --ops 500 --stolen-kb 100 --output data/bench/50mb.pb
//! ```
//!
//! Files land in `data/bench/` by default.
use clap::Parser;
use roto_benches::hackers::{
CampaignBuilder, ConnectionBuilder, HackerBuilder, OperationBuilder, ToolBuilder, WormBuilder,
};
use std::io::{self, Write};
use std::path::Path;
// =============================================================================
// CLI
// =============================================================================
#[derive(Parser)]
#[command(
name = "gen_bench_data",
about = "Generate Hackers-themed proto binaries for benchmarks"
)]
struct Args {
/// Output file. Defaults to data/bench/<preset>.pb when --preset is used.
#[arg(short, long)]
output: Option<String>,
/// Named size preset: tiny | small | medium | large | huge
#[arg(short, long)]
preset: Option<String>,
/// Number of Operation messages.
#[arg(long, default_value_t = 100)]
ops: usize,
/// Kilobytes of random stolen_data padding per Operation.
#[arg(long, default_value_t = 0)]
stolen_kb: usize,
/// Hacker crew members per Operation.
#[arg(long, default_value_t = 3)]
crew: usize,
/// RNG seed.
#[arg(long, default_value_t = 42)]
seed: u64,
}
// =============================================================================
// Minimal xorshift64 RNG
// =============================================================================
struct Rng(u64);
impl Rng {
fn new(seed: u64) -> Self {
Self(if seed == 0 { 0xdeadbeef_cafebabe } else { seed })
}
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
fn below(&mut self, n: usize) -> usize {
(self.next() as usize) % n
}
fn range(&mut self, lo: u64, hi: u64) -> u64 {
lo + self.next() % (hi - lo)
}
fn bool(&mut self) -> bool {
self.next() & 1 == 0
}
fn pick<'a, T>(&mut self, s: &'a [T]) -> &'a T {
&s[self.below(s.len())]
}
fn bytes(&mut self, n: usize) -> Vec<u8> {
(0..n).map(|_| self.next() as u8).collect()
}
}
// =============================================================================
// Flavour text
// =============================================================================
const HANDLES: &[&str] = &[
"Zero Cool",
"Acid Burn",
"Phantom Phreak",
"Cereal Killer",
"Lord Nikon",
"The Plague",
"Crash Override",
];
const REAL_NAMES: &[&str] = &[
"Dade Murphy",
"Kate Libby",
"Richard Gill",
"Emmanuel Goldstein",
"Paul Cook",
"Eugene Belford",
];
const EXPLOITS: &[&str] = &[
"buffer overflow",
"stack smash",
"heap spray",
"race condition",
"SQL injection",
"CSRF",
"XSS",
"RCE",
"privesc",
"kernel panic",
];
const TOOL_NAMES: &[&str] = &[
"nmap",
"metasploit",
"netcat",
"tcpdump",
"Wireshark",
"sqlmap",
"Burp Suite",
"hashcat",
"john",
];
const CORPS: &[&str] = &[
"ELLINGSON MINERAL",
"Cyberdelia",
"The Gibson",
"Prism BBS",
"Elite BBS",
"CRT Systems",
];
const LOG_LINES: &[&str] = &[
"Hack the planet!",
"Mess with the best, die like the rest.",
"I'm in.",
"They're tracing us.",
"It's a Unix system! I know this!",
"You are elite.",
"Garbage file accessed.",
];
const WORM_TARGETS: &[&str] = &[
"ELLINGSON MINERAL",
"Cyberdelia",
"The Gibson",
"Prism",
"CRT BBS",
];
const OP_NAMES: &[&str] = &[
"OPERATION HACK THE PLANET",
"GIBSON BREACH",
"ELLINGSON STING",
"WORM UNLEASHED",
"PHANTOM ACCESS",
"DA VINCI",
];
// =============================================================================
// Message builders using the generated roto::hackers API
// =============================================================================
fn gen_tool(rng: &mut Rng) -> Vec<u8> {
let payload_n = rng.range(8, 64) as usize;
let payload = rng.bytes(payload_n);
let version = format!("{}.{}", rng.range(1, 9), rng.range(0, 99));
let mut buf = vec![0u8; 512];
ToolBuilder::builder(&mut buf)
.name(*rng.pick(TOOL_NAMES))
.unwrap()
.version(&version)
.unwrap()
.payload(&payload)
.unwrap()
.is_active(rng.bool() as u64)
.unwrap()
.exploit_count(rng.range(0, 50) as i32)
.unwrap()
.finish()
.unwrap()
.to_vec()
}
fn gen_connection(rng: &mut Rng) -> Vec<u8> {
let host = format!("192.168.{}.{}", rng.range(1, 254), rng.range(1, 254));
let session_key = rng.bytes(32);
let mut buf = vec![0u8; 256];
ConnectionBuilder::builder(&mut buf)
.host(&host)
.unwrap()
.port(rng.range(1024, 65535) as i32)
.unwrap()
.encrypted(rng.bool() as u64)
.unwrap()
.bandwidth_bps(rng.range(1200, 1_000_000_000))
.unwrap()
.session_key(&session_key)
.unwrap()
.finish()
.unwrap()
.to_vec()
}
fn gen_hacker(rng: &mut Rng) -> Vec<u8> {
let tools: Vec<Vec<u8>> = (0..rng.range(1, 4)).map(|_| gen_tool(rng)).collect();
let connection = gen_connection(rng);
// Float (skill_level) is written as raw bytes by the generated builder
let skill_bits = (rng.range(10, 100) as f32 / 10.0).to_bits().to_le_bytes();
let handle = *rng.pick(HANDLES);
let real_name = *rng.pick(REAL_NAMES);
let n_exploits = rng.range(2, 5) as usize;
let exploits: Vec<&str> = (0..n_exploits).map(|_| *rng.pick(EXPLOITS)).collect();
let crew_id = rng.next();
let mut buf = vec![0u8; 8 * 1024];
let mut b = HackerBuilder::builder(&mut buf)
.handle(handle)
.unwrap()
.real_name(real_name)
.unwrap()
.age(rng.range(16, 35) as i32)
.unwrap()
.skill_level(&skill_bits)
.unwrap()
.is_elite(rng.bool() as u64)
.unwrap()
.crew_id(crew_id)
.unwrap();
for e in &exploits {
b = b.exploits(e).unwrap();
}
for t in &tools {
b = b.tools(t).unwrap();
}
b.active_connection(&connection)
.unwrap()
.finish()
.unwrap()
.to_vec()
}
fn gen_worm(rng: &mut Rng) -> Vec<u8> {
let payload = rng.bytes(64);
let name = format!("da_vinci.{}", rng.range(1, 99));
let n_targets = rng.range(1, 4) as usize;
let targets: Vec<&str> = (0..n_targets).map(|_| *rng.pick(WORM_TARGETS)).collect();
let mut buf = vec![0u8; 1024];
let mut b = WormBuilder::builder(&mut buf)
.name(&name)
.unwrap()
.variant(rng.range(1, 5) as i32)
.unwrap()
.size_bytes(rng.range(1024, 10_000_000))
.unwrap()
.payload(&payload)
.unwrap()
.polymorphic(rng.bool() as u64)
.unwrap();
for t in &targets {
b = b.targets(t).unwrap();
}
b.finish().unwrap().to_vec()
}
fn gen_operation(rng: &mut Rng, crew_count: usize, stolen_bytes: usize) -> Vec<u8> {
let crew: Vec<Vec<u8>> = (0..crew_count).map(|_| gen_hacker(rng)).collect();
let worm = gen_worm(rng);
let stolen = if stolen_bytes > 0 {
rng.bytes(stolen_bytes)
} else {
vec![]
};
let codename = *rng.pick(OP_NAMES);
let corp = *rng.pick(CORPS);
let ts = 810_000_000u64 + rng.range(0, 10_000_000);
let n_logs = rng.range(2, 6) as usize;
let logs: Vec<&str> = (0..n_logs).map(|_| *rng.pick(LOG_LINES)).collect();
// Operation buffer: crew + worm + stolen_data + small overhead
let crew_size: usize = crew.iter().map(|h| h.len() + 5).sum();
let buf_size = crew_size + worm.len() + stolen_bytes + 1024;
let mut buf = vec![0u8; buf_size];
let mut b = OperationBuilder::builder(&mut buf)
.codename(codename)
.unwrap()
.target_corp(corp)
.unwrap()
.timestamp(ts)
.unwrap()
.successful(rng.bool() as u64)
.unwrap();
if stolen_bytes > 0 {
b = b.stolen_data(&stolen).unwrap();
}
for h in &crew {
b = b.crew(h).unwrap();
}
b = b.worm(&worm).unwrap();
for l in &logs {
b = b.log_entries(l).unwrap();
}
b.severity(rng.range(1, 10) as i32)
.unwrap()
.finish()
.unwrap()
.to_vec()
}
fn gen_campaign(
rng: &mut Rng,
op_count: usize,
crew_per_op: usize,
stolen_bytes: usize,
) -> Vec<u8> {
let ops: Vec<Vec<u8>> = (0..op_count)
.map(|i| {
if i > 0 && i % 100 == 0 {
eprintln!(
" {i}/{op_count} operations ({:.1} MB in ops so far)…",
i * (stolen_bytes + 10_000) / 1_000_000
);
}
gen_operation(rng, crew_per_op, stolen_bytes)
})
.collect();
// Pre-compute campaign buffer size: for each op the wire encoding is
// tag(1B) + varint_length(1-5B) + op_bytes
let ops_wire_size: usize = ops
.iter()
.map(|o| 1 + varint_len(o.len() as u64) + o.len())
.sum();
let mut buf = vec![0u8; ops_wire_size + 64];
let mut b = CampaignBuilder::builder(&mut buf)
.name("HACK THE PLANET CAMPAIGN")
.unwrap();
for op in &ops {
b = b.operations(op).unwrap();
}
b.total_bytes_stolen((stolen_bytes * op_count) as u64)
.unwrap()
.finish()
.unwrap()
.to_vec()
}
/// Number of bytes needed to encode `v` as a varint.
fn varint_len(mut v: u64) -> usize {
let mut n = 1usize;
while v >= 128 {
v >>= 7;
n += 1;
}
n
}
// =============================================================================
// Preset table
// =============================================================================
struct Preset {
ops: usize,
crew: usize,
stolen_kb: usize,
}
fn resolve(name: &str) -> Option<Preset> {
match name {
// ops crew stolen_kb approx size
"tiny" => Some(Preset {
ops: 1,
crew: 1,
stolen_kb: 0,
}), // ~400 B
"small" => Some(Preset {
ops: 20,
crew: 3,
stolen_kb: 0,
}), // ~25 KB
"medium" => Some(Preset {
ops: 2_000,
crew: 3,
stolen_kb: 0,
}), // ~2 MB
"large" => Some(Preset {
ops: 200,
crew: 3,
stolen_kb: 500,
}), // ~100 MB
"huge" => Some(Preset {
ops: 1_000,
crew: 3,
stolen_kb: 500,
}), // ~500 MB
_ => None,
}
}
// =============================================================================
// main
// =============================================================================
fn main() {
let mut args = Args::parse();
if let Some(ref name) = args.preset.clone() {
match resolve(name) {
Some(p) => {
args.ops = p.ops;
args.crew = p.crew;
args.stolen_kb = p.stolen_kb;
}
None => {
eprintln!("Unknown preset '{name}'. Valid: tiny, small, medium, large, huge");
std::process::exit(1);
}
}
}
let stolen_bytes = args.stolen_kb * 1024;
let approx_mb = args.ops * (700 + args.crew * 300 + stolen_bytes) / 1_000_000;
eprintln!(
"Generating: {} ops × {} crew, {} KB stolen_data each → ~{} MB",
args.ops, args.crew, args.stolen_kb, approx_mb
);
let mut rng = Rng::new(args.seed);
let data = gen_campaign(&mut rng, args.ops, args.crew, stolen_bytes);
eprintln!(
"Generated {} bytes ({:.2} MB)",
data.len(),
data.len() as f64 / 1_000_000.0
);
// Default output: data/bench/<preset>.pb, or stdout if no output and no preset
let out_path = args.output.clone().or_else(|| {
args.preset
.as_ref()
.map(|p| format!("benches/data/bench/{}.pb", p))
});
match out_path {
Some(ref path) => {
if let Some(parent) = Path::new(path).parent() {
if !parent.as_os_str().is_empty() {
std::fs::create_dir_all(parent).unwrap_or_else(|e| eprintln!("Warning: {e}"));
}
}
std::fs::write(path, &data).unwrap_or_else(|e| {
eprintln!("Error writing {path}: {e}");
std::process::exit(1);
});
eprintln!("Saved to {path}");
}
None => {
io::stdout().write_all(&data).unwrap_or_else(|e| {
eprintln!("Error: {e}");
std::process::exit(1);
});
}
}
}
+1
View File
@@ -0,0 +1 @@
pub mod protobuf;
@@ -0,0 +1 @@
pub mod plugin;
@@ -0,0 +1,783 @@
// @generated by protoc-gen-roto — do not edit
#[allow(unused_imports)]
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};
use core::str;
#[cfg(feature = "alloc")]
use bytes::{Bytes, BytesMut, Buf, BufMut};
use crate::google::protobuf::descriptor;
pub struct Version<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
major_offset: Option<usize>,
minor_offset: Option<usize>,
patch_offset: Option<usize>,
suffix_offset: Option<usize>,
}
impl<'a> Version<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut major_offset = None;
let mut minor_offset = None;
let mut patch_offset = None;
let mut suffix_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { major_offset = Some(offset); }
if tag.field_number == 2 { minor_offset = Some(offset); }
if tag.field_number == 3 { patch_offset = Some(offset); }
if tag.field_number == 4 { suffix_offset = Some(offset); }
}
Ok(Self {
accessor,
major_offset,
minor_offset,
patch_offset,
suffix_offset,
})
}
pub fn major(&self) -> roto_runtime::Result<i32> {
let offset = self.major_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn major_or_default(&self) -> roto_runtime::Result<i32> {
self.major().or(Ok(0))
}
pub fn has_major(&self) -> bool { self.major_offset.is_some() }
pub fn minor(&self) -> roto_runtime::Result<i32> {
let offset = self.minor_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn minor_or_default(&self) -> roto_runtime::Result<i32> {
self.minor().or(Ok(0))
}
pub fn has_minor(&self) -> bool { self.minor_offset.is_some() }
pub fn patch(&self) -> roto_runtime::Result<i32> {
let offset = self.patch_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn patch_or_default(&self) -> roto_runtime::Result<i32> {
self.patch().or(Ok(0))
}
pub fn has_patch(&self) -> bool { self.patch_offset.is_some() }
pub fn suffix(&self) -> roto_runtime::Result<&'a str> {
let offset = self.suffix_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn suffix_or_default(&self) -> roto_runtime::Result<&'a str> {
self.suffix().or(Ok(""))
}
pub fn has_suffix(&self) -> bool { self.suffix_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct VersionBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
major_written: bool,
minor_written: bool,
patch_written: bool,
suffix_written: bool,
}
impl<'b> VersionBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> VersionBuilder<'_> {
VersionBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
major_written: false,
minor_written: false,
patch_written: false,
suffix_written: false,
}
}
pub fn major(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(1, value)?;
self.major_written = true;
Ok(self)
}
pub fn minor(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(2, value)?;
self.minor_written = true;
Ok(self)
}
pub fn patch(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(3, value)?;
self.patch_written = true;
Ok(self)
}
pub fn suffix(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(4, value)?;
self.suffix_written = true;
Ok(self)
}
pub fn with(mut self, msg: &Version<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.major_written,
2 => self.minor_written,
3 => self.patch_written,
4 => self.suffix_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedVersion {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedVersion {
type Reader<'a> = Version<'a>;
fn reader(&self) -> Version<'_> {
Version::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedVersion {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedVersion { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct CodeGeneratorRequest<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
file_to_generate_start: Option<usize>,
file_to_generate_end: Option<usize>,
parameter_offset: Option<usize>,
proto_file_start: Option<usize>,
proto_file_end: Option<usize>,
source_file_descriptors_start: Option<usize>,
source_file_descriptors_end: Option<usize>,
compiler_version_offset: Option<usize>,
}
impl<'a> CodeGeneratorRequest<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut file_to_generate_start = None;
let mut file_to_generate_end = None;
let mut parameter_offset = None;
let mut proto_file_start = None;
let mut proto_file_end = None;
let mut source_file_descriptors_start = None;
let mut source_file_descriptors_end = None;
let mut compiler_version_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 {
if file_to_generate_start.is_none() { file_to_generate_start = Some(offset); }
file_to_generate_end = Some(offset);
}
if tag.field_number == 2 { parameter_offset = Some(offset); }
if tag.field_number == 15 {
if proto_file_start.is_none() { proto_file_start = Some(offset); }
proto_file_end = Some(offset);
}
if tag.field_number == 17 {
if source_file_descriptors_start.is_none() { source_file_descriptors_start = Some(offset); }
source_file_descriptors_end = Some(offset);
}
if tag.field_number == 3 { compiler_version_offset = Some(offset); }
}
Ok(Self {
accessor,
file_to_generate_start, file_to_generate_end,
parameter_offset,
proto_file_start, proto_file_end,
source_file_descriptors_start, source_file_descriptors_end,
compiler_version_offset,
})
}
pub fn file_to_generate(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.file_to_generate_start, self.file_to_generate_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(1, start, end),
_ => self.accessor.iter_repeated(1),
}
}
pub fn parameter(&self) -> roto_runtime::Result<&'a str> {
let offset = self.parameter_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn parameter_or_default(&self) -> roto_runtime::Result<&'a str> {
self.parameter().or(Ok(""))
}
pub fn has_parameter(&self) -> bool { self.parameter_offset.is_some() }
pub fn proto_file(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.proto_file_start, self.proto_file_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(15, start, end),
_ => self.accessor.iter_repeated(15),
}
}
pub fn source_file_descriptors(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.source_file_descriptors_start, self.source_file_descriptors_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(17, start, end),
_ => self.accessor.iter_repeated(17),
}
}
pub fn compiler_version(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.compiler_version_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn compiler_version_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.compiler_version().or(Ok(&[]))
}
pub fn has_compiler_version(&self) -> bool { self.compiler_version_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct CodeGeneratorRequestBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
file_to_generate_written: bool,
parameter_written: bool,
proto_file_written: bool,
source_file_descriptors_written: bool,
compiler_version_written: bool,
}
impl<'b> CodeGeneratorRequestBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> CodeGeneratorRequestBuilder<'_> {
CodeGeneratorRequestBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
file_to_generate_written: false,
parameter_written: false,
proto_file_written: false,
source_file_descriptors_written: false,
compiler_version_written: false,
}
}
pub fn file_to_generate(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.file_to_generate_written = true;
Ok(self)
}
pub fn parameter(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(2, value)?;
self.parameter_written = true;
Ok(self)
}
pub fn proto_file(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(15, value)?;
self.proto_file_written = true;
Ok(self)
}
pub fn source_file_descriptors(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(17, value)?;
self.source_file_descriptors_written = true;
Ok(self)
}
pub fn compiler_version(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(3, value)?;
self.compiler_version_written = true;
Ok(self)
}
pub fn with(mut self, msg: &CodeGeneratorRequest<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.file_to_generate_written,
2 => self.parameter_written,
15 => self.proto_file_written,
17 => self.source_file_descriptors_written,
3 => self.compiler_version_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedCodeGeneratorRequest {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedCodeGeneratorRequest {
type Reader<'a> = CodeGeneratorRequest<'a>;
fn reader(&self) -> CodeGeneratorRequest<'_> {
CodeGeneratorRequest::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedCodeGeneratorRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedCodeGeneratorRequest { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct CodeGeneratorResponse<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
error_offset: Option<usize>,
supported_features_offset: Option<usize>,
minimum_edition_offset: Option<usize>,
maximum_edition_offset: Option<usize>,
file_start: Option<usize>,
file_end: Option<usize>,
}
impl<'a> CodeGeneratorResponse<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut error_offset = None;
let mut supported_features_offset = None;
let mut minimum_edition_offset = None;
let mut maximum_edition_offset = None;
let mut file_start = None;
let mut file_end = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { error_offset = Some(offset); }
if tag.field_number == 2 { supported_features_offset = Some(offset); }
if tag.field_number == 3 { minimum_edition_offset = Some(offset); }
if tag.field_number == 4 { maximum_edition_offset = Some(offset); }
if tag.field_number == 15 {
if file_start.is_none() { file_start = Some(offset); }
file_end = Some(offset);
}
}
Ok(Self {
accessor,
error_offset,
supported_features_offset,
minimum_edition_offset,
maximum_edition_offset,
file_start, file_end,
})
}
pub fn error(&self) -> roto_runtime::Result<&'a str> {
let offset = self.error_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn error_or_default(&self) -> roto_runtime::Result<&'a str> {
self.error().or(Ok(""))
}
pub fn has_error(&self) -> bool { self.error_offset.is_some() }
pub fn supported_features(&self) -> roto_runtime::Result<u32> {
let offset = self.supported_features_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as u32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn supported_features_or_default(&self) -> roto_runtime::Result<u32> {
self.supported_features().or(Ok(0))
}
pub fn has_supported_features(&self) -> bool { self.supported_features_offset.is_some() }
pub fn minimum_edition(&self) -> roto_runtime::Result<i32> {
let offset = self.minimum_edition_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn minimum_edition_or_default(&self) -> roto_runtime::Result<i32> {
self.minimum_edition().or(Ok(0))
}
pub fn has_minimum_edition(&self) -> bool { self.minimum_edition_offset.is_some() }
pub fn maximum_edition(&self) -> roto_runtime::Result<i32> {
let offset = self.maximum_edition_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn maximum_edition_or_default(&self) -> roto_runtime::Result<i32> {
self.maximum_edition().or(Ok(0))
}
pub fn has_maximum_edition(&self) -> bool { self.maximum_edition_offset.is_some() }
pub fn file(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.file_start, self.file_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(15, start, end),
_ => self.accessor.iter_repeated(15),
}
}
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct CodeGeneratorResponseBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
error_written: bool,
supported_features_written: bool,
minimum_edition_written: bool,
maximum_edition_written: bool,
file_written: bool,
}
impl<'b> CodeGeneratorResponseBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> CodeGeneratorResponseBuilder<'_> {
CodeGeneratorResponseBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
error_written: false,
supported_features_written: false,
minimum_edition_written: false,
maximum_edition_written: false,
file_written: false,
}
}
pub fn error(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.error_written = true;
Ok(self)
}
pub fn supported_features(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(2, value)?;
self.supported_features_written = true;
Ok(self)
}
pub fn minimum_edition(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(3, value)?;
self.minimum_edition_written = true;
Ok(self)
}
pub fn maximum_edition(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(4, value)?;
self.maximum_edition_written = true;
Ok(self)
}
pub fn file(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(15, value)?;
self.file_written = true;
Ok(self)
}
pub fn with(mut self, msg: &CodeGeneratorResponse<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.error_written,
2 => self.supported_features_written,
3 => self.minimum_edition_written,
4 => self.maximum_edition_written,
15 => self.file_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedCodeGeneratorResponse {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedCodeGeneratorResponse {
type Reader<'a> = CodeGeneratorResponse<'a>;
fn reader(&self) -> CodeGeneratorResponse<'_> {
CodeGeneratorResponse::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedCodeGeneratorResponse {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedCodeGeneratorResponse { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub mod code_generator_response {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum Feature {
FEATURENONE = 0,
FEATUREPROTO3OPTIONAL = 1,
FEATURESUPPORTSEDITIONS = 2,
}
impl Feature {
pub fn from_i32(value: i32) -> Self {
match value {
0 => Feature::FEATURENONE,
1 => Feature::FEATUREPROTO3OPTIONAL,
2 => Feature::FEATURESUPPORTSEDITIONS,
_ => Feature::FEATURENONE,
}
}
}
pub struct File<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
insertion_point_offset: Option<usize>,
content_offset: Option<usize>,
generated_code_info_offset: Option<usize>,
}
impl<'a> File<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut name_offset = None;
let mut insertion_point_offset = None;
let mut content_offset = None;
let mut generated_code_info_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { name_offset = Some(offset); }
if tag.field_number == 2 { insertion_point_offset = Some(offset); }
if tag.field_number == 15 { content_offset = Some(offset); }
if tag.field_number == 16 { generated_code_info_offset = Some(offset); }
}
Ok(Self {
accessor,
name_offset,
insertion_point_offset,
content_offset,
generated_code_info_offset,
})
}
pub fn name(&self) -> roto_runtime::Result<&'a str> {
let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
pub fn insertion_point(&self) -> roto_runtime::Result<&'a str> {
let offset = self.insertion_point_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn insertion_point_or_default(&self) -> roto_runtime::Result<&'a str> {
self.insertion_point().or(Ok(""))
}
pub fn has_insertion_point(&self) -> bool { self.insertion_point_offset.is_some() }
pub fn content(&self) -> roto_runtime::Result<&'a str> {
let offset = self.content_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn content_or_default(&self) -> roto_runtime::Result<&'a str> {
self.content().or(Ok(""))
}
pub fn has_content(&self) -> bool { self.content_offset.is_some() }
pub fn generated_code_info(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.generated_code_info_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn generated_code_info_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.generated_code_info().or(Ok(&[]))
}
pub fn has_generated_code_info(&self) -> bool { self.generated_code_info_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct FileBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
name_written: bool,
insertion_point_written: bool,
content_written: bool,
generated_code_info_written: bool,
}
impl<'b> FileBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> FileBuilder<'_> {
FileBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
name_written: false,
insertion_point_written: false,
content_written: false,
generated_code_info_written: false,
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn insertion_point(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(2, value)?;
self.insertion_point_written = true;
Ok(self)
}
pub fn content(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(15, value)?;
self.content_written = true;
Ok(self)
}
pub fn generated_code_info(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(16, value)?;
self.generated_code_info_written = true;
Ok(self)
}
pub fn with(mut self, msg: &File<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.name_written,
2 => self.insertion_point_written,
15 => self.content_written,
16 => self.generated_code_info_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedFile {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedFile {
type Reader<'a> = File<'a>;
fn reader(&self) -> File<'_> {
File::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedFile {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedFile { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
}
use crate::google::protobuf::descriptor;
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
pub mod compiler;
pub mod descriptor;
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
pub mod hackers;
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "roto-codegen"
version = "0.1.0"
edition = "2024"
[dependencies]
roto-runtime = { path = "../runtime" }
roto-tonic = { path = "../roto-tonic" }
clap = { version = "4", features = ["derive"] }
log = "0.4"
env_logger = "0.11"
bytes = "1.7"
http-body = "1.0"
http-body-util = "0.1"
tower = "0.4"
tonic = "0.12"
tokio-stream = "0.1"
futures-util = "0.3"
+1
View File
@@ -0,0 +1 @@
bench/
+9
View File
@@ -0,0 +1,9 @@
«
codegen/data/test_map.proto roto.test"y
MapTest4
my_map ( 2.roto.test.MapTest.MyMapEntryRmyMap8
MyMapEntry
key ( Rkey
value (Rvalue:8bproto3
+7
View File
@@ -0,0 +1,7 @@
syntax = "proto3";
package roto.test;
message MapTest {
map<string, int32> my_map = 1;
}
Binary file not shown.
@@ -0,0 +1,180 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
// Author: kenton@google.com (Kenton Varda)
//
// protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is
// just a program that reads a CodeGeneratorRequest from stdin and writes a
// CodeGeneratorResponse to stdout.
//
// Plugins written using C++ can use google/protobuf/compiler/plugin.h instead
// of dealing with the raw protocol defined here.
//
// A plugin executable needs only to be placed somewhere in the path. The
// plugin should be named "protoc-gen-$NAME", and will then be used when the
// flag "--${NAME}_out" is passed to protoc.
syntax = "proto2";
package google.protobuf.compiler;
option java_package = "com.google.protobuf.compiler";
option java_outer_classname = "PluginProtos";
import "google/protobuf/descriptor.proto";
option csharp_namespace = "Google.Protobuf.Compiler";
option go_package = "google.golang.org/protobuf/types/pluginpb";
// The version number of protocol compiler.
message Version {
optional int32 major = 1;
optional int32 minor = 2;
optional int32 patch = 3;
// A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should
// be empty for mainline stable releases.
optional string suffix = 4;
}
// An encoded CodeGeneratorRequest is written to the plugin's stdin.
message CodeGeneratorRequest {
// The .proto files that were explicitly listed on the command-line. The
// code generator should generate code only for these files. Each file's
// descriptor will be included in proto_file, below.
repeated string file_to_generate = 1;
// The generator parameter passed on the command-line.
optional string parameter = 2;
// FileDescriptorProtos for all files in files_to_generate and everything
// they import. The files will appear in topological order, so each file
// appears before any file that imports it.
//
// Note: the files listed in files_to_generate will include runtime-retention
// options only, but all other files will include source-retention options.
// The source_file_descriptors field below is available in case you need
// source-retention options for files_to_generate.
//
// protoc guarantees that all proto_files will be written after
// the fields above, even though this is not technically guaranteed by the
// protobuf wire format. This theoretically could allow a plugin to stream
// in the FileDescriptorProtos and handle them one by one rather than read
// the entire set into memory at once. However, as of this writing, this
// is not similarly optimized on protoc's end -- it will store all fields in
// memory at once before sending them to the plugin.
//
// Type names of fields and extensions in the FileDescriptorProto are always
// fully qualified.
repeated FileDescriptorProto proto_file = 15;
// File descriptors with all options, including source-retention options.
// These descriptors are only provided for the files listed in
// files_to_generate.
repeated FileDescriptorProto source_file_descriptors = 17;
// The version number of protocol compiler.
optional Version compiler_version = 3;
}
// The plugin writes an encoded CodeGeneratorResponse to stdout.
message CodeGeneratorResponse {
// Error message. If non-empty, code generation failed. The plugin process
// should exit with status code zero even if it reports an error in this way.
//
// This should be used to indicate errors in .proto files which prevent the
// code generator from generating correct code. Errors which indicate a
// problem in protoc itself -- such as the input CodeGeneratorRequest being
// unparseable -- should be reported by writing a message to stderr and
// exiting with a non-zero status code.
optional string error = 1;
// A bitmask of supported features that the code generator supports.
// This is a bitwise "or" of values from the Feature enum.
optional uint64 supported_features = 2;
// Sync with code_generator.h.
enum Feature {
FEATURE_NONE = 0;
FEATURE_PROTO3_OPTIONAL = 1;
FEATURE_SUPPORTS_EDITIONS = 2;
}
// The minimum edition this plugin supports. This will be treated as an
// Edition enum, but we want to allow unknown values. It should be specified
// according the edition enum value, *not* the edition number. Only takes
// effect for plugins that have FEATURE_SUPPORTS_EDITIONS set.
optional int32 minimum_edition = 3;
// The maximum edition this plugin supports. This will be treated as an
// Edition enum, but we want to allow unknown values. It should be specified
// according the edition enum value, *not* the edition number. Only takes
// effect for plugins that have FEATURE_SUPPORTS_EDITIONS set.
optional int32 maximum_edition = 4;
// Represents a single generated file.
message File {
// The file name, relative to the output directory. The name must not
// contain "." or ".." components and must be relative, not be absolute (so,
// the file cannot lie outside the output directory). "/" must be used as
// the path separator, not "\".
//
// If the name is omitted, the content will be appended to the previous
// file. This allows the generator to break large files into small chunks,
// and allows the generated text to be streamed back to protoc so that large
// files need not reside completely in memory at one time. Note that as of
// this writing protoc does not optimize for this -- it will read the entire
// CodeGeneratorResponse before writing files to disk.
optional string name = 1;
// If non-empty, indicates that the named file should already exist, and the
// content here is to be inserted into that file at a defined insertion
// point. This feature allows a code generator to extend the output
// produced by another code generator. The original generator may provide
// insertion points by placing special annotations in the file that look
// like:
// @@protoc_insertion_point(NAME)
// The annotation can have arbitrary text before and after it on the line,
// which allows it to be placed in a comment. NAME should be replaced with
// an identifier naming the point -- this is what other generators will use
// as the insertion_point. Code inserted at this point will be placed
// immediately above the line containing the insertion point (thus multiple
// insertions to the same point will come out in the order they were added).
// The double-@ is intended to make it unlikely that the generated code
// could contain things that look like insertion points by accident.
//
// For example, the C++ code generator places the following line in the
// .pb.h files that it generates:
// // @@protoc_insertion_point(namespace_scope)
// This line appears within the scope of the file's package namespace, but
// outside of any particular class. Another plugin can then specify the
// insertion_point "namespace_scope" to generate additional classes or
// other declarations that should be placed in this scope.
//
// Note that if the line containing the insertion point begins with
// whitespace, the same whitespace will be added to every line of the
// inserted text. This is useful for languages like Python, where
// indentation matters. In these languages, the insertion point comment
// should be indented the same amount as any inserted code will need to be
// in order to work correctly in that context.
//
// The code generator that generates the initial file and the one which
// inserts into it must both run as part of a single invocation of protoc.
// Code generators are executed in the order in which they appear on the
// command line.
//
// If |insertion_point| is present, |name| must also be present.
optional string insertion_point = 2;
// The file contents.
optional string content = 15;
// Information describing the file content being inserted. If an insertion
// point is used, this information will be appropriately offset and inserted
// into the code generation metadata for the generated files.
optional GeneratedCodeInfo generated_code_info = 16;
}
repeated File file = 15;
}
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
syntax = "proto3";
message Tool {
string name = 1;
string version = 2;
bytes payload = 3;
bool is_active = 4;
int32 exploit_count = 5;
}
message Connection {
string host = 1;
int32 port = 2;
bool encrypted = 3;
int64 bandwidth_bps = 4;
bytes session_key = 5;
}
message Hacker {
string handle = 1;
string real_name = 2;
int32 age = 3;
float skill_level = 4; // Fixed32
bool is_elite = 5;
int64 crew_id = 6;
repeated string exploits = 7;
repeated Tool tools = 8;
Connection active_connection = 9;
}
message Worm {
string name = 1;
int32 variant = 2;
int64 size_bytes = 3;
bytes payload = 4;
bool polymorphic = 5;
repeated string targets = 6;
}
message Operation {
string codename = 1;
string target_corp = 2;
int64 timestamp = 3;
bool successful = 4;
bytes stolen_data = 5;
repeated Hacker crew = 6;
Worm worm = 7;
repeated string log_entries = 8;
int32 severity = 9;
}
message Campaign {
string name = 1;
repeated Operation operations = 2;
int64 total_bytes_stolen = 3;
}
+42
View File
@@ -0,0 +1,42 @@
use clap::Parser;
use roto_codegen::generator::generate_protobuf_code;
use roto_codegen::google::protobuf::descriptor::FileDescriptorSet;
use std::fs;
use std::path::PathBuf;
#[derive(Parser)]
#[command(
author,
version,
about = "Generates Rust accessor and builder code from a protobuf descriptor set"
)]
struct Args {
/// Path to the descriptor set file (.desc)
#[arg(short, long)]
input: PathBuf,
/// Path to the output directory
#[arg(short, long)]
output: PathBuf,
/// Files to generate. If omitted, all files are generated.
#[arg(short, long, value_delimiter = ',')]
files: Option<Vec<String>>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let data = fs::read(&args.input)?;
let set = FileDescriptorSet::new(&data).expect("Failed to parse FileDescriptorSet");
let files = generate_protobuf_code(&set, args.files.as_deref(), true);
for (filename, content) in files {
let path = args.output.join(filename);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, content)?;
}
Ok(())
}
@@ -1,10 +1,11 @@
use env_logger::init;
use log::{error, info};
use roto::generator::generate_rust_code;
use roto::proto_gen::google::protobuf::descriptor::{
CodeGeneratorRequest, CodeGeneratorResponse, FileDescriptorSet, ResponseFile,
use roto_codegen::generator::generate_rust_code;
use roto_codegen::google::protobuf::compiler::plugin::{
CodeGeneratorRequest, CodeGeneratorResponseBuilder, code_generator_response::FileBuilder,
};
use roto::ProtoBuilder;
use roto_codegen::google::protobuf::descriptor::FileDescriptorSet;
// use roto_runtime::ProtoBuilder;
use std::io::{self, Read, Write};
fn main() {
@@ -40,7 +41,9 @@ fn run() -> std::result::Result<(), Box<dyn std::error::Error>> {
}
/// Core logic that transforms a CodeGeneratorRequest into a serialized CodeGeneratorResponse.
fn handle_request(request: &CodeGeneratorRequest) -> std::result::Result<Vec<u8>, Box<dyn std::error::Error>> {
fn handle_request(
request: &CodeGeneratorRequest,
) -> std::result::Result<Vec<u8>, Box<dyn std::error::Error>> {
// 2. Construct a FileDescriptorSet from the request's proto_files
let mut set_buf = Vec::new();
for file_res in request.proto_file() {
@@ -55,7 +58,7 @@ fn handle_request(request: &CodeGeneratorRequest) -> std::result::Result<Vec<u8>
// Write length as varint
let len = file_data.len() as u64;
let mut len_buf = [0u8; 10];
let len_size = roto::write_varint(len, &mut len_buf).map_err(|e| {
let len_size = roto_runtime::write_varint(len, &mut len_buf).map_err(|e| {
error!("Failed to write varint length: {:?}", e);
e
})?;
@@ -67,38 +70,36 @@ fn handle_request(request: &CodeGeneratorRequest) -> std::result::Result<Vec<u8>
let set = FileDescriptorSet::new(&set_buf)?;
let files_to_generate: Vec<String> = request
.file_to_generate()
.filter_map(|res| {
let (bytes, _) = res.ok()?;
std::str::from_utf8(bytes).ok().map(|s| s.to_string())
})
.collect();
// Generate the Rust code
info!("Generating Rust code from descriptor set...");
let generated_code = generate_rust_code(&set);
// Determine the output filename
let mut output_filename = "roto_generated.rs".to_string();
if let Some(first_file) = request.file_to_generate().next() {
if let Ok((name_bytes, _)) = first_file {
if let Ok(name) = std::str::from_utf8(name_bytes) {
output_filename = format!("{}.rs", name.replace(".proto", ""));
}
}
}
let generated_files = generate_rust_code(&set, Some(&files_to_generate), false);
// Construct the response
let mut response_buf = vec![0u8; 1024 * 1024 * 2]; // Allocate 2MB for response
let mut resp_builder = CodeGeneratorResponse::builder(&mut response_buf);
let mut resp_builder = CodeGeneratorResponseBuilder::builder(&mut response_buf);
for (filename, content) in generated_files {
let mut file_buf = vec![0u8; 1024 * 1024 * 2];
let final_file = ResponseFile::builder(&mut file_buf)
.name(&output_filename)?
.content(&generated_code)?
let final_file = FileBuilder::builder(&mut file_buf)
.name(&filename)?
.content(&content)?
.finish()
.map_err(|e| {
error!("Failed to build ResponseFile: {:?}", e);
error!("Failed to build ResponseFile {}: {:?}", filename, e);
e
})?;
resp_builder = resp_builder.file(final_file)?;
}
let final_response_slice = resp_builder
.add_file(final_file)?
.finish()
.map_err(|e| {
let final_response_slice = resp_builder.finish().map_err(|e| {
error!("Failed to finish CodeGeneratorResponse: {:?}", e);
e
})?;
@@ -123,12 +124,19 @@ mod tests {
}
let data = fs::read(request_path).expect("Failed to read request.bin");
let request = CodeGeneratorRequest::new(&data).expect("Failed to parse CodeGeneratorRequest");
let request =
CodeGeneratorRequest::new(&data).expect("Failed to parse CodeGeneratorRequest");
let result = handle_request(&request);
assert!(result.is_ok(), "handle_request should succeed with request.bin");
assert!(
result.is_ok(),
"handle_request should succeed with request.bin"
);
let response = result.unwrap();
assert!(!response.is_empty(), "The generated response should not be empty");
assert!(
!response.is_empty(),
"The generated response should not be empty"
);
}
}
+42
View File
@@ -0,0 +1,42 @@
use clap::Parser;
use roto_codegen::generator::generate_service_code;
use roto_codegen::google::protobuf::descriptor::FileDescriptorSet;
use std::fs;
use std::path::PathBuf;
#[derive(Parser)]
#[command(
author,
version,
about = "Generates Rust gRPC service code from a protobuf descriptor set"
)]
struct Args {
/// Path to the descriptor set file (.desc)
#[arg(short, long)]
input: PathBuf,
/// Path to the output directory
#[arg(short, long)]
output: PathBuf,
/// Files to generate. If omitted, all files are generated.
#[arg(short, long, value_delimiter = ',')]
files: Option<Vec<String>>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let data = fs::read(&args.input)?;
let set = FileDescriptorSet::new(&data).expect("Failed to parse FileDescriptorSet");
let files = generate_service_code(&set, args.files.as_deref(), true);
for (filename, content) in files {
let path = args.output.join(filename);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, content)?;
}
Ok(())
}
+467
View File
@@ -0,0 +1,467 @@
use crate::google::protobuf::descriptor::{DescriptorProto, EnumDescriptorProto, FieldDescriptorProto, MessageOptions, OneofDescriptorProto};
use crate::google::protobuf::descriptor::FileDescriptorSet;
use crate::generator::types::map_type_to_rust_builder;
use roto_runtime::ProtoAccessor;
use crate::generator::utils::{to_pascal_case, to_snake_case};
use crate::generator::types::map_type_to_rust_accessor;
pub fn write_enum(enum_proto: &EnumDescriptorProto, output: &mut String) {
let enum_name = to_pascal_case(enum_proto.name().unwrap());
output.push_str(&format!(
"#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n#[repr(i32)]\npub enum {} {{\n",
enum_name
));
let mut values = enum_proto.value();
let mut zero_variant_name = None;
while let Some(val_res) = values.next() {
let (val_data, _) = val_res.expect("Failed to iterate enum");
let accessor =
ProtoAccessor::new(val_data).expect("Failed to parse EnumValueDescriptorProto");
let (name_bytes, _) = accessor.get_value(1).expect("Enum value name missing");
let name = std::str::from_utf8(name_bytes).expect("Enum value name invalid utf8");
let (num_bytes, _) = accessor.get_value(2).expect("Enum value number missing");
let (num, _) =
roto_runtime::read_varint(num_bytes).expect("Enum value number invalid varint");
let pascal_name = to_pascal_case(name);
if num == 0 {
zero_variant_name = Some(pascal_name.clone());
}
output.push_str(&format!(" {} = {},\n", pascal_name, num));
}
if zero_variant_name.is_none() {
output.push_str(" Unknown = 0,\n");
zero_variant_name = Some("Unknown".to_string());
}
output.push_str("}\n\n");
output.push_str(&format!(
"impl {} {{\n pub fn from_i32(value: i32) -> Self {{\n match value {{\n",
enum_name
));
let mut values = enum_proto.value();
while let Some(val_res) = values.next() {
let (val_data, _) = val_res.expect("Failed to read enum value");
let accessor =
ProtoAccessor::new(val_data).expect("Failed to parse EnumValueDescriptorProto");
let (name_bytes, _) = accessor.get_value(1).expect("Enum value name missing");
let name = std::str::from_utf8(name_bytes).expect("Enum value name invalid utf8");
let (num_bytes, _) = accessor.get_value(2).expect("Enum value number missing");
let (num, _) =
roto_runtime::read_varint(num_bytes).expect("Enum value number invalid varint");
output.push_str(&format!(
" {} => {}::{},\n",
num,
enum_name,
to_pascal_case(name)
));
}
output.push_str(&format!(
" _ => {}::{},\n",
enum_name,
zero_variant_name.as_ref().unwrap()
));
output.push_str(" }\n }\n}\n\n");
}
pub fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
let msg_name = to_pascal_case(msg_proto.name().unwrap());
let mod_name = to_snake_case(msg_proto.name().unwrap());
let mut fields_info = Vec::new();
for field_res in msg_proto.field() {
let (field_data, _) = field_res.expect("Failed to iterate field");
let field_proto =
FieldDescriptorProto::new(field_data).expect("Failed to parse FieldDescriptorProto");
let field_name = field_proto.name().unwrap();
let tag = field_proto.number().unwrap();
let f_type = field_proto.r#type().unwrap() as i32;
let f_label = field_proto.label().unwrap() as i32;
let oneof_index = field_proto.oneof_index().ok();
let is_map = field_proto
.options()
.map(|opt| {
MessageOptions::new(opt)
.unwrap()
.map_entry()
.unwrap_or(false)
})
.unwrap_or(false);
fields_info.push((
field_name.to_string(),
tag,
f_type,
f_label,
oneof_index,
is_map,
));
}
let mut oneofs = Vec::new();
for o_res in msg_proto.oneof_decl() {
let (o, _) = o_res.expect("Failed to iterate oneof");
oneofs.push(o);
}
output.push_str(&format!("pub struct {}<'a> {{\n", msg_name));
output.push_str(" accessor: roto_runtime::ProtoAccessor<'a>,\n");
for (field_name, _tag, _f_type, f_label, _oneof_index, _is_map) in &fields_info {
if *f_label == 3 {
output.push_str(&format!(" {}_start: Option<usize>,\n", field_name));
output.push_str(&format!(" {}_end: Option<usize>,\n", field_name));
} else {
output.push_str(&format!(" {}_offset: Option<usize>,\n", field_name));
}
}
output.push_str("}\n\n");
output.push_str(&format!("impl<'a> {}<'a> {{\n", msg_name));
output.push_str(" pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {\n");
output.push_str(" let accessor = roto_runtime::ProtoAccessor::new(data)?;\n");
for (name, _, _, label, _oneof_index, _is_map) in &fields_info {
if *label == 3 {
output.push_str(&format!(" let mut {}_start = None;\n", name));
output.push_str(&format!(" let mut {}_end = None;\n", name));
} else {
output.push_str(&format!(" let mut {}_offset = None;\n", name));
}
}
output.push_str(" for item in accessor.fields() {\n");
output.push_str(" let (offset, tag, _) = item?;\n");
for (name, tag, _, label, _oneof_index, _is_map) in &fields_info {
if *label == 3 {
output.push_str(&format!(" if tag.field_number == {} {{\n", tag));
output.push_str(&format!(
" if {}_start.is_none() {{ {}_start = Some(offset); }}\n",
name, name
));
output.push_str(&format!(" {}_end = Some(offset);\n", name));
output.push_str(" }\n");
} else {
output.push_str(&format!(
" if tag.field_number == {} {{ {}_offset = Some(offset); }}\n",
tag, name
));
}
}
output.push_str(" }\n\n");
output.push_str(" Ok(Self {\n");
output.push_str(" accessor,\n");
for (name, _, _, label, _oneof_index, _is_map) in &fields_info {
if *label == 3 {
output.push_str(&format!("{}_start, {}_end,\n", name, name));
} else {
output.push_str(&format!("{}_offset,\n", name));
}
}
output.push_str(" })\n }\n\n");
for (field_name, tag, f_type, f_label, _oneof_index, is_map) in &fields_info {
let (rust_type, logic, default_val) = map_type_to_rust_accessor(*f_type, *f_label, *is_map);
let safe_name = if field_name == "type" {
format!("r#{}", field_name)
} else {
field_name.clone()
};
if *f_label == 3 {
output.push_str(&format!(
" pub fn {}(&self) -> {} {{\n",
safe_name, rust_type
));
output.push_str(&format!(
" match (self.{}_start, self.{}_end) {{\n",
field_name, field_name
));
if *is_map {
output.push_str(&format!(" (Some(start), Some(end)) => roto_runtime::MapFieldIterator::new(self.accessor.iter_repeated_range({}, start, end)),\n", tag));
output.push_str(&format!(
" _ => roto_runtime::MapFieldIterator::new(self.accessor.iter_repeated({})),\n",
tag
));
} else {
output.push_str(&format!(" (Some(start), Some(end)) => self.accessor.iter_repeated_range({}, start, end),\n", tag));
output.push_str(&format!(
" _ => self.accessor.iter_repeated({}),\n",
tag
));
}
output.push_str(" }\n }\n\n");
} else {
output.push_str(&format!(
" pub fn {}(&self) -> roto_runtime::Result<{}> {{\n",
safe_name, rust_type
));
output.push_str(&format!(
" let offset = self.{}_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;\n",
field_name
));
output.push_str(" let (bytes, _) = self.accessor.get_value_at(offset)?;\n");
output.push_str(&format!(" {}\n", logic));
output.push_str(" }\n\n");
output.push_str(&format!(
" pub fn {}_or_default(&self) -> roto_runtime::Result<{}> {{\n",
safe_name, rust_type
));
output.push_str(&format!(
" self.{}().or(Ok({}))\n",
safe_name, default_val
));
output.push_str(" }\n\n");
output.push_str(&format!(
" pub fn has_{}(&self) -> bool {{ self.{}_offset.is_some() }}\n\n",
field_name, field_name
));
}
}
for (oneof_index, oneof_proto) in oneofs.iter().enumerate() {
let oneof_desc =
OneofDescriptorProto::new(*oneof_proto).expect("Failed to parse OneofDescriptorProto");
let oneof_name = oneof_desc.name().unwrap();
let pascal_oneof_name = to_pascal_case(oneof_name);
let snake_oneof_name = to_snake_case(oneof_name);
let return_type = format!("{}::{}<'a>", mod_name, pascal_oneof_name);
let signature = format!(
" pub fn which_{}(&self) -> roto_runtime::Result<Option<{}> > {{\n",
snake_oneof_name, return_type
);
output.push_str(&signature);
for (field_name, _tag, _f_type, _f_label, f_oneof_index, _is_map) in &fields_info {
if *f_oneof_index == Some(oneof_index as i32) {
let safe_field_name = if field_name == "type" {
format!("r#{}", field_name)
} else {
field_name.clone()
};
output.push_str(&format!(
" if self.{}_offset.is_some() {{\n",
field_name
));
output.push_str(&format!(
" return Ok(Some({}::{}::{} (self.{}()?)));\n",
mod_name, pascal_oneof_name, safe_field_name, safe_field_name
));
output.push_str(" }\n");
}
}
output.push_str(" Ok(None)\n }\n\n");
}
// raw_fields() convenience on the message struct (before closing the impl)
output.push_str(" pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {\n");
output.push_str(" self.accessor.raw_fields()\n");
output.push_str(" }\n\n");
output.push_str("}\n\n");
// Collect builder field info so we can use it multiple times below.
// Tuple: (field_name, safe_name, tag, rust_type, write_method)
let mut builder_fields: Vec<(String, String, u32, String, String)> = Vec::new();
for field_res in msg_proto.field() {
let (field_data, _) = field_res.expect("Failed to iterate field");
let field_proto =
FieldDescriptorProto::new(field_data).expect("Failed to parse FieldDescriptorProto");
let field_name = field_proto.name().unwrap().to_string();
let safe_name = if field_name == "type" {
format!("r#{}", field_name)
} else {
field_name.clone()
};
let tag = field_proto.number().unwrap();
let f_type = field_proto.r#type().unwrap() as i32;
let (rust_type, method) = map_type_to_rust_builder(f_type);
builder_fields.push((field_name, safe_name, tag as u32, rust_type, method));
}
// Builder struct — one `_written: bool` flag per field
output.push_str(&format!("pub struct {}Builder<'b> {{\n", msg_name));
output.push_str(" builder: roto_runtime::ProtoBuilder<'b>,\n");
for (field_name, _, _, _, _) in &builder_fields {
output.push_str(&format!(" {}_written: bool,\n", field_name));
}
output.push_str(&format!("}}\n\nimpl<'b> {}Builder<'b> {{\n", msg_name));
// Constructor — initialise every flag to false
output.push_str(&format!(
" pub fn builder(buf: &mut [u8]) -> {}Builder<'_> {{\n {}Builder {{\n",
msg_name, msg_name
));
output.push_str(" builder: roto_runtime::ProtoBuilder::new(buf),\n");
for (field_name, _, _, _, _) in &builder_fields {
output.push_str(&format!(" {}_written: false,\n", field_name));
}
output.push_str(" }\n }\n\n");
// Per-field setters — mark field as written
for (field_name, safe_name, tag, rust_type, method) in &builder_fields {
output.push_str(&format!(
" pub fn {}(mut self, value: {}) -> roto_runtime::Result<Self> {{\n self.builder.{}({}, value)?;\n self.{}_written = true;\n Ok(self)\n }}\n\n",
safe_name, rust_type, method, tag, field_name
));
}
// with() — copies unseen fields from an existing message
output.push_str(&format!(
" pub fn with(mut self, msg: &{}<'_>) -> roto_runtime::Result<Self> {{\n",
msg_name
));
output.push_str(" for item in msg.accessor.raw_fields() {\n");
output.push_str(" let (field_number, raw_bytes) = item?;\n");
output.push_str(" let is_written = match field_number {\n");
for (field_name, _, tag, _, _) in &builder_fields {
output.push_str(&format!(
" {} => self.{}_written,\n",
tag, field_name
));
}
output.push_str(" _ => false,\n");
output.push_str(" };\n");
output.push_str(" if !is_written {\n");
output.push_str(" self.builder.write_raw(raw_bytes)?;\n");
output.push_str(" }\n");
output.push_str(" }\n");
output.push_str(" Ok(self)\n");
output.push_str(" }\n\n");
output.push_str(&format!(" pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {{\n self.builder.finish()\n }}\n}}\n\n"));
output.push_str(&format!(
"#[cfg(feature = \"alloc\")]\npub struct Owned{} {{\n",
msg_name
));
output.push_str(" pub data: bytes::Bytes,\n");
output.push_str("}\n\n");
output.push_str(&format!(
"#[cfg(feature = \"alloc\")]\nimpl roto_runtime::RotoOwned for Owned{} {{\n",
msg_name
));
output.push_str(&format!(" type Reader<'a> = {}<'a>;\n", msg_name));
output.push_str(&format!(" fn reader(&self) -> {}<'_> {{\n", msg_name));
output.push_str(&format!(
" {}::new(&self.data).expect(\"failed to create reader\")\n",
msg_name
));
output.push_str(" }\n");
output.push_str("}\n\n");
output.push_str(&format!(
"#[cfg(feature = \"alloc\")]\nimpl roto_runtime::RotoMessage for Owned{} {{\n",
msg_name
));
output.push_str(" fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {\n");
output.push_str(&format!(" Ok(Owned{} {{ data: buf }})\n", msg_name));
output.push_str(" }\n\n");
output.push_str(" fn bytes(&self) -> bytes::Bytes {\n");
output.push_str(" self.data.clone()\n");
output.push_str(" }\n");
output.push_str("}\n\n");
let mut nested_enums = Vec::new();
for e_res in msg_proto.enum_type() {
if let Ok((e, _)) = e_res {
nested_enums.push(e);
}
}
let mut nested_msgs = Vec::new();
for m_res in msg_proto.nested_type() {
if let Ok((m, _)) = m_res {
nested_msgs.push(m);
}
}
if !nested_enums.is_empty() || !nested_msgs.is_empty() || !oneofs.is_empty() {
let mod_name = to_snake_case(msg_proto.name().unwrap());
output.push_str(&format!("pub mod {} {{\n", mod_name));
for e_data in &nested_enums {
write_enum(
&EnumDescriptorProto::new(e_data)
.expect("Failed to parse nested EnumDescriptorProto"),
output,
);
}
for m_data in &nested_msgs {
write_message(
&DescriptorProto::new(m_data).expect("Failed to parse nested DescriptorProto"),
output,
);
}
for (oneof_index, oneof_proto) in oneofs.iter().enumerate() {
let oneof_desc = OneofDescriptorProto::new(*oneof_proto)
.expect("Failed to parse OneofDescriptorProto");
let oneof_name = oneof_desc.name().unwrap();
let pascal_oneof_name = to_pascal_case(oneof_name);
output.push_str(&format!("pub enum {}<'a> {{\n", pascal_oneof_name));
for (field_name, _tag, f_type, f_label, f_oneof_index, _is_map) in &fields_info {
if *f_oneof_index == Some(oneof_index as i32) {
let (rust_type, _, _) = map_type_to_rust_accessor(*f_type, *f_label, *_is_map);
let safe_field_name = if field_name == "type" {
format!("r#{}", field_name)
} else {
field_name.clone()
};
output.push_str(&format!(" {}({}),\n", safe_field_name, rust_type));
}
}
output.push_str("}\n\n");
}
}
if !nested_enums.is_empty() || !nested_msgs.is_empty() || !oneofs.is_empty() {
output.push_str("}\n\n");
}
}
pub fn generate_protobuf_code(
set: &FileDescriptorSet,
files_to_generate: Option<&[String]>,
generate_mod_files: bool,
) -> Vec<(String, String)> {
generate_files_common(
set,
files_to_generate,
generate_mod_files,
DATA_IMPORTS,
|file_proto, output| {
// Enums
for enum_res in file_proto.enum_type() {
let (enum_data, _) = enum_res.expect("Failed to iterate enum");
write_enum(
&EnumDescriptorProto::new(enum_data)
.expect("Failed to parse EnumDescriptorProto"),
output,
);
}
// Messages
for msg_res in file_proto.message_type() {
let (msg_data, _) = msg_res.expect("Failed to iterate message");
write_message(
&DescriptorProto::new(msg_data).expect("Failed to parse DescriptorProto"),
output,
);
}
},
)
}
File diff suppressed because it is too large Load Diff
+258
View File
@@ -0,0 +1,258 @@
use crate::google::protobuf::descriptor::{ServiceDescriptorProto, MethodDescriptorProto};
use crate::google::protobuf::descriptor::FileDescriptorSet;
use crate::generator::generate_files_common;
use crate::generator::SERVICE_IMPORTS;
use crate::generator::utils::{to_pascal_case, to_snake_case};
pub fn generate_service_code(
set: &FileDescriptorSet,
files_to_generate: Option<&[String]>,
generate_mod_files: bool,
) -> Vec<(String, String)> {
generate_files_common(
set,
files_to_generate,
generate_mod_files,
"",
|file_proto, output| {
let package = file_proto.package().unwrap_or("").to_string();
// Services
for svc_res in file_proto.service() {
let (svc_data, _) = svc_res.expect("Failed to iterate service");
write_service(
&ServiceDescriptorProto::new(svc_data)
.expect("Failed to parse ServiceDescriptorProto"),
&package,
output,
);
}
},
)
}
pub fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut String) {
output.push_str(SERVICE_IMPORTS);
output.push_str("\n");
let svc_name = to_pascal_case(svc_proto.name().unwrap());
output.push_str("#[cfg(feature = \"alloc\")]\n");
output.push_str(&format!(
"#[async_trait::async_trait]\npub trait {}: Send + Sync + 'static {{\n",
svc_name
));
for method_res in svc_proto.method() {
let (method_data, _) = method_res.expect("Failed to iterate method");
let method_proto =
MethodDescriptorProto::new(method_data).expect("Failed to parse MethodDescriptorProto");
let method_name = to_snake_case(method_proto.name().unwrap());
let input_full_name = method_proto.input_type().unwrap();
let output_full_name = method_proto.output_type().unwrap();
let input_type = input_full_name.split('.').last().unwrap();
let output_type = output_full_name.split('.').last().unwrap();
let input_owned = format!("Owned{}", input_type);
let output_owned = format!("Owned{}", output_type);
let client_streaming = method_proto.client_streaming().unwrap_or(false);
let server_streaming = method_proto.server_streaming().unwrap_or(false);
let req_type = if client_streaming {
format!("Request<tonic::Streaming<{}>>", input_owned)
} else {
format!("Request<{}>", input_owned)
};
let resp_type = if server_streaming {
format!(
"Response<Pin<Box<dyn Stream<Item = std::result::Result<{}, Status>> + Send>>>",
output_owned
)
} else {
format!("Response<{}>", output_owned)
};
output.push_str(&format!(
" async fn {}(&self, request: {}) -> std::result::Result<{}, Status>;\n",
method_name, req_type, resp_type
));
}
output.push_str("}\n\n");
let server_name = format!("{}Server", svc_name);
output.push_str("#[cfg(feature = \"alloc\")]\n");
output.push_str(&format!(
"#[derive(Clone)]\npub struct {} {{\n",
server_name
));
output.push_str(&format!(" inner: Arc<dyn {}>,\n", svc_name));
output.push_str(" pool: Arc<BufferPool>,\n");
output.push_str("}\n\n");
output.push_str("#[cfg(feature = \"alloc\")]\n");
output.push_str(&format!("impl {} {{\n", server_name));
output.push_str(&format!(
" pub fn new(inner: Arc<dyn {}>, pool: Arc<BufferPool>) -> Self {{\n",
svc_name
));
output.push_str(" Self { inner, pool }\n");
output.push_str(" }\n");
output.push_str("}\n\n");
output.push_str("#[cfg(feature = \"alloc\")]\n");
output.push_str(&format!(
"impl tonic::server::NamedService for {} {{\n",
server_name
));
let full_svc_name = if package.is_empty() {
svc_proto.name().unwrap().to_string()
} else {
format!("{}.{}", package, svc_proto.name().unwrap())
};
output.push_str(&format!(
" const NAME: &'static str = \"{}\";\n",
full_svc_name
));
output.push_str("}\n\n");
output.push_str("#[cfg(feature = \"alloc\")]\n");
output.push_str(&format!(
"impl Service<http::Request<BoxBody>> for {} {{\n",
server_name
));
output.push_str(" type Response = http::Response<BoxBody>;\n");
output.push_str(" type Error = std::convert::Infallible;\n");
output.push_str(" type Future = Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;\n\n");
output.push_str(" fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {\n");
output.push_str(" Poll::Ready(Ok(()))\n");
output.push_str(" }\n\n");
output.push_str(" fn call(&mut self, req: http::Request<BoxBody>) -> Self::Future {\n");
output.push_str(" let inner = self.inner.clone();\n");
output.push_str(" let pool = self.pool.clone();\n");
output.push_str(" Box::pin(async move {\n");
output.push_str(" let path = req.uri().path().to_string();\n");
output.push_str(" let body = req.into_body();\n");
output.push_str(" let mut buf = pool.get();\n");
output.push_str(" let mut stream = body;\n");
output.push_str(" while let Some(frame_result) = stream.frame().await {\n");
output.push_str(" let frame = frame_result.expect(\"Body frame error\");\n");
output.push_str(" if let Some(data) = frame.data_ref() {\n");
output.push_str(" buf.put(data.clone());\n");
output.push_str(" }\n");
output.push_str(" }\n\n");
output.push_str(" let total_len = buf.len();\n");
output.push_str(" let bytes_vec = buf.split_to(total_len).freeze();\n");
output.push_str(" pool.put(buf);\n");
output.push_str(" if bytes_vec.len() < 5 {\n");
output.push_str(" let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));\n");
output.push_str(" return Ok(http::Response::builder().status(200).body(res_body).unwrap());\n");
output.push_str(" }\n\n");
output.push_str(" let payload = bytes_vec.slice(5..);\n");
output.push_str(" let mut routed = false;\n\n");
let mut methods = Vec::new();
for method_res in svc_proto.method() {
let (method_data, _) = method_res.expect("Failed to iterate method");
let method_proto =
MethodDescriptorProto::new(method_data).expect("Failed to parse MethodDescriptorProto");
let original_method_name = method_proto.name().unwrap().to_string();
let method_name = to_snake_case(&original_method_name);
let input_full_name = method_proto.input_type().unwrap();
let input_type = input_full_name.split('.').last().unwrap();
let input_owned = format!("Owned{}", input_type);
let server_streaming = method_proto.server_streaming().unwrap_or(false);
methods.push((
original_method_name,
method_name,
input_owned,
server_streaming,
));
}
for (original_method_name, method_name, input_owned, server_streaming) in methods {
if server_streaming {
// For streaming RPCs, we don't implement the server logic yet.
// We just make it compile by returning a "not implemented" response.
let full_path = if package.is_empty() {
format!("/{}/{}", svc_proto.name().unwrap(), original_method_name)
} else {
format!(
"/{}.{}/{}",
package,
svc_proto.name().unwrap(),
original_method_name
)
};
output.push_str(&format!(" if path == \"{}\" {{\n", full_path));
output.push_str(" let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));\n");
output.push_str(" return Ok(http::Response::builder().status(200).body(res_body).unwrap());\n");
output.push_str(" }\n");
continue;
}
let full_path = if package.is_empty() {
format!("/{}/{}", svc_proto.name().unwrap(), original_method_name)
} else {
format!(
"/{}.{}/{}",
package,
svc_proto.name().unwrap(),
original_method_name
)
};
output.push_str(&format!(" if path == \"{}\" {{\n", full_path));
output.push_str(&format!(
" let request_msg = match {}::decode(payload) {{\n",
input_owned
));
output.push_str(" Ok(msg) => msg,\n");
output.push_str(" Err(e) => {\n");
output.push_str(" let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));\n");
output.push_str(" return Ok(http::Response::builder().status(200).body(res_body).unwrap());\n");
output.push_str(" }\n");
output.push_str(" };\n\n");
output.push_str(&format!(
" let response = match inner.{}(Request::new(request_msg)).await {{\n",
method_name
));
output.push_str(" Ok(res) => res,\n");
output.push_str(" Err(e) => {\n");
output.push_str(" let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));\n");
output.push_str(" return Ok(http::Response::builder().status(200).body(res_body).unwrap());\n");
output.push_str(" }\n");
output.push_str(" };\n\n");
output.push_str(" let response_msg = response.into_inner();\n");
output.push_str(" let response_bytes = response_msg.bytes();\n");
output.push_str(" let mut res_buf = pool.get();\n");
output.push_str(" res_buf.put_u8(0);\n");
output.push_str(" let len = response_bytes.len() as u32;\n");
output.push_str(" res_buf.put_slice(&len.to_be_bytes());\n");
output.push_str(" res_buf.put_slice(&response_bytes);\n");
output.push_str(" let frame_len = res_buf.len();\n");
output.push_str(" let frame = res_buf.split_to(frame_len).freeze();\n");
output.push_str(" pool.put(res_buf);\n");
output.push_str(
" let res_body = BoxBody::new(StatusBody::new(Some(frame), 0));\n",
);
output.push_str(" routed = true;\n");
output.push_str(" return Ok(http::Response::builder().status(200).header(\"content-type\", \"application/grpc\").body(res_body).unwrap());\n");
output.push_str(" }\n");
}
output.push_str(" if !routed {\n");
output.push_str(" let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));\n");
output.push_str(" return Ok(http::Response::builder().status(200).body(res_body).unwrap());\n");
output.push_str(" }\n");
output.push_str(" Ok(http::Response::builder().status(200).body(BoxBody::new(StatusBody::new(None, 0))).unwrap())\n");
output.push_str(" })\n");
output.push_str(" }\n");
output.push_str("}\n");
}
+151
View File
@@ -0,0 +1,151 @@
use crate::google::protobuf::descriptor::FieldDescriptorProto;
use crate::google::protobuf::descriptor::DescriptorProto;
pub fn map_type_to_rust_accessor(
field_type: i32,
label: i32,
is_map: bool,
) -> (String, String, String) {
if label == 3 {
// LABEL_REPEATED
let iterator_type = if is_map {
"roto_runtime::MapFieldIterator<'a>"
} else {
"roto_runtime::RepeatedFieldIterator<'a>"
};
return (
iterator_type.to_string(),
"".to_string(), // Not used for repeated fields in the same way
"".to_string(), // Not used for repeated fields
);
}
match field_type {
9 => (
"&'a str".to_string(),
"core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
"\"\"".to_string(),
), // TYPE_STRING
1 => (
"f64".to_string(),
"Ok(f64::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?))".to_string(),
"0.0".to_string(),
), // TYPE_DOUBLE
2 => (
"f32".to_string(),
"Ok(f32::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?))".to_string(),
"0.0".to_string(),
), // TYPE_FLOAT
3 | 5 | 15 | 17 => (
"i32".to_string(),
"roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
"0".to_string(),
), // INT/SINT/SFIXED 32
4 | 6 | 13 => (
"u32".to_string(),
"roto_runtime::read_varint(bytes).map(|(v, _)| v as u32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
"0".to_string(),
), // UINT/FIXED 32
16 | 18 => (
"i64".to_string(),
"roto_runtime::read_varint(bytes).map(|(v, _)| v as i64).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
"0".to_string(),
), // SINT/SFIXED 64
7 | 14 => (
"u64".to_string(),
"roto_runtime::read_varint(bytes).map(|(v, _)| v as u64).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
"0".to_string(),
), // UINT/FIXED 64
8 => (
"bool".to_string(),
"roto_runtime::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
"false".to_string(),
), // TYPE_BOOL
11 | 12 => (
"&'a [u8]".to_string(),
"Ok(bytes)".to_string(),
"&[]".to_string(),
), // MESSAGE/BYTES
_ => (
"&'a [u8]".to_string(),
"Ok(bytes)".to_string(),
"&[]".to_string(),
),
}
}
EOF > /opt/workspace/codegen/src/generator/types.rs
use crate::google::protobuf::descriptor::FieldDescriptorProto;
use crate::google::protobuf::descriptor::DescriptorProto;
pub fn map_type_to_rust_accessor(
field_type: i32,
label: i32,
is_map: bool,
) -> (String, String, String) {
if label == 3 {
// LABEL_REPEATED
let iterator_type = if is_map {
"roto_runtime::MapFieldIterator<'a>"
} else {
"roto_runtime::RepeatedFieldIterator<'a>"
};
return (
iterator_type.to_string(),
"".to_string(), // Not used for repeated fields in the same way
"".to_string(), // Not used for repeated fields
);
}
match field_type {
9 => (
"&'a str".to_string(),
"core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
"\"\"".to_string(),
), // TYPE_STRING
1 => (
"f64".to_string(),
"Ok(f64::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?))".to_string(),
"0.0".to_string(),
), // TYPE_DOUBLE
2 => (
"f32".to_string(),
"Ok(f32::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?))".to_string(),
"0.0".to_string(),
), // TYPE_FLOAT
3 | 5 | 15 | 17 => (
"i32".to_string(),
"roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
"0".to_string(),
), // INT/SINT/SFIXED 32
4 | 6 | 13 => (
"u32".to_string(),
"roto_runtime::read_varint(bytes).map(|(v, _)| v as u32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
"0".to_string(),
), // UINT/FIXED 32
16 | 18 => (
"i64".to_string(),
"roto_runtime::read_varint(bytes).map(|(v, _)| v as i64).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
"0".to_string(),
), // SINT/SFIXED 64
7 | 14 => (
"u64".to_string(),
"roto_runtime::read_varint(bytes).map(|(v, _)| v as u64).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
"0".to_string(),
), // UINT/FIXED 64
8 => (
"bool".to_string(),
"roto_runtime::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
"false".to_string(),
), // TYPE_BOOL
11 | 12 => (
"&'a [u8]".to_string(),
"Ok(bytes)".to_string(),
"&[]".to_string(),
), // MESSAGE/BYTES
_ => (
"&'a [u8]".to_string(),
"Ok(bytes)".to_string(),
"&[]".to_string(),
),
}
}
+57
View File
@@ -0,0 +1,57 @@
pub const DATA_IMPORTS: &str = "use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};\nuse core::str;\n#[cfg(feature = \"alloc\")]\nuse bytes::{Bytes, BytesMut, Buf, BufMut};\n";
pub fn to_pascal_case(s: &str) -> String {
s.split('_')
.map(|word| {
let mut chars = word.chars();
match chars.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + chars.as_str(),
}
})
.collect()
}
pub fn to_snake_case(s: &str) -> String {
let mut result = String::new();
for (i, c) in s.chars().enumerate() {
if c.is_uppercase() {
if i > 0 {
result.push('_');
}
result.push(c.to_ascii_lowercase());
} else {
result.push(c);
}
}
result
}
EOF > /opt/workspace/codegen/src/generator/utils.rs
pub const DATA_IMPORTS: &str = "use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};\nuse core::str;\n#[cfg(feature = \"alloc\")]\nuse bytes::{Bytes, BytesMut, Buf, BufMut};\n";
pub fn to_pascal_case(s: &str) -> String {
s.split('_')
.map(|word| {
let mut chars = word.chars();
match chars.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + chars.as_str(),
}
})
.collect()
}
pub fn to_snake_case(s: &str) -> String {
let mut result = String::new();
for (i, c) in s.chars().enumerate() {
if c.is_uppercase() {
if i > 0 {
result.push('_');
}
result.push(c.to_ascii_lowercase());
} else {
result.push(c);
}
}
result
}
+1
View File
@@ -0,0 +1 @@
pub mod protobuf;
@@ -0,0 +1 @@
pub mod plugin;
@@ -0,0 +1,781 @@
// @generated by protoc-gen-roto — do not edit
#[allow(unused_imports)]
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator};
use std::str;
use bytes::{Bytes, BytesMut, Buf, BufMut};
use tonic::{Request, Response, Status};
use tokio_stream::Stream;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::future::Future;
use tonic::body::BoxBody;
use tower::Service;
use futures_util::StreamExt;
use http_body_util::BodyExt;
use http_body::Body;
use roto_tonic::{BufferPool, StatusBody};
use crate::google::protobuf::descriptor;
pub struct Version<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
major_offset: Option<usize>,
minor_offset: Option<usize>,
patch_offset: Option<usize>,
suffix_offset: Option<usize>,
}
impl<'a> Version<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut major_offset = None;
let mut minor_offset = None;
let mut patch_offset = None;
let mut suffix_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { major_offset = Some(offset); }
if tag.field_number == 2 { minor_offset = Some(offset); }
if tag.field_number == 3 { patch_offset = Some(offset); }
if tag.field_number == 4 { suffix_offset = Some(offset); }
}
Ok(Self {
accessor,
major_offset,
minor_offset,
patch_offset,
suffix_offset,
})
}
pub fn major(&self) -> roto_runtime::Result<i32> {
let offset = self.major_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn major_or_default(&self) -> roto_runtime::Result<i32> {
self.major().or(Ok(0))
}
pub fn has_major(&self) -> bool { self.major_offset.is_some() }
pub fn minor(&self) -> roto_runtime::Result<i32> {
let offset = self.minor_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn minor_or_default(&self) -> roto_runtime::Result<i32> {
self.minor().or(Ok(0))
}
pub fn has_minor(&self) -> bool { self.minor_offset.is_some() }
pub fn patch(&self) -> roto_runtime::Result<i32> {
let offset = self.patch_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn patch_or_default(&self) -> roto_runtime::Result<i32> {
self.patch().or(Ok(0))
}
pub fn has_patch(&self) -> bool { self.patch_offset.is_some() }
pub fn suffix(&self) -> roto_runtime::Result<&'a str> {
let offset = self.suffix_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn suffix_or_default(&self) -> roto_runtime::Result<&'a str> {
self.suffix().or(Ok(""))
}
pub fn has_suffix(&self) -> bool { self.suffix_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct VersionBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
major_written: bool,
minor_written: bool,
patch_written: bool,
suffix_written: bool,
}
impl<'b> VersionBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> VersionBuilder<'_> {
VersionBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
major_written: false,
minor_written: false,
patch_written: false,
suffix_written: false,
}
}
pub fn major(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(1, value)?;
self.major_written = true;
Ok(self)
}
pub fn minor(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(2, value)?;
self.minor_written = true;
Ok(self)
}
pub fn patch(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(3, value)?;
self.patch_written = true;
Ok(self)
}
pub fn suffix(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(4, value)?;
self.suffix_written = true;
Ok(self)
}
pub fn with(mut self, msg: &Version<'_>) -> roto_runtime::Result<Self> {
for item in msg.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.major_written,
2 => self.minor_written,
3 => self.patch_written,
4 => self.suffix_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedVersion {
pub data: bytes::Bytes,
}
impl roto_runtime::RotoOwned for OwnedVersion {
type Reader<'a> = Version<'a>;
fn reader(&self) -> Version<'_> {
Version::new(&self.data).expect("failed to create reader")
}
}
impl roto_runtime::RotoMessage for OwnedVersion {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedVersion { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct CodeGeneratorRequest<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
file_to_generate_start: Option<usize>,
file_to_generate_end: Option<usize>,
parameter_offset: Option<usize>,
proto_file_start: Option<usize>,
proto_file_end: Option<usize>,
source_file_descriptors_start: Option<usize>,
source_file_descriptors_end: Option<usize>,
compiler_version_offset: Option<usize>,
}
impl<'a> CodeGeneratorRequest<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut file_to_generate_start = None;
let mut file_to_generate_end = None;
let mut parameter_offset = None;
let mut proto_file_start = None;
let mut proto_file_end = None;
let mut source_file_descriptors_start = None;
let mut source_file_descriptors_end = None;
let mut compiler_version_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 {
if file_to_generate_start.is_none() { file_to_generate_start = Some(offset); }
file_to_generate_end = Some(offset);
}
if tag.field_number == 2 { parameter_offset = Some(offset); }
if tag.field_number == 15 {
if proto_file_start.is_none() { proto_file_start = Some(offset); }
proto_file_end = Some(offset);
}
if tag.field_number == 17 {
if source_file_descriptors_start.is_none() { source_file_descriptors_start = Some(offset); }
source_file_descriptors_end = Some(offset);
}
if tag.field_number == 3 { compiler_version_offset = Some(offset); }
}
Ok(Self {
accessor,
file_to_generate_start, file_to_generate_end,
parameter_offset,
proto_file_start, proto_file_end,
source_file_descriptors_start, source_file_descriptors_end,
compiler_version_offset,
})
}
pub fn file_to_generate(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.file_to_generate_start, self.file_to_generate_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(1, start, end),
_ => self.accessor.iter_repeated(1),
}
}
pub fn parameter(&self) -> roto_runtime::Result<&'a str> {
let offset = self.parameter_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn parameter_or_default(&self) -> roto_runtime::Result<&'a str> {
self.parameter().or(Ok(""))
}
pub fn has_parameter(&self) -> bool { self.parameter_offset.is_some() }
pub fn proto_file(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.proto_file_start, self.proto_file_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(15, start, end),
_ => self.accessor.iter_repeated(15),
}
}
pub fn source_file_descriptors(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.source_file_descriptors_start, self.source_file_descriptors_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(17, start, end),
_ => self.accessor.iter_repeated(17),
}
}
pub fn compiler_version(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.compiler_version_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn compiler_version_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.compiler_version().or(Ok(&[]))
}
pub fn has_compiler_version(&self) -> bool { self.compiler_version_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct CodeGeneratorRequestBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
file_to_generate_written: bool,
parameter_written: bool,
proto_file_written: bool,
source_file_descriptors_written: bool,
compiler_version_written: bool,
}
impl<'b> CodeGeneratorRequestBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> CodeGeneratorRequestBuilder<'_> {
CodeGeneratorRequestBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
file_to_generate_written: false,
parameter_written: false,
proto_file_written: false,
source_file_descriptors_written: false,
compiler_version_written: false,
}
}
pub fn file_to_generate(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.file_to_generate_written = true;
Ok(self)
}
pub fn parameter(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(2, value)?;
self.parameter_written = true;
Ok(self)
}
pub fn proto_file(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(15, value)?;
self.proto_file_written = true;
Ok(self)
}
pub fn source_file_descriptors(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(17, value)?;
self.source_file_descriptors_written = true;
Ok(self)
}
pub fn compiler_version(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(3, value)?;
self.compiler_version_written = true;
Ok(self)
}
pub fn with(mut self, msg: &CodeGeneratorRequest<'_>) -> roto_runtime::Result<Self> {
for item in msg.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.file_to_generate_written,
2 => self.parameter_written,
15 => self.proto_file_written,
17 => self.source_file_descriptors_written,
3 => self.compiler_version_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedCodeGeneratorRequest {
pub data: bytes::Bytes,
}
impl roto_runtime::RotoOwned for OwnedCodeGeneratorRequest {
type Reader<'a> = CodeGeneratorRequest<'a>;
fn reader(&self) -> CodeGeneratorRequest<'_> {
CodeGeneratorRequest::new(&self.data).expect("failed to create reader")
}
}
impl roto_runtime::RotoMessage for OwnedCodeGeneratorRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedCodeGeneratorRequest { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct CodeGeneratorResponse<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
error_offset: Option<usize>,
supported_features_offset: Option<usize>,
minimum_edition_offset: Option<usize>,
maximum_edition_offset: Option<usize>,
file_start: Option<usize>,
file_end: Option<usize>,
}
impl<'a> CodeGeneratorResponse<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut error_offset = None;
let mut supported_features_offset = None;
let mut minimum_edition_offset = None;
let mut maximum_edition_offset = None;
let mut file_start = None;
let mut file_end = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { error_offset = Some(offset); }
if tag.field_number == 2 { supported_features_offset = Some(offset); }
if tag.field_number == 3 { minimum_edition_offset = Some(offset); }
if tag.field_number == 4 { maximum_edition_offset = Some(offset); }
if tag.field_number == 15 {
if file_start.is_none() { file_start = Some(offset); }
file_end = Some(offset);
}
}
Ok(Self {
accessor,
error_offset,
supported_features_offset,
minimum_edition_offset,
maximum_edition_offset,
file_start, file_end,
})
}
pub fn error(&self) -> roto_runtime::Result<&'a str> {
let offset = self.error_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn error_or_default(&self) -> roto_runtime::Result<&'a str> {
self.error().or(Ok(""))
}
pub fn has_error(&self) -> bool { self.error_offset.is_some() }
pub fn supported_features(&self) -> roto_runtime::Result<u32> {
let offset = self.supported_features_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as u32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn supported_features_or_default(&self) -> roto_runtime::Result<u32> {
self.supported_features().or(Ok(0))
}
pub fn has_supported_features(&self) -> bool { self.supported_features_offset.is_some() }
pub fn minimum_edition(&self) -> roto_runtime::Result<i32> {
let offset = self.minimum_edition_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn minimum_edition_or_default(&self) -> roto_runtime::Result<i32> {
self.minimum_edition().or(Ok(0))
}
pub fn has_minimum_edition(&self) -> bool { self.minimum_edition_offset.is_some() }
pub fn maximum_edition(&self) -> roto_runtime::Result<i32> {
let offset = self.maximum_edition_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn maximum_edition_or_default(&self) -> roto_runtime::Result<i32> {
self.maximum_edition().or(Ok(0))
}
pub fn has_maximum_edition(&self) -> bool { self.maximum_edition_offset.is_some() }
pub fn file(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.file_start, self.file_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(15, start, end),
_ => self.accessor.iter_repeated(15),
}
}
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct CodeGeneratorResponseBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
error_written: bool,
supported_features_written: bool,
minimum_edition_written: bool,
maximum_edition_written: bool,
file_written: bool,
}
impl<'b> CodeGeneratorResponseBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> CodeGeneratorResponseBuilder<'_> {
CodeGeneratorResponseBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
error_written: false,
supported_features_written: false,
minimum_edition_written: false,
maximum_edition_written: false,
file_written: false,
}
}
pub fn error(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.error_written = true;
Ok(self)
}
pub fn supported_features(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(2, value)?;
self.supported_features_written = true;
Ok(self)
}
pub fn minimum_edition(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(3, value)?;
self.minimum_edition_written = true;
Ok(self)
}
pub fn maximum_edition(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(4, value)?;
self.maximum_edition_written = true;
Ok(self)
}
pub fn file(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(15, value)?;
self.file_written = true;
Ok(self)
}
pub fn with(mut self, msg: &CodeGeneratorResponse<'_>) -> roto_runtime::Result<Self> {
for item in msg.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.error_written,
2 => self.supported_features_written,
3 => self.minimum_edition_written,
4 => self.maximum_edition_written,
15 => self.file_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedCodeGeneratorResponse {
pub data: bytes::Bytes,
}
impl roto_runtime::RotoOwned for OwnedCodeGeneratorResponse {
type Reader<'a> = CodeGeneratorResponse<'a>;
fn reader(&self) -> CodeGeneratorResponse<'_> {
CodeGeneratorResponse::new(&self.data).expect("failed to create reader")
}
}
impl roto_runtime::RotoMessage for OwnedCodeGeneratorResponse {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedCodeGeneratorResponse { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub mod code_generator_response {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum Feature {
FEATURENONE = 0,
FEATUREPROTO3OPTIONAL = 1,
FEATURESUPPORTSEDITIONS = 2,
}
impl Feature {
pub fn from_i32(value: i32) -> Self {
match value {
0 => Feature::FEATURENONE,
1 => Feature::FEATUREPROTO3OPTIONAL,
2 => Feature::FEATURESUPPORTSEDITIONS,
_ => Feature::FEATURENONE,
}
}
}
pub struct File<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
insertion_point_offset: Option<usize>,
content_offset: Option<usize>,
generated_code_info_offset: Option<usize>,
}
impl<'a> File<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut name_offset = None;
let mut insertion_point_offset = None;
let mut content_offset = None;
let mut generated_code_info_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { name_offset = Some(offset); }
if tag.field_number == 2 { insertion_point_offset = Some(offset); }
if tag.field_number == 15 { content_offset = Some(offset); }
if tag.field_number == 16 { generated_code_info_offset = Some(offset); }
}
Ok(Self {
accessor,
name_offset,
insertion_point_offset,
content_offset,
generated_code_info_offset,
})
}
pub fn name(&self) -> roto_runtime::Result<&'a str> {
let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
pub fn insertion_point(&self) -> roto_runtime::Result<&'a str> {
let offset = self.insertion_point_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn insertion_point_or_default(&self) -> roto_runtime::Result<&'a str> {
self.insertion_point().or(Ok(""))
}
pub fn has_insertion_point(&self) -> bool { self.insertion_point_offset.is_some() }
pub fn content(&self) -> roto_runtime::Result<&'a str> {
let offset = self.content_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn content_or_default(&self) -> roto_runtime::Result<&'a str> {
self.content().or(Ok(""))
}
pub fn has_content(&self) -> bool { self.content_offset.is_some() }
pub fn generated_code_info(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.generated_code_info_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn generated_code_info_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.generated_code_info().or(Ok(&[]))
}
pub fn has_generated_code_info(&self) -> bool { self.generated_code_info_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct FileBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
name_written: bool,
insertion_point_written: bool,
content_written: bool,
generated_code_info_written: bool,
}
impl<'b> FileBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> FileBuilder<'_> {
FileBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
name_written: false,
insertion_point_written: false,
content_written: false,
generated_code_info_written: false,
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn insertion_point(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(2, value)?;
self.insertion_point_written = true;
Ok(self)
}
pub fn content(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(15, value)?;
self.content_written = true;
Ok(self)
}
pub fn generated_code_info(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(16, value)?;
self.generated_code_info_written = true;
Ok(self)
}
pub fn with(mut self, msg: &File<'_>) -> roto_runtime::Result<Self> {
for item in msg.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.name_written,
2 => self.insertion_point_written,
15 => self.content_written,
16 => self.generated_code_info_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedFile {
pub data: bytes::Bytes,
}
impl roto_runtime::RotoOwned for OwnedFile {
type Reader<'a> = File<'a>;
fn reader(&self) -> File<'_> {
File::new(&self.data).expect("failed to create reader")
}
}
impl roto_runtime::RotoMessage for OwnedFile {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedFile { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
}
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
pub mod compiler;
pub mod descriptor;
+2
View File
@@ -0,0 +1,2 @@
pub mod generator;
pub mod google;
+96
View File
@@ -0,0 +1,96 @@
use roto_codegen::google::protobuf::compiler::plugin::CodeGeneratorRequest;
use roto_codegen::google::protobuf::descriptor::FileDescriptorSet;
use std::fs;
use std::process::Command;
#[test]
fn test_generated_code_builds() {
// 1. Generate Rust code from data/request.bin
let request_path = "data/request.bin";
let data = fs::read(request_path).expect("Failed to read request.bin");
let request = CodeGeneratorRequest::new(&data).expect("Failed to parse CodeGeneratorRequest");
// Mimic the logic from protoc-gen-roto to build a FileDescriptorSet
let mut set_buf = Vec::new();
for file_res in request.proto_file() {
let (file_data, _) = file_res.expect("Failed to iterate proto_file");
// Tag 1, Length-delimited: (1 << 3) | 2 = 10
set_buf.push(10);
// Write length as varint
let len = file_data.len() as u64;
let mut len_buf = [0u8; 10];
let len_size =
roto_runtime::write_varint(len, &mut len_buf).expect("Failed to write varint length");
set_buf.extend_from_slice(&len_buf[..len_size]);
// Write data
set_buf.extend_from_slice(file_data);
}
let set = FileDescriptorSet::new(&set_buf).expect("Failed to create FileDescriptorSet");
let generated_files = roto_codegen::generator::generate_rust_code(&set, None, false);
assert!(
!generated_files.is_empty(),
"Generated code should not be empty"
);
// 2. Setup a temporary Cargo project to verify the code builds
let codegen_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let project_root = codegen_root.parent().expect("Failed to get project root");
let temp_project_dir = std::path::PathBuf::from("/tmp/roto_test_gen_project");
// Clean up previous runs
if temp_project_dir.exists() {
fs::remove_dir_all(&temp_project_dir).expect("Failed to clean up temp project directory");
}
// Create new library project
let status = Command::new("cargo")
.args(["new", "--lib", temp_project_dir.to_str().expect("Invalid path")])
.status()
.expect("Failed to run cargo new");
assert!(status.success(), "cargo new failed");
// 3. Configure the project to depend on the current roto crate
let cargo_toml_path = temp_project_dir.join("Cargo.toml");
let cargo_toml_content =
fs::read_to_string(&cargo_toml_path).expect("Failed to read Cargo.toml");
let updated_cargo_toml = format!(
"{}\n\nroto-codegen = {{ path = \"{}\" }}\nroto-runtime = {{ path = \"{}\" }}\nroto-tonic = {{ path = \"{}\" }}\nbytes = \"1.7\"\ntonic = \"0.12\"\ntokio-stream = \"0.1\"\ntower = \"0.4\"\nfutures-util = \"0.3\"\nhttp-body-util = \"0.1\"\nhttp-body = \"1.0\"\n\n[workspace]\n",
cargo_toml_content,
codegen_root.to_string_lossy(),
project_root.join("runtime").to_string_lossy(),
project_root.join("roto-tonic").to_string_lossy()
);
fs::write(cargo_toml_path, updated_cargo_toml).expect("Failed to write Cargo.toml");
// 4. Write the generated code to src/lib.rs
// The generated code uses `use crate::{...}`, but it's now in a separate crate.
// Replace `crate` with `roto_tonic` to reference the types in the dependency.
let mut all_code = String::new();
for (_, content) in generated_files {
let replaced = content.replace("use crate::{BufferPool, StatusBody};", "use roto_tonic::{BufferPool, StatusBody};");
all_code.push_str(&replaced);
all_code.push_str("\n");
}
let lib_path = temp_project_dir.join("src/lib.rs");
fs::write(lib_path, all_code).expect("Failed to write generated code to src/lib.rs");
// 5. Attempt to build the project
let build_output = Command::new("cargo")
.args(["build"])
.current_dir(&temp_project_dir)
.output()
.expect("Failed to run cargo build");
if !build_output.status.success() {
eprintln!("Cargo build failed output:\n{}", String::from_utf8_lossy(&build_output.stderr));
}
assert!(
build_output.status.success(),
"The generated Rust code failed to build in a standalone project!"
);
}
Binary file not shown.
+76
View File
@@ -0,0 +1,76 @@
use roto_codegen::google::protobuf::descriptor::FileDescriptorSet;
use std::fs;
use std::process::Command;
#[test]
fn test_helloworld_generated_code_builds() {
// 1. Load FileDescriptorSet from helloworld.desc
let desc_path = "helloworld.desc";
// Note: This assumes helloworld.desc is in the working directory of the test.
// We might need to provide the full path or copy it to the test data directory.
let data = fs::read(desc_path).expect("Failed to read helloworld.desc");
let set = FileDescriptorSet::new(&data)
.expect("Failed to create FileDescriptorSet from helloworld.desc");
let generated_files = roto_codegen::generator::generate_rust_code(&set, None, false);
assert!(
!generated_files.is_empty(),
"Generated code should not be empty"
);
for (path, content) in &generated_files {
println!("--- File: {} ---\n{}", path, content);
}
// 2. Setup a temporary Cargo project to verify the code builds
let codegen_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let project_root = codegen_root.parent().expect("Failed to get project root");
let temp_project_dir = std::path::PathBuf::from("/tmp/roto_helloworld_gen_project");
// Clean up previous runs
if temp_project_dir.exists() {
fs::remove_dir_all(&temp_project_dir).expect("Failed to clean up temp project directory");
}
// Create new library project
let status = Command::new("cargo")
.args(["new", "--lib", temp_project_dir.to_str().expect("Invalid path")])
.status()
.expect("Failed to run cargo new");
assert!(status.success(), "cargo new failed");
// 3. Configure the project to depend on the current roto crate
let cargo_toml_path = temp_project_dir.join("Cargo.toml");
let cargo_toml_content =
fs::read_to_string(&cargo_toml_path).expect("Failed to read Cargo.toml");
let updated_cargo_toml = format!(
"{}\n\nroto-codegen = {{ path = \"{}\" }}\nroto-runtime = {{ path = \"{}\" }}\nroto-tonic = {{ path = \"{}\" }}\nbytes = \"1.7\"\ntonic = \"0.12\"\ntokio-stream = \"0.1\"\ntower = \"0.4\"\nfutures-util = \"0.3\"\nhttp-body-util = \"0.1\"\nhttp-body = \"1.0\"\n\nhttp = \"1.0\"\n\n[workspace]\n",
cargo_toml_content,
codegen_root.to_string_lossy(),
project_root.join("runtime").to_string_lossy(),
project_root.join("roto-tonic").to_string_lossy()
);
fs::write(cargo_toml_path, updated_cargo_toml).expect("Failed to write Cargo.toml");
// 4. Write the generated code to src/lib.rs
let mut all_code = String::new();
for (_, content) in generated_files {
let replaced = content.replace("use crate::{BufferPool, StatusBody};", "use roto_tonic::{BufferPool, StatusBody};");
all_code.push_str(&replaced);
all_code.push_str("\n");
}
let lib_path = temp_project_dir.join("src/lib.rs");
fs::write(lib_path, all_code).expect("Failed to write generated code to src/lib.rs");
// 5. Attempt to build the project
let build_status = Command::new("cargo")
.args(["build"])
.current_dir(&temp_project_dir)
.status()
.expect("Failed to run cargo build");
assert!(
build_status.success(),
"The generated Rust code for helloworld.proto failed to build in a standalone project!"
);
}
+74
View File
@@ -0,0 +1,74 @@
use roto_codegen::google::protobuf::descriptor::FileDescriptorSet;
use std::fs;
use std::process::Command;
#[test]
fn test_map_generated_code_builds() {
// 1. Load FileDescriptorSet from data/test_map.desc
let desc_path = "data/test_map.desc";
let data = fs::read(desc_path).expect("Failed to read test_map.desc");
let set = FileDescriptorSet::new(&data)
.expect("Failed to create FileDescriptorSet from test_map.desc");
let generated_files = roto_codegen::generator::generate_rust_code(&set, None, false);
assert!(
!generated_files.is_empty(),
"Generated code should not be empty"
);
// 2. Setup a temporary Cargo project to verify the code builds
let codegen_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let project_root = codegen_root.parent().expect("Failed to get project root");
let temp_project_dir = std::path::PathBuf::from("/tmp/roto_test_map_gen_project");
// Clean up previous runs
if temp_project_dir.exists() {
fs::remove_dir_all(&temp_project_dir).expect("Failed to clean up temp project directory");
}
// Create new library project
let status = Command::new("cargo")
.args(["new", "--lib", temp_project_dir.to_str().expect("Invalid path")])
.status()
.expect("Failed to run cargo new");
assert!(status.success(), "cargo new failed");
// 3. Configure the project to depend on the current roto crate
let cargo_toml_path = temp_project_dir.join("Cargo.toml");
let cargo_toml_content =
fs::read_to_string(&cargo_toml_path).expect("Failed to read Cargo.toml");
let updated_cargo_toml = format!(
"{}\n\nroto-codegen = {{ path = \"{}\" }}\nroto-runtime = {{ path = \"{}\" }}\nroto-tonic = {{ path = \"{}\" }}\nbytes = \"1.7\"\ntonic = \"0.12\"\ntokio-stream = \"0.1\"\ntower = \"0.4\"\nfutures-util = \"0.3\"\nhttp-body-util = \"0.1\"\nhttp-body = \"1.0\"\n\n[workspace]\n",
cargo_toml_content,
codegen_root.to_string_lossy(),
project_root.join("runtime").to_string_lossy(),
project_root.join("roto-tonic").to_string_lossy()
);
fs::write(cargo_toml_path, updated_cargo_toml).expect("Failed to write Cargo.toml");
// 4. Write the generated code to src/lib.rs
let mut all_code = String::new();
for (_, content) in generated_files {
let replaced = content.replace("use crate::{BufferPool, StatusBody};", "use roto_tonic::{BufferPool, StatusBody};");
all_code.push_str(&replaced);
all_code.push_str("\n");
}
let lib_path = temp_project_dir.join("src/lib.rs");
fs::write(lib_path, all_code).expect("Failed to write generated code to src/lib.rs");
// 5. Attempt to build the project
let output = Command::new("cargo")
.args(["build"])
.current_dir(&temp_project_dir)
.output()
.expect("Failed to run cargo build");
if !output.status.success() {
eprintln!("Cargo build failed:\n{}", String::from_utf8_lossy(&output.stderr));
}
assert!(
output.status.success(),
"The generated Rust code for test_map.proto failed to build in a standalone project!"
);
}
+55
View File
@@ -0,0 +1,55 @@
use roto_codegen::generator::generate_rust_code;
use roto_codegen::google::protobuf::descriptor::FileDescriptorSet;
use std::fs;
#[test]
fn test_nested_proto_generation_contains_modules() {
let request_path = "data/request.bin";
if !std::path::Path::new(request_path).exists() {
panic!("data/request.bin not found. This test requires the sample request binary.");
}
let data = fs::read(request_path).expect("Failed to read request.bin");
// The existing test logic to build a FileDescriptorSet from CodeGeneratorRequest
// We can simplify this by just wrapping the data if it's already a FileDescriptorSet,
// but request.bin is usually a CodeGeneratorRequest.
// Let's use the same logic as build_generated_code.rs to get a FileDescriptorSet
let request =
roto_codegen::google::protobuf::compiler::plugin::CodeGeneratorRequest::new(&data)
.expect("Failed to parse CodeGeneratorRequest");
let mut set_buf = Vec::new();
for file_res in request.proto_file() {
let (file_data, _) = file_res.expect("Failed to iterate proto_file");
set_buf.push(10);
let len = file_data.len() as u64;
let mut len_buf = [0u8; 10];
let len_size =
roto_runtime::write_varint(len, &mut len_buf).expect("Failed to write varint length");
set_buf.extend_from_slice(&len_buf[..len_size]);
set_buf.extend_from_slice(file_data);
}
let set = FileDescriptorSet::new(&set_buf).expect("Failed to create FileDescriptorSet");
let generated_files = generate_rust_code(&set, None, false);
let all_code: String = generated_files
.into_iter()
.map(|(_, content)| content)
.collect();
println!("Generated Code:\n{}", all_code);
// We want to see if any message has a nested module.
// Since we don't know exactly what's in request.bin, we'll look for ANY 'pub mod' inside the generated code
// that isn't at the top level (though the generator puts them inside the message definition).
assert!(
all_code.contains("pub mod "),
"Generated code should contain at least one nested module for nested types"
);
assert!(
all_code.contains("pub struct "),
"Generated code should contain structs"
);
}
+22
View File
@@ -0,0 +1,22 @@
use roto_codegen::generator::generate_rust_code;
use roto_codegen::google::protobuf::descriptor::{
DescriptorProto, FieldDescriptorProto, FileDescriptorSet,
};
use std::collections::HashMap;
#[test]
fn test_oneof_generation() {
let mut set = FileDescriptorSet::new(b"").unwrap(); // Simplified for testing
// In a real scenario, we'd build up a FileDescriptorSet from a proto.
// For this unit test, we'll manually construct a DescriptorProto that has a oneof.
// However, generate_rust_code takes a FileDescriptorSet.
// Let's mock a simple setup.
// Since manually constructing FileDescriptorSet is complex, let's instead check if the
// generator logic for oneofs produces the expected strings given a DescriptorProto.
// But the current tests use load_generated_code() which reads from data/request.bin.
// Let's see if we can find a way to test just the write_message function or similar.
}
+70
View File
@@ -0,0 +1,70 @@
use roto_codegen::google::protobuf::descriptor::FileDescriptorSet;
use std::fs;
use std::process::Command;
#[test]
fn test_types_generated_code_builds() {
// 1. Load FileDescriptorSet from data/test_types.desc
let desc_path = "data/test_types.desc";
let data = fs::read(desc_path).expect("Failed to read test_types.desc");
let set = FileDescriptorSet::new(&data)
.expect("Failed to create FileDescriptorSet from test_types.desc");
let generated_files = roto_codegen::generator::generate_rust_code(&set, None, false);
assert!(
!generated_files.is_empty(),
"Generated code should not be empty"
);
// 2. Setup a temporary Cargo project to verify the code builds
let codegen_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let project_root = codegen_root.parent().expect("Failed to get project root");
let temp_project_dir = std::path::PathBuf::from("/tmp/roto_test_types_gen_project");
// Clean up previous runs
if temp_project_dir.exists() {
fs::remove_dir_all(&temp_project_dir).expect("Failed to clean up temp project directory");
}
// Create new library project
let status = Command::new("cargo")
.args(["new", "--lib", temp_project_dir.to_str().expect("Invalid path")])
.status()
.expect("Failed to run cargo new");
assert!(status.success(), "cargo new failed");
// 3. Configure the project to depend on the current roto crate
let cargo_toml_path = temp_project_dir.join("Cargo.toml");
let cargo_toml_content =
fs::read_to_string(&cargo_toml_path).expect("Failed to read Cargo.toml");
let updated_cargo_toml = format!(
"{}\n\nroto-codegen = {{ path = \"{}\" }}\nroto-runtime = {{ path = \"{}\" }}\nroto-tonic = {{ path = \"{}\" }}\nbytes = \"1.7\"\ntonic = \"0.12\"\ntokio-stream = \"0.1\"\ntower = \"0.4\"\nfutures-util = \"0.3\"\nhttp-body-util = \"0.1\"\nhttp-body = \"1.0\"\n\n[workspace]\n",
cargo_toml_content,
codegen_root.to_string_lossy(),
project_root.join("runtime").to_string_lossy(),
project_root.join("roto-tonic").to_string_lossy()
);
fs::write(cargo_toml_path, updated_cargo_toml).expect("Failed to write Cargo.toml");
// 4. Write the generated code to src/lib.rs
let mut all_code = String::new();
for (_, content) in generated_files {
let replaced = content.replace("use crate::{BufferPool, StatusBody};", "use roto_tonic::{BufferPool, StatusBody};");
all_code.push_str(&replaced);
all_code.push_str("\n");
}
let lib_path = temp_project_dir.join("src/lib.rs");
fs::write(lib_path, all_code).expect("Failed to write generated code to src/lib.rs");
// 5. Attempt to build the project
let build_status = Command::new("cargo")
.args(["build"])
.current_dir(&temp_project_dir)
.status()
.expect("Failed to run cargo build");
assert!(
build_status.success(),
"The generated Rust code for test_types.proto failed to build in a standalone project!"
);
}
+80
View File
@@ -0,0 +1,80 @@
use roto_codegen::generator::generate_rust_code;
use roto_codegen::google::protobuf::compiler::plugin::CodeGeneratorRequest;
use roto_codegen::google::protobuf::descriptor::FileDescriptorSet;
use std::fs;
fn load_generated_code() -> String {
let data = fs::read("data/request.bin").expect("Failed to read data/request.bin");
let request = CodeGeneratorRequest::new(&data).expect("Failed to parse CodeGeneratorRequest");
let mut set_buf = Vec::new();
for file_res in request.proto_file() {
let (file_data, _) = file_res.expect("Failed to iterate proto_file");
set_buf.push(10u8);
let len = file_data.len() as u64;
let mut len_buf = [0u8; 10];
let len_size = roto_runtime::write_varint(len, &mut len_buf).unwrap();
set_buf.extend_from_slice(&len_buf[..len_size]);
set_buf.extend_from_slice(file_data);
}
let set = FileDescriptorSet::new(&set_buf).expect("Failed to create FileDescriptorSet");
generate_rust_code(&set, None, false)
.into_iter()
.map(|(_, content)| content)
.collect()
}
#[test]
fn test_builder_structs_have_written_flags() {
let code = load_generated_code();
assert!(
code.contains("_written: bool"),
"Builder structs should contain `_written: bool` fields for each proto field"
);
}
#[test]
fn test_builder_constructor_initialises_written_flags_to_false() {
let code = load_generated_code();
assert!(
code.contains("_written: false"),
"Builder constructors should initialise every `_written` flag to false"
);
}
#[test]
fn test_builder_setters_mark_field_as_written() {
let code = load_generated_code();
assert!(
code.contains("_written = true"),
"Each builder setter should set its `_written` flag to true"
);
}
#[test]
fn test_builder_has_with_method() {
let code = load_generated_code();
assert!(
code.contains("pub fn with("),
"Each builder impl should expose a `with` method"
);
}
#[test]
fn test_message_structs_have_raw_fields_method() {
let code = load_generated_code();
assert!(
code.contains("pub fn raw_fields("),
"Each message struct impl should expose a `raw_fields` method"
);
}
#[test]
fn test_with_method_uses_write_raw() {
let code = load_generated_code();
assert!(
code.contains("write_raw(raw_bytes)"),
"The `with` method should call `write_raw` to copy field bytes"
);
}
+35
View File
@@ -0,0 +1,35 @@
[package]
name = "hello-world"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "server"
path = "src/bin/server.rs"
[[bin]]
name = "client"
path = "src/bin/client.rs"
[dependencies]
roto-runtime = { path = "../../runtime" }
roto-tonic = { path = "../../roto-tonic" }
tonic = "0.12"
tokio = { version = "1.38", features = ["full"] }
tokio-stream = "0.1"
bytes = "1.7"
prost = "0.13"
tower = "0.4"
futures-util = "0.3"
http-body-util = "0.1"
http = "1.1"
http-body = "1.0"
async-trait = "0.1"
[build-dependencies]
tonic-build = "0.12"
roto-codegen = { path = "../../codegen" }
[features]
default = ["alloc"]
alloc = []
+20
View File
@@ -0,0 +1,20 @@
# Hello World Example
This example demonstrates a simple gRPC service using `roto`.
## Running the server
```bash
cargo run --bin server
```
## Calling the service
You can use `grpc_cli` to call the `HelloWorld` RPC:
```bash
grpc_cli call [::1]:50051 hello.HelloWorldService.HelloWorld 'name: "World"' \
--protofiles examples/hello_world/proto/hello.proto \
--proto_path examples/hello_world/proto \
--channel_creds_type insecure
```
+27
View File
@@ -0,0 +1,27 @@
fn main() {
let proto_file = "proto/hello.proto";
let out_dir = std::env::var("OUT_DIR").unwrap();
let dest_path = std::path::Path::new(&out_dir).join("hello.rs");
// Find the protoc-gen-roto binary
// Since we added roto-codegen to build-dependencies, it will be built.
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let target_dir = std::path::Path::new(&manifest_dir).join("../../target/debug");
let plugin_path = target_dir.join("protoc-gen-roto");
if !plugin_path.exists() {
panic!("protoc-gen-roto plugin not found at {:?}", plugin_path);
}
let status = std::process::Command::new("protoc")
.arg(format!("--plugin=protoc-gen-roto={}", plugin_path.display()))
.arg(format!("--roto_out={}", out_dir))
.arg(format!("--roto_opt=src=proto")) // Assuming the plugin handles this or we just pass it
.arg(proto_file)
.status()
.expect("Failed to execute protoc");
if !status.success() {
panic!("protoc failed with status {}", status);
}
}
+15
View File
@@ -0,0 +1,15 @@
syntax = "proto3";
package hello;
service HelloWorldService {
rpc HelloWorld (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
+334
View File
@@ -0,0 +1,334 @@
// @generated by protoc-gen-roto — do not edit
#[allow(unused_imports)]
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};
use core::str;
#[cfg(feature = "alloc")]
use bytes::{Bytes, BytesMut, Buf, BufMut};
pub struct HelloRequest<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
}
impl<'a> HelloRequest<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut name_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { name_offset = Some(offset); }
}
Ok(Self {
accessor,
name_offset,
})
}
pub fn name(&self) -> roto_runtime::Result<&'a str> {
let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloRequestBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
name_written: bool,
}
impl<'b> HelloRequestBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloRequestBuilder<'_> {
HelloRequestBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
name_written: false,
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn with(mut self, msg: &HelloRequest<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.name_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedHelloRequest {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedHelloRequest {
type Reader<'a> = HelloRequest<'a>;
fn reader(&self) -> HelloRequest<'_> {
HelloRequest::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedHelloRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHelloRequest { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct HelloResponse<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
message_offset: Option<usize>,
}
impl<'a> HelloResponse<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut message_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { message_offset = Some(offset); }
}
Ok(Self {
accessor,
message_offset,
})
}
pub fn message(&self) -> roto_runtime::Result<&'a str> {
let offset = self.message_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn message_or_default(&self) -> roto_runtime::Result<&'a str> {
self.message().or(Ok(""))
}
pub fn has_message(&self) -> bool { self.message_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloResponseBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
message_written: bool,
}
impl<'b> HelloResponseBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloResponseBuilder<'_> {
HelloResponseBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
message_written: false,
}
}
pub fn message(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.message_written = true;
Ok(self)
}
pub fn with(mut self, msg: &HelloResponse<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.message_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedHelloResponse {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedHelloResponse {
type Reader<'a> = HelloResponse<'a>;
fn reader(&self) -> HelloResponse<'_> {
HelloResponse::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedHelloResponse {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHelloResponse { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
#[cfg(feature = "alloc")]
use tonic::{Request, Response, Status};
#[cfg(feature = "alloc")]
use tokio_stream::Stream;
#[cfg(feature = "alloc")]
use std::pin::Pin;
#[cfg(feature = "alloc")]
use std::sync::Arc;
#[cfg(feature = "alloc")]
use std::task::{Context, Poll};
#[cfg(feature = "alloc")]
use std::future::Future;
#[cfg(feature = "alloc")]
use tonic::body::BoxBody;
#[cfg(feature = "alloc")]
use tower::Service;
#[cfg(feature = "alloc")]
use futures_util::StreamExt;
#[cfg(feature = "alloc")]
use http_body_util::BodyExt;
#[cfg(feature = "alloc")]
use http_body::Body;
#[cfg(feature = "alloc")]
use crate::{BufferPool, StatusBody};
#[cfg(feature = "alloc")]
#[async_trait::async_trait]
pub trait HelloWorldService: Send + Sync + 'static {
async fn hello_world(&self, request: Request<OwnedHelloRequest>) -> std::result::Result<Response<OwnedHelloResponse>, Status>;
}
#[cfg(feature = "alloc")]
#[derive(Clone)]
pub struct HelloWorldServiceServer {
inner: Arc<dyn HelloWorldService>,
pool: Arc<BufferPool>,
}
#[cfg(feature = "alloc")]
impl HelloWorldServiceServer {
pub fn new(inner: Arc<dyn HelloWorldService>, pool: Arc<BufferPool>) -> Self {
Self { inner, pool }
}
}
#[cfg(feature = "alloc")]
impl tonic::server::NamedService for HelloWorldServiceServer {
const NAME: &'static str = "hello.HelloWorldService";
}
#[cfg(feature = "alloc")]
impl Service<http::Request<BoxBody>> for HelloWorldServiceServer {
type Response = http::Response<BoxBody>;
type Error = std::convert::Infallible;
type Future = Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<BoxBody>) -> Self::Future {
let inner = self.inner.clone();
let pool = self.pool.clone();
Box::pin(async move {
let path = req.uri().path().to_string();
let body = req.into_body();
let mut buf = pool.get();
let mut stream = body;
while let Some(frame_result) = stream.frame().await {
let frame = frame_result.expect("Body frame error");
if let Some(data) = frame.data_ref() {
buf.put(data.clone());
}
}
let total_len = buf.len();
let bytes_vec = buf.split_to(total_len).freeze();
pool.put(buf);
if bytes_vec.len() < 5 {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
let payload = bytes_vec.slice(5..);
let mut routed = false;
if path == "/hello.HelloWorldService/HelloWorld" {
let request_msg = match OwnedHelloRequest::decode(payload) {
Ok(msg) => msg,
Err(e) => {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
};
let response = match inner.hello_world(Request::new(request_msg)).await {
Ok(res) => res,
Err(e) => {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
};
let response_msg = response.into_inner();
let response_bytes = response_msg.bytes();
let mut res_buf = pool.get();
res_buf.put_u8(0);
let len = response_bytes.len() as u32;
res_buf.put_slice(&len.to_be_bytes());
res_buf.put_slice(&response_bytes);
let frame_len = res_buf.len();
let frame = res_buf.split_to(frame_len).freeze();
pool.put(res_buf);
let res_body = BoxBody::new(StatusBody::new(Some(frame), 0));
routed = true;
return Ok(http::Response::builder().status(200).header("content-type", "application/grpc").body(res_body).unwrap());
}
if !routed {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
Ok(http::Response::builder().status(200).body(BoxBody::new(StatusBody::new(None, 0))).unwrap())
})
}
}
+69
View File
@@ -0,0 +1,69 @@
use tonic::Request;
use roto_tonic::RotoCodec;
use hello::{HelloWorldService, OwnedHelloRequest, OwnedHelloResponse};
use roto_runtime::RotoOwned;
use std::task::{Context, Poll};
use tower::Service;
pub use roto_tonic::{BufferPool, StatusBody};
pub mod hello {
include!(concat!(env!("OUT_DIR"), "/hello.rs"));
}
struct ReadyService<S>(S);
impl<S, Req> Service<Req> for ReadyService<S>
where
S: Service<Req>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx)
}
fn call(&mut self, req: Req) -> S::Future {
let waker = futures_util::task::noop_waker();
let mut cx = std::task::Context::from_waker(&waker);
let _ = self.poll_ready(&mut cx);
self.0.call(req)
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let channel = tonic::transport::Channel::from_static("http://[::1]:50051")
.connect()
.await?;
let ready_channel = ReadyService(channel);
let mut client = tonic::client::Grpc::new(ready_channel);
// We need to specify the method path. For HelloWorldService/HelloWorld, it is "/hello.HelloWorldService/HelloWorld"
let mut buf = vec![0u8; 1024];
let slice = hello::HelloRequestBuilder::builder(&mut buf)
.name("Roto").unwrap()
.finish().unwrap();
let request = OwnedHelloRequest {
data: bytes::Bytes::copy_from_slice(slice),
};
// In tonic's Grpc client, we specify the codec separately.
let response = client
.unary(
Request::new(request),
http::uri::PathAndQuery::from_static("/hello.HelloWorldService/HelloWorld"),
RotoCodec::<OwnedHelloResponse, OwnedHelloRequest>::default(),
)
.await?;
let response_msg: OwnedHelloResponse = response.into_inner();
let reader = response_msg.reader();
println!("Server responded: {}", reader.message().unwrap_or("No message"));
Ok(())
}
+179
View File
@@ -0,0 +1,179 @@
use std::pin::Pin;
use std::future::Future;
use std::task::{Context, Poll};
use std::sync::{Arc, Mutex};
use tonic::{transport::Server, Request, Response, Status};
use roto_tonic::RotoCodec;
use hello::{HelloWorldService, OwnedHelloRequest, OwnedHelloResponse};
use tower::Service;
use bytes::{Bytes, BytesMut, Buf, BufMut};
use tonic::body::BoxBody;
use futures_util::StreamExt;
use roto_runtime::{RotoOwned, RotoMessage};
use http_body_util::BodyExt;
use http_body::Body;
pub use roto_tonic::{BufferPool, StatusBody};
pub mod hello {
include!(concat!(env!("OUT_DIR"), "/hello.rs"));
}
#[derive(Clone)]
pub struct MyHelloWorld {
pool: Arc<BufferPool>,
}
impl MyHelloWorld {
pub fn new(pool: Arc<BufferPool>) -> Self {
Self { pool }
}
}
#[tonic::async_trait]
impl HelloWorldService for MyHelloWorld {
async fn hello_world(
&self,
request: Request<OwnedHelloRequest>,
) -> Result<Response<OwnedHelloResponse>, Status> {
let req = request.into_inner();
let reader = req.reader();
let name = reader.name().unwrap_or("Unknown");
let mut buf = self.pool.get();
buf.resize(1024, 0);
let slice = hello::HelloResponseBuilder::builder(&mut buf[..])
.message(&format!("Hello {}!", name)).unwrap()
.finish().unwrap();
let res_len = slice.len();
let response_bytes = buf.split_to(res_len).freeze();
self.pool.put(buf);
let reply = OwnedHelloResponse {
data: response_bytes,
};
Ok(Response::new(reply))
}
}
// --- Tonic Glue ---
#[derive(Clone)]
pub struct HelloWorldServer {
inner: Arc<MyHelloWorld>,
pool: Arc<BufferPool>,
}
impl HelloWorldServer {
pub fn new(inner: MyHelloWorld, pool: Arc<BufferPool>) -> Self {
Self { inner: Arc::new(inner), pool }
}
}
impl tonic::server::NamedService for HelloWorldServer {
const NAME: &'static str = "hello.HelloWorldService";
}
impl Service<http::Request<BoxBody>> for HelloWorldServer {
type Response = http::Response<BoxBody>;
type Error = std::convert::Infallible;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<BoxBody>) -> Self::Future {
let inner = self.inner.clone();
let pool = self.pool.clone();
println!("Server received request: {} {}", req.method(), req.uri());
Box::pin(async move {
let body = req.into_body();
let mut buf = pool.get();
let mut stream = body;
while let Some(frame_result) = stream.frame().await {
let frame = frame_result.map_err(|e| {
println!("Body frame error: {}", e);
panic!("Body frame error: {}", e);
})?;
if let Some(data) = frame.data_ref() {
buf.put(data.clone());
}
}
let total_len = buf.len();
let bytes_vec = buf.split_to(total_len).freeze();
pool.put(buf);
println!("Collected body bytes: {} bytes", bytes_vec.len());
if bytes_vec.len() < 5 {
println!("Body too short: {} bytes", bytes_vec.len());
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder()
.status(200)
.body(res_body)
.unwrap());
}
println!("Decoding request from {} bytes", bytes_vec.len() - 5);
let request_msg = match OwnedHelloRequest::decode(bytes_vec.slice(5..)) {
Ok(msg) => msg,
Err(e) => {
println!("Decode error: {}", e);
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
};
println!("Request decoded successfully");
let response = match inner.hello_world(Request::new(request_msg)).await {
Ok(res) => res,
Err(e) => {
println!("Service error: {}", e);
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
};
let response_msg = response.into_inner();
let response_bytes = response_msg.bytes();
println!("Service responded with {} bytes", response_bytes.len());
let mut res_buf = pool.get();
res_buf.put_u8(0);
let len = response_bytes.len() as u32;
res_buf.put_slice(&len.to_be_bytes());
res_buf.put_slice(&response_bytes);
let frame_len = res_buf.len();
let frame = res_buf.split_to(frame_len).freeze();
pool.put(res_buf);
let res_body = BoxBody::new(StatusBody::new(Some(frame), 0));
Ok(http::Response::builder()
.status(200)
.header("content-type", "application/grpc")
.body(res_body)
.unwrap())
})
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr: std::net::SocketAddr = "[::1]:50051".parse()?;
let pool = Arc::new(BufferPool::new(1024));
let hello = MyHelloWorld::new(pool.clone());
println!("Server listening on {}", addr);
Server::builder()
.add_service(HelloWorldServer::new(hello, pool))
.serve(addr)
.await?;
Ok(())
}
+2
View File
@@ -0,0 +1,2 @@
[build]
target = "thumbv7em-none-eabihf"
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "no_std_test"
version = "0.1.0"
edition = "2021"
[dependencies]
roto-runtime = { path = "../../runtime", default-features = false }
prost = "0.13"
bytes = "1.8"
+805
View File
@@ -0,0 +1,805 @@
// @generated by protoc-gen-roto — do not edit
#[allow(unused_imports)]
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};
use core::str;
#[cfg(feature = "alloc")]
use bytes::{Bytes, BytesMut, Buf, BufMut};
pub struct Hello<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
d_offset: Option<usize>,
f_offset: Option<usize>,
b_offset: Option<usize>,
n_offset: Option<usize>,
l_offset: Option<usize>,
c1_offset: Option<usize>,
c2_offset: Option<usize>,
pets_start: Option<usize>,
pets_end: Option<usize>,
}
impl<'a> Hello<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut name_offset = None;
let mut d_offset = None;
let mut f_offset = None;
let mut b_offset = None;
let mut n_offset = None;
let mut l_offset = None;
let mut c1_offset = None;
let mut c2_offset = None;
let mut pets_start = None;
let mut pets_end = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { name_offset = Some(offset); }
if tag.field_number == 2 { d_offset = Some(offset); }
if tag.field_number == 3 { f_offset = Some(offset); }
if tag.field_number == 4 { b_offset = Some(offset); }
if tag.field_number == 5 { n_offset = Some(offset); }
if tag.field_number == 6 { l_offset = Some(offset); }
if tag.field_number == 7 { c1_offset = Some(offset); }
if tag.field_number == 8 { c2_offset = Some(offset); }
if tag.field_number == 9 {
if pets_start.is_none() { pets_start = Some(offset); }
pets_end = Some(offset);
}
}
Ok(Self {
accessor,
name_offset,
d_offset,
f_offset,
b_offset,
n_offset,
l_offset,
c1_offset,
c2_offset,
pets_start, pets_end,
})
}
pub fn name(&self) -> roto_runtime::Result<&'a str> {
let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
pub fn d(&self) -> roto_runtime::Result<f64> {
let offset = self.d_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(f64::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?))
}
pub fn d_or_default(&self) -> roto_runtime::Result<f64> {
self.d().or(Ok(0.0))
}
pub fn has_d(&self) -> bool { self.d_offset.is_some() }
pub fn f(&self) -> roto_runtime::Result<f32> {
let offset = self.f_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(f32::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?))
}
pub fn f_or_default(&self) -> roto_runtime::Result<f32> {
self.f().or(Ok(0.0))
}
pub fn has_f(&self) -> bool { self.f_offset.is_some() }
pub fn b(&self) -> roto_runtime::Result<bool> {
let offset = self.b_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn b_or_default(&self) -> roto_runtime::Result<bool> {
self.b().or(Ok(false))
}
pub fn has_b(&self) -> bool { self.b_offset.is_some() }
pub fn n(&self) -> roto_runtime::Result<i32> {
let offset = self.n_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn n_or_default(&self) -> roto_runtime::Result<i32> {
self.n().or(Ok(0))
}
pub fn has_n(&self) -> bool { self.n_offset.is_some() }
pub fn l(&self) -> roto_runtime::Result<i32> {
let offset = self.l_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn l_or_default(&self) -> roto_runtime::Result<i32> {
self.l().or(Ok(0))
}
pub fn has_l(&self) -> bool { self.l_offset.is_some() }
pub fn c1(&self) -> roto_runtime::Result<&'a str> {
let offset = self.c1_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn c1_or_default(&self) -> roto_runtime::Result<&'a str> {
self.c1().or(Ok(""))
}
pub fn has_c1(&self) -> bool { self.c1_offset.is_some() }
pub fn c2(&self) -> roto_runtime::Result<bool> {
let offset = self.c2_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn c2_or_default(&self) -> roto_runtime::Result<bool> {
self.c2().or(Ok(false))
}
pub fn has_c2(&self) -> bool { self.c2_offset.is_some() }
pub fn pets(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.pets_start, self.pets_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(9, start, end),
_ => self.accessor.iter_repeated(9),
}
}
pub fn which_choice(&self) -> roto_runtime::Result<Option<hello::Choice<'a>> > {
if self.c1_offset.is_some() {
return Ok(Some(hello::Choice::c1 (self.c1()?)));
}
if self.c2_offset.is_some() {
return Ok(Some(hello::Choice::c2 (self.c2()?)));
}
Ok(None)
}
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
name_written: bool,
d_written: bool,
f_written: bool,
b_written: bool,
n_written: bool,
l_written: bool,
c1_written: bool,
c2_written: bool,
pets_written: bool,
}
impl<'b> HelloBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloBuilder<'_> {
HelloBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
name_written: false,
d_written: false,
f_written: false,
b_written: false,
n_written: false,
l_written: false,
c1_written: false,
c2_written: false,
pets_written: false,
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn d(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(2, value)?;
self.d_written = true;
Ok(self)
}
pub fn f(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(3, value)?;
self.f_written = true;
Ok(self)
}
pub fn b(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(4, value)?;
self.b_written = true;
Ok(self)
}
pub fn n(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(5, value)?;
self.n_written = true;
Ok(self)
}
pub fn l(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(6, value)?;
self.l_written = true;
Ok(self)
}
pub fn c1(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(7, value)?;
self.c1_written = true;
Ok(self)
}
pub fn c2(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(8, value)?;
self.c2_written = true;
Ok(self)
}
pub fn pets(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(9, value)?;
self.pets_written = true;
Ok(self)
}
pub fn with(mut self, msg: &Hello<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.name_written,
2 => self.d_written,
3 => self.f_written,
4 => self.b_written,
5 => self.n_written,
6 => self.l_written,
7 => self.c1_written,
8 => self.c2_written,
9 => self.pets_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedHello {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedHello {
type Reader<'a> = Hello<'a>;
fn reader(&self) -> Hello<'_> {
Hello::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedHello {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHello { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub mod hello {
pub struct Pet<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
color_offset: Option<usize>,
}
impl<'a> Pet<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut name_offset = None;
let mut color_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { name_offset = Some(offset); }
if tag.field_number == 2 { color_offset = Some(offset); }
}
Ok(Self {
accessor,
name_offset,
color_offset,
})
}
pub fn name(&self) -> roto_runtime::Result<&'a str> {
let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
pub fn color(&self) -> roto_runtime::Result<u64> {
let offset = self.color_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as u64).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn color_or_default(&self) -> roto_runtime::Result<u64> {
self.color().or(Ok(0))
}
pub fn has_color(&self) -> bool { self.color_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct PetBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
name_written: bool,
color_written: bool,
}
impl<'b> PetBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> PetBuilder<'_> {
PetBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
name_written: false,
color_written: false,
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn color(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(2, value)?;
self.color_written = true;
Ok(self)
}
pub fn with(mut self, msg: &Pet<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.name_written,
2 => self.color_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedPet {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedPet {
type Reader<'a> = Pet<'a>;
fn reader(&self) -> Pet<'_> {
Pet::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedPet {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedPet { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub mod pet {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum Color {
BLACK = 0,
WHITE = 1,
BLUE = 2,
RED = 3,
YELLOW = 4,
GREEN = 5,
}
impl Color {
pub fn from_i32(value: i32) -> Self {
match value {
0 => Color::BLACK,
1 => Color::WHITE,
2 => Color::BLUE,
3 => Color::RED,
4 => Color::YELLOW,
5 => Color::GREEN,
_ => Color::BLACK,
}
}
}
}
pub enum Choice<'a> {
c1(&'a str),
c2(bool),
}
}
pub struct HelloRequest<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
request_offset: Option<usize>,
}
impl<'a> HelloRequest<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut request_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { request_offset = Some(offset); }
}
Ok(Self {
accessor,
request_offset,
})
}
pub fn request(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.request_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn request_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.request().or(Ok(&[]))
}
pub fn has_request(&self) -> bool { self.request_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloRequestBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
request_written: bool,
}
impl<'b> HelloRequestBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloRequestBuilder<'_> {
HelloRequestBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
request_written: false,
}
}
pub fn request(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(1, value)?;
self.request_written = true;
Ok(self)
}
pub fn with(mut self, msg: &HelloRequest<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.request_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedHelloRequest {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedHelloRequest {
type Reader<'a> = HelloRequest<'a>;
fn reader(&self) -> HelloRequest<'_> {
HelloRequest::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedHelloRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHelloRequest { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct HelloReply<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
response_offset: Option<usize>,
}
impl<'a> HelloReply<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut response_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { response_offset = Some(offset); }
}
Ok(Self {
accessor,
response_offset,
})
}
pub fn response(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.response_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn response_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.response().or(Ok(&[]))
}
pub fn has_response(&self) -> bool { self.response_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloReplyBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
response_written: bool,
}
impl<'b> HelloReplyBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloReplyBuilder<'_> {
HelloReplyBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
response_written: false,
}
}
pub fn response(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(1, value)?;
self.response_written = true;
Ok(self)
}
pub fn with(mut self, msg: &HelloReply<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.response_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedHelloReply {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedHelloReply {
type Reader<'a> = HelloReply<'a>;
fn reader(&self) -> HelloReply<'_> {
HelloReply::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedHelloReply {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHelloReply { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
#[cfg(feature = "alloc")]
use tonic::{Request, Response, Status};
#[cfg(feature = "alloc")]
use tokio_stream::Stream;
#[cfg(feature = "alloc")]
use std::pin::Pin;
#[cfg(feature = "alloc")]
use std::sync::Arc;
#[cfg(feature = "alloc")]
use std::task::{Context, Poll};
#[cfg(feature = "alloc")]
use std::future::Future;
#[cfg(feature = "alloc")]
use tonic::body::BoxBody;
#[cfg(feature = "alloc")]
use tower::Service;
#[cfg(feature = "alloc")]
use futures_util::StreamExt;
#[cfg(feature = "alloc")]
use http_body_util::BodyExt;
#[cfg(feature = "alloc")]
use http_body::Body;
#[cfg(feature = "alloc")]
use crate::{BufferPool, StatusBody};
#[cfg(feature = "alloc")]
#[async_trait::async_trait]
pub trait Greeter: Send + Sync + 'static {
async fn say_hello(&self, request: Request<OwnedHelloRequest>) -> std::result::Result<Response<OwnedHelloReply>, Status>;
}
#[cfg(feature = "alloc")]
#[derive(Clone)]
pub struct GreeterServer {
inner: Arc<dyn Greeter>,
pool: Arc<BufferPool>,
}
#[cfg(feature = "alloc")]
impl GreeterServer {
pub fn new(inner: Arc<dyn Greeter>, pool: Arc<BufferPool>) -> Self {
Self { inner, pool }
}
}
#[cfg(feature = "alloc")]
impl tonic::server::NamedService for GreeterServer {
const NAME: &'static str = "helloworld.Greeter";
}
#[cfg(feature = "alloc")]
impl Service<http::Request<BoxBody>> for GreeterServer {
type Response = http::Response<BoxBody>;
type Error = std::convert::Infallible;
type Future = Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<BoxBody>) -> Self::Future {
let inner = self.inner.clone();
let pool = self.pool.clone();
Box::pin(async move {
let path = req.uri().path().to_string();
let body = req.into_body();
let mut buf = pool.get();
let mut stream = body;
while let Some(frame_result) = stream.frame().await {
let frame = frame_result.expect("Body frame error");
if let Some(data) = frame.data_ref() {
buf.put(data.clone());
}
}
let total_len = buf.len();
let bytes_vec = buf.split_to(total_len).freeze();
pool.put(buf);
if bytes_vec.len() < 5 {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
let payload = bytes_vec.slice(5..);
let mut routed = false;
if path == "/helloworld.Greeter/SayHello" {
let request_msg = match OwnedHelloRequest::decode(payload) {
Ok(msg) => msg,
Err(e) => {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
};
let response = match inner.say_hello(Request::new(request_msg)).await {
Ok(res) => res,
Err(e) => {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
};
let response_msg = response.into_inner();
let response_bytes = response_msg.bytes();
let mut res_buf = pool.get();
res_buf.put_u8(0);
let len = response_bytes.len() as u32;
res_buf.put_slice(&len.to_be_bytes());
res_buf.put_slice(&response_bytes);
let frame_len = res_buf.len();
let frame = res_buf.split_to(frame_len).freeze();
pool.put(res_buf);
let res_body = BoxBody::new(StatusBody::new(Some(frame), 0));
routed = true;
return Ok(http::Response::builder().status(200).header("content-type", "application/grpc").body(res_body).unwrap());
}
if !routed {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
Ok(http::Response::builder().status(200).body(BoxBody::new(StatusBody::new(None, 0))).unwrap())
})
}
}
+14
View File
@@ -0,0 +1,14 @@
#![no_std]
#![no_main]
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {}
}
Submodule
+1
Submodule grpc_bench added at c645de5855
BIN
View File
Binary file not shown.
+773
View File
@@ -0,0 +1,773 @@
// @generated by protoc-gen-roto — do not edit
#[allow(unused_imports)]
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};
use std::str;
use bytes::{Bytes, BytesMut, Buf, BufMut};
use tonic::{Request, Response, Status};
use tokio_stream::Stream;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::future::Future;
use tonic::body::BoxBody;
use tower::Service;
use futures_util::StreamExt;
use http_body_util::BodyExt;
use http_body::Body;
use roto_tonic::{BufferPool, StatusBody};
pub struct Hello<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
d_offset: Option<usize>,
f_offset: Option<usize>,
b_offset: Option<usize>,
n_offset: Option<usize>,
l_offset: Option<usize>,
c1_offset: Option<usize>,
c2_offset: Option<usize>,
pets_start: Option<usize>,
pets_end: Option<usize>,
}
impl<'a> Hello<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut name_offset = None;
let mut d_offset = None;
let mut f_offset = None;
let mut b_offset = None;
let mut n_offset = None;
let mut l_offset = None;
let mut c1_offset = None;
let mut c2_offset = None;
let mut pets_start = None;
let mut pets_end = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { name_offset = Some(offset); }
if tag.field_number == 2 { d_offset = Some(offset); }
if tag.field_number == 3 { f_offset = Some(offset); }
if tag.field_number == 4 { b_offset = Some(offset); }
if tag.field_number == 5 { n_offset = Some(offset); }
if tag.field_number == 6 { l_offset = Some(offset); }
if tag.field_number == 7 { c1_offset = Some(offset); }
if tag.field_number == 8 { c2_offset = Some(offset); }
if tag.field_number == 9 {
if pets_start.is_none() { pets_start = Some(offset); }
pets_end = Some(offset);
}
}
Ok(Self {
accessor,
name_offset,
d_offset,
f_offset,
b_offset,
n_offset,
l_offset,
c1_offset,
c2_offset,
pets_start, pets_end,
})
}
pub fn name(&self) -> roto_runtime::Result<&'a str> {
let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
std::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
pub fn d(&self) -> roto_runtime::Result<f64> {
let offset = self.d_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(f64::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?))
}
pub fn d_or_default(&self) -> roto_runtime::Result<f64> {
self.d().or(Ok(0.0))
}
pub fn has_d(&self) -> bool { self.d_offset.is_some() }
pub fn f(&self) -> roto_runtime::Result<f32> {
let offset = self.f_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(f32::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?))
}
pub fn f_or_default(&self) -> roto_runtime::Result<f32> {
self.f().or(Ok(0.0))
}
pub fn has_f(&self) -> bool { self.f_offset.is_some() }
pub fn b(&self) -> roto_runtime::Result<bool> {
let offset = self.b_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn b_or_default(&self) -> roto_runtime::Result<bool> {
self.b().or(Ok(false))
}
pub fn has_b(&self) -> bool { self.b_offset.is_some() }
pub fn n(&self) -> roto_runtime::Result<i32> {
let offset = self.n_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn n_or_default(&self) -> roto_runtime::Result<i32> {
self.n().or(Ok(0))
}
pub fn has_n(&self) -> bool { self.n_offset.is_some() }
pub fn l(&self) -> roto_runtime::Result<i32> {
let offset = self.l_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn l_or_default(&self) -> roto_runtime::Result<i32> {
self.l().or(Ok(0))
}
pub fn has_l(&self) -> bool { self.l_offset.is_some() }
pub fn c1(&self) -> roto_runtime::Result<&'a str> {
let offset = self.c1_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
std::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn c1_or_default(&self) -> roto_runtime::Result<&'a str> {
self.c1().or(Ok(""))
}
pub fn has_c1(&self) -> bool { self.c1_offset.is_some() }
pub fn c2(&self) -> roto_runtime::Result<bool> {
let offset = self.c2_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn c2_or_default(&self) -> roto_runtime::Result<bool> {
self.c2().or(Ok(false))
}
pub fn has_c2(&self) -> bool { self.c2_offset.is_some() }
pub fn pets(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.pets_start, self.pets_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(9, start, end),
_ => self.accessor.iter_repeated(9),
}
}
pub fn which_choice(&self) -> roto_runtime::Result<Option<hello::Choice<'a>> > {
if self.c1_offset.is_some() {
return Ok(Some(hello::Choice::c1 (self.c1()?)));
}
if self.c2_offset.is_some() {
return Ok(Some(hello::Choice::c2 (self.c2()?)));
}
Ok(None)
}
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
name_written: bool,
d_written: bool,
f_written: bool,
b_written: bool,
n_written: bool,
l_written: bool,
c1_written: bool,
c2_written: bool,
pets_written: bool,
}
impl<'b> HelloBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloBuilder<'_> {
HelloBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
name_written: false,
d_written: false,
f_written: false,
b_written: false,
n_written: false,
l_written: false,
c1_written: false,
c2_written: false,
pets_written: false,
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn d(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(2, value)?;
self.d_written = true;
Ok(self)
}
pub fn f(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(3, value)?;
self.f_written = true;
Ok(self)
}
pub fn b(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(4, value)?;
self.b_written = true;
Ok(self)
}
pub fn n(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(5, value)?;
self.n_written = true;
Ok(self)
}
pub fn l(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(6, value)?;
self.l_written = true;
Ok(self)
}
pub fn c1(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(7, value)?;
self.c1_written = true;
Ok(self)
}
pub fn c2(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(8, value)?;
self.c2_written = true;
Ok(self)
}
pub fn pets(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(9, value)?;
self.pets_written = true;
Ok(self)
}
pub fn with(mut self, msg: &Hello<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.name_written,
2 => self.d_written,
3 => self.f_written,
4 => self.b_written,
5 => self.n_written,
6 => self.l_written,
7 => self.c1_written,
8 => self.c2_written,
9 => self.pets_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedHello {
pub data: bytes::Bytes,
}
impl roto_runtime::RotoOwned for OwnedHello {
type Reader<'a> = Hello<'a>;
fn reader(&self) -> Hello<'_> {
Hello::new(&self.data).expect("failed to create reader")
}
}
impl roto_runtime::RotoMessage for OwnedHello {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHello { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub mod hello {
pub struct Pet<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
color_offset: Option<usize>,
}
impl<'a> Pet<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut name_offset = None;
let mut color_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { name_offset = Some(offset); }
if tag.field_number == 2 { color_offset = Some(offset); }
}
Ok(Self {
accessor,
name_offset,
color_offset,
})
}
pub fn name(&self) -> roto_runtime::Result<&'a str> {
let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
std::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
pub fn color(&self) -> roto_runtime::Result<u64> {
let offset = self.color_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as u64).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn color_or_default(&self) -> roto_runtime::Result<u64> {
self.color().or(Ok(0))
}
pub fn has_color(&self) -> bool { self.color_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct PetBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
name_written: bool,
color_written: bool,
}
impl<'b> PetBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> PetBuilder<'_> {
PetBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
name_written: false,
color_written: false,
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn color(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(2, value)?;
self.color_written = true;
Ok(self)
}
pub fn with(mut self, msg: &Pet<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.name_written,
2 => self.color_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedPet {
pub data: bytes::Bytes,
}
impl roto_runtime::RotoOwned for OwnedPet {
type Reader<'a> = Pet<'a>;
fn reader(&self) -> Pet<'_> {
Pet::new(&self.data).expect("failed to create reader")
}
}
impl roto_runtime::RotoMessage for OwnedPet {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedPet { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub mod pet {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum Color {
BLACK = 0,
WHITE = 1,
BLUE = 2,
RED = 3,
YELLOW = 4,
GREEN = 5,
}
impl Color {
pub fn from_i32(value: i32) -> Self {
match value {
0 => Color::BLACK,
1 => Color::WHITE,
2 => Color::BLUE,
3 => Color::RED,
4 => Color::YELLOW,
5 => Color::GREEN,
_ => Color::BLACK,
}
}
}
}
pub enum Choice<'a> {
c1(&'a str),
c2(bool),
}
}
pub struct HelloRequest<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
request_offset: Option<usize>,
}
impl<'a> HelloRequest<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut request_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { request_offset = Some(offset); }
}
Ok(Self {
accessor,
request_offset,
})
}
pub fn request(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.request_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn request_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.request().or(Ok(&[]))
}
pub fn has_request(&self) -> bool { self.request_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloRequestBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
request_written: bool,
}
impl<'b> HelloRequestBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloRequestBuilder<'_> {
HelloRequestBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
request_written: false,
}
}
pub fn request(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(1, value)?;
self.request_written = true;
Ok(self)
}
pub fn with(mut self, msg: &HelloRequest<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.request_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedHelloRequest {
pub data: bytes::Bytes,
}
impl roto_runtime::RotoOwned for OwnedHelloRequest {
type Reader<'a> = HelloRequest<'a>;
fn reader(&self) -> HelloRequest<'_> {
HelloRequest::new(&self.data).expect("failed to create reader")
}
}
impl roto_runtime::RotoMessage for OwnedHelloRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHelloRequest { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct HelloReply<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
response_offset: Option<usize>,
}
impl<'a> HelloReply<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut response_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { response_offset = Some(offset); }
}
Ok(Self {
accessor,
response_offset,
})
}
pub fn response(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.response_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn response_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.response().or(Ok(&[]))
}
pub fn has_response(&self) -> bool { self.response_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloReplyBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
response_written: bool,
}
impl<'b> HelloReplyBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloReplyBuilder<'_> {
HelloReplyBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
response_written: false,
}
}
pub fn response(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(1, value)?;
self.response_written = true;
Ok(self)
}
pub fn with(mut self, msg: &HelloReply<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.response_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedHelloReply {
pub data: bytes::Bytes,
}
impl roto_runtime::RotoOwned for OwnedHelloReply {
type Reader<'a> = HelloReply<'a>;
fn reader(&self) -> HelloReply<'_> {
HelloReply::new(&self.data).expect("failed to create reader")
}
}
impl roto_runtime::RotoMessage for OwnedHelloReply {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHelloReply { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
#[tonic::async_trait]
pub trait Greeter: Send + Sync + 'static {
async fn say_hello(&self, request: Request<OwnedHelloRequest>) -> std::result::Result<Response<OwnedHelloReply>, Status>;
}
pub struct GreeterServer {
inner: Arc<dyn Greeter>,
pool: Arc<BufferPool>,
}
impl GreeterServer {
pub fn new(inner: Arc<dyn Greeter>, pool: Arc<BufferPool>) -> Self {
Self { inner, pool }
}
}
impl tonic::server::NamedService for GreeterServer {
const NAME: &'static str = "Greeter";
}
impl Service<http::Request<BoxBody>> for GreeterServer {
type Response = http::Response<BoxBody>;
type Error = std::convert::Infallible;
type Future = Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<BoxBody>) -> Self::Future {
let inner = self.inner.clone();
let pool = self.pool.clone();
Box::pin(async move {
let path = req.uri().path().to_string();
let body = req.into_body();
let mut buf = pool.get();
let mut stream = body;
while let Some(frame_result) = stream.frame().await {
let frame = frame_result.expect("Body frame error");
if let Some(data) = frame.data_ref() {
buf.put(data.clone());
}
}
let total_len = buf.len();
let bytes_vec = buf.split_to(total_len).freeze();
pool.put(buf);
if bytes_vec.len() < 5 {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
let payload = bytes_vec.slice(5..);
let mut routed = false;
if path == "/Greeter/say_hello" {
let request_msg = match OwnedHelloRequest::decode(payload) {
Ok(msg) => msg,
Err(e) => {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
};
let response = match inner.say_hello(Request::new(request_msg)).await {
Ok(res) => res,
Err(e) => {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
};
let response_msg = response.into_inner();
let response_bytes = response_msg.bytes();
let mut res_buf = pool.get();
res_buf.put_u8(0);
let len = response_bytes.len() as u32;
res_buf.put_slice(&len.to_be_bytes());
res_buf.put_slice(&response_bytes);
let frame_len = res_buf.len();
let frame = res_buf.split_to(frame_len).freeze();
pool.put(res_buf);
let res_body = BoxBody::new(StatusBody::new(Some(frame), 0));
routed = true;
return Ok(http::Response::builder().status(200).header("content-type", "application/grpc").body(res_body).unwrap());
}
if !routed {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
Ok(http::Response::builder().status(200).body(BoxBody::new(StatusBody::new(None, 0))).unwrap())
})
}
}
+12
View File
@@ -0,0 +1,12 @@
You are the Codemonkey. Your role is the pure implementation of technical tasks.
Your workflow:
1. Review the implementation plan in the `artifacts/` directory.
2. Implement the changes in the codebase following the provided plan and established coding standards.
3. After every significant change, you MUST:
- Ensure the codebase compiles without errors.
- Run all relevant tests to ensure no regressions were introduced.
- If tests fail, fix the issues before proceeding.
4. Document your progress and the final outcome in a verification report in the `artifacts/` directory (e.g., `artifacts/verification_report.md`). This report should list the tasks completed and provide evidence (e.g., test output) that the solution works as intended.
5. Do not deviate from the plan without consulting the Orchestrator.
6. Once the tasks are complete and verified, notify the Orchestrator that the codebase is updated and the verification report artifact is ready.
+9
View File
@@ -0,0 +1,9 @@
You are the Coordinator. Your role is to transform high-level problem statements and research data into a concrete, executable implementation plan.
Your workflow:
1. Review the problem statement and the `artifacts/research_report.md` produced by the Research agent.
2. Break down the overall goal into a sequence of small, manageable, and independent tasks.
3. For each task, specify the expected outcome and any dependencies on previous tasks.
4. Ensure the plan is logically ordered and covers all edge cases identified during research.
5. Save the final plan as `artifacts/implementation_plan.md` for human review and as a guide for the Codemonkey.
6. Notify the Orchestrator that the implementation plan artifact is ready.
+14
View File
@@ -0,0 +1,14 @@
You are the Orchestrator. Your role is to manage the end-to-end resolution of a complex technical problem. Make extensive use of the
subagent tool.
Your workflow:
1. Analyze the initial problem statement.
2. Delegate research to the Research agent. Ensure the Research agent produces a `artifacts/research_report.md` for human review.
3. Coordinate with the Coordinator agent to break down the findings into a detailed plan. Ensure the Coordinator produces a `artifacts/implementation_plan.md` for human review.
4. Delegate the implementation of these steps to the Codemonkey agent. Ensure the Codemonkey produces a `artifacts/verification_report.md` upon completion.
5. Review the artifacts in the `artifacts/` directory and the results from the Codemonkey agent, ensuring all requirements are met and the solution is correct.
6. If issues arise, loop back to the Research or Coordinator agents as needed, updating the relevant artifacts.
Your goal is to act as the central hub of communication and decision-making, ensuring a systematic and verified approach to the problem.
You must tell subagents what persona they are, and provide them the path of the persona file they should read.
+13
View File
@@ -0,0 +1,13 @@
You are the Researcher. Your role is to conduct a comprehensive initial analysis of a problem to identify the best technical approach.
Your workflow:
1. Analyze the problem statement to identify key technical requirements and unknown areas.
2. Use available tools (such as SearXNG and fetch) to research existing libraries, frameworks, APIs, and best practices relevant to the problem.
3. Explore the current codebase to understand how the new functionality fits in or what existing patterns should be followed.
4. Compile a detailed report including:
- Recommended tools and libraries.
- Potential challenges or pitfalls.
- Suggested architectural approach.
- Relevant documentation links.
5. Save this report as `artifacts/research_report.md` for human review.
6. Notify the Orchestrator that the research report artifact is ready.
+56
View File
@@ -0,0 +1,56 @@
syntax = "proto3";
message Tool {
string name = 1;
string version = 2;
bytes payload = 3;
bool is_active = 4;
int32 exploit_count = 5;
}
message Connection {
string host = 1;
int32 port = 2;
bool encrypted = 3;
int64 bandwidth_bps = 4;
bytes session_key = 5;
}
message Hacker {
string handle = 1;
string real_name = 2;
int32 age = 3;
float skill_level = 4; // Fixed32
bool is_elite = 5;
int64 crew_id = 6;
repeated string exploits = 7;
repeated Tool tools = 8;
Connection active_connection = 9;
}
message Worm {
string name = 1;
int32 variant = 2;
int64 size_bytes = 3;
bytes payload = 4;
bool polymorphic = 5;
repeated string targets = 6;
}
message Operation {
string codename = 1;
string target_corp = 2;
int64 timestamp = 3;
bool successful = 4;
bytes stolen_data = 5;
repeated Hacker crew = 6;
Worm worm = 7;
repeated string log_entries = 8;
int32 severity = 9;
}
message Campaign {
string name = 1;
repeated Operation operations = 2;
int64 total_bytes_stolen = 3;
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
option go_package = "proto/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The actual message exchanged by the client and the server.
// NOTE: When creating a custom scenario plese edit only this message.
message Hello {
string name = 1;
double d = 2;
float f = 3;
bool b = 4;
int32 n = 5;
int64 l = 6;
oneof choice {
string c1 = 7;
bool c2 = 8;
}
message Pet {
enum Color {
BLACK = 0;
WHITE = 1;
BLUE = 2;
RED = 3;
YELLOW = 4;
GREEN = 5;
}
string name = 1;
Color color = 2;
}
repeated Pet pets = 9;
}
// The request message from the client.
message HelloRequest {
Hello request = 1;
}
// The response message from the server.
message HelloReply {
Hello response = 1;
}
+6
View File
@@ -0,0 +1,6 @@
[package]
name = "protos"
version = "0.1.0"
edition = "2024"
[dependencies]
+3
View File
@@ -0,0 +1,3 @@
pub fn hello() {
println!("Hello from protos!");
}
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "roto-tonic"
version = "0.1.0"
edition = "2024"
[dependencies]
roto-runtime = { path = "../runtime" }
tonic = "0.12"
bytes = "1.7"
prost = "0.13"
http-body = "1.0"
http-body-util = "0.1"
tower = "0.4"
futures-util = "0.3"
async-trait = "0.1"
tokio-stream = { version = "0.1", features = ["net"] }
tokio = { version = "1.38", features = ["full"] }
http = "1.1"
[build-dependencies]
tonic-build = "0.12"
[features]
default = ["alloc"]
alloc = []
+36
View File
@@ -0,0 +1,36 @@
use std::env;
use std::process::Command;
use std::path::PathBuf;
fn main() {
let proto_file = "proto/interop.proto";
// 1. Generate prost/tonic code
tonic_build::compile_protos(proto_file).expect("Failed to compile protos with tonic-build");
// 2. Generate roto code
// Find protoc-gen-roto
// We assume it's in the target/debug folder of the root project
let root_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let plugin_path = PathBuf::from(&root_dir)
.join("../")
.join("target/debug/protoc-gen-roto");
if !plugin_path.exists() {
println!("cargo:warning=protoc-gen-roto plugin not found at {:?}. Roto code generation will be skipped.", plugin_path);
return;
}
let out_dir = PathBuf::from(&root_dir).join("src/generated");
let status = Command::new("protoc")
.arg(format!("--plugin=protoc-gen-roto={}", plugin_path.to_str().unwrap()))
.arg(format!("--roto_out={}", out_dir.to_str().unwrap()))
.arg(proto_file)
.status()
.expect("Failed to execute protoc");
if !status.success() {
panic!("protoc failed to generate roto code");
}
}
+26
View File
@@ -0,0 +1,26 @@
syntax = "proto3";
package interop;
service InteropService {
// Expected to succeed
rpc UnaryCall (UnaryRequest) returns (UnaryResponse);
// Expected to fail (roto does not support streaming)
rpc StreamingCall (StreamingRequest) returns (stream StreamingResponse);
}
message UnaryRequest {
string message = 1;
}
message UnaryResponse {
string reply = 1;
}
message StreamingRequest {
string query = 1;
}
message StreamingResponse {
string item = 1;
}
+774
View File
@@ -0,0 +1,774 @@
// @generated by protoc-gen-roto — do not edit
#[allow(unused_imports)]
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};
use std::str;
use bytes::{Bytes, BytesMut, Buf, BufMut};
use tonic::{Request, Response, Status};
use tokio_stream::Stream;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::future::Future;
use tonic::body::BoxBody;
use tower::Service;
use futures_util::StreamExt;
use http_body_util::BodyExt;
use http_body::Body;
use crate::{BufferPool, StatusBody};
pub struct Hello<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
d_offset: Option<usize>,
f_offset: Option<usize>,
b_offset: Option<usize>,
n_offset: Option<usize>,
l_offset: Option<usize>,
c1_offset: Option<usize>,
c2_offset: Option<usize>,
pets_start: Option<usize>,
pets_end: Option<usize>,
}
impl<'a> Hello<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut name_offset = None;
let mut d_offset = None;
let mut f_offset = None;
let mut b_offset = None;
let mut n_offset = None;
let mut l_offset = None;
let mut c1_offset = None;
let mut c2_offset = None;
let mut pets_start = None;
let mut pets_end = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { name_offset = Some(offset); }
if tag.field_number == 2 { d_offset = Some(offset); }
if tag.field_number == 3 { f_offset = Some(offset); }
if tag.field_number == 4 { b_offset = Some(offset); }
if tag.field_number == 5 { n_offset = Some(offset); }
if tag.field_number == 6 { l_offset = Some(offset); }
if tag.field_number == 7 { c1_offset = Some(offset); }
if tag.field_number == 8 { c2_offset = Some(offset); }
if tag.field_number == 9 {
if pets_start.is_none() { pets_start = Some(offset); }
pets_end = Some(offset);
}
}
Ok(Self {
accessor,
name_offset,
d_offset,
f_offset,
b_offset,
n_offset,
l_offset,
c1_offset,
c2_offset,
pets_start, pets_end,
})
}
pub fn name(&self) -> roto_runtime::Result<&'a str> {
let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
std::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
pub fn d(&self) -> roto_runtime::Result<f64> {
let offset = self.d_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(f64::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?))
}
pub fn d_or_default(&self) -> roto_runtime::Result<f64> {
self.d().or(Ok(0.0))
}
pub fn has_d(&self) -> bool { self.d_offset.is_some() }
pub fn f(&self) -> roto_runtime::Result<f32> {
let offset = self.f_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(f32::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?))
}
pub fn f_or_default(&self) -> roto_runtime::Result<f32> {
self.f().or(Ok(0.0))
}
pub fn has_f(&self) -> bool { self.f_offset.is_some() }
pub fn b(&self) -> roto_runtime::Result<bool> {
let offset = self.b_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn b_or_default(&self) -> roto_runtime::Result<bool> {
self.b().or(Ok(false))
}
pub fn has_b(&self) -> bool { self.b_offset.is_some() }
pub fn n(&self) -> roto_runtime::Result<i32> {
let offset = self.n_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn n_or_default(&self) -> roto_runtime::Result<i32> {
self.n().or(Ok(0))
}
pub fn has_n(&self) -> bool { self.n_offset.is_some() }
pub fn l(&self) -> roto_runtime::Result<i32> {
let offset = self.l_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn l_or_default(&self) -> roto_runtime::Result<i32> {
self.l().or(Ok(0))
}
pub fn has_l(&self) -> bool { self.l_offset.is_some() }
pub fn c1(&self) -> roto_runtime::Result<&'a str> {
let offset = self.c1_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
std::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn c1_or_default(&self) -> roto_runtime::Result<&'a str> {
self.c1().or(Ok(""))
}
pub fn has_c1(&self) -> bool { self.c1_offset.is_some() }
pub fn c2(&self) -> roto_runtime::Result<bool> {
let offset = self.c2_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn c2_or_default(&self) -> roto_runtime::Result<bool> {
self.c2().or(Ok(false))
}
pub fn has_c2(&self) -> bool { self.c2_offset.is_some() }
pub fn pets(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.pets_start, self.pets_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(9, start, end),
_ => self.accessor.iter_repeated(9),
}
}
pub fn which_choice(&self) -> roto_runtime::Result<Option<hello::Choice<'a>> > {
if self.c1_offset.is_some() {
return Ok(Some(hello::Choice::c1 (self.c1()?)));
}
if self.c2_offset.is_some() {
return Ok(Some(hello::Choice::c2 (self.c2()?)));
}
Ok(None)
}
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
name_written: bool,
d_written: bool,
f_written: bool,
b_written: bool,
n_written: bool,
l_written: bool,
c1_written: bool,
c2_written: bool,
pets_written: bool,
}
impl<'b> HelloBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloBuilder<'_> {
HelloBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
name_written: false,
d_written: false,
f_written: false,
b_written: false,
n_written: false,
l_written: false,
c1_written: false,
c2_written: false,
pets_written: false,
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn d(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(2, value)?;
self.d_written = true;
Ok(self)
}
pub fn f(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(3, value)?;
self.f_written = true;
Ok(self)
}
pub fn b(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(4, value)?;
self.b_written = true;
Ok(self)
}
pub fn n(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(5, value)?;
self.n_written = true;
Ok(self)
}
pub fn l(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(6, value)?;
self.l_written = true;
Ok(self)
}
pub fn c1(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(7, value)?;
self.c1_written = true;
Ok(self)
}
pub fn c2(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(8, value)?;
self.c2_written = true;
Ok(self)
}
pub fn pets(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(9, value)?;
self.pets_written = true;
Ok(self)
}
pub fn with(mut self, msg: &Hello<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.name_written,
2 => self.d_written,
3 => self.f_written,
4 => self.b_written,
5 => self.n_written,
6 => self.l_written,
7 => self.c1_written,
8 => self.c2_written,
9 => self.pets_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedHello {
pub data: bytes::Bytes,
}
impl roto_runtime::RotoOwned for OwnedHello {
type Reader<'a> = Hello<'a>;
fn reader(&self) -> Hello<'_> {
Hello::new(&self.data).expect("failed to create reader")
}
}
impl roto_runtime::RotoMessage for OwnedHello {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHello { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub mod hello {
pub struct Pet<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
color_offset: Option<usize>,
}
impl<'a> Pet<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut name_offset = None;
let mut color_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { name_offset = Some(offset); }
if tag.field_number == 2 { color_offset = Some(offset); }
}
Ok(Self {
accessor,
name_offset,
color_offset,
})
}
pub fn name(&self) -> roto_runtime::Result<&'a str> {
let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
std::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
pub fn color(&self) -> roto_runtime::Result<u64> {
let offset = self.color_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as u64).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn color_or_default(&self) -> roto_runtime::Result<u64> {
self.color().or(Ok(0))
}
pub fn has_color(&self) -> bool { self.color_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct PetBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
name_written: bool,
color_written: bool,
}
impl<'b> PetBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> PetBuilder<'_> {
PetBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
name_written: false,
color_written: false,
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn color(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(2, value)?;
self.color_written = true;
Ok(self)
}
pub fn with(mut self, msg: &Pet<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.name_written,
2 => self.color_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedPet {
pub data: bytes::Bytes,
}
impl roto_runtime::RotoOwned for OwnedPet {
type Reader<'a> = Pet<'a>;
fn reader(&self) -> Pet<'_> {
Pet::new(&self.data).expect("failed to create reader")
}
}
impl roto_runtime::RotoMessage for OwnedPet {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedPet { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub mod pet {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum Color {
BLACK = 0,
WHITE = 1,
BLUE = 2,
RED = 3,
YELLOW = 4,
GREEN = 5,
}
impl Color {
pub fn from_i32(value: i32) -> Self {
match value {
0 => Color::BLACK,
1 => Color::WHITE,
2 => Color::BLUE,
3 => Color::RED,
4 => Color::YELLOW,
5 => Color::GREEN,
_ => Color::BLACK,
}
}
}
}
pub enum Choice<'a> {
c1(&'a str),
c2(bool),
}
}
pub struct HelloRequest<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
request_offset: Option<usize>,
}
impl<'a> HelloRequest<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut request_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { request_offset = Some(offset); }
}
Ok(Self {
accessor,
request_offset,
})
}
pub fn request(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.request_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn request_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.request().or(Ok(&[]))
}
pub fn has_request(&self) -> bool { self.request_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloRequestBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
request_written: bool,
}
impl<'b> HelloRequestBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloRequestBuilder<'_> {
HelloRequestBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
request_written: false,
}
}
pub fn request(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(1, value)?;
self.request_written = true;
Ok(self)
}
pub fn with(mut self, msg: &HelloRequest<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.request_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedHelloRequest {
pub data: bytes::Bytes,
}
impl roto_runtime::RotoOwned for OwnedHelloRequest {
type Reader<'a> = HelloRequest<'a>;
fn reader(&self) -> HelloRequest<'_> {
HelloRequest::new(&self.data).expect("failed to create reader")
}
}
impl roto_runtime::RotoMessage for OwnedHelloRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHelloRequest { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct HelloReply<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
response_offset: Option<usize>,
}
impl<'a> HelloReply<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut response_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { response_offset = Some(offset); }
}
Ok(Self {
accessor,
response_offset,
})
}
pub fn response(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.response_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn response_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.response().or(Ok(&[]))
}
pub fn has_response(&self) -> bool { self.response_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloReplyBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
response_written: bool,
}
impl<'b> HelloReplyBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloReplyBuilder<'_> {
HelloReplyBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
response_written: false,
}
}
pub fn response(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(1, value)?;
self.response_written = true;
Ok(self)
}
pub fn with(mut self, msg: &HelloReply<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.response_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedHelloReply {
pub data: bytes::Bytes,
}
impl roto_runtime::RotoOwned for OwnedHelloReply {
type Reader<'a> = HelloReply<'a>;
fn reader(&self) -> HelloReply<'_> {
HelloReply::new(&self.data).expect("failed to create reader")
}
}
impl roto_runtime::RotoMessage for OwnedHelloReply {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHelloReply { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
#[tonic::async_trait]
pub trait Greeter: Send + Sync + 'static {
async fn say_hello(&self, request: Request<OwnedHelloRequest>) -> std::result::Result<Response<OwnedHelloReply>, Status>;
}
#[derive(Clone)]
pub struct GreeterServer {
inner: Arc<dyn Greeter>,
pool: Arc<BufferPool>,
}
impl GreeterServer {
pub fn new(inner: Arc<dyn Greeter>, pool: Arc<BufferPool>) -> Self {
Self { inner, pool }
}
}
impl tonic::server::NamedService for GreeterServer {
const NAME: &'static str = "helloworld.Greeter";
}
impl Service<http::Request<BoxBody>> for GreeterServer {
type Response = http::Response<BoxBody>;
type Error = std::convert::Infallible;
type Future = Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<BoxBody>) -> Self::Future {
let inner = self.inner.clone();
let pool = self.pool.clone();
Box::pin(async move {
let path = req.uri().path().to_string();
let body = req.into_body();
let mut buf = pool.get();
let mut stream = body;
while let Some(frame_result) = stream.frame().await {
let frame = frame_result.expect("Body frame error");
if let Some(data) = frame.data_ref() {
buf.put(data.clone());
}
}
let total_len = buf.len();
let bytes_vec = buf.split_to(total_len).freeze();
pool.put(buf);
if bytes_vec.len() < 5 {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
let payload = bytes_vec.slice(5..);
let mut routed = false;
if path == "/helloworld.Greeter/say_hello" {
let request_msg = match OwnedHelloRequest::decode(payload) {
Ok(msg) => msg,
Err(e) => {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
};
let response = match inner.say_hello(Request::new(request_msg)).await {
Ok(res) => res,
Err(e) => {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
};
let response_msg = response.into_inner();
let response_bytes = response_msg.bytes();
let mut res_buf = pool.get();
res_buf.put_u8(0);
let len = response_bytes.len() as u32;
res_buf.put_slice(&len.to_be_bytes());
res_buf.put_slice(&response_bytes);
let frame_len = res_buf.len();
let frame = res_buf.split_to(frame_len).freeze();
pool.put(res_buf);
let res_body = BoxBody::new(StatusBody::new(Some(frame), 0));
routed = true;
return Ok(http::Response::builder().status(200).header("content-type", "application/grpc").body(res_body).unwrap());
}
if !routed {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
Ok(http::Response::builder().status(200).body(BoxBody::new(StatusBody::new(None, 0))).unwrap())
})
}
}
+539
View File
@@ -0,0 +1,539 @@
// @generated by protoc-gen-roto — do not edit
#[allow(unused_imports)]
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};
use core::str;
#[cfg(feature = "alloc")]
use bytes::{Bytes, BytesMut, Buf, BufMut};
pub struct UnaryRequest<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
message_offset: Option<usize>,
}
impl<'a> UnaryRequest<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut message_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { message_offset = Some(offset); }
}
Ok(Self {
accessor,
message_offset,
})
}
pub fn message(&self) -> roto_runtime::Result<&'a str> {
let offset = self.message_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn message_or_default(&self) -> roto_runtime::Result<&'a str> {
self.message().or(Ok(""))
}
pub fn has_message(&self) -> bool { self.message_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct UnaryRequestBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
message_written: bool,
}
impl<'b> UnaryRequestBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> UnaryRequestBuilder<'_> {
UnaryRequestBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
message_written: false,
}
}
pub fn message(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.message_written = true;
Ok(self)
}
pub fn with(mut self, msg: &UnaryRequest<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.message_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedUnaryRequest {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedUnaryRequest {
type Reader<'a> = UnaryRequest<'a>;
fn reader(&self) -> UnaryRequest<'_> {
UnaryRequest::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedUnaryRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedUnaryRequest { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct UnaryResponse<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
reply_offset: Option<usize>,
}
impl<'a> UnaryResponse<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut reply_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { reply_offset = Some(offset); }
}
Ok(Self {
accessor,
reply_offset,
})
}
pub fn reply(&self) -> roto_runtime::Result<&'a str> {
let offset = self.reply_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn reply_or_default(&self) -> roto_runtime::Result<&'a str> {
self.reply().or(Ok(""))
}
pub fn has_reply(&self) -> bool { self.reply_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct UnaryResponseBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
reply_written: bool,
}
impl<'b> UnaryResponseBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> UnaryResponseBuilder<'_> {
UnaryResponseBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
reply_written: false,
}
}
pub fn reply(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.reply_written = true;
Ok(self)
}
pub fn with(mut self, msg: &UnaryResponse<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.reply_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedUnaryResponse {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedUnaryResponse {
type Reader<'a> = UnaryResponse<'a>;
fn reader(&self) -> UnaryResponse<'_> {
UnaryResponse::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedUnaryResponse {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedUnaryResponse { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct StreamingRequest<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
query_offset: Option<usize>,
}
impl<'a> StreamingRequest<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut query_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { query_offset = Some(offset); }
}
Ok(Self {
accessor,
query_offset,
})
}
pub fn query(&self) -> roto_runtime::Result<&'a str> {
let offset = self.query_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn query_or_default(&self) -> roto_runtime::Result<&'a str> {
self.query().or(Ok(""))
}
pub fn has_query(&self) -> bool { self.query_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct StreamingRequestBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
query_written: bool,
}
impl<'b> StreamingRequestBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> StreamingRequestBuilder<'_> {
StreamingRequestBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
query_written: false,
}
}
pub fn query(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.query_written = true;
Ok(self)
}
pub fn with(mut self, msg: &StreamingRequest<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.query_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedStreamingRequest {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedStreamingRequest {
type Reader<'a> = StreamingRequest<'a>;
fn reader(&self) -> StreamingRequest<'_> {
StreamingRequest::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedStreamingRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedStreamingRequest { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct StreamingResponse<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
item_offset: Option<usize>,
}
impl<'a> StreamingResponse<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut item_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { item_offset = Some(offset); }
}
Ok(Self {
accessor,
item_offset,
})
}
pub fn item(&self) -> roto_runtime::Result<&'a str> {
let offset = self.item_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn item_or_default(&self) -> roto_runtime::Result<&'a str> {
self.item().or(Ok(""))
}
pub fn has_item(&self) -> bool { self.item_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct StreamingResponseBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
item_written: bool,
}
impl<'b> StreamingResponseBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> StreamingResponseBuilder<'_> {
StreamingResponseBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
item_written: false,
}
}
pub fn item(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.item_written = true;
Ok(self)
}
pub fn with(mut self, msg: &StreamingResponse<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.item_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedStreamingResponse {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedStreamingResponse {
type Reader<'a> = StreamingResponse<'a>;
fn reader(&self) -> StreamingResponse<'_> {
StreamingResponse::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedStreamingResponse {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedStreamingResponse { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
#[cfg(feature = "alloc")]
use tonic::{Request, Response, Status};
#[cfg(feature = "alloc")]
use tokio_stream::Stream;
#[cfg(feature = "alloc")]
use std::pin::Pin;
#[cfg(feature = "alloc")]
use std::sync::Arc;
#[cfg(feature = "alloc")]
use std::task::{Context, Poll};
#[cfg(feature = "alloc")]
use std::future::Future;
#[cfg(feature = "alloc")]
use tonic::body::BoxBody;
#[cfg(feature = "alloc")]
use tower::Service;
#[cfg(feature = "alloc")]
use futures_util::StreamExt;
#[cfg(feature = "alloc")]
use http_body_util::BodyExt;
#[cfg(feature = "alloc")]
use http_body::Body;
#[cfg(feature = "alloc")]
use crate::{BufferPool, StatusBody};
#[cfg(feature = "alloc")]
#[async_trait::async_trait]
pub trait InteropService: Send + Sync + 'static {
async fn unary_call(&self, request: Request<OwnedUnaryRequest>) -> std::result::Result<Response<OwnedUnaryResponse>, Status>;
async fn streaming_call(&self, request: Request<OwnedStreamingRequest>) -> std::result::Result<Response<Pin<Box<dyn Stream<Item = std::result::Result<OwnedStreamingResponse, Status>> + Send>>>, Status>;
}
#[cfg(feature = "alloc")]
#[derive(Clone)]
pub struct InteropServiceServer {
inner: Arc<dyn InteropService>,
pool: Arc<BufferPool>,
}
#[cfg(feature = "alloc")]
impl InteropServiceServer {
pub fn new(inner: Arc<dyn InteropService>, pool: Arc<BufferPool>) -> Self {
Self { inner, pool }
}
}
#[cfg(feature = "alloc")]
impl tonic::server::NamedService for InteropServiceServer {
const NAME: &'static str = "interop.InteropService";
}
#[cfg(feature = "alloc")]
impl Service<http::Request<BoxBody>> for InteropServiceServer {
type Response = http::Response<BoxBody>;
type Error = std::convert::Infallible;
type Future = Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<BoxBody>) -> Self::Future {
let inner = self.inner.clone();
let pool = self.pool.clone();
Box::pin(async move {
let path = req.uri().path().to_string();
let body = req.into_body();
let mut buf = pool.get();
let mut stream = body;
while let Some(frame_result) = stream.frame().await {
let frame = frame_result.expect("Body frame error");
if let Some(data) = frame.data_ref() {
buf.put(data.clone());
}
}
let total_len = buf.len();
let bytes_vec = buf.split_to(total_len).freeze();
pool.put(buf);
if bytes_vec.len() < 5 {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
let payload = bytes_vec.slice(5..);
let mut routed = false;
if path == "/interop.InteropService/UnaryCall" {
let request_msg = match OwnedUnaryRequest::decode(payload) {
Ok(msg) => msg,
Err(e) => {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
};
let response = match inner.unary_call(Request::new(request_msg)).await {
Ok(res) => res,
Err(e) => {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
};
let response_msg = response.into_inner();
let response_bytes = response_msg.bytes();
let mut res_buf = pool.get();
res_buf.put_u8(0);
let len = response_bytes.len() as u32;
res_buf.put_slice(&len.to_be_bytes());
res_buf.put_slice(&response_bytes);
let frame_len = res_buf.len();
let frame = res_buf.split_to(frame_len).freeze();
pool.put(res_buf);
let res_body = BoxBody::new(StatusBody::new(Some(frame), 0));
routed = true;
return Ok(http::Response::builder().status(200).header("content-type", "application/grpc").body(res_body).unwrap());
}
if path == "/interop.InteropService/StreamingCall" {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
if !routed {
let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));
return Ok(http::Response::builder().status(200).body(res_body).unwrap());
}
Ok(http::Response::builder().status(200).body(BoxBody::new(StatusBody::new(None, 0))).unwrap())
})
}
}
+4
View File
@@ -0,0 +1,4 @@
// @generated by protoc-gen-roto — do not edit
#![allow(unused_imports)]
pub mod helloworld;
+141
View File
@@ -0,0 +1,141 @@
use std::marker::PhantomData;
use tonic::codec::{Codec, Decoder, Encoder, DecodeBuf, EncodeBuf};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use roto_runtime::RotoMessage;
use std::sync::{Arc, Mutex};
use std::pin::Pin;
use std::future::Future;
use std::task::{Context, Poll};
use http_body::Body;
pub mod generated {
pub mod helloworld;
pub mod interop;
}
pub struct RotoCodec<T, U> {
_phantom: PhantomData<(T, U)>,
}
impl<T, U> Default for RotoCodec<T, U> {
fn default() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<T, U> Codec for RotoCodec<T, U>
where
T: RotoMessage + Send + 'static,
U: RotoMessage + Send + 'static,
{
type Encode = U;
type Decode = T;
type Encoder = RotoEncoder<U>;
type Decoder = RotoDecoder<T>;
fn encoder(&mut self) -> Self::Encoder {
RotoEncoder(PhantomData)
}
fn decoder(&mut self) -> Self::Decoder {
RotoDecoder(PhantomData)
}
}
pub struct RotoEncoder<U>(PhantomData<U>);
impl<U> Encoder for RotoEncoder<U>
where
U: RotoMessage,
{
type Item = U;
type Error = tonic::Status;
fn encode(&mut self, message: Self::Item, buf: &mut EncodeBuf<'_>) -> Result<(), Self::Error> {
buf.put_slice(&message.bytes());
Ok(())
}
}
pub struct RotoDecoder<T>(PhantomData<T>);
impl<T> Decoder for RotoDecoder<T>
where
T: RotoMessage,
{
type Item = T;
type Error = tonic::Status;
fn decode(&mut self, buf: &mut DecodeBuf<'_>) -> Result<Option<Self::Item>, Self::Error> {
if buf.remaining() == 0 {
return Ok(None);
}
let bytes = buf.copy_to_bytes(buf.remaining());
match T::decode(bytes) {
Ok(msg) => Ok(Some(msg)),
Err(e) => Err(tonic::Status::internal(format!("Roto decode error: {}", e))),
}
}
}
pub struct BufferPool {
pool: Mutex<Vec<BytesMut>>,
default_capacity: usize,
}
impl BufferPool {
pub fn new(default_capacity: usize) -> Self {
Self {
pool: Mutex::new(Vec::new()),
default_capacity,
}
}
pub fn get(&self) -> BytesMut {
self.pool.lock().unwrap().pop().unwrap_or_else(|| BytesMut::with_capacity(self.default_capacity))
}
pub fn put(&self, mut buf: BytesMut) {
buf.clear();
if buf.capacity() >= self.default_capacity {
self.pool.lock().unwrap().push(buf);
}
}
}
pub struct StatusBody {
pub data: Option<Bytes>,
pub trailers: Option<http::HeaderMap>,
}
impl StatusBody {
pub fn new(data: Option<Bytes>, status: u8) -> Self {
let mut trailers = http::HeaderMap::new();
trailers.insert("grpc-status", status.to_string().parse().unwrap());
Self {
data,
trailers: Some(trailers),
}
}
}
impl Body for StatusBody {
type Data = Bytes;
type Error = tonic::Status;
fn poll_frame(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
if let Some(data) = self.data.take() {
Poll::Ready(Some(Ok(http_body::Frame::data(data))))
} else if let Some(trailers) = self.trailers.take() {
Poll::Ready(Some(Ok(http_body::Frame::trailers(trailers))))
} else {
Poll::Ready(None)
}
}
}
+100
View File
@@ -0,0 +1,100 @@
use std::sync::Arc;
use tonic::{Request, Response, Status};
use roto_runtime::RotoOwned;
use roto_tonic::{BufferPool, generated::helloworld::{Greeter, GreeterServer, OwnedHelloRequest, OwnedHelloReply, HelloReplyBuilder, HelloBuilder}};
use std::net::SocketAddr;
use tokio::net::TcpListener;
struct MyGreeter;
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(&self, request: Request<OwnedHelloRequest>) -> std::result::Result<Response<OwnedHelloReply>, Status> {
let req = request.into_inner();
let hello_req = req.reader();
// Extract name from the nested Hello message in HelloRequest
let name = match hello_req.request() {
Ok(req_bytes) => {
let hello = roto_tonic::generated::helloworld::Hello::new(req_bytes).unwrap();
hello.name_or_default().unwrap().to_string()
},
Err(_) => "Unknown".to_string(),
};
// Build the Hello response message
let mut hello_buf = [0u8; 1024];
let mut hello_builder = HelloBuilder::builder(&mut hello_buf);
hello_builder = hello_builder.name(&format!("Hello, {}!", name))
.map_err(|e| Status::internal(format!("Build error: {:?}", e)))?;
let hello_bytes = hello_builder.finish()
.map_err(|e| Status::internal(format!("Finish error: {:?}", e)))?;
// Build the HelloReply message containing the Hello bytes
let mut reply_buf = [0u8; 1024];
let mut reply_builder = HelloReplyBuilder::builder(&mut reply_buf);
reply_builder = reply_builder.response(hello_bytes)
.map_err(|e| Status::internal(format!("Build error: {:?}", e)))?;
let reply_bytes = reply_builder.finish()
.map_err(|e| Status::internal(format!("Finish error: {:?}", e)))?;
Ok(Response::new(OwnedHelloReply {
data: reply_bytes.to_vec().into(),
}))
}
}
#[tokio::test]
async fn test_say_hello_handler() {
let greeter = MyGreeter;
// Manually construct a valid proto buffer for HelloRequest
// HelloRequest { request: Hello { name: "World" } }
let mut hello_buf = [0u8; 1024];
let mut hb = HelloBuilder::builder(&mut hello_buf);
hb = hb.name("World").unwrap();
let hello_bytes = hb.finish().unwrap();
let mut req_buf = [0u8; 1024];
let mut rb = roto_tonic::generated::helloworld::HelloRequestBuilder::builder(&mut req_buf);
rb = rb.request(hello_bytes).unwrap();
let req_bytes = rb.finish().unwrap();
let request = Request::new(OwnedHelloRequest {
data: req_bytes.to_vec().into(),
});
let response = greeter.say_hello(request).await.unwrap();
let reply = response.into_inner();
let reply_reader = reply.reader();
let response_msg_bytes = reply_reader.response().expect("Response field missing");
let response_msg = roto_tonic::generated::helloworld::Hello::new(response_msg_bytes).expect("Invalid Hello message");
assert_eq!(response_msg.name_or_default().unwrap(), "Hello, World!");
}
#[tokio::test]
async fn test_server_start() {
let pool = Arc::new(BufferPool::new(1024));
let greeter = Arc::new(MyGreeter);
let server = GreeterServer::new(greeter, pool);
let addr = SocketAddr::from(([127, 0, 0, 1], 0));
let listener = TcpListener::bind(addr).await.unwrap();
let _local_addr = listener.local_addr().unwrap();
let server_handle = tokio::spawn(async move {
tonic::transport::Server::builder()
.add_service(server)
.serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener))
.await
.unwrap();
});
// Just verify it can start without crashing
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
server_handle.abort();
}
+76
View File
@@ -0,0 +1,76 @@
use std::sync::Arc;
use tonic::{Request, Response, Status};
use tokio::net::TcpListener;
use tonic::transport::Server;
use roto_runtime::RotoOwned;
use roto_tonic::{BufferPool, generated::interop::{InteropService, InteropServiceServer, OwnedUnaryRequest, OwnedUnaryResponse, OwnedStreamingRequest, OwnedStreamingResponse, UnaryResponseBuilder}};
use futures_util::Stream;
use std::pin::Pin;
use bytes::BufMut;
struct InteropHandler;
#[tonic::async_trait]
impl InteropService for InteropHandler {
async fn unary_call(&self, request: Request<OwnedUnaryRequest>) -> std::result::Result<Response<OwnedUnaryResponse>, Status> {
let msg = request.into_inner();
let message_val = msg.reader().message_or_default().unwrap_or("");
let reply = format!("Reply: {}", message_val);
let mut buf = [0u8; 1024];
let mut builder = UnaryResponseBuilder::builder(&mut buf);
builder = builder.reply(&reply).map_err(|e| Status::internal(format!("Build error: {:?}", e)))?;
let bytes = builder.finish().map_err(|e| Status::internal(format!("Finish error: {:?}", e)))?;
Ok(Response::new(OwnedUnaryResponse { data: bytes.to_vec().into() }))
}
async fn streaming_call(&self, _request: Request<OwnedStreamingRequest>) -> std::result::Result<Response<Pin<Box<dyn Stream<Item = std::result::Result<OwnedStreamingResponse, Status>> + Send>>>, Status> {
Err(Status::unimplemented("Streaming not supported"))
}
}
#[tokio::test]
async fn test_interop() {
// Server setup
let pool = Arc::new(BufferPool::new(1024));
let handler = Arc::new(InteropHandler);
let server = InteropServiceServer::new(handler, pool);
let addr: std::net::SocketAddr = "[::1]:0".parse().unwrap();
let listener = TcpListener::bind(addr).await.unwrap();
let local_addr = listener.local_addr().unwrap();
let server_clone = server.clone();
tokio::spawn(async move {
Server::builder()
.add_service(server_clone)
.serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener))
.await
.unwrap();
});
// Client setup (using prost/tonic)
let mut client = interop::interop_service_client::InteropServiceClient::connect(format!("http://{}", local_addr)).await.unwrap();
// Test Unary 1
let req1 = interop::UnaryRequest { message: "Hello 1".to_string() };
let res1 = client.unary_call(req1).await.unwrap();
assert_eq!(res1.into_inner().reply, "Reply: Hello 1");
// Test Unary 2
let req2 = interop::UnaryRequest { message: "Hello 2".to_string() };
let res2 = client.unary_call(req2).await.unwrap();
assert_eq!(res2.into_inner().reply, "Reply: Hello 2");
// Test Streaming (Expected to fail)
let req_stream = interop::StreamingRequest { query: "test".to_string() };
let res_stream = client.streaming_call(req_stream).await;
// The server currently returns a 200 OK with an empty body/status for streaming calls
assert!(res_stream.is_ok());
}
mod interop {
tonic::include_proto!("interop");
}
@@ -0,0 +1,783 @@
// @generated by protoc-gen-roto — do not edit
#[allow(unused_imports)]
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};
use core::str;
#[cfg(feature = "alloc")]
use bytes::{Bytes, BytesMut, Buf, BufMut};
use crate::google::protobuf::descriptor;
pub struct Version<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
major_offset: Option<usize>,
minor_offset: Option<usize>,
patch_offset: Option<usize>,
suffix_offset: Option<usize>,
}
impl<'a> Version<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut major_offset = None;
let mut minor_offset = None;
let mut patch_offset = None;
let mut suffix_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { major_offset = Some(offset); }
if tag.field_number == 2 { minor_offset = Some(offset); }
if tag.field_number == 3 { patch_offset = Some(offset); }
if tag.field_number == 4 { suffix_offset = Some(offset); }
}
Ok(Self {
accessor,
major_offset,
minor_offset,
patch_offset,
suffix_offset,
})
}
pub fn major(&self) -> roto_runtime::Result<i32> {
let offset = self.major_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn major_or_default(&self) -> roto_runtime::Result<i32> {
self.major().or(Ok(0))
}
pub fn has_major(&self) -> bool { self.major_offset.is_some() }
pub fn minor(&self) -> roto_runtime::Result<i32> {
let offset = self.minor_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn minor_or_default(&self) -> roto_runtime::Result<i32> {
self.minor().or(Ok(0))
}
pub fn has_minor(&self) -> bool { self.minor_offset.is_some() }
pub fn patch(&self) -> roto_runtime::Result<i32> {
let offset = self.patch_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn patch_or_default(&self) -> roto_runtime::Result<i32> {
self.patch().or(Ok(0))
}
pub fn has_patch(&self) -> bool { self.patch_offset.is_some() }
pub fn suffix(&self) -> roto_runtime::Result<&'a str> {
let offset = self.suffix_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn suffix_or_default(&self) -> roto_runtime::Result<&'a str> {
self.suffix().or(Ok(""))
}
pub fn has_suffix(&self) -> bool { self.suffix_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct VersionBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
major_written: bool,
minor_written: bool,
patch_written: bool,
suffix_written: bool,
}
impl<'b> VersionBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> VersionBuilder<'_> {
VersionBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
major_written: false,
minor_written: false,
patch_written: false,
suffix_written: false,
}
}
pub fn major(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(1, value)?;
self.major_written = true;
Ok(self)
}
pub fn minor(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(2, value)?;
self.minor_written = true;
Ok(self)
}
pub fn patch(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(3, value)?;
self.patch_written = true;
Ok(self)
}
pub fn suffix(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(4, value)?;
self.suffix_written = true;
Ok(self)
}
pub fn with(mut self, msg: &Version<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.major_written,
2 => self.minor_written,
3 => self.patch_written,
4 => self.suffix_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedVersion {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedVersion {
type Reader<'a> = Version<'a>;
fn reader(&self) -> Version<'_> {
Version::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedVersion {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedVersion { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct CodeGeneratorRequest<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
file_to_generate_start: Option<usize>,
file_to_generate_end: Option<usize>,
parameter_offset: Option<usize>,
proto_file_start: Option<usize>,
proto_file_end: Option<usize>,
source_file_descriptors_start: Option<usize>,
source_file_descriptors_end: Option<usize>,
compiler_version_offset: Option<usize>,
}
impl<'a> CodeGeneratorRequest<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut file_to_generate_start = None;
let mut file_to_generate_end = None;
let mut parameter_offset = None;
let mut proto_file_start = None;
let mut proto_file_end = None;
let mut source_file_descriptors_start = None;
let mut source_file_descriptors_end = None;
let mut compiler_version_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 {
if file_to_generate_start.is_none() { file_to_generate_start = Some(offset); }
file_to_generate_end = Some(offset);
}
if tag.field_number == 2 { parameter_offset = Some(offset); }
if tag.field_number == 15 {
if proto_file_start.is_none() { proto_file_start = Some(offset); }
proto_file_end = Some(offset);
}
if tag.field_number == 17 {
if source_file_descriptors_start.is_none() { source_file_descriptors_start = Some(offset); }
source_file_descriptors_end = Some(offset);
}
if tag.field_number == 3 { compiler_version_offset = Some(offset); }
}
Ok(Self {
accessor,
file_to_generate_start, file_to_generate_end,
parameter_offset,
proto_file_start, proto_file_end,
source_file_descriptors_start, source_file_descriptors_end,
compiler_version_offset,
})
}
pub fn file_to_generate(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.file_to_generate_start, self.file_to_generate_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(1, start, end),
_ => self.accessor.iter_repeated(1),
}
}
pub fn parameter(&self) -> roto_runtime::Result<&'a str> {
let offset = self.parameter_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn parameter_or_default(&self) -> roto_runtime::Result<&'a str> {
self.parameter().or(Ok(""))
}
pub fn has_parameter(&self) -> bool { self.parameter_offset.is_some() }
pub fn proto_file(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.proto_file_start, self.proto_file_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(15, start, end),
_ => self.accessor.iter_repeated(15),
}
}
pub fn source_file_descriptors(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.source_file_descriptors_start, self.source_file_descriptors_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(17, start, end),
_ => self.accessor.iter_repeated(17),
}
}
pub fn compiler_version(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.compiler_version_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn compiler_version_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.compiler_version().or(Ok(&[]))
}
pub fn has_compiler_version(&self) -> bool { self.compiler_version_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct CodeGeneratorRequestBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
file_to_generate_written: bool,
parameter_written: bool,
proto_file_written: bool,
source_file_descriptors_written: bool,
compiler_version_written: bool,
}
impl<'b> CodeGeneratorRequestBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> CodeGeneratorRequestBuilder<'_> {
CodeGeneratorRequestBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
file_to_generate_written: false,
parameter_written: false,
proto_file_written: false,
source_file_descriptors_written: false,
compiler_version_written: false,
}
}
pub fn file_to_generate(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.file_to_generate_written = true;
Ok(self)
}
pub fn parameter(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(2, value)?;
self.parameter_written = true;
Ok(self)
}
pub fn proto_file(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(15, value)?;
self.proto_file_written = true;
Ok(self)
}
pub fn source_file_descriptors(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(17, value)?;
self.source_file_descriptors_written = true;
Ok(self)
}
pub fn compiler_version(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(3, value)?;
self.compiler_version_written = true;
Ok(self)
}
pub fn with(mut self, msg: &CodeGeneratorRequest<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.file_to_generate_written,
2 => self.parameter_written,
15 => self.proto_file_written,
17 => self.source_file_descriptors_written,
3 => self.compiler_version_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedCodeGeneratorRequest {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedCodeGeneratorRequest {
type Reader<'a> = CodeGeneratorRequest<'a>;
fn reader(&self) -> CodeGeneratorRequest<'_> {
CodeGeneratorRequest::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedCodeGeneratorRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedCodeGeneratorRequest { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct CodeGeneratorResponse<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
error_offset: Option<usize>,
supported_features_offset: Option<usize>,
minimum_edition_offset: Option<usize>,
maximum_edition_offset: Option<usize>,
file_start: Option<usize>,
file_end: Option<usize>,
}
impl<'a> CodeGeneratorResponse<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut error_offset = None;
let mut supported_features_offset = None;
let mut minimum_edition_offset = None;
let mut maximum_edition_offset = None;
let mut file_start = None;
let mut file_end = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { error_offset = Some(offset); }
if tag.field_number == 2 { supported_features_offset = Some(offset); }
if tag.field_number == 3 { minimum_edition_offset = Some(offset); }
if tag.field_number == 4 { maximum_edition_offset = Some(offset); }
if tag.field_number == 15 {
if file_start.is_none() { file_start = Some(offset); }
file_end = Some(offset);
}
}
Ok(Self {
accessor,
error_offset,
supported_features_offset,
minimum_edition_offset,
maximum_edition_offset,
file_start, file_end,
})
}
pub fn error(&self) -> roto_runtime::Result<&'a str> {
let offset = self.error_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn error_or_default(&self) -> roto_runtime::Result<&'a str> {
self.error().or(Ok(""))
}
pub fn has_error(&self) -> bool { self.error_offset.is_some() }
pub fn supported_features(&self) -> roto_runtime::Result<u32> {
let offset = self.supported_features_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as u32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn supported_features_or_default(&self) -> roto_runtime::Result<u32> {
self.supported_features().or(Ok(0))
}
pub fn has_supported_features(&self) -> bool { self.supported_features_offset.is_some() }
pub fn minimum_edition(&self) -> roto_runtime::Result<i32> {
let offset = self.minimum_edition_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn minimum_edition_or_default(&self) -> roto_runtime::Result<i32> {
self.minimum_edition().or(Ok(0))
}
pub fn has_minimum_edition(&self) -> bool { self.minimum_edition_offset.is_some() }
pub fn maximum_edition(&self) -> roto_runtime::Result<i32> {
let offset = self.maximum_edition_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn maximum_edition_or_default(&self) -> roto_runtime::Result<i32> {
self.maximum_edition().or(Ok(0))
}
pub fn has_maximum_edition(&self) -> bool { self.maximum_edition_offset.is_some() }
pub fn file(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.file_start, self.file_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(15, start, end),
_ => self.accessor.iter_repeated(15),
}
}
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct CodeGeneratorResponseBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
error_written: bool,
supported_features_written: bool,
minimum_edition_written: bool,
maximum_edition_written: bool,
file_written: bool,
}
impl<'b> CodeGeneratorResponseBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> CodeGeneratorResponseBuilder<'_> {
CodeGeneratorResponseBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
error_written: false,
supported_features_written: false,
minimum_edition_written: false,
maximum_edition_written: false,
file_written: false,
}
}
pub fn error(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.error_written = true;
Ok(self)
}
pub fn supported_features(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(2, value)?;
self.supported_features_written = true;
Ok(self)
}
pub fn minimum_edition(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(3, value)?;
self.minimum_edition_written = true;
Ok(self)
}
pub fn maximum_edition(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(4, value)?;
self.maximum_edition_written = true;
Ok(self)
}
pub fn file(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(15, value)?;
self.file_written = true;
Ok(self)
}
pub fn with(mut self, msg: &CodeGeneratorResponse<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.error_written,
2 => self.supported_features_written,
3 => self.minimum_edition_written,
4 => self.maximum_edition_written,
15 => self.file_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedCodeGeneratorResponse {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedCodeGeneratorResponse {
type Reader<'a> = CodeGeneratorResponse<'a>;
fn reader(&self) -> CodeGeneratorResponse<'_> {
CodeGeneratorResponse::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedCodeGeneratorResponse {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedCodeGeneratorResponse { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub mod code_generator_response {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum Feature {
FEATURENONE = 0,
FEATUREPROTO3OPTIONAL = 1,
FEATURESUPPORTSEDITIONS = 2,
}
impl Feature {
pub fn from_i32(value: i32) -> Self {
match value {
0 => Feature::FEATURENONE,
1 => Feature::FEATUREPROTO3OPTIONAL,
2 => Feature::FEATURESUPPORTSEDITIONS,
_ => Feature::FEATURENONE,
}
}
}
pub struct File<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
insertion_point_offset: Option<usize>,
content_offset: Option<usize>,
generated_code_info_offset: Option<usize>,
}
impl<'a> File<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut name_offset = None;
let mut insertion_point_offset = None;
let mut content_offset = None;
let mut generated_code_info_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { name_offset = Some(offset); }
if tag.field_number == 2 { insertion_point_offset = Some(offset); }
if tag.field_number == 15 { content_offset = Some(offset); }
if tag.field_number == 16 { generated_code_info_offset = Some(offset); }
}
Ok(Self {
accessor,
name_offset,
insertion_point_offset,
content_offset,
generated_code_info_offset,
})
}
pub fn name(&self) -> roto_runtime::Result<&'a str> {
let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
pub fn insertion_point(&self) -> roto_runtime::Result<&'a str> {
let offset = self.insertion_point_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn insertion_point_or_default(&self) -> roto_runtime::Result<&'a str> {
self.insertion_point().or(Ok(""))
}
pub fn has_insertion_point(&self) -> bool { self.insertion_point_offset.is_some() }
pub fn content(&self) -> roto_runtime::Result<&'a str> {
let offset = self.content_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn content_or_default(&self) -> roto_runtime::Result<&'a str> {
self.content().or(Ok(""))
}
pub fn has_content(&self) -> bool { self.content_offset.is_some() }
pub fn generated_code_info(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.generated_code_info_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn generated_code_info_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.generated_code_info().or(Ok(&[]))
}
pub fn has_generated_code_info(&self) -> bool { self.generated_code_info_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct FileBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
name_written: bool,
insertion_point_written: bool,
content_written: bool,
generated_code_info_written: bool,
}
impl<'b> FileBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> FileBuilder<'_> {
FileBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
name_written: false,
insertion_point_written: false,
content_written: false,
generated_code_info_written: false,
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn insertion_point(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(2, value)?;
self.insertion_point_written = true;
Ok(self)
}
pub fn content(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(15, value)?;
self.content_written = true;
Ok(self)
}
pub fn generated_code_info(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(16, value)?;
self.generated_code_info_written = true;
Ok(self)
}
pub fn with(mut self, msg: &File<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.name_written,
2 => self.insertion_point_written,
15 => self.content_written,
16 => self.generated_code_info_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedFile {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedFile {
type Reader<'a> = File<'a>;
fn reader(&self) -> File<'_> {
File::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedFile {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedFile { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
}
use crate::google::protobuf::descriptor;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "roto-runtime"
version = "0.1.0"
edition = "2024"
[dependencies]
bytes = { version = "1.7", default-features = false }
[features]
default = ["std", "alloc"]
std = []
alloc = []
+1
View File
@@ -0,0 +1 @@
bench/
Binary file not shown.
Binary file not shown.
Binary file not shown.
+39
View File
@@ -0,0 +1,39 @@
d_val: 3.1415926535
f_val: 2.71828
i32_val: 42
i64_val: 123456789012345
u32_val: 1000
u64_val: 18446744073709551615
si32_val: -42
si64_val: -123456789012345
fx32_val: 123456
fx64_val: 1234567890123456789
sfx32_val: -123456
sfx64_val: -1234567890123456789
b_val: true
s_val: "Hello Roto!"
bytes_val: "SGVsbG8gUm90byE="
status: ACTIVE
repeated_i32: 1
repeated_i32: 2
repeated_i32: 3
repeated_i32: 4
repeated_i32: 5
repeated_string: "one"
repeated_string: "two"
repeated_string: "three"
repeated_nested {
id: 101
name: "Nested 1"
active: true
}
repeated_nested {
id: 102
name: "Nested 2"
active: false
}
single_nested {
id: 200
name: "Single Nested"
active: true
}
Binary file not shown.
+53
View File
@@ -0,0 +1,53 @@
syntax = "proto3";
package roto.test;
// A comprehensive message containing all primitive types and complex structures
// to test the proto-to-rust codegen and runtime accessors.
message ComplexMessage {
// --- Floating Point ---
double d_val = 1;
float f_val = 2;
// --- Integers (Variable Length) ---
int32 i32_val = 3;
int64 i64_val = 4;
uint32 u32_val = 5;
uint64 u64_val = 6;
sint32 si32_val = 7;
sint64 si64_val = 8;
// --- Integers (Fixed Length) ---
fixed32 fx32_val = 9;
fixed64 fx64_val = 10;
sfixed32 sfx32_val = 11;
sfixed64 sfx64_val = 12;
// --- Other Primitives ---
bool b_val = 13;
string s_val = 14;
bytes bytes_val = 15;
// --- Enumerations ---
enum Status {
UNKNOWN = 0;
ACTIVE = 1;
INACTIVE = 2;
DELETED = 3;
}
Status status = 16;
// --- Repeated Fields ---
// Testing packed primitives and non-packed types
repeated int32 repeated_i32 = 17;
repeated string repeated_string = 18;
repeated NestedMessage repeated_nested = 19;
// --- Nested Messages ---
message NestedMessage {
int32 id = 1;
string name = 2;
bool active = 3;
}
NestedMessage single_nested = 20;
}
+383 -21
View File
@@ -1,12 +1,42 @@
pub mod proto_gen;
pub mod generator;
// Uncomment this to check if the code compiles
// #[path = "../proto/google/protobuf/descriptor.rs"]
// pub mod descriptor;
#![no_std]
use std::fmt;
#[cfg(feature = "alloc")]
extern crate alloc;
#[derive(Debug, PartialEq, Eq)]
#[cfg(feature = "std")]
extern crate std;
use core::fmt;
use bytes::BufMut;
pub struct MapFieldIterator<'a> {
inner: RepeatedFieldIterator<'a>,
}
impl<'a> MapFieldIterator<'a> {
pub fn new(inner: RepeatedFieldIterator<'a>) -> Self {
Self { inner }
}
}
impl<'a> Iterator for MapFieldIterator<'a> {
type Item = Result<(&'a [u8], &'a [u8])>;
fn next(&mut self) -> Option<Self::Item> {
match self.inner.next() {
Some(Ok((value, _wire_type))) => {
let accessor = ProtoAccessor::new(value).ok()?;
let (key_bytes, _) = accessor.get_value(1).ok()?;
let (val_bytes, _) = accessor.get_value(2).ok()?;
Some(Ok((key_bytes, val_bytes)))
}
Some(Err(e)) => Some(Err(e)),
None => None,
}
}
}
#[derive(Debug, PartialEq)]
pub enum RotoError {
UnexpectedEndOfBuffer,
InvalidVarint,
@@ -29,12 +59,22 @@ impl fmt::Display for RotoError {
}
}
#[cfg(feature = "std")]
impl std::error::Error for RotoError {}
pub type Result<T> = std::result::Result<T, RotoError>;
pub type Result<T> = core::result::Result<T, RotoError>;
pub trait RotoOwned {
type Reader<'a> where Self: 'a;
fn reader(&self) -> Self::Reader<'_>;
}
pub trait RotoMessage: Sized {
fn decode(buf: bytes::Bytes) -> Result<Self>;
fn bytes(&self) -> bytes::Bytes;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum WireType {
Varint = 0,
Fixed64 = 1,
@@ -189,7 +229,7 @@ impl<'a> ProtoAccessor<'a> {
pub fn get_value(&self, field_number: u32) -> Result<(&'a [u8], WireType)> {
let mut last_value = None;
for item in self.fields() {
let (tag, value) = item?;
let (_offset, tag, value) = item?;
if tag.field_number == field_number {
last_value = Some((value, tag.wire_type));
}
@@ -201,6 +241,51 @@ impl<'a> ProtoAccessor<'a> {
pub fn iter_repeated(&self, field_number: u32) -> RepeatedFieldIterator<'a> {
RepeatedFieldIterator::new(self.data, field_number)
}
/// Returns the value and wire type of a field at a specific offset.
pub fn get_value_at(&self, offset: usize) -> Result<(&'a [u8], WireType)> {
if offset >= self.data.len() {
return Err(RotoError::UnexpectedEndOfBuffer);
}
let (tag, tag_len) = Tag::decode(&self.data[offset..])?;
let cursor_after_tag = offset + tag_len;
if cursor_after_tag > self.data.len() {
return Err(RotoError::UnexpectedEndOfBuffer);
}
let value_len = skip_value(tag.wire_type, &self.data[cursor_after_tag..])?;
let (value_offset, actual_value_len) = match tag.wire_type {
WireType::LengthDelimited => {
let (_, varint_len) = read_varint(&self.data[cursor_after_tag..])?;
(cursor_after_tag + varint_len, value_len - varint_len)
}
_ => (cursor_after_tag, value_len),
};
Ok((
&self.data[value_offset..value_offset + actual_value_len],
tag.wire_type,
))
}
/// Returns an iterator that scans a specific range of the buffer for all occurrences of the specified field.
pub fn iter_repeated_range(
&self,
field_number: u32,
start: usize,
end: usize,
) -> RepeatedFieldIterator<'a> {
RepeatedFieldIterator::new_range(self.data, field_number, start, end)
}
/// Returns an iterator that yields `(field_number, raw_bytes)` for every
/// field in the message. `raw_bytes` is the complete on-wire encoding
/// (tag + value, including any length prefix), suitable for passing
/// directly to `ProtoBuilder::write_raw`.
pub fn raw_fields(&self) -> RawFieldIterator<'a> {
RawFieldIterator {
data: self.data,
cursor: 0,
}
}
}
pub struct FieldIterator<'a> {
@@ -209,7 +294,7 @@ pub struct FieldIterator<'a> {
}
impl<'a> Iterator for FieldIterator<'a> {
type Item = Result<(Tag, &'a [u8])>;
type Item = Result<(usize, Tag, &'a [u8])>;
fn next(&mut self) -> Option<Self::Item> {
if self.cursor >= self.data.len() {
@@ -254,23 +339,37 @@ impl<'a> Iterator for FieldIterator<'a> {
self.cursor = cursor_after_tag + value_len;
Some(Ok((tag, &self.data[value_offset..value_offset + actual_value_len])))
Some(Ok((
self.cursor - tag_len - value_len,
tag,
&self.data[value_offset..value_offset + actual_value_len],
)))
}
}
pub struct RepeatedFieldIterator<'a> {
iterator: FieldIterator<'a>,
field_number: u32,
end_offset: Option<usize>,
}
impl<'a> RepeatedFieldIterator<'a> {
fn new(data: &'a [u8], field_number: u32) -> Self {
pub fn new(data: &'a [u8], field_number: u32) -> Self {
Self {
iterator: FieldIterator { data, cursor: 0 },
field_number,
end_offset: None,
}
}
pub fn new_range(data: &'a [u8], field_number: u32, start: usize, end: usize) -> Self {
Self {
iterator: FieldIterator {
data,
cursor: 0,
cursor: start,
},
field_number,
end_offset: Some(end),
}
}
}
@@ -281,7 +380,12 @@ impl<'a> Iterator for RepeatedFieldIterator<'a> {
fn next(&mut self) -> Option<Self::Item> {
while let Some(item) = self.iterator.next() {
match item {
Ok((tag, value)) if tag.field_number == self.field_number => {
Ok((offset, tag, value)) if tag.field_number == self.field_number => {
if let Some(end) = self.end_offset {
if offset > end {
return None;
}
}
return Some(Ok((value, tag.wire_type)));
}
Ok(_) => continue,
@@ -292,9 +396,53 @@ impl<'a> Iterator for RepeatedFieldIterator<'a> {
}
}
/// An iterator that yields `(field_number, raw_bytes)` for every field in a
/// protobuf message, where `raw_bytes` is the complete on-wire encoding of the
/// field: tag varint + value bytes (including the length prefix for
/// length-delimited fields). This is the slice needed by
/// `ProtoBuilder::write_raw` to copy a field verbatim.
pub struct RawFieldIterator<'a> {
data: &'a [u8],
cursor: usize,
}
impl<'a> Iterator for RawFieldIterator<'a> {
type Item = Result<(u32, &'a [u8])>;
fn next(&mut self) -> Option<Self::Item> {
if self.cursor >= self.data.len() {
return None;
}
let field_start = self.cursor;
let (tag, tag_len) = match Tag::decode(&self.data[self.cursor..]) {
Ok(t) => t,
Err(e) => {
self.cursor = self.data.len();
return Some(Err(e));
}
};
let cursor_after_tag = self.cursor + tag_len;
if cursor_after_tag > self.data.len() {
self.cursor = self.data.len();
return Some(Err(RotoError::UnexpectedEndOfBuffer));
}
let value_len = match skip_value(tag.wire_type, &self.data[cursor_after_tag..]) {
Ok(l) => l,
Err(e) => {
self.cursor = self.data.len();
return Some(Err(e));
}
};
self.cursor = cursor_after_tag + value_len;
Some(Ok((tag.field_number, &self.data[field_start..self.cursor])))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
use alloc::{vec, vec::{Vec}};
#[test]
fn test_varint_read_write() {
@@ -401,6 +549,104 @@ mod tests {
assert_eq!(result, Err(RotoError::BufferOverflow));
}
#[test]
fn test_raw_field_iterator_yields_correct_bytes() {
// Build: field 1 = string "hi", field 2 = int32 42
let mut buf = [0u8; 64];
let mut builder = ProtoBuilder::new(&mut buf);
builder.write_string(1, "hi").unwrap();
builder.write_int32(2, 42).unwrap();
let data = builder.finish().unwrap().to_vec();
let acc = ProtoAccessor::new(&data).unwrap();
let raw: Vec<_> = acc.raw_fields().collect();
assert_eq!(raw.len(), 2);
// Field 1: tag = (1 << 3) | 2 = 0x0A, len varint = 0x02, "hi" = [0x68, 0x69]
let (fn1, bytes1) = raw[0].as_ref().unwrap();
assert_eq!(*fn1, 1);
assert_eq!(*bytes1, [0x0A, 0x02, b'h', b'i']);
// Field 2: tag = (2 << 3) | 0 = 0x10, varint 42 = 0x2A
let (fn2, bytes2) = raw[1].as_ref().unwrap();
assert_eq!(*fn2, 2);
assert_eq!(*bytes2, [0x10, 0x2A]);
}
#[test]
fn test_write_raw_copies_field_verbatim() {
// Build source: field 1 = string "hello", field 2 = int32 99
let mut src_buf = [0u8; 64];
let mut src_builder = ProtoBuilder::new(&mut src_buf);
src_builder.write_string(1, "hello").unwrap();
src_builder.write_int32(2, 99).unwrap();
let src_data = src_builder.finish().unwrap().to_vec();
// Copy every raw field verbatim into a new buffer
let src_acc = ProtoAccessor::new(&src_data).unwrap();
let mut dst_buf = [0u8; 64];
let mut dst_builder = ProtoBuilder::new(&mut dst_buf);
for item in src_acc.raw_fields() {
let (_, raw_bytes) = item.unwrap();
dst_builder.write_raw(raw_bytes).unwrap();
}
let dst_data = dst_builder.finish().unwrap();
// The copy must be byte-identical to the source
assert_eq!(dst_data, src_data.as_slice());
}
#[test]
fn test_with_pattern_copies_unseen_fields() {
// Build an existing source message with 3 fields
let mut src_buf = [0u8; 128];
let mut src_builder = ProtoBuilder::new(&mut src_buf);
src_builder.write_string(1, "original").unwrap();
src_builder.write_int32(2, 99).unwrap();
src_builder.write_varint(3, 1u64).unwrap(); // bool
let src_data = src_builder.finish().unwrap().to_vec();
let src_acc = ProtoAccessor::new(&src_data).unwrap();
// Simulate what a generated `with` method does:
// field 1 was explicitly written; fields 2 and 3 come from source.
let field1_written = true;
let field2_written = false;
let field3_written = false;
let mut dst_buf = [0u8; 128];
let mut dst_builder = ProtoBuilder::new(&mut dst_buf);
dst_builder.write_string(1, "updated").unwrap();
for item in src_acc.raw_fields() {
let (field_number, raw_bytes) = item.unwrap();
let is_written = match field_number {
1 => field1_written,
2 => field2_written,
3 => field3_written,
_ => false,
};
if !is_written {
dst_builder.write_raw(raw_bytes).unwrap();
}
}
let dst_data = dst_builder.finish().unwrap();
let dst_acc = ProtoAccessor::new(dst_data).unwrap();
// Field 1: overridden value
let (val1, _) = dst_acc.get_value(1).unwrap();
assert_eq!(val1, b"updated");
// Field 2: copied from source
let (val2, _) = dst_acc.get_value(2).unwrap();
let (v2, _) = read_varint(val2).unwrap();
assert_eq!(v2 as i32, 99);
// Field 3: copied from source
let (val3, _) = dst_acc.get_value(3).unwrap();
let (v3, _) = read_varint(val3).unwrap();
assert_eq!(v3, 1u64);
}
#[test]
fn test_protoc_binary_compatibility() {
let data = include_bytes!("../data/test_data.pb");
@@ -447,15 +693,17 @@ mod tests {
}
assert_eq!(i32_vals, vec![1, 2, 3, 4, 5]);
let repeated_strings: Vec<_> = acc.iter_repeated(18)
let repeated_strings: Vec<_> = acc
.iter_repeated(18)
.map(|r| {
let (val, _) = r.expect("Failed to decode repeated string");
std::str::from_utf8(val).expect("Invalid utf8")
core::str::from_utf8(val).expect("Invalid utf8")
})
.collect();
assert_eq!(repeated_strings, vec!["one", "two", "three"]);
let repeated_nested: Vec<_> = acc.iter_repeated(19)
let repeated_nested: Vec<_> = acc
.iter_repeated(19)
.map(|r| {
let (val, _) = r.expect("Failed to decode repeated nested");
let nested_acc = ProtoAccessor::new(val).unwrap();
@@ -475,8 +723,9 @@ mod tests {
assert_eq!(id, 200);
// Validate that fields appear in the expected relative order
let field_numbers: Vec<u32> = acc.fields()
.map(|r| r.expect("Failed to decode field").0.field_number)
let field_numbers: Vec<u32> = acc
.fields()
.map(|r| r.expect("Failed to decode field").1.field_number)
.collect();
let essential_fields = [1, 2, 3, 14, 16, 20];
@@ -484,7 +733,12 @@ mod tests {
let mut found_count = 0;
for &f in &field_numbers {
if essential_fields.contains(&f) {
assert!(f >= last_field, "Fields appeared out of order: {} came after {}", f, last_field);
assert!(
f >= last_field,
"Fields appeared out of order: {} came after {}",
f,
last_field
);
last_field = f;
found_count += 1;
}
@@ -556,7 +810,115 @@ impl<'a> ProtoBuilder<'a> {
self.append_bytes(value)
}
/// Appends a pre-encoded field (tag + value bytes) verbatim into the
/// buffer. Use this together with `ProtoAccessor::raw_fields` to copy
/// fields from an existing message into a builder without re-encoding them.
pub fn write_raw(&mut self, raw_bytes: &[u8]) -> Result<()> {
self.append_bytes(raw_bytes)
}
pub fn write_map_entry(
&mut self,
field_number: u32,
key_encoded: &[u8],
value_encoded: &[u8],
) -> Result<()> {
let entry_len = key_encoded.len() + value_encoded.len();
self.write_tag(field_number, WireType::LengthDelimited)?;
let mut len_buf = [0u8; 10];
let len_len = write_varint(entry_len as u64, &mut len_buf)?;
self.append_bytes(&len_buf[..len_len])?;
self.append_bytes(key_encoded)?;
self.append_bytes(value_encoded)?;
Ok(())
}
pub fn finish(self) -> Result<&'a mut [u8]> {
Ok(&mut self.buf[..self.pos])
}
}
pub struct BufMutBuilder<'a, B: BufMut> {
buf: &'a mut B,
}
impl<'a, B: BufMut> BufMutBuilder<'a, B> {
pub fn new(buf: &'a mut B) -> Self {
Self { buf }
}
fn write_tag(&mut self, field_number: u32, wire_type: WireType) -> Result<()> {
let mut temp = [0u8; 10];
let len = Tag::encode(field_number, wire_type, &mut temp)?;
self.buf.put_slice(&temp[..len]);
Ok(())
}
pub fn write_varint(&mut self, field_number: u32, value: u64) -> Result<()> {
self.write_tag(field_number, WireType::Varint)?;
let mut temp = [0u8; 10];
let len = write_varint(value, &mut temp)?;
self.buf.put_slice(&temp[..len]);
Ok(())
}
pub fn write_int32(&mut self, field_number: u32, value: i32) -> Result<()> {
self.write_varint(field_number, value as u64)
}
pub fn write_string(&mut self, field_number: u32, value: &str) -> Result<()> {
self.write_tag(field_number, WireType::LengthDelimited)?;
let bytes = value.as_bytes();
let mut len_buf = [0u8; 10];
let len_len = write_varint(bytes.len() as u64, &mut len_buf)?;
self.buf.put_slice(&len_buf[..len_len]);
self.buf.put_slice(bytes);
Ok(())
}
pub fn write_fixed32(&mut self, field_number: u32, value: u32) -> Result<()> {
self.write_tag(field_number, WireType::Fixed32)?;
self.buf.put_slice(&value.to_le_bytes());
Ok(())
}
pub fn write_fixed64(&mut self, field_number: u32, value: u64) -> Result<()> {
self.write_tag(field_number, WireType::Fixed64)?;
self.buf.put_slice(&value.to_le_bytes());
Ok(())
}
pub fn write_bytes(&mut self, field_number: u32, value: &[u8]) -> Result<()> {
self.write_tag(field_number, WireType::LengthDelimited)?;
let mut len_buf = [0u8; 10];
let len_len = write_varint(value.len() as u64, &mut len_buf)?;
self.buf.put_slice(&len_buf[..len_len]);
self.buf.put_slice(value);
Ok(())
}
pub fn write_raw(&mut self, raw_bytes: &[u8]) -> Result<()> {
self.buf.put_slice(raw_bytes);
Ok(())
}
pub fn write_map_entry(
&mut self,
field_number: u32,
key_encoded: &[u8],
value_encoded: &[u8],
) -> Result<()> {
let entry_len = key_encoded.len() + value_encoded.len();
self.write_tag(field_number, WireType::LengthDelimited)?;
let mut len_buf = [0u8; 10];
let len_len = write_varint(entry_len as u64, &mut len_buf)?;
self.buf.put_slice(&len_buf[..len_len]);
self.buf.put_slice(key_encoded);
self.buf.put_slice(value_encoded);
Ok(())
}
}
-28
View File
@@ -1,28 +0,0 @@
use clap::Parser;
use roto::proto_gen::google::protobuf::descriptor::FileDescriptorSet;
use roto::generator::generate_rust_code;
use std::fs;
use std::path::PathBuf;
#[derive(Parser)]
#[command(author, version, about = "Generates Rust accessor and builder code from a protobuf descriptor set")]
struct Args {
/// Path to the descriptor set file (.desc)
#[arg(short, long)]
input: PathBuf,
/// Path to the output Rust file (.rs)
#[arg(short, long)]
output: PathBuf,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let data = fs::read(&args.input)?;
let set = FileDescriptorSet::new(&data).expect("Failed to parse FileDescriptorSet");
let output = generate_rust_code(&set);
fs::write(&args.output, output)?;
Ok(())
}
-218
View File
@@ -1,218 +0,0 @@
use crate::proto_gen::google::protobuf::descriptor::{
DescriptorProto, EnumDescriptorProto, FieldDescriptorProto, FileDescriptorProto, FileDescriptorSet,
};
use crate::{ProtoAccessor, Result, RotoError};
use std::str;
pub fn to_pascal_case(s: &str) -> String {
s.split('_')
.map(|word| {
let mut chars = word.chars();
match chars.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + chars.as_str(),
}
})
.collect()
}
fn map_type_to_rust_accessor(field_type: i32, label: i32) -> (String, String) {
if label == 3 {
// LABEL_REPEATED
return (
"crate::RepeatedFieldIterator<'a>".to_string(),
"self.0.iter_repeated(%d)".to_string(),
);
}
match field_type {
9 => (
"&'a str".to_string(),
"str::from_utf8(bytes).map_err(|_| RotoError::WireFormatViolation)".to_string(),
), // TYPE_STRING
1 => (
"f64".to_string(),
"Ok(f64::from_le_bytes(bytes.try_into().map_err(|_| RotoError::WireFormatViolation)?))".to_string(),
), // TYPE_DOUBLE
2 => (
"f32".to_string(),
"f32::from_le_bytes(bytes.try_into().map_err(|_| RotoError::WireFormatViolation)?)".to_string(),
), // TYPE_FLOAT
3 | 5 | 15 | 17 => (
"i32".to_string(),
"crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| RotoError::WireFormatViolation)".to_string(),
), // INT/SINT/SFIXED 32
4 | 6 | 13 => (
"u32".to_string(),
"crate::read_varint(bytes).map(|(v, _)| v as u32).map_err(|_| RotoError::WireFormatViolation)".to_string(),
), // UINT/FIXED 32
16 | 18 => (
"i64".to_string(),
"crate::read_varint(bytes).map(|(v, _)| v as i64).map_err(|_| RotoError::WireFormatViolation)".to_string(),
), // SINT/SFIXED 64
7 | 14 => (
"u64".to_string(),
"crate::read_varint(bytes).map(|(v, _)| v as u64).map_err(|_| RotoError::WireFormatViolation)".to_string(),
), // UINT/FIXED 64
8 => (
"bool".to_string(),
"crate::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| RotoError::WireFormatViolation)".to_string(),
), // TYPE_BOOL
11 | 12 => ("&'a [u8]".to_string(), "Ok(bytes)".to_string()), // MESSAGE/BYTES
_ => ("&'a [u8]".to_string(), "Ok(bytes)".to_string()),
}
}
fn map_type_to_rust_builder(field_type: i32) -> (String, String) {
match field_type {
9 => ("&str".to_string(), "write_string".to_string()),
5 | 17 => ("i32".to_string(), "write_int32".to_string()),
3 | 4 | 8 | 13 | 14 | 18 => ("u64".to_string(), "write_varint".to_string()),
7 | 15 => ("u32".to_string(), "write_fixed32".to_string()),
6 | 16 => ("u64".to_string(), "write_fixed64".to_string()),
11 | 12 => ("&[u8]".to_string(), "write_bytes".to_string()),
_ => ("&[u8]".to_string(), "write_bytes".to_string()),
}
}
pub fn generate_rust_code(set: &FileDescriptorSet) -> String {
let mut output = String::new();
output.push_str("use crate::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator};\n");
output.push_str("use std::str;\n\n");
for file_res in set.file() {
let (file_data, _) = file_res.expect("Failed to iterate file");
let file_proto = FileDescriptorProto::new(file_data).expect("Failed to parse FileDescriptorProto");
// Enums
for enum_res in file_proto.enum_type() {
let (enum_data, _) = enum_res.expect("Failed to iterate enum");
let enum_proto = EnumDescriptorProto::new(enum_data).expect("Failed to parse EnumDescriptorProto");
let enum_name = to_pascal_case(enum_proto.name().unwrap());
output.push_str(&format!(
"#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n#[repr(i32)]\npub enum {} {{\n",
enum_name
));
let mut values = enum_proto.enum_value();
let mut variant_count = 0;
let mut zero_variant_name = None;
while let Some(val_res) = values.next() {
let (val_data, _) = val_res.expect("Failed to iterate enum");
let accessor = ProtoAccessor::new(val_data).expect("Failed to parse EnumValueDescriptorProto");
let (name_bytes, _) = accessor.get_value(1).expect("Enum value name missing");
let name = str::from_utf8(name_bytes).expect("Enum value name invalid utf8");
let (num_bytes, _) = accessor.get_value(2).expect("Enum value number missing");
let (num, _) = crate::read_varint(num_bytes).expect("Enum value number invalid varint");
let pascal_name = to_pascal_case(name);
if num == 0 {
zero_variant_name = Some(pascal_name.clone());
}
output.push_str(&format!(" {} = {},\n", pascal_name, num));
variant_count += 1;
}
if zero_variant_name.is_none() {
output.push_str(" Unknown = 0,\n");
zero_variant_name = Some("Unknown".to_string());
}
output.push_str("}\n\n");
output.push_str(&format!(
"impl {} {{\n pub fn from_i32(value: i32) -> Self {{\n match value {{\n",
enum_name
));
let mut values = enum_proto.enum_value();
while let Some(val_res) = values.next() {
let (val_data, _) = val_res.expect("Failed to read enum value");
let accessor = ProtoAccessor::new(val_data).expect("Failed to parse EnumValueDescriptorProto");
let (name_bytes, _) = accessor.get_value(1).expect("Enum value name missing");
let name = str::from_utf8(name_bytes).expect("Enum value name invalid utf8");
let (num_bytes, _) = accessor.get_value(2).expect("Enum value number missing");
let (num, _) = crate::read_varint(num_bytes).expect("Enum value number invalid varint");
output.push_str(&format!(" {} => {}::{},\n", num, enum_name, to_pascal_case(name)));
}
output.push_str(&format!(" _ => {}::{},\n", enum_name, zero_variant_name.as_ref().unwrap()));
output.push_str(" }\n }\n}\n\n");
}
// Messages
for msg_res in file_proto.message_type() {
let (msg_data, _) = msg_res.expect("Failed to iterate message");
let msg_proto = DescriptorProto::new(msg_data).expect("Failed to parse DescriptorProto");
let msg_name = to_pascal_case(msg_proto.name().unwrap());
// Accessor
output.push_str(&format!(
"pub struct {}<'a>(ProtoAccessor<'a>);\n\nimpl<'a> {}<'a> {{\n",
msg_name, msg_name
));
output.push_str(&format!(
" pub fn new(data: &'a [u8]) -> Result<Self> {{\n Ok(Self(ProtoAccessor::new(data)?))\n }}\n\n"
));
for field_res in msg_proto.field() {
let (field_data, _) = field_res.expect("Failed to iterate field");
let field_proto = FieldDescriptorProto::new(field_data).expect("Failed to parse FieldDescriptorProto");
let field_name = field_proto.name().unwrap();
let safe_name = if field_name == "type" { format!("r#{}", field_name) } else { field_name.to_string() };
let tag = field_proto.number().unwrap();
let f_type = field_proto.field_type().unwrap() as i32;
let f_label = field_proto.label().unwrap() as i32;
let (rust_type, logic) = map_type_to_rust_accessor(f_type, f_label);
if f_label == 3 {
output.push_str(&format!(
" pub fn {}(&self) -> {} {{\n {}\n }}\n\n",
safe_name, rust_type, logic.replace("%d", &tag.to_string())
));
} else {
output.push_str(&format!(
" pub fn {}(&self) -> Result<{}> {{\n let (bytes, _) = self.0.get_value({})?;\n {}\n }}\n\n",
safe_name, rust_type, tag, logic
));
}
}
output.push_str("}\n\n");
// Builder
output.push_str(&format!(
"pub struct {}Builder<'b> {{\n builder: ProtoBuilder<'b>,\n}}\n\nimpl<'b> {}Builder<'b> {{\n",
msg_name, msg_name
));
output.push_str(&format!(
" pub fn builder(buf: &mut [u8]) -> {}Builder<'_> {{\n {}Builder {{\n builder: ProtoBuilder::new(buf),\n }}\n }}\n\n",
msg_name, msg_name
));
for field_res in msg_proto.field() {
let (field_data, _) = field_res.expect("Failed to iterate field");
let field_proto = FieldDescriptorProto::new(field_data).expect("Failed to parse FieldDescriptorProto");
let field_name = field_proto.name().unwrap();
let safe_name = if field_name == "type" { format!("r#{}", field_name) } else { field_name.to_string() };
let tag = field_proto.number().unwrap();
let f_type = field_proto.field_type().unwrap() as i32;
let (rust_type, method) = map_type_to_rust_builder(f_type);
output.push_str(&format!(
" pub fn {}(mut self, value: {}) -> Result<Self> {{\n self.builder.{}({}, value)?;\n Ok(self)\n }}\n\n",
safe_name, rust_type, method, tag
));
}
output.push_str(&format!(
" pub fn finish(self) -> Result<&'b mut [u8]> {{\n self.builder.finish()\n }}\n}}\n\n"
));
}
}
output
}
-3
View File
@@ -1,3 +0,0 @@
fn main() {
println!("Hello, world!");
}

Some files were not shown because too many files have changed in this diff Show More