Fix code generator and add compilation test
Correct module paths and accessor usage in the generator to ensure generated code builds. Add a test that verifies this by compiling the generated code in a temporary project.
This commit is contained in:
@@ -4,3 +4,4 @@ test_types_gen_project
|
|||||||
test_map_gen_project
|
test_map_gen_project
|
||||||
test_grpc_project
|
test_grpc_project
|
||||||
artifacts/
|
artifacts/
|
||||||
|
temp_test_project/
|
||||||
|
|||||||
Generated
+17
@@ -1352,6 +1352,23 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "temp_test_project"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"roto-codegen",
|
||||||
|
"roto-runtime",
|
||||||
|
"roto-tonic",
|
||||||
|
"tokio-stream",
|
||||||
|
"tonic",
|
||||||
|
"tower 0.4.13",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.27.0"
|
version = "3.27.0"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ members = [
|
|||||||
"protos",
|
"protos",
|
||||||
"benches",
|
"benches",
|
||||||
"roto-tonic",
|
"roto-tonic",
|
||||||
|
"temp_test_project",
|
||||||
"examples/hello_world",
|
"examples/hello_world",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
+15
-14
@@ -51,7 +51,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(),
|
||||||
"str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
|
"std::str::from_utf8(bytes).map_err(|_| roto_runtime::RotoError::WireFormatViolation)".to_string(),
|
||||||
"\"\"".to_string(),
|
"\"\"".to_string(),
|
||||||
), // TYPE_STRING
|
), // TYPE_STRING
|
||||||
1 => (
|
1 => (
|
||||||
@@ -117,7 +117,7 @@ fn write_enum(enum_proto: &EnumDescriptorProto, output: &mut String) {
|
|||||||
let accessor =
|
let accessor =
|
||||||
ProtoAccessor::new(val_data).expect("Failed to parse EnumValueDescriptorProto");
|
ProtoAccessor::new(val_data).expect("Failed to parse EnumValueDescriptorProto");
|
||||||
let (name_bytes, _) = accessor.get_value(1).expect("Enum value name missing");
|
let (name_bytes, _) = accessor.get_value(1).expect("Enum value name missing");
|
||||||
let name = str::from_utf8(name_bytes).expect("Enum value name invalid utf8");
|
let name = std::str::from_utf8(name_bytes).expect("Enum value name invalid utf8");
|
||||||
let (num_bytes, _) = accessor.get_value(2).expect("Enum value number missing");
|
let (num_bytes, _) = accessor.get_value(2).expect("Enum value number missing");
|
||||||
let (num, _) =
|
let (num, _) =
|
||||||
roto_runtime::read_varint(num_bytes).expect("Enum value number invalid varint");
|
roto_runtime::read_varint(num_bytes).expect("Enum value number invalid varint");
|
||||||
@@ -147,7 +147,7 @@ fn write_enum(enum_proto: &EnumDescriptorProto, output: &mut String) {
|
|||||||
let accessor =
|
let accessor =
|
||||||
ProtoAccessor::new(val_data).expect("Failed to parse EnumValueDescriptorProto");
|
ProtoAccessor::new(val_data).expect("Failed to parse EnumValueDescriptorProto");
|
||||||
let (name_bytes, _) = accessor.get_value(1).expect("Enum value name missing");
|
let (name_bytes, _) = accessor.get_value(1).expect("Enum value name missing");
|
||||||
let name = str::from_utf8(name_bytes).expect("Enum value name invalid utf8");
|
let name = std::str::from_utf8(name_bytes).expect("Enum value name invalid utf8");
|
||||||
let (num_bytes, _) = accessor.get_value(2).expect("Enum value number missing");
|
let (num_bytes, _) = accessor.get_value(2).expect("Enum value number missing");
|
||||||
let (num, _) =
|
let (num, _) =
|
||||||
roto_runtime::read_varint(num_bytes).expect("Enum value number invalid varint");
|
roto_runtime::read_varint(num_bytes).expect("Enum value number invalid varint");
|
||||||
@@ -170,6 +170,7 @@ fn write_enum(enum_proto: &EnumDescriptorProto, output: &mut String) {
|
|||||||
|
|
||||||
fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
|
fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
|
||||||
let msg_name = to_pascal_case(msg_proto.name().unwrap());
|
let msg_name = to_pascal_case(msg_proto.name().unwrap());
|
||||||
|
let mod_name = to_snake_case(msg_proto.name().unwrap());
|
||||||
|
|
||||||
let mut fields_info = Vec::new();
|
let mut fields_info = Vec::new();
|
||||||
for field_res in msg_proto.field() {
|
for field_res in msg_proto.field() {
|
||||||
@@ -332,10 +333,12 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
|
|||||||
let pascal_oneof_name = to_pascal_case(oneof_name);
|
let pascal_oneof_name = to_pascal_case(oneof_name);
|
||||||
let snake_oneof_name = to_snake_case(oneof_name);
|
let snake_oneof_name = to_snake_case(oneof_name);
|
||||||
|
|
||||||
output.push_str(&format!(
|
let return_type = format!("{}::{}<'a>", mod_name, pascal_oneof_name);
|
||||||
" pub fn which_{}(&self) -> roto_runtime::Result<Option<{}::{}<'a>>> {{\n",
|
let signature = format!(
|
||||||
snake_oneof_name, msg_name, pascal_oneof_name
|
" pub fn which_{}(&self) -> roto_runtime::Result<Option<{}> > {{\n",
|
||||||
));
|
snake_oneof_name, return_type
|
||||||
|
);
|
||||||
|
output.push_str(&signature);
|
||||||
for (field_name, _tag, _f_type, _f_label, f_oneof_index, _is_map) in &fields_info {
|
for (field_name, _tag, _f_type, _f_label, f_oneof_index, _is_map) in &fields_info {
|
||||||
if *f_oneof_index == Some(oneof_index as i32) {
|
if *f_oneof_index == Some(oneof_index as i32) {
|
||||||
let safe_field_name = if field_name == "type" {
|
let safe_field_name = if field_name == "type" {
|
||||||
@@ -348,8 +351,8 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
|
|||||||
field_name
|
field_name
|
||||||
));
|
));
|
||||||
output.push_str(&format!(
|
output.push_str(&format!(
|
||||||
" return Ok(Some({}::{} (self.{}()?)));\n",
|
" return Ok(Some({}::{}::{} (self.{}()?)));\n",
|
||||||
pascal_oneof_name, safe_field_name, safe_field_name
|
mod_name, pascal_oneof_name, safe_field_name, safe_field_name
|
||||||
));
|
));
|
||||||
output.push_str(" }\n");
|
output.push_str(" }\n");
|
||||||
}
|
}
|
||||||
@@ -414,7 +417,7 @@ fn write_message(msg_proto: &DescriptorProto, output: &mut String) {
|
|||||||
" pub fn with(mut self, msg: &{}<'_>) -> roto_runtime::Result<Self> {{\n",
|
" pub fn with(mut self, msg: &{}<'_>) -> roto_runtime::Result<Self> {{\n",
|
||||||
msg_name
|
msg_name
|
||||||
));
|
));
|
||||||
output.push_str(" for item in msg.raw_fields() {\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 (field_number, raw_bytes) = item?;\n");
|
||||||
output.push_str(" let is_written = match field_number {\n");
|
output.push_str(" let is_written = match field_number {\n");
|
||||||
for (field_name, _, tag, _, _) in &builder_fields {
|
for (field_name, _, tag, _, _) in &builder_fields {
|
||||||
@@ -563,7 +566,7 @@ pub fn generate_rust_code(
|
|||||||
|
|
||||||
for dep_res in file_proto.dependency() {
|
for dep_res in file_proto.dependency() {
|
||||||
let (dep_data, _) = dep_res.expect("Failed to iterate dependency");
|
let (dep_data, _) = dep_res.expect("Failed to iterate dependency");
|
||||||
let dep_name = str::from_utf8(dep_data).expect("Dependency name invalid utf8");
|
let dep_name = std::str::from_utf8(dep_data).expect("Dependency name invalid utf8");
|
||||||
let dep_mod_path = dep_name.replace(".proto", "").replace('/', "::");
|
let dep_mod_path = dep_name.replace(".proto", "").replace('/', "::");
|
||||||
output.push_str(&format!("use crate::{};\n", dep_mod_path));
|
output.push_str(&format!("use crate::{};\n", dep_mod_path));
|
||||||
}
|
}
|
||||||
@@ -725,9 +728,7 @@ fn write_service(svc_proto: &ServiceDescriptorProto, output: &mut String) {
|
|||||||
output.push_str(" let mut buf = pool.get();\n");
|
output.push_str(" let mut buf = pool.get();\n");
|
||||||
output.push_str(" let mut stream = body;\n");
|
output.push_str(" let mut stream = body;\n");
|
||||||
output.push_str(" while let Some(frame_result) = stream.frame().await {\n");
|
output.push_str(" while let Some(frame_result) = stream.frame().await {\n");
|
||||||
output.push_str(" let frame = frame_result.map_err(|e| {\n");
|
output.push_str(" let frame = frame_result.expect(\"Body frame error\");\n");
|
||||||
output.push_str(" panic!(\"Body frame error: {}\", e);\n");
|
|
||||||
output.push_str(" })?;\n");
|
|
||||||
output.push_str(" if let Some(data) = frame.data_ref() {\n");
|
output.push_str(" if let Some(data) = frame.data_ref() {\n");
|
||||||
output.push_str(" buf.put(data.clone());\n");
|
output.push_str(" buf.put(data.clone());\n");
|
||||||
output.push_str(" }\n");
|
output.push_str(" }\n");
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,76 @@
|
|||||||
|
use roto_codegen::google::protobuf::descriptor::FileDescriptorSet;
|
||||||
|
use std::fs;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_helloworld_generated_code_builds() {
|
||||||
|
// 1. Load FileDescriptorSet from helloworld.desc
|
||||||
|
let desc_path = "helloworld.desc";
|
||||||
|
// Note: This assumes helloworld.desc is in the working directory of the test.
|
||||||
|
// We might need to provide the full path or copy it to the test data directory.
|
||||||
|
let data = fs::read(desc_path).expect("Failed to read helloworld.desc");
|
||||||
|
let set = FileDescriptorSet::new(&data)
|
||||||
|
.expect("Failed to create FileDescriptorSet from helloworld.desc");
|
||||||
|
|
||||||
|
let generated_files = roto_codegen::generator::generate_rust_code(&set, None, false);
|
||||||
|
assert!(
|
||||||
|
!generated_files.is_empty(),
|
||||||
|
"Generated code should not be empty"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (path, content) in &generated_files {
|
||||||
|
println!("--- File: {} ---\n{}", path, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Setup a temporary Cargo project to verify the code builds
|
||||||
|
let codegen_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
let project_root = codegen_root.parent().expect("Failed to get project root");
|
||||||
|
let temp_project_dir = std::path::PathBuf::from("/tmp/roto_helloworld_gen_project");
|
||||||
|
|
||||||
|
// Clean up previous runs
|
||||||
|
if temp_project_dir.exists() {
|
||||||
|
fs::remove_dir_all(&temp_project_dir).expect("Failed to clean up temp project directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new library project
|
||||||
|
let status = Command::new("cargo")
|
||||||
|
.args(["new", "--lib", temp_project_dir.to_str().expect("Invalid path")])
|
||||||
|
.status()
|
||||||
|
.expect("Failed to run cargo new");
|
||||||
|
assert!(status.success(), "cargo new failed");
|
||||||
|
|
||||||
|
// 3. Configure the project to depend on the current roto crate
|
||||||
|
let cargo_toml_path = temp_project_dir.join("Cargo.toml");
|
||||||
|
let cargo_toml_content =
|
||||||
|
fs::read_to_string(&cargo_toml_path).expect("Failed to read Cargo.toml");
|
||||||
|
let updated_cargo_toml = format!(
|
||||||
|
"{}\n\nroto-codegen = {{ path = \"{}\" }}\nroto-runtime = {{ path = \"{}\" }}\nroto-tonic = {{ path = \"{}\" }}\nbytes = \"1.7\"\ntonic = \"0.12\"\ntokio-stream = \"0.1\"\ntower = \"0.4\"\nfutures-util = \"0.3\"\nhttp-body-util = \"0.1\"\nhttp-body = \"1.0\"\n\n[workspace]\n",
|
||||||
|
cargo_toml_content,
|
||||||
|
codegen_root.to_string_lossy(),
|
||||||
|
project_root.join("runtime").to_string_lossy(),
|
||||||
|
project_root.join("roto-tonic").to_string_lossy()
|
||||||
|
);
|
||||||
|
fs::write(cargo_toml_path, updated_cargo_toml).expect("Failed to write Cargo.toml");
|
||||||
|
|
||||||
|
// 4. Write the generated code to src/lib.rs
|
||||||
|
let mut all_code = String::new();
|
||||||
|
for (_, content) in generated_files {
|
||||||
|
all_code.push_str(&content);
|
||||||
|
all_code.push_str("\n");
|
||||||
|
}
|
||||||
|
let final_code = all_code.replace("use crate::", "use roto::");
|
||||||
|
let lib_path = temp_project_dir.join("src/lib.rs");
|
||||||
|
fs::write(lib_path, final_code).expect("Failed to write generated code to src/lib.rs");
|
||||||
|
|
||||||
|
// 5. Attempt to build the project
|
||||||
|
let build_status = Command::new("cargo")
|
||||||
|
.args(["build"])
|
||||||
|
.current_dir(&temp_project_dir)
|
||||||
|
.status()
|
||||||
|
.expect("Failed to run cargo build");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
build_status.success(),
|
||||||
|
"The generated Rust code for helloworld.proto failed to build in a standalone project!"
|
||||||
|
);
|
||||||
|
}
|
||||||
Binary file not shown.
Reference in New Issue
Block a user