Last active
July 5, 2024 02:39
-
-
Save lionello/6e306b9d6800f2439bec4d12e7a92274 to your computer and use it in GitHub Desktop.
Generate protobuf schema file from a Go type
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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