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
This commit is contained in:
@@ -7,23 +7,15 @@ use crate::runtime::ProtoAccessor;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::str;
|
||||
|
||||
const DATA_IMPORTS: &str = "#[allow(unused, unused_imports, unused_assignments, unused_variables, non_camel_case_types)]\n\
|
||||
use roto_runtime::{ProtoAccessor, ProtoBuilder, RevBuilder, Result, RotoError, read_varint, RepeatedFieldIterator, RotoMessage};\n\
|
||||
use core::str;\n\
|
||||
use bytes::{Bytes, BytesMut, Buf, BufMut};\n";
|
||||
const SERVICE_IMPORTS: &str = "#[allow(unused, unused_imports, unused_assignments, unused_variables, non_camel_case_types)]\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('_')
|
||||
@@ -654,13 +646,12 @@ pub fn write_message(
|
||||
));
|
||||
}
|
||||
|
||||
// with() — copies unseen fields from an existing message (iterate in reverse for RevBuilder)
|
||||
// with() — copies unseen fields from an existing message
|
||||
output.push_str(&format!(
|
||||
" pub fn with(mut self, msg: &{}<'_>) -> roto_runtime::Result<Self> {{\n",
|
||||
msg_name
|
||||
));
|
||||
output.push_str(" let fields: Vec<_> = msg.accessor.raw_fields().collect();\n");
|
||||
output.push_str(" for item in fields.into_iter().rev() {\n");
|
||||
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 {
|
||||
@@ -680,34 +671,32 @@ pub fn write_message(
|
||||
|
||||
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");
|
||||
|
||||
output.push_str(&format!(
|
||||
"impl roto_runtime::RotoOwned for Owned{} {{\n",
|
||||
msg_name
|
||||
"#[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
|
||||
));
|
||||
output.push_str(&format!(" type Reader<'a> = {}<'a>;\n", msg_name));
|
||||
output.push_str(&format!(" fn reader(&self) -> {}<'_> {{\n", msg_name));
|
||||
output.push_str(&format!(
|
||||
" {}::new(&self.data).expect(\"failed to create reader\")\n",
|
||||
msg_name
|
||||
));
|
||||
output.push_str(" }\n");
|
||||
output.push_str("}\n\n");
|
||||
|
||||
output.push_str(&format!(
|
||||
"impl roto_runtime::RotoMessage for Owned{} {{\n",
|
||||
msg_name
|
||||
));
|
||||
output.push_str(" fn decode(buf: bytes::Bytes) -> roto_runtime::Result<Self> {\n");
|
||||
output.push_str(&format!(" Ok(Owned{} {{ data: buf }})\n", msg_name));
|
||||
output.push_str(" }\n\n");
|
||||
output.push_str(" fn bytes(&self) -> bytes::Bytes {\n");
|
||||
output.push_str(" self.data.clone()\n");
|
||||
output.push_str(" }\n");
|
||||
output.push_str("}\n\n");
|
||||
|
||||
let mut nested_enums = Vec::new();
|
||||
for e_res in msg_proto.enum_type() {
|
||||
@@ -1069,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
|
||||
));
|
||||
|
||||
@@ -1121,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
|
||||
@@ -1138,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() {
|
||||
@@ -1153,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");
|
||||
|
||||
Reference in New Issue
Block a user