GitRoot
Craft your forge, Build your project, Grow your community freely
1// SPDX-FileCopyrightText: 2026 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5package main
6
7import (
8 "fmt"
9 "go/types"
10 "path/filepath"
11 "slices"
12 "strings"
13
14 "github.com/gobeam/stringy"
15)
16
17type Rust struct{}
18
19func (Rust) Extension() string {
20 return ".rs"
21}
22
23func (r Rust) TemplateStruct(g *Generate, f *FileToGen, st Struct) (string, []string) {
24 fieldStrings := []string{}
25 imports := []string{}
26 for _, field := range st.Fields {
27 typeStr, imp := r.TypeToLang(field.Type(), f.OriginFilePath, g.structLocations)
28 if imp != "" && !slices.Contains(imports, imp) {
29 imports = append(imports, imp)
30 }
31
32 fieldName := stringy.New(field.Name()).SnakeCase().ToLower()
33 fieldStrings = append(fieldStrings, fmt.Sprintf(" pub %s: %s,", fieldName, typeStr))
34 }
35 structName := stringy.New(st.TypeName).PascalCase().Get()
36 if !slices.Contains(imports, "use serde::{Deserialize, Serialize};") {
37 imports = append(imports, "use serde::{Deserialize, Serialize};")
38 }
39 return fmt.Sprintf(`#[derive(Debug, Deserialize, Serialize, Clone)]
40#[serde(rename_all = "camelCase")]
41pub struct %s {
42%s
43}
44`, structName, strings.Join(fieldStrings, "\n")), imports
45}
46
47func (r Rust) TemplateEnum(en Enum, variants []string) string {
48 enumName := stringy.New(en.TypeName).PascalCase().Get()
49 vPascal := []string{}
50 for _, v := range variants {
51 variantPascal := stringy.New(v).PascalCase().Get()
52 vPascal = append(vPascal, fmt.Sprintf(" %s,", variantPascal))
53 }
54 return fmt.Sprintf(`#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
55#[serde(rename_all = "lowercase")]
56pub enum %s {
57%s
58}
59`, enumName, strings.Join(vPascal, "\n"))
60}
61
62func (r Rust) FormatFuncName(fn Func) string {
63 return stringy.New(fn.Name).SnakeCase().ToLower()
64}
65
66func (r Rust) FormatParams(sig *types.Signature, isMethod bool, currentFile string, locations map[string]string) (string, []string) {
67 var params []string
68 var imports []string
69
70 if isMethod {
71 params = append(params, "&self")
72 }
73
74 p := sig.Params()
75 for i := 0; i < p.Len(); i++ {
76 v := p.At(i)
77 typeStr, imp := r.TypeToLang(v.Type(), currentFile, locations)
78 if imp != "" {
79 imports = append(imports, imp)
80 }
81
82 name := v.Name()
83 if name == "" {
84 name = fmt.Sprintf("_arg%d", i)
85 } else {
86 name = stringy.New(name).SnakeCase().ToLower()
87 }
88
89 params = append(params, fmt.Sprintf("%s: %s", name, typeStr))
90 }
91
92 return strings.Join(params, ", "), imports
93}
94
95func (r Rust) FormatReturnType(sig *types.Signature, currentFile string, locations map[string]string) string {
96 res := sig.Results()
97 if res.Len() == 0 {
98 return ""
99 }
100
101 if res.Len() == 1 {
102 typeStr, _ := r.TypeToLang(res.At(0).Type(), currentFile, locations)
103 return fmt.Sprintf(" -> %s", typeStr)
104 }
105
106 var returns []string
107 for i := 0; i < res.Len(); i++ {
108 typeStr, _ := r.TypeToLang(res.At(i).Type(), currentFile, locations)
109 returns = append(returns, typeStr)
110 }
111 return fmt.Sprintf(" -> (%s)", strings.Join(returns, ", "))
112}
113
114func (r Rust) FormatExportFunc(wasmName string, funcName string) string {
115 return fmt.Sprintf(`#[cfg_attr(all(target_arch = "wasm32"), unsafe(export_name = "%s"))]
116pub fn %s(json: &str) {
117 with_plugin(|plugin| plugin.%s(serde_json::from_str(json).expect("JSON was not well-formatted")));
118}
119`, wasmName, funcName, funcName)
120}
121
122func (r Rust) FormatImportFunc(wasmName string, funcName string, paramsList string, returnType string) string {
123 return fmt.Sprintf(`#[link(wasm_import_module = "gitroot", link_name = "%s")] extern "C"
124pub fn %s(%s)%s {\n unimplemented!();\n}\n\n
125`, wasmName, funcName, paramsList, returnType)
126}
127
128func (r Rust) TypeToLang(t types.Type, currentFile string, locations map[string]string) (string, string) {
129 if sig, ok := t.(*types.Signature); ok && sig.Recv() == nil {
130 var cbParams []string
131 var cbImports []string
132
133 p := sig.Params()
134 for i := 0; i < p.Len(); i++ {
135 pType, pImp := r.TypeToLang(p.At(i).Type(), currentFile, locations)
136 if pImp != "" {
137 cbImports = append(cbImports, pImp)
138 }
139 cbParams = append(cbParams, pType)
140 }
141
142 rustCallbackType := fmt.Sprintf("impl Fn(%s)", strings.Join(cbParams, ", "))
143 return rustCallbackType, strings.Join(cbImports, "\n")
144 }
145
146 if slice, ok := t.(*types.Slice); ok {
147 elemType, imp := r.TypeToLang(slice.Elem(), currentFile, locations)
148 return fmt.Sprintf("Vec<%s>", elemType), imp
149 }
150
151 if ptr, ok := t.(*types.Pointer); ok {
152 return r.TypeToLang(ptr.Elem(), currentFile, locations)
153 }
154
155 if named, ok := t.(*types.Named); ok {
156 obj := named.Obj()
157 typeName := stringy.New(obj.Name()).PascalCase().Get()
158
159 originFile, exists := locations[obj.Name()]
160 if exists {
161 if originFile == currentFile {
162 return typeName, ""
163 }
164
165 moduleName := strings.TrimSuffix(filepath.Base(originFile), ".go")
166 parentDir := filepath.Base(filepath.Dir(originFile))
167 rustImport := fmt.Sprintf("use crate::%s::%s::%s;", parentDir, moduleName, typeName)
168 return typeName, rustImport
169 }
170 }
171
172 switch t.String() {
173 case "string":
174 return "String", ""
175 case "map[string]string":
176 return "HashMap<String, String>", "use std::collections::HashMap;"
177 case "map[string]any":
178 return "Value", "use serde_json::Value;"
179 case "uint64":
180 return "u64", ""
181 case "uint32":
182 return "i32", ""
183 case "int32":
184 return "i32", ""
185 case "int":
186 return "i32", ""
187 case "bool":
188 return "bool", ""
189 case "time.Time":
190 return "DateTime<Local>", "use chrono::{Local, DateTime};"
191 }
192
193 fmt.Printf("🐞 Type not found %s\n", t.String())
194
195 return t.String(), ""
196}