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  }