github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/formatter/reflect.go (about)

     1  // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
     2  //go:build go1.19
     3  
     4  package formatter
     5  
     6  import (
     7  	"encoding/json"
     8  	"reflect"
     9  	"unicode"
    10  
    11  	"github.com/pkg/errors"
    12  )
    13  
    14  // MarshalJSON marshals x into json
    15  // It differs a bit from encoding/json MarshalJSON function for formatter
    16  func MarshalJSON(x any) ([]byte, error) {
    17  	m, err := marshalMap(x)
    18  	if err != nil {
    19  		return nil, err
    20  	}
    21  	return json.Marshal(m)
    22  }
    23  
    24  // marshalMap marshals x to map[string]any
    25  func marshalMap(x any) (map[string]any, error) {
    26  	val := reflect.ValueOf(x)
    27  	if val.Kind() != reflect.Ptr {
    28  		return nil, errors.Errorf("expected a pointer to a struct, got %v", val.Kind())
    29  	}
    30  	if val.IsNil() {
    31  		return nil, errors.Errorf("expected a pointer to a struct, got nil pointer")
    32  	}
    33  	valElem := val.Elem()
    34  	if valElem.Kind() != reflect.Struct {
    35  		return nil, errors.Errorf("expected a pointer to a struct, got a pointer to %v", valElem.Kind())
    36  	}
    37  	typ := val.Type()
    38  	m := make(map[string]any)
    39  	for i := 0; i < val.NumMethod(); i++ {
    40  		k, v, err := marshalForMethod(typ.Method(i), val.Method(i))
    41  		if err != nil {
    42  			return nil, err
    43  		}
    44  		if k != "" {
    45  			m[k] = v
    46  		}
    47  	}
    48  	return m, nil
    49  }
    50  
    51  var unmarshallableNames = map[string]struct{}{"FullHeader": {}}
    52  
    53  // marshalForMethod returns the map key and the map value for marshalling the method.
    54  // It returns ("", nil, nil) for valid but non-marshallable parameter. (e.g. "unexportedFunc()")
    55  func marshalForMethod(typ reflect.Method, val reflect.Value) (string, any, error) {
    56  	if val.Kind() != reflect.Func {
    57  		return "", nil, errors.Errorf("expected func, got %v", val.Kind())
    58  	}
    59  	name, numIn, numOut := typ.Name, val.Type().NumIn(), val.Type().NumOut()
    60  	_, blackListed := unmarshallableNames[name]
    61  	// FIXME: In text/template, (numOut == 2) is marshallable,
    62  	//        if the type of the second param is error.
    63  	marshallable := unicode.IsUpper(rune(name[0])) && !blackListed &&
    64  		numIn == 0 && numOut == 1
    65  	if !marshallable {
    66  		return "", nil, nil
    67  	}
    68  	result := val.Call(make([]reflect.Value, numIn))
    69  	intf := result[0].Interface()
    70  	return name, intf, nil
    71  }