@wireio/protoc-gen-solana 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Convert a protobuf fully-qualified name to a Rust-safe identifier (PascalCase struct name).
3
+ * e.g. "my_package.MyMessage" → "MyMessage"
4
+ */
5
+ export declare function protoNameToRust(fqn: string): string;
6
+ /**
7
+ * Keep snake_case field name as-is for Rust struct members.
8
+ * e.g. "user_name" → "user_name"
9
+ * Only converts camelCase → snake_case if needed.
10
+ */
11
+ export declare function toSnakeCase(name: string): string;
12
+ /**
13
+ * Generate output .rs filename for a given .proto file, optionally rooted
14
+ * under a directory derived from the proto package name.
15
+ * e.g. "my_service.proto" with package "example.nested"
16
+ * → "example/nested/my_service.pb.rs"
17
+ */
18
+ export declare function protoFileToRsFile(protoFile: string, packageName?: string): string;
19
+ //# sourceMappingURL=names.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../src/util/names.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAYjF"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Convert a protobuf fully-qualified name to a Rust-safe identifier (PascalCase struct name).
3
+ * e.g. "my_package.MyMessage" → "MyMessage"
4
+ */
5
+ export function protoNameToRust(fqn) {
6
+ const parts = fqn.split(".");
7
+ return parts[parts.length - 1];
8
+ }
9
+ /**
10
+ * Keep snake_case field name as-is for Rust struct members.
11
+ * e.g. "user_name" → "user_name"
12
+ * Only converts camelCase → snake_case if needed.
13
+ */
14
+ export function toSnakeCase(name) {
15
+ // Already snake_case in proto, but handle camelCase just in case
16
+ return name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
17
+ }
18
+ /**
19
+ * Generate output .rs filename for a given .proto file, optionally rooted
20
+ * under a directory derived from the proto package name.
21
+ * e.g. "my_service.proto" with package "example.nested"
22
+ * → "example/nested/my_service.pb.rs"
23
+ */
24
+ export function protoFileToRsFile(protoFile, packageName) {
25
+ const base = protoFile.replace(/\.proto$/, "");
26
+ const parts = base.split("/");
27
+ const filename = parts[parts.length - 1];
28
+ // Rust files use snake_case
29
+ const snakeFilename = filename
30
+ .replace(/([a-z])([A-Z])/g, "$1_$2")
31
+ .toLowerCase();
32
+ const rsBasename = `${snakeFilename}.pb.rs`;
33
+ if (!packageName)
34
+ return rsBasename;
35
+ const dir = packageName.split(".").join("/");
36
+ return `${dir}/${rsBasename}`;
37
+ }
38
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmFtZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbC9uYW1lcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsZUFBZSxDQUFDLEdBQVc7SUFDekMsTUFBTSxLQUFLLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUM1QixPQUFPLEtBQUssQ0FBQyxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFBO0FBQ2hDLENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLFdBQVcsQ0FBQyxJQUFZO0lBQ3RDLGlFQUFpRTtJQUNqRSxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsaUJBQWlCLEVBQUUsT0FBTyxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUE7QUFDL0QsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUFDLFNBQWlCLEVBQUUsV0FBb0I7SUFDdkUsTUFBTSxJQUFJLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDLENBQUE7SUFDOUMsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUM3QixNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQTtJQUN4Qyw0QkFBNEI7SUFDNUIsTUFBTSxhQUFhLEdBQUcsUUFBUTtTQUMzQixPQUFPLENBQUMsaUJBQWlCLEVBQUUsT0FBTyxDQUFDO1NBQ25DLFdBQVcsRUFBRSxDQUFBO0lBQ2hCLE1BQU0sVUFBVSxHQUFHLEdBQUcsYUFBYSxRQUFRLENBQUE7SUFDM0MsSUFBSSxDQUFDLFdBQVc7UUFBRSxPQUFPLFVBQVUsQ0FBQTtJQUNuQyxNQUFNLEdBQUcsR0FBRyxXQUFXLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUM1QyxPQUFPLEdBQUcsR0FBRyxJQUFJLFVBQVUsRUFBRSxDQUFBO0FBQy9CLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvbnZlcnQgYSBwcm90b2J1ZiBmdWxseS1xdWFsaWZpZWQgbmFtZSB0byBhIFJ1c3Qtc2FmZSBpZGVudGlmaWVyIChQYXNjYWxDYXNlIHN0cnVjdCBuYW1lKS5cbiAqIGUuZy4gXCJteV9wYWNrYWdlLk15TWVzc2FnZVwiIOKGkiBcIk15TWVzc2FnZVwiXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwcm90b05hbWVUb1J1c3QoZnFuOiBzdHJpbmcpOiBzdHJpbmcge1xuICBjb25zdCBwYXJ0cyA9IGZxbi5zcGxpdChcIi5cIilcbiAgcmV0dXJuIHBhcnRzW3BhcnRzLmxlbmd0aCAtIDFdXG59XG5cbi8qKlxuICogS2VlcCBzbmFrZV9jYXNlIGZpZWxkIG5hbWUgYXMtaXMgZm9yIFJ1c3Qgc3RydWN0IG1lbWJlcnMuXG4gKiBlLmcuIFwidXNlcl9uYW1lXCIg4oaSIFwidXNlcl9uYW1lXCJcbiAqIE9ubHkgY29udmVydHMgY2FtZWxDYXNlIOKGkiBzbmFrZV9jYXNlIGlmIG5lZWRlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRvU25ha2VDYXNlKG5hbWU6IHN0cmluZyk6IHN0cmluZyB7XG4gIC8vIEFscmVhZHkgc25ha2VfY2FzZSBpbiBwcm90bywgYnV0IGhhbmRsZSBjYW1lbENhc2UganVzdCBpbiBjYXNlXG4gIHJldHVybiBuYW1lLnJlcGxhY2UoLyhbYS16XSkoW0EtWl0pL2csIFwiJDFfJDJcIikudG9Mb3dlckNhc2UoKVxufVxuXG4vKipcbiAqIEdlbmVyYXRlIG91dHB1dCAucnMgZmlsZW5hbWUgZm9yIGEgZ2l2ZW4gLnByb3RvIGZpbGUsIG9wdGlvbmFsbHkgcm9vdGVkXG4gKiB1bmRlciBhIGRpcmVjdG9yeSBkZXJpdmVkIGZyb20gdGhlIHByb3RvIHBhY2thZ2UgbmFtZS5cbiAqIGUuZy4gXCJteV9zZXJ2aWNlLnByb3RvXCIgd2l0aCBwYWNrYWdlIFwiZXhhbXBsZS5uZXN0ZWRcIlxuICogICAgICDihpIgXCJleGFtcGxlL25lc3RlZC9teV9zZXJ2aWNlLnBiLnJzXCJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHByb3RvRmlsZVRvUnNGaWxlKHByb3RvRmlsZTogc3RyaW5nLCBwYWNrYWdlTmFtZT86IHN0cmluZyk6IHN0cmluZyB7XG4gIGNvbnN0IGJhc2UgPSBwcm90b0ZpbGUucmVwbGFjZSgvXFwucHJvdG8kLywgXCJcIilcbiAgY29uc3QgcGFydHMgPSBiYXNlLnNwbGl0KFwiL1wiKVxuICBjb25zdCBmaWxlbmFtZSA9IHBhcnRzW3BhcnRzLmxlbmd0aCAtIDFdXG4gIC8vIFJ1c3QgZmlsZXMgdXNlIHNuYWtlX2Nhc2VcbiAgY29uc3Qgc25ha2VGaWxlbmFtZSA9IGZpbGVuYW1lXG4gICAgLnJlcGxhY2UoLyhbYS16XSkoW0EtWl0pL2csIFwiJDFfJDJcIilcbiAgICAudG9Mb3dlckNhc2UoKVxuICBjb25zdCByc0Jhc2VuYW1lID0gYCR7c25ha2VGaWxlbmFtZX0ucGIucnNgXG4gIGlmICghcGFja2FnZU5hbWUpIHJldHVybiByc0Jhc2VuYW1lXG4gIGNvbnN0IGRpciA9IHBhY2thZ2VOYW1lLnNwbGl0KFwiLlwiKS5qb2luKFwiL1wiKVxuICByZXR1cm4gYCR7ZGlyfS8ke3JzQmFzZW5hbWV9YFxufVxuIl19
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@wireio/protoc-gen-solana",
3
+ "version": "1.0.1",
4
+ "description": "protoc plugin generating Rust encode/decode modules from protobuf3 definitions for Solana programs",
5
+ "private": false,
6
+ "type": "module",
7
+ "types": "lib/index.d.ts",
8
+ "module": "dist/bundle/protoc-gen-solana.mjs",
9
+ "bin": {
10
+ "protoc-gen-solana": "dist/bundle/protoc-gen-solana.mjs"
11
+ },
12
+ "pkg": {
13
+ "assets": [
14
+ "rs/**/*"
15
+ ]
16
+ },
17
+ "files": [
18
+ "lib",
19
+ "dist/bundle",
20
+ "rs",
21
+ "README.md"
22
+ ],
23
+ "dependencies": {
24
+ "protobufjs": "^8.0.0",
25
+ "tracer": "^1.3.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^25.3.0",
29
+ "@yao-pkg/pkg": "^6.14.1",
30
+ "esbuild": "^0.27.3",
31
+ "prettier": "^3.8.1",
32
+ "protoc": "^29.6.0",
33
+ "typescript": "^5.9.3"
34
+ },
35
+ "engines": {
36
+ "node": ">=24"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc -b tsconfig.json",
40
+ "build:dev": "tsc -b tsconfig.json -w",
41
+ "bundle": "node esbuild.config.js",
42
+ "bundle:dev": "node esbuild.config.js -w",
43
+ "dev": "concurrently npm:build:dev npm:bundle:dev",
44
+ "dist": "pnpm build && pnpm bundle && pkg -c package.json --output dist/bin/protoc-gen-solana -d --options experimental-require-module dist/bundle/protoc-gen-solana.mjs",
45
+ "format": "prettier --write \"src/**/*.{ts,tsx}\"",
46
+ "generate:test": "npm run dist && mkdir -p ./dist/tests/generated && npx protoc --plugin=protoc-gen-solana=$PWD/dist/bin/protoc-gen-solana --solana_out=./dist/tests/generated tests/protos/*.proto",
47
+ "clean": "rm -rf lib dist"
48
+ }
49
+ }
@@ -0,0 +1,352 @@
1
+ // Shared protobuf3 wire format primitives for protoc-gen-solana.
2
+ // This file is also emitted by the plugin alongside generated codecs.
3
+ //
4
+ // Optimized for Solana's compute budget: minimal allocations,
5
+ // no unnecessary copies, and efficient varint handling.
6
+
7
+ use std::fmt;
8
+
9
+ // ── Error type ───────────────────────────────────────────────────────
10
+
11
+ #[derive(Debug, Clone)]
12
+ pub enum DecodeError {
13
+ BufferOverflow,
14
+ InvalidVarint,
15
+ UnknownWireType(u64),
16
+ InvalidData(&'static str),
17
+ }
18
+
19
+ impl fmt::Display for DecodeError {
20
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21
+ match self {
22
+ DecodeError::BufferOverflow => write!(f, "protobuf: buffer overflow"),
23
+ DecodeError::InvalidVarint => write!(f, "protobuf: invalid varint"),
24
+ DecodeError::UnknownWireType(wt) => write!(f, "protobuf: unknown wire type {}", wt),
25
+ DecodeError::InvalidData(msg) => write!(f, "protobuf: {}", msg),
26
+ }
27
+ }
28
+ }
29
+
30
+ // ── Key (tag) encode / decode ────────────────────────────────────────
31
+
32
+ #[inline]
33
+ pub fn encode_key(buf: &mut Vec<u8>, tag: u64) {
34
+ encode_varint(buf, tag);
35
+ }
36
+
37
+ #[inline]
38
+ pub fn decode_key(data: &[u8], pos: usize) -> Result<(u64, usize), DecodeError> {
39
+ decode_varint(data, pos)
40
+ }
41
+
42
+ // ── Wire Type 0: Varint ──────────────────────────────────────────────
43
+
44
+ #[inline]
45
+ pub fn encode_varint(buf: &mut Vec<u8>, mut value: u64) {
46
+ loop {
47
+ if value < 0x80 {
48
+ buf.push(value as u8);
49
+ return;
50
+ }
51
+ buf.push(((value & 0x7F) | 0x80) as u8);
52
+ value >>= 7;
53
+ }
54
+ }
55
+
56
+ #[inline]
57
+ pub fn decode_varint(data: &[u8], mut pos: usize) -> Result<(u64, usize), DecodeError> {
58
+ let mut result: u64 = 0;
59
+ let mut shift: u32 = 0;
60
+ loop {
61
+ if pos >= data.len() {
62
+ return Err(DecodeError::BufferOverflow);
63
+ }
64
+ let b = data[pos];
65
+ pos += 1;
66
+ result |= ((b & 0x7F) as u64) << shift;
67
+ if b & 0x80 == 0 {
68
+ return Ok((result, pos));
69
+ }
70
+ shift += 7;
71
+ if shift > 63 {
72
+ return Err(DecodeError::InvalidVarint);
73
+ }
74
+ }
75
+ }
76
+
77
+ // ── Bool ─────────────────────────────────────────────────────────────
78
+
79
+ #[inline]
80
+ pub fn encode_bool(buf: &mut Vec<u8>, value: bool) {
81
+ buf.push(if value { 1 } else { 0 });
82
+ }
83
+
84
+ #[inline]
85
+ pub fn decode_bool(data: &[u8], pos: usize) -> Result<(bool, usize), DecodeError> {
86
+ let (v, new_pos) = decode_varint(data, pos)?;
87
+ Ok((v != 0, new_pos))
88
+ }
89
+
90
+ // ── ZigZag (sint32/sint64) ───────────────────────────────────────────
91
+
92
+ #[inline]
93
+ pub fn encode_zigzag32(buf: &mut Vec<u8>, value: i32) {
94
+ let encoded = ((value << 1) ^ (value >> 31)) as u32;
95
+ encode_varint(buf, encoded as u64);
96
+ }
97
+
98
+ #[inline]
99
+ pub fn decode_zigzag32(data: &[u8], pos: usize) -> Result<(i32, usize), DecodeError> {
100
+ let (raw, new_pos) = decode_varint(data, pos)?;
101
+ let n = raw as u32;
102
+ let value = ((n >> 1) as i32) ^ (-((n & 1) as i32));
103
+ Ok((value, new_pos))
104
+ }
105
+
106
+ #[inline]
107
+ pub fn encode_zigzag64(buf: &mut Vec<u8>, value: i64) {
108
+ let encoded = ((value << 1) ^ (value >> 63)) as u64;
109
+ encode_varint(buf, encoded);
110
+ }
111
+
112
+ #[inline]
113
+ pub fn decode_zigzag64(data: &[u8], pos: usize) -> Result<(i64, usize), DecodeError> {
114
+ let (raw, new_pos) = decode_varint(data, pos)?;
115
+ let value = ((raw >> 1) as i64) ^ (-((raw & 1) as i64));
116
+ Ok((value, new_pos))
117
+ }
118
+
119
+ // ── Wire Type 1: 64-bit (little-endian) ─────────────────────────────
120
+
121
+ #[inline]
122
+ pub fn encode_fixed64(buf: &mut Vec<u8>, value: u64) {
123
+ buf.extend_from_slice(&value.to_le_bytes());
124
+ }
125
+
126
+ #[inline]
127
+ pub fn decode_fixed64(data: &[u8], pos: usize) -> Result<(u64, usize), DecodeError> {
128
+ if pos + 8 > data.len() {
129
+ return Err(DecodeError::BufferOverflow);
130
+ }
131
+ let value = u64::from_le_bytes(data[pos..pos + 8].try_into().unwrap());
132
+ Ok((value, pos + 8))
133
+ }
134
+
135
+ #[inline]
136
+ pub fn encode_sfixed64(buf: &mut Vec<u8>, value: i64) {
137
+ encode_fixed64(buf, value as u64);
138
+ }
139
+
140
+ #[inline]
141
+ pub fn decode_sfixed64(data: &[u8], pos: usize) -> Result<(i64, usize), DecodeError> {
142
+ let (raw, new_pos) = decode_fixed64(data, pos)?;
143
+ Ok((raw as i64, new_pos))
144
+ }
145
+
146
+ // ── Wire Type 5: 32-bit (little-endian) ─────────────────────────────
147
+
148
+ #[inline]
149
+ pub fn encode_fixed32(buf: &mut Vec<u8>, value: u32) {
150
+ buf.extend_from_slice(&value.to_le_bytes());
151
+ }
152
+
153
+ #[inline]
154
+ pub fn decode_fixed32(data: &[u8], pos: usize) -> Result<(u32, usize), DecodeError> {
155
+ if pos + 4 > data.len() {
156
+ return Err(DecodeError::BufferOverflow);
157
+ }
158
+ let value = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap());
159
+ Ok((value, pos + 4))
160
+ }
161
+
162
+ #[inline]
163
+ pub fn encode_sfixed32(buf: &mut Vec<u8>, value: i32) {
164
+ encode_fixed32(buf, value as u32);
165
+ }
166
+
167
+ #[inline]
168
+ pub fn decode_sfixed32(data: &[u8], pos: usize) -> Result<(i32, usize), DecodeError> {
169
+ let (raw, new_pos) = decode_fixed32(data, pos)?;
170
+ Ok((raw as i32, new_pos))
171
+ }
172
+
173
+ // ── Wire Type 2: Length-delimited ────────────────────────────────────
174
+
175
+ #[inline]
176
+ pub fn encode_bytes(buf: &mut Vec<u8>, value: &[u8]) {
177
+ encode_varint(buf, value.len() as u64);
178
+ buf.extend_from_slice(value);
179
+ }
180
+
181
+ #[inline]
182
+ pub fn decode_bytes(data: &[u8], pos: usize) -> Result<(Vec<u8>, usize), DecodeError> {
183
+ let (len, pos) = decode_varint(data, pos)?;
184
+ let len = len as usize;
185
+ if pos + len > data.len() {
186
+ return Err(DecodeError::BufferOverflow);
187
+ }
188
+ Ok((data[pos..pos + len].to_vec(), pos + len))
189
+ }
190
+
191
+ #[inline]
192
+ pub fn encode_string(buf: &mut Vec<u8>, value: &str) {
193
+ encode_bytes(buf, value.as_bytes());
194
+ }
195
+
196
+ #[inline]
197
+ pub fn decode_string(data: &[u8], pos: usize) -> Result<(String, usize), DecodeError> {
198
+ let (raw, new_pos) = decode_bytes(data, pos)?;
199
+ String::from_utf8(raw)
200
+ .map(|s| (s, new_pos))
201
+ .map_err(|_| DecodeError::InvalidData("invalid UTF-8 in string field"))
202
+ }
203
+
204
+ // ── Skip unknown fields ──────────────────────────────────────────────
205
+
206
+ #[inline]
207
+ pub fn skip_field(data: &[u8], pos: usize, wire_type: u64) -> Result<usize, DecodeError> {
208
+ match wire_type {
209
+ 0 => {
210
+ // Varint: skip until MSB is clear
211
+ let (_, new_pos) = decode_varint(data, pos)?;
212
+ Ok(new_pos)
213
+ }
214
+ 1 => {
215
+ // 64-bit: skip 8 bytes
216
+ if pos + 8 > data.len() {
217
+ return Err(DecodeError::BufferOverflow);
218
+ }
219
+ Ok(pos + 8)
220
+ }
221
+ 2 => {
222
+ // Length-delimited: read length, skip that many bytes
223
+ let (len, new_pos) = decode_varint(data, pos)?;
224
+ let end = new_pos + len as usize;
225
+ if end > data.len() {
226
+ return Err(DecodeError::BufferOverflow);
227
+ }
228
+ Ok(end)
229
+ }
230
+ 5 => {
231
+ // 32-bit: skip 4 bytes
232
+ if pos + 4 > data.len() {
233
+ return Err(DecodeError::BufferOverflow);
234
+ }
235
+ Ok(pos + 4)
236
+ }
237
+ _ => Err(DecodeError::UnknownWireType(wire_type)),
238
+ }
239
+ }
240
+
241
+ #[cfg(test)]
242
+ mod tests {
243
+ use super::*;
244
+
245
+ #[test]
246
+ fn test_varint_roundtrip() {
247
+ for &val in &[0u64, 1, 127, 128, 255, 300, 16384, u64::MAX] {
248
+ let mut buf = Vec::new();
249
+ encode_varint(&mut buf, val);
250
+ let (decoded, pos) = decode_varint(&buf, 0).unwrap();
251
+ assert_eq!(decoded, val);
252
+ assert_eq!(pos, buf.len());
253
+ }
254
+ }
255
+
256
+ #[test]
257
+ fn test_bool_roundtrip() {
258
+ for &val in &[true, false] {
259
+ let mut buf = Vec::new();
260
+ encode_bool(&mut buf, val);
261
+ let (decoded, _) = decode_bool(&buf, 0).unwrap();
262
+ assert_eq!(decoded, val);
263
+ }
264
+ }
265
+
266
+ #[test]
267
+ fn test_zigzag32_roundtrip() {
268
+ for &val in &[0i32, 1, -1, 2, -2, i32::MAX, i32::MIN] {
269
+ let mut buf = Vec::new();
270
+ encode_zigzag32(&mut buf, val);
271
+ let (decoded, _) = decode_zigzag32(&buf, 0).unwrap();
272
+ assert_eq!(decoded, val);
273
+ }
274
+ }
275
+
276
+ #[test]
277
+ fn test_zigzag64_roundtrip() {
278
+ for &val in &[0i64, 1, -1, 2, -2, i64::MAX, i64::MIN] {
279
+ let mut buf = Vec::new();
280
+ encode_zigzag64(&mut buf, val);
281
+ let (decoded, _) = decode_zigzag64(&buf, 0).unwrap();
282
+ assert_eq!(decoded, val);
283
+ }
284
+ }
285
+
286
+ #[test]
287
+ fn test_fixed64_roundtrip() {
288
+ for &val in &[0u64, 1, 0xDEAD_BEEF, u64::MAX] {
289
+ let mut buf = Vec::new();
290
+ encode_fixed64(&mut buf, val);
291
+ let (decoded, _) = decode_fixed64(&buf, 0).unwrap();
292
+ assert_eq!(decoded, val);
293
+ }
294
+ }
295
+
296
+ #[test]
297
+ fn test_fixed32_roundtrip() {
298
+ for &val in &[0u32, 1, 0xDEAD, u32::MAX] {
299
+ let mut buf = Vec::new();
300
+ encode_fixed32(&mut buf, val);
301
+ let (decoded, _) = decode_fixed32(&buf, 0).unwrap();
302
+ assert_eq!(decoded, val);
303
+ }
304
+ }
305
+
306
+ #[test]
307
+ fn test_string_roundtrip() {
308
+ for val in &["", "hello", "hello world 🌍"] {
309
+ let mut buf = Vec::new();
310
+ encode_string(&mut buf, val);
311
+ let (decoded, _) = decode_string(&buf, 0).unwrap();
312
+ assert_eq!(&decoded, val);
313
+ }
314
+ }
315
+
316
+ #[test]
317
+ fn test_bytes_roundtrip() {
318
+ for val in &[vec![], vec![1u8, 2, 3], vec![0xFF; 300]] {
319
+ let mut buf = Vec::new();
320
+ encode_bytes(&mut buf, val);
321
+ let (decoded, _) = decode_bytes(&buf, 0).unwrap();
322
+ assert_eq!(&decoded, val);
323
+ }
324
+ }
325
+
326
+ #[test]
327
+ fn test_skip_field() {
328
+ // Varint
329
+ let mut buf = Vec::new();
330
+ encode_varint(&mut buf, 300);
331
+ let new_pos = skip_field(&buf, 0, 0).unwrap();
332
+ assert_eq!(new_pos, buf.len());
333
+
334
+ // Fixed64
335
+ let mut buf = Vec::new();
336
+ encode_fixed64(&mut buf, 42);
337
+ let new_pos = skip_field(&buf, 0, 1).unwrap();
338
+ assert_eq!(new_pos, 8);
339
+
340
+ // Length-delimited
341
+ let mut buf = Vec::new();
342
+ encode_string(&mut buf, "hello");
343
+ let new_pos = skip_field(&buf, 0, 2).unwrap();
344
+ assert_eq!(new_pos, buf.len());
345
+
346
+ // Fixed32
347
+ let mut buf = Vec::new();
348
+ encode_fixed32(&mut buf, 42);
349
+ let new_pos = skip_field(&buf, 0, 5).unwrap();
350
+ assert_eq!(new_pos, 4);
351
+ }
352
+ }