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}