github.com/unionj-cloud/go-doudou/v2@v2.3.5/toolkit/protobuf/v3/message.go (about)

     1  package v3
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"github.com/iancoleman/strcase"
     7  	"github.com/unionj-cloud/go-doudou/v2/toolkit/astutils"
     8  	"github.com/unionj-cloud/go-doudou/v2/toolkit/sliceutils"
     9  	"github.com/unionj-cloud/go-doudou/v2/toolkit/stringutils"
    10  	"reflect"
    11  	"regexp"
    12  	"strings"
    13  	"unicode"
    14  )
    15  
    16  var _ ProtobufType = (*Enum)(nil)
    17  var _ ProtobufType = (*Message)(nil)
    18  
    19  type ProtobufType interface {
    20  	GetName() string
    21  	String() string
    22  	Inner() bool
    23  }
    24  
    25  var MessageStore = make(map[string]Message)
    26  
    27  var EnumStore = make(map[string]Enum)
    28  
    29  var ImportStore = make(map[string]struct{})
    30  
    31  var MessageNames []string
    32  
    33  type EnumField struct {
    34  	Name   string
    35  	Number int
    36  }
    37  
    38  func (receiver ProtoGenerator) newEnumField(field string, index int) EnumField {
    39  	return EnumField{
    40  		Name:   strings.ToUpper(strcase.ToSnake(field)),
    41  		Number: index,
    42  	}
    43  }
    44  
    45  type Enum struct {
    46  	Name   string
    47  	Fields []EnumField
    48  }
    49  
    50  func (e Enum) Inner() bool {
    51  	return false
    52  }
    53  
    54  func (e Enum) String() string {
    55  	return e.Name
    56  }
    57  
    58  func (e Enum) GetName() string {
    59  	return e.Name
    60  }
    61  
    62  func (receiver ProtoGenerator) NewEnum(enumMeta astutils.EnumMeta) Enum {
    63  	var fields []EnumField
    64  	for i, field := range enumMeta.Values {
    65  		fields = append(fields, receiver.newEnumField(field, i))
    66  	}
    67  	return Enum{
    68  		Name:   strcase.ToCamel(enumMeta.Name),
    69  		Fields: fields,
    70  	}
    71  }
    72  
    73  // Message represents protobuf message definition
    74  type Message struct {
    75  	Name       string
    76  	Fields     []Field
    77  	Comments   []string
    78  	IsInner    bool
    79  	IsScalar   bool
    80  	IsMap      bool
    81  	IsRepeated bool
    82  	IsTopLevel bool
    83  	// IsImported denotes the message will be imported from third-party, such as from google/protobuf
    84  	IsImported bool
    85  }
    86  
    87  func (m Message) Inner() bool {
    88  	return m.IsInner
    89  }
    90  
    91  func (m Message) GetName() string {
    92  	return m.Name
    93  }
    94  
    95  func (m Message) String() string {
    96  	switch {
    97  	case reflect.DeepEqual(m, Any):
    98  		return "anypb.Any"
    99  	case reflect.DeepEqual(m, Empty):
   100  		return "emptypb.Empty"
   101  	default:
   102  		return m.Name
   103  	}
   104  }
   105  
   106  // NewMessage returns message instance from astutils.StructMeta
   107  func (receiver ProtoGenerator) NewMessage(structmeta astutils.StructMeta) Message {
   108  	var fields []Field
   109  	for i, field := range structmeta.Fields {
   110  		fields = append(fields, receiver.newField(field, i+1))
   111  	}
   112  	return Message{
   113  		Name:       strcase.ToCamel(structmeta.Name),
   114  		Fields:     fields,
   115  		Comments:   structmeta.Comments,
   116  		IsTopLevel: true,
   117  	}
   118  }
   119  
   120  // Field represents protobuf message field definition
   121  type Field struct {
   122  	Name     string
   123  	Type     ProtobufType
   124  	Number   int
   125  	Comments []string
   126  	JsonName string
   127  }
   128  
   129  func (receiver ProtoGenerator) newField(field astutils.FieldMeta, index int) Field {
   130  	t := receiver.MessageOf(field.Type)
   131  	if t.Inner() {
   132  		message := t.(Message)
   133  		message.Name = strcase.ToCamel(field.Name)
   134  		t = message
   135  	}
   136  	fieldName := receiver.fieldNamingFunc(field.Name)
   137  	return Field{
   138  		Name:     fieldName,
   139  		Type:     t,
   140  		Number:   index,
   141  		Comments: field.Comments,
   142  		JsonName: fieldName,
   143  	}
   144  }
   145  
   146  var (
   147  	Double = Message{
   148  		Name:     "double",
   149  		IsScalar: true,
   150  	}
   151  	Float = Message{
   152  		Name:     "float",
   153  		IsScalar: true,
   154  	}
   155  	Int32 = Message{
   156  		Name:     "int32",
   157  		IsScalar: true,
   158  	}
   159  	Int64 = Message{
   160  		Name:     "int64",
   161  		IsScalar: true,
   162  	}
   163  	Uint32 = Message{
   164  		Name:     "uint32",
   165  		IsScalar: true,
   166  	}
   167  	Uint64 = Message{
   168  		Name:     "uint64",
   169  		IsScalar: true,
   170  	}
   171  	Bool = Message{
   172  		Name:     "bool",
   173  		IsScalar: true,
   174  	}
   175  	String = Message{
   176  		Name:     "string",
   177  		IsScalar: true,
   178  	}
   179  	Bytes = Message{
   180  		Name:     "bytes",
   181  		IsScalar: true,
   182  	}
   183  	Any = Message{
   184  		Name:       "google.protobuf.Any",
   185  		IsTopLevel: true,
   186  		IsImported: true,
   187  	}
   188  	Empty = Message{
   189  		Name:       "google.protobuf.Empty",
   190  		IsTopLevel: true,
   191  		IsImported: true,
   192  	}
   193  	Time = Message{
   194  		Name:     "google.protobuf.Timestamp",
   195  		IsScalar: true,
   196  	}
   197  )
   198  
   199  func (receiver ProtoGenerator) MessageOf(ft string) ProtobufType {
   200  	if astutils.IsVarargs(ft) {
   201  		ft = astutils.ToSlice(ft)
   202  	}
   203  	ft = strings.TrimLeft(ft, "*")
   204  	switch ft {
   205  	case "int", "int8", "int16", "int32", "byte", "rune", "complex64", "complex128":
   206  		return Int32
   207  	case "uint", "uint8", "uint16", "uint32":
   208  		return Uint32
   209  	case "int64":
   210  		return Int64
   211  	case "uint64", "uintptr":
   212  		return Uint64
   213  	case "bool":
   214  		return Bool
   215  	case "string", "error", "[]rune", "decimal.Decimal":
   216  		return String
   217  	case "[]byte", "v3.FileModel", "os.File":
   218  		return Bytes
   219  	case "float32":
   220  		return Float
   221  	case "float64":
   222  		return Double
   223  	case "time.Time", "gorm.DeletedAt", "customtypes.Time":
   224  		//ImportStore["google/protobuf/timestamp.proto"] = struct{}{}
   225  		//return Time
   226  		return String
   227  	default:
   228  		return receiver.handleDefaultCase(ft)
   229  	}
   230  }
   231  
   232  var anonystructre *regexp.Regexp
   233  
   234  func init() {
   235  	anonystructre = regexp.MustCompile(`anonystruct«(.*)»`)
   236  }
   237  
   238  func (receiver ProtoGenerator) handleDefaultCase(ft string) ProtobufType {
   239  	if strings.HasPrefix(ft, "map[") {
   240  		elem := ft[strings.Index(ft, "]")+1:]
   241  		key := ft[4:strings.Index(ft, "]")]
   242  		keyMessage := receiver.MessageOf(key)
   243  		if reflect.DeepEqual(keyMessage, Float) || reflect.DeepEqual(keyMessage, Double) || reflect.DeepEqual(keyMessage, Bytes) {
   244  			panic("floating point types and bytes cannot be key_type of maps, please refer to https://developers.google.com/protocol-buffers/docs/proto3#maps")
   245  		}
   246  		elemMessage := receiver.MessageOf(elem)
   247  		if strings.HasPrefix(elemMessage.GetName(), "map<") {
   248  			panic("the value_type cannot be another map, please refer to https://developers.google.com/protocol-buffers/docs/proto3#maps")
   249  		}
   250  		return Message{
   251  			Name:  fmt.Sprintf("map<%s, %s>", keyMessage.GetName(), elemMessage.GetName()),
   252  			IsMap: true,
   253  		}
   254  	}
   255  	if strings.HasPrefix(ft, "[") {
   256  		elem := ft[strings.Index(ft, "]")+1:]
   257  		elemMessage := receiver.MessageOf(elem)
   258  		if strings.HasPrefix(elemMessage.GetName(), "map<") {
   259  			panic("map fields cannot be repeated, please refer to https://developers.google.com/protocol-buffers/docs/proto3#maps")
   260  		}
   261  		messageName := elemMessage.GetName()
   262  		if strings.Contains(elemMessage.GetName(), "repeated ") {
   263  			messageName = messageName[strings.LastIndex(messageName, ".")+1:]
   264  			messageName = "Nested" + strcase.ToCamel(messageName)
   265  			fieldName := receiver.fieldNamingFunc(messageName)
   266  			MessageStore[messageName] = Message{
   267  				Name: messageName,
   268  				Fields: []Field{
   269  					{
   270  						Name:     fieldName,
   271  						Type:     elemMessage,
   272  						Number:   1,
   273  						JsonName: fieldName,
   274  					},
   275  				},
   276  				IsInner: true,
   277  			}
   278  		}
   279  		return Message{
   280  			Name:       fmt.Sprintf("repeated %s", messageName),
   281  			IsRepeated: true,
   282  		}
   283  	}
   284  	if anonystructre.MatchString(ft) {
   285  		result := anonystructre.FindStringSubmatch(ft)
   286  		var structmeta astutils.StructMeta
   287  		json.Unmarshal([]byte(result[1]), &structmeta)
   288  		message := receiver.NewMessage(structmeta)
   289  		message.IsInner = true
   290  		message.IsTopLevel = false
   291  		return message
   292  	}
   293  	var title string
   294  	if !strings.Contains(ft, ".") {
   295  		title = ft
   296  	}
   297  	if stringutils.IsEmpty(title) {
   298  		title = ft[strings.LastIndex(ft, ".")+1:]
   299  	}
   300  	if stringutils.IsNotEmpty(title) {
   301  		if unicode.IsUpper(rune(title[0])) {
   302  			if sliceutils.StringContains(MessageNames, title) {
   303  				return Message{
   304  					Name:       strcase.ToCamel(title),
   305  					IsTopLevel: true,
   306  				}
   307  			}
   308  		}
   309  		if e, ok := EnumStore[title]; ok {
   310  			return e
   311  		}
   312  	}
   313  	ImportStore["google/protobuf/any.proto"] = struct{}{}
   314  	return Any
   315  }