knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/duck/verify.go (about)

     1  /*
     2  Copyright 2018 The Knative 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 duck
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  
    23  	"knative.dev/pkg/apis/duck/ducktypes"
    24  	"knative.dev/pkg/kmp"
    25  )
    26  
    27  type (
    28  	Implementable = ducktypes.Implementable
    29  	Populatable   = ducktypes.Populatable
    30  )
    31  
    32  // VerifyType verifies that a particular concrete resource properly implements
    33  // the provided Implementable duck type.  It is expected that under the resource
    34  // definition implementing a particular "Fooable" that one would write:
    35  //
    36  //	type ConcreteResource struct { ... }
    37  //
    38  //	// Check that ConcreteResource properly implement Fooable.
    39  //	err := duck.VerifyType(&ConcreteResource{}, &something.Fooable{})
    40  //
    41  // This will return an error if the duck typing is not satisfied.
    42  func VerifyType(instance interface{}, iface Implementable) error {
    43  	// Create instances of the full resource for our input and ultimate result
    44  	// that we will compare at the end.
    45  	input, output := iface.GetFullType(), iface.GetFullType()
    46  
    47  	if err := roundTrip(instance, input, output); err != nil {
    48  		return err
    49  	}
    50  
    51  	// Now verify that we were able to roundtrip all of our fields through the type
    52  	// we are checking.
    53  	if diff, err := kmp.SafeDiff(input, output); err != nil {
    54  		return err
    55  	} else if diff != "" {
    56  		return fmt.Errorf("%T does not implement the duck type %T, the following fields were lost: %s",
    57  			instance, iface, diff)
    58  	}
    59  	return nil
    60  }
    61  
    62  // ConformsToType will return true or false depending on whether a
    63  // concrete resource properly implements the provided Implementable
    64  // duck type.
    65  //
    66  // It will return an error if marshal/unmarshalling fails
    67  func ConformsToType(instance interface{}, iface Implementable) (bool, error) {
    68  	input, output := iface.GetFullType(), iface.GetFullType()
    69  
    70  	if err := roundTrip(instance, input, output); err != nil {
    71  		return false, err
    72  	}
    73  
    74  	return kmp.SafeEqual(input, output)
    75  }
    76  
    77  func roundTrip(instance interface{}, input, output Populatable) error {
    78  	// Populate our input resource with values we will roundtrip.
    79  	input.Populate()
    80  
    81  	// Serialize the input to JSON and deserialize that into the provided instance
    82  	// of the type that we are checking.
    83  	if before, err := json.Marshal(input); err != nil {
    84  		return fmt.Errorf("error serializing duck type %T error: %w", input, err)
    85  	} else if err := json.Unmarshal(before, instance); err != nil {
    86  		return fmt.Errorf("error deserializing duck type %T into %T error: %w", input, instance, err)
    87  	}
    88  
    89  	// Serialize the instance we are checking to JSON and deserialize that into the
    90  	// output resource.
    91  	if after, err := json.Marshal(instance); err != nil {
    92  		return fmt.Errorf("error serializing %T error: %w", instance, err)
    93  	} else if err := json.Unmarshal(after, output); err != nil {
    94  		return fmt.Errorf("error deserializing %T into duck type %T error: %w", instance, output, err)
    95  	}
    96  
    97  	return nil
    98  }