RegisterCmd registers a new command that will automatically marshal to and from JSON-RPC with full type checking and positional parameter support. It also accepts usage flags which identify the circumstances under which the command can be used.

This package automatically registers all of the exported commands by default using this function, however it is also exported so callers can easily register custom types.

The type format is very strict since it needs to be able to automatically marshal to and from JSON-RPC 1.0. The following enumerates the requirements:

- The provided command must be a single pointer to a struct
- All fields must be exported
- The order of the positional parameters in the marshalled JSON will be in
  the same order as declared in the struct definition
- Struct embedding is not supported
- Struct fields may NOT be channels, functions, complex, or interface
- A field in the provided struct with a pointer is treated as optional
- Multiple indirections (i.e **int) are not supported
- Once the first optional field (pointer) is encountered, the remaining
  fields must also be optional fields (pointers) as required by positional
- A field that has a 'jsonrpcdefault' struct tag must be an optional field

NOTE: This function only needs to be able to examine the structure of the passed struct, so it does not need to be an actual instance. Therefore, it is recommended to simply pass a nil pointer cast to the appropriate type. For example, (*FooCmd)(nil).

RegisterCmd is referenced in 1 repository


func RegisterCmd(method string, cmd interface{}, flags UsageFlag) error {
	defer registerLock.Unlock()

	if _, ok := methodToConcreteType[method]; ok {
		str := fmt.Sprintf("method %q is already registered", method)
		return makeError(ErrDuplicateMethod, str)

	// Ensure that no unrecognized flag bits were specified.
	if ^(highestUsageFlagBit-1)&flags != 0 {
		str := fmt.Sprintf("invalid usage flags specified for method "+
			"%s: %v", method, flags)
		return makeError(ErrInvalidUsageFlags, str)

	rtp := reflect.TypeOf(cmd)
	if rtp.Kind() != reflect.Ptr {
		str := fmt.Sprintf("type must be *struct not '%s (%s)'", rtp,
		return makeError(ErrInvalidType, str)
	rt := rtp.Elem()
	if rt.Kind() != reflect.Struct {
		str := fmt.Sprintf("type must be *struct not '%s (*%s)'",
			rtp, rt.Kind())
		return makeError(ErrInvalidType, str)

	// Enumerate the struct fields to validate them and gather parameter
	// information.
	numFields := rt.NumField()
	numOptFields := 0
	defaults := make(map[int]reflect.Value)
	for i := 0; i < numFields; i++ {
		rtf := rt.Field(i)
		if rtf.Anonymous {
			str := fmt.Sprintf("embedded fields are not supported "+
				"(field name: %q)", rtf.Name)
			return makeError(ErrEmbeddedType, str)
		if rtf.PkgPath != "" {
			str := fmt.Sprintf("unexported fields are not supported "+
				"(field name: %q)", rtf.Name)
			return makeError(ErrUnexportedField, str)

		// Disallow types that can't be JSON encoded.  Also, determine
		// if the field is optional based on it being a pointer.
		var isOptional bool
		switch kind := rtf.Type.Kind(); kind {
		case reflect.Ptr:
			isOptional = true
			kind = rtf.Type.Elem().Kind()
			if !isAcceptableKind(kind) {
				str := fmt.Sprintf("unsupported field type "+
					"'%s (%s)' (field name %q)", rtf.Type,
					baseKindString(rtf.Type), rtf.Name)
				return makeError(ErrUnsupportedFieldType, str)

		// Count the optional fields and ensure all fields after the
		// first optional field are also optional.
		if isOptional {
		} else {
			if numOptFields > 0 {
				str := fmt.Sprintf("all fields after the first "+
					"optional field must also be optional "+
					"(field name %q)", rtf.Name)
				return makeError(ErrNonOptionalField, str)

		// Ensure the default value can be unsmarshalled into the type
		// and that defaults are only specified for optional fields.
		if tag := rtf.Tag.Get("jsonrpcdefault"); tag != "" {
			if !isOptional {
				str := fmt.Sprintf("required fields must not "+
					"have a default specified (field name "+
					"%q)", rtf.Name)
				return makeError(ErrNonOptionalDefault, str)

			rvf := reflect.New(rtf.Type.Elem())
			err := json.Unmarshal([]byte(tag), rvf.Interface())
			if err != nil {
				str := fmt.Sprintf("default value of %q is "+
					"the wrong type (field name %q)", tag,
				return makeError(ErrMismatchedDefault, str)
			defaults[i] = rvf

	// Update the registration maps.
	methodToConcreteType[method] = rtp
	methodToInfo[method] = methodInfo{
		maxParams:    numFields,
		numReqParams: numFields - numOptFields,
		numOptParams: numOptFields,
		defaults:     defaults,
		flags:        flags,
	concreteTypeToMethod[rtp] = method
	return nil