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  }