github.com/clubpay/ronykit/kit@v0.14.4-0.20240515065620-d0dace45cbc7/desc/service.go (about) 1 package desc 2 3 import ( 4 "fmt" 5 "reflect" 6 7 "github.com/clubpay/ronykit/kit" 8 "github.com/clubpay/ronykit/kit/utils/reflector" 9 ) 10 11 // Service is the description of the kit.Service you are going to create. It then 12 // generates a kit.Service by calling Generate method. 13 type Service struct { 14 Name string 15 Version string 16 Description string 17 Encoding kit.Encoding 18 PossibleErrors []Error 19 Wrappers []kit.ServiceWrapper 20 Contracts []Contract 21 Handlers []kit.HandlerFunc 22 23 contractNames map[string]struct{} 24 } 25 26 var _ kit.ServiceDescriptor = (*Service)(nil) 27 28 func NewService(name string) *Service { 29 return &Service{ 30 Name: name, 31 contractNames: map[string]struct{}{}, 32 } 33 } 34 35 func (s *Service) SetEncoding(enc kit.Encoding) *Service { 36 s.Encoding = enc 37 38 return s 39 } 40 41 func (s *Service) SetVersion(v string) *Service { 42 s.Version = v 43 44 return s 45 } 46 47 func (s *Service) SetDescription(d string) *Service { 48 s.Description = d 49 50 return s 51 } 52 53 // AddHandler adds handlers to run before and/or after the contract's handlers 54 func (s *Service) AddHandler(h ...kit.HandlerFunc) *Service { 55 s.Handlers = append(s.Handlers, h...) 56 57 return s 58 } 59 60 // AddWrapper adds service wrappers to the Service description. 61 func (s *Service) AddWrapper(wrappers ...kit.ServiceWrapper) *Service { 62 s.Wrappers = append(s.Wrappers, wrappers...) 63 64 return s 65 } 66 67 // AddContract adds a contract to the service. 68 func (s *Service) AddContract(contracts ...*Contract) *Service { 69 for idx := range contracts { 70 if contracts[idx] == nil { 71 continue 72 } 73 74 // If the encoding is not set for contract, it takes the Service encoding as its encoding. 75 if contracts[idx].Encoding == kit.Undefined { 76 contracts[idx].Encoding = s.Encoding 77 } 78 79 s.Contracts = append(s.Contracts, *contracts[idx]) 80 } 81 82 return s 83 } 84 85 // AddError sets the possible errors for all the Contracts of this Service. 86 // Using this method is OPTIONAL, which mostly could be used by external tools such as 87 // Swagger or any other doc generator tools. 88 // NOTE: The auto-generated stub also use these errors to identifies if the response should be considered 89 // 90 // as error or successful. 91 func (s *Service) AddError(err kit.ErrorMessage) *Service { 92 s.PossibleErrors = append( 93 s.PossibleErrors, 94 Error{ 95 Code: err.GetCode(), 96 Item: err.GetItem(), 97 Message: err, 98 }, 99 ) 100 101 return s 102 } 103 104 // Generate generates the kit.Service 105 func (s *Service) Generate() kit.Service { 106 svc := &serviceImpl{ 107 name: s.Name, 108 h: s.Handlers, 109 } 110 111 var index int64 112 for _, c := range s.Contracts { 113 reflector.Register(c.Input) 114 reflector.Register(c.Output) 115 116 index++ 117 contractID := fmt.Sprintf("%s.%d", svc.name, index) 118 if c.Name != "" { 119 if _, ok := s.contractNames[c.Name]; ok { 120 panic(fmt.Sprintf("contract name %s already defined in service %s", c.Name, svc.name)) 121 } 122 contractID = fmt.Sprintf("%s.%s", svc.name, c.Name) 123 s.contractNames[c.Name] = struct{}{} 124 } 125 126 contracts := make([]kit.Contract, len(c.RouteSelectors)) 127 for idx, s := range c.RouteSelectors { 128 ci := (&contractImpl{id: contractID}). 129 addHandler(svc.h...). 130 addHandler(c.Handlers...). 131 setModifier(c.Modifiers...). 132 setInput(c.Input). 133 setOutput(c.Output). 134 setRouteSelector(s.Selector). 135 setMemberSelector(c.EdgeSelector). 136 setEncoding(c.Encoding) 137 138 contracts[idx] = kit.WrapContract(ci, c.Wrappers...) 139 } 140 141 svc.contracts = append(svc.contracts, contracts...) 142 } 143 144 return kit.WrapService(svc, s.Wrappers...) 145 } 146 147 // Stub returns the Stub, which describes the stub specification and 148 // could be used to auto-generate stub for this service. 149 func (s *Service) Stub(tags ...string) (*Stub, error) { 150 stub := newStub(tags...) 151 152 if err := s.dtoStub(stub); err != nil { 153 return nil, err 154 } 155 156 for _, c := range s.Contracts { 157 for _, rs := range c.RouteSelectors { 158 if rrs, ok := rs.Selector.(kit.RESTRouteSelector); ok { 159 if rrs.GetMethod() == "" || rrs.GetPath() == "" { 160 continue 161 } 162 s.restStub(stub, c, rs.Name, rrs) 163 } 164 165 if rrs, ok := rs.Selector.(kit.RPCRouteSelector); ok { 166 s.rpcStub(stub, c, rs.Name, rrs) 167 } 168 } 169 } 170 171 return stub, nil 172 } 173 174 func (s *Service) dtoStub(stub *Stub) error { 175 for _, c := range s.Contracts { 176 err := stub.addDTO(reflect.TypeOf(c.Input), false) 177 if err != nil { 178 return err 179 } 180 if c.Output != nil { 181 err = stub.addDTO(reflect.TypeOf(c.Output), false) 182 if err != nil { 183 return err 184 } 185 } 186 187 // detect error DTOs for service 188 for _, pe := range s.PossibleErrors { 189 err = stub.addDTO(reflect.TypeOf(pe.Message), true) 190 if err != nil { 191 return err 192 } 193 } 194 195 // detect error DTOs for contract 196 for _, pe := range c.PossibleErrors { 197 err = stub.addDTO(reflect.TypeOf(pe.Message), true) 198 if err != nil { 199 return err 200 } 201 } 202 } 203 204 return nil 205 } 206 207 func (s *Service) rpcStub( 208 stub *Stub, c Contract, routeName string, rrs kit.RPCRouteSelector, 209 ) { 210 m := RPCMethod{ 211 Name: routeName, 212 Predicate: rrs.GetPredicate(), 213 Encoding: getEncoding(rrs), 214 } 215 216 if dto, ok := stub.getDTO(reflect.TypeOf(c.Input)); ok { 217 m.Request = dto 218 } 219 if c.Output != nil { 220 if dto, ok := stub.getDTO(reflect.TypeOf(c.Output)); ok { 221 m.Response = dto 222 } 223 } 224 225 var possibleErrors []Error 226 possibleErrors = append(possibleErrors, s.PossibleErrors...) 227 possibleErrors = append(possibleErrors, c.PossibleErrors...) 228 for _, e := range possibleErrors { 229 if dto, ok := stub.getDTO(reflect.TypeOf(e.Message)); ok { 230 m.addPossibleError( 231 ErrorDTO{ 232 Code: e.Code, 233 Item: e.Item, 234 DTO: dto, 235 }, 236 ) 237 } 238 } 239 } 240 241 func (s *Service) restStub( 242 stub *Stub, c Contract, routeName string, rrs kit.RESTRouteSelector, 243 ) { 244 m := RESTMethod{ 245 Name: routeName, 246 Method: rrs.GetMethod(), 247 Path: rrs.GetPath(), 248 Encoding: getEncoding(rrs), 249 } 250 251 if dto, ok := stub.getDTO(reflect.TypeOf(c.Input)); ok { 252 m.Request = dto 253 } 254 if c.Output != nil { 255 if dto, ok := stub.getDTO(reflect.TypeOf(c.Output)); ok { 256 m.Response = dto 257 } 258 } 259 260 var possibleErrors []Error 261 possibleErrors = append(possibleErrors, s.PossibleErrors...) 262 possibleErrors = append(possibleErrors, c.PossibleErrors...) 263 for _, e := range possibleErrors { 264 if dto, ok := stub.getDTO(reflect.TypeOf(e.Message)); ok { 265 m.addPossibleError( 266 ErrorDTO{ 267 Code: e.Code, 268 Item: e.Item, 269 DTO: dto, 270 }, 271 ) 272 } 273 } 274 275 stub.RESTs = append(stub.RESTs, m) 276 } 277 278 func getEncoding(rrs kit.RouteSelector) string { 279 switch rrs.GetEncoding().Tag() { 280 case kit.JSON.Tag(): 281 return "kit.JSON" 282 case kit.Proto.Tag(): 283 return "kit.Proto" 284 case kit.MSG.Tag(): 285 return "kit.MSG" 286 case "": 287 return "kit.JSON" 288 default: 289 return fmt.Sprintf("kit.CustomEncoding(%q)", rrs.GetEncoding().Tag()) 290 } 291 } 292 293 // serviceImpl is a simple implementation of kit.Service interface. 294 type serviceImpl struct { 295 name string 296 h []kit.HandlerFunc 297 contracts []kit.Contract 298 } 299 300 var _ kit.Service = (*serviceImpl)(nil) 301 302 func (s *serviceImpl) Name() string { 303 return s.name 304 } 305 306 func (s *serviceImpl) Contracts() []kit.Contract { 307 return s.contracts 308 }