func search(a ASTNode, symbolPath string) ASTNode {
	rv := reflect.ValueOf(a)
	if rv.Kind() == reflect.Slice {
		for i := 0; i < rv.Len(); i++ {
			if found := search(rv.Index(i).Interface(), symbolPath); found != nil {
				return found
			}
		}
		return nil
	}
	switch n := a.(type) {
	case *descriptor.FileDescriptorProto:
		if v := search(n.MessageType, symbolPath); v != nil {
			return v
		}
		if v := search(n.EnumType, symbolPath); v != nil {
			return v
		}
		if v := search(n.Service, symbolPath); v != nil {
			return v
		}
		if v := search(n.Extension, symbolPath); v != nil {
			return v
		}
		return nil
	case ASTNamedNode:
		if symbolPath == n.GetName() {
			return a
		}
		if CountElem(symbolPath) == 1 {
			return nil
		}
		msg, ok := a.(*descriptor.DescriptorProto)
		if !ok {
			return nil
		}
		symbolPath = TrimElem(symbolPath, 1)
		if v := search(msg.NestedType, symbolPath); v != nil {
			return v
		}
		return search(msg.EnumType, symbolPath)
	default:
		panic("unexpected type")
	}
}
search searches below the given AST node for an message, enum, service, or
extension with the given symbol path. Valid input types are:

ASTNamedNode
[]ASTNamedNode
*descriptor.FileDescriptorProto
package util
import (
	"reflect"
	"strings"
	"github.com/golang/protobuf/protoc-gen-go/descriptor"
)
type ASTNode interface{}
type ASTNamedNode interface {
	GetName() string
}