github.com/clubpay/ronykit/kit@v0.14.4-0.20240515065620-d0dace45cbc7/desc/stub.go (about) 1 package desc 2 3 import ( 4 "reflect" 5 "strings" 6 7 "github.com/clubpay/ronykit/kit" 8 "github.com/clubpay/ronykit/kit/errors" 9 ) 10 11 // DTO represents the description of Data Transfer Object of the Stub 12 type DTO struct { 13 // Comments could be used by generators to print some useful information about this DTO 14 Comments []string 15 // Name is the name of this DTO struct 16 Name string 17 Type string 18 RType reflect.Type 19 IsErr bool 20 Fields []DTOField 21 } 22 23 func (dto DTO) CodeField() string { 24 var fn string 25 for _, f := range dto.Fields { 26 x := strings.ToLower(f.Name) 27 if f.Type != "int" { 28 continue 29 } 30 if x == "code" { 31 return f.Name 32 } 33 if strings.HasPrefix(f.Name, "code") { 34 fn = f.Name 35 } 36 } 37 38 return fn 39 } 40 41 func (dto DTO) ItemField() string { 42 var fn string 43 for _, f := range dto.Fields { 44 x := strings.ToLower(f.Name) 45 if f.RType.Kind() != reflect.String { 46 continue 47 } 48 if x == "item" || x == "items" { 49 return f.Name 50 } 51 if strings.HasPrefix(f.Name, "item") { 52 fn = f.Name 53 } 54 } 55 56 return fn 57 } 58 59 // DTOField represents description of a field of the DTO 60 type DTOField struct { 61 // Name of this field 62 Name string 63 // RType is the reflected type of this field for handling more complex cases 64 RType reflect.Type 65 // Type of this field and if this type is slice or map then it might have one or two 66 // subtypes 67 Type string 68 SubType1 string 69 SubType2 string 70 // If this field was an embedded field means fields are coming from an embedded DTO 71 // If Embedded is TRUE then for sure IsDTO must be TRUE 72 Embedded bool 73 IsDTO bool 74 IsPtr bool 75 Tags []DTOFieldTag 76 } 77 78 func (x DTOField) GetTag(name string) string { 79 for _, t := range x.Tags { 80 if t.Name == name { 81 return t.Value 82 } 83 } 84 85 return "" 86 } 87 88 // DTOFieldTag represents description of a tag of the DTOField 89 type DTOFieldTag struct { 90 Name string 91 Value string 92 } 93 94 // ErrorDTO represents description of a Data Object Transfer which is used to 95 // show an error case. 96 type ErrorDTO struct { 97 Code int 98 Item string 99 DTO DTO 100 } 101 102 // RESTMethod represents description of a Contract with kit.RESTRouteSelector. 103 type RESTMethod struct { 104 Name string 105 Method string 106 Path string 107 Encoding string 108 Request DTO 109 Response DTO 110 PossibleErrors []ErrorDTO 111 } 112 113 func (rm *RESTMethod) addPossibleError(dto ErrorDTO) { 114 for _, e := range rm.PossibleErrors { 115 if e.Code == dto.Code { 116 return 117 } 118 } 119 rm.PossibleErrors = append(rm.PossibleErrors, dto) 120 } 121 122 // RPCMethod represents description of a Contract with kit.RPCRouteSelector 123 type RPCMethod struct { 124 Name string 125 Predicate string 126 Request DTO 127 Response DTO 128 PossibleErrors []ErrorDTO 129 Encoding string 130 kit.IncomingRPCContainer 131 kit.OutgoingRPCContainer 132 } 133 134 func (rm *RPCMethod) addPossibleError(dto ErrorDTO) { 135 for _, e := range rm.PossibleErrors { 136 if e.Code == dto.Code { 137 return 138 } 139 } 140 rm.PossibleErrors = append(rm.PossibleErrors, dto) 141 } 142 143 // Stub represents description of a stub of the service described by Service descriptor. 144 type Stub struct { 145 tags []string 146 DTOs map[string]DTO 147 RESTs []RESTMethod 148 RPCs []RPCMethod 149 } 150 151 func newStub(tags ...string) *Stub { 152 return &Stub{ 153 tags: tags, 154 DTOs: map[string]DTO{}, 155 } 156 } 157 158 //nolint:gocognit 159 func (d *Stub) addDTO(mTyp reflect.Type, isErr bool) error { 160 if mTyp.Kind() == reflect.Ptr { 161 mTyp = mTyp.Elem() 162 } 163 164 dto := DTO{ 165 Name: mTyp.Name(), 166 RType: mTyp, 167 Type: typ("", mTyp), 168 IsErr: isErr, 169 } 170 171 if mTyp == reflect.TypeOf(kit.RawMessage{}) { 172 return nil 173 } 174 175 // We don't support non-struct DTOs 176 if mTyp.Kind() != reflect.Struct { 177 return errUnsupportedType(mTyp.Kind().String()) 178 } 179 180 // if DTO is already parsed just return 181 if _, ok := d.DTOs[dto.Name]; ok { 182 return nil 183 } 184 185 d.DTOs[dto.Name] = dto 186 187 for i := 0; i < mTyp.NumField(); i++ { 188 ft := mTyp.Field(i) 189 fe := extractElem(ft) 190 dtoF := DTOField{ 191 Name: ft.Name, 192 RType: ft.Type, 193 IsPtr: ft.Type.Kind() == reflect.Ptr, 194 Type: typ("", ft.Type), 195 Embedded: ft.Anonymous, 196 } 197 198 for _, t := range d.tags { 199 v, ok := ft.Tag.Lookup(t) 200 if ok { 201 dtoF.Tags = append( 202 dtoF.Tags, 203 DTOFieldTag{ 204 Name: t, 205 Value: v, 206 }, 207 ) 208 } 209 } 210 211 switch fe.Kind() { 212 case reflect.Struct: 213 dtoF.IsDTO = true 214 err := d.addDTO(fe, false) 215 if err != nil { 216 return err 217 } 218 case reflect.Map: 219 dtoF.SubType1 = typ("", fe.Key()) 220 dtoF.SubType2 = typ("", fe.Elem()) 221 if fe.Key().Kind() == reflect.Struct { 222 err := d.addDTO(fe.Key(), false) 223 if err != nil { 224 return err 225 } 226 } 227 if fe.Elem().Kind() == reflect.Struct { 228 err := d.addDTO(fe.Elem(), false) 229 if err != nil { 230 return err 231 } 232 } 233 case reflect.Slice, reflect.Array: 234 dtoF.SubType1 = typ("", fe.Elem()) 235 if fe.Elem().Kind() == reflect.Struct { 236 err := d.addDTO(fe.Elem(), false) 237 if err != nil { 238 return err 239 } 240 } 241 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 242 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 243 reflect.Float32, reflect.Float64, reflect.Bool, 244 reflect.String, reflect.Uintptr, reflect.Ptr: 245 default: 246 continue 247 } 248 249 dto.Fields = append(dto.Fields, dtoF) 250 } 251 252 d.DTOs[dto.Name] = dto 253 254 return nil 255 } 256 257 func extractElem(in reflect.StructField) reflect.Type { 258 t := in.Type 259 k := t.Kind() 260 261 Loop: 262 if k == reflect.Ptr { 263 return t.Elem() 264 } 265 if k == reflect.Slice { 266 switch t.Elem().Kind() { 267 default: 268 case reflect.Ptr: 269 t = t.Elem() 270 k = t.Kind() 271 272 goto Loop 273 } 274 275 return t.Elem() 276 } 277 278 return t 279 } 280 281 func (d *Stub) getDTO(mTyp reflect.Type) (DTO, bool) { 282 if mTyp.Kind() == reflect.Ptr { 283 mTyp = mTyp.Elem() 284 } 285 286 dto, ok := d.DTOs[mTyp.Name()] 287 288 return dto, ok 289 } 290 291 func (d Stub) Tags() []string { 292 return d.tags 293 } 294 295 var errUnsupportedType = errors.NewG("non-struct types as DTO : %s") 296 297 func MergeStubs(stubs ...*Stub) *Stub { 298 stub := newStub() 299 300 for _, s := range stubs { 301 for _, dto := range s.DTOs { 302 stub.DTOs[dto.Name] = dto 303 } 304 305 stub.RESTs = append(stub.RESTs, s.RESTs...) 306 stub.RPCs = append(stub.RPCs, s.RPCs...) 307 stub.tags = appendUnique(stub.tags, s.tags...) 308 } 309 310 return stub 311 } 312 313 func appendUnique(slice []string, s ...string) []string { 314 for _, z := range s { 315 found := false 316 for _, x := range slice { 317 if z == x { 318 found = true 319 320 break 321 } 322 } 323 if !found { 324 slice = append(slice, z) 325 } 326 } 327 328 return slice 329 }