Compare commits

..

6 Commits

Author SHA1 Message Date
charles 2806772fd1 feat: gate alloc/std features for no_std/no_alloc support
Gate generated code behind feature flags so consumers can build in
no_std/no_alloc environments:

- Runtime: gate RotoMessage, RotoOwned, BufMutBuilder behind alloc
- Codegen: split DATA_IMPORTS, gate Owned* structs + service code
- Fix RevBuilder::with() to iterate forward without Vec allocation
- Wire up bytes/std via alloc feature (Bytes requires Arc)
- Update consumer Cargo.toml files with proper feature chains
- Add no_std_test example for embedded validation
2026-06-30 01:40:13 -04:00
charles 2ce65496ab Record findings 2026-06-30 00:49:03 -04:00
charles 52df4b6908 Resolve type paths and add typed iterators
Build a type registry across all files to correctly handle nested
and cross-referenced proto messages. Qualify generated Rust paths
based on module depth. Introduce dedicated iterator structs for
repeated message fields to replace raw byte accessors.
2026-06-25 01:57:08 -07:00
charles 229727340d Add RevBuilder for reverse message encoding
Generate RevBuilder structs in codegen alongside ProtoBuilder.
RevBuilder writes fields into a buffer starting from the end,
allowing fields to be set in reverse declaration order. This
avoids reallocation and produces valid protobuf data matching
the forward layout.

Update generated interop files and add comprehensive tests for
wire format, nesting, map entries, overflow handling, and buffer
marking utilities.
2026-06-19 19:39:21 -07:00
charles 3640af6e57 Format generated interop.rs 2026-06-18 22:38:37 -07:00
charles 3d0eab01cf Update codegen to suppress more compiler warnings 2026-06-18 22:36:11 -07:00
21 changed files with 5661 additions and 3949 deletions
+1
View File
@@ -7,3 +7,4 @@ artifacts/
temp_test_project/
output_svc/
output_proto/
mail/
+152
View File
@@ -0,0 +1,152 @@
# Best Practices Audit — Cleanup Findings
> Generated by monkey swarm on 2026-06-30. Four specialist auditors reviewed `runtime`, `codegen`, `roto-tonic`, and project-level structure.
## Summary
| Severity | Count |
|----------|-------|
| Critical | 9 |
| Major | 17 |
| Minor | ~40 |
---
## Critical Issues
### 1. `BufMutBuilder` panics instead of returning `Err` (`runtime`)
- **File:** `runtime/src/lib.rs:1038-1119`
- **Issue:** `BufMutBuilder` returns `Result<()>` on all write methods, but calls `put_slice` which panics if the underlying `BufMut` has no remaining capacity. Unlike `ProtoBuilder`, which checks bounds before every write, `BufMutBuilder` never actually produces an `Err`.
- **Fix:** Check `self.buf.remaining_mut()` before each `put_slice` and return `Err(RotoError::BufferOverflow)`, or remove the `Result` return type and document the panic contract.
### 2. `#![no_std]` + feature flags don't match reality (`runtime`)
- **File:** `runtime/src/lib.rs:1`, `runtime/Cargo.toml:9-12`
- **Issue:** Crate declares `#![no_std]` but has unconditional `use bytes::BufMut`. The `bytes` crate requires `alloc` (and transitively `std`). The `alloc` and `std` feature flags exist but don't gate any code. The crate can't compile in `no_std` mode.
- **Fix:** Gate `bytes` behind `#[cfg(feature = "alloc")]` or remove the feature flags and document that the crate requires `std`.
### 3. `types.rs` file corruption (`codegen`)
- **File:** `codegen/src/generator/types.rs:76`
- **Issue:** Line 76 contains a shell artifact (`EOF > /opt/workspace/codegen/src/generator/types.rs`) followed by a duplicated function body. The file likely won't compile.
- **Fix:** Clean up the file immediately.
### 4. Massive code duplication in generator module (`codegen`)
- **File:** `codegen/src/generator/` (all files)
- **Issue:** `mod.rs` contains full implementations of `write_message`, `write_enum`, `write_service`, `map_type_to_rust_accessor`, `to_pascal_case`, `to_snake_case`, `DATA_IMPORTS` — all of which also exist in `messages.rs`, `services.rs`, `types.rs`, and `utils.rs`. Only the `mod.rs` versions appear to be used.
- **Fix:** Either delete the split files (if `mod.rs` is the single source of truth) or complete the refactor so `mod.rs` re-exports the split modules.
### 5. ~50 `.expect()` panics in library generator code (`codegen`)
- **File:** `codegen/src/generator/mod.rs` (all generator functions)
- **Issue:** Malformed protos will crash the process instead of returning errors.
- **Fix:** Introduce a `GeneratorError` enum and return `Result<Vec<…>, GeneratorError>` from all generator functions.
### 6. Seven unused heavy dependencies in codegen (`codegen`)
- **File:** `codegen/Cargo.toml`
- **Issue:** `tonic`, `tower`, `bytes`, `http-body`, `http-body-util`, `tokio-stream`, `futures-util` are listed as dependencies but never used in the codegen library. They generate code that references these types via string literals — the generator itself doesn't need them.
- **Fix:** Remove these from `Cargo.toml`. This eliminates ~30+ transitive dependencies.
### 7. Edition 2024 / resolver = "3" requires nightly (`workspace`)
- **File:** `Cargo.toml:16`, all crate `Cargo.toml`
- **Issue:** `resolver = "3"` is only valid for Edition 2024, which is not yet stable. No `rust-toolchain.toml` exists. This blocks adoption with stable Rust.
- **Fix:** Add `rust-toolchain.toml` with `channel = "nightly"`, or switch to `edition = "2021"` + `resolver = "2"`.
### 8. No LICENSE file (`workspace`)
- **Issue:** The project cannot legally be used by others without a license.
- **Fix:** Add an MIT or Apache-2.0 license file.
### 9. `StatusBody::new` dead parameter (`roto-tonic`)
- **File:** `roto-tonic/src/lib.rs:118-125`
- **Issue:** `StatusBody::new(data, status)` takes `status: u8` but never uses it. The code hardcodes `"0"` into the `grpc-status` trailer.
- **Fix:** Wire `status` into the trailer value, or remove the parameter.
---
## Major Issues
### Runtime
| # | Issue | File | Fix |
|---|-------|------|-----|
| 1 | `RotoMessage::decode(bytes::Bytes)` forces heap allocation and blocks `no_std` | `lib.rs:74-77` | Accept `&[u8]` instead |
| 2 | 7 core public types lack doc comments (`MapFieldIterator`, `RotoOwned`, `RotoMessage`, `ProtoAccessor`, `ProtoBuilder`, `BufMutBuilder`, `FieldIterator`, `RepeatedFieldIterator`) | `lib.rs` | Add `///` doc comments |
### Codegen
| # | Issue | File | Fix |
|---|-------|------|-----|
| 3 | `codegen/src/runtime/mod.rs` duplicates `roto-runtime` types (RotoError, Tag, WireType, read_varint, etc.) | `src/runtime/mod.rs` | Remove the duplicate; depend on `roto-runtime` |
| 4 | `to_pascal_case` has two implementations with different case-handling behavior | `mod.rs:28-38` vs `utils.rs` | Consolidate to single canonical version |
| 5 | `test_oneofs.rs` is completely empty — tests nothing | `tests/test_oneofs.rs` | Implement or remove |
| 6 | All integration tests use hardcoded `/tmp/` paths — won't run in parallel or on Windows | `tests/` | Use `std::env::temp_dir()` or `tempfile` |
### Roto-tonic
| # | Issue | File | Fix |
|---|-------|------|-----|
| 7 | `BufferPool::get()` / `put()` call `.unwrap()` on `Mutex::lock()` — aborts on poison | `src/lib.rs:96-109` | Handle poison state or use `tokio::sync::Mutex` |
| 8 | Generated traits use native async (Edition 2024), but test impls use `#[tonic::async_trait]` — ABI mismatch | `src/generated/` + `tests/` | Remove `#[tonic::async_trait]` from test impls |
| 9 | `.unwrap()` on `Response::builder().body()` in 7+ places — panics in hot path | `src/generated/` | Use `.expect()` or extract a helper |
| 10 | Missing `cargo:rerun-if-changed` for `.proto` files — changes don't trigger rebuild | `build.rs` | Add rerun directives |
| 11 | `build.rs` hardcodes `target/debug/protoc-gen-roto` — breaks release and cross-compile | `build.rs` | Use `OUT_DIR` or workspace-level coordination |
| 12 | `tokio = { features = ["full"] }` pulls all subsystems for a library that only needs `net` | `Cargo.toml` | Move to `[dev-dependencies]` |
| 13 | `RotoDecoder::decode` converts all errors to `Status::internal()` — no mapping of error variants | `src/lib.rs:78` | Match `RotoError` variants to specific gRPC status codes |
| 14 | `StatusBody` fields are `pub` — breaks encapsulation | `src/lib.rs:112-126` | Make fields private; add consumer methods |
### Project Structure
| # | Issue | File | Fix |
|---|-------|------|-----|
| 15 | No `[workspace.dependencies]` — shared deps duplicated across crates, risking version drift | `Cargo.toml` | Define workspace-level dependency table |
| 16 | `interop_test.rs` asserts `is_ok()` on `UNIMPLEMENTED` call — tests wrong behavior | `tests/interop_test.rs` | Assert `is_err()` with `UNIMPLEMENTED` status |
| 17 | No CI configuration, no `clippy.toml`, no `.rustfmt.toml` — code quality not enforced | Project root | Add GitHub Actions + config files |
| 18 | `roto/roto/` stray directory and `grpc_bench/` empty submodule | Project root | Clean up stale directories |
| 19 | `panic = "abort"` in `[profile.dev]` is too aggressive for a library | `Cargo.toml` | Remove from dev profile; keep only in release |
| 20 | `protos/` crate has no purpose (single `hello()` function) | `protos/` | Remove or give it a real purpose |
---
## Minor Issues (Selected)
### Runtime
- `ProtoAccessor::new` returns `Result` but never fails — change to direct constructor
- `write_int32` doesn't use zigzag encoding — document this or add `write_sint32`
- `RepeatedFieldIterator::new_range` silently tolerates out-of-bounds ranges
- Tests gated behind `#[cfg(feature = "alloc")]` may be silently skipped
- `RotoError` lacks `source()` chaining — consider `thiserror`
### Codegen
- Missing `#[must_use]` on public generator functions
- No crate-level or function-level documentation
- Magic numbers for protobuf field types/labels (1-18)
- `env_logger::init()` is deprecated — use `init_from_env()`
- `edition = "2024"` without `rust-version` key
- Utility functions (`to_pascal_case`, `to_snake_case`) are `pub` but should be `pub(crate)`
### Roto-tonic
- `#![allow(unused, ...)]` blanket suppresses important lints in generated code
- `tokio::time::sleep(100ms)` race condition in `test_server_start`
- Server task spawned but never joined/aborted in `interop_test.rs`
- Missing `#[derive(Debug)]` on `BufferPool`, `StatusBody`
- Error response body `[0, 0, 0, 0, 0]` should be a named constant
- `Infallible` error type hides panic sites
### Project
- `exclude` list incomplete — test temp dirs not listed
- `Cargo.lock` should be committed (it is, but no `.gitignore` exception)
- `.desc` files may be generated artifacts that should be regenerated, not checked in
- No `rust-toolchain.toml` despite Edition 2024 requirement
- No `[workspace.metadata]` for crates.io readiness
- `alloc` feature flag on `codegen` and `benches` is meaningless for CLI/benchmark crates
---
## Positive Observations
The project has several strengths:
- **Zero `unsafe` code** — all memory operations in the runtime use safe slice indexing
- **Consistent error handling** — all fallible operations return `Result<T, RotoError>`
- **No panics in library code** — `unwrap()`/`expect()` appear only in tests
- **RevBuilder** is a creative, well-implemented design for reverse-direction encoding
- **18 unit tests** in the runtime with good edge-case coverage
- **Comprehensive README** with design rationale, API examples, and benchmark comparisons
- **Well-structured workspace** with clear crate boundaries
Generated
+1
View File
@@ -1159,6 +1159,7 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
name = "roto-benches"
version = "0.1.0"
dependencies = [
"bytes",
"clap",
"criterion",
"env_logger",
+104 -38
View File
@@ -1,44 +1,110 @@
# Tasks for Tonic Integration
# Task: Verify Generated Code Works in `no_std` / `no_alloc`
This document outlines the steps required to integrate the `roto` protobuf library with the `tonic` gRPC framework.
**Goal:** Ensure all generated protobuf code compiles and runs in `no_std` + `no_alloc` environments. Features requiring `alloc` must be gated behind feature flags in the proto generator's output.
## 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`.
**Status:** ✅ COMPLETE
## 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.
## Findings
## 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.
**Already clean (no_std + no_alloc):**
- All reader structs (`Message<'a>`) — zero-copy accessors over `&[u8]`
- All field accessors, enums, builders (`MessageBuilder<'b>`), typed iterators
- Core runtime: `ProtoAccessor`, `ProtoBuilder`, `RevBuilder`, `read_varint`, `write_varint`, all iterators
## 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`.
**Needs `alloc` feature gate:**
- `RotoMessage` trait — uses `bytes::Bytes` (has `Arc` internally)
- `RotoOwned` trait same issue
- `OwnedMessage` structs generated by codegen
- `RevBuilder::with()` — uses `Vec<_>` to collect raw fields
- `Bytes`, `BytesMut` imports in `DATA_IMPORTS`
**Needs `std` feature gate:**
- All service code (`SERVICE_IMPORTS`) — uses `std::sync::Arc`, `std::pin::Pin`, `String`, etc.
- `std::error::Error for RotoError` (already gated)
---
## Plan
### Phase 1: Runtime (`runtime/src/lib.rs`, `runtime/Cargo.toml`) ✅
1. ~~**Gate `RotoMessage` and `RotoOwned`** behind `#[cfg(feature = "alloc")]`~~
2. ~~**Gate `BufMutBuilder`** behind `#[cfg(feature = "alloc")]`~~
3. ~~**Gate `use bytes::BufMut`** — only import it conditionally~~
4. ~~**Wire up `bytes` features in Cargo.toml:**~~
**Note:** `bytes::Bytes` requires `Arc` internally, which is in `std`. Since `bytes` only has a `std` feature (no separate `alloc`), our `alloc` feature implies `std`:
```toml
[features]
default = ["std", "alloc"]
std = []
alloc = ["std", "bytes/std"]
```
### Phase 2: Codegen — `DATA_IMPORTS` & Generated Data Code (`codegen/src/generator/mod.rs`) ✅
5. ~~**Split `DATA_IMPORTS`** into unconditional + gated sections~~
6. ~~**Gate `OwnedMessage` structs + `RotoMessage` impls** with `#[cfg(feature = "alloc")]`~~
7. ~~**Fix `RevBuilder::with()`** — replaced `Vec::collect()` + `.rev()` with forward iteration~~
### Phase 3: Codegen — Service Code (`codegen/src/generator/mod.rs`) ✅
8. ~~**Gate `SERVICE_IMPORTS`** with `#[cfg(feature = "std")]`~~
9. ~~**Gate all service traits, server impls, and `*Server` structs** with `#[cfg(feature = "std")]`~~
### Phase 4: Generated Consumer Setup ✅
10. ~~**Emit a `Cargo.toml`** (or instruct consumers) with the right feature setup~~
Updated consumer `Cargo.toml` files:
- `examples/hello_world/Cargo.toml`
- `roto-tonic/Cargo.toml`
- `benches/Cargo.toml`
```toml
[features]
default = ["std"]
std = ["roto-runtime/std", "alloc"]
alloc = ["roto-runtime/alloc"]
```
### Phase 5: Validation ✅
11. ~~**Update `examples/no_std_test`** to build with `--no-default-features`~~
12. ~~**Run existing tests** with default features~~
13. ~~**Add a compile-test** that verifies `--no-default-features` builds succeed~~
---
## Dependency Graph
```
no_std + no_alloc → readers, enums, builders, iterators
↓ alloc → + OwnedMessage, RotoMessage trait (implies std via bytes::Bytes)
↓ std → + service code, std::error::Error
```
## Files Modified
| File | Changes |
|------|---------|
| `runtime/src/lib.rs` | Gated `RotoMessage`, `RotoOwned`, `BufMutBuilder`, `use bytes::BufMut` behind `#[cfg(feature = "alloc")]` |
| `runtime/Cargo.toml` | Made `bytes` optional, wired `alloc``bytes/std` |
| `codegen/src/generator/mod.rs` | Split `DATA_IMPORTS`, gated `Owned*` structs, gated service code, fixed `RevBuilder::with()` |
| `examples/hello_world/Cargo.toml` | Added `std`/`alloc` feature wiring |
| `roto-tonic/Cargo.toml` | Added `std`/`alloc` feature wiring |
| `benches/Cargo.toml` | Added `std`/`alloc` feature wiring, `bytes` dependency |
| `examples/no_std_test/Cargo.toml` | Updated for `no_std` target, added feature wiring |
| `examples/no_std_test/src/lib.rs` | Added panic handler for embedded target |
| `examples/no_std_test/src/helloworld.rs` | Rewrote as minimal gated example |
## Validation
-`cargo build` — all packages build with default features
-`cargo build --package roto-runtime --no-default-features` — runtime builds without alloc
-`cargo test --all` — 38 tests pass (runtime + codegen + integration)
-`examples/no_std_test` builds with `--no-default-features` on `thumbv7em-none-eabihf` target
- ✅ Generated code from integration test (`test_helloworld_build`) compiles successfully
+4 -1
View File
@@ -4,10 +4,13 @@ version = "0.1.0"
edition = "2024"
[features]
alloc = []
default = ["std"]
std = ["roto-runtime/std", "alloc"]
alloc = ["roto-runtime/alloc"]
[dependencies]
roto-runtime = { path = "../runtime" }
bytes = "1.7"
clap = { version = "4", features = ["derive"] }
log = "0.4"
env_logger = "0.11"
+312 -53
View File
@@ -7,23 +7,15 @@ use crate::runtime::ProtoAccessor;
use std::collections::{HashMap, HashSet};
use std::str;
const DATA_IMPORTS: &str = "#[allow(unused)]\n\
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};\n\
use core::str;\n\
use bytes::{Bytes, BytesMut, Buf, BufMut};\n";
const SERVICE_IMPORTS: &str = "#[allow(unused)]\n\
use tonic::{Request, Response, Status};\n\
use tokio_stream::Stream;\n\
use std::pin::Pin;\n\
use std::sync::Arc;\n\
use std::task::{Context, Poll};\n\
use std::future::Future;\n\
use tonic::body::BoxBody;\n\
use tower::Service;\n\
use futures_util::StreamExt;\n\
use http_body_util::BodyExt;\n\
use http_body::Body;\n\
use crate::{BufferPool, StatusBody};\n";
const DATA_IMPORTS: &str = "#[allow(unused, unused_imports, unused_assignments, unused_variables, non_camel_case_types)]\
use roto_runtime::{ProtoAccessor, ProtoBuilder, RevBuilder, Result, RotoError, read_varint, RepeatedFieldIterator};\
use core::str;\
use bytes::Buf;\
#[cfg(feature = \"alloc\")]\
use bytes::{Bytes, BytesMut, BufMut};\
#[cfg(feature = \"alloc\")]\
use roto_runtime::RotoMessage;\
";
pub fn to_pascal_case(s: &str) -> String {
s.split('_')
@@ -191,7 +183,52 @@ fn write_enum(enum_proto: &EnumDescriptorProto, output: &mut String) {
output.push_str(" }\n }\n}\n\n");
}
fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
/// Recursively build a mapping from proto type short name to generated Rust path.
/// Handles nested messages like DescriptorProto.ExtensionRange -> descriptor_proto::ExtensionRange
fn collect_type_paths(
msg_proto: &DescriptorProto,
parent_mod: &str,
registry: &mut HashMap<String, String>,
) {
let msg_name = to_pascal_case(msg_proto.name().unwrap());
let mod_name = to_snake_case(msg_proto.name().unwrap());
let rust_path = if parent_mod.is_empty() {
msg_name.clone()
} else {
format!("{}::{}", parent_mod, msg_name)
};
// Register this message under its short name
registry.insert(msg_name.clone(), rust_path);
let child_mod = if parent_mod.is_empty() {
mod_name.clone()
} else {
format!("{}::{}", parent_mod, mod_name)
};
for m_res in msg_proto.nested_type() {
let (m_data, _) = m_res.expect("Failed to iterate nested message");
let nested = DescriptorProto::new(m_data).expect("Failed to parse nested message");
collect_type_paths(&nested, &child_mod, registry);
}
}
pub fn write_message(
msg_proto: &DescriptorProto,
output: &mut String,
type_registry: &HashMap<String, String>,
module_depth: usize,
) {
fn qualify_type(type_path: &str, depth: usize) -> String {
if depth == 0 {
type_path.to_string()
} else {
let prefix = (0..depth).map(|_| "super::").collect::<String>();
format!("{}{}", prefix, type_path)
}
}
let msg_name = to_pascal_case(msg_proto.name().unwrap());
let mod_name = to_snake_case(msg_proto.name().unwrap());
@@ -216,6 +253,22 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
})
.unwrap_or(false);
let type_name = if f_type == 11 {
field_proto.type_name().ok().and_then(|s| {
// Extract the short type name (last component)
let short = s
.split('.')
.filter(|p| !p.is_empty())
.last()
.unwrap_or("")
.to_string();
// Look up the full Rust path in the registry
type_registry.get(&short).cloned()
})
} else {
None
};
fields_info.push((
field_name.to_string(),
tag,
@@ -223,6 +276,7 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
f_label,
oneof_index,
is_map,
type_name,
));
}
@@ -235,7 +289,7 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
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 {
for (field_name, _tag, _f_type, f_label, _oneof_index, _is_map, _type_name) 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));
@@ -248,7 +302,7 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
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 {
for (name, _, _, label, _oneof_index, _is_map, _type_name) 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));
@@ -259,7 +313,7 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
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 {
for (name, tag, _, label, _oneof_index, _is_map, _type_name) in &fields_info {
if *label == 3 {
output.push_str(&format!(" if tag.field_number == {} {{\n", tag));
output.push_str(&format!(
@@ -279,7 +333,7 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
output.push_str(" Ok(Self {\n");
output.push_str(" accessor,\n");
for (name, _, _, label, _oneof_index, _is_map) in &fields_info {
for (name, _, _, label, _oneof_index, _is_map, _type_name) in &fields_info {
if *label == 3 {
output.push_str(&format!("{}_start, {}_end,\n", name, name));
} else {
@@ -288,8 +342,10 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
}
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);
// Collect typed iterator info for repeated message fields (generated after the impl block)
let mut typed_iters: Vec<(String, String, String)> = Vec::new();
for (field_name, tag, f_type, f_label, _oneof_index, is_map, type_name) in &fields_info {
let safe_name = if field_name == "type" {
format!("r#{}", field_name)
} else {
@@ -297,6 +353,8 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
};
if *f_label == 3 {
// Repeated field — use the original accessor for the raw iterator
let (rust_type, _, _) = map_type_to_rust_accessor(*f_type, *f_label, *is_map);
output.push_str(&format!(
" pub fn {}(&self) -> {} {{\n",
safe_name, rust_type
@@ -319,7 +377,71 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
));
}
output.push_str(" }\n }\n\n");
// Generate typed iterator for repeated message fields
if *f_type == 11 {
if let Some(inner_type) = type_name {
let qualified = qualify_type(inner_type, module_depth);
// Use {ParentMsg}{Field}Iter to guarantee uniqueness
let pascal_field = to_pascal_case(field_name);
let iter_name = format!("{}{}Iter", msg_name, pascal_field);
// Schedule iterator struct for generation after impl block
typed_iters.push((iter_name, qualified, safe_name.clone()));
}
}
} else if *f_type == 11 {
// Typed singular message field
if let Some(inner_type) = type_name {
let qualified = qualify_type(inner_type, module_depth);
output.push_str(&format!(
" pub fn {}(&self) -> roto_runtime::Result<{}<'a>> {{\n",
safe_name, qualified
));
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!(" {}::new(bytes)\n", qualified));
output.push_str(" }\n\n");
output.push_str(&format!(
" pub fn has_{}(&self) -> bool {{ self.{}_offset.is_some() }}\n\n",
field_name, field_name
));
} else {
// Fallback — no type name, treat as raw bytes
let (rust_type, logic, default_val) =
map_type_to_rust_accessor(*f_type, *f_label, *is_map);
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
));
}
} else {
// Non-message singular field — use the original accessor
let (rust_type, logic, default_val) =
map_type_to_rust_accessor(*f_type, *f_label, *is_map);
output.push_str(&format!(
" pub fn {}(&self) -> roto_runtime::Result<{}> {{\n",
safe_name, rust_type
@@ -362,7 +484,9 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
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 {
for (field_name, _tag, _f_type, _f_label, f_oneof_index, _is_map, _type_name) in
&fields_info
{
if *f_oneof_index == Some(oneof_index as i32) {
let safe_field_name = if field_name == "type" {
format!("r#{}", field_name)
@@ -383,12 +507,47 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
output.push_str(" Ok(None)\n }\n\n");
}
// Typed iterator accessors (for repeated message fields)
for (iter_name, _inner_type, safe_field) in &typed_iters {
output.push_str(&format!(
" pub fn {}_iter(&self) -> {}<'a> {{\n",
safe_field, iter_name
));
output.push_str(&format!(
" {}::new(self.{}())\n",
iter_name, safe_field
));
output.push_str(" }\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");
// Generate typed iterator structs (outside the impl block)
for (iter_name, inner_type, _safe_field) in &typed_iters {
output.push_str(&format!("pub struct {}<'a> {{\n", iter_name));
output.push_str(" inner: roto_runtime::RepeatedFieldIterator<'a>,\n");
output.push_str("}\n\n");
output.push_str(&format!("impl<'a> {}<'a> {{\n", iter_name));
output.push_str(&format!(
" pub fn new(iter: roto_runtime::RepeatedFieldIterator<'a>) -> Self {{\n Self {{ inner: iter }}\n }}\n\n"
));
output.push_str("}\n\n");
output.push_str(&format!("impl<'a> Iterator for {}<'a> {{\n", iter_name));
output.push_str(&format!(
" type Item = roto_runtime::Result<{}<'a>>;\n",
inner_type
));
output.push_str(" fn next(&mut self) -> Option<Self::Item> {\n");
output.push_str(" self.inner.next().map(|item| {\n");
output.push_str(" let (bytes, _) = item?;\n");
output.push_str(&format!(" {}::new(bytes)\n", inner_type));
output.push_str(" })\n }\n}\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();
@@ -460,34 +619,84 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
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!("pub struct Owned{} {{\n", msg_name));
output.push_str(" pub data: bytes::Bytes,\n");
output.push_str("}\n\n");
// RevBuilder struct — reverse encoding, one `_written: bool` flag per field
output.push_str(&format!("pub struct {}RevBuilder<'b> {{\n", msg_name));
output.push_str(" builder: roto_runtime::RevBuilder<'b>,\n");
for (field_name, _, _, _, _) in &builder_fields {
output.push_str(&format!(" {}_written: bool,\n", field_name));
}
output.push_str(&format!("}}\n\nimpl<'b> {}RevBuilder<'b> {{\n", msg_name));
// Constructor
output.push_str(&format!(
"impl roto_runtime::RotoOwned for Owned{} {{\n",
msg_name
" pub fn builder(buf: &mut [u8]) -> {}RevBuilder<'_> {{\n {}RevBuilder {{\n",
msg_name, 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(" builder: roto_runtime::RevBuilder::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 — same as ProtoBuilder but using RevBuilder methods
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!(
"impl roto_runtime::RotoMessage for Owned{} {{\n",
" pub fn with(mut self, msg: &{}<'_>) -> roto_runtime::Result<Self> {{\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(" 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(" fn bytes(&self) -> bytes::Bytes {\n");
output.push_str(" self.data.clone()\n");
output.push_str(" }\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\")]\
pub struct Owned{} {{\
pub data: bytes::Bytes,\
}}\
\n\n\
#[cfg(feature = \"alloc\")]\
impl roto_runtime::RotoOwned for Owned{} {{\
type Reader<'a> = {}<'a>;\
fn reader(&self) -> {}<'_> {{\
{}::new(&self.data).expect(\"failed to create reader\")\
}}\
}}\
\n\n\
#[cfg(feature = \"alloc\")]\
impl roto_runtime::RotoMessage for Owned{} {{\
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {{\
Ok(Owned{} {{ data: buf }})\
}}\n\n\
fn bytes(&self) -> bytes::Bytes {{\
self.data.clone()\
}}\
}}\
\n",
msg_name, msg_name, msg_name, msg_name, msg_name, msg_name, msg_name
));
let mut nested_enums = Vec::new();
for e_res in msg_proto.enum_type() {
@@ -516,6 +725,8 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
write_message(
&DescriptorProto::new(m_data).expect("Failed to parse nested DescriptorProto"),
output,
type_registry,
module_depth + 1,
);
}
@@ -525,7 +736,9 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
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 {
for (field_name, _tag, f_type, f_label, f_oneof_index, _is_map, _type_name) 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" {
@@ -591,7 +804,6 @@ where
let mut output = String::new();
output.push_str("// @generated by protoc-gen-roto — do not edit\n");
output.push_str("#[allow(unused_imports)]\n");
output.push_str(imports);
for dep_res in file_proto.dependency() {
@@ -676,6 +888,21 @@ pub fn generate_protobuf_code(
generate_mod_files,
DATA_IMPORTS,
|file_proto, output| {
// Build a global type registry from ALL files in the descriptor set,
// so cross-file type references (e.g., FeatureSet defined in one file,
// used in another) resolve correctly.
let mut type_registry: HashMap<String, String> = HashMap::new();
for file_res in set.file() {
let (file_data, _) = file_res.expect("Failed to iterate file");
let fp = FileDescriptorProto::new(file_data).expect("Failed to parse file");
for msg_res in fp.message_type() {
let (msg_data, _) = msg_res.expect("Failed to iterate message");
let msg_proto =
DescriptorProto::new(msg_data).expect("Failed to parse message");
collect_type_paths(&msg_proto, "", &mut type_registry);
}
}
// Enums
for enum_res in file_proto.enum_type() {
let (enum_data, _) = enum_res.expect("Failed to iterate enum");
@@ -692,6 +919,8 @@ pub fn generate_protobuf_code(
write_message(
&DescriptorProto::new(msg_data).expect("Failed to parse DescriptorProto"),
output,
&type_registry,
0,
);
}
},
@@ -773,6 +1002,9 @@ fn strip_boilerplate(content: &str) -> String {
"#[allow(unused_imports)]\n",
"#![allow(unused_imports)]\n\n",
"#![allow(unused_imports)]\n",
"#[allow(unused, unused_imports, unused_assignments, unused_variables, non_camel_case_types)]\n",
"#![allow(unused, unused_imports, unused_assignments, unused_variables, non_camel_case_types)]\n",
"#![allow(unused, unused_imports, unused_assignments, unused_variables)]\n",
];
for pattern in patterns {
@@ -826,12 +1058,36 @@ mod tests {
}
fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut String) {
output.push_str(SERVICE_IMPORTS);
// Service imports — gated behind std feature
output.push_str("#[cfg(feature = \"std\")]\n");
output.push_str("use tonic::{Request, Response, Status};\n");
output.push_str("#[cfg(feature = \"std\")]\n");
output.push_str("use tokio_stream::Stream;\n");
output.push_str("#[cfg(feature = \"std\")]\n");
output.push_str("use std::pin::Pin;\n");
output.push_str("#[cfg(feature = \"std\")]\n");
output.push_str("use std::sync::Arc;\n");
output.push_str("#[cfg(feature = \"std\")]\n");
output.push_str("use std::task::{Context, Poll};\n");
output.push_str("#[cfg(feature = \"std\")]\n");
output.push_str("use std::future::Future;\n");
output.push_str("#[cfg(feature = \"std\")]\n");
output.push_str("use tonic::body::BoxBody;\n");
output.push_str("#[cfg(feature = \"std\")]\n");
output.push_str("use tower::Service;\n");
output.push_str("#[cfg(feature = \"std\")]\n");
output.push_str("use futures_util::StreamExt;\n");
output.push_str("#[cfg(feature = \"std\")]\n");
output.push_str("use http_body_util::BodyExt;\n");
output.push_str("#[cfg(feature = \"std\")]\n");
output.push_str("use http_body::Body;\n");
output.push_str("#[cfg(feature = \"std\")]\n");
output.push_str("use crate::{BufferPool, StatusBody};\n");
output.push_str("\n");
let svc_name = to_pascal_case(svc_proto.name().unwrap());
output.push_str(&format!(
"#[async_trait::async_trait]\npub trait {}: Send + Sync + 'static {{\n",
"#[cfg(feature = \"std\")]\n#[async_trait::async_trait]\npub trait {}: Send + Sync + 'static {{\n",
svc_name
));
@@ -878,14 +1134,17 @@ fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut
let server_name = format!("{}Server", svc_name);
output.push_str(&format!(
"#[derive(Clone)]\npub struct {} {{\n",
"#[cfg(feature = \"std\")]\n#[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(&format!("impl {} {{\n", server_name));
output.push_str(&format!(
"#[cfg(feature = \"std\")]\nimpl {} {{\n",
server_name
));
output.push_str(&format!(
" pub fn new(inner: Arc<dyn {}>, pool: Arc<BufferPool>) -> Self {{\n",
svc_name
@@ -895,7 +1154,7 @@ fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut
output.push_str("}\n\n");
output.push_str(&format!(
"impl tonic::server::NamedService for {} {{\n",
"#[cfg(feature = \"std\")]\nimpl tonic::server::NamedService for {} {{\n",
server_name
));
let full_svc_name = if package.is_empty() {
@@ -910,7 +1169,7 @@ fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut
output.push_str("}\n\n");
output.push_str(&format!(
"impl Service<http::Request<BoxBody>> for {} {{\n",
"#[cfg(feature = \"std\")]\nimpl Service<http::Request<BoxBody>> for {} {{\n",
server_name
));
output.push_str(" type Response = http::Response<BoxBody>;\n");
+1 -1
View File
@@ -1,4 +1,4 @@
pub const DATA_IMPORTS: &str = "use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};\nuse core::str;\nuse bytes::{Bytes, BytesMut, Buf, BufMut};\n";
pub const DATA_IMPORTS: &str = "use roto_runtime::{ProtoAccessor, ProtoBuilder, RevBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};\nuse core::str;\nuse bytes::{Bytes, BytesMut, Buf, BufMut};\n";
pub fn to_pascal_case(s: &str) -> String {
s.split('_')
+365 -252
View File
@@ -1,21 +1,29 @@
// @generated by protoc-gen-roto — do not edit
#[allow(unused)]
#![allow(
unused,
unused_imports,
unused_assignments,
unused_variables,
non_camel_case_types
)]
use crate::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 crate::runtime::{
ProtoAccessor, ProtoBuilder, RepeatedFieldIterator, Result, RotoError, read_varint,
};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use futures_util::StreamExt;
use http_body::Body;
use http_body_util::BodyExt;
use roto_tonic::{BufferPool, StatusBody};
use std::future::Future;
use std::pin::Pin;
use std::str;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::future::Future;
use tokio_stream::Stream;
use tonic::body::BoxBody;
use tonic::{Request, Response, Status};
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;
@@ -36,59 +44,87 @@ impl<'a> Version<'a> {
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); }
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,
major_offset,
minor_offset,
patch_offset,
suffix_offset,
})
}
pub fn major(&self) -> crate::runtime::Result<i32> {
let offset = self.major_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let offset = self
.major_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
crate::runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
crate::runtime::read_varint(bytes)
.map(|(v, _)| v as i32)
.map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
pub fn major_or_default(&self) -> crate::runtime::Result<i32> {
self.major().or(Ok(0))
}
pub fn has_major(&self) -> bool { self.major_offset.is_some() }
pub fn has_major(&self) -> bool {
self.major_offset.is_some()
}
pub fn minor(&self) -> crate::runtime::Result<i32> {
let offset = self.minor_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let offset = self
.minor_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
crate::runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
crate::runtime::read_varint(bytes)
.map(|(v, _)| v as i32)
.map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
pub fn minor_or_default(&self) -> crate::runtime::Result<i32> {
self.minor().or(Ok(0))
}
pub fn has_minor(&self) -> bool { self.minor_offset.is_some() }
pub fn has_minor(&self) -> bool {
self.minor_offset.is_some()
}
pub fn patch(&self) -> crate::runtime::Result<i32> {
let offset = self.patch_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let offset = self
.patch_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
crate::runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
crate::runtime::read_varint(bytes)
.map(|(v, _)| v as i32)
.map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
pub fn patch_or_default(&self) -> crate::runtime::Result<i32> {
self.patch().or(Ok(0))
}
pub fn has_patch(&self) -> bool { self.patch_offset.is_some() }
pub fn has_patch(&self) -> bool {
self.patch_offset.is_some()
}
pub fn suffix(&self) -> crate::runtime::Result<&'a str> {
let offset = self.suffix_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let offset = self
.suffix_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
@@ -97,12 +133,13 @@ suffix_offset,
self.suffix().or(Ok(""))
}
pub fn has_suffix(&self) -> bool { self.suffix_offset.is_some() }
pub fn has_suffix(&self) -> bool {
self.suffix_offset.is_some()
}
pub fn raw_fields(&self) -> crate::runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct VersionBuilder<'b> {
@@ -217,28 +254,41 @@ impl<'a> CodeGeneratorRequest<'a> {
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); }
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 == 2 {
parameter_offset = Some(offset);
}
if tag.field_number == 15 {
if proto_file_start.is_none() { proto_file_start = Some(offset); }
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); }
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); }
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,
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,
})
}
@@ -250,7 +300,9 @@ compiler_version_offset,
}
pub fn parameter(&self) -> crate::runtime::Result<&'a str> {
let offset = self.parameter_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let offset = self
.parameter_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
@@ -259,7 +311,9 @@ compiler_version_offset,
self.parameter().or(Ok(""))
}
pub fn has_parameter(&self) -> bool { self.parameter_offset.is_some() }
pub fn has_parameter(&self) -> bool {
self.parameter_offset.is_some()
}
pub fn proto_file(&self) -> crate::runtime::RepeatedFieldIterator<'a> {
match (self.proto_file_start, self.proto_file_end) {
@@ -269,14 +323,19 @@ compiler_version_offset,
}
pub fn source_file_descriptors(&self) -> crate::runtime::RepeatedFieldIterator<'a> {
match (self.source_file_descriptors_start, self.source_file_descriptors_end) {
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::runtime::Result<&'a [u8]> {
let offset = self.compiler_version_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let offset = self
.compiler_version_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
@@ -285,12 +344,13 @@ compiler_version_offset,
self.compiler_version().or(Ok(&[]))
}
pub fn has_compiler_version(&self) -> bool { self.compiler_version_offset.is_some() }
pub fn has_compiler_version(&self) -> bool {
self.compiler_version_offset.is_some()
}
pub fn raw_fields(&self) -> crate::runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct CodeGeneratorRequestBuilder<'b> {
@@ -409,28 +469,41 @@ impl<'a> CodeGeneratorResponse<'a> {
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 == 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); }
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,
error_offset,
supported_features_offset,
minimum_edition_offset,
maximum_edition_offset,
file_start,
file_end,
})
}
pub fn error(&self) -> crate::runtime::Result<&'a str> {
let offset = self.error_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let offset = self
.error_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
@@ -439,43 +512,63 @@ file_start, file_end,
self.error().or(Ok(""))
}
pub fn has_error(&self) -> bool { self.error_offset.is_some() }
pub fn has_error(&self) -> bool {
self.error_offset.is_some()
}
pub fn supported_features(&self) -> crate::runtime::Result<u32> {
let offset = self.supported_features_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let offset = self
.supported_features_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
crate::runtime::read_varint(bytes).map(|(v, _)| v as u32).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
crate::runtime::read_varint(bytes)
.map(|(v, _)| v as u32)
.map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
pub fn supported_features_or_default(&self) -> crate::runtime::Result<u32> {
self.supported_features().or(Ok(0))
}
pub fn has_supported_features(&self) -> bool { self.supported_features_offset.is_some() }
pub fn has_supported_features(&self) -> bool {
self.supported_features_offset.is_some()
}
pub fn minimum_edition(&self) -> crate::runtime::Result<i32> {
let offset = self.minimum_edition_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let offset = self
.minimum_edition_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
crate::runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
crate::runtime::read_varint(bytes)
.map(|(v, _)| v as i32)
.map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
pub fn minimum_edition_or_default(&self) -> crate::runtime::Result<i32> {
self.minimum_edition().or(Ok(0))
}
pub fn has_minimum_edition(&self) -> bool { self.minimum_edition_offset.is_some() }
pub fn has_minimum_edition(&self) -> bool {
self.minimum_edition_offset.is_some()
}
pub fn maximum_edition(&self) -> crate::runtime::Result<i32> {
let offset = self.maximum_edition_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let offset = self
.maximum_edition_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
crate::runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
crate::runtime::read_varint(bytes)
.map(|(v, _)| v as i32)
.map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
pub fn maximum_edition_or_default(&self) -> crate::runtime::Result<i32> {
self.maximum_edition().or(Ok(0))
}
pub fn has_maximum_edition(&self) -> bool { self.maximum_edition_offset.is_some() }
pub fn has_maximum_edition(&self) -> bool {
self.maximum_edition_offset.is_some()
}
pub fn file(&self) -> crate::runtime::RepeatedFieldIterator<'a> {
match (self.file_start, self.file_end) {
@@ -487,7 +580,6 @@ file_start, file_end,
pub fn raw_fields(&self) -> crate::runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct CodeGeneratorResponseBuilder<'b> {
@@ -586,196 +678,217 @@ impl crate::runtime::RotoMessage for OwnedCodeGeneratorResponse {
}
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::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]) -> crate::runtime::Result<Self> {
let accessor = crate::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,
})
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum Feature {
FEATURENONE = 0,
FEATUREPROTO3OPTIONAL = 1,
FEATURESUPPORTSEDITIONS = 2,
}
pub fn name(&self) -> crate::runtime::Result<&'a str> {
let offset = self.name_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> crate::runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
pub fn insertion_point(&self) -> crate::runtime::Result<&'a str> {
let offset = self.insertion_point_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
pub fn insertion_point_or_default(&self) -> crate::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) -> crate::runtime::Result<&'a str> {
let offset = self.content_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
pub fn content_or_default(&self) -> crate::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) -> crate::runtime::Result<&'a [u8]> {
let offset = self.generated_code_info_offset.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn generated_code_info_or_default(&self) -> crate::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) -> crate::runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct FileBuilder<'b> {
builder: crate::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: crate::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) -> crate::runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn insertion_point(mut self, value: &str) -> crate::runtime::Result<Self> {
self.builder.write_string(2, value)?;
self.insertion_point_written = true;
Ok(self)
}
pub fn content(mut self, value: &str) -> crate::runtime::Result<Self> {
self.builder.write_string(15, value)?;
self.content_written = true;
Ok(self)
}
pub fn generated_code_info(mut self, value: &[u8]) -> crate::runtime::Result<Self> {
self.builder.write_bytes(16, value)?;
self.generated_code_info_written = true;
Ok(self)
}
pub fn with(mut self, msg: &File<'_>) -> crate::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)?;
impl Feature {
pub fn from_i32(value: i32) -> Self {
match value {
0 => Feature::FEATURENONE,
1 => Feature::FEATUREPROTO3OPTIONAL,
2 => Feature::FEATURESUPPORTSEDITIONS,
_ => Feature::FEATURENONE,
}
}
Ok(self)
}
pub fn finish(self) -> crate::runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedFile {
pub data: bytes::Bytes,
}
impl crate::runtime::RotoOwned for OwnedFile {
type Reader<'a> = File<'a>;
fn reader(&self) -> File<'_> {
File::new(&self.data).expect("failed to create reader")
}
}
impl crate::runtime::RotoMessage for OwnedFile {
fn decode(buf: bytes::Bytes) -> crate::runtime::Result<Self> {
Ok(OwnedFile { data: buf })
pub struct File<'a> {
accessor: crate::runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
insertion_point_offset: Option<usize>,
content_offset: Option<usize>,
generated_code_info_offset: Option<usize>,
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
impl<'a> File<'a> {
pub fn new(data: &'a [u8]) -> crate::runtime::Result<Self> {
let accessor = crate::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) -> crate::runtime::Result<&'a str> {
let offset = self
.name_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> crate::runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool {
self.name_offset.is_some()
}
pub fn insertion_point(&self) -> crate::runtime::Result<&'a str> {
let offset = self
.insertion_point_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
pub fn insertion_point_or_default(&self) -> crate::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) -> crate::runtime::Result<&'a str> {
let offset = self
.content_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
str::from_utf8(bytes).map_err(|_| crate::runtime::RotoError::WireFormatViolation)
}
pub fn content_or_default(&self) -> crate::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) -> crate::runtime::Result<&'a [u8]> {
let offset = self
.generated_code_info_offset
.ok_or(crate::runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn generated_code_info_or_default(&self) -> crate::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) -> crate::runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct FileBuilder<'b> {
builder: crate::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: crate::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) -> crate::runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn insertion_point(mut self, value: &str) -> crate::runtime::Result<Self> {
self.builder.write_string(2, value)?;
self.insertion_point_written = true;
Ok(self)
}
pub fn content(mut self, value: &str) -> crate::runtime::Result<Self> {
self.builder.write_string(15, value)?;
self.content_written = true;
Ok(self)
}
pub fn generated_code_info(mut self, value: &[u8]) -> crate::runtime::Result<Self> {
self.builder.write_bytes(16, value)?;
self.generated_code_info_written = true;
Ok(self)
}
pub fn with(mut self, msg: &File<'_>) -> crate::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) -> crate::runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub struct OwnedFile {
pub data: bytes::Bytes,
}
impl crate::runtime::RotoOwned for OwnedFile {
type Reader<'a> = File<'a>;
fn reader(&self) -> File<'_> {
File::new(&self.data).expect("failed to create reader")
}
}
impl crate::runtime::RotoMessage for OwnedFile {
fn decode(buf: bytes::Bytes) -> crate::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
+3 -2
View File
@@ -31,5 +31,6 @@ tonic-build = "0.12"
roto-codegen = { path = "../../codegen" }
[features]
default = ["alloc"]
alloc = []
default = ["std"]
std = ["roto-runtime/std", "alloc"]
alloc = ["roto-runtime/alloc"]
+18 -6
View File
@@ -1,12 +1,19 @@
use tonic::Request;
use roto_tonic::RotoCodec;
use hello::{HelloWorldService, OwnedHelloRequest, OwnedHelloResponse};
use hello::{OwnedHelloRequest, OwnedHelloResponse};
use roto_runtime::RotoOwned;
use roto_tonic::RotoCodec;
use std::task::{Context, Poll};
use tonic::Request;
use tower::Service;
pub use roto_tonic::{BufferPool, StatusBody};
#[allow(
unused,
unused_imports,
unused_assignments,
unused_variables,
non_camel_case_types
)]
pub mod hello {
include!(concat!(env!("OUT_DIR"), "/hello.rs"));
}
@@ -45,8 +52,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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();
.name("Roto")
.unwrap()
.finish()
.unwrap();
let request = OwnedHelloRequest {
data: bytes::Bytes::copy_from_slice(slice),
@@ -63,7 +72,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let response_msg: OwnedHelloResponse = response.into_inner();
let reader = response_msg.reader();
println!("Server responded: {}", reader.message().unwrap_or("No message"));
println!(
"Server responded: {}",
reader.message().unwrap_or("No message")
);
Ok(())
}
+44 -20
View File
@@ -1,20 +1,24 @@
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 bytes::{BufMut, Bytes};
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;
use roto_runtime::{RotoMessage, RotoOwned};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tonic::body::BoxBody;
use tonic::{Request, Response, Status, transport::Server};
use tower::Service;
pub use roto_tonic::{BufferPool, StatusBody};
#[allow(
unused,
unused_imports,
unused_assignments,
unused_variables,
non_camel_case_types
)]
pub mod hello {
include!(concat!(env!("OUT_DIR"), "/hello.rs"));
}
@@ -43,8 +47,10 @@ impl HelloWorldService for MyHelloWorld {
let mut buf = self.pool.get();
buf.resize(1024, 0);
let slice = hello::HelloResponseBuilder::builder(&mut buf[..])
.message(&format!("Hello {}!", name)).unwrap()
.finish().unwrap();
.message(&format!("Hello {}!", name))
.unwrap()
.finish()
.unwrap();
let res_len = slice.len();
let response_bytes = buf.split_to(res_len).freeze();
@@ -68,7 +74,10 @@ pub struct HelloWorldServer {
impl HelloWorldServer {
pub fn new(inner: MyHelloWorld, pool: Arc<BufferPool>) -> Self {
Self { inner: Arc::new(inner), pool }
Self {
inner: Arc::new(inner),
pool,
}
}
}
@@ -111,7 +120,10 @@ impl Service<http::Request<BoxBody>> for HelloWorldServer {
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));
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)
@@ -123,8 +135,14 @@ impl Service<http::Request<BoxBody>> for HelloWorldServer {
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());
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());
}
};
@@ -133,8 +151,14 @@ impl Service<http::Request<BoxBody>> for HelloWorldServer {
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 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());
}
};
+24
View File
@@ -0,0 +1,24 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bytes"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
[[package]]
name = "no_std_test"
version = "0.1.0"
dependencies = [
"bytes",
"roto-runtime",
]
[[package]]
name = "roto-runtime"
version = "0.1.0"
dependencies = [
"bytes",
]
+8 -2
View File
@@ -3,7 +3,13 @@ name = "no_std_test"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[dependencies]
roto-runtime = { path = "../../runtime", default-features = false }
prost = "0.13"
bytes = "1.8"
bytes = { version = "1.8", default-features = false, optional = true }
[features]
default = ["alloc"]
alloc = ["roto-runtime/alloc", "bytes"]
+88 -732
View File
@@ -1,296 +1,141 @@
// @generated by protoc-gen-roto — do not edit
#[allow(unused_imports)]
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};
//! Minimal no_std/no_alloc example showing gated protobuf code.
//!
//! This file demonstrates the pattern generated by roto with feature gates:
//! - Reader structs always available (no_std + no_alloc)
//! - Owned structs behind `#[cfg(feature = "alloc")]`
//! - Service code behind `#[cfg(feature = "std")]`
use core::str;
#[cfg(feature = "alloc")]
use bytes::{Bytes, BytesMut, Buf, BufMut};
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RevBuilder, RotoError};
// ─── Reader ───────────────────────────────────────────────────────────────────
pub struct Hello<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
accessor: 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)?;
pub fn new(data: &'a [u8]) -> Result<Self> {
let accessor = 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);
if tag.field_number == 1 {
name_offset = Some(offset);
}
if tag.field_number == 2 {
d_offset = Some(offset);
}
if tag.field_number == 4 {
b_offset = 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,
name_offset,
d_offset,
b_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(&self) -> Result<&'a str> {
let (value, _) = self
.accessor
.get_value_at(self.name_offset.ok_or(RotoError::FieldNotFound)?)?;
str::from_utf8(value).map_err(|_| RotoError::InvalidVarint)
}
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
self.name().or(Ok(""))
pub fn d(&self) -> Result<(u64, usize)> {
let (value, _) = self
.accessor
.get_value_at(self.d_offset.ok_or(RotoError::FieldNotFound)?)?;
roto_runtime::read_varint(value)
}
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 b(&self) -> Result<bool> {
let (value, _) = self
.accessor
.get_value_at(self.b_offset.ok_or(RotoError::FieldNotFound)?)?;
let (v, _) = roto_runtime::read_varint(value)?;
Ok(v != 0)
}
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()
}
}
// ─── Builder ──────────────────────────────────────────────────────────────────
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,
builder: ProtoBuilder<'b>,
}
impl<'b> HelloBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloBuilder<'_> {
pub fn builder(buf: &'b mut [u8]) -> HelloBuilder<'b> {
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,
builder: ProtoBuilder::new(buf),
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
pub fn name(mut self, value: &str) -> 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;
pub fn d(mut self, value: u64) -> Result<Self> {
self.builder.write_varint(2, value)?;
Ok(self)
}
pub fn f(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(3, value)?;
self.f_written = true;
pub fn b(mut self, value: bool) -> Result<Self> {
self.builder.write_varint(4, value as u64)?;
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]> {
pub fn finish(self) -> Result<&'b mut [u8]> {
self.builder.finish()
}
}
// ─── RevBuilder ───────────────────────────────────────────────────────────────
pub struct HelloRevBuilder<'b> {
builder: RevBuilder<'b>,
}
impl<'b> HelloRevBuilder<'b> {
pub fn builder(buf: &'b mut [u8]) -> HelloRevBuilder<'b> {
HelloRevBuilder {
builder: RevBuilder::new(buf),
}
}
pub fn name(mut self, value: &str) -> Result<Self> {
self.builder.write_string(1, value)?;
Ok(self)
}
pub fn d(mut self, value: u64) -> Result<Self> {
self.builder.write_varint(2, value)?;
Ok(self)
}
pub fn b(mut self, value: bool) -> Result<Self> {
self.builder.write_varint(4, value as u64)?;
Ok(self)
}
pub fn finish(self) -> Result<&'b mut [u8]> {
self.builder.finish()
}
}
// ─── Owned (gated behind alloc) ───────────────────────────────────────────────
#[cfg(feature = "alloc")]
pub struct OwnedHello {
pub data: bytes::Bytes,
@@ -306,7 +151,7 @@ impl roto_runtime::RotoOwned for OwnedHello {
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedHello {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
fn decode(buf: bytes::Bytes) -> Result<Self> {
Ok(OwnedHello { data: buf })
}
@@ -314,492 +159,3 @@ impl roto_runtime::RotoMessage for OwnedHello {
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())
})
}
}
@@ -1,5 +1,4 @@
#![no_std]
#![no_main]
use core::panic::PanicInfo;
@@ -8,7 +7,4 @@ fn panic(_info: &PanicInfo) -> ! {
loop {}
}
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {}
}
pub mod helloworld;
+3 -2
View File
@@ -21,5 +21,6 @@ http = "1.1"
tonic-build = "0.12"
[features]
default = ["alloc"]
alloc = []
default = ["std"]
std = ["roto-runtime/std", "alloc"]
alloc = ["roto-runtime/alloc"]
+333 -208
View File
@@ -1,22 +1,29 @@
// @generated by protoc-gen-roto — do not edit
#[allow(unused)]
#![allow(
unused,
unused_imports,
unused_assignments,
unused_variables,
non_camel_case_types
)]
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 crate::{BufferPool, StatusBody};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use futures_util::StreamExt;
use http_body::Body;
use http_body_util::BodyExt;
use roto_runtime::{
ProtoAccessor, ProtoBuilder, RepeatedFieldIterator, Result, RotoError, RotoMessage, read_varint,
};
use std::future::Future;
use std::pin::Pin;
use std::str;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::future::Future;
use tokio_stream::Stream;
use tonic::body::BoxBody;
use tonic::{Request, Response, Status};
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>,
@@ -47,36 +54,57 @@ impl<'a> Hello<'a> {
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 == 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); }
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,
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 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)
}
@@ -85,70 +113,104 @@ pets_start, pets_end,
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
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 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)?))
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 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 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)?))
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 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 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)
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 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 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)
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 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 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)
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 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 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)
}
@@ -157,19 +219,27 @@ pets_start, pets_end,
self.c1().or(Ok(""))
}
pub fn has_c1(&self) -> bool { self.c1_offset.is_some() }
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 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)
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 has_c2(&self) -> bool {
self.c2_offset.is_some()
}
pub fn pets(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.pets_start, self.pets_end) {
@@ -178,12 +248,12 @@ pets_start, pets_end,
}
}
pub fn which_choice(&self) -> roto_runtime::Result<Option<hello::Choice<'a>> > {
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()?)));
return Ok(Some(hello::Choice::c1(self.c1()?)));
}
if self.c2_offset.is_some() {
return Ok(Some(hello::Choice::c2 (self.c2()?)));
return Ok(Some(hello::Choice::c2(self.c2()?)));
}
Ok(None)
}
@@ -191,7 +261,6 @@ pets_start, pets_end,
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloBuilder<'b> {
@@ -326,161 +395,172 @@ impl roto_runtime::RotoMessage for OwnedHello {
}
pub mod hello {
pub struct Pet<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
color_offset: Option<usize>,
}
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); }
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,
})
}
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(&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 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 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(&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 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 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 raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
pub struct PetBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
name_written: bool,
color_written: bool,
}
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)?;
impl<'b> PetBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> PetBuilder<'_> {
PetBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
name_written: false,
color_written: false,
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub struct OwnedPet {
pub data: bytes::Bytes,
}
pub fn color(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(2, value)?;
self.color_written = true;
Ok(self)
}
impl roto_runtime::RotoOwned for OwnedPet {
type Reader<'a> = Pet<'a>;
fn reader(&self) -> Pet<'_> {
Pet::new(&self.data).expect("failed to create reader")
}
}
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)
}
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 fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
}
}
pub struct OwnedPet {
pub data: bytes::Bytes,
}
pub enum Choice<'a> {
c1(&'a str),
c2(bool),
}
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> {
@@ -494,17 +574,21 @@ impl<'a> HelloRequest<'a> {
let mut request_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { request_offset = Some(offset); }
if tag.field_number == 1 {
request_offset = Some(offset);
}
}
Ok(Self {
accessor,
request_offset,
request_offset,
})
}
pub fn request(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.request_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let offset = self
.request_offset
.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
@@ -513,12 +597,13 @@ request_offset,
self.request().or(Ok(&[]))
}
pub fn has_request(&self) -> bool { self.request_offset.is_some() }
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> {
@@ -591,17 +676,21 @@ impl<'a> HelloReply<'a> {
let mut response_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { response_offset = Some(offset); }
if tag.field_number == 1 {
response_offset = Some(offset);
}
}
Ok(Self {
accessor,
response_offset,
response_offset,
})
}
pub fn response(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.response_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let offset = self
.response_offset
.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
@@ -610,12 +699,13 @@ response_offset,
self.response().or(Ok(&[]))
}
pub fn has_response(&self) -> bool { self.response_offset.is_some() }
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> {
@@ -679,7 +769,10 @@ impl roto_runtime::RotoMessage for OwnedHelloReply {
#[tonic::async_trait]
pub trait Greeter: Send + Sync + 'static {
async fn say_hello(&self, request: Request<OwnedHelloRequest>) -> std::result::Result<Response<OwnedHelloReply>, Status>;
async fn say_hello(
&self,
request: Request<OwnedHelloRequest>,
) -> std::result::Result<Response<OwnedHelloReply>, Status>;
}
#[derive(Clone)]
@@ -701,7 +794,8 @@ impl tonic::server::NamedService for GreeterServer {
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>>;
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(()))
@@ -726,8 +820,14 @@ impl Service<http::Request<BoxBody>> for GreeterServer {
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 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..);
@@ -737,16 +837,28 @@ impl Service<http::Request<BoxBody>> for GreeterServer {
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 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 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());
}
};
@@ -762,13 +874,26 @@ impl Service<http::Request<BoxBody>> for GreeterServer {
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());
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());
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())
Ok(http::Response::builder()
.status(200)
.body(BoxBody::new(StatusBody::new(None, 0)))
.unwrap())
})
}
}
+174 -65
View File
@@ -1,9 +1,5 @@
// @generated by protoc-gen-roto — do not edit
#![allow(unused)]
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};
use core::str;
use bytes::{Bytes, BytesMut, Buf, BufMut};
#[allow(unused, unused_imports, unused_assignments, unused_variables, non_camel_case_types)]use roto_runtime::{ProtoAccessor, ProtoBuilder, RevBuilder, Result, RotoError, read_varint, RepeatedFieldIterator};use core::str;use bytes::Buf;#[cfg(feature = "alloc")]use bytes::{Bytes, BytesMut, BufMut};#[cfg(feature = "alloc")]use roto_runtime::RotoMessage;
pub struct UnaryRequest<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
message_offset: Option<usize>,
@@ -80,27 +76,51 @@ impl<'b> UnaryRequestBuilder<'b> {
}
}
pub struct OwnedUnaryRequest {
pub data: bytes::Bytes,
pub struct UnaryRequestRevBuilder<'b> {
builder: roto_runtime::RevBuilder<'b>,
message_written: bool,
}
impl roto_runtime::RotoOwned for OwnedUnaryRequest {
type Reader<'a> = UnaryRequest<'a>;
fn reader(&self) -> UnaryRequest<'_> {
UnaryRequest::new(&self.data).expect("failed to create reader")
impl<'b> UnaryRequestRevBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> UnaryRequestRevBuilder<'_> {
UnaryRequestRevBuilder {
builder: roto_runtime::RevBuilder::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()
}
}
impl roto_runtime::RotoMessage for OwnedUnaryRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedUnaryRequest { data: buf })
}
#[cfg(feature = "alloc")]pub struct OwnedUnaryRequest {pub data: bytes::Bytes,}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
#[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>,
@@ -177,27 +197,51 @@ impl<'b> UnaryResponseBuilder<'b> {
}
}
pub struct OwnedUnaryResponse {
pub data: bytes::Bytes,
pub struct UnaryResponseRevBuilder<'b> {
builder: roto_runtime::RevBuilder<'b>,
reply_written: bool,
}
impl roto_runtime::RotoOwned for OwnedUnaryResponse {
type Reader<'a> = UnaryResponse<'a>;
fn reader(&self) -> UnaryResponse<'_> {
UnaryResponse::new(&self.data).expect("failed to create reader")
impl<'b> UnaryResponseRevBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> UnaryResponseRevBuilder<'_> {
UnaryResponseRevBuilder {
builder: roto_runtime::RevBuilder::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()
}
}
impl roto_runtime::RotoMessage for OwnedUnaryResponse {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedUnaryResponse { data: buf })
}
#[cfg(feature = "alloc")]pub struct OwnedUnaryResponse {pub data: bytes::Bytes,}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
#[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>,
@@ -274,27 +318,51 @@ impl<'b> StreamingRequestBuilder<'b> {
}
}
pub struct OwnedStreamingRequest {
pub data: bytes::Bytes,
pub struct StreamingRequestRevBuilder<'b> {
builder: roto_runtime::RevBuilder<'b>,
query_written: bool,
}
impl roto_runtime::RotoOwned for OwnedStreamingRequest {
type Reader<'a> = StreamingRequest<'a>;
fn reader(&self) -> StreamingRequest<'_> {
StreamingRequest::new(&self.data).expect("failed to create reader")
impl<'b> StreamingRequestRevBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> StreamingRequestRevBuilder<'_> {
StreamingRequestRevBuilder {
builder: roto_runtime::RevBuilder::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()
}
}
impl roto_runtime::RotoMessage for OwnedStreamingRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedStreamingRequest { data: buf })
}
#[cfg(feature = "alloc")]pub struct OwnedStreamingRequest {pub data: bytes::Bytes,}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
#[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>,
@@ -371,64 +439,105 @@ impl<'b> StreamingResponseBuilder<'b> {
}
}
pub struct OwnedStreamingResponse {
pub data: bytes::Bytes,
pub struct StreamingResponseRevBuilder<'b> {
builder: roto_runtime::RevBuilder<'b>,
item_written: bool,
}
impl roto_runtime::RotoOwned for OwnedStreamingResponse {
type Reader<'a> = StreamingResponse<'a>;
fn reader(&self) -> StreamingResponse<'_> {
StreamingResponse::new(&self.data).expect("failed to create reader")
impl<'b> StreamingResponseRevBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> StreamingResponseRevBuilder<'_> {
StreamingResponseRevBuilder {
builder: roto_runtime::RevBuilder::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()
}
}
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")]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 = "std")]
use tonic::{Request, Response, Status};
#[cfg(feature = "std")]
use tokio_stream::Stream;
#[cfg(feature = "std")]
use std::pin::Pin;
#[cfg(feature = "std")]
use std::sync::Arc;
#[cfg(feature = "std")]
use std::task::{Context, Poll};
#[cfg(feature = "std")]
use std::future::Future;
#[cfg(feature = "std")]
use tonic::body::BoxBody;
#[cfg(feature = "std")]
use tower::Service;
#[cfg(feature = "std")]
use futures_util::StreamExt;
#[cfg(feature = "std")]
use http_body_util::BodyExt;
#[cfg(feature = "std")]
use http_body::Body;
#[cfg(feature = "std")]
use crate::{BufferPool, StatusBody};
#[cfg(feature = "std")]
#[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 = "std")]
#[derive(Clone)]
pub struct InteropServiceServer {
inner: Arc<dyn InteropService>,
pool: Arc<BufferPool>,
}
#[cfg(feature = "std")]
impl InteropServiceServer {
pub fn new(inner: Arc<dyn InteropService>, pool: Arc<BufferPool>) -> Self {
Self { inner, pool }
}
}
#[cfg(feature = "std")]
impl tonic::server::NamedService for InteropServiceServer {
const NAME: &'static str = "interop.InteropService";
}
#[cfg(feature = "std")]
impl Service<http::Request<BoxBody>> for InteropServiceServer {
type Response = http::Response<BoxBody>;
type Error = std::convert::Infallible;
@@ -462,12 +571,12 @@ impl Service<http::Request<BoxBody>> for InteropServiceServer {
}
let payload = bytes_vec.slice(5..);
#[allow(unused_assignments)]
let mut routed = false;
if path == "/interop.InteropService/UnaryCall" {
let request_msg = match OwnedUnaryRequest::decode(payload) {
Ok(msg) => msg,
Err(e) => {
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());
}
@@ -475,7 +584,7 @@ impl Service<http::Request<BoxBody>> for InteropServiceServer {
let response = match inner.unary_call(Request::new(request_msg)).await {
Ok(res) => res,
Err(e) => {
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());
}
+4 -4
View File
@@ -4,9 +4,9 @@ version = "0.1.0"
edition = "2024"
[dependencies]
bytes = { version = "1.7", default-features = false }
bytes = { version = "1.7", default-features = false, optional = true }
[features]
default = ["std", "alloc"]
std = []
alloc = []
default = ["std"]
std = ["alloc"]
alloc = ["bytes/std"]
+391 -3
View File
@@ -7,7 +7,6 @@ extern crate alloc;
extern crate std;
use core::fmt;
use bytes::BufMut;
pub struct MapFieldIterator<'a> {
inner: RepeatedFieldIterator<'a>,
@@ -64,11 +63,15 @@ impl std::error::Error for RotoError {}
pub type Result<T> = core::result::Result<T, RotoError>;
#[cfg(feature = "alloc")]
pub trait RotoOwned {
type Reader<'a> where Self: 'a;
type Reader<'a>
where
Self: 'a;
fn reader(&self) -> Self::Reader<'_>;
}
#[cfg(feature = "alloc")]
pub trait RotoMessage: Sized {
fn decode(buf: bytes::Bytes) -> Result<Self>;
fn bytes(&self) -> bytes::Bytes;
@@ -442,7 +445,7 @@ impl<'a> Iterator for RawFieldIterator<'a> {
mod tests {
use super::*;
#[cfg(feature = "alloc")]
use alloc::{vec, vec::{Vec}};
use alloc::{vec, vec::Vec};
#[test]
fn test_varint_read_write() {
@@ -745,6 +748,199 @@ mod tests {
}
assert_eq!(found_count, essential_fields.len());
}
#[test]
fn test_revbuilder_string_wire_format() {
let mut buf = [0u8; 256];
let mut builder = RevBuilder::new(&mut buf);
builder.write_string(1, "hello").unwrap();
let data = builder.finish().unwrap();
assert_eq!(data, &[0x0A, 0x05, b'h', b'e', b'l', b'l', b'o']);
let acc = ProtoAccessor::new(data).unwrap();
let (val, wt) = acc.get_value(1).unwrap();
assert_eq!(wt, WireType::LengthDelimited);
assert_eq!(val, b"hello");
}
#[test]
fn test_revbuilder_multiple_fields_reverse_order() {
let mut buf = [0u8; 256];
let mut builder = RevBuilder::new(&mut buf);
builder.write_varint(3, 300).unwrap();
builder.write_string(2, "hi").unwrap();
builder.write_varint(1, 42).unwrap();
let data = builder.finish().unwrap();
let acc = ProtoAccessor::new(data).unwrap();
let (val1, _) = acc.get_value(1).unwrap();
let (v1, _) = read_varint(val1).unwrap();
assert_eq!(v1, 42);
let (val2, _) = acc.get_value(2).unwrap();
assert_eq!(val2, b"hi");
let (val3, _) = acc.get_value(3).unwrap();
let (v3, _) = read_varint(val3).unwrap();
assert_eq!(v3, 300);
}
#[test]
fn test_revbuilder_nested_message() {
let mut buf = [0u8; 256];
let mut builder = RevBuilder::new(&mut buf);
// Mark before writing inner fields
let inner_start = builder.mark();
// Write inner field
builder.write_varint(1, 100).unwrap();
// Write length prefix and tag for outer field
builder.write_length_since(inner_start).unwrap();
builder.put_tag(1, WireType::LengthDelimited).unwrap();
let data = builder.finish().unwrap();
let acc = ProtoAccessor::new(data).unwrap();
let (nested_bytes, wt) = acc.get_value(1).unwrap();
assert_eq!(wt, WireType::LengthDelimited);
let nested_acc = ProtoAccessor::new(nested_bytes).unwrap();
let (inner_val, _) = nested_acc.get_value(1).unwrap();
let (inner_v, _) = read_varint(inner_val).unwrap();
assert_eq!(inner_v, 100);
}
#[test]
fn test_revbuilder_readable_by_accessor() {
let mut buf = [0u8; 256];
let mut builder = RevBuilder::new(&mut buf);
builder.write_fixed64(6, 0xDEADBEEFCAFEBABE).unwrap();
builder.write_fixed32(5, 0xDEADBEEFu32).unwrap();
builder.write_bytes(4, &[1, 2, 3, 255, 0]).unwrap();
builder.write_string(3, "protobuf").unwrap();
builder.write_int32(2, -42).unwrap();
builder.write_varint(1, 0x1234ABCD).unwrap();
let data = builder.finish().unwrap();
let acc = ProtoAccessor::new(data).unwrap();
let (v1, _) = acc.get_value(1).unwrap();
let (val1, _) = read_varint(v1).unwrap();
assert_eq!(val1, 0x1234ABCD);
let (v2, _) = acc.get_value(2).unwrap();
let (val2, _) = read_varint(v2).unwrap();
assert_eq!(val2, -42i32 as u64);
assert_eq!(acc.get_value(3).unwrap().0, b"protobuf");
assert_eq!(acc.get_value(4).unwrap().0, &[1, 2, 3, 255, 0]);
assert_eq!(acc.get_value(5).unwrap().0, &(0xDEADBEEFu32).to_le_bytes());
assert_eq!(
acc.get_value(6).unwrap().0,
&(0xDEADBEEFCAFEBABEu64).to_le_bytes()
);
}
#[test]
fn test_revbuilder_buffer_overflow() {
let mut buf = [0u8; 4];
let mut builder = RevBuilder::new(&mut buf);
let result = builder.write_string(1, "hello");
assert_eq!(result, Err(RotoError::BufferOverflow));
let mut buf2 = [0u8; 1];
let mut builder2 = RevBuilder::new(&mut buf2);
let result2 = builder2.write_varint(1000, 1);
assert_eq!(result2, Err(RotoError::BufferOverflow));
}
#[test]
fn test_revbuilder_matches_proto_builder() {
let mut fwd_buf = [0u8; 256];
let mut fwd_builder = ProtoBuilder::new(&mut fwd_buf);
fwd_builder.write_string(1, "hello").unwrap();
fwd_builder.write_int32(2, 42).unwrap();
fwd_builder.write_bytes(3, &[1, 2, 3]).unwrap();
let fwd_data = fwd_builder.finish().unwrap();
let mut rev_buf = [0u8; 256];
let mut rev_builder = RevBuilder::new(&mut rev_buf);
rev_builder.write_bytes(3, &[1, 2, 3]).unwrap();
rev_builder.write_int32(2, 42).unwrap();
rev_builder.write_string(1, "hello").unwrap();
let rev_data = rev_builder.finish().unwrap();
let fwd_acc = ProtoAccessor::new(fwd_data).unwrap();
let rev_acc = ProtoAccessor::new(rev_data).unwrap();
let (f1, _) = fwd_acc.get_value(1).unwrap();
let (r1, _) = rev_acc.get_value(1).unwrap();
assert_eq!(f1, r1);
assert_eq!(f1, b"hello");
let (f2, _) = fwd_acc.get_value(2).unwrap();
let (r2, _) = rev_acc.get_value(2).unwrap();
assert_eq!(f2, r2);
assert_eq!(f2, &[42]);
let (f3, _) = fwd_acc.get_value(3).unwrap();
let (r3, _) = rev_acc.get_value(3).unwrap();
assert_eq!(f3, r3);
assert_eq!(f3, &[1, 2, 3]);
}
#[test]
fn test_revbuilder_empty_finish() {
let mut buf = [1u8; 64];
let builder = RevBuilder::new(&mut buf);
let data = builder.finish().unwrap();
assert!(data.is_empty());
}
#[test]
fn test_revbuilder_map_entry() {
let mut buf = [0u8; 256];
let mut builder = RevBuilder::new(&mut buf);
let mut key_buf = [0u8; 16];
let key_tag_len = Tag::encode(1, WireType::Varint, &mut key_buf).unwrap();
key_buf[key_tag_len] = 7;
let key_bytes = &key_buf[..key_tag_len + 1];
let mut val_buf = [0u8; 16];
let val_tag_len = Tag::encode(2, WireType::LengthDelimited, &mut val_buf).unwrap();
val_buf[val_tag_len] = 1;
val_buf[val_tag_len + 1] = b'x';
let val_bytes = &val_buf[..val_tag_len + 2];
builder.write_map_entry(10, key_bytes, val_bytes).unwrap();
let data = builder.finish().unwrap();
let acc = ProtoAccessor::new(data).unwrap();
let (entry_bytes, _) = acc.get_value(10).unwrap();
let entry_acc = ProtoAccessor::new(entry_bytes).unwrap();
let (key_v, _) = entry_acc.get_value(1).unwrap();
let (key_val, _) = read_varint(key_v).unwrap();
assert_eq!(key_val, 7);
let (val_v, _) = entry_acc.get_value(2).unwrap();
assert_eq!(val_v, b"x");
}
#[test]
fn test_revbuilder_written_since() {
let mut buf = [0u8; 256];
let mut builder = RevBuilder::new(&mut buf);
let m1 = builder.mark();
builder.write_string(1, "test").unwrap();
assert!(builder.written_since(m1) > 0);
let m2 = builder.mark();
builder.write_varint(2, 999).unwrap();
assert!(builder.written_since(m2) > 0);
let total = builder.written_since(m1);
let just_field2 = builder.written_since(m2);
assert!(total > just_field2);
}
#[test]
fn test_revbuilder_write_raw() {
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();
let src_data = src_builder.finish().unwrap();
let src_acc = ProtoAccessor::new(src_data).unwrap();
let mut dst_buf = [0u8; 128];
let mut dst_builder = RevBuilder::new(&mut dst_buf);
let fields: Vec<_> = src_acc.raw_fields().collect();
for item in fields.into_iter().rev() {
let (_, raw_bytes) = item.unwrap();
dst_builder.write_raw(raw_bytes).unwrap();
}
let dst_data = dst_builder.finish().unwrap();
let dst_acc = ProtoAccessor::new(dst_data).unwrap();
let (d1, _) = dst_acc.get_value(1).unwrap();
assert_eq!(d1, b"original");
let (d2, _) = dst_acc.get_value(2).unwrap();
assert_eq!(d2, &[99]);
}
}
pub struct ProtoBuilder<'a> {
@@ -840,10 +1036,15 @@ impl<'a> ProtoBuilder<'a> {
}
}
#[cfg(feature = "alloc")]
use bytes::BufMut;
#[cfg(feature = "alloc")]
pub struct BufMutBuilder<'a, B: BufMut> {
buf: &'a mut B,
}
#[cfg(feature = "alloc")]
impl<'a, B: BufMut> BufMutBuilder<'a, B> {
pub fn new(buf: &'a mut B) -> Self {
Self { buf }
@@ -922,3 +1123,190 @@ impl<'a, B: BufMut> BufMutBuilder<'a, B> {
Ok(())
}
}
/// A reverse-direction protobuf builder that writes fields BACKWARDS through
/// a buffer, from the end toward the beginning. This enables zero-copy
/// single-pass encoding for length-delimited payloads: write the payload
/// first, compute its size, then write the length varint and tag going backwards.
///
/// After encoding, the valid data is in `buf[pos..buf.len()]` — no memmove is
/// needed because the first byte we wrote (now at `pos`) IS the first byte of
/// the wire-format message.
///
/// # Invariant
///
/// `pos` always points to the **start** of valid data. Data lives in
/// `buf[pos..buf.len()]`. `pos` starts at `buf.len()` (empty) and
/// decreases as we write.
pub struct RevBuilder<'a> {
buf: &'a mut [u8],
pos: usize,
}
impl<'a> RevBuilder<'a> {
/// Create a new `RevBuilder` that writes backwards into `buf`.
///
/// The buffer is initially empty; valid data grows from the end
/// toward the beginning.
pub fn new(buf: &'a mut [u8]) -> Self {
let pos = buf.len();
Self { buf, pos }
}
// ------------------------------------------------------------------
// Low-level primitives (write at current pos, moving pos left)
// ------------------------------------------------------------------
/// Encode `value` as a varint and place it at the current position,
/// moving `pos` left by the encoded length.
pub fn put_varint(&mut self, value: u64) -> Result<()> {
let mut temp = [0u8; 10];
let len = write_varint(value, &mut temp)?;
if self.pos < len {
return Err(RotoError::BufferOverflow);
}
self.pos -= len;
self.buf[self.pos..self.pos + len].copy_from_slice(&temp[..len]);
Ok(())
}
/// Copy `bytes` into the buffer at the current position, moving `pos`
/// left by `bytes.len()`.
pub fn put_slice(&mut self, bytes: &[u8]) -> Result<()> {
let len = bytes.len();
if self.pos < len {
return Err(RotoError::BufferOverflow);
}
self.pos -= len;
self.buf[self.pos..self.pos + len].copy_from_slice(bytes);
Ok(())
}
/// Encode a tag varint at the current position.
pub fn put_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)?;
if self.pos < len {
return Err(RotoError::BufferOverflow);
}
self.pos -= len;
self.buf[self.pos..self.pos + len].copy_from_slice(&temp[..len]);
Ok(())
}
// ------------------------------------------------------------------
// High-level field writers
// ------------------------------------------------------------------
/// Encode a length-delimited string field.
///
/// Write order: payload → length varint → tag (all going backwards),
/// which produces the correct wire order: `[tag][len][payload]`.
pub fn write_string(&mut self, field_number: u32, value: &str) -> Result<()> {
let bytes = value.as_bytes();
// payload first (moves pos left)
self.put_slice(bytes)?;
// length
self.put_varint(bytes.len() as u64)?;
// tag
self.put_tag(field_number, WireType::LengthDelimited)?;
Ok(())
}
/// Encode a length-delimited bytes field.
pub fn write_bytes(&mut self, field_number: u32, value: &[u8]) -> Result<()> {
self.put_slice(value)?;
self.put_varint(value.len() as u64)?;
self.put_tag(field_number, WireType::LengthDelimited)?;
Ok(())
}
/// Encode a varint field (tag + varint value).
pub fn write_varint(&mut self, field_number: u32, value: u64) -> Result<()> {
self.put_varint(value)?;
self.put_tag(field_number, WireType::Varint)?;
Ok(())
}
/// Encode an int32 field (same wire encoding as varint).
pub fn write_int32(&mut self, field_number: u32, value: i32) -> Result<()> {
self.write_varint(field_number, value as u64)
}
/// Encode a fixed32 field (tag + 4-byte LE value).
pub fn write_fixed32(&mut self, field_number: u32, value: u32) -> Result<()> {
self.put_slice(&value.to_le_bytes())?;
self.put_tag(field_number, WireType::Fixed32)?;
Ok(())
}
/// Encode a fixed64 field (tag + 8-byte LE value).
pub fn write_fixed64(&mut self, field_number: u32, value: u64) -> Result<()> {
self.put_slice(&value.to_le_bytes())?;
self.put_tag(field_number, WireType::Fixed64)?;
Ok(())
}
/// Write a pre-encoded field (tag + value) verbatim into the buffer.
///
/// Useful for the `with()` pattern: copy raw bytes from an existing
/// message without re-encoding.
pub fn write_raw(&mut self, raw_bytes: &[u8]) -> Result<()> {
self.put_slice(raw_bytes)?;
Ok(())
}
/// Encode a map entry as a length-delimited field containing
/// `key_encoded` followed by `value_encoded`.
pub fn write_map_entry(
&mut self,
field_number: u32,
key_encoded: &[u8],
value_encoded: &[u8],
) -> Result<()> {
// We want final bytes: [key...][value...] (in wire order)
// Written backwards: first write value at right, then key at left.
// So the last thing written (key) ends up at the start (left).
self.put_slice(value_encoded)?;
self.put_slice(key_encoded)?;
let entry_len = key_encoded.len() + value_encoded.len();
self.put_varint(entry_len as u64)?;
self.put_tag(field_number, WireType::LengthDelimited)?;
Ok(())
}
// ------------------------------------------------------------------
// Nested-message helpers
// ------------------------------------------------------------------
/// Return the current position (start of valid data).
///
/// Call this *before* writing nested fields, then pass the returned
/// value to [`write_length_since`](Self::write_length_since) to
/// encode the length prefix.
pub fn mark(&self) -> usize {
self.pos
}
/// Return the number of bytes written since `mark`.
pub fn written_since(&self, mark: usize) -> usize {
mark - self.pos
}
/// Write the length-prefix varint for the bytes that were written
/// between `mark` and now. Call this *after* encoding the nested
/// message fields.
pub fn write_length_since(&mut self, mark: usize) -> Result<()> {
let len = mark - self.pos;
self.put_varint(len as u64)?;
Ok(())
}
/// Finalize and return the encoded slice.
///
/// The valid data is `buf[pos..buf.len()]` — the bytes we wrote,
/// in correct wire-format order.
pub fn finish(self) -> Result<&'a mut [u8]> {
Ok(&mut self.buf[self.pos..])
}
}