...

Source file src/github.com/go-openapi/swag/yaml_test.go

Documentation: github.com/go-openapi/swag

     1  // Copyright 2015 go-swagger maintainers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package swag
    16  
    17  import (
    18  	"encoding/json"
    19  	"net/http"
    20  	"testing"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  	yaml "gopkg.in/yaml.v3"
    25  )
    26  
    27  func TestJSONToYAML(t *testing.T) {
    28  	sd := `{"1":"the int key value","name":"a string value","y":"some value"}`
    29  	var data JSONMapSlice
    30  	require.NoError(t, json.Unmarshal([]byte(sd), &data))
    31  
    32  	y, err := data.MarshalYAML()
    33  	require.NoError(t, err)
    34  	const expected = `"1": the int key value
    35  name: a string value
    36  y: some value
    37  `
    38  	assert.Equal(t, expected, string(y.([]byte)))
    39  
    40  	nstd := `{"1":"the int key value","name":"a string value","y":"some value","tag":{"name":"tag name"}}`
    41  	const nestpected = `"1": the int key value
    42  name: a string value
    43  y: some value
    44  tag:
    45      name: tag name
    46  `
    47  	var ndata JSONMapSlice
    48  	require.NoError(t, json.Unmarshal([]byte(nstd), &ndata))
    49  	ny, err := ndata.MarshalYAML()
    50  	require.NoError(t, err)
    51  	assert.Equal(t, nestpected, string(ny.([]byte)))
    52  
    53  	ydoc, err := BytesToYAMLDoc([]byte(fixtures2224))
    54  	require.NoError(t, err)
    55  	b, err := YAMLToJSON(ydoc)
    56  	require.NoError(t, err)
    57  
    58  	var bdata JSONMapSlice
    59  	require.NoError(t, json.Unmarshal(b, &bdata))
    60  
    61  }
    62  
    63  func TestJSONToYAMLWithNull(t *testing.T) {
    64  	const (
    65  		jazon    = `{"1":"the int key value","name":null,"y":"some value"}`
    66  		expected = `"1": the int key value
    67  name: null
    68  y: some value
    69  `
    70  	)
    71  	var data JSONMapSlice
    72  	require.NoError(t, json.Unmarshal([]byte(jazon), &data))
    73  	ny, err := data.MarshalYAML()
    74  	require.NoError(t, err)
    75  	assert.Equal(t, expected, string(ny.([]byte)))
    76  }
    77  
    78  func TestMarshalYAML(t *testing.T) {
    79  	t.Run("marshalYAML should be deterministic", func(t *testing.T) {
    80  		const (
    81  			jazon    = `{"1":"x","2":null,"3":{"a":1,"b":2,"c":3}}`
    82  			expected = `"1": x
    83  "2": null
    84  "3":
    85      a: !!float 1
    86      b: !!float 2
    87      c: !!float 3
    88  `
    89  		)
    90  		const iterations = 10
    91  		for n := 0; n < iterations; n++ {
    92  			var data JSONMapSlice
    93  			require.NoError(t, json.Unmarshal([]byte(jazon), &data))
    94  			ny, err := data.MarshalYAML()
    95  			require.NoError(t, err)
    96  			assert.Equal(t, expected, string(ny.([]byte)))
    97  		}
    98  	})
    99  }
   100  
   101  func TestYAMLToJSON(t *testing.T) {
   102  	sd := `---
   103  1: the int key value
   104  name: a string value
   105  'y': some value
   106  `
   107  	var data yaml.Node
   108  	_ = yaml.Unmarshal([]byte(sd), &data)
   109  
   110  	d, err := YAMLToJSON(data)
   111  	require.NoError(t, err)
   112  	require.NotNil(t, d)
   113  	assert.Equal(t, `{"1":"the int key value","name":"a string value","y":"some value"}`, string(d))
   114  
   115  	ns := []*yaml.Node{
   116  		{
   117  			Kind:  yaml.ScalarNode,
   118  			Value: "true",
   119  			Tag:   "!!bool",
   120  		},
   121  		{
   122  			Kind:  yaml.ScalarNode,
   123  			Value: "the bool value",
   124  			Tag:   "!!str",
   125  		},
   126  	}
   127  	data.Content[0].Content = append(data.Content[0].Content, ns...)
   128  	d, err = YAMLToJSON(data)
   129  	require.Error(t, err)
   130  	require.Nil(t, d)
   131  
   132  	data.Content[0].Content = data.Content[0].Content[:len(data.Content[0].Content)-2]
   133  
   134  	tag := []*yaml.Node{
   135  		{
   136  			Kind:  yaml.ScalarNode,
   137  			Value: "tag",
   138  			Tag:   "!!str",
   139  		},
   140  		{
   141  			Kind: yaml.MappingNode,
   142  			Content: []*yaml.Node{
   143  				{
   144  					Kind:  yaml.ScalarNode,
   145  					Value: "name",
   146  					Tag:   "!!str",
   147  				},
   148  				{
   149  					Kind:  yaml.ScalarNode,
   150  					Value: "tag name",
   151  					Tag:   "!!str",
   152  				},
   153  			},
   154  		},
   155  	}
   156  	data.Content[0].Content = append(data.Content[0].Content, tag...)
   157  
   158  	d, err = YAMLToJSON(data)
   159  	require.NoError(t, err)
   160  	assert.Equal(t, `{"1":"the int key value","name":"a string value","y":"some value","tag":{"name":"tag name"}}`, string(d))
   161  
   162  	tag[1].Content = []*yaml.Node{
   163  		{
   164  			Kind:  yaml.ScalarNode,
   165  			Value: "true",
   166  			Tag:   "!!bool",
   167  		},
   168  		{
   169  			Kind:  yaml.ScalarNode,
   170  			Value: "the bool tag name",
   171  			Tag:   "!!str",
   172  		},
   173  	}
   174  
   175  	d, err = YAMLToJSON(data)
   176  	require.Error(t, err)
   177  	require.Nil(t, d)
   178  
   179  	var lst []interface{}
   180  	lst = append(lst, "hello")
   181  
   182  	d, err = YAMLToJSON(lst)
   183  	require.NoError(t, err)
   184  	require.NotNil(t, d)
   185  	assert.Equal(t, []byte(`["hello"]`), []byte(d))
   186  
   187  	lst = append(lst, data)
   188  
   189  	d, err = YAMLToJSON(lst)
   190  	require.Error(t, err)
   191  	require.Nil(t, d)
   192  
   193  	_, err = BytesToYAMLDoc([]byte("- name: hello\n"))
   194  	require.Error(t, err)
   195  
   196  	dd, err := BytesToYAMLDoc([]byte("description: 'object created'\n"))
   197  	require.NoError(t, err)
   198  
   199  	d, err = YAMLToJSON(dd)
   200  	require.NoError(t, err)
   201  	assert.Equal(t, json.RawMessage(`{"description":"object created"}`), d)
   202  }
   203  
   204  var yamlPestoreServer = func(rw http.ResponseWriter, _ *http.Request) {
   205  	rw.WriteHeader(http.StatusOK)
   206  	_, _ = rw.Write([]byte(yamlPetStore))
   207  }
   208  
   209  func TestWithYKey(t *testing.T) {
   210  	doc, err := BytesToYAMLDoc([]byte(withYKey))
   211  	require.NoError(t, err)
   212  
   213  	_, err = YAMLToJSON(doc)
   214  	require.NoError(t, err)
   215  
   216  	doc, err = BytesToYAMLDoc([]byte(withQuotedYKey))
   217  	require.NoError(t, err)
   218  	jsond, err := YAMLToJSON(doc)
   219  	require.NoError(t, err)
   220  
   221  	var yt struct {
   222  		Definitions struct {
   223  			Viewbox struct {
   224  				Properties struct {
   225  					Y struct {
   226  						Type string `json:"type"`
   227  					} `json:"y"`
   228  				} `json:"properties"`
   229  			} `json:"viewbox"`
   230  		} `json:"definitions"`
   231  	}
   232  	require.NoError(t, json.Unmarshal(jsond, &yt))
   233  	assert.Equal(t, "integer", yt.Definitions.Viewbox.Properties.Y.Type)
   234  }
   235  
   236  func TestMapKeyTypes(t *testing.T) {
   237  	dm := map[interface{}]interface{}{
   238  		12345:               "int",
   239  		int8(1):             "int8",
   240  		int16(12345):        "int16",
   241  		int32(12345678):     "int32",
   242  		int64(12345678910):  "int64",
   243  		uint(12345):         "uint",
   244  		uint8(1):            "uint8",
   245  		uint16(12345):       "uint16",
   246  		uint32(12345678):    "uint32",
   247  		uint64(12345678910): "uint64",
   248  	}
   249  	_, err := YAMLToJSON(dm)
   250  	require.NoError(t, err)
   251  }
   252  
   253  const fixtures2224 = `definitions:
   254    Time:
   255      type: string
   256      format: date-time
   257      x-go-type:
   258        import:
   259          package: time
   260        embedded: true
   261        type: Time
   262      x-nullable: true
   263  
   264    TimeAsObject:  # <- time.Time is actually a struct
   265      type: string
   266      format: date-time
   267      x-go-type:
   268        import:
   269          package: time
   270          hints:
   271            kind: object
   272        embedded: true
   273        type: Time
   274      x-nullable: true
   275  
   276    Raw:
   277      x-go-type:
   278        import:
   279          package: encoding/json
   280        hints:
   281          kind: primitive
   282        embedded: true
   283        type: RawMessage
   284  
   285    Request:
   286      x-go-type:
   287        import:
   288          package: net/http
   289        hints:
   290          kind: object
   291        embedded: true
   292        type: Request
   293  
   294    RequestPointer:
   295      x-go-type:
   296        import:
   297          package: net/http
   298        hints:
   299          kind: object
   300          nullable: true
   301        embedded: true
   302        type: Request
   303  
   304    OldStyleImport:
   305      type: object
   306      x-go-type:
   307        import:
   308          package: net/http
   309        type: Request
   310        hints:
   311          noValidation: true
   312  
   313    OldStyleRenamed:
   314      type: object
   315      x-go-type:
   316        import:
   317          package: net/http
   318        type: Request
   319        hints:
   320          noValidation: true
   321      x-go-name: OldRenamed
   322  
   323    ObjectWithEmbedded:
   324      type: object
   325      properties:
   326        a:
   327          $ref: '#/definitions/Time'
   328        b:
   329          $ref: '#/definitions/Request'
   330        c:
   331          $ref: '#/definitions/TimeAsObject'
   332        d:
   333          $ref: '#/definitions/Raw'
   334        e:
   335          $ref: '#/definitions/JSONObject'
   336        f:
   337          $ref: '#/definitions/JSONMessage'
   338        g:
   339          $ref: '#/definitions/JSONObjectWithAlias'
   340  
   341    ObjectWithExternals:
   342      type: object
   343      properties:
   344        a:
   345          $ref: '#/definitions/OldStyleImport'
   346        b:
   347          $ref: '#/definitions/OldStyleRenamed'
   348  
   349    Base:
   350      properties: &base
   351        id:
   352          type: integer
   353          format: uint64
   354          x-go-custom-tag: 'gorm:"primary_key"'
   355        FBID:
   356          type: integer
   357          format: uint64
   358          x-go-custom-tag: 'gorm:"index"'
   359        created_at:
   360          $ref: "#/definitions/Time"
   361        updated_at:
   362          $ref: "#/definitions/Time"
   363        version:
   364          type: integer
   365          format: uint64
   366  
   367    HotspotType:
   368      type: string
   369      enum:
   370        - A
   371        - B
   372        - C
   373  
   374    Hotspot:
   375      type: object
   376      allOf:
   377        - properties: *base
   378        - properties:
   379            access_points:
   380              type: array
   381              items:
   382                $ref: '#/definitions/AccessPoint'
   383            type:
   384              $ref: '#/definitions/HotspotType'
   385          required:
   386            - type
   387  
   388    AccessPoint:
   389      type: object
   390      allOf:
   391        - properties: *base
   392        - properties:
   393            mac_address:
   394              type: string
   395              x-go-custom-tag: 'gorm:"index;not null;unique"'
   396            hotspot_id:
   397              type: integer
   398              format: uint64
   399            hotspot:
   400              $ref: '#/definitions/Hotspot'
   401  
   402    JSONObject:
   403      type: object
   404      additionalProperties:
   405        type: array
   406        items:
   407          $ref: '#/definitions/Raw'
   408  
   409    JSONObjectWithAlias:
   410      type: object
   411      additionalProperties:
   412        type: object
   413        properties:
   414          message:
   415            $ref: '#/definitions/JSONMessage'
   416  
   417    JSONMessage:
   418      $ref: '#/definitions/Raw'
   419  
   420    Incorrect:
   421      x-go-type:
   422        import:
   423          package: net
   424          hints:
   425            kind: array
   426        embedded: true
   427        type: Buffers
   428      x-nullable: true
   429  `
   430  
   431  const withQuotedYKey = `consumes:
   432  - application/json
   433  definitions:
   434    viewBox:
   435      type: object
   436      properties:
   437        x:
   438          type: integer
   439          format: int16
   440        # y -> types don't match: expect map key string or int get: bool
   441        "y":
   442          type: integer
   443          format: int16
   444        width:
   445          type: integer
   446          format: int16
   447        height:
   448          type: integer
   449          format: int16
   450  info:
   451    description: Test RESTful APIs
   452    title: Test Server
   453    version: 1.0.0
   454  basePath: /api
   455  paths:
   456    /test:
   457      get:
   458        operationId: findAll
   459        parameters:
   460          - name: since
   461            in: query
   462            type: integer
   463            format: int64
   464          - name: limit
   465            in: query
   466            type: integer
   467            format: int32
   468            default: 20
   469        responses:
   470          200:
   471            description: Array[Trigger]
   472            schema:
   473              type: array
   474              items:
   475                $ref: "#/definitions/viewBox"
   476  produces:
   477  - application/json
   478  schemes:
   479  - https
   480  swagger: "2.0"
   481  `
   482  
   483  const withYKey = `consumes:
   484  - application/json
   485  definitions:
   486    viewBox:
   487      type: object
   488      properties:
   489        x:
   490          type: integer
   491          format: int16
   492        # y -> types don't match: expect map key string or int get: bool
   493        y:
   494          type: integer
   495          format: int16
   496        width:
   497          type: integer
   498          format: int16
   499        height:
   500          type: integer
   501          format: int16
   502  info:
   503    description: Test RESTful APIs
   504    title: Test Server
   505    version: 1.0.0
   506  basePath: /api
   507  paths:
   508    /test:
   509      get:
   510        operationId: findAll
   511        parameters:
   512          - name: since
   513            in: query
   514            type: integer
   515            format: int64
   516          - name: limit
   517            in: query
   518            type: integer
   519            format: int32
   520            default: 20
   521        responses:
   522          200:
   523            description: Array[Trigger]
   524            schema:
   525              type: array
   526              items:
   527                $ref: "#/definitions/viewBox"
   528  produces:
   529  - application/json
   530  schemes:
   531  - https
   532  swagger: "2.0"
   533  `
   534  
   535  const yamlPetStore = `swagger: '2.0'
   536  info:
   537    version: '1.0.0'
   538    title: Swagger Petstore
   539    description: A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification
   540    termsOfService: http://helloreverb.com/terms/
   541    contact:
   542      name: Swagger API team
   543      email: foo@example.com
   544      url: http://swagger.io
   545    license:
   546      name: MIT
   547      url: http://opensource.org/licenses/MIT
   548  host: petstore.swagger.wordnik.com
   549  basePath: /api
   550  schemes:
   551    - http
   552  consumes:
   553    - application/json
   554  produces:
   555    - application/json
   556  paths:
   557    /pets:
   558      get:
   559        description: Returns all pets from the system that the user has access to
   560        operationId: findPets
   561        produces:
   562          - application/json
   563          - application/xml
   564          - text/xml
   565          - text/html
   566        parameters:
   567          - name: tags
   568            in: query
   569            description: tags to filter by
   570            required: false
   571            type: array
   572            items:
   573              type: string
   574            collectionFormat: csv
   575          - name: limit
   576            in: query
   577            description: maximum number of results to return
   578            required: false
   579            type: integer
   580            format: int32
   581        responses:
   582          '200':
   583            description: pet response
   584            schema:
   585              type: array
   586              items:
   587                $ref: '#/definitions/pet'
   588          default:
   589            description: unexpected error
   590            schema:
   591              $ref: '#/definitions/errorModel'
   592      post:
   593        description: Creates a new pet in the store.  Duplicates are allowed
   594        operationId: addPet
   595        produces:
   596          - application/json
   597        parameters:
   598          - name: pet
   599            in: body
   600            description: Pet to add to the store
   601            required: true
   602            schema:
   603              $ref: '#/definitions/newPet'
   604        responses:
   605          '200':
   606            description: pet response
   607            schema:
   608              $ref: '#/definitions/pet'
   609          default:
   610            description: unexpected error
   611            schema:
   612              $ref: '#/definitions/errorModel'
   613    /pets/{id}:
   614      get:
   615        description: Returns a user based on a single ID, if the user does not have access to the pet
   616        operationId: findPetById
   617        produces:
   618          - application/json
   619          - application/xml
   620          - text/xml
   621          - text/html
   622        parameters:
   623          - name: id
   624            in: path
   625            description: ID of pet to fetch
   626            required: true
   627            type: integer
   628            format: int64
   629        responses:
   630          '200':
   631            description: pet response
   632            schema:
   633              $ref: '#/definitions/pet'
   634          default:
   635            description: unexpected error
   636            schema:
   637              $ref: '#/definitions/errorModel'
   638      delete:
   639        description: deletes a single pet based on the ID supplied
   640        operationId: deletePet
   641        parameters:
   642          - name: id
   643            in: path
   644            description: ID of pet to delete
   645            required: true
   646            type: integer
   647            format: int64
   648        responses:
   649          '204':
   650            description: pet deleted
   651          default:
   652            description: unexpected error
   653            schema:
   654              $ref: '#/definitions/errorModel'
   655  definitions:
   656    pet:
   657      required:
   658        - id
   659        - name
   660      properties:
   661        id:
   662          type: integer
   663          format: int64
   664        name:
   665          type: string
   666        tag:
   667          type: string
   668    newPet:
   669      allOf:
   670        - $ref: '#/definitions/pet'
   671        - required:
   672            - name
   673          properties:
   674            id:
   675              type: integer
   676              format: int64
   677            name:
   678              type: string
   679    errorModel:
   680      required:
   681        - code
   682        - message
   683      properties:
   684        code:
   685          type: integer
   686          format: int32
   687        message:
   688          type: string
   689  `
   690  

View as plain text