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 }