aptos_sdk/codegen/
types.rs1use std::collections::HashMap;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct RustType {
8 pub path: String,
10 pub needs_bcs: bool,
12 pub is_ref: bool,
14 pub doc: Option<String>,
16}
17
18impl RustType {
19 #[must_use]
21 pub fn new(path: impl Into<String>) -> Self {
22 Self {
23 path: path.into(),
24 needs_bcs: true,
25 is_ref: false,
26 doc: None,
27 }
28 }
29
30 #[must_use]
32 pub fn primitive(path: impl Into<String>) -> Self {
33 Self {
34 path: path.into(),
35 needs_bcs: false,
36 is_ref: false,
37 doc: None,
38 }
39 }
40
41 #[must_use]
43 pub fn reference(mut self) -> Self {
44 self.is_ref = true;
45 self
46 }
47
48 #[must_use]
50 pub fn with_doc(mut self, doc: impl Into<String>) -> Self {
51 self.doc = Some(doc.into());
52 self
53 }
54
55 pub fn as_arg_type(&self) -> String {
57 if self.is_ref {
58 format!("&{}", self.path)
59 } else {
60 self.path.clone()
61 }
62 }
63
64 pub fn as_return_type(&self) -> String {
66 self.path.clone()
67 }
68}
69
70#[derive(Debug, Clone)]
72pub struct MoveTypeMapper {
73 custom_mappings: HashMap<String, RustType>,
75}
76
77impl Default for MoveTypeMapper {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl MoveTypeMapper {
84 pub fn new() -> Self {
86 Self {
87 custom_mappings: HashMap::new(),
88 }
89 }
90
91 pub fn add_mapping(&mut self, move_type: impl Into<String>, rust_type: RustType) {
93 self.custom_mappings.insert(move_type.into(), rust_type);
94 }
95
96 pub fn map_type(&self, move_type: &str) -> RustType {
98 if let Some(rust_type) = self.custom_mappings.get(move_type) {
100 return rust_type.clone();
101 }
102
103 match move_type {
105 "bool" => RustType::primitive("bool"),
106 "u8" => RustType::primitive("u8"),
107 "u16" => RustType::primitive("u16"),
108 "u32" => RustType::primitive("u32"),
109 "u64" => RustType::primitive("u64"),
110 "u128" => RustType::primitive("u128"),
111 "u256" => RustType::new("MoveU256"),
116 "address" => RustType::new("AccountAddress"),
117 "signer" | "&signer" => RustType::new("AccountAddress")
118 .with_doc("Signer address (automatically set to sender)"),
119 _ => self.map_complex_type(move_type),
120 }
121 }
122
123 fn map_complex_type(&self, move_type: &str) -> RustType {
125 if move_type.starts_with("vector<") && move_type.ends_with('>') {
127 let inner = &move_type[7..move_type.len() - 1];
128 let inner_type = self.map_type(inner);
129
130 if inner == "u8" {
132 return RustType::new("Vec<u8>").with_doc("Bytes");
133 }
134
135 return RustType::new(format!("Vec<{}>", inner_type.path));
136 }
137
138 if move_type.contains("::option::Option<")
140 && let Some(start) = move_type.find("Option<")
141 {
142 let rest = &move_type[start + 7..];
143 if let Some(end) = rest.rfind('>') {
144 let inner = &rest[..end];
145 let inner_type = self.map_type(inner);
146 return RustType::new(format!("Option<{}>", inner_type.path));
147 }
148 }
149
150 if move_type == "0x1::string::String" || move_type.ends_with("::string::String") {
152 return RustType::new("String");
153 }
154
155 if move_type.contains("::object::Object<") {
157 return RustType::new("AccountAddress").with_doc("Object address");
158 }
159
160 if move_type.contains("::") {
162 let part_count = move_type.matches("::").count() + 1;
164 if part_count >= 3 {
165 if let Some(struct_name) = move_type.rsplit("::").next() {
167 let base_name = struct_name.split('<').next().unwrap_or(struct_name);
168
169 let rust_name = to_pascal_case(base_name);
171 return RustType::new(rust_name).with_doc(format!("Move type: {move_type}"));
172 }
173 }
174 }
175
176 RustType::new("serde_json::Value").with_doc(format!("Unknown Move type: {move_type}"))
178 }
179
180 pub fn to_bcs_arg(&self, move_type: &str, var_name: &str) -> String {
182 let rust_type = self.map_type(move_type);
183
184 if !rust_type.needs_bcs {
185 return format!(
187 "::aptos_sdk::aptos_bcs::to_bytes(&{var_name}).map_err(|e| AptosError::Bcs(e.to_string()))?"
188 );
189 }
190
191 format!(
194 "::aptos_sdk::aptos_bcs::to_bytes(&{var_name}).map_err(|e| AptosError::Bcs(e.to_string()))?"
195 )
196 }
197
198 pub fn is_signer_param(&self, move_type: &str) -> bool {
201 move_type == "&signer" || move_type == "signer"
202 }
203}
204
205pub fn to_pascal_case(s: &str) -> String {
207 let mut result = String::new();
208 let mut capitalize_next = true;
209
210 for c in s.chars() {
211 if c == '_' || c == '-' || c == ' ' {
212 capitalize_next = true;
213 } else if capitalize_next {
214 result.push(c.to_ascii_uppercase());
215 capitalize_next = false;
216 } else {
217 result.push(c);
218 }
219 }
220
221 result
222}
223
224pub fn to_snake_case(s: &str) -> String {
226 let mut result = String::new();
227
228 for (i, c) in s.chars().enumerate() {
229 if c.is_ascii_uppercase() {
230 if i > 0 {
231 result.push('_');
232 }
233 result.push(c.to_ascii_lowercase());
234 } else {
235 result.push(c);
236 }
237 }
238
239 result
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn test_primitive_mapping() {
248 let mapper = MoveTypeMapper::new();
249
250 assert_eq!(mapper.map_type("bool").path, "bool");
251 assert_eq!(mapper.map_type("u8").path, "u8");
252 assert_eq!(mapper.map_type("u64").path, "u64");
253 assert_eq!(mapper.map_type("u128").path, "u128");
254 assert_eq!(mapper.map_type("address").path, "AccountAddress");
255 }
256
257 #[test]
258 fn test_vector_mapping() {
259 let mapper = MoveTypeMapper::new();
260
261 assert_eq!(mapper.map_type("vector<u8>").path, "Vec<u8>");
262 assert_eq!(
263 mapper.map_type("vector<address>").path,
264 "Vec<AccountAddress>"
265 );
266 assert_eq!(mapper.map_type("vector<u64>").path, "Vec<u64>");
267 }
268
269 #[test]
270 fn test_string_mapping() {
271 let mapper = MoveTypeMapper::new();
272
273 assert_eq!(mapper.map_type("0x1::string::String").path, "String");
274 }
275
276 #[test]
277 fn test_to_pascal_case() {
278 assert_eq!(to_pascal_case("hello_world"), "HelloWorld");
279 assert_eq!(to_pascal_case("coin"), "Coin");
280 assert_eq!(to_pascal_case("aptos_coin"), "AptosCoin");
281 }
282
283 #[test]
284 fn test_to_snake_case() {
285 assert_eq!(to_snake_case("HelloWorld"), "hello_world");
286 assert_eq!(to_snake_case("Coin"), "coin");
287 assert_eq!(to_snake_case("AptosCoin"), "aptos_coin");
288 }
289
290 #[test]
291 fn test_rust_type_new() {
292 let rt = RustType::new("MyType");
293 assert_eq!(rt.path, "MyType");
294 assert!(rt.needs_bcs);
295 assert!(!rt.is_ref);
296 assert!(rt.doc.is_none());
297 }
298
299 #[test]
300 fn test_rust_type_primitive() {
301 let rt = RustType::primitive("u64");
302 assert_eq!(rt.path, "u64");
303 assert!(!rt.needs_bcs);
304 }
305
306 #[test]
307 fn test_rust_type_reference() {
308 let rt = RustType::new("MyType").reference();
309 assert!(rt.is_ref);
310 assert_eq!(rt.as_arg_type(), "&MyType");
311 }
312
313 #[test]
314 fn test_rust_type_with_doc() {
315 let rt = RustType::new("MyType").with_doc("My documentation");
316 assert_eq!(rt.doc, Some("My documentation".to_string()));
317 }
318
319 #[test]
320 fn test_rust_type_as_return_type() {
321 let rt = RustType::new("MyType").reference();
322 assert_eq!(rt.as_return_type(), "MyType"); }
324
325 #[test]
326 fn test_mapper_default() {
327 let mapper = MoveTypeMapper::default();
328 assert_eq!(mapper.map_type("bool").path, "bool");
329 }
330
331 #[test]
332 fn test_mapper_custom_mapping() {
333 let mut mapper = MoveTypeMapper::new();
334 mapper.add_mapping("MyCustomType", RustType::new("CustomRustType"));
335 assert_eq!(mapper.map_type("MyCustomType").path, "CustomRustType");
336 }
337
338 #[test]
339 fn test_mapper_u256() {
340 let mapper = MoveTypeMapper::new();
343 assert_eq!(mapper.map_type("u256").path, "MoveU256");
344 }
345
346 #[test]
347 fn test_mapper_signer() {
348 let mapper = MoveTypeMapper::new();
349 assert_eq!(mapper.map_type("&signer").path, "AccountAddress");
350 assert_eq!(mapper.map_type("signer").path, "AccountAddress");
351 }
352
353 #[test]
354 fn test_mapper_nested_vector() {
355 let mapper = MoveTypeMapper::new();
356 let result = mapper.map_type("vector<vector<u8>>");
357 assert_eq!(result.path, "Vec<Vec<u8>>");
358 }
359
360 #[test]
361 fn test_mapper_option_type() {
362 let mapper = MoveTypeMapper::new();
363 let result = mapper.map_type("0x1::option::Option<u64>");
364 assert_eq!(result.path, "Option<u64>");
365 }
366
367 #[test]
368 fn test_mapper_object_type() {
369 let mapper = MoveTypeMapper::new();
370 let result = mapper.map_type("0x1::object::Object<Token>");
371 assert_eq!(result.path, "AccountAddress");
372 }
373
374 #[test]
375 fn test_mapper_unknown_struct() {
376 let mapper = MoveTypeMapper::new();
377 let result = mapper.map_type("0x1::module::SomeStruct");
378 assert!(result.doc.is_some());
379 }
380
381 #[test]
382 fn test_mapper_unknown_type() {
383 let mapper = MoveTypeMapper::new();
384 let result = mapper.map_type("some_completely_unknown_thing");
385 assert_eq!(result.path, "serde_json::Value");
386 }
387
388 #[test]
389 fn test_to_bcs_arg_address() {
390 let mapper = MoveTypeMapper::new();
391 let result = mapper.to_bcs_arg("address", "my_addr");
392 assert!(result.contains("aptos_bcs::to_bytes"));
393 assert!(result.contains("my_addr"));
394 }
395
396 #[test]
397 fn test_to_bcs_arg_vector_u8() {
398 let mapper = MoveTypeMapper::new();
399 let result = mapper.to_bcs_arg("vector<u8>", "my_bytes");
400 assert!(result.contains("aptos_bcs::to_bytes"));
401 }
402
403 #[test]
404 fn test_to_bcs_arg_vector_other() {
405 let mapper = MoveTypeMapper::new();
406 let result = mapper.to_bcs_arg("vector<u64>", "my_vec");
407 assert!(result.contains("aptos_bcs::to_bytes"));
408 }
409
410 #[test]
411 fn test_to_bcs_arg_string() {
412 let mapper = MoveTypeMapper::new();
413 let result = mapper.to_bcs_arg("0x1::string::String", "my_string");
414 assert!(result.contains("aptos_bcs::to_bytes"));
415 }
416
417 #[test]
418 fn test_to_bcs_arg_other_string() {
419 let mapper = MoveTypeMapper::new();
420 let result = mapper.to_bcs_arg("0xabc::my_module::string::String", "s");
421 assert!(result.contains("aptos_bcs::to_bytes"));
422 }
423
424 #[test]
425 fn test_is_signer_param() {
426 let mapper = MoveTypeMapper::new();
427 assert!(mapper.is_signer_param("&signer"));
428 assert!(mapper.is_signer_param("signer"));
429 assert!(!mapper.is_signer_param("address"));
430 assert!(!mapper.is_signer_param("u64"));
431 }
432
433 #[test]
434 fn test_to_pascal_case_with_spaces() {
435 assert_eq!(to_pascal_case("hello world"), "HelloWorld");
436 }
437
438 #[test]
439 fn test_to_pascal_case_with_dashes() {
440 assert_eq!(to_pascal_case("hello-world"), "HelloWorld");
441 }
442
443 #[test]
444 fn test_to_snake_case_single_word() {
445 assert_eq!(to_snake_case("hello"), "hello");
446 }
447
448 #[test]
449 fn test_to_snake_case_already_lowercase() {
450 assert_eq!(to_snake_case("helloworld"), "helloworld");
451 }
452}