github.com/dashpay/godash@v0.0.0-20160726055534-e038a21e0e3d/btcjson/register.go (about)

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