...

Source file src/k8s.io/kube-openapi/pkg/generators/openapi_test.go

Documentation: k8s.io/kube-openapi/pkg/generators

     1  /*
     2  Copyright 2016 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 generators
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"go/format"
    23  	"path"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	"golang.org/x/tools/go/packages"
    29  	"golang.org/x/tools/go/packages/packagestest"
    30  	"k8s.io/gengo/v2/generator"
    31  	"k8s.io/gengo/v2/namer"
    32  	"k8s.io/gengo/v2/parser"
    33  	"k8s.io/gengo/v2/types"
    34  )
    35  
    36  func construct(t *testing.T, cfg *packages.Config, nameSystems namer.NameSystems, defaultSystem string, pkg string) *generator.Context {
    37  	p := parser.New()
    38  	if err := p.LoadPackagesWithConfigForTesting(cfg, pkg); err != nil {
    39  		t.Fatalf("failed to load package: %v", err)
    40  	}
    41  	c, err := generator.NewContext(p, nameSystems, defaultSystem)
    42  	if err != nil {
    43  		t.Fatalf("failed to make a context: %v", err)
    44  	}
    45  	return c
    46  }
    47  
    48  func testOpenAPITypeWriter(t *testing.T, cfg *packages.Config) (error, error, *bytes.Buffer, *bytes.Buffer, []string) {
    49  	pkgBase := "example.com/base"
    50  	// `path` vs. `filepath` because packages use '/'
    51  	inputPkg := path.Join(pkgBase, "foo")
    52  	outputPkg := path.Join(pkgBase, "output")
    53  	imports := generator.NewImportTrackerForPackage(outputPkg)
    54  	rawNamer := namer.NewRawNamer(outputPkg, imports)
    55  	namers := namer.NameSystems{
    56  		"raw": rawNamer,
    57  		"private": &namer.NameStrategy{
    58  			Join: func(pre string, in []string, post string) string {
    59  				return strings.Join(in, "_")
    60  			},
    61  			PrependPackageNames: 4, // enough to fully qualify from k8s.io/api/...
    62  		},
    63  	}
    64  	context := construct(t, cfg, namers, "raw", inputPkg)
    65  	universe := context.Universe
    66  	blahT := universe.Type(types.Name{Package: inputPkg, Name: "Blah"})
    67  
    68  	callBuffer := &bytes.Buffer{}
    69  	callSW := generator.NewSnippetWriter(callBuffer, context, "$", "$")
    70  	callError := newOpenAPITypeWriter(callSW, context).generateCall(blahT)
    71  
    72  	funcBuffer := &bytes.Buffer{}
    73  	funcSW := generator.NewSnippetWriter(funcBuffer, context, "$", "$")
    74  	funcError := newOpenAPITypeWriter(funcSW, context).generate(blahT)
    75  
    76  	return callError, funcError, callBuffer, funcBuffer, imports.ImportLines()
    77  }
    78  
    79  // NOTE: the usual order of arguments for an assertion would be want, got, but
    80  // this helper function flips that in favor of callsite readability.
    81  func assertEqual(t *testing.T, got, want string) {
    82  	t.Helper()
    83  	want = strings.TrimSpace(want)
    84  	got = strings.TrimSpace(got)
    85  	if !cmp.Equal(want, got) {
    86  		t.Errorf("Wrong result:\n%s", cmp.Diff(want, got))
    87  	}
    88  }
    89  
    90  func TestSimple(t *testing.T) {
    91  	inputFile := `
    92  		package foo
    93  
    94  		// Blah is a test.
    95  		// +k8s:openapi-gen=true
    96  		// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
    97  		type Blah struct {
    98  			// A simple string
    99  			String string
   100  			// A simple int
   101  			Int int ` + "`" + `json:",omitempty"` + "`" + `
   102  			// An int considered string simple int
   103  			IntString int ` + "`" + `json:",string"` + "`" + `
   104  			// A simple int64
   105  			Int64 int64
   106  			// A simple int32
   107  			Int32 int32
   108  			// A simple int16
   109  			Int16 int16
   110  			// A simple int8
   111  			Int8 int8
   112  			// A simple int
   113  			Uint uint
   114  			// A simple int64
   115  			Uint64 uint64
   116  			// A simple int32
   117  			Uint32 uint32
   118  			// A simple int16
   119  			Uint16 uint16
   120  			// A simple int8
   121  			Uint8 uint8
   122  			// A simple byte
   123  			Byte byte
   124  			// A simple boolean
   125  			Bool bool
   126  			// A simple float64
   127  			Float64 float64
   128  			// A simple float32
   129  			Float32 float32
   130  			// a base64 encoded characters
   131  			ByteArray []byte
   132  			// a member with an extension
   133  			// +k8s:openapi-gen=x-kubernetes-member-tag:member_test
   134  			WithExtension string
   135  			// a member with struct tag as extension
   136  			// +patchStrategy=merge
   137  			// +patchMergeKey=pmk
   138  			WithStructTagExtension string ` + "`" + `patchStrategy:"merge" patchMergeKey:"pmk"` + "`" + `
   139  			// a member with a list type
   140  			// +listType=atomic
   141  			// +default=["foo", "bar"]
   142  			WithListType []string
   143  			// a member with a map type
   144  			// +listType=atomic
   145  			// +default={"foo": "bar", "fizz": "buzz"}
   146  			Map map[string]string
   147  			// a member with a string pointer
   148  			// +default="foo"
   149  			StringPointer *string
   150  			// an int member with a default
   151  			// +default=1
   152  			OmittedInt int ` + "`" + `json:"omitted,omitempty"` + "`" + `
   153  			// a field with an invalid escape sequence in comment
   154  			// ex) regexp:^.*\.yaml$
   155  			InvalidEscapeSequenceInComment string
   156  		}`
   157  
   158  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
   159  		e := packagestest.Export(t, x, []packagestest.Module{{
   160  			Name: "example.com/base/foo",
   161  			Files: map[string]interface{}{
   162  				"foo.go": inputFile,
   163  			},
   164  		}})
   165  		defer e.Cleanup()
   166  
   167  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
   168  		if callErr != nil {
   169  			t.Fatal(callErr)
   170  		}
   171  		if funcErr != nil {
   172  			t.Fatal(funcErr)
   173  		}
   174  		assertEqual(t, callBuffer.String(),
   175  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
   176  
   177  		assertEqual(t, funcBuffer.String(),
   178  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
   179  return common.OpenAPIDefinition{
   180  Schema: spec.Schema{
   181  SchemaProps: spec.SchemaProps{
   182  Description: "Blah is a test.",
   183  Type: []string{"object"},
   184  Properties: map[string]spec.Schema{
   185  "String": {
   186  SchemaProps: spec.SchemaProps{
   187  Description: "A simple string",
   188  Default: "",
   189  Type: []string{"string"},
   190  Format: "",
   191  },
   192  },
   193  "Int64": {
   194  SchemaProps: spec.SchemaProps{
   195  Description: "A simple int64",
   196  Default: 0,
   197  Type: []string{"integer"},
   198  Format: "int64",
   199  },
   200  },
   201  "Int32": {
   202  SchemaProps: spec.SchemaProps{
   203  Description: "A simple int32",
   204  Default: 0,
   205  Type: []string{"integer"},
   206  Format: "int32",
   207  },
   208  },
   209  "Int16": {
   210  SchemaProps: spec.SchemaProps{
   211  Description: "A simple int16",
   212  Default: 0,
   213  Type: []string{"integer"},
   214  Format: "int32",
   215  },
   216  },
   217  "Int8": {
   218  SchemaProps: spec.SchemaProps{
   219  Description: "A simple int8",
   220  Default: 0,
   221  Type: []string{"integer"},
   222  Format: "byte",
   223  },
   224  },
   225  "Uint": {
   226  SchemaProps: spec.SchemaProps{
   227  Description: "A simple int",
   228  Default: 0,
   229  Type: []string{"integer"},
   230  Format: "int32",
   231  },
   232  },
   233  "Uint64": {
   234  SchemaProps: spec.SchemaProps{
   235  Description: "A simple int64",
   236  Default: 0,
   237  Type: []string{"integer"},
   238  Format: "int64",
   239  },
   240  },
   241  "Uint32": {
   242  SchemaProps: spec.SchemaProps{
   243  Description: "A simple int32",
   244  Default: 0,
   245  Type: []string{"integer"},
   246  Format: "int64",
   247  },
   248  },
   249  "Uint16": {
   250  SchemaProps: spec.SchemaProps{
   251  Description: "A simple int16",
   252  Default: 0,
   253  Type: []string{"integer"},
   254  Format: "int32",
   255  },
   256  },
   257  "Uint8": {
   258  SchemaProps: spec.SchemaProps{
   259  Description: "A simple int8",
   260  Default: 0,
   261  Type: []string{"integer"},
   262  Format: "byte",
   263  },
   264  },
   265  "Byte": {
   266  SchemaProps: spec.SchemaProps{
   267  Description: "A simple byte",
   268  Default: 0,
   269  Type: []string{"integer"},
   270  Format: "byte",
   271  },
   272  },
   273  "Bool": {
   274  SchemaProps: spec.SchemaProps{
   275  Description: "A simple boolean",
   276  Default: false,
   277  Type: []string{"boolean"},
   278  Format: "",
   279  },
   280  },
   281  "Float64": {
   282  SchemaProps: spec.SchemaProps{
   283  Description: "A simple float64",
   284  Default: 0,
   285  Type: []string{"number"},
   286  Format: "double",
   287  },
   288  },
   289  "Float32": {
   290  SchemaProps: spec.SchemaProps{
   291  Description: "A simple float32",
   292  Default: 0,
   293  Type: []string{"number"},
   294  Format: "float",
   295  },
   296  },
   297  "ByteArray": {
   298  SchemaProps: spec.SchemaProps{
   299  Description: "a base64 encoded characters",
   300  Type: []string{"string"},
   301  Format: "byte",
   302  },
   303  },
   304  "WithExtension": {
   305  VendorExtensible: spec.VendorExtensible{
   306  Extensions: spec.Extensions{
   307  "x-kubernetes-member-tag": "member_test",
   308  },
   309  },
   310  SchemaProps: spec.SchemaProps{
   311  Description: "a member with an extension",
   312  Default: "",
   313  Type: []string{"string"},
   314  Format: "",
   315  },
   316  },
   317  "WithStructTagExtension": {
   318  VendorExtensible: spec.VendorExtensible{
   319  Extensions: spec.Extensions{
   320  "x-kubernetes-patch-merge-key": "pmk",
   321  "x-kubernetes-patch-strategy": "merge",
   322  },
   323  },
   324  SchemaProps: spec.SchemaProps{
   325  Description: "a member with struct tag as extension",
   326  Default: "",
   327  Type: []string{"string"},
   328  Format: "",
   329  },
   330  },
   331  "WithListType": {
   332  VendorExtensible: spec.VendorExtensible{
   333  Extensions: spec.Extensions{
   334  "x-kubernetes-list-type": "atomic",
   335  },
   336  },
   337  SchemaProps: spec.SchemaProps{
   338  Description: "a member with a list type",
   339  Default: []interface {}{"foo", "bar"},
   340  Type: []string{"array"},
   341  Items: &spec.SchemaOrArray{
   342  Schema: &spec.Schema{
   343  SchemaProps: spec.SchemaProps{
   344  Default: "",
   345  Type: []string{"string"},
   346  Format: "",
   347  },
   348  },
   349  },
   350  },
   351  },
   352  "Map": {
   353  VendorExtensible: spec.VendorExtensible{
   354  Extensions: spec.Extensions{
   355  "x-kubernetes-list-type": "atomic",
   356  },
   357  },
   358  SchemaProps: spec.SchemaProps{
   359  Description: "a member with a map type",
   360  Default: map[string]interface {}{"fizz":"buzz", "foo":"bar"},
   361  Type: []string{"object"},
   362  AdditionalProperties: &spec.SchemaOrBool{
   363  Allows: true,
   364  Schema: &spec.Schema{
   365  SchemaProps: spec.SchemaProps{
   366  Default: "",
   367  Type: []string{"string"},
   368  Format: "",
   369  },
   370  },
   371  },
   372  },
   373  },
   374  "StringPointer": {
   375  SchemaProps: spec.SchemaProps{
   376  Description: "a member with a string pointer",
   377  Default: "foo",
   378  Type: []string{"string"},
   379  Format: "",
   380  },
   381  },
   382  "omitted": {
   383  SchemaProps: spec.SchemaProps{
   384  Description: "an int member with a default",
   385  Default: 1,
   386  Type: []string{"integer"},
   387  Format: "int32",
   388  },
   389  },
   390  "InvalidEscapeSequenceInComment": {
   391  SchemaProps: spec.SchemaProps{
   392  Description: "a field with an invalid escape sequence in comment ex) regexp:^.*\\.yaml$",
   393  Default: "",
   394  Type: []string{"string"},
   395  Format: "",
   396  },
   397  },
   398  },
   399  Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension","WithStructTagExtension","WithListType","Map","StringPointer","InvalidEscapeSequenceInComment"},
   400  },
   401  VendorExtensible: spec.VendorExtensible{
   402  Extensions: spec.Extensions{
   403  "x-kubernetes-type-tag": "type_test",
   404  },
   405  },
   406  },
   407  }
   408  }`)
   409  	})
   410  }
   411  
   412  func TestEmptyProperties(t *testing.T) {
   413  	inputFile := `
   414  		package foo
   415  
   416  		// Blah demonstrate a struct without fields.
   417  		type Blah struct {
   418  		}`
   419  
   420  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
   421  		e := packagestest.Export(t, x, []packagestest.Module{{
   422  			Name: "example.com/base/foo",
   423  			Files: map[string]interface{}{
   424  				"foo.go": inputFile,
   425  			},
   426  		}})
   427  		defer e.Cleanup()
   428  
   429  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
   430  		if callErr != nil {
   431  			t.Fatal(callErr)
   432  		}
   433  		if funcErr != nil {
   434  			t.Fatal(funcErr)
   435  		}
   436  		assertEqual(t, callBuffer.String(),
   437  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
   438  		assertEqual(t, funcBuffer.String(),
   439  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
   440  return common.OpenAPIDefinition{
   441  Schema: spec.Schema{
   442  SchemaProps: spec.SchemaProps{
   443  Description: "Blah demonstrate a struct without fields.",
   444  Type: []string{"object"},
   445  },
   446  },
   447  }
   448  }`)
   449  	})
   450  }
   451  
   452  func TestNestedStruct(t *testing.T) {
   453  	inputFile := `
   454  		package foo
   455  
   456  		// Nested is used as struct field
   457  		type Nested struct {
   458  		  // A simple string
   459  		  String string
   460  		}
   461  
   462  		// Blah demonstrate a struct with struct field.
   463  		type Blah struct {
   464  		  // A struct field
   465  		  Field Nested
   466  		}`
   467  
   468  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
   469  		e := packagestest.Export(t, x, []packagestest.Module{{
   470  			Name: "example.com/base/foo",
   471  			Files: map[string]interface{}{
   472  				"foo.go": inputFile,
   473  			},
   474  		}})
   475  		defer e.Cleanup()
   476  
   477  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
   478  		if callErr != nil {
   479  			t.Fatal(callErr)
   480  		}
   481  		if funcErr != nil {
   482  			t.Fatal(funcErr)
   483  		}
   484  		assertEqual(t, callBuffer.String(),
   485  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
   486  		assertEqual(t, funcBuffer.String(),
   487  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
   488  return common.OpenAPIDefinition{
   489  Schema: spec.Schema{
   490  SchemaProps: spec.SchemaProps{
   491  Description: "Blah demonstrate a struct with struct field.",
   492  Type: []string{"object"},
   493  Properties: map[string]spec.Schema{
   494  "Field": {
   495  SchemaProps: spec.SchemaProps{
   496  Description: "A struct field",
   497  Default: map[string]interface {}{},
   498  Ref: ref("example.com/base/foo.Nested"),
   499  },
   500  },
   501  },
   502  Required: []string{"Field"},
   503  },
   504  },
   505  Dependencies: []string{
   506  "example.com/base/foo.Nested",},
   507  }
   508  }`)
   509  	})
   510  }
   511  
   512  func TestNestedStructPointer(t *testing.T) {
   513  	inputFile := `
   514  		package foo
   515  
   516  		// Nested is used as struct pointer field
   517  		type Nested struct {
   518  		  // A simple string
   519  		  String string
   520  		}
   521  
   522  		// Blah demonstrate a struct with struct pointer field.
   523  		type Blah struct {
   524  		  // A struct pointer field
   525  		  Field *Nested
   526  		}`
   527  
   528  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
   529  		e := packagestest.Export(t, x, []packagestest.Module{{
   530  			Name: "example.com/base/foo",
   531  			Files: map[string]interface{}{
   532  				"foo.go": inputFile,
   533  			},
   534  		}})
   535  		defer e.Cleanup()
   536  
   537  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
   538  
   539  		if callErr != nil {
   540  			t.Fatal(callErr)
   541  		}
   542  		if funcErr != nil {
   543  			t.Fatal(funcErr)
   544  		}
   545  		assertEqual(t, callBuffer.String(),
   546  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
   547  		assertEqual(t, funcBuffer.String(),
   548  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
   549  return common.OpenAPIDefinition{
   550  Schema: spec.Schema{
   551  SchemaProps: spec.SchemaProps{
   552  Description: "Blah demonstrate a struct with struct pointer field.",
   553  Type: []string{"object"},
   554  Properties: map[string]spec.Schema{
   555  "Field": {
   556  SchemaProps: spec.SchemaProps{
   557  Description: "A struct pointer field",
   558  Ref: ref("example.com/base/foo.Nested"),
   559  },
   560  },
   561  },
   562  Required: []string{"Field"},
   563  },
   564  },
   565  Dependencies: []string{
   566  "example.com/base/foo.Nested",},
   567  }
   568  }`)
   569  	})
   570  }
   571  
   572  func TestEmbeddedStruct(t *testing.T) {
   573  	inputFile := `
   574  		package foo
   575  
   576  		// Nested is used as embedded struct field
   577  		type Nested struct {
   578  		  // A simple string
   579  		  String string
   580  		}
   581  
   582  		// Blah demonstrate a struct with embedded struct field.
   583  		type Blah struct {
   584  		  // An embedded struct field
   585  		  Nested
   586  		}`
   587  
   588  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
   589  		e := packagestest.Export(t, x, []packagestest.Module{{
   590  			Name: "example.com/base/foo",
   591  			Files: map[string]interface{}{
   592  				"foo.go": inputFile,
   593  			},
   594  		}})
   595  		defer e.Cleanup()
   596  
   597  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
   598  		if callErr != nil {
   599  			t.Fatal(callErr)
   600  		}
   601  		if funcErr != nil {
   602  			t.Fatal(funcErr)
   603  		}
   604  		assertEqual(t, callBuffer.String(),
   605  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
   606  		assertEqual(t, funcBuffer.String(),
   607  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
   608  return common.OpenAPIDefinition{
   609  Schema: spec.Schema{
   610  SchemaProps: spec.SchemaProps{
   611  Description: "Blah demonstrate a struct with embedded struct field.",
   612  Type: []string{"object"},
   613  Properties: map[string]spec.Schema{
   614  "Nested": {
   615  SchemaProps: spec.SchemaProps{
   616  Description: "An embedded struct field",
   617  Default: map[string]interface {}{},
   618  Ref: ref("example.com/base/foo.Nested"),
   619  },
   620  },
   621  },
   622  Required: []string{"Nested"},
   623  },
   624  },
   625  Dependencies: []string{
   626  "example.com/base/foo.Nested",},
   627  }
   628  }`)
   629  	})
   630  }
   631  
   632  func TestSingleEmbeddedStruct(t *testing.T) {
   633  	inputFile := `
   634  		package foo
   635  
   636  		import "time"
   637  
   638  		// Nested is used as embedded struct field
   639  		type Nested struct {
   640  		  // A simple string
   641  		  time.Duration
   642  		}
   643  
   644  		// Blah demonstrate a struct with embedded struct field.
   645  		type Blah struct {
   646  		  // An embedded struct field
   647  		  // +default="10ms"
   648  		  Nested ` + "`" + `json:"nested,omitempty" protobuf:"bytes,5,opt,name=nested"` + "`" + `
   649  		}`
   650  
   651  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
   652  		e := packagestest.Export(t, x, []packagestest.Module{{
   653  			Name: "example.com/base/foo",
   654  			Files: map[string]interface{}{
   655  				"foo.go": inputFile,
   656  			},
   657  		}})
   658  		defer e.Cleanup()
   659  
   660  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
   661  		if callErr != nil {
   662  			t.Fatal(callErr)
   663  		}
   664  		if funcErr != nil {
   665  			t.Fatal(funcErr)
   666  		}
   667  		assertEqual(t, callBuffer.String(),
   668  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
   669  		assertEqual(t, funcBuffer.String(),
   670  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
   671  return common.OpenAPIDefinition{
   672  Schema: spec.Schema{
   673  SchemaProps: spec.SchemaProps{
   674  Description: "Blah demonstrate a struct with embedded struct field.",
   675  Type: []string{"object"},
   676  Properties: map[string]spec.Schema{
   677  "nested": {
   678  SchemaProps: spec.SchemaProps{
   679  Description: "An embedded struct field",
   680  Default: "10ms",
   681  Ref: ref("example.com/base/foo.Nested"),
   682  },
   683  },
   684  },
   685  },
   686  },
   687  Dependencies: []string{
   688  "example.com/base/foo.Nested",},
   689  }
   690  }`)
   691  	})
   692  }
   693  
   694  func TestEmbeddedInlineStruct(t *testing.T) {
   695  	inputFile := `
   696  	package foo
   697  
   698  		// Nested is used as embedded inline struct field
   699  		type Nested struct {
   700  		  // A simple string
   701  		  String string
   702  		}
   703  
   704  		// Blah demonstrate a struct with embedded inline struct field.
   705  		type Blah struct {
   706  		  // An embedded inline struct field
   707  		  Nested ` + "`" + `json:",inline,omitempty"` + "`" + `
   708  		}`
   709  
   710  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
   711  		e := packagestest.Export(t, x, []packagestest.Module{{
   712  			Name: "example.com/base/foo",
   713  			Files: map[string]interface{}{
   714  				"foo.go": inputFile,
   715  			},
   716  		}})
   717  		defer e.Cleanup()
   718  
   719  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
   720  		if callErr != nil {
   721  			t.Fatal(callErr)
   722  		}
   723  		if funcErr != nil {
   724  			t.Fatal(funcErr)
   725  		}
   726  		assertEqual(t, callBuffer.String(),
   727  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
   728  		assertEqual(t, funcBuffer.String(),
   729  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
   730  return common.OpenAPIDefinition{
   731  Schema: spec.Schema{
   732  SchemaProps: spec.SchemaProps{
   733  Description: "Blah demonstrate a struct with embedded inline struct field.",
   734  Type: []string{"object"},
   735  Properties: map[string]spec.Schema{
   736  "String": {
   737  SchemaProps: spec.SchemaProps{
   738  Description: "A simple string",
   739  Default: "",
   740  Type: []string{"string"},
   741  Format: "",
   742  },
   743  },
   744  },
   745  Required: []string{"String"},
   746  },
   747  },
   748  }
   749  }`)
   750  	})
   751  }
   752  
   753  func TestEmbeddedInlineStructPointer(t *testing.T) {
   754  	inputFile := `
   755  		package foo
   756  
   757  		// Nested is used as embedded inline struct pointer field.
   758  		type Nested struct {
   759  		  // A simple string
   760  		  String string
   761  		}
   762  
   763  		// Blah demonstrate a struct with embedded inline struct pointer field.
   764  		type Blah struct {
   765  		  // An embedded inline struct pointer field
   766  		  *Nested ` + "`" + `json:",inline,omitempty"` + "`" + `
   767  		}`
   768  
   769  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
   770  		e := packagestest.Export(t, x, []packagestest.Module{{
   771  			Name: "example.com/base/foo",
   772  			Files: map[string]interface{}{
   773  				"foo.go": inputFile,
   774  			},
   775  		}})
   776  		defer e.Cleanup()
   777  
   778  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
   779  		if callErr != nil {
   780  			t.Fatal(callErr)
   781  		}
   782  		if funcErr != nil {
   783  			t.Fatal(funcErr)
   784  		}
   785  		assertEqual(t, callBuffer.String(),
   786  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
   787  		assertEqual(t, funcBuffer.String(),
   788  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
   789  return common.OpenAPIDefinition{
   790  Schema: spec.Schema{
   791  SchemaProps: spec.SchemaProps{
   792  Description: "Blah demonstrate a struct with embedded inline struct pointer field.",
   793  Type: []string{"object"},
   794  Properties: map[string]spec.Schema{
   795  "String": {
   796  SchemaProps: spec.SchemaProps{
   797  Description: "A simple string",
   798  Default: "",
   799  Type: []string{"string"},
   800  Format: "",
   801  },
   802  },
   803  },
   804  Required: []string{"String"},
   805  },
   806  },
   807  }
   808  }`)
   809  	})
   810  }
   811  
   812  func TestNestedMapString(t *testing.T) {
   813  	inputFile := `
   814  		package foo
   815  
   816  		// Map sample tests openAPIGen.generateMapProperty method.
   817  		type Blah struct {
   818  			// A sample String to String map
   819  			StringToArray map[string]map[string]string
   820  		}`
   821  
   822  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
   823  		e := packagestest.Export(t, x, []packagestest.Module{{
   824  			Name: "example.com/base/foo",
   825  			Files: map[string]interface{}{
   826  				"foo.go": inputFile,
   827  			},
   828  		}})
   829  		defer e.Cleanup()
   830  
   831  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
   832  		if callErr != nil {
   833  			t.Fatal(callErr)
   834  		}
   835  		if funcErr != nil {
   836  			t.Fatal(funcErr)
   837  		}
   838  		assertEqual(t, callBuffer.String(),
   839  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
   840  		assertEqual(t, funcBuffer.String(),
   841  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
   842  return common.OpenAPIDefinition{
   843  Schema: spec.Schema{
   844  SchemaProps: spec.SchemaProps{
   845  Description: "Map sample tests openAPIGen.generateMapProperty method.",
   846  Type: []string{"object"},
   847  Properties: map[string]spec.Schema{
   848  "StringToArray": {
   849  SchemaProps: spec.SchemaProps{
   850  Description: "A sample String to String map",
   851  Type: []string{"object"},
   852  AdditionalProperties: &spec.SchemaOrBool{
   853  Allows: true,
   854  Schema: &spec.Schema{
   855  SchemaProps: spec.SchemaProps{
   856  Type: []string{"object"},
   857  AdditionalProperties: &spec.SchemaOrBool{
   858  Allows: true,
   859  Schema: &spec.Schema{
   860  SchemaProps: spec.SchemaProps{
   861  Default: "",
   862  Type: []string{"string"},
   863  Format: "",
   864  },
   865  },
   866  },
   867  },
   868  },
   869  },
   870  },
   871  },
   872  },
   873  Required: []string{"StringToArray"},
   874  },
   875  },
   876  }
   877  }`)
   878  	})
   879  }
   880  
   881  func TestNestedMapInt(t *testing.T) {
   882  	inputFile := `
   883  		package foo
   884  
   885  		// Map sample tests openAPIGen.generateMapProperty method.
   886  		type Blah struct {
   887  			// A sample String to String map
   888  			StringToArray map[string]map[string]int
   889  		}`
   890  
   891  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
   892  		e := packagestest.Export(t, x, []packagestest.Module{{
   893  			Name: "example.com/base/foo",
   894  			Files: map[string]interface{}{
   895  				"foo.go": inputFile,
   896  			},
   897  		}})
   898  		defer e.Cleanup()
   899  
   900  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
   901  		if callErr != nil {
   902  			t.Fatal(callErr)
   903  		}
   904  		if funcErr != nil {
   905  			t.Fatal(funcErr)
   906  		}
   907  		assertEqual(t, callBuffer.String(),
   908  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
   909  		assertEqual(t, funcBuffer.String(),
   910  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
   911  return common.OpenAPIDefinition{
   912  Schema: spec.Schema{
   913  SchemaProps: spec.SchemaProps{
   914  Description: "Map sample tests openAPIGen.generateMapProperty method.",
   915  Type: []string{"object"},
   916  Properties: map[string]spec.Schema{
   917  "StringToArray": {
   918  SchemaProps: spec.SchemaProps{
   919  Description: "A sample String to String map",
   920  Type: []string{"object"},
   921  AdditionalProperties: &spec.SchemaOrBool{
   922  Allows: true,
   923  Schema: &spec.Schema{
   924  SchemaProps: spec.SchemaProps{
   925  Type: []string{"object"},
   926  AdditionalProperties: &spec.SchemaOrBool{
   927  Allows: true,
   928  Schema: &spec.Schema{
   929  SchemaProps: spec.SchemaProps{
   930  Default: 0,
   931  Type: []string{"integer"},
   932  Format: "int32",
   933  },
   934  },
   935  },
   936  },
   937  },
   938  },
   939  },
   940  },
   941  },
   942  Required: []string{"StringToArray"},
   943  },
   944  },
   945  }
   946  }`)
   947  	})
   948  }
   949  
   950  func TestNestedMapBoolean(t *testing.T) {
   951  	inputFile := `
   952  		package foo
   953  
   954  		// Map sample tests openAPIGen.generateMapProperty method.
   955  		type Blah struct {
   956  			// A sample String to String map
   957  			StringToArray map[string]map[string]bool
   958  		}`
   959  
   960  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
   961  		e := packagestest.Export(t, x, []packagestest.Module{{
   962  			Name: "example.com/base/foo",
   963  			Files: map[string]interface{}{
   964  				"foo.go": inputFile,
   965  			},
   966  		}})
   967  		defer e.Cleanup()
   968  
   969  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
   970  		if callErr != nil {
   971  			t.Fatal(callErr)
   972  		}
   973  		if funcErr != nil {
   974  			t.Fatal(funcErr)
   975  		}
   976  		assertEqual(t, callBuffer.String(),
   977  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
   978  		assertEqual(t, funcBuffer.String(),
   979  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
   980  return common.OpenAPIDefinition{
   981  Schema: spec.Schema{
   982  SchemaProps: spec.SchemaProps{
   983  Description: "Map sample tests openAPIGen.generateMapProperty method.",
   984  Type: []string{"object"},
   985  Properties: map[string]spec.Schema{
   986  "StringToArray": {
   987  SchemaProps: spec.SchemaProps{
   988  Description: "A sample String to String map",
   989  Type: []string{"object"},
   990  AdditionalProperties: &spec.SchemaOrBool{
   991  Allows: true,
   992  Schema: &spec.Schema{
   993  SchemaProps: spec.SchemaProps{
   994  Type: []string{"object"},
   995  AdditionalProperties: &spec.SchemaOrBool{
   996  Allows: true,
   997  Schema: &spec.Schema{
   998  SchemaProps: spec.SchemaProps{
   999  Default: false,
  1000  Type: []string{"boolean"},
  1001  Format: "",
  1002  },
  1003  },
  1004  },
  1005  },
  1006  },
  1007  },
  1008  },
  1009  },
  1010  },
  1011  Required: []string{"StringToArray"},
  1012  },
  1013  },
  1014  }
  1015  }`)
  1016  	})
  1017  }
  1018  
  1019  func TestFailingSample1(t *testing.T) {
  1020  	inputFile := `
  1021  		package foo
  1022  
  1023  		// Map sample tests openAPIGen.generateMapProperty method.
  1024  		type Blah struct {
  1025  			// A sample String to String map
  1026  			StringToArray map[string]map[string]map[int]string
  1027  		}`
  1028  
  1029  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1030  		e := packagestest.Export(t, x, []packagestest.Module{{
  1031  			Name: "example.com/base/foo",
  1032  			Files: map[string]interface{}{
  1033  				"foo.go": inputFile,
  1034  			},
  1035  		}})
  1036  		defer e.Cleanup()
  1037  
  1038  		_, funcErr, _, _, _ := testOpenAPITypeWriter(t, e.Config)
  1039  		if funcErr == nil {
  1040  			t.Fatalf("An error was expected")
  1041  		}
  1042  		assertEqual(t,
  1043  			"failed to generate map property in example.com/base/foo.Blah: StringToArray: map with non-string keys are not supported by OpenAPI in map[int]string",
  1044  			funcErr.Error())
  1045  	})
  1046  }
  1047  
  1048  func TestFailingSample2(t *testing.T) {
  1049  	inputFile := `
  1050  		package foo
  1051  
  1052  		// Map sample tests openAPIGen.generateMapProperty method.
  1053  		type Blah struct {
  1054  			// A sample String to String map
  1055  			StringToArray map[int]string
  1056  		}`
  1057  
  1058  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1059  		e := packagestest.Export(t, x, []packagestest.Module{{
  1060  			Name: "example.com/base/foo",
  1061  			Files: map[string]interface{}{
  1062  				"foo.go": inputFile,
  1063  			},
  1064  		}})
  1065  		defer e.Cleanup()
  1066  
  1067  		_, funcErr, _, _, _ := testOpenAPITypeWriter(t, e.Config)
  1068  		if funcErr == nil {
  1069  			t.Fatalf("An error was expected")
  1070  		}
  1071  		assertEqual(t,
  1072  			"failed to generate map property in example.com/base/foo.Blah: StringToArray: map with non-string keys are not supported by OpenAPI in map[int]string",
  1073  			funcErr.Error())
  1074  	})
  1075  }
  1076  
  1077  func TestFailingDefaultEnforced(t *testing.T) {
  1078  	tests := []struct {
  1079  		definition    string
  1080  		expectedError string
  1081  	}{{
  1082  		definition: `
  1083  			package foo
  1084  
  1085  			type Blah struct {
  1086  				// +default=5
  1087  				Int int
  1088  			}`,
  1089  		expectedError: "failed to generate default in example.com/base/foo.Blah: Int: invalid default value (5) for non-pointer/non-omitempty. If specified, must be: 0",
  1090  	}, {
  1091  		definition: `
  1092  			package foo
  1093  
  1094  			type Blah struct {
  1095  				// +default={"foo": 5}
  1096  				Struct struct{
  1097  					foo int
  1098  				}
  1099  			}`,
  1100  		expectedError: `failed to generate default in example.com/base/foo.Blah: Struct: invalid default value (map[string]interface {}{"foo":5}) for non-pointer/non-omitempty. If specified, must be: {}`,
  1101  	}, {
  1102  		definition: `
  1103  			package foo
  1104  
  1105  			type Blah struct {
  1106  				List []Item
  1107  
  1108  			}
  1109  
  1110  			// +default="foo"
  1111  			type Item string`,
  1112  		expectedError: `failed to generate slice property in example.com/base/foo.Blah: List: invalid default value ("foo") for non-pointer/non-omitempty. If specified, must be: ""`,
  1113  	}, {
  1114  		definition: `
  1115  			package foo
  1116  
  1117  			type Blah struct {
  1118  				Map map[string]Item
  1119  
  1120  			}
  1121  
  1122  			// +default="foo"
  1123  			type Item string`,
  1124  		expectedError: `failed to generate map property in example.com/base/foo.Blah: Map: invalid default value ("foo") for non-pointer/non-omitempty. If specified, must be: ""`,
  1125  	}}
  1126  
  1127  	for i, test := range tests {
  1128  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
  1129  			packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1130  				e := packagestest.Export(t, x, []packagestest.Module{{
  1131  					Name: "example.com/base/foo",
  1132  					Files: map[string]interface{}{
  1133  						"foo.go": test.definition,
  1134  					},
  1135  				}})
  1136  				defer e.Cleanup()
  1137  
  1138  				_, funcErr, _, _, _ := testOpenAPITypeWriter(t, e.Config)
  1139  				if funcErr == nil {
  1140  					t.Fatalf("An error was expected")
  1141  				}
  1142  				assertEqual(t, test.expectedError, funcErr.Error())
  1143  			})
  1144  		})
  1145  	}
  1146  }
  1147  
  1148  func TestCustomDef(t *testing.T) {
  1149  	inputFile := `
  1150  		package foo
  1151  
  1152  		import openapi "k8s.io/kube-openapi/pkg/common"
  1153  
  1154  		type Blah struct {
  1155  		}
  1156  
  1157  		func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition {
  1158  			return openapi.OpenAPIDefinition{
  1159  				Schema: spec.Schema{
  1160  					SchemaProps: spec.SchemaProps{
  1161  						Type:   []string{"string"},
  1162  						Format: "date-time",
  1163  					},
  1164  				},
  1165  			}
  1166  		}`
  1167  	commonFile := `package common
  1168  
  1169  		type OpenAPIDefinition struct {}`
  1170  
  1171  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1172  		e := packagestest.Export(t, x, []packagestest.Module{{
  1173  			Name: "example.com/base/foo",
  1174  			Files: map[string]interface{}{
  1175  				"foo.go": inputFile,
  1176  			},
  1177  		}, {
  1178  			Name: "k8s.io/kube-openapi/pkg/common",
  1179  			Files: map[string]interface{}{
  1180  				"common.go": commonFile,
  1181  			},
  1182  		}})
  1183  		defer e.Cleanup()
  1184  
  1185  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  1186  		if callErr != nil {
  1187  			t.Fatal(callErr)
  1188  		}
  1189  		if funcErr != nil {
  1190  			t.Fatal(funcErr)
  1191  		}
  1192  		assertEqual(t, callBuffer.String(),
  1193  			`"example.com/base/foo.Blah": foo.Blah{}.OpenAPIDefinition(),`)
  1194  		assertEqual(t, "", funcBuffer.String())
  1195  	})
  1196  }
  1197  
  1198  func TestCustomDefV3(t *testing.T) {
  1199  	inputFile := `
  1200  		package foo
  1201  
  1202  		import openapi "k8s.io/kube-openapi/pkg/common"
  1203  
  1204  		type Blah struct {
  1205  		}
  1206  
  1207  		func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition {
  1208  			return openapi.OpenAPIDefinition{
  1209  				Schema: spec.Schema{
  1210  					SchemaProps: spec.SchemaProps{
  1211  						Type:   []string{"string"},
  1212  						Format: "date-time",
  1213  					},
  1214  				},
  1215  			}
  1216  		}`
  1217  	commonFile := `package common
  1218  
  1219  		type OpenAPIDefinition struct {}`
  1220  
  1221  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1222  		e := packagestest.Export(t, x, []packagestest.Module{{
  1223  			Name: "example.com/base/foo",
  1224  			Files: map[string]interface{}{
  1225  				"foo.go": inputFile,
  1226  			},
  1227  		}, {
  1228  			Name: "k8s.io/kube-openapi/pkg/common",
  1229  			Files: map[string]interface{}{
  1230  				"common.go": commonFile,
  1231  			},
  1232  		}})
  1233  		defer e.Cleanup()
  1234  
  1235  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  1236  		if callErr != nil {
  1237  			t.Fatal(callErr)
  1238  		}
  1239  		if funcErr != nil {
  1240  			t.Fatal(funcErr)
  1241  		}
  1242  		assertEqual(t, callBuffer.String(),
  1243  			`"example.com/base/foo.Blah": foo.Blah{}.OpenAPIV3Definition(),`)
  1244  		assertEqual(t, "", funcBuffer.String())
  1245  	})
  1246  }
  1247  
  1248  func TestCustomDefV2AndV3(t *testing.T) {
  1249  	inputFile := `
  1250  		package foo
  1251  
  1252  		import openapi "k8s.io/kube-openapi/pkg/common"
  1253  
  1254  		type Blah struct {
  1255  		}
  1256  
  1257  		func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition {
  1258  			return openapi.OpenAPIDefinition{
  1259  				Schema: spec.Schema{
  1260  					SchemaProps: spec.SchemaProps{
  1261  						Type:   []string{"string"},
  1262  						Format: "date-time",
  1263  					},
  1264  				},
  1265  			}
  1266  		}
  1267  
  1268  		func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition {
  1269  			return openapi.OpenAPIDefinition{
  1270  				Schema: spec.Schema{
  1271  					SchemaProps: spec.SchemaProps{
  1272  						Type:   []string{"string"},
  1273  						Format: "date-time",
  1274  					},
  1275  				},
  1276  			}
  1277  		}`
  1278  	commonFile := `package common
  1279  
  1280  		type OpenAPIDefinition struct {}`
  1281  
  1282  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1283  		e := packagestest.Export(t, x, []packagestest.Module{{
  1284  			Name: "example.com/base/foo",
  1285  			Files: map[string]interface{}{
  1286  				"foo.go": inputFile,
  1287  			},
  1288  		}, {
  1289  			Name: "k8s.io/kube-openapi/pkg/common",
  1290  			Files: map[string]interface{}{
  1291  				"common.go": commonFile,
  1292  			},
  1293  		}})
  1294  		defer e.Cleanup()
  1295  
  1296  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  1297  		if callErr != nil {
  1298  			t.Fatal(callErr)
  1299  		}
  1300  		if funcErr != nil {
  1301  			t.Fatal(funcErr)
  1302  		}
  1303  		assertEqual(t, callBuffer.String(),
  1304  			`"example.com/base/foo.Blah": common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), foo.Blah{}.OpenAPIDefinition()),`)
  1305  		assertEqual(t, "", funcBuffer.String())
  1306  	})
  1307  }
  1308  
  1309  func TestCustomDefs(t *testing.T) {
  1310  	inputFile := `
  1311  		package foo
  1312  
  1313  		// Blah is a custom type
  1314  		type Blah struct {
  1315  		}
  1316  
  1317  		func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} }
  1318  		func (_ Blah) OpenAPISchemaFormat() string { return "date-time" }`
  1319  
  1320  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1321  		e := packagestest.Export(t, x, []packagestest.Module{{
  1322  			Name: "example.com/base/foo",
  1323  			Files: map[string]interface{}{
  1324  				"foo.go": inputFile,
  1325  			},
  1326  		}})
  1327  		defer e.Cleanup()
  1328  
  1329  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  1330  		if callErr != nil {
  1331  			t.Fatal(callErr)
  1332  		}
  1333  		if funcErr != nil {
  1334  			t.Fatal(funcErr)
  1335  		}
  1336  		assertEqual(t, callBuffer.String(),
  1337  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
  1338  		assertEqual(t, funcBuffer.String(),
  1339  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  1340  return common.OpenAPIDefinition{
  1341  Schema: spec.Schema{
  1342  SchemaProps: spec.SchemaProps{
  1343  Description: "Blah is a custom type",
  1344  Type:foo.Blah{}.OpenAPISchemaType(),
  1345  Format:foo.Blah{}.OpenAPISchemaFormat(),
  1346  },
  1347  },
  1348  }
  1349  }`)
  1350  	})
  1351  }
  1352  
  1353  func TestCustomDefsV3(t *testing.T) {
  1354  	inputFile := `
  1355  		package foo
  1356  
  1357  		import openapi "k8s.io/kube-openapi/pkg/common"
  1358  
  1359  		// Blah is a custom type
  1360  		type Blah struct {
  1361  		}
  1362  
  1363  		func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition {
  1364  			return openapi.OpenAPIDefinition{
  1365  				Schema: spec.Schema{
  1366  					SchemaProps: spec.SchemaProps{
  1367  						Type:   []string{"string"},
  1368  						Format: "date-time",
  1369  					},
  1370  				},
  1371  			}
  1372  		}
  1373  
  1374  		func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} }
  1375  		func (_ Blah) OpenAPISchemaFormat() string { return "date-time" }`
  1376  	commonFile := `package common
  1377  
  1378  		type OpenAPIDefinition struct {}`
  1379  
  1380  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1381  		e := packagestest.Export(t, x, []packagestest.Module{{
  1382  			Name: "example.com/base/foo",
  1383  			Files: map[string]interface{}{
  1384  				"foo.go": inputFile,
  1385  			},
  1386  		}, {
  1387  			Name: "k8s.io/kube-openapi/pkg/common",
  1388  			Files: map[string]interface{}{
  1389  				"common.go": commonFile,
  1390  			},
  1391  		}})
  1392  		defer e.Cleanup()
  1393  
  1394  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  1395  		if callErr != nil {
  1396  			t.Fatal(callErr)
  1397  		}
  1398  		if funcErr != nil {
  1399  			t.Fatal(funcErr)
  1400  		}
  1401  		assertEqual(t, callBuffer.String(),
  1402  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
  1403  		assertEqual(t, funcBuffer.String(),
  1404  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  1405  return common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), common.OpenAPIDefinition{
  1406  Schema: spec.Schema{
  1407  SchemaProps: spec.SchemaProps{
  1408  Description: "Blah is a custom type",
  1409  Type:foo.Blah{}.OpenAPISchemaType(),
  1410  Format:foo.Blah{}.OpenAPISchemaFormat(),
  1411  },
  1412  },
  1413  })
  1414  }`)
  1415  	})
  1416  }
  1417  
  1418  func TestV3OneOfTypes(t *testing.T) {
  1419  	inputFile := `
  1420  		package foo
  1421  
  1422  		// Blah is a custom type
  1423  		type Blah struct {
  1424  		}
  1425  
  1426  		func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} }
  1427  		func (_ Blah) OpenAPISchemaFormat() string { return "date-time" }
  1428  		func (_ Blah) OpenAPIV3OneOfTypes() []string { return []string{"string", "number"} }`
  1429  
  1430  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1431  		e := packagestest.Export(t, x, []packagestest.Module{{
  1432  			Name: "example.com/base/foo",
  1433  			Files: map[string]interface{}{
  1434  				"foo.go": inputFile,
  1435  			},
  1436  		}})
  1437  		defer e.Cleanup()
  1438  
  1439  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  1440  		if callErr != nil {
  1441  			t.Fatal(callErr)
  1442  		}
  1443  		if funcErr != nil {
  1444  			t.Fatal(funcErr)
  1445  		}
  1446  		assertEqual(t, callBuffer.String(),
  1447  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
  1448  		assertEqual(t, funcBuffer.String(),
  1449  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  1450  return common.EmbedOpenAPIDefinitionIntoV2Extension(common.OpenAPIDefinition{
  1451  Schema: spec.Schema{
  1452  SchemaProps: spec.SchemaProps{
  1453  Description: "Blah is a custom type",
  1454  OneOf:common.GenerateOpenAPIV3OneOfSchema(foo.Blah{}.OpenAPIV3OneOfTypes()),
  1455  Format:foo.Blah{}.OpenAPISchemaFormat(),
  1456  },
  1457  },
  1458  },common.OpenAPIDefinition{
  1459  Schema: spec.Schema{
  1460  SchemaProps: spec.SchemaProps{
  1461  Description: "Blah is a custom type",
  1462  Type:foo.Blah{}.OpenAPISchemaType(),
  1463  Format:foo.Blah{}.OpenAPISchemaFormat(),
  1464  },
  1465  },
  1466  })
  1467  }`)
  1468  	})
  1469  }
  1470  
  1471  func TestPointer(t *testing.T) {
  1472  	inputFile := `
  1473  		package foo
  1474  
  1475  		// PointerSample demonstrate pointer's properties
  1476  		type Blah struct {
  1477  			// A string pointer
  1478  			StringPointer *string
  1479  			// A struct pointer
  1480  			StructPointer *Blah
  1481  			// A slice pointer
  1482  			SlicePointer *[]string
  1483  			// A map pointer
  1484  			MapPointer *map[string]string
  1485  		}`
  1486  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1487  		e := packagestest.Export(t, x, []packagestest.Module{{
  1488  			Name: "example.com/base/foo",
  1489  			Files: map[string]interface{}{
  1490  				"foo.go": inputFile,
  1491  			},
  1492  		}})
  1493  		defer e.Cleanup()
  1494  
  1495  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  1496  		if callErr != nil {
  1497  			t.Fatal(callErr)
  1498  		}
  1499  		if funcErr != nil {
  1500  			t.Fatal(funcErr)
  1501  		}
  1502  		assertEqual(t, callBuffer.String(),
  1503  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
  1504  		assertEqual(t, funcBuffer.String(),
  1505  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  1506  return common.OpenAPIDefinition{
  1507  Schema: spec.Schema{
  1508  SchemaProps: spec.SchemaProps{
  1509  Description: "PointerSample demonstrate pointer's properties",
  1510  Type: []string{"object"},
  1511  Properties: map[string]spec.Schema{
  1512  "StringPointer": {
  1513  SchemaProps: spec.SchemaProps{
  1514  Description: "A string pointer",
  1515  Type: []string{"string"},
  1516  Format: "",
  1517  },
  1518  },
  1519  "StructPointer": {
  1520  SchemaProps: spec.SchemaProps{
  1521  Description: "A struct pointer",
  1522  Ref: ref("example.com/base/foo.Blah"),
  1523  },
  1524  },
  1525  "SlicePointer": {
  1526  SchemaProps: spec.SchemaProps{
  1527  Description: "A slice pointer",
  1528  Type: []string{"array"},
  1529  Items: &spec.SchemaOrArray{
  1530  Schema: &spec.Schema{
  1531  SchemaProps: spec.SchemaProps{
  1532  Default: "",
  1533  Type: []string{"string"},
  1534  Format: "",
  1535  },
  1536  },
  1537  },
  1538  },
  1539  },
  1540  "MapPointer": {
  1541  SchemaProps: spec.SchemaProps{
  1542  Description: "A map pointer",
  1543  Type: []string{"object"},
  1544  AdditionalProperties: &spec.SchemaOrBool{
  1545  Allows: true,
  1546  Schema: &spec.Schema{
  1547  SchemaProps: spec.SchemaProps{
  1548  Default: "",
  1549  Type: []string{"string"},
  1550  Format: "",
  1551  },
  1552  },
  1553  },
  1554  },
  1555  },
  1556  },
  1557  Required: []string{"StringPointer","StructPointer","SlicePointer","MapPointer"},
  1558  },
  1559  },
  1560  Dependencies: []string{
  1561  "example.com/base/foo.Blah",},
  1562  }
  1563  }`)
  1564  	})
  1565  }
  1566  
  1567  func TestNestedLists(t *testing.T) {
  1568  	inputFile := `
  1569  		package foo
  1570  
  1571  		// Blah is a test.
  1572  		// +k8s:openapi-gen=true
  1573  		// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
  1574  		type Blah struct {
  1575  			// Nested list
  1576  			NestedList [][]int64
  1577  		}`
  1578  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1579  		e := packagestest.Export(t, x, []packagestest.Module{{
  1580  			Name: "example.com/base/foo",
  1581  			Files: map[string]interface{}{
  1582  				"foo.go": inputFile,
  1583  			},
  1584  		}})
  1585  		defer e.Cleanup()
  1586  
  1587  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  1588  		if callErr != nil {
  1589  			t.Fatal(callErr)
  1590  		}
  1591  		if funcErr != nil {
  1592  			t.Fatal(funcErr)
  1593  		}
  1594  		assertEqual(t, callBuffer.String(),
  1595  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
  1596  		assertEqual(t, funcBuffer.String(),
  1597  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  1598  return common.OpenAPIDefinition{
  1599  Schema: spec.Schema{
  1600  SchemaProps: spec.SchemaProps{
  1601  Description: "Blah is a test.",
  1602  Type: []string{"object"},
  1603  Properties: map[string]spec.Schema{
  1604  "NestedList": {
  1605  SchemaProps: spec.SchemaProps{
  1606  Description: "Nested list",
  1607  Type: []string{"array"},
  1608  Items: &spec.SchemaOrArray{
  1609  Schema: &spec.Schema{
  1610  SchemaProps: spec.SchemaProps{
  1611  Type: []string{"array"},
  1612  Items: &spec.SchemaOrArray{
  1613  Schema: &spec.Schema{
  1614  SchemaProps: spec.SchemaProps{
  1615  Default: 0,
  1616  Type: []string{"integer"},
  1617  Format: "int64",
  1618  },
  1619  },
  1620  },
  1621  },
  1622  },
  1623  },
  1624  },
  1625  },
  1626  },
  1627  Required: []string{"NestedList"},
  1628  },
  1629  VendorExtensible: spec.VendorExtensible{
  1630  Extensions: spec.Extensions{
  1631  "x-kubernetes-type-tag": "type_test",
  1632  },
  1633  },
  1634  },
  1635  }
  1636  }`)
  1637  	})
  1638  }
  1639  
  1640  func TestNestListOfMaps(t *testing.T) {
  1641  	inputFile := `
  1642  		package foo
  1643  
  1644  		// Blah is a test.
  1645  		// +k8s:openapi-gen=true
  1646  		// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
  1647  		type Blah struct {
  1648  			// Nested list of maps
  1649  			NestedListOfMaps [][]map[string]string
  1650  		}`
  1651  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1652  		e := packagestest.Export(t, x, []packagestest.Module{{
  1653  			Name: "example.com/base/foo",
  1654  			Files: map[string]interface{}{
  1655  				"foo.go": inputFile,
  1656  			},
  1657  		}})
  1658  		defer e.Cleanup()
  1659  
  1660  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  1661  		if callErr != nil {
  1662  			t.Fatal(callErr)
  1663  		}
  1664  		if funcErr != nil {
  1665  			t.Fatal(funcErr)
  1666  		}
  1667  		assertEqual(t, callBuffer.String(),
  1668  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
  1669  		assertEqual(t, funcBuffer.String(),
  1670  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  1671  return common.OpenAPIDefinition{
  1672  Schema: spec.Schema{
  1673  SchemaProps: spec.SchemaProps{
  1674  Description: "Blah is a test.",
  1675  Type: []string{"object"},
  1676  Properties: map[string]spec.Schema{
  1677  "NestedListOfMaps": {
  1678  SchemaProps: spec.SchemaProps{
  1679  Description: "Nested list of maps",
  1680  Type: []string{"array"},
  1681  Items: &spec.SchemaOrArray{
  1682  Schema: &spec.Schema{
  1683  SchemaProps: spec.SchemaProps{
  1684  Type: []string{"array"},
  1685  Items: &spec.SchemaOrArray{
  1686  Schema: &spec.Schema{
  1687  SchemaProps: spec.SchemaProps{
  1688  Type: []string{"object"},
  1689  AdditionalProperties: &spec.SchemaOrBool{
  1690  Allows: true,
  1691  Schema: &spec.Schema{
  1692  SchemaProps: spec.SchemaProps{
  1693  Default: "",
  1694  Type: []string{"string"},
  1695  Format: "",
  1696  },
  1697  },
  1698  },
  1699  },
  1700  },
  1701  },
  1702  },
  1703  },
  1704  },
  1705  },
  1706  },
  1707  },
  1708  Required: []string{"NestedListOfMaps"},
  1709  },
  1710  VendorExtensible: spec.VendorExtensible{
  1711  Extensions: spec.Extensions{
  1712  "x-kubernetes-type-tag": "type_test",
  1713  },
  1714  },
  1715  },
  1716  }
  1717  }`)
  1718  	})
  1719  }
  1720  
  1721  func TestExtensions(t *testing.T) {
  1722  	inputFile := `
  1723  		package foo
  1724  
  1725  		// Blah is a test.
  1726  		// +k8s:openapi-gen=true
  1727  		// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
  1728  		type Blah struct {
  1729  			// a member with a list type with two map keys
  1730  			// +listType=map
  1731  			// +listMapKey=port
  1732  			// +listMapKey=protocol
  1733  			WithListField []string
  1734  
  1735  			// another member with a list type with one map key
  1736  			// +listType=map
  1737  			// +listMapKey=port
  1738  			WithListField2 []string
  1739  		}`
  1740  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1741  		e := packagestest.Export(t, x, []packagestest.Module{{
  1742  			Name: "example.com/base/foo",
  1743  			Files: map[string]interface{}{
  1744  				"foo.go": inputFile,
  1745  			},
  1746  		}})
  1747  		defer e.Cleanup()
  1748  
  1749  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  1750  		if callErr != nil {
  1751  			t.Fatal(callErr)
  1752  		}
  1753  		if funcErr != nil {
  1754  			t.Fatal(funcErr)
  1755  		}
  1756  		assertEqual(t, callBuffer.String(),
  1757  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
  1758  		assertEqual(t, funcBuffer.String(),
  1759  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  1760  return common.OpenAPIDefinition{
  1761  Schema: spec.Schema{
  1762  SchemaProps: spec.SchemaProps{
  1763  Description: "Blah is a test.",
  1764  Type: []string{"object"},
  1765  Properties: map[string]spec.Schema{
  1766  "WithListField": {
  1767  VendorExtensible: spec.VendorExtensible{
  1768  Extensions: spec.Extensions{
  1769  "x-kubernetes-list-map-keys": []interface{}{
  1770  "port",
  1771  "protocol",
  1772  },
  1773  "x-kubernetes-list-type": "map",
  1774  },
  1775  },
  1776  SchemaProps: spec.SchemaProps{
  1777  Description: "a member with a list type with two map keys",
  1778  Type: []string{"array"},
  1779  Items: &spec.SchemaOrArray{
  1780  Schema: &spec.Schema{
  1781  SchemaProps: spec.SchemaProps{
  1782  Default: "",
  1783  Type: []string{"string"},
  1784  Format: "",
  1785  },
  1786  },
  1787  },
  1788  },
  1789  },
  1790  "WithListField2": {
  1791  VendorExtensible: spec.VendorExtensible{
  1792  Extensions: spec.Extensions{
  1793  "x-kubernetes-list-map-keys": []interface{}{
  1794  "port",
  1795  },
  1796  "x-kubernetes-list-type": "map",
  1797  },
  1798  },
  1799  SchemaProps: spec.SchemaProps{
  1800  Description: "another member with a list type with one map key",
  1801  Type: []string{"array"},
  1802  Items: &spec.SchemaOrArray{
  1803  Schema: &spec.Schema{
  1804  SchemaProps: spec.SchemaProps{
  1805  Default: "",
  1806  Type: []string{"string"},
  1807  Format: "",
  1808  },
  1809  },
  1810  },
  1811  },
  1812  },
  1813  },
  1814  Required: []string{"WithListField","WithListField2"},
  1815  },
  1816  VendorExtensible: spec.VendorExtensible{
  1817  Extensions: spec.Extensions{
  1818  "x-kubernetes-type-tag": "type_test",
  1819  },
  1820  },
  1821  },
  1822  }
  1823  }`)
  1824  	})
  1825  }
  1826  
  1827  func TestUnion(t *testing.T) {
  1828  	inputFile := `
  1829  		package foo
  1830  
  1831  		// Blah is a test.
  1832  		// +k8s:openapi-gen=true
  1833  		// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
  1834  		// +union
  1835  		type Blah struct {
  1836  			// +unionDiscriminator
  1837  			Discriminator *string ` + "`" + `json:"discriminator"` + "`" + `
  1838  				// +optional
  1839  				Numeric int ` + "`" + `json:"numeric"` + "`" + `
  1840  				// +optional
  1841  				String string ` + "`" + `json:"string"` + "`" + `
  1842  				// +optional
  1843  				Float float64 ` + "`" + `json:"float"` + "`" + `
  1844  		}`
  1845  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1846  		e := packagestest.Export(t, x, []packagestest.Module{{
  1847  			Name: "example.com/base/foo",
  1848  			Files: map[string]interface{}{
  1849  				"foo.go": inputFile,
  1850  			},
  1851  		}})
  1852  		defer e.Cleanup()
  1853  
  1854  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  1855  		if callErr != nil {
  1856  			t.Fatal(callErr)
  1857  		}
  1858  		if funcErr != nil {
  1859  			t.Fatal(funcErr)
  1860  		}
  1861  		assertEqual(t, callBuffer.String(),
  1862  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
  1863  		assertEqual(t, funcBuffer.String(),
  1864  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  1865  return common.OpenAPIDefinition{
  1866  Schema: spec.Schema{
  1867  SchemaProps: spec.SchemaProps{
  1868  Description: "Blah is a test.",
  1869  Type: []string{"object"},
  1870  Properties: map[string]spec.Schema{
  1871  "discriminator": {
  1872  SchemaProps: spec.SchemaProps{
  1873  Type: []string{"string"},
  1874  Format: "",
  1875  },
  1876  },
  1877  "numeric": {
  1878  SchemaProps: spec.SchemaProps{
  1879  Default: 0,
  1880  Type: []string{"integer"},
  1881  Format: "int32",
  1882  },
  1883  },
  1884  "string": {
  1885  SchemaProps: spec.SchemaProps{
  1886  Default: "",
  1887  Type: []string{"string"},
  1888  Format: "",
  1889  },
  1890  },
  1891  "float": {
  1892  SchemaProps: spec.SchemaProps{
  1893  Default: 0,
  1894  Type: []string{"number"},
  1895  Format: "double",
  1896  },
  1897  },
  1898  },
  1899  Required: []string{"discriminator"},
  1900  },
  1901  VendorExtensible: spec.VendorExtensible{
  1902  Extensions: spec.Extensions{
  1903  "x-kubernetes-type-tag": "type_test",
  1904  "x-kubernetes-unions": []interface{}{
  1905  map[string]interface{}{
  1906  "discriminator": "discriminator",
  1907  "fields-to-discriminateBy": map[string]interface{}{
  1908  "float": "Float",
  1909  "numeric": "Numeric",
  1910  "string": "String",
  1911  },
  1912  },
  1913  },
  1914  },
  1915  },
  1916  },
  1917  }
  1918  }`)
  1919  	})
  1920  }
  1921  
  1922  func TestEnumAlias(t *testing.T) {
  1923  	inputFile := `
  1924  		package foo
  1925  
  1926  		import "example.com/base/bar"
  1927  
  1928  		// EnumType is the enumType.
  1929  		// +enum
  1930  		type EnumType = bar.EnumType
  1931  
  1932  		// EnumA is a.
  1933  		const EnumA EnumType = bar.EnumA
  1934  		// EnumB is b.
  1935  		const EnumB EnumType = bar.EnumB
  1936  
  1937  		// Blah is a test.
  1938  		// +k8s:openapi-gen=true
  1939  		type Blah struct {
  1940  			// Value is the value.
  1941  			Value EnumType
  1942  		}`
  1943  	otherFile := `
  1944  		package bar
  1945  
  1946  		// EnumType is the enumType.
  1947  		// +enum
  1948  		type EnumType string
  1949  
  1950  		// EnumA is a.
  1951  		const EnumA EnumType = "a"
  1952  		// EnumB is b.
  1953  		const EnumB EnumType = "b"`
  1954  
  1955  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  1956  		e := packagestest.Export(t, x, []packagestest.Module{{
  1957  			Name: "example.com/base/foo",
  1958  			Files: map[string]interface{}{
  1959  				"foo.go": inputFile,
  1960  			},
  1961  		}, {
  1962  			Name: "example.com/base/bar",
  1963  			Files: map[string]interface{}{
  1964  				"bar.go": otherFile,
  1965  			},
  1966  		}})
  1967  		defer e.Cleanup()
  1968  
  1969  		callErr, funcErr, _, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  1970  		if callErr != nil {
  1971  			t.Fatal(callErr)
  1972  		}
  1973  		if funcErr != nil {
  1974  			t.Fatal(funcErr)
  1975  		}
  1976  		assertEqual(t, funcBuffer.String(),
  1977  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  1978  return common.OpenAPIDefinition{
  1979  Schema: spec.Schema{
  1980  SchemaProps: spec.SchemaProps{
  1981  Description: "Blah is a test.",
  1982  Type: []string{"object"},
  1983  Properties: map[string]spec.Schema{
  1984  "Value": {
  1985  SchemaProps: spec.SchemaProps{`+"\n"+
  1986  				"Description: \"Value is the value.\\n\\nPossible enum values:\\n - `\\\"a\\\"` is a.\\n - `\\\"b\\\"` is b.\","+`
  1987  Default: "",
  1988  Type: []string{"string"},
  1989  Format: "",
  1990  Enum: []interface{}{"a", "b"},
  1991  },
  1992  },
  1993  },
  1994  Required: []string{"Value"},
  1995  },
  1996  },
  1997  }
  1998  }`)
  1999  	})
  2000  }
  2001  
  2002  func TestEnum(t *testing.T) {
  2003  	inputFile := `
  2004  		package foo
  2005  
  2006  		// EnumType is the enumType.
  2007  		// +enum
  2008  		type EnumType string
  2009  
  2010  		// EnumA is a.
  2011  		const EnumA EnumType = "a"
  2012  		// EnumB is b.
  2013  		const EnumB EnumType = "b"
  2014  
  2015  		// Blah is a test.
  2016  		// +k8s:openapi-gen=true
  2017  		// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
  2018  		type Blah struct {
  2019  		  // Value is the value.
  2020  			Value EnumType
  2021  			NoCommentEnum EnumType
  2022  		  // +optional
  2023  			OptionalEnum *EnumType
  2024  			List []EnumType
  2025  			Map map[string]EnumType
  2026  		}`
  2027  
  2028  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  2029  		e := packagestest.Export(t, x, []packagestest.Module{{
  2030  			Name: "example.com/base/foo",
  2031  			Files: map[string]interface{}{
  2032  				"foo.go": inputFile,
  2033  			},
  2034  		}})
  2035  		defer e.Cleanup()
  2036  
  2037  		callErr, funcErr, _, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  2038  		if callErr != nil {
  2039  			t.Fatal(callErr)
  2040  		}
  2041  		if funcErr != nil {
  2042  			t.Fatal(funcErr)
  2043  		}
  2044  		assertEqual(t, funcBuffer.String(),
  2045  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  2046  return common.OpenAPIDefinition{
  2047  Schema: spec.Schema{
  2048  SchemaProps: spec.SchemaProps{
  2049  Description: "Blah is a test.",
  2050  Type: []string{"object"},
  2051  Properties: map[string]spec.Schema{
  2052  "Value": {
  2053  SchemaProps: spec.SchemaProps{`+"\n"+
  2054  				"Description: \"Value is the value.\\n\\nPossible enum values:\\n - `\\\"a\\\"` is a.\\n - `\\\"b\\\"` is b.\","+`
  2055  Default: "",
  2056  Type: []string{"string"},
  2057  Format: "",
  2058  Enum: []interface{}{"a", "b"},
  2059  },
  2060  },
  2061  "NoCommentEnum": {
  2062  SchemaProps: spec.SchemaProps{`+"\n"+
  2063  				"Description: \"Possible enum values:\\n - `\\\"a\\\"` is a.\\n - `\\\"b\\\"` is b.\","+`
  2064  Default: "",
  2065  Type: []string{"string"},
  2066  Format: "",
  2067  Enum: []interface{}{"a", "b"},
  2068  },
  2069  },
  2070  "OptionalEnum": {
  2071  SchemaProps: spec.SchemaProps{`+"\n"+
  2072  				"Description: \"Possible enum values:\\n - `\\\"a\\\"` is a.\\n - `\\\"b\\\"` is b.\","+`
  2073  Type: []string{"string"},
  2074  Format: "",
  2075  Enum: []interface{}{"a", "b"},
  2076  },
  2077  },
  2078  "List": {
  2079  SchemaProps: spec.SchemaProps{
  2080  Type: []string{"array"},
  2081  Items: &spec.SchemaOrArray{
  2082  Schema: &spec.Schema{
  2083  SchemaProps: spec.SchemaProps{
  2084  Default: "",
  2085  Type: []string{"string"},
  2086  Format: "",
  2087  Enum: []interface{}{"a", "b"},
  2088  },
  2089  },
  2090  },
  2091  },
  2092  },
  2093  "Map": {
  2094  SchemaProps: spec.SchemaProps{
  2095  Type: []string{"object"},
  2096  AdditionalProperties: &spec.SchemaOrBool{
  2097  Allows: true,
  2098  Schema: &spec.Schema{
  2099  SchemaProps: spec.SchemaProps{
  2100  Default: "",
  2101  Type: []string{"string"},
  2102  Format: "",
  2103  Enum: []interface{}{"a", "b"},
  2104  },
  2105  },
  2106  },
  2107  },
  2108  },
  2109  },
  2110  Required: []string{"Value","NoCommentEnum","List","Map"},
  2111  },
  2112  VendorExtensible: spec.VendorExtensible{
  2113  Extensions: spec.Extensions{
  2114  "x-kubernetes-type-tag": "type_test",
  2115  },
  2116  },
  2117  },
  2118  }
  2119  }`)
  2120  	})
  2121  }
  2122  
  2123  func TestSymbolReference(t *testing.T) {
  2124  	inputFile := `
  2125  		package foo
  2126  
  2127  		// +k8s:openapi-gen=true
  2128  		type Blah struct {
  2129  			// +default="A Default Value"
  2130  			// +optional
  2131  			Value *string
  2132  
  2133  			// User constant local to the output package fully qualified
  2134  			// +default=ref(example.com/base/output.MyConst)
  2135  			// +optional
  2136  			FullyQualifiedOutputValue *string
  2137  
  2138  			// Local to types but not to output
  2139  			// +default=ref(MyConst)
  2140  			// +optional
  2141  			LocalValue *string
  2142  
  2143  			// +default=ref(example.com/base/foo.MyConst)
  2144  			// +optional
  2145  			FullyQualifiedLocalValue *string
  2146  
  2147  			// +default=ref(k8s.io/api/v1.TerminationPathDefault)
  2148  			// +optional
  2149  			FullyQualifiedExternalValue *string
  2150  		}`
  2151  
  2152  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  2153  		e := packagestest.Export(t, x, []packagestest.Module{{
  2154  			Name: "example.com/base/foo",
  2155  			Files: map[string]interface{}{
  2156  				"foo.go": inputFile,
  2157  			},
  2158  		}})
  2159  		defer e.Cleanup()
  2160  
  2161  		callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config)
  2162  		if funcErr != nil {
  2163  			t.Fatalf("Unexpected funcErr: %v", funcErr)
  2164  		}
  2165  		if callErr != nil {
  2166  			t.Fatalf("Unexpected callErr: %v", callErr)
  2167  		}
  2168  		expImports := []string{
  2169  			`foo "example.com/base/foo"`,
  2170  			`v1 "k8s.io/api/v1"`,
  2171  			`common "k8s.io/kube-openapi/pkg/common"`,
  2172  			`spec "k8s.io/kube-openapi/pkg/validation/spec"`,
  2173  		}
  2174  		if !cmp.Equal(imports, expImports) {
  2175  			t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports))
  2176  		}
  2177  
  2178  		if formatted, err := format.Source(funcBuffer.Bytes()); err != nil {
  2179  			t.Fatal(err)
  2180  		} else {
  2181  			assertEqual(t, string(formatted), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  2182  	return common.OpenAPIDefinition{
  2183  		Schema: spec.Schema{
  2184  			SchemaProps: spec.SchemaProps{
  2185  				Type: []string{"object"},
  2186  				Properties: map[string]spec.Schema{
  2187  					"Value": {
  2188  						SchemaProps: spec.SchemaProps{
  2189  							Default: "A Default Value",
  2190  							Type:    []string{"string"},
  2191  							Format:  "",
  2192  						},
  2193  					},
  2194  					"FullyQualifiedOutputValue": {
  2195  						SchemaProps: spec.SchemaProps{
  2196  							Description: "User constant local to the output package fully qualified",
  2197  							Default:     MyConst,
  2198  							Type:        []string{"string"},
  2199  							Format:      "",
  2200  						},
  2201  					},
  2202  					"LocalValue": {
  2203  						SchemaProps: spec.SchemaProps{
  2204  							Description: "Local to types but not to output",
  2205  							Default:     foo.MyConst,
  2206  							Type:        []string{"string"},
  2207  							Format:      "",
  2208  						},
  2209  					},
  2210  					"FullyQualifiedLocalValue": {
  2211  						SchemaProps: spec.SchemaProps{
  2212  							Default: foo.MyConst,
  2213  							Type:    []string{"string"},
  2214  							Format:  "",
  2215  						},
  2216  					},
  2217  					"FullyQualifiedExternalValue": {
  2218  						SchemaProps: spec.SchemaProps{
  2219  							Default: v1.TerminationPathDefault,
  2220  							Type:    []string{"string"},
  2221  							Format:  "",
  2222  						},
  2223  					},
  2224  				},
  2225  			},
  2226  		},
  2227  	}
  2228  }`)
  2229  		}
  2230  	})
  2231  }
  2232  
  2233  // Show that types with unmarshalJSON in their hierarchy do not have struct
  2234  // defaults enforced, and that aliases and embededd types are respected
  2235  func TestMustEnforceDefaultStruct(t *testing.T) {
  2236  	inputFile := `
  2237  		package foo
  2238  
  2239  		type Time struct {
  2240  			value interface{}
  2241  		}
  2242  
  2243  
  2244  		type TimeWithoutUnmarshal struct {
  2245  			value interface{}
  2246  		}
  2247  
  2248  		func (_ TimeWithoutUnmarshal) OpenAPISchemaType() []string { return []string{"string"} }
  2249  		func (_ TimeWithoutUnmarshal) OpenAPISchemaFormat() string { return "date-time" }
  2250  
  2251  		func (_ Time) UnmarshalJSON([]byte) error {
  2252  			return nil
  2253  		}
  2254  
  2255  
  2256  		func (_ Time) OpenAPISchemaType() []string { return []string{"string"} }
  2257  		func (_ Time) OpenAPISchemaFormat() string { return "date-time" }
  2258  
  2259  		// Time with UnmarshalJSON defined on pointer instead of struct
  2260  		type MicroTime struct {
  2261  			value interface{}
  2262  		}
  2263  
  2264  		func (t *MicroTime) UnmarshalJSON([]byte) error {
  2265  			return nil
  2266  		}
  2267  
  2268  		func (_ MicroTime) OpenAPISchemaType() []string { return []string{"string"} }
  2269  		func (_ MicroTime) OpenAPISchemaFormat() string { return "date-time" }
  2270  
  2271  		type Int64 int64
  2272  
  2273  		type Duration struct {
  2274  			Int64
  2275  		}
  2276  
  2277  		func (_ Duration) OpenAPISchemaType() []string { return []string{"string"} }
  2278  		func (_ Duration) OpenAPISchemaFormat() string { return "" }
  2279  
  2280  		type NothingSpecial struct {
  2281  			Field string
  2282  		}
  2283  
  2284  		// +k8s:openapi-gen=true
  2285  		type Blah struct {
  2286  			Embedded Duration
  2287  			PointerUnmarshal MicroTime
  2288  			StructUnmarshal Time
  2289  			NoUnmarshal TimeWithoutUnmarshal
  2290  			Regular NothingSpecial
  2291  		}`
  2292  
  2293  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  2294  		e := packagestest.Export(t, x, []packagestest.Module{{
  2295  			Name: "example.com/base/foo",
  2296  			Files: map[string]interface{}{
  2297  				"foo.go": inputFile,
  2298  			},
  2299  		}})
  2300  		defer e.Cleanup()
  2301  
  2302  		callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config)
  2303  		if funcErr != nil {
  2304  			t.Fatalf("Unexpected funcErr: %v", funcErr)
  2305  		}
  2306  		if callErr != nil {
  2307  			t.Fatalf("Unexpected callErr: %v", callErr)
  2308  		}
  2309  		expImports := []string{
  2310  			`foo "example.com/base/foo"`,
  2311  			`common "k8s.io/kube-openapi/pkg/common"`,
  2312  			`spec "k8s.io/kube-openapi/pkg/validation/spec"`,
  2313  		}
  2314  		if !cmp.Equal(imports, expImports) {
  2315  			t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports))
  2316  		}
  2317  
  2318  		if formatted, err := format.Source(funcBuffer.Bytes()); err != nil {
  2319  			t.Fatal(err)
  2320  		} else {
  2321  			assertEqual(t, string(formatted), `func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  2322  	return common.OpenAPIDefinition{
  2323  		Schema: spec.Schema{
  2324  			SchemaProps: spec.SchemaProps{
  2325  				Type: []string{"object"},
  2326  				Properties: map[string]spec.Schema{
  2327  					"Embedded": {
  2328  						SchemaProps: spec.SchemaProps{
  2329  							Default: 0,
  2330  							Ref:     ref("example.com/base/foo.Duration"),
  2331  						},
  2332  					},
  2333  					"PointerUnmarshal": {
  2334  						SchemaProps: spec.SchemaProps{
  2335  							Ref: ref("example.com/base/foo.MicroTime"),
  2336  						},
  2337  					},
  2338  					"StructUnmarshal": {
  2339  						SchemaProps: spec.SchemaProps{
  2340  							Ref: ref("example.com/base/foo.Time"),
  2341  						},
  2342  					},
  2343  					"NoUnmarshal": {
  2344  						SchemaProps: spec.SchemaProps{
  2345  							Default: map[string]interface{}{},
  2346  							Ref:     ref("example.com/base/foo.TimeWithoutUnmarshal"),
  2347  						},
  2348  					},
  2349  					"Regular": {
  2350  						SchemaProps: spec.SchemaProps{
  2351  							Default: map[string]interface{}{},
  2352  							Ref:     ref("example.com/base/foo.NothingSpecial"),
  2353  						},
  2354  					},
  2355  				},
  2356  				Required: []string{"Embedded", "PointerUnmarshal", "StructUnmarshal", "NoUnmarshal", "Regular"},
  2357  			},
  2358  		},
  2359  		Dependencies: []string{
  2360  			"example.com/base/foo.Duration", "example.com/base/foo.MicroTime", "example.com/base/foo.NothingSpecial", "example.com/base/foo.Time", "example.com/base/foo.TimeWithoutUnmarshal"},
  2361  	}
  2362  }`)
  2363  		}
  2364  	})
  2365  }
  2366  
  2367  func TestMarkerComments(t *testing.T) {
  2368  	inputFile := `
  2369  		package foo
  2370  
  2371  		// +k8s:openapi-gen=true
  2372  		// +k8s:validation:maxProperties=10
  2373  		// +k8s:validation:minProperties=1
  2374  		// +k8s:validation:exclusiveMinimum
  2375  		// +k8s:validation:exclusiveMaximum
  2376  		type Blah struct {
  2377  
  2378  			// Integer with min and max values
  2379  			// +k8s:validation:minimum=0
  2380  			// +k8s:validation:maximum=10
  2381  			// +k8s:validation:exclusiveMinimum
  2382  			// +k8s:validation:exclusiveMaximum
  2383  			IntValue int
  2384  
  2385  			// String with min and max lengths
  2386  			// +k8s:validation:minLength=1
  2387  			// +k8s:validation:maxLength=10
  2388  			// +k8s:validation:pattern="^foo$[0-9]+"
  2389  			StringValue string
  2390  
  2391  			// +k8s:validation:maxitems=10
  2392  			// +k8s:validation:minItems=1
  2393  			// +k8s:validation:uniqueItems
  2394  			ArrayValue []string
  2395  
  2396  			// +k8s:validation:maxProperties=10
  2397  			// +k8s:validation:minProperties=1
  2398  			ObjValue map[string]interface{}
  2399  		}`
  2400  
  2401  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  2402  		e := packagestest.Export(t, x, []packagestest.Module{{
  2403  			Name: "example.com/base/foo",
  2404  			Files: map[string]interface{}{
  2405  				"foo.go": inputFile,
  2406  			},
  2407  		}})
  2408  		defer e.Cleanup()
  2409  
  2410  		callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config)
  2411  		if funcErr != nil {
  2412  			t.Fatalf("Unexpected funcErr: %v", funcErr)
  2413  		}
  2414  		if callErr != nil {
  2415  			t.Fatalf("Unexpected callErr: %v", callErr)
  2416  		}
  2417  		expImports := []string{
  2418  			`foo "example.com/base/foo"`,
  2419  			`common "k8s.io/kube-openapi/pkg/common"`,
  2420  			`spec "k8s.io/kube-openapi/pkg/validation/spec"`,
  2421  			`ptr "k8s.io/utils/ptr"`,
  2422  		}
  2423  		if !cmp.Equal(imports, expImports) {
  2424  			t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports))
  2425  		}
  2426  
  2427  		if formatted, err := format.Source(funcBuffer.Bytes()); err != nil {
  2428  			t.Fatalf("%v\n%v", err, string(funcBuffer.Bytes()))
  2429  		} else {
  2430  			formatted_expected, ree := format.Source([]byte(`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  2431  		return common.OpenAPIDefinition{
  2432  			Schema: spec.Schema{
  2433  				SchemaProps: spec.SchemaProps{
  2434  					Type: 			  []string{"object"},
  2435  					ExclusiveMinimum: true,
  2436  					ExclusiveMaximum: true,
  2437  					MinProperties:    ptr.To[int64](1),
  2438  					MaxProperties:    ptr.To[int64](10),
  2439  					Properties: map[string]spec.Schema{
  2440  						"IntValue": {
  2441  							SchemaProps: spec.SchemaProps{
  2442  								Description: "Integer with min and max values",
  2443  								Default: 	 0,
  2444  								Minimum:	 ptr.To[float64](0),
  2445  								Maximum:	 ptr.To[float64](10),
  2446  								ExclusiveMinimum: true,
  2447  								ExclusiveMaximum: true,
  2448  								Type:        []string{"integer"},
  2449  								Format:	  	 "int32",
  2450  							},
  2451  						},
  2452  						"StringValue": {
  2453  							SchemaProps: spec.SchemaProps{
  2454  								Description: "String with min and max lengths",
  2455  								Default:	 "",
  2456  								MinLength:	 ptr.To[int64](1),
  2457  								MaxLength:	 ptr.To[int64](10),
  2458  								Pattern:	 "^foo$[0-9]+",
  2459  								Type:        []string{"string"},
  2460  								Format:	  	 "",
  2461  							},
  2462  						},
  2463  						"ArrayValue": {
  2464  							SchemaProps: spec.SchemaProps{
  2465  								MinItems:	 ptr.To[int64](1),
  2466  								MaxItems:	 ptr.To[int64](10),
  2467  								UniqueItems: true,
  2468  								Type: []string{"array"},
  2469  								Items: &spec.SchemaOrArray{
  2470  									Schema: &spec.Schema{
  2471  										SchemaProps: spec.SchemaProps{
  2472  											Default: "",
  2473  											Type:    []string{"string"},
  2474  											Format:  "",
  2475  										},
  2476  									},
  2477  								},
  2478  							},
  2479  						},
  2480  						"ObjValue": {
  2481  							SchemaProps: spec.SchemaProps{
  2482  								MinProperties:	 ptr.To[int64](1),
  2483  								MaxProperties:	 ptr.To[int64](10),
  2484  								Type: []string{"object"},
  2485  									AdditionalProperties: &spec.SchemaOrBool{
  2486  										Allows: true,
  2487  										Schema: &spec.Schema{
  2488  											SchemaProps: spec.SchemaProps{
  2489  												Type:   []string{"object"},
  2490  												Format: "",
  2491  											},
  2492  										},
  2493  									},
  2494  							},
  2495  						},
  2496  					},
  2497  					Required: []string{"IntValue", "StringValue", "ArrayValue", "ObjValue"},
  2498  				},
  2499  			},
  2500  		}
  2501  	}`))
  2502  			if ree != nil {
  2503  				t.Fatal(ree)
  2504  			}
  2505  			assertEqual(t, string(formatted), string(formatted_expected))
  2506  		}
  2507  	})
  2508  }
  2509  
  2510  func TestCELMarkerComments(t *testing.T) {
  2511  	inputFile := `
  2512  		package foo
  2513  
  2514  		// +k8s:openapi-gen=true
  2515  		// +k8s:validation:cel[0]:rule="self == oldSelf"
  2516  		// +k8s:validation:cel[0]:message="message1"
  2517  		type Blah struct {
  2518  			// +k8s:validation:cel[0]:rule="self.length() > 0"
  2519  			// +k8s:validation:cel[0]:message="string message"
  2520  			// +k8s:validation:cel[1]:rule="self.length() % 2 == 0"
  2521  			// +k8s:validation:cel[1]:messageExpression="self + ' hello'"
  2522  			// +k8s:validation:cel[1]:optionalOldSelf
  2523  			// +optional
  2524  			Field string
  2525  		}`
  2526  
  2527  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  2528  		e := packagestest.Export(t, x, []packagestest.Module{{
  2529  			Name: "example.com/base/foo",
  2530  			Files: map[string]interface{}{
  2531  				"foo.go": inputFile,
  2532  			},
  2533  		}})
  2534  		defer e.Cleanup()
  2535  
  2536  		callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config)
  2537  		if funcErr != nil {
  2538  			t.Fatalf("Unexpected funcErr: %v", funcErr)
  2539  		}
  2540  		if callErr != nil {
  2541  			t.Fatalf("Unexpected callErr: %v", callErr)
  2542  		}
  2543  		expImports := []string{
  2544  			`foo "example.com/base/foo"`,
  2545  			`common "k8s.io/kube-openapi/pkg/common"`,
  2546  			`spec "k8s.io/kube-openapi/pkg/validation/spec"`,
  2547  		}
  2548  		if !cmp.Equal(imports, expImports) {
  2549  			t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports))
  2550  		}
  2551  
  2552  		if formatted, err := format.Source(funcBuffer.Bytes()); err != nil {
  2553  			t.Fatalf("%v\n%v", err, string(funcBuffer.Bytes()))
  2554  		} else {
  2555  			formatted_expected, ree := format.Source([]byte(`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  2556  		return common.OpenAPIDefinition{
  2557  			Schema: spec.Schema{
  2558  				SchemaProps: spec.SchemaProps{
  2559  					Type: 			  []string{"object"},
  2560  					Properties: map[string]spec.Schema{
  2561  						"Field": {
  2562  							VendorExtensible: spec.VendorExtensible{
  2563  								Extensions: spec.Extensions{
  2564  									"x-kubernetes-validations": []interface{}{map[string]interface{}{"message": "string message", "rule": "self.length() > 0"}, map[string]interface{}{"messageExpression": "self + ' hello'", "optionalOldSelf": true, "rule": "self.length() % 2 == 0"}},
  2565  								},
  2566  							},
  2567  							SchemaProps: spec.SchemaProps{
  2568  								Default: "",
  2569  								Type:    []string{"string"},
  2570  								Format:  "",
  2571  							},
  2572  						},
  2573  					},
  2574  				},
  2575  				VendorExtensible: spec.VendorExtensible{
  2576  					Extensions: spec.Extensions{
  2577  						"x-kubernetes-validations": []interface{}{map[string]interface{}{"message": "message1", "rule": "self == oldSelf"}},
  2578  					},
  2579  				},
  2580  			},
  2581  		}
  2582  	}`))
  2583  			if ree != nil {
  2584  				t.Fatal(ree)
  2585  			}
  2586  			assertEqual(t, string(formatted_expected), string(formatted))
  2587  		}
  2588  	})
  2589  }
  2590  
  2591  func TestMultilineCELMarkerComments(t *testing.T) {
  2592  	inputFile := `
  2593  		package foo
  2594  
  2595  		// +k8s:openapi-gen=true
  2596  		// +k8s:validation:cel[0]:rule="self == oldSelf"
  2597  		// +k8s:validation:cel[0]:message="message1"
  2598  		// +k8s:validation:cel[0]:fieldPath="field"
  2599  		type Blah struct {
  2600  			// +k8s:validation:cel[0]:rule="self.length() > 0"
  2601  			// +k8s:validation:cel[0]:message="string message"
  2602  			// +k8s:validation:cel[0]:reason="Invalid"
  2603  			// +k8s:validation:cel[1]:rule>  !oldSelf.hasValue() || self.length() % 2 == 0
  2604  			// +k8s:validation:cel[1]:rule>     ? self.field == "even"
  2605  			// +k8s:validation:cel[1]:rule>     : self.field == "odd"
  2606  			// +k8s:validation:cel[1]:messageExpression="field must be whether the length of the string is even or odd"
  2607  			// +k8s:validation:cel[1]:optionalOldSelf
  2608  			// +optional
  2609  			Field string
  2610  		}`
  2611  
  2612  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  2613  		e := packagestest.Export(t, x, []packagestest.Module{{
  2614  			Name: "example.com/base/foo",
  2615  			Files: map[string]interface{}{
  2616  				"foo.go": inputFile,
  2617  			},
  2618  		}})
  2619  		defer e.Cleanup()
  2620  
  2621  		callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config)
  2622  		if funcErr != nil {
  2623  			t.Fatalf("Unexpected funcErr: %v", funcErr)
  2624  		}
  2625  		if callErr != nil {
  2626  			t.Fatalf("Unexpected callErr: %v", callErr)
  2627  		}
  2628  		expImports := []string{
  2629  			`foo "example.com/base/foo"`,
  2630  			`common "k8s.io/kube-openapi/pkg/common"`,
  2631  			`spec "k8s.io/kube-openapi/pkg/validation/spec"`,
  2632  		}
  2633  		if !cmp.Equal(imports, expImports) {
  2634  			t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports))
  2635  		}
  2636  
  2637  		if formatted, err := format.Source(funcBuffer.Bytes()); err != nil {
  2638  			t.Fatalf("%v\n%v", err, string(funcBuffer.Bytes()))
  2639  		} else {
  2640  			formatted_expected, ree := format.Source([]byte(`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  2641  		return common.OpenAPIDefinition{
  2642  			Schema: spec.Schema{
  2643  				SchemaProps: spec.SchemaProps{
  2644  					Type: 			  []string{"object"},
  2645  					Properties: map[string]spec.Schema{
  2646  						"Field": {
  2647  							VendorExtensible: spec.VendorExtensible{
  2648  								Extensions: spec.Extensions{
  2649  									"x-kubernetes-validations": []interface{}{map[string]interface{}{"message": "string message", "reason": "Invalid", "rule": "self.length() > 0"}, map[string]interface{}{"messageExpression": "field must be whether the length of the string is even or odd", "optionalOldSelf": true, "rule": "!oldSelf.hasValue() || self.length() % 2 == 0\n? self.field == \"even\"\n: self.field == \"odd\""}},
  2650  								},
  2651  							},
  2652  							SchemaProps: spec.SchemaProps{
  2653  								Default: "",
  2654  								Type:    []string{"string"},
  2655  								Format:  "",
  2656  							},
  2657  						},
  2658  					},
  2659  				},
  2660  				VendorExtensible: spec.VendorExtensible{
  2661  					Extensions: spec.Extensions{
  2662  						"x-kubernetes-validations": []interface{}{map[string]interface{}{"fieldPath": "field", "message": "message1", "rule": "self == oldSelf"}},
  2663  					},
  2664  				},
  2665  			},
  2666  		}
  2667  	}`))
  2668  			if ree != nil {
  2669  				t.Fatal(ree)
  2670  			}
  2671  			assertEqual(t, string(formatted_expected), string(formatted))
  2672  		}
  2673  	})
  2674  }
  2675  
  2676  func TestRequired(t *testing.T) {
  2677  	inputFile := `
  2678  		package foo
  2679  
  2680  		// +k8s:openapi-gen=true
  2681  		type Blah struct {
  2682  			// +optional
  2683  			OptionalField string
  2684  
  2685  			// +required
  2686  			RequiredField string
  2687  
  2688  			// +required
  2689  			RequiredPointerField *string ` + "`json:\"requiredPointerField,omitempty\"`" + `
  2690  
  2691  			// +optional
  2692  			OptionalPointerField *string ` + "`json:\"optionalPointerField,omitempty\"`" + `
  2693  
  2694  			ImplicitlyRequiredField string
  2695  			ImplicitlyOptionalField string ` + "`json:\"implicitlyOptionalField,omitempty\"`" + `
  2696  		}`
  2697  
  2698  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  2699  		e := packagestest.Export(t, x, []packagestest.Module{{
  2700  			Name: "example.com/base/foo",
  2701  			Files: map[string]interface{}{
  2702  				"foo.go": inputFile,
  2703  			},
  2704  		}})
  2705  		defer e.Cleanup()
  2706  
  2707  		callErr, funcErr, _, funcBuffer, imports := testOpenAPITypeWriter(t, e.Config)
  2708  		if funcErr != nil {
  2709  			t.Fatalf("Unexpected funcErr: %v", funcErr)
  2710  		}
  2711  		if callErr != nil {
  2712  			t.Fatalf("Unexpected callErr: %v", callErr)
  2713  		}
  2714  		expImports := []string{
  2715  			`foo "example.com/base/foo"`,
  2716  			`common "k8s.io/kube-openapi/pkg/common"`,
  2717  			`spec "k8s.io/kube-openapi/pkg/validation/spec"`,
  2718  		}
  2719  		if !cmp.Equal(imports, expImports) {
  2720  			t.Errorf("wrong imports:\n%s", cmp.Diff(expImports, imports))
  2721  		}
  2722  
  2723  		if formatted, err := format.Source(funcBuffer.Bytes()); err != nil {
  2724  			t.Fatalf("%v\n%v", err, string(funcBuffer.Bytes()))
  2725  		} else {
  2726  			formatted_expected, ree := format.Source([]byte(`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  2727  		return common.OpenAPIDefinition{
  2728  			Schema: spec.Schema{
  2729  				SchemaProps: spec.SchemaProps{
  2730  					Type: 			  []string{"object"},
  2731  					Properties: map[string]spec.Schema{
  2732  						"OptionalField": {
  2733  							SchemaProps: spec.SchemaProps{
  2734  								Default: "",
  2735  								Type:    []string{"string"},
  2736  								Format:  "",
  2737  							},
  2738  						},
  2739     						"RequiredField": {
  2740     							SchemaProps: spec.SchemaProps{
  2741     								Default: "",
  2742     								Type:    []string{"string"},
  2743     								Format:  "",
  2744     							},
  2745     						},
  2746     						"requiredPointerField": {
  2747     							SchemaProps: spec.SchemaProps{
  2748     								Type:   []string{"string"},
  2749     								Format: "",
  2750     							},
  2751     						},
  2752     						"optionalPointerField": {
  2753     							SchemaProps: spec.SchemaProps{
  2754     								Type:   []string{"string"},
  2755     								Format: "",
  2756     							},
  2757     						},
  2758     						"ImplicitlyRequiredField": {
  2759     							SchemaProps: spec.SchemaProps{
  2760     								Default: "",
  2761     								Type:    []string{"string"},
  2762     								Format:  "",
  2763     							},
  2764     						},
  2765     						"implicitlyOptionalField": {
  2766     							SchemaProps: spec.SchemaProps{
  2767     								Type:   []string{"string"},
  2768     								Format: "",
  2769     							},
  2770     						},
  2771  					},
  2772  					Required: []string{"RequiredField", "requiredPointerField", "ImplicitlyRequiredField"},
  2773  				},
  2774  			},
  2775  		}
  2776  	}`))
  2777  			if ree != nil {
  2778  				t.Fatal(ree)
  2779  			}
  2780  			assertEqual(t, string(formatted_expected), string(formatted))
  2781  		}
  2782  	})
  2783  
  2784  	// Show specifying both is an error
  2785  	badFile := `
  2786  		package foo
  2787  
  2788  		// +k8s:openapi-gen=true
  2789  		type Blah struct {
  2790  			// +optional
  2791  			// +required
  2792  			ConfusingField string
  2793  		}`
  2794  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  2795  		e := packagestest.Export(t, x, []packagestest.Module{{
  2796  			Name: "example.com/base/foo",
  2797  			Files: map[string]interface{}{
  2798  				"foo.go": badFile,
  2799  			},
  2800  		}})
  2801  		defer e.Cleanup()
  2802  
  2803  		callErr, funcErr, _, _, _ := testOpenAPITypeWriter(t, e.Config)
  2804  		if callErr != nil {
  2805  			t.Errorf("Unexpected callErr: %v", callErr)
  2806  		}
  2807  		if funcErr == nil {
  2808  			t.Fatalf("Expected funcErr")
  2809  		}
  2810  		if !strings.Contains(funcErr.Error(), "cannot be both optional and required") {
  2811  			t.Errorf("Unexpected error: %v", funcErr)
  2812  		}
  2813  	})
  2814  }
  2815  
  2816  func TestMarkerCommentsCustomDefsV3(t *testing.T) {
  2817  	inputFile := `
  2818  		package foo
  2819  
  2820  		import openapi "k8s.io/kube-openapi/pkg/common"
  2821  
  2822  		// +k8s:validation:maxProperties=10
  2823  		type Blah struct {
  2824  		}
  2825  
  2826  		func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition {
  2827  			return openapi.OpenAPIDefinition{
  2828  				Schema: spec.Schema{
  2829  					SchemaProps: spec.SchemaProps{
  2830  						Type:   []string{"object"},
  2831  						MaxProperties: ptr.To[int64](10),
  2832  						Format: "ipv4",
  2833  					},
  2834  				},
  2835  			}
  2836  		}
  2837  
  2838  		func (_ Blah) OpenAPISchemaType() []string { return []string{"object"} }
  2839  		func (_ Blah) OpenAPISchemaFormat() string { return "ipv4" }`
  2840  	commonFile := `package common
  2841  
  2842  		type OpenAPIDefinition struct {}`
  2843  
  2844  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  2845  		e := packagestest.Export(t, x, []packagestest.Module{{
  2846  			Name: "example.com/base/foo",
  2847  			Files: map[string]interface{}{
  2848  				"foo.go": inputFile,
  2849  			},
  2850  		}, {
  2851  			Name: "k8s.io/kube-openapi/pkg/common",
  2852  			Files: map[string]interface{}{
  2853  				"common.go": commonFile,
  2854  			},
  2855  		}})
  2856  		defer e.Cleanup()
  2857  
  2858  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  2859  		if callErr != nil {
  2860  			t.Fatal(callErr)
  2861  		}
  2862  		if funcErr != nil {
  2863  			t.Fatal(funcErr)
  2864  		}
  2865  		assertEqual(t, callBuffer.String(),
  2866  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
  2867  		assertEqual(t, funcBuffer.String(),
  2868  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  2869  return common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), common.OpenAPIDefinition{
  2870  Schema: spec.Schema{
  2871  SchemaProps: spec.SchemaProps{
  2872  Type:foo.Blah{}.OpenAPISchemaType(),
  2873  Format:foo.Blah{}.OpenAPISchemaFormat(),
  2874  MaxProperties: ptr.To[int64](10),
  2875  },
  2876  },
  2877  })
  2878  }`)
  2879  	})
  2880  }
  2881  
  2882  func TestMarkerCommentsV3OneOfTypes(t *testing.T) {
  2883  	inputFile := `
  2884  		package foo
  2885  
  2886  		// +k8s:validation:maxLength=10
  2887  		type Blah struct {
  2888  		}
  2889  
  2890  		func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} }
  2891  		func (_ Blah) OpenAPIV3OneOfTypes() []string { return []string{"string", "array"} }
  2892  		func (_ Blah) OpenAPISchemaFormat() string { return "ipv4" }`
  2893  	packagestest.TestAll(t, func(t *testing.T, x packagestest.Exporter) {
  2894  		e := packagestest.Export(t, x, []packagestest.Module{{
  2895  			Name: "example.com/base/foo",
  2896  			Files: map[string]interface{}{
  2897  				"foo.go": inputFile,
  2898  			},
  2899  		}})
  2900  		defer e.Cleanup()
  2901  
  2902  		callErr, funcErr, callBuffer, funcBuffer, _ := testOpenAPITypeWriter(t, e.Config)
  2903  		if callErr != nil {
  2904  			t.Fatal(callErr)
  2905  		}
  2906  		if funcErr != nil {
  2907  			t.Fatal(funcErr)
  2908  		}
  2909  		assertEqual(t, callBuffer.String(),
  2910  			`"example.com/base/foo.Blah": schema_examplecom_base_foo_Blah(ref),`)
  2911  		assertEqual(t, funcBuffer.String(),
  2912  			`func schema_examplecom_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
  2913  return common.EmbedOpenAPIDefinitionIntoV2Extension(common.OpenAPIDefinition{
  2914  Schema: spec.Schema{
  2915  SchemaProps: spec.SchemaProps{
  2916  OneOf:common.GenerateOpenAPIV3OneOfSchema(foo.Blah{}.OpenAPIV3OneOfTypes()),
  2917  Format:foo.Blah{}.OpenAPISchemaFormat(),
  2918  MaxLength: ptr.To[int64](10),
  2919  },
  2920  },
  2921  },common.OpenAPIDefinition{
  2922  Schema: spec.Schema{
  2923  SchemaProps: spec.SchemaProps{
  2924  Type:foo.Blah{}.OpenAPISchemaType(),
  2925  Format:foo.Blah{}.OpenAPISchemaFormat(),
  2926  MaxLength: ptr.To[int64](10),
  2927  },
  2928  },
  2929  })
  2930  }`)
  2931  	})
  2932  }
  2933  

View as plain text