Skip to content

Commit

Permalink
Add back support for gogo through handler interface
Browse files Browse the repository at this point in the history
A handler is an unexported type that is used to abstract external type
registries, such as gogoproto.
This allows us to add back gogo support but allow those who don't need
it to compile it out with the `!no_gogo` build tag.

Signed-off-by: Brian Goff <[email protected]>
  • Loading branch information
cpuguy83 committed Oct 15, 2024
1 parent 7d3d258 commit 5745849
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 6 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ As a containerd sub-project, you will find the:
* and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md)

information in our [`containerd/project`](https://github.com/containerd/project) repository.

## Optional

By default, support for gogoproto is available along side the standard Google
protobuf types.
You can choose to leave gogo support out by using the `!no_gogo` build tag.
50 changes: 44 additions & 6 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,16 @@ import (
var (
mu sync.RWMutex
registry = make(map[reflect.Type]string)
handlers []handler
)

type handler interface {
Marshaller(interface{}) func() ([]byte, error)
Unmarshaller(interface{}) func([]byte) error
TypeURL(interface{}) string
GetType(url string) reflect.Type
}

// Definitions of common error types used throughout typeurl.
//
// These error types are used with errors.Wrap and errors.Wrapf to add context
Expand Down Expand Up @@ -112,6 +120,11 @@ func TypeURL(v interface{}) (string, error) {
case proto.Message:
return string(t.ProtoReflect().Descriptor().FullName()), nil
default:
for _, h := range handlers {
if u := h.TypeURL(v); u != "" {
return u, nil
}
}
return "", fmt.Errorf("type %s: %w", reflect.TypeOf(v), ErrNotFound)
}
}
Expand Down Expand Up @@ -147,7 +160,18 @@ func MarshalAny(v interface{}) (Any, error) {
return proto.Marshal(t)
}
default:
marshal = json.Marshal
for _, h := range handlers {
if m := h.Marshaller(v); m != nil {
marshal = func(v interface{}) ([]byte, error) {
return m()
}
break
}
}

if marshal == nil {
marshal = json.Marshal
}
}

url, err := TypeURL(v)
Expand Down Expand Up @@ -236,12 +260,17 @@ func unmarshal(typeURL string, value []byte, v interface{}) (interface{}, error)

pm, ok := v.(proto.Message)
if ok {
err = proto.Unmarshal(value, pm)
} else {
err = json.Unmarshal(value, v)
return v, proto.Unmarshal(value, pm)
}

return v, err
for _, h := range handlers {
if unmarshal := h.Unmarshaller(v); unmarshal != nil {
return v, unmarshal(value)
}
}

// fallback to json unmarshaller
return v, json.Unmarshal(value, v)
}

func getTypeByUrl(url string) (reflect.Type, error) {
Expand All @@ -255,7 +284,16 @@ func getTypeByUrl(url string) (reflect.Type, error) {
mu.RUnlock()
mt, err := protoregistry.GlobalTypes.FindMessageByURL(url)
if err != nil {
return nil, fmt.Errorf("type with url %s: %w", url, ErrNotFound)
e := protoregistry.NotFound
if !errors.Is(err, e) {
return nil, fmt.Errorf("type with url %s: %w", url, ErrNotFound)
}

for _, h := range handlers {
if t := h.GetType(url); t != nil {
return t, nil
}
}
}
empty := mt.New().Interface()
return reflect.TypeOf(empty).Elem(), nil
Expand Down
68 changes: 68 additions & 0 deletions types_gogo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//go:build !no_gogo

/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package typeurl

import (
"reflect"

gogoproto "github.com/gogo/protobuf/proto"
)

func init() {
handlers = append(handlers, gogoHandler{})
}

type gogoHandler struct{}

func (gogoHandler) Marshaller(v interface{}) func() ([]byte, error) {
pm, ok := v.(gogoproto.Message)
if !ok {
return nil
}
return func() ([]byte, error) {
return gogoproto.Marshal(pm)
}
}

func (gogoHandler) Unmarshaller(v interface{}) func([]byte) error {
pm, ok := v.(gogoproto.Message)
if !ok {
return nil
}

return func(dt []byte) error {
return gogoproto.Unmarshal(dt, pm)
}
}

func (gogoHandler) TypeURL(v interface{}) string {
pm, ok := v.(gogoproto.Message)
if !ok {
return ""
}
return gogoproto.MessageName(pm)
}

func (gogoHandler) GetType(url string) reflect.Type {
t := gogoproto.MessageType(url)
if t == nil {
return nil
}
return t.Elem()
}

0 comments on commit 5745849

Please sign in to comment.