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 }