Skip to content

Instantly share code, notes, and snippets.

@lionello
Last active July 5, 2024 02:39
Show Gist options
  • Save lionello/6e306b9d6800f2439bec4d12e7a92274 to your computer and use it in GitHub Desktop.
Save lionello/6e306b9d6800f2439bec4d12e7a92274 to your computer and use it in GitHub Desktop.
Generate protobuf schema file from a Go type
package main
import (
"fmt"
"reflect"
"regexp"
"strings"
compose "github.com/compose-spec/compose-go/v2/types"
)
func main() {
p := compose.Project{}
proto := GenerateProto(p, "Project")
fmt.Println("syntax = \"proto3\";\n\n" + proto)
}
func GenerateProto(obj interface{}, messageName string) string {
val := reflect.TypeOf(obj)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return ""
}
proto := fmt.Sprintf("message %s {\n", messageName)
nestedMessages := ""
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fieldType, nestedMessage := getProtoType(field.Type)
if nestedMessage != "" {
nestedMessages += nestedMessage
}
proto += fmt.Sprintf(" %s %s = %d;\n", fieldType, toSnakeCase(field.Name), i+1)
}
proto += "}\n" + nestedMessages
return proto
}
func getProtoType(t reflect.Type) (string, string) {
switch t.Kind() {
case reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
return "int32", ""
case reflect.Int64:
return "int64", ""
case reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint:
return "uint32", ""
case reflect.Uint64:
return "uint64", ""
case reflect.Float32:
return "float", ""
case reflect.Float64:
return "double", ""
case reflect.String:
// TODO: many strings are actually enums
return t.Name(), ""
case reflect.Bool:
return "bool", ""
case reflect.Ptr:
nestedType, nestedMessage := getProtoType(t.Elem())
return nestedType, nestedMessage
case reflect.Slice:
elemType, nestedMessage := getProtoType(t.Elem())
return "repeated " + elemType, nestedMessage
case reflect.Map:
keyType, _ := getProtoType(t.Key())
valueType, nestedMessage := getProtoType(t.Elem())
mapTypeName := fmt.Sprintf("map<%s, %s>", keyType, valueType)
return mapTypeName, nestedMessage
case reflect.Struct:
nestedMessage := GenerateProto(reflect.New(t).Elem().Interface(), t.Name())
return t.Name(), nestedMessage
case reflect.Interface:
return "google.protobuf.Any", ""
default:
panic("unsupported type: " + t.String())
}
}
var snakeRE = regexp.MustCompile("([a-z0-9])([A-Z])")
func toSnakeCase(s string) string {
return strings.ToLower(snakeRE.ReplaceAllString(s, "${1}_${2}"))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment