mirror of https://github.com/knative/caching.git
114 lines
3.9 KiB
Go
114 lines
3.9 KiB
Go
/*
|
|
Copyright 2018 The Knative 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 duck
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"knative.dev/pkg/apis"
|
|
"knative.dev/pkg/kmp"
|
|
)
|
|
|
|
// Implementable is implemented by the Fooable duck type that consumers
|
|
// are expected to embed as a `.status.fooable` field.
|
|
type Implementable interface {
|
|
// GetFullType returns an instance of a full resource wrapping
|
|
// an instance of this Implementable that can populate its fields
|
|
// to verify json roundtripping.
|
|
GetFullType() Populatable
|
|
}
|
|
|
|
// Populatable is implemented by a skeleton resource wrapping an Implementable
|
|
// duck type. It will generally have TypeMeta, ObjectMeta, and a Status field
|
|
// wrapping a Fooable field.
|
|
type Populatable interface {
|
|
apis.Listable
|
|
|
|
// Populate fills in all possible fields, so that we can verify that
|
|
// they roundtrip properly through JSON.
|
|
Populate()
|
|
}
|
|
|
|
// VerifyType verifies that a particular concrete resource properly implements
|
|
// the provided Implementable duck type. It is expected that under the resource
|
|
// definition implementing a particular "Fooable" that one would write:
|
|
//
|
|
// type ConcreteResource struct { ... }
|
|
//
|
|
// // Check that ConcreteResource properly implement Fooable.
|
|
// err := duck.VerifyType(&ConcreteResource{}, &something.Fooable{})
|
|
//
|
|
// This will return an error if the duck typing is not satisfied.
|
|
func VerifyType(instance interface{}, iface Implementable) error {
|
|
// Create instances of the full resource for our input and ultimate result
|
|
// that we will compare at the end.
|
|
input, output := iface.GetFullType(), iface.GetFullType()
|
|
|
|
if err := roundTrip(instance, input, output); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Now verify that we were able to roundtrip all of our fields through the type
|
|
// we are checking.
|
|
if diff, err := kmp.SafeDiff(input, output); err != nil {
|
|
return err
|
|
} else if diff != "" {
|
|
return fmt.Errorf("%T does not implement the duck type %T, the following fields were lost: %s",
|
|
instance, iface, diff)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ConformsToType will return true or false depending on whether a
|
|
// concrete resource properly implements the provided Implementable
|
|
// duck type.
|
|
//
|
|
// It will return an error if marshal/unmarshalling fails
|
|
func ConformsToType(instance interface{}, iface Implementable) (bool, error) {
|
|
input, output := iface.GetFullType(), iface.GetFullType()
|
|
|
|
if err := roundTrip(instance, input, output); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return kmp.SafeEqual(input, output)
|
|
}
|
|
|
|
func roundTrip(instance interface{}, input, output Populatable) error {
|
|
// Populate our input resource with values we will roundtrip.
|
|
input.Populate()
|
|
|
|
// Serialize the input to JSON and deserialize that into the provided instance
|
|
// of the type that we are checking.
|
|
if before, err := json.Marshal(input); err != nil {
|
|
return fmt.Errorf("error serializing duck type %T error: %s", input, err)
|
|
} else if err := json.Unmarshal(before, instance); err != nil {
|
|
return fmt.Errorf("error deserializing duck type %T into %T error: %s", input, instance, err)
|
|
}
|
|
|
|
// Serialize the instance we are checking to JSON and deserialize that into the
|
|
// output resource.
|
|
if after, err := json.Marshal(instance); err != nil {
|
|
return fmt.Errorf("error serializing %T error: %s", instance, err)
|
|
} else if err := json.Unmarshal(after, output); err != nil {
|
|
return fmt.Errorf("error deserializing %T into duck type %T error: %s", instance, output, err)
|
|
}
|
|
|
|
return nil
|
|
}
|