k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/util/jsontesting/json_roundtrip.go (about) 1 /* 2 Copyright 2022 The Kubernetes 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 jsontesting 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "reflect" 23 "strings" 24 25 kjson "sigs.k8s.io/json" 26 27 "github.com/go-openapi/jsonreference" 28 "github.com/google/go-cmp/cmp" 29 "github.com/google/go-cmp/cmp/cmpopts" 30 ) 31 32 func JsonCompare(got, want []byte) error { 33 if d := cmp.Diff(got, want, cmp.Transformer("JSONBytes", func(in []byte) (out interface{}) { 34 if strictErrors, err := kjson.UnmarshalStrict(in, &out); strictErrors != nil || err != nil { 35 return in 36 } 37 return out 38 })); d != "" { 39 return fmt.Errorf("JSON mismatch (-got +want):\n%s", d) 40 } 41 return nil 42 } 43 44 type RoundTripTestCase struct { 45 Name string 46 JSON string 47 Object json.Marshaler 48 49 // An error that is expected when `Object` is marshalled to json 50 // If `Object` does not exist, then it is inferred from the provided JSON 51 ExpectedMarshalError string 52 53 // An error that is expected when the provided JSON is unmarshalled 54 // If `JSON` does not exist then this it is inferred from the provided `Object` 55 ExpectedUnmarshalError string 56 } 57 58 type MarshalerUnmarshaler interface { 59 json.Unmarshaler 60 json.Marshaler 61 } 62 63 func (t RoundTripTestCase) RoundTripTest(example MarshalerUnmarshaler) error { 64 var jsonBytes []byte 65 var err error 66 67 // Tests whether the provided error matches the given pattern, and says 68 // whether the test is finished, and the error to return 69 expectError := func(e error, name string, expected string) (testFinished bool, err error) { 70 if len(expected) > 0 { 71 if e == nil || !strings.Contains(e.Error(), expected) { 72 return true, fmt.Errorf("expected %v error containing substring: '%s'. but got actual error '%v'", name, expected, e) 73 } 74 75 // If an error was expected and achieved, we stop the test 76 // since it cannot be continued. But the return nil error since it 77 // was expected. 78 return true, nil 79 } else if e != nil { 80 return true, fmt.Errorf("unexpected %v error: %w", name, e) 81 } 82 83 return false, nil 84 } 85 86 // If user did not provide JSON and instead provided Object, infer JSON 87 // from the provided object. 88 if len(t.JSON) == 0 { 89 jsonBytes, err = json.Marshal(t.Object) 90 if testFinished, err := expectError(err, "marshal", t.ExpectedMarshalError); testFinished { 91 return err 92 } 93 } else { 94 jsonBytes = []byte(t.JSON) 95 } 96 97 err = example.UnmarshalJSON(jsonBytes) 98 if testFinished, err := expectError(err, "unmarshal", t.ExpectedUnmarshalError); testFinished { 99 return err 100 } 101 102 if t.Object != nil && !reflect.DeepEqual(t.Object, example) { 103 return fmt.Errorf("test case expected to unmarshal to specific value: %v", cmp.Diff(t.Object, example, cmpopts.IgnoreUnexported(jsonreference.Ref{}))) 104 } 105 106 reEncoded, err := json.Marshal(example) 107 if err != nil { 108 return fmt.Errorf("failed to marshal decoded value: %w", err) 109 } 110 111 // Check expected marshal error if it has not yet been checked 112 // (for case where JSON is provided, and object is not) 113 if testFinished, err := expectError(err, "marshal", t.ExpectedMarshalError); testFinished { 114 return err 115 } 116 // Marshal both re-encoded, and original JSON into interface 117 // to compare them without ordering issues 118 var expected map[string]interface{} 119 var actual map[string]interface{} 120 121 if err = json.Unmarshal(jsonBytes, &expected); err != nil { 122 return fmt.Errorf("failed to unmarshal test json: %w", err) 123 } 124 125 if err = json.Unmarshal(reEncoded, &actual); err != nil { 126 return fmt.Errorf("failed to unmarshal actual data: %w", err) 127 } 128 129 if !reflect.DeepEqual(expected, actual) { 130 return fmt.Errorf("expected equal values: %v", cmp.Diff(expected, actual)) 131 } 132 133 return nil 134 }