Compare commits

..

3 Commits

Author SHA1 Message Date
charles 7e43e09f66 Clean up generated code and implement strip_boilerplate
Remove redundant newlines and duplicate headers from generated files
and prevent the creation of empty sub-modules. Implement the
`strip_boilerplate` function to remove generated headers and
attributes, including a comprehensive test suite.
2026-05-19 23:08:05 -07:00
charles 117cbf812b Introduce alloc feature for optional allocation
Wrap heap-allocated types and service generation in the `alloc` feature
flag to support environments without a memory allocator.
2026-05-19 21:55:18 -07:00
charles 6910f11d69 Add no_std test example and update alloc gating
Update codegen to make `bytes` imports conditional on the `alloc`
feature and add a test example for the `thumbv7em-none-eabihf`
target.
2026-05-18 08:43:22 -07:00
11 changed files with 1024 additions and 72 deletions
Generated
+26
View File
@@ -310,6 +310,12 @@ dependencies = [
"itertools", "itertools",
] ]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.8.6" version = "0.8.6"
@@ -347,6 +353,16 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "embedded-alloc"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddae17915accbac2cfbc64ea0ae6e3b330e6ea124ba108dada63646fd3c6f815"
dependencies = [
"critical-section",
"linked_list_allocator",
]
[[package]] [[package]]
name = "env_filter" name = "env_filter"
version = "1.0.1" version = "1.0.1"
@@ -546,6 +562,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
name = "hello-world" name = "hello-world"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
@@ -776,6 +793,12 @@ version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "linked_list_allocator"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.12.1" version = "0.12.1"
@@ -836,6 +859,8 @@ checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
name = "no_std_test" name = "no_std_test"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bytes",
"embedded-alloc",
"roto-runtime", "roto-runtime",
] ]
@@ -1201,6 +1226,7 @@ dependencies = [
name = "roto-tonic" name = "roto-tonic"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
+184 -46
View File
@@ -1,13 +1,25 @@
use crate::google::protobuf::descriptor::{ use crate::google::protobuf::descriptor::{
DescriptorProto, EnumDescriptorProto, FieldDescriptorProto, FileDescriptorProto, DescriptorProto, EnumDescriptorProto, FieldDescriptorProto, FileDescriptorProto,
FileDescriptorSet, MessageOptions, MethodDescriptorProto, OneofDescriptorProto, ServiceDescriptorProto, FileDescriptorSet, MessageOptions, MethodDescriptorProto, OneofDescriptorProto,
ServiceDescriptorProto,
}; };
use roto_runtime::ProtoAccessor; use roto_runtime::ProtoAccessor;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::str; use std::str;
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"; const DATA_IMPORTS: &str = "use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};\nuse core::str;\n#[cfg(feature = \"alloc\")]\nuse bytes::{Bytes, BytesMut, Buf, BufMut};\n";
const SERVICE_IMPORTS: &str = "use tonic::{Request, Response, Status};\nuse tokio_stream::Stream;\nuse std::pin::Pin;\nuse std::sync::Arc;\nuse std::task::{Context, Poll};\nuse std::future::Future;\nuse tonic::body::BoxBody;\nuse tower::Service;\nuse futures_util::StreamExt;\nuse http_body_util::BodyExt;\nuse http_body::Body;\nuse crate::{BufferPool, StatusBody};\n"; const SERVICE_IMPORTS: &str = "#[cfg(feature = \"alloc\")]\nuse tonic::{Request, Response, Status};\n\
#[cfg(feature = \"alloc\")]\nuse tokio_stream::Stream;\n\
#[cfg(feature = \"alloc\")]\nuse std::pin::Pin;\n\
#[cfg(feature = \"alloc\")]\nuse std::sync::Arc;\n\
#[cfg(feature = \"alloc\")]\nuse std::task::{Context, Poll};\n\
#[cfg(feature = \"alloc\")]\nuse std::future::Future;\n\
#[cfg(feature = \"alloc\")]\nuse tonic::body::BoxBody;\n\
#[cfg(feature = \"alloc\")]\nuse tower::Service;\n\
#[cfg(feature = \"alloc\")]\nuse futures_util::StreamExt;\n\
#[cfg(feature = \"alloc\")]\nuse http_body_util::BodyExt;\n\
#[cfg(feature = \"alloc\")]\nuse http_body::Body;\n\
#[cfg(feature = \"alloc\")]\nuse crate::{BufferPool, StatusBody};\n";
pub fn to_pascal_case(s: &str) -> String { pub fn to_pascal_case(s: &str) -> String {
s.split('_') s.split('_')
@@ -36,7 +48,11 @@ pub fn to_snake_case(s: &str) -> String {
result result
} }
fn map_type_to_rust_accessor(field_type: i32, label: i32, is_map: bool) -> (String, String, String) { fn map_type_to_rust_accessor(
field_type: i32,
label: i32,
is_map: bool,
) -> (String, String, String) {
if label == 3 { if label == 3 {
// LABEL_REPEATED // LABEL_REPEATED
let iterator_type = if is_map { let iterator_type = if is_map {
@@ -54,7 +70,7 @@ fn map_type_to_rust_accessor(field_type: i32, label: i32, is_map: bool) -> (Stri
match field_type { match field_type {
9 => ( 9 => (
"&'a str".to_string(), "&'a str".to_string(),
"std::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(), "core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
"\"\"".to_string(), "\"\"".to_string(),
), // TYPE_STRING ), // TYPE_STRING
1 => ( 1 => (
@@ -440,18 +456,30 @@ 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 fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {{\n self.builder.finish()\n }}\n}}\n\n"));
output.push_str(&format!("#[cfg(feature = \"alloc\")]\npub struct Owned{} {{\n", msg_name)); output.push_str(&format!(
"#[cfg(feature = \"alloc\")]\npub struct Owned{} {{\n",
msg_name
));
output.push_str(" pub data: bytes::Bytes,\n"); output.push_str(" pub data: bytes::Bytes,\n");
output.push_str("}\n\n"); output.push_str("}\n\n");
output.push_str(&format!("#[cfg(feature = \"alloc\")]\nimpl roto_runtime::RotoOwned for Owned{} {{\n", msg_name)); output.push_str(&format!(
"#[cfg(feature = \"alloc\")]\nimpl roto_runtime::RotoOwned for Owned{} {{\n",
msg_name
));
output.push_str(&format!(" type Reader<'a> = {}<'a>;\n", msg_name)); output.push_str(&format!(" type Reader<'a> = {}<'a>;\n", msg_name));
output.push_str(&format!(" fn reader(&self) -> {}<'_> {{\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(&format!(
" {}::new(&self.data).expect(\"failed to create reader\")\n",
msg_name
));
output.push_str(" }\n"); output.push_str(" }\n");
output.push_str("}\n\n"); output.push_str("}\n\n");
output.push_str(&format!("#[cfg(feature = \"alloc\")]\nimpl roto_runtime::RotoMessage for Owned{} {{\n", msg_name)); output.push_str(&format!(
"#[cfg(feature = \"alloc\")]\nimpl roto_runtime::RotoMessage for Owned{} {{\n",
msg_name
));
output.push_str(" fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {\n"); output.push_str(" fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {\n");
output.push_str(&format!(" Ok(Owned{} {{ data: buf }})\n", msg_name)); output.push_str(&format!(" Ok(Owned{} {{ data: buf }})\n", msg_name));
output.push_str(" }\n\n"); output.push_str(" }\n\n");
@@ -551,11 +579,18 @@ where
} }
} }
let rust_file_name = format!("{}.rs", std::path::Path::new(proto_name).file_stem().unwrap().to_str().unwrap()); let rust_file_name = format!(
"{}.rs",
std::path::Path::new(proto_name)
.file_stem()
.unwrap()
.to_str()
.unwrap()
);
let mut output = String::new(); let mut output = String::new();
output.push_str("// @generated by protoc-gen-roto — do not edit\n"); output.push_str("// @generated by protoc-gen-roto — do not edit\n");
output.push_str("#[allow(unused_imports)]\n\n"); output.push_str("#[allow(unused_imports)]\n");
output.push_str(imports); output.push_str(imports);
for dep_res in file_proto.dependency() { for dep_res in file_proto.dependency() {
@@ -600,10 +635,12 @@ where
} }
let mut root_mod_content = String::new(); let mut root_mod_content = String::new();
root_mod_content.push_str("// @generated by protoc-gen-roto — do not edit\n");
root_mod_content.push_str("#![allow(unused_imports)]\n\n");
let mut sorted_root_mods: Vec<_> = root_mods.into_iter().collect(); let mut sorted_root_mods: Vec<_> = root_mods.into_iter().collect();
sorted_root_mods.sort(); sorted_root_mods.sort();
if sorted_root_mods.is_empty() {
root_mod_content.push_str("// @generated by protoc-gen-roto — do not edit\n");
root_mod_content.push_str("#![allow(unused_imports)]\n\n");
}
for m in sorted_root_mods { for m in sorted_root_mods {
root_mod_content.push_str(&format!("pub mod {};\n", m)); root_mod_content.push_str(&format!("pub mod {};\n", m));
} }
@@ -611,6 +648,9 @@ where
for (mod_path, sub_mods) in mod_files { for (mod_path, sub_mods) in mod_files {
let mut content = String::new(); let mut content = String::new();
if sub_mods.is_empty() {
continue;
}
content.push_str("// @generated by protoc-gen-roto — do not edit\n"); content.push_str("// @generated by protoc-gen-roto — do not edit\n");
content.push_str("#![allow(unused_imports)]\n\n"); content.push_str("#![allow(unused_imports)]\n\n");
let mut sorted_subs: Vec<_> = sub_mods.into_iter().collect(); let mut sorted_subs: Vec<_> = sub_mods.into_iter().collect();
@@ -639,7 +679,8 @@ pub fn generate_protobuf_code(
for enum_res in file_proto.enum_type() { for enum_res in file_proto.enum_type() {
let (enum_data, _) = enum_res.expect("Failed to iterate enum"); let (enum_data, _) = enum_res.expect("Failed to iterate enum");
write_enum( write_enum(
&EnumDescriptorProto::new(enum_data).expect("Failed to parse EnumDescriptorProto"), &EnumDescriptorProto::new(enum_data)
.expect("Failed to parse EnumDescriptorProto"),
output, output,
); );
} }
@@ -665,14 +706,15 @@ pub fn generate_service_code(
set, set,
files_to_generate, files_to_generate,
generate_mod_files, generate_mod_files,
SERVICE_IMPORTS, "",
|file_proto, output| { |file_proto, output| {
let package = file_proto.package().unwrap_or("").to_string(); let package = file_proto.package().unwrap_or("").to_string();
// Services // Services
for svc_res in file_proto.service() { for svc_res in file_proto.service() {
let (svc_data, _) = svc_res.expect("Failed to iterate service"); let (svc_data, _) = svc_res.expect("Failed to iterate service");
write_service( write_service(
&ServiceDescriptorProto::new(svc_data).expect("Failed to parse ServiceDescriptorProto"), &ServiceDescriptorProto::new(svc_data)
.expect("Failed to parse ServiceDescriptorProto"),
&package, &package,
output, output,
); );
@@ -709,13 +751,7 @@ pub fn generate_rust_code(
result.sort_by(|a, b| a.0.cmp(&b.0)); result.sort_by(|a, b| a.0.cmp(&b.0));
if generate_mod_files { if generate_mod_files {
let mods = generate_files_common( let mods = generate_files_common(set, files_to_generate, true, "", |_, _| {});
set,
files_to_generate,
true,
"",
|_, _| {},
);
for (filename, content) in mods { for (filename, content) in mods {
if filename == "mod.rs" || filename.contains("/mod.rs") { if filename == "mod.rs" || filename.contains("/mod.rs") {
result.push((filename, content)); result.push((filename, content));
@@ -727,25 +763,81 @@ pub fn generate_rust_code(
} }
fn strip_boilerplate(content: &str) -> String { fn strip_boilerplate(content: &str) -> String {
// Find the first occurrence of a service definition or a trait let mut stripped = content;
// In our case, the services start after the dependency imports and a newline. let header = "// @generated by protoc-gen-roto — do not edit\n";
if let Some(idx) = content.find("pub trait ") { if stripped.starts_with(header) {
return content[idx..].to_string(); stripped = &stripped[header.len()..];
let patterns = [
"#[allow(unused_imports)]\n",
"#![allow(unused_imports)]\n\n",
"#![allow(unused_imports)]\n",
];
for pattern in patterns {
if stripped.starts_with(pattern) {
stripped = &stripped[pattern.len()..];
break;
} }
if let Some(idx) = content.find("pub struct ") {
// This might be a message, but generate_service_code only generates services (and their server structs)
return content[idx..].to_string();
} }
content.to_string() }
stripped.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strip_boilerplate_standard() {
let content = "// @generated by protoc-gen-roto — do not edit\n#[allow(unused_imports)]\nuse std::str;\n";
let stripped = strip_boilerplate(content);
assert_eq!(stripped, "use std::str;\n");
}
#[test]
fn test_strip_boilerplate_crate_level() {
let content = "// @generated by protoc-gen-roto — do not edit\n#![allow(unused_imports)]\n\nuse std::str;\n";
let stripped = strip_boilerplate(content);
assert_eq!(stripped, "use std::str;\n");
}
#[test]
fn test_strip_boilerplate_crate_level_single_newline() {
let content = "// @generated by protoc-gen-roto — do not edit\n#![allow(unused_imports)]\nuse std::str;\n";
let stripped = strip_boilerplate(content);
assert_eq!(stripped, "use std::str;\n");
}
#[test]
fn test_strip_boilerplate_no_boilerplate() {
let content = "use std::str;\n";
let stripped = strip_boilerplate(content);
assert_eq!(stripped, "use std::str;\n");
}
#[test]
fn test_strip_boilerplate_only_header() {
let content = "// @generated by protoc-gen-roto — do not edit\n";
let stripped = strip_boilerplate(content);
assert_eq!(stripped, "");
}
} }
fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut String) { fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut String) {
output.push_str(SERVICE_IMPORTS);
output.push_str("\n");
let svc_name = to_pascal_case(svc_proto.name().unwrap()); let svc_name = to_pascal_case(svc_proto.name().unwrap());
output.push_str(&format!("#[tonic::async_trait]\npub trait {}: Send + Sync + 'static {{\n", svc_name)); output.push_str("#[cfg(feature = \"alloc\")]\n");
output.push_str(&format!(
"#[async_trait::async_trait]\npub trait {}: Send + Sync + 'static {{\n",
svc_name
));
for method_res in svc_proto.method() { for method_res in svc_proto.method() {
let (method_data, _) = method_res.expect("Failed to iterate method"); let (method_data, _) = method_res.expect("Failed to iterate method");
let method_proto = MethodDescriptorProto::new(method_data).expect("Failed to parse MethodDescriptorProto"); let method_proto =
MethodDescriptorProto::new(method_data).expect("Failed to parse MethodDescriptorProto");
let method_name = to_snake_case(method_proto.name().unwrap()); let method_name = to_snake_case(method_proto.name().unwrap());
let input_full_name = method_proto.input_type().unwrap(); let input_full_name = method_proto.input_type().unwrap();
@@ -767,7 +859,10 @@ fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut
}; };
let resp_type = if server_streaming { let resp_type = if server_streaming {
format!("Response<Pin<Box<dyn Stream<Item = std::result::Result<{}, Status>> + Send>>>", output_owned) format!(
"Response<Pin<Box<dyn Stream<Item = std::result::Result<{}, Status>> + Send>>>",
output_owned
)
} else { } else {
format!("Response<{}>", output_owned) format!("Response<{}>", output_owned)
}; };
@@ -780,27 +875,46 @@ fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut
output.push_str("}\n\n"); output.push_str("}\n\n");
let server_name = format!("{}Server", svc_name); let server_name = format!("{}Server", svc_name);
output.push_str(&format!("#[derive(Clone)]\npub struct {} {{\n", server_name)); output.push_str("#[cfg(feature = \"alloc\")]\n");
output.push_str(&format!(
"#[derive(Clone)]\npub struct {} {{\n",
server_name
));
output.push_str(&format!(" inner: Arc<dyn {}>,\n", svc_name)); output.push_str(&format!(" inner: Arc<dyn {}>,\n", svc_name));
output.push_str(" pool: Arc<BufferPool>,\n"); output.push_str(" pool: Arc<BufferPool>,\n");
output.push_str("}\n\n"); output.push_str("}\n\n");
output.push_str("#[cfg(feature = \"alloc\")]\n");
output.push_str(&format!("impl {} {{\n", server_name)); output.push_str(&format!("impl {} {{\n", server_name));
output.push_str(&format!(" pub fn new(inner: Arc<dyn {}>, pool: Arc<BufferPool>) -> Self {{\n", svc_name)); output.push_str(&format!(
" pub fn new(inner: Arc<dyn {}>, pool: Arc<BufferPool>) -> Self {{\n",
svc_name
));
output.push_str(" Self { inner, pool }\n"); output.push_str(" Self { inner, pool }\n");
output.push_str(" }\n"); output.push_str(" }\n");
output.push_str("}\n\n"); output.push_str("}\n\n");
output.push_str(&format!("impl tonic::server::NamedService for {} {{\n", server_name)); output.push_str("#[cfg(feature = \"alloc\")]\n");
output.push_str(&format!(
"impl tonic::server::NamedService for {} {{\n",
server_name
));
let full_svc_name = if package.is_empty() { let full_svc_name = if package.is_empty() {
svc_proto.name().unwrap().to_string() svc_proto.name().unwrap().to_string()
} else { } else {
format!("{}.{}", package, svc_proto.name().unwrap()) format!("{}.{}", package, svc_proto.name().unwrap())
}; };
output.push_str(&format!(" const NAME: &'static str = \"{}\";\n", full_svc_name)); output.push_str(&format!(
" const NAME: &'static str = \"{}\";\n",
full_svc_name
));
output.push_str("}\n\n"); output.push_str("}\n\n");
output.push_str(&format!("impl Service<http::Request<BoxBody>> for {} {{\n", server_name)); output.push_str("#[cfg(feature = \"alloc\")]\n");
output.push_str(&format!(
"impl Service<http::Request<BoxBody>> for {} {{\n",
server_name
));
output.push_str(" type Response = http::Response<BoxBody>;\n"); output.push_str(" type Response = http::Response<BoxBody>;\n");
output.push_str(" type Error = std::convert::Infallible;\n"); output.push_str(" type Error = std::convert::Infallible;\n");
output.push_str(" type Future = Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;\n\n"); output.push_str(" type Future = Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;\n\n");
@@ -837,14 +951,20 @@ fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut
let mut methods = Vec::new(); let mut methods = Vec::new();
for method_res in svc_proto.method() { for method_res in svc_proto.method() {
let (method_data, _) = method_res.expect("Failed to iterate method"); let (method_data, _) = method_res.expect("Failed to iterate method");
let method_proto = MethodDescriptorProto::new(method_data).expect("Failed to parse MethodDescriptorProto"); let method_proto =
MethodDescriptorProto::new(method_data).expect("Failed to parse MethodDescriptorProto");
let original_method_name = method_proto.name().unwrap().to_string(); let original_method_name = method_proto.name().unwrap().to_string();
let method_name = to_snake_case(&original_method_name); let method_name = to_snake_case(&original_method_name);
let input_full_name = method_proto.input_type().unwrap(); let input_full_name = method_proto.input_type().unwrap();
let input_type = input_full_name.split('.').last().unwrap(); let input_type = input_full_name.split('.').last().unwrap();
let input_owned = format!("Owned{}", input_type); let input_owned = format!("Owned{}", input_type);
let server_streaming = method_proto.server_streaming().unwrap_or(false); let server_streaming = method_proto.server_streaming().unwrap_or(false);
methods.push((original_method_name, method_name, input_owned, server_streaming)); methods.push((
original_method_name,
method_name,
input_owned,
server_streaming,
));
} }
for (original_method_name, method_name, input_owned, server_streaming) in methods { for (original_method_name, method_name, input_owned, server_streaming) in methods {
@@ -854,7 +974,12 @@ fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut
let full_path = if package.is_empty() { let full_path = if package.is_empty() {
format!("/{}/{}", svc_proto.name().unwrap(), original_method_name) format!("/{}/{}", svc_proto.name().unwrap(), original_method_name)
} else { } else {
format!("/{}.{}/{}", package, svc_proto.name().unwrap(), original_method_name) format!(
"/{}.{}/{}",
package,
svc_proto.name().unwrap(),
original_method_name
)
}; };
output.push_str(&format!(" if path == \"{}\" {{\n", full_path)); output.push_str(&format!(" if path == \"{}\" {{\n", full_path));
output.push_str(" let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));\n"); output.push_str(" let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));\n");
@@ -865,17 +990,28 @@ fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut
let full_path = if package.is_empty() { let full_path = if package.is_empty() {
format!("/{}/{}", svc_proto.name().unwrap(), original_method_name) format!("/{}/{}", svc_proto.name().unwrap(), original_method_name)
} else { } else {
format!("/{}.{}/{}", package, svc_proto.name().unwrap(), original_method_name) format!(
"/{}.{}/{}",
package,
svc_proto.name().unwrap(),
original_method_name
)
}; };
output.push_str(&format!(" if path == \"{}\" {{\n", full_path)); output.push_str(&format!(" if path == \"{}\" {{\n", full_path));
output.push_str(&format!(" let request_msg = match {}::decode(payload) {{\n", input_owned)); output.push_str(&format!(
" let request_msg = match {}::decode(payload) {{\n",
input_owned
));
output.push_str(" Ok(msg) => msg,\n"); output.push_str(" Ok(msg) => msg,\n");
output.push_str(" Err(e) => {\n"); output.push_str(" Err(e) => {\n");
output.push_str(" let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));\n"); output.push_str(" let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));\n");
output.push_str(" return Ok(http::Response::builder().status(200).body(res_body).unwrap());\n"); output.push_str(" return Ok(http::Response::builder().status(200).body(res_body).unwrap());\n");
output.push_str(" }\n"); output.push_str(" }\n");
output.push_str(" };\n\n"); output.push_str(" };\n\n");
output.push_str(&format!(" let response = match inner.{}(Request::new(request_msg)).await {{\n", method_name)); output.push_str(&format!(
" let response = match inner.{}(Request::new(request_msg)).await {{\n",
method_name
));
output.push_str(" Ok(res) => res,\n"); output.push_str(" Ok(res) => res,\n");
output.push_str(" Err(e) => {\n"); output.push_str(" Err(e) => {\n");
output.push_str(" let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));\n"); output.push_str(" let res_body = BoxBody::new(StatusBody::new(Some(Bytes::from_static(&[0, 0, 0, 0, 0])), 0));\n");
@@ -892,7 +1028,9 @@ fn write_service(svc_proto: &ServiceDescriptorProto, package: &str, output: &mut
output.push_str(" let frame_len = res_buf.len();\n"); output.push_str(" let frame_len = res_buf.len();\n");
output.push_str(" let frame = res_buf.split_to(frame_len).freeze();\n"); output.push_str(" let frame = res_buf.split_to(frame_len).freeze();\n");
output.push_str(" pool.put(res_buf);\n"); output.push_str(" pool.put(res_buf);\n");
output.push_str(" let res_body = BoxBody::new(StatusBody::new(Some(frame), 0));\n"); output.push_str(
" let res_body = BoxBody::new(StatusBody::new(Some(frame), 0));\n",
);
output.push_str(" routed = true;\n"); output.push_str(" routed = true;\n");
output.push_str(" return Ok(http::Response::builder().status(200).header(\"content-type\", \"application/grpc\").body(res_body).unwrap());\n"); output.push_str(" return Ok(http::Response::builder().status(200).header(\"content-type\", \"application/grpc\").body(res_body).unwrap());\n");
output.push_str(" }\n"); output.push_str(" }\n");
+5
View File
@@ -24,7 +24,12 @@ futures-util = "0.3"
http-body-util = "0.1" http-body-util = "0.1"
http = "1.1" http = "1.1"
http-body = "1.0" http-body = "1.0"
async-trait = "0.1"
[build-dependencies] [build-dependencies]
tonic-build = "0.12" tonic-build = "0.12"
roto-codegen = { path = "../../codegen" } roto-codegen = { path = "../../codegen" }
[features]
default = ["alloc"]
alloc = []
+2
View File
@@ -0,0 +1,2 @@
[build]
target = "thumbv7em-none-eabihf"
+5
View File
@@ -5,6 +5,11 @@ edition = "2024"
[dependencies] [dependencies]
roto-runtime = { path = "../../runtime", default-features = false } roto-runtime = { path = "../../runtime", default-features = false }
embedded-alloc = { version = "0.5", optional = true }
bytes = { version = "1.7", default-features = false }
[features]
alloc = ["roto-runtime/alloc", "embedded-alloc"]
[profile.dev] [profile.dev]
panic = "abort" panic = "abort"
+677
View File
@@ -0,0 +1,677 @@
// @generated by protoc-gen-roto — do not edit
#[allow(unused_imports)]
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};
use core::str;
use bytes::{Bytes, BytesMut, Buf, BufMut};
pub struct Hello<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
d_offset: Option<usize>,
f_offset: Option<usize>,
b_offset: Option<usize>,
n_offset: Option<usize>,
l_offset: Option<usize>,
c1_offset: Option<usize>,
c2_offset: Option<usize>,
pets_start: Option<usize>,
pets_end: Option<usize>,
}
impl<'a> Hello<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut name_offset = None;
let mut d_offset = None;
let mut f_offset = None;
let mut b_offset = None;
let mut n_offset = None;
let mut l_offset = None;
let mut c1_offset = None;
let mut c2_offset = None;
let mut pets_start = None;
let mut pets_end = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { name_offset = Some(offset); }
if tag.field_number == 2 { d_offset = Some(offset); }
if tag.field_number == 3 { f_offset = Some(offset); }
if tag.field_number == 4 { b_offset = Some(offset); }
if tag.field_number == 5 { n_offset = Some(offset); }
if tag.field_number == 6 { l_offset = Some(offset); }
if tag.field_number == 7 { c1_offset = Some(offset); }
if tag.field_number == 8 { c2_offset = Some(offset); }
if tag.field_number == 9 {
if pets_start.is_none() { pets_start = Some(offset); }
pets_end = Some(offset);
}
}
Ok(Self {
accessor,
name_offset,
d_offset,
f_offset,
b_offset,
n_offset,
l_offset,
c1_offset,
c2_offset,
pets_start, pets_end,
})
}
pub fn name(&self) -> roto_runtime::Result<&'a str> {
let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
pub fn d(&self) -> roto_runtime::Result<f64> {
let offset = self.d_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(f64::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?))
}
pub fn d_or_default(&self) -> roto_runtime::Result<f64> {
self.d().or(Ok(0.0))
}
pub fn has_d(&self) -> bool { self.d_offset.is_some() }
pub fn f(&self) -> roto_runtime::Result<f32> {
let offset = self.f_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(f32::from_le_bytes(bytes.try_into().map_err(|_| roto_runtime::RotoError::WireFormatViolation)?))
}
pub fn f_or_default(&self) -> roto_runtime::Result<f32> {
self.f().or(Ok(0.0))
}
pub fn has_f(&self) -> bool { self.f_offset.is_some() }
pub fn b(&self) -> roto_runtime::Result<bool> {
let offset = self.b_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn b_or_default(&self) -> roto_runtime::Result<bool> {
self.b().or(Ok(false))
}
pub fn has_b(&self) -> bool { self.b_offset.is_some() }
pub fn n(&self) -> roto_runtime::Result<i32> {
let offset = self.n_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn n_or_default(&self) -> roto_runtime::Result<i32> {
self.n().or(Ok(0))
}
pub fn has_n(&self) -> bool { self.n_offset.is_some() }
pub fn l(&self) -> roto_runtime::Result<i32> {
let offset = self.l_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn l_or_default(&self) -> roto_runtime::Result<i32> {
self.l().or(Ok(0))
}
pub fn has_l(&self) -> bool { self.l_offset.is_some() }
pub fn c1(&self) -> roto_runtime::Result<&'a str> {
let offset = self.c1_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn c1_or_default(&self) -> roto_runtime::Result<&'a str> {
self.c1().or(Ok(""))
}
pub fn has_c1(&self) -> bool { self.c1_offset.is_some() }
pub fn c2(&self) -> roto_runtime::Result<bool> {
let offset = self.c2_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn c2_or_default(&self) -> roto_runtime::Result<bool> {
self.c2().or(Ok(false))
}
pub fn has_c2(&self) -> bool { self.c2_offset.is_some() }
pub fn pets(&self) -> roto_runtime::RepeatedFieldIterator<'a> {
match (self.pets_start, self.pets_end) {
(Some(start), Some(end)) => self.accessor.iter_repeated_range(9, start, end),
_ => self.accessor.iter_repeated(9),
}
}
pub fn which_choice(&self) -> roto_runtime::Result<Option<hello::Choice<'a>> > {
if self.c1_offset.is_some() {
return Ok(Some(hello::Choice::c1 (self.c1()?)));
}
if self.c2_offset.is_some() {
return Ok(Some(hello::Choice::c2 (self.c2()?)));
}
Ok(None)
}
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
name_written: bool,
d_written: bool,
f_written: bool,
b_written: bool,
n_written: bool,
l_written: bool,
c1_written: bool,
c2_written: bool,
pets_written: bool,
}
impl<'b> HelloBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloBuilder<'_> {
HelloBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
name_written: false,
d_written: false,
f_written: false,
b_written: false,
n_written: false,
l_written: false,
c1_written: false,
c2_written: false,
pets_written: false,
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn d(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(2, value)?;
self.d_written = true;
Ok(self)
}
pub fn f(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(3, value)?;
self.f_written = true;
Ok(self)
}
pub fn b(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(4, value)?;
self.b_written = true;
Ok(self)
}
pub fn n(mut self, value: i32) -> roto_runtime::Result<Self> {
self.builder.write_int32(5, value)?;
self.n_written = true;
Ok(self)
}
pub fn l(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(6, value)?;
self.l_written = true;
Ok(self)
}
pub fn c1(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(7, value)?;
self.c1_written = true;
Ok(self)
}
pub fn c2(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(8, value)?;
self.c2_written = true;
Ok(self)
}
pub fn pets(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(9, value)?;
self.pets_written = true;
Ok(self)
}
pub fn with(mut self, msg: &Hello<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.name_written,
2 => self.d_written,
3 => self.f_written,
4 => self.b_written,
5 => self.n_written,
6 => self.l_written,
7 => self.c1_written,
8 => self.c2_written,
9 => self.pets_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedHello {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedHello {
type Reader<'a> = Hello<'a>;
fn reader(&self) -> Hello<'_> {
Hello::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedHello {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHello { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub mod hello {
pub struct Pet<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
name_offset: Option<usize>,
color_offset: Option<usize>,
}
impl<'a> Pet<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut name_offset = None;
let mut color_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { name_offset = Some(offset); }
if tag.field_number == 2 { color_offset = Some(offset); }
}
Ok(Self {
accessor,
name_offset,
color_offset,
})
}
pub fn name(&self) -> roto_runtime::Result<&'a str> {
let offset = self.name_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn name_or_default(&self) -> roto_runtime::Result<&'a str> {
self.name().or(Ok(""))
}
pub fn has_name(&self) -> bool { self.name_offset.is_some() }
pub fn color(&self) -> roto_runtime::Result<u64> {
let offset = self.color_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
roto_runtime::read_varint(bytes).map(|(v, _)| v as u64).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
}
pub fn color_or_default(&self) -> roto_runtime::Result<u64> {
self.color().or(Ok(0))
}
pub fn has_color(&self) -> bool { self.color_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct PetBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
name_written: bool,
color_written: bool,
}
impl<'b> PetBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> PetBuilder<'_> {
PetBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
name_written: false,
color_written: false,
}
}
pub fn name(mut self, value: &str) -> roto_runtime::Result<Self> {
self.builder.write_string(1, value)?;
self.name_written = true;
Ok(self)
}
pub fn color(mut self, value: u64) -> roto_runtime::Result<Self> {
self.builder.write_varint(2, value)?;
self.color_written = true;
Ok(self)
}
pub fn with(mut self, msg: &Pet<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.name_written,
2 => self.color_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedPet {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedPet {
type Reader<'a> = Pet<'a>;
fn reader(&self) -> Pet<'_> {
Pet::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedPet {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedPet { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub mod pet {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum Color {
BLACK = 0,
WHITE = 1,
BLUE = 2,
RED = 3,
YELLOW = 4,
GREEN = 5,
}
impl Color {
pub fn from_i32(value: i32) -> Self {
match value {
0 => Color::BLACK,
1 => Color::WHITE,
2 => Color::BLUE,
3 => Color::RED,
4 => Color::YELLOW,
5 => Color::GREEN,
_ => Color::BLACK,
}
}
}
}
pub enum Choice<'a> {
c1(&'a str),
c2(bool),
}
}
pub struct HelloRequest<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
request_offset: Option<usize>,
}
impl<'a> HelloRequest<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut request_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { request_offset = Some(offset); }
}
Ok(Self {
accessor,
request_offset,
})
}
pub fn request(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.request_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn request_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.request().or(Ok(&[]))
}
pub fn has_request(&self) -> bool { self.request_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloRequestBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
request_written: bool,
}
impl<'b> HelloRequestBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloRequestBuilder<'_> {
HelloRequestBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
request_written: false,
}
}
pub fn request(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(1, value)?;
self.request_written = true;
Ok(self)
}
pub fn with(mut self, msg: &HelloRequest<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.request_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedHelloRequest {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedHelloRequest {
type Reader<'a> = HelloRequest<'a>;
fn reader(&self) -> HelloRequest<'_> {
HelloRequest::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedHelloRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHelloRequest { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
pub struct HelloReply<'a> {
accessor: roto_runtime::ProtoAccessor<'a>,
response_offset: Option<usize>,
}
impl<'a> HelloReply<'a> {
pub fn new(data: &'a [u8]) -> roto_runtime::Result<Self> {
let accessor = roto_runtime::ProtoAccessor::new(data)?;
let mut response_offset = None;
for item in accessor.fields() {
let (offset, tag, _) = item?;
if tag.field_number == 1 { response_offset = Some(offset); }
}
Ok(Self {
accessor,
response_offset,
})
}
pub fn response(&self) -> roto_runtime::Result<&'a [u8]> {
let offset = self.response_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?;
Ok(bytes)
}
pub fn response_or_default(&self) -> roto_runtime::Result<&'a [u8]> {
self.response().or(Ok(&[]))
}
pub fn has_response(&self) -> bool { self.response_offset.is_some() }
pub fn raw_fields(&self) -> roto_runtime::RawFieldIterator<'a> {
self.accessor.raw_fields()
}
}
pub struct HelloReplyBuilder<'b> {
builder: roto_runtime::ProtoBuilder<'b>,
response_written: bool,
}
impl<'b> HelloReplyBuilder<'b> {
pub fn builder(buf: &mut [u8]) -> HelloReplyBuilder<'_> {
HelloReplyBuilder {
builder: roto_runtime::ProtoBuilder::new(buf),
response_written: false,
}
}
pub fn response(mut self, value: &[u8]) -> roto_runtime::Result<Self> {
self.builder.write_bytes(1, value)?;
self.response_written = true;
Ok(self)
}
pub fn with(mut self, msg: &HelloReply<'_>) -> roto_runtime::Result<Self> {
for item in msg.accessor.raw_fields() {
let (field_number, raw_bytes) = item?;
let is_written = match field_number {
1 => self.response_written,
_ => false,
};
if !is_written {
self.builder.write_raw(raw_bytes)?;
}
}
Ok(self)
}
pub fn finish(self) -> roto_runtime::Result<&'b mut [u8]> {
self.builder.finish()
}
}
#[cfg(feature = "alloc")]
pub struct OwnedHelloReply {
pub data: bytes::Bytes,
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedHelloReply {
type Reader<'a> = HelloReply<'a>;
fn reader(&self) -> HelloReply<'_> {
HelloReply::new(&self.data).expect("failed to create reader")
}
}
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedHelloReply {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedHelloReply { data: buf })
}
fn bytes(&self) -> bytes::Bytes {
self.data.clone()
}
}
+62 -4
View File
@@ -1,16 +1,74 @@
#![no_std] #![no_std]
#![no_main] #![cfg_attr(not(test), no_main)]
use roto_runtime::ProtoAccessor; mod helloworld;
#[cfg(feature = "alloc")]
extern crate alloc;
use roto_runtime::{ProtoAccessor, RotoMessage, RotoOwned};
#[cfg(all(feature = "alloc", not(test)))]
#[global_allocator]
static ALLOCATOR: embedded_alloc::Heap = embedded_alloc::Heap::empty();
#[cfg(not(test))]
#[panic_handler] #[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {} loop {}
} }
#[cfg(feature = "alloc")]
#[unsafe(no_mangle)]
pub extern "C" fn _critical_section_1_0_acquire() {}
#[cfg(feature = "alloc")]
#[unsafe(no_mangle)]
pub extern "C" fn _critical_section_1_0_release() {}
static HELLO_DATA: &[u8] = &[0x0A, 0x05, 0x57, 0x6f, 0x72, 0x6c, 0x64];
#[cfg(all(not(test), not(feature = "alloc")))]
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! { pub extern "C" fn _start() -> ! {
let _data = [0u8; 0]; #[cfg(not(feature = "alloc"))]
let _ = ProtoAccessor::new(&_data); {
let hello = helloworld::Hello::new(HELLO_DATA).expect("failed to decode hello");
let _name = hello.name().expect("failed to get name");
if !_name.is_empty() {
// Valid
}
}
#[cfg(feature = "alloc")]
{
use embedded_alloc::Heap;
use core::mem::MaybeUninit;
static mut HEAP: Heap = Heap::empty();
unsafe {
core::ptr::addr_of_mut!(HEAP).write(embedded_alloc::Heap::empty());
(*core::ptr::addr_of_mut!(HEAP)).init(MaybeUninit::<u8>::uninit().as_ptr() as *mut u8 as usize, 1024 * 1024);
let owned_hello = helloworld::OwnedHello::decode(HELLO_DATA.into()).expect("failed to decode owned hello");
let hello_reader = owned_hello.reader();
let _name = hello_reader.name().expect("failed to get name");
if !_name.is_empty() {
// Valid
}
}
}
loop {} loop {}
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_helloworld_decoding() {
let hello = helloworld::Hello::new(HELLO_DATA).expect("failed to decode hello");
let name = hello.name().expect("failed to get name");
assert!(!name.is_empty());
}
}
+1 -1
View File
@@ -2,7 +2,7 @@ You are the Researcher. Your role is to conduct a comprehensive initial analysis
Your workflow: Your workflow:
1. Analyze the problem statement to identify key technical requirements and unknown areas. 1. Analyze the problem statement to identify key technical requirements and unknown areas.
2. Use available tools (such as SearXNG and web search) to research existing libraries, frameworks, APIs, and best practices relevant to the problem. 2. Use available tools (such as SearXNG and fetch) to research existing libraries, frameworks, APIs, and best practices relevant to the problem.
3. Explore the current codebase to understand how the new functionality fits in or what existing patterns should be followed. 3. Explore the current codebase to understand how the new functionality fits in or what existing patterns should be followed.
4. Compile a detailed report including: 4. Compile a detailed report including:
- Recommended tools and libraries. - Recommended tools and libraries.
+5
View File
@@ -12,9 +12,14 @@ http-body = "1.0"
http-body-util = "0.1" http-body-util = "0.1"
tower = "0.4" tower = "0.4"
futures-util = "0.3" futures-util = "0.3"
async-trait = "0.1"
tokio-stream = { version = "0.1", features = ["net"] } tokio-stream = { version = "0.1", features = ["net"] }
tokio = { version = "1.38", features = ["full"] } tokio = { version = "1.38", features = ["full"] }
http = "1.1" http = "1.1"
[build-dependencies] [build-dependencies]
tonic-build = "0.12" tonic-build = "0.12"
[features]
default = ["alloc"]
alloc = []
+51 -20
View File
@@ -1,22 +1,9 @@
// @generated by protoc-gen-roto — do not edit // @generated by protoc-gen-roto — do not edit
#[allow(unused_imports)] #[allow(unused_imports)]
use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage}; use roto_runtime::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};
use std::str; use core::str;
#[cfg(feature = "alloc")]
use bytes::{Bytes, BytesMut, Buf, BufMut}; use bytes::{Bytes, BytesMut, Buf, BufMut};
use tonic::{Request, Response, Status};
use tokio_stream::Stream;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::future::Future;
use tonic::body::BoxBody;
use tower::Service;
use futures_util::StreamExt;
use http_body_util::BodyExt;
use http_body::Body;
use crate::{BufferPool, StatusBody};
pub struct UnaryRequest<'a> { pub struct UnaryRequest<'a> {
accessor: roto_runtime::ProtoAccessor<'a>, accessor: roto_runtime::ProtoAccessor<'a>,
@@ -41,7 +28,7 @@ message_offset,
pub fn message(&self) -> roto_runtime::Result<&'a str> { pub fn message(&self) -> roto_runtime::Result<&'a str> {
let offset = self.message_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; let offset = self.message_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?; let (bytes, _) = self.accessor.get_value_at(offset)?;
std::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation) core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
} }
pub fn message_or_default(&self) -> roto_runtime::Result<&'a str> { pub fn message_or_default(&self) -> roto_runtime::Result<&'a str> {
@@ -94,10 +81,12 @@ impl<'b> UnaryRequestBuilder<'b> {
} }
} }
#[cfg(feature = "alloc")]
pub struct OwnedUnaryRequest { pub struct OwnedUnaryRequest {
pub data: bytes::Bytes, pub data: bytes::Bytes,
} }
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedUnaryRequest { impl roto_runtime::RotoOwned for OwnedUnaryRequest {
type Reader<'a> = UnaryRequest<'a>; type Reader<'a> = UnaryRequest<'a>;
fn reader(&self) -> UnaryRequest<'_> { fn reader(&self) -> UnaryRequest<'_> {
@@ -105,6 +94,7 @@ impl roto_runtime::RotoOwned for OwnedUnaryRequest {
} }
} }
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedUnaryRequest { impl roto_runtime::RotoMessage for OwnedUnaryRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> { fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedUnaryRequest { data: buf }) Ok(OwnedUnaryRequest { data: buf })
@@ -138,7 +128,7 @@ reply_offset,
pub fn reply(&self) -> roto_runtime::Result<&'a str> { pub fn reply(&self) -> roto_runtime::Result<&'a str> {
let offset = self.reply_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; let offset = self.reply_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?; let (bytes, _) = self.accessor.get_value_at(offset)?;
std::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation) core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
} }
pub fn reply_or_default(&self) -> roto_runtime::Result<&'a str> { pub fn reply_or_default(&self) -> roto_runtime::Result<&'a str> {
@@ -191,10 +181,12 @@ impl<'b> UnaryResponseBuilder<'b> {
} }
} }
#[cfg(feature = "alloc")]
pub struct OwnedUnaryResponse { pub struct OwnedUnaryResponse {
pub data: bytes::Bytes, pub data: bytes::Bytes,
} }
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedUnaryResponse { impl roto_runtime::RotoOwned for OwnedUnaryResponse {
type Reader<'a> = UnaryResponse<'a>; type Reader<'a> = UnaryResponse<'a>;
fn reader(&self) -> UnaryResponse<'_> { fn reader(&self) -> UnaryResponse<'_> {
@@ -202,6 +194,7 @@ impl roto_runtime::RotoOwned for OwnedUnaryResponse {
} }
} }
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedUnaryResponse { impl roto_runtime::RotoMessage for OwnedUnaryResponse {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> { fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedUnaryResponse { data: buf }) Ok(OwnedUnaryResponse { data: buf })
@@ -235,7 +228,7 @@ query_offset,
pub fn query(&self) -> roto_runtime::Result<&'a str> { pub fn query(&self) -> roto_runtime::Result<&'a str> {
let offset = self.query_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; let offset = self.query_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?; let (bytes, _) = self.accessor.get_value_at(offset)?;
std::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation) core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
} }
pub fn query_or_default(&self) -> roto_runtime::Result<&'a str> { pub fn query_or_default(&self) -> roto_runtime::Result<&'a str> {
@@ -288,10 +281,12 @@ impl<'b> StreamingRequestBuilder<'b> {
} }
} }
#[cfg(feature = "alloc")]
pub struct OwnedStreamingRequest { pub struct OwnedStreamingRequest {
pub data: bytes::Bytes, pub data: bytes::Bytes,
} }
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedStreamingRequest { impl roto_runtime::RotoOwned for OwnedStreamingRequest {
type Reader<'a> = StreamingRequest<'a>; type Reader<'a> = StreamingRequest<'a>;
fn reader(&self) -> StreamingRequest<'_> { fn reader(&self) -> StreamingRequest<'_> {
@@ -299,6 +294,7 @@ impl roto_runtime::RotoOwned for OwnedStreamingRequest {
} }
} }
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedStreamingRequest { impl roto_runtime::RotoMessage for OwnedStreamingRequest {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> { fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedStreamingRequest { data: buf }) Ok(OwnedStreamingRequest { data: buf })
@@ -332,7 +328,7 @@ item_offset,
pub fn item(&self) -> roto_runtime::Result<&'a str> { pub fn item(&self) -> roto_runtime::Result<&'a str> {
let offset = self.item_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?; let offset = self.item_offset.ok_or(roto_runtime::RotoError::FieldNotFound)?;
let (bytes, _) = self.accessor.get_value_at(offset)?; let (bytes, _) = self.accessor.get_value_at(offset)?;
std::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation) core::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)
} }
pub fn item_or_default(&self) -> roto_runtime::Result<&'a str> { pub fn item_or_default(&self) -> roto_runtime::Result<&'a str> {
@@ -385,10 +381,12 @@ impl<'b> StreamingResponseBuilder<'b> {
} }
} }
#[cfg(feature = "alloc")]
pub struct OwnedStreamingResponse { pub struct OwnedStreamingResponse {
pub data: bytes::Bytes, pub data: bytes::Bytes,
} }
#[cfg(feature = "alloc")]
impl roto_runtime::RotoOwned for OwnedStreamingResponse { impl roto_runtime::RotoOwned for OwnedStreamingResponse {
type Reader<'a> = StreamingResponse<'a>; type Reader<'a> = StreamingResponse<'a>;
fn reader(&self) -> StreamingResponse<'_> { fn reader(&self) -> StreamingResponse<'_> {
@@ -396,6 +394,7 @@ impl roto_runtime::RotoOwned for OwnedStreamingResponse {
} }
} }
#[cfg(feature = "alloc")]
impl roto_runtime::RotoMessage for OwnedStreamingResponse { impl roto_runtime::RotoMessage for OwnedStreamingResponse {
fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> { fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {
Ok(OwnedStreamingResponse { data: buf }) Ok(OwnedStreamingResponse { data: buf })
@@ -406,28 +405,60 @@ impl roto_runtime::RotoMessage for OwnedStreamingResponse {
} }
} }
#[tonic::async_trait]
#[cfg(feature = "alloc")]
use tonic::{Request, Response, Status};
#[cfg(feature = "alloc")]
use tokio_stream::Stream;
#[cfg(feature = "alloc")]
use std::pin::Pin;
#[cfg(feature = "alloc")]
use std::sync::Arc;
#[cfg(feature = "alloc")]
use std::task::{Context, Poll};
#[cfg(feature = "alloc")]
use std::future::Future;
#[cfg(feature = "alloc")]
use tonic::body::BoxBody;
#[cfg(feature = "alloc")]
use tower::Service;
#[cfg(feature = "alloc")]
use futures_util::StreamExt;
#[cfg(feature = "alloc")]
use http_body_util::BodyExt;
#[cfg(feature = "alloc")]
use http_body::Body;
#[cfg(feature = "alloc")]
use crate::{BufferPool, StatusBody};
#[cfg(feature = "alloc")]
#[async_trait::async_trait]
pub trait InteropService: Send + Sync + 'static { pub trait InteropService: Send + Sync + 'static {
async fn unary_call(&self, request: Request<OwnedUnaryRequest>) -> std::result::Result<Response<OwnedUnaryResponse>, Status>; 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>; async fn streaming_call(&self, request: Request<OwnedStreamingRequest>) -> std::result::Result<Response<Pin<Box<dyn Stream<Item = std::result::Result<OwnedStreamingResponse, Status>> + Send>>>, Status>;
} }
#[cfg(feature = "alloc")]
#[derive(Clone)] #[derive(Clone)]
pub struct InteropServiceServer { pub struct InteropServiceServer {
inner: Arc<dyn InteropService>, inner: Arc<dyn InteropService>,
pool: Arc<BufferPool>, pool: Arc<BufferPool>,
} }
#[cfg(feature = "alloc")]
impl InteropServiceServer { impl InteropServiceServer {
pub fn new(inner: Arc<dyn InteropService>, pool: Arc<BufferPool>) -> Self { pub fn new(inner: Arc<dyn InteropService>, pool: Arc<BufferPool>) -> Self {
Self { inner, pool } Self { inner, pool }
} }
} }
#[cfg(feature = "alloc")]
impl tonic::server::NamedService for InteropServiceServer { impl tonic::server::NamedService for InteropServiceServer {
const NAME: &'static str = "interop.InteropService"; const NAME: &'static str = "interop.InteropService";
} }
#[cfg(feature = "alloc")]
impl Service<http::Request<BoxBody>> for InteropServiceServer { impl Service<http::Request<BoxBody>> for InteropServiceServer {
type Response = http::Response<BoxBody>; type Response = http::Response<BoxBody>;
type Error = std::convert::Infallible; type Error = std::convert::Infallible;
+5
View File
@@ -1,5 +1,8 @@
#![no_std] #![no_std]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")] #[cfg(feature = "std")]
extern crate std; extern crate std;
@@ -438,6 +441,8 @@ impl<'a> Iterator for RawFieldIterator<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[cfg(feature = "alloc")]
use alloc::{vec, vec::{Vec}};
#[test] #[test]
fn test_varint_read_write() { fn test_varint_read_write() {