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 }