130 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			130 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package typeurl
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"path"
 | |
| 	"reflect"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/containerd/containerd/errdefs"
 | |
| 	"github.com/gogo/protobuf/proto"
 | |
| 	"github.com/gogo/protobuf/types"
 | |
| )
 | |
| 
 | |
| const Prefix = "types.containerd.io"
 | |
| 
 | |
| var (
 | |
| 	mu       sync.Mutex
 | |
| 	registry = make(map[reflect.Type]string)
 | |
| )
 | |
| 
 | |
| // Register a type with the base url of the type
 | |
| func Register(v interface{}, args ...string) {
 | |
| 	t := tryDereference(v)
 | |
| 	mu.Lock()
 | |
| 	defer mu.Unlock()
 | |
| 	if _, ok := registry[t]; ok {
 | |
| 		panic(errdefs.ErrAlreadyExists)
 | |
| 	}
 | |
| 	registry[t] = path.Join(append([]string{Prefix}, args...)...)
 | |
| }
 | |
| 
 | |
| // TypeURL returns the type url for a registred type
 | |
| func TypeURL(v interface{}) (string, error) {
 | |
| 	mu.Lock()
 | |
| 	u, ok := registry[tryDereference(v)]
 | |
| 	mu.Unlock()
 | |
| 	if !ok {
 | |
| 		// fallback to the proto registry if it is a proto message
 | |
| 		pb, ok := v.(proto.Message)
 | |
| 		if !ok {
 | |
| 			return "", errdefs.ErrNotFound
 | |
| 		}
 | |
| 		return path.Join(Prefix, proto.MessageName(pb)), nil
 | |
| 	}
 | |
| 	return u, nil
 | |
| }
 | |
| 
 | |
| // Is returns true if the type of the Any is the same as v
 | |
| func Is(any *types.Any, v interface{}) bool {
 | |
| 	// call to check that v is a pointer
 | |
| 	tryDereference(v)
 | |
| 	url, err := TypeURL(v)
 | |
| 	if err != nil {
 | |
| 		return false
 | |
| 	}
 | |
| 	return any.TypeUrl == url
 | |
| }
 | |
| 
 | |
| // MarshalAny marshals the value v into an any with the correct TypeUrl
 | |
| func MarshalAny(v interface{}) (*types.Any, error) {
 | |
| 	var data []byte
 | |
| 	url, err := TypeURL(v)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	switch t := v.(type) {
 | |
| 	case proto.Message:
 | |
| 		data, err = proto.Marshal(t)
 | |
| 	default:
 | |
| 		data, err = json.Marshal(v)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return &types.Any{
 | |
| 		TypeUrl: url,
 | |
| 		Value:   data,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // UnmarshalAny unmarshals the any type into a concrete type
 | |
| func UnmarshalAny(any *types.Any) (interface{}, error) {
 | |
| 	t, err := getTypeByUrl(any.TypeUrl)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	v := reflect.New(t.t).Interface()
 | |
| 	if t.isProto {
 | |
| 		err = proto.Unmarshal(any.Value, v.(proto.Message))
 | |
| 	} else {
 | |
| 		err = json.Unmarshal(any.Value, v)
 | |
| 	}
 | |
| 	return v, err
 | |
| }
 | |
| 
 | |
| type urlType struct {
 | |
| 	t       reflect.Type
 | |
| 	isProto bool
 | |
| }
 | |
| 
 | |
| func getTypeByUrl(url string) (urlType, error) {
 | |
| 	for t, u := range registry {
 | |
| 		if u == url {
 | |
| 			return urlType{
 | |
| 				t: t,
 | |
| 			}, nil
 | |
| 		}
 | |
| 	}
 | |
| 	// fallback to proto registry
 | |
| 	t := proto.MessageType(strings.TrimPrefix(url, Prefix+"/"))
 | |
| 	if t != nil {
 | |
| 		return urlType{
 | |
| 			// get the underlying Elem because proto returns a pointer to the type
 | |
| 			t:       t.Elem(),
 | |
| 			isProto: true,
 | |
| 		}, nil
 | |
| 	}
 | |
| 	return urlType{}, errdefs.ErrNotFound
 | |
| }
 | |
| 
 | |
| func tryDereference(v interface{}) reflect.Type {
 | |
| 	t := reflect.TypeOf(v)
 | |
| 	if t.Kind() == reflect.Ptr {
 | |
| 		// require check of pointer but dereference to register
 | |
| 		return t.Elem()
 | |
| 	}
 | |
| 	panic("v is not a pointer to a type")
 | |
| }
 | 
