package internal
import (
"strings"
"unicode"
)
type Service struct {
Name string
Permission string
Interface string
Methods []Method
Doc string
Structs []StructDef
}
type Capability struct {
Name string
Interface string
Required bool
Methods []Export
Doc string
Structs []StructDef
TypeAliases []TypeAlias
Consts []ConstGroup
SourceFile string
}
type TypeAlias struct {
Name string
Type string
Doc string
}
type ConstGroup struct {
Type string
Values []ConstDef
}
type ConstDef struct {
Name string
Value string
Doc string
}
func (c Capability) KnownStructs() map[string]bool {
result := make(map[string]bool)
for _, st := range c.Structs {
result[st.Name] = true
}
return result
}
type Export struct {
Name string
ExportName string
Input Param
Output Param
Doc string
}
func (e Export) ProviderInterfaceName() string {
name := e.Name
for _, prefix := range []string{"Get", "On"} {
if strings.HasPrefix(name, prefix) {
name = name[len(prefix):]
break
}
}
return name + "Provider"
}
func (e Export) ImplVarName() string {
name := e.Name
for _, prefix := range []string{"Get", "On"} {
if strings.HasPrefix(name, prefix) {
name = name[len(prefix):]
break
}
}
if len(name) > 0 {
name = strings.ToLower(string(name[0])) + name[1:]
}
return name + "Impl"
}
func (e Export) ExportFuncName() string {
parts := strings.Split(e.ExportName, "_")
var result strings.Builder
result.WriteString("_")
for _, part := range parts {
if len(part) > 0 {
result.WriteString(strings.ToUpper(string(part[0])))
result.WriteString(part[1:])
}
}
return result.String()
}
func (e Export) HasInput() bool {
return e.Input.Type != ""
}
func (e Export) HasOutput() bool {
return e.Output.Type != ""
}
func (e Export) IsPointerOutput() bool {
return strings.HasPrefix(e.Output.Type, "*")
}
type StructDef struct {
Name string
Fields []FieldDef
Doc string
}
type FieldDef struct {
Name string
Type string
JSONTag string
OmitEmpty bool
Doc string
}
func (s Service) OutputFileName() string {
return strings.ToLower(s.Name) + "_gen.go"
}
func (s Service) ExportPrefix() string {
return strings.ToLower(s.Name)
}
func (s Service) KnownStructs() map[string]bool {
result := make(map[string]bool)
for _, st := range s.Structs {
result[st.Name] = true
}
return result
}
func (s Service) HasErrors() bool {
for _, m := range s.Methods {
if m.HasError {
return true
}
}
return false
}
type Method struct {
Name string
ExportName string
Params []Param
Returns []Param
HasError bool
Doc string
}
func (m Method) FunctionName(servicePrefix string) string {
if m.ExportName != "" {
return m.ExportName
}
return servicePrefix + "_" + strings.ToLower(m.Name)
}
func (m Method) RequestTypeName(serviceName string) string {
return serviceName + m.Name + "Request"
}
func (m Method) ResponseTypeName(serviceName string) string {
return serviceName + m.Name + "Response"
}
func (m Method) ClientRequestTypeName(serviceName string) string {
return lowerFirst(serviceName) + m.Name + "Request"
}
func (m Method) ClientResponseTypeName(serviceName string) string {
return lowerFirst(serviceName) + m.Name + "Response"
}
func lowerFirst(s string) string {
if s == "" {
return s
}
r := []rune(s)
r[0] = unicode.ToLower(r[0])
return string(r)
}
func (m Method) HasParams() bool {
return len(m.Params) > 0
}
func (m Method) HasReturns() bool {
return len(m.Returns) > 0
}
func (m Method) IsErrorOnly() bool {
return m.HasError && !m.HasReturns()
}
func (m Method) IsSingleReturn() bool {
return len(m.Returns) == 1
}
func (m Method) IsMultiReturn() bool {
return len(m.Returns) > 1
}
func (m Method) IsOptionPattern() bool {
if len(m.Returns) != 2 {
return false
}
if m.Returns[1].Type != "bool" {
return false
}
if m.Returns[0].Type == "bool" {
return false
}
name := strings.ToLower(m.Returns[1].Name)
return name == "exists" || name == "ok" || name == "found"
}
func (m Method) ReturnSignature() string {
if m.IsErrorOnly() {
return "error"
}
var parts []string
for _, r := range m.Returns {
parts = append(parts, r.Type)
}
if m.HasError {
parts = append(parts, "error")
}
if len(parts) == 1 {
return parts[0]
}
return "(" + strings.Join(parts, ", ") + ")"
}
func (m Method) ZeroValues() string {
var zeros []string
for _, r := range m.Returns {
zeros = append(zeros, zeroValue(r.Type))
}
return strings.Join(zeros, ", ")
}
func zeroValue(typ string) string {
switch {
case typ == "string":
return `""`
case typ == "bool":
return "false"
case typ == "int", typ == "int8", typ == "int16", typ == "int32", typ == "int64",
typ == "uint", typ == "uint8", typ == "uint16", typ == "uint32", typ == "uint64",
typ == "float32", typ == "float64":
return "0"
case typ == "[]byte":
return "nil"
case strings.HasPrefix(typ, "[]"):
return "nil"
case strings.HasPrefix(typ, "map["):
return "nil"
case strings.HasPrefix(typ, "*"):
return "nil"
case typ == "any", typ == "interface{}":
return "nil"
default:
return typ + "{}"
}
}
type Param struct {
Name string
Type string
JSONName string
}
func (p Param) IsByteSlice() bool {
return p.Type == "[]byte"
}
func (f FieldDef) IsByteSlice() bool {
return f.Type == "[]byte"
}
func (s Service) HasByteFields() bool {
for _, m := range s.Methods {
for _, p := range m.Params {
if p.IsByteSlice() {
return true
}
}
for _, r := range m.Returns {
if r.IsByteSlice() {
return true
}
}
}
for _, st := range s.Structs {
for _, f := range st.Fields {
if f.IsByteSlice() {
return true
}
}
}
return false
}
func (c Capability) HasByteFields() bool {
for _, st := range c.Structs {
for _, f := range st.Fields {
if f.IsByteSlice() {
return true
}
}
}
return false
}
func NewParam(name, typ string) Param {
return Param{
Name: name,
Type: typ,
JSONName: toJSONName(name),
}
}
func toJSONName(name string) string {
if name == "" {
return ""
}
runes := []rune(name)
result := make([]rune, 0, len(runes))
for i, r := range runes {
if i == 0 {
result = append(result, unicode.ToLower(r))
} else if unicode.IsUpper(r) {
prevIsUpper := unicode.IsUpper(runes[i-1])
nextIsLower := i+1 < len(runes) && unicode.IsLower(runes[i+1])
if prevIsUpper && !nextIsLower {
result = append(result, unicode.ToLower(r))
} else if prevIsUpper && nextIsLower {
result = append(result, r)
} else {
result = append(result, r)
}
} else {
result = append(result, r)
}
}
return string(result)
}
func ToPythonType(goType string) string {
switch goType {
case "string":
return "str"
case "int", "int32", "int64":
return "int"
case "float32", "float64":
return "float"
case "bool":
return "bool"
case "[]byte":
return "bytes"
default:
return "Any"
}
}
func ToSnakeCase(s string) string {
var result strings.Builder
runes := []rune(s)
for i, r := range runes {
if i > 0 && r >= 'A' && r <= 'Z' {
prevUpper := runes[i-1] >= 'A' && runes[i-1] <= 'Z'
nextUpper := i+1 < len(runes) && runes[i+1] >= 'A' && runes[i+1] <= 'Z'
atEnd := i+1 == len(runes)
if !prevUpper || (!nextUpper && !atEnd) {
result.WriteByte('_')
}
}
result.WriteRune(r)
}
return strings.ToLower(result.String())
}
func (m Method) PythonFunctionName(servicePrefix string) string {
return ToSnakeCase(servicePrefix + m.Name)
}
func (m Method) PythonResultTypeName(serviceName string) string {
return serviceName + m.Name + "Result"
}
func (m Method) NeedsResultClass() bool {
return len(m.Returns) > 1
}
func (p Param) PythonType() string {
return ToPythonType(p.Type)
}
func (p Param) PythonName() string {
return ToSnakeCase(p.Name)
}
func ToRustType(goType string) string {
return ToRustTypeWithStructs(goType, nil)
}
func RustParamType(goType string) string {
if goType == "string" {
return "&str"
}
return ToRustType(goType)
}
func RustDefaultValue(goType string) string {
switch goType {
case "string":
return `String::new()`
case "int", "int32", "int64", "uint", "uint32", "uint64":
return "0"
case "float32", "float64":
return "0.0"
case "bool":
return "false"
default:
if strings.HasPrefix(goType, "[]") {
return "Vec::new()"
}
if strings.HasPrefix(goType, "map[") {
return "std::collections::HashMap::new()"
}
if strings.HasPrefix(goType, "*") {
return "None"
}
return "serde_json::Value::Null"
}
}
func (m Method) RustFunctionName(_ string) string {
return ToSnakeCase(m.Name)
}
func RustDocComment(doc string) string {
if doc == "" {
return ""
}
lines := strings.Split(doc, "\n")
var result []string
for _, line := range lines {
result = append(result, "/// "+line)
}
return strings.Join(result, "\n")
}
func (p Param) RustType() string {
return ToRustType(p.Type)
}
func (p Param) RustTypeWithStructs(knownStructs map[string]bool) string {
return ToRustTypeWithStructs(p.Type, knownStructs)
}
func (p Param) RustParamType() string {
return RustParamType(p.Type)
}
func (p Param) RustParamTypeWithStructs(knownStructs map[string]bool) string {
if p.Type == "string" {
return "&str"
}
return ToRustTypeWithStructs(p.Type, knownStructs)
}
func (p Param) RustName() string {
return ToSnakeCase(p.Name)
}
func (p Param) NeedsToOwned() bool {
return p.Type == "string"
}
func (f FieldDef) RustType(knownStructs map[string]bool) string {
return ToRustTypeWithStructs(f.Type, knownStructs)
}
func (f FieldDef) RustName() string {
return ToSnakeCase(f.Name)
}
func (f FieldDef) NeedsDefault() bool {
return f.OmitEmpty
}
func ToRustTypeWithStructs(goType string, knownStructs map[string]bool) string {
if strings.HasPrefix(goType, "*") {
inner := ToRustTypeWithStructs(goType[1:], knownStructs)
return "Option<" + inner + ">"
}
if strings.HasPrefix(goType, "[]") {
if goType == "[]byte" {
return "Vec<u8>"
}
inner := ToRustTypeWithStructs(goType[2:], knownStructs)
return "Vec<" + inner + ">"
}
if strings.HasPrefix(goType, "map[") {
rest := goType[4:]
depth := 1
keyEnd := 0
for i, r := range rest {
if r == '[' {
depth++
} else if r == ']' {
depth--
if depth == 0 {
keyEnd = i
break
}
}
}
keyType := rest[:keyEnd]
valueType := rest[keyEnd+1:]
return "std::collections::HashMap<" + ToRustTypeWithStructs(keyType, knownStructs) + ", " + ToRustTypeWithStructs(valueType, knownStructs) + ">"
}
switch goType {
case "string":
return "String"
case "int", "int32":
return "i32"
case "int64":
return "i64"
case "uint", "uint32":
return "u32"
case "uint64":
return "u64"
case "float32":
return "f32"
case "float64":
return "f64"
case "bool":
return "bool"
case "interface{}", "any":
return "serde_json::Value"
default:
if knownStructs != nil && knownStructs[goType] {
return goType
}
return "serde_json::Value"
}
}