diff --git a/crates/generators/rust/src/lib.rs b/crates/generators/rust/src/lib.rs index 82bf657..13a5b08 100644 --- a/crates/generators/rust/src/lib.rs +++ b/crates/generators/rust/src/lib.rs @@ -88,7 +88,7 @@ impl RustGenerator { quote! { pub mod blueberry_generated { pub mod runtime; - pub use runtime::BinarySerializable; + pub use runtime::CdrEncoding; $(for def in defs => $def) } @@ -117,17 +117,16 @@ impl RustGenerator { quote! { #[test] fn $test_name() { - use crate::blueberry_generated::runtime::BinarySerializable; + use crate::blueberry_generated::runtime::CdrEncoding; use $type_path; let value = $type_ident::default(); let bytes = value.to_payload().expect("to_payload"); - let hex: String = bytes.iter().map(|b| format!("{:02X}", b)).collect(); println!("HEX: 0x{}", hex); - let decoded = $type_ident_clone::from_payload(&bytes).expect("from_payload"); + let decoded = <$type_ident_clone as CdrEncoding>::from_payload(&bytes).expect("from_payload"); assert_eq!(value, decoded); println!("{:?}", decoded); @@ -145,191 +144,36 @@ impl RustGenerator { } fn runtime_module_items(&self) -> Tokens { - // Static runtime module copied from previous implementation. quote! { - use std::io::{Cursor, Read}; + use cdr::{deserialize, serialize, CdrLe, Infinite}; + use serde::{de::DeserializeOwned, Serialize}; #[derive(Debug)] pub enum Error { - UnexpectedEof, - InvalidUtf8, - StringTooLong { limit: usize, actual: usize }, - SequenceTooLong { limit: usize, actual: usize }, + Cdr(cdr::Error), InvalidEnum { name: &'static str, value: i64 }, } - pub trait BinarySerializable: Sized { - fn to_payload(&self) -> Result, Error> { - let mut buf = Vec::new(); - self.write_payload(&mut buf)?; - Ok(buf) - } - - fn write_payload(&self, buf: &mut Vec) -> Result<(), Error>; - - fn from_payload(bytes: &[u8]) -> Result { - let mut cursor = Cursor::new(bytes); - Self::read_payload(&mut cursor) + impl From for Error { + fn from(err: cdr::Error) -> Self { + Error::Cdr(err) } - - fn read_payload(cursor: &mut Cursor<&[u8]>) -> Result; - } - - pub fn write_bool(buf: &mut Vec, value: bool) { - buf.push(if value { 1 } else { 0 }); - } - - pub fn read_bool(cursor: &mut Cursor<&[u8]>) -> Result { - let mut byte = [0u8; 1]; - cursor.read_exact(&mut byte).map_err(|_| Error::UnexpectedEof)?; - Ok(byte[0] != 0) - } - - pub fn write_u8(buf: &mut Vec, value: u8) { - buf.push(value); - } - - pub fn read_u8(cursor: &mut Cursor<&[u8]>) -> Result { - let mut byte = [0u8; 1]; - cursor.read_exact(&mut byte).map_err(|_| Error::UnexpectedEof)?; - Ok(byte[0]) - } - - pub fn write_i16(buf: &mut Vec, value: i16) { - buf.extend_from_slice(&value.to_le_bytes()); - } - - pub fn read_i16(cursor: &mut Cursor<&[u8]>) -> Result { - let mut data = [0u8; std::mem::size_of::()]; - cursor.read_exact(&mut data).map_err(|_| Error::UnexpectedEof)?; - Ok(i16::from_le_bytes(data)) - } - - pub fn write_u16(buf: &mut Vec, value: u16) { - buf.extend_from_slice(&value.to_le_bytes()); - } - - pub fn read_u16(cursor: &mut Cursor<&[u8]>) -> Result { - let mut data = [0u8; std::mem::size_of::()]; - cursor.read_exact(&mut data).map_err(|_| Error::UnexpectedEof)?; - Ok(u16::from_le_bytes(data)) - } - - pub fn write_i32(buf: &mut Vec, value: i32) { - buf.extend_from_slice(&value.to_le_bytes()); - } - - pub fn read_i32(cursor: &mut Cursor<&[u8]>) -> Result { - let mut data = [0u8; std::mem::size_of::()]; - cursor.read_exact(&mut data).map_err(|_| Error::UnexpectedEof)?; - Ok(i32::from_le_bytes(data)) - } - - pub fn write_u32(buf: &mut Vec, value: u32) { - buf.extend_from_slice(&value.to_le_bytes()); - } - - pub fn read_u32(cursor: &mut Cursor<&[u8]>) -> Result { - let mut data = [0u8; std::mem::size_of::()]; - cursor.read_exact(&mut data).map_err(|_| Error::UnexpectedEof)?; - Ok(u32::from_le_bytes(data)) - } - - pub fn write_i64(buf: &mut Vec, value: i64) { - buf.extend_from_slice(&value.to_le_bytes()); } - pub fn read_i64(cursor: &mut Cursor<&[u8]>) -> Result { - let mut data = [0u8; std::mem::size_of::()]; - cursor.read_exact(&mut data).map_err(|_| Error::UnexpectedEof)?; - Ok(i64::from_le_bytes(data)) - } - - pub fn write_u64(buf: &mut Vec, value: u64) { - buf.extend_from_slice(&value.to_le_bytes()); - } - - pub fn read_u64(cursor: &mut Cursor<&[u8]>) -> Result { - let mut data = [0u8; std::mem::size_of::()]; - cursor.read_exact(&mut data).map_err(|_| Error::UnexpectedEof)?; - Ok(u64::from_le_bytes(data)) - } - - pub fn write_f32(buf: &mut Vec, value: f32) { - buf.extend_from_slice(&value.to_le_bytes()); - } - - pub fn read_f32(cursor: &mut Cursor<&[u8]>) -> Result { - let mut data = [0u8; std::mem::size_of::()]; - cursor.read_exact(&mut data).map_err(|_| Error::UnexpectedEof)?; - Ok(f32::from_le_bytes(data)) - } - - pub fn write_f64(buf: &mut Vec, value: f64) { - buf.extend_from_slice(&value.to_le_bytes()); - } - - pub fn read_f64(cursor: &mut Cursor<&[u8]>) -> Result { - let mut data = [0u8; std::mem::size_of::()]; - cursor.read_exact(&mut data).map_err(|_| Error::UnexpectedEof)?; - Ok(f64::from_le_bytes(data)) - } - - pub fn write_string(buf: &mut Vec, value: &str, bound: Option) -> Result<(), Error> { - if let Some(limit) = bound { - if value.len() > limit { - return Err(Error::StringTooLong { limit, actual: value.len() }); - } - } - write_u32(buf, value.len() as u32); - buf.extend_from_slice(value.as_bytes()); - Ok(()) - } - - pub fn read_string(cursor: &mut Cursor<&[u8]>, bound: Option) -> Result { - let len = read_u32(cursor)? as usize; - if let Some(limit) = bound { - if len > limit { - return Err(Error::StringTooLong { limit, actual: len }); - } + pub trait CdrEncoding: Serialize + DeserializeOwned { + fn to_payload(&self) -> Result, Error> { + serialize::<_, _, CdrLe>(self, Infinite).map_err(Error::Cdr) } - let mut data = vec![0u8; len]; - cursor.read_exact(&mut data).map_err(|_| Error::UnexpectedEof)?; - String::from_utf8(data).map_err(|_| Error::InvalidUtf8) - } - pub fn write_vec(buf: &mut Vec, values: &[T], bound: Option, mut serializer: F) -> Result<(), Error> - where - F: FnMut(&T, &mut Vec) -> Result<(), Error>, - { - if let Some(limit) = bound { - if values.len() > limit { - return Err(Error::SequenceTooLong { limit, actual: values.len() }); - } - } - write_u32(buf, values.len() as u32); - for value in values { - serializer(value, buf)?; + fn from_payload(bytes: &[u8]) -> Result + where + Self: Sized, + { + deserialize(bytes).map_err(Error::Cdr) } - Ok(()) } - pub fn read_vec(cursor: &mut Cursor<&[u8]>, bound: Option, mut parser: F) -> Result, Error> - where - F: FnMut(&mut Cursor<&[u8]>) -> Result, - { - let len = read_u32(cursor)? as usize; - if let Some(limit) = bound { - if len > limit { - return Err(Error::SequenceTooLong { limit, actual: len }); - } - } - let mut items = Vec::with_capacity(len); - for _ in 0..len { - items.push(parser(cursor)?); - } - Ok(items) - } + impl CdrEncoding for T where T: Serialize + DeserializeOwned {} } } @@ -381,7 +225,6 @@ impl RustGenerator { .as_ref() .map(|t| self.render_type(t, scope)) .unwrap_or_else(|| quote!(u32)); - let serde_attr = self.serde_attr_tokens(); let variants: Vec = enum_def .node .enumerators @@ -440,9 +283,17 @@ impl RustGenerator { let ident_from_body = ident.clone(); quote! { $(for doc in docs => $doc) - $serde_attr #[repr($repr_ty_attr)] - #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] + #[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + Default, + ::serde::Serialize, + ::serde::Deserialize + )] pub enum $ident { $(for v in variants => $v) } @@ -502,55 +353,20 @@ impl RustGenerator { }) .collect(); let docs = self.doc_attributes(comments); - let serde_attr = self.serde_attr_tokens(); - - let writes: Vec = members - .iter() - .map(|member| { - let field = member.name.clone(); - let expr = quote!(&self.$field); - self.serialize_value(expr, &member.ty, scope) - }) - .collect(); - - let reads: Vec = members - .iter() - .map(|member| { - let field = member.name.clone(); - let expr = self.deserialize_value(&member.ty, scope); - quote! { - let $field = $expr?; - } - }) - .collect(); - - let field_names: Vec = members - .iter() - .map(|member| { - let name = member.name.clone(); - quote!($name,) - }) - .collect(); quote! { $(for doc in docs => $doc) - $serde_attr - #[derive(Clone, Debug, PartialEq, Default)] + #[derive( + Clone, + Debug, + PartialEq, + Default, + ::serde::Serialize, + ::serde::Deserialize + )] pub struct $ident { $(for f in fields => $f) } - - impl runtime::BinarySerializable for $ident { - fn write_payload(&self, buf: &mut Vec) -> Result<(), runtime::Error> { - $(for w in writes => $w) - Ok(()) - } - - fn read_payload(cursor: &mut std::io::Cursor<&[u8]>) -> Result { - $(for r in reads => $r) - Ok(Self { $(for n in field_names => $n) }) - } - } } } @@ -571,95 +387,6 @@ impl RustGenerator { } } - fn serialize_value(&self, expr: Tokens, ty: &Type, scope: &[String]) -> Tokens { - match ty { - Type::Long => quote!(runtime::write_i32(buf, *$expr);), - Type::Short => quote!(runtime::write_i16(buf, *$expr);), - Type::UnsignedShort => quote!(runtime::write_u16(buf, *$expr);), - Type::UnsignedLong => quote!(runtime::write_u32(buf, *$expr);), - Type::LongLong => quote!(runtime::write_i64(buf, *$expr);), - Type::UnsignedLongLong => quote!(runtime::write_u64(buf, *$expr);), - Type::Float => quote!(runtime::write_f32(buf, *$expr);), - Type::Double => quote!(runtime::write_f64(buf, *$expr);), - Type::Boolean => quote!(runtime::write_bool(buf, *$expr);), - Type::Octet => quote!(buf.push(*$expr);), - Type::String { bound } => { - let limit = self.bound_tokens(*bound); - quote!(runtime::write_string(buf, $expr, $limit)?;) - } - Type::Sequence { element_type, size } => { - let limit = self.bound_tokens(*size); - let inner = self.serialize_value(quote!(value), element_type, scope); - let element_ty = self.render_type(element_type, scope); - quote! { - runtime::write_vec( - buf, - $expr, - $limit, - |value: &$element_ty, buf| { - $inner - Ok(()) - }, - )?; - } - } - Type::ScopedName(path) => { - if let Some(base_type) = self.registry.enum_repr(path) { - let writer = self.writer_fn(base_type); - quote!(runtime::$writer(buf, (*$expr).into());) - } else { - quote!(runtime::BinarySerializable::write_payload($expr, buf)?;) - } - } - _ => quote! { - compile_error!("unsupported type for serialization in rust generator"); - }, - } - } - - fn deserialize_value(&self, ty: &Type, scope: &[String]) -> Tokens { - match ty { - Type::Long => quote!(runtime::read_i32(cursor)), - Type::Short => quote!(runtime::read_i16(cursor)), - Type::UnsignedShort => quote!(runtime::read_u16(cursor)), - Type::UnsignedLong => quote!(runtime::read_u32(cursor)), - Type::LongLong => quote!(runtime::read_i64(cursor)), - Type::UnsignedLongLong => quote!(runtime::read_u64(cursor)), - Type::Float => quote!(runtime::read_f32(cursor)), - Type::Double => quote!(runtime::read_f64(cursor)), - Type::Boolean => quote!(runtime::read_bool(cursor)), - Type::Octet => quote!(runtime::read_u8(cursor)), - Type::String { bound } => { - let limit = self.bound_tokens(*bound); - quote!(runtime::read_string(cursor, $limit)) - } - Type::Sequence { element_type, size } => { - let limit = self.bound_tokens(*size); - let inner = self.deserialize_value(element_type, scope); - quote! { - runtime::read_vec(cursor, $limit, |cursor| $inner) - } - } - Type::ScopedName(path) => { - if let Some(base_type) = self.registry.enum_repr(path) { - let reader = self.reader_fn(base_type); - let enum_path = self.relative_path(path, scope); - let repr_ty = self.render_type(base_type, scope); - quote! {{ - let raw = runtime::$reader(cursor)?; - <$enum_path as ::core::convert::TryFrom<$repr_ty>>::try_from(raw) - }} - } else { - let path_tokens = self.relative_path(path, scope); - quote!(<$path_tokens as runtime::BinarySerializable>::read_payload(cursor)) - } - } - _ => quote! { - compile_error!("unsupported type for deserialization in rust generator"); - }, - } - } - fn render_type(&self, ty: &Type, scope: &[String]) -> Tokens { match ty { Type::Long => quote!(i32), @@ -731,47 +458,10 @@ impl RustGenerator { } } - fn serde_attr_tokens(&self) -> Tokens { - quote!(#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]) - } - fn default_attr_tokens(&self) -> Tokens { quote!(#[default]) } - fn bound_tokens(&self, bound: Option) -> Tokens { - match bound { - Some(limit) => quote!(Some($(format!("{}usize", limit)))), - None => quote!(None), - } - } - - fn writer_fn(&self, ty: &Type) -> Tokens { - match ty { - Type::Short => quote!(write_i16), - Type::UnsignedShort => quote!(write_u16), - Type::Long => quote!(write_i32), - Type::UnsignedLong => quote!(write_u32), - Type::LongLong => quote!(write_i64), - Type::UnsignedLongLong => quote!(write_u64), - Type::Octet => quote!(write_u8), - _ => quote!(write_u32), - } - } - - fn reader_fn(&self, ty: &Type) -> Tokens { - match ty { - Type::Short => quote!(read_i16), - Type::UnsignedShort => quote!(read_u16), - Type::Long => quote!(read_i32), - Type::UnsignedLong => quote!(read_u32), - Type::LongLong => quote!(read_i64), - Type::UnsignedLongLong => quote!(read_u64), - Type::Octet => quote!(read_u8), - _ => quote!(read_u32), - } - } - fn relative_path(&self, target: &[String], _scope: &[String]) -> Tokens { let mut path = String::from("crate::blueberry_generated"); for segment in target { @@ -965,10 +655,6 @@ impl TypeRegistry { members } - fn enum_repr(&self, path: &[String]) -> Option<&Type> { - self.enums.get(path) - } - fn resolve_typedef(&self, name: &[String], scope: &[String]) -> Option> { self.resolve_path(name, scope, self.typedefs.keys()) } diff --git a/crates/generators/rust/tests/compile_generated.rs b/crates/generators/rust/tests/compile_generated.rs index 7433457..53185ca 100644 --- a/crates/generators/rust/tests/compile_generated.rs +++ b/crates/generators/rust/tests/compile_generated.rs @@ -39,20 +39,48 @@ fn compile_fixture(relative_path: &str) { eprintln!("Wrote generated output to {}", dump_path.display()); } } - let output_path = temp_dir.path().join("generated.rlib"); - - let output = Command::new("rustc") - .arg("--edition=2024") - .arg("--crate-type=lib") - .arg(&source_path) - .arg("-o") - .arg(&output_path) + let manifest_path = temp_dir.path().join("Cargo.toml"); + fs::write( + &manifest_path, + r#"[package] +name = "blueberry-generated" +version = "0.0.0" +edition = "2024" + +[dependencies] +serde = { version = "1", features = ["derive"] } +cdr = "0.2" +"#, + ) + .expect("failed to write Cargo.toml"); + + // Place generated files where Cargo expects them. + let src_dir = temp_dir.path().join("src"); + fs::create_dir_all(&src_dir).expect("failed to create src directory"); + fs::write( + src_dir.join("lib.rs"), + fs::read_to_string(&source_path).unwrap(), + ) + .expect("failed to copy root file"); + let runtime_src = temp_dir.path().join("rust/blueberry_generated/runtime.rs"); + let runtime_dest = src_dir.join("blueberry_generated"); + fs::create_dir_all(&runtime_dest).expect("failed to create runtime directory"); + fs::write( + runtime_dest.join("runtime.rs"), + fs::read_to_string(runtime_src).unwrap(), + ) + .expect("failed to copy runtime file"); + + let output = Command::new("cargo") + .arg("check") + .arg("--manifest-path") + .arg(&manifest_path) .output() - .expect("failed to invoke rustc"); + .expect("failed to invoke cargo"); assert!( output.status.success(), - "rustc failed: {}", + "cargo check failed: {}", String::from_utf8_lossy(&output.stderr) ); }