github.com/ffalor/go-swagger@v0.0.0-20231011000038-9f25265ac351/generator/types_test.go (about)

     1  package generator
     2  
     3  import (
     4  	"encoding/json"
     5  	"io"
     6  	"log"
     7  	"os"
     8  	"strconv"
     9  	"testing"
    10  
    11  	"github.com/go-openapi/loads"
    12  	"github.com/go-openapi/spec"
    13  	"github.com/go-openapi/swag"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  type externalTypeFixture struct {
    18  	title     string
    19  	schema    string
    20  	expected  *externalTypeDefinition
    21  	knownDefs struct{ tpe, pkg, alias string }
    22  	resolved  resolvedType
    23  }
    24  
    25  func makeResolveExternalTypes() []externalTypeFixture {
    26  	return []externalTypeFixture{
    27  		{
    28  			title: "hint as map",
    29  			schema: `{
    30  		"type": "object",
    31  		"x-go-type": {
    32  			"type": "Mytype",
    33  			"import": {
    34  				"package": "github.com/fredbi/mymodels",
    35  				"alias": "external"
    36  			},
    37  			"hints": {
    38  			  "kind": "map"
    39  			},
    40  			"embedded": false
    41  		}
    42  	}`,
    43  			expected: &externalTypeDefinition{
    44  				Type: "Mytype",
    45  				Import: struct {
    46  					Package string
    47  					Alias   string
    48  				}{
    49  					Package: "github.com/fredbi/mymodels",
    50  					Alias:   "external",
    51  				},
    52  				Hints: struct {
    53  					Kind         string
    54  					Nullable     *bool
    55  					NoValidation *bool
    56  				}{
    57  					Kind: "map",
    58  				},
    59  				Embedded: false,
    60  			},
    61  			knownDefs: struct{ tpe, pkg, alias string }{
    62  				tpe:   "external.Mytype",
    63  				pkg:   "github.com/fredbi/mymodels",
    64  				alias: "external",
    65  			},
    66  			resolved: resolvedType{
    67  				GoType:         "external.Mytype",
    68  				IsMap:          true,
    69  				SwaggerType:    "object",
    70  				IsEmptyOmitted: true,
    71  				Pkg:            "github.com/fredbi/mymodels",
    72  				PkgAlias:       "external",
    73  			},
    74  		},
    75  		{
    76  			title: "hint as map, embedded",
    77  			schema: `{
    78  		"type": "object",
    79  		"x-go-type": {
    80  			"type": "Mytype",
    81  			"import": {
    82  				"package": "github.com/fredbi/mymodels",
    83  				"alias": "external"
    84  			},
    85  			"hints": {
    86  			  "kind": "map"
    87  			},
    88  			"embedded": true
    89  		}
    90  	}`,
    91  			expected: &externalTypeDefinition{
    92  				Type: "Mytype",
    93  				Import: struct {
    94  					Package string
    95  					Alias   string
    96  				}{
    97  					Package: "github.com/fredbi/mymodels",
    98  					Alias:   "external",
    99  				},
   100  				Hints: struct {
   101  					Kind         string
   102  					Nullable     *bool
   103  					NoValidation *bool
   104  				}{
   105  					Kind: "map",
   106  				},
   107  				Embedded: true,
   108  			},
   109  			knownDefs: struct{ tpe, pkg, alias string }{
   110  				tpe:   "A",
   111  				pkg:   "",
   112  				alias: "",
   113  			},
   114  			resolved: resolvedType{
   115  				GoType:         "A",
   116  				IsMap:          true,
   117  				SwaggerType:    "object",
   118  				IsEmptyOmitted: true,
   119  			},
   120  		},
   121  		{
   122  			title: "hint as array, nullable",
   123  			schema: `{
   124  		"type": "object",
   125  		"x-go-type": {
   126  			"type": "Mytype",
   127  			"import": {
   128  				"package": "github.com/fredbi/mymodels"
   129  			},
   130  			"hints": {
   131  			  "kind": "array",
   132  				"nullable": true
   133  			}
   134  		}
   135  	}`,
   136  			expected: &externalTypeDefinition{
   137  				Type: "Mytype",
   138  				Import: struct {
   139  					Package string
   140  					Alias   string
   141  				}{
   142  					Package: "github.com/fredbi/mymodels",
   143  					// Alias:   "mymodels",
   144  				},
   145  				Hints: struct {
   146  					Kind         string
   147  					Nullable     *bool
   148  					NoValidation *bool
   149  				}{
   150  					Kind:     "array",
   151  					Nullable: swag.Bool(true),
   152  				},
   153  				Embedded: false,
   154  			},
   155  			knownDefs: struct{ tpe, pkg, alias string }{tpe: "mymodels.Mytype", pkg: "github.com/fredbi/mymodels", alias: "mymodels"},
   156  			resolved: resolvedType{
   157  				GoType:         "mymodels.Mytype",
   158  				IsArray:        true,
   159  				SwaggerType:    "array",
   160  				IsEmptyOmitted: false,
   161  				Pkg:            "github.com/fredbi/mymodels",
   162  				PkgAlias:       "mymodels",
   163  				IsNullable:     true,
   164  			},
   165  		},
   166  		{
   167  			title: "hint as map, unaliased",
   168  			schema: `{
   169  		"type": "object",
   170  		"x-go-type": {
   171  			"type": "Mytype",
   172  			"import": {
   173  				"package": "github.com/fredbi/mymodels"
   174  			},
   175  			"hints": {
   176  			  "kind": "map"
   177  			}
   178  		}
   179  	}`,
   180  			expected: &externalTypeDefinition{
   181  				Type: "Mytype",
   182  				Import: struct {
   183  					Package string
   184  					Alias   string
   185  				}{
   186  					Package: "github.com/fredbi/mymodels",
   187  					// Alias:   "mymodels",
   188  				},
   189  				Hints: struct {
   190  					Kind         string
   191  					Nullable     *bool
   192  					NoValidation *bool
   193  				}{
   194  					Kind: "map",
   195  				},
   196  			},
   197  			knownDefs: struct{ tpe, pkg, alias string }{tpe: "mymodels.Mytype", pkg: "github.com/fredbi/mymodels", alias: "mymodels"},
   198  			resolved: resolvedType{
   199  				GoType:         "mymodels.Mytype",
   200  				IsMap:          true,
   201  				SwaggerType:    "object",
   202  				IsEmptyOmitted: true,
   203  				Pkg:            "github.com/fredbi/mymodels",
   204  				PkgAlias:       "mymodels",
   205  			},
   206  		},
   207  		{
   208  			title: "hint as tuple, unaliased",
   209  			schema: `{
   210  		"type": "object",
   211  		"x-go-type": {
   212  			"type": "Mytype",
   213  			"import": {
   214  				"package": "github.com/fredbi/mymodels"
   215  			},
   216  			"hints": {
   217  			  "kind": "tuple"
   218  			}
   219  		}
   220  	}`,
   221  			expected: &externalTypeDefinition{
   222  				Type: "Mytype",
   223  				Import: struct {
   224  					Package string
   225  					Alias   string
   226  				}{
   227  					Package: "github.com/fredbi/mymodels",
   228  					// Alias:   "mymodels",
   229  				},
   230  				Hints: struct {
   231  					Kind         string
   232  					Nullable     *bool
   233  					NoValidation *bool
   234  				}{
   235  					Kind: "tuple",
   236  				},
   237  			},
   238  			knownDefs: struct{ tpe, pkg, alias string }{tpe: "mymodels.Mytype", pkg: "github.com/fredbi/mymodels", alias: "mymodels"},
   239  			resolved: resolvedType{
   240  				GoType:         "mymodels.Mytype",
   241  				IsTuple:        true,
   242  				SwaggerType:    "array",
   243  				IsEmptyOmitted: true,
   244  				Pkg:            "github.com/fredbi/mymodels",
   245  				PkgAlias:       "mymodels",
   246  			},
   247  		},
   248  		{
   249  			title: "hint as primitive, unaliased",
   250  			schema: `{
   251  		"type": "number",
   252  		"x-go-type": {
   253  			"type": "Mytype",
   254  			"import": {
   255  				"package": "github.com/fredbi/mymodels"
   256  			},
   257  			"hints": {
   258  			  "kind": "primitive"
   259  			}
   260  		}
   261  	}`,
   262  			expected: &externalTypeDefinition{
   263  				Type: "Mytype",
   264  				Import: struct {
   265  					Package string
   266  					Alias   string
   267  				}{
   268  					Package: "github.com/fredbi/mymodels",
   269  					// Alias:   "mymodels",
   270  				},
   271  				Hints: struct {
   272  					Kind         string
   273  					Nullable     *bool
   274  					NoValidation *bool
   275  				}{
   276  					Kind: "primitive",
   277  				},
   278  			},
   279  			knownDefs: struct{ tpe, pkg, alias string }{tpe: "mymodels.Mytype", pkg: "github.com/fredbi/mymodels", alias: "mymodels"},
   280  			resolved: resolvedType{
   281  				GoType:         "mymodels.Mytype",
   282  				IsPrimitive:    true,
   283  				SwaggerType:    "",
   284  				IsEmptyOmitted: true,
   285  				Pkg:            "github.com/fredbi/mymodels",
   286  				PkgAlias:       "mymodels",
   287  			},
   288  		},
   289  		{
   290  			title: "default model package",
   291  			schema: `{
   292  		"type": "number",
   293  		"x-go-type": {
   294  			"type": "Mytype",
   295  			"hints": {
   296  			  "kind": "primitive"
   297  			}
   298  		}
   299  	}`,
   300  			expected: &externalTypeDefinition{
   301  				Type: "Mytype",
   302  				Import: struct {
   303  					Package string
   304  					Alias   string
   305  				}{
   306  					// Package: "github.com/example/custom",
   307  					// Alias:   "custom",
   308  				},
   309  				Hints: struct {
   310  					Kind         string
   311  					Nullable     *bool
   312  					NoValidation *bool
   313  				}{
   314  					Kind: "primitive",
   315  				},
   316  			},
   317  			knownDefs: struct{ tpe, pkg, alias string }{tpe: "Mytype", pkg: "", alias: ""},
   318  			resolved: resolvedType{
   319  				GoType:         "Mytype",
   320  				IsPrimitive:    true,
   321  				SwaggerType:    "",
   322  				IsEmptyOmitted: true,
   323  				Pkg:            "",
   324  				PkgAlias:       "",
   325  			},
   326  		},
   327  	}
   328  }
   329  
   330  func TestShortCircuitResolveExternal(t *testing.T) {
   331  	defer discardOutput()()
   332  
   333  	for i, toPin := range makeResolveExternalTypes() {
   334  		fixture := toPin
   335  		var title string
   336  		if fixture.title == "" {
   337  			title = strconv.Itoa(i)
   338  		} else {
   339  			title = fixture.title
   340  		}
   341  		t.Run(title, func(t *testing.T) {
   342  			jazonDoc := fixture.schema
   343  			doc, err := loads.Embedded([]byte(jazonDoc), []byte(jazonDoc))
   344  			require.NoErrorf(t, err, "fixture %d", i)
   345  
   346  			r := newTypeResolver("models", "github.com/example/custom", doc)
   347  			var schema spec.Schema
   348  			err = json.Unmarshal([]byte(jazonDoc), &schema)
   349  			require.NoErrorf(t, err, "fixture %d", i)
   350  
   351  			extType, ok := hasExternalType(schema.Extensions)
   352  			require.Truef(t, ok, "fixture %d", i)
   353  			require.NotNil(t, extType)
   354  
   355  			tpe, pkg, alias := r.knownDefGoType("A", schema, r.goTypeName)
   356  			require.EqualValuesf(t, fixture.knownDefs, struct{ tpe, pkg, alias string }{tpe, pkg, alias}, "fixture %d", i)
   357  
   358  			resolved := r.shortCircuitResolveExternal(tpe, pkg, alias, extType, &schema, false)
   359  
   360  			require.EqualValues(t, fixture.expected, extType)
   361  
   362  			resolved.Extensions = nil // don't assert this
   363  			require.EqualValuesf(t, fixture.resolved, resolved, "fixture %d", i)
   364  		})
   365  	}
   366  }
   367  
   368  type guardValidationsFixture struct {
   369  	Title        string
   370  	ResolvedType string
   371  	Type         interface {
   372  		Validations() spec.SchemaValidations
   373  		SetValidations(spec.SchemaValidations)
   374  	}
   375  	Asserter func(testing.TB, spec.SchemaValidations)
   376  }
   377  
   378  func makeGuardValidationFixtures() []guardValidationsFixture {
   379  	return []guardValidationsFixture{
   380  		{
   381  			Title:        "simple schema: guard array",
   382  			ResolvedType: "array",
   383  			Type: spec.NewItems().
   384  				Typed("number", "int64").
   385  				WithValidations(spec.CommonValidations{MinLength: swag.Int64(15), Maximum: swag.Float64(12.00)}).
   386  				UniqueValues(),
   387  			Asserter: func(t testing.TB, val spec.SchemaValidations) {
   388  				require.False(t, val.HasNumberValidations(), "expected no number validations, got: %#v", val)
   389  				require.False(t, val.HasStringValidations(), "expected no string validations, got: %#v", val)
   390  				require.True(t, val.HasArrayValidations(), "expected array validations, got: %#v", val)
   391  			},
   392  		},
   393  		{
   394  			Title:        "simple schema: guard string",
   395  			ResolvedType: "string",
   396  			Type: spec.QueryParam("p1").
   397  				Typed("string", "uuid").
   398  				WithValidations(spec.CommonValidations{MinItems: swag.Int64(15), Maximum: swag.Float64(12.00)}).
   399  				WithMinLength(12),
   400  			Asserter: func(t testing.TB, val spec.SchemaValidations) {
   401  				require.False(t, val.HasNumberValidations(), "expected no number validations, got: %#v", val)
   402  				require.False(t, val.HasArrayValidations(), "expected no array validations, got: %#v", val)
   403  				require.True(t, val.HasStringValidations(), "expected string validations, got: %#v", val)
   404  			},
   405  		},
   406  		{
   407  			Title:        "simple schema: guard file (1/3)",
   408  			ResolvedType: "file",
   409  			Type: spec.FileParam("p1").
   410  				WithValidations(spec.CommonValidations{MinItems: swag.Int64(15), Maximum: swag.Float64(12.00)}).
   411  				WithMinLength(12),
   412  			Asserter: func(t testing.TB, val spec.SchemaValidations) {
   413  				require.False(t, val.HasNumberValidations(), "expected no number validations, got: %#v", val)
   414  				require.False(t, val.HasArrayValidations(), "expected no array validations, got: %#v", val)
   415  				require.True(t, val.HasStringValidations(), "expected string validations, got: %#v", val)
   416  			},
   417  		},
   418  		{
   419  			Title:        "simple schema: guard file (2/3)",
   420  			ResolvedType: "file",
   421  			Type: spec.FileParam("p1").
   422  				WithValidations(spec.CommonValidations{
   423  					MinItems: swag.Int64(15),
   424  					Maximum:  swag.Float64(12.00),
   425  					Pattern:  "xyz",
   426  					Enum:     []interface{}{"x", 34},
   427  				}),
   428  			Asserter: func(t testing.TB, val spec.SchemaValidations) {
   429  				require.False(t, val.HasNumberValidations(), "expected no number validations, got: %#v", val)
   430  				require.False(t, val.HasArrayValidations(), "expected no array validations, got: %#v", val)
   431  				require.False(t, val.HasStringValidations(), "expected no string validations, got: %#v", val)
   432  				require.False(t, val.HasEnum(), "expected no enum validations, got: %#v", val)
   433  			},
   434  		},
   435  		{
   436  			Title:        "schema: guard object",
   437  			ResolvedType: "object",
   438  			Type: spec.RefSchema("#/definitions/nowhere").
   439  				WithValidations(spec.SchemaValidations{
   440  					CommonValidations: spec.CommonValidations{
   441  						MinItems: swag.Int64(15),
   442  						Maximum:  swag.Float64(12.00),
   443  					},
   444  					MinProperties: swag.Int64(10),
   445  				}).
   446  				WithMinLength(12),
   447  			Asserter: func(t testing.TB, val spec.SchemaValidations) {
   448  				require.False(t, val.HasNumberValidations(), "expected no number validations, got: %#v", val)
   449  				require.False(t, val.HasArrayValidations(), "expected no array validations, got: %#v", val)
   450  				require.False(t, val.HasStringValidations(), "expected no string validations, got: %#v", val)
   451  				require.True(t, val.HasObjectValidations(), "expected object validations, got: %#v", val)
   452  			},
   453  		},
   454  		{
   455  			Title:        "simple schema: guard number",
   456  			ResolvedType: "number",
   457  			Type: spec.QueryParam("p1").
   458  				Typed("number", "double").
   459  				WithValidations(spec.CommonValidations{MinItems: swag.Int64(15), MultipleOf: swag.Float64(12.00), Pattern: "xyz"}).
   460  				WithMinLength(12),
   461  			Asserter: func(t testing.TB, val spec.SchemaValidations) {
   462  				require.False(t, val.HasArrayValidations(), "expected no array validations, got: %#v", val)
   463  				require.False(t, val.HasStringValidations(), "expected no string validations, got: %#v", val)
   464  				require.True(t, val.HasNumberValidations(), "expected number validations, got: %#v", val)
   465  			},
   466  		},
   467  	}
   468  }
   469  
   470  func TestGuardValidations(t *testing.T) {
   471  	log.SetOutput(io.Discard)
   472  	defer func() {
   473  		log.SetOutput(os.Stdout)
   474  	}()
   475  
   476  	for _, toPin := range makeGuardValidationFixtures() {
   477  		testCase := toPin
   478  		t.Run(testCase.Title, func(t *testing.T) {
   479  			t.Parallel()
   480  			input := testCase.Type
   481  			guardValidations(testCase.ResolvedType, input)
   482  			if testCase.Asserter != nil {
   483  				testCase.Asserter(t, input.Validations())
   484  			}
   485  		})
   486  	}
   487  }
   488  
   489  func makeGuardFormatFixtures() []guardValidationsFixture {
   490  	return []guardValidationsFixture{
   491  		{
   492  			Title:        "schema: guard date format",
   493  			ResolvedType: "date",
   494  			Type: spec.StringProperty().
   495  				WithValidations(spec.SchemaValidations{
   496  					CommonValidations: spec.CommonValidations{
   497  						MinLength: swag.Int64(15),
   498  						Pattern:   "xyz",
   499  						Enum:      []interface{}{"x", 34},
   500  					}}),
   501  			Asserter: func(t testing.TB, val spec.SchemaValidations) {
   502  				require.True(t, val.HasStringValidations(), "expected string validations, got: %#v", val)
   503  				require.True(t, val.HasEnum())
   504  			},
   505  		},
   506  		{
   507  			Title:        "simple schema: guard binary format",
   508  			ResolvedType: "binary",
   509  			Type: spec.StringProperty().
   510  				WithValidations(spec.SchemaValidations{
   511  					CommonValidations: spec.CommonValidations{
   512  						MinLength: swag.Int64(15),
   513  						Pattern:   "xyz",
   514  						Enum:      []interface{}{"x", 34},
   515  					}}),
   516  			Asserter: func(t testing.TB, val spec.SchemaValidations) {
   517  				require.False(t, val.HasStringValidations(), "expected no string validations, got: %#v", val)
   518  				require.False(t, val.HasEnum())
   519  			},
   520  		},
   521  	}
   522  }
   523  
   524  func TestGuardFormatConflicts(t *testing.T) {
   525  	defer discardOutput()()
   526  
   527  	for _, toPin := range makeGuardFormatFixtures() {
   528  		testCase := toPin
   529  		t.Run(testCase.Title, func(t *testing.T) {
   530  			t.Parallel()
   531  			input := testCase.Type
   532  			guardFormatConflicts(testCase.ResolvedType, input)
   533  			if testCase.Asserter != nil {
   534  				testCase.Asserter(t, input.Validations())
   535  			}
   536  		})
   537  	}
   538  }