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 }