code.vegaprotocol.io/vega@v0.79.0/libs/jsonrpc/dispatcher.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package jsonrpc
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"sort"
    22  	"strings"
    23  
    24  	"go.uber.org/zap"
    25  )
    26  
    27  type InterceptorFunc func(ctx context.Context, request Request) *ErrorDetails
    28  
    29  // Dispatcher forward the request it gets in input to the associated method.
    30  // Despite being useful for simple use cases, it may not fit more advanced
    31  // use cases.
    32  type Dispatcher struct {
    33  	log *zap.Logger
    34  	// commands maps a method to a command.
    35  	commands map[string]Command
    36  	// interceptors holds the pre-checks to run before dispatching a request.
    37  	interceptors []InterceptorFunc
    38  }
    39  
    40  func (a *Dispatcher) DispatchRequest(ctx context.Context, request Request) *Response {
    41  	traceID := TraceIDFromContext(ctx)
    42  
    43  	if err := request.Check(); err != nil {
    44  		a.log.Info("invalid request",
    45  			zap.String("trace-id", traceID),
    46  			zap.Error(err))
    47  		return NewErrorResponse(request.ID, NewInvalidRequest(err))
    48  	}
    49  
    50  	for _, interceptor := range a.interceptors {
    51  		if errDetails := interceptor(ctx, request); errDetails != nil {
    52  			return NewErrorResponse(request.ID, errDetails)
    53  		}
    54  	}
    55  
    56  	command, ok := a.commands[request.Method]
    57  	if !ok {
    58  		a.log.Info("invalid method",
    59  			zap.String("trace-id", traceID),
    60  			zap.String("method", request.Method))
    61  		return NewErrorResponse(request.ID, NewMethodNotFound(request.Method))
    62  	}
    63  
    64  	result, errorDetails := command.Handle(ctx, request.Params)
    65  	if errorDetails != nil {
    66  		a.log.Info("method failed",
    67  			zap.String("trace-id", traceID),
    68  			zap.Any("error", errorDetails))
    69  		return NewErrorResponse(request.ID, errorDetails)
    70  	}
    71  	a.log.Info("method succeeded",
    72  		zap.String("trace-id", traceID))
    73  	return NewSuccessfulResponse(request.ID, result)
    74  }
    75  
    76  func (a *Dispatcher) RegisterMethod(method string, handler Command) {
    77  	if len(strings.Trim(method, " \t\r\n")) == 0 {
    78  		a.log.Panic("method cannot be empty")
    79  	}
    80  
    81  	if handler == nil {
    82  		a.log.Panic("handler cannot be nil")
    83  	}
    84  
    85  	if _, ok := a.commands[method]; ok {
    86  		a.log.Panic(fmt.Sprintf("method %q is already registered", method))
    87  	}
    88  
    89  	a.commands[method] = handler
    90  	a.log.Info("new JSON-RPC method registered", zap.String("method", method))
    91  }
    92  
    93  func (a *Dispatcher) RegisteredMethods() []string {
    94  	methods := make([]string, 0, len(a.commands))
    95  	for method := range a.commands {
    96  		methods = append(methods, method)
    97  	}
    98  	sort.Strings(methods)
    99  	return methods
   100  }
   101  
   102  func (a *Dispatcher) AddInterceptor(interceptor InterceptorFunc) {
   103  	a.interceptors = append(a.interceptors, interceptor)
   104  }
   105  
   106  func NewDispatcher(log *zap.Logger) *Dispatcher {
   107  	return &Dispatcher{
   108  		log:          log,
   109  		commands:     map[string]Command{},
   110  		interceptors: []InterceptorFunc{},
   111  	}
   112  }