Compare commits
55 Commits
858c6968d4
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| de6af24565 | |||
| 6085ef3923 | |||
| f03e1afdbf | |||
| 7e43e09f66 | |||
| 117cbf812b | |||
| 6910f11d69 | |||
| fa4d8cca83 | |||
| 956993d1d0 | |||
| b2c5639338 | |||
| 33f3e58f74 | |||
| 89455190b1 | |||
| b11b068345 | |||
| 56fc787f7a | |||
| 43dcfabcdc | |||
| 2202548ae5 | |||
| 809a0d844c | |||
| db89c9842a | |||
| 546b74149e | |||
| 372eab94d4 | |||
| a3fece24fc | |||
| cc82e990ba | |||
| a9fef01950 | |||
| da7ba47505 | |||
| 00b3dcd9a6 | |||
| 08be61966c | |||
| db2bf1bffd | |||
| dfdcd8ae46 | |||
| daa42d2d07 | |||
| 804ff3ead0 | |||
| 02a0b0d908 | |||
| 17ab0d1670 | |||
| 792f2d5f5d | |||
| 3280092e08 | |||
| 6e045fd808 | |||
| 7e368feddf | |||
| d9186e697e | |||
| 13625a48c9 | |||
| a20eed7223 | |||
| 8395195ac1 | |||
| f76a020b1e | |||
| 80f3aa49ba | |||
| 847c6cdc66 | |||
| e5c2479985 | |||
| d21ff797b0 | |||
| a0c2747583 | |||
| 40e342f221 | |||
| ea68537f0b | |||
| d71d96c15f | |||
| 3e5544d3d8 | |||
| 04ef952a58 | |||
| 05e4c275bb | |||
| 6821bd1cca | |||
| 4a6a09cff1 | |||
| b03ec9eba9 | |||
| ca81a2d010 |
@@ -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/
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "grpc_bench"]
|
||||
path = grpc_bench
|
||||
url = https://github.com/Lesnyrumcajs/grpc_bench.git
|
||||
@@ -7,6 +7,8 @@ you should be able to work without user assistance.
|
||||
|
||||
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
|
||||
@@ -14,6 +16,6 @@ If you are writing code, write tests first. The tests must pass for your work to
|
||||
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
|
||||
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
File diff suppressed because it is too large
Load Diff
+19
-8
@@ -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"
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
format!("{} is about {}", hello_world, inner_world)
|
||||
}
|
||||
```
|
||||
|
||||
### Sample builder usage
|
||||
### Reading
|
||||
|
||||
```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 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))
|
||||
}
|
||||
```
|
||||
|
||||
### High level design
|
||||
Fields absent from the binary data return `Err(roto::RotoError::FieldNotFound)`.
|
||||
|
||||
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.
|
||||
### Writing
|
||||
|
||||
### Literature
|
||||
Nested messages must be serialized into a scratch buffer first, then embedded as raw bytes in the
|
||||
outer builder.
|
||||
|
||||
https://protobuf.dev/programming-guides/encoding/
|
||||
```rust
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### Updating messages
|
||||
|
||||
You can read a message, modify specific fields, and use `.with()` to copy the remaining fields from the original binary.
|
||||
|
||||
```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 (~1–2 ns).
|
||||
> roto re-decodes the value at its pre-recorded byte offset on every call
|
||||
> (~7–17 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.
|
||||
|
||||
@@ -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`.
|
||||
@@ -0,0 +1 @@
|
||||
data
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
pub mod hackers;
|
||||
@@ -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"
|
||||
@@ -0,0 +1 @@
|
||||
bench/
|
||||
@@ -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
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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,13 +1,11 @@
|
||||
use env_logger::init;
|
||||
use log::{error, info};
|
||||
use roto::generator::generate_rust_code;
|
||||
use roto::google::protobuf::descriptor::{
|
||||
FileDescriptorSet
|
||||
};
|
||||
use roto::google::protobuf::compiler::plugin::{
|
||||
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() {
|
||||
@@ -43,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() {
|
||||
@@ -58,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
|
||||
})?;
|
||||
@@ -70,7 +70,8 @@ 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()
|
||||
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())
|
||||
@@ -98,12 +99,10 @@ fn handle_request(request: &CodeGeneratorRequest) -> std::result::Result<Vec<u8>
|
||||
resp_builder = resp_builder.file(final_file)?;
|
||||
}
|
||||
|
||||
let final_response_slice = resp_builder
|
||||
.finish()
|
||||
.map_err(|e| {
|
||||
error!("Failed to finish CodeGeneratorResponse: {:?}", e);
|
||||
e
|
||||
})?;
|
||||
let final_response_slice = resp_builder.finish().map_err(|e| {
|
||||
error!("Failed to finish CodeGeneratorResponse: {:?}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
// The finish() method returns a reference to the buffer.
|
||||
// We convert it to a Vec<u8> to return it from this function.
|
||||
@@ -125,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"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
use clap::Parser;
|
||||
use roto::google::protobuf::descriptor::{
|
||||
FileDescriptorSet
|
||||
};
|
||||
use roto::generator::generate_rust_code;
|
||||
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 accessor and builder code from a protobuf descriptor set")]
|
||||
#[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)]
|
||||
@@ -27,7 +29,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let data = fs::read(&args.input)?;
|
||||
let set = FileDescriptorSet::new(&data).expect("Failed to parse FileDescriptorSet");
|
||||
|
||||
let files = generate_rust_code(&set, args.files.as_deref(), true);
|
||||
let files = generate_service_code(&set, args.files.as_deref(), true);
|
||||
|
||||
for (filename, content) in files {
|
||||
let path = args.output.join(filename);
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -0,0 +1,2 @@
|
||||
pub mod compiler;
|
||||
pub mod descriptor;
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod generator;
|
||||
pub mod google;
|
||||
@@ -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.
@@ -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!"
|
||||
);
|
||||
}
|
||||
@@ -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!"
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
use roto::generator::generate_rust_code;
|
||||
use roto::google::protobuf::descriptor::{
|
||||
FileDescriptorSet
|
||||
};
|
||||
use roto_codegen::generator::generate_rust_code;
|
||||
use roto_codegen::google::protobuf::descriptor::FileDescriptorSet;
|
||||
use std::fs;
|
||||
|
||||
#[test]
|
||||
@@ -18,8 +16,9 @@ fn test_nested_proto_generation_contains_modules() {
|
||||
// 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::google::protobuf::compiler::plugin::CodeGeneratorRequest::new(&data)
|
||||
.expect("Failed to parse CodeGeneratorRequest");
|
||||
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() {
|
||||
@@ -27,7 +26,8 @@ fn test_nested_proto_generation_contains_modules() {
|
||||
set_buf.push(10);
|
||||
let len = file_data.len() as u64;
|
||||
let mut len_buf = [0u8; 10];
|
||||
let len_size = roto::write_varint(len, &mut len_buf).expect("Failed to write varint length");
|
||||
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);
|
||||
}
|
||||
@@ -35,12 +35,21 @@ fn test_nested_proto_generation_contains_modules() {
|
||||
|
||||
let generated_files = generate_rust_code(&set, None, false);
|
||||
|
||||
let all_code: String = generated_files.into_iter().map(|(_, content)| content).collect();
|
||||
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");
|
||||
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"
|
||||
);
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
@@ -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!"
|
||||
);
|
||||
}
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
@@ -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 = []
|
||||
@@ -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
|
||||
```
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target = "thumbv7em-none-eabihf"
|
||||
@@ -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"
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
Binary file not shown.
+773
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "protos"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn hello() {
|
||||
println!("Hello from protos!");
|
||||
}
|
||||
@@ -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 = []
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// @generated by protoc-gen-roto — do not edit
|
||||
#![allow(unused_imports)]
|
||||
|
||||
pub mod helloworld;
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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 = []
|
||||
@@ -0,0 +1 @@
|
||||
bench/
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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.
@@ -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;
|
||||
}
|
||||
+339
-21
@@ -1,12 +1,42 @@
|
||||
pub mod generator;
|
||||
pub mod google;
|
||||
// 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,
|
||||
@@ -220,13 +260,32 @@ impl<'a> ProtoAccessor<'a> {
|
||||
}
|
||||
_ => (cursor_after_tag, value_len),
|
||||
};
|
||||
Ok((&self.data[value_offset..value_offset + actual_value_len], tag.wire_type))
|
||||
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> {
|
||||
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> {
|
||||
@@ -280,7 +339,11 @@ impl<'a> Iterator for FieldIterator<'a> {
|
||||
|
||||
self.cursor = cursor_after_tag + value_len;
|
||||
|
||||
Some(Ok((self.cursor - tag_len - value_len, 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],
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,10 +356,7 @@ pub struct RepeatedFieldIterator<'a> {
|
||||
impl<'a> RepeatedFieldIterator<'a> {
|
||||
pub fn new(data: &'a [u8], field_number: u32) -> Self {
|
||||
Self {
|
||||
iterator: FieldIterator {
|
||||
data,
|
||||
cursor: 0,
|
||||
},
|
||||
iterator: FieldIterator { data, cursor: 0 },
|
||||
field_number,
|
||||
end_offset: None,
|
||||
}
|
||||
@@ -336,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() {
|
||||
@@ -445,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");
|
||||
@@ -491,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();
|
||||
@@ -519,7 +723,8 @@ mod tests {
|
||||
assert_eq!(id, 200);
|
||||
|
||||
// Validate that fields appear in the expected relative order
|
||||
let field_numbers: Vec<u32> = acc.fields()
|
||||
let field_numbers: Vec<u32> = acc
|
||||
.fields()
|
||||
.map(|r| r.expect("Failed to decode field").1.field_number)
|
||||
.collect();
|
||||
|
||||
@@ -528,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;
|
||||
}
|
||||
@@ -600,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(())
|
||||
}
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
use crate::google::protobuf::descriptor::{
|
||||
DescriptorProto, EnumDescriptorProto, FileDescriptorProto, FieldDescriptorProto, FileDescriptorSet
|
||||
};
|
||||
use crate::ProtoAccessor;
|
||||
use std::str;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn map_type_to_rust_accessor(field_type: i32, label: i32) -> (String, String) {
|
||||
if label == 3 {
|
||||
// LABEL_REPEATED
|
||||
return (
|
||||
"crate::RepeatedFieldIterator<'a>".to_string(),
|
||||
"".to_string(), // Not used for repeated fields in the same way
|
||||
);
|
||||
}
|
||||
|
||||
match field_type {
|
||||
9 => (
|
||||
"&'a str".to_string(),
|
||||
"str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)".to_string(),
|
||||
), // TYPE_STRING
|
||||
1 => (
|
||||
"f64".to_string(),
|
||||
"Ok(f64::from_le_bytes(bytes.try_into().map_err(|_| crate::RotoError::WireFormatViolation)?))".to_string(),
|
||||
), // TYPE_DOUBLE
|
||||
2 => (
|
||||
"f32".to_string(),
|
||||
"f32::from_le_bytes(bytes.try_into().map_err(|_| crate::RotoError::WireFormatViolation)?)".to_string(),
|
||||
), // TYPE_FLOAT
|
||||
3 | 5 | 15 | 17 => (
|
||||
"i32".to_string(),
|
||||
"crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::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(|_| crate::RotoError::WireFormatViolation)".to_string(),
|
||||
), // UINT/FIXED 32
|
||||
16 | 18 => (
|
||||
"i64".to_string(),
|
||||
"crate::read_varint(bytes).map(|(v, _)| v as i64).map_err(|_| crate::RotoError::WireFormatViolation)".to_string(),
|
||||
), // SINT/SFIXED 64
|
||||
7 | 14 => (
|
||||
"u64".to_string(),
|
||||
"crate::read_varint(bytes).map(|(v, _)| v as u64).map_err(|_| crate::RotoError::WireFormatViolation)".to_string(),
|
||||
), // UINT/FIXED 64
|
||||
8 => (
|
||||
"bool".to_string(),
|
||||
"crate::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| crate::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 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 = 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));
|
||||
}
|
||||
|
||||
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 = 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");
|
||||
}
|
||||
|
||||
fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
|
||||
let msg_name = to_pascal_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;
|
||||
|
||||
fields_info.push((field_name.to_string(), tag, f_type, f_label));
|
||||
}
|
||||
|
||||
output.push_str(&format!(
|
||||
"pub struct {}<'a> {{\n",
|
||||
msg_name
|
||||
));
|
||||
if !fields_info.is_empty() {
|
||||
output.push_str(" accessor: crate::ProtoAccessor<'a>,\n");
|
||||
}
|
||||
|
||||
for (field_name, _tag, _f_type, f_label) 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]) -> crate::Result<Self> {\n");
|
||||
if !fields_info.is_empty() {
|
||||
output.push_str(" let accessor = crate::ProtoAccessor::new(data)?;\n");
|
||||
|
||||
for (name, _, _, label) 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) 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");
|
||||
} else {
|
||||
output.push_str(" let _ = crate::ProtoAccessor::new(data)?;\n");
|
||||
}
|
||||
|
||||
output.push_str(" Ok(Self {\n");
|
||||
if !fields_info.is_empty() {
|
||||
output.push_str(" accessor,\n");
|
||||
}
|
||||
for (name, _, _, label) 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) in fields_info {
|
||||
let (rust_type, logic) = map_type_to_rust_accessor(f_type, f_label);
|
||||
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));
|
||||
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) -> crate::Result<{}> {{\n", safe_name, rust_type));
|
||||
output.push_str(&format!(" let offset = self.{}_offset.ok_or(crate::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("}\n\n");
|
||||
|
||||
// Builder
|
||||
output.push_str(&format!(
|
||||
"pub struct {}Builder<'b> {{\n builder: crate::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: crate::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.r#type().unwrap() as i32;
|
||||
let (rust_type, method) = map_type_to_rust_builder(f_type);
|
||||
output.push_str(&format!(
|
||||
" pub fn {}(mut self, value: {}) -> crate::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) -> crate::Result<&'b mut [u8]> {{\n self.builder.finish()\n }}\n}}\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() {
|
||||
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);
|
||||
}
|
||||
output.push_str("}\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
files_to_generate: Option<&[String]>,
|
||||
generate_mod_files: bool,
|
||||
) -> Vec<(String, String)> {
|
||||
let mut generated_files = Vec::new();
|
||||
|
||||
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");
|
||||
let proto_name = file_proto.name().expect("File proto name missing");
|
||||
|
||||
if let Some(filter) = files_to_generate {
|
||||
if !filter.contains(&proto_name.to_string()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let rust_file_name = format!("{}.rs", proto_name.replace(".proto", ""));
|
||||
|
||||
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 dep_res in file_proto.dependency() {
|
||||
let (dep_data, _) = dep_res.expect("Failed to iterate dependency");
|
||||
let dep_name = str::from_utf8(dep_data).expect("Dependency name invalid utf8");
|
||||
let dep_mod_path = dep_name.replace(".proto", "").replace('/', "::");
|
||||
output.push_str(&format!("use crate::{};\n", dep_mod_path));
|
||||
}
|
||||
output.push_str("\n");
|
||||
|
||||
// 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"), &mut 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"), &mut output);
|
||||
}
|
||||
generated_files.push((rust_file_name, output));
|
||||
}
|
||||
|
||||
if !generate_mod_files {
|
||||
return generated_files;
|
||||
}
|
||||
|
||||
let mut all_paths: Vec<String> = generated_files.iter().map(|(p, _)| p.clone()).collect();
|
||||
all_paths.sort();
|
||||
|
||||
let mut mod_files: HashMap<String, HashSet<String>> = HashMap::new();
|
||||
for path in &all_paths {
|
||||
let parts: Vec<&str> = path.split('/').collect();
|
||||
let mut current_dir = String::new();
|
||||
for i in 0..parts.len() - 1 {
|
||||
if !current_dir.is_empty() {
|
||||
current_dir.push('/');
|
||||
}
|
||||
current_dir.push_str(parts[i]);
|
||||
let mod_path = format!("{}/mod.rs", current_dir);
|
||||
let sub_mod = parts[i + 1].replace(".rs", "");
|
||||
mod_files.entry(mod_path).or_default().insert(sub_mod);
|
||||
}
|
||||
}
|
||||
|
||||
let mut root_mods = HashSet::new();
|
||||
for path in &all_paths {
|
||||
let parts: Vec<&str> = path.split('/').collect();
|
||||
root_mods.insert(parts[0].replace(".rs", ""));
|
||||
}
|
||||
|
||||
let mut root_mod_content = String::new();
|
||||
let mut sorted_root_mods: Vec<_> = root_mods.into_iter().collect();
|
||||
sorted_root_mods.sort();
|
||||
for m in sorted_root_mods {
|
||||
root_mod_content.push_str(&format!("pub mod {};\n", m));
|
||||
}
|
||||
generated_files.push(("mod.rs".to_string(), root_mod_content));
|
||||
|
||||
for (mod_path, sub_mods) in mod_files {
|
||||
let mut content = String::new();
|
||||
let mut sorted_subs: Vec<_> = sub_mods.into_iter().collect();
|
||||
sorted_subs.sort();
|
||||
for sub in sorted_subs {
|
||||
content.push_str(&format!("pub mod {};\n", sub));
|
||||
}
|
||||
generated_files.push((mod_path, content));
|
||||
}
|
||||
|
||||
generated_files
|
||||
}
|
||||
@@ -1,455 +0,0 @@
|
||||
use std::str;
|
||||
|
||||
// use crate::google::protobuf::descriptor;
|
||||
|
||||
pub struct Version<'a> {
|
||||
accessor: crate::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]) -> crate::Result<Self> {
|
||||
let accessor = crate::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) -> crate::Result<i32> {
|
||||
let offset = self.major_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
pub fn minor(&self) -> crate::Result<i32> {
|
||||
let offset = self.minor_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
pub fn patch(&self) -> crate::Result<i32> {
|
||||
let offset = self.patch_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
pub fn suffix(&self) -> crate::Result<&'a str> {
|
||||
let offset = self.suffix_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub struct VersionBuilder<'b> {
|
||||
builder: crate::ProtoBuilder<'b>,
|
||||
}
|
||||
|
||||
impl<'b> VersionBuilder<'b> {
|
||||
pub fn builder(buf: &mut [u8]) -> VersionBuilder<'_> {
|
||||
VersionBuilder {
|
||||
builder: crate::ProtoBuilder::new(buf),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn major(mut self, value: i32) -> crate::Result<Self> {
|
||||
self.builder.write_int32(1, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn minor(mut self, value: i32) -> crate::Result<Self> {
|
||||
self.builder.write_int32(2, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn patch(mut self, value: i32) -> crate::Result<Self> {
|
||||
self.builder.write_int32(3, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn suffix(mut self, value: &str) -> crate::Result<Self> {
|
||||
self.builder.write_string(4, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn finish(self) -> crate::Result<&'b mut [u8]> {
|
||||
self.builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CodeGeneratorRequest<'a> {
|
||||
accessor: crate::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]) -> crate::Result<Self> {
|
||||
let accessor = crate::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) -> crate::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) -> crate::Result<&'a str> {
|
||||
let offset = self.parameter_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
pub fn proto_file(&self) -> crate::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) -> crate::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) -> crate::Result<&'a [u8]> {
|
||||
let offset = self.compiler_version_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub struct CodeGeneratorRequestBuilder<'b> {
|
||||
builder: crate::ProtoBuilder<'b>,
|
||||
}
|
||||
|
||||
impl<'b> CodeGeneratorRequestBuilder<'b> {
|
||||
pub fn builder(buf: &mut [u8]) -> CodeGeneratorRequestBuilder<'_> {
|
||||
CodeGeneratorRequestBuilder {
|
||||
builder: crate::ProtoBuilder::new(buf),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_to_generate(mut self, value: &str) -> crate::Result<Self> {
|
||||
self.builder.write_string(1, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn parameter(mut self, value: &str) -> crate::Result<Self> {
|
||||
self.builder.write_string(2, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn proto_file(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||
self.builder.write_bytes(15, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn source_file_descriptors(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||
self.builder.write_bytes(17, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn compiler_version(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||
self.builder.write_bytes(3, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn finish(self) -> crate::Result<&'b mut [u8]> {
|
||||
self.builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CodeGeneratorResponse<'a> {
|
||||
accessor: crate::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]) -> crate::Result<Self> {
|
||||
let accessor = crate::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) -> crate::Result<&'a str> {
|
||||
let offset = self.error_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
pub fn supported_features(&self) -> crate::Result<u32> {
|
||||
let offset = self.supported_features_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
crate::read_varint(bytes).map(|(v, _)| v as u32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
pub fn minimum_edition(&self) -> crate::Result<i32> {
|
||||
let offset = self.minimum_edition_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
pub fn maximum_edition(&self) -> crate::Result<i32> {
|
||||
let offset = self.maximum_edition_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
pub fn file(&self) -> crate::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 struct CodeGeneratorResponseBuilder<'b> {
|
||||
builder: crate::ProtoBuilder<'b>,
|
||||
}
|
||||
|
||||
impl<'b> CodeGeneratorResponseBuilder<'b> {
|
||||
pub fn builder(buf: &mut [u8]) -> CodeGeneratorResponseBuilder<'_> {
|
||||
CodeGeneratorResponseBuilder {
|
||||
builder: crate::ProtoBuilder::new(buf),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error(mut self, value: &str) -> crate::Result<Self> {
|
||||
self.builder.write_string(1, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn supported_features(mut self, value: u64) -> crate::Result<Self> {
|
||||
self.builder.write_varint(2, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn minimum_edition(mut self, value: i32) -> crate::Result<Self> {
|
||||
self.builder.write_int32(3, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn maximum_edition(mut self, value: i32) -> crate::Result<Self> {
|
||||
self.builder.write_int32(4, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn file(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||
self.builder.write_bytes(15, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn finish(self) -> crate::Result<&'b mut [u8]> {
|
||||
self.builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
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: crate::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]) -> crate::Result<Self> {
|
||||
let accessor = crate::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) -> crate::Result<&'a str> {
|
||||
let offset = self.name_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
pub fn insertion_point(&self) -> crate::Result<&'a str> {
|
||||
let offset = self.insertion_point_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
pub fn content(&self) -> crate::Result<&'a str> {
|
||||
let offset = self.content_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||
}
|
||||
|
||||
pub fn generated_code_info(&self) -> crate::Result<&'a [u8]> {
|
||||
let offset = self.generated_code_info_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub struct FileBuilder<'b> {
|
||||
builder: crate::ProtoBuilder<'b>,
|
||||
}
|
||||
|
||||
impl<'b> FileBuilder<'b> {
|
||||
pub fn builder(buf: &mut [u8]) -> FileBuilder<'_> {
|
||||
FileBuilder {
|
||||
builder: crate::ProtoBuilder::new(buf),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(mut self, value: &str) -> crate::Result<Self> {
|
||||
self.builder.write_string(1, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn insertion_point(mut self, value: &str) -> crate::Result<Self> {
|
||||
self.builder.write_string(2, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn content(mut self, value: &str) -> crate::Result<Self> {
|
||||
self.builder.write_string(15, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn generated_code_info(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||
self.builder.write_bytes(16, value)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn finish(self) -> crate::Result<&'b mut [u8]> {
|
||||
self.builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user