...

Source file src/github.com/containerd/typeurl/v2/types.go

Documentation: github.com/containerd/typeurl/v2

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package typeurl
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"path"
    24  	"reflect"
    25  	"sync"
    26  
    27  	gogoproto "github.com/gogo/protobuf/proto"
    28  	"google.golang.org/protobuf/proto"
    29  	"google.golang.org/protobuf/reflect/protoregistry"
    30  )
    31  
    32  var (
    33  	mu       sync.RWMutex
    34  	registry = make(map[reflect.Type]string)
    35  )
    36  
    37  // Definitions of common error types used throughout typeurl.
    38  //
    39  // These error types are used with errors.Wrap and errors.Wrapf to add context
    40  // to an error.
    41  //
    42  // To detect an error class, use errors.Is() functions to tell whether an
    43  // error is of this type.
    44  
    45  var (
    46  	ErrNotFound = errors.New("not found")
    47  )
    48  
    49  // Any contains an arbitrary protcol buffer message along with its type.
    50  //
    51  // While there is google.golang.org/protobuf/types/known/anypb.Any,
    52  // we'd like to have our own to hide the underlying protocol buffer
    53  // implementations from containerd clients.
    54  //
    55  // https://developers.google.com/protocol-buffers/docs/proto3#any
    56  type Any interface {
    57  	// GetTypeUrl returns a URL/resource name that uniquely identifies
    58  	// the type of the serialized protocol buffer message.
    59  	GetTypeUrl() string
    60  
    61  	// GetValue returns a valid serialized protocol buffer of the type that
    62  	// GetTypeUrl() indicates.
    63  	GetValue() []byte
    64  }
    65  
    66  type anyType struct {
    67  	typeURL string
    68  	value   []byte
    69  }
    70  
    71  func (a *anyType) GetTypeUrl() string {
    72  	if a == nil {
    73  		return ""
    74  	}
    75  	return a.typeURL
    76  }
    77  
    78  func (a *anyType) GetValue() []byte {
    79  	if a == nil {
    80  		return nil
    81  	}
    82  	return a.value
    83  }
    84  
    85  // Register a type with a base URL for JSON marshaling. When the MarshalAny and
    86  // UnmarshalAny functions are called they will treat the Any type value as JSON.
    87  // To use protocol buffers for handling the Any value the proto.Register
    88  // function should be used instead of this function.
    89  func Register(v interface{}, args ...string) {
    90  	var (
    91  		t = tryDereference(v)
    92  		p = path.Join(args...)
    93  	)
    94  	mu.Lock()
    95  	defer mu.Unlock()
    96  	if et, ok := registry[t]; ok {
    97  		if et != p {
    98  			panic(fmt.Errorf("type registered with alternate path %q != %q", et, p))
    99  		}
   100  		return
   101  	}
   102  	registry[t] = p
   103  }
   104  
   105  // TypeURL returns the type url for a registered type.
   106  func TypeURL(v interface{}) (string, error) {
   107  	mu.RLock()
   108  	u, ok := registry[tryDereference(v)]
   109  	mu.RUnlock()
   110  	if !ok {
   111  		switch t := v.(type) {
   112  		case proto.Message:
   113  			return string(t.ProtoReflect().Descriptor().FullName()), nil
   114  		case gogoproto.Message:
   115  			return gogoproto.MessageName(t), nil
   116  		default:
   117  			return "", fmt.Errorf("type %s: %w", reflect.TypeOf(v), ErrNotFound)
   118  		}
   119  	}
   120  	return u, nil
   121  }
   122  
   123  // Is returns true if the type of the Any is the same as v.
   124  func Is(any Any, v interface{}) bool {
   125  	// call to check that v is a pointer
   126  	tryDereference(v)
   127  	url, err := TypeURL(v)
   128  	if err != nil {
   129  		return false
   130  	}
   131  	return any.GetTypeUrl() == url
   132  }
   133  
   134  // MarshalAny marshals the value v into an any with the correct TypeUrl.
   135  // If the provided object is already a proto.Any message, then it will be
   136  // returned verbatim. If it is of type proto.Message, it will be marshaled as a
   137  // protocol buffer. Otherwise, the object will be marshaled to json.
   138  func MarshalAny(v interface{}) (Any, error) {
   139  	var marshal func(v interface{}) ([]byte, error)
   140  	switch t := v.(type) {
   141  	case Any:
   142  		// avoid reserializing the type if we have an any.
   143  		return t, nil
   144  	case proto.Message:
   145  		marshal = func(v interface{}) ([]byte, error) {
   146  			return proto.Marshal(t)
   147  		}
   148  	case gogoproto.Message:
   149  		marshal = func(v interface{}) ([]byte, error) {
   150  			return gogoproto.Marshal(t)
   151  		}
   152  	default:
   153  		marshal = json.Marshal
   154  	}
   155  
   156  	url, err := TypeURL(v)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	data, err := marshal(v)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	return &anyType{
   166  		typeURL: url,
   167  		value:   data,
   168  	}, nil
   169  }
   170  
   171  // UnmarshalAny unmarshals the any type into a concrete type.
   172  func UnmarshalAny(any Any) (interface{}, error) {
   173  	return UnmarshalByTypeURL(any.GetTypeUrl(), any.GetValue())
   174  }
   175  
   176  // UnmarshalByTypeURL unmarshals the given type and value to into a concrete type.
   177  func UnmarshalByTypeURL(typeURL string, value []byte) (interface{}, error) {
   178  	return unmarshal(typeURL, value, nil)
   179  }
   180  
   181  // UnmarshalTo unmarshals the any type into a concrete type passed in the out
   182  // argument. It is identical to UnmarshalAny, but lets clients provide a
   183  // destination type through the out argument.
   184  func UnmarshalTo(any Any, out interface{}) error {
   185  	return UnmarshalToByTypeURL(any.GetTypeUrl(), any.GetValue(), out)
   186  }
   187  
   188  // UnmarshalToByTypeURL unmarshals the given type and value into a concrete type passed
   189  // in the out argument. It is identical to UnmarshalByTypeURL, but lets clients
   190  // provide a destination type through the out argument.
   191  func UnmarshalToByTypeURL(typeURL string, value []byte, out interface{}) error {
   192  	_, err := unmarshal(typeURL, value, out)
   193  	return err
   194  }
   195  
   196  func unmarshal(typeURL string, value []byte, v interface{}) (interface{}, error) {
   197  	t, err := getTypeByUrl(typeURL)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	if v == nil {
   203  		v = reflect.New(t.t).Interface()
   204  	} else {
   205  		// Validate interface type provided by client
   206  		vURL, err := TypeURL(v)
   207  		if err != nil {
   208  			return nil, err
   209  		}
   210  		if typeURL != vURL {
   211  			return nil, fmt.Errorf("can't unmarshal type %q to output %q", typeURL, vURL)
   212  		}
   213  	}
   214  
   215  	if t.isProto {
   216  		switch t := v.(type) {
   217  		case proto.Message:
   218  			err = proto.Unmarshal(value, t)
   219  		case gogoproto.Message:
   220  			err = gogoproto.Unmarshal(value, t)
   221  		}
   222  	} else {
   223  		err = json.Unmarshal(value, v)
   224  	}
   225  
   226  	return v, err
   227  }
   228  
   229  type urlType struct {
   230  	t       reflect.Type
   231  	isProto bool
   232  }
   233  
   234  func getTypeByUrl(url string) (urlType, error) {
   235  	mu.RLock()
   236  	for t, u := range registry {
   237  		if u == url {
   238  			mu.RUnlock()
   239  			return urlType{
   240  				t: t,
   241  			}, nil
   242  		}
   243  	}
   244  	mu.RUnlock()
   245  	// fallback to proto registry
   246  	t := gogoproto.MessageType(url)
   247  	if t != nil {
   248  		return urlType{
   249  			// get the underlying Elem because proto returns a pointer to the type
   250  			t:       t.Elem(),
   251  			isProto: true,
   252  		}, nil
   253  	}
   254  	mt, err := protoregistry.GlobalTypes.FindMessageByURL(url)
   255  	if err != nil {
   256  		return urlType{}, fmt.Errorf("type with url %s: %w", url, ErrNotFound)
   257  	}
   258  	empty := mt.New().Interface()
   259  	return urlType{t: reflect.TypeOf(empty).Elem(), isProto: true}, nil
   260  }
   261  
   262  func tryDereference(v interface{}) reflect.Type {
   263  	t := reflect.TypeOf(v)
   264  	if t.Kind() == reflect.Ptr {
   265  		// require check of pointer but dereference to register
   266  		return t.Elem()
   267  	}
   268  	panic("v is not a pointer to a type")
   269  }
   270  

View as plain text