1/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package generators
18
19import (
20	"bytes"
21	"fmt"
22	"path/filepath"
23	"strings"
24	"testing"
25
26	"github.com/stretchr/testify/assert"
27
28	"k8s.io/gengo/generator"
29	"k8s.io/gengo/namer"
30	"k8s.io/gengo/parser"
31	"k8s.io/gengo/types"
32)
33
34func construct(t *testing.T, files map[string]string, testNamer namer.Namer) (*parser.Builder, types.Universe, []*types.Type) {
35	b := parser.New()
36	for name, src := range files {
37		if err := b.AddFileForTest(filepath.Dir(name), name, []byte(src)); err != nil {
38			t.Fatal(err)
39		}
40	}
41	u, err := b.FindTypes()
42	if err != nil {
43		t.Fatal(err)
44	}
45	orderer := namer.Orderer{Namer: testNamer}
46	o := orderer.OrderUniverse(u)
47	return b, u, o
48}
49
50func testOpenAPITypeWriter(t *testing.T, code string) (error, error, *assert.Assertions, *bytes.Buffer, *bytes.Buffer) {
51	assert := assert.New(t)
52	var testFiles = map[string]string{
53		"base/foo/bar.go": code,
54	}
55	rawNamer := namer.NewRawNamer("o", nil)
56	namers := namer.NameSystems{
57		"raw": namer.NewRawNamer("", nil),
58		"private": &namer.NameStrategy{
59			Join: func(pre string, in []string, post string) string {
60				return strings.Join(in, "_")
61			},
62			PrependPackageNames: 4, // enough to fully qualify from k8s.io/api/...
63		},
64	}
65	builder, universe, _ := construct(t, testFiles, rawNamer)
66	context, err := generator.NewContext(builder, namers, "raw")
67	if err != nil {
68		t.Fatal(err)
69	}
70	blahT := universe.Type(types.Name{Package: "base/foo", Name: "Blah"})
71
72	callBuffer := &bytes.Buffer{}
73	callSW := generator.NewSnippetWriter(callBuffer, context, "$", "$")
74	callError := newOpenAPITypeWriter(callSW, context).generateCall(blahT)
75
76	funcBuffer := &bytes.Buffer{}
77	funcSW := generator.NewSnippetWriter(funcBuffer, context, "$", "$")
78	funcError := newOpenAPITypeWriter(funcSW, context).generate(blahT)
79
80	return callError, funcError, assert, callBuffer, funcBuffer
81}
82
83func TestSimple(t *testing.T) {
84	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
85package foo
86
87// Blah is a test.
88// +k8s:openapi-gen=true
89// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
90type Blah struct {
91	// A simple string
92	String string
93	// A simple int
94	Int int `+"`"+`json:",omitempty"`+"`"+`
95	// An int considered string simple int
96	IntString int `+"`"+`json:",string"`+"`"+`
97	// A simple int64
98	Int64 int64
99	// A simple int32
100	Int32 int32
101	// A simple int16
102	Int16 int16
103	// A simple int8
104	Int8 int8
105	// A simple int
106	Uint uint
107	// A simple int64
108	Uint64 uint64
109	// A simple int32
110	Uint32 uint32
111	// A simple int16
112	Uint16 uint16
113	// A simple int8
114	Uint8 uint8
115	// A simple byte
116	Byte byte
117	// A simple boolean
118	Bool bool
119	// A simple float64
120	Float64 float64
121	// A simple float32
122	Float32 float32
123	// a base64 encoded characters
124	ByteArray []byte
125	// a member with an extension
126	// +k8s:openapi-gen=x-kubernetes-member-tag:member_test
127	WithExtension string
128	// a member with struct tag as extension
129	// +patchStrategy=merge
130	// +patchMergeKey=pmk
131	WithStructTagExtension string `+"`"+`patchStrategy:"merge" patchMergeKey:"pmk"`+"`"+`
132	// a member with a list type
133	// +listType=atomic
134	// +default=["foo", "bar"]
135	WithListType []string
136	// a member with a map type
137	// +listType=atomic
138	// +default={"foo": "bar", "fizz": "buzz"}
139	Map map[string]string
140	// a member with a string pointer
141	// +default="foo"
142	StringPointer *string
143	// an int member with a default
144	// +default=1
145	OmittedInt int `+"`"+`json:"omitted,omitempty"`+"`"+`
146}
147		`)
148	if callErr != nil {
149		t.Fatal(callErr)
150	}
151	if funcErr != nil {
152		t.Fatal(funcErr)
153	}
154	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
155`, callBuffer.String())
156	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
157return common.OpenAPIDefinition{
158Schema: spec.Schema{
159SchemaProps: spec.SchemaProps{
160Description: "Blah is a test.",
161Type: []string{"object"},
162Properties: map[string]spec.Schema{
163"String": {
164SchemaProps: spec.SchemaProps{
165Description: "A simple string",
166Default: "",
167Type: []string{"string"},
168Format: "",
169},
170},
171"Int64": {
172SchemaProps: spec.SchemaProps{
173Description: "A simple int64",
174Default: 0,
175Type: []string{"integer"},
176Format: "int64",
177},
178},
179"Int32": {
180SchemaProps: spec.SchemaProps{
181Description: "A simple int32",
182Default: 0,
183Type: []string{"integer"},
184Format: "int32",
185},
186},
187"Int16": {
188SchemaProps: spec.SchemaProps{
189Description: "A simple int16",
190Default: 0,
191Type: []string{"integer"},
192Format: "int32",
193},
194},
195"Int8": {
196SchemaProps: spec.SchemaProps{
197Description: "A simple int8",
198Default: 0,
199Type: []string{"integer"},
200Format: "byte",
201},
202},
203"Uint": {
204SchemaProps: spec.SchemaProps{
205Description: "A simple int",
206Default: 0,
207Type: []string{"integer"},
208Format: "int32",
209},
210},
211"Uint64": {
212SchemaProps: spec.SchemaProps{
213Description: "A simple int64",
214Default: 0,
215Type: []string{"integer"},
216Format: "int64",
217},
218},
219"Uint32": {
220SchemaProps: spec.SchemaProps{
221Description: "A simple int32",
222Default: 0,
223Type: []string{"integer"},
224Format: "int64",
225},
226},
227"Uint16": {
228SchemaProps: spec.SchemaProps{
229Description: "A simple int16",
230Default: 0,
231Type: []string{"integer"},
232Format: "int32",
233},
234},
235"Uint8": {
236SchemaProps: spec.SchemaProps{
237Description: "A simple int8",
238Default: 0,
239Type: []string{"integer"},
240Format: "byte",
241},
242},
243"Byte": {
244SchemaProps: spec.SchemaProps{
245Description: "A simple byte",
246Default: 0,
247Type: []string{"integer"},
248Format: "byte",
249},
250},
251"Bool": {
252SchemaProps: spec.SchemaProps{
253Description: "A simple boolean",
254Default: false,
255Type: []string{"boolean"},
256Format: "",
257},
258},
259"Float64": {
260SchemaProps: spec.SchemaProps{
261Description: "A simple float64",
262Default: 0,
263Type: []string{"number"},
264Format: "double",
265},
266},
267"Float32": {
268SchemaProps: spec.SchemaProps{
269Description: "A simple float32",
270Default: 0,
271Type: []string{"number"},
272Format: "float",
273},
274},
275"ByteArray": {
276SchemaProps: spec.SchemaProps{
277Description: "a base64 encoded characters",
278Type: []string{"string"},
279Format: "byte",
280},
281},
282"WithExtension": {
283VendorExtensible: spec.VendorExtensible{
284Extensions: spec.Extensions{
285"x-kubernetes-member-tag": "member_test",
286},
287},
288SchemaProps: spec.SchemaProps{
289Description: "a member with an extension",
290Default: "",
291Type: []string{"string"},
292Format: "",
293},
294},
295"WithStructTagExtension": {
296VendorExtensible: spec.VendorExtensible{
297Extensions: spec.Extensions{
298"x-kubernetes-patch-merge-key": "pmk",
299"x-kubernetes-patch-strategy": "merge",
300},
301},
302SchemaProps: spec.SchemaProps{
303Description: "a member with struct tag as extension",
304Default: "",
305Type: []string{"string"},
306Format: "",
307},
308},
309"WithListType": {
310VendorExtensible: spec.VendorExtensible{
311Extensions: spec.Extensions{
312"x-kubernetes-list-type": "atomic",
313},
314},
315SchemaProps: spec.SchemaProps{
316Description: "a member with a list type",
317Default: []interface {}{"foo", "bar"},
318Type: []string{"array"},
319Items: &spec.SchemaOrArray{
320Schema: &spec.Schema{
321SchemaProps: spec.SchemaProps{
322Default: "",
323Type: []string{"string"},
324Format: "",
325},
326},
327},
328},
329},
330"Map": {
331VendorExtensible: spec.VendorExtensible{
332Extensions: spec.Extensions{
333"x-kubernetes-list-type": "atomic",
334},
335},
336SchemaProps: spec.SchemaProps{
337Description: "a member with a map type",
338Default: map[string]interface {}{"fizz":"buzz", "foo":"bar"},
339Type: []string{"object"},
340AdditionalProperties: &spec.SchemaOrBool{
341Allows: true,
342Schema: &spec.Schema{
343SchemaProps: spec.SchemaProps{
344Default: "",
345Type: []string{"string"},
346Format: "",
347},
348},
349},
350},
351},
352"StringPointer": {
353SchemaProps: spec.SchemaProps{
354Description: "a member with a string pointer",
355Default: "foo",
356Type: []string{"string"},
357Format: "",
358},
359},
360"omitted": {
361SchemaProps: spec.SchemaProps{
362Description: "an int member with a default",
363Default: 1,
364Type: []string{"integer"},
365Format: "int32",
366},
367},
368},
369Required: []string{"String","Int64","Int32","Int16","Int8","Uint","Uint64","Uint32","Uint16","Uint8","Byte","Bool","Float64","Float32","ByteArray","WithExtension","WithStructTagExtension","WithListType","Map","StringPointer"},
370},
371VendorExtensible: spec.VendorExtensible{
372Extensions: spec.Extensions{
373"x-kubernetes-type-tag": "type_test",
374},
375},
376},
377}
378}
379
380`, funcBuffer.String())
381}
382
383func TestEmptyProperties(t *testing.T) {
384	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
385package foo
386
387// Blah demonstrate a struct without fields.
388type Blah struct {
389}
390	`)
391	if callErr != nil {
392		t.Fatal(callErr)
393	}
394	if funcErr != nil {
395		t.Fatal(funcErr)
396	}
397	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
398`, callBuffer.String())
399	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
400return common.OpenAPIDefinition{
401Schema: spec.Schema{
402SchemaProps: spec.SchemaProps{
403Description: "Blah demonstrate a struct without fields.",
404Type: []string{"object"},
405},
406},
407}
408}
409
410`, funcBuffer.String())
411}
412
413func TestNestedStruct(t *testing.T) {
414	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
415package foo
416
417// Nested is used as struct field
418type Nested struct {
419  // A simple string
420  String string
421}
422
423// Blah demonstrate a struct with struct field.
424type Blah struct {
425  // A struct field
426  Field Nested
427}
428	`)
429	if callErr != nil {
430		t.Fatal(callErr)
431	}
432	if funcErr != nil {
433		t.Fatal(funcErr)
434	}
435	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
436`, callBuffer.String())
437	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
438return common.OpenAPIDefinition{
439Schema: spec.Schema{
440SchemaProps: spec.SchemaProps{
441Description: "Blah demonstrate a struct with struct field.",
442Type: []string{"object"},
443Properties: map[string]spec.Schema{
444"Field": {
445SchemaProps: spec.SchemaProps{
446Description: "A struct field",
447Default: map[string]interface {}{},
448Ref: ref("base/foo.Nested"),
449},
450},
451},
452Required: []string{"Field"},
453},
454},
455Dependencies: []string{
456"base/foo.Nested",},
457}
458}
459
460`, funcBuffer.String())
461}
462
463func TestNestedStructPointer(t *testing.T) {
464	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
465package foo
466
467// Nested is used as struct pointer field
468type Nested struct {
469  // A simple string
470  String string
471}
472
473// Blah demonstrate a struct with struct pointer field.
474type Blah struct {
475  // A struct pointer field
476  Field *Nested
477}
478	`)
479	if callErr != nil {
480		t.Fatal(callErr)
481	}
482	if funcErr != nil {
483		t.Fatal(funcErr)
484	}
485	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
486`, callBuffer.String())
487	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
488return common.OpenAPIDefinition{
489Schema: spec.Schema{
490SchemaProps: spec.SchemaProps{
491Description: "Blah demonstrate a struct with struct pointer field.",
492Type: []string{"object"},
493Properties: map[string]spec.Schema{
494"Field": {
495SchemaProps: spec.SchemaProps{
496Description: "A struct pointer field",
497Ref: ref("base/foo.Nested"),
498},
499},
500},
501Required: []string{"Field"},
502},
503},
504Dependencies: []string{
505"base/foo.Nested",},
506}
507}
508
509`, funcBuffer.String())
510}
511
512func TestEmbeddedStruct(t *testing.T) {
513	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
514package foo
515
516// Nested is used as embedded struct field
517type Nested struct {
518  // A simple string
519  String string
520}
521
522// Blah demonstrate a struct with embedded struct field.
523type Blah struct {
524  // An embedded struct field
525  Nested
526}
527	`)
528	if callErr != nil {
529		t.Fatal(callErr)
530	}
531	if funcErr != nil {
532		t.Fatal(funcErr)
533	}
534	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
535`, callBuffer.String())
536	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
537return common.OpenAPIDefinition{
538Schema: spec.Schema{
539SchemaProps: spec.SchemaProps{
540Description: "Blah demonstrate a struct with embedded struct field.",
541Type: []string{"object"},
542Properties: map[string]spec.Schema{
543"Nested": {
544SchemaProps: spec.SchemaProps{
545Description: "An embedded struct field",
546Default: map[string]interface {}{},
547Ref: ref("base/foo.Nested"),
548},
549},
550},
551Required: []string{"Nested"},
552},
553},
554Dependencies: []string{
555"base/foo.Nested",},
556}
557}
558
559`, funcBuffer.String())
560}
561
562func TestEmbeddedInlineStruct(t *testing.T) {
563	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
564package foo
565
566// Nested is used as embedded inline struct field
567type Nested struct {
568  // A simple string
569  String string
570}
571
572// Blah demonstrate a struct with embedded inline struct field.
573type Blah struct {
574  // An embedded inline struct field
575  Nested `+"`"+`json:",inline,omitempty"`+"`"+`
576}
577	`)
578	if callErr != nil {
579		t.Fatal(callErr)
580	}
581	if funcErr != nil {
582		t.Fatal(funcErr)
583	}
584	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
585`, callBuffer.String())
586	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
587return common.OpenAPIDefinition{
588Schema: spec.Schema{
589SchemaProps: spec.SchemaProps{
590Description: "Blah demonstrate a struct with embedded inline struct field.",
591Type: []string{"object"},
592Properties: map[string]spec.Schema{
593"String": {
594SchemaProps: spec.SchemaProps{
595Description: "A simple string",
596Default: "",
597Type: []string{"string"},
598Format: "",
599},
600},
601},
602Required: []string{"String"},
603},
604},
605}
606}
607
608`, funcBuffer.String())
609}
610
611func TestEmbeddedInlineStructPointer(t *testing.T) {
612	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
613package foo
614
615// Nested is used as embedded inline struct pointer field.
616type Nested struct {
617  // A simple string
618  String string
619}
620
621// Blah demonstrate a struct with embedded inline struct pointer field.
622type Blah struct {
623  // An embedded inline struct pointer field
624  *Nested `+"`"+`json:",inline,omitempty"`+"`"+`
625}
626	`)
627	if callErr != nil {
628		t.Fatal(callErr)
629	}
630	if funcErr != nil {
631		t.Fatal(funcErr)
632	}
633	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
634`, callBuffer.String())
635	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
636return common.OpenAPIDefinition{
637Schema: spec.Schema{
638SchemaProps: spec.SchemaProps{
639Description: "Blah demonstrate a struct with embedded inline struct pointer field.",
640Type: []string{"object"},
641Properties: map[string]spec.Schema{
642"String": {
643SchemaProps: spec.SchemaProps{
644Description: "A simple string",
645Default: "",
646Type: []string{"string"},
647Format: "",
648},
649},
650},
651Required: []string{"String"},
652},
653},
654}
655}
656
657`, funcBuffer.String())
658}
659
660func TestNestedMapString(t *testing.T) {
661	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
662package foo
663
664// Map sample tests openAPIGen.generateMapProperty method.
665type Blah struct {
666	// A sample String to String map
667	StringToArray map[string]map[string]string
668}
669	`)
670	if callErr != nil {
671		t.Fatal(callErr)
672	}
673	if funcErr != nil {
674		t.Fatal(funcErr)
675	}
676	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
677`, callBuffer.String())
678	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
679return common.OpenAPIDefinition{
680Schema: spec.Schema{
681SchemaProps: spec.SchemaProps{
682Description: "Map sample tests openAPIGen.generateMapProperty method.",
683Type: []string{"object"},
684Properties: map[string]spec.Schema{
685"StringToArray": {
686SchemaProps: spec.SchemaProps{
687Description: "A sample String to String map",
688Type: []string{"object"},
689AdditionalProperties: &spec.SchemaOrBool{
690Allows: true,
691Schema: &spec.Schema{
692SchemaProps: spec.SchemaProps{
693Type: []string{"object"},
694AdditionalProperties: &spec.SchemaOrBool{
695Allows: true,
696Schema: &spec.Schema{
697SchemaProps: spec.SchemaProps{
698Default: "",
699Type: []string{"string"},
700Format: "",
701},
702},
703},
704},
705},
706},
707},
708},
709},
710Required: []string{"StringToArray"},
711},
712},
713}
714}
715
716`, funcBuffer.String())
717}
718
719func TestNestedMapInt(t *testing.T) {
720	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
721package foo
722
723// Map sample tests openAPIGen.generateMapProperty method.
724type Blah struct {
725	// A sample String to String map
726	StringToArray map[string]map[string]int
727}
728	`)
729	if callErr != nil {
730		t.Fatal(callErr)
731	}
732	if funcErr != nil {
733		t.Fatal(funcErr)
734	}
735	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
736`, callBuffer.String())
737	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
738return common.OpenAPIDefinition{
739Schema: spec.Schema{
740SchemaProps: spec.SchemaProps{
741Description: "Map sample tests openAPIGen.generateMapProperty method.",
742Type: []string{"object"},
743Properties: map[string]spec.Schema{
744"StringToArray": {
745SchemaProps: spec.SchemaProps{
746Description: "A sample String to String map",
747Type: []string{"object"},
748AdditionalProperties: &spec.SchemaOrBool{
749Allows: true,
750Schema: &spec.Schema{
751SchemaProps: spec.SchemaProps{
752Type: []string{"object"},
753AdditionalProperties: &spec.SchemaOrBool{
754Allows: true,
755Schema: &spec.Schema{
756SchemaProps: spec.SchemaProps{
757Default: 0,
758Type: []string{"integer"},
759Format: "int32",
760},
761},
762},
763},
764},
765},
766},
767},
768},
769Required: []string{"StringToArray"},
770},
771},
772}
773}
774
775`, funcBuffer.String())
776}
777
778func TestNestedMapBoolean(t *testing.T) {
779	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
780package foo
781
782// Map sample tests openAPIGen.generateMapProperty method.
783type Blah struct {
784	// A sample String to String map
785	StringToArray map[string]map[string]bool
786}
787	`)
788	if callErr != nil {
789		t.Fatal(callErr)
790	}
791	if funcErr != nil {
792		t.Fatal(funcErr)
793	}
794	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
795`, callBuffer.String())
796	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
797return common.OpenAPIDefinition{
798Schema: spec.Schema{
799SchemaProps: spec.SchemaProps{
800Description: "Map sample tests openAPIGen.generateMapProperty method.",
801Type: []string{"object"},
802Properties: map[string]spec.Schema{
803"StringToArray": {
804SchemaProps: spec.SchemaProps{
805Description: "A sample String to String map",
806Type: []string{"object"},
807AdditionalProperties: &spec.SchemaOrBool{
808Allows: true,
809Schema: &spec.Schema{
810SchemaProps: spec.SchemaProps{
811Type: []string{"object"},
812AdditionalProperties: &spec.SchemaOrBool{
813Allows: true,
814Schema: &spec.Schema{
815SchemaProps: spec.SchemaProps{
816Default: false,
817Type: []string{"boolean"},
818Format: "",
819},
820},
821},
822},
823},
824},
825},
826},
827},
828Required: []string{"StringToArray"},
829},
830},
831}
832}
833
834`, funcBuffer.String())
835}
836
837func TestFailingSample1(t *testing.T) {
838	_, funcErr, assert, _, _ := testOpenAPITypeWriter(t, `
839package foo
840
841// Map sample tests openAPIGen.generateMapProperty method.
842type Blah struct {
843	// A sample String to String map
844	StringToArray map[string]map[string]map[int]string
845}
846	`)
847	if assert.Error(funcErr, "An error was expected") {
848		assert.Equal(funcErr, fmt.Errorf("failed to generate map property in base/foo.Blah: StringToArray: map with non-string keys are not supported by OpenAPI in map[int]string"))
849	}
850}
851
852func TestFailingSample2(t *testing.T) {
853	_, funcErr, assert, _, _ := testOpenAPITypeWriter(t, `
854package foo
855
856// Map sample tests openAPIGen.generateMapProperty method.
857type Blah struct {
858	// A sample String to String map
859	StringToArray map[int]string
860}	`)
861	if assert.Error(funcErr, "An error was expected") {
862		assert.Equal(funcErr, fmt.Errorf("failed to generate map property in base/foo.Blah: StringToArray: map with non-string keys are not supported by OpenAPI in map[int]string"))
863	}
864}
865
866func TestFailingDefaultEnforced(t *testing.T) {
867	tests := []struct {
868		definition    string
869		expectedError error
870	}{
871		{
872			definition: `
873package foo
874
875type Blah struct {
876	// +default=5
877	Int int
878}	`,
879			expectedError: fmt.Errorf("failed to generate default in base/foo.Blah: Int: invalid default value (5) for non-pointer/non-omitempty. If specified, must be: 0"),
880		},
881		{
882			definition: `
883package foo
884
885type Blah struct {
886	// +default={"foo": 5}
887	Struct struct{
888		foo int
889	}
890}	`,
891			expectedError: fmt.Errorf(`failed to generate default in base/foo.Blah: Struct: invalid default value (map[string]interface {}{"foo":5}) for non-pointer/non-omitempty. If specified, must be: {}`),
892		},
893		{
894			definition: `
895package foo
896
897type Blah struct {
898	List []Item
899
900}
901
902// +default="foo"
903type Item string	`,
904			expectedError: fmt.Errorf(`failed to generate slice property in base/foo.Blah: List: invalid default value ("foo") for non-pointer/non-omitempty. If specified, must be: ""`),
905		},
906		{
907			definition: `
908package foo
909
910type Blah struct {
911	Map map[string]Item
912
913}
914
915// +default="foo"
916type Item string	`,
917			expectedError: fmt.Errorf(`failed to generate map property in base/foo.Blah: Map: invalid default value ("foo") for non-pointer/non-omitempty. If specified, must be: ""`),
918		},
919	}
920
921	for i, test := range tests {
922		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
923			_, funcErr, assert, _, _ := testOpenAPITypeWriter(t, test.definition)
924			if assert.Error(funcErr, "An error was expected") {
925				assert.Equal(funcErr, test.expectedError)
926			}
927		})
928	}
929}
930
931func TestCustomDef(t *testing.T) {
932	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
933package foo
934
935import openapi "k8s.io/kube-openapi/pkg/common"
936
937type Blah struct {
938}
939
940func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition {
941	return openapi.OpenAPIDefinition{
942		Schema: spec.Schema{
943			SchemaProps: spec.SchemaProps{
944				Type:   []string{"string"},
945				Format: "date-time",
946			},
947		},
948	}
949}
950`)
951	if callErr != nil {
952		t.Fatal(callErr)
953	}
954	if funcErr != nil {
955		t.Fatal(funcErr)
956	}
957	assert.Equal(`"base/foo.Blah": foo.Blah{}.OpenAPIDefinition(),
958`, callBuffer.String())
959	assert.Equal(``, funcBuffer.String())
960}
961
962func TestCustomDefV3(t *testing.T) {
963	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
964package foo
965
966import openapi "k8s.io/kube-openapi/pkg/common"
967
968type Blah struct {
969}
970
971func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition {
972	return openapi.OpenAPIDefinition{
973		Schema: spec.Schema{
974			SchemaProps: spec.SchemaProps{
975				Type:   []string{"string"},
976				Format: "date-time",
977			},
978		},
979	}
980}
981`)
982	if callErr != nil {
983		t.Fatal(callErr)
984	}
985	if funcErr != nil {
986		t.Fatal(funcErr)
987	}
988	assert.Equal(`"base/foo.Blah": foo.Blah{}.OpenAPIV3Definition(),
989`, callBuffer.String())
990	assert.Equal(``, funcBuffer.String())
991}
992
993func TestCustomDefV2AndV3(t *testing.T) {
994	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
995package foo
996
997import openapi "k8s.io/kube-openapi/pkg/common"
998
999type Blah struct {
1000}
1001
1002func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition {
1003	return openapi.OpenAPIDefinition{
1004		Schema: spec.Schema{
1005			SchemaProps: spec.SchemaProps{
1006				Type:   []string{"string"},
1007				Format: "date-time",
1008			},
1009		},
1010	}
1011}
1012
1013func (_ Blah) OpenAPIDefinition() openapi.OpenAPIDefinition {
1014	return openapi.OpenAPIDefinition{
1015		Schema: spec.Schema{
1016			SchemaProps: spec.SchemaProps{
1017				Type:   []string{"string"},
1018				Format: "date-time",
1019			},
1020		},
1021	}
1022}
1023`)
1024	if callErr != nil {
1025		t.Fatal(callErr)
1026	}
1027	if funcErr != nil {
1028		t.Fatal(funcErr)
1029	}
1030	assert.Equal(`"base/foo.Blah": common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), foo.Blah{}.OpenAPIDefinition()),
1031`, callBuffer.String())
1032	assert.Equal(``, funcBuffer.String())
1033}
1034
1035func TestCustomDefs(t *testing.T) {
1036	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
1037package foo
1038
1039// Blah is a custom type
1040type Blah struct {
1041}
1042
1043func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} }
1044func (_ Blah) OpenAPISchemaFormat() string { return "date-time" }
1045`)
1046	if callErr != nil {
1047		t.Fatal(callErr)
1048	}
1049	if funcErr != nil {
1050		t.Fatal(funcErr)
1051	}
1052	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
1053`, callBuffer.String())
1054	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
1055return common.OpenAPIDefinition{
1056Schema: spec.Schema{
1057SchemaProps: spec.SchemaProps{
1058Description: "Blah is a custom type",
1059Type:foo.Blah{}.OpenAPISchemaType(),
1060Format:foo.Blah{}.OpenAPISchemaFormat(),
1061},
1062},
1063}
1064}
1065
1066`, funcBuffer.String())
1067}
1068
1069func TestCustomDefsV3(t *testing.T) {
1070	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
1071package foo
1072
1073import openapi "k8s.io/kube-openapi/pkg/common"
1074
1075// Blah is a custom type
1076type Blah struct {
1077}
1078
1079func (_ Blah) OpenAPIV3Definition() openapi.OpenAPIDefinition {
1080	return openapi.OpenAPIDefinition{
1081		Schema: spec.Schema{
1082			SchemaProps: spec.SchemaProps{
1083				Type:   []string{"string"},
1084				Format: "date-time",
1085			},
1086		},
1087	}
1088}
1089
1090func (_ Blah) OpenAPISchemaType() []string { return []string{"string"} }
1091func (_ Blah) OpenAPISchemaFormat() string { return "date-time" }
1092`)
1093	if callErr != nil {
1094		t.Fatal(callErr)
1095	}
1096	if funcErr != nil {
1097		t.Fatal(funcErr)
1098	}
1099	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
1100`, callBuffer.String())
1101	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
1102return common.EmbedOpenAPIDefinitionIntoV2Extension(foo.Blah{}.OpenAPIV3Definition(), common.OpenAPIDefinition{
1103Schema: spec.Schema{
1104SchemaProps: spec.SchemaProps{
1105Description: "Blah is a custom type",
1106Type:foo.Blah{}.OpenAPISchemaType(),
1107Format:foo.Blah{}.OpenAPISchemaFormat(),
1108},
1109},
1110})
1111}
1112
1113`, funcBuffer.String())
1114}
1115
1116func TestPointer(t *testing.T) {
1117	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
1118package foo
1119
1120// PointerSample demonstrate pointer's properties
1121type Blah struct {
1122	// A string pointer
1123	StringPointer *string
1124	// A struct pointer
1125	StructPointer *Blah
1126	// A slice pointer
1127	SlicePointer *[]string
1128	// A map pointer
1129	MapPointer *map[string]string
1130}
1131	`)
1132	if callErr != nil {
1133		t.Fatal(callErr)
1134	}
1135	if funcErr != nil {
1136		t.Fatal(funcErr)
1137	}
1138	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
1139`, callBuffer.String())
1140	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
1141return common.OpenAPIDefinition{
1142Schema: spec.Schema{
1143SchemaProps: spec.SchemaProps{
1144Description: "PointerSample demonstrate pointer's properties",
1145Type: []string{"object"},
1146Properties: map[string]spec.Schema{
1147"StringPointer": {
1148SchemaProps: spec.SchemaProps{
1149Description: "A string pointer",
1150Type: []string{"string"},
1151Format: "",
1152},
1153},
1154"StructPointer": {
1155SchemaProps: spec.SchemaProps{
1156Description: "A struct pointer",
1157Ref: ref("base/foo.Blah"),
1158},
1159},
1160"SlicePointer": {
1161SchemaProps: spec.SchemaProps{
1162Description: "A slice pointer",
1163Type: []string{"array"},
1164Items: &spec.SchemaOrArray{
1165Schema: &spec.Schema{
1166SchemaProps: spec.SchemaProps{
1167Default: "",
1168Type: []string{"string"},
1169Format: "",
1170},
1171},
1172},
1173},
1174},
1175"MapPointer": {
1176SchemaProps: spec.SchemaProps{
1177Description: "A map pointer",
1178Type: []string{"object"},
1179AdditionalProperties: &spec.SchemaOrBool{
1180Allows: true,
1181Schema: &spec.Schema{
1182SchemaProps: spec.SchemaProps{
1183Default: "",
1184Type: []string{"string"},
1185Format: "",
1186},
1187},
1188},
1189},
1190},
1191},
1192Required: []string{"StringPointer","StructPointer","SlicePointer","MapPointer"},
1193},
1194},
1195Dependencies: []string{
1196"base/foo.Blah",},
1197}
1198}
1199
1200`, funcBuffer.String())
1201}
1202
1203func TestNestedLists(t *testing.T) {
1204	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
1205package foo
1206
1207// Blah is a test.
1208// +k8s:openapi-gen=true
1209// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
1210type Blah struct {
1211	// Nested list
1212	NestedList [][]int64
1213}
1214`)
1215	if callErr != nil {
1216		t.Fatal(callErr)
1217	}
1218	if funcErr != nil {
1219		t.Fatal(funcErr)
1220	}
1221	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
1222`, callBuffer.String())
1223	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
1224return common.OpenAPIDefinition{
1225Schema: spec.Schema{
1226SchemaProps: spec.SchemaProps{
1227Description: "Blah is a test.",
1228Type: []string{"object"},
1229Properties: map[string]spec.Schema{
1230"NestedList": {
1231SchemaProps: spec.SchemaProps{
1232Description: "Nested list",
1233Type: []string{"array"},
1234Items: &spec.SchemaOrArray{
1235Schema: &spec.Schema{
1236SchemaProps: spec.SchemaProps{
1237Type: []string{"array"},
1238Items: &spec.SchemaOrArray{
1239Schema: &spec.Schema{
1240SchemaProps: spec.SchemaProps{
1241Default: 0,
1242Type: []string{"integer"},
1243Format: "int64",
1244},
1245},
1246},
1247},
1248},
1249},
1250},
1251},
1252},
1253Required: []string{"NestedList"},
1254},
1255VendorExtensible: spec.VendorExtensible{
1256Extensions: spec.Extensions{
1257"x-kubernetes-type-tag": "type_test",
1258},
1259},
1260},
1261}
1262}
1263
1264`, funcBuffer.String())
1265}
1266
1267func TestNestListOfMaps(t *testing.T) {
1268	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
1269package foo
1270
1271// Blah is a test.
1272// +k8s:openapi-gen=true
1273// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
1274type Blah struct {
1275	// Nested list of maps
1276	NestedListOfMaps [][]map[string]string
1277}
1278`)
1279	if callErr != nil {
1280		t.Fatal(callErr)
1281	}
1282	if funcErr != nil {
1283		t.Fatal(funcErr)
1284	}
1285	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
1286`, callBuffer.String())
1287	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
1288return common.OpenAPIDefinition{
1289Schema: spec.Schema{
1290SchemaProps: spec.SchemaProps{
1291Description: "Blah is a test.",
1292Type: []string{"object"},
1293Properties: map[string]spec.Schema{
1294"NestedListOfMaps": {
1295SchemaProps: spec.SchemaProps{
1296Description: "Nested list of maps",
1297Type: []string{"array"},
1298Items: &spec.SchemaOrArray{
1299Schema: &spec.Schema{
1300SchemaProps: spec.SchemaProps{
1301Type: []string{"array"},
1302Items: &spec.SchemaOrArray{
1303Schema: &spec.Schema{
1304SchemaProps: spec.SchemaProps{
1305Type: []string{"object"},
1306AdditionalProperties: &spec.SchemaOrBool{
1307Allows: true,
1308Schema: &spec.Schema{
1309SchemaProps: spec.SchemaProps{
1310Default: "",
1311Type: []string{"string"},
1312Format: "",
1313},
1314},
1315},
1316},
1317},
1318},
1319},
1320},
1321},
1322},
1323},
1324},
1325Required: []string{"NestedListOfMaps"},
1326},
1327VendorExtensible: spec.VendorExtensible{
1328Extensions: spec.Extensions{
1329"x-kubernetes-type-tag": "type_test",
1330},
1331},
1332},
1333}
1334}
1335
1336`, funcBuffer.String())
1337}
1338
1339func TestExtensions(t *testing.T) {
1340	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
1341package foo
1342
1343// Blah is a test.
1344// +k8s:openapi-gen=true
1345// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
1346type Blah struct {
1347	// a member with a list type with two map keys
1348	// +listType=map
1349	// +listMapKey=port
1350	// +listMapKey=protocol
1351	WithListField []string
1352
1353	// another member with a list type with one map key
1354	// +listType=map
1355	// +listMapKey=port
1356	WithListField2 []string
1357}
1358		`)
1359	if callErr != nil {
1360		t.Fatal(callErr)
1361	}
1362	if funcErr != nil {
1363		t.Fatal(funcErr)
1364	}
1365	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
1366`, callBuffer.String())
1367	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
1368return common.OpenAPIDefinition{
1369Schema: spec.Schema{
1370SchemaProps: spec.SchemaProps{
1371Description: "Blah is a test.",
1372Type: []string{"object"},
1373Properties: map[string]spec.Schema{
1374"WithListField": {
1375VendorExtensible: spec.VendorExtensible{
1376Extensions: spec.Extensions{
1377"x-kubernetes-list-map-keys": []interface{}{
1378"port",
1379"protocol",
1380},
1381"x-kubernetes-list-type": "map",
1382},
1383},
1384SchemaProps: spec.SchemaProps{
1385Description: "a member with a list type with two map keys",
1386Type: []string{"array"},
1387Items: &spec.SchemaOrArray{
1388Schema: &spec.Schema{
1389SchemaProps: spec.SchemaProps{
1390Default: "",
1391Type: []string{"string"},
1392Format: "",
1393},
1394},
1395},
1396},
1397},
1398"WithListField2": {
1399VendorExtensible: spec.VendorExtensible{
1400Extensions: spec.Extensions{
1401"x-kubernetes-list-map-keys": []interface{}{
1402"port",
1403},
1404"x-kubernetes-list-type": "map",
1405},
1406},
1407SchemaProps: spec.SchemaProps{
1408Description: "another member with a list type with one map key",
1409Type: []string{"array"},
1410Items: &spec.SchemaOrArray{
1411Schema: &spec.Schema{
1412SchemaProps: spec.SchemaProps{
1413Default: "",
1414Type: []string{"string"},
1415Format: "",
1416},
1417},
1418},
1419},
1420},
1421},
1422Required: []string{"WithListField","WithListField2"},
1423},
1424VendorExtensible: spec.VendorExtensible{
1425Extensions: spec.Extensions{
1426"x-kubernetes-type-tag": "type_test",
1427},
1428},
1429},
1430}
1431}
1432
1433`, funcBuffer.String())
1434}
1435
1436func TestUnion(t *testing.T) {
1437	callErr, funcErr, assert, callBuffer, funcBuffer := testOpenAPITypeWriter(t, `
1438package foo
1439
1440// Blah is a test.
1441// +k8s:openapi-gen=true
1442// +k8s:openapi-gen=x-kubernetes-type-tag:type_test
1443// +union
1444type Blah struct {
1445	// +unionDiscriminator
1446	Discriminator *string `+"`"+`json:"discriminator"`+"`"+`
1447        // +optional
1448        Numeric int `+"`"+`json:"numeric"`+"`"+`
1449        // +optional
1450        String string `+"`"+`json:"string"`+"`"+`
1451        // +optional
1452        Float float64 `+"`"+`json:"float"`+"`"+`
1453}
1454		`)
1455	if callErr != nil {
1456		t.Fatal(callErr)
1457	}
1458	if funcErr != nil {
1459		t.Fatal(funcErr)
1460	}
1461	assert.Equal(`"base/foo.Blah": schema_base_foo_Blah(ref),
1462`, callBuffer.String())
1463	assert.Equal(`func schema_base_foo_Blah(ref common.ReferenceCallback) common.OpenAPIDefinition {
1464return common.OpenAPIDefinition{
1465Schema: spec.Schema{
1466SchemaProps: spec.SchemaProps{
1467Description: "Blah is a test.",
1468Type: []string{"object"},
1469Properties: map[string]spec.Schema{
1470"discriminator": {
1471SchemaProps: spec.SchemaProps{
1472Type: []string{"string"},
1473Format: "",
1474},
1475},
1476"numeric": {
1477SchemaProps: spec.SchemaProps{
1478Default: 0,
1479Type: []string{"integer"},
1480Format: "int32",
1481},
1482},
1483"string": {
1484SchemaProps: spec.SchemaProps{
1485Default: "",
1486Type: []string{"string"},
1487Format: "",
1488},
1489},
1490"float": {
1491SchemaProps: spec.SchemaProps{
1492Default: 0,
1493Type: []string{"number"},
1494Format: "double",
1495},
1496},
1497},
1498Required: []string{"discriminator"},
1499},
1500VendorExtensible: spec.VendorExtensible{
1501Extensions: spec.Extensions{
1502"x-kubernetes-type-tag": "type_test",
1503"x-kubernetes-unions": []interface{}{
1504map[string]interface{}{
1505"discriminator": "discriminator",
1506"fields-to-discriminateBy": map[string]interface{}{
1507"float": "Float",
1508"numeric": "Numeric",
1509"string": "String",
1510},
1511},
1512},
1513},
1514},
1515},
1516}
1517}
1518
1519`, funcBuffer.String())
1520}
1521