include!(concat!(env!("OUT_DIR"), "/oxiproto.fixtures.rs"));
use oxiproto::{OxiMessage, OxiName, OxiProtoError};
#[derive(Debug, Default, Clone, PartialEq)]
struct UserNative {
id: i32,
name: String,
tags: Vec<String>,
active: bool,
_unknown: oxiproto::wire::UnknownFields,
}
impl OxiName for UserNative {
const NAME: &'static str = "User";
const PACKAGE: &'static str = "oxiproto.fixtures";
}
impl OxiMessage for UserNative {
fn encoded_len(&self) -> usize {
use oxiproto::wire::varint::encoded_len_varint;
use oxiproto::wire::WireType;
let mut len = 0usize;
if self.id != 0 {
len += encoded_len_varint(8u64);
len += encoded_len_varint(self.id as u64);
}
if !self.name.is_empty() {
len += encoded_len_varint(18u64);
let nb = self.name.len();
len += encoded_len_varint(nb as u64);
len += nb;
}
for tag_str in &self.tags {
len += encoded_len_varint(26u64);
let nb = tag_str.len();
len += encoded_len_varint(nb as u64);
len += nb;
}
if self.active {
len += encoded_len_varint(32u64);
len += 1; }
let _ = WireType::Varint; len += self._unknown.encoded_len();
len
}
fn encode_raw(&self, buf: &mut oxiproto::wire::EncodeBuffer) {
use oxiproto::wire::WireType;
if self.id != 0 {
buf.write_tag(1, WireType::Varint)
.expect("write_tag field 1");
buf.write_varint(self.id as i64 as u64);
}
if !self.name.is_empty() {
buf.write_tag(2, WireType::Len).expect("write_tag field 2");
buf.write_string(&self.name);
}
for tag_str in &self.tags {
buf.write_tag(3, WireType::Len).expect("write_tag field 3");
buf.write_string(tag_str);
}
if self.active {
buf.write_tag(4, WireType::Varint)
.expect("write_tag field 4");
buf.write_bool(true);
}
self._unknown.encode_to(buf);
}
fn merge(&mut self, buf: &mut oxiproto::wire::DecodeBuffer) -> oxiproto::OxiProtoResult<()> {
use oxiproto::wire::WireType;
loop {
if buf.is_empty() {
break;
}
let tag = match buf.read_tag() {
Ok(t) => t,
Err(oxiproto::wire::WireError::UnexpectedEof) => break,
Err(e) => return Err(OxiProtoError::WireFormatError(e)),
};
match (tag.field_number, tag.wire_type) {
(1, WireType::Varint) => {
self.id = buf.read_varint().map_err(OxiProtoError::WireFormatError)? as i32;
}
(2, WireType::Len) => {
let slice = buf
.read_length_delimited()
.map_err(OxiProtoError::WireFormatError)?;
self.name = String::from_utf8(slice.to_vec())
.map_err(|e| OxiProtoError::ParseError(e.to_string()))?;
}
(3, WireType::Len) => {
let slice = buf
.read_length_delimited()
.map_err(OxiProtoError::WireFormatError)?;
let s = String::from_utf8(slice.to_vec())
.map_err(|e| OxiProtoError::ParseError(e.to_string()))?;
self.tags.push(s);
}
(4, WireType::Varint) => {
self.active = buf.read_varint().map_err(OxiProtoError::WireFormatError)? != 0;
}
(_, wt) => {
match wt {
WireType::Varint => {
let v = buf.read_varint().map_err(OxiProtoError::WireFormatError)?;
self._unknown.push_varint(tag.field_number, v);
}
WireType::I64 => {
let v = buf.read_fixed64().map_err(OxiProtoError::WireFormatError)?;
self._unknown.push_fixed64(tag.field_number, v);
}
WireType::Len => {
let slice = buf
.read_length_delimited()
.map_err(OxiProtoError::WireFormatError)?;
self._unknown
.push_length_delimited(tag.field_number, slice.to_vec());
}
WireType::I32 => {
let v = buf.read_fixed32().map_err(OxiProtoError::WireFormatError)?;
self._unknown.push_fixed32(tag.field_number, v);
}
WireType::SGroup | WireType::EGroup => {
buf.skip_field(wt).map_err(OxiProtoError::WireFormatError)?;
}
}
}
}
}
Ok(())
}
fn clear(&mut self) {
self.id = 0;
self.name.clear();
self.tags.clear();
self.active = false;
self._unknown.clear();
}
}
#[test]
fn oxi_name_correct() {
assert_eq!(UserNative::full_name(), "oxiproto.fixtures.User");
assert_eq!(
UserNative::type_url(),
"type.googleapis.com/oxiproto.fixtures.User"
);
}
#[test]
fn oxi_message_encode_decode_round_trip() {
let user = UserNative {
id: 42,
name: "Alice".to_owned(),
tags: vec!["admin".to_owned(), "user".to_owned()],
active: true,
_unknown: Default::default(),
};
let bytes = oxiproto::encode(&user);
let decoded = oxiproto::decode::<UserNative>(&bytes).expect("decode should succeed");
assert_eq!(user, decoded);
}
#[test]
fn oxi_message_encode_empty_is_zero_bytes() {
let user = UserNative::default();
let bytes = oxiproto::encode(&user);
assert!(
bytes.is_empty(),
"all-default proto3 message should encode to zero bytes"
);
}
#[test]
fn encoded_len_matches_actual_length() {
let user = UserNative {
id: 1,
name: "Bob".to_owned(),
tags: vec!["x".to_owned()],
active: false,
_unknown: Default::default(),
};
let bytes = oxiproto::encode(&user);
assert_eq!(
user.encoded_len(),
bytes.len(),
"encoded_len() must equal the actual byte count"
);
}
#[test]
fn wire_byte_cross_validation_vs_prost() {
let oxi_user = UserNative {
id: 99,
name: "CrossVal".to_owned(),
tags: vec!["a".to_owned(), "b".to_owned()],
active: true,
_unknown: Default::default(),
};
let oxi_bytes = oxiproto::encode(&oxi_user);
use prost::Message as _;
let prost_user = User {
id: 99,
name: "CrossVal".to_owned(),
tags: vec!["a".to_owned(), "b".to_owned()],
active: true,
};
let prost_bytes = prost_user.encode_to_vec();
assert_eq!(
oxi_bytes, prost_bytes,
"OxiMessage and prost::Message must produce identical wire bytes"
);
}
#[test]
fn decode_with_unknown_fields_preserves_round_trip() {
use prost::Message as _;
let prost_user = User {
id: 5,
name: "Unknown".to_owned(),
tags: vec![],
active: false,
};
let mut prost_bytes = prost_user.encode_to_vec();
prost_bytes.extend_from_slice(&[0x98, 0x06, 0x2a]);
let decoded = oxiproto::decode::<UserNative>(&prost_bytes)
.expect("decode with unknown fields should succeed");
assert_eq!(decoded.id, 5);
assert_eq!(decoded.name, "Unknown");
assert!(
!decoded._unknown.is_empty(),
"unknown field 99 must be stored"
);
let re_encoded = oxiproto::encode(&decoded);
assert_eq!(
re_encoded.len(),
prost_bytes.len(),
"re-encoded bytes should have same length when unknown fields are preserved"
);
}
#[cfg(all(feature = "build", feature = "codegen"))]
#[test]
fn codegen_with_oxi_message_impl_produces_valid_rust() {
use std::time::SystemTime;
let nanos = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.subsec_nanos();
let temp_dir = std::env::temp_dir().join(format!("oxiproto-f-test-{nanos}"));
std::fs::create_dir_all(&temp_dir).expect("create temp dir");
let proto_path = temp_dir.join("test.proto");
std::fs::write(
&proto_path,
b"syntax = \"proto3\";\nmessage Foo { int32 x = 1; }\n",
)
.expect("write proto");
let fds = oxiproto::build::compile_to_fds(&[&proto_path], &[&temp_dir])
.expect("compile_to_fds should succeed");
let mut opts = oxiproto_codegen::CodegenOptions::new();
opts.emit_oxi_message_impl = true;
let code = oxiproto::codegen::generate_with_options(&fds, &opts)
.expect("generate_with_options should succeed");
assert!(
code.contains("impl ::oxiproto_core::OxiMessage for Foo"),
"Generated code should contain OxiMessage impl, got:\n{code}"
);
assert!(
code.contains("impl ::oxiproto_core::OxiName for Foo"),
"Generated code should contain OxiName impl, got:\n{code}"
);
let _ = std::fs::remove_dir_all(&temp_dir);
}