register.go raw

   1  package btcjson
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  	"reflect"
   7  	"sort"
   8  	"strconv"
   9  	"strings"
  10  	"sync"
  11  )
  12  
  13  // UsageFlag define flags that specify additional properties about the circumstances under which a command can be used.
  14  type UsageFlag uint32
  15  
  16  // MethodInfo keeps track of information about each registered method such as the parameter information.
  17  type MethodInfo struct {
  18  	MaxParams    int
  19  	NumReqParams int
  20  	numOptParams int
  21  	defaults     map[int]reflect.Value
  22  	flags        UsageFlag
  23  	usage        string
  24  }
  25  
  26  const (
  27  	// UFWalletOnly indicates that the command can only be used with an RPC server that supports wallet commands.
  28  	UFWalletOnly UsageFlag = 1 << iota
  29  	// UFWebsocketOnly indicates that the command can only be used when communicating with an RPC server over
  30  	// websockets. This typically applies to notifications and notification registration functions since neiher makes
  31  	// since when using a single-shot HTTP-POST request.
  32  	UFWebsocketOnly
  33  	// UFNotification indicates that the command is actually a notification. This means when it is marshalled, the ID
  34  	// must be nil.
  35  	UFNotification
  36  	// highestUsageFlagBit is the maximum usage flag bit and is used in the stringer and tests to ensure all of the
  37  	// above constants have been tested.
  38  	highestUsageFlagBit
  39  )
  40  
  41  var (
  42  	concreteTypeToMethod = make(map[reflect.Type]string)
  43  	methodToConcreteType = make(map[string]reflect.Type)
  44  	methodToInfo         = make(map[string]MethodInfo)
  45  	// These fields are used to map the registered types to method names.
  46  	registerLock sync.RWMutex
  47  	// Map of UsageFlag values back to their constant names for pretty printing.
  48  	usageFlagStrings = map[UsageFlag]string{
  49  		UFWalletOnly:    "UFWalletOnly",
  50  		UFWebsocketOnly: "UFWebsocketOnly",
  51  		UFNotification:  "UFNotification",
  52  	}
  53  )
  54  
  55  // String returns the UsageFlag in human-readable form.
  56  func (fl UsageFlag) String() string {
  57  	// No flags are set.
  58  	if fl == 0 {
  59  		return "0x0"
  60  	}
  61  	// Add individual bit flags.
  62  	s := ""
  63  	for flag := UFWalletOnly; flag < highestUsageFlagBit; flag <<= 1 {
  64  		if fl&flag == flag {
  65  			s += usageFlagStrings[flag] + "|"
  66  			fl -= flag
  67  		}
  68  	}
  69  	// Add remaining value as raw hex.
  70  	s = strings.TrimRight(s, "|")
  71  	if fl != 0 {
  72  		s += "|0x" + strconv.FormatUint(uint64(fl), 16)
  73  	}
  74  	s = strings.TrimLeft(s, "|")
  75  	return s
  76  }
  77  
  78  // MustRegisterCmd performs the same function as RegisterCmd except it panics if there is an error. This should only be
  79  // called from package init functions.
  80  func MustRegisterCmd(method string, cmd interface{}, flags UsageFlag) {
  81  	if e := RegisterCmd(method, cmd, flags); E.Chk(e) {
  82  		panic(fmt.Sprintf("failed to register type %q: %v\n", method,
  83  			e,
  84  		),
  85  		)
  86  	}
  87  	RegisteredCommands[method] = cmd
  88  }
  89  
  90  var RegisteredCommands = make(map[string]interface{})
  91  
  92  /*
  93  RegisterCmd registers a new command that will automatically marshal to and from JSON-RPC with full type checking and
  94  positional parameter support. It also accepts usage flags which identify the circumstances under which the command can
  95  be used.
  96  
  97  This package automatically registers all of the exported commands by default using this function, however it is also
  98  exported so callers can easily register custom types.
  99  
 100  The type format is very strict since it needs to be able to automatically marshal to and from JSON-RPC 1.0. The
 101  following enumerates the requirements:
 102  
 103    - The provided command must be a single pointer to a struct
 104  
 105    - All fields must be exported
 106  
 107    - The order of the positional parameters in the marshalled JSON will be in the same order as declared in the struct
 108    definition
 109  
 110    - Struct embedding is not supported
 111  
 112    - Struct fields may NOT be channels, functions, complex, or interface
 113  
 114    - A field in the provided struct with a pointer is treated as optional
 115  
 116    - Multiple indirections (i.e **int) are not supported
 117  
 118    - Once the first optional field (pointer) is encountered, the remaining fields must also be optional fields (pointers)
 119    as required by positional netparams
 120  
 121    - A field that has a 'jsonrpcdefault' struct tag must be an optional field (pointer)
 122  
 123  NOTE: This function only needs to be able to examine the structure of the passed struct, so it does not need to be an
 124  actual instance. Therefore, it is recommended to simply pass a nil pointer cast to the appropriate type. For example,
 125  (*FooCmd)(nil).
 126  */
 127  func RegisterCmd(method string, cmd interface{}, flags UsageFlag) (e error) {
 128  	registerLock.Lock()
 129  	defer registerLock.Unlock()
 130  	if _, ok := methodToConcreteType[method]; ok {
 131  		str := fmt.Sprintf("method %q is already registered", method)
 132  		return makeError(ErrDuplicateMethod, str)
 133  	}
 134  	// Ensure that no unrecognized flag bits were specified.
 135  	if ^(highestUsageFlagBit-1)&flags != 0 {
 136  		str := fmt.Sprintf("invalid usage flags specified for method "+
 137  			"%s: %v", method, flags,
 138  		)
 139  		return makeError(ErrInvalidUsageFlags, str)
 140  	}
 141  	rtp := reflect.TypeOf(cmd)
 142  	if rtp.Kind() != reflect.Ptr {
 143  		str := fmt.Sprintf("type must be *struct not '%s (%s)'", rtp,
 144  			rtp.Kind(),
 145  		)
 146  		return makeError(ErrInvalidType, str)
 147  	}
 148  	rt := rtp.Elem()
 149  	if rt.Kind() != reflect.Struct {
 150  		str := fmt.Sprintf("type must be *struct not '%s (*%s)'",
 151  			rtp, rt.Kind(),
 152  		)
 153  		return makeError(ErrInvalidType, str)
 154  	}
 155  	// Enumerate the struct fields to validate them and gather parameter information.
 156  	numFields := rt.NumField()
 157  	numOptFields := 0
 158  	defaults := make(map[int]reflect.Value)
 159  	for i := 0; i < numFields; i++ {
 160  		rtf := rt.Field(i)
 161  		if rtf.Anonymous {
 162  			str := fmt.Sprintf("embedded fields are not supported "+
 163  				"(field name: %q)", rtf.Name,
 164  			)
 165  			return makeError(ErrEmbeddedType, str)
 166  		}
 167  		if rtf.PkgPath != "" {
 168  			str := fmt.Sprintf("unexported fields are not supported "+
 169  				"(field name: %q)", rtf.Name,
 170  			)
 171  			return makeError(ErrUnexportedField, str)
 172  		}
 173  		// Disallow types that can't be JSON encoded.  Also, determine if the field is optional based on it being a pointer.
 174  		var isOptional bool
 175  		switch kind := rtf.Type.Kind(); kind {
 176  		case reflect.Ptr:
 177  			isOptional = true
 178  			kind = rtf.Type.Elem().Kind()
 179  			fallthrough
 180  		default:
 181  			if !isAcceptableKind(kind) {
 182  				str := fmt.Sprintf("unsupported field type "+
 183  					"'%s (%s)' (field name %q)", rtf.Type,
 184  					baseKindString(rtf.Type), rtf.Name,
 185  				)
 186  				return makeError(ErrUnsupportedFieldType, str)
 187  			}
 188  		}
 189  		// Count the optional fields and ensure all fields after the first optional field are also optional.
 190  		if isOptional {
 191  			numOptFields++
 192  		} else {
 193  			if numOptFields > 0 {
 194  				str := fmt.Sprintf("all fields after the first "+
 195  					"optional field must also be optional "+
 196  					"(field name %q)", rtf.Name,
 197  				)
 198  				return makeError(ErrNonOptionalField, str)
 199  			}
 200  		}
 201  		// Ensure the default value can be unmarshalled into the type and that defaults are only specified for optional
 202  		// fields.
 203  		if tag := rtf.Tag.Get("jsonrpcdefault"); tag != "" {
 204  			if !isOptional {
 205  				str := fmt.Sprintf("required fields must not "+
 206  					"have a default specified (field name "+
 207  					"%q)", rtf.Name,
 208  				)
 209  				return makeError(ErrNonOptionalDefault, str)
 210  			}
 211  			rvf := reflect.New(rtf.Type.Elem())
 212  			e := json.Unmarshal([]byte(tag), rvf.Interface())
 213  			if e != nil {
 214  				E.Ln(e)
 215  				str := fmt.Sprintf("default value of %q is "+
 216  					"the wrong type (field name %q)", tag,
 217  					rtf.Name,
 218  				)
 219  				return makeError(ErrMismatchedDefault, str)
 220  			}
 221  			defaults[i] = rvf
 222  		}
 223  	}
 224  	// Update the registration maps.
 225  	methodToConcreteType[method] = rtp
 226  	methodToInfo[method] = MethodInfo{
 227  		MaxParams:    numFields,
 228  		NumReqParams: numFields - numOptFields,
 229  		numOptParams: numOptFields,
 230  		defaults:     defaults,
 231  		flags:        flags,
 232  	}
 233  	concreteTypeToMethod[rtp] = method
 234  	return nil
 235  }
 236  
 237  // RegisteredCmdMethods returns a sorted list of methods for all registered commands.
 238  func RegisteredCmdMethods() []string {
 239  	registerLock.Lock()
 240  	defer registerLock.Unlock()
 241  	methods := make([]string, 0, len(methodToInfo))
 242  	for k := range methodToInfo {
 243  		methods = append(methods, k)
 244  	}
 245  	sort.Strings(methods)
 246  	return methods
 247  }
 248  
 249  // baseKindString returns the base kind for a given reflect.Type after indirecting through all pointers.
 250  func baseKindString(rt reflect.Type) string {
 251  	numIndirects := 0
 252  	for rt.Kind() == reflect.Ptr {
 253  		numIndirects++
 254  		rt = rt.Elem()
 255  	}
 256  	return fmt.Sprintf("%s%s", strings.Repeat("*", numIndirects), rt.Kind())
 257  }
 258  
 259  // isAcceptableKind returns whether or not the passed field type is a supported type. It is called after the first
 260  // pointer indirection, so further pointers are not supported.
 261  func isAcceptableKind(kind reflect.Kind) bool {
 262  	switch kind {
 263  	case reflect.Chan:
 264  		fallthrough
 265  	case reflect.Complex64:
 266  		fallthrough
 267  	case reflect.Complex128:
 268  		fallthrough
 269  	case reflect.Func:
 270  		fallthrough
 271  	case reflect.Ptr:
 272  		fallthrough
 273  	case reflect.Interface:
 274  		return false
 275  	}
 276  	return true
 277  }
 278