diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..7dc7009 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,52 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "friday" + open-pull-requests-limit: 2 # <- default is 5 + groups: # <- group all github actions updates in a single PR + # 1. development-dependencies are auto-merged + development-dependencies: + patterns: + - '*' + + - package-ecosystem: "gomod" + # We define 4 groups of dependencies to regroup update pull requests: + # - development (e.g. test dependencies) + # - go-openapi updates + # - golang.org (e.g. golang.org/x/... packages) + # - other dependencies (direct or indirect) + # + # * All groups are checked once a week and each produce at most 1 PR. + # * All dependabot PRs are auto-approved + # + # Auto-merging policy, when requirements are met: + # 1. development-dependencies are auto-merged + # 2. golang.org-dependencies are auto-merged + # 3. go-openapi patch updates are auto-merged. Minor/major version updates require a manual merge. + # 4. other dependencies require a manual merge + directory: "/" + schedule: + interval: "weekly" + day: "friday" + open-pull-requests-limit: 4 + groups: + development-dependencies: + patterns: + - "github.com/stretchr/testify" + + golang.org-dependencies: + patterns: + - "golang.org/*" + + go-openapi-dependencies: + patterns: + - "github.com/go-openapi/*" + + other-dependencies: + exclude-patterns: + - "github.com/go-openapi/*" + - "github.com/stretchr/testify" + - "golang.org/*" diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml new file mode 100644 index 0000000..b4009db --- /dev/null +++ b/.github/workflows/auto-merge.yml @@ -0,0 +1,43 @@ +name: Dependabot auto-merge +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v1 + + - name: Auto-approve all dependabot PRs + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Auto-merge dependabot PRs for development dependencies + if: contains(steps.metadata.outputs.dependency-group, 'development-dependencies') + run: gh pr merge --auto --rebase "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Auto-merge dependabot PRs for go-openapi patches + if: contains(steps.metadata.outputs.dependency-group, 'go-openapi-dependencies') && (steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch') + run: gh pr merge --auto --rebase "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Auto-merge dependabot PRs for golang.org updates + if: contains(steps.metadata.outputs.dependency-group, 'golang.org-dependencies') + run: gh pr merge --auto --rebase "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index 79a485c..e1c413e 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -1,23 +1,58 @@ -name: Go Test +name: go test -on: [push, pull_request] +on: + push: + tags: + - v* + branches: + - master + + pull_request: jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + check-latest: true + cache: true + - name: golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + version: latest + only-new-issues: true + skip-cache: true test: - - name: Test + name: Unit tests runs-on: ${{ matrix.os }} strategy: matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] - steps: + go_version: ['oldstable', 'stable' ] - - uses: actions/setup-go@v2 + steps: + - name: Run unit tests + uses: actions/setup-go@v5 with: - go-version: 1.x + go-version: '${{ matrix.go_version }}' + check-latest: true + cache: true + + - uses: actions/checkout@v4 - - uses: actions/checkout@v2 + - run: go test -v -race -coverprofile="coverage-${{ matrix.os }}.${{ matrix.go_version }}.out" -covermode=atomic -coverpkg=$(go list)/... ./... - - run: go test + - name: Upload coverage to codecov + uses: codecov/codecov-action@v4 + with: + files: './coverage-${{ matrix.os }}.${{ matrix.go_version }}.out' + flags: '${{ matrix.go_version }}' + os: '${{ matrix.os }}' + fail_ci_if_error: false + verbose: true diff --git a/.gitignore b/.gitignore index dd91ed6..f47cb20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -secrets.yml -coverage.out +*.out diff --git a/.golangci.yml b/.golangci.yml index 835d55e..22f8d21 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -11,7 +11,7 @@ linters-settings: threshold: 200 goconst: min-len: 2 - min-occurrences: 2 + min-occurrences: 3 linters: enable-all: true @@ -40,3 +40,22 @@ linters: - tparallel - thelper - ifshort + - exhaustruct + - varnamelen + - gci + - depguard + - errchkjson + - inamedparam + - nonamedreturns + - musttag + - ireturn + - forcetypeassert + - cyclop + # deprecated linters + - deadcode + - interfacer + - scopelint + - varcheck + - structcheck + - golint + - nosnakecase diff --git a/README.md b/README.md index 18782c6..7fd2810 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ -# OAI object model +# OpenAPI v2 object model [![Build Status](https://github.com/go-openapi/spec/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/spec/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/spec/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/spec) -[![Build Status](https://travis-ci.org/go-openapi/spec.svg?branch=master)](https://travis-ci.org/go-openapi/spec) - -[![codecov](https://codecov.io/gh/go-openapi/spec/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/spec) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io) [![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/spec/master/LICENSE) [![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/spec.svg)](https://pkg.go.dev/github.com/go-openapi/spec) @@ -32,3 +29,26 @@ The object model for OpenAPI specification documents. > This [discussion thread](https://github.com/go-openapi/spec/issues/21) relates the full story. > > An early attempt to support Swagger 3 may be found at: https://github.com/go-openapi/spec3 + +* Does the unmarshaling support YAML? + +> Not directly. The exposed types know only how to unmarshal from JSON. +> +> In order to load a YAML document as a Swagger spec, you need to use the loaders provided by +> github.com/go-openapi/loads +> +> Take a look at the example there: https://pkg.go.dev/github.com/go-openapi/loads#example-Spec +> +> See also https://github.com/go-openapi/spec/issues/164 + +* How can I validate a spec? + +> Validation is provided by [the validate package](http://github.com/go-openapi/validate) + +* Why do we have an `ID` field for `Schema` which is not part of the swagger spec? + +> We found jsonschema compatibility more important: since `id` in jsonschema influences +> how `$ref` are resolved. +> This `id` does not conflict with any property named `id`. +> +> See also https://github.com/go-openapi/spec/issues/23 diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 0903593..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: "0.1.{build}" - -clone_folder: C:\go-openapi\spec -shallow_clone: true # for startup speed -pull_requests: - do_not_increment_build_number: true - -#skip_tags: true -#skip_branch_with_pr: true - -# appveyor.yml -build: off - -environment: - GOPATH: c:\gopath - -stack: go 1.15 - -test_script: - - go test -v -timeout 20m ./... - -deploy: off - -notifications: - - provider: Slack - incoming_webhook: https://hooks.slack.com/services/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 - auth_token: - secure: Sf7kZf7ZGbnwWUMpffHwMu5A0cHkLK2MYY32LNTPj4+/3qC3Ghl7+9v4TSLOqOlCwdRNjOGblAq7s+GDJed6/xgRQl1JtCi1klzZNrYX4q01pgTPvvGcwbBkIYgeMaPeIRcK9OZnud7sRXdttozgTOpytps2U6Js32ip7uj5mHSg2ub0FwoSJwlS6dbezZ8+eDhoha0F/guY99BEwx8Bd+zROrT2TFGsSGOFGN6wFc7moCqTHO/YkWib13a2QNXqOxCCVBy/lt76Wp+JkeFppjHlzs/2lP3EAk13RIUAaesdEUHvIHrzCyNJEd3/+KO2DzsWOYfpktd+KBCvgaYOsoo7ubdT3IROeAegZdCgo/6xgCEsmFc9ZcqCfN5yNx2A+BZ2Vwmpws+bQ1E1+B5HDzzaiLcYfG4X2O210QVGVDLWsv1jqD+uPYeHY2WRfh5ZsIUFvaqgUEnwHwrK44/8REAhQavt1QAj5uJpsRd7CkRVPWRNK+yIky+wgbVUFEchRNmS55E7QWf+W4+4QZkQi7vUTMc9nbTUu2Es9NfvfudOpM2wZbn98fjpb/qq/nRv6Bk+ca+7XD5/IgNLMbWp2ouDdzbiHLCOfDUiHiDJhLfFZx9Bwo7ZwfzeOlbrQX66bx7xRKYmOe4DLrXhNcpbsMa8qbfxlZRCmYbubB/Y8h4= - channel: bots - on_build_success: false - on_build_failure: true - on_build_status_changed: true diff --git a/bindata.go b/bindata.go deleted file mode 100644 index afc8385..0000000 --- a/bindata.go +++ /dev/null @@ -1,297 +0,0 @@ -// Code generated by go-bindata. DO NOT EDIT. -// sources: -// schemas/jsonschema-draft-04.json (4.357kB) -// schemas/v2/schema.json (40.248kB) - -package spec - -import ( - "bytes" - "compress/gzip" - "crypto/sha256" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" -) - -func bindataRead(data []byte, name string) ([]byte, error) { - gz, err := gzip.NewReader(bytes.NewBuffer(data)) - if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) - } - - var buf bytes.Buffer - _, err = io.Copy(&buf, gz) - clErr := gz.Close() - - if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) - } - if clErr != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -type asset struct { - bytes []byte - info os.FileInfo - digest [sha256.Size]byte -} - -type bindataFileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time -} - -func (fi bindataFileInfo) Name() string { - return fi.name -} -func (fi bindataFileInfo) Size() int64 { - return fi.size -} -func (fi bindataFileInfo) Mode() os.FileMode { - return fi.mode -} -func (fi bindataFileInfo) ModTime() time.Time { - return fi.modTime -} -func (fi bindataFileInfo) IsDir() bool { - return false -} -func (fi bindataFileInfo) Sys() interface{} { - return nil -} - -var _jsonschemaDraft04Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x57\x3d\x6f\xdb\x3c\x10\xde\xf3\x2b\x08\x26\x63\xf2\x2a\x2f\xd0\xc9\x5b\xd1\x2e\x01\x5a\x34\x43\x37\x23\x03\x6d\x9d\x6c\x06\x14\xa9\x50\x54\x60\xc3\xd0\x7f\x2f\x28\x4a\x14\x29\x91\x92\x2d\xa7\x8d\x97\x28\xbc\xaf\xe7\x8e\xf7\xc5\xd3\x0d\x42\x08\x61\x9a\xe2\x15\xc2\x7b\xa5\x8a\x55\x92\xbc\x96\x82\x3f\x94\xdb\x3d\xe4\xe4\x3f\x21\x77\x49\x2a\x49\xa6\x1e\x1e\xbf\x24\xe6\xec\x16\xdf\x1b\xa1\x3b\xf3\xff\x02\xc9\x14\xca\xad\xa4\x85\xa2\x82\x6b\xe9\x6f\x42\x02\x32\x2c\x28\x07\x45\x5a\x15\x3d\x77\x46\x39\xd5\xcc\x25\x5e\x21\x83\xb8\x21\x18\xb6\xaf\x52\x92\xa3\x47\x68\x88\xea\x58\x80\x56\x4e\x1a\xf2\xbd\x4f\xcc\x29\x7f\x52\x90\x6b\x7d\xff\x0f\x48\xb4\x3d\x3f\x21\x7c\x27\x21\xd3\x2a\x6e\x31\xaa\x2d\x53\xdd\xf3\xe3\x42\x94\x54\xd1\x77\x78\xe2\x0a\x76\x20\xe3\x20\x68\xcb\x30\x86\x41\xf3\x2a\xc7\x2b\xf4\x78\x8e\xfe\xef\x90\x91\x8a\xa9\xc7\xb1\x1d\xc2\xd8\x2f\x0d\x75\xed\xc1\x4e\x9c\xc8\x25\x43\xac\xa8\xbe\xd7\xcc\xa9\xd1\xa9\x21\xa0\x1a\xbd\x04\x61\x94\x34\x2f\x18\xfc\x3e\x16\x50\x8e\x4d\x03\x6f\x1c\x58\xdb\x48\x23\xbc\x11\x82\x01\xe1\xfa\xd3\x3a\x8e\x30\xaf\x18\x33\x7f\xf3\x8d\x39\x11\x9b\x57\xd8\x2a\xfd\x55\x2a\x49\xf9\x0e\xc7\xec\x37\xd4\x25\xf7\xec\x5c\x66\xc7\xd7\x99\xaa\xcf\x4f\x89\x8a\xd3\xb7\x0a\x3a\xaa\x92\x15\xf4\x30\x6f\x1c\xb0\xd6\x46\xe7\x98\x39\x2d\xa4\x28\x40\x2a\x3a\x88\x9e\x29\xba\x88\x37\x2d\xca\x60\x38\xfa\xba\x5b\x20\xac\xa8\x62\xb0\x4c\xd4\xaf\xda\x45\x0a\xba\x5c\x3b\xb9\xc7\x79\xc5\x14\x2d\x18\x34\x19\x1c\x51\xdb\x25\x4d\xb4\x7e\x06\x14\x38\x6c\x59\x55\xd2\x77\xf8\x69\x59\xfc\x7b\x73\xed\x93\x43\xcb\x32\x6d\x3c\x28\xdc\x1b\x9a\xd3\x62\xab\xc2\x27\xf7\x41\xc9\x08\x2b\x23\x08\xad\x13\x57\x21\x9c\xd3\x72\x0d\x42\x72\xf8\x01\x7c\xa7\xf6\x83\xce\x39\xd7\x82\x3c\x1f\x2f\xd6\x60\x1b\xa2\xdf\x35\x89\x52\x20\xe7\x73\x74\xe0\x66\x26\x64\x4e\xb4\x97\x58\xc2\x0e\x0e\xe1\x60\x92\x34\x6d\xa0\x10\xd6\xb5\x83\x61\x27\xe6\x47\xd3\x89\xbd\x63\xfd\x3b\x8d\x03\x3d\x6c\x42\x2d\x5b\x70\xee\xe8\xdf\x4b\xf4\x66\x4e\xe1\x01\x45\x17\x80\x74\xad\x4f\xc3\xf3\xae\xc6\x1d\xc6\xd7\xc2\xce\xc9\xe1\x29\x30\x86\x2f\x4a\xa6\x4b\x15\x84\x73\xc9\x6f\xfd\x7f\xa5\x6e\x9e\xbd\xf1\xb0\xd4\xdd\x45\x5a\xc2\x3e\x4b\x78\xab\xa8\x84\x74\x4a\x91\x3b\x92\x23\x05\xf2\x1c\x1e\x7b\xf3\x09\xf8\xcf\xab\x24\xb6\x60\xa2\xe8\x4c\x9f\x75\x77\xaa\x8c\xe6\x01\x45\x36\x86\xcf\xc3\x63\x3a\xea\xd4\x8d\x7e\x06\xac\x14\x0a\xe0\x29\xf0\xed\x07\x22\x1a\x65\xda\x44\xae\xa2\x73\x1a\xe6\x90\x69\xa2\x8c\x46\xb2\x2f\xde\x49\x38\x08\xed\xfe\xfd\x41\xaf\x9f\xa9\x55\xd7\xdd\x22\x8d\xfa\x45\x63\xc5\x0f\x80\xf3\xb4\x08\xd6\x79\x30\x9e\x93\xee\x59\xa6\xd0\x4b\xee\x22\xe3\x33\xc1\x3a\x27\x68\x36\x78\x7e\x87\x0a\x06\xd5\x2e\x20\xd3\xaf\x15\xfb\xd8\x3b\x73\x14\xbb\x92\xed\x05\x5d\x2e\x29\x38\x2c\x94\xe4\x42\x45\x5e\xd3\xb5\x7d\xdf\x47\xca\x38\xb4\x5c\xaf\xfb\x7d\xdd\x6d\xf4\xa1\x2d\x77\xdd\x2f\xce\x6d\xc4\x7b\x8b\x4e\x67\xa9\x6f\xfe\x04\x00\x00\xff\xff\xb1\xd1\x27\x78\x05\x11\x00\x00") - -func jsonschemaDraft04JsonBytes() ([]byte, error) { - return bindataRead( - _jsonschemaDraft04Json, - "jsonschema-draft-04.json", - ) -} - -func jsonschemaDraft04Json() (*asset, error) { - bytes, err := jsonschemaDraft04JsonBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "jsonschema-draft-04.json", size: 4357, mode: os.FileMode(0640), modTime: time.Unix(1568963823, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe1, 0x48, 0x9d, 0xb, 0x47, 0x55, 0xf0, 0x27, 0x93, 0x30, 0x25, 0x91, 0xd3, 0xfc, 0xb8, 0xf0, 0x7b, 0x68, 0x93, 0xa8, 0x2a, 0x94, 0xf2, 0x48, 0x95, 0xf8, 0xe4, 0xed, 0xf1, 0x1b, 0x82, 0xe2}} - return a, nil -} - -var _v2SchemaJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5d\x4f\x93\xdb\x36\xb2\xbf\xfb\x53\xa0\x14\x57\xd9\xae\xd8\x92\xe3\xf7\x2e\xcf\x97\xd4\xbc\xd8\x49\x66\x37\x5e\x4f\x79\x26\xbb\x87\x78\x5c\x05\x91\x2d\x09\x09\x09\x30\x00\x38\x33\x5a\xef\x7c\xf7\x2d\xf0\x9f\x08\x02\x20\x41\x8a\xd2\xc8\x0e\x0f\xa9\x78\x28\xa0\xd1\xdd\x68\x34\x7e\xdd\xf8\xf7\xf9\x11\x42\x33\x49\x64\x04\xb3\xd7\x68\x76\x86\xfe\x76\xf9\xfe\x1f\xe8\x32\xd8\x40\x8c\xd1\x8a\x71\x74\x79\x8b\xd7\x6b\xe0\xe8\xd5\xfc\x25\x3a\xbb\x38\x9f\xcf\x9e\xab\x0a\x24\x54\xa5\x37\x52\x26\xaf\x17\x0b\x91\x17\x99\x13\xb6\xb8\x79\xb5\x10\x59\xdd\xf9\xef\x82\xd1\x6f\xf2\xc2\x8f\xf3\x4f\xb5\x1a\xea\xc7\x17\x45\x41\xc6\xd7\x8b\x90\xe3\x95\x7c\xf1\xf2\x7f\x8b\xca\x45\x3d\xb9\x4d\x32\xa6\xd8\xf2\x77\x08\x64\xfe\x8d\xc3\x9f\x29\xe1\xa0\x9a\xff\xed\x11\x42\x08\xcd\x8a\xd6\xb3\x9f\x15\x67\x74\xc5\xca\x7f\x27\x58\x6e\xc4\xec\x11\x42\xd7\x59\x5d\x1c\x86\x44\x12\x46\x71\x74\xc1\x59\x02\x5c\x12\x10\xb3\xd7\x68\x85\x23\x01\x59\x81\x04\x4b\x09\x9c\x6a\xbf\x7e\xce\x49\x7d\xba\x7b\x51\xfd\xa1\x44\xe2\xb0\x52\xac\x7d\xb3\x08\x61\x45\x68\x46\x56\x2c\x6e\x80\x86\x8c\xbf\xbd\x93\x40\x05\x61\x74\x96\x95\xbe\x7f\x84\xd0\x7d\x4e\xde\x42\xb7\xe4\xbe\x46\xbb\x14\x5b\x48\x4e\xe8\xba\x90\x05\xa1\x19\xd0\x34\xae\xc4\xce\xbe\xbc\x9a\xbf\x9c\x15\x7f\x5d\x57\xc5\x42\x10\x01\x27\x89\xe2\x48\x51\xb9\xda\x40\xd5\x87\x37\xc0\x15\x5f\x88\xad\x90\xdc\x10\x81\x42\x16\xa4\x31\x50\x39\x2f\x38\xad\xab\xb0\x53\xd8\xac\x94\x56\x6f\xc3\x84\xf4\x11\xa4\x50\xb3\xfa\xe9\xd3\x6f\x9f\x3e\xdf\x2f\xd0\xeb\x8f\x1f\x3f\x7e\xbc\xfe\xf6\xe9\xf7\xaf\x5f\x7f\xfc\x18\x7e\xfb\xec\xfb\xc7\xb3\x36\x79\x54\x43\xe8\x29\xc5\x31\x20\xc6\x11\x49\x9e\xe5\x12\x41\x66\xa0\xe8\xed\x1d\x8e\x93\x08\x5e\xa3\x27\x3b\xc3\x7c\xa2\x73\xba\xc4\x02\x2e\xb0\xdc\xf4\xe5\x76\xd1\xca\x96\xa2\x8a\x94\xcd\x21\xc9\x6c\xec\x2c\x70\x42\x9e\x34\x74\x9d\x19\x7c\xcd\x20\x9c\xea\x2e\x0a\xfe\x42\x84\xd4\x29\x04\x8c\x8a\xb4\x41\xa2\xc1\xdc\x19\x8a\x88\x90\x4a\x49\xef\xce\xdf\xbd\x45\x4a\x52\x81\x70\x10\x40\x22\x21\x44\xcb\x6d\xc5\xec\x4e\x3c\x1c\x45\xef\x57\x9a\xb5\x7d\xae\xfe\xe5\xe4\x31\x86\x90\xe0\xab\x6d\x02\x3b\x2e\xcb\x11\x90\xd9\xa8\xc6\x77\xc2\x59\x98\x06\xfd\xf9\x2e\x78\x45\x01\xa6\xa8\xa0\x71\x5c\xbe\x33\xa7\xd2\xd9\x5f\x95\xef\xd9\xd5\xac\xfd\xdc\x5d\xbf\x5e\xb8\xd1\x3e\xc7\x31\x48\xe0\x5e\x4c\x14\x65\xdf\xb8\xa8\x71\x10\x09\xa3\xc2\xc7\x02\xcb\xa2\x4e\x5a\x02\x82\x94\x13\xb9\xf5\x30\xe6\xb2\xa4\xb5\xfe\x9b\x3e\x7a\xb2\x55\xd2\xa8\x4a\xbc\x16\xb6\x71\x8e\x39\xc7\xdb\x9d\xe1\x10\x09\x71\xbd\x9c\xb3\x41\x89\xd7\xa5\x89\xdc\x57\xb5\x53\x4a\xfe\x4c\xe1\xbc\xa0\x21\x79\x0a\x1a\x0f\x70\xa7\x5c\x08\x8e\xde\xb0\xc0\x43\x24\xad\x74\x63\x0e\xb1\xd9\x90\xe1\xb0\x2d\x13\xa7\x6d\x78\xfd\x04\x14\x38\x8e\x90\xaa\xce\x63\xac\x3e\x23\xbc\x64\xa9\xb4\xf8\x03\x63\xde\xcd\xbe\x16\x13\x4a\x55\xac\x82\x12\xc6\xac\xd4\x35\xf7\x22\xd4\x3a\xff\x22\x73\x0e\x6e\x51\xa0\x75\x1e\xae\x8f\xe8\x5d\xc7\x59\xe6\xe4\x9a\x18\x8d\xd6\x1c\x53\x84\x4d\xb7\x67\x28\x37\x09\x84\x69\x88\x12\x0e\x01\x11\x80\x32\xa2\xf5\xb9\xaa\xc6\xd9\x73\x53\xab\xfb\xb4\x2e\x20\xc6\x54\x92\xa0\x9a\xf3\x69\x1a\x2f\x81\x77\x37\xae\x53\x1a\xce\x40\xc4\xa8\x82\x1c\xb5\xef\xda\x24\x7d\xb9\x61\x69\x14\xa2\x25\xa0\x90\xac\x56\xc0\x81\x4a\xb4\xe2\x2c\xce\x4a\x64\x7a\x9a\x23\xf4\x13\x91\x3f\xa7\x4b\xf4\x63\x84\x6f\x18\x87\x10\xbd\xc3\xfc\x8f\x90\xdd\x52\x44\x04\xc2\x51\xc4\x6e\x21\x74\x48\x21\x81\xc7\xe2\xfd\xea\x12\xf8\x0d\x09\xf6\xe9\x47\x35\xaf\x67\xc4\x14\xf7\x22\x27\x97\xe1\xe2\x76\x2d\x06\x8c\x4a\x1c\x48\x3f\x73\x2d\x0b\x5b\x29\x45\x24\x00\x2a\x0c\x11\xec\x94\xca\xc2\xa6\xc1\x37\x21\x43\x83\x3b\x5f\x97\xf1\x43\x5e\x53\x73\x19\xa5\x36\xd8\x2d\x05\x2e\x34\x0b\xeb\x39\xfc\x1d\x63\x51\x01\xbd\x3d\xbb\x90\x84\x40\x25\x59\x6d\x09\x5d\xa3\x1c\x37\xe6\x5c\x16\x9a\x40\x09\x70\xc1\xe8\x82\xf1\x35\xa6\xe4\xdf\x99\x5c\x8e\x9e\x4d\x79\xb4\x27\x2f\xbf\x7e\xf8\x05\x25\x8c\x50\xa9\x98\x29\x90\x62\x60\xea\x75\xae\x13\xca\xbf\x2b\x1a\x29\x27\x76\xd6\x20\xc6\x64\x5f\xe6\x32\x1a\x08\x87\x21\x07\x21\xbc\xb4\xe4\xe0\x32\x67\xa6\xcd\xf3\x1e\xcd\xd9\x6b\xb6\x6f\x8e\x27\xa7\xed\xdb\xe7\xbc\xcc\x1a\x07\xce\x6f\x87\x33\xf0\xba\x51\x17\x22\x66\x78\x79\x8e\xce\xe5\x13\x81\x80\x06\x2c\xe5\x78\x0d\xa1\xb2\xb8\x54\xa8\x79\x09\xbd\xbf\x3c\x47\x01\x8b\x13\x2c\xc9\x32\xaa\xaa\x1d\xd5\xee\xab\x36\xbd\x6c\xfd\x54\x6c\xc8\x08\x01\x3c\xbd\xe7\x07\x88\xb0\x24\x37\x79\x90\x28\x4a\x1d\x10\x1a\x92\x1b\x12\xa6\x38\x42\x40\xc3\x4c\x43\x62\x8e\xae\x36\xb0\x45\x71\x2a\xa4\x9a\x23\x79\x59\xb1\xa8\xf2\xa4\x0c\x60\x9f\xcc\x8d\x40\xf5\x80\xca\xa8\x99\xc3\xa7\x85\x1f\x31\x25\xa9\x82\xc5\x6d\xbd\xd8\x36\x76\x7c\x02\x28\x97\xf6\x1d\x74\x3b\x11\x7e\x91\xae\x32\xf8\x6c\xf4\xe6\x7b\x9a\xa5\x1f\x62\xc6\x21\xcf\x9a\xe5\xed\x8b\x02\xf3\x2c\x33\x33\xdf\x00\xca\xc9\x09\xb4\x04\xf5\xa5\x08\xd7\xc3\x02\x18\x66\xf1\xab\x1e\x83\x37\x4c\xcd\x12\xc1\x1d\x50\xf6\xaa\xbd\xfe\xe2\x73\x48\x38\x08\xa0\x32\x9b\x18\x44\x86\x0b\x6a\xc1\xaa\x26\x96\x2d\x96\x3c\xa0\x54\x65\x73\xe3\x08\xb5\x8b\x99\xbd\x82\xbc\x9e\xc2\xe8\x53\x46\x83\x3f\x33\x54\x2b\x5b\xad\x92\x79\xd9\x8f\x5d\x93\x98\xf2\xe6\xc6\x1c\xe6\x9a\x9e\xfc\x43\x82\x31\x66\x8e\x53\x77\xfe\x90\xe7\xf3\xf6\xe9\x62\x23\x3f\x10\x93\x18\xae\x72\x1a\x9d\xf9\x48\xcb\xcc\x5a\x65\xc7\x4a\x04\xf0\xf3\xd5\xd5\x05\x8a\x41\x08\xbc\x86\x86\x43\x51\x6c\xe0\x46\x57\xf6\x44\x40\x0d\xfb\xff\xa2\xc3\x7c\x3d\x39\x84\xdc\x09\x22\x64\x4f\x12\xd9\xba\xaa\xf6\xe3\xbd\x56\xdd\x91\x25\x6a\x14\x9c\x89\x34\x8e\x31\xdf\xee\x15\x7e\x2f\x39\x81\x15\x2a\x28\x95\x66\x51\xf5\xfd\x83\xc5\xfe\x15\x07\xcf\xf7\x08\xee\x1d\x8e\xb6\xc5\x52\xcc\x8c\x5a\x93\x66\xc5\xd8\x79\x38\x46\xd6\xa7\x88\x37\xc9\x2e\xe3\xd2\xa5\x7b\x4b\x3a\xdc\xa1\xdc\x9e\x29\xf1\x8c\x8a\x99\x16\x47\x8d\xd4\x78\x8b\xf6\x1c\xe9\x71\x54\x1b\x69\xa8\x4a\x93\x37\xe5\xb2\x2c\x4f\x0c\x92\xab\xa0\x73\x32\x72\x59\xd3\xf0\x2d\x8d\xed\xca\x37\x16\x19\x9e\xdb\x1c\xab\x17\x49\xc3\x0f\x37\xdc\x88\xb1\xb4\xd4\x42\xcb\x58\x5e\x6a\x52\x0b\x15\x10\x0a\xb0\x04\xe7\xf8\x58\x32\x16\x01\xa6\xcd\x01\xb2\xc2\x69\x24\x35\x38\x6f\x30\x6a\xae\x1b\xb4\x71\xaa\xad\x1d\xa0\xd6\x20\x2d\x8b\x3c\xc6\x82\x62\x27\x34\x6d\x15\x84\x7b\x43\xb1\x35\x78\xa6\x24\x77\x28\xc1\x6e\xfc\xe9\x48\x74\xf4\x15\xe3\xe1\x84\x42\x88\x40\x7a\x26\x49\x3b\x48\xb1\xa4\x19\x8e\x0c\xa7\xb5\x01\x6c\x0c\x97\x61\x8a\xc2\x32\xd8\x8c\x44\x69\x24\xbf\x65\x1d\x74\xd6\xe5\x44\xef\xec\x48\x5e\xb7\x8a\xa3\x29\x8e\x41\x64\xce\x1f\x88\xdc\x00\x47\x4b\x40\x98\x6e\xd1\x0d\x8e\x48\x98\x63\x5c\x21\xb1\x4c\x05\x0a\x58\x98\xc5\x6d\x4f\x0a\x77\x53\x4f\x8b\xc4\x44\x1f\xb2\xdf\x8d\x3b\xea\x9f\xfe\xf6\xf2\xc5\xff\x5d\x7f\xfe\x9f\xfb\x67\x8f\xff\xf3\xe9\x69\xd1\xfe\xb3\xc7\xfd\x3c\xf8\x3f\x71\x94\x82\x23\xd1\x72\x00\xb7\x42\x99\x6c\xc0\x60\x7b\x0f\x79\xea\xa8\x53\x4b\x56\x31\xfa\x0b\x52\x9f\x96\xdb\xcd\x2f\xd7\x67\xcd\x04\x19\x85\xfe\xdb\x02\x9a\x59\x03\xad\x63\x3c\xea\xff\x2e\x18\xfd\x00\xd9\xe2\x56\x60\x59\x93\xb9\xb6\xb2\x3e\x3c\x2c\xab\x0f\xa7\xb2\x89\x43\xc7\xf6\xd5\xce\x2e\xad\xa6\xa9\xed\xa6\xc6\x5a\xb4\xa6\x67\xdf\x8c\x26\x7b\x50\x5a\x91\x08\x2e\x6d\xd4\x3a\xc1\x9d\xf2\xdb\xde\x1e\xb2\x2c\x6c\xa5\x64\xc9\x16\xb4\x90\xaa\x4a\xb7\x0c\xde\x13\xc3\x2a\x9a\x11\x9b\x7a\x1b\x3d\x95\x97\x37\x31\x6b\x69\x7e\x34\xc0\x67\x1f\x66\x19\x49\xef\xf1\x25\xf5\xac\x0e\xea\x0a\x28\x8d\x4d\x7e\xd9\x57\x4b\x49\xe5\xc6\xb3\x25\xfd\xe6\x57\x42\x25\xac\xcd\xcf\x36\x74\x8e\xca\x24\x47\xe7\x80\xa8\x92\x72\xbd\x3d\x84\x2d\x65\xe2\x82\x1a\x9c\xc4\x44\x92\x1b\x10\x79\x8a\xc4\x4a\x2f\x60\x51\x04\x81\xaa\xf0\xa3\x95\x27\xd7\x12\x7b\xa3\x96\x03\x45\x96\xc1\x8a\x07\xc9\xb2\xb0\x95\x52\x8c\xef\x48\x9c\xc6\x7e\x94\xca\xc2\x0e\x07\x12\x44\xa9\x20\x37\xf0\xae\x0f\x49\xa3\x96\x9d\x4b\x42\x7b\x70\x59\x14\xee\xe0\xb2\x0f\x49\xa3\x96\x4b\x97\xbf\x00\x5d\x4b\x4f\xfc\xbb\x2b\xee\x92\xb9\x17\xb5\xaa\xb8\x0b\x97\x17\x9b\x43\xfd\xd6\xc2\xb2\xc2\x2e\x29\xcf\xfd\x87\x4a\x55\xda\x25\x63\x1f\x5a\x65\x69\x2b\x2d\x3d\x67\xe9\x41\xae\x5e\xc1\x6e\x2b\xd4\xdb\x3e\xa8\xd3\x26\xd2\x48\x92\x24\xca\x61\x86\x8f\x8c\xbb\xf2\x8e\x91\xdf\x1f\x06\x19\x33\xf3\x03\x4d\xba\xcd\xe2\x2d\xfb\x69\xe9\x16\x15\x13\xd5\x56\x85\x4e\x3c\x5b\x8a\xbf\x25\x72\x83\xee\x5e\x20\x22\xf2\xc8\xaa\x7b\xdb\x8e\xe4\x29\x58\xca\x38\xb7\x3f\x2e\x59\xb8\xbd\xa8\x16\x16\xf7\xdb\x79\x51\x9f\x5a\xb4\x8d\x87\x3a\x6e\xbc\x3e\xc5\xb4\xcd\x58\xf9\xf5\x3c\xb9\x6f\x49\xaf\x57\xc1\xfa\x1c\x5d\x6d\x88\x8a\x8b\xd3\x28\xcc\xb7\xef\x10\x8a\x4a\x74\xa9\x4a\xa7\x62\xbf\x0d\x76\x23\x6f\x59\xd9\x31\xee\x40\x11\xfb\x28\xec\x8d\x22\x1c\x13\x5a\x64\x94\x23\x16\x60\xbb\xd2\x7c\xa0\x98\xb2\xe5\x6e\xbc\x54\x33\xe0\x3e\xb9\x52\x17\xdb\xb7\x1b\xc8\x12\x20\x8c\x23\xca\x64\x7e\x78\xa3\x62\x5b\x75\x56\xd9\x9e\x2a\x91\x27\xb0\x70\x34\x1f\x90\x89\xb5\x86\x73\x7e\x71\xda\x1e\xfb\x3a\x72\xdc\x5e\x79\x88\xcb\x74\x79\xd9\x64\xe4\xd4\xc2\x9e\xce\xb1\xfe\x85\x5a\xc0\xe9\x0c\x34\x3d\xd0\x43\xce\xa1\x36\x39\xd5\xa1\x4e\xf5\xf8\xb1\xa9\x23\x08\x75\x84\xac\x53\x6c\x3a\xc5\xa6\x53\x6c\x3a\xc5\xa6\x7f\xc5\xd8\xf4\x51\xfd\xff\x25\x4e\xfa\x33\x05\xbe\x9d\x60\xd2\x04\x93\x6a\x5f\x33\x9b\x98\x50\xd2\xe1\x50\x52\xc6\xcc\xdb\x38\x91\xdb\xe6\xaa\xa2\x8f\xa1\x6a\xa6\xd4\xc6\x56\xd6\x8c\x40\x02\x68\x48\xe8\x1a\xe1\x9a\xd9\x2e\xb7\x05\xc3\x34\xda\x2a\xbb\xcd\x12\x36\x98\x22\x50\x4c\xa1\x1b\xc5\xd5\x84\xf0\xbe\x24\x84\xf7\x2f\x22\x37\xef\x94\xd7\x9f\xa0\xde\x04\xf5\x26\xa8\x37\x41\x3d\x64\x40\x3d\xe5\xf2\xde\x60\x89\x27\xb4\x37\xa1\xbd\xda\xd7\xd2\x2c\x26\xc0\x37\x01\x3e\x1b\xef\x5f\x06\xe0\x6b\x7c\x5c\x91\x08\x26\x10\x38\x81\xc0\x09\x04\x76\x4a\x3d\x81\xc0\xbf\x12\x08\x4c\xb0\xdc\x7c\x99\x00\xd0\x75\x70\xb4\xf8\x5a\x7c\xea\xde\x3e\x39\x08\x30\x5a\x27\x35\xed\xb4\x65\xad\x69\x74\x10\x88\x79\xe2\x30\x52\x19\xd6\x04\x21\xa7\x95\xd5\x0e\x03\xf8\xda\x20\xd7\x84\xb4\x26\xa4\x35\x21\xad\x09\x69\x21\x03\x69\x51\x46\xff\xff\x18\x9b\x54\xed\x87\x47\x06\x9d\x4e\x73\x6e\x9a\xb3\xa9\xce\x83\x5e\x4b\xc6\x71\x20\x45\xd7\x72\xf5\x40\x72\x0e\x34\x6c\xf4\x6c\xf3\xba\x5e\x4b\x97\x0e\x52\xb8\xbe\x8b\x79\xa0\x10\x86\xa1\x75\xb0\x6f\xec\xc8\xf4\x3d\x4d\x7b\x86\xc2\x02\x31\x12\x51\xbf\x07\x94\xad\x10\xd6\x2e\x79\xcf\xe9\x1c\xf5\x1e\x31\x23\x5c\x18\xfb\x9c\xfb\x70\xe0\x62\xbd\xf7\xb5\x94\xcf\xf3\xf6\xfa\xc5\x4e\x9c\x85\x76\x1d\xae\x37\xbc\xde\xa3\x41\xcb\x29\xd0\x5e\x70\x67\x50\x93\x6d\x98\xa8\xd3\x67\x0f\x68\xb1\xeb\x38\x47\x07\x10\x1b\xd2\xe2\x18\x68\x6d\x40\xbb\xa3\x40\xba\x21\xf2\x8e\x81\xfb\xf6\x92\x77\x2f\x70\xe8\xdb\xb2\x36\xbf\x30\x91\xc5\x21\xe7\x45\xcc\x34\x0c\x48\x8e\xd0\xf2\x9b\x7c\x3c\xbd\x1c\x04\x3e\x07\xe8\x7c\x2f\x84\x7a\x48\x4d\x1f\xba\xe1\x76\x45\x7b\x60\xe0\x01\xca\xee\x04\xca\x31\xbe\x73\x5f\xa3\x70\x0c\xad\x1f\xa5\xf5\x76\xd5\xbb\xd2\x7e\xfb\x30\x90\xcf\xfa\x67\x7a\xe6\xc3\x37\x42\x19\xe2\xc9\x9c\x61\x4c\xe7\xd1\x77\x55\x86\x6e\x8f\x7b\x85\x42\x33\xa3\xaa\x57\xae\xfd\xd5\xcc\x9c\x56\x68\xe2\xde\x0e\xa8\x2c\xa9\xb0\x7d\xf0\x54\x2d\x80\xf2\x48\x39\x3d\x98\x1a\x6d\x0b\x9d\xba\x53\xfb\xce\xf8\xd1\x7e\xbb\x60\x4f\x06\xf5\xce\xda\xab\xeb\xca\xcb\xd5\xac\x20\xda\x72\x3b\xa2\x4b\x38\xd7\xb5\x89\xbe\x42\xd9\xb9\x73\xc4\x0c\x6d\xb7\xd9\xf8\x8d\xbd\x3e\x9c\xf5\x53\x68\x48\x14\x36\x8f\x09\xc5\x92\xf1\x21\xd1\x09\x07\x1c\xbe\xa7\x91\xf3\x6a\xc8\xc1\x57\xb0\xdd\xc5\xc6\x1d\xad\x76\x1d\xa8\x82\x0e\x4c\x38\xfe\xa5\x8c\xc5\x0a\x40\x5d\xa1\xbb\x98\xd1\xfb\x74\x61\xed\x1a\x98\xaf\x3c\x8c\x1e\xe3\xc2\x92\x29\x74\x3e\x99\xd0\xf9\x41\x50\xd0\x38\x4b\x57\x7e\x5b\x7a\x0e\xe6\xce\x4e\xd7\x19\x35\x57\xbb\x3c\x3c\xd2\x5e\x4f\x4b\x4c\xf7\x0f\x4d\x2b\x91\x5d\x94\xa6\x95\xc8\x69\x25\x72\x5a\x89\x7c\xb8\x95\xc8\x07\x80\x8c\xda\x9c\x64\x7b\xb7\x71\xdf\x57\x12\x4b\x9a\x1f\x72\x0c\x13\x03\xad\x3c\xd5\x4e\xde\x8e\x57\x13\x6d\x34\x86\xcf\x97\xe6\xa4\x68\xc4\xb0\xf6\xc9\xc2\xeb\x8d\x0b\xd7\xcd\xfe\xba\xa6\xf5\x30\xeb\x30\x33\xbe\xc7\x56\x27\xab\x08\xd9\x6d\xbb\x09\xee\x7c\x2d\xcf\xee\x87\x38\xac\xc8\xdd\x90\x9a\x58\x4a\x4e\x96\xa9\x79\x79\xf3\xde\x20\xf0\x96\xe3\x24\x19\xeb\xba\xf2\x53\x19\xab\x12\xaf\x47\xb3\xa0\x3e\xef\x9b\x8d\x6d\x6d\x7b\xde\x3b\x3b\x1a\xc0\x3f\x95\x7e\xed\x78\xfb\x76\xb8\xaf\xb3\xdd\xc5\xeb\x95\xed\x5a\x62\x41\x82\xb3\x54\x6e\x80\x4a\x92\x6f\x36\xbd\x34\xae\xde\x6f\xa4\xc0\xbc\x08\xe3\x84\xfc\x1d\xb6\xe3\xd0\x62\x38\x95\x9b\x57\xe7\x71\x12\x91\x80\xc8\x31\x69\x5e\x60\x21\x6e\x19\x0f\xc7\xa4\x79\x96\x28\x3e\x47\x54\x65\x41\x36\x08\x40\x88\x1f\x58\x08\x56\xaa\xd5\xbf\xaf\xad\x96\xd7\xd6\xcf\x87\xf5\x34\x0f\x71\x93\x6e\x26\xed\x98\x5b\x9f\x4f\xcf\x95\x34\xc6\xd7\x11\xfa\xb0\x81\x22\x1a\xdb\xdf\x8e\xdc\xc3\xb9\xf8\xdd\x5d\x3c\x74\xe6\xea\xb7\x8b\xbf\xf5\x6e\xb3\x46\x2e\x64\xf4\xab\x3c\x4e\xcf\x36\x1d\xfe\xfa\xb8\x36\xba\x8a\xd8\xad\xf6\xc6\x41\x2a\x37\x8c\x17\x0f\xda\xfe\xda\xe7\x65\xbc\x71\x2c\x36\x57\x8a\x47\x12\x4c\xf1\xbd\x77\x6b\xa4\x50\x7e\x77\x7b\x22\x60\x89\xef\xcd\xf5\xb9\x0c\x97\x79\x0d\x2b\x35\x43\xcb\x3d\x24\xf1\x78\xfc\xf8\xcb\x1f\x15\x06\xe2\x78\xd8\x51\x21\xd9\x1f\xf0\xf5\x8f\x86\xa4\x50\xfa\xb1\x47\x43\xa5\xdd\x69\x14\xe8\xa3\xc0\x86\x91\xa7\x81\x50\xb4\x7c\xc0\x81\x80\x77\x7a\x9f\xc6\xc2\xa9\x8c\x05\x33\xb0\x3b\x31\xa4\xf4\xd7\x1b\x26\x55\x97\x7c\x65\xf8\x69\x1a\x84\x8e\x41\x78\xd9\xec\xc5\x11\x16\x1e\x74\x91\xf5\x56\xf5\x57\x49\x47\x5c\x92\xa9\x1e\x99\x36\xf4\xdb\xb1\x0e\xd3\x78\x02\xb0\x9b\x25\xcb\xe9\xe9\x1d\x0d\x44\x01\x42\x08\x91\x64\xd9\xdd\x37\x08\x17\xef\xf9\xe5\x0f\xbd\x46\x91\xf5\xf9\x89\x92\x37\xdd\x89\x59\x44\x1f\x9c\xee\x34\x1e\xbe\x47\x83\x32\x72\x8e\x37\xdf\xac\x69\x38\xef\x75\xb0\xda\xdb\xac\x83\x94\x2f\x39\xa6\x62\x05\x1c\x25\x9c\x49\x16\xb0\xa8\x3c\xc7\x7e\x76\x71\x3e\x6f\xb5\x24\xe7\xe8\xb7\xb9\xc7\x6c\x43\x92\xee\x21\xd4\x17\xa1\x7f\xba\x35\xfe\xae\x39\xbc\xde\xba\x69\xd9\x8e\xe1\x62\xde\x64\x7d\x16\x88\x1b\xed\x29\x11\xfd\x4f\xa9\xff\x99\x90\xc4\xf6\xf4\xf9\x6e\xe9\x28\x23\xd7\xca\xe5\xee\xee\x9f\x63\xb1\x5b\xfb\x10\xd7\x2f\x1d\xf2\xe3\xbf\xb9\xb5\x6f\xa4\x6d\x7d\x25\x79\xfb\x24\x31\xea\x56\xbe\x5d\x53\xcd\x2d\x36\xa3\x6d\xdf\xab\x1c\xb8\x6d\x6f\xc0\x98\xa7\xdd\xaa\x86\x8c\x1d\x39\xa3\x9d\x70\x2b\x9b\x68\xd9\xfd\x33\xfe\xa9\xb6\x4a\x2e\x63\x0f\xcf\x68\x27\xd9\x4c\xb9\x46\x6d\xcb\xbe\xa1\xa8\xd6\x5f\xc6\xd6\x9f\xf1\x4f\xf4\xd4\xb4\x78\xd0\xd6\xf4\x13\x3c\x3b\xac\xd0\xdc\x90\x34\xda\xc9\xb4\x9a\x1a\x8d\xbd\x93\x87\xd4\xe2\x21\x1b\xb3\x2b\xd1\xbe\xe7\x69\xd4\x53\x67\xd5\x40\xa0\xe3\x19\x3f\x6d\x1a\xbc\x0e\x86\x3c\x10\xb4\x3d\x2a\xcd\x78\x32\xe6\xab\xbd\x36\xc9\xf4\x3a\x58\xae\xc3\xf4\x47\xea\xbf\xfb\x47\xff\x0d\x00\x00\xff\xff\xd2\x32\x5a\x28\x38\x9d\x00\x00") - -func v2SchemaJsonBytes() ([]byte, error) { - return bindataRead( - _v2SchemaJson, - "v2/schema.json", - ) -} - -func v2SchemaJson() (*asset, error) { - bytes, err := v2SchemaJsonBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "v2/schema.json", size: 40248, mode: os.FileMode(0640), modTime: time.Unix(1568964748, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xab, 0x88, 0x5e, 0xf, 0xbf, 0x17, 0x74, 0x0, 0xb2, 0x5a, 0x7f, 0xbc, 0x58, 0xcd, 0xc, 0x25, 0x73, 0xd5, 0x29, 0x1c, 0x7a, 0xd0, 0xce, 0x79, 0xd4, 0x89, 0x31, 0x27, 0x90, 0xf2, 0xff, 0xe6}} - return a, nil -} - -// Asset loads and returns the asset for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func Asset(name string) ([]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) - } - return a.bytes, nil - } - return nil, fmt.Errorf("Asset %s not found", name) -} - -// AssetString returns the asset contents as a string (instead of a []byte). -func AssetString(name string) (string, error) { - data, err := Asset(name) - return string(data), err -} - -// MustAsset is like Asset but panics when Asset would return an error. -// It simplifies safe initialization of global variables. -func MustAsset(name string) []byte { - a, err := Asset(name) - if err != nil { - panic("asset: Asset(" + name + "): " + err.Error()) - } - - return a -} - -// MustAssetString is like AssetString but panics when Asset would return an -// error. It simplifies safe initialization of global variables. -func MustAssetString(name string) string { - return string(MustAsset(name)) -} - -// AssetInfo loads and returns the asset info for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func AssetInfo(name string) (os.FileInfo, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) - } - return a.info, nil - } - return nil, fmt.Errorf("AssetInfo %s not found", name) -} - -// AssetDigest returns the digest of the file with the given name. It returns an -// error if the asset could not be found or the digest could not be loaded. -func AssetDigest(name string) ([sha256.Size]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err) - } - return a.digest, nil - } - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name) -} - -// Digests returns a map of all known files and their checksums. -func Digests() (map[string][sha256.Size]byte, error) { - mp := make(map[string][sha256.Size]byte, len(_bindata)) - for name := range _bindata { - a, err := _bindata[name]() - if err != nil { - return nil, err - } - mp[name] = a.digest - } - return mp, nil -} - -// AssetNames returns the names of the assets. -func AssetNames() []string { - names := make([]string, 0, len(_bindata)) - for name := range _bindata { - names = append(names, name) - } - return names -} - -// _bindata is a table, holding each asset generator, mapped to its name. -var _bindata = map[string]func() (*asset, error){ - "jsonschema-draft-04.json": jsonschemaDraft04Json, - - "v2/schema.json": v2SchemaJson, -} - -// AssetDir returns the file names below a certain -// directory embedded in the file by go-bindata. -// For example if you run go-bindata on data/... and data contains the -// following hierarchy: -// data/ -// foo.txt -// img/ -// a.png -// b.png -// then AssetDir("data") would return []string{"foo.txt", "img"}, -// AssetDir("data/img") would return []string{"a.png", "b.png"}, -// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and -// AssetDir("") will return []string{"data"}. -func AssetDir(name string) ([]string, error) { - node := _bintree - if len(name) != 0 { - canonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(canonicalName, "/") - for _, p := range pathList { - node = node.Children[p] - if node == nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - } - } - if node.Func != nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - rv := make([]string, 0, len(node.Children)) - for childName := range node.Children { - rv = append(rv, childName) - } - return rv, nil -} - -type bintree struct { - Func func() (*asset, error) - Children map[string]*bintree -} - -var _bintree = &bintree{nil, map[string]*bintree{ - "jsonschema-draft-04.json": {jsonschemaDraft04Json, map[string]*bintree{}}, - "v2": {nil, map[string]*bintree{ - "schema.json": {v2SchemaJson, map[string]*bintree{}}, - }}, -}} - -// RestoreAsset restores an asset under the given directory. -func RestoreAsset(dir, name string) error { - data, err := Asset(name) - if err != nil { - return err - } - info, err := AssetInfo(name) - if err != nil { - return err - } - err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) - if err != nil { - return err - } - err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) - if err != nil { - return err - } - return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) -} - -// RestoreAssets restores an asset under the given directory recursively. -func RestoreAssets(dir, name string) error { - children, err := AssetDir(name) - // File - if err != nil { - return RestoreAsset(dir, name) - } - // Dir - for _, child := range children { - err = RestoreAssets(dir, filepath.Join(name, child)) - if err != nil { - return err - } - } - return nil -} - -func _filePath(dir, name string) string { - canonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) -} diff --git a/circular_test.go b/circular_test.go index 90bc6d2..c064b61 100644 --- a/circular_test.go +++ b/circular_test.go @@ -2,9 +2,9 @@ package spec import ( "encoding/json" - "io/ioutil" "net/http" "net/http/httptest" + "os" "path/filepath" "testing" "time" @@ -175,7 +175,7 @@ func TestExpandCircular_Bitbucket(t *testing.T) { func TestExpandCircular_ResponseWithRoot(t *testing.T) { rootDoc := new(Swagger) - b, err := ioutil.ReadFile(filepath.Join("fixtures", "more_circulars", "resp.json")) + b, err := os.ReadFile(filepath.Join("fixtures", "more_circulars", "resp.json")) require.NoError(t, err) require.NoError(t, json.Unmarshal(b, rootDoc)) @@ -219,7 +219,7 @@ func TestExpandCircular_SpecExpansion(t *testing.T) { func TestExpandCircular_RemoteCircularID(t *testing.T) { go func() { - err := http.ListenAndServe("localhost:1234", http.FileServer(http.Dir("fixtures/more_circulars/remote"))) + err := http.ListenAndServe("localhost:1234", http.FileServer(http.Dir("fixtures/more_circulars/remote"))) //#nosec if err != nil { panic(err.Error()) } @@ -232,7 +232,7 @@ func TestExpandCircular_RemoteCircularID(t *testing.T) { assertRefResolve(t, jazon, "", root, &ExpandOptions{RelativeBase: fixturePath}) assertRefExpand(t, jazon, "", root, &ExpandOptions{RelativeBase: fixturePath}) - assert.NoError(t, ExpandSchemaWithBasePath(root, nil, &ExpandOptions{})) + require.NoError(t, ExpandSchemaWithBasePath(root, nil, &ExpandOptions{})) jazon = asJSON(t, root) diff --git a/contact_info_test.go b/contact_info_test.go index 3ca2315..fedd81a 100644 --- a/contact_info_test.go +++ b/contact_info_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const contactInfoJSON = `{ @@ -36,13 +37,11 @@ var contactInfo = ContactInfo{ContactInfoProps: ContactInfoProps{ func TestIntegrationContactInfo(t *testing.T) { b, err := json.MarshalIndent(contactInfo, "", "\t") - if assert.NoError(t, err) { - assert.Equal(t, contactInfoJSON, string(b)) - } + require.NoError(t, err) + assert.Equal(t, contactInfoJSON, string(b)) actual := ContactInfo{} err = json.Unmarshal([]byte(contactInfoJSON), &actual) - if assert.NoError(t, err) { - assert.EqualValues(t, contactInfo, actual) - } + require.NoError(t, err) + assert.EqualValues(t, contactInfo, actual) } diff --git a/debug_test.go b/debug_test.go index 74c2a78..c74fc28 100644 --- a/debug_test.go +++ b/debug_test.go @@ -15,7 +15,6 @@ package spec import ( - "io/ioutil" "os" "sync" "testing" @@ -28,7 +27,7 @@ var ( ) func TestDebug(t *testing.T) { - tmpFile, _ := ioutil.TempFile("", "debug-test") + tmpFile, _ := os.CreateTemp("", "debug-test") tmpName := tmpFile.Name() defer func() { Debug = false diff --git a/embed.go b/embed.go new file mode 100644 index 0000000..1f42847 --- /dev/null +++ b/embed.go @@ -0,0 +1,17 @@ +package spec + +import ( + "embed" + "path" +) + +//go:embed schemas/*.json schemas/*/*.json +var assets embed.FS + +func jsonschemaDraft04JSONBytes() ([]byte, error) { + return assets.ReadFile(path.Join("schemas", "jsonschema-draft-04.json")) +} + +func v2SchemaJSONBytes() ([]byte, error) { + return assets.ReadFile(path.Join("schemas", "v2", "schema.json")) +} diff --git a/expander.go b/expander.go index 942bd73..dd7eb6e 100644 --- a/expander.go +++ b/expander.go @@ -27,7 +27,6 @@ import ( // all relative $ref's will be resolved from there. // // PathLoader injects a document loading method. By default, this resolves to the function provided by the SpecLoader package variable. -// type ExpandOptions struct { RelativeBase string // the path to the root document to expand. This is a file, not a directory SkipSchemas bool // do not expand schemas, just paths, parameters and responses @@ -58,7 +57,7 @@ func ExpandSpec(spec *Swagger, options *ExpandOptions) error { if !options.SkipSchemas { for key, definition := range spec.Definitions { parentRefs := make([]string, 0, 10) - parentRefs = append(parentRefs, fmt.Sprintf("#/definitions/%s", key)) + parentRefs = append(parentRefs, "#/definitions/"+key) def, err := expandSchema(definition, parentRefs, resolver, specBasePath) if resolver.shouldStopOnError(err) { @@ -103,15 +102,21 @@ const rootBase = ".root" // baseForRoot loads in the cache the root document and produces a fake ".root" base path entry // for further $ref resolution -// -// Setting the cache is optional and this parameter may safely be left to nil. func baseForRoot(root interface{}, cache ResolutionCache) string { + // cache the root document to resolve $ref's + normalizedBase := normalizeBase(rootBase) + if root == nil { - return "" + // ensure that we never leave a nil root: always cache the root base pseudo-document + cachedRoot, found := cache.Get(normalizedBase) + if found && cachedRoot != nil { + // the cache is already preloaded with a root + return normalizedBase + } + + root = map[string]interface{}{} } - // cache the root document to resolve $ref's - normalizedBase := normalizeBase(rootBase) cache.Set(normalizedBase, root) return normalizedBase @@ -208,7 +213,19 @@ func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, ba } if target.Ref.String() != "" { - return expandSchemaRef(target, parentRefs, resolver, basePath) + if !resolver.options.SkipSchemas { + return expandSchemaRef(target, parentRefs, resolver, basePath) + } + + // when "expand" with SkipSchema, we just rebase the existing $ref without replacing + // the full schema. + rebasedRef, err := NewRef(normalizeURI(target.Ref.String(), basePath)) + if err != nil { + return nil, err + } + target.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID) + + return &target, nil } for k := range target.Definitions { @@ -526,21 +543,25 @@ func getRefAndSchema(input interface{}) (*Ref, *Schema, error) { } func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePath string) error { - ref, _, err := getRefAndSchema(input) + ref, sch, err := getRefAndSchema(input) if err != nil { return err } - if ref == nil { + if ref == nil && sch == nil { // nothing to do return nil } parentRefs := make([]string, 0, 10) - if err = resolver.deref(input, parentRefs, basePath); resolver.shouldStopOnError(err) { - return err + if ref != nil { + // dereference this $ref + if err = resolver.deref(input, parentRefs, basePath); resolver.shouldStopOnError(err) { + return err + } + + ref, sch, _ = getRefAndSchema(input) } - ref, sch, _ := getRefAndSchema(input) if ref.String() != "" { transitiveResolver := resolver.transitiveResolver(basePath, *ref) basePath = resolver.updateBasePath(transitiveResolver, basePath) @@ -552,6 +573,7 @@ func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePa if ref != nil { *ref = Ref{} } + return nil } @@ -561,38 +583,29 @@ func expandParameterOrResponse(input interface{}, resolver *schemaLoader, basePa return ern } - switch { - case resolver.isCircular(&rebasedRef, basePath, parentRefs...): + if resolver.isCircular(&rebasedRef, basePath, parentRefs...) { // this is a circular $ref: stop expansion if !resolver.options.AbsoluteCircularRef { sch.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID) } else { sch.Ref = rebasedRef } - case !resolver.options.SkipSchemas: - // schema expanded to a $ref in another root - sch.Ref = rebasedRef - debugLog("rebased to: %s", sch.Ref.String()) - default: - // skip schema expansion but rebase $ref to schema - sch.Ref = denormalizeRef(&rebasedRef, resolver.context.basePath, resolver.context.rootID) } } + // $ref expansion or rebasing is performed by expandSchema below if ref != nil { *ref = Ref{} } // expand schema - if !resolver.options.SkipSchemas { - s, err := expandSchema(*sch, parentRefs, resolver, basePath) - if resolver.shouldStopOnError(err) { - return err - } - if s == nil { - // guard for when continuing on error - return nil - } + // yes, we do it even if options.SkipSchema is true: we have to go down that rabbit hole and rebase nested $ref) + s, err := expandSchema(*sch, parentRefs, resolver, basePath) + if resolver.shouldStopOnError(err) { + return err + } + + if s != nil { // guard for when continuing on error *sch = *s } diff --git a/expander_test.go b/expander_test.go index c54632b..8b0c8c4 100644 --- a/expander_test.go +++ b/expander_test.go @@ -16,7 +16,7 @@ package spec import ( "encoding/json" - "io/ioutil" + "io" "log" "net/http" "net/http/httptest" @@ -42,6 +42,37 @@ var ( specs = filepath.Join("fixtures", "specs") ) +func TestExpand_Issue148(t *testing.T) { + fp := filepath.Join("fixtures", "bugs", "schema-148.json") + b, err := jsonDoc(fp) + require.NoError(t, err) + + assertAdditionalProps := func(sp Swagger) func(*testing.T) { + return func(t *testing.T) { + require.Len(t, sp.Definitions, 2) + + require.Contains(t, sp.Definitions, "empty") + empty := sp.Definitions["empty"] + require.NotNil(t, empty.AdditionalProperties) + require.NotNil(t, empty.AdditionalProperties.Schema) + require.True(t, empty.AdditionalProperties.Allows) + + require.Contains(t, sp.Definitions, "false") + additionalIsFalse := sp.Definitions["false"] + require.NotNil(t, additionalIsFalse.AdditionalProperties) + require.Nil(t, additionalIsFalse.AdditionalProperties.Schema) + require.False(t, additionalIsFalse.AdditionalProperties.Allows) + } + } + + var sp Swagger + require.NoError(t, json.Unmarshal(b, &sp)) + t.Run("check additional properties", assertAdditionalProps(sp)) + + require.NoError(t, ExpandSpec(&sp, nil)) + t.Run("check additional properties after expansion", assertAdditionalProps(sp)) +} + func TestExpand_KnownRef(t *testing.T) { // json schema draft 4 meta schema is embedded by default: it resolves without setup schema := RefProperty("http://json-schema.org/draft-04/schema#") @@ -351,11 +382,11 @@ func TestExpand_ContinueOnError(t *testing.T) { specPath := filepath.Join("fixtures", "expansion", "missingRef.json") defer log.SetOutput(os.Stdout) - log.SetOutput(ioutil.Discard) + log.SetOutput(io.Discard) // missing $ref in spec missingRefDoc, err := jsonDoc(specPath) - assert.NoError(t, err) + require.NoError(t, err) testCase := struct { Input *Swagger `json:"input"` @@ -369,7 +400,7 @@ func TestExpand_ContinueOnError(t *testing.T) { } require.NoError(t, ExpandSpec(testCase.Input, opts)) - assert.Equal(t, testCase.Input, testCase.Expected, "Should continue expanding spec when a definition can't be found.") + assert.Equal(t, testCase.Expected, testCase.Input, "Should continue expanding spec when a definition can't be found.") // missing $ref in items doc, err := jsonDoc("fixtures/expansion/missingItemRef.json") @@ -787,25 +818,24 @@ func resolutionContextServer() *httptest.Server { server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if req.URL.Path == "/resolution.json" { - b, _ := ioutil.ReadFile(filepath.Join(specs, "resolution.json")) + b, _ := os.ReadFile(filepath.Join(specs, "resolution.json")) var ctnt map[string]interface{} _ = json.Unmarshal(b, &ctnt) ctnt["id"] = servedAt rw.Header().Set("Content-Type", "application/json") - rw.WriteHeader(200) + rw.WriteHeader(http.StatusOK) bb, _ := json.Marshal(ctnt) _, _ = rw.Write(bb) return } if req.URL.Path == "/resolution2.json" { - b, _ := ioutil.ReadFile(filepath.Join(specs, "resolution2.json")) + b, _ := os.ReadFile(filepath.Join(specs, "resolution2.json")) var ctnt map[string]interface{} _ = json.Unmarshal(b, &ctnt) ctnt["id"] = servedAt rw.Header().Set("Content-Type", "application/json") - rw.WriteHeader(200) bb, _ := json.Marshal(ctnt) _, _ = rw.Write(bb) return @@ -813,7 +843,6 @@ func resolutionContextServer() *httptest.Server { if req.URL.Path == "/boolProp.json" { rw.Header().Set("Content-Type", "application/json") - rw.WriteHeader(200) b, _ := json.Marshal(map[string]interface{}{ "type": "boolean", }) @@ -823,7 +852,6 @@ func resolutionContextServer() *httptest.Server { if req.URL.Path == "/deeper/stringProp.json" { rw.Header().Set("Content-Type", "application/json") - rw.WriteHeader(200) b, _ := json.Marshal(map[string]interface{}{ "type": "string", }) @@ -833,7 +861,6 @@ func resolutionContextServer() *httptest.Server { if req.URL.Path == "/deeper/arrayProp.json" { rw.Header().Set("Content-Type", "application/json") - rw.WriteHeader(200) b, _ := json.Marshal(map[string]interface{}{ "type": "array", "items": map[string]interface{}{ @@ -911,7 +938,7 @@ func TestExpand_RemoteRefWithNestedResolutionContextWithFragment(t *testing.T) { func TestExpand_TransitiveRefs(t *testing.T) { basePath := filepath.Join(specs, "todos.json") - rawSpec, err := ioutil.ReadFile(basePath) + rawSpec, err := os.ReadFile(basePath) require.NoError(t, err) var spec *Swagger @@ -1073,6 +1100,53 @@ func TestExpand_ExtraItems(t *testing.T) { }`, jazon) } +func TestExpand_Issue145(t *testing.T) { + cwd, err := os.Getwd() + require.NoError(t, err) + pseudoRoot := normalizeBase(filepath.Join(cwd, rootBase)) + + // assert the internal behavior of baseForRoot() + t.Run("with nil root, empty cache", func(t *testing.T) { + cache := defaultResolutionCache() + require.Equal(t, pseudoRoot, baseForRoot(nil, cache)) + + t.Run("empty root is cached", func(t *testing.T) { + value, ok := cache.Get(pseudoRoot) + require.True(t, ok) // found in cache + asMap, ok := value.(map[string]interface{}) + require.True(t, ok) + require.Empty(t, asMap) + }) + }) + + t.Run("with non-nil root, empty cache", func(t *testing.T) { + cache := defaultResolutionCache() + require.Equal(t, pseudoRoot, baseForRoot(map[string]interface{}{"key": "arbitrary"}, cache)) + + t.Run("non-empty root is cached", func(t *testing.T) { + value, ok := cache.Get(pseudoRoot) + require.True(t, ok) // found in cache + asMap, ok := value.(map[string]interface{}) + require.True(t, ok) + require.Contains(t, asMap, "key") + require.Equal(t, "arbitrary", asMap["key"]) + }) + + t.Run("with nil root, non-empty cache", func(t *testing.T) { + require.Equal(t, pseudoRoot, baseForRoot(nil, cache)) + + t.Run("non-empty root is kept", func(t *testing.T) { + value, ok := cache.Get(pseudoRoot) + require.True(t, ok) // found in cache + asMap, ok := value.(map[string]interface{}) + require.True(t, ok) + require.Contains(t, asMap, "key") + require.Equal(t, "arbitrary", asMap["key"]) + }) + }) + }) +} + // PetStore20 json doc for swagger 2.0 pet store const PetStore20 = `{ "swagger": "2.0", diff --git a/fixtures/bugs/145/Program Files (x86)/AppName/ref.json b/fixtures/bugs/145/Program Files (x86)/AppName/ref.json new file mode 100644 index 0000000..10a8cb7 --- /dev/null +++ b/fixtures/bugs/145/Program Files (x86)/AppName/ref.json @@ -0,0 +1,17 @@ +{ +"definitions": { + "todo-partial": { + "title": "Todo Partial", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "completed": { + "type": ["boolean", "null"] + } + }, + "required": ["name", "completed"] + } + } +} diff --git a/fixtures/bugs/145/Program Files (x86)/AppName/todos.common.json b/fixtures/bugs/145/Program Files (x86)/AppName/todos.common.json new file mode 100644 index 0000000..1c43908 --- /dev/null +++ b/fixtures/bugs/145/Program Files (x86)/AppName/todos.common.json @@ -0,0 +1,103 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0", + "title": "To-do Demo", + "description": + "### Notes:\n\nThis OAS2 (Swagger 2) specification defines common models and responses, that other specifications may reference.\n\nFor example, check out the user poperty in the main.oas2 todo-partial model - it references the user model in this specification!\n\nLikewise, the main.oas2 operations reference the shared error responses in this common specification.", + "contact": { + "name": "Stoplight", + "url": "https://stoplight.io" + }, + "license": { + "name": "MIT" + } + }, + "host": "example.com", + "securityDefinitions": {}, + "paths": {}, + "responses": { + "401": { + "description": "", + "schema": { + "$ref": "#/definitions/error-response" + }, + "examples": { + "application/json": { + "status": "401", + "error": "Not Authorized" + } + } + }, + "403": { + "description": "", + "schema": { + "$ref": "#/definitions/error-response" + }, + "examples": { + "application/json": { + "status": "403", + "error": "Forbbiden" + } + } + }, + "404": { + "description": "", + "schema": { + "$ref": "#/definitions/error-response" + }, + "examples": { + "application/json": { + "status": "404", + "error": "Not Found" + } + } + }, + "500": { + "description": "", + "schema": { + "$ref": "#/definitions/error-response" + }, + "examples": { + "application/json": { + "status": "500", + "error": "Server Error" + } + } + } + }, + "definitions": { + "user": { + "title": "User", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The user's full name." + }, + "age": { + "type": "number", + "minimum": 0, + "maximum": 150 + }, + "error": { + "$ref": "#/definitions/error-response" + } + }, + "required": ["name", "age"] + }, + "error-response": { + "type": "object", + "title": "Error Response", + "properties": { + "status": { + "type": "string" + }, + "error": { + "type": "string" + } + }, + "required": ["status", "error"] + } + } +} diff --git a/fixtures/bugs/145/Program Files (x86)/AppName/todos.json b/fixtures/bugs/145/Program Files (x86)/AppName/todos.json new file mode 100644 index 0000000..9c5072e --- /dev/null +++ b/fixtures/bugs/145/Program Files (x86)/AppName/todos.json @@ -0,0 +1,336 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0", + "title": "To-do Demo", + "description": "This OAS2 (Swagger 2) file represents a real API that lives at http://todos.stoplight.io.\n\nFor authentication information, click the apikey security scheme in the editor sidebar.", + "contact": { + "name": "Stoplight", + "url": "https://stoplight.io" + }, + "license": { + "name": "MIT" + } + }, + "host": "todos.stoplight.io", + "schemes": ["http"], + "consumes": ["application/json"], + "produces": ["application/json"], + "securityDefinitions": { + "Basic": { + "type": "basic" + }, + "API Key": { + "type": "apiKey", + "name": "apikey", + "in": "query" + } + }, + "paths": { + "/todos/{todoId}": { + "parameters": [{ + "name": "todoId", + "in": "path", + "required": true, + "type": "string" + }], + "get": { + "operationId": "GET_todo", + "summary": "Get Todo", + "tags": ["Todos"], + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/todo-full" + }, + "examples": { + "application/json": { + "id": 1, + "name": "get food", + "completed": false, + "completed_at": "1955-04-23T13:22:52.685Z", + "created_at": "1994-11-05T03:26:51.471Z", + "updated_at": "1989-07-29T11:30:06.701Z" + }, + "/todos/foobar": "{\n\t\"foo\": \"bar\"\n}\n", + "/todos/chores": { + "id": 9000, + "name": "Do Chores", + "completed": false, + "created_at": "2014-08-28T14:14:28.494Z", + "updated_at": "2014-08-28T14:14:28.494Z" + }, + "new": { + "name": "esse qui proident labore", + "completed": null, + "id": 920778, + "completed_at": "2014-01-07T07:49:55.123Z", + "created_at": "1948-04-21T12:04:21.282Z", + "updated_at": "1951-12-19T11:10:34.039Z", + "user": { + "name": "irure deserunt fugiat", + "age": 121.45395681110494 + }, + "float": -47990796.228164576 + } + } + }, + "404": { + "$ref": "./todos.common.json#/responses/404" + }, + "500": { + "$ref": "./todos.common.json#/responses/500" + } + }, + "parameters": [{ + "in": "query", + "name": "", + "type": "string" + }] + }, + "put": { + "operationId": "PUT_todos", + "summary": "Update Todo", + "tags": ["Todos"], + "parameters": [{ + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/todo-partial", + "example": { + "name": "my todo's new name", + "completed": false + } + } + }], + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/todo-full" + }, + "examples": { + "application/json": { + "id": 9000, + "name": "It's Over 9000!!!", + "completed": true, + "completed_at": null, + "created_at": "2014-08-28T14:14:28.494Z", + "updated_at": "2015-08-28T14:14:28.494Z" + } + } + }, + "401": { + "$ref": "./todos.common.json#/responses/401" + }, + "404": { + "$ref": "./todos.common.json#/responses/404" + }, + "500": { + "$ref": "./todos.common.json#/responses/500" + } + }, + "security": [{ + "Basic": [] + }, + { + "API Key": [] + } + ] + }, + "delete": { + "operationId": "DELETE_todo", + "summary": "Delete Todo", + "tags": ["Todos"], + "responses": { + "204": { + "description": "" + }, + "401": { + "$ref": "./todos.common.json#/responses/401" + }, + "404": { + "$ref": "./todos.common.json#/responses/404" + }, + "500": { + "$ref": "./todos.common.json#/responses/500" + } + }, + "security": [{ + "Basic": [] + }, + { + "API Key": [] + } + ] + } + }, + "/todos": { + "post": { + "operationId": "POST_todos", + "summary": "Create Todo", + "tags": ["Todos"], + "parameters": [{ + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/todo-partial", + "example": { + "name": "my todo's name", + "completed": false + } + } + }], + "responses": { + "201": { + "description": "", + "schema": { + "$ref": "#/definitions/todo-full" + }, + "examples": { + "application/json": { + "id": 9000, + "name": "It's Over 9000!!!", + "completed": null, + "completed_at": null, + "created_at": "2014-08-28T14:14:28.494Z", + "updated_at": "2014-08-28T14:14:28.494Z" + }, + "/todos/chores": { + "id": 9000, + "name": "Do Chores", + "completed": false, + "created_at": "2014-08-28T14:14:28.494Z", + "updated_at": "2014-08-28T14:14:28.494Z" + } + } + }, + "401": { + "$ref": "./todos.common.json#/responses/401" + }, + "500": { + "$ref": "./todos.common.json#/responses/500" + } + }, + "security": [{ + "API Key": [] + }, + { + "Basic": [] + } + ], + "description": "This creates a Todo object.\n\nTesting `inline code`." + }, + "get": { + "operationId": "GET_todos", + "summary": "List Todos", + "tags": ["Todos"], + "parameters": [{ + "$ref": "#/parameters/limit" + }, + { + "$ref": "#/parameters/skip" + } + ], + "responses": { + "200": { + "description": "", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/todo-full" + } + }, + "examples": { + "application/json": [{ + "id": 1, + "name": "design the thingz", + "completed": true + }, + { + "id": 2, + "name": "mock the thingz", + "completed": true + }, + { + "id": 3, + "name": "code the thingz", + "completed": false + } + ], + "empty": [] + }, + "headers": { + "foo": { + "type": "string", + "default": "bar" + } + } + }, + "500": { + "$ref": "./todos.common.json#/responses/500" + } + }, + "description": "​" + } + } + }, + "parameters": { + "limit": { + "name": "limit", + "in": "query", + "description": "This is how it works.", + "required": false, + "type": "integer", + "maximum": 100 + }, + "skip": { + "name": "skip", + "in": "query", + "required": false, + "type": "string" + } + }, + "definitions": { + "todo-partial": { + "$ref": "ref.json#/definitions/todo-partial" + }, + "todo-full": { + "title": "Todo Full", + "allOf": [{ + "$ref": "#/definitions/todo-partial" + }, + { + "type": "object", + "properties": { + "id": { + "type": "integer", + "minimum": 0, + "maximum": 1000000 + }, + "completed_at": { + "type": ["string", "null"], + "format": "date-time" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "user": { + "$ref": "./todos.common.json#/definitions/user" + } + }, + "required": ["id", "user"] + } + ] + } + }, + "tags": [{ + "name": "Todos" + }] +} diff --git a/fixtures/bugs/2743/not-working/minimal.yaml b/fixtures/bugs/2743/not-working/minimal.yaml new file mode 100644 index 0000000..2c46773 --- /dev/null +++ b/fixtures/bugs/2743/not-working/minimal.yaml @@ -0,0 +1,7 @@ +swagger: '2.0' +info: + version: 0.0.0 + title: Simple API +paths: + /bar: + $ref: 'swagger/paths/bar.yml' diff --git a/fixtures/bugs/2743/not-working/spec.yaml b/fixtures/bugs/2743/not-working/spec.yaml new file mode 100644 index 0000000..9255173 --- /dev/null +++ b/fixtures/bugs/2743/not-working/spec.yaml @@ -0,0 +1,11 @@ +swagger: '2.0' +info: + version: 0.0.0 + title: Simple API +paths: + /foo: + $ref: 'swagger/paths/foo.yml' + /bar: + $ref: 'swagger/paths/bar.yml' + /nested: + $ref: 'swagger/paths/nested.yml#/response' diff --git a/fixtures/bugs/2743/not-working/swagger/definitions.yml b/fixtures/bugs/2743/not-working/swagger/definitions.yml new file mode 100644 index 0000000..438d6ee --- /dev/null +++ b/fixtures/bugs/2743/not-working/swagger/definitions.yml @@ -0,0 +1,9 @@ +ErrorPayload: + title: Error Payload + required: + - errors + properties: + errors: + type: array + items: + $ref: './items.yml#/ErrorDetailsItem' \ No newline at end of file diff --git a/fixtures/bugs/2743/not-working/swagger/items.yml b/fixtures/bugs/2743/not-working/swagger/items.yml new file mode 100644 index 0000000..e4a050c --- /dev/null +++ b/fixtures/bugs/2743/not-working/swagger/items.yml @@ -0,0 +1,15 @@ +ErrorDetailsItem: + title: Error details item + description: Represents an item of the list of details of an error. + required: + - message + - code + properties: + message: + type: string + code: + type: string + details: + type: array + items: + type: string \ No newline at end of file diff --git a/fixtures/bugs/2743/not-working/swagger/paths/bar.yml b/fixtures/bugs/2743/not-working/swagger/paths/bar.yml new file mode 100644 index 0000000..c813579 --- /dev/null +++ b/fixtures/bugs/2743/not-working/swagger/paths/bar.yml @@ -0,0 +1,8 @@ +get: + responses: + 200: + description: OK + schema: + type: array + items: + $ref: '../user/index.yml#/User' ## this doesn't work \ No newline at end of file diff --git a/fixtures/bugs/2743/not-working/swagger/paths/foo.yml b/fixtures/bugs/2743/not-working/swagger/paths/foo.yml new file mode 100644 index 0000000..a5c0de1 --- /dev/null +++ b/fixtures/bugs/2743/not-working/swagger/paths/foo.yml @@ -0,0 +1,8 @@ +get: + responses: + 200: + description: OK + 500: + description: OK + schema: + $ref: '../definitions.yml#/ErrorPayload' diff --git a/fixtures/bugs/2743/not-working/swagger/paths/index.yml b/fixtures/bugs/2743/not-working/swagger/paths/index.yml new file mode 100644 index 0000000..7cdcdd4 --- /dev/null +++ b/fixtures/bugs/2743/not-working/swagger/paths/index.yml @@ -0,0 +1,6 @@ +/foo: + $ref: ./foo.yml +/bar: + $ref: ./bar.yml +/nested: + $ref: ./nested.yml#/response diff --git a/fixtures/bugs/2743/not-working/swagger/paths/nested.yml b/fixtures/bugs/2743/not-working/swagger/paths/nested.yml new file mode 100644 index 0000000..5c7cf0d --- /dev/null +++ b/fixtures/bugs/2743/not-working/swagger/paths/nested.yml @@ -0,0 +1,14 @@ +response: + get: + responses: + 200: + description: OK + schema: + $ref: '#/definitions/SameFileReference' + +definitions: + SameFileReference: + type: object + properties: + name: + type: string diff --git a/fixtures/bugs/2743/not-working/swagger/user/index.yml b/fixtures/bugs/2743/not-working/swagger/user/index.yml new file mode 100644 index 0000000..99c1c6f --- /dev/null +++ b/fixtures/bugs/2743/not-working/swagger/user/index.yml @@ -0,0 +1,2 @@ +User: + $ref: './model.yml' diff --git a/fixtures/bugs/2743/not-working/swagger/user/model.yml b/fixtures/bugs/2743/not-working/swagger/user/model.yml new file mode 100644 index 0000000..5cb91cd --- /dev/null +++ b/fixtures/bugs/2743/not-working/swagger/user/model.yml @@ -0,0 +1,4 @@ +type: object +properties: + name: + type: string diff --git a/fixtures/bugs/2743/working/spec.yaml b/fixtures/bugs/2743/working/spec.yaml new file mode 100644 index 0000000..9255173 --- /dev/null +++ b/fixtures/bugs/2743/working/spec.yaml @@ -0,0 +1,11 @@ +swagger: '2.0' +info: + version: 0.0.0 + title: Simple API +paths: + /foo: + $ref: 'swagger/paths/foo.yml' + /bar: + $ref: 'swagger/paths/bar.yml' + /nested: + $ref: 'swagger/paths/nested.yml#/response' diff --git a/fixtures/bugs/2743/working/swagger/definitions.yml b/fixtures/bugs/2743/working/swagger/definitions.yml new file mode 100644 index 0000000..438d6ee --- /dev/null +++ b/fixtures/bugs/2743/working/swagger/definitions.yml @@ -0,0 +1,9 @@ +ErrorPayload: + title: Error Payload + required: + - errors + properties: + errors: + type: array + items: + $ref: './items.yml#/ErrorDetailsItem' \ No newline at end of file diff --git a/fixtures/bugs/2743/working/swagger/items.yml b/fixtures/bugs/2743/working/swagger/items.yml new file mode 100644 index 0000000..e4a050c --- /dev/null +++ b/fixtures/bugs/2743/working/swagger/items.yml @@ -0,0 +1,15 @@ +ErrorDetailsItem: + title: Error details item + description: Represents an item of the list of details of an error. + required: + - message + - code + properties: + message: + type: string + code: + type: string + details: + type: array + items: + type: string \ No newline at end of file diff --git a/fixtures/bugs/2743/working/swagger/paths/bar.yml b/fixtures/bugs/2743/working/swagger/paths/bar.yml new file mode 100644 index 0000000..41dc76a --- /dev/null +++ b/fixtures/bugs/2743/working/swagger/paths/bar.yml @@ -0,0 +1,8 @@ +get: + responses: + 200: + description: OK + schema: + type: array + items: + $ref: './swagger/user/index.yml#/User' ## this works \ No newline at end of file diff --git a/fixtures/bugs/2743/working/swagger/paths/foo.yml b/fixtures/bugs/2743/working/swagger/paths/foo.yml new file mode 100644 index 0000000..a5c0de1 --- /dev/null +++ b/fixtures/bugs/2743/working/swagger/paths/foo.yml @@ -0,0 +1,8 @@ +get: + responses: + 200: + description: OK + 500: + description: OK + schema: + $ref: '../definitions.yml#/ErrorPayload' diff --git a/fixtures/bugs/2743/working/swagger/paths/index.yml b/fixtures/bugs/2743/working/swagger/paths/index.yml new file mode 100644 index 0000000..7cdcdd4 --- /dev/null +++ b/fixtures/bugs/2743/working/swagger/paths/index.yml @@ -0,0 +1,6 @@ +/foo: + $ref: ./foo.yml +/bar: + $ref: ./bar.yml +/nested: + $ref: ./nested.yml#/response diff --git a/fixtures/bugs/2743/working/swagger/paths/nested.yml b/fixtures/bugs/2743/working/swagger/paths/nested.yml new file mode 100644 index 0000000..5c7cf0d --- /dev/null +++ b/fixtures/bugs/2743/working/swagger/paths/nested.yml @@ -0,0 +1,14 @@ +response: + get: + responses: + 200: + description: OK + schema: + $ref: '#/definitions/SameFileReference' + +definitions: + SameFileReference: + type: object + properties: + name: + type: string diff --git a/fixtures/bugs/2743/working/swagger/user/index.yml b/fixtures/bugs/2743/working/swagger/user/index.yml new file mode 100644 index 0000000..99c1c6f --- /dev/null +++ b/fixtures/bugs/2743/working/swagger/user/index.yml @@ -0,0 +1,2 @@ +User: + $ref: './model.yml' diff --git a/fixtures/bugs/2743/working/swagger/user/model.yml b/fixtures/bugs/2743/working/swagger/user/model.yml new file mode 100644 index 0000000..5cb91cd --- /dev/null +++ b/fixtures/bugs/2743/working/swagger/user/model.yml @@ -0,0 +1,4 @@ +type: object +properties: + name: + type: string diff --git a/fixtures/bugs/schema-148.json b/fixtures/bugs/schema-148.json new file mode 100644 index 0000000..a233257 --- /dev/null +++ b/fixtures/bugs/schema-148.json @@ -0,0 +1,10 @@ +{ + "definitions": { + "empty": { + "additionalProperties": {} + }, + "false": { + "additionalProperties": false + } + } +} diff --git a/go.mod b/go.mod index 9e3e36f..996f90a 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,18 @@ module github.com/go-openapi/spec require ( - github.com/go-openapi/jsonpointer v0.19.5 - github.com/go-openapi/jsonreference v0.20.0 - github.com/go-openapi/swag v0.19.15 - github.com/stretchr/testify v1.6.1 - gopkg.in/yaml.v2 v2.4.0 + github.com/go-openapi/jsonpointer v0.21.0 + github.com/go-openapi/jsonreference v0.21.0 + github.com/go-openapi/swag v0.23.0 + github.com/stretchr/testify v1.9.0 + gopkg.in/yaml.v3 v3.0.1 ) -go 1.13 +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect +) + +go 1.20 diff --git a/go.sum b/go.sum index 7427dbd..b5bc1d1 100644 --- a/go.sum +++ b/go.sum @@ -1,41 +1,23 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= -github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/header_test.go b/header_test.go index 19d08cc..67a893a 100644 --- a/header_test.go +++ b/header_test.go @@ -20,8 +20,11 @@ import ( "github.com/go-openapi/swag" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +const epsilon = 1e-9 + func float64Ptr(f float64) *float64 { return &f } @@ -83,9 +86,8 @@ const headerJSON = `{ func TestIntegrationHeader(t *testing.T) { var actual Header - if assert.NoError(t, json.Unmarshal([]byte(headerJSON), &actual)) { - assert.EqualValues(t, actual, header) - } + require.NoError(t, json.Unmarshal([]byte(headerJSON), &actual)) + assert.EqualValues(t, actual, header) assertParsesJSON(t, headerJSON, header) } @@ -93,37 +95,38 @@ func TestIntegrationHeader(t *testing.T) { func TestJSONLookupHeader(t *testing.T) { var def string res, err := header.JSONLookup("default") - if !assert.NoError(t, err) || !assert.NotNil(t, res) || !assert.IsType(t, def, res) { - t.FailNow() - return - } - def = res.(string) + require.NoError(t, err) + require.NotNil(t, res) + require.IsType(t, def, res) + + var ok bool + def, ok = res.(string) + require.True(t, ok) assert.Equal(t, "8", def) var x *interface{} res, err = header.JSONLookup("x-framework") - if !assert.NoError(t, err) || !assert.NotNil(t, res) || !assert.IsType(t, x, res) { - t.FailNow() - return - } + require.NoError(t, err) + require.NotNil(t, res) + require.IsType(t, x, res) - x = res.(*interface{}) + x, ok = res.(*interface{}) + require.True(t, ok) assert.EqualValues(t, "swagger-go", *x) res, err = header.JSONLookup("unknown") - if !assert.Error(t, err) || !assert.Nil(t, res) { - t.FailNow() - return - } + require.Error(t, err) + require.Nil(t, res) var max *float64 res, err = header.JSONLookup("maximum") - if !assert.NoError(t, err) || !assert.NotNil(t, res) || !assert.IsType(t, max, res) { - t.FailNow() - return - } - max = res.(*float64) - assert.Equal(t, float64(100), *max) + require.NoError(t, err) + require.NotNil(t, res) + require.IsType(t, max, res) + + max, ok = res.(*float64) + require.True(t, ok) + assert.InDelta(t, float64(100), *max, epsilon) } func TestResponseHeaueder(t *testing.T) { diff --git a/helpers_spec_test.go b/helpers_spec_test.go index 40fa75b..4dd12c5 100644 --- a/helpers_spec_test.go +++ b/helpers_spec_test.go @@ -71,7 +71,7 @@ func assertRefInJSONRegexp(t testing.TB, jazon, match string) { // assertRefExpand ensures that all $ref in some json doc expand properly against a root document. // // "exclude" is a regexp pattern to ignore certain $ref (e.g. some specs may embed $ref that are not processed, such as extensions). -func assertRefExpand(t *testing.T, jazon, exclude string, root interface{}, opts ...*spec.ExpandOptions) { +func assertRefExpand(t *testing.T, jazon, _ string, root interface{}, opts ...*spec.ExpandOptions) { if len(opts) > 0 { assertRefWithFunc(t, "expand-with-base", jazon, "", func(t *testing.T, match string) { ref := spec.RefSchema(match) diff --git a/helpers_test.go b/helpers_test.go index 0d54150..f16a4dd 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -105,7 +105,7 @@ func assertNoRef(t testing.TB, jazon string) { // assertRefExpand ensures that all $ref in some json doc expand properly against a root document. // // "exclude" is a regexp pattern to ignore certain $ref (e.g. some specs may embed $ref that are not processed, such as extensions). -func assertRefExpand(t *testing.T, jazon, exclude string, root interface{}, opts ...*ExpandOptions) { +func assertRefExpand(t *testing.T, jazon, _ string, root interface{}, opts ...*ExpandOptions) { assertRefWithFunc(t, jazon, "", func(t *testing.T, match string) { ref := RefSchema(match) if len(opts) > 0 { diff --git a/info.go b/info.go index c458b49..582f0fd 100644 --- a/info.go +++ b/info.go @@ -16,6 +16,7 @@ package spec import ( "encoding/json" + "strconv" "strings" "github.com/go-openapi/jsonpointer" @@ -40,6 +41,24 @@ func (e Extensions) GetString(key string) (string, bool) { return "", false } +// GetInt gets a int value from the extensions +func (e Extensions) GetInt(key string) (int, bool) { + realKey := strings.ToLower(key) + + if v, ok := e.GetString(realKey); ok { + if r, err := strconv.Atoi(v); err == nil { + return r, true + } + } + + if v, ok := e[realKey]; ok { + if r, rOk := v.(float64); rOk { + return int(r), true + } + } + return -1, false +} + // GetBool gets a string value from the extensions func (e Extensions) GetBool(key string) (bool, bool) { if v, ok := e[strings.ToLower(key)]; ok { diff --git a/info_test.go b/info_test.go index 5ecf1bb..a8e3fbe 100644 --- a/info_test.go +++ b/info_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const infoJSON = `{ @@ -57,25 +58,19 @@ var info = Info{ func TestIntegrationInfo_Serialize(t *testing.T) { b, err := json.MarshalIndent(info, "", "\t") - if assert.NoError(t, err) { - assert.Equal(t, infoJSON, string(b)) - } + require.NoError(t, err) + assert.Equal(t, infoJSON, string(b)) } func TestIntegrationInfo_Deserialize(t *testing.T) { actual := Info{} - err := json.Unmarshal([]byte(infoJSON), &actual) - if assert.NoError(t, err) { - assert.EqualValues(t, info, actual) - } + require.NoError(t, json.Unmarshal([]byte(infoJSON), &actual)) + assert.EqualValues(t, info, actual) } func TestInfoGobEncoding(t *testing.T) { var src, dst Info - if assert.NoError(t, json.Unmarshal([]byte(infoJSON), &src)) { - assert.EqualValues(t, src, info) - } else { - t.FailNow() - } + require.NoError(t, json.Unmarshal([]byte(infoJSON), &src)) + assert.EqualValues(t, src, info) doTestAnyGobEncoding(t, &src, &dst) } diff --git a/items_test.go b/items_test.go index edd75c2..4d4e458 100644 --- a/items_test.go +++ b/items_test.go @@ -20,6 +20,7 @@ import ( "github.com/go-openapi/swag" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var items = Items{ @@ -74,9 +75,8 @@ const itemsJSON = `{ func TestIntegrationItems(t *testing.T) { var actual Items - if assert.NoError(t, json.Unmarshal([]byte(itemsJSON), &actual)) { - assert.EqualValues(t, actual, items) - } + require.NoError(t, json.Unmarshal([]byte(itemsJSON), &actual)) + assert.EqualValues(t, actual, items) assertParsesJSON(t, itemsJSON, items) } @@ -152,38 +152,38 @@ func TestItemsBuilder(t *testing.T) { func TestJSONLookupItems(t *testing.T) { res, err := items.JSONLookup("$ref") - if !assert.NoError(t, err) { - t.FailNow() - return - } - if assert.IsType(t, &Ref{}, res) { - ref := res.(*Ref) - assert.EqualValues(t, MustCreateRef("Dog"), *ref) - } + require.NoError(t, err) + require.NotNil(t, res) + require.IsType(t, &Ref{}, res) + + var ok bool + ref, ok := res.(*Ref) + require.True(t, ok) + assert.EqualValues(t, MustCreateRef("Dog"), *ref) var max *float64 res, err = items.JSONLookup("maximum") - if !assert.NoError(t, err) || !assert.NotNil(t, res) || !assert.IsType(t, max, res) { - t.FailNow() - return - } - max = res.(*float64) - assert.Equal(t, float64(100), *max) + require.NoError(t, err) + require.NotNil(t, res) + require.IsType(t, max, res) + + max, ok = res.(*float64) + require.True(t, ok) + assert.InDelta(t, float64(100), *max, epsilon) var f string res, err = items.JSONLookup("collectionFormat") - if !assert.NoError(t, err) || !assert.NotNil(t, res) || !assert.IsType(t, f, res) { - t.FailNow() - return - } - f = res.(string) + require.NoError(t, err) + require.NotNil(t, res) + require.IsType(t, f, res) + + f, ok = res.(string) + require.True(t, ok) assert.Equal(t, "csv", f) res, err = items.JSONLookup("unknown") - if !assert.Error(t, err) || !assert.Nil(t, res) { - t.FailNow() - return - } + require.Error(t, err) + require.Nil(t, res) } func TestItemsWithValidation(t *testing.T) { diff --git a/license_test.go b/license_test.go index 407df73..398f0d8 100644 --- a/license_test.go +++ b/license_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var license = License{ @@ -36,13 +37,11 @@ func TestIntegrationLicense(t *testing.T) { // const licenseYAML = "name: the name\nurl: the url\n" b, err := json.MarshalIndent(license, "", "\t") - if assert.NoError(t, err) { - assert.Equal(t, licenseJSON, string(b)) - } + require.NoError(t, err) + assert.Equal(t, licenseJSON, string(b)) actual := License{} err = json.Unmarshal([]byte(licenseJSON), &actual) - if assert.NoError(t, err) { - assert.EqualValues(t, license, actual) - } + require.NoError(t, err) + assert.EqualValues(t, license, actual) } diff --git a/normalizer_nonwindows.go b/normalizer_nonwindows.go index 2df0723..f19f1a8 100644 --- a/normalizer_nonwindows.go +++ b/normalizer_nonwindows.go @@ -40,5 +40,5 @@ func repairURI(in string) (*url.URL, string) { return u, "" } -func fixWindowsURI(u *url.URL, in string) { +func fixWindowsURI(_ *url.URL, _ string) { } diff --git a/normalizer_test.go b/normalizer_test.go index b152297..8d72c28 100644 --- a/normalizer_test.go +++ b/normalizer_test.go @@ -219,6 +219,30 @@ func TestNormalizer_NormalizeURI(t *testing.T) { base: `https://example.com//base/Spec.json`, expOutput: `https://example.com/base/Resources.yaml#/definitions/Pets`, }, + { + // escaped characters in base (1) + refPath: `Resources.yaml#/definitions/Pets`, + base: `https://example.com/base (x86)/Spec.json`, + expOutput: `https://example.com/base%20%28x86%29/Resources.yaml#/definitions/Pets`, + }, + { + // escaped characters in base (2) + refPath: `Resources.yaml#/definitions/Pets`, + base: `https://example.com/base [x86]/Spec.json`, + expOutput: `https://example.com/base%20%5Bx86%5D/Resources.yaml#/definitions/Pets`, + }, + { + // escaped characters in joined fragment + refPath: `Resources.yaml#/definitions (x86)/Pets`, + base: `https://example.com/base/Spec.json`, + expOutput: `https://example.com/base/Resources.yaml#/definitions%20(x86)/Pets`, + }, + { + // escaped characters in joined path + refPath: `Resources [x86].yaml#/definitions/Pets`, + base: `https://example.com/base/Spec.json`, + expOutput: `https://example.com/base/Resources%20%5Bx86%5D.yaml#/definitions/Pets`, + }, } }() @@ -245,6 +269,7 @@ func TestNormalizer_NormalizeBase(t *testing.T) { if runtime.GOOS == windowsOS { cwd = "/" + strings.ToLower(filepath.ToSlash(cwd)) } + const fileScheme = "file:///" for _, toPin := range []struct { Base, Expected string @@ -317,7 +342,7 @@ func TestNormalizer_NormalizeBase(t *testing.T) { { // path clean Base: "///folder//subfolder///file.json/", - Expected: "file:///" + currentDriveLetter + ":/folder/subfolder/file.json", + Expected: fileScheme + currentDriveLetter + ":/folder/subfolder/file.json", Windows: true, }, { @@ -344,18 +369,18 @@ func TestNormalizer_NormalizeBase(t *testing.T) { { // handling dots (3/6): valid, cleaned to /c:/ on windows Base: "/..", - Expected: "file:///" + currentDriveLetter + ":", + Expected: fileScheme + currentDriveLetter + ":", Windows: true, }, { // handling dots (4/6): dodgy specification - resolved to / Base: `file:/.`, - Expected: "file:///", + Expected: fileScheme, }, { // handling dots (5/6): dodgy specification - resolved to / Base: `file:/..`, - Expected: "file:///", + Expected: fileScheme, }, { // handling dots (6/6) @@ -377,7 +402,7 @@ func TestNormalizer_NormalizeBase(t *testing.T) { // windows-only cases { Base: "/base/sub/file.json", - Expected: "file:///" + currentDriveLetter + ":/base/sub/file.json", // on windows, filepath.Abs("/a/b") prepends the "c:" drive + Expected: fileScheme + currentDriveLetter + ":/base/sub/file.json", // on windows, filepath.Abs("/a/b") prepends the "c:" drive Windows: true, }, { @@ -422,6 +447,11 @@ func TestNormalizer_NormalizeBase(t *testing.T) { Expected: "file:///e:/base/sub/file.json", Windows: true, }, + { + // escaped characters in base (1) + Base: `file:///c:/base (x86)/spec.json`, + Expected: `file:///c:/base%20%28x86%29/spec.json`, + }, } { testCase := toPin if testCase.Windows && runtime.GOOS != windowsOS { diff --git a/operation.go b/operation.go index 995ce6a..a69cca8 100644 --- a/operation.go +++ b/operation.go @@ -217,9 +217,12 @@ func (o *Operation) AddParam(param *Parameter) *Operation { for i, p := range o.Parameters { if p.Name == param.Name && p.In == param.In { - params := append(o.Parameters[:i], *param) + params := make([]Parameter, 0, len(o.Parameters)+1) + params = append(params, o.Parameters[:i]...) + params = append(params, *param) params = append(params, o.Parameters[i+1:]...) o.Parameters = params + return o } } diff --git a/operation_test.go b/operation_test.go index 812cdda..495f609 100644 --- a/operation_test.go +++ b/operation_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var operation = Operation{ @@ -82,30 +83,28 @@ func TestSuccessResponse(t *testing.T) { resp, n, f := ope.SuccessResponse() assert.Nil(t, resp) assert.Equal(t, 0, n) - assert.Equal(t, false, f) + assert.False(t, f) resp, n, f = operation.SuccessResponse() - if assert.NotNil(t, resp) { - assert.Equal(t, "void response", resp.Description) - } + require.NotNil(t, resp) + assert.Equal(t, "void response", resp.Description) + assert.Equal(t, 0, n) - assert.Equal(t, false, f) + assert.False(t, f) + + require.NoError(t, json.Unmarshal([]byte(operationJSON), ope)) - err := json.Unmarshal([]byte(operationJSON), ope) - if !assert.Nil(t, err) { - t.FailNow() - } ope = ope.RespondsWith(301, &Response{ ResponseProps: ResponseProps{ Description: "failure", }, }) resp, n, f = ope.SuccessResponse() - if assert.NotNil(t, resp) { - assert.Equal(t, "void response", resp.Description) - } + require.NotNil(t, resp) + assert.Equal(t, "void response", resp.Description) + assert.Equal(t, 0, n) - assert.Equal(t, false, f) + assert.False(t, f) ope = ope.RespondsWith(200, &Response{ ResponseProps: ResponseProps{ @@ -114,11 +113,11 @@ func TestSuccessResponse(t *testing.T) { }) resp, n, f = ope.SuccessResponse() - if assert.NotNil(t, resp) { - assert.Equal(t, "success", resp.Description) - } + require.NotNil(t, resp) + assert.Equal(t, "success", resp.Description) + assert.Equal(t, 200, n) - assert.Equal(t, true, f) + assert.True(t, f) } func TestOperationBuilder(t *testing.T) { @@ -146,7 +145,9 @@ func TestOperationBuilder(t *testing.T) { WithSummary("my summary"). WithExternalDocs("some doc", "https://www.example.com") - jazon, _ := json.MarshalIndent(ope, "", " ") + jazon, err := json.MarshalIndent(ope, "", " ") + require.NoError(t, err) + assert.JSONEq(t, `{ "operationId": "operationID", "description": "test operation", @@ -201,8 +202,11 @@ func TestOperationBuilder(t *testing.T) { // check token lookup token, err := ope.JSONLookup("responses") - assert.NoError(t, err) - jazon, _ = json.MarshalIndent(token, "", " ") + require.NoError(t, err) + + jazon, err = json.MarshalIndent(token, "", " ") + require.NoError(t, err) + assert.JSONEq(t, `{ "200": { "description": "success" @@ -219,7 +223,9 @@ func TestOperationBuilder(t *testing.T) { RemoveParam("fakeParam", "query"). Undeprecate(). WithExternalDocs("", "") - jazon, _ = json.MarshalIndent(ope, "", " ") + jazon, err = json.MarshalIndent(ope, "", " ") + require.NoError(t, err) + assert.JSONEq(t, `{ "security": [ { @@ -252,9 +258,8 @@ func TestOperationBuilder(t *testing.T) { func TestIntegrationOperation(t *testing.T) { var actual Operation - if assert.NoError(t, json.Unmarshal([]byte(operationJSON), &actual)) { - assert.EqualValues(t, actual, operation) - } + require.NoError(t, json.Unmarshal([]byte(operationJSON), &actual)) + assert.EqualValues(t, actual, operation) assertParsesJSON(t, operationJSON, operation) } @@ -263,21 +268,19 @@ func TestSecurityProperty(t *testing.T) { // Ensure we omit security key when unset securityNotSet := OperationProps{} jsonResult, err := json.Marshal(securityNotSet) - if assert.NoError(t, err) { - assert.NotContains(t, string(jsonResult), "security", "security key should be omitted when unset") - } + require.NoError(t, err) + assert.NotContains(t, string(jsonResult), "security", "security key should be omitted when unset") // Ensure we preserve the security key when it contains an empty (zero length) slice securityContainsEmptyArray := OperationProps{ Security: []map[string][]string{}, } jsonResult, err = json.Marshal(securityContainsEmptyArray) - if assert.NoError(t, err) { - var props OperationProps - if assert.NoError(t, json.Unmarshal(jsonResult, &props)) { - assert.Equal(t, securityContainsEmptyArray, props) - } - } + require.NoError(t, err) + + var props OperationProps + require.NoError(t, json.Unmarshal(jsonResult, &props)) + assert.Equal(t, securityContainsEmptyArray, props) } func TestOperationGobEncoding(t *testing.T) { @@ -346,10 +349,7 @@ func TestOperationGobEncoding(t *testing.T) { func doTestOperationGobEncoding(t *testing.T, fixture string) { var src, dst Operation - - if !assert.NoError(t, json.Unmarshal([]byte(fixture), &src)) { - t.FailNow() - } + require.NoError(t, json.Unmarshal([]byte(fixture), &src)) doTestAnyGobEncoding(t, &src, &dst) } @@ -359,18 +359,12 @@ func doTestAnyGobEncoding(t *testing.T, src, dst interface{}) { var b bytes.Buffer err := gob.NewEncoder(&b).Encode(src) - if !assert.NoError(t, err) { - t.FailNow() - } + require.NoError(t, err) err = gob.NewDecoder(&b).Decode(dst) - if !assert.NoError(t, err) { - t.FailNow() - } + require.NoError(t, err) jazon, err := json.MarshalIndent(dst, "", " ") - if !assert.NoError(t, err) { - t.FailNow() - } + require.NoError(t, err) assert.JSONEq(t, string(expectedJSON), string(jazon)) } diff --git a/parameter.go b/parameter.go index 2b2b89b..bd4f1cd 100644 --- a/parameter.go +++ b/parameter.go @@ -84,27 +84,27 @@ type ParamProps struct { // Parameter a unique parameter is defined by a combination of a [name](#parameterName) and [location](#parameterIn). // // There are five possible parameter types. -// * Path - Used together with [Path Templating](#pathTemplating), where the parameter value is actually part -// of the operation's URL. This does not include the host or base path of the API. For example, in `/items/{itemId}`, -// the path parameter is `itemId`. -// * Query - Parameters that are appended to the URL. For example, in `/items?id=###`, the query parameter is `id`. -// * Header - Custom headers that are expected as part of the request. -// * Body - The payload that's appended to the HTTP request. Since there can only be one payload, there can only be -// _one_ body parameter. The name of the body parameter has no effect on the parameter itself and is used for -// documentation purposes only. Since Form parameters are also in the payload, body and form parameters cannot exist -// together for the same operation. -// * Form - Used to describe the payload of an HTTP request when either `application/x-www-form-urlencoded` or -// `multipart/form-data` are used as the content type of the request (in Swagger's definition, -// the [`consumes`](#operationConsumes) property of an operation). This is the only parameter type that can be used -// to send files, thus supporting the `file` type. Since form parameters are sent in the payload, they cannot be -// declared together with a body parameter for the same operation. Form parameters have a different format based on -// the content-type used (for further details, consult http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4). -// * `application/x-www-form-urlencoded` - Similar to the format of Query parameters but as a payload. -// For example, `foo=1&bar=swagger` - both `foo` and `bar` are form parameters. This is normally used for simple -// parameters that are being transferred. -// * `multipart/form-data` - each parameter takes a section in the payload with an internal header. -// For example, for the header `Content-Disposition: form-data; name="submit-name"` the name of the parameter is -// `submit-name`. This type of form parameters is more commonly used for file transfers. +// - Path - Used together with [Path Templating](#pathTemplating), where the parameter value is actually part +// of the operation's URL. This does not include the host or base path of the API. For example, in `/items/{itemId}`, +// the path parameter is `itemId`. +// - Query - Parameters that are appended to the URL. For example, in `/items?id=###`, the query parameter is `id`. +// - Header - Custom headers that are expected as part of the request. +// - Body - The payload that's appended to the HTTP request. Since there can only be one payload, there can only be +// _one_ body parameter. The name of the body parameter has no effect on the parameter itself and is used for +// documentation purposes only. Since Form parameters are also in the payload, body and form parameters cannot exist +// together for the same operation. +// - Form - Used to describe the payload of an HTTP request when either `application/x-www-form-urlencoded` or +// `multipart/form-data` are used as the content type of the request (in Swagger's definition, +// the [`consumes`](#operationConsumes) property of an operation). This is the only parameter type that can be used +// to send files, thus supporting the `file` type. Since form parameters are sent in the payload, they cannot be +// declared together with a body parameter for the same operation. Form parameters have a different format based on +// the content-type used (for further details, consult http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4). +// - `application/x-www-form-urlencoded` - Similar to the format of Query parameters but as a payload. +// For example, `foo=1&bar=swagger` - both `foo` and `bar` are form parameters. This is normally used for simple +// parameters that are being transferred. +// - `multipart/form-data` - each parameter takes a section in the payload with an internal header. +// For example, for the header `Content-Disposition: form-data; name="submit-name"` the name of the parameter is +// `submit-name`. This type of form parameters is more commonly used for file transfers. // // For more information: http://goo.gl/8us55a#parameterObject type Parameter struct { diff --git a/parameters_test.go b/parameters_test.go index 0f5ad3c..702d867 100644 --- a/parameters_test.go +++ b/parameters_test.go @@ -20,6 +20,7 @@ import ( "github.com/go-openapi/swag" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var parameter = Parameter{ @@ -92,9 +93,8 @@ var parameterJSON = `{ func TestIntegrationParameter(t *testing.T) { var actual Parameter - if assert.NoError(t, json.Unmarshal([]byte(parameterJSON), &actual)) { - assert.EqualValues(t, actual, parameter) - } + require.NoError(t, json.Unmarshal([]byte(parameterJSON), &actual)) + assert.EqualValues(t, actual, parameter) assertParsesJSON(t, parameterJSON, parameter) } @@ -158,9 +158,7 @@ func TestParameterSerialization(t *testing.T) { func TestParameterGobEncoding(t *testing.T) { var src, dst Parameter - if !assert.NoError(t, json.Unmarshal([]byte(parameterJSON), &src)) { - t.FailNow() - } + require.NoError(t, json.Unmarshal([]byte(parameterJSON), &src)) doTestAnyGobEncoding(t, &src, &dst) } diff --git a/path_item_test.go b/path_item_test.go index 3c78e90..c3507a0 100644 --- a/path_item_test.go +++ b/path_item_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var pathItem = PathItem{ @@ -73,9 +74,8 @@ const pathItemJSON = `{ func TestIntegrationPathItem(t *testing.T) { var actual PathItem - if assert.NoError(t, json.Unmarshal([]byte(pathItemJSON), &actual)) { - assert.EqualValues(t, actual, pathItem) - } + require.NoError(t, json.Unmarshal([]byte(pathItemJSON), &actual)) + assert.EqualValues(t, actual, pathItem) assertParsesJSON(t, pathItemJSON, pathItem) } diff --git a/paths_test.go b/paths_test.go index ff5626a..4592818 100644 --- a/paths_test.go +++ b/paths_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var paths = Paths{ @@ -34,9 +35,8 @@ const pathsJSON = `{"x-framework":"go-swagger","/":{"$ref":"cats"}}` func TestIntegrationPaths(t *testing.T) { var actual Paths - if assert.NoError(t, json.Unmarshal([]byte(pathsJSON), &actual)) { - assert.EqualValues(t, actual, paths) - } + require.NoError(t, json.Unmarshal([]byte(pathsJSON), &actual)) + assert.EqualValues(t, actual, paths) assertParsesJSON(t, pathsJSON, paths) diff --git a/properties.go b/properties.go index 2af1378..91d2435 100644 --- a/properties.go +++ b/properties.go @@ -42,8 +42,8 @@ func (items OrderSchemaItems) MarshalJSON() ([]byte, error) { func (items OrderSchemaItems) Len() int { return len(items) } func (items OrderSchemaItems) Swap(i, j int) { items[i], items[j] = items[j], items[i] } func (items OrderSchemaItems) Less(i, j int) (ret bool) { - ii, oki := items[i].Extensions.GetString("x-order") - ij, okj := items[j].Extensions.GetString("x-order") + ii, oki := items[i].Extensions.GetInt("x-order") + ij, okj := items[j].Extensions.GetInt("x-order") if oki { if okj { defer func() { @@ -56,7 +56,7 @@ func (items OrderSchemaItems) Less(i, j int) (ret bool) { ret = reflect.ValueOf(ii).String() < reflect.ValueOf(ij).String() } }() - return reflect.ValueOf(ii).Int() < reflect.ValueOf(ij).Int() + return ii < ij } return true } else if okj { diff --git a/ref_test.go b/ref_test.go index 7dc2f59..6f4c0de 100644 --- a/ref_test.go +++ b/ref_test.go @@ -21,27 +21,24 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // pin pointing go-swagger/go-swagger#1816 issue with cloning ref's func TestCloneRef(t *testing.T) { var b bytes.Buffer src := MustCreateRef("#/definitions/test") - err := gob.NewEncoder(&b).Encode(&src) - if !assert.NoError(t, err) { - t.FailNow() - } + require.NoError(t, + gob.NewEncoder(&b).Encode(&src), + ) var dst Ref - err = gob.NewDecoder(&b).Decode(&dst) - if !assert.NoError(t, err) { - t.FailNow() - } + require.NoError(t, + gob.NewDecoder(&b).Decode(&dst), + ) jazon, err := json.Marshal(dst) - if !assert.NoError(t, err) { - t.FailNow() - } + require.NoError(t, err) assert.Equal(t, `{"$ref":"#/definitions/test"}`, string(jazon)) } diff --git a/resolver_test.go b/resolver_test.go index 21df897..b1c977a 100644 --- a/resolver_test.go +++ b/resolver_test.go @@ -2,9 +2,9 @@ package spec import ( "encoding/json" - "io/ioutil" "net/http" "net/http/httptest" + "os" "path/filepath" "testing" @@ -132,7 +132,7 @@ func TestResolveRemoteRef_RootSame(t *testing.T) { defer server.Close() rootDoc := new(Swagger) - b, err := ioutil.ReadFile(filepath.Join(specs, "refed.json")) + b, err := os.ReadFile(filepath.Join(specs, "refed.json")) require.NoError(t, err) require.NoError(t, json.Unmarshal(b, rootDoc)) @@ -160,7 +160,7 @@ func TestResolveRemoteRef_FromFragment(t *testing.T) { defer server.Close() rootDoc := new(Swagger) - b, err := ioutil.ReadFile(filepath.Join(specs, "refed.json")) + b, err := os.ReadFile(filepath.Join(specs, "refed.json")) require.NoError(t, err) require.NoError(t, json.Unmarshal(b, rootDoc)) @@ -180,7 +180,7 @@ func TestResolveRemoteRef_FromInvalidFragment(t *testing.T) { defer server.Close() rootDoc := new(Swagger) - b, err := ioutil.ReadFile(filepath.Join(specs, "refed.json")) + b, err := os.ReadFile(filepath.Join(specs, "refed.json")) require.NoError(t, err) require.NoError(t, json.Unmarshal(b, rootDoc)) @@ -189,7 +189,7 @@ func TestResolveRemoteRef_FromInvalidFragment(t *testing.T) { require.NoError(t, err) resolver := defaultSchemaLoader(rootDoc, nil, nil, nil) - assert.Error(t, resolver.Resolve(&ref, &tgt, "")) + require.Error(t, resolver.Resolve(&ref, &tgt, "")) } /* This next test will have to wait until we do full $ID analysis for every subschema on every file that is referenced */ @@ -199,7 +199,7 @@ func TestResolveRemoteRef_FromInvalidFragment(t *testing.T) { // defer server.Close() // // rootDoc := new(Swagger) -// b, err := ioutil.ReadFile("fixtures/specs/refed.json") +// b, err := os.ReadFile("fixtures/specs/refed.json") // require.NoError(t, err) && assert.NoError(t, json.Unmarshal(b, rootDoc)) // // var tgt Schema @@ -217,7 +217,7 @@ func TestResolveRemoteRef_ToParameter(t *testing.T) { defer server.Close() rootDoc := new(Swagger) - b, err := ioutil.ReadFile(filepath.Join(specs, "refed.json")) + b, err := os.ReadFile(filepath.Join(specs, "refed.json")) require.NoError(t, err) require.NoError(t, json.Unmarshal(b, rootDoc)) @@ -242,7 +242,7 @@ func TestResolveRemoteRef_ToPathItem(t *testing.T) { defer server.Close() rootDoc := new(Swagger) - b, err := ioutil.ReadFile(filepath.Join(specs, "refed.json")) + b, err := os.ReadFile(filepath.Join(specs, "refed.json")) require.NoError(t, err) require.NoError(t, json.Unmarshal(b, rootDoc)) @@ -261,7 +261,7 @@ func TestResolveRemoteRef_ToResponse(t *testing.T) { defer server.Close() rootDoc := new(Swagger) - b, err := ioutil.ReadFile(filepath.Join(specs, "refed.json")) + b, err := os.ReadFile(filepath.Join(specs, "refed.json")) require.NoError(t, err) require.NoError(t, json.Unmarshal(b, rootDoc)) @@ -312,7 +312,7 @@ func TestResolveLocalRef_FromInvalidFragment(t *testing.T) { func TestResolveLocalRef_Parameter(t *testing.T) { rootDoc := new(Swagger) - b, err := ioutil.ReadFile(filepath.Join(specs, "refed.json")) + b, err := os.ReadFile(filepath.Join(specs, "refed.json")) require.NoError(t, err) basePath := filepath.Join(specs, "refed.json") @@ -335,7 +335,7 @@ func TestResolveLocalRef_Parameter(t *testing.T) { func TestResolveLocalRef_PathItem(t *testing.T) { rootDoc := new(Swagger) - b, err := ioutil.ReadFile(filepath.Join(specs, "refed.json")) + b, err := os.ReadFile(filepath.Join(specs, "refed.json")) require.NoError(t, err) basePath := filepath.Join(specs, "refed.json") @@ -352,7 +352,7 @@ func TestResolveLocalRef_PathItem(t *testing.T) { func TestResolveLocalRef_Response(t *testing.T) { rootDoc := new(Swagger) - b, err := ioutil.ReadFile(filepath.Join(specs, "refed.json")) + b, err := os.ReadFile(filepath.Join(specs, "refed.json")) require.NoError(t, err) basePath := filepath.Join(specs, "refed.json") diff --git a/response_test.go b/response_test.go index 71578ab..d03720c 100644 --- a/response_test.go +++ b/response_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var response = Response{ @@ -45,48 +46,46 @@ const responseJSON = `{ func TestIntegrationResponse(t *testing.T) { var actual Response - if assert.NoError(t, json.Unmarshal([]byte(responseJSON), &actual)) { - assert.EqualValues(t, actual, response) - } + require.NoError(t, json.Unmarshal([]byte(responseJSON), &actual)) + assert.EqualValues(t, actual, response) assertParsesJSON(t, responseJSON, response) } func TestJSONLookupResponse(t *testing.T) { res, err := response.JSONLookup("$ref") - if !assert.NoError(t, err) { - t.FailNow() - return - } - if assert.IsType(t, &Ref{}, res) { - ref := res.(*Ref) - assert.EqualValues(t, MustCreateRef("Dog"), *ref) - } + require.NoError(t, err) + require.NotNil(t, res) + require.IsType(t, &Ref{}, res) + + var ok bool + ref, ok := res.(*Ref) + require.True(t, ok) + assert.EqualValues(t, MustCreateRef("Dog"), *ref) var def string res, err = response.JSONLookup("description") - if !assert.NoError(t, err) || !assert.NotNil(t, res) || !assert.IsType(t, def, res) { - t.FailNow() - return - } - def = res.(string) + require.NoError(t, err) + require.NotNil(t, res) + require.IsType(t, def, res) + + def, ok = res.(string) + require.True(t, ok) assert.Equal(t, "Dog exists", def) var x *interface{} res, err = response.JSONLookup("x-go-name") - if !assert.NoError(t, err) || !assert.NotNil(t, res) || !assert.IsType(t, x, res) { - t.FailNow() - return - } + require.NoError(t, err) + require.NotNil(t, res) + require.IsType(t, x, res) - x = res.(*interface{}) + x, ok = res.(*interface{}) + require.True(t, ok) assert.EqualValues(t, "PutDogExists", *x) res, err = response.JSONLookup("unknown") - if !assert.Error(t, err) || !assert.Nil(t, res) { - t.FailNow() - return - } + require.Error(t, err) + require.Nil(t, res) } func TestResponseBuild(t *testing.T) { @@ -95,7 +94,9 @@ func TestResponseBuild(t *testing.T) { WithSchema(new(Schema).Typed("object", "")). AddHeader("x-header", ResponseHeader().Typed("string", "")). AddExample("application/json", `{"key":"value"}`) - jazon, _ := json.MarshalIndent(resp, "", " ") + jazon, err := json.MarshalIndent(resp, "", " ") + require.NoError(t, err) + assert.JSONEq(t, `{ "description": "some response", "schema": { diff --git a/responses.go b/responses.go index 4efb6f8..16c3076 100644 --- a/responses.go +++ b/responses.go @@ -19,6 +19,7 @@ import ( "fmt" "reflect" "strconv" + "strings" "github.com/go-openapi/swag" ) @@ -62,6 +63,7 @@ func (r *Responses) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &r.ResponsesProps); err != nil { return err } + if err := json.Unmarshal(data, &r.VendorExtensible); err != nil { return err } @@ -107,20 +109,31 @@ func (r ResponsesProps) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals responses from JSON func (r *ResponsesProps) UnmarshalJSON(data []byte) error { - var res map[string]Response + var res map[string]json.RawMessage if err := json.Unmarshal(data, &res); err != nil { - return nil + return err } + if v, ok := res["default"]; ok { - r.Default = &v + var defaultRes Response + if err := json.Unmarshal(v, &defaultRes); err != nil { + return err + } + r.Default = &defaultRes delete(res, "default") } for k, v := range res { - if nk, err := strconv.Atoi(k); err == nil { - if r.StatusCodeResponses == nil { - r.StatusCodeResponses = map[int]Response{} + if !strings.HasPrefix(k, "x-") { + var statusCodeResp Response + if err := json.Unmarshal(v, &statusCodeResp); err != nil { + return err + } + if nk, err := strconv.Atoi(k); err == nil { + if r.StatusCodeResponses == nil { + r.StatusCodeResponses = map[int]Response{} + } + r.StatusCodeResponses[nk] = statusCodeResp } - r.StatusCodeResponses[nk] = v } } return nil diff --git a/responses_test.go b/responses_test.go new file mode 100644 index 0000000..fc626bf --- /dev/null +++ b/responses_test.go @@ -0,0 +1,128 @@ +// Copyright 2017 go-swagger maintainers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var responses = Responses{ + VendorExtensible: VendorExtensible{ + Extensions: map[string]interface{}{ + "x-go-name": "PutDogExists", + }, + }, + ResponsesProps: ResponsesProps{ + StatusCodeResponses: map[int]Response{ + 200: { + Refable: Refable{Ref: MustCreateRef("Dog")}, + VendorExtensible: VendorExtensible{ + Extensions: map[string]interface{}{ + "x-go-name": "PutDogExists", + }, + }, + ResponseProps: ResponseProps{ + Description: "Dog exists", + Schema: &Schema{SchemaProps: SchemaProps{Type: []string{"string"}}}, + }, + }, + }, + }, +} + +const responsesJSON = `{ + "x-go-name": "PutDogExists", + "200": { + "$ref": "Dog", + "x-go-name": "PutDogExists", + "description": "Dog exists", + "schema": { + "type": "string" + } + } +}` + +func TestIntegrationResponses(t *testing.T) { + var actual Responses + require.NoError(t, json.Unmarshal([]byte(responsesJSON), &actual)) + assert.EqualValues(t, actual, responses) + + assertParsesJSON(t, responsesJSON, responses) +} + +func TestJSONLookupResponses(t *testing.T) { + resp200, ok := responses.StatusCodeResponses[200] + require.True(t, ok) + + res, err := resp200.JSONLookup("$ref") + require.NoError(t, err) + require.NotNil(t, res) + require.IsType(t, &Ref{}, res) + + ref, ok := res.(*Ref) + require.True(t, ok) + assert.EqualValues(t, MustCreateRef("Dog"), *ref) + + var def string + res, err = resp200.JSONLookup("description") + require.NoError(t, err) + require.NotNil(t, res) + require.IsType(t, def, res) + + def, ok = res.(string) + require.True(t, ok) + assert.Equal(t, "Dog exists", def) + + var x *interface{} + res, err = responses.JSONLookup("x-go-name") + require.NoError(t, err) + require.NotNil(t, res) + require.IsType(t, x, res) + + x, ok = res.(*interface{}) + require.True(t, ok) + assert.EqualValues(t, "PutDogExists", *x) + + res, err = responses.JSONLookup("unknown") + require.Error(t, err) + require.Nil(t, res) +} + +func TestResponsesBuild(t *testing.T) { + resp := NewResponse(). + WithDescription("some response"). + WithSchema(new(Schema).Typed("object", "")). + AddHeader("x-header", ResponseHeader().Typed("string", "")). + AddExample("application/json", `{"key":"value"}`) + jazon, _ := json.MarshalIndent(resp, "", " ") + assert.JSONEq(t, `{ + "description": "some response", + "schema": { + "type": "object" + }, + "headers": { + "x-header": { + "type": "string" + } + }, + "examples": { + "application/json": "{\"key\":\"value\"}" + } + }`, string(jazon)) +} diff --git a/schema_loader.go b/schema_loader.go index b81175a..0059b99 100644 --- a/schema_loader.go +++ b/schema_loader.go @@ -168,14 +168,7 @@ func (r *schemaLoader) load(refURL *url.URL) (interface{}, url.URL, bool, error) normalized := normalizeBase(pth) debugLog("loading doc from: %s", normalized) - unescaped, err := url.PathUnescape(normalized) - if err != nil { - return nil, url.URL{}, false, err - } - - u := url.URL{Path: unescaped} - - data, fromCache := r.cache.Get(u.RequestURI()) + data, fromCache := r.cache.Get(normalized) if fromCache { return data, toFetch, fromCache, nil } diff --git a/schema_loader_test.go b/schema_loader_test.go new file mode 100644 index 0000000..67a1e79 --- /dev/null +++ b/schema_loader_test.go @@ -0,0 +1,33 @@ +package spec + +import ( + "encoding/json" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLoader_Issue145(t *testing.T) { + t.Run("with ExpandSpec", func(t *testing.T) { + basePath := filepath.Join("fixtures", "bugs", "145", "Program Files (x86)", "AppName", "todos.json") + todosDoc, err := jsonDoc(basePath) + require.NoError(t, err) + + spec := new(Swagger) + require.NoError(t, json.Unmarshal(todosDoc, spec)) + + require.NoError(t, ExpandSpec(spec, &ExpandOptions{RelativeBase: basePath})) + }) + + t.Run("with ExpandSchema", func(t *testing.T) { + basePath := filepath.Join("fixtures", "bugs", "145", "Program Files (x86)", "AppName", "ref.json") + schemaDoc, err := jsonDoc(basePath) + require.NoError(t, err) + + sch := new(Schema) + require.NoError(t, json.Unmarshal(schemaDoc, sch)) + + require.NoError(t, ExpandSchema(sch, nil, nil)) + }) +} diff --git a/schema_test.go b/schema_test.go index 110764e..038284c 100644 --- a/schema_test.go +++ b/schema_test.go @@ -20,6 +20,7 @@ import ( "github.com/go-openapi/swag" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var schema = Schema{ @@ -153,56 +154,56 @@ func TestSchema(t *testing.T) { expected := map[string]interface{}{} _ = json.Unmarshal([]byte(schemaJSON), &expected) b, err := json.Marshal(schema) - if assert.NoError(t, err) { - var actual map[string]interface{} - _ = json.Unmarshal(b, &actual) - assert.Equal(t, expected, actual) - } + require.NoError(t, err) + + var actual map[string]interface{} + require.NoError(t, json.Unmarshal(b, &actual)) + assert.Equal(t, expected, actual) actual2 := Schema{} - if assert.NoError(t, json.Unmarshal([]byte(schemaJSON), &actual2)) { - assert.Equal(t, schema.Ref, actual2.Ref) - assert.Equal(t, schema.Description, actual2.Description) - assert.Equal(t, schema.Maximum, actual2.Maximum) - assert.Equal(t, schema.Minimum, actual2.Minimum) - assert.Equal(t, schema.ExclusiveMinimum, actual2.ExclusiveMinimum) - assert.Equal(t, schema.ExclusiveMaximum, actual2.ExclusiveMaximum) - assert.Equal(t, schema.MaxLength, actual2.MaxLength) - assert.Equal(t, schema.MinLength, actual2.MinLength) - assert.Equal(t, schema.Pattern, actual2.Pattern) - assert.Equal(t, schema.MaxItems, actual2.MaxItems) - assert.Equal(t, schema.MinItems, actual2.MinItems) - assert.True(t, actual2.UniqueItems) - assert.Equal(t, schema.MultipleOf, actual2.MultipleOf) - assert.Equal(t, schema.Enum, actual2.Enum) - assert.Equal(t, schema.Type, actual2.Type) - assert.Equal(t, schema.Format, actual2.Format) - assert.Equal(t, schema.Title, actual2.Title) - assert.Equal(t, schema.MaxProperties, actual2.MaxProperties) - assert.Equal(t, schema.MinProperties, actual2.MinProperties) - assert.Equal(t, schema.Required, actual2.Required) - assert.Equal(t, schema.Items, actual2.Items) - assert.Equal(t, schema.AllOf, actual2.AllOf) - assert.Equal(t, schema.Properties, actual2.Properties) - assert.Equal(t, schema.Discriminator, actual2.Discriminator) - assert.Equal(t, schema.ReadOnly, actual2.ReadOnly) - assert.Equal(t, schema.XML, actual2.XML) - assert.Equal(t, schema.ExternalDocs, actual2.ExternalDocs) - assert.Equal(t, schema.AdditionalProperties, actual2.AdditionalProperties) - assert.Equal(t, schema.Extensions, actual2.Extensions) - examples := actual2.Example.([]interface{}) - expEx := schema.Example.([]interface{}) - ex1 := examples[0].(map[string]interface{}) - ex2 := examples[1].(map[string]interface{}) - exp1 := expEx[0].(map[string]interface{}) - exp2 := expEx[1].(map[string]interface{}) - - assert.EqualValues(t, exp1["id"], ex1["id"]) - assert.Equal(t, exp1["name"], ex1["name"]) - assert.EqualValues(t, exp2["id"], ex2["id"]) - assert.Equal(t, exp2["name"], ex2["name"]) - } + require.NoError(t, json.Unmarshal([]byte(schemaJSON), &actual2)) + + assert.Equal(t, schema.Ref, actual2.Ref) + assert.Equal(t, schema.Description, actual2.Description) + assert.Equal(t, schema.Maximum, actual2.Maximum) + assert.Equal(t, schema.Minimum, actual2.Minimum) + assert.Equal(t, schema.ExclusiveMinimum, actual2.ExclusiveMinimum) + assert.Equal(t, schema.ExclusiveMaximum, actual2.ExclusiveMaximum) + assert.Equal(t, schema.MaxLength, actual2.MaxLength) + assert.Equal(t, schema.MinLength, actual2.MinLength) + assert.Equal(t, schema.Pattern, actual2.Pattern) + assert.Equal(t, schema.MaxItems, actual2.MaxItems) + assert.Equal(t, schema.MinItems, actual2.MinItems) + assert.True(t, actual2.UniqueItems) + assert.Equal(t, schema.MultipleOf, actual2.MultipleOf) + assert.Equal(t, schema.Enum, actual2.Enum) + assert.Equal(t, schema.Type, actual2.Type) + assert.Equal(t, schema.Format, actual2.Format) + assert.Equal(t, schema.Title, actual2.Title) + assert.Equal(t, schema.MaxProperties, actual2.MaxProperties) + assert.Equal(t, schema.MinProperties, actual2.MinProperties) + assert.Equal(t, schema.Required, actual2.Required) + assert.Equal(t, schema.Items, actual2.Items) + assert.Equal(t, schema.AllOf, actual2.AllOf) + assert.Equal(t, schema.Properties, actual2.Properties) + assert.Equal(t, schema.Discriminator, actual2.Discriminator) + assert.Equal(t, schema.ReadOnly, actual2.ReadOnly) + assert.Equal(t, schema.XML, actual2.XML) + assert.Equal(t, schema.ExternalDocs, actual2.ExternalDocs) + assert.Equal(t, schema.AdditionalProperties, actual2.AdditionalProperties) + assert.Equal(t, schema.Extensions, actual2.Extensions) + + examples := actual2.Example.([]interface{}) + expEx := schema.Example.([]interface{}) + ex1 := examples[0].(map[string]interface{}) + ex2 := examples[1].(map[string]interface{}) + exp1 := expEx[0].(map[string]interface{}) + exp2 := expEx[1].(map[string]interface{}) + assert.EqualValues(t, exp1["id"], ex1["id"]) + assert.Equal(t, exp1["name"], ex1["name"]) + assert.EqualValues(t, exp2["id"], ex2["id"]) + assert.Equal(t, exp2["name"], ex2["name"]) } func BenchmarkSchemaUnmarshal(b *testing.B) { diff --git a/spec.go b/spec.go index 7d38b6e..876aa12 100644 --- a/spec.go +++ b/spec.go @@ -26,7 +26,7 @@ import ( const ( // SwaggerSchemaURL the url for the swagger 2.0 schema to validate specs SwaggerSchemaURL = "http://swagger.io/v2/schema.json#" - // JSONSchemaURL the url for the json schema schema + // JSONSchemaURL the url for the json schema JSONSchemaURL = "http://json-schema.org/draft-04/schema#" ) @@ -41,7 +41,7 @@ func MustLoadJSONSchemaDraft04() *Schema { // JSONSchemaDraft04 loads the json schema document for json shema draft04 func JSONSchemaDraft04() (*Schema, error) { - b, err := Asset("jsonschema-draft-04.json") + b, err := jsonschemaDraft04JSONBytes() if err != nil { return nil, err } @@ -65,7 +65,7 @@ func MustLoadSwagger20Schema() *Schema { // Swagger20Schema loads the swagger 2.0 schema from the embedded assets func Swagger20Schema() (*Schema, error) { - b, err := Asset("v2/schema.json") + b, err := v2SchemaJSONBytes() if err != nil { return nil, err } diff --git a/spec_test.go b/spec_test.go index 8274c95..ae7d492 100644 --- a/spec_test.go +++ b/spec_test.go @@ -25,6 +25,38 @@ import ( ) // Test unitary fixture for dev and bug fixing + +func TestSpec_Issue2743(t *testing.T) { + t.Run("should expand but produce unresolvable $ref", func(t *testing.T) { + path := filepath.Join("fixtures", "bugs", "2743", "working", "spec.yaml") + sp := loadOrFail(t, path) + require.NoError(t, + spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: true, PathLoader: testLoader}), + ) + + t.Run("all $ref do not resolve when expanding again", func(t *testing.T) { + err := spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader}) + require.Error(t, err) + require.ErrorContains(t, err, filepath.FromSlash("swagger/paths/swagger/user/index.yml")) + }) + }) + + t.Run("should expand and produce resolvable $ref", func(t *testing.T) { + path := filepath.Join("fixtures", "bugs", "2743", "not-working", "spec.yaml") + sp := loadOrFail(t, path) + require.NoError(t, + spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: true, PathLoader: testLoader}), + ) + + t.Run("all $ref properly reolve when expanding again", func(t *testing.T) { + require.NoError(t, + spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false, PathLoader: testLoader}), + ) + require.NotContainsf(t, asJSON(t, sp), "$ref", "all $ref's should have been expanded properly") + }) + }) +} + func TestSpec_Issue1429(t *testing.T) { path := filepath.Join("fixtures", "bugs", "1429", "swagger.yaml") diff --git a/structs_test.go b/structs_test.go index d4b87f4..d464b93 100644 --- a/structs_test.go +++ b/structs_test.go @@ -20,7 +20,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) func assertSerializeJSON(t testing.TB, actual interface{}, expected string) bool { @@ -28,7 +28,7 @@ func assertSerializeJSON(t testing.TB, actual interface{}, expected string) bool if err != nil { return assert.Fail(t, "unable to marshal to json (%s): %#v", err, actual) } - return assert.Equal(t, string(ser), expected) + return assert.Equal(t, expected, string(ser)) } func assertSerializeYAML(t testing.TB, actual interface{}, expected string) bool { @@ -36,7 +36,7 @@ func assertSerializeYAML(t testing.TB, actual interface{}, expected string) bool if err != nil { return assert.Fail(t, "unable to marshal to yaml (%s): %#v", err, actual) } - return assert.Equal(t, string(ser), expected) + return assert.Equal(t, expected, string(ser)) } func derefTypeOf(expected interface{}) (tpe reflect.Type) { @@ -65,7 +65,7 @@ func assertParsesJSON(t testing.TB, actual string, expected interface{}) bool { if !isPointed(expected) { act = reflect.Indirect(parsed).Interface() } - return assert.Equal(t, act, expected) + return assert.Equal(t, expected, act) } func assertParsesYAML(t testing.TB, actual string, expected interface{}) bool { @@ -78,7 +78,7 @@ func assertParsesYAML(t testing.TB, actual string, expected interface{}) bool { if !isPointed(expected) { act = reflect.Indirect(parsed).Interface() } - return assert.EqualValues(t, act, expected) + return assert.EqualValues(t, expected, act) } func TestSerialization_SerializeJSON(t *testing.T) { diff --git a/swagger.go b/swagger.go index 44722ff..1590fd1 100644 --- a/swagger.go +++ b/swagger.go @@ -253,7 +253,7 @@ func (s SchemaOrBool) MarshalJSON() ([]byte, error) { // UnmarshalJSON converts this bool or schema object from a JSON structure func (s *SchemaOrBool) UnmarshalJSON(data []byte) error { var nw SchemaOrBool - if len(data) >= 4 { + if len(data) > 0 { if data[0] == '{' { var sch Schema if err := json.Unmarshal(data, &sch); err != nil { @@ -261,7 +261,7 @@ func (s *SchemaOrBool) UnmarshalJSON(data []byte) error { } nw.Schema = &sch } - nw.Allows = !(data[0] == 'f' && data[1] == 'a' && data[2] == 'l' && data[3] == 's' && data[4] == 'e') + nw.Allows = !bytes.Equal(data, []byte("false")) } *s = nw return nil diff --git a/swagger_test.go b/swagger_test.go index 23aa509..59e2403 100644 --- a/swagger_test.go +++ b/swagger_test.go @@ -107,7 +107,6 @@ const specJSON = `{ "x-schemes": ["unix","amqp"] }` -// // func verifySpecSerialize(specJSON []byte, spec Swagger) { // expected := map[string]interface{}{} // json.Unmarshal(specJSON, &expected) @@ -120,96 +119,96 @@ const specJSON = `{ // } /* -// assertEquivalent is currently unused -func assertEquivalent(t testing.TB, actual, expected interface{}) bool { - if actual == nil || expected == nil || reflect.DeepEqual(actual, expected) { - return true - } - - actualType := reflect.TypeOf(actual) - expectedType := reflect.TypeOf(expected) - if reflect.TypeOf(actual).ConvertibleTo(expectedType) { - expectedValue := reflect.ValueOf(expected) - if swag.IsZero(expectedValue) && swag.IsZero(reflect.ValueOf(actual)) { + // assertEquivalent is currently unused + func assertEquivalent(t testing.TB, actual, expected interface{}) bool { + if actual == nil || expected == nil || reflect.DeepEqual(actual, expected) { return true } - // Attempt comparison after type conversion - if reflect.DeepEqual(actual, expectedValue.Convert(actualType).Interface()) { + actualType := reflect.TypeOf(actual) + expectedType := reflect.TypeOf(expected) + if reflect.TypeOf(actual).ConvertibleTo(expectedType) { + expectedValue := reflect.ValueOf(expected) + if swag.IsZero(expectedValue) && swag.IsZero(reflect.ValueOf(actual)) { + return true + } + + // Attempt comparison after type conversion + if reflect.DeepEqual(actual, expectedValue.Convert(actualType).Interface()) { + return true + } + } + + // Last ditch effort + if fmt.Sprintf("%#v", expected) == fmt.Sprintf("%#v", actual) { return true } + errFmt := "Expected: '%[1]T(%[1]#v)'\nActual: '%[2]T(%[2]#v)'\n(Should be equivalent)!" + return assert.Fail(t, errFmt, expected, actual) } - // Last ditch effort - if fmt.Sprintf("%#v", expected) == fmt.Sprintf("%#v", actual) { - return true - } - errFmt := "Expected: '%T(%#v)'\nActual: '%T(%#v)'\n(Should be equivalent)!" - return assert.Fail(t, errFmt, expected, expected, actual, actual) -} + // ShouldBeEquivalentTo is currently unused + func ShouldBeEquivalentTo(actual interface{}, expecteds ...interface{}) string { + expected := expecteds[0] + if actual == nil || expected == nil { + return "" + } -// ShouldBeEquivalentTo is currently unused -func ShouldBeEquivalentTo(actual interface{}, expecteds ...interface{}) string { - expected := expecteds[0] - if actual == nil || expected == nil { - return "" - } + if reflect.DeepEqual(expected, actual) { + return "" + } - if reflect.DeepEqual(expected, actual) { - return "" - } + actualType := reflect.TypeOf(actual) + expectedType := reflect.TypeOf(expected) + if reflect.TypeOf(actual).ConvertibleTo(expectedType) { + expectedValue := reflect.ValueOf(expected) + if swag.IsZero(expectedValue) && swag.IsZero(reflect.ValueOf(actual)) { + return "" + } - actualType := reflect.TypeOf(actual) - expectedType := reflect.TypeOf(expected) - if reflect.TypeOf(actual).ConvertibleTo(expectedType) { - expectedValue := reflect.ValueOf(expected) - if swag.IsZero(expectedValue) && swag.IsZero(reflect.ValueOf(actual)) { - return "" + // Attempt comparison after type conversion + if reflect.DeepEqual(actual, expectedValue.Convert(actualType).Interface()) { + return "" + } } - // Attempt comparison after type conversion - if reflect.DeepEqual(actual, expectedValue.Convert(actualType).Interface()) { + // Last ditch effort + if fmt.Sprintf("%#v", expected) == fmt.Sprintf("%#v", actual) { return "" } + errFmt := "Expected: '%[1]T(%[1]#v)'\nActual: '%[2]T(%[2]#v)'\n(Should be equivalent)!" + return fmt.Sprintf(errFmt, expected, actual) } - // Last ditch effort - if fmt.Sprintf("%#v", expected) == fmt.Sprintf("%#v", actual) { - return "" - } - errFmt := "Expected: '%T(%#v)'\nActual: '%T(%#v)'\n(Should be equivalent)!" - return fmt.Sprintf(errFmt, expected, expected, actual, actual) - -} - -// assertSpecMaps is currently unused -func assertSpecMaps(t testing.TB, actual, expected map[string]interface{}) bool { - res := true - if id, ok := expected["id"]; ok { - res = assert.Equal(t, id, actual["id"]) + // assertSpecMaps is currently unused + func assertSpecMaps(t testing.TB, actual, expected map[string]interface{}) bool { + res := true + if id, ok := expected["id"]; ok { + res = assert.Equal(t, id, actual["id"]) + } + res = res && assert.Equal(t, expected["consumes"], actual["consumes"]) + res = res && assert.Equal(t, expected["produces"], actual["produces"]) + res = res && assert.Equal(t, expected["schemes"], actual["schemes"]) + res = res && assert.Equal(t, expected["swagger"], actual["swagger"]) + res = res && assert.Equal(t, expected["info"], actual["info"]) + res = res && assert.Equal(t, expected["host"], actual["host"]) + res = res && assert.Equal(t, expected["basePath"], actual["basePath"]) + res = res && assert.Equal(t, expected["paths"], actual["paths"]) + res = res && assert.Equal(t, expected["definitions"], actual["definitions"]) + res = res && assert.Equal(t, expected["responses"], actual["responses"]) + res = res && assert.Equal(t, expected["securityDefinitions"], actual["securityDefinitions"]) + res = res && assert.Equal(t, expected["tags"], actual["tags"]) + res = res && assert.Equal(t, expected["externalDocs"], actual["externalDocs"]) + res = res && assert.Equal(t, expected["x-some-extension"], actual["x-some-extension"]) + res = res && assert.Equal(t, expected["x-schemes"], actual["x-schemes"]) + + return res } - res = res && assert.Equal(t, expected["consumes"], actual["consumes"]) - res = res && assert.Equal(t, expected["produces"], actual["produces"]) - res = res && assert.Equal(t, expected["schemes"], actual["schemes"]) - res = res && assert.Equal(t, expected["swagger"], actual["swagger"]) - res = res && assert.Equal(t, expected["info"], actual["info"]) - res = res && assert.Equal(t, expected["host"], actual["host"]) - res = res && assert.Equal(t, expected["basePath"], actual["basePath"]) - res = res && assert.Equal(t, expected["paths"], actual["paths"]) - res = res && assert.Equal(t, expected["definitions"], actual["definitions"]) - res = res && assert.Equal(t, expected["responses"], actual["responses"]) - res = res && assert.Equal(t, expected["securityDefinitions"], actual["securityDefinitions"]) - res = res && assert.Equal(t, expected["tags"], actual["tags"]) - res = res && assert.Equal(t, expected["externalDocs"], actual["externalDocs"]) - res = res && assert.Equal(t, expected["x-some-extension"], actual["x-some-extension"]) - res = res && assert.Equal(t, expected["x-schemes"], actual["x-schemes"]) - - return res -} */ + func assertSpecs(t testing.TB, actual, expected Swagger) bool { expected.Swagger = "2.0" - return assert.Equal(t, actual, expected) + return assert.Equal(t, expected, actual) } /* @@ -233,7 +232,7 @@ func assertSpecJSON(t testing.TB, specJSON []byte) bool { if !assert.NoError(t, json.Unmarshal(cb, &actual)) { return false } - return assertSpecMaps(t, actual, expected) + return assertSpecMaps(t, expected, actual ) } */ @@ -241,48 +240,41 @@ func TestSwaggerSpec_Serialize(t *testing.T) { expected := make(map[string]interface{}) _ = json.Unmarshal([]byte(specJSON), &expected) b, err := json.MarshalIndent(spec, "", " ") - if assert.NoError(t, err) { - var actual map[string]interface{} - err := json.Unmarshal(b, &actual) - if assert.NoError(t, err) { - assert.EqualValues(t, actual, expected) - } - } + require.NoError(t, err) + var actual map[string]interface{} + require.NoError(t, json.Unmarshal(b, &actual)) + assert.EqualValues(t, expected, actual) } func TestSwaggerSpec_Deserialize(t *testing.T) { var actual Swagger - err := json.Unmarshal([]byte(specJSON), &actual) - if assert.NoError(t, err) { - assert.EqualValues(t, actual, spec) - } + require.NoError(t, json.Unmarshal([]byte(specJSON), &actual)) + assert.EqualValues(t, actual, spec) } func TestVendorExtensionStringSlice(t *testing.T) { var actual Swagger - err := json.Unmarshal([]byte(specJSON), &actual) - if assert.NoError(t, err) { - schemes, ok := actual.Extensions.GetStringSlice("x-schemes") - if assert.True(t, ok) { - assert.EqualValues(t, []string{"unix", "amqp"}, schemes) - } - notSlice, ok := actual.Extensions.GetStringSlice("x-some-extension") - assert.Nil(t, notSlice) - assert.False(t, ok) - - actual.AddExtension("x-another-ext", 100) - notString, ok := actual.Extensions.GetStringSlice("x-another-ext") - assert.Nil(t, notString) - assert.False(t, ok) - - actual.AddExtension("x-another-slice-ext", []interface{}{100, 100}) - notStringSlice, ok := actual.Extensions.GetStringSlice("x-another-slice-ext") - assert.Nil(t, notStringSlice) - assert.False(t, ok) - - _, ok = actual.Extensions.GetStringSlice("x-notfound-ext") - assert.False(t, ok) - } + require.NoError(t, json.Unmarshal([]byte(specJSON), &actual)) + schemes, ok := actual.Extensions.GetStringSlice("x-schemes") + require.True(t, ok) + assert.EqualValues(t, []string{"unix", "amqp"}, schemes) + + notSlice, ok := actual.Extensions.GetStringSlice("x-some-extension") + assert.Nil(t, notSlice) + assert.False(t, ok) + + actual.AddExtension("x-another-ext", 100) + notString, ok := actual.Extensions.GetStringSlice("x-another-ext") + assert.Nil(t, notString) + assert.False(t, ok) + + actual.AddExtension("x-another-slice-ext", []interface{}{100, 100}) + notStringSlice, ok := actual.Extensions.GetStringSlice("x-another-slice-ext") + assert.Nil(t, notStringSlice) + assert.False(t, ok) + + _, ok = actual.Extensions.GetStringSlice("x-notfound-ext") + assert.False(t, ok) } func TestOptionalSwaggerProps_Serialize(t *testing.T) { @@ -307,26 +299,25 @@ func TestOptionalSwaggerProps_Serialize(t *testing.T) { var minimalSpec Swagger err := json.Unmarshal(minimalJSONSpec, &minimalSpec) - if assert.NoError(t, err) { - bytes, err := json.Marshal(&minimalSpec) - if assert.NoError(t, err) { - var ms map[string]interface{} - if err := json.Unmarshal(bytes, &ms); assert.NoError(t, err) { - assert.NotContains(t, ms, "consumes") - assert.NotContains(t, ms, "produces") - assert.NotContains(t, ms, "schemes") - assert.NotContains(t, ms, "host") - assert.NotContains(t, ms, "basePath") - assert.NotContains(t, ms, "definitions") - assert.NotContains(t, ms, "parameters") - assert.NotContains(t, ms, "responses") - assert.NotContains(t, ms, "securityDefinitions") - assert.NotContains(t, ms, "security") - assert.NotContains(t, ms, "tags") - assert.NotContains(t, ms, "externalDocs") - } - } - } + require.NoError(t, err) + bytes, err := json.Marshal(&minimalSpec) + require.NoError(t, err) + + var ms map[string]interface{} + require.NoError(t, json.Unmarshal(bytes, &ms)) + + assert.NotContains(t, ms, "consumes") + assert.NotContains(t, ms, "produces") + assert.NotContains(t, ms, "schemes") + assert.NotContains(t, ms, "host") + assert.NotContains(t, ms, "basePath") + assert.NotContains(t, ms, "definitions") + assert.NotContains(t, ms, "parameters") + assert.NotContains(t, ms, "responses") + assert.NotContains(t, ms, "securityDefinitions") + assert.NotContains(t, ms, "security") + assert.NotContains(t, ms, "tags") + assert.NotContains(t, ms, "externalDocs") } var minimalJSONSpec = []byte(`{ @@ -376,16 +367,15 @@ var minimalJSONSpec = []byte(`{ func TestSecurityRequirements(t *testing.T) { var minimalSpec Swagger - err := json.Unmarshal(minimalJSONSpec, &minimalSpec) - if assert.NoError(t, err) { - sec := minimalSpec.Paths.Paths["/"].Get.Security - require.Len(t, sec, 3) - assert.Contains(t, sec[0], "basic") - assert.Contains(t, sec[0], "apiKey") - assert.NotNil(t, sec[1]) - assert.Empty(t, sec[1]) - assert.Contains(t, sec[2], "queryKey") - } + require.NoError(t, json.Unmarshal(minimalJSONSpec, &minimalSpec)) + + sec := minimalSpec.Paths.Paths["/"].Get.Security + require.Len(t, sec, 3) + assert.Contains(t, sec[0], "basic") + assert.Contains(t, sec[0], "apiKey") + assert.NotNil(t, sec[1]) + assert.Empty(t, sec[1]) + assert.Contains(t, sec[2], "queryKey") } func TestSwaggerGobEncoding(t *testing.T) { @@ -396,10 +386,7 @@ func TestSwaggerGobEncoding(t *testing.T) { func doTestSwaggerGobEncoding(t *testing.T, fixture string) { var src, dst Swagger - - if !assert.NoError(t, json.Unmarshal([]byte(fixture), &src)) { - t.FailNow() - } + require.NoError(t, json.Unmarshal([]byte(fixture), &src)) doTestAnyGobEncoding(t, &src, &dst) } diff --git a/url_go18.go b/url_go18.go deleted file mode 100644 index 60b7851..0000000 --- a/url_go18.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build !go1.19 -// +build !go1.19 - -package spec - -import "net/url" - -var parseURL = url.Parse diff --git a/url_go19.go b/url_go19.go index 392e3e6..5bdfe40 100644 --- a/url_go19.go +++ b/url_go19.go @@ -1,6 +1,3 @@ -//go:build go1.19 -// +build go1.19 - package spec import "net/url" diff --git a/validations_test.go b/validations_test.go index b62f82e..6582921 100644 --- a/validations_test.go +++ b/validations_test.go @@ -75,7 +75,6 @@ func TestValidations(t *testing.T) { expectedSV.PatternProperties = nil expectedSV.MinProperties = nil expectedSV.MaxProperties = nil - val = mkVal() val = mkVal() cv.SetValidations(val) @@ -110,7 +109,7 @@ func TestValidations(t *testing.T) { require.False(t, cv.HasArrayValidations()) sv.SetValidations(val) - sv.ClearObjectValidations(func(validation string, value interface{}) { + sv.ClearObjectValidations(func(validation string, _ interface{}) { switch validation { case "minProperties", "maxProperties", "patternProperties": return diff --git a/xml_object_test.go b/xml_object_test.go index da616a9..8573e82 100644 --- a/xml_object_test.go +++ b/xml_object_test.go @@ -19,14 +19,14 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestXmlObject_Serialize(t *testing.T) { obj1 := XMLObject{} actual, err := json.Marshal(obj1) - if assert.NoError(t, err) { - assert.Equal(t, "{}", string(actual)) - } + require.NoError(t, err) + assert.Equal(t, "{}", string(actual)) obj2 := XMLObject{ Name: "the name", @@ -37,24 +37,22 @@ func TestXmlObject_Serialize(t *testing.T) { } actual, err = json.Marshal(obj2) - if assert.NoError(t, err) { - var ad map[string]interface{} - if assert.NoError(t, json.Unmarshal(actual, &ad)) { - assert.Equal(t, obj2.Name, ad["name"]) - assert.Equal(t, obj2.Namespace, ad["namespace"]) - assert.Equal(t, obj2.Prefix, ad["prefix"]) - assert.True(t, ad["attribute"].(bool)) - assert.True(t, ad["wrapped"].(bool)) - } - } + require.NoError(t, err) + + var ad map[string]interface{} + require.NoError(t, json.Unmarshal(actual, &ad)) + assert.Equal(t, obj2.Name, ad["name"]) + assert.Equal(t, obj2.Namespace, ad["namespace"]) + assert.Equal(t, obj2.Prefix, ad["prefix"]) + assert.True(t, ad["attribute"].(bool)) + assert.True(t, ad["wrapped"].(bool)) } func TestXmlObject_Deserialize(t *testing.T) { expected := XMLObject{} actual := XMLObject{} - if assert.NoError(t, json.Unmarshal([]byte("{}"), &actual)) { - assert.Equal(t, expected, actual) - } + require.NoError(t, json.Unmarshal([]byte("{}"), &actual)) + assert.Equal(t, expected, actual) completed := `{"name":"the name","namespace":"the namespace","prefix":"the prefix","attribute":true,"wrapped":true}` expected = XMLObject{ @@ -64,8 +62,8 @@ func TestXmlObject_Deserialize(t *testing.T) { Attribute: true, Wrapped: true, } + actual = XMLObject{} - if assert.NoError(t, json.Unmarshal([]byte(completed), &actual)) { - assert.Equal(t, expected, actual) - } + require.NoError(t, json.Unmarshal([]byte(completed), &actual)) + assert.Equal(t, expected, actual) }