├── .cligen.yml
├── .editorconfig
├── .github
├── PULL_REQUEST_TEMPLATE.md
├── dependabot.yml
└── workflows
│ ├── cd.yml
│ └── ci.yml
├── .gitignore
├── .golangci.yml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── cmd
└── dummy
│ └── main.go
├── examples
├── README.md
└── docker
│ ├── Dockerfile
│ ├── README.md
│ └── openapi.yml
├── go.mod
├── go.sum
├── internal
├── api
│ ├── api.go
│ ├── api_test.go
│ ├── build.go
│ ├── build_test.go
│ ├── find.go
│ └── find_test.go
├── config
│ ├── config.go
│ ├── config_test.go
│ ├── logger.go
│ └── server.go
├── exitcode
│ └── exitcode.go
├── logger
│ ├── logger.go
│ └── logger_test.go
├── middleware
│ ├── logging.go
│ └── logging_test.go
├── parse
│ ├── parse.go
│ ├── parse_test.go
│ └── testdata
│ │ ├── api.raml
│ │ ├── empty-openapi.yml
│ │ ├── openapi
│ │ ├── openapi.yml
│ │ ├── openapi3.yml
│ │ └── schema.graphql
├── pkg
│ ├── logger
│ │ └── logger.go
│ └── pathfmt
│ │ ├── pathfmt.go
│ │ └── pathfmt_test.go
├── read
│ ├── read.go
│ ├── read_test.go
│ └── testdata
│ │ └── openapi3.yml
└── server
│ ├── handler.go
│ ├── handler_test.go
│ ├── server.go
│ └── server_test.go
└── test
├── dummy_test.go
└── testdata
├── cases.yml
└── openapi.yml
/.cligen.yml:
--------------------------------------------------------------------------------
1 | name: "Dummy"
2 | commands:
3 | - name: "server"
4 | alias: "s"
5 | description: "run mock server"
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | charset = utf-8
7 |
8 | [*.go]
9 | indent_style = tab
10 | indent_size = 4
11 |
12 | [Makefile]
13 | indent_style = tab
14 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### :heavy_check_mark: Checklist:
2 | - [ ] Have only one commit (if not, squash them into one commit)
3 | - [ ] Tests for the changes have been added (for features / bug fixes / refactoring)
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | updates:
4 | - package-ecosystem: gomod
5 | directory: /
6 | schedule:
7 | interval: daily
8 | reviewers:
9 | - "neotoolkit/team"
10 | - package-ecosystem: github-actions
11 | directory: "/"
12 | schedule:
13 | interval: daily
14 | reviewers:
15 | - "neotoolkit/team"
16 |
--------------------------------------------------------------------------------
/.github/workflows/cd.yml:
--------------------------------------------------------------------------------
1 | name: CD
2 | on:
3 | release:
4 | types: [published]
5 |
6 | env:
7 | REGISTRY: ghcr.io
8 | IMAGE_NAME: ${{ github.event.repository.name }}
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | permissions:
14 | packages: write
15 | contents: read
16 |
17 | steps:
18 | - name: Checkout code
19 | uses: actions/checkout@v3
20 |
21 | - name: Set up Docker Buildx
22 | uses: docker/setup-buildx-action@v2
23 |
24 | - name: Log in to registry
25 | uses: docker/login-action@v2
26 | with:
27 | registry: ${{ env.REGISTRY }}
28 | username: ${{ github.actor }}
29 | password: ${{ secrets.GITHUB_TOKEN }}
30 |
31 | - name: Set version
32 | id: set-version
33 | run: |
34 | VERSION=${GITHUB_REF/refs\/tags\//}
35 |
36 | echo ::set-output name=VERSION::${VERSION}
37 |
38 | - name: Build and push
39 | id: docker_build
40 | uses: docker/build-push-action@v3
41 | with:
42 | push: true
43 | tags: ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ steps.set-version.outputs.VERSION }}
44 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 |
6 | jobs:
7 | build:
8 | uses: neotoolkit/.github/.github/workflows/build.yml@main
9 | dependency:
10 | uses: neotoolkit/.github/.github/workflows/dependency.yml@main
11 | lint:
12 | uses: neotoolkit/.github/.github/workflows/lint.yml@main
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories
15 | vendor/
16 |
17 | # IDEA settings
18 | .idea
19 |
20 | # Pact logs
21 | test/log/pact.log
22 |
23 | # macOS
24 | .DS_Store
25 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | linters:
2 | disable-all: true
3 | enable:
4 | - bodyclose
5 | - deadcode
6 | - dupl
7 | - errcheck
8 | - gochecknoglobals
9 | - gocritic
10 | - gocyclo
11 | - gosimple
12 | - govet
13 | - ineffassign
14 | - makezero
15 | - misspell
16 | - nilerr
17 | - prealloc
18 | - revive
19 | - staticcheck
20 | - structcheck
21 | - stylecheck
22 | - testpackage
23 | - typecheck
24 | - unparam
25 | - unused
26 | - varcheck
27 | - wsl
28 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:latest
2 |
3 | RUN go install github.com/neotoolkit/dummy/cmd/dummy@latest
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 neotoolkit
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all tidy fmt imports lint test
2 |
3 | .PHONY: all
4 | all: tidy fmt imports lint test
5 |
6 | .PHONY: tidy
7 | tidy:
8 | go mod tidy
9 |
10 | .PHONY: fmt
11 | fmt:
12 | go fmt ./...
13 |
14 | .PHONY: imports
15 | imports:
16 | goimports -local github.com/neotoolkit/dummy/ -w -l ./
17 |
18 | .PHONY: lint
19 | lint:
20 | golangci-lint run
21 |
22 | .PHONY: test
23 | test:
24 | go test ./...
25 | cover:
26 | go test -coverprofile=coverage.out && go tool cover -html=coverage.out
27 |
28 | .PHONY: local
29 | local:
30 | go run ./cmd/dummy -port=8080 server examples/docker/openapi3.yml
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # dummy
2 |
3 | [![CI-img]][CI-url]
4 | [![pkg-img]][pkg-url]
5 | [![reportcard-img]][reportcard-url]
6 | [![coverage-img]][coverage-url]
7 | [![version-img]][version-url]
8 |
9 |
10 |
11 | Run mock server based off an API contract with one command
12 |
13 | [Dummy](https://evrone.com/dummy?utm_source=github&utm_campaign=dummy) is created & supported by [Evrone](https://evrone.com?utm_source=github&utm_campaign=dummy)
14 |
15 | ## Features
16 | - Supports `OpenAPI 3.x`
17 |
18 | ## Installation
19 | ```shell
20 | go install github.com/neotoolkit/dummy/cmd/dummy@latest
21 | ```
22 |
23 | ## Usage
24 | Dummy can help you run mock server based off an API contract, which helps people see how your API will work before you even have it built. Run it locally with the `dummy s` command to run your API on a HTTP server you can interact with.
25 | ```shell
26 | dummy s openapi.yml
27 | ```
28 | ```shell
29 | dummy s https://raw.githubusercontent.com/neotoolkit/dummy/main/examples/docker/openapi.yml
30 | ```
31 | More usage [examples](examples)
32 |
33 | ## Documentation
34 | See [these docs][pkg-url].
35 |
36 | ## License
37 | [MIT License](LICENSE).
38 |
39 | ## Sponsors
40 |
41 |
42 |
44 |
45 |
46 |
47 | [CI-img]: https://github.com/neotoolkit/dummy/workflows/CI/badge.svg
48 | [CI-url]: https://github.com/neotoolkit/dummy/actions
49 | [pkg-img]: https://pkg.go.dev/badge/neotoolkit/dummy
50 | [pkg-url]: https://pkg.go.dev/github.com/neotoolkit/dummy
51 | [reportcard-img]: https://goreportcard.com/badge/neotoolkit/dummy
52 | [reportcard-url]: https://goreportcard.com/report/neotoolkit/dummy
53 | [coverage-img]: https://codecov.io/gh/neotoolkit/dummy/branch/main/graph/badge.svg
54 | [coverage-url]: https://codecov.io/gh/neotoolkit/dummy
55 | [version-img]: https://img.shields.io/github/v/release/neotoolkit/dummy
56 | [version-url]: https://github.com/neotoolkit/dummy/releases
57 |
--------------------------------------------------------------------------------
/cmd/dummy/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "flag"
7 | "fmt"
8 | "net/http"
9 | "os"
10 | "os/signal"
11 | "syscall"
12 | "time"
13 |
14 | "github.com/cristalhq/acmd"
15 |
16 | "github.com/neotoolkit/dummy/internal/config"
17 | "github.com/neotoolkit/dummy/internal/logger"
18 | "github.com/neotoolkit/dummy/internal/parse"
19 | "github.com/neotoolkit/dummy/internal/server"
20 | )
21 |
22 | const version = "0.2.1"
23 |
24 | func main() {
25 | err := run()
26 | if err != nil {
27 | fmt.Fprintf(os.Stderr, "server run error: %v\n", err)
28 | }
29 | }
30 |
31 | func run() error {
32 | cmds := []acmd.Command{
33 | {
34 | Name: "server",
35 | Alias: "s",
36 | Description: "run mock server",
37 | Do: func(ctx context.Context, args []string) error {
38 | cfg := config.NewConfig()
39 |
40 | cfg.Server.Path = args[0]
41 |
42 | fs := flag.NewFlagSet("dummy", flag.ContinueOnError)
43 | fs.StringVar(&cfg.Server.Port, "port", "8080", "")
44 | fs.StringVar(&cfg.Logger.Level, "logger-level", "info", "")
45 | if err := fs.Parse(args[1:]); err != nil {
46 | return err
47 | }
48 |
49 | api, err := parse.Parse(cfg.Server.Path)
50 | if err != nil {
51 | return fmt.Errorf("specification parse error: %w", err)
52 | }
53 |
54 | l := logger.NewLogger(cfg.Logger.Level)
55 | h := server.NewHandlers(api, l)
56 | s := server.NewServer(cfg.Server, l, h)
57 |
58 | go func() {
59 | if err := s.Run(); !errors.Is(err, http.ErrServerClosed) {
60 | l.Errorf("run server error: %v", err)
61 | }
62 | }()
63 |
64 | interrupt := make(chan os.Signal, 1)
65 | signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
66 |
67 | x := <-interrupt
68 | l.Infof("received `%v`", x)
69 |
70 | const timeout = 5 * time.Second
71 |
72 | ctx, cancelFunc := context.WithTimeout(ctx, timeout)
73 | defer cancelFunc()
74 |
75 | return s.Stop(ctx)
76 | },
77 | },
78 | }
79 |
80 | r := acmd.RunnerOf(cmds, acmd.Config{
81 | AppName: "Dummy",
82 | Version: version,
83 | })
84 |
85 | return r.Run()
86 | }
87 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 | ```shell
3 | dummy s openapi.yml
4 | ```
5 | ```shell
6 | dummy server openapi.yml
7 | ```
8 | ```shell
9 | dummy s openapi.json
10 | ```
11 | ```shell
12 | dummy s https://raw.githubusercontent.com/neotoolkit/dummy/main/examples/docker/openapi.yml
13 | ```
14 |
--------------------------------------------------------------------------------
/examples/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:latest
2 |
3 | RUN go install github.com/neotoolkit/dummy/cmd/dummy@latest
4 |
5 | COPY ./openapi3.yml .
6 |
7 | WORKDIR .
8 |
9 | CMD ["dummy", "s", "openapi.yml", "-port=8080"]
10 |
--------------------------------------------------------------------------------
/examples/docker/README.md:
--------------------------------------------------------------------------------
1 | :one:
2 | ```bash
3 | docker build -t dummy .
4 | ```
5 | :two:
6 | ```bash
7 | docker run -p 8080:8080 dummy
8 | ```
9 | :white_check_mark:
--------------------------------------------------------------------------------
/examples/docker/openapi.yml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 |
3 | info:
4 | title: Users dummy API
5 | version: 0.1.0
6 |
7 | paths:
8 | /users:
9 | post:
10 | requestBody:
11 | required: true
12 | content:
13 | application/json:
14 | schema:
15 | $ref: "#/components/schemas/User"
16 |
17 | responses:
18 | '201':
19 | description: ''
20 | content:
21 | application/json:
22 | schema:
23 | $ref: '#/components/schemas/User'
24 |
25 | get:
26 | responses:
27 | '200':
28 | description: ''
29 | content:
30 | application/json:
31 | schema:
32 | type: array
33 | items:
34 | $ref: '#/components/schemas/User'
35 | example:
36 | - id: e1afccea-5168-4735-84d4-cb96f6fb5d25
37 | firstName: Elon
38 | lastName: Musk
39 | - id: 472063cc-4c83-11ec-81d3-0242ac130003
40 | firstName: Sergey
41 | lastName: Brin
42 |
43 | /users/{userId}:
44 | get:
45 | parameters:
46 | - in: path
47 | name: userId
48 | description: ''
49 | required: true
50 | schema:
51 | type: string
52 |
53 | responses:
54 | '200':
55 | description: ''
56 | content:
57 | application/json:
58 | schema:
59 | $ref: '#/components/schemas/User'
60 |
61 | components:
62 | schemas:
63 | User:
64 | type: object
65 | required:
66 | - id
67 | - firstName
68 | - lastName
69 | properties:
70 | id:
71 | type: string
72 | format: uuid
73 | firstName:
74 | type: string
75 | lastName:
76 | type: string
77 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/neotoolkit/dummy
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/cristalhq/acmd v0.8.0
7 | github.com/goccy/go-yaml v1.9.5
8 | github.com/lamoda/gonkey v1.18.2
9 | github.com/neotoolkit/faker v0.9.1
10 | github.com/neotoolkit/openapi v0.10.0
11 | github.com/stretchr/testify v1.8.0
12 | go.uber.org/zap v1.21.0
13 | )
14 |
15 | require (
16 | github.com/Masterminds/goutils v1.1.1 // indirect
17 | github.com/Masterminds/semver/v3 v3.1.1 // indirect
18 | github.com/Masterminds/sprig/v3 v3.2.2 // indirect
19 | github.com/aerospike/aerospike-client-go/v5 v5.8.0 // indirect
20 | github.com/davecgh/go-spew v1.1.1 // indirect
21 | github.com/fatih/color v1.13.0 // indirect
22 | github.com/google/uuid v1.3.0 // indirect
23 | github.com/huandu/xstrings v1.3.2 // indirect
24 | github.com/imdario/mergo v0.3.13 // indirect
25 | github.com/joho/godotenv v1.4.0 // indirect
26 | github.com/kylelemons/godebug v1.1.0 // indirect
27 | github.com/lib/pq v1.10.4 // indirect
28 | github.com/mattn/go-colorable v0.1.12 // indirect
29 | github.com/mattn/go-isatty v0.0.14 // indirect
30 | github.com/mitchellh/copystructure v1.2.0 // indirect
31 | github.com/mitchellh/reflectwalk v1.0.2 // indirect
32 | github.com/pkg/errors v0.9.1 // indirect
33 | github.com/pmezard/go-difflib v1.0.0 // indirect
34 | github.com/shopspring/decimal v1.2.0 // indirect
35 | github.com/spf13/cast v1.3.1 // indirect
36 | github.com/tidwall/gjson v1.13.0 // indirect
37 | github.com/tidwall/match v1.1.1 // indirect
38 | github.com/tidwall/pretty v1.2.0 // indirect
39 | github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 // indirect
40 | go.uber.org/atomic v1.7.0 // indirect
41 | go.uber.org/multierr v1.6.0 // indirect
42 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
43 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
44 | golang.org/x/sys v0.0.0-20220207234003-57398862261d // indirect
45 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
46 | gopkg.in/yaml.v2 v2.4.0 // indirect
47 | gopkg.in/yaml.v3 v3.0.1 // indirect
48 | )
49 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
2 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
3 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
4 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
5 | github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
6 | github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
7 | github.com/aerospike/aerospike-client-go/v5 v5.8.0 h1:EUV2wG80yIenQqOyUlf5NfyhagPIwoeL09MJIE+xILE=
8 | github.com/aerospike/aerospike-client-go/v5 v5.8.0/go.mod h1:rJ/KpmClE7kiBPfvAPrGw9WuNOiz8v2uKbQaUyYPXtI=
9 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
10 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
11 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
12 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
13 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
14 | github.com/cristalhq/acmd v0.8.0 h1:0NFc0ixjHMJ1kJyoBZoklcSgw4PGBpD6XRuMv733xEg=
15 | github.com/cristalhq/acmd v0.8.0/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ=
16 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
17 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
18 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
19 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
20 | github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
21 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
22 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
23 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
24 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
25 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
26 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
27 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
28 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
29 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
30 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
31 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
32 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
33 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
34 | github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0=
35 | github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
36 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
37 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
38 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
39 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
40 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
41 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
42 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
43 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
44 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
45 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
46 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
47 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
48 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
49 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
50 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
51 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
52 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
53 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
54 | github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
55 | github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
56 | github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
57 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
58 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
59 | github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
60 | github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
61 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
62 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
63 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
64 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
65 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
66 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
67 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
68 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
69 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
70 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
71 | github.com/lamoda/gonkey v1.18.2 h1:DXMa/WTx9LjJPP2C3xD6n6inZP1X0fhzbdNqnw0m5v4=
72 | github.com/lamoda/gonkey v1.18.2/go.mod h1:w0jZz5DPVWmYcnFy8Yq5RZatYmhmHNUxsZcJezurw9E=
73 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
74 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
75 | github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
76 | github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
77 | github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
78 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
79 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
80 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
81 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
82 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
83 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
84 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
85 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
86 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
87 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
88 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
89 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
90 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
91 | github.com/neotoolkit/faker v0.9.1 h1:Z5bvSQoqm9usI2Q6X5/7aeUhhI81geTcy2VP5Qdaay8=
92 | github.com/neotoolkit/faker v0.9.1/go.mod h1:xo8gPYed0+xG7RQHV3wwKMyE4i0wkMMjNoyxjihgWno=
93 | github.com/neotoolkit/openapi v0.10.0 h1:kg7hrBQc3CxkwVkrWp0MXzqxWMcCQGuQZDWKBJLeZY4=
94 | github.com/neotoolkit/openapi v0.10.0/go.mod h1:/Yh4psCpGJAbsMCmp2W8dkL+JQjBYChxtAeeCDNoCsc=
95 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
96 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
97 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
98 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
99 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
100 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
101 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
102 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
103 | github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
104 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
105 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
106 | github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
107 | github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
108 | github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
109 | github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
110 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
111 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
112 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
113 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
114 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
115 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
116 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
117 | github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
118 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
119 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
120 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
121 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
122 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
123 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
124 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
125 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
126 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
127 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
128 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
129 | github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M=
130 | github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
131 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
132 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
133 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
134 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
135 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
136 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
137 | github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
138 | github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
139 | github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
140 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
141 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
142 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
143 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
144 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
145 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
146 | go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
147 | go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
148 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
149 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
150 | golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
151 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
152 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
153 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
154 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
155 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
156 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
157 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
158 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
159 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
160 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
161 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
162 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
163 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
164 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
165 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
166 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
167 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
168 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
169 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
170 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
171 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
172 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
173 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
174 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
175 | golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
176 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
177 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
178 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
179 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
180 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
181 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
182 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
183 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
184 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
185 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
186 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
187 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
188 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
189 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
190 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
191 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
192 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
193 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
194 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
195 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
196 | golang.org/x/sys v0.0.0-20220207234003-57398862261d h1:Bm7BNOQt2Qv7ZqysjeLjgCBanX+88Z/OtdvsrEv1Djc=
197 | golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
198 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
199 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
200 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
201 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
202 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
203 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
204 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
205 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
206 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
207 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
208 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
209 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
210 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
211 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
212 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
213 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
214 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
215 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
216 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
217 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
218 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
219 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
220 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
221 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
222 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
223 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
224 | gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY=
225 | gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
226 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
227 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
228 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
229 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
230 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
231 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
232 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
233 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
234 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
235 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
236 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
237 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
238 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
239 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
240 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
241 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
242 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
243 |
--------------------------------------------------------------------------------
/internal/api/api.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | // API -.
4 | type API struct {
5 | Operations []Operation
6 | }
7 |
8 | // Operation -.
9 | type Operation struct {
10 | Method string
11 | Path string
12 | Body map[string]FieldType
13 | Responses []Response
14 | }
15 |
16 | // FieldType -.
17 | type FieldType struct {
18 | Required bool
19 | Type string
20 | }
21 |
22 | // Response -.
23 | type Response struct {
24 | StatusCode int
25 | MediaType string
26 | Schema Schema
27 | Example any
28 | Examples map[string]any
29 | }
30 |
31 | // ExampleValue -.
32 | func (r Response) ExampleValue(key string) any {
33 | if nil == r.Schema {
34 | return nil
35 | }
36 |
37 | example, ok := r.Examples[key]
38 | if ok {
39 | return example
40 | }
41 |
42 | if r.Example != nil {
43 | return r.Example
44 | }
45 |
46 | return r.Schema.ExampleValue()
47 | }
48 |
49 | // Schema -.
50 | type Schema interface {
51 | ExampleValue() any
52 | }
53 |
54 | // BooleanSchema -.
55 | type BooleanSchema struct {
56 | Example bool
57 | }
58 |
59 | // ExampleValue -.
60 | func (b BooleanSchema) ExampleValue() any {
61 | return b.Example
62 | }
63 |
64 | // IntSchema -.
65 | type IntSchema struct {
66 | Example int64
67 | }
68 |
69 | // ExampleValue -.
70 | func (i IntSchema) ExampleValue() any {
71 | return i.Example
72 | }
73 |
74 | // FloatSchema -.
75 | type FloatSchema struct {
76 | Example float64
77 | }
78 |
79 | // ExampleValue -.
80 | func (f FloatSchema) ExampleValue() any {
81 | return f.Example
82 | }
83 |
84 | // StringSchema -.
85 | type StringSchema struct {
86 | Example string
87 | }
88 |
89 | // ExampleValue -.
90 | func (s StringSchema) ExampleValue() any {
91 | return s.Example
92 | }
93 |
94 | // ArraySchema -.
95 | type ArraySchema struct {
96 | Type Schema
97 | Example []interface{}
98 | }
99 |
100 | // ExampleValue -.
101 | func (a ArraySchema) ExampleValue() any {
102 | if len(a.Example) > 0 {
103 | return a.Example
104 | }
105 |
106 | return []any{a.Type.ExampleValue()}
107 | }
108 |
109 | // ObjectSchema -.
110 | type ObjectSchema struct {
111 | Properties map[string]Schema
112 | Example map[string]any
113 | }
114 |
115 | // ExampleValue -.
116 | func (o ObjectSchema) ExampleValue() any {
117 | if len(o.Example) > 0 {
118 | return o.Example
119 | }
120 |
121 | example := make(map[string]any, len(o.Properties))
122 |
123 | for key, propSchema := range o.Properties {
124 | example[key] = propSchema.ExampleValue()
125 | }
126 |
127 | return example
128 | }
129 |
130 | // FakerSchema -.
131 | type FakerSchema struct {
132 | Example any
133 | }
134 |
135 | // ExampleValue -.
136 | func (f FakerSchema) ExampleValue() any {
137 | return f.Example
138 | }
139 |
--------------------------------------------------------------------------------
/internal/api/api_test.go:
--------------------------------------------------------------------------------
1 | package api_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 |
8 | "github.com/neotoolkit/dummy/internal/api"
9 | )
10 |
11 | func TestSchema_ExampleValue(t *testing.T) {
12 | tests := []struct {
13 | name string
14 | schema api.Schema
15 | want any
16 | }{
17 | {
18 | name: "boolean: default",
19 | schema: api.BooleanSchema{},
20 | want: false,
21 | },
22 | {
23 | name: "boolean: with example",
24 | schema: api.BooleanSchema{Example: true},
25 | want: true,
26 | },
27 | {
28 | name: "int: default",
29 | schema: api.IntSchema{},
30 | want: int64(0),
31 | },
32 | {
33 | name: "int: with example",
34 | schema: api.IntSchema{Example: 42},
35 | want: int64(42),
36 | },
37 | {
38 | name: "float: default",
39 | schema: api.FloatSchema{},
40 | want: 0.0,
41 | },
42 | {
43 | name: "float: with example",
44 | schema: api.FloatSchema{Example: 4.2},
45 | want: 4.2,
46 | },
47 | {
48 | name: "string: default",
49 | schema: api.StringSchema{},
50 | want: "",
51 | },
52 | {
53 | name: "string: with example",
54 | schema: api.StringSchema{Example: "John"},
55 | want: "John",
56 | },
57 | {
58 | name: "array: default",
59 | schema: api.ArraySchema{Type: api.StringSchema{}},
60 | want: []any{""},
61 | },
62 | {
63 | name: "array: with int example",
64 | schema: api.ArraySchema{Example: []any{4, 2}},
65 | want: []any{4, 2},
66 | },
67 | {
68 | name: "array: with string example",
69 | schema: api.ArraySchema{Example: []any{"4", "2"}},
70 | want: []any{"4", "2"},
71 | },
72 | {
73 | name: "object: default",
74 | schema: api.ObjectSchema{},
75 | want: map[string]any{},
76 | },
77 | {
78 | name: "object: with example",
79 | schema: api.ObjectSchema{Example: map[string]any{"a": "4", "b": "2"}},
80 | want: map[string]any{"a": "4", "b": "2"},
81 | },
82 | }
83 |
84 | for _, tc := range tests {
85 | t.Run(tc.name, func(t *testing.T) {
86 | got := tc.schema.ExampleValue()
87 |
88 | require.Equal(t, tc.want, got)
89 | })
90 | }
91 | }
92 |
93 | func TestResponse_ExampleValue(t *testing.T) {
94 | tests := []struct {
95 | name string
96 | response api.Response
97 | key string
98 | want any
99 | }{
100 | {
101 | name: "boolean examples",
102 | response: api.Response{
103 | Schema: api.BooleanSchema{},
104 | Examples: map[string]any{"first": false, "second": true},
105 | },
106 | key: "first",
107 | want: false,
108 | },
109 | {
110 | name: "int examples",
111 | response: api.Response{
112 | Schema: api.IntSchema{},
113 | Examples: map[string]any{"first": int64(4), "second": int64(2)},
114 | },
115 | key: "first",
116 | want: int64(4),
117 | },
118 | {
119 | name: "float examples",
120 | response: api.Response{
121 | Schema: api.FloatSchema{},
122 | Examples: map[string]any{"first": 4.0, "second": 2.0},
123 | },
124 | key: "first",
125 | want: 4.0,
126 | },
127 | {
128 | name: "string examples",
129 | response: api.Response{
130 | Schema: api.StringSchema{},
131 | Examples: map[string]any{"first": "abc", "second": "xyz"},
132 | },
133 | key: "first",
134 | want: "abc",
135 | },
136 | {
137 | name: "array examples",
138 | response: api.Response{
139 | Schema: api.ArraySchema{},
140 | Examples: map[string]any{"first": []int64{4, 2}, "second": []int64{0, 0}},
141 | },
142 | key: "first",
143 | want: []int64{4, 2},
144 | },
145 | {
146 | name: "object examples",
147 | response: api.Response{
148 | Schema: api.ObjectSchema{},
149 | Examples: map[string]any{"first": map[string]any{"first": "abc"}, "second": map[string]any{"first": "xyz"}},
150 | },
151 | key: "first",
152 | want: map[string]interface{}{"first": "abc"},
153 | },
154 | {
155 | name: "use schema example",
156 | response: api.Response{
157 | Examples: map[string]any{"first": map[string]any{"first": "abc"}, "second": map[string]any{"first": "xyz"}},
158 | Schema: api.ObjectSchema{
159 | Example: map[string]any{"first": "schema variant"},
160 | },
161 | },
162 | key: "third",
163 | want: map[string]any{"first": "schema variant"},
164 | },
165 | {
166 | name: "nil schema",
167 | response: api.Response{
168 | Schema: nil,
169 | Examples: map[string]any{"first": false, "second": true},
170 | },
171 | key: "first",
172 | want: nil,
173 | },
174 | {
175 | name: "example",
176 | response: api.Response{
177 | Schema: api.ObjectSchema{},
178 | Example: map[string]any{"first": "abc", "second": "def"},
179 | },
180 | key: "",
181 | want: map[string]any{"first": "abc", "second": "def"},
182 | },
183 | {
184 | name: "object properties",
185 | response: api.Response{
186 | Schema: api.ObjectSchema{
187 | Properties: map[string]api.Schema{
188 | "key": api.ObjectSchema{},
189 | },
190 | },
191 | },
192 | key: "",
193 | want: map[string]any{"key": map[string]any{}},
194 | },
195 | {
196 | name: "faker schema",
197 | response: api.Response{
198 | Schema: api.FakerSchema{},
199 | },
200 | key: "",
201 | want: nil,
202 | },
203 | }
204 |
205 | for _, tc := range tests {
206 | t.Run(tc.name, func(t *testing.T) {
207 | got := tc.response.ExampleValue(tc.key)
208 |
209 | require.Equal(t, tc.want, got)
210 | })
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/internal/api/build.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net/http"
7 | "strconv"
8 |
9 | "github.com/neotoolkit/faker"
10 | "github.com/neotoolkit/openapi"
11 |
12 | "github.com/neotoolkit/dummy/internal/pkg/pathfmt"
13 | )
14 |
15 | // SchemaTypeError -.
16 | type SchemaTypeError struct {
17 | SchemaType string
18 | }
19 |
20 | func (e *SchemaTypeError) Error() string {
21 | return "unknown type " + e.SchemaType
22 | }
23 |
24 | // ErrEmptyItems -.
25 | var ErrEmptyItems = errors.New("empty items in array")
26 |
27 | // ArrayExampleError -.
28 | type ArrayExampleError struct {
29 | Data any
30 | }
31 |
32 | func (e *ArrayExampleError) Error() string {
33 | return fmt.Sprintf("unpredicted type for example %T", e.Data)
34 | }
35 |
36 | func ParseArrayExample(data any) ([]any, error) {
37 | if nil == data {
38 | return []any{}, nil
39 | }
40 |
41 | d, ok := data.([]any)
42 | if ok {
43 | res := make([]any, len(d))
44 | for k, v := range d {
45 | res[k] = v.(map[string]any)
46 | }
47 |
48 | return res, nil
49 | }
50 |
51 | return nil, &ArrayExampleError{Data: data}
52 | }
53 |
54 | // ObjectExampleError -.
55 | type ObjectExampleError struct {
56 | Data any
57 | }
58 |
59 | // Error -.
60 | func (e *ObjectExampleError) Error() string {
61 | return fmt.Sprintf("unpredicted type for example %T", e.Data)
62 | }
63 |
64 | func ParseObjectExample(data any) (map[string]any, error) {
65 | if nil == data {
66 | return map[string]any{}, nil
67 | }
68 |
69 | d, ok := data.(map[string]any)
70 | if ok {
71 | return d, nil
72 | }
73 |
74 | return nil, &ObjectExampleError{Data: data}
75 | }
76 |
77 | type Builder struct {
78 | OpenAPI openapi.OpenAPI
79 | Operations []Operation
80 | }
81 |
82 | // Build -.
83 | func (b *Builder) Build() (API, error) {
84 | for path, method := range b.OpenAPI.Paths {
85 | if err := b.Add(path, http.MethodGet, method.Get); err != nil {
86 | return API{}, err
87 | }
88 |
89 | if err := b.Add(path, http.MethodPost, method.Post); err != nil {
90 | return API{}, err
91 | }
92 |
93 | if err := b.Add(path, http.MethodPut, method.Put); err != nil {
94 | return API{}, err
95 | }
96 |
97 | if err := b.Add(path, http.MethodPatch, method.Patch); err != nil {
98 | return API{}, err
99 | }
100 |
101 | if err := b.Add(path, http.MethodDelete, method.Delete); err != nil {
102 | return API{}, err
103 | }
104 | }
105 |
106 | return API{Operations: b.Operations}, nil
107 | }
108 |
109 | // Add -.
110 | func (b *Builder) Add(path, method string, o *openapi.Operation) error {
111 | if o != nil {
112 | p := pathfmt.RemoveTrailingSlash(path)
113 |
114 | operation, err := b.Set(p, method, o)
115 | if err != nil {
116 | return err
117 | }
118 |
119 | b.Operations = append(b.Operations, operation)
120 | }
121 |
122 | return nil
123 | }
124 |
125 | // Set -.
126 | func (b *Builder) Set(path, method string, o *openapi.Operation) (Operation, error) {
127 | operation := Operation{
128 | Method: method,
129 | Path: path,
130 | }
131 |
132 | if nil == o {
133 | return operation, nil
134 | }
135 |
136 | body, ok := o.RequestBody.Content["application/json"]
137 | if ok || len(o.RequestBody.Ref) > 0 {
138 | var s openapi.Schema
139 |
140 | switch {
141 | case len(o.RequestBody.Ref) > 0:
142 | requestBody, err := b.OpenAPI.LookupRequestBodyByReference(o.RequestBody.Ref)
143 | if err != nil {
144 | return Operation{}, fmt.Errorf("resolve reference: %w", err)
145 | }
146 |
147 | body, ok := requestBody.Content["application/json"]
148 | if ok {
149 | s = body.Schema
150 | }
151 | case body.Schema.IsRef():
152 | schema, err := b.OpenAPI.LookupSchemaByReference(body.Schema.Ref)
153 | if err != nil {
154 | return Operation{}, fmt.Errorf("resolve reference: %w", err)
155 | }
156 |
157 | s = schema
158 | default:
159 | s = body.Schema
160 | }
161 |
162 | operation.Body = make(map[string]FieldType, len(s.Properties))
163 |
164 | for _, v := range s.Required {
165 | operation.Body[v] = FieldType{Required: true}
166 | }
167 |
168 | for k, v := range s.Properties {
169 | operation.Body[k] = FieldType{
170 | Required: operation.Body[k].Required,
171 | Type: v.Type,
172 | }
173 | }
174 | }
175 |
176 | for code, resp := range o.Responses {
177 | statusCode, err := strconv.Atoi(code)
178 | if err != nil {
179 | return Operation{}, err
180 | }
181 |
182 | if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices {
183 | continue
184 | }
185 |
186 | content, ok := resp.Content["application/json"]
187 | if !ok {
188 | operation.Responses = append(operation.Responses, Response{
189 | StatusCode: statusCode,
190 | })
191 |
192 | continue
193 | }
194 |
195 | example := openapi.ExampleToResponse(content.Example)
196 |
197 | examples := make(map[string]any, len(content.Examples)+1)
198 |
199 | if len(content.Examples) > 0 {
200 | for key, e := range content.Examples {
201 | examples[key] = openapi.ExampleToResponse(e.Value)
202 | }
203 |
204 | examples[""] = openapi.ExampleToResponse(content.Examples[content.Examples.GetKeys()[0]].Value)
205 | }
206 |
207 | schema, err := b.convertSchema(content.Schema)
208 | if err != nil {
209 | return Operation{}, err
210 | }
211 |
212 | operation.Responses = append(operation.Responses, Response{
213 | StatusCode: statusCode,
214 | MediaType: "application/json",
215 | Schema: schema,
216 | Example: example,
217 | Examples: examples,
218 | })
219 | }
220 |
221 | return operation, nil
222 | }
223 |
224 | func (b *Builder) convertSchema(s openapi.Schema) (Schema, error) {
225 | if s.IsRef() {
226 | schema, err := b.OpenAPI.LookupSchemaByReference(s.Ref)
227 | if err != nil {
228 | return nil, fmt.Errorf("resolve reference: %w", err)
229 | }
230 |
231 | s = schema
232 | }
233 |
234 | f, err := faker.Faker(s.Faker)
235 | if s.Faker != "" && err == nil {
236 | return FakerSchema{Example: f}, nil
237 | }
238 |
239 | switch s.Type {
240 | case "boolean":
241 | val, _ := s.Example.(bool)
242 | return BooleanSchema{Example: val}, nil
243 | case "integer":
244 | val, _ := s.Example.(int64)
245 | return IntSchema{Example: val}, nil
246 | case "number":
247 | val, _ := s.Example.(float64)
248 | return FloatSchema{Example: val}, nil
249 | case "string":
250 | val, _ := s.Example.(string)
251 | return StringSchema{Example: val}, nil
252 | case "array":
253 | if nil == s.Items {
254 | return nil, ErrEmptyItems
255 | }
256 |
257 | itemsSchema, err := b.convertSchema(*s.Items)
258 | if err != nil {
259 | return nil, err
260 | }
261 |
262 | arrExample, err := ParseArrayExample(s.Example)
263 | if err != nil {
264 | return nil, err
265 | }
266 |
267 | return ArraySchema{
268 | Type: itemsSchema,
269 | Example: arrExample,
270 | }, nil
271 | case "object":
272 | obj := ObjectSchema{Properties: make(map[string]Schema, len(s.Properties))}
273 |
274 | for key, prop := range s.Properties {
275 | propSchema, err := b.convertSchema(*prop)
276 | if err != nil {
277 | return nil, err
278 | }
279 |
280 | obj.Properties[key] = propSchema
281 | }
282 |
283 | objExample, err := ParseObjectExample(s.Example)
284 | if err != nil {
285 | return nil, err
286 | }
287 |
288 | obj.Example = objExample
289 |
290 | return obj, nil
291 | default:
292 | return nil, &SchemaTypeError{SchemaType: s.Type}
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/internal/api/build_test.go:
--------------------------------------------------------------------------------
1 | package api_test
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net/http"
7 | "strconv"
8 | "testing"
9 |
10 | "github.com/neotoolkit/openapi"
11 | "github.com/stretchr/testify/require"
12 |
13 | "github.com/neotoolkit/dummy/internal/api"
14 | )
15 |
16 | func TestSchemaTypeError(t *testing.T) {
17 | got := &api.SchemaTypeError{
18 | SchemaType: "",
19 | }
20 |
21 | require.Equal(t, got.Error(), "unknown type ")
22 | }
23 |
24 | func TestArrayExampleError(t *testing.T) {
25 | got := &api.ArrayExampleError{
26 | Data: "",
27 | }
28 |
29 | require.Equal(t, got.Error(), "unpredicted type for example string")
30 | }
31 |
32 | func TestParseArrayExample(t *testing.T) {
33 | tests := []struct {
34 | name string
35 | data any
36 | want []any
37 | err error
38 | }{
39 | {
40 | name: "nil data",
41 | data: nil,
42 | want: []any{},
43 | err: nil,
44 | },
45 | {
46 | name: "array",
47 | data: []any{
48 | map[string]any{
49 | "key": "value",
50 | },
51 | },
52 | want: []any{
53 | map[string]any{
54 | "key": "value",
55 | },
56 | },
57 | err: nil,
58 | },
59 | {
60 | name: "not array",
61 | data: "string",
62 | want: nil,
63 | err: &api.ArrayExampleError{Data: "string"},
64 | },
65 | }
66 |
67 | for _, tc := range tests {
68 | t.Run(tc.name, func(t *testing.T) {
69 | res, err := api.ParseArrayExample(tc.data)
70 | if err != nil {
71 | require.EqualError(t, err, tc.err.Error())
72 | }
73 |
74 | require.Equal(t, tc.want, res)
75 | })
76 | }
77 | }
78 |
79 | func TestObjectExampleError(t *testing.T) {
80 | got := &api.ObjectExampleError{
81 | Data: "",
82 | }
83 |
84 | require.Equal(t, got.Error(), "unpredicted type for example string")
85 | }
86 |
87 | func TestParseObjectExample(t *testing.T) {
88 | tests := []struct {
89 | name string
90 | data any
91 | want map[string]any
92 | err error
93 | }{
94 | {
95 | name: "nil data",
96 | data: nil,
97 | want: map[string]any{},
98 | err: nil,
99 | },
100 | {
101 | name: "object",
102 | data: map[string]any{},
103 | want: map[string]any{},
104 | err: nil,
105 | },
106 | {
107 | name: "not object",
108 | data: "string",
109 | want: nil,
110 | err: &api.ObjectExampleError{Data: "string"},
111 | },
112 | }
113 |
114 | for _, tc := range tests {
115 | t.Run(tc.name, func(t *testing.T) {
116 | res, err := api.ParseObjectExample(tc.data)
117 | if err != nil {
118 | require.EqualError(t, err, tc.err.Error())
119 | }
120 |
121 | require.Equal(t, tc.want, res)
122 | })
123 | }
124 | }
125 |
126 | func TestBuilder_Build(t *testing.T) {
127 | tests := []struct {
128 | name string
129 | builder api.Builder
130 | want api.API
131 | err error
132 | }{
133 | {
134 | name: "",
135 | builder: api.Builder{},
136 | want: api.API{},
137 | err: nil,
138 | },
139 | {
140 | name: "GET",
141 | builder: api.Builder{
142 | OpenAPI: openapi.OpenAPI{
143 | Paths: map[string]*openapi.Path{
144 | "test": {
145 | Get: &openapi.Operation{},
146 | },
147 | },
148 | },
149 | },
150 | want: api.API{
151 | Operations: []api.Operation{
152 | {
153 | Method: "GET",
154 | Path: "test",
155 | Body: map[string]api.FieldType(nil),
156 | Responses: []api.Response(nil),
157 | },
158 | },
159 | },
160 | err: nil,
161 | },
162 | {
163 | name: "Wrong status code in GET",
164 | builder: api.Builder{
165 | OpenAPI: openapi.OpenAPI{
166 | Paths: map[string]*openapi.Path{
167 | "test": {
168 | Get: &openapi.Operation{
169 | Responses: map[string]*openapi.Response{
170 | "Wrong status code": nil,
171 | },
172 | },
173 | },
174 | },
175 | },
176 | },
177 | want: api.API{},
178 | err: &strconv.NumError{
179 | Func: "Atoi",
180 | Num: "Wrong status code",
181 | Err: strconv.ErrSyntax,
182 | },
183 | },
184 | {
185 | name: "POST",
186 | builder: api.Builder{
187 | OpenAPI: openapi.OpenAPI{
188 | Paths: map[string]*openapi.Path{
189 | "test": {
190 | Post: &openapi.Operation{},
191 | },
192 | },
193 | },
194 | },
195 | want: api.API{
196 | Operations: []api.Operation{
197 | {
198 | Method: "POST",
199 | Path: "test",
200 | Body: map[string]api.FieldType(nil),
201 | Responses: []api.Response(nil),
202 | },
203 | },
204 | },
205 | err: nil,
206 | },
207 | {
208 | name: "Wrong schema reference in POST",
209 | builder: api.Builder{
210 | OpenAPI: openapi.OpenAPI{
211 | Paths: map[string]*openapi.Path{
212 | "test": {
213 | Post: &openapi.Operation{
214 | RequestBody: openapi.RequestBody{
215 | Content: map[string]*openapi.MediaType{
216 | "application/json": {
217 | Schema: openapi.Schema{
218 | Ref: "wrong schema reference",
219 | },
220 | },
221 | },
222 | },
223 | },
224 | },
225 | },
226 | },
227 | },
228 | want: api.API{},
229 | err: fmt.Errorf("resolve reference: %w", &openapi.SchemaError{Ref: "wrong schema reference"}),
230 | },
231 | {
232 | name: "PUT",
233 | builder: api.Builder{
234 | OpenAPI: openapi.OpenAPI{
235 | Paths: map[string]*openapi.Path{
236 | "test": {
237 | Put: &openapi.Operation{},
238 | },
239 | },
240 | },
241 | },
242 | want: api.API{
243 | Operations: []api.Operation{
244 | {
245 | Method: "PUT",
246 | Path: "test",
247 | Body: map[string]api.FieldType(nil),
248 | Responses: []api.Response(nil),
249 | },
250 | },
251 | },
252 | err: nil,
253 | },
254 | {
255 | name: "Wrong schema reference in PUT",
256 | builder: api.Builder{
257 | OpenAPI: openapi.OpenAPI{
258 | Paths: map[string]*openapi.Path{
259 | "test": {
260 | Put: &openapi.Operation{
261 | RequestBody: openapi.RequestBody{
262 | Content: map[string]*openapi.MediaType{
263 | "application/json": {
264 | Schema: openapi.Schema{
265 | Ref: "wrong schema reference",
266 | },
267 | },
268 | },
269 | },
270 | },
271 | },
272 | },
273 | },
274 | },
275 | want: api.API{},
276 | err: fmt.Errorf("resolve reference: %w", &openapi.SchemaError{Ref: "wrong schema reference"}),
277 | },
278 | {
279 | name: "PATCH",
280 | builder: api.Builder{
281 | OpenAPI: openapi.OpenAPI{
282 | Paths: map[string]*openapi.Path{
283 | "test": {
284 | Patch: &openapi.Operation{},
285 | },
286 | },
287 | },
288 | },
289 | want: api.API{
290 | Operations: []api.Operation{
291 | {
292 | Method: "PATCH",
293 | Path: "test",
294 | Body: map[string]api.FieldType(nil),
295 | Responses: []api.Response(nil),
296 | },
297 | },
298 | },
299 | err: nil,
300 | },
301 | {
302 | name: "Wrong schema reference in PATCH",
303 | builder: api.Builder{
304 | OpenAPI: openapi.OpenAPI{
305 | Paths: map[string]*openapi.Path{
306 | "test": {
307 | Patch: &openapi.Operation{
308 | RequestBody: openapi.RequestBody{
309 | Content: map[string]*openapi.MediaType{
310 | "application/json": {
311 | Schema: openapi.Schema{
312 | Ref: "wrong schema reference",
313 | },
314 | },
315 | },
316 | },
317 | },
318 | },
319 | },
320 | },
321 | },
322 | want: api.API{},
323 | err: fmt.Errorf("resolve reference: %w", &openapi.SchemaError{Ref: "wrong schema reference"}),
324 | },
325 | {
326 | name: "DELETE",
327 | builder: api.Builder{
328 | OpenAPI: openapi.OpenAPI{
329 | Paths: map[string]*openapi.Path{
330 | "test": {
331 | Delete: &openapi.Operation{},
332 | },
333 | },
334 | },
335 | },
336 | want: api.API{
337 | Operations: []api.Operation{
338 | {
339 | Method: "DELETE",
340 | Path: "test",
341 | Body: map[string]api.FieldType(nil),
342 | Responses: []api.Response(nil),
343 | },
344 | },
345 | },
346 | err: nil,
347 | },
348 | {
349 | name: "Wrong status code in DELETE",
350 | builder: api.Builder{
351 | OpenAPI: openapi.OpenAPI{
352 | Paths: map[string]*openapi.Path{
353 | "test": {
354 | Delete: &openapi.Operation{
355 | Responses: map[string]*openapi.Response{
356 | "Wrong status code": nil,
357 | },
358 | },
359 | },
360 | },
361 | },
362 | },
363 | want: api.API{},
364 | err: &strconv.NumError{
365 | Func: "Atoi",
366 | Num: "Wrong status code",
367 | Err: strconv.ErrSyntax,
368 | },
369 | },
370 | }
371 |
372 | for _, tc := range tests {
373 | t.Run(tc.name, func(t *testing.T) {
374 | b := tc.builder
375 |
376 | res, err := b.Build()
377 | if err != nil {
378 | require.EqualError(t, err, tc.err.Error())
379 | }
380 |
381 | require.Equal(t, tc.want, res)
382 | })
383 | }
384 | }
385 |
386 | func TestBuilder_Add(t *testing.T) {
387 | tests := []struct {
388 | name string
389 | builder api.Builder
390 | path string
391 | method string
392 | operation *openapi.Operation
393 | err error
394 | }{
395 | {
396 | name: "",
397 | builder: api.Builder{},
398 | path: "",
399 | method: "",
400 | operation: nil,
401 | err: nil,
402 | },
403 | {
404 | name: "",
405 | builder: api.Builder{},
406 | path: "",
407 | method: "",
408 | operation: &openapi.Operation{
409 | RequestBody: openapi.RequestBody{
410 | Content: map[string]*openapi.MediaType{
411 | "application/json": {
412 | Schema: openapi.Schema{
413 | Ref: "wrong schema reference",
414 | },
415 | },
416 | },
417 | },
418 | },
419 | err: fmt.Errorf("resolve reference: %w", &openapi.SchemaError{Ref: "wrong schema reference"}),
420 | },
421 | {
422 | name: "",
423 | builder: api.Builder{},
424 | path: "",
425 | method: "",
426 | operation: &openapi.Operation{},
427 | err: nil,
428 | },
429 | }
430 |
431 | for _, tc := range tests {
432 | t.Run(tc.name, func(t *testing.T) {
433 | b := tc.builder
434 |
435 | err := b.Add(tc.path, tc.method, tc.operation)
436 | if err != nil {
437 | require.EqualError(t, err, tc.err.Error())
438 | }
439 | })
440 | }
441 | }
442 |
443 | func TestBuilder_Set(t *testing.T) {
444 | tests := []struct {
445 | name string
446 | builder api.Builder
447 | path string
448 | method string
449 | operation *openapi.Operation
450 | want api.Operation
451 | err error
452 | }{
453 | {
454 | name: "",
455 | builder: api.Builder{},
456 | path: "",
457 | method: "",
458 | operation: nil,
459 | want: api.Operation{},
460 | err: nil,
461 | },
462 | {
463 | name: "",
464 | builder: api.Builder{},
465 | path: "",
466 | method: "",
467 | operation: &openapi.Operation{
468 | RequestBody: openapi.RequestBody{
469 | Content: map[string]*openapi.MediaType{
470 | "application/json": {
471 | Schema: openapi.Schema{
472 | Ref: "wrong schema reference",
473 | },
474 | },
475 | },
476 | },
477 | },
478 | want: api.Operation{},
479 | err: fmt.Errorf("resolve reference: %w", &openapi.SchemaError{Ref: "wrong schema reference"}),
480 | },
481 | {
482 | name: "",
483 | builder: api.Builder{},
484 | path: "",
485 | method: "",
486 | operation: &openapi.Operation{
487 | RequestBody: openapi.RequestBody{
488 | Content: map[string]*openapi.MediaType{
489 | "application/json": {
490 | Schema: openapi.Schema{
491 | Required: []string{"field"},
492 | Properties: map[string]*openapi.Schema{
493 | "prop": {},
494 | },
495 | },
496 | },
497 | },
498 | },
499 | },
500 | want: api.Operation{
501 | Body: map[string]api.FieldType{
502 | "field": {
503 | Required: true,
504 | Type: "",
505 | },
506 | "prop": {
507 | Required: false,
508 | Type: "",
509 | },
510 | },
511 | },
512 | err: nil,
513 | },
514 | {
515 | name: "",
516 | builder: api.Builder{},
517 | path: "",
518 | method: "",
519 | operation: &openapi.Operation{
520 | Responses: openapi.Responses{
521 | "200": {},
522 | },
523 | },
524 | want: api.Operation{
525 | Responses: []api.Response{
526 | {
527 | StatusCode: http.StatusOK,
528 | },
529 | },
530 | },
531 | err: nil,
532 | },
533 | {
534 | name: "",
535 | builder: api.Builder{},
536 | path: "",
537 | method: "",
538 | operation: &openapi.Operation{
539 | Responses: openapi.Responses{
540 | "200": {
541 | Content: map[string]*openapi.MediaType{
542 | "application/json": {
543 | Example: "example",
544 | },
545 | },
546 | },
547 | },
548 | },
549 | want: api.Operation{},
550 | err: &api.SchemaTypeError{SchemaType: ""},
551 | },
552 | {
553 | name: "",
554 | builder: api.Builder{},
555 | path: "",
556 | method: "",
557 | operation: &openapi.Operation{
558 | RequestBody: openapi.RequestBody{
559 | Ref: "request-body",
560 | },
561 | },
562 | want: api.Operation{},
563 | err: errors.New("resolve reference: unknown request body request-body"),
564 | },
565 | }
566 |
567 | for _, tc := range tests {
568 | t.Run(tc.name, func(t *testing.T) {
569 | b := tc.builder
570 |
571 | got, err := b.Set(tc.path, tc.method, tc.operation)
572 | if err != nil {
573 | require.EqualError(t, err, tc.err.Error())
574 | }
575 |
576 | require.Equal(t, tc.want, got)
577 | })
578 | }
579 | }
580 |
--------------------------------------------------------------------------------
/internal/api/find.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "io"
7 | "net/http"
8 | "strings"
9 | )
10 |
11 | // FindResponseError -.
12 | type FindResponseError struct {
13 | Method string
14 | Path string
15 | }
16 |
17 | // Error -.
18 | func (e *FindResponseError) Error() string {
19 | return "not specified operation: " + e.Method + " " + e.Path
20 | }
21 |
22 | // FindResponseParams -.
23 | type FindResponseParams struct {
24 | Path string
25 | Method string
26 | Body io.ReadCloser
27 | MediaType string
28 | }
29 |
30 | // ErrEmptyRequireField -.
31 | var ErrEmptyRequireField = errors.New("empty require field")
32 |
33 | // FindResponse -.
34 | func (a API) FindResponse(params FindResponseParams) (Response, error) {
35 | operation, ok := a.findOperation(params)
36 | if !ok {
37 | return Response{}, &FindResponseError{
38 | Method: params.Method,
39 | Path: params.Path,
40 | }
41 | }
42 |
43 | switch params.Method {
44 | case http.MethodPost, http.MethodPut, http.MethodPatch:
45 | var body map[string]any
46 |
47 | err := json.NewDecoder(params.Body).Decode(&body)
48 | if err != nil {
49 | return Response{}, err
50 | }
51 |
52 | for k, v := range operation.Body {
53 | _, ok := body[k]
54 | if !ok && v.Required {
55 | return Response{}, ErrEmptyRequireField
56 | }
57 | }
58 | }
59 |
60 | response, ok := operation.findOperationResponse(params)
61 | if !ok {
62 | return operation.Responses[0], nil
63 | }
64 |
65 | return response, nil
66 | }
67 |
68 | func (a API) findOperation(params FindResponseParams) (Operation, bool) {
69 | for _, op := range a.Operations {
70 | if !IsPathMatchTemplate(params.Path, op.Path) {
71 | continue
72 | }
73 |
74 | if params.Method != op.Method {
75 | continue
76 | }
77 |
78 | return op, true
79 | }
80 |
81 | return Operation{}, false
82 | }
83 |
84 | func (o Operation) findOperationResponse(params FindResponseParams) (Response, bool) {
85 | for _, r := range o.Responses {
86 | if r.MediaType != params.MediaType {
87 | continue
88 | }
89 |
90 | return r, true
91 | }
92 |
93 | return Response{}, false
94 | }
95 |
96 | // IsPathMatchTemplate returns true if path matches template
97 | func IsPathMatchTemplate(path, pathTemplate string) bool {
98 | pathSegments := strings.Split(path, "/")
99 | templateSegments := strings.Split(pathTemplate, "/")
100 |
101 | if len(pathSegments) != len(templateSegments) {
102 | return false
103 | }
104 |
105 | for i := 0; i < len(pathSegments); i++ {
106 | if strings.HasPrefix(templateSegments[i], "{") && strings.HasSuffix(templateSegments[i], "}") {
107 | continue
108 | }
109 |
110 | if pathSegments[i] != templateSegments[i] {
111 | return false
112 | }
113 | }
114 |
115 | return true
116 | }
117 |
--------------------------------------------------------------------------------
/internal/api/find_test.go:
--------------------------------------------------------------------------------
1 | package api_test
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/require"
10 |
11 | "github.com/neotoolkit/dummy/internal/api"
12 | )
13 |
14 | type ResponseParamsBody struct {
15 | Body any
16 | IsBodyBroken bool
17 | }
18 |
19 | func (params ResponseParamsBody) Read(p []byte) (n int, err error) {
20 | jsonData, err := json.Marshal(params.Body)
21 | if params.IsBodyBroken || err != nil {
22 | jsonData = []byte{1, 2, 3}
23 | }
24 |
25 | readerResult, readerError := bytes.NewReader(jsonData).Read(p)
26 |
27 | return readerResult, readerError
28 | }
29 |
30 | func (ResponseParamsBody) Close() error {
31 | return nil
32 | }
33 |
34 | func TestIsPathMatchTemplate(t *testing.T) {
35 | tests := []struct {
36 | name string
37 | path string
38 | param string
39 | want bool
40 | }{
41 | {
42 | name: "",
43 | path: "",
44 | param: "",
45 | want: true,
46 | },
47 | {
48 | name: "",
49 | path: "/path",
50 | param: "/path",
51 | want: true,
52 | },
53 | {
54 | name: "",
55 | path: "/path/1",
56 | param: "/path/{1}",
57 | want: true,
58 | },
59 | {
60 | name: "",
61 | path: "/path/1/path/2",
62 | param: "/path/{1}/path/{2}",
63 | want: true,
64 | },
65 | {
66 | name: "",
67 | path: "/path/1/path1/2",
68 | param: "/path/{1}/path/{2}",
69 | want: false,
70 | },
71 | {
72 | name: "",
73 | path: "/path/1/path/2",
74 | param: "/path/{1}/path1/{2}",
75 | want: false,
76 | },
77 | {
78 | name: "",
79 | path: "/path/1/path/2/path",
80 | param: "/path/{1}/path/{2}",
81 | want: false,
82 | },
83 | }
84 |
85 | for _, tc := range tests {
86 | t.Run(tc.name, func(t *testing.T) {
87 | got := api.IsPathMatchTemplate(tc.path, tc.param)
88 |
89 | require.Equal(t, tc.want, got)
90 | })
91 | }
92 | }
93 |
94 | func TestFindResponseError(t *testing.T) {
95 | got := &api.FindResponseError{
96 | Method: "test method",
97 | Path: "test path",
98 | }
99 |
100 | require.Equal(t, got.Error(), "not specified operation: test method test path")
101 | }
102 |
103 | func TestFindResponse(t *testing.T) {
104 | bodyFixedPath := map[string]api.FieldType{
105 | "param1": {
106 | Required: true,
107 | Type: "string",
108 | },
109 | "param2": {
110 | Required: false,
111 | Type: "string",
112 | },
113 | }
114 |
115 | responseFixedPathJSON := api.Response{
116 | StatusCode: 200,
117 | MediaType: "application/json",
118 | }
119 | responseFixedPathZip := api.Response{
120 | StatusCode: 200,
121 | MediaType: "application/zip",
122 | }
123 |
124 | operationFixedPath := api.Operation{
125 | Method: "POST",
126 | Path: "some/fixed/path",
127 | Body: bodyFixedPath,
128 | Responses: []api.Response{
129 | responseFixedPathJSON,
130 | responseFixedPathZip,
131 | },
132 | }
133 |
134 | a := api.API{
135 | Operations: []api.Operation{
136 | operationFixedPath,
137 | },
138 | }
139 |
140 | mismatchByOperationPathError := api.FindResponseError{
141 | Method: "POST",
142 | Path: "some/other/path",
143 | }
144 | mismatchByOperationMethodError := api.FindResponseError{
145 | Method: "GET",
146 | Path: "some/fixed/path",
147 | }
148 | bodyWithoutRequiredParamError := errors.New("empty require field")
149 |
150 | bodyWithoutRequiredParam := map[string]any{
151 | "param3": "qwe",
152 | "param2": "rty",
153 | }
154 | bodyWithoutOptionalParam := map[string]any{
155 | "param3": "qwe",
156 | "param1": "rty",
157 | }
158 | bodyWithAllParam := map[string]any{
159 | "param1": "qwe",
160 | "param2": "rty",
161 | }
162 |
163 | emptyResponse := api.Response{}
164 |
165 | tests := []struct {
166 | name string
167 | path string
168 | method string
169 | body any
170 | wantFirst api.Response
171 | wantSecond any
172 | responseMediaType string
173 | }{
174 | {
175 | name: "Mismatch by operation path",
176 | path: "some/other/path",
177 | method: "POST",
178 | body: bodyFixedPath,
179 | wantFirst: emptyResponse,
180 | wantSecond: &mismatchByOperationPathError,
181 | },
182 | {
183 | name: "Mismatch by operation method",
184 | path: "some/fixed/path",
185 | method: "GET",
186 | body: bodyFixedPath,
187 | wantFirst: emptyResponse,
188 | wantSecond: &mismatchByOperationMethodError,
189 | },
190 | {
191 | name: "Mismatch by body without required params",
192 | path: "some/fixed/path",
193 | method: "POST",
194 | body: bodyWithoutRequiredParam,
195 | wantFirst: emptyResponse,
196 | wantSecond: bodyWithoutRequiredParamError,
197 | },
198 | {
199 | name: "Match by body without optional params",
200 | path: "some/fixed/path",
201 | method: "POST",
202 | body: bodyWithoutOptionalParam,
203 | wantFirst: responseFixedPathJSON,
204 | wantSecond: nil,
205 | },
206 | {
207 | name: "Match by response without selected media type",
208 | path: "some/fixed/path",
209 | method: "POST",
210 | body: bodyWithAllParam,
211 | wantFirst: responseFixedPathJSON,
212 | wantSecond: nil,
213 | },
214 | {
215 | name: "Match by all criteria",
216 | path: "some/fixed/path",
217 | method: "POST",
218 | body: bodyWithAllParam,
219 | wantFirst: responseFixedPathZip,
220 | wantSecond: nil,
221 | responseMediaType: "application/zip",
222 | },
223 | }
224 |
225 | for _, tc := range tests {
226 | t.Run(tc.name, func(t *testing.T) {
227 | bodyReader := ResponseParamsBody{Body: tc.body}
228 | mediaType := "application/xml"
229 |
230 | if tc.responseMediaType != "" {
231 | mediaType = tc.responseMediaType
232 | }
233 | params := api.FindResponseParams{
234 | Path: tc.path,
235 | Method: tc.method,
236 | Body: bodyReader,
237 | MediaType: mediaType,
238 | }
239 | firstResult, secondResult := a.FindResponse(params)
240 |
241 | require.Equal(t, tc.wantFirst, firstResult)
242 | require.Equal(t, tc.wantSecond, secondResult)
243 | })
244 | }
245 |
246 | t.Run("Broken json in body reader", func(t *testing.T) {
247 | params := api.FindResponseParams{
248 | Path: "some/fixed/path",
249 | Method: "POST",
250 | Body: ResponseParamsBody{Body: bodyWithAllParam, IsBodyBroken: true},
251 | MediaType: "application/xml",
252 | }
253 |
254 | _, err := a.FindResponse(params)
255 | require.Error(t, err)
256 | })
257 | }
258 |
--------------------------------------------------------------------------------
/internal/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | // Config is struct for Config
4 | type Config struct {
5 | Server Server
6 | Logger Logger
7 | }
8 |
9 | // NewConfig returns a new instance of Config instance
10 | func NewConfig() *Config {
11 | return &Config{}
12 | }
13 |
--------------------------------------------------------------------------------
/internal/config/config_test.go:
--------------------------------------------------------------------------------
1 | package config_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 |
8 | "github.com/neotoolkit/dummy/internal/config"
9 | )
10 |
11 | func TestNewConfig(t *testing.T) {
12 | conf := config.NewConfig()
13 |
14 | require.IsType(t, &config.Config{}, conf)
15 | }
16 |
--------------------------------------------------------------------------------
/internal/config/logger.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | // Logger is struct for Logger
4 | type Logger struct {
5 | Level string
6 | }
7 |
--------------------------------------------------------------------------------
/internal/config/server.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | // Server is struct for Server
4 | type Server struct {
5 | // Path to OpenAPI specification
6 | Path string
7 | Port string
8 | }
9 |
--------------------------------------------------------------------------------
/internal/exitcode/exitcode.go:
--------------------------------------------------------------------------------
1 | package exitcode
2 |
3 | const (
4 | // Success -.
5 | Success = iota
6 | // Failure -.
7 | Failure
8 | )
9 |
--------------------------------------------------------------------------------
/internal/logger/logger.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "os"
5 | "strings"
6 |
7 | "go.uber.org/zap"
8 | "go.uber.org/zap/zapcore"
9 | )
10 |
11 | type Logger struct {
12 | logger *zap.SugaredLogger
13 | }
14 |
15 | func NewLogger(level string) *Logger {
16 | encoderCfg := zapcore.EncoderConfig{
17 | MessageKey: "msg",
18 | LevelKey: "level",
19 | TimeKey: "time",
20 | NameKey: "logger",
21 | EncodeLevel: zapcore.LowercaseLevelEncoder,
22 | EncodeTime: zapcore.ISO8601TimeEncoder,
23 | EncodeDuration: zapcore.StringDurationEncoder,
24 | }
25 | encoder := zapcore.NewJSONEncoder(encoderCfg)
26 | l := encodeLevel(level)
27 | core := zapcore.NewCore(encoder, os.Stdout, l)
28 | logger := zap.New(core)
29 |
30 | return &Logger{logger: logger.Sugar()}
31 | }
32 |
33 | func (l *Logger) Debug(msg string) {
34 | l.logger.Debug(msg)
35 | }
36 |
37 | func (l *Logger) Info(msg string) {
38 | l.logger.Info(msg)
39 | }
40 |
41 | func (l *Logger) Infof(msg string, args ...any) {
42 | l.logger.Infof(msg, args)
43 | }
44 |
45 | func (l *Logger) Infow(msg string, w ...any) {
46 | l.logger.Infow(msg, w...)
47 | }
48 |
49 | func (l *Logger) Warn(msg string) {
50 | l.logger.Warn(msg)
51 | }
52 |
53 | func (l *Logger) Error(msg string) {
54 | l.logger.Error(msg)
55 | }
56 |
57 | func (l *Logger) Errorf(msg string, args ...any) {
58 | l.logger.Errorf(msg, args)
59 | }
60 |
61 | func (l *Logger) Errorw(msg string, w ...any) {
62 | l.logger.Errorw(msg, w...)
63 | }
64 |
65 | func encodeLevel(level string) zapcore.Level {
66 | switch strings.ToLower(level) {
67 | case "debug":
68 | return zap.DebugLevel
69 | case "info":
70 | return zap.InfoLevel
71 | case "warn":
72 | return zap.WarnLevel
73 | case "error":
74 | return zap.ErrorLevel
75 | default:
76 | return zap.InfoLevel
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/internal/logger/logger_test.go:
--------------------------------------------------------------------------------
1 | package logger_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 |
8 | "github.com/neotoolkit/dummy/internal/logger"
9 | )
10 |
11 | func TestNewLogger(t *testing.T) {
12 | tests := []struct {
13 | name string
14 | level string
15 | }{
16 | {
17 | name: "",
18 | level: "",
19 | },
20 | {
21 | name: "Logger with INFO level",
22 | level: "INFO",
23 | },
24 | {
25 | name: "Logger with DEBUG level",
26 | level: "DEBUG",
27 | },
28 | }
29 |
30 | for _, tc := range tests {
31 | t.Run(tc.name, func(t *testing.T) {
32 | got := logger.NewLogger(tc.level)
33 |
34 | require.IsType(t, &logger.Logger{}, got)
35 | })
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/internal/middleware/logging.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "net/http"
5 | "time"
6 |
7 | "github.com/neotoolkit/dummy/internal/pkg/logger"
8 | )
9 |
10 | type responseWriter struct {
11 | http.ResponseWriter
12 | status int
13 | }
14 |
15 | func wrapResponseWriter(w http.ResponseWriter) *responseWriter {
16 | return &responseWriter{ResponseWriter: w}
17 | }
18 |
19 | func (rw *responseWriter) Status() int {
20 | return rw.status
21 | }
22 |
23 | func (rw *responseWriter) WriteHeader(code int) {
24 | rw.status = code
25 | rw.ResponseWriter.WriteHeader(code)
26 | }
27 |
28 | // Logging -.
29 | func Logging(next http.Handler, logger logger.Logger) http.Handler {
30 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
31 | logger.Infow("request",
32 | "path", r.URL.Path,
33 | "method", r.Method,
34 | "header", r.Header,
35 | "body", r.Body,
36 | )
37 |
38 | wrapped := wrapResponseWriter(w)
39 |
40 | start := time.Now()
41 |
42 | next.ServeHTTP(wrapped, r)
43 |
44 | logger.Infow("response",
45 | "status-code", wrapped.Status(),
46 | "header", wrapped.Header(),
47 | "duration", time.Since(start),
48 | )
49 | })
50 | }
51 |
--------------------------------------------------------------------------------
/internal/middleware/logging_test.go:
--------------------------------------------------------------------------------
1 | package middleware_test
2 |
3 | import (
4 | "github.com/neotoolkit/dummy/internal/logger"
5 | "github.com/neotoolkit/dummy/internal/middleware"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 | )
10 |
11 | func TestLogging(t *testing.T) {
12 | next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
13 | w.WriteHeader(http.StatusOK)
14 | })
15 |
16 | l := logger.NewLogger("DEBUG")
17 |
18 | handler := middleware.Logging(next, l)
19 |
20 | req := httptest.NewRequest("GET", "https://", nil)
21 |
22 | handler.ServeHTTP(httptest.NewRecorder(), req)
23 | }
24 |
--------------------------------------------------------------------------------
/internal/parse/parse.go:
--------------------------------------------------------------------------------
1 | package parse
2 |
3 | import (
4 | "errors"
5 | "path/filepath"
6 |
7 | "github.com/goccy/go-yaml"
8 | "github.com/neotoolkit/openapi"
9 |
10 | "github.com/neotoolkit/dummy/internal/api"
11 | "github.com/neotoolkit/dummy/internal/read"
12 | )
13 |
14 | type SpecType string
15 |
16 | const (
17 | OpenAPI SpecType = "OpenAPI"
18 | GraphQL SpecType = "GraphQL"
19 | Unknown SpecType = "Unknown"
20 | )
21 |
22 | var ErrEmptySpecTypePath = errors.New("empty spec type path")
23 |
24 | // SpecTypeError -.
25 | type SpecTypeError struct {
26 | Path string
27 | }
28 |
29 | // Error -.
30 | func (e *SpecTypeError) Error() string {
31 | return "specification type not implemented, path: " + e.Path
32 | }
33 |
34 | // SpecFileError -.
35 | type SpecFileError struct {
36 | Path string
37 | }
38 |
39 | // Error -.
40 | func (e *SpecFileError) Error() string {
41 | return e.Path + " without format"
42 | }
43 |
44 | // Parse -.
45 | func Parse(path string) (api.API, error) {
46 | file, err := read.Read(path)
47 | if err != nil {
48 | return api.API{}, err
49 | }
50 |
51 | specType, err := GetSpecType(path)
52 | if err != nil {
53 | return api.API{}, err
54 | }
55 |
56 | switch specType {
57 | case OpenAPI:
58 | oapi, err := openapi.Parse(file)
59 | if err != nil {
60 | return api.API{}, err
61 | }
62 |
63 | b := &api.Builder{
64 | OpenAPI: oapi,
65 | }
66 |
67 | return b.Build()
68 | case GraphQL:
69 | return api.API{}, nil
70 | }
71 |
72 | return api.API{}, nil
73 | }
74 |
75 | // GetSpecType returns specification type for path
76 | func GetSpecType(path string) (SpecType, error) {
77 | if len(path) == 0 {
78 | return Unknown, ErrEmptySpecTypePath
79 | }
80 |
81 | p := filepath.Ext(path)
82 |
83 | if len(p) == 0 {
84 | return Unknown, &SpecFileError{Path: path}
85 | }
86 |
87 | file, err := read.Read(path)
88 | if err != nil {
89 | return Unknown, err
90 | }
91 |
92 | switch p {
93 | case ".yml", ".yaml":
94 | var oapi openapi.OpenAPI
95 |
96 | if err := yaml.Unmarshal(file, &oapi); err != nil || len(oapi.OpenAPI) == 0 {
97 | return Unknown, &SpecTypeError{Path: path}
98 | }
99 |
100 | return OpenAPI, nil
101 | case ".graphql":
102 | return GraphQL, nil
103 | default:
104 | return Unknown, &SpecFileError{Path: path}
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/internal/parse/parse_test.go:
--------------------------------------------------------------------------------
1 | package parse_test
2 |
3 | import (
4 | "errors"
5 | "io/fs"
6 | "sort"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/require"
10 |
11 | "github.com/neotoolkit/dummy/internal/api"
12 | "github.com/neotoolkit/dummy/internal/parse"
13 | )
14 |
15 | func TestSpecTypeError(t *testing.T) {
16 | got := &parse.SpecTypeError{
17 | Path: "test",
18 | }
19 |
20 | require.Equal(t, got.Error(), "specification type not implemented, path: test")
21 | }
22 |
23 | func TestSpecFileError(t *testing.T) {
24 | got := &parse.SpecFileError{
25 | Path: "test",
26 | }
27 |
28 | require.Equal(t, got.Error(), "test without format")
29 | }
30 |
31 | func TestParse(t *testing.T) {
32 | tests := []struct {
33 | name string
34 | path string
35 | want api.API
36 | err error
37 | }{
38 | {
39 | name: "empty path",
40 | path: "",
41 | want: api.API{},
42 | err: &fs.PathError{
43 | Op: "open",
44 | Path: "",
45 | Err: errors.New("no such file or directory"),
46 | },
47 | },
48 | {
49 | name: "file without format",
50 | path: "./testdata/openapi",
51 | want: api.API{},
52 | err: &parse.SpecFileError{
53 | Path: "./testdata/openapi",
54 | },
55 | },
56 | {
57 | name: "graphql",
58 | path: "./testdata/schema.graphql",
59 | want: api.API{},
60 | err: nil,
61 | },
62 | {
63 | name: "",
64 | path: "./testdata/empty-openapi.yml",
65 | want: api.API{},
66 | err: &parse.SpecTypeError{
67 | Path: "./testdata/empty-openapi.yml",
68 | },
69 | },
70 | {
71 | name: "",
72 | path: "./testdata/openapi.yml",
73 | want: api.API{},
74 | err: nil,
75 | },
76 | {
77 | name: "unknown format",
78 | path: "./testdata/api.raml",
79 | want: api.API{},
80 | err: &parse.SpecFileError{
81 | Path: "./testdata/api.raml",
82 | },
83 | },
84 | }
85 |
86 | for _, tc := range tests {
87 | t.Run(tc.name, func(t *testing.T) {
88 | got, err := parse.Parse(tc.path)
89 | if err != nil {
90 | require.EqualError(t, err, tc.err.Error())
91 | }
92 | require.Equal(t, tc.want, got)
93 | })
94 | }
95 | }
96 |
97 | func TestParse_YAML(t *testing.T) {
98 | expected := api.API{
99 | Operations: []api.Operation{
100 | {
101 | Method: "POST",
102 | Path: "/users",
103 | Body: map[string]api.FieldType{
104 | "id": {
105 | Required: true,
106 | Type: "string",
107 | },
108 | "firstName": {
109 | Required: true,
110 | Type: "string",
111 | },
112 | "lastName": {
113 | Required: true,
114 | Type: "string",
115 | },
116 | },
117 | Responses: []api.Response{
118 | {
119 | StatusCode: 201,
120 | MediaType: "application/json",
121 | Schema: api.ObjectSchema{
122 | Properties: map[string]api.Schema{
123 | "id": api.StringSchema{Example: "380ed0b7-eb21-4ad4-acd0-efa90cf69c6a"},
124 | "firstName": api.StringSchema{Example: "Larry"},
125 | "lastName": api.StringSchema{Example: "Page"},
126 | },
127 | Example: map[string]any{},
128 | },
129 | Examples: map[string]any{},
130 | },
131 | },
132 | },
133 | {
134 | Method: "GET",
135 | Path: "/users",
136 | Responses: []api.Response{
137 | {
138 | StatusCode: 200,
139 | MediaType: "application/json",
140 | Schema: api.ArraySchema{
141 | Type: api.ObjectSchema{
142 | Properties: map[string]api.Schema{
143 | "id": api.StringSchema{Example: "380ed0b7-eb21-4ad4-acd0-efa90cf69c6a"},
144 | "firstName": api.StringSchema{Example: "Larry"},
145 | "lastName": api.StringSchema{Example: "Page"},
146 | },
147 | Example: map[string]any{},
148 | },
149 | Example: []any{},
150 | },
151 | Example: []map[string]any{
152 | {
153 | "id": "e1afccea-5168-4735-84d4-cb96f6fb5d25",
154 | "firstName": "Elon",
155 | "lastName": "Musk",
156 | },
157 | {
158 | "id": "472063cc-4c83-11ec-81d3-0242ac130003",
159 | "firstName": "Sergey",
160 | "lastName": "Brin",
161 | },
162 | },
163 | Examples: map[string]any{},
164 | },
165 | },
166 | },
167 | {
168 | Method: "GET",
169 | Path: "/users/{userId}",
170 | Responses: []api.Response{
171 | {
172 | StatusCode: 200,
173 | MediaType: "application/json",
174 | Schema: api.ObjectSchema{
175 | Properties: map[string]api.Schema{
176 | "id": api.StringSchema{Example: "380ed0b7-eb21-4ad4-acd0-efa90cf69c6a"},
177 | "firstName": api.StringSchema{Example: "Larry"},
178 | "lastName": api.StringSchema{Example: "Page"},
179 | },
180 | Example: map[string]any{},
181 | },
182 | Examples: map[string]any{},
183 | },
184 | },
185 | },
186 | },
187 | }
188 |
189 | openapi, err := parse.Parse("testdata/openapi3.yml")
190 |
191 | require.NoError(t, err)
192 | require.Equalf(t, testable(t, expected), testable(t, openapi), `parsed schema from "testdata/openapi3.yml"`)
193 | }
194 |
195 | func testable(t *testing.T, api api.API) api.API {
196 | t.Helper()
197 |
198 | sort.Slice(api.Operations, func(i, j int) bool {
199 | a, b := api.Operations[i], api.Operations[j]
200 |
201 | if a.Method > b.Method {
202 | return false
203 | }
204 |
205 | if a.Method < b.Method {
206 | return true
207 | }
208 |
209 | return a.Path < b.Path
210 | })
211 |
212 | return api
213 | }
214 |
215 | func TestGetSpecType(t *testing.T) {
216 | tests := []struct {
217 | name string
218 | path string
219 | want parse.SpecType
220 | err error
221 | }{
222 | {
223 | name: "",
224 | path: "",
225 | want: parse.Unknown,
226 | err: parse.ErrEmptySpecTypePath,
227 | },
228 | {
229 | name: "",
230 | path: "./testdata/openapi3",
231 | want: parse.Unknown,
232 | err: &parse.SpecFileError{
233 | Path: "./testdata/openapi3",
234 | },
235 | },
236 | {
237 | name: "",
238 | path: "./testdata/openapi3.yml",
239 | want: parse.OpenAPI,
240 | err: nil,
241 | },
242 | {
243 | name: "",
244 | path: "./testdata/api.raml",
245 | want: parse.Unknown,
246 | err: &parse.SpecFileError{
247 | Path: "./testdata/api.raml",
248 | },
249 | },
250 | {
251 | name: "",
252 | path: "./testdata/schema.graphql",
253 | want: parse.GraphQL,
254 | err: nil,
255 | },
256 | }
257 |
258 | for _, tc := range tests {
259 | t.Run(tc.name, func(t *testing.T) {
260 | got, err := parse.GetSpecType(tc.path)
261 | if err != nil {
262 | require.EqualError(t, err, tc.err.Error())
263 | }
264 | require.Equal(t, tc.want, got)
265 | })
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/internal/parse/testdata/api.raml:
--------------------------------------------------------------------------------
1 | #%RAML 1.0
2 | ---
3 |
--------------------------------------------------------------------------------
/internal/parse/testdata/empty-openapi.yml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neotoolkit/dummy/5a873434ccc69ba574404ace33f26e703e3b7600/internal/parse/testdata/empty-openapi.yml
--------------------------------------------------------------------------------
/internal/parse/testdata/openapi:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Users dummy API
4 | version: 0.1.0
5 | paths:
6 | /users:
7 | post:
8 | requestBody:
9 | required: true
10 | content:
11 | application/json:
12 | schema:
13 | $ref: "#/components/schemas/User"
14 | responses:
15 | '201':
16 | description: ''
17 | content:
18 | application/json:
19 | schema:
20 | $ref: '#/components/schemas/User'
21 | get:
22 | responses:
23 | '200':
24 | description: ''
25 | content:
26 | application/json:
27 | schema:
28 | type: array
29 | items:
30 | $ref: '#/components/schemas/User'
31 | example:
32 | - id: e1afccea-5168-4735-84d4-cb96f6fb5d25
33 | firstName: Elon
34 | lastName: Musk
35 | - id: 472063cc-4c83-11ec-81d3-0242ac130003
36 | firstName: Sergey
37 | lastName: Brin
38 |
39 | /users/{userId}:
40 | get:
41 | parameters:
42 | - in: path
43 | name: userId
44 | description: ''
45 | required: true
46 | schema:
47 | type: string
48 | responses:
49 | '200':
50 | description: ''
51 | content:
52 | application/json:
53 | schema:
54 | $ref: '#/components/schemas/User'
55 |
56 | components:
57 | schemas:
58 | User:
59 | type: object
60 | required:
61 | - id
62 | - firstName
63 | - lastName
64 | properties:
65 | id:
66 | type: string
67 | format: uuid
68 | example: 380ed0b7-eb21-4ad4-acd0-efa90cf69c6a
69 | firstName:
70 | type: string
71 | example: Larry
72 | lastName:
73 | type: string
74 | example: Page
75 |
--------------------------------------------------------------------------------
/internal/parse/testdata/openapi.yml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Users dummy API
4 | version: 0.1.0
5 |
--------------------------------------------------------------------------------
/internal/parse/testdata/openapi3.yml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | title: Users dummy API
4 | version: 0.1.0
5 | paths:
6 | /users:
7 | post:
8 | requestBody:
9 | required: true
10 | content:
11 | application/json:
12 | schema:
13 | $ref: "#/components/schemas/User"
14 | responses:
15 | '201':
16 | description: ''
17 | content:
18 | application/json:
19 | schema:
20 | $ref: '#/components/schemas/User'
21 | get:
22 | responses:
23 | '200':
24 | description: ''
25 | content:
26 | application/json:
27 | schema:
28 | type: array
29 | items:
30 | $ref: '#/components/schemas/User'
31 | example:
32 | - id: e1afccea-5168-4735-84d4-cb96f6fb5d25
33 | firstName: Elon
34 | lastName: Musk
35 | - id: 472063cc-4c83-11ec-81d3-0242ac130003
36 | firstName: Sergey
37 | lastName: Brin
38 |
39 | /users/{userId}:
40 | get:
41 | parameters:
42 | - in: path
43 | name: userId
44 | description: ''
45 | required: true
46 | schema:
47 | type: string
48 | responses:
49 | '200':
50 | description: ''
51 | content:
52 | application/json:
53 | schema:
54 | $ref: '#/components/schemas/User'
55 |
56 | components:
57 | schemas:
58 | User:
59 | type: object
60 | required:
61 | - id
62 | - firstName
63 | - lastName
64 | properties:
65 | id:
66 | type: string
67 | format: uuid
68 | example: 380ed0b7-eb21-4ad4-acd0-efa90cf69c6a
69 | firstName:
70 | type: string
71 | example: Larry
72 | lastName:
73 | type: string
74 | example: Page
75 |
--------------------------------------------------------------------------------
/internal/parse/testdata/schema.graphql:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/neotoolkit/dummy/5a873434ccc69ba574404ace33f26e703e3b7600/internal/parse/testdata/schema.graphql
--------------------------------------------------------------------------------
/internal/pkg/logger/logger.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | type Logger interface {
4 | Debug(msg string)
5 | Info(msg string)
6 | Infof(msg string, args ...any)
7 | Infow(msg string, w ...any)
8 | Warn(msg string)
9 | Error(msg string)
10 | Errorf(msg string, args ...any)
11 | Errorw(msg string, w ...any)
12 | }
13 |
--------------------------------------------------------------------------------
/internal/pkg/pathfmt/pathfmt.go:
--------------------------------------------------------------------------------
1 | package pathfmt
2 |
3 | import "strings"
4 |
5 | // RemoveTrailingSlash returns path without trailing slash
6 | func RemoveTrailingSlash(path string) string {
7 | if len(path) > 0 && path[len(path)-1] == '/' {
8 | return path[0 : len(path)-1]
9 | }
10 |
11 | return path
12 | }
13 |
14 | // RemoveFragment - clear url from reference in path
15 | func RemoveFragment(path string) string {
16 | return RemoveTrailingSlash(strings.Split(path, "#")[0])
17 | }
18 |
--------------------------------------------------------------------------------
/internal/pkg/pathfmt/pathfmt_test.go:
--------------------------------------------------------------------------------
1 | package pathfmt_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/neotoolkit/dummy/internal/pkg/pathfmt"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestRemoveTrailingSlash(t *testing.T) {
11 | tests := []struct {
12 | name string
13 | path string
14 | want string
15 | }{
16 | {
17 | name: "",
18 | path: "",
19 | want: "",
20 | },
21 | {
22 | name: "",
23 | path: "/",
24 | want: "",
25 | },
26 | {
27 | name: "",
28 | path: "path/",
29 | want: "path",
30 | },
31 | }
32 |
33 | for _, tc := range tests {
34 | t.Run(tc.name, func(t *testing.T) {
35 | got := pathfmt.RemoveTrailingSlash(tc.path)
36 |
37 | require.Equal(t, tc.want, got)
38 | })
39 | }
40 | }
41 |
42 | func TestRemoveFragment(t *testing.T) {
43 | tests := []struct {
44 | name string
45 | path string
46 | want string
47 | }{
48 | {
49 | name: "",
50 | path: "",
51 | want: "",
52 | },
53 | {
54 | name: "",
55 | path: "/",
56 | want: "",
57 | },
58 | {
59 | name: "",
60 | path: "/#",
61 | want: "",
62 | },
63 | }
64 |
65 | for _, tc := range tests {
66 | t.Run(tc.name, func(t *testing.T) {
67 | got := pathfmt.RemoveFragment(tc.path)
68 |
69 | require.Equal(t, tc.want, got)
70 | })
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/internal/read/read.go:
--------------------------------------------------------------------------------
1 | package read
2 |
3 | import (
4 | "io/ioutil"
5 | "net/http"
6 | "strings"
7 | )
8 |
9 | // Read -.
10 | func Read(path string) ([]byte, error) {
11 | if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
12 | return url(path)
13 | }
14 |
15 | return file(path)
16 | }
17 |
18 | func url(url string) ([]byte, error) {
19 | resp, err := http.Get(url)
20 | if err != nil {
21 | return nil, err
22 | }
23 | defer resp.Body.Close()
24 |
25 | body, err := ioutil.ReadAll(resp.Body)
26 | if err != nil {
27 | return nil, err
28 | }
29 |
30 | return body, nil
31 | }
32 |
33 | func file(path string) ([]byte, error) {
34 | file, err := ioutil.ReadFile(path)
35 | if err != nil {
36 | return nil, err
37 | }
38 |
39 | return file, nil
40 | }
41 |
--------------------------------------------------------------------------------
/internal/read/read_test.go:
--------------------------------------------------------------------------------
1 | package read_test
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io/fs"
7 | "net/http"
8 | "net/http/httptest"
9 | "net/url"
10 | "testing"
11 |
12 | "github.com/stretchr/testify/require"
13 |
14 | "github.com/neotoolkit/dummy/internal/read"
15 | )
16 |
17 | func TestRead(t *testing.T) {
18 | mux := http.NewServeMux()
19 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
20 | w.Header().Set("Content-Type", "application/json")
21 | fmt.Fprintln(w, `{
22 | "openapi": "3.0.3",
23 | "info": {
24 | "title": "Test dummy API",
25 | "version": "0.1.0"
26 | },
27 | "paths": {
28 | "/healthz": {
29 | "get": {
30 | "responses": {
31 | "201": {
32 | "description": ""
33 | }
34 | }
35 | }
36 | }
37 | }
38 | }`)
39 | })
40 | mux.HandleFunc("/error", func(w http.ResponseWriter, r *http.Request) {
41 | w.Header().Add("Content-Length", "50")
42 | // return less bytes, which will result in an "unexpected EOF" from ioutil.ReadAll()
43 | fmt.Fprintln(w, []byte("a"))
44 | })
45 |
46 | ts := httptest.NewServer(mux)
47 | defer ts.Close()
48 |
49 | tests := []struct {
50 | name string
51 | path string
52 | err error
53 | }{
54 | {
55 | name: "empty URL",
56 | path: "https://",
57 | err: &url.Error{
58 | Op: "Get",
59 | URL: "https:",
60 | Err: errors.New("http: no Host in request URL"),
61 | },
62 | },
63 | {
64 | name: "read from URL error",
65 | path: ts.URL + "/error",
66 | err: errors.New("unexpected EOF"),
67 | },
68 | {
69 | name: "read from URL",
70 | path: ts.URL,
71 | err: nil,
72 | },
73 | {
74 | name: "read from file",
75 | path: "./testdata/openapi3.yml",
76 | err: nil,
77 | },
78 | {
79 | name: "empty file",
80 | path: "",
81 | err: &fs.PathError{
82 | Op: "open",
83 | Path: "",
84 | Err: errors.New("no such file or directory"),
85 | },
86 | },
87 | }
88 |
89 | for _, tc := range tests {
90 | t.Run(tc.name, func(t *testing.T) {
91 | got, err := read.Read(tc.path)
92 | if err != nil {
93 | require.EqualError(t, err, tc.err.Error())
94 | }
95 |
96 | require.IsType(t, []byte{}, got)
97 | })
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/internal/read/testdata/openapi3.yml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 |
3 | info:
4 | title: Users dummy API
5 | version: 0.1.0
6 |
7 | paths:
8 | /users:
9 | post:
10 | requestBody:
11 | required: true
12 | content:
13 | application/json:
14 | schema:
15 | $ref: "#/components/schemas/User"
16 | responses:
17 | '201':
18 | description: ''
19 | content:
20 | application/json:
21 | schema:
22 | $ref: '#/components/schemas/User'
23 | example:
24 | id: e1afccea-5168-4735-84d4-cb96f6fb5d25
25 | firstName: Elon
26 | lastName: Musk
27 |
28 | components:
29 | schemas:
30 | User:
31 | type: object
32 | required:
33 | - id
34 | - firstName
35 | - lastName
36 | properties:
37 | id:
38 | type: string
39 | format: uuid
40 | firstName:
41 | type: string
42 | lastName:
43 | type: string
44 |
--------------------------------------------------------------------------------
/internal/server/handler.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "io"
7 | "net/http"
8 |
9 | "github.com/neotoolkit/dummy/internal/api"
10 | "github.com/neotoolkit/dummy/internal/logger"
11 | "github.com/neotoolkit/dummy/internal/pkg/pathfmt"
12 | )
13 |
14 | // Handlers -.
15 | type Handlers struct {
16 | API api.API
17 | Logger *logger.Logger
18 | }
19 |
20 | // NewHandlers returns a new instance of Handlers
21 | func NewHandlers(api api.API, l *logger.Logger) Handlers {
22 | return Handlers{
23 | API: api,
24 | Logger: l,
25 | }
26 | }
27 |
28 | // Handler -.
29 | func (s *Server) Handler(w http.ResponseWriter, r *http.Request) {
30 | if setStatusCode(w, r.Header.Get("X-Set-Status-Code")) {
31 | return
32 | }
33 |
34 | w.Header().Set("Content-Type", "application/json")
35 |
36 | p := pathfmt.RemoveFragment(r.URL.Path)
37 |
38 | response, ok, err := s.Handlers.Get(p, r.Method, r.Body)
39 | if ok {
40 | if _, ok := err.(*json.SyntaxError); ok || errors.Is(err, api.ErrEmptyRequireField) || err == io.EOF {
41 | w.WriteHeader(http.StatusBadRequest)
42 |
43 | return
44 | }
45 |
46 | w.WriteHeader(response.StatusCode)
47 | resp := response.ExampleValue(r.Header.Get("X-Example"))
48 |
49 | if nil == resp {
50 | return
51 | }
52 |
53 | bytes, err := json.Marshal(resp)
54 | if err != nil {
55 | s.Logger.Errorf("serialize response error: %v", err)
56 | }
57 |
58 | if _, err := w.Write(bytes); err != nil {
59 | s.Logger.Errorf("write response error: %v", err)
60 | }
61 |
62 | return
63 | }
64 |
65 | w.WriteHeader(http.StatusNotFound)
66 | }
67 |
68 | // Get -.
69 | func (h Handlers) Get(path, method string, body io.ReadCloser) (api.Response, bool, error) {
70 | response, err := h.API.FindResponse(api.FindResponseParams{
71 | Path: path,
72 | Method: method,
73 | Body: body,
74 | })
75 | if err != nil {
76 | if errors.Is(err, api.ErrEmptyRequireField) {
77 | return api.Response{}, true, err
78 | }
79 |
80 | if err == io.EOF {
81 | return api.Response{}, true, err
82 | }
83 |
84 | if _, ok := err.(*json.SyntaxError); ok {
85 | return api.Response{}, true, err
86 | }
87 |
88 | return api.Response{}, false, err
89 | }
90 |
91 | return response, true, nil
92 | }
93 |
94 | func setStatusCode(w http.ResponseWriter, statusCode string) bool {
95 | switch statusCode {
96 | case "500":
97 | w.WriteHeader(http.StatusInternalServerError)
98 |
99 | return true
100 | default:
101 | return false
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/internal/server/handler_test.go:
--------------------------------------------------------------------------------
1 | package server_test
2 |
3 | import (
4 | "github.com/neotoolkit/dummy/internal/api"
5 | "github.com/neotoolkit/dummy/internal/logger"
6 | "github.com/neotoolkit/dummy/internal/server"
7 | "github.com/stretchr/testify/require"
8 | "testing"
9 | )
10 |
11 | func TestNewHandlers(t *testing.T) {
12 | h := server.NewHandlers(api.API{}, &logger.Logger{})
13 |
14 | require.IsType(t, server.Handlers{}, h)
15 | }
16 |
17 | func TestNewHandlers_Get(t *testing.T) {
18 | h := server.NewHandlers(api.API{}, &logger.Logger{})
19 |
20 | resp, ok, err := h.Get("", "", nil)
21 |
22 | require.Equal(t, api.Response{}, resp)
23 | require.Equal(t, false, ok)
24 | require.NotNil(t, err)
25 | }
26 |
--------------------------------------------------------------------------------
/internal/server/server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "context"
5 | "net/http"
6 |
7 | "github.com/neotoolkit/dummy/internal/config"
8 | "github.com/neotoolkit/dummy/internal/middleware"
9 | "github.com/neotoolkit/dummy/internal/pkg/logger"
10 | )
11 |
12 | // Server is struct for Server
13 | type Server struct {
14 | Config config.Server
15 | Server *http.Server
16 | Logger logger.Logger
17 | Handlers Handlers
18 | }
19 |
20 | // NewServer returns a new instance of Server instance
21 | func NewServer(config config.Server, l logger.Logger, h Handlers) *Server {
22 | return &Server{
23 | Config: config,
24 | Logger: l,
25 | Handlers: h,
26 | }
27 | }
28 |
29 | // Run -.
30 | func (s *Server) Run() error {
31 | mux := http.NewServeMux()
32 |
33 | mux.HandleFunc("/", s.Handler)
34 |
35 | handler := middleware.Logging(mux, s.Logger)
36 |
37 | s.Server = &http.Server{
38 | Addr: ":" + s.Config.Port,
39 | Handler: handler,
40 | }
41 |
42 | s.Logger.Infof("Running mock server on %s port", s.Config.Port)
43 |
44 | err := s.Server.ListenAndServe()
45 | if err != nil {
46 | return err
47 | }
48 |
49 | return nil
50 | }
51 |
52 | func (s *Server) Stop(ctx context.Context) error {
53 | return s.Server.Shutdown(ctx)
54 | }
55 |
--------------------------------------------------------------------------------
/internal/server/server_test.go:
--------------------------------------------------------------------------------
1 | package server_test
2 |
3 | import (
4 | "github.com/neotoolkit/dummy/internal/config"
5 | "github.com/neotoolkit/dummy/internal/logger"
6 | "github.com/neotoolkit/dummy/internal/server"
7 | "github.com/stretchr/testify/require"
8 | "testing"
9 | )
10 |
11 | func TestNewServer(t *testing.T) {
12 | s := server.NewServer(config.Server{}, &logger.Logger{}, server.Handlers{})
13 |
14 | require.IsType(t, &server.Server{}, s)
15 | }
16 |
--------------------------------------------------------------------------------
/test/dummy_test.go:
--------------------------------------------------------------------------------
1 | package test_test
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "testing"
7 |
8 | "github.com/lamoda/gonkey/runner"
9 |
10 | "github.com/neotoolkit/dummy/internal/config"
11 | "github.com/neotoolkit/dummy/internal/logger"
12 | "github.com/neotoolkit/dummy/internal/parse"
13 | "github.com/neotoolkit/dummy/internal/server"
14 | )
15 |
16 | func TestDummy(t *testing.T) {
17 | api, err := parse.Parse("./testdata/openapi.yml")
18 | if err != nil {
19 | t.Fatal(err)
20 | }
21 |
22 | s := new(server.Server)
23 | conf := config.NewConfig()
24 | s.Config = conf.Server
25 | s.Handlers.API = api
26 | s.Logger = logger.NewLogger(conf.Logger.Level)
27 |
28 | mux := http.NewServeMux()
29 | mux.HandleFunc("/", s.Handler)
30 | newServer := httptest.NewServer(mux)
31 |
32 | runner.RunWithTesting(t, &runner.RunWithTestingParams{
33 | Server: newServer,
34 | TestsDir: "./testdata/cases.yml",
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/test/testdata/cases.yml:
--------------------------------------------------------------------------------
1 | - name: Create user. Bad request. Empty lastName
2 | method: POST
3 | path: /users
4 |
5 | request: |
6 | {
7 | "firstName": "Elon"
8 | }
9 |
10 | response:
11 | 400: |
12 |
13 | - name: Create user. Bad request. Empty firstName
14 | method: POST
15 | path: /users
16 |
17 | request: |
18 | {
19 | "lastName": "Musk"
20 | }
21 |
22 | response:
23 | 400: |
24 |
25 | - name: Create user
26 | method: POST
27 | path: /users
28 |
29 | request: |
30 | {
31 | "firstName": "Elon",
32 | "lastName": "Musk"
33 | }
34 |
35 | response:
36 | 201: |
37 | {
38 | "id":"e1afccea-5168-4735-84d4-cb96f6fb5d25",
39 | "firstName":"Elon",
40 | "lastName":"Musk"
41 | }
42 |
43 | - name: Get users
44 | method: GET
45 | path: /users
46 |
47 | response:
48 | 200: |
49 | [
50 | {
51 | "id": "e1afccea-5168-4735-84d4-cb96f6fb5d25",
52 | "firstName": "Elon",
53 | "lastName": "Musk"
54 | },
55 | {
56 | "id":"472063cc-4c83-11ec-81d3-0242ac130003",
57 | "firstName":"Sergey",
58 | "lastName":"Brin"
59 | }
60 | ]
61 |
62 | - name: Get user by ID
63 | method: GET
64 | path: /users/e1afccea-5168-4735-84d4-cb96f6fb5d25
65 |
66 | response:
67 | 200: |
68 | {
69 | "id":"e1afccea-5168-4735-84d4-cb96f6fb5d25",
70 | "firstName":"Elon",
71 | "lastName":"Musk"
72 | }
73 |
74 | - name: Update user. Bad request. Empty lastName
75 | method: PUT
76 | path: /users/e1afccea-5168-4735-84d4-cb96f6fb5d25
77 |
78 | request: |
79 | {
80 | "firstName": "Elon"
81 | }
82 |
83 | response:
84 | 400: |
85 |
86 | - name: Update user. Bad request. Empty firstName
87 | method: PUT
88 | path: /users/e1afccea-5168-4735-84d4-cb96f6fb5d25
89 |
90 | request: |
91 | {
92 | "lastName": "Musk"
93 | }
94 |
95 | response:
96 | 400: |
97 |
98 | - name: Update user
99 | method: PUT
100 | path: /users/e1afccea-5168-4735-84d4-cb96f6fb5d25
101 |
102 | request: |
103 | {
104 | "firstName": "Elon",
105 | "lastName": "Musk"
106 | }
107 |
108 | response:
109 | 200: |
110 | {
111 | "id":"e1afccea-5168-4735-84d4-cb96f6fb5d25",
112 | "firstName":"Elon",
113 | "lastName":"Musk"
114 | }
115 |
116 | - name: Update user. Bad request. Empty lastName
117 | method: PATCH
118 | path: /users/e1afccea-5168-4735-84d4-cb96f6fb5d25
119 |
120 | request: |
121 | {
122 | "firstName": "Elon"
123 | }
124 |
125 | response:
126 | 400: |
127 |
128 | - name: Update user. Bad request. Empty firstName
129 | method: PATCH
130 | path: /users/e1afccea-5168-4735-84d4-cb96f6fb5d25
131 |
132 | request: |
133 | {
134 | "lastName": "Musk"
135 | }
136 |
137 | response:
138 | 400: |
139 |
140 | - name: Update user
141 | method: PATCH
142 | path: /users/e1afccea-5168-4735-84d4-cb96f6fb5d25
143 |
144 | request: |
145 | {
146 | "firstName": "Elon",
147 | "lastName": "Musk"
148 | }
149 |
150 | response:
151 | 200: |
152 | {
153 | "id":"e1afccea-5168-4735-84d4-cb96f6fb5d25",
154 | "firstName":"Elon",
155 | "lastName":"Musk"
156 | }
157 |
158 | - name: Delete user by ID
159 | method: DELETE
160 | path: /users/e1afccea-5168-4735-84d4-cb96f6fb5d25
161 |
162 | response:
163 | 204: |
164 |
165 | - name: Not Found
166 | method: GET
167 | path: /
168 |
169 | response:
170 | 404: |
171 |
172 | - name: Set status code
173 | method: GET
174 | path: /users
175 | headers:
176 | X-Set-Status-Code: 500
177 |
178 | response:
179 | 500: |
180 |
--------------------------------------------------------------------------------
/test/testdata/openapi.yml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 |
3 | info:
4 | title: Users dummy API
5 | version: 0.1.0
6 |
7 | paths:
8 | /users:
9 | post:
10 | requestBody:
11 | $ref: "#/components/requestBodies/UserBody"
12 | responses:
13 | '201':
14 | description: ''
15 | content:
16 | application/json:
17 | schema:
18 | $ref: '#/components/schemas/User'
19 | example:
20 | id: e1afccea-5168-4735-84d4-cb96f6fb5d25
21 | firstName: Elon
22 | lastName: Musk
23 | '500':
24 | description: ''
25 | get:
26 | responses:
27 | '200':
28 | description: ''
29 | content:
30 | application/json:
31 | schema:
32 | $ref: '#/components/schemas/Users'
33 | example:
34 | - id: e1afccea-5168-4735-84d4-cb96f6fb5d25
35 | firstName: Elon
36 | lastName: Musk
37 | - id: 472063cc-4c83-11ec-81d3-0242ac130003
38 | firstName: Sergey
39 | lastName: Brin
40 | /users/{userId}:
41 | get:
42 | parameters:
43 | - in: path
44 | name: userId
45 | description: ''
46 | required: true
47 | schema:
48 | type: string
49 | responses:
50 | '200':
51 | description: ''
52 | content:
53 | application/json:
54 | schema:
55 | $ref: '#/components/schemas/User'
56 | example:
57 | id: e1afccea-5168-4735-84d4-cb96f6fb5d25
58 | firstName: Elon
59 | lastName: Musk
60 | put:
61 | parameters:
62 | - in: path
63 | name: userId
64 | description: ''
65 | required: true
66 | schema:
67 | type: string
68 | requestBody:
69 | $ref: "#/components/requestBodies/UserBody"
70 | responses:
71 | '200':
72 | description: ''
73 | content:
74 | application/json:
75 | schema:
76 | $ref: '#/components/schemas/User'
77 | example:
78 | id: e1afccea-5168-4735-84d4-cb96f6fb5d25
79 | firstName: Elon
80 | lastName: Musk
81 | patch:
82 | parameters:
83 | - in: path
84 | name: userId
85 | description: ''
86 | required: true
87 | schema:
88 | type: string
89 | requestBody:
90 | $ref: "#/components/requestBodies/UserBody"
91 | responses:
92 | '200':
93 | description: ''
94 | content:
95 | application/json:
96 | schema:
97 | $ref: '#/components/schemas/User'
98 | example:
99 | id: e1afccea-5168-4735-84d4-cb96f6fb5d25
100 | firstName: Elon
101 | lastName: Musk
102 | delete:
103 | parameters:
104 | - in: path
105 | name: userId
106 | description: ''
107 | required: true
108 | schema:
109 | type: string
110 | responses:
111 | '204':
112 | description: ''
113 |
114 | components:
115 | schemas:
116 | User:
117 | type: object
118 | required:
119 | - id
120 | - firstName
121 | - lastName
122 | properties:
123 | id:
124 | type: string
125 | format: uuid
126 | example: e1afccea-5168-4735-84d4-cb96f6fb5d25
127 | firstName:
128 | type: string
129 | example: Elon
130 | lastName:
131 | type: string
132 | example: Musk
133 | Users:
134 | type: array
135 | items:
136 | $ref: '#/components/schemas/User'
137 |
138 | requestBodies:
139 | UserBody:
140 | content:
141 | application/json:
142 | schema:
143 | type: object
144 | required:
145 | - firstName
146 | - lastName
147 | properties:
148 | firstName:
149 | type: string
150 | lastName:
151 | type: string
152 |
--------------------------------------------------------------------------------