Repository: swaggo/swag
Branch: master
Commit: 652e82cfb75b
Files: 304
Total size: 1.1 MB
Directory structure:
gitextract_qin4zfzz/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── Bug_report.md
│ │ └── Feature_request.md
│ ├── actions/
│ │ └── danger/
│ │ └── Dockerfile
│ ├── main.workflow
│ └── workflows/
│ ├── ci.yml
│ └── docker.yml
├── .gitignore
├── .goreleaser.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── Makefile
├── PULL_REQUEST_TEMPLATE.md
├── README.md
├── README_pt.md
├── README_zh-CN.md
├── cmd/
│ └── swag/
│ └── main.go
├── const.go
├── doc.go
├── enums.go
├── enums_test.go
├── example/
│ ├── basic/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── main.go
│ │ └── web/
│ │ └── handler.go
│ ├── celler/
│ │ ├── README.md
│ │ ├── controller/
│ │ │ ├── accounts.go
│ │ │ ├── admin.go
│ │ │ ├── bottles.go
│ │ │ ├── controller.go
│ │ │ └── examples.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── httputil/
│ │ │ └── error.go
│ │ ├── main.go
│ │ └── model/
│ │ ├── account.go
│ │ ├── admin.go
│ │ ├── bottle.go
│ │ └── error.go
│ ├── go-module-support/
│ │ ├── docs/
│ │ │ ├── docs.go
│ │ │ ├── swagger.json
│ │ │ └── swagger.yaml
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── main.go
│ │ └── web/
│ │ └── handler.go
│ ├── markdown/
│ │ ├── admin.md
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── api.md
│ │ ├── docs/
│ │ │ ├── docs.go
│ │ │ ├── swagger.json
│ │ │ └── swagger.yaml
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── main.go
│ ├── object-map-example/
│ │ ├── controller/
│ │ │ ├── api.go
│ │ │ ├── controller.go
│ │ │ └── response.go
│ │ ├── docs/
│ │ │ ├── docs.go
│ │ │ ├── swagger.json
│ │ │ └── swagger.yaml
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── main.go
│ └── override/
│ ├── .swaggo
│ ├── docs/
│ │ ├── docs.go
│ │ ├── swagger.json
│ │ └── swagger.yaml
│ ├── handler.go
│ └── main.go
├── field_parser.go
├── field_parser_test.go
├── format/
│ ├── format.go
│ └── format_test.go
├── formatter.go
├── formatter_test.go
├── gen/
│ ├── gen.go
│ └── gen_test.go
├── generics.go
├── generics_test.go
├── go.mod
├── go.sum
├── golist.go
├── golist_test.go
├── license
├── operation.go
├── operation_test.go
├── package.go
├── packages.go
├── packages_test.go
├── parser.go
├── parser_test.go
├── parsergopackages.go
├── schema.go
├── schema_test.go
├── spec.go
├── spec_test.go
├── swagger.go
├── swagger_test.go
├── testdata/
│ ├── alias_import/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── data/
│ │ │ └── applicationresponse.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ └── types/
│ │ └── application.go
│ ├── alias_nested/
│ │ ├── cmd/
│ │ │ └── main/
│ │ │ └── main.go
│ │ ├── expected.json
│ │ └── pkg/
│ │ ├── bad/
│ │ │ └── data.go
│ │ └── good/
│ │ └── data.go
│ ├── alias_type/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── data/
│ │ │ └── alias.go
│ │ ├── main.go
│ │ └── types/
│ │ └── alias.go
│ ├── api.md
│ ├── code_examples/
│ │ ├── api/
│ │ │ └── api1.go
│ │ ├── broken.json
│ │ ├── example.json
│ │ └── main.go
│ ├── composition/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── common/
│ │ │ └── response.go
│ │ ├── expected.json
│ │ └── main.go
│ ├── conflict_name/
│ │ ├── api/
│ │ │ ├── api1.go
│ │ │ └── api2.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ ├── model/
│ │ │ └── model.go
│ │ └── model2/
│ │ └── model.go
│ ├── delims/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── expected.json
│ │ └── main.go
│ ├── deprecated_router/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── expected.json
│ │ └── main.go
│ ├── deps_having_invalid_pkg/
│ │ └── main.go
│ ├── description_line_continuation/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── expected.json
│ │ └── main.go
│ ├── duplicated/
│ │ ├── api/
│ │ │ └── api.go
│ │ └── main.go
│ ├── duplicated2/
│ │ ├── api/
│ │ │ └── api.go
│ │ └── main.go
│ ├── duplicated_function_scoped/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── main.go
│ │ └── other_api/
│ │ └── api.go
│ ├── enums/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── consts/
│ │ │ └── const.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ └── types/
│ │ └── model.go
│ ├── error/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── errors/
│ │ │ └── errors.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ └── web/
│ │ └── handler.go
│ ├── extensionsFail1.go
│ ├── extensionsFail2.go
│ ├── external_models/
│ │ ├── external/
│ │ │ └── model.go
│ │ └── main/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── expected.json
│ │ └── main.go
│ ├── fixes-432/
│ │ ├── a/
│ │ │ └── a.go
│ │ ├── b/
│ │ │ └── b.go
│ │ └── cmd/
│ │ └── main.go
│ ├── format_dst/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── main.go
│ │ └── web/
│ │ └── handler.go
│ ├── format_empty/
│ │ └── empty.go
│ ├── format_src/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── main.go
│ │ └── web/
│ │ └── handler.go
│ ├── format_test/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── main.go
│ │ └── web/
│ │ └── handler.go
│ ├── generics_arrays/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ ├── types/
│ │ │ └── post.go
│ │ └── web/
│ │ └── handler.go
│ ├── generics_basic/
│ │ ├── .swaggo
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ ├── types/
│ │ │ ├── post.go
│ │ │ └── string.go
│ │ └── web/
│ │ └── handler.go
│ ├── generics_function_scoped/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ └── types/
│ │ └── response.go
│ ├── generics_multi_level_nesting/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ └── web/
│ │ └── handler.go
│ ├── generics_names/
│ │ ├── api/
│ │ │ ├── api.go
│ │ │ └── api_alias_pkg.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ ├── types/
│ │ │ └── post.go
│ │ └── web/
│ │ └── handler.go
│ ├── generics_nested/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ ├── types/
│ │ │ └── post.go
│ │ └── web/
│ │ └── handler.go
│ ├── generics_package_alias/
│ │ ├── external/
│ │ │ ├── external1/
│ │ │ │ └── external.go
│ │ │ ├── external2/
│ │ │ │ └── external.go
│ │ │ ├── external3/
│ │ │ │ └── external.go
│ │ │ └── external4/
│ │ │ └── external.go
│ │ └── internal/
│ │ ├── api/
│ │ │ ├── api1.go
│ │ │ ├── api2.go
│ │ │ ├── api3.go
│ │ │ ├── api4.go
│ │ │ ├── api5.go
│ │ │ ├── api6.go
│ │ │ ├── api7.go
│ │ │ └── api8.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ ├── path1/
│ │ │ └── v1/
│ │ │ └── product.go
│ │ ├── path2/
│ │ │ └── v1/
│ │ │ └── product.go
│ │ └── path3/
│ │ └── v1/
│ │ └── product.go
│ ├── generics_property/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ ├── types/
│ │ │ └── post.go
│ │ └── web/
│ │ └── handler.go
│ ├── global_override/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── data/
│ │ │ └── applicationresponse.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ ├── othertypes/
│ │ │ └── application.go
│ │ └── types/
│ │ └── application.go
│ ├── global_security/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── expected.json
│ │ └── main.go
│ ├── golist/
│ │ ├── api/
│ │ │ ├── api.go
│ │ │ ├── foo.c
│ │ │ └── foo.h
│ │ └── main.go
│ ├── golist_disablemodule/
│ │ ├── api/
│ │ │ ├── api.go
│ │ │ ├── foo.c
│ │ │ └── foo.h
│ │ └── main.go
│ ├── golist_invalid/
│ │ └── main.go
│ ├── invalid_external_pkg/
│ │ ├── invalid/
│ │ │ └── normal.go
│ │ └── main.go
│ ├── json_field_string/
│ │ └── main.go
│ ├── main.go
│ ├── markdown.go
│ ├── nested/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── common/
│ │ │ └── data.go
│ │ ├── expected.json
│ │ └── main.go
│ ├── nested2/
│ │ ├── data.go
│ │ └── inner/
│ │ └── data.go
│ ├── non_exported_json_fields/
│ │ └── main.go
│ ├── param_structs/
│ │ └── structs.go
│ ├── pare_outside_dependencies/
│ │ └── cmd/
│ │ └── main.go
│ ├── parseExtension/
│ │ └── parseExtension.go
│ ├── pet/
│ │ ├── main.go
│ │ └── web/
│ │ └── handler.go
│ ├── quotes/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── api.md
│ │ ├── expected.json
│ │ └── main.go
│ ├── recursive_with_name/
│ │ └── main.go
│ ├── simple/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── cross/
│ │ │ └── test.go
│ │ ├── expected.json
│ │ ├── main.go
│ │ └── web/
│ │ └── handler.go
│ ├── simple2/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── main.go
│ │ └── web/
│ │ └── handler.go
│ ├── simple3/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── main.go
│ │ └── web/
│ │ └── handler.go
│ ├── simple_cgo/
│ │ ├── api/
│ │ │ └── api.go
│ │ └── main.go
│ ├── single_file_api/
│ │ └── main.go
│ ├── state/
│ │ ├── admin_expected.json
│ │ ├── api/
│ │ │ ├── api.go
│ │ │ └── api_user.go
│ │ ├── main.go
│ │ ├── user_expected.json
│ │ └── web/
│ │ └── handler.go
│ ├── struct_comment/
│ │ ├── api/
│ │ │ └── api.go
│ │ ├── main.go
│ │ └── web/
│ │ └── handler.go
│ ├── tags/
│ │ ├── apes.md
│ │ ├── api.md
│ │ ├── cats.md
│ │ └── main.go
│ ├── tags2/
│ │ ├── apes.md
│ │ ├── api.md
│ │ └── main.go
│ ├── tags_nonexistend_tag/
│ │ ├── apes.md
│ │ ├── api.md
│ │ └── main.go
│ ├── templated.go
│ └── users.md
├── types.go
├── utils.go
├── utils_test.go
└── version.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/Bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Your swag version**
e.g. 1.4.1
**Your go version**
e.g. 1.12.0
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser: [e.g. chrome, safari]
- Version: [e.g. 22]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/Feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/actions/danger/Dockerfile
================================================
FROM ruby:2.6
LABEL "com.github.actions.name"="Danger"
LABEL "com.github.actions.description"="Run Danger"
LABEL "com.github.actions.icon"="alert-triangle"
LABEL "com.github.actions.color"="yellow"
RUN apt-get update -qq && apt-get install -y build-essential p7zip unzip
RUN gem install danger -v '>= 5.10.3'
RUN gem install danger-checkstyle_format
ENTRYPOINT "danger"
CMD "--verbose"
================================================
FILE: .github/main.workflow
================================================
workflow "DangerPullRequest" {
on = "pull_request"
resolves = ["Danger"]
}
action "Danger" {
uses = "pei0804/GithubActions/danger@master"
secrets = ["GITHUB_TOKEN"]
}
================================================
FILE: .github/workflows/ci.yml
================================================
name: build
on:
push:
branches: [ master, v2 ]
pull_request:
branches: [ master, v2 ]
jobs:
test:
strategy:
matrix:
go: [ '1.21.x', '1.22.x', '1.23.x', '1.24.x', '1.25.x' ]
platform: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: deps
run: make deps
- name: static program analysis
run: make fmt-check vet
- name: build
run: make build
- name: test
run: make test
- name: coverage
run: bash <(curl -s https://codecov.io/bash)
================================================
FILE: .github/workflows/docker.yml
================================================
name: docker
on:
push:
tags:
- 'v*'
permissions:
contents: read
packages: write
jobs:
docker-build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Login to Github Packages
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/swaggo/swag
- name: Build image and push to GitHub Container Registry
id: docker_build
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{github.ref_name}}
labels: ${{ steps.meta.outputs.labels }}
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
================================================
FILE: .gitignore
================================================
dist
testdata/simple*/docs
testdata/quotes/docs
testdata/quotes/quotes.so
testdata/delims/docs
testdata/delims/delims.so
example/basic/docs/*
example/celler/docs/*
cover.out
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.idea
.vscode
# Etc
.DS_Store
/swag
/swag.exe
================================================
FILE: .goreleaser.yml
================================================
build:
main: cmd/swag/main.go
goos:
- linux
- darwin
goarch:
- amd64
- arm64
- 386
env:
- CGO_ENABLED=0
archives:
- id: foo
name_template: >-
{{ .ProjectName }}_
{{- .Version }}_
{{- if eq .Os "linux"}}Linux{{ else if eq .Os "darwin"}}Darwin{{ else }}{{ .Os }}{{ end }}_
{{- if eq .Arch "386" }}i386{{ else if eq .Arch "amd64" }}x86_64{{ else }}{{ .Arch }}{{ end }}
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [gitter.im/swaggo/swag](https://gitter.im/swaggo/swag).The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
When contributing to this repository, please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before making a change.
Please note we have a code of conduct, please follow it in all your interactions with the project.
## Pull Request Process
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request
Please make an issue first if the change is likely to increase.
================================================
FILE: Dockerfile
================================================
# Dockerfile References: https://docs.docker.com/engine/reference/builder/
# Start from the latest golang base image
FROM --platform=$BUILDPLATFORM golang:1.24-alpine as builder
# Set the Current Working Directory inside the container
WORKDIR /app
# Copy go mod and sum files
COPY go.mod go.sum ./
# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
RUN go mod download
# Copy the source from the current directory to the Working Directory inside the container
COPY . .
# Configure go compiler target platform
ARG TARGETOS
ARG TARGETARCH
ENV GOARCH=$TARGETARCH \
GOOS=$TARGETOS
# Build the Go app
RUN CGO_ENABLED=0 GOOS=linux go build -v -a -installsuffix cgo -o swag cmd/swag/main.go
######## Start a new stage from scratch #######
FROM --platform=$TARGETPLATFORM scratch
WORKDIR /code/
# Copy the Pre-built binary file from the previous stage
COPY --from=builder /app/swag /bin/swag
ENTRYPOINT ["/bin/swag"]
================================================
FILE: Makefile
================================================
GOCMD:=$(shell which go)
GOLINT:=$(shell which golint)
GOIMPORT:=$(shell which goimports)
GOFMT:=$(shell which gofmt)
GOBUILD:=$(GOCMD) build
GOINSTALL:=$(GOCMD) install
GOCLEAN:=$(GOCMD) clean
GOTEST:=$(GOCMD) test
GOMODTIDY:=$(GOCMD) mod tidy
GOGET:=$(GOCMD) get
GOLIST:=$(GOCMD) list
GOVET:=$(GOCMD) vet
GOPATH:=$(shell $(GOCMD) env GOPATH)
u := $(if $(update),-u)
BINARY_NAME:=swag
PACKAGES:=$(shell $(GOLIST) github.com/swaggo/swag github.com/swaggo/swag/cmd/swag github.com/swaggo/swag/gen github.com/swaggo/swag/format)
GOFILES:=$(shell find . -name "*.go" -type f)
all: test build
.PHONY: build
build: deps
$(GOBUILD) -o $(BINARY_NAME) ./cmd/swag
.PHONY: install
install: deps
$(GOINSTALL) ./cmd/swag
.PHONY: test
test:
echo "mode: count" > coverage.out
for PKG in $(PACKAGES); do \
$(GOCMD) test -v -covermode=count -coverprofile=profile.out $$PKG > tmp.out; \
cat tmp.out; \
if grep -q "^--- FAIL" tmp.out; then \
rm tmp.out; \
exit 1; \
elif grep -q "build failed" tmp.out; then \
rm tmp.out; \
exit; \
fi; \
if [ -f profile.out ]; then \
cat profile.out | grep -v "mode:" >> coverage.out; \
rm profile.out; \
fi; \
done
.PHONY: clean
clean:
$(GOCLEAN)
rm -f $(BINARY_NAME)
.PHONY: deps
deps:
$(GOMODTIDY)
.PHONY: vet
vet: deps
$(GOVET) $(PACKAGES)
.PHONY: fmt
fmt:
$(GOFMT) -s -w $(GOFILES)
.PHONY: fmt-check
fmt-check:
@diff=$$($(GOFMT) -s -d $(GOFILES)); \
if [ -n "$$diff" ]; then \
echo "Please run 'make fmt' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi;
.PHONY: view-covered
view-covered:
$(GOTEST) -coverprofile=cover.out $(TARGET)
$(GOCMD) tool cover -html=cover.out
================================================
FILE: PULL_REQUEST_TEMPLATE.md
================================================
**Describe the PR**
e.g. add cool parser.
**Relation issue**
e.g. https://github.com/swaggo/swag/pull/118/files
**Additional context**
Add any other context about the problem here.
================================================
FILE: README.md
================================================
# swag
🌍 *[English](README.md) ∙ [简体中文](README_zh-CN.md) ∙ [Português](README_pt.md)*
[](https://github.com/features/actions)
[](https://codecov.io/gh/swaggo/swag)
[](https://goreportcard.com/report/github.com/swaggo/swag)
[](https://godoc.org/github.com/swaggo/swag)
[](#backers)
[](#sponsors) [](https://app.fossa.io/projects/git%2Bgithub.com%2Fswaggo%2Fswag?ref=badge_shield)
[](https://github.com/swaggo/swag/releases)
Swag converts Go annotations to Swagger Documentation 2.0. We've created a variety of plugins for popular [Go web frameworks](#supported-web-frameworks). This allows you to quickly integrate with an existing Go project (using Swagger UI).
## Contents
- [Getting started](#getting-started)
- [Supported Web Frameworks](#supported-web-frameworks)
- [How to use it with Gin](#how-to-use-it-with-gin)
- [The swag formatter](#the-swag-formatter)
- [Implementation Status](#implementation-status)
- [Declarative Comments Format](#declarative-comments-format)
- [General API Info](#general-api-info)
- [API Operation](#api-operation)
- [Security](#security)
- [Examples](#examples)
- [Descriptions over multiple lines](#descriptions-over-multiple-lines)
- [User defined structure with an array type](#user-defined-structure-with-an-array-type)
- [Function scoped struct declaration](#function-scoped-struct-declaration)
- [Model composition in response](#model-composition-in-response)
- [Add request headers](#add-request-headers)
- [Add response headers](#add-response-headers)
- [Use multiple path params](#use-multiple-path-params)
- [Example value of struct](#example-value-of-struct)
- [SchemaExample of body](#schemaexample-of-body)
- [Description of struct](#description-of-struct)
- [Use swaggertype tag to supported custom type](#use-swaggertype-tag-to-supported-custom-type)
- [Use global overrides to support a custom type](#use-global-overrides-to-support-a-custom-type)
- [Use swaggerignore tag to exclude a field](#use-swaggerignore-tag-to-exclude-a-field)
- [Add extension info to struct field](#add-extension-info-to-struct-field)
- [Rename model to display](#rename-model-to-display)
- [How to use security annotations](#how-to-use-security-annotations)
- [Add a description for enum items](#add-a-description-for-enum-items)
- [Generate only specific docs file types](#generate-only-specific-docs-file-types)
- [How to use Go generic types](#how-to-use-generics)
- [About the Project](#about-the-project)
## Getting started
1. Add comments to your API source code, See [Declarative Comments Format](#declarative-comments-format).
2. Install swag by using:
```sh
go install github.com/swaggo/swag/cmd/swag@latest
```
To build from source you need [Go](https://golang.org/dl/) (1.19 or newer).
Alternatively you can run the docker image:
```sh
docker run --rm -v $(pwd):/code ghcr.io/swaggo/swag:latest
```
Or download a pre-compiled binary from the [release page](https://github.com/swaggo/swag/releases).
3. Run `swag init` in the project's root folder which contains the `main.go` file. This will parse your comments and generate the required files (`docs` folder and `docs/docs.go`).
```sh
swag init
```
Make sure to import the generated `docs/docs.go` so that your specific configuration gets `init`'ed. If your General API annotations do not live in `main.go`, you can let swag know with `-g` flag.
```go
import _ "example-module-name/docs"
```
```sh
swag init -g http/api.go
```
4. (optional) Use `swag fmt` format the SWAG comment. (Please upgrade to the latest version)
```sh
swag fmt
```
## swag cli
```sh
swag init -h
NAME:
swag init - Create docs.go
USAGE:
swag init [command options] [arguments...]
OPTIONS:
--quiet, -q Make the logger quiet. (default: false)
--generalInfo value, -g value Go file path in which 'swagger general API Info' is written (default: "main.go")
--dir value, -d value Directories you want to parse,comma separated and general-info file must be in the first one (default: "./")
--exclude value Exclude directories and files when searching, comma separated
--propertyStrategy value, -p value Property Naming Strategy like snakecase,camelcase,pascalcase (default: "camelcase")
--output value, -o value Output directory for all the generated files(swagger.json, swagger.yaml and docs.go) (default: "./docs")
--outputTypes value, --ot value Output types of generated files (docs.go, swagger.json, swagger.yaml) like go,json,yaml (default: "go,json,yaml")
--parseVendor Parse go files in 'vendor' folder, disabled by default (default: false)
--parseDependency, --pd Parse go files inside dependency folder, disabled by default (default: false)
--parseDependencyLevel, --pdl Enhancement of '--parseDependency', parse go files inside dependency folder, 0 disabled, 1 only parse models, 2 only parse operations, 3 parse all (default: 0)
--markdownFiles value, --md value Parse folder containing markdown files to use as description, disabled by default
--codeExampleFiles value, --cef value Parse folder containing code example files to use for the x-codeSamples extension, disabled by default
--parseInternal Parse go files in internal packages, disabled by default (default: false)
--generatedTime Generate timestamp at the top of docs.go, disabled by default (default: false)
--parseDepth value Dependency parse depth (default: 100)
--requiredByDefault Set validation required for all fields by default (default: false)
--instanceName value This parameter can be used to name different swagger document instances. It is optional.
--overridesFile value File to read global type overrides from. (default: ".swaggo")
--parseGoList Parse dependency via 'go list' (default: true)
--tags value, -t value A comma-separated list of tags to filter the APIs for which the documentation is generated.Special case if the tag is prefixed with the '!' character then the APIs with that tag will be excluded
--templateDelims value, --td value Provide custom delimiters for Go template generation. The format is leftDelim,rightDelim. For example: "[[,]]"
--collectionFormat value, --cf value Set default collection format (default: "csv")
--state value Initial state for the state machine (default: ""), @HostState in root file, @State in other files
--parseFuncBody Parse API info within body of functions in go files, disabled by default (default: false)
--help, -h show help (default: false)
```
```bash
swag fmt -h
NAME:
swag fmt - format swag comments
USAGE:
swag fmt [command options] [arguments...]
OPTIONS:
--dir value, -d value Directories you want to parse,comma separated and general-info file must be in the first one (default: "./")
--exclude value Exclude directories and files when searching, comma separated
--generalInfo value, -g value Go file path in which 'swagger general API Info' is written (default: "main.go")
--help, -h show help (default: false)
```
## Supported Web Frameworks
- [gin](http://github.com/swaggo/gin-swagger)
- [echo](http://github.com/swaggo/echo-swagger)
- [buffalo](https://github.com/swaggo/buffalo-swagger)
- [net/http](https://github.com/swaggo/http-swagger)
- [gorilla/mux](https://github.com/swaggo/http-swagger)
- [go-chi/chi](https://github.com/swaggo/http-swagger)
- [flamingo](https://github.com/i-love-flamingo/swagger)
- [fiber](https://github.com/gofiber/swagger)
- [atreugo](https://github.com/Nerzal/atreugo-swagger)
- [hertz](https://github.com/hertz-contrib/swagger)
## How to use it with Gin
Find the example source code [here](https://github.com/swaggo/swag/tree/master/example/celler).
Finish the steps in [Getting started](#getting-started)
1. After using `swag init` to generate Swagger 2.0 docs, import the following packages:
```go
import "github.com/swaggo/gin-swagger" // gin-swagger middleware
import "github.com/swaggo/files" // swagger embed files
```
2. Add [General API](#general-api-info) annotations in `main.go` code:
```go
// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.basic BasicAuth
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api/
func main() {
r := gin.Default()
c := controller.NewController()
v1 := r.Group("/api/v1")
{
accounts := v1.Group("/accounts")
{
accounts.GET(":id", c.ShowAccount)
accounts.GET("", c.ListAccounts)
accounts.POST("", c.AddAccount)
accounts.DELETE(":id", c.DeleteAccount)
accounts.PATCH(":id", c.UpdateAccount)
accounts.POST(":id/images", c.UploadAccountImage)
}
//...
}
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run(":8080")
}
//...
```
Additionally some general API info can be set dynamically. The generated code package `docs` exports `SwaggerInfo` variable which we can use to set the title, description, version, host and base path programmatically. Example using Gin:
```go
package main
import (
"github.com/gin-gonic/gin"
"github.com/swaggo/files"
"github.com/swaggo/gin-swagger"
"./docs" // docs is generated by Swag CLI, you have to import it.
)
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
func main() {
// programmatically set swagger info
docs.SwaggerInfo.Title = "Swagger Example API"
docs.SwaggerInfo.Description = "This is a sample server Petstore server."
docs.SwaggerInfo.Version = "1.0"
docs.SwaggerInfo.Host = "petstore.swagger.io"
docs.SwaggerInfo.BasePath = "/v2"
docs.SwaggerInfo.Schemes = []string{"http", "https"}
r := gin.New()
// use ginSwagger middleware to serve the API docs
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run()
}
```
3. Add [API Operation](#api-operation) annotations in `controller` code
``` go
package controller
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/swaggo/swag/example/celler/httputil"
"github.com/swaggo/swag/example/celler/model"
)
// ShowAccount godoc
// @Summary Show an account
// @Description get string by ID
// @Tags accounts
// @Accept json
// @Produce json
// @Param id path int true "Account ID"
// @Success 200 {object} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts/{id} [get]
func (c *Controller) ShowAccount(ctx *gin.Context) {
id := ctx.Param("id")
aid, err := strconv.Atoi(id)
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
account, err := model.AccountOne(aid)
if err != nil {
httputil.NewError(ctx, http.StatusNotFound, err)
return
}
ctx.JSON(http.StatusOK, account)
}
// ListAccounts godoc
// @Summary List accounts
// @Description get accounts
// @Tags accounts
// @Accept json
// @Produce json
// @Param q query string false "name search by q" Format(email)
// @Success 200 {array} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts [get]
func (c *Controller) ListAccounts(ctx *gin.Context) {
q := ctx.Request.URL.Query().Get("q")
accounts, err := model.AccountsAll(q)
if err != nil {
httputil.NewError(ctx, http.StatusNotFound, err)
return
}
ctx.JSON(http.StatusOK, accounts)
}
//...
```
```console
swag init
```
4. Run your app, and browse to http://localhost:8080/swagger/index.html. You will see Swagger 2.0 Api documents as shown below:

## The swag formatter
The Swag Comments can be automatically formatted, just like 'go fmt'.
Find the result of formatting [here](https://github.com/swaggo/swag/tree/master/example/celler).
Usage:
```shell
swag fmt
```
Exclude folder:
```shell
swag fmt -d ./ --exclude ./internal
```
When using `swag fmt`, you need to ensure that you have a doc comment for the function to ensure correct formatting.
This is due to `swag fmt` indenting swag comments with tabs, which is only allowed *after* a standard doc comment.
For example, use
```go
// ListAccounts lists all existing accounts
//
// @Summary List accounts
// @Description get accounts
// @Tags accounts
// @Accept json
// @Produce json
// @Param q query string false "name search by q" Format(email)
// @Success 200 {array} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts [get]
func (c *Controller) ListAccounts(ctx *gin.Context) {
```
## Implementation Status
[Swagger 2.0 document](https://swagger.io/docs/specification/2-0/basic-structure/)
- [x] Basic Structure
- [x] API Host and Base Path
- [x] Paths and Operations
- [x] Describing Parameters
- [x] Describing Request Body
- [x] Describing Responses
- [x] MIME Types
- [x] Authentication
- [x] Basic Authentication
- [x] API Keys
- [x] Adding Examples
- [x] File Upload
- [x] Enums
- [x] Grouping Operations With Tags
- [ ] Swagger Extensions
# Declarative Comments Format
## General API Info
**Example**
[celler/main.go](https://github.com/swaggo/swag/blob/master/example/celler/main.go)
| annotation | description | example |
|-------------|--------------------------------------------|---------------------------------|
| title | **Required.** The title of the application.| // @title Swagger Example API |
| version | **Required.** Provides the version of the application API.| // @version 1.0 |
| description | A short description of the application. |// @description This is a sample server celler server. |
| tag.name | Name of a tag.| // @tag.name This is the name of the tag |
| tag.description | Description of the tag | // @tag.description Cool Description |
| tag.docs.url | Url of the external Documentation of the tag | // @tag.docs.url https://example.com|
| tag.docs.description | Description of the external Documentation of the tag| // @tag.docs.description Best example documentation |
| termsOfService | The Terms of Service for the API.| // @termsOfService http://swagger.io/terms/ |
| contact.name | The contact information for the exposed API.| // @contact.name API Support |
| contact.url | The URL pointing to the contact information. MUST be in the format of a URL. | // @contact.url http://www.swagger.io/support|
| contact.email| The email address of the contact person/organization. MUST be in the format of an email address.| // @contact.email support@swagger.io |
| license.name | **Required.** The license name used for the API.|// @license.name Apache 2.0|
| license.url | A URL to the license used for the API. MUST be in the format of a URL. | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html |
| host | The host (name or ip) serving the API. | // @host localhost:8080 |
| BasePath | The base path on which the API is served. | // @BasePath /api/v1 |
| accept | A list of MIME types the APIs can consume. Note that Accept only affects operations with a request body, such as POST, PUT and PATCH. Value MUST be as described under [Mime Types](#mime-types). | // @accept json |
| produce | A list of MIME types the APIs can produce. Value MUST be as described under [Mime Types](#mime-types). | // @produce json |
| query.collection.format | The default collection(array) param format in query,enums:csv,multi,pipes,tsv,ssv. If not set, csv is the default.| // @query.collection.format multi
| schemes | The transfer protocol for the operation that separated by spaces. | // @schemes http https |
| externalDocs.description | Description of the external document. | // @externalDocs.description OpenAPI |
| externalDocs.url | URL of the external document. | // @externalDocs.url https://swagger.io/resources/open-api/ |
| x-name | The extension key, must be start by x- and take only json value | // @x-example-key {"key": "value"} |
### Using markdown descriptions
When a short string in your documentation is insufficient, or you need images, code examples and things like that you may want to use markdown descriptions. In order to use markdown descriptions use the following annotations.
| annotation | description | example |
|-------------|--------------------------------------------|---------------------------------|
| title | **Required.** The title of the application.| // @title Swagger Example API |
| version | **Required.** Provides the version of the application API.| // @version 1.0 |
| description.markdown | A short description of the application. Parsed from the api.md file. This is an alternative to @description |// @description.markdown No value needed, this parses the description from api.md |
| tag.name | Name of a tag.| // @tag.name This is the name of the tag |
| tag.description.markdown | Description of the tag this is an alternative to tag.description. The description will be read from a file named like tagname.md | // @tag.description.markdown |
| tag.x-name | The extension key, must be start by x- and take only string value | // @x-example-key value |
## API Operation
**Example**
[celler/controller](https://github.com/swaggo/swag/tree/master/example/celler/controller)
| annotation | description |
|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| description | A verbose explanation of the operation behavior. |
| description.markdown | A short description of the application. The description will be read from a file. E.g. `@description.markdown details` will load `details.md` | // @description.file endpoint.description.markdown |
| id | A unique string used to identify the operation. Must be unique among all API operations. |
| tags | A list of tags to each API operation that separated by commas. |
| summary | A short summary of what the operation does. |
| accept | A list of MIME types the APIs can consume. Note that Accept only affects operations with a request body, such as POST, PUT and PATCH. Value MUST be as described under [Mime Types](#mime-types). |
| produce | A list of MIME types the APIs can produce. Value MUST be as described under [Mime Types](#mime-types). |
| param | Parameters that separated by spaces. `param name`,`param type`,`data type`,`is mandatory?`,`comment` `attribute(optional)` |
| security | [Security](#security) to each API operation. |
| success | Success response that separated by spaces. `return code or default`,`{param type}`,`data type`,`comment` |
| failure | Failure response that separated by spaces. `return code or default`,`{param type}`,`data type`,`comment` |
| response | As same as `success` and `failure` |
| header | Header in response that separated by spaces. `return code`,`{param type}`,`data type`,`comment` |
| router | Path definition that separated by spaces. `path`,`[httpMethod]` |
| deprecatedrouter | As same as router, but deprecated. |
| x-name | The extension key, must be start by x- and take only json value. |
| x-codeSample | Optional Markdown usage. take `file` as parameter. This will then search for a file named like the summary in the given folder. |
| deprecated | Mark endpoint as deprecated. |
## Mime Types
`swag` accepts all MIME Types which are in the correct format, that is, match `*/*`.
Besides that, `swag` also accepts aliases for some MIME Types as follows:
| Alias | MIME Type |
|-----------------------|-----------------------------------|
| json | application/json |
| xml | text/xml |
| plain | text/plain |
| html | text/html |
| mpfd | multipart/form-data |
| x-www-form-urlencoded | application/x-www-form-urlencoded |
| json-api | application/vnd.api+json |
| json-stream | application/x-json-stream |
| octet-stream | application/octet-stream |
| png | image/png |
| jpeg | image/jpeg |
| gif | image/gif |
| event-stream | text/event-stream |
## Param Type
- query
- path
- header
- body
- formData
## Data Type
- string (string)
- integer (int, uint, uint32, uint64)
- number (float32)
- boolean (bool)
- file (param data type when uploading)
- user defined struct
## Security
| annotation | description | parameters | example |
|------------|-------------|------------|---------|
| securitydefinitions.basic | [Basic](https://swagger.io/docs/specification/2-0/authentication/basic-authentication/) auth. | | // @securityDefinitions.basic BasicAuth |
| securitydefinitions.apikey | [API key](https://swagger.io/docs/specification/2-0/authentication/api-keys/) auth. | in, name, description | // @securityDefinitions.apikey ApiKeyAuth |
| securitydefinitions.oauth2.application | [OAuth2 application](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, scope, description | // @securitydefinitions.oauth2.application OAuth2Application |
| securitydefinitions.oauth2.implicit | [OAuth2 implicit](https://swagger.io/docs/specification/authentication/oauth2/) auth. | authorizationUrl, scope, description | // @securitydefinitions.oauth2.implicit OAuth2Implicit |
| securitydefinitions.oauth2.password | [OAuth2 password](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, scope, description | // @securitydefinitions.oauth2.password OAuth2Password |
| securitydefinitions.oauth2.accessCode | [OAuth2 access code](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, authorizationUrl, scope, description | // @securitydefinitions.oauth2.accessCode OAuth2AccessCode |
| parameters annotation | example |
|---------------------------------|-------------------------------------------------------------------------|
| in | // @in header |
| name | // @name Authorization |
| tokenUrl | // @tokenUrl https://example.com/oauth/token |
| authorizationurl | // @authorizationurl https://example.com/oauth/authorize |
| scope.hoge | // @scope.write Grants write access |
| description | // @description OAuth protects our entity endpoints |
## Attribute
```go
// @Param enumstring query string false "string enums" Enums(A, B, C)
// @Param enumint query int false "int enums" Enums(1, 2, 3)
// @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3)
// @Param string query string false "string valid" minlength(5) maxlength(10)
// @Param int query int false "int valid" minimum(1) maximum(10)
// @Param default query string false "string default" default(A)
// @Param example query string false "string example" example(string)
// @Param collection query []string false "string collection" collectionFormat(multi)
// @Param extensions query []string false "string collection" extensions(x-example=test,x-nullable)
```
It also works for the struct fields:
```go
type Foo struct {
Bar string `minLength:"4" maxLength:"16" example:"random string"`
Baz int `minimum:"10" maximum:"20" default:"15"`
Qux []string `enums:"foo,bar,baz"`
}
```
### Available
Field Name | Type | Description
---|:---:|---
validate | `string` | Determines the validation for the parameter. Possible values are: `required,optional`.
json | `string` | JSON tag options. The `omitempty` option will mark the field as not required.
default | * | Declares the value of the parameter that the server will use if none is provided, for example a "count" to control the number of results per page might default to 100 if not supplied by the client in the request. (Note: "default" has no meaning for required parameters.) See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2. Unlike JSON Schema this value MUST conform to the defined [`type`](#parameterType) for this parameter.
maximum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2.
minimum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3.
multipleOf | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.1.
maxLength | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.1.
minLength | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2.
enums | [\*] | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1.
format | `string` | The extending format for the previously mentioned [`type`](#parameterType). See [Data Type Formats](https://swagger.io/specification/v2/#dataTypeFormat) for further details.
collectionFormat | `string` |Determines the format of the array if type array is used. Possible values are:
- `csv` - comma separated values `foo,bar`.
- `ssv` - space separated values `foo bar`.
- `tsv` - tab separated values `foo\tbar`.
- `pipes` - pipe separated values
foo|bar. - `multi` - corresponds to multiple parameter instances instead of multiple values for a single instance `foo=bar&foo=baz`. This is valid only for parameters [`in`](#parameterIn) "query" or "formData".
Default value is `csv`.
example | * | Declares the example for the parameter value
extensions | `string` | Add extension to parameters.
### Future
Field Name | Type | Description
---|:---:|---
pattern | `string` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3.
maxItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2.
minItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3.
uniqueItems | `boolean` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4.
## Examples
### Descriptions over multiple lines
You can add descriptions spanning multiple lines in either the general api description or routes definitions like so:
```go
// @description This is the first line
// @description This is the second line
// @description And so forth.
```
### User defined structure with an array type
```go
// @Success 200 {array} model.Account <-- This is a user defined struct.
```
```go
package model
type Account struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"account name"`
}
```
### Function scoped struct declaration
You can declare your request response structs inside a function body.
You must have to follow the naming convention `.. `.
```go
package main
// @Param request body main.MyHandler.request true "query params"
// @Success 200 {object} main.MyHandler.response
// @Router /test [post]
func MyHandler() {
type request struct {
RequestField string
}
type response struct {
ResponseField string
}
}
```
### Model composition in response
```go
// JSONResult's data field will be overridden by the specific type proto.Order
@success 200 {object} jsonresult.JSONResult{data=proto.Order} "desc"
```
```go
type JSONResult struct {
Code int `json:"code" `
Message string `json:"message"`
Data interface{} `json:"data"`
}
type Order struct { //in `proto` package
Id uint `json:"id"`
Data interface{} `json:"data"`
}
```
- also support array of objects and primitive types as nested response
```go
@success 200 {object} jsonresult.JSONResult{data=[]proto.Order} "desc"
@success 200 {object} jsonresult.JSONResult{data=string} "desc"
@success 200 {object} jsonresult.JSONResult{data=[]string} "desc"
```
- overriding multiple fields. field will be added if not exists
```go
@success 200 {object} jsonresult.JSONResult{data1=string,data2=[]string,data3=proto.Order,data4=[]proto.Order} "desc"
```
- overriding deep-level fields
```go
type DeepObject struct { //in `proto` package
...
}
@success 200 {object} jsonresult.JSONResult{data1=proto.Order{data=proto.DeepObject},data2=[]proto.Order{data=[]proto.DeepObject}} "desc"
```
### Add request headers
```go
// @Param X-MyHeader header string true "MyHeader must be set for valid response"
// @Param X-API-VERSION header string true "API version eg.: 1.0"
```
### Add response headers
```go
// @Success 200 {string} string "ok"
// @failure 400 {string} string "error"
// @response default {string} string "other error"
// @Header 200 {string} Location "/entity/1"
// @Header 200,400,default {string} Token "token"
// @Header all {string} Token2 "token2"
```
### Use multiple path params
```go
/// ...
// @Param group_id path int true "Group ID"
// @Param account_id path int true "Account ID"
// ...
// @Router /examples/groups/{group_id}/accounts/{account_id} [get]
```
### Add multiple paths
```go
/// ...
// @Param group_id path int true "Group ID"
// @Param user_id path int true "User ID"
// ...
// @Router /examples/groups/{group_id}/user/{user_id}/address [put]
// @Router /examples/user/{user_id}/address [put]
```
### Example value of struct
```go
type Account struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"account name"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"`
}
```
### SchemaExample of body
```go
// @Param email body string true "message/rfc822" SchemaExample(Subject: Testmail\r\n\r\nBody Message\r\n)
```
### Description of struct
```go
// Account model info
// @Description User account information
// @Description with user id and username
type Account struct {
// ID this is userid
ID int `json:"id"`
Name string `json:"name"` // This is Name
}
```
[#708](https://github.com/swaggo/swag/issues/708) The parser handles only struct comments starting with `@Description` attribute.
But it writes all struct field comments as is.
So, generated swagger doc as follows:
```json
"Account": {
"type":"object",
"description": "User account information with user id and username"
"properties": {
"id": {
"type": "integer",
"description": "ID this is userid"
},
"name": {
"type":"string",
"description": "This is Name"
}
}
}
```
### Use swaggertype tag to supported custom type
[#201](https://github.com/swaggo/swag/issues/201#issuecomment-475479409)
```go
type TimestampTime struct {
time.Time
}
///implement encoding.JSON.Marshaler interface
func (t *TimestampTime) MarshalJSON() ([]byte, error) {
bin := make([]byte, 16)
bin = strconv.AppendInt(bin[:0], t.Time.Unix(), 10)
return bin, nil
}
func (t *TimestampTime) UnmarshalJSON(bin []byte) error {
v, err := strconv.ParseInt(string(bin), 10, 64)
if err != nil {
return err
}
t.Time = time.Unix(v, 0)
return nil
}
///
type Account struct {
// Override primitive type by simply specifying it via `swaggertype` tag
ID sql.NullInt64 `json:"id" swaggertype:"integer"`
// Override struct type to a primitive type 'integer' by specifying it via `swaggertype` tag
RegisterTime TimestampTime `json:"register_time" swaggertype:"primitive,integer"`
// Array types can be overridden using "array," format
Coeffs []big.Float `json:"coeffs" swaggertype:"array,number"`
}
```
[#379](https://github.com/swaggo/swag/issues/379)
```go
type CerticateKeyPair struct {
Crt []byte `json:"crt" swaggertype:"string" format:"base64" example:"U3dhZ2dlciByb2Nrcw=="`
Key []byte `json:"key" swaggertype:"string" format:"base64" example:"U3dhZ2dlciByb2Nrcw=="`
}
```
generated swagger doc as follows:
```go
"api.MyBinding": {
"type":"object",
"properties":{
"crt":{
"type":"string",
"format":"base64",
"example":"U3dhZ2dlciByb2Nrcw=="
},
"key":{
"type":"string",
"format":"base64",
"example":"U3dhZ2dlciByb2Nrcw=="
}
}
}
```
### Use global overrides to support a custom type
If you are using generated files, the [`swaggertype`](#use-swaggertype-tag-to-supported-custom-type) or `swaggerignore` tags may not be possible.
By passing a mapping to swag with `--overridesFile` you can tell swag to use one type in place of another wherever it appears. By default, if a `.swaggo` file is present in the current directory it will be used.
Go code:
```go
type MyStruct struct {
ID sql.NullInt64 `json:"id"`
Name sql.NullString `json:"name"`
}
```
`.swaggo`:
```
// Replace all NullInt64 with int
replace database/sql.NullInt64 int
// Don't include any fields of type database/sql.NullString in the swagger docs
skip database/sql.NullString
```
Possible directives are comments (beginning with `//`), `replace path/to/a.type path/to/b.type`, and `skip path/to/a.type`.
(Note that the full paths to any named types must be provided to prevent problems when multiple packages define a type with the same name)
Rendered:
```go
"types.MyStruct": {
"id": "integer"
}
```
### Use swaggerignore tag to exclude a field
```go
type Account struct {
ID string `json:"id"`
Name string `json:"name"`
Ignored int `swaggerignore:"true"`
}
```
### Add extension info to struct field
```go
type Account struct {
ID string `json:"id" extensions:"x-nullable,x-abc=def,!x-omitempty"` // extensions fields must start with "x-"
}
```
generate swagger doc as follows:
```go
"Account": {
"type": "object",
"properties": {
"id": {
"type": "string",
"x-nullable": true,
"x-abc": "def",
"x-omitempty": false
}
}
}
```
### Rename model to display
```golang
type Resp struct {
Code int
}//@name Response
```
### How to use security annotations
General API info.
```go
// @securityDefinitions.basic BasicAuth
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
```
Each API operation.
```go
// @Security ApiKeyAuth
```
Make it OR condition
```go
// @Security ApiKeyAuth
// @Security OAuth2Application[write, admin]
```
Make it AND condition
```go
// @Security ApiKeyAuth && firebase
// @Security OAuth2Application[write, admin] && APIKeyAuth
```
### Generate enum types from enum constants
You can generate enums from ordered constants. Each enum variant can have a comment, an override name, or both. This works with both iota-defined and manually defined constants.
```go
type Difficulty string
const (
Easy Difficulty = "easy" // You can add a comment to the enum variant.
Medium Difficulty = "medium" // @name MediumDifficulty
Hard Difficulty = "hard" // @name HardDifficulty You can have a name override and a comment.
)
type Class int
const (
First Class = iota // @name FirstClass
Second // Name override and comment rules apply here just as above.
Third // @name ThirdClass This one has a name override and a comment.
)
// There is no need to add `enums:"..."` to the fields, it is automatically generated from the ordered consts.
type Quiz struct {
Difficulty Difficulty
Class Class
Questions []string
Answers []string
}
```
### Add a description for enum items
```go
type Example struct {
// Sort order:
// * asc - Ascending, from A to Z.
// * desc - Descending, from Z to A.
Order string `enums:"asc,desc"`
}
```
### Generate only specific docs file types
By default `swag` command generates Swagger specification in three different files/file types:
- docs.go
- swagger.json
- swagger.yaml
If you would like to limit a set of file types which should be generated you can use `--outputTypes` (short `-ot`) flag. Default value is `go,json,yaml` - output types separated with comma. To limit output only to `go` and `yaml` files, you would write `go,yaml`. With complete command that would be `swag init --outputTypes go,yaml`.
### How to use Generics
```go
// @Success 200 {object} web.GenericNestedResponse[types.Post]
// @Success 204 {object} web.GenericNestedResponse[types.Post, Types.AnotherOne]
// @Success 201 {object} web.GenericNestedResponse[web.GenericInnerType[types.Post]]
func GetPosts(w http.ResponseWriter, r *http.Request) {
_ = web.GenericNestedResponse[types.Post]{}
}
```
See [this file](https://github.com/swaggo/swag/blob/master/testdata/generics_nested/api/api.go) for more details
and other examples.
### Change the default Go Template action delimiters
[#980](https://github.com/swaggo/swag/issues/980)
[#1177](https://github.com/swaggo/swag/issues/1177)
If your swagger annotations or struct fields contain "{{" or "}}", the template generation will most likely fail, as these are the default delimiters for [go templates](https://pkg.go.dev/text/template#Template.Delims).
To make the generation work properly, you can change the default delimiters with `-td`. For example:
```console
swag init -g http/api.go -td "[[,]]"
```
The new delimiter is a string with the format "``,``".
### Parse Internal and Dependency Packages
If the struct is defined in a dependency package, use `--parseDependency`.
If the struct is defined in your main project, use `--parseInternal`.
if you want to include both internal and from dependencies use both flags
```
swag init --parseDependency --parseInternal
```
## About the Project
This project was inspired by [yvasiyarov/swagger](https://github.com/yvasiyarov/swagger) but we simplified the usage and added support a variety of [web frameworks](#supported-web-frameworks). Gopher image source is [tenntenn/gopher-stickers](https://github.com/tenntenn/gopher-stickers). It has licenses [creative commons licensing](http://creativecommons.org/licenses/by/3.0/deed.en).
## Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
## Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/swag#backer)]
## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/swag#sponsor)]
## License
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fswaggo%2Fswag?ref=badge_large)
================================================
FILE: README_pt.md
================================================
# swag
🌍 *[English](README.md) ∙ [简体中文](README_zh-CN.md) ∙ [Português](README_pt.md)*
[](https://github.com/features/actions)
[](https://codecov.io/gh/swaggo/swag)
[](https://goreportcard.com/report/github.com/swaggo/swag)
[](https://codebeat.co/projects/github-com-swaggo-swag-master)
[](https://godoc.org/github.com/swaggo/swag)
[](#backers)
[](#sponsors) [](https://app.fossa.io/projects/git%2Bgithub.com%2Fswaggo%2Fswag?ref=badge_shield)
[](https://github.com/swaggo/swag/releases)
Swag converte anotações Go para Documentação Swagger 2.0. Criámos uma variedade de plugins para populares [Go web frameworks](#supported-web-frameworks). Isto permite uma integração rápida com um projecto Go existente (utilizando a Swagger UI).
## Conteúdo
- [Começando](#começando)
- [Estruturas Web Suportadas](#estruturas-web-suportadas)
- [Como utilizá-lo com Gin](#como-como-ser-como-gin)
- [O formatador de swag](#a-formatação-de-swag)
- [Estado de Implementação](#implementação-estado)
- [Formato dos comentários declarativos](#formato-dos-comentarios-declarativos)
- [Informações Gerais API](#informações-gerais-api)
- [Operação API](#api-operacao)
- [Segurança](#seguranca)
- [Exemplos](#exemplos)
- [Descrições em múltiplas linhas](#descricoes-sobre-múltiplas-linhas)
- [Estrutura definida pelo utilizador com um tipo de matriz](#-estrutura-definida-pelo-utilizador-com-um-um-tipo)
- [Declaração de estruturação de funções](#function-scoped-struct-declaration)
- [Composição do modelo em resposta](#model-composição-em-resposta)
- [Adicionar um cabeçalho em resposta](#add-a-headers-in-response)
- [Utilizar parâmetros de caminhos múltiplos](#use-multiple-path-params)
- [Exemplo de valor de estrutura](#exemplo-do-valor-de-estrutura)
- [Schema Exemplo do corpo](#schemaexample-of-body)
- [Descrição da estrutura](#descrição-da-estrutura)
- [Usar etiqueta do tipo swaggertype para suportar o tipo personalizado](#use-swaggertype-tag-to-supported-custom-type)
- [Utilizar anulações globais para suportar um tipo personalizado](#use-global-overrides-to-support-a-custom-type)
- [Use swaggerignore tag para excluir um campo](#use-swaggerignore-tag-to-excluir-um-campo)
- [Adicionar informações de extensão ao campo de estruturação](#add-extension-info-to-struct-field)
- [Renomear modelo a expor](#renome-modelo-a-exibir)
- [Como utilizar as anotações de segurança](#como-utilizar-as-anotações-de-segurança)
- [Adicionar uma descrição para enumerar artigos](#add-a-description-for-enum-items)
- [Gerar apenas tipos de ficheiros de documentos específicos](#generate-only-specific-docs-file-file-types)
- [Como usar tipos genéricos](#como-usar-tipos-genéricos)
- [Sobre o projecto](#sobre-o-projecto)
## Começando
1. Adicione comentários ao código-fonte da API, consulte [Formato dos comentários declarativos](#declarative-comments-format).
2. Descarregue o swag utilizando:
```sh
go install github.com/swaggo/swag/cmd/swag@latest
```
Para construir a partir da fonte é necessário [Go](https://golang.org/dl/) (1.19 ou mais recente).
Ou descarregar um binário pré-compilado a partir da [página de lançamento](https://github.com/swaggo/swag/releases).
3. Executar `swag init` na pasta raiz do projecto que contém o ficheiro `main.go`. Isto irá analisar os seus comentários e gerar os ficheiros necessários (pasta `docs` e `docs/docs.go`).
```sh
swag init
```
Certifique-se de importar os `docs/docs.go` gerados para que a sua configuração específica fique "init" ed. Se as suas anotações API gerais não viverem em `main.go`, pode avisar a swag com a bandeira `-g`.
```sh
swag init -g http/api.go
```
4. (opcional) Utilizar o formato `swag fmt` no comentário SWAG. (Por favor, actualizar para a versão mais recente)
```sh
swag fmt
```
## swag cli
```sh
swag init -h
NOME:
swag init - Criar docs.go
UTILIZAÇÃO:
swag init [opções de comando] [argumentos...]
OPÇÕES:
--quiet, -q Fazer o logger ficar quiet (por padrão: falso)
--generalInfo valor, -g valor Go caminho do ficheiro em que 'swagger general API Info' está escrito (por padrão: "main.go")
--dir valor, -d valor Os directórios que deseja analisar, separados por vírgulas e de informação geral devem estar no primeiro (por padrão: "./")
--exclude valor Excluir directórios e ficheiros ao pesquisar, separados por vírgulas
-propertyStrategy da estratégia, -p valor da propriedadeEstratégia de nomeação de propriedades como snakecase,camelcase,pascalcase (por padrão: "camelcase")
--output de saída, -o valor directório de saída para todos os ficheiros gerados(swagger.json, swagger.yaml e docs.go) (por padrão: "./docs")
--outputTypes valor de saídaTypes, -- valor de saída Tipos de ficheiros gerados (docs.go, swagger.json, swagger.yaml) como go,json,yaml (por padrão: "go,json,yaml")
--parseVendor ParseVendor Parse go files na pasta 'vendor', desactivado por padrão (padrão: falso)
--parseInternal Parse go ficheiros em pacotes internos, desactivados por padrão (padrão: falso)
--generatedTime Gerar timestamp no topo dos docs.go, desactivado por padrão (padrão: falso)
--parteDepth value Dependência profundidade parse (por padrão: 100)
--templateDelims value, --td value fornecem delimitadores personalizados para a geração de modelos Go. O formato é leftDelim,rightDelim. Por exemplo: "[[,]]"
...
--help, -h mostrar ajuda (por padrão: falso)
```
```bash
swag fmt -h
NOME:
swag fmt - formato swag comentários
UTILIZAÇÃO:
swag fmt [opções de comando] [argumentos...]
OPÇÕES:
--dir valor, -d valor Os directórios que pretende analisar, separados por vírgulas e de informação geral devem estar no primeiro (por padrão: "./")
--excluir valor Excluir directórios e ficheiros ao pesquisar, separados por vírgulas
--generalInfo value, -g value Go file path in which 'swagger general API Info' is written (por padrão: "main.go")
--ajuda, -h mostrar ajuda (por padrão: falso)
```
## Estruturas Web Suportadas
- [gin](http://github.com/swaggo/gin-swagger)
- [echo](http://github.com/swaggo/echo-swagger)
- [buffalo](https://github.com/swaggo/buffalo-swagger)
- [net/http](https://github.com/swaggo/http-swagger)
- [gorilla/mux](https://github.com/swaggo/http-swagger)
- [go-chi/chi](https://github.com/swaggo/http-swagger)
- [flamingo](https://github.com/i-love-flamingo/swagger)
- [fiber](https://github.com/gofiber/swagger)
- [atreugo](https://github.com/Nerzal/atreugo-swagger)
- [hertz](https://github.com/hertz-contrib/swagger)
## Como utilizá-lo com Gin
Encontrar o código fonte de exemplo [aqui](https://github.com/swaggo/swag/tree/master/example/celler).
1. Depois de utilizar `swag init` para gerar os documentos Swagger 2.0, importar os seguintes pacotes:
```go
import "github.com/swaggo/gin-swagger" // gin-swagger middleware
import "github.com/swaggo/files" // swagger embed files
```
2. Adicionar [Informações Gerais API](#general-api-info) anotações em código `main.go`:
```go
// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.basic BasicAuth
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api/
func main() {
r := gin.Default()
c := controller.NewController()
v1 := r.Group("/api/v1")
{
accounts := v1.Group("/accounts")
{
accounts.GET(":id", c.ShowAccount)
accounts.GET("", c.ListAccounts)
accounts.POST("", c.AddAccount)
accounts.DELETE(":id", c.DeleteAccount)
accounts.PATCH(":id", c.UpdateAccount)
accounts.POST(":id/images", c.UploadAccountImage)
}
//...
}
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run(":8080")
}
//...
```
Além disso, algumas informações API gerais podem ser definidas de forma dinâmica. O pacote de código gerado `docs` exporta a variável `SwaggerInfo` que podemos utilizar para definir programticamente o título, descrição, versão, hospedeiro e caminho base. Exemplo utilizando Gin:
```go
package main
import (
"github.com/gin-gonic/gin"
"github.com/swaggo/files"
"github.com/swaggo/gin-swagger"
"./docs" // docs is generated by Swag CLI, you have to import it.
)
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
func main() {
// programmatically set swagger info
docs.SwaggerInfo.Title = "Swagger Example API"
docs.SwaggerInfo.Description = "This is a sample server Petstore server."
docs.SwaggerInfo.Version = "1.0"
docs.SwaggerInfo.Host = "petstore.swagger.io"
docs.SwaggerInfo.BasePath = "/v2"
docs.SwaggerInfo.Schemes = []string{"http", "https"}
r := gin.New()
// use ginSwagger middleware to serve the API docs
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run()
}
```
3. Adicionar [Operação API](#api-operacao) anotações em código `controller`
```go
package controller
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/swaggo/swag/example/celler/httputil"
"github.com/swaggo/swag/example/celler/model"
)
// ShowAccount godoc
// @Summary Show an account
// @Description get string by ID
// @Tags accounts
// @Accept json
// @Produce json
// @Param id path int true "Account ID"
// @Success 200 {object} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts/{id} [get]
func (c *Controller) ShowAccount(ctx *gin.Context) {
id := ctx.Param("id")
aid, err := strconv.Atoi(id)
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
account, err := model.AccountOne(aid)
if err != nil {
httputil.NewError(ctx, http.StatusNotFound, err)
return
}
ctx.JSON(http.StatusOK, account)
}
// ListAccounts godoc
// @Summary List accounts
// @Description get accounts
// @Tags accounts
// @Accept json
// @Produce json
// @Param q query string false "name search by q" Format(email)
// @Success 200 {array} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts [get]
func (c *Controller) ListAccounts(ctx *gin.Context) {
q := ctx.Request.URL.Query().Get("q")
accounts, err := model.AccountsAll(q)
if err != nil {
httputil.NewError(ctx, http.StatusNotFound, err)
return
}
ctx.JSON(http.StatusOK, accounts)
}
//...
```
```console
swag init
```
4. Execute a sua aplicação, e navegue para http://localhost:8080/swagger/index.html. Verá os documentos Swagger 2.0 Api, como mostrado abaixo:

## O formatador de swag
Os Swag Comments podem ser formatados automaticamente, assim como 'go fmt'.
Encontre o resultado da formatação [aqui](https://github.com/swaggo/swag/tree/master/example/celler).
Usage:
```shell
swag fmt
```
Exclude folder:
```shell
swag fmt -d ./ --exclude ./internal
```
Ao utilizar `swag fmt`, é necessário assegurar-se de que tem um comentário doc para a função a fim de assegurar uma formatação correcta.
Isto deve-se ao `swag fmt` que traça comentários swag com separadores, o que só é permitido *após* um comentário doc padrão.
Por exemplo, utilizar
```go
// ListAccounts lists all existing accounts
//
// @Summary List accounts
// @Description get accounts
// @Tags accounts
// @Accept json
// @Produce json
// @Param q query string false "name search by q" Format(email)
// @Success 200 {array} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts [get]
func (c *Controller) ListAccounts(ctx *gin.Context) {
```
## Estado de Implementação
[Documento Swagger 2.0](https://swagger.io/docs/specification/2-0/basic-structure/)
- [x] Estrutura básica
- [x] Hospedeiro API e Caminho Base
- [x] Caminhos e operações
- [x] Descrição dos parâmetros
- [x] Descrever o corpo do pedido
- [x] Descrição das respostas
- [x] Tipos MIME
- [x] Autenticação
- [x] Autenticação básica
- [x] Chaves API
- [x] Acrescentar exemplos
- [x] Carregamento de ficheiros
- [x] Enums
- [x] Operações de Agrupamento com Etiquetas
- Extensões Swagger
## Formato dos comentários declarativos
## Informações Gerais API
**Exemplo**
[celler/main.go](https://github.com/swaggo/swag/blob/master/example/celler/main.go)
| anotação | descrição | exemplo |
|-------------|--------------------------------------------|---------------------------------|
| title | **Obrigatório.** O título da aplicação.| // @title Swagger Example API |
| version | **Obrigatório.** Fornece a versão da aplicação API.| // @version 1.0 |
| description | Uma breve descrição da candidatura. |// @descrição Este é um servidor servidor de celas de amostra. |
| tag.name | Nome de uma tag.| // @tag.name Este é o nome da tag |
| tag.description | Descrição da tag | // @tag.description Cool Description |
| tag.docs.url | Url da Documentação externa da tag | // @tag.docs.url https://example.com|
| tag.docs.description | Descrição da documentação externa da tag| // @tag.docs.description Melhor exemplo de documentação |
| TermsOfService | Os Termos de Serviço para o API.| // @termsOfService http://swagger.io/terms/ |
| contact.name | A informação de contacto para a API exposta.| // @contacto.name Suporte API |
| contact.url | O URL que aponta para as informações de contacto. DEVE estar no formato de um URL. | // @contact.url http://www.swagger.io/support|
| contact.email| O endereço de email da pessoa/organização de contacto. DEVE estar no formato de um endereço de correio electrónico.| // @contact.email support@swagger.io |
| license.name | **Obrigatório.** O nome da licença utilizada para a API.|// @licença.name Apache 2.0|
| license.url | Um URL para a licença utilizada para a API. DEVE estar no formato de um URL. | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html |
| host | O anfitrião (nome ou ip) que serve o API. | // @host localhost:8080 |
| BasePath | O caminho de base sobre o qual o API é servido. | // @BasePath /api/v1 |
| accept | Uma lista de tipos de MIME que os APIs podem consumir. Note que accept só afecta operações com um organismo de pedido, tais como POST, PUT e PATCH. O valor DEVE ser o descrito em [Tipos de Mime](#mime-types). | // @accept json |
| produce | Uma lista de tipos de MIME que os APIs podem produce. O valor DEVE ser o descrito em [Tipos de Mime](#mime-types). | // @produce json |
| query.collection.format | O formato padrão de param de colecção(array) em query,enums:csv,multi,pipes,tsv,ssv. Se não definido, csv é o padrão.| // @query.collection.format multi
| schemes | O protocolo de transferência para a operação que separou por espaços. | // @schemes http https |
| externalDocs.description | Descrição do documento externo. | // @externalDocs.description OpenAPI |
| externalDocs.url | URL do documento externo. | // @externalDocs.url https://swagger.io/resources/open-api/ |
| x-name | A chave de extensão, deve ser iniciada por x- e tomar apenas o valor json | // @x-example-key {"chave": "valor"} |
### Usando descrições de remarcação para baixo
Quando uma pequena sequência na sua documentação é insuficiente, ou precisa de imagens, exemplos de códigos e coisas do género, pode querer usar descrições de marcação. Para utilizar as descrições markdown, utilize as seguintes anotações.
| anotação | descrição | exemplo |
|-------------|--------------------------------------------|---------------------------------|
| title | **Obrigatório.** O título da aplicação.| // @title Swagger Example API |
| version | **Obrigatório.** Fornece a versão da aplicação API.| // @versão 1.0 |
| description.markdown | Uma breve descrição da candidatura. Parsed a partir do ficheiro api.md. Esta é uma alternativa a @description |// @description.markdown Sem valor necessário, isto analisa a descrição do ficheiro api.md |.
| tag.name | Nome de uma tag.| // @tag.name Este é o nome da tag |
| tag.description.markdown | Descrição da tag esta é uma alternativa à tag.description. A descrição será lida a partir de um ficheiro nomeado como tagname.md | // @tag.description.markdown |
## Operação API
**Exemplo**
[celler/controller](https://github.com/swaggo/swag/tree/master/example/celler/controller)
| anotação | descrição |
|-------------|----------------------------------------------------------------------------------------------------------------------------|
| descrição | Uma explicação verbosa do comportamento da operação. |
| description.markdown | Uma breve descrição da candidatura. A descrição será lida a partir de um ficheiro. Por exemplo, `@description.markdown details` irá carregar `details.md`| // @description.file endpoint.description.markdown |
| id | Um fio único utilizado para identificar a operação. Deve ser única entre todas as operações API. |
| tags | Uma lista de tags para cada operação API que separou por vírgulas. |
| summary | Um breve resumo do que a operação faz. |
| accept | Uma lista de tipos de MIME que os APIs podem consumir. Note que accept só afecta operações com um organismo de pedido, tais como POST, PUT e PATCH. O valor DEVE ser o descrito em [Tipos de Mime](#mime-types). |
| produce | Uma lista de tipos de MIME que os APIs podem produce. O valor DEVE ser o descrito em [Tipos de Mime](#mime-types). |
| param | Parâmetros que se separaram por espaços. `param name`,`param type`,`data type`,`is mandatory?`,`comment` `attribute(optional)` |
| security | [Segurança](#security) para cada operação API. |
| success | resposta de sucesso que separou por espaços. `return code or default`,`{param type}`,`data type`,`comment` |.
| failure | Resposta de falha que separou por espaços. `return code or default`,`{param type}`,`data type`,`comment` |
| response | Igual ao `sucesso` e `falha` |
| header | Cabeçalho em resposta que separou por espaços. `código de retorno`,`{tipo de parâmetro}`,`tipo de dados`,`comentário` |.
| router | Definição do caminho que separou por espaços. caminho",`path`,`[httpMethod]` |[httpMethod]` |
| x-name | A chave de extensão, deve ser iniciada por x- e tomar apenas o valor json. |
| x-codeSample | Optional Markdown use. tomar `file` como parâmetro. Isto irá então procurar um ficheiro nomeado como o resumo na pasta dada. |
| deprecated | Marcar o ponto final como depreciado. |
## Mime Types
`swag` aceita todos os tipos MIME que estão no formato correcto, ou seja, correspondem `*/*`.
Além disso, `swag` também aceita pseudónimos para alguns tipos de MIME, como se segue:
| Alias | MIME Type |
|-----------------------|-----------------------------------|
| json | application/json |
| xml | text/xml |
| plain | text/plain |
| html | text/html |
| mpfd | multipart/form-data |
| x-www-form-urlencoded | application/x-www-form-urlencoded |
| json-api | application/vnd.api+json |
| json-stream | application/x-json-stream |
| octet-stream | application/octet-stream |
| png | image/png |
| jpeg | image/jpeg |
| gif | image/gif |
| event-stream | text/event-stream |
## Tipo de parâmetro
- query
- path
- header
- body
- formData
## Tipo de dados
- string (string)
- integer (int, uint, uint32, uint64)
- number (float32)
- boolean (bool)
- file (param data type when uploading)
- user defined struct
## Segurança
| anotação | descrição | parâmetros | exemplo |
|------------|-------------|------------|---------|
| securitydefinitions.basic | [Basic](https://swagger.io/docs/specification/2-0/authentication/basic-authentication/) auth. | | // @securityDefinitions.basicAuth | [Básico]()
| securitydefinitions.apikey | [chave API](https://swagger.io/docs/specification/2-0/authentication/api-keys/) auth. | in, name, description | // @securityDefinitions.apikey ApiKeyAuth |
| securitydefinitions.oauth2.application | [Aplicação OAuth2](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, scope, description | // @securitydefinitions.oauth2.application OAuth2Application |
| securitydefinitions.oauth2.implicit | [OAuth2 implicit](https://swagger.io/docs/specification/authentication/oauth2/) auth. | authorizationUrl, scope, description | // @securitydefinitions.oauth2.implicit OAuth2Implicit | [OAuth2Implicit]()
| securitydefinitions.oauth2.password | [OAuth2 password](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, scope, description | // @securitydefinitions.oauth2.password OAuth2Password |
| securitydefinitions.oauth2.accessCode | [código de acesso OAuth2](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, authorizationUrl, scope, description | // @securitydefinitions.oauth2.accessCode OAuth2AccessCode | [código de acesso OAuth2.accessCode]()
| anotação de parâmetros | exemplo |
|---------------------------------|-------------------------------------------------------------------------|
| in | // @in header |
| name | // @name Authorization |
| tokenUrl | // @tokenUrl https://example.com/oauth/token |
| authorizationurl | // @authorizationurl https://example.com/oauth/authorize |
| scope.hoge | // @scope.write Grants write access |
| description | // @descrição OAuth protege os pontos finais da nossa entidade |
## Atributo
```go
// @Param enumstring query string false "string enums" Enums(A, B, C)
// @Param enumint query int false "int enums" Enums(1, 2, 3)
// @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3)
// @Param string query string false "string valid" minlength(5) maxlength(10)
// @Param int query int false "int valid" minimum(1) maximum(10)
// @Param default query string false "string default" default(A)
// @Param example query string false "string example" example(string)
// @Param collection query []string false "string collection" collectionFormat(multi)
// @Param extensions query []string false "string collection" extensions(x-example=test,x-nullable)
```
It also works for the struct fields:
```go
type Foo struct {
Bar string `minLength:"4" maxLength:"16" example:"random string"`
Baz int `minimum:"10" maximum:"20" default:"15"`
Qux []string `enums:"foo,bar,baz"`
}
```
### Disponível
Nome do campo | Tipo | Descrição
---|:---:|---
validate | `string` | Determina a validação para o parâmetro. Os valores possíveis são: `required,optional`.
default | * | Declara o valor do parâmetro que o servidor utilizará se nenhum for fornecido, por exemplo, uma "contagem" para controlar o número de resultados por página poderá ser por defeito de 100 se não for fornecido pelo cliente no pedido. (Nota: "por defeito" não tem significado para os parâmetros requeridos).
See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2. Ao contrário do esquema JSON, este valor DEVE estar em conformidade com o definido [`type`](#parameterType) para este parâmetro.
maximum | `number` | Ver https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2.
minimum | `number` | Ver https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3.
multipleOf | `number` | Ver https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.1.
maxLength | `integer` | Ver https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.1.
minLength | `integer` | Ver https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2.
enums | [\*] | Ver https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1.
format | `string` | O formato de extensão para o anteriormente mencionado [`type`](#parameterType). Ver [Data Type Formats](https://swagger.io/specification/v2/#dataTypeFormat) para mais detalhes.
collectionFormat | `string` |Determina o formato da matriz se for utilizada uma matriz de tipos. Os valores possíveis são: - `csv` - valores separados por vírgulas `foo,bar`.
- `ssv` - valores separados por espaço `foo bar`.
- `tsv` - valores separados por tabulação `foo\tbar`.
- `pipes` - valores separados por tubo
foo|bar. - `multi` - corresponde a múltiplas instâncias de parâmetros em vez de múltiplos valores para uma única instância `foo=bar&foo=baz`. This is valid only for parameters [`in`](#parameterIn) "query" or "formData".
Default value is `csv`.
example | * | Declara o exemplo para o valor do parâmetro
extensions | `string` | Acrescentar extensão aos parâmetros.
### Futuro
Nome do campo | Tipo | Description
---|:---:|---
pattern | `string` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3.
maxItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2.
minItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3.
uniqueItems | `boolean` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4.
## Exemplos
### Descrições em múltiplas linhas
É possível acrescentar descrições que abranjam várias linhas tanto na descrição geral da api como em definições de rotas como esta:
```go
// @description This is the first line
// @description This is the second line
// @description And so forth.
```
### Estrutura definida pelo utilizador com um tipo de matriz
```go
// @Success 200 {array} model.Account <-- This is a user defined struct.
```
```go
package model
type Account struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"account name"`
}
```
### Declaração de estruturação de funções
Pode declarar as estruturas de resposta do seu pedido dentro de um corpo funcional.
Deve ter de seguir a convenção de nomeação
`.. `.
```go
package main
// @Param request body main.MyHandler.request true "query params"
// @Success 200 {object} main.MyHandler.response
// @Router /test [post]
func MyHandler() {
type request struct {
RequestField string
}
type response struct {
ResponseField string
}
}
```
### Composição do modelo em resposta
```go
// JSONResult's data field will be overridden by the specific type proto.Order
@success 200 {object} jsonresult.JSONResult{data=proto.Order} "desc"
```
```go
type JSONResult struct {
Code int `json:"code" `
Message string `json:"message"`
Data interface{} `json:"data"`
}
type Order struct { //in `proto` package
Id uint `json:"id"`
Data interface{} `json:"data"`
}
```
- também suportam uma variedade de objectos e tipos primitivos como resposta aninhada
```go
@success 200 {object} jsonresult.JSONResult{data=[]proto.Order} "desc"
@success 200 {object} jsonresult.JSONResult{data=string} "desc"
@success 200 {object} jsonresult.JSONResult{data=[]string} "desc"
```
- campos múltiplos que se sobrepõem. campo será adicionado se não existir
```go
@success 200 {object} jsonresult.JSONResult{data1=string,data2=[]string,data3=proto.Order,data4=[]proto.Order} "desc"
```
- overriding deep-level fields
```go
type DeepObject struct { //in `proto` package
...
}
@success 200 {object} jsonresult.JSONResult{data1=proto.Order{data=proto.DeepObject},data2=[]proto.Order{data=[]proto.DeepObject}} "desc"
```
### Adicionar um cabeçalho em resposta
```go
// @Success 200 {string} string "ok"
// @failure 400 {string} string "error"
// @response default {string} string "other error"
// @Header 200 {string} Location "/entity/1"
// @Header 200,400,default {string} Token "token"
// @Header all {string} Token2 "token2"
```
### Utilizar parâmetros de caminhos múltiplos
```go
/// ...
// @Param group_id path int true "Group ID"
// @Param account_id path int true "Account ID"
// ...
// @Router /examples/groups/{group_id}/accounts/{account_id} [get]
```
### Adicionar múltiplos caminhos
```go
/// ...
// @Param group_id path int true "Group ID"
// @Param user_id path int true "User ID"
// ...
// @Router /examples/groups/{group_id}/user/{user_id}/address [put]
// @Router /examples/user/{user_id}/address [put]
```
### Exemplo de valor de estrutura
```go
type Account struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"account name"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"`
}
```
### Schema Exemplo do corpo
```go
// @Param email body string true "message/rfc822" SchemaExample(Subject: Testmail\r\n\r\nBody Message\r\n)
```
### Descrição da estrutura
```go
// Account model info
// @Description User account information
// @Description with user id and username
type Account struct {
// ID this is userid
ID int `json:"id"`
Name string `json:"name"` // This is Name
}
```
[#708](https://github.com/swaggo/swag/issues/708) O analisador trata apenas de comentários estruturais a partir de `@Description` attribute.
Assim, gerou o doc. de swagger como se segue:
```json
"Account": {
"type":"object",
"description": "User account information with user id and username"
"properties": {
"id": {
"type": "integer",
"description": "ID this is userid"
},
"name": {
"type":"string",
"description": "This is Name"
}
}
}
```
### Usar etiqueta do tipo swaggertype para suportar o tipo personalizado
[#201](https://github.com/swaggo/swag/issues/201#issuecomment-475479409)
```go
type TimestampTime struct {
time.Time
}
///implement encoding.JSON.Marshaler interface
func (t *TimestampTime) MarshalJSON() ([]byte, error) {
bin := make([]byte, 16)
bin = strconv.AppendInt(bin[:0], t.Time.Unix(), 10)
return bin, nil
}
func (t *TimestampTime) UnmarshalJSON(bin []byte) error {
v, err := strconv.ParseInt(string(bin), 10, 64)
if err != nil {
return err
}
t.Time = time.Unix(v, 0)
return nil
}
///
type Account struct {
// Override primitive type by simply specifying it via `swaggertype` tag
ID sql.NullInt64 `json:"id" swaggertype:"integer"`
// Override struct type to a primitive type 'integer' by specifying it via `swaggertype` tag
RegisterTime TimestampTime `json:"register_time" swaggertype:"primitive,integer"`
// Array types can be overridden using "array," format
Coeffs []big.Float `json:"coeffs" swaggertype:"array,number"`
}
```
[#379](https://github.com/swaggo/swag/issues/379)
```go
type CerticateKeyPair struct {
Crt []byte `json:"crt" swaggertype:"string" format:"base64" example:"U3dhZ2dlciByb2Nrcw=="`
Key []byte `json:"key" swaggertype:"string" format:"base64" example:"U3dhZ2dlciByb2Nrcw=="`
}
```
generated swagger doc as follows:
```go
"api.MyBinding": {
"type":"object",
"properties":{
"crt":{
"type":"string",
"format":"base64",
"example":"U3dhZ2dlciByb2Nrcw=="
},
"key":{
"type":"string",
"format":"base64",
"example":"U3dhZ2dlciByb2Nrcw=="
}
}
}
```
### Utilizar anulações globais para suportar um tipo personalizado
Se estiver a utilizar ficheiros gerados, as etiquetas [`swaggertype`](#use-swaggertype-tag-to-supported-custom-type) ou `swaggerignore` podem não ser possíveis.
Ao passar um mapeamento para swag com `--overridesFile` pode dizer swag para utilizar um tipo no lugar de outro onde quer que apareça. Por defeito, se um ficheiro `.swaggo` estiver presente no directório actual, será utilizado.
Go code:
```go
type MyStruct struct {
ID sql.NullInt64 `json:"id"`
Name sql.NullString `json:"name"`
}
```
`.swaggo`:
```
// Substituir todos os NullInt64 por int
replace database/sql.NullInt64 int
// Não inclua quaisquer campos do tipo base de database/sql.
NullString no swagger docs
skip database/sql.NullString
```
As directivas possíveis são comentários (começando por `//`), `replace path/to/a.type path/to/b.type`, e `skip path/to/a.type`.
(Note que os caminhos completos para qualquer tipo nomeado devem ser fornecidos para evitar problemas quando vários pacotes definem um tipo com o mesmo nome)
Entregue em:
```go
"types.MyStruct": {
"id": "integer"
}
### Use swaggerignore tag para excluir um campo
```go
type Account struct {
ID string `json:"id"`
Name string `json:"name"`
Ignored int `swaggerignore:"true"`
}
```
### Adicionar informações de extensão ao campo de estruturação
```go
type Account struct {
ID string `json:"id" extensions:"x-nullable,x-abc=def,!x-omitempty"` // extensions fields must start with "x-"
}
```
gerar doc. de swagger como se segue:
```go
"Account": {
"type": "object",
"properties": {
"id": {
"type": "string",
"x-nullable": true,
"x-abc": "def",
"x-omitempty": false
}
}
}
```
### Renomear modelo a expor
```golang
type Resp struct {
Code int
}//@name Response
```
### Como utilizar as anotações de segurança
Informações API gerais.
```go
// @securityDefinitions.basic BasicAuth
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
```
Cada operação API.
```go
// @Security ApiKeyAuth
```
Faça-o OR condicione-o
```go
// @Security ApiKeyAuth
// @Security OAuth2Application[write, admin]
```
Faça-o AND condição
```go
// @Security ApiKeyAuth && firebase
// @Security OAuth2Application[write, admin] && APIKeyAuth
```
### Adicionar uma descrição para enumerar artigos
```go
type Example struct {
// Sort order:
// * asc - Ascending, from A to Z.
// * desc - Descending, from Z to A.
Order string `enums:"asc,desc"`
}
```
### Gerar apenas tipos de ficheiros de documentos específicos
Por defeito, o comando `swag` gera especificação Swagger em três tipos diferentes de ficheiros/arquivos:
- docs.go
- swagger.json
- swagger.yaml
Se desejar limitar um conjunto de tipos de ficheiros que devem ser gerados pode utilizar a bandeira `--outputTypes` (short `-ot`). O valor por defeito é `go,json,yaml` - tipos de saída separados por vírgula. Para limitar a saída apenas a ficheiros `go` e `yaml`, escrever-se-ia `go,yaml'. Com comando completo que seria `swag init --outputTypes go,yaml`.
### Como usar tipos genéricos
```go
// @Success 200 {object} web.GenericNestedResponse[types.Post]
// @Success 204 {object} web.GenericNestedResponse[types.Post, Types.AnotherOne]
// @Success 201 {object} web.GenericNestedResponse[web.GenericInnerType[types.Post]]
func GetPosts(w http.ResponseWriter, r *http.Request) {
_ = web.GenericNestedResponse[types.Post]{}
}
```
Para mais detalhes e outros exemplos, veja [esse arquivo](https://github.com/swaggo/swag/blob/master/testdata/generics_nested/api/api.go)
### Alterar os delimitadores de acção padrão Go Template
[#980](https://github.com/swaggo/swag/issues/980)
[#1177](https://github.com/swaggo/swag/issues/1177)
Se as suas anotações ou campos estruturantes contêm "{{" or "}}", a geração de modelos irá muito provavelmente falhar, uma vez que estes são os delimitadores por defeito para [go templates](https://pkg.go.dev/text/template#Template.Delims).
Para que a geração funcione correctamente, pode alterar os delimitadores por defeito com `-td'. Por exemplo:
``console
swag init -g http/api.go -td "[[,]"
```
O novo delimitador é um fio com o formato "``,``".
## Sobre o projecto
Este projecto foi inspirado por [yvasiyarov/swagger](https://github.com/yvasiyarov/swagger) mas simplificámos a utilização e acrescentámos apoio a uma variedade de [frameworks web](#estruturas-web-suportadas). A fonte de imagem Gopher é [tenntenn/gopher-stickers](https://github.com/tenntenn/gopher-stickers). Tem licenças [creative commons licensing](http://creativecommons.org/licenses/by/3.0/deed.en).
## Contribuidores
Este projecto existe graças a todas as pessoas que contribuem. [[Contribute](CONTRIBUTING.md)].
## Apoios
Obrigado a todos os nossos apoiantes! 🙏 [[Become a backer](https://opencollective.com/swag#backer)]
## Patrocinadores
Apoiar este projecto tornando-se um patrocinador. O seu logótipo aparecerá aqui com um link para o seu website. [[Become a sponsor](https://opencollective.com/swag#sponsor)]
## Licença
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fswaggo%2Fswag?ref=badge_large)
================================================
FILE: README_zh-CN.md
================================================
# swag
🌍 *[English](README.md) ∙ [简体中文](README_zh-CN.md)*
[](https://travis-ci.org/swaggo/swag)
[](https://codecov.io/gh/swaggo/swag)
[](https://goreportcard.com/report/github.com/swaggo/swag)
[](https://codebeat.co/projects/github-com-swaggo-swag-master)
[](https://godoc.org/github.com/swaggo/swag)
[](#backers)
[](#sponsors) [](https://app.fossa.io/projects/git%2Bgithub.com%2Fswaggo%2Fswag?ref=badge_shield)
[](https://github.com/swaggo/swag/releases)
Swag将Go的注释转换为Swagger2.0文档。我们为流行的 [Go Web Framework](#支持的Web框架) 创建了各种插件,这样可以与现有Go项目快速集成(使用Swagger UI)。
## 目录
- [快速开始](#快速开始)
- [支持的Web框架](#支持的web框架)
- [如何与Gin集成](#如何与gin集成)
- [格式化说明](#格式化说明)
- [开发现状](#开发现状)
- [声明式注释格式](#声明式注释格式)
- [通用API信息](#通用api信息)
- [API操作](#api操作)
- [安全性](#安全性)
- [样例](#样例)
- [多行的描述](#多行的描述)
- [用户自定义的具有数组类型的结构](#用户自定义的具有数组类型的结构)
- [响应对象中的模型组合](#响应对象中的模型组合)
- [在响应中增加头字段](#在响应中增加头字段)
- [使用多路径参数](#使用多路径参数)
- [结构体的示例值](#结构体的示例值)
- [结构体描述](#结构体描述)
- [使用`swaggertype`标签更改字段类型](#使用`swaggertype`标签更改字段类型)
- [使用`swaggerignore`标签排除字段](#使用swaggerignore标签排除字段)
- [将扩展信息添加到结构字段](#将扩展信息添加到结构字段)
- [对展示的模型重命名](#对展示的模型重命名)
- [如何使用安全性注释](#如何使用安全性注释)
- [项目相关](#项目相关)
## 快速开始
1. 将注释添加到API源代码中,请参阅声明性注释格式。
2. 使用如下命令下载swag:
```bash
go install github.com/swaggo/swag/cmd/swag@latest
```
从源码开始构建的话,需要有Go环境(1.19及以上版本)。
或者从github的release页面下载预编译好的二进制文件。
3. 在包含`main.go`文件的项目根目录运行`swag init`。这将会解析注释并生成需要的文件(`docs`文件夹和`docs/docs.go`)。
```bash
swag init
```
确保导入了生成的`docs/docs.go`文件,这样特定的配置文件才会被初始化。如果通用API注释没有写在`main.go`中,可以使用`-g`标识符来告知swag。
```bash
swag init -g http/api.go
```
4. (可选) 使用`fmt`格式化 SWAG 注释。(请先升级到最新版本)
```bash
swag fmt
```
## swag cli
```bash
swag init -h
NAME:
swag init - Create docs.go
USAGE:
swag init [command options] [arguments...]
OPTIONS:
--generalInfo value, -g value API通用信息所在的go源文件路径,如果是相对路径则基于API解析目录 (默认: "main.go")
--dir value, -d value API解析目录 (默认: "./")
--exclude value 解析扫描时排除的目录,多个目录可用逗号分隔(默认:空)
--propertyStrategy value, -p value 结构体字段命名规则,三种:snakecase,camelcase,pascalcase (默认: "camelcase")
--output value, -o value 文件(swagger.json, swagger.yaml and doc.go)输出目录 (默认: "./docs")
--parseVendor 是否解析vendor目录里的go源文件,默认不
--parseDependency 是否解析依赖目录中的go源文件,默认不
--parseDependencyLevel, --pdl 对'--parseDependency'参数进行增强, 是否解析依赖目录中的go源文件, 0 不解析, 1 只解析对象模型, 2 只解析API, 3 对象模型和API都解析 (default: 0)
--markdownFiles value, --md value 指定API的描述信息所使用的markdown文件所在的目录
--generatedTime 是否输出时间到输出文件docs.go的顶部,默认是
--codeExampleFiles value, --cef value 解析包含用于 x-codeSamples 扩展的代码示例文件的文件夹,默认禁用
--parseInternal 解析 internal 包中的go文件,默认禁用
--parseDepth value 依赖解析深度 (默认: 100)
--instanceName value 设置文档实例名 (默认: "swagger")
```
```bash
swag fmt -h
NAME:
swag fmt - format swag comments
USAGE:
swag fmt [command options] [arguments...]
OPTIONS:
--dir value, -d value API解析目录 (默认: "./")
--exclude value 解析扫描时排除的目录,多个目录可用逗号分隔(默认:空)
--generalInfo value, -g value API通用信息所在的go源文件路径,如果是相对路径则基于API解析目录 (默认: "main.go")
--help, -h show help (default: false)
```
## 支持的Web框架
- [gin](http://github.com/swaggo/gin-swagger)
- [echo](http://github.com/swaggo/echo-swagger)
- [buffalo](https://github.com/swaggo/buffalo-swagger)
- [net/http](https://github.com/swaggo/http-swagger)
- [gorilla/mux](https://github.com/swaggo/http-swagger)
- [go-chi/chi](https://github.com/swaggo/http-swagger)
- [flamingo](https://github.com/i-love-flamingo/swagger)
- [fiber](https://github.com/gofiber/swagger)
- [atreugo](https://github.com/Nerzal/atreugo-swagger)
- [hertz](https://github.com/hertz-contrib/swagger)
## 如何与Gin集成
[点击此处](https://github.com/swaggo/swag/tree/master/example/celler)查看示例源代码。
1. 使用`swag init`生成Swagger2.0文档后,导入如下代码包:
```go
import "github.com/swaggo/gin-swagger" // gin-swagger middleware
import "github.com/swaggo/files" // swagger embed files
```
2. 在`main.go`源代码中添加通用的API注释:
```go
// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.basic BasicAuth
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api/
func main() {
r := gin.Default()
c := controller.NewController()
v1 := r.Group("/api/v1")
{
accounts := v1.Group("/accounts")
{
accounts.GET(":id", c.ShowAccount)
accounts.GET("", c.ListAccounts)
accounts.POST("", c.AddAccount)
accounts.DELETE(":id", c.DeleteAccount)
accounts.PATCH(":id", c.UpdateAccount)
accounts.POST(":id/images", c.UploadAccountImage)
}
//...
}
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run(":8080")
}
//...
```
此外,可以动态设置一些通用的API信息。生成的代码包`docs`导出`SwaggerInfo`变量,使用该变量可以通过编码的方式设置标题、描述、版本、主机和基础路径。使用Gin的示例:
```go
package main
import (
"github.com/gin-gonic/gin"
"github.com/swaggo/files"
"github.com/swaggo/gin-swagger"
"./docs" // docs is generated by Swag CLI, you have to import it.
)
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
func main() {
// programatically set swagger info
docs.SwaggerInfo.Title = "Swagger Example API"
docs.SwaggerInfo.Description = "This is a sample server Petstore server."
docs.SwaggerInfo.Version = "1.0"
docs.SwaggerInfo.Host = "petstore.swagger.io"
docs.SwaggerInfo.BasePath = "/v2"
docs.SwaggerInfo.Schemes = []string{"http", "https"}
r := gin.New()
// use ginSwagger middleware to serve the API docs
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run()
}
```
3. 在`controller`代码中添加API操作注释:
```go
package controller
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/swaggo/swag/example/celler/httputil"
"github.com/swaggo/swag/example/celler/model"
)
// ShowAccount godoc
// @Summary Show an account
// @Description get string by ID
// @Tags accounts
// @Accept json
// @Produce json
// @Param id path int true "Account ID"
// @Success 200 {object} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts/{id} [get]
func (c *Controller) ShowAccount(ctx *gin.Context) {
id := ctx.Param("id")
aid, err := strconv.Atoi(id)
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
account, err := model.AccountOne(aid)
if err != nil {
httputil.NewError(ctx, http.StatusNotFound, err)
return
}
ctx.JSON(http.StatusOK, account)
}
// ListAccounts godoc
// @Summary List accounts
// @Description get accounts
// @Tags accounts
// @Accept json
// @Produce json
// @Param q query string false "name search by q" Format(email)
// @Success 200 {array} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts [get]
func (c *Controller) ListAccounts(ctx *gin.Context) {
q := ctx.Request.URL.Query().Get("q")
accounts, err := model.AccountsAll(q)
if err != nil {
httputil.NewError(ctx, http.StatusNotFound, err)
return
}
ctx.JSON(http.StatusOK, accounts)
}
//...
```
```bash
swag init
```
4. 运行程序,然后在浏览器中访问 http://localhost:8080/swagger/index.html 。将看到Swagger 2.0 Api文档,如下所示:

## 格式化说明
可以针对Swag的注释自动格式化,就像`go fmt`。
此处查看格式化结果 [here](https://github.com/swaggo/swag/tree/master/example/celler).
示例:
```shell
swag fmt
```
排除目录(不扫描)示例:
```shell
swag fmt -d ./ --exclude ./internal
```
## 开发现状
[Swagger 2.0 文档](https://swagger.io/docs/specification/2-0/basic-structure/)
- [x] Basic Structure
- [x] API Host and Base Path
- [x] Paths and Operations
- [x] Describing Parameters
- [x] Describing Request Body
- [x] Describing Responses
- [x] MIME Types
- [x] Authentication
- [x] Basic Authentication
- [x] API Keys
- [x] Adding Examples
- [x] File Upload
- [x] Enums
- [x] Grouping Operations With Tags
- [ ] Swagger Extensions
## 声明式注释格式
## 通用API信息
**示例** [`celler/main.go`](https://github.com/swaggo/swag/blob/master/example/celler/main.go)
| 注释 | 说明 | 示例 |
| ----------------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| title | **必填** 应用程序的名称。 | // @title Swagger Example API |
| version | **必填** 提供应用程序API的版本。 | // @version 1.0 |
| description | 应用程序的简短描述。 | // @description This is a sample server celler server. |
| tag.name | 标签的名称。 | // @tag.name This is the name of the tag |
| tag.description | 标签的描述。 | // @tag.description Cool Description |
| tag.docs.url | 标签的外部文档的URL。 | // @tag.docs.url https://example.com |
| tag.docs.description | 标签的外部文档说明。 | // @tag.docs.description Best example documentation |
| termsOfService | API的服务条款。 | // @termsOfService http://swagger.io/terms/ |
| contact.name | 公开的API的联系信息。 | // @contact.name API Support |
| contact.url | 联系信息的URL。 必须采用网址格式。 | // @contact.url http://www.swagger.io/support |
| contact.email | 联系人/组织的电子邮件地址。 必须采用电子邮件地址的格式。 | // @contact.email support@swagger.io |
| license.name | **必填** 用于API的许可证名称。 | // @license.name Apache 2.0 |
| license.url | 用于API的许可证的URL。 必须采用网址格式。 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html |
| host | 运行API的主机(主机名或IP地址)。 | // @host localhost:8080 |
| BasePath | 运行API的基本路径。 | // @BasePath /api/v1 |
| accept | API 可以使用的 MIME 类型列表。 请注意,Accept 仅影响具有请求正文的操作,例如 POST、PUT 和 PATCH。 值必须如“[Mime类型](#mime类型)”中所述。 | // @accept json |
| produce | API可以生成的MIME类型的列表。值必须如“[Mime类型](#mime类型)”中所述。 | // @produce json |
| query.collection.format | 请求URI query里数组参数的默认格式:csv,multi,pipes,tsv,ssv。 如果未设置,则默认为csv。 | // @query.collection.format multi |
| schemes | 用空格分隔的请求的传输协议。 | // @schemes http https |
| externalDocs.description | Description of the external document. | // @externalDocs.description OpenAPI |
| externalDocs.url | URL of the external document. | // @externalDocs.url https://swagger.io/resources/open-api/ |
| x-name | 扩展的键必须以x-开头,并且只能使用json值 | // @x-example-key {"key": "value"} |
### 使用Markdown描述
如果文档中的短字符串不足以完整表达,或者需要展示图片,代码示例等类似的内容,则可能需要使用Markdown描述。要使用Markdown描述,请使用一下注释。
| 注释 | 说明 | 示例 |
| ------------------------ | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
| title | **必填** 应用程序的名称。 | // @title Swagger Example API |
| version | **必填** 提供应用程序API的版本。 | // @version 1.0 |
| description.markdown | 应用程序的简短描述。 从`api.md`文件中解析。 这是`@description`的替代用法。 | // @description.markdown No value needed, this parses the description from api.md |
| tag.name | 标签的名称。 | // @tag.name This is the name of the tag |
| tag.description.markdown | 标签说明,这是`tag.description`的替代用法。 该描述将从名为`tagname.md的`文件中读取。 | // @tag.description.markdown |
## API操作
Example [celler/controller](https://github.com/swaggo/swag/tree/master/example/celler/controller)
| 注释 | 描述 |
|----------------------|------------------------------------------------------------------------------------------------|
| description | 操作行为的详细说明。 |
| description.markdown | 应用程序的简短描述。该描述将从名为`endpointname.md`的文件中读取。 |
| id | 用于标识操作的唯一字符串。在所有API操作中必须唯一。 |
| tags | 每个API操作的标签列表,以逗号分隔。 |
| summary | 该操作的简短摘要。 |
| accept | API 可以使用的 MIME 类型列表。 请注意,Accept 仅影响具有请求正文的操作,例如 POST、PUT 和 PATCH。 值必须如“[Mime类型](#mime类型)”中所述。 |
| produce | API可以生成的MIME类型的列表。值必须如“[Mime类型](#mime类型)”中所述。 |
| param | 用空格分隔的参数。`param name`,`param type`,`data type`,`is mandatory?`,`comment` `attribute(optional)` |
| security | 每个API操作的[安全性](#安全性)。 |
| success | 以空格分隔的成功响应。`return code`,`{param type}`,`data type`,`comment` |
| failure | 以空格分隔的故障响应。`return code`,`{param type}`,`data type`,`comment` |
| response | 与success、failure作用相同 |
| header | 以空格分隔的头字段。 `return code`,`{param type}`,`data type`,`comment` |
| router | 以空格分隔的路径定义。 `path`,`[httpMethod]` |
| deprecatedrouter | 与router相同,但是是deprecated的。 |
| x-name | 扩展字段必须以`x-`开头,并且只能使用json值。 |
| deprecated | 将当前API操作的所有路径设置为deprecated |
## Mime类型
`swag` 接受所有格式正确的MIME类型, 即使匹配 `*/*`。除此之外,`swag`还接受某些MIME类型的别名,如下所示:
| Alias | MIME Type |
| --------------------- | --------------------------------- |
| json | application/json |
| xml | text/xml |
| plain | text/plain |
| html | text/html |
| mpfd | multipart/form-data |
| x-www-form-urlencoded | application/x-www-form-urlencoded |
| json-api | application/vnd.api+json |
| json-stream | application/x-json-stream |
| octet-stream | application/octet-stream |
| png | image/png |
| jpeg | image/jpeg |
| gif | image/gif |
| event-stream | text/event-stream |
## 参数类型
- query
- path
- header
- body
- formData
## 数据类型
- string (string)
- integer (int, uint, uint32, uint64)
- number (float32)
- boolean (bool)
- user defined struct
## 安全性
| 注释 | 描述 | 参数 | 示例 |
| -------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------ |
| securitydefinitions.basic | [Basic](https://swagger.io/docs/specification/2-0/authentication/basic-authentication/) auth. | | // @securityDefinitions.basic BasicAuth |
| securitydefinitions.apikey | [API key](https://swagger.io/docs/specification/2-0/authentication/api-keys/) auth. | in, name | // @securityDefinitions.apikey ApiKeyAuth |
| securitydefinitions.oauth2.application | [OAuth2 application](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, scope | // @securitydefinitions.oauth2.application OAuth2Application |
| securitydefinitions.oauth2.implicit | [OAuth2 implicit](https://swagger.io/docs/specification/authentication/oauth2/) auth. | authorizationUrl, scope | // @securitydefinitions.oauth2.implicit OAuth2Implicit |
| securitydefinitions.oauth2.password | [OAuth2 password](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, scope | // @securitydefinitions.oauth2.password OAuth2Password |
| securitydefinitions.oauth2.accessCode | [OAuth2 access code](https://swagger.io/docs/specification/authentication/oauth2/) auth. | tokenUrl, authorizationUrl, scope | // @securitydefinitions.oauth2.accessCode OAuth2AccessCode |
| 参数注释 | 示例 |
| ---------------- | -------------------------------------------------------- |
| in | // @in header |
| name | // @name Authorization |
| tokenUrl | // @tokenUrl https://example.com/oauth/token |
| authorizationurl | // @authorizationurl https://example.com/oauth/authorize |
| scope.hoge | // @scope.write Grants write access |
## 属性
```go
// @Param enumstring query string false "string enums" Enums(A, B, C)
// @Param enumint query int false "int enums" Enums(1, 2, 3)
// @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3)
// @Param string query string false "string valid" minlength(5) maxlength(10)
// @Param int query int false "int valid" minimum(1) maximum(10)
// @Param default query string false "string default" default(A)
// @Param collection query []string false "string collection" collectionFormat(multi)
// @Param extensions query []string false "string collection" extensions(x-example=test,x-nullable)
```
也适用于结构体字段:
```go
type Foo struct {
Bar string `minLength:"4" maxLength:"16"`
Baz int `minimum:"10" maximum:"20" default:"15"`
Qux []string `enums:"foo,bar,baz"`
}
```
### 当前可用的
| 字段名 | 类型 | 描述 |
| ---------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| default | * | 声明如果未提供任何参数,则服务器将使用的默认参数值,例如,如果请求中的客户端未提供该参数,则用于控制每页结果数的“计数”可能默认为100。 (注意:“default”对于必需的参数没有意义)。参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2。 与JSON模式不同,此值务必符合此参数的定义[类型](#parameterType)。 |
| maximum | `number` | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2. |
| minimum | `number` | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3. |
| maxLength | `integer` | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.1. |
| minLength | `integer` | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.2. |
| enums | [\*] | 参看 https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1. |
| format | `string` | 上面提到的[类型](#parameterType)的扩展格式。有关更多详细信息,请参见[数据类型格式](https://swagger.io/specification/v2/#dataTypeFormat)。 |
| collectionFormat | `string` | 指定query数组参数的格式。 可能的值为: - `csv` - 逗号分隔值 `foo,bar`.
- `ssv` - 空格分隔值 `foo bar`.
- `tsv` - 制表符分隔值 `foo\tbar`.
- `pipes` - 管道符分隔值
foo|bar. - `multi` - 对应于多个参数实例,而不是单个实例 `foo=bar&foo=baz` 的多个值。这仅对“`query`”或“`formData`”中的参数有效。
默认值是 `csv`。 |
### 进一步的
| 字段名 | 类型 | 描述 |
| ----------- | :-------: | ---------------------------------------------------------------------------------- |
| multipleOf | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.1. |
| pattern | `string` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3. |
| maxItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.2. |
| minItems | `integer` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.3. |
| uniqueItems | `boolean` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.3.4. |
## 样例
### 多行的描述
可以在常规api描述或路由定义中添加跨越多行的描述,如下所示:
```go
// @description This is the first line
// @description This is the second line
// @description And so forth.
```
### 用户自定义的具有数组类型的结构
```go
// @Success 200 {array} model.Account <-- This is a user defined struct.
```
```go
package model
type Account struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"account name"`
}
```
### 响应对象中的模型组合
```go
// JSONResult的data字段类型将被proto.Order类型替换
@success 200 {object} jsonresult.JSONResult{data=proto.Order} "desc"
```
```go
type JSONResult struct {
Code int `json:"code" `
Message string `json:"message"`
Data interface{} `json:"data"`
}
type Order struct { //in `proto` package
...
}
```
- 还支持对象数组和原始类型作为嵌套响应
```go
@success 200 {object} jsonresult.JSONResult{data=[]proto.Order} "desc"
@success 200 {object} jsonresult.JSONResult{data=string} "desc"
@success 200 {object} jsonresult.JSONResult{data=[]string} "desc"
```
- 替换多个字段的类型。如果某字段不存在,将添加该字段。
```go
@success 200 {object} jsonresult.JSONResult{data1=string,data2=[]string,data3=proto.Order,data4=[]proto.Order} "desc"
```
### 在响应中增加头字段
```go
// @Success 200 {string} string "ok"
// @failure 400 {string} string "error"
// @response default {string} string "other error"
// @Header 200 {string} Location "/entity/1"
// @Header 200,400,default {string} Token "token"
// @Header all {string} Token2 "token2"
```
### 使用多路径参数
```go
/// ...
// @Param group_id path int true "Group ID"
// @Param account_id path int true "Account ID"
// ...
// @Router /examples/groups/{group_id}/accounts/{account_id} [get]
```
### 结构体的示例值
```go
type Account struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"account name"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"`
}
```
### 结构体描述
```go
type Account struct {
// ID this is userid
ID int `json:"id"`
Name string `json:"name"` // This is Name
}
```
### 使用`swaggertype`标签更改字段类型
[#201](https://github.com/swaggo/swag/issues/201#issuecomment-475479409)
```go
type TimestampTime struct {
time.Time
}
///实现encoding.JSON.Marshaler接口
func (t *TimestampTime) MarshalJSON() ([]byte, error) {
bin := make([]byte, 16)
bin = strconv.AppendInt(bin[:0], t.Time.Unix(), 10)
return bin, nil
}
///实现encoding.JSON.Unmarshaler接口
func (t *TimestampTime) UnmarshalJSON(bin []byte) error {
v, err := strconv.ParseInt(string(bin), 10, 64)
if err != nil {
return err
}
t.Time = time.Unix(v, 0)
return nil
}
///
type Account struct {
// 使用`swaggertype`标签将别名类型更改为内置类型integer
ID sql.NullInt64 `json:"id" swaggertype:"integer"`
// 使用`swaggertype`标签更改struct类型为内置类型integer
RegisterTime TimestampTime `json:"register_time" swaggertype:"primitive,integer"`
// Array types can be overridden using "array," format
Coeffs []big.Float `json:"coeffs" swaggertype:"array,number"`
}
```
[#379](https://github.com/swaggo/swag/issues/379)
```go
type CerticateKeyPair struct {
Crt []byte `json:"crt" swaggertype:"string" format:"base64" example:"U3dhZ2dlciByb2Nrcw=="`
Key []byte `json:"key" swaggertype:"string" format:"base64" example:"U3dhZ2dlciByb2Nrcw=="`
}
```
生成的swagger文档如下:
```go
"api.MyBinding": {
"type":"object",
"properties":{
"crt":{
"type":"string",
"format":"base64",
"example":"U3dhZ2dlciByb2Nrcw=="
},
"key":{
"type":"string",
"format":"base64",
"example":"U3dhZ2dlciByb2Nrcw=="
}
}
}
```
### 使用`swaggerignore`标签排除字段
```go
type Account struct {
ID string `json:"id"`
Name string `json:"name"`
Ignored int `swaggerignore:"true"`
}
```
### 将扩展信息添加到结构字段
```go
type Account struct {
ID string `json:"id" extensions:"x-nullable,x-abc=def,!x-omitempty"` // 扩展字段必须以"x-"开头
}
```
生成swagger文档,如下所示:
```go
"Account": {
"type": "object",
"properties": {
"id": {
"type": "string",
"x-nullable": true,
"x-abc": "def",
"x-omitempty": false
}
}
}
```
### 对展示的模型重命名
```go
type Resp struct {
Code int
}//@name Response
```
### 如何使用安全性注释
通用API信息。
```go
// @securityDefinitions.basic BasicAuth
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
```
每个API操作。
```go
// @Security ApiKeyAuth
```
使用AND条件。
```go
// @Security ApiKeyAuth && OAuth2Application[write, admin]
```
## 项目相关
This project was inspired by [yvasiyarov/swagger](https://github.com/yvasiyarov/swagger) but we simplified the usage and added support a variety of [web frameworks](#supported-web-frameworks). Gopher image source is [tenntenn/gopher-stickers](https://github.com/tenntenn/gopher-stickers). It has licenses [creative commons licensing](http://creativecommons.org/licenses/by/3.0/deed.en).
## 贡献者
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
## 支持者
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/swag#backer)]
## 赞助商
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/swag#sponsor)]
## License
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fswaggo%2Fswag?ref=badge_large)
================================================
FILE: cmd/swag/main.go
================================================
package main
import (
"fmt"
"io"
"log"
"os"
"strings"
"github.com/urfave/cli/v2"
"github.com/swaggo/swag"
"github.com/swaggo/swag/format"
"github.com/swaggo/swag/gen"
)
const (
searchDirFlag = "dir"
excludeFlag = "exclude"
generalInfoFlag = "generalInfo"
pipeFlag = "pipe"
propertyStrategyFlag = "propertyStrategy"
outputFlag = "output"
outputTypesFlag = "outputTypes"
parseVendorFlag = "parseVendor"
parseDependencyFlag = "parseDependency"
useStructNameFlag = "useStructName"
parseDependencyLevelFlag = "parseDependencyLevel"
markdownFilesFlag = "markdownFiles"
codeExampleFilesFlag = "codeExampleFiles"
parseInternalFlag = "parseInternal"
generatedTimeFlag = "generatedTime"
requiredByDefaultFlag = "requiredByDefault"
parseDepthFlag = "parseDepth"
instanceNameFlag = "instanceName"
overridesFileFlag = "overridesFile"
parseGoListFlag = "parseGoList"
quietFlag = "quiet"
tagsFlag = "tags"
parseExtensionFlag = "parseExtension"
templateDelimsFlag = "templateDelims"
packageName = "packageName"
collectionFormatFlag = "collectionFormat"
packagePrefixFlag = "packagePrefix"
stateFlag = "state"
parseFuncBodyFlag = "parseFuncBody"
parseGoPackagesFlag = "parseGoPackages"
)
var initFlags = []cli.Flag{
&cli.BoolFlag{
Name: quietFlag,
Aliases: []string{"q"},
Usage: "Make the logger quiet.",
},
&cli.StringFlag{
Name: generalInfoFlag,
Aliases: []string{"g"},
Value: "main.go",
Usage: "Go file path in which 'swagger general API Info' is written",
},
&cli.StringFlag{
Name: searchDirFlag,
Aliases: []string{"d"},
Value: "./",
Usage: "Directories you want to parse,comma separated and general-info file must be in the first one",
},
&cli.StringFlag{
Name: excludeFlag,
Usage: "Exclude directories and files when searching, comma separated",
},
&cli.StringFlag{
Name: propertyStrategyFlag,
Aliases: []string{"p"},
Value: swag.CamelCase,
Usage: "Property Naming Strategy like " + swag.SnakeCase + "," + swag.CamelCase + "," + swag.PascalCase,
},
&cli.StringFlag{
Name: outputFlag,
Aliases: []string{"o"},
Value: "./docs",
Usage: "Output directory for all the generated files(swagger.json, swagger.yaml and docs.go)",
},
&cli.StringFlag{
Name: outputTypesFlag,
Aliases: []string{"ot"},
Value: "go,json,yaml",
Usage: "Output types of generated files (docs.go, swagger.json, swagger.yaml) like go,json,yaml",
},
&cli.BoolFlag{
Name: parseVendorFlag,
Usage: "Parse go files in 'vendor' folder, disabled by default",
},
&cli.IntFlag{
Name: parseDependencyLevelFlag,
Aliases: []string{"pdl"},
Usage: "Parse go files inside dependency folder, 0 disabled, 1 only parse models, 2 only parse operations, 3 parse all",
},
&cli.BoolFlag{
Name: parseDependencyFlag,
Aliases: []string{"pd"},
Usage: "Parse go files inside dependency folder, disabled by default",
},
&cli.BoolFlag{
Name: useStructNameFlag,
Aliases: []string{"st"},
Usage: "Dont use those ugly full-path names when using dependency flag",
},
&cli.StringFlag{
Name: markdownFilesFlag,
Aliases: []string{"md"},
Value: "",
Usage: "Parse folder containing markdown files to use as description, disabled by default",
},
&cli.StringFlag{
Name: codeExampleFilesFlag,
Aliases: []string{"cef"},
Value: "",
Usage: "Parse folder containing code example files to use for the x-codeSamples extension, disabled by default",
},
&cli.BoolFlag{
Name: parseInternalFlag,
Usage: "Parse go files in internal packages, disabled by default",
},
&cli.BoolFlag{
Name: generatedTimeFlag,
Usage: "Generate timestamp at the top of docs.go, disabled by default",
},
&cli.IntFlag{
Name: parseDepthFlag,
Value: 100,
Usage: "Dependency parse depth",
},
&cli.BoolFlag{
Name: requiredByDefaultFlag,
Usage: "Set validation required for all fields by default",
},
&cli.StringFlag{
Name: instanceNameFlag,
Value: "",
Usage: "This parameter can be used to name different swagger document instances. It is optional.",
},
&cli.StringFlag{
Name: overridesFileFlag,
Value: gen.DefaultOverridesFile,
Usage: "File to read global type overrides from.",
},
&cli.BoolFlag{
Name: parseGoListFlag,
Value: true,
Usage: "Parse dependency via 'go list'",
},
&cli.StringFlag{
Name: parseExtensionFlag,
Value: "",
Usage: "Parse only those operations that match given extension",
},
&cli.StringFlag{
Name: tagsFlag,
Aliases: []string{"t"},
Value: "",
Usage: "A comma-separated list of tags to filter the APIs for which the documentation is generated.Special case if the tag is prefixed with the '!' character then the APIs with that tag will be excluded",
},
&cli.StringFlag{
Name: templateDelimsFlag,
Aliases: []string{"td"},
Value: "",
Usage: "Provide custom delimiters for Go template generation. The format is leftDelim,rightDelim. For example: \"[[,]]\"",
},
&cli.StringFlag{
Name: packageName,
Value: "",
Usage: "A package name of docs.go, using output directory name by default (check `--output` option)",
},
&cli.StringFlag{
Name: collectionFormatFlag,
Aliases: []string{"cf"},
Value: "csv",
Usage: "Set default collection format",
},
&cli.StringFlag{
Name: packagePrefixFlag,
Value: "",
Usage: "Parse only packages whose import path match the given prefix, comma separated",
},
&cli.StringFlag{
Name: stateFlag,
Value: "",
Usage: "Set host state for swagger.json",
},
&cli.BoolFlag{
Name: parseFuncBodyFlag,
Usage: "Parse API info within body of functions in go files, disabled by default",
},
&cli.BoolFlag{
Name: parseGoPackagesFlag,
Usage: "Parse Go sources by golang.org/x/tools/go/packages, disabled by default",
},
}
func initAction(ctx *cli.Context) error {
strategy := ctx.String(propertyStrategyFlag)
switch strategy {
case swag.CamelCase, swag.SnakeCase, swag.PascalCase:
default:
return fmt.Errorf("not supported %s propertyStrategy", strategy)
}
leftDelim, rightDelim := "{{", "}}"
if ctx.IsSet(templateDelimsFlag) {
delims := strings.Split(ctx.String(templateDelimsFlag), ",")
if len(delims) != 2 {
return fmt.Errorf(
"exactly two template delimiters must be provided, comma separated",
)
} else if delims[0] == delims[1] {
return fmt.Errorf("template delimiters must be different")
}
leftDelim, rightDelim = strings.TrimSpace(
delims[0],
), strings.TrimSpace(
delims[1],
)
}
outputTypes := strings.Split(ctx.String(outputTypesFlag), ",")
if len(outputTypes) == 0 {
return fmt.Errorf("no output types specified")
}
logger := log.New(os.Stdout, "", log.LstdFlags)
if ctx.Bool(quietFlag) {
logger = log.New(io.Discard, "", log.LstdFlags)
}
collectionFormat := swag.TransToValidCollectionFormat(
ctx.String(collectionFormatFlag),
)
if collectionFormat == "" {
return fmt.Errorf(
"not supported %s collectionFormat",
ctx.String(collectionFormat),
)
}
var pdv = ctx.Int(parseDependencyLevelFlag)
if pdv == 0 {
if ctx.Bool(parseDependencyFlag) {
pdv = 1
}
}
return gen.New().Build(&gen.Config{
SearchDir: ctx.String(searchDirFlag),
Excludes: ctx.String(excludeFlag),
ParseExtension: ctx.String(parseExtensionFlag),
MainAPIFile: ctx.String(generalInfoFlag),
PropNamingStrategy: strategy,
OutputDir: ctx.String(outputFlag),
OutputTypes: outputTypes,
ParseVendor: ctx.Bool(parseVendorFlag),
ParseDependency: pdv,
MarkdownFilesDir: ctx.String(markdownFilesFlag),
ParseInternal: ctx.Bool(parseInternalFlag),
UseStructNames: ctx.Bool(useStructNameFlag),
GeneratedTime: ctx.Bool(generatedTimeFlag),
RequiredByDefault: ctx.Bool(requiredByDefaultFlag),
CodeExampleFilesDir: ctx.String(codeExampleFilesFlag),
ParseDepth: ctx.Int(parseDepthFlag),
InstanceName: ctx.String(instanceNameFlag),
OverridesFile: ctx.String(overridesFileFlag),
ParseGoList: ctx.Bool(parseGoListFlag),
Tags: ctx.String(tagsFlag),
LeftTemplateDelim: leftDelim,
RightTemplateDelim: rightDelim,
PackageName: ctx.String(packageName),
Debugger: logger,
CollectionFormat: collectionFormat,
PackagePrefix: ctx.String(packagePrefixFlag),
State: ctx.String(stateFlag),
ParseFuncBody: ctx.Bool(parseFuncBodyFlag),
ParseGoPackages: ctx.Bool(parseGoPackagesFlag),
})
}
func main() {
app := cli.NewApp()
app.Version = swag.Version
app.Usage = "Automatically generate RESTful API documentation with Swagger 2.0 for Go."
app.Commands = []*cli.Command{
{
Name: "init",
Aliases: []string{"i"},
Usage: "Create docs.go",
Action: initAction,
Flags: initFlags,
},
{
Name: "fmt",
Aliases: []string{"f"},
Usage: "format swag comments",
Action: func(c *cli.Context) error {
if c.Bool(pipeFlag) {
return format.New().Run(os.Stdin, os.Stdout)
}
searchDir := c.String(searchDirFlag)
excludeDir := c.String(excludeFlag)
mainFile := c.String(generalInfoFlag)
return format.New().Build(&format.Config{
SearchDir: searchDir,
Excludes: excludeDir,
MainFile: mainFile,
})
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: searchDirFlag,
Aliases: []string{"d"},
Value: "./",
Usage: "Directories you want to parse,comma separated and general-info file must be in the first one",
},
&cli.StringFlag{
Name: excludeFlag,
Usage: "Exclude directories and files when searching, comma separated",
},
&cli.StringFlag{
Name: generalInfoFlag,
Aliases: []string{"g"},
Value: "main.go",
Usage: "Go file path in which 'swagger general API Info' is written",
},
&cli.BoolFlag{
Name: "pipe",
Aliases: []string{"p"},
Value: false,
Usage: "Read from stdin, write to stdout.",
},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
================================================
FILE: const.go
================================================
package swag
import (
"go/ast"
"go/token"
"reflect"
"strconv"
"strings"
"unicode/utf8"
)
// ConstVariable a model to record a const variable
type ConstVariable struct {
Name *ast.Ident
Type ast.Expr
Value any
Comment string
File *ast.File
Pkg *PackageDefinitions
}
// VariableName gets the name for this const variable, taking into account comment overrides.
func (cv *ConstVariable) VariableName() string {
if ignoreNameOverride(cv.Name.Name) {
return cv.Name.Name[1:]
}
if overriddenName := cv.nameOverride(); overriddenName != "" {
return overriddenName
}
return cv.Name.Name
}
func (cv *ConstVariable) nameOverride() string {
if len(cv.Comment) == 0 {
return ""
}
comment := strings.TrimSpace(strings.TrimLeft(cv.Comment, "/"))
texts := overrideNameRegex.FindStringSubmatch(comment)
if len(texts) > 1 {
return texts[1]
}
return ""
}
var escapedChars = map[uint8]uint8{
'n': '\n',
'r': '\r',
't': '\t',
'v': '\v',
'\\': '\\',
'"': '"',
}
// EvaluateEscapedChar parse escaped character
func EvaluateEscapedChar(text string) rune {
if len(text) == 1 {
return rune(text[0])
}
if len(text) == 2 && text[0] == '\\' {
return rune(escapedChars[text[1]])
}
if len(text) == 6 && text[0:2] == "\\u" {
n, err := strconv.ParseInt(text[2:], 16, 32)
if err == nil {
return rune(n)
}
}
return 0
}
// EvaluateEscapedString parse escaped characters in string
func EvaluateEscapedString(text string) string {
if !strings.ContainsRune(text, '\\') {
return text
}
result := make([]byte, 0, len(text))
for i := 0; i < len(text); i++ {
if text[i] == '\\' {
i++
if text[i] == 'u' {
i++
char, err := strconv.ParseInt(text[i:i+4], 16, 32)
if err == nil {
result = utf8.AppendRune(result, rune(char))
}
i += 3
} else if c, ok := escapedChars[text[i]]; ok {
result = append(result, c)
}
} else {
result = append(result, text[i])
}
}
return string(result)
}
// EvaluateDataConversion evaluate the type a explicit type conversion
func EvaluateDataConversion(x any, typeName string) any {
switch value := x.(type) {
case int:
switch typeName {
case "int":
return int(value)
case "byte":
return byte(value)
case "int8":
return int8(value)
case "int16":
return int16(value)
case "int32":
return int32(value)
case "int64":
return int64(value)
case "uint":
return uint(value)
case "uint8":
return uint8(value)
case "uint16":
return uint16(value)
case "uint32":
return uint32(value)
case "uint64":
return uint64(value)
case "rune":
return rune(value)
}
case uint:
switch typeName {
case "int":
return int(value)
case "byte":
return byte(value)
case "int8":
return int8(value)
case "int16":
return int16(value)
case "int32":
return int32(value)
case "int64":
return int64(value)
case "uint":
return uint(value)
case "uint8":
return uint8(value)
case "uint16":
return uint16(value)
case "uint32":
return uint32(value)
case "uint64":
return uint64(value)
case "rune":
return rune(value)
}
case int8:
switch typeName {
case "int":
return int(value)
case "byte":
return byte(value)
case "int8":
return int8(value)
case "int16":
return int16(value)
case "int32":
return int32(value)
case "int64":
return int64(value)
case "uint":
return uint(value)
case "uint8":
return uint8(value)
case "uint16":
return uint16(value)
case "uint32":
return uint32(value)
case "uint64":
return uint64(value)
case "rune":
return rune(value)
}
case uint8:
switch typeName {
case "int":
return int(value)
case "byte":
return byte(value)
case "int8":
return int8(value)
case "int16":
return int16(value)
case "int32":
return int32(value)
case "int64":
return int64(value)
case "uint":
return uint(value)
case "uint8":
return uint8(value)
case "uint16":
return uint16(value)
case "uint32":
return uint32(value)
case "uint64":
return uint64(value)
case "rune":
return rune(value)
}
case int16:
switch typeName {
case "int":
return int(value)
case "byte":
return byte(value)
case "int8":
return int8(value)
case "int16":
return int16(value)
case "int32":
return int32(value)
case "int64":
return int64(value)
case "uint":
return uint(value)
case "uint8":
return uint8(value)
case "uint16":
return uint16(value)
case "uint32":
return uint32(value)
case "uint64":
return uint64(value)
case "rune":
return rune(value)
}
case uint16:
switch typeName {
case "int":
return int(value)
case "byte":
return byte(value)
case "int8":
return int8(value)
case "int16":
return int16(value)
case "int32":
return int32(value)
case "int64":
return int64(value)
case "uint":
return uint(value)
case "uint8":
return uint8(value)
case "uint16":
return uint16(value)
case "uint32":
return uint32(value)
case "uint64":
return uint64(value)
case "rune":
return rune(value)
}
case int32:
switch typeName {
case "int":
return int(value)
case "byte":
return byte(value)
case "int8":
return int8(value)
case "int16":
return int16(value)
case "int32":
return int32(value)
case "int64":
return int64(value)
case "uint":
return uint(value)
case "uint8":
return uint8(value)
case "uint16":
return uint16(value)
case "uint32":
return uint32(value)
case "uint64":
return uint64(value)
case "rune":
return rune(value)
case "string":
return string(value)
}
case uint32:
switch typeName {
case "int":
return int(value)
case "byte":
return byte(value)
case "int8":
return int8(value)
case "int16":
return int16(value)
case "int32":
return int32(value)
case "int64":
return int64(value)
case "uint":
return uint(value)
case "uint8":
return uint8(value)
case "uint16":
return uint16(value)
case "uint32":
return uint32(value)
case "uint64":
return uint64(value)
case "rune":
return rune(value)
}
case int64:
switch typeName {
case "int":
return int(value)
case "byte":
return byte(value)
case "int8":
return int8(value)
case "int16":
return int16(value)
case "int32":
return int32(value)
case "int64":
return int64(value)
case "uint":
return uint(value)
case "uint8":
return uint8(value)
case "uint16":
return uint16(value)
case "uint32":
return uint32(value)
case "uint64":
return uint64(value)
case "rune":
return rune(value)
}
case uint64:
switch typeName {
case "int":
return int(value)
case "byte":
return byte(value)
case "int8":
return int8(value)
case "int16":
return int16(value)
case "int32":
return int32(value)
case "int64":
return int64(value)
case "uint":
return uint(value)
case "uint8":
return uint8(value)
case "uint16":
return uint16(value)
case "uint32":
return uint32(value)
case "uint64":
return uint64(value)
case "rune":
return rune(value)
}
case string:
switch typeName {
case "string":
return value
}
}
return nil
}
// EvaluateUnary evaluate the type and value of a unary expression
func EvaluateUnary(x any, operator token.Token, xtype ast.Expr) (any, ast.Expr) {
switch operator {
case token.SUB:
switch value := x.(type) {
case int:
return -value, xtype
case int8:
return -value, xtype
case int16:
return -value, xtype
case int32:
return -value, xtype
case int64:
return -value, xtype
}
case token.XOR:
switch value := x.(type) {
case int:
return ^value, xtype
case int8:
return ^value, xtype
case int16:
return ^value, xtype
case int32:
return ^value, xtype
case int64:
return ^value, xtype
case uint:
return ^value, xtype
case uint8:
return ^value, xtype
case uint16:
return ^value, xtype
case uint32:
return ^value, xtype
case uint64:
return ^value, xtype
}
}
return nil, nil
}
// EvaluateBinary evaluate the type and value of a binary expression
func EvaluateBinary(x, y any, operator token.Token, xtype, ytype ast.Expr) (any, ast.Expr) {
if operator == token.SHR || operator == token.SHL {
var rightOperand uint64
yValue := reflect.ValueOf(y)
if yValue.CanUint() {
rightOperand = yValue.Uint()
} else if yValue.CanInt() {
rightOperand = uint64(yValue.Int())
}
switch operator {
case token.SHL:
switch xValue := x.(type) {
case int:
return xValue << rightOperand, xtype
case int8:
return xValue << rightOperand, xtype
case int16:
return xValue << rightOperand, xtype
case int32:
return xValue << rightOperand, xtype
case int64:
return xValue << rightOperand, xtype
case uint:
return xValue << rightOperand, xtype
case uint8:
return xValue << rightOperand, xtype
case uint16:
return xValue << rightOperand, xtype
case uint32:
return xValue << rightOperand, xtype
case uint64:
return xValue << rightOperand, xtype
}
case token.SHR:
switch xValue := x.(type) {
case int:
return xValue >> rightOperand, xtype
case int8:
return xValue >> rightOperand, xtype
case int16:
return xValue >> rightOperand, xtype
case int32:
return xValue >> rightOperand, xtype
case int64:
return xValue >> rightOperand, xtype
case uint:
return xValue >> rightOperand, xtype
case uint8:
return xValue >> rightOperand, xtype
case uint16:
return xValue >> rightOperand, xtype
case uint32:
return xValue >> rightOperand, xtype
case uint64:
return xValue >> rightOperand, xtype
}
}
return nil, nil
}
evalType := xtype
if evalType == nil {
evalType = ytype
}
xValue := reflect.ValueOf(x)
yValue := reflect.ValueOf(y)
if xValue.Kind() == reflect.String && yValue.Kind() == reflect.String {
return xValue.String() + yValue.String(), evalType
}
var targetValue reflect.Value
if xValue.Kind() != reflect.Int {
targetValue = reflect.New(xValue.Type()).Elem()
} else {
targetValue = reflect.New(yValue.Type()).Elem()
}
switch operator {
case token.ADD:
if xValue.CanInt() && yValue.CanInt() {
targetValue.SetInt(xValue.Int() + yValue.Int())
} else if xValue.CanUint() && yValue.CanUint() {
targetValue.SetUint(xValue.Uint() + yValue.Uint())
} else if xValue.CanInt() && yValue.CanUint() {
targetValue.SetUint(uint64(xValue.Int()) + yValue.Uint())
} else if xValue.CanUint() && yValue.CanInt() {
targetValue.SetUint(xValue.Uint() + uint64(yValue.Int()))
}
case token.SUB:
if xValue.CanInt() && yValue.CanInt() {
targetValue.SetInt(xValue.Int() - yValue.Int())
} else if xValue.CanUint() && yValue.CanUint() {
targetValue.SetUint(xValue.Uint() - yValue.Uint())
} else if xValue.CanInt() && yValue.CanUint() {
targetValue.SetUint(uint64(xValue.Int()) - yValue.Uint())
} else if xValue.CanUint() && yValue.CanInt() {
targetValue.SetUint(xValue.Uint() - uint64(yValue.Int()))
}
case token.MUL:
if xValue.CanInt() && yValue.CanInt() {
targetValue.SetInt(xValue.Int() * yValue.Int())
} else if xValue.CanUint() && yValue.CanUint() {
targetValue.SetUint(xValue.Uint() * yValue.Uint())
} else if xValue.CanInt() && yValue.CanUint() {
targetValue.SetUint(uint64(xValue.Int()) * yValue.Uint())
} else if xValue.CanUint() && yValue.CanInt() {
targetValue.SetUint(xValue.Uint() * uint64(yValue.Int()))
}
case token.QUO:
if xValue.CanInt() && yValue.CanInt() {
targetValue.SetInt(xValue.Int() / yValue.Int())
} else if xValue.CanUint() && yValue.CanUint() {
targetValue.SetUint(xValue.Uint() / yValue.Uint())
} else if xValue.CanInt() && yValue.CanUint() {
targetValue.SetUint(uint64(xValue.Int()) / yValue.Uint())
} else if xValue.CanUint() && yValue.CanInt() {
targetValue.SetUint(xValue.Uint() / uint64(yValue.Int()))
}
case token.REM:
if xValue.CanInt() && yValue.CanInt() {
targetValue.SetInt(xValue.Int() % yValue.Int())
} else if xValue.CanUint() && yValue.CanUint() {
targetValue.SetUint(xValue.Uint() % yValue.Uint())
} else if xValue.CanInt() && yValue.CanUint() {
targetValue.SetUint(uint64(xValue.Int()) % yValue.Uint())
} else if xValue.CanUint() && yValue.CanInt() {
targetValue.SetUint(xValue.Uint() % uint64(yValue.Int()))
}
case token.AND:
if xValue.CanInt() && yValue.CanInt() {
targetValue.SetInt(xValue.Int() & yValue.Int())
} else if xValue.CanUint() && yValue.CanUint() {
targetValue.SetUint(xValue.Uint() & yValue.Uint())
} else if xValue.CanInt() && yValue.CanUint() {
targetValue.SetUint(uint64(xValue.Int()) & yValue.Uint())
} else if xValue.CanUint() && yValue.CanInt() {
targetValue.SetUint(xValue.Uint() & uint64(yValue.Int()))
}
case token.OR:
if xValue.CanInt() && yValue.CanInt() {
targetValue.SetInt(xValue.Int() | yValue.Int())
} else if xValue.CanUint() && yValue.CanUint() {
targetValue.SetUint(xValue.Uint() | yValue.Uint())
} else if xValue.CanInt() && yValue.CanUint() {
targetValue.SetUint(uint64(xValue.Int()) | yValue.Uint())
} else if xValue.CanUint() && yValue.CanInt() {
targetValue.SetUint(xValue.Uint() | uint64(yValue.Int()))
}
case token.XOR:
if xValue.CanInt() && yValue.CanInt() {
targetValue.SetInt(xValue.Int() ^ yValue.Int())
} else if xValue.CanUint() && yValue.CanUint() {
targetValue.SetUint(xValue.Uint() ^ yValue.Uint())
} else if xValue.CanInt() && yValue.CanUint() {
targetValue.SetUint(uint64(xValue.Int()) ^ yValue.Uint())
} else if xValue.CanUint() && yValue.CanInt() {
targetValue.SetUint(xValue.Uint() ^ uint64(yValue.Int()))
}
}
return targetValue.Interface(), evalType
}
================================================
FILE: doc.go
================================================
/*
Package swag converts Go annotations to Swagger Documentation 2.0.
See https://github.com/swaggo/swag for more information about swag.
*/
package swag // import "github.com/swaggo/swag"
================================================
FILE: enums.go
================================================
package swag
const (
enumVarNamesExtension = "x-enum-varnames"
enumCommentsExtension = "x-enum-comments"
enumDescriptionsExtension = "x-enum-descriptions"
)
// EnumValue a model to record an enum consts variable
type EnumValue struct {
key string
Value any
Comment string
}
================================================
FILE: enums_test.go
================================================
package swag
import (
"encoding/json"
"math/bits"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseGlobalEnums(t *testing.T) {
searchDir := "testdata/enums"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
constsPath := "github.com/swaggo/swag/testdata/enums/consts"
assert.Equal(t, bits.UintSize, p.packages.packages[constsPath].ConstTable["uintSize"].Value)
assert.Equal(t, int32(62), p.packages.packages[constsPath].ConstTable["maxBase"].Value)
assert.Equal(t, 8, p.packages.packages[constsPath].ConstTable["shlByLen"].Value)
assert.Equal(t, 255, p.packages.packages[constsPath].ConstTable["hexnum"].Value)
assert.Equal(t, 15, p.packages.packages[constsPath].ConstTable["octnum"].Value)
assert.Equal(t, `aa\nbb\u8888cc`, p.packages.packages[constsPath].ConstTable["nonescapestr"].Value)
assert.Equal(t, "aa\nbb\u8888cc", p.packages.packages[constsPath].ConstTable["escapestr"].Value)
assert.Equal(t, 1_000_000, p.packages.packages[constsPath].ConstTable["underscored"].Value)
assert.Equal(t, 0b10001000, p.packages.packages[constsPath].ConstTable["binaryInteger"].Value)
assert.Equal(t, 0o755, p.packages.packages[constsPath].ConstTable["octInteger"].Value)
typesPath := "github.com/swaggo/swag/testdata/enums/types"
difficultyEnums := p.packages.packages[typesPath].TypeDefinitions["Difficulty"].Enums
assert.Equal(t, "Easy", difficultyEnums[0].key)
assert.Equal(t, "", difficultyEnums[0].Comment)
assert.Equal(t, "Medium", difficultyEnums[1].key)
assert.Equal(t, "This one also has a comment", difficultyEnums[1].Comment)
assert.Equal(t, "DifficultyHard", difficultyEnums[2].key)
assert.Equal(t, "This means really hard", difficultyEnums[2].Comment)
genericDifficultyEnums := p.packages.packages[typesPath].TypeDefinitions["GenericDifficulty"].Enums
assert.Equal(t, "GenericEasy", genericDifficultyEnums[0].key)
assert.Equal(t, "", genericDifficultyEnums[0].Comment)
assert.Equal(t, "GenericMedium", genericDifficultyEnums[1].key)
assert.Equal(t, "This one also has a comment", genericDifficultyEnums[1].Comment)
assert.Equal(t, "GenericDifficultyHard", genericDifficultyEnums[2].key)
assert.Equal(t, "This means really hard", genericDifficultyEnums[2].Comment)
securityLevelEnums := p.packages.packages[typesPath].TypeDefinitions["SecurityClearance"].Enums
assert.Equal(t, "Public", securityLevelEnums[0].key)
assert.Equal(t, "", securityLevelEnums[0].Comment)
assert.Equal(t, "SecurityClearanceSensitive", securityLevelEnums[1].key)
assert.Equal(t, "Name override and comment rules apply here just as above", securityLevelEnums[1].Comment)
assert.Equal(t, "SuperSecret", securityLevelEnums[2].key)
assert.Equal(t, "This one has a name override and a comment", securityLevelEnums[2].Comment)
}
================================================
FILE: example/basic/api/api.go
================================================
package api
import (
"encoding/json"
"net/http"
"github.com/swaggo/swag/example/basic/web"
)
// GetStringByInt example
//
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID get-string-by-int
// @Accept json
// @Produce json
// @Param some_id path int true "Some ID"
// @Param some_id body web.Pet true "Some ID"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
func GetStringByInt(w http.ResponseWriter, r *http.Request) {
var pet web.Pet
if err := json.NewDecoder(r.Body).Decode(&pet); err != nil {
// write your code
return
}
// write your code
}
// GetStructArrayByString example
//
// @Description get struct array by ID
// @ID get-struct-array-by-string
// @Accept json
// @Produce json
// @Param some_id path string true "Some ID"
// @Param offset query int true "Offset"
// @Param limit query int true "Offset"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-struct-array-by-string/{some_id} [get]
func GetStructArrayByString(w http.ResponseWriter, r *http.Request) {
// write your code
}
// Upload example
//
// @Summary Upload file
// @Description Upload file
// @ID file.upload
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "this is a test file"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /file/upload [post]
func Upload(w http.ResponseWriter, r *http.Request) {
// write your code
}
// AnonymousField example
//
// @Summary use Anonymous field
// @Success 200 {object} web.RevValue "ok"
func AnonymousField() {
}
// Pet3 example
type Pet3 struct {
ID int `json:"id"`
}
================================================
FILE: example/basic/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/example/basic/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
func main() {
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
http.HandleFunc("//testapi/get-struct-array-by-string/", api.GetStructArrayByString)
http.HandleFunc("/testapi/upload", api.Upload)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: example/basic/web/handler.go
================================================
package web
import (
"time"
)
// Pet example
type Pet struct {
ID int `json:"id"`
Category struct {
ID int `json:"id"`
Name string `json:"name"`
} `json:"category"`
Name string `json:"name"`
PhotoUrls []string `json:"photoUrls"`
Tags []Tag `json:"tags"`
Status string `json:"status"`
}
// Tag example
type Tag struct {
ID int `json:"id"`
Name string `json:"name"`
}
// Pet2 example
type Pet2 struct {
ID int `json:"id"`
}
// APIError example
type APIError struct {
ErrorCode int
ErrorMessage string
CreatedAt time.Time
}
// RevValueBase example
type RevValueBase struct {
Status bool `json:"Status"`
Err int32 `json:"Err"`
}
// RevValue example
type RevValue struct {
RevValueBase
Data int `json:"Data"`
}
================================================
FILE: example/celler/README.md
================================================
# Celler example
Gen doc
```console
$ go get -u github.com/swaggo/swag/cmd/swag
$ swag init
```
Run app
```console
$ go run main.go
```
[open swagger](http://localhost:8080/swagger/index.html)
================================================
FILE: example/celler/controller/accounts.go
================================================
package controller
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/swaggo/swag/example/celler/httputil"
"github.com/swaggo/swag/example/celler/model"
)
// ShowAccount godoc
//
// @Summary Show an account
// @Description get string by ID
// @Tags accounts
// @Accept json
// @Produce json
// @Param id path int true "Account ID"
// @Success 200 {object} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts/{id} [get]
func (c *Controller) ShowAccount(ctx *gin.Context) {
id := ctx.Param("id")
aid, err := strconv.Atoi(id)
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
account, err := model.AccountOne(aid)
if err != nil {
httputil.NewError(ctx, http.StatusNotFound, err)
return
}
ctx.JSON(http.StatusOK, account)
}
// ListAccounts godoc
//
// @Summary List accounts
// @Description get accounts
// @Tags accounts
// @Accept json
// @Produce json
// @Param q query string false "name search by q" Format(email)
// @Success 200 {array} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts [get]
func (c *Controller) ListAccounts(ctx *gin.Context) {
q := ctx.Request.URL.Query().Get("q")
accounts, err := model.AccountsAll(q)
if err != nil {
httputil.NewError(ctx, http.StatusNotFound, err)
return
}
ctx.JSON(http.StatusOK, accounts)
}
// AddAccount godoc
//
// @Summary Add an account
// @Description add by json account
// @Tags accounts
// @Accept json
// @Produce json
// @Param account body model.AddAccount true "Add account"
// @Success 200 {object} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts [post]
func (c *Controller) AddAccount(ctx *gin.Context) {
var addAccount model.AddAccount
if err := ctx.ShouldBindJSON(&addAccount); err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
if err := addAccount.Validation(); err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
account := model.Account{
Name: addAccount.Name,
}
lastID, err := account.Insert()
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
account.ID = lastID
ctx.JSON(http.StatusOK, account)
}
// UpdateAccount godoc
//
// @Summary Update an account
// @Description Update by json account
// @Tags accounts
// @Accept json
// @Produce json
// @Param id path int true "Account ID"
// @Param account body model.UpdateAccount true "Update account"
// @Success 200 {object} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts/{id} [patch]
func (c *Controller) UpdateAccount(ctx *gin.Context) {
id := ctx.Param("id")
aid, err := strconv.Atoi(id)
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
var updateAccount model.UpdateAccount
if err := ctx.ShouldBindJSON(&updateAccount); err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
account := model.Account{
ID: aid,
Name: updateAccount.Name,
}
err = account.Update()
if err != nil {
httputil.NewError(ctx, http.StatusNotFound, err)
return
}
ctx.JSON(http.StatusOK, account)
}
// DeleteAccount godoc
//
// @Summary Delete an account
// @Description Delete by account ID
// @Tags accounts
// @Accept json
// @Produce json
// @Param id path int true "Account ID" Format(int64)
// @Success 204 {object} model.Account
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts/{id} [delete]
func (c *Controller) DeleteAccount(ctx *gin.Context) {
id := ctx.Param("id")
aid, err := strconv.Atoi(id)
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
err = model.Delete(aid)
if err != nil {
httputil.NewError(ctx, http.StatusNotFound, err)
return
}
ctx.JSON(http.StatusNoContent, gin.H{})
}
// UploadAccountImage godoc
//
// @Summary Upload account image
// @Description Upload file
// @Tags accounts
// @Accept multipart/form-data
// @Produce json
// @Param id path int true "Account ID"
// @Param file formData file true "account image"
// @Success 200 {object} controller.Message
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /accounts/{id}/images [post]
func (c *Controller) UploadAccountImage(ctx *gin.Context) {
id, err := strconv.Atoi(ctx.Param("id"))
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
file, err := ctx.FormFile("file")
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
ctx.JSON(http.StatusOK, Message{Message: fmt.Sprintf("upload complete userID=%d filename=%s", id, file.Filename)})
}
================================================
FILE: example/celler/controller/admin.go
================================================
package controller
import (
"errors"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/swaggo/swag/example/celler/httputil"
"github.com/swaggo/swag/example/celler/model"
)
// Auth godoc
//
// @Summary Auth admin
// @Description get admin info
// @Tags accounts,admin
// @Accept json
// @Produce json
// @Success 200 {object} model.Admin
// @Failure 400 {object} httputil.HTTPError
// @Failure 401 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Security ApiKeyAuth
// @Router /admin/auth [post]
func (c *Controller) Auth(ctx *gin.Context) {
authHeader := ctx.GetHeader("Authorization")
if len(authHeader) == 0 {
httputil.NewError(ctx, http.StatusBadRequest, errors.New("please set Header Authorization"))
return
}
if authHeader != "admin" {
httputil.NewError(ctx, http.StatusUnauthorized, fmt.Errorf("this user isn't authorized to operation key=%s expected=admin", authHeader))
return
}
admin := model.Admin{
ID: 1,
Name: "admin",
}
ctx.JSON(http.StatusOK, admin)
}
================================================
FILE: example/celler/controller/bottles.go
================================================
package controller
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/swaggo/swag/example/celler/httputil"
"github.com/swaggo/swag/example/celler/model"
)
// ShowBottle godoc
//
// @Summary Show a bottle
// @Description get string by ID
// @ID get-string-by-int
// @Tags bottles
// @Accept json
// @Produce json
// @Param id path int true "Bottle ID"
// @Success 200 {object} model.Bottle
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /bottles/{id} [get]
func (c *Controller) ShowBottle(ctx *gin.Context) {
id := ctx.Param("id")
bid, err := strconv.Atoi(id)
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
bottle, err := model.BottleOne(bid)
if err != nil {
httputil.NewError(ctx, http.StatusNotFound, err)
return
}
ctx.JSON(http.StatusOK, bottle)
}
// ListBottles godoc
//
// @Summary List bottles
// @Description get bottles
// @Tags bottles
// @Accept json
// @Produce json
// @Success 200 {array} model.Bottle
// @Failure 400 {object} httputil.HTTPError
// @Failure 404 {object} httputil.HTTPError
// @Failure 500 {object} httputil.HTTPError
// @Router /bottles [get]
func (c *Controller) ListBottles(ctx *gin.Context) {
bottles, err := model.BottlesAll()
if err != nil {
httputil.NewError(ctx, http.StatusNotFound, err)
return
}
ctx.JSON(http.StatusOK, bottles)
}
================================================
FILE: example/celler/controller/controller.go
================================================
package controller
// Controller example
type Controller struct {
}
// NewController example
func NewController() *Controller {
return &Controller{}
}
// Message example
type Message struct {
Message string `json:"message" example:"message"`
}
================================================
FILE: example/celler/controller/examples.go
================================================
package controller
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/swaggo/swag/example/celler/httputil"
)
// PingExample godoc
//
// @Summary ping example
// @Description do ping
// @Tags example
// @Accept json
// @Produce plain
// @Success 200 {string} string "pong"
// @Failure 400 {string} string "ok"
// @Failure 404 {string} string "ok"
// @Failure 500 {string} string "ok"
// @Router /examples/ping [get]
func (c *Controller) PingExample(ctx *gin.Context) {
ctx.String(http.StatusOK, "pong")
}
// CalcExample godoc
//
// @Summary calc example
// @Description plus
// @Tags example
// @Accept json
// @Produce plain
// @Param val1 query int true "used for calc"
// @Param val2 query int true "used for calc"
// @Success 200 {integer} string "answer"
// @Failure 400 {string} string "ok"
// @Failure 404 {string} string "ok"
// @Failure 500 {string} string "ok"
// @Router /examples/calc [get]
func (c *Controller) CalcExample(ctx *gin.Context) {
val1, err := strconv.Atoi(ctx.Query("val1"))
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
val2, err := strconv.Atoi(ctx.Query("val2"))
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
ans := val1 + val2
ctx.String(http.StatusOK, "%d", ans)
}
// PathParamsExample godoc
//
// @Summary path params example
// @Description path params
// @Tags example
// @Accept json
// @Produce plain
// @Param group_id path int true "Group ID"
// @Param account_id path int true "Account ID"
// @Success 200 {string} string "answer"
// @Failure 400 {string} string "ok"
// @Failure 404 {string} string "ok"
// @Failure 500 {string} string "ok"
// @Router /examples/groups/{group_id}/accounts/{account_id} [get]
func (c *Controller) PathParamsExample(ctx *gin.Context) {
groupID, err := strconv.Atoi(ctx.Param("group_id"))
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
accountID, err := strconv.Atoi(ctx.Param("account_id"))
if err != nil {
httputil.NewError(ctx, http.StatusBadRequest, err)
return
}
ctx.String(http.StatusOK, "group_id=%d account_id=%d", groupID, accountID)
}
// HeaderExample godoc
//
// @Summary custome header example
// @Description custome header
// @Tags example
// @Accept json
// @Produce plain
// @Param Authorization header string true "Authentication header"
// @Success 200 {string} string "answer"
// @Failure 400 {string} string "ok"
// @Failure 404 {string} string "ok"
// @Failure 500 {string} string "ok"
// @Router /examples/header [get]
func (c *Controller) HeaderExample(ctx *gin.Context) {
ctx.String(http.StatusOK, ctx.GetHeader("Authorization"))
}
// SecuritiesExample godoc
//
// @Summary custome header example
// @Description custome header
// @Tags example
// @Accept json
// @Produce json
// @Param Authorization header string true "Authentication header"
// @Success 200 {string} string "answer"
// @Failure 400 {string} string "ok"
// @Failure 404 {string} string "ok"
// @Failure 500 {string} string "ok"
// @Security ApiKeyAuth
// @Security OAuth2Implicit[admin, write]
// @Router /examples/securities [get]
func (c *Controller) SecuritiesExample(ctx *gin.Context) {
}
// AttributeExample godoc
//
// @Summary attribute example
// @Description attribute
// @Tags example
// @Accept json
// @Produce plain
// @Param enumstring query string false "string enums" Enums(A, B, C)
// @Param enumint query int false "int enums" Enums(1, 2, 3)
// @Param enumnumber query number false "int enums" Enums(1.1, 1.2, 1.3)
// @Param string query string false "string valid" minlength(5) maxlength(10)
// @Param int query int false "int valid" minimum(1) maximum(10)
// @Param default query string false "string default" default(A)
// @Success 200 {string} string "answer"
// @Failure 400 {string} string "ok"
// @Failure 404 {string} string "ok"
// @Failure 500 {string} string "ok"
// @Router /examples/attribute [get]
func (c *Controller) AttributeExample(ctx *gin.Context) {
ctx.String(http.StatusOK, fmt.Sprintf("enumstring=%s enumint=%s enumnumber=%s string=%s int=%s default=%s",
ctx.Query("enumstring"),
ctx.Query("enumint"),
ctx.Query("enumnumber"),
ctx.Query("string"),
ctx.Query("int"),
ctx.Query("default"),
))
}
// PostExample godoc
//
// @Summary post request example
// @Description post request example
// @Accept json
// @Produce plain
// @Param message body model.Account true "Account Info"
// @Success 200 {string} string "success"
// @Failure 500 {string} string "fail"
// @Router /examples/post [post]
func (c *Controller) PostExample(ctx *gin.Context) {
}
================================================
FILE: example/celler/go.mod
================================================
module github.com/swaggo/swag/example/celler
go 1.24.0
require (
github.com/gin-gonic/gin v1.9.1
github.com/gofrs/uuid v4.2.0+incompatible
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2
github.com/swaggo/gin-swagger v1.4.2
github.com/swaggo/swag v1.8.1
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: example/celler/go.sum
================================================
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.3 h1:etUaeesHhEORpZMp18zoOhepboiWnFtXrBZxszWUn4k=
github.com/gin-contrib/gzip v0.0.3/go.mod h1:YxxswVZIqOvcHEQpsSn+QF5guQtO1dCfy0shBPy4jFc=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM=
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
github.com/swaggo/gin-swagger v1.4.2 h1:qDs1YrBOTnurDG/JVMc8678KhoS1B1okQGPtIqVz4YU=
github.com/swaggo/gin-swagger v1.4.2/go.mod h1:hmJ1vPn+XjUvnbzjCdUAxVqgraxELxk8x5zAsjCE5mg=
github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU=
github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI=
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
================================================
FILE: example/celler/httputil/error.go
================================================
package httputil
import "github.com/gin-gonic/gin"
// NewError example
func NewError(ctx *gin.Context, status int, err error) {
er := HTTPError{
Code: status,
Message: err.Error(),
}
ctx.JSON(status, er)
}
// HTTPError example
type HTTPError struct {
Code int `json:"code" example:"400"`
Message string `json:"message" example:"status bad request"`
}
================================================
FILE: example/celler/main.go
================================================
package main
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/swaggo/swag/example/celler/controller"
_ "github.com/swaggo/swag/example/celler/docs"
"github.com/swaggo/swag/example/celler/httputil"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @description Description for what is this security definition being used
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationUrl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationUrl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
r := gin.Default()
c := controller.NewController()
v1 := r.Group("/api/v1")
{
accounts := v1.Group("/accounts")
{
accounts.GET(":id", c.ShowAccount)
accounts.GET("", c.ListAccounts)
accounts.POST("", c.AddAccount)
accounts.DELETE(":id", c.DeleteAccount)
accounts.PATCH(":id", c.UpdateAccount)
accounts.POST(":id/images", c.UploadAccountImage)
}
bottles := v1.Group("/bottles")
{
bottles.GET(":id", c.ShowBottle)
bottles.GET("", c.ListBottles)
}
admin := v1.Group("/admin")
{
admin.Use(auth())
admin.POST("/auth", c.Auth)
}
examples := v1.Group("/examples")
{
examples.GET("ping", c.PingExample)
examples.GET("calc", c.CalcExample)
examples.GET("groups/:group_id/accounts/:account_id", c.PathParamsExample)
examples.GET("header", c.HeaderExample)
examples.GET("securities", c.SecuritiesExample)
examples.GET("attribute", c.AttributeExample)
}
}
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run(":8080")
}
func auth() gin.HandlerFunc {
return func(c *gin.Context) {
if len(c.GetHeader("Authorization")) == 0 {
httputil.NewError(c, http.StatusUnauthorized, errors.New("Authorization is required Header"))
c.Abort()
}
c.Next()
}
}
================================================
FILE: example/celler/model/account.go
================================================
package model
import (
"errors"
"fmt"
uuid "github.com/gofrs/uuid"
)
// Account example
type Account struct {
ID int `json:"id" example:"1" format:"int64"`
Name string `json:"name" example:"account name"`
UUID uuid.UUID `json:"uuid" example:"550e8400-e29b-41d4-a716-446655440000" format:"uuid"`
}
// example
var (
ErrNameInvalid = errors.New("name is empty")
)
// AddAccount example
type AddAccount struct {
Name string `json:"name" example:"account name"`
}
// Validation example
func (a AddAccount) Validation() error {
switch {
case len(a.Name) == 0:
return ErrNameInvalid
default:
return nil
}
}
// UpdateAccount example
type UpdateAccount struct {
Name string `json:"name" example:"account name"`
}
// Validation example
func (a UpdateAccount) Validation() error {
switch {
case len(a.Name) == 0:
return ErrNameInvalid
default:
return nil
}
}
// AccountsAll example
func AccountsAll(q string) ([]Account, error) {
if q == "" {
return accounts, nil
}
as := []Account{}
for k, v := range accounts {
if q == v.Name {
as = append(as, accounts[k])
}
}
return as, nil
}
// AccountOne example
func AccountOne(id int) (Account, error) {
for _, v := range accounts {
if id == v.ID {
return v, nil
}
}
return Account{}, ErrNoRow
}
// Insert example
func (a Account) Insert() (int, error) {
accountMaxID++
a.ID = accountMaxID
a.Name = fmt.Sprintf("account_%d", accountMaxID)
accounts = append(accounts, a)
return accountMaxID, nil
}
// Delete example
func Delete(id int) error {
for k, v := range accounts {
if id == v.ID {
accounts = append(accounts[:k], accounts[k+1:]...)
return nil
}
}
return fmt.Errorf("account id=%d is not found", id)
}
// Update example
func (a Account) Update() error {
for k, v := range accounts {
if a.ID == v.ID {
accounts[k].Name = a.Name
return nil
}
}
return fmt.Errorf("account id=%d is not found", a.ID)
}
var accountMaxID = 3
var accounts = []Account{
{ID: 1, Name: "account_1"},
{ID: 2, Name: "account_2"},
{ID: 3, Name: "account_3"},
}
================================================
FILE: example/celler/model/admin.go
================================================
package model
// Admin example
type Admin struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"admin name"`
}
================================================
FILE: example/celler/model/bottle.go
================================================
package model
// Bottle example
type Bottle struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"bottle_name"`
Account Account `json:"account"`
}
// BottlesAll example
func BottlesAll() ([]Bottle, error) {
return bottles, nil
}
// BottleOne example
func BottleOne(id int) (*Bottle, error) {
for _, v := range bottles {
if id == v.ID {
return &v, nil
}
}
return nil, ErrNoRow
}
var bottles = []Bottle{
{ID: 1, Name: "bottle_1", Account: Account{ID: 1, Name: "accout_1"}},
{ID: 2, Name: "bottle_2", Account: Account{ID: 2, Name: "accout_2"}},
{ID: 3, Name: "bottle_3", Account: Account{ID: 3, Name: "accout_3"}},
}
================================================
FILE: example/celler/model/error.go
================================================
package model
import "errors"
var (
// ErrNoRow example
ErrNoRow = errors.New("no rows in result set")
)
================================================
FILE: example/go-module-support/docs/docs.go
================================================
// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "petstore.swagger.io",
BasePath: "/v2",
Schemes: []string{},
Title: "Swagger Example API",
Description: "This is a sample server Petstore server.",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}
================================================
FILE: example/go-module-support/docs/swagger.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"paths": {}
}
================================================
FILE: example/go-module-support/docs/swagger.yaml
================================================
basePath: /v2
host: petstore.swagger.io
info:
contact:
email: support@swagger.io
name: API Support
url: http://www.swagger.io/support
description: This is a sample server Petstore server.
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: http://swagger.io/terms/
title: Swagger Example API
version: "1.0"
paths: {}
swagger: "2.0"
================================================
FILE: example/go-module-support/go.mod
================================================
module github.com/swaggo/swag/example/go-module-support
go 1.24.0
require (
github.com/gin-gonic/gin v1.9.1
github.com/swaggo/examples v0.0.0-20190624100559-f57286ab550c
github.com/swaggo/swag v1.8.1
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: example/go-module-support/go.sum
================================================
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/swaggo/examples v0.0.0-20190624100559-f57286ab550c h1:wgBp6VweQ9dML4cKbjh0sV0xxvxgqCrCRiHG6losLv4=
github.com/swaggo/examples v0.0.0-20190624100559-f57286ab550c/go.mod h1:U21M3+8BIXRyR/pwjJ7X0D36sVTzFMiOyUAdgvVfUVI=
github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI=
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
================================================
FILE: example/go-module-support/main.go
================================================
package main
import (
"github.com/gin-gonic/gin"
"github.com/swaggo/examples/go-module-support/api" // included package from external
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
func main() {
r := gin.New()
r.GET("/testapi/get-string-by-int/:some_id", api.GetStringByInt)
r.GET("//testapi/get-struct-array-by-string/:some_id", api.GetStructArrayByString)
r.Run()
}
================================================
FILE: example/go-module-support/web/handler.go
================================================
package web
import (
"time"
)
// Pet example
type Pet struct {
ID int `json:"id"`
Category struct {
ID int `json:"id"`
Name string `json:"name"`
} `json:"category"`
Name string `json:"name"`
PhotoUrls []string `json:"photoUrls"`
Tags []Tag `json:"tags"`
Status string `json:"status"`
}
// Tag example
type Tag struct {
ID int `json:"id"`
Name string `json:"name"`
}
// Pet2 example
type Pet2 struct {
ID int `json:"id"`
}
// APIError example
type APIError struct {
ErrorCode int
ErrorMessage string
CreatedAt time.Time
}
// RevValueBase example
type RevValueBase struct {
Status bool `json:"Status"`
Err int32 `json:"Err"`
}
// RevValue example
type RevValue struct {
RevValueBase
Data int `json:"Data"`
}
================================================
FILE: example/markdown/admin.md
================================================
# Admin TAG API documentation
**Admin** functions goes here
For more info please read [link](/docs/readme.md).
================================================
FILE: example/markdown/api/api.go
================================================
package api
import (
"net/http"
"time"
)
// User example
type User struct {
ID int64
Email string
Password string
}
// UsersCollection example
type UsersCollection []User
// Error example
type APIError struct {
ErrorCode int
ErrorMessage string
CreatedAt time.Time
}
// ListUsers example
//
// @Summary List users from the store
// @Tags admin
// @Accept json
// @Produce json
// @Success 200 {array} api.UsersCollection "ok"
// @Router /admin/user/ [get]
func ListUsers(w http.ResponseWriter, r *http.Request) {
// write your code
}
// GetUser example
//
// @Summary Read user from the store
// @Tags admin
// @Accept json
// @Produce json
// @Param id path int true "User Id"
// @Success 200 {object} api.User
// @Failure 400 {object} api.APIError "We need ID!!"
// @Failure 404 {object} api.APIError "Can not find ID"
// @Router /admin/user/{id} [get]
func GetUser(w http.ResponseWriter, r *http.Request) {
// write your code
}
// AddUser example
//
// @Summary Add a new user to the store
// @Tags admin
// @Accept json
// @Produce json
// @Param message body api.User true "User Data"
// @Success 200 {string} string "ok"
// @Failure 400 {object} api.APIError "We need ID!!"
// @Failure 404 {object} api.APIError "Can not find ID"
// @Router /admin/user/ [post]
func AddUser(w http.ResponseWriter, r *http.Request) {
// write your code
}
// UpdateUser example
//
// @Summary Add a new user to the store
// @Tags admin
// @Accept json
// @Produce json
// @Param message body api.User true "User Data"
// @Success 200 {string} string "ok"
// @Failure 400 {object} api.APIError "We need ID!!"
// @Failure 404 {object} api.APIError "Can not find ID"
// @Router /admin/user/ [put]
func UpdateUser(w http.ResponseWriter, r *http.Request) {
// write your code
}
================================================
FILE: example/markdown/api.md
================================================
# General API documentation
**Warning** this api is not production ready. Use at your own risk.
In order to re-generate the documentation you need to run
`swag init --md .`
================================================
FILE: example/markdown/docs/docs.go
================================================
// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/admin/user/": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "List users from the store",
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/api.User"
}
}
}
}
}
},
"put": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Add a new user to the store",
"parameters": [
{
"description": "User Data",
"name": "message",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/api.User"
}
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/api.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/api.APIError"
}
}
}
},
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Add a new user to the store",
"parameters": [
{
"description": "User Data",
"name": "message",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/api.User"
}
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/api.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/api.APIError"
}
}
}
}
},
"/admin/user/{id}": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Read user from the store",
"parameters": [
{
"type": "integer",
"description": "User Id",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.User"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/api.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/api.APIError"
}
}
}
}
}
},
"definitions": {
"api.APIError": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"errorCode": {
"type": "integer"
},
"errorMessage": {
"type": "string"
}
}
},
"api.User": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"id": {
"type": "integer"
},
"password": {
"type": "string"
}
}
}
},
"tags": [
{
"description": "# Admin TAG API documentation\n\n**Admin** functions goes here \n\nFor more info please read [link](/docs/readme.md).\n\n",
"name": "admin"
}
]
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "",
BasePath: "/v2",
Schemes: []string{},
Title: "Swagger Example API",
Description: "# General API documentation\n\n**Warning** this api is not production ready. Use at your own risk.\n\nIn order to re-generate the documentation you need to run\n\n`swag init --md .`\n",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}
================================================
FILE: example/markdown/docs/swagger.json
================================================
{
"swagger": "2.0",
"info": {
"description": "# General API documentation\n\n**Warning** this api is not production ready. Use at your own risk.\n\nIn order to re-generate the documentation you need to run\n\n`swag init --md .`\n",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"basePath": "/v2",
"paths": {
"/admin/user/": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "List users from the store",
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/api.User"
}
}
}
}
}
},
"put": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Add a new user to the store",
"parameters": [
{
"description": "User Data",
"name": "message",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/api.User"
}
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/api.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/api.APIError"
}
}
}
},
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Add a new user to the store",
"parameters": [
{
"description": "User Data",
"name": "message",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/api.User"
}
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/api.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/api.APIError"
}
}
}
}
},
"/admin/user/{id}": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Read user from the store",
"parameters": [
{
"type": "integer",
"description": "User Id",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.User"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/api.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/api.APIError"
}
}
}
}
}
},
"definitions": {
"api.APIError": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"errorCode": {
"type": "integer"
},
"errorMessage": {
"type": "string"
}
}
},
"api.User": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"id": {
"type": "integer"
},
"password": {
"type": "string"
}
}
}
},
"tags": [
{
"description": "# Admin TAG API documentation\n\n**Admin** functions goes here \n\nFor more info please read [link](/docs/readme.md).\n\n",
"name": "admin"
}
]
}
================================================
FILE: example/markdown/docs/swagger.yaml
================================================
basePath: /v2
definitions:
api.APIError:
properties:
createdAt:
type: string
errorCode:
type: integer
errorMessage:
type: string
type: object
api.User:
properties:
email:
type: string
id:
type: integer
password:
type: string
type: object
info:
contact:
email: support@swagger.io
name: API Support
url: http://www.swagger.io/support
description: |
# General API documentation
**Warning** this api is not production ready. Use at your own risk.
In order to re-generate the documentation you need to run
`swag init --md .`
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: http://swagger.io/terms/
title: Swagger Example API
version: "1.0"
paths:
/admin/user/:
get:
consumes:
- application/json
produces:
- application/json
responses:
"200":
description: ok
schema:
items:
items:
$ref: '#/definitions/api.User'
type: array
type: array
summary: List users from the store
tags:
- admin
post:
consumes:
- application/json
parameters:
- description: User Data
in: body
name: message
required: true
schema:
$ref: '#/definitions/api.User'
produces:
- application/json
responses:
"200":
description: ok
schema:
type: string
"400":
description: We need ID!!
schema:
$ref: '#/definitions/api.APIError'
"404":
description: Can not find ID
schema:
$ref: '#/definitions/api.APIError'
summary: Add a new user to the store
tags:
- admin
put:
consumes:
- application/json
parameters:
- description: User Data
in: body
name: message
required: true
schema:
$ref: '#/definitions/api.User'
produces:
- application/json
responses:
"200":
description: ok
schema:
type: string
"400":
description: We need ID!!
schema:
$ref: '#/definitions/api.APIError'
"404":
description: Can not find ID
schema:
$ref: '#/definitions/api.APIError'
summary: Add a new user to the store
tags:
- admin
/admin/user/{id}:
get:
consumes:
- application/json
parameters:
- description: User Id
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.User'
"400":
description: We need ID!!
schema:
$ref: '#/definitions/api.APIError'
"404":
description: Can not find ID
schema:
$ref: '#/definitions/api.APIError'
summary: Read user from the store
tags:
- admin
swagger: "2.0"
tags:
- description: "# Admin TAG API documentation\n\n**Admin** functions goes here \n\nFor
more info please read [link](/docs/readme.md).\n\n"
name: admin
================================================
FILE: example/markdown/go.mod
================================================
module github.com/swaggo/swag/example/markdown
go 1.18
require (
github.com/gorilla/mux v1.8.0
github.com/swaggo/http-swagger v1.2.6
github.com/swaggo/swag v1.8.1
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/spec v0.20.5 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/tools v0.1.10 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
================================================
FILE: example/markdown/go.sum
================================================
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/spec v0.20.5 h1:skHa8av4VnAtJU5zyAUXrrdK/NDiVX8lchbG+BfcdrE=
github.com/go-openapi/spec v0.20.5/go.mod h1:QbfOSIVt3/sac+a1wzmKbbcLXm5NdZnyBZYtCijp43o=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM=
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
github.com/swaggo/http-swagger v1.2.6 h1:ihTjChUoSRMpFMjWw+0AkL1Ti4r6v8pCgVYLmQVRlRw=
github.com/swaggo/http-swagger v1.2.6/go.mod h1:CcoICgY3yVDk2u1LQUCMHbAj0fjlxIX+873psXlIKNA=
github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU=
github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI=
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: example/markdown/main.go
================================================
package main
import (
"net/http"
"github.com/gorilla/mux"
httpSwagger "github.com/swaggo/http-swagger"
"github.com/swaggo/swag/example/markdown/api"
_ "github.com/swaggo/swag/example/markdown/docs"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @description.markdown
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @tag.name admin
// @tag.description.markdown
// @BasePath /v2
func main() {
router := mux.NewRouter()
router.HandleFunc("/admin/user/", api.ListUsers).Methods("GET")
router.HandleFunc("/admin/user/{id}", api.GetUser).Methods("GET")
router.HandleFunc("/admin/user/", api.AddUser).Methods("POST")
router.HandleFunc("/admin/user/{id}", api.UpdateUser).Methods("PUT")
router.PathPrefix("/swagger/").Handler(httpSwagger.WrapHandler)
http.ListenAndServe(":8080", router)
}
================================================
FILE: example/object-map-example/controller/api.go
================================================
package controller
import "github.com/gin-gonic/gin"
// GetMap godoc
//
// @Summary Get Map Example
// @Description get map
// @ID get-map
// @Accept json
// @Produce json
// @Success 200 {object} Response
// @Router /test [get]
func (c *Controller) GetMap(ctx *gin.Context) {
ctx.JSON(200, Response{
Title: map[string]string{
"en": "Map",
},
CustomType: map[string]interface{}{
"key": "value",
},
Object: Data{
Text: "object text",
},
})
}
================================================
FILE: example/object-map-example/controller/controller.go
================================================
package controller
// Controller example
type Controller struct {
}
// NewController example
func NewController() *Controller {
return &Controller{}
}
================================================
FILE: example/object-map-example/controller/response.go
================================================
package controller
type Response struct {
Title map[string]string `json:"title" example:"en:Map,ru:Карта,kk:Карталар"`
CustomType map[string]interface{} `json:"map_data" swaggertype:"object,string" example:"key:value,key2:value2"`
Object Data `json:"object"`
}
type Data struct {
Text string `json:"title" example:"Object data"`
}
================================================
FILE: example/object-map-example/docs/docs.go
================================================
// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/test": {
"get": {
"description": "get map",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Get Map Example",
"operationId": "get-map",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controller.Response"
}
}
}
}
}
},
"definitions": {
"controller.Data": {
"type": "object",
"properties": {
"title": {
"type": "string",
"example": "Object data"
}
}
},
"controller.Response": {
"type": "object",
"properties": {
"map_data": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"example": {
"key": "value",
"key2": "value2"
}
},
"object": {
"$ref": "#/definitions/controller.Data"
},
"title": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"example": {
"en": "Map",
"kk": "Карталар",
"ru": "Карта"
}
}
}
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "localhost:8080",
BasePath: "/api/v1",
Schemes: []string{},
Title: "Swagger Map Example API",
Description: "",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}
================================================
FILE: example/object-map-example/docs/swagger.json
================================================
{
"swagger": "2.0",
"info": {
"title": "Swagger Map Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "localhost:8080",
"basePath": "/api/v1",
"paths": {
"/test": {
"get": {
"description": "get map",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Get Map Example",
"operationId": "get-map",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controller.Response"
}
}
}
}
}
},
"definitions": {
"controller.Data": {
"type": "object",
"properties": {
"title": {
"type": "string",
"example": "Object data"
}
}
},
"controller.Response": {
"type": "object",
"properties": {
"map_data": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"example": {
"key": "value",
"key2": "value2"
}
},
"object": {
"$ref": "#/definitions/controller.Data"
},
"title": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"example": {
"en": "Map",
"kk": "Карталар",
"ru": "Карта"
}
}
}
}
}
}
================================================
FILE: example/object-map-example/docs/swagger.yaml
================================================
basePath: /api/v1
definitions:
controller.Data:
properties:
title:
example: Object data
type: string
type: object
controller.Response:
properties:
map_data:
additionalProperties:
type: string
example:
key: value
key2: value2
type: object
object:
$ref: '#/definitions/controller.Data'
title:
additionalProperties:
type: string
example:
en: Map
kk: Карталар
ru: Карта
type: object
type: object
host: localhost:8080
info:
contact: {}
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: http://swagger.io/terms/
title: Swagger Map Example API
version: "1.0"
paths:
/test:
get:
consumes:
- application/json
description: get map
operationId: get-map
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controller.Response'
summary: Get Map Example
swagger: "2.0"
================================================
FILE: example/object-map-example/go.mod
================================================
module github.com/swaggo/swag/example/object-map-example
go 1.24.0
require (
github.com/gin-gonic/gin v1.9.1
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2
github.com/swaggo/gin-swagger v1.4.2
github.com/swaggo/swag v1.8.1
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/spec v0.20.5 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: example/object-map-example/go.sum
================================================
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.3 h1:etUaeesHhEORpZMp18zoOhepboiWnFtXrBZxszWUn4k=
github.com/gin-contrib/gzip v0.0.3/go.mod h1:YxxswVZIqOvcHEQpsSn+QF5guQtO1dCfy0shBPy4jFc=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/spec v0.20.5 h1:skHa8av4VnAtJU5zyAUXrrdK/NDiVX8lchbG+BfcdrE=
github.com/go-openapi/spec v0.20.5/go.mod h1:QbfOSIVt3/sac+a1wzmKbbcLXm5NdZnyBZYtCijp43o=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM=
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
github.com/swaggo/gin-swagger v1.4.2 h1:qDs1YrBOTnurDG/JVMc8678KhoS1B1okQGPtIqVz4YU=
github.com/swaggo/gin-swagger v1.4.2/go.mod h1:hmJ1vPn+XjUvnbzjCdUAxVqgraxELxk8x5zAsjCE5mg=
github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU=
github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI=
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
================================================
FILE: example/object-map-example/main.go
================================================
package main
import (
"github.com/gin-gonic/gin"
"github.com/swaggo/swag/example/object-map-example/controller"
_ "github.com/swaggo/swag/example/object-map-example/docs"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
// @title Swagger Map Example API
// @version 1.0
// @termsOfService http://swagger.io/terms/
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
func main() {
r := gin.Default()
c := controller.NewController()
v1 := r.Group("/api/v1")
{
test := v1.Group("/map")
{
test.GET("", c.GetMap)
}
}
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run(":8080")
}
================================================
FILE: example/override/.swaggo
================================================
replace sql.NullString string
replace sql.NullInt64 int64
================================================
FILE: example/override/docs/docs.go
================================================
// Package docs GENERATED BY SWAG; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/testapi/update-product/{product_id}": {
"post": {
"consumes": [
"application/json"
],
"summary": "Update product attributes",
"operationId": "update-product",
"parameters": [
{
"type": "integer",
"description": "Product ID",
"name": "product_id",
"in": "path",
"required": true
},
{
"description": " ",
"name": "_",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/main.ProductUpdates"
}
}
],
"responses": {}
}
}
},
"definitions": {
"main.ProductUpdates": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"stock": {
"type": "integer"
},
"type": {
"type": "string"
}
}
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "product_info.swagger.io",
BasePath: "/v2",
Schemes: []string{},
Title: "Swagger Example API",
Description: "This is a sample server for updating product information.",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}
================================================
FILE: example/override/docs/swagger.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server for updating product information.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "product_info.swagger.io",
"basePath": "/v2",
"paths": {
"/testapi/update-product/{product_id}": {
"post": {
"consumes": [
"application/json"
],
"summary": "Update product attributes",
"operationId": "update-product",
"parameters": [
{
"type": "integer",
"description": "Product ID",
"name": "product_id",
"in": "path",
"required": true
},
{
"description": " ",
"name": "_",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/main.ProductUpdates"
}
}
],
"responses": {}
}
}
},
"definitions": {
"main.ProductUpdates": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"stock": {
"type": "integer"
},
"type": {
"type": "string"
}
}
}
}
}
================================================
FILE: example/override/docs/swagger.yaml
================================================
basePath: /v2
definitions:
main.ProductUpdates:
properties:
description:
type: string
stock:
type: integer
type:
type: string
type: object
host: product_info.swagger.io
info:
contact:
email: support@swagger.io
name: API Support
url: http://www.swagger.io/support
description: This is a sample server for updating product information.
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: http://swagger.io/terms/
title: Swagger Example API
version: "1.0"
paths:
/testapi/update-product/{product_id}:
post:
consumes:
- application/json
operationId: update-product
parameters:
- description: Product ID
in: path
name: product_id
required: true
type: integer
- description: ' '
in: body
name: _
required: true
schema:
$ref: '#/definitions/main.ProductUpdates'
responses: {}
summary: Update product attributes
swagger: "2.0"
================================================
FILE: example/override/handler.go
================================================
package main
import (
"database/sql"
"encoding/json"
"net/http"
)
type ProductUpdates struct {
Type sql.NullString `json:"type"`
Description sql.NullString `json:"description"`
Stock sql.NullInt64 `json:"stock"`
}
// UpdateProduct example
//
// @Summary Update product attributes
// @ID update-product
// @Accept json
// @Param product_id path int true "Product ID"
// @Param _ body ProductUpdates true " "
// @Router /testapi/update-product/{product_id} [post]
func UpdateProduct(w http.ResponseWriter, r *http.Request) {
var pUpdates ProductUpdates
if err := json.NewDecoder(r.Body).Decode(&pUpdates); err != nil {
// write your code
return
}
// write your code
}
================================================
FILE: example/override/main.go
================================================
package main
import (
"net/http"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server with null types overridden with primitive types.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host product_info.swagger.io
// @BasePath /v2
func main() {
http.HandleFunc("/testapi/update-product", UpdateProduct)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: field_parser.go
================================================
package swag
import (
"fmt"
"go/ast"
"reflect"
"regexp"
"strconv"
"strings"
"sync"
"unicode"
"github.com/go-openapi/spec"
)
var _ FieldParser = &tagBaseFieldParser{p: nil, field: nil, tag: ""}
const (
requiredLabel = "required"
optionalLabel = "optional"
omitEmptyLabel = "omitempty"
swaggerTypeTag = "swaggertype"
swaggerIgnoreTag = "swaggerignore"
)
type tagBaseFieldParser struct {
p *Parser
field *ast.Field
tag reflect.StructTag
}
func newTagBaseFieldParser(p *Parser, field *ast.Field) FieldParser {
fieldParser := tagBaseFieldParser{
p: p,
field: field,
tag: "",
}
if fieldParser.field.Tag != nil {
fieldParser.tag = reflect.StructTag(strings.ReplaceAll(field.Tag.Value, "`", ""))
}
return &fieldParser
}
func (ps *tagBaseFieldParser) ShouldSkip() bool {
// Skip non-exported fields.
if ps.field.Names != nil && !ast.IsExported(ps.field.Names[0].Name) {
return true
}
if ps.field.Tag == nil {
return false
}
ignoreTag := ps.tag.Get(swaggerIgnoreTag)
if strings.EqualFold(ignoreTag, "true") {
return true
}
// json:"tag,hoge"
name := strings.TrimSpace(strings.Split(ps.tag.Get(jsonTag), ",")[0])
if name == "-" {
return true
}
return false
}
func (ps *tagBaseFieldParser) FieldNames() ([]string, error) {
if len(ps.field.Names) <= 1 {
// if embedded but with a json/form name ??
if ps.field.Tag != nil {
// json:"tag,hoge"
name := strings.TrimSpace(strings.Split(ps.tag.Get(jsonTag), ",")[0])
if name != "" {
return []string{name}, nil
}
// use "form" tag over json tag
name = ps.FormName()
if name != "" {
return []string{name}, nil
}
}
if len(ps.field.Names) == 0 {
return nil, nil
}
}
var names = make([]string, 0, len(ps.field.Names))
for _, name := range ps.field.Names {
switch ps.p.PropNamingStrategy {
case SnakeCase:
names = append(names, toSnakeCase(name.Name))
case PascalCase:
names = append(names, name.Name)
default:
names = append(names, toLowerCamelCase(name.Name))
}
}
return names, nil
}
func (ps *tagBaseFieldParser) FirstTagValue(tag string) string {
if ps.field.Tag != nil {
return strings.TrimRight(strings.TrimSpace(strings.Split(ps.tag.Get(tag), ",")[0]), "[]")
}
return ""
}
func (ps *tagBaseFieldParser) FormName() string {
return ps.FirstTagValue(formTag)
}
func (ps *tagBaseFieldParser) QueryName() string {
return ps.FirstTagValue(queryTag)
}
func (ps *tagBaseFieldParser) HeaderName() string {
return ps.FirstTagValue(headerTag)
}
func (ps *tagBaseFieldParser) PathName() string {
return ps.FirstTagValue(uriTag)
}
func (ps *tagBaseFieldParser) ParamName() string {
return ps.FirstTagValue(paramTag)
}
func toSnakeCase(in string) string {
var (
runes = []rune(in)
length = len(runes)
out []rune
)
for idx := 0; idx < length; idx++ {
if idx > 0 && unicode.IsUpper(runes[idx]) &&
((idx+1 < length && unicode.IsLower(runes[idx+1])) || unicode.IsLower(runes[idx-1])) {
out = append(out, '_')
}
out = append(out, unicode.ToLower(runes[idx]))
}
return string(out)
}
func toLowerCamelCase(in string) string {
var flag bool
out := make([]rune, len(in))
runes := []rune(in)
for i, curr := range runes {
if (i == 0 && unicode.IsUpper(curr)) || (flag && unicode.IsUpper(curr)) {
out[i] = unicode.ToLower(curr)
flag = true
continue
}
out[i] = curr
flag = false
}
return string(out)
}
func (ps *tagBaseFieldParser) CustomSchema() (*spec.Schema, error) {
if ps.field.Tag == nil {
return nil, nil
}
typeTag := ps.tag.Get(swaggerTypeTag)
if typeTag != "" {
return BuildCustomSchema(strings.Split(typeTag, ","))
}
return nil, nil
}
type structField struct {
title string
schemaType string
arrayType string
formatType string
maximum *float64
minimum *float64
multipleOf *float64
maxLength *int64
minLength *int64
maxItems *int64
minItems *int64
exampleValue any
enums []any
enumVarNames []any
unique bool
}
// splitNotWrapped slices s into all substrings separated by sep if sep is not
// wrapped by brackets and returns a slice of the substrings between those separators.
func splitNotWrapped(s string, sep rune) []string {
openCloseMap := map[rune]rune{
'(': ')',
'[': ']',
'{': '}',
}
var (
result = make([]string, 0)
current = strings.Builder{}
openCount = 0
openChar rune
)
for _, char := range s {
switch {
case openChar == 0 && openCloseMap[char] != 0:
openChar = char
openCount++
current.WriteRune(char)
case char == openChar:
openCount++
current.WriteRune(char)
case openCount > 0 && char == openCloseMap[openChar]:
openCount--
current.WriteRune(char)
case openCount == 0 && char == sep:
result = append(result, current.String())
openChar = 0
current = strings.Builder{}
default:
current.WriteRune(char)
}
}
if current.String() != "" {
result = append(result, current.String())
}
return result
}
// ComplementSchema complement schema with field properties
func (ps *tagBaseFieldParser) ComplementSchema(schema *spec.Schema) error {
types := ps.p.GetSchemaTypePath(schema, 2)
if len(types) == 0 {
return fmt.Errorf("invalid type for field: %s", ps.field.Names[0])
}
if IsRefSchema(schema) {
var newSchema = spec.Schema{}
err := ps.complementSchema(&newSchema, types)
if err != nil {
return err
}
if !reflect.ValueOf(newSchema).IsZero() {
*schema = *(newSchema.WithAllOf(*schema))
}
return nil
}
return ps.complementSchema(schema, types)
}
// complementSchema complement schema with field properties
func (ps *tagBaseFieldParser) complementSchema(schema *spec.Schema, types []string) error {
if ps.field.Tag == nil {
if ps.field.Doc != nil {
schema.Description = strings.TrimSpace(ps.field.Doc.Text())
}
if schema.Description == "" && ps.field.Comment != nil {
schema.Description = strings.TrimSpace(ps.field.Comment.Text())
}
return nil
}
field := &structField{
schemaType: types[0],
formatType: ps.tag.Get(formatTag),
title: ps.tag.Get(titleTag),
}
if len(types) > 1 && (types[0] == ARRAY || types[0] == OBJECT) {
field.arrayType = types[1]
}
jsonTagValue := ps.tag.Get(jsonTag)
bindingTagValue := ps.tag.Get(bindingTag)
if bindingTagValue != "" {
parseValidTags(bindingTagValue, field)
}
validateTagValue := ps.tag.Get(validateTag)
if validateTagValue != "" {
parseValidTags(validateTagValue, field)
}
enumsTagValue := ps.tag.Get(enumsTag)
if enumsTagValue != "" {
err := parseEnumTags(enumsTagValue, field)
if err != nil {
return err
}
}
if IsNumericType(field.schemaType) || IsNumericType(field.arrayType) {
maximum, err := getFloatTag(ps.tag, maximumTag)
if err != nil {
return err
}
if maximum != nil {
field.maximum = maximum
}
minimum, err := getFloatTag(ps.tag, minimumTag)
if err != nil {
return err
}
if minimum != nil {
field.minimum = minimum
}
multipleOf, err := getFloatTag(ps.tag, multipleOfTag)
if err != nil {
return err
}
if multipleOf != nil {
field.multipleOf = multipleOf
}
}
if field.schemaType == STRING || field.arrayType == STRING {
maxLength, err := getIntTag(ps.tag, maxLengthTag)
if err != nil {
return err
}
if maxLength != nil {
field.maxLength = maxLength
}
minLength, err := getIntTag(ps.tag, minLengthTag)
if err != nil {
return err
}
if minLength != nil {
field.minLength = minLength
}
}
// json:"name,string" or json:",string"
exampleTagValue, ok := ps.tag.Lookup(exampleTag)
if ok {
field.exampleValue = exampleTagValue
if !strings.Contains(jsonTagValue, ",string") {
example, err := defineTypeOfExample(field.schemaType, field.arrayType, exampleTagValue)
if err != nil {
return err
}
field.exampleValue = example
}
}
// perform this after setting everything else (min, max, etc...)
if strings.Contains(jsonTagValue, ",string") {
// @encoding/json: "It applies only to fields of string, floating point, integer, or boolean types."
defaultValues := map[string]string{
// Zero Values as string
STRING: "",
INTEGER: "0",
BOOLEAN: "false",
NUMBER: "0",
}
defaultValue, ok := defaultValues[field.schemaType]
if ok {
field.schemaType = STRING
*schema = *PrimitiveSchema(field.schemaType)
if field.exampleValue == nil {
// if exampleValue is not defined by the user,
// we will force an example with a correct value
// (eg: int->"0", bool:"false")
field.exampleValue = defaultValue
}
}
}
if ps.field.Doc != nil {
schema.Description = strings.TrimSpace(ps.field.Doc.Text())
}
if schema.Description == "" && ps.field.Comment != nil {
schema.Description = strings.TrimSpace(ps.field.Comment.Text())
}
schema.ReadOnly = ps.tag.Get(readOnlyTag) == "true"
defaultTagValue, ok := ps.tag.Lookup(defaultTag)
if ok {
value, err := defineType(field.schemaType, defaultTagValue)
if err != nil {
return err
}
schema.Default = value
}
schema.Example = field.exampleValue
if field.schemaType != ARRAY {
schema.Format = field.formatType
}
schema.Title = field.title
extensionsTagValue := ps.tag.Get(extensionsTag)
if extensionsTagValue != "" {
schema.Extensions = setExtensionParam(extensionsTagValue)
}
varNamesTag := ps.tag.Get("x-enum-varnames")
if varNamesTag != "" {
varNames := strings.Split(varNamesTag, ",")
if len(varNames) != len(field.enums) {
return fmt.Errorf("invalid count of x-enum-varnames. expected %d, got %d", len(field.enums), len(varNames))
}
field.enumVarNames = nil
for _, v := range varNames {
field.enumVarNames = append(field.enumVarNames, v)
}
if field.schemaType == ARRAY {
// Add the var names in the items schema
if schema.Items.Schema.Extensions == nil {
schema.Items.Schema.Extensions = map[string]any{}
}
schema.Items.Schema.Extensions[enumVarNamesExtension] = field.enumVarNames
} else {
// Add to top level schema
if schema.Extensions == nil {
schema.Extensions = map[string]any{}
}
schema.Extensions[enumVarNamesExtension] = field.enumVarNames
}
}
eleSchema := schema
if field.schemaType == ARRAY {
// For Array only
schema.MaxItems = field.maxItems
schema.MinItems = field.minItems
schema.UniqueItems = field.unique
if schema.Items != nil {
eleSchema = schema.Items.Schema
}
eleSchema.Format = field.formatType
}
eleSchema.Maximum = field.maximum
eleSchema.Minimum = field.minimum
eleSchema.MultipleOf = field.multipleOf
eleSchema.MaxLength = field.maxLength
eleSchema.MinLength = field.minLength
eleSchema.Enum = field.enums
return nil
}
func getFloatTag(structTag reflect.StructTag, tagName string) (*float64, error) {
strValue := structTag.Get(tagName)
if strValue == "" {
return nil, nil
}
value, err := strconv.ParseFloat(strValue, 64)
if err != nil {
return nil, fmt.Errorf("can't parse numeric value of %q tag: %v", tagName, err)
}
return &value, nil
}
func getIntTag(structTag reflect.StructTag, tagName string) (*int64, error) {
strValue := structTag.Get(tagName)
if strValue == "" {
return nil, nil
}
value, err := strconv.ParseInt(strValue, 10, 64)
if err != nil {
return nil, fmt.Errorf("can't parse numeric value of %q tag: %v", tagName, err)
}
return &value, nil
}
func (ps *tagBaseFieldParser) IsRequired() (bool, error) {
if ps.field.Tag == nil {
return false, nil
}
bindingTag := ps.tag.Get(bindingTag)
if bindingTag != "" {
for _, val := range strings.Split(bindingTag, ",") {
switch val {
case requiredLabel:
return true, nil
case optionalLabel:
return false, nil
}
}
}
validateTag := ps.tag.Get(validateTag)
if validateTag != "" {
for _, val := range strings.Split(validateTag, ",") {
switch val {
case requiredLabel:
return true, nil
case optionalLabel:
return false, nil
}
}
}
jsonTag := ps.tag.Get(jsonTag)
if jsonTag != "" {
for _, val := range strings.Split(jsonTag, ",") {
if val == omitEmptyLabel {
return false, nil
}
}
}
return ps.p.RequiredByDefault, nil
}
func parseValidTags(validTag string, sf *structField) {
// `validate:"required,max=10,min=1"`
// ps. required checked by IsRequired().
for _, val := range strings.Split(validTag, ",") {
var (
valValue string
keyVal = strings.Split(val, "=")
)
switch len(keyVal) {
case 1:
case 2:
valValue = strings.ReplaceAll(strings.ReplaceAll(keyVal[1], utf8HexComma, ","), utf8Pipe, "|")
default:
continue
}
switch keyVal[0] {
case "max", "lte":
sf.setMax(valValue)
case "min", "gte":
sf.setMin(valValue)
case "oneof":
sf.setOneOf(valValue)
case "unique":
if sf.schemaType == ARRAY {
sf.unique = true
}
case "dive":
// ignore dive
return
default:
continue
}
}
}
func parseEnumTags(enumTag string, field *structField) error {
enumType := field.schemaType
if field.schemaType == ARRAY {
enumType = field.arrayType
}
field.enums = nil
for _, e := range strings.Split(enumTag, ",") {
value, err := defineType(enumType, e)
if err != nil {
return err
}
field.enums = append(field.enums, value)
}
return nil
}
func (sf *structField) setOneOf(valValue string) {
if len(sf.enums) != 0 {
return
}
enumType := sf.schemaType
if sf.schemaType == ARRAY {
enumType = sf.arrayType
}
valValues := parseOneOfParam2(valValue)
for i := range valValues {
value, err := defineType(enumType, valValues[i])
if err != nil {
continue
}
sf.enums = append(sf.enums, value)
}
}
func (sf *structField) setMin(valValue string) {
value, err := strconv.ParseFloat(valValue, 64)
if err != nil {
return
}
switch sf.schemaType {
case INTEGER, NUMBER:
sf.minimum = &value
case STRING:
intValue := int64(value)
sf.minLength = &intValue
case ARRAY:
intValue := int64(value)
sf.minItems = &intValue
}
}
func (sf *structField) setMax(valValue string) {
value, err := strconv.ParseFloat(valValue, 64)
if err != nil {
return
}
switch sf.schemaType {
case INTEGER, NUMBER:
sf.maximum = &value
case STRING:
intValue := int64(value)
sf.maxLength = &intValue
case ARRAY:
intValue := int64(value)
sf.maxItems = &intValue
}
}
const (
utf8HexComma = "0x2C"
utf8Pipe = "0x7C"
)
// These code copy from
// https://github.com/go-playground/validator/blob/d4271985b44b735c6f76abc7a06532ee997f9476/baked_in.go#L207
// ---.
var oneofValsCache = map[string][]string{}
var oneofValsCacheRWLock = sync.RWMutex{}
var splitParamsRegex = regexp.MustCompile(`'[^']*'|\S+`)
func parseOneOfParam2(param string) []string {
oneofValsCacheRWLock.RLock()
values, ok := oneofValsCache[param]
oneofValsCacheRWLock.RUnlock()
if !ok {
oneofValsCacheRWLock.Lock()
values = splitParamsRegex.FindAllString(param, -1)
for i := 0; i < len(values); i++ {
values[i] = strings.ReplaceAll(values[i], "'", "")
}
oneofValsCache[param] = values
oneofValsCacheRWLock.Unlock()
}
return values
}
// ---.
================================================
FILE: field_parser_test.go
================================================
package swag
import (
"go/ast"
"testing"
"github.com/go-openapi/spec"
"github.com/stretchr/testify/assert"
)
func TestDefaultFieldParser(t *testing.T) {
t.Run("Example tag", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"string"}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" example:"one"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, "one", schema.Example)
schema = spec.Schema{}
schema.Type = []string{"string"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" example:""`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, "", schema.Example)
schema = spec.Schema{}
schema.Type = []string{"float"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" example:"one"`,
}},
).ComplementSchema(&schema)
assert.Error(t, err)
})
t.Run("Format tag", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"string"}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" format:"csv"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, "csv", schema.Format)
})
t.Run("Title tag", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"string"}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" title:"myfield"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, "myfield", schema.Title)
})
t.Run("Required tag", func(t *testing.T) {
t.Parallel()
got, err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" binding:"required"`,
}},
).IsRequired()
assert.NoError(t, err)
assert.Equal(t, true, got)
got, err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required"`,
}},
).IsRequired()
assert.NoError(t, err)
assert.Equal(t, true, got)
})
t.Run("Default required tag", func(t *testing.T) {
t.Parallel()
got, err := newTagBaseFieldParser(
&Parser{
RequiredByDefault: true,
},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test"`,
}},
).IsRequired()
assert.NoError(t, err)
assert.True(t, got)
})
t.Run("Optional tag", func(t *testing.T) {
t.Parallel()
got, err := newTagBaseFieldParser(
&Parser{
RequiredByDefault: true,
},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" binding:"optional"`,
}},
).IsRequired()
assert.NoError(t, err)
assert.False(t, got)
got, err = newTagBaseFieldParser(
&Parser{
RequiredByDefault: true,
},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"optional"`,
}},
).IsRequired()
assert.NoError(t, err)
assert.False(t, got)
got, err = newTagBaseFieldParser(
&Parser{
RequiredByDefault: true,
},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test,omitempty"`,
}},
).IsRequired()
assert.NoError(t, err)
assert.False(t, got)
})
t.Run("Extensions tag", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"int"}
schema.Extensions = map[string]interface{}{}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" extensions:"x-nullable,x-abc=def,!x-omitempty,x-example=[0, 9],x-example2={çãíœ, (bar=(abc, def)), [0,9]}"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, true, schema.Extensions["x-nullable"])
assert.Equal(t, "def", schema.Extensions["x-abc"])
assert.Equal(t, false, schema.Extensions["x-omitempty"])
assert.Equal(t, "[0, 9]", schema.Extensions["x-example"])
assert.Equal(t, "{çãíœ, (bar=(abc, def)), [0,9]}", schema.Extensions["x-example2"])
})
t.Run("Enums tag", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"string"}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" enums:"a,b,c"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, []interface{}{"a", "b", "c"}, schema.Enum)
schema = spec.Schema{}
schema.Type = []string{"float"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" enums:"a,b,c"`,
}},
).ComplementSchema(&schema)
assert.Error(t, err)
})
t.Run("EnumVarNames tag", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"int"}
schema.Extensions = map[string]interface{}{}
schema.Enum = []interface{}{}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" enums:"0,1,2" x-enum-varnames:"Daily,Weekly,Monthly"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, []interface{}{"Daily", "Weekly", "Monthly"}, schema.Extensions["x-enum-varnames"])
schema = spec.Schema{}
schema.Type = []string{"int"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" enums:"0,1,2,3" x-enum-varnames:"Daily,Weekly,Monthly"`,
}},
).ComplementSchema(&schema)
assert.Error(t, err)
// Test for an array of enums
schema = spec.Schema{}
schema.Type = []string{"array"}
schema.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"int"},
},
},
}
schema.Extensions = map[string]interface{}{}
schema.Enum = []interface{}{}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" enums:"0,1,2" x-enum-varnames:"Daily,Weekly,Monthly"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, []interface{}{"Daily", "Weekly", "Monthly"}, schema.Items.Schema.Extensions["x-enum-varnames"])
assert.Equal(t, spec.Extensions{}, schema.Extensions)
})
t.Run("Default tag", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"string"}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" default:"pass"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, "pass", schema.Default)
schema = spec.Schema{}
schema.Type = []string{"float"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" default:"pass"`,
}},
).ComplementSchema(&schema)
assert.Error(t, err)
})
t.Run("Numeric value", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"integer"}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" maximum:"1"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
max := float64(1)
assert.Equal(t, &max, schema.Maximum)
schema = spec.Schema{}
schema.Type = []string{"integer"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" maximum:"one"`,
}},
).ComplementSchema(&schema)
assert.Error(t, err)
schema = spec.Schema{}
schema.Type = []string{"number"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" maximum:"1"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
max = float64(1)
assert.Equal(t, &max, schema.Maximum)
schema = spec.Schema{}
schema.Type = []string{"number"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" maximum:"one"`,
}},
).ComplementSchema(&schema)
assert.Error(t, err)
schema = spec.Schema{}
schema.Type = []string{"number"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" multipleOf:"1"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
multipleOf := float64(1)
assert.Equal(t, &multipleOf, schema.MultipleOf)
schema = spec.Schema{}
schema.Type = []string{"number"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" multipleOf:"one"`,
}},
).ComplementSchema(&schema)
assert.Error(t, err)
schema = spec.Schema{}
schema.Type = []string{"integer"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" minimum:"1"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
min := float64(1)
assert.Equal(t, &min, schema.Minimum)
schema = spec.Schema{}
schema.Type = []string{"integer"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" minimum:"one"`,
}},
).ComplementSchema(&schema)
assert.Error(t, err)
})
t.Run("String value", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"string"}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" maxLength:"1"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
max := int64(1)
assert.Equal(t, &max, schema.MaxLength)
schema = spec.Schema{}
schema.Type = []string{"string"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" maxLength:"one"`,
}},
).ComplementSchema(&schema)
assert.Error(t, err)
schema = spec.Schema{}
schema.Type = []string{"string"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" minLength:"1"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
min := int64(1)
assert.Equal(t, &min, schema.MinLength)
schema = spec.Schema{}
schema.Type = []string{"string"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" minLength:"one"`,
}},
).ComplementSchema(&schema)
assert.Error(t, err)
})
t.Run("Readonly tag", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"string"}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" readonly:"true"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, true, schema.ReadOnly)
})
t.Run("Invalid tag", func(t *testing.T) {
t.Parallel()
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Names: []*ast.Ident{{Name: "BasicStruct"}}},
).ComplementSchema(nil)
assert.Error(t, err)
})
}
func TestValidTags(t *testing.T) {
t.Run("Required with max/min tag", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"string"}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,max=10,min=1"`,
}},
).ComplementSchema(&schema)
max := int64(10)
min := int64(1)
assert.NoError(t, err)
assert.Equal(t, &max, schema.MaxLength)
assert.Equal(t, &min, schema.MinLength)
schema = spec.Schema{}
schema.Type = []string{"string"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,max=10,gte=1"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, &max, schema.MaxLength)
assert.Equal(t, &min, schema.MinLength)
schema = spec.Schema{}
schema.Type = []string{"integer"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,max=10,min=1"`,
}},
).ComplementSchema(&schema)
maxFloat64 := float64(10)
minFloat64 := float64(1)
assert.NoError(t, err)
assert.Equal(t, &maxFloat64, schema.Maximum)
assert.Equal(t, &minFloat64, schema.Minimum)
schema = spec.Schema{}
schema.Type = []string{"array"}
schema.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,max=10,min=1"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, &max, schema.MaxItems)
assert.Equal(t, &min, schema.MinItems)
// wrong validate tag will be ignored.
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,max=ten,min=1"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Empty(t, schema.MaxItems)
assert.Equal(t, &min, schema.MinItems)
})
t.Run("Required with oneof tag", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"string"}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,oneof='red book' 'green book'"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, []interface{}{"red book", "green book"}, schema.Enum)
schema = spec.Schema{}
schema.Type = []string{"integer"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,oneof=1 2 3"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, []interface{}{1, 2, 3}, schema.Enum)
schema = spec.Schema{}
schema.Type = []string{"array"}
schema.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,oneof=red green yellow"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, []interface{}{"red", "green", "yellow"}, schema.Items.Schema.Enum)
schema = spec.Schema{}
schema.Type = []string{"string"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,oneof='red green' blue 'c0x2Cc' 'd0x7Cd'"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, []interface{}{"red green", "blue", "c,c", "d|d"}, schema.Enum)
schema = spec.Schema{}
schema.Type = []string{"string"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,oneof='c0x9Ab' book"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, []interface{}{"c0x9Ab", "book"}, schema.Enum)
schema = spec.Schema{}
schema.Type = []string{"string"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" binding:"oneof=foo bar" validate:"required,oneof=foo bar" enums:"a,b,c"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, []interface{}{"a", "b", "c"}, schema.Enum)
schema = spec.Schema{}
schema.Type = []string{"string"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" binding:"oneof=aa bb" validate:"required,oneof=foo bar"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, []interface{}{"aa", "bb"}, schema.Enum)
})
t.Run("Required with unique tag", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"array"}
schema.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,unique"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, true, schema.UniqueItems)
})
t.Run("All tag", func(t *testing.T) {
t.Parallel()
schema := spec.Schema{}
schema.Type = []string{"array"}
schema.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
}
err := newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,unique,max=10,min=1,oneof=a0x2Cc 'c0x7Cd book',omitempty,dive,max=1"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, true, schema.UniqueItems)
max := int64(10)
min := int64(1)
assert.Equal(t, &max, schema.MaxItems)
assert.Equal(t, &min, schema.MinItems)
assert.Equal(t, []interface{}{"a,c", "c|d book"}, schema.Items.Schema.Enum)
schema = spec.Schema{}
schema.Type = []string{"array"}
schema.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,oneof=,max=10=90,min=1"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Empty(t, schema.UniqueItems)
assert.Empty(t, schema.MaxItems)
assert.Equal(t, &min, schema.MinItems)
schema = spec.Schema{}
schema.Type = []string{"array"}
schema.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,max=10,min=one"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Equal(t, &max, schema.MaxItems)
assert.Empty(t, schema.MinItems)
schema = spec.Schema{}
schema.Type = []string{"integer"}
err = newTagBaseFieldParser(
&Parser{},
&ast.Field{
Names: []*ast.Ident{{Name: "Test"}},
Tag: &ast.BasicLit{
Value: `json:"test" validate:"required,oneof=one two"`,
}},
).ComplementSchema(&schema)
assert.NoError(t, err)
assert.Empty(t, schema.Enum)
})
t.Run("Form Filed Name", func(t *testing.T) {
t.Parallel()
filednames, err := newTagBaseFieldParser(
&Parser{},
&ast.Field{
Names: []*ast.Ident{{Name: "Test"}},
Tag: &ast.BasicLit{
Value: `form:"test[]"`,
}},
).FieldNames()
assert.NoError(t, err)
assert.Equal(t, "test", filednames[0])
filednames, err = newTagBaseFieldParser(
&Parser{},
&ast.Field{
Names: []*ast.Ident{{Name: "Test"}},
Tag: &ast.BasicLit{
Value: `form:"test"`,
}},
).FieldNames()
assert.NoError(t, err)
assert.Equal(t, "test", filednames[0])
})
t.Run("Two Names", func(t *testing.T) {
t.Parallel()
fieldnames, err := newTagBaseFieldParser(
&Parser{},
&ast.Field{
Names: []*ast.Ident{{Name: "X"}, {Name: "Y"}},
},
).FieldNames()
assert.NoError(t, err)
assert.Equal(t, 2, len(fieldnames))
assert.Equal(t, "x", fieldnames[0])
assert.Equal(t, "y", fieldnames[1])
})
}
================================================
FILE: format/format.go
================================================
package format
import (
"bytes"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/swaggo/swag"
"golang.org/x/sync/errgroup"
)
// Format implements `fmt` command for formatting swag comments in Go source
// files.
type Format struct {
formatter *swag.Formatter
// exclude exclude dirs and files in SearchDir
exclude map[string]bool
}
// New creates a new Format instance
func New() *Format {
return &Format{
exclude: map[string]bool{},
formatter: swag.NewFormatter(),
}
}
// Config specifies configuration for a format run
type Config struct {
// SearchDir the swag would be parse
SearchDir string
// excludes dirs and files in SearchDir,comma separated
Excludes string
// MainFile (DEPRECATED)
MainFile string
}
var defaultExcludes = []string{"docs", "vendor"}
// Build runs formatter according to configuration in config
func (f *Format) Build(config *Config) error {
searchDirs := strings.Split(config.SearchDir, ",")
for _, searchDir := range searchDirs {
if _, err := os.Stat(searchDir); os.IsNotExist(err) {
return fmt.Errorf("fmt: %w", err)
}
for _, d := range defaultExcludes {
f.exclude[filepath.Join(searchDir, d)] = true
}
}
for _, fi := range strings.Split(config.Excludes, ",") {
if fi = strings.TrimSpace(fi); fi != "" {
f.exclude[filepath.Clean(fi)] = true
}
}
var eg errgroup.Group
eg.SetLimit(runtime.GOMAXPROCS(0))
for _, searchDir := range searchDirs {
err := filepath.Walk(searchDir, func(path string, fileInfo fs.FileInfo, err error) error {
if fileInfo.IsDir() && f.excludeDir(path) {
return filepath.SkipDir
}
if f.excludeFile(path) {
return nil
}
eg.Go(func() error {
return f.format(path)
})
return nil
})
if err != nil {
return err
}
}
if err := eg.Wait(); err != nil {
return err
}
return nil
}
func (f *Format) excludeDir(path string) bool {
return f.exclude[path] ||
filepath.Base(path)[0] == '.' &&
len(filepath.Base(path)) > 1 // exclude hidden folders
}
func (f *Format) excludeFile(path string) bool {
return f.exclude[path] ||
strings.HasSuffix(strings.ToLower(path), "_test.go") ||
filepath.Ext(path) != ".go"
}
func (f *Format) format(path string) error {
original, err := os.ReadFile(path)
if err != nil {
return err
}
contents := make([]byte, len(original))
copy(contents, original)
formatted, err := f.formatter.Format(path, contents)
if err != nil {
return err
}
if bytes.Equal(original, formatted) {
// Skip write if no change
return nil
}
return write(path, formatted)
}
func write(path string, contents []byte) error {
originalFileInfo, err := os.Stat(path)
if err != nil {
return err
}
f, err := os.CreateTemp(filepath.Dir(path), filepath.Base(path))
if err != nil {
return err
}
defer os.Remove(f.Name())
if _, err := f.Write(contents); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
if err := os.Chmod(f.Name(), originalFileInfo.Mode()); err != nil {
return err
}
return os.Rename(f.Name(), path)
}
// Run the format on src and write the result to dst.
func (f *Format) Run(src io.Reader, dst io.Writer) error {
contents, err := io.ReadAll(src)
if err != nil {
return err
}
result, err := f.formatter.Format("", contents)
if err != nil {
return err
}
r := bytes.NewReader(result)
if _, err := io.Copy(dst, r); err != nil {
return err
}
return nil
}
================================================
FILE: format/format_test.go
================================================
package format
import (
"bytes"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFormat_Format(t *testing.T) {
fx := setup(t)
assert.NoError(t, New().Build(&Config{SearchDir: fx.basedir}))
assert.True(t, fx.isFormatted("main.go"))
assert.True(t, fx.isFormatted("api/api.go"))
}
func TestFormat_PermissionsPreserved(t *testing.T) {
fx := setup(t)
originalFileInfo, err := os.Stat(filepath.Join(fx.basedir, "main.go"))
if err != nil {
t.Fatal(err)
}
assert.NoError(t, New().Build(&Config{SearchDir: fx.basedir}))
assert.True(t, permissionsEqual(t, filepath.Join(fx.basedir, "main.go"), originalFileInfo.Mode()))
assert.True(t, permissionsEqual(t, filepath.Join(fx.basedir, "api/api.go"), originalFileInfo.Mode()))
}
func TestFormat_ExcludeDir(t *testing.T) {
fx := setup(t)
assert.NoError(t, New().Build(&Config{
SearchDir: fx.basedir,
Excludes: filepath.Join(fx.basedir, "api"),
}))
assert.False(t, fx.isFormatted("api/api.go"))
}
func TestFormat_ExcludeFile(t *testing.T) {
fx := setup(t)
assert.NoError(t, New().Build(&Config{
SearchDir: fx.basedir,
Excludes: filepath.Join(fx.basedir, "main.go"),
}))
assert.False(t, fx.isFormatted("main.go"))
}
func TestFormat_DefaultExcludes(t *testing.T) {
fx := setup(t)
assert.NoError(t, New().Build(&Config{SearchDir: fx.basedir}))
assert.False(t, fx.isFormatted("api/api_test.go"))
assert.False(t, fx.isFormatted("docs/docs.go"))
}
func TestFormat_ParseError(t *testing.T) {
fx := setup(t)
os.WriteFile(filepath.Join(fx.basedir, "parse_error.go"), []byte(`package main
func invalid() {`), 0644)
assert.Error(t, New().Build(&Config{SearchDir: fx.basedir}))
}
func TestFormat_ReadError(t *testing.T) {
fx := setup(t)
os.Chmod(filepath.Join(fx.basedir, "main.go"), 0)
assert.Error(t, New().Build(&Config{SearchDir: fx.basedir}))
}
func TestFormat_WriteError(t *testing.T) {
fx := setup(t)
os.Chmod(fx.basedir, 0555)
assert.Error(t, New().Build(&Config{SearchDir: fx.basedir}))
os.Chmod(fx.basedir, 0755)
}
func TestFormat_InvalidSearchDir(t *testing.T) {
formatter := New()
assert.Error(t, formatter.Build(&Config{SearchDir: "no_such_dir"}))
}
type fixture struct {
t *testing.T
basedir string
}
func setup(t *testing.T) *fixture {
fx := &fixture{
t: t,
basedir: t.TempDir(),
}
for filename, contents := range testFiles {
fullpath := filepath.Join(fx.basedir, filepath.Clean(filename))
if err := os.MkdirAll(filepath.Dir(fullpath), 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(fullpath, contents, 0644); err != nil {
t.Fatal(err)
}
}
return fx
}
func (fx *fixture) isFormatted(file string) bool {
contents, err := os.ReadFile(filepath.Join(fx.basedir, file))
if err != nil {
fx.t.Fatal(err)
}
return !bytes.Equal(testFiles[file], contents)
}
func permissionsEqual(t *testing.T, path string, expectedMode os.FileMode) bool {
fileInfo, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
return expectedMode == fileInfo.Mode()
}
var testFiles = map[string][]byte{
"api/api.go": []byte(`package api
import "net/http"
// @Summary Add a new pet to the store
// @Description get string by ID
func GetStringByInt(w http.ResponseWriter, r *http.Request) {
//write your code
}`),
"api/api_test.go": []byte(`package api
// @Summary API Test
// @Description Should not be formatted
func TestApi(t *testing.T) {}`),
"docs/docs.go": []byte(`package docs
// @Summary Documentation package
// @Description Should not be formatted`),
"main.go": []byte(`package main
import (
"net/http"
"github.com/swaggo/swag/format/testdata/api"
)
// @title Swagger Example API
// @version 1.0
func main() {
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
}`),
"README.md": []byte(`# Format test`),
}
================================================
FILE: formatter.go
================================================
package swag
import (
"bytes"
"fmt"
"go/ast"
goparser "go/parser"
"go/token"
"log"
"os"
"regexp"
"sort"
"strings"
"text/tabwriter"
"golang.org/x/tools/imports"
)
// Check of @Param @Success @Failure @Response @Header
var specialTagForSplit = map[string]bool{
paramAttr: true,
successAttr: true,
failureAttr: true,
responseAttr: true,
headerAttr: true,
}
var skipChar = map[byte]byte{
'"': '"',
'(': ')',
'{': '}',
'[': ']',
}
// Formatter implements a formatter for Go source files.
type Formatter struct {
// debugging output goes here
debug Debugger
}
// NewFormatter create a new formatter instance.
func NewFormatter() *Formatter {
formatter := &Formatter{
debug: log.New(os.Stdout, "", log.LstdFlags),
}
return formatter
}
// Format formats swag comments in contents. It uses fileName to report errors
// that happen during parsing of contents.
func (f *Formatter) Format(fileName string, contents []byte) ([]byte, error) {
fileSet := token.NewFileSet()
astFile, err := goparser.ParseFile(fileSet, fileName, contents, goparser.ParseComments)
if err != nil {
return nil, err
}
// We skip generated files to not interfere with the formatting and
// introduce unnecessary changes.
if ast.IsGenerated(astFile) {
return contents, nil
}
// Formatting changes are described as an edit list of byte range
// replacements. We make these content-level edits directly rather than
// changing the AST nodes and writing those out (via [go/printer] or
// [go/format]) so that we only change the formatting of Swag attribute
// comments. This won't touch the formatting of any other comments, or of
// functions, etc.
maxEdits := 0
for _, comment := range astFile.Comments {
maxEdits += len(comment.List)
}
edits := make(edits, 0, maxEdits)
for _, comment := range astFile.Comments {
formatFuncDoc(fileSet, comment.List, &edits)
}
formatted, err := imports.Process(fileName, edits.apply(contents), nil)
if err != nil {
return nil, err
}
return formatted, nil
}
type edit struct {
begin int
end int
replacement []byte
}
type edits []edit
func (edits edits) apply(contents []byte) []byte {
// Apply the edits with the highest offset first, so that earlier edits
// don't affect the offsets of later edits.
sort.Slice(edits, func(i, j int) bool {
return edits[i].begin > edits[j].begin
})
for _, edit := range edits {
prefix := contents[:edit.begin]
suffix := contents[edit.end:]
contents = append(prefix, append(edit.replacement, suffix...)...)
}
return contents
}
// formatFuncDoc reformats the comment lines in commentList, and appends any
// changes to the edit list.
func formatFuncDoc(fileSet *token.FileSet, commentList []*ast.Comment, edits *edits) {
// Building the edit list to format a comment block is a two-step process.
// First, we iterate over each comment line looking for Swag attributes. In
// each one we find, we replace alignment whitespace with a tab character,
// then write the result into a tab writer.
linesToComments := make(map[int]int, len(commentList))
buffer := &bytes.Buffer{}
w := tabwriter.NewWriter(buffer, 1, 4, 1, '\t', 0)
for commentIndex, comment := range commentList {
text := comment.Text
if attr, body, found := swagComment(text); found {
formatted := "//\t" + attr
if body != "" {
formatted += "\t" + splitComment2(attr, body)
}
_, _ = fmt.Fprintln(w, formatted)
linesToComments[len(linesToComments)] = commentIndex
}
}
// Once we've loaded all of the comment lines to be aligned into the tab
// writer, flushing it causes the aligned text to be written out to the
// backing buffer.
_ = w.Flush()
// Now the second step: we iterate over the aligned comment lines that were
// written into the backing buffer, pair each one up to its original
// comment line, and use the combination to describe the edit that needs to
// be made to the original input.
formattedComments := bytes.Split(buffer.Bytes(), []byte("\n"))
for lineIndex, commentIndex := range linesToComments {
comment := commentList[commentIndex]
*edits = append(*edits, edit{
begin: fileSet.Position(comment.Pos()).Offset,
end: fileSet.Position(comment.End()).Offset,
replacement: formattedComments[lineIndex],
})
}
}
func splitComment2(attr, body string) string {
if specialTagForSplit[strings.ToLower(attr)] {
for i := 0; i < len(body); i++ {
if skipEnd, ok := skipChar[body[i]]; ok {
skipStart, n := body[i], 1
for i++; i < len(body); i++ {
if skipStart != skipEnd && body[i] == skipStart {
n++
} else if body[i] == skipEnd {
n--
if n == 0 {
break
}
}
}
} else if body[i] == ' ' || body[i] == '\t' {
j := i
for ; j < len(body) && (body[j] == ' ' || body[j] == '\t'); j++ {
}
body = replaceRange(body, i, j, "\t")
}
}
}
return body
}
func replaceRange(s string, start, end int, new string) string {
return s[:start] + new + s[end:]
}
var swagCommentLineExpression = regexp.MustCompile(`^\/\/\s+(@[\S.]+)\s*(.*)`)
func swagComment(comment string) (string, string, bool) {
matches := swagCommentLineExpression.FindStringSubmatch(comment)
if matches == nil {
return "", "", false
}
return matches[1], matches[2], true
}
================================================
FILE: formatter_test.go
================================================
package swag
import (
"testing"
"github.com/stretchr/testify/assert"
)
const (
SearchDir = "./testdata/format_test"
Excludes = "./testdata/format_test/web"
MainFile = "main.go"
)
func testFormat(t *testing.T, filename, contents, want string) {
got, err := NewFormatter().Format(filename, []byte(contents))
assert.NoError(t, err)
assert.Equal(t, want, string(got))
}
func Test_FormatMain(t *testing.T) {
contents := `package main
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {}`
want := `package main
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {}
`
testFormat(t, "main.go", contents, want)
}
func Test_FormatMultipleFunctions(t *testing.T) {
contents := `package main
// @Produce json
// @Success 200 {object} string
// @Failure 400 {object} string
func A() {}
// @Description Description of B.
// @Produce json
// @Success 200 {array} string
// @Failure 400 {object} string
func B() {}`
want := `package main
// @Produce json
// @Success 200 {object} string
// @Failure 400 {object} string
func A() {}
// @Description Description of B.
// @Produce json
// @Success 200 {array} string
// @Failure 400 {object} string
func B() {}
`
testFormat(t, "main.go", contents, want)
}
func Test_FormatApi(t *testing.T) {
contents := `package api
import "net/http"
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID get-string-by-int
// @Accept json
// @Produce json
// @Param some_id path int true "Some ID" Format(int64)
// @Param some_id body web.Pet true "Some ID"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
func GetStringByInt(w http.ResponseWriter, r *http.Request) {}`
want := `package api
import "net/http"
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID get-string-by-int
// @Accept json
// @Produce json
// @Param some_id path int true "Some ID" Format(int64)
// @Param some_id body web.Pet true "Some ID"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
func GetStringByInt(w http.ResponseWriter, r *http.Request) {}
`
testFormat(t, "api.go", contents, want)
}
func Test_NonSwagComment(t *testing.T) {
contents := `package api
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID get-string-by-int
// @ Accept json
// This is not a @swag comment`
want := `package api
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID get-string-by-int
// @ Accept json
// This is not a @swag comment
`
testFormat(t, "non_swag.go", contents, want)
}
func Test_EmptyComment(t *testing.T) {
contents := `package empty
// @Summary Add a new pet to the store
// @Description `
want := `package empty
// @Summary Add a new pet to the store
// @Description
`
testFormat(t, "empty.go", contents, want)
}
func Test_AlignAttribute(t *testing.T) {
contents := `package align
// @Summary Add a new pet to the store
// @Description Description`
want := `package align
// @Summary Add a new pet to the store
// @Description Description
`
testFormat(t, "align.go", contents, want)
}
func Test_SyntaxError(t *testing.T) {
contents := []byte(`package invalid
func invalid() {`)
_, err := NewFormatter().Format("invalid.go", contents)
assert.Error(t, err)
}
func Test_splitComment2(t *testing.T) {
type args struct {
attr string
body string
}
tests := []struct {
name string
args args
want string
}{
{
"test_splitComment2_1",
args{
attr: "@param",
body: " data body web.GenericBodyMulti[[]types.Post, [][]types.Post]",
},
"\tdata\tbody\tweb.GenericBodyMulti[[]types.Post, [][]types.Post]",
},
{
"test_splitComment2_2",
args{
attr: "@param",
body: ` some_id path int true "Some ID" Format(int64)`,
},
"\tsome_id\tpath\tint\ttrue\t\"Some ID\"\tFormat(int64)",
},
{
"test_splitComment2_3",
args{
attr: "@param",
body: ` @Param some_id body web.Pet true "Some ID"`,
},
"\t@Param\tsome_id\tbody\tweb.Pet\ttrue\t\"Some ID\"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, splitComment2(tt.args.attr, tt.args.body), "splitComment2(%v, %v)", tt.args.attr, tt.args.body)
})
}
}
func Test_FormatSkipGeneratedFile(t *testing.T) {
contents := `// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
package storage
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}
`
want := `// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
package storage
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}
`
testFormat(t, "db.sql.go", contents, want)
}
================================================
FILE: gen/gen.go
================================================
package gen
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"go/format"
"io"
"log"
"os"
"path"
"path/filepath"
"strings"
"text/template"
"time"
"github.com/go-openapi/spec"
"github.com/swaggo/swag"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"sigs.k8s.io/yaml"
)
var open = os.Open
// DefaultOverridesFile is the location swagger will look for type overrides.
const DefaultOverridesFile = ".swaggo"
type genTypeWriter func(*Config, *spec.Swagger) error
// Gen presents a generate tool for swag.
type Gen struct {
json func(data any) ([]byte, error)
jsonIndent func(data any) ([]byte, error)
jsonToYAML func(data []byte) ([]byte, error)
outputTypeMap map[string]genTypeWriter
debug Debugger
}
// Debugger is the interface that wraps the basic Printf method.
type Debugger interface {
Printf(format string, v ...any)
}
// New creates a new Gen.
func New() *Gen {
gen := Gen{
json: json.Marshal,
jsonIndent: func(data any) ([]byte, error) {
return json.MarshalIndent(data, "", " ")
},
jsonToYAML: yaml.JSONToYAML,
debug: log.New(os.Stdout, "", log.LstdFlags),
}
gen.outputTypeMap = map[string]genTypeWriter{
"go": gen.writeDocSwagger,
"json": gen.writeJSONSwagger,
"yaml": gen.writeYAMLSwagger,
"yml": gen.writeYAMLSwagger,
}
return &gen
}
// Config presents Gen configurations.
type Config struct {
Debugger swag.Debugger
// SearchDir the swag would parse,comma separated if multiple
SearchDir string
// excludes dirs and files in SearchDir,comma separated
Excludes string
// outputs only specific extension
ParseExtension string
// OutputDir represents the output directory for all the generated files
OutputDir string
// OutputTypes define types of files which should be generated
OutputTypes []string
// MainAPIFile the Go file path in which 'swagger general API Info' is written
MainAPIFile string
// PropNamingStrategy represents property naming strategy like snake case,camel case,pascal case
PropNamingStrategy string
// MarkdownFilesDir used to find markdown files, which can be used for tag descriptions
MarkdownFilesDir string
// CodeExampleFilesDir used to find code example files, which can be used for x-codeSamples
CodeExampleFilesDir string
// InstanceName is used to get distinct names for different swagger documents in the
// same project. The default value is "swagger".
InstanceName string
// ParseDepth dependency parse depth
ParseDepth int
// ParseVendor whether swag should be parse vendor folder
ParseVendor bool
// ParseDependencies whether swag should be parse outside dependency folder: 0 none, 1 models, 2 operations, 3 all
ParseDependency int
// UseStructNames stick to the struct name instead of those ugly full-path names
UseStructNames bool
// ParseInternal whether swag should parse internal packages
ParseInternal bool
// Strict whether swag should error or warn when it detects cases which are most likely user errors
Strict bool
// GeneratedTime whether swag should generate the timestamp at the top of docs.go
GeneratedTime bool
// RequiredByDefault set validation required for all fields by default
RequiredByDefault bool
// OverridesFile defines global type overrides.
OverridesFile string
// ParseGoList whether swag use go list to parse dependency
ParseGoList bool
// include only tags mentioned when searching, comma separated
Tags string
// LeftTemplateDelim defines the left delimiter for the template generation
LeftTemplateDelim string
// RightTemplateDelim defines the right delimiter for the template generation
RightTemplateDelim string
// PackageName defines package name of generated `docs.go`
PackageName string
// CollectionFormat set default collection format
CollectionFormat string
// Parse only packages whose import path match the given prefix, comma separated
PackagePrefix string
// State set host state
State string
// ParseFuncBody whether swag should parse api info inside of funcs
ParseFuncBody bool
// ParseGoPackages whether swag use golang.org/x/tools/go/packages to parse source.
ParseGoPackages bool
}
// Build builds swagger json file for given searchDir and mainAPIFile. Returns json.
func (g *Gen) Build(config *Config) error {
if config.Debugger != nil {
g.debug = config.Debugger
}
if config.InstanceName == "" {
config.InstanceName = swag.Name
}
searchDirs := strings.Split(config.SearchDir, ",")
if !config.ParseGoPackages { // packages.Load support pattern like ./...
for _, searchDir := range searchDirs {
if _, err := os.Stat(searchDir); os.IsNotExist(err) {
return fmt.Errorf("dir: %s does not exist", searchDir)
}
}
}
if config.LeftTemplateDelim == "" {
config.LeftTemplateDelim = "{{"
}
if config.RightTemplateDelim == "" {
config.RightTemplateDelim = "}}"
}
var overrides map[string]string
if config.OverridesFile != "" {
overridesFile, err := open(config.OverridesFile)
if err != nil {
// Don't bother reporting if the default file is missing; assume there are no overrides
if !(config.OverridesFile == DefaultOverridesFile && os.IsNotExist(err)) {
return fmt.Errorf("could not open overrides file: %w", err)
}
} else {
g.debug.Printf("Using overrides from %s", config.OverridesFile)
overrides, err = parseOverrides(overridesFile)
if err != nil {
return err
}
}
}
g.debug.Printf("Generate swagger docs....")
p := swag.New(
swag.SetParseDependency(config.ParseDependency),
swag.SetUseStructName(config.UseStructNames),
swag.SetMarkdownFileDirectory(config.MarkdownFilesDir),
swag.SetDebugger(config.Debugger),
swag.SetExcludedDirsAndFiles(config.Excludes),
swag.SetParseExtension(config.ParseExtension),
swag.SetCodeExamplesDirectory(config.CodeExampleFilesDir),
swag.SetStrict(config.Strict),
swag.SetOverrides(overrides),
swag.ParseUsingGoList(config.ParseGoList),
swag.SetTags(config.Tags),
swag.SetCollectionFormat(config.CollectionFormat),
swag.SetPackagePrefix(config.PackagePrefix),
)
p.PropNamingStrategy = config.PropNamingStrategy
p.ParseVendor = config.ParseVendor
p.ParseInternal = config.ParseInternal
p.RequiredByDefault = config.RequiredByDefault
p.HostState = config.State
p.ParseFuncBody = config.ParseFuncBody
p.ParseGoPackages = config.ParseGoPackages
if err := p.ParseAPIMultiSearchDir(searchDirs, config.MainAPIFile, config.ParseDepth); err != nil {
return err
}
swagger := p.GetSwagger()
if err := os.MkdirAll(config.OutputDir, os.ModePerm); err != nil {
return err
}
for _, outputType := range config.OutputTypes {
outputType = strings.ToLower(strings.TrimSpace(outputType))
if typeWriter, ok := g.outputTypeMap[outputType]; ok {
if err := typeWriter(config, swagger); err != nil {
return err
}
} else {
log.Printf("output type '%s' not supported", outputType)
}
}
return nil
}
func (g *Gen) writeDocSwagger(config *Config, swagger *spec.Swagger) error {
var filename = "docs.go"
if config.State != "" {
filename = config.State + "_" + filename
}
if config.InstanceName != swag.Name {
filename = config.InstanceName + "_" + filename
}
docFileName := path.Join(config.OutputDir, filename)
absOutputDir, err := filepath.Abs(config.OutputDir)
if err != nil {
return err
}
var packageName string
if len(config.PackageName) > 0 {
packageName = config.PackageName
} else {
packageName = filepath.Base(absOutputDir)
packageName = strings.ReplaceAll(packageName, "-", "_")
}
docs, err := os.Create(docFileName)
if err != nil {
return err
}
defer docs.Close()
// Write doc
err = g.writeGoDoc(packageName, docs, swagger, config)
if err != nil {
return err
}
g.debug.Printf("create docs.go at %+v", docFileName)
return nil
}
func (g *Gen) writeJSONSwagger(config *Config, swagger *spec.Swagger) error {
var filename = "swagger.json"
if config.State != "" {
filename = config.State + "_" + filename
}
if config.InstanceName != swag.Name {
filename = config.InstanceName + "_" + filename
}
jsonFileName := path.Join(config.OutputDir, filename)
b, err := g.jsonIndent(swagger)
if err != nil {
return err
}
err = g.writeFile(b, jsonFileName)
if err != nil {
return err
}
g.debug.Printf("create swagger.json at %+v", jsonFileName)
return nil
}
func (g *Gen) writeYAMLSwagger(config *Config, swagger *spec.Swagger) error {
var filename = "swagger.yaml"
if config.State != "" {
filename = config.State + "_" + filename
}
if config.InstanceName != swag.Name {
filename = config.InstanceName + "_" + filename
}
yamlFileName := path.Join(config.OutputDir, filename)
b, err := g.json(swagger)
if err != nil {
return err
}
y, err := g.jsonToYAML(b)
if err != nil {
return fmt.Errorf("cannot covert json to yaml error: %s", err)
}
err = g.writeFile(y, yamlFileName)
if err != nil {
return err
}
g.debug.Printf("create swagger.yaml at %+v", yamlFileName)
return nil
}
func (g *Gen) writeFile(b []byte, file string) error {
f, err := os.Create(file)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(b)
return err
}
func (g *Gen) formatSource(src []byte) []byte {
code, err := format.Source(src)
if err != nil {
code = src // Formatter failed, return original code.
}
return code
}
// Read and parse the overrides file.
func parseOverrides(r io.Reader) (map[string]string, error) {
overrides := make(map[string]string)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
// Skip comments
if len(line) > 1 && line[0:2] == "//" {
continue
}
parts := strings.Fields(line)
switch len(parts) {
case 0:
// only whitespace
continue
case 2:
// either a skip or malformed
if parts[0] != "skip" {
return nil, fmt.Errorf("could not parse override: '%s'", line)
}
overrides[parts[1]] = ""
case 3:
// either a replace or malformed
if parts[0] != "replace" {
return nil, fmt.Errorf("could not parse override: '%s'", line)
}
overrides[parts[1]] = parts[2]
default:
return nil, fmt.Errorf("could not parse override: '%s'", line)
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error reading overrides file: %w", err)
}
return overrides, nil
}
func (g *Gen) writeGoDoc(packageName string, output io.Writer, swagger *spec.Swagger, config *Config) error {
generator, err := template.New("swagger_info").Funcs(template.FuncMap{
"printDoc": func(v string) string {
// Add schemes
v = "{\n \"schemes\": " + config.LeftTemplateDelim + " marshal .Schemes " + config.RightTemplateDelim + "," + v[1:]
// Sanitize backticks
return strings.Replace(v, "`", "`+\"`\"+`", -1)
},
}).Parse(packageTemplate)
if err != nil {
return err
}
swaggerSpec := &spec.Swagger{
VendorExtensible: swagger.VendorExtensible,
SwaggerProps: spec.SwaggerProps{
ID: swagger.ID,
Consumes: swagger.Consumes,
Produces: swagger.Produces,
Swagger: swagger.Swagger,
Info: &spec.Info{
VendorExtensible: swagger.Info.VendorExtensible,
InfoProps: spec.InfoProps{
Description: config.LeftTemplateDelim + "escape .Description" + config.RightTemplateDelim,
Title: config.LeftTemplateDelim + ".Title" + config.RightTemplateDelim,
TermsOfService: swagger.Info.TermsOfService,
Contact: swagger.Info.Contact,
License: swagger.Info.License,
Version: config.LeftTemplateDelim + ".Version" + config.RightTemplateDelim,
},
},
Host: config.LeftTemplateDelim + ".Host" + config.RightTemplateDelim,
BasePath: config.LeftTemplateDelim + ".BasePath" + config.RightTemplateDelim,
Paths: swagger.Paths,
Definitions: swagger.Definitions,
Parameters: swagger.Parameters,
Responses: swagger.Responses,
SecurityDefinitions: swagger.SecurityDefinitions,
Security: swagger.Security,
Tags: swagger.Tags,
ExternalDocs: swagger.ExternalDocs,
},
}
// crafted docs.json
buf, err := g.jsonIndent(swaggerSpec)
if err != nil {
return err
}
state := ""
if len(config.State) > 0 {
state = cases.Title(language.English).String(strings.ToLower(config.State))
}
buffer := &bytes.Buffer{}
err = generator.Execute(buffer, struct {
Timestamp time.Time
Doc string
Host string
PackageName string
BasePath string
Title string
Description string
Version string
State string
InstanceName string
Schemes []string
GeneratedTime bool
LeftTemplateDelim string
RightTemplateDelim string
}{
Timestamp: time.Now(),
GeneratedTime: config.GeneratedTime,
Doc: string(buf),
Host: swagger.Host,
PackageName: packageName,
BasePath: swagger.BasePath,
Schemes: swagger.Schemes,
Title: swagger.Info.Title,
Description: swagger.Info.Description,
Version: swagger.Info.Version,
State: state,
InstanceName: config.InstanceName,
LeftTemplateDelim: config.LeftTemplateDelim,
RightTemplateDelim: config.RightTemplateDelim,
})
if err != nil {
return err
}
code := g.formatSource(buffer.Bytes())
// write
_, err = output.Write(code)
return err
}
var packageTemplate = `// Package {{.PackageName}} Code generated by swaggo/swag{{ if .GeneratedTime }} at {{ .Timestamp }}{{ end }}. DO NOT EDIT
package {{.PackageName}}
import "github.com/swaggo/swag"
const docTemplate{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }}{{ .State }} = ` + "`{{ printDoc .Doc}}`" + `
// Swagger{{ .State }}Info{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }} holds exported Swagger Info so clients can modify it
var Swagger{{ .State }}Info{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }} = &swag.Spec{
Version: {{ printf "%q" .Version}},
Host: {{ printf "%q" .Host}},
BasePath: {{ printf "%q" .BasePath}},
Schemes: []string{ {{ range $index, $schema := .Schemes}}{{if gt $index 0}},{{end}}{{printf "%q" $schema}}{{end}} },
Title: {{ printf "%q" .Title}},
Description: {{ printf "%q" .Description}},
InfoInstanceName: {{ printf "%q" .InstanceName }},
SwaggerTemplate: docTemplate{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }}{{ .State }},
LeftDelim: {{ printf "%q" .LeftTemplateDelim}},
RightDelim: {{ printf "%q" .RightTemplateDelim}},
}
func init() {
swag.Register(Swagger{{ .State }}Info{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }}.InstanceName(), Swagger{{ .State }}Info{{ if ne .InstanceName "swagger" }}{{ .InstanceName }} {{- end }})
}
`
================================================
FILE: gen/gen_test.go
================================================
package gen
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"plugin"
"strings"
"testing"
"github.com/go-openapi/spec"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/swaggo/swag"
)
const searchDir = "../testdata/simple"
var outputTypes = []string{"go", "json", "yaml"}
func TestGen_Build(t *testing.T) {
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple/docs",
OutputTypes: outputTypes,
PropNamingStrategy: "",
}
assert.NoError(t, New().Build(config))
expectedFiles := []string{
filepath.Join(config.OutputDir, "docs.go"),
filepath.Join(config.OutputDir, "swagger.json"),
filepath.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
_ = os.Remove(expectedFile)
}
}
func TestGen_SpecificOutputTypes(t *testing.T) {
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple/docs",
OutputTypes: []string{"go", "unknownType"},
PropNamingStrategy: "",
}
assert.NoError(t, New().Build(config))
tt := []struct {
expectedFile string
shouldExist bool
}{
{filepath.Join(config.OutputDir, "docs.go"), true},
{filepath.Join(config.OutputDir, "swagger.json"), false},
{filepath.Join(config.OutputDir, "swagger.yaml"), false},
}
for _, tc := range tt {
_, err := os.Stat(tc.expectedFile)
if tc.shouldExist {
if os.IsNotExist(err) {
require.NoError(t, err)
}
} else {
require.Error(t, err)
require.True(t, errors.Is(err, os.ErrNotExist))
}
_ = os.Remove(tc.expectedFile)
}
}
func TestGen_BuildInstanceName(t *testing.T) {
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple/docs",
OutputTypes: outputTypes,
PropNamingStrategy: "",
}
assert.NoError(t, New().Build(config))
goSourceFile := filepath.Join(config.OutputDir, "docs.go")
// Validate default registration name
expectedCode, err := os.ReadFile(goSourceFile)
if err != nil {
require.NoError(t, err)
}
if !strings.Contains(
string(expectedCode),
"swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)",
) {
t.Fatal(errors.New("generated go code does not contain the correct default registration sequence"))
}
if !strings.Contains(
string(expectedCode),
"var SwaggerInfo =",
) {
t.Fatal(errors.New("generated go code does not contain the correct default variable declaration"))
}
// Custom name
config.InstanceName = "Custom"
goSourceFile = filepath.Join(config.OutputDir, config.InstanceName+"_"+"docs.go")
assert.NoError(t, New().Build(config))
expectedCode, err = os.ReadFile(goSourceFile)
if err != nil {
require.NoError(t, err)
}
if !strings.Contains(
string(expectedCode),
"swag.Register(SwaggerInfoCustom.InstanceName(), SwaggerInfoCustom)",
) {
t.Fatal(errors.New("generated go code does not contain the correct registration sequence"))
}
if !strings.Contains(
string(expectedCode),
"var SwaggerInfoCustom =",
) {
t.Fatal(errors.New("generated go code does not contain the correct variable declaration"))
}
// cleanup
expectedFiles := []string{
filepath.Join(config.OutputDir, config.InstanceName+"_"+"docs.go"),
filepath.Join(config.OutputDir, config.InstanceName+"_"+"swagger.json"),
filepath.Join(config.OutputDir, config.InstanceName+"_"+"swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
_ = os.Remove(expectedFile)
}
}
func TestGen_BuildSnakeCase(t *testing.T) {
config := &Config{
SearchDir: "../testdata/simple2",
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple2/docs",
OutputTypes: outputTypes,
PropNamingStrategy: swag.SnakeCase,
}
assert.NoError(t, New().Build(config))
expectedFiles := []string{
filepath.Join(config.OutputDir, "docs.go"),
filepath.Join(config.OutputDir, "swagger.json"),
filepath.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
_ = os.Remove(expectedFile)
}
}
func TestGen_BuildLowerCamelcase(t *testing.T) {
config := &Config{
SearchDir: "../testdata/simple3",
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple3/docs",
OutputTypes: outputTypes,
PropNamingStrategy: "",
}
assert.NoError(t, New().Build(config))
expectedFiles := []string{
filepath.Join(config.OutputDir, "docs.go"),
filepath.Join(config.OutputDir, "swagger.json"),
filepath.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
_ = os.Remove(expectedFile)
}
}
func TestGen_BuildDescriptionWithQuotes(t *testing.T) {
config := &Config{
SearchDir: "../testdata/quotes",
MainAPIFile: "./main.go",
OutputDir: "../testdata/quotes/docs",
OutputTypes: outputTypes,
MarkdownFilesDir: "../testdata/quotes",
}
require.NoError(t, New().Build(config))
expectedFiles := []string{
filepath.Join(config.OutputDir, "docs.go"),
filepath.Join(config.OutputDir, "swagger.json"),
filepath.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
}
cmd := exec.Command("go", "build", "-buildmode=plugin", "github.com/swaggo/swag/testdata/quotes")
cmd.Dir = config.SearchDir
output, err := cmd.CombinedOutput()
if err != nil {
require.NoError(t, err, string(output))
}
p, err := plugin.Open(filepath.Join(config.SearchDir, "quotes.so"))
if err != nil {
require.NoError(t, err)
}
defer os.Remove("quotes.so")
readDoc, err := p.Lookup("ReadDoc")
if err != nil {
require.NoError(t, err)
}
jsonOutput := readDoc.(func() string)()
var jsonDoc interface{}
if err := json.Unmarshal([]byte(jsonOutput), &jsonDoc); err != nil {
require.NoError(t, err)
}
expectedJSON, err := os.ReadFile(filepath.Join(config.SearchDir, "expected.json"))
if err != nil {
require.NoError(t, err)
}
assert.JSONEq(t, string(expectedJSON), jsonOutput)
}
func TestGen_BuildDocCustomDelims(t *testing.T) {
config := &Config{
SearchDir: "../testdata/delims",
MainAPIFile: "./main.go",
OutputDir: "../testdata/delims/docs",
OutputTypes: outputTypes,
MarkdownFilesDir: "../testdata/delims",
InstanceName: "CustomDelims",
LeftTemplateDelim: "{%",
RightTemplateDelim: "%}",
}
require.NoError(t, New().Build(config))
expectedFiles := []string{
filepath.Join(config.OutputDir, "CustomDelims_docs.go"),
filepath.Join(config.OutputDir, "CustomDelims_swagger.json"),
filepath.Join(config.OutputDir, "CustomDelims_swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
}
cmd := exec.Command("go", "build", "-buildmode=plugin", "github.com/swaggo/swag/testdata/delims")
cmd.Dir = config.SearchDir
output, err := cmd.CombinedOutput()
if err != nil {
require.NoError(t, err, string(output))
}
p, err := plugin.Open(filepath.Join(config.SearchDir, "delims.so"))
if err != nil {
require.NoError(t, err)
}
defer os.Remove("delims.so")
readDoc, err := p.Lookup("ReadDoc")
if err != nil {
require.NoError(t, err)
}
jsonOutput := readDoc.(func() string)()
var jsonDoc interface{}
if err := json.Unmarshal([]byte(jsonOutput), &jsonDoc); err != nil {
require.NoError(t, err)
}
expectedJSON, err := os.ReadFile(filepath.Join(config.SearchDir, "expected.json"))
if err != nil {
require.NoError(t, err)
}
assert.JSONEq(t, string(expectedJSON), jsonOutput)
}
func TestGen_jsonIndent(t *testing.T) {
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple/docs",
OutputTypes: outputTypes,
PropNamingStrategy: "",
}
gen := New()
gen.jsonIndent = func(data interface{}) ([]byte, error) {
return nil, errors.New("fail")
}
assert.Error(t, gen.Build(config))
}
func TestGen_jsonToYAML(t *testing.T) {
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple/docs",
OutputTypes: outputTypes,
PropNamingStrategy: "",
}
gen := New()
gen.jsonToYAML = func(data []byte) ([]byte, error) {
return nil, errors.New("fail")
}
assert.Error(t, gen.Build(config))
expectedFiles := []string{
filepath.Join(config.OutputDir, "docs.go"),
filepath.Join(config.OutputDir, "swagger.json"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
_ = os.Remove(expectedFile)
}
}
func TestGen_SearchDirIsNotExist(t *testing.T) {
var swaggerConfDir, propNamingStrategy string
config := &Config{
SearchDir: "../isNotExistDir",
MainAPIFile: "./main.go",
OutputDir: swaggerConfDir,
OutputTypes: outputTypes,
PropNamingStrategy: propNamingStrategy,
}
assert.EqualError(t, New().Build(config), "dir: ../isNotExistDir does not exist")
}
func TestGen_MainAPiNotExist(t *testing.T) {
var swaggerConfDir, propNamingStrategy string
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./notExists.go",
OutputDir: swaggerConfDir,
OutputTypes: outputTypes,
PropNamingStrategy: propNamingStrategy,
}
assert.Error(t, New().Build(config))
}
func TestGen_OutputIsNotExist(t *testing.T) {
var propNamingStrategy string
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./main.go",
OutputDir: "/dev/null",
OutputTypes: outputTypes,
PropNamingStrategy: propNamingStrategy,
}
assert.Error(t, New().Build(config))
}
func TestGen_FailToWrite(t *testing.T) {
outputDir := filepath.Join(os.TempDir(), "swagg", "test")
outputTypes := []string{"go", "json", "yaml"}
var propNamingStrategy string
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./main.go",
OutputDir: outputDir,
OutputTypes: outputTypes,
PropNamingStrategy: propNamingStrategy,
}
err := os.MkdirAll(outputDir, 0755)
if err != nil {
require.NoError(t, err)
}
_ = os.RemoveAll(filepath.Join(outputDir, "swagger.yaml"))
err = os.Mkdir(filepath.Join(outputDir, "swagger.yaml"), 0755)
if err != nil {
require.NoError(t, err)
}
assert.Error(t, New().Build(config))
_ = os.RemoveAll(filepath.Join(outputDir, "swagger.json"))
err = os.Mkdir(filepath.Join(outputDir, "swagger.json"), 0755)
if err != nil {
require.NoError(t, err)
}
assert.Error(t, New().Build(config))
_ = os.RemoveAll(filepath.Join(outputDir, "docs.go"))
err = os.Mkdir(filepath.Join(outputDir, "docs.go"), 0755)
if err != nil {
require.NoError(t, err)
}
assert.Error(t, New().Build(config))
err = os.RemoveAll(outputDir)
if err != nil {
require.NoError(t, err)
}
}
func TestGen_configWithOutputDir(t *testing.T) {
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple/docs",
OutputTypes: outputTypes,
PropNamingStrategy: "",
}
assert.NoError(t, New().Build(config))
expectedFiles := []string{
filepath.Join(config.OutputDir, "docs.go"),
filepath.Join(config.OutputDir, "swagger.json"),
filepath.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
_ = os.Remove(expectedFile)
}
}
func TestGen_configWithOutputTypesAll(t *testing.T) {
searchDir := "../testdata/simple"
outputTypes := []string{"go", "json", "yaml"}
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple/docs",
OutputTypes: outputTypes,
PropNamingStrategy: "",
}
assert.NoError(t, New().Build(config))
expectedFiles := []string{
path.Join(config.OutputDir, "docs.go"),
path.Join(config.OutputDir, "swagger.json"),
path.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
t.Fatal(err)
}
_ = os.Remove(expectedFile)
}
}
func TestGen_configWithOutputTypesSingle(t *testing.T) {
searchDir := "../testdata/simple"
outputTypes := []string{"go", "json", "yaml"}
for _, outputType := range outputTypes {
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple/docs",
OutputTypes: []string{outputType},
PropNamingStrategy: "",
}
assert.NoError(t, New().Build(config))
outFileName := "swagger"
if outputType == "go" {
outFileName = "docs"
}
expectedFiles := []string{
path.Join(config.OutputDir, outFileName+"."+outputType),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
t.Fatal(err)
}
_ = os.Remove(expectedFile)
}
}
}
func TestGen_formatSource(t *testing.T) {
src := `package main
import "net
func main() {}
`
g := New()
res := g.formatSource([]byte(src))
assert.Equal(t, []byte(src), res, "Should return same content due to fmt fail")
src2 := `package main
import "fmt"
func main() {
fmt.Print("Hello world")
}
`
res = g.formatSource([]byte(src2))
assert.NotEqual(t, []byte(src2), res, "Should return fmt code")
}
type mockWriter struct {
hook func([]byte)
}
func (w *mockWriter) Write(data []byte) (int, error) {
if w.hook != nil {
w.hook(data)
}
return len(data), nil
}
func TestGen_writeGoDoc(t *testing.T) {
gen := New()
swapTemplate := packageTemplate
packageTemplate = `{{{`
err := gen.writeGoDoc("docs", nil, nil, &Config{})
assert.Error(t, err)
packageTemplate = `{{.Data}}`
swagger := &spec.Swagger{
VendorExtensible: spec.VendorExtensible{},
SwaggerProps: spec.SwaggerProps{
Info: &spec.Info{},
},
}
err = gen.writeGoDoc("docs", &mockWriter{}, swagger, &Config{})
assert.Error(t, err)
packageTemplate = `{{ if .GeneratedTime }}Fake Time{{ end }}`
err = gen.writeGoDoc("docs",
&mockWriter{
hook: func(data []byte) {
assert.Equal(t, "Fake Time", string(data))
},
}, swagger, &Config{GeneratedTime: true})
assert.NoError(t, err)
err = gen.writeGoDoc("docs",
&mockWriter{
hook: func(data []byte) {
assert.Equal(t, "", string(data))
},
}, swagger, &Config{GeneratedTime: false})
assert.NoError(t, err)
packageTemplate = swapTemplate
}
func TestGen_GeneratedDoc(t *testing.T) {
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple/docs",
OutputTypes: outputTypes,
PropNamingStrategy: "",
}
assert.NoError(t, New().Build(config))
goCMD, err := exec.LookPath("go")
assert.NoError(t, err)
cmd := exec.Command(goCMD, "build", filepath.Join(config.OutputDir, "docs.go"))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
assert.NoError(t, cmd.Run())
expectedFiles := []string{
filepath.Join(config.OutputDir, "docs.go"),
filepath.Join(config.OutputDir, "swagger.json"),
filepath.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
_ = os.Remove(expectedFile)
}
}
func TestGen_cgoImports(t *testing.T) {
config := &Config{
SearchDir: "../testdata/simple_cgo",
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple_cgo/docs",
OutputTypes: outputTypes,
PropNamingStrategy: "",
ParseDependency: 1,
}
assert.NoError(t, New().Build(config))
expectedFiles := []string{
filepath.Join(config.OutputDir, "docs.go"),
filepath.Join(config.OutputDir, "swagger.json"),
filepath.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
_ = os.Remove(expectedFile)
}
}
func TestGen_parseOverrides(t *testing.T) {
testCases := []struct {
Name string
Data string
Expected map[string]string
ExpectedError error
}{
{
Name: "replace",
Data: `replace github.com/foo/bar baz`,
Expected: map[string]string{
"github.com/foo/bar": "baz",
},
},
{
Name: "skip",
Data: `skip github.com/foo/bar`,
Expected: map[string]string{
"github.com/foo/bar": "",
},
},
{
Name: "generic-simple",
Data: `replace types.Field[string] string`,
Expected: map[string]string{
"types.Field[string]": "string",
},
},
{
Name: "generic-double",
Data: `replace types.Field[string,string] string`,
Expected: map[string]string{
"types.Field[string,string]": "string",
},
},
{
Name: "comment",
Data: `// this is a comment
replace foo bar`,
Expected: map[string]string{
"foo": "bar",
},
},
{
Name: "ignore whitespace",
Data: `
replace foo bar`,
Expected: map[string]string{
"foo": "bar",
},
},
{
Name: "unknown directive",
Data: `foo`,
ExpectedError: fmt.Errorf("could not parse override: 'foo'"),
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
overrides, err := parseOverrides(strings.NewReader(tc.Data))
assert.Equal(t, tc.Expected, overrides)
assert.Equal(t, tc.ExpectedError, err)
})
}
}
func TestGen_TypeOverridesFile(t *testing.T) {
customPath := "/foo/bar/baz"
tmp, err := os.CreateTemp("", "")
require.NoError(t, err)
defer os.Remove(tmp.Name())
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple/docs",
PropNamingStrategy: "",
}
t.Run("Default file is missing", func(t *testing.T) {
open = func(path string) (*os.File, error) {
assert.Equal(t, DefaultOverridesFile, path)
return nil, os.ErrNotExist
}
defer func() {
open = os.Open
}()
config.OverridesFile = DefaultOverridesFile
err := New().Build(config)
assert.NoError(t, err)
})
t.Run("Default file is present", func(t *testing.T) {
open = func(path string) (*os.File, error) {
assert.Equal(t, DefaultOverridesFile, path)
return tmp, nil
}
defer func() {
open = os.Open
}()
config.OverridesFile = DefaultOverridesFile
err := New().Build(config)
assert.NoError(t, err)
})
t.Run("Different file is missing", func(t *testing.T) {
open = func(path string) (*os.File, error) {
assert.Equal(t, customPath, path)
return nil, os.ErrNotExist
}
defer func() {
open = os.Open
}()
config.OverridesFile = customPath
err := New().Build(config)
assert.EqualError(t, err, "could not open overrides file: file does not exist")
})
t.Run("Different file is present", func(t *testing.T) {
open = func(path string) (*os.File, error) {
assert.Equal(t, customPath, path)
return tmp, nil
}
defer func() {
open = os.Open
}()
config.OverridesFile = customPath
err := New().Build(config)
assert.NoError(t, err)
})
}
func TestGen_Debugger(t *testing.T) {
var buf bytes.Buffer
config := &Config{
SearchDir: searchDir,
MainAPIFile: "./main.go",
OutputDir: "../testdata/simple/docs",
OutputTypes: outputTypes,
PropNamingStrategy: "",
Debugger: log.New(&buf, "", log.LstdFlags),
}
assert.True(t, buf.Len() == 0)
assert.NoError(t, New().Build(config))
assert.True(t, buf.Len() > 0)
expectedFiles := []string{
filepath.Join(config.OutputDir, "docs.go"),
filepath.Join(config.OutputDir, "swagger.json"),
filepath.Join(config.OutputDir, "swagger.yaml"),
}
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
_ = os.Remove(expectedFile)
}
}
func TestGen_ErrorAndInterface(t *testing.T) {
config := &Config{
SearchDir: "../testdata/error",
MainAPIFile: "./main.go",
OutputDir: "../testdata/error/docs",
OutputTypes: outputTypes,
PropNamingStrategy: "",
}
assert.NoError(t, New().Build(config))
expectedFiles := []string{
filepath.Join(config.OutputDir, "docs.go"),
filepath.Join(config.OutputDir, "swagger.json"),
filepath.Join(config.OutputDir, "swagger.yaml"),
}
t.Cleanup(func() {
for _, expectedFile := range expectedFiles {
_ = os.Remove(expectedFile)
}
})
// check files
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
}
// check content
jsonOutput, err := os.ReadFile(filepath.Join(config.OutputDir, "swagger.json"))
if err != nil {
require.NoError(t, err)
}
expectedJSON, err := os.ReadFile(filepath.Join(config.SearchDir, "expected.json"))
if err != nil {
require.NoError(t, err)
}
assert.JSONEq(t, string(expectedJSON), string(jsonOutput))
}
func TestGen_StateAdmin(t *testing.T) {
config := &Config{
SearchDir: "../testdata/state",
MainAPIFile: "./main.go",
OutputDir: "../testdata/state/docs",
OutputTypes: outputTypes,
PropNamingStrategy: "",
State: "admin",
}
assert.NoError(t, New().Build(config))
expectedFiles := []string{
filepath.Join(config.OutputDir, "admin_docs.go"),
filepath.Join(config.OutputDir, "admin_swagger.json"),
filepath.Join(config.OutputDir, "admin_swagger.yaml"),
}
t.Cleanup(func() {
for _, expectedFile := range expectedFiles {
_ = os.Remove(expectedFile)
}
})
// check files
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
}
// check content
jsonOutput, err := os.ReadFile(filepath.Join(config.OutputDir, "admin_swagger.json"))
require.NoError(t, err)
expectedJSON, err := os.ReadFile(filepath.Join(config.SearchDir, "admin_expected.json"))
require.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(jsonOutput))
}
func TestGen_StateUser(t *testing.T) {
config := &Config{
SearchDir: "../testdata/state",
MainAPIFile: "./main.go",
OutputDir: "../testdata/state/docs",
OutputTypes: outputTypes,
PropNamingStrategy: "",
State: "user",
}
assert.NoError(t, New().Build(config))
expectedFiles := []string{
filepath.Join(config.OutputDir, "user_docs.go"),
filepath.Join(config.OutputDir, "user_swagger.json"),
filepath.Join(config.OutputDir, "user_swagger.yaml"),
}
t.Cleanup(func() {
for _, expectedFile := range expectedFiles {
_ = os.Remove(expectedFile)
}
})
// check files
for _, expectedFile := range expectedFiles {
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
require.NoError(t, err)
}
}
// check content
jsonOutput, err := os.ReadFile(filepath.Join(config.OutputDir, "user_swagger.json"))
require.NoError(t, err)
expectedJSON, err := os.ReadFile(filepath.Join(config.SearchDir, "user_expected.json"))
require.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(jsonOutput))
}
================================================
FILE: generics.go
================================================
package swag
import (
"errors"
"fmt"
"go/ast"
"strings"
"unicode"
"github.com/go-openapi/spec"
)
type genericTypeSpec struct {
TypeSpec *TypeSpecDef
Name string
}
type formalParamType struct {
Name string
Type string
}
func (t *genericTypeSpec) TypeName() string {
if t.TypeSpec != nil {
return t.TypeSpec.TypeName()
}
return t.Name
}
func normalizeGenericTypeName(name string) string {
return strings.Replace(name, ".", "_", -1)
}
func (pkgDefs *PackagesDefinitions) getTypeFromGenericParam(genericParam string, file *ast.File) (typeSpecDef *TypeSpecDef) {
if strings.HasPrefix(genericParam, "[]") {
typeSpecDef = pkgDefs.getTypeFromGenericParam(genericParam[2:], file)
if typeSpecDef == nil {
return nil
}
var expr ast.Expr
switch typeSpecDef.TypeSpec.Type.(type) {
case *ast.ArrayType, *ast.MapType:
expr = typeSpecDef.TypeSpec.Type
default:
name := typeSpecDef.TypeName()
expr = ast.NewIdent(name)
if _, ok := pkgDefs.uniqueDefinitions[name]; !ok {
pkgDefs.uniqueDefinitions[name] = typeSpecDef
}
}
return &TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: ast.NewIdent(string(IgnoreNameOverridePrefix) + "array_" + typeSpecDef.TypeName()),
Type: &ast.ArrayType{
Elt: expr,
},
},
Enums: typeSpecDef.Enums,
PkgPath: typeSpecDef.PkgPath,
ParentSpec: typeSpecDef.ParentSpec,
SchemaName: "array_" + typeSpecDef.SchemaName,
NotUnique: false,
}
}
if strings.HasPrefix(genericParam, "map[") {
parts := strings.SplitN(genericParam[4:], "]", 2)
if len(parts) != 2 {
return nil
}
typeSpecDef = pkgDefs.getTypeFromGenericParam(parts[1], file)
if typeSpecDef == nil {
return nil
}
var expr ast.Expr
switch typeSpecDef.TypeSpec.Type.(type) {
case *ast.ArrayType, *ast.MapType:
expr = typeSpecDef.TypeSpec.Type
default:
name := typeSpecDef.TypeName()
expr = ast.NewIdent(name)
if _, ok := pkgDefs.uniqueDefinitions[name]; !ok {
pkgDefs.uniqueDefinitions[name] = typeSpecDef
}
}
return &TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: ast.NewIdent(string(IgnoreNameOverridePrefix) + "map_" + parts[0] + "_" + typeSpecDef.TypeName()),
Type: &ast.MapType{
Key: ast.NewIdent(parts[0]), //assume key is string or integer
Value: expr,
},
},
Enums: typeSpecDef.Enums,
PkgPath: typeSpecDef.PkgPath,
ParentSpec: typeSpecDef.ParentSpec,
SchemaName: "map_" + parts[0] + "_" + typeSpecDef.SchemaName,
NotUnique: false,
}
}
if IsGolangPrimitiveType(genericParam) {
return &TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: ast.NewIdent(genericParam),
Type: ast.NewIdent(genericParam),
},
SchemaName: genericParam,
}
}
return pkgDefs.FindTypeSpec(genericParam, file)
}
func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, original *TypeSpecDef, fullGenericForm string) *TypeSpecDef {
if original == nil || original.TypeSpec.TypeParams == nil || len(original.TypeSpec.TypeParams.List) == 0 {
return original
}
name, genericParams := splitGenericsTypeName(fullGenericForm)
if genericParams == nil {
return nil
}
//generic[x,y any,z any] considered, TODO what if the type is not `any`, but a concrete one, such as `int32|int64` or an certain interface{}
var formals []formalParamType
for _, field := range original.TypeSpec.TypeParams.List {
for _, ident := range field.Names {
formal := formalParamType{Name: ident.Name}
if ident, ok := field.Type.(*ast.Ident); ok {
formal.Type = ident.Name
}
formals = append(formals, formal)
}
}
if len(genericParams) != len(formals) {
return nil
}
genericParamTypeDefs := map[string]*genericTypeSpec{}
for i, genericParam := range genericParams {
var typeDef *TypeSpecDef
if !IsGolangPrimitiveType(genericParam) {
typeDef = pkgDefs.getTypeFromGenericParam(genericParam, file)
if typeDef != nil {
genericParam = typeDef.TypeName()
if _, ok := pkgDefs.uniqueDefinitions[genericParam]; !ok {
pkgDefs.uniqueDefinitions[genericParam] = typeDef
}
}
}
genericParamTypeDefs[formals[i].Name] = &genericTypeSpec{
TypeSpec: typeDef,
Name: genericParam,
}
}
name = fmt.Sprintf("%s%s-", string(IgnoreNameOverridePrefix), original.TypeName())
schemaName := fmt.Sprintf("%s-", original.SchemaName)
var nameParts []string
var schemaNameParts []string
for _, def := range formals {
if specDef, ok := genericParamTypeDefs[def.Name]; ok {
nameParts = append(nameParts, specDef.Name)
schemaNamePart := specDef.Name
if specDef.TypeSpec != nil {
schemaNamePart = specDef.TypeSpec.SchemaName
}
schemaNameParts = append(schemaNameParts, schemaNamePart)
}
}
name += normalizeGenericTypeName(strings.Join(nameParts, "-"))
schemaName += normalizeGenericTypeName(strings.Join(schemaNameParts, "-"))
if typeSpec, ok := pkgDefs.uniqueDefinitions[name]; ok {
return typeSpec
}
parametrizedTypeSpec := &TypeSpecDef{
File: original.File,
PkgPath: original.PkgPath,
Enums: original.Enums,
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{
Name: name,
NamePos: original.TypeSpec.Name.NamePos,
Obj: original.TypeSpec.Name.Obj,
},
Doc: original.TypeSpec.Doc,
Assign: original.TypeSpec.Assign,
},
SchemaName: schemaName,
}
pkgDefs.uniqueDefinitions[name] = parametrizedTypeSpec
parametrizedTypeSpec.TypeSpec.Type = pkgDefs.resolveGenericType(original.File, original.TypeSpec.Type, genericParamTypeDefs)
return parametrizedTypeSpec
}
// splitGenericsTypeName splits a generic struct name in his parts
func splitGenericsTypeName(fullGenericForm string) (string, []string) {
//remove all spaces character
fullGenericForm = strings.Map(func(r rune) rune {
if unicode.IsSpace(r) {
return -1
}
return r
}, fullGenericForm)
// split only at the first '[' and remove the last ']'
if fullGenericForm[len(fullGenericForm)-1] != ']' {
return "", nil
}
genericParams := strings.SplitN(fullGenericForm[:len(fullGenericForm)-1], "[", 2)
if len(genericParams) == 1 {
return "", nil
}
// generic type name
genericTypeName := genericParams[0]
depth := 0
genericParams = strings.FieldsFunc(genericParams[1], func(r rune) bool {
if r == '[' {
depth++
} else if r == ']' {
depth--
} else if r == ',' && depth == 0 {
return true
}
return false
})
if depth != 0 {
return "", nil
}
return genericTypeName, genericParams
}
func (pkgDefs *PackagesDefinitions) getParametrizedType(genTypeSpec *genericTypeSpec) ast.Expr {
if genTypeSpec.TypeSpec != nil && strings.Contains(genTypeSpec.Name, ".") {
parts := strings.SplitN(genTypeSpec.Name, ".", 2)
return &ast.SelectorExpr{
X: &ast.Ident{Name: parts[0]},
Sel: &ast.Ident{Name: parts[1]},
}
}
//a primitive type name or a type name in current package
return &ast.Ident{Name: genTypeSpec.Name}
}
func (pkgDefs *PackagesDefinitions) resolveGenericType(file *ast.File, expr ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) ast.Expr {
switch astExpr := expr.(type) {
case *ast.Ident:
if genTypeSpec, ok := genericParamTypeDefs[astExpr.Name]; ok {
return pkgDefs.getParametrizedType(genTypeSpec)
}
case *ast.ArrayType:
return &ast.ArrayType{
Elt: pkgDefs.resolveGenericType(file, astExpr.Elt, genericParamTypeDefs),
Len: astExpr.Len,
Lbrack: astExpr.Lbrack,
}
case *ast.MapType:
return &ast.MapType{
Map: astExpr.Map,
Key: pkgDefs.resolveGenericType(file, astExpr.Key, genericParamTypeDefs),
Value: pkgDefs.resolveGenericType(file, astExpr.Value, genericParamTypeDefs),
}
case *ast.StarExpr:
return &ast.StarExpr{
Star: astExpr.Star,
X: pkgDefs.resolveGenericType(file, astExpr.X, genericParamTypeDefs),
}
case *ast.IndexExpr, *ast.IndexListExpr:
fullGenericName, _ := getGenericFieldType(file, expr, genericParamTypeDefs)
typeDef := pkgDefs.FindTypeSpec(fullGenericName, file)
if typeDef != nil {
return typeDef.TypeSpec.Name
}
case *ast.StructType:
newStructTypeDef := &ast.StructType{
Struct: astExpr.Struct,
Incomplete: astExpr.Incomplete,
Fields: &ast.FieldList{
Opening: astExpr.Fields.Opening,
Closing: astExpr.Fields.Closing,
},
}
for _, field := range astExpr.Fields.List {
newField := &ast.Field{
Type: field.Type,
Doc: field.Doc,
Names: field.Names,
Tag: field.Tag,
Comment: field.Comment,
}
newField.Type = pkgDefs.resolveGenericType(file, field.Type, genericParamTypeDefs)
newStructTypeDef.Fields.List = append(newStructTypeDef.Fields.List, newField)
}
return newStructTypeDef
}
return expr
}
func getExtendedGenericFieldType(file *ast.File, field ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) (string, error) {
switch fieldType := field.(type) {
case *ast.ArrayType:
fieldName, err := getExtendedGenericFieldType(file, fieldType.Elt, genericParamTypeDefs)
return "[]" + fieldName, err
case *ast.StarExpr:
return getExtendedGenericFieldType(file, fieldType.X, genericParamTypeDefs)
case *ast.Ident:
if genericParamTypeDefs != nil {
if typeSpec, ok := genericParamTypeDefs[fieldType.Name]; ok {
return typeSpec.Name, nil
}
}
if fieldType.Obj == nil {
return fieldType.Name, nil
}
tSpec := &TypeSpecDef{
File: file,
TypeSpec: fieldType.Obj.Decl.(*ast.TypeSpec),
PkgPath: file.Name.Name,
}
return tSpec.TypeName(), nil
default:
return getFieldType(file, field, genericParamTypeDefs)
}
}
func getGenericFieldType(file *ast.File, field ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) (string, error) {
var fullName string
var baseName string
var err error
switch fieldType := field.(type) {
case *ast.IndexListExpr:
baseName, err = getGenericTypeName(file, fieldType.X)
if err != nil {
return "", err
}
fullName = baseName + "["
for _, index := range fieldType.Indices {
fieldName, err := getExtendedGenericFieldType(file, index, genericParamTypeDefs)
if err != nil {
return "", err
}
fullName += fieldName + ","
}
fullName = strings.TrimRight(fullName, ",") + "]"
case *ast.IndexExpr:
baseName, err = getGenericTypeName(file, fieldType.X)
if err != nil {
return "", err
}
indexName, err := getExtendedGenericFieldType(file, fieldType.Index, genericParamTypeDefs)
if err != nil {
return "", err
}
fullName = fmt.Sprintf("%s[%s]", baseName, indexName)
}
if fullName == "" {
return "", fmt.Errorf("unknown field type %#v", field)
}
var packageName string
if !strings.Contains(baseName, ".") {
if file.Name == nil {
return "", errors.New("file name is nil")
}
packageName, _ = getFieldType(file, file.Name, genericParamTypeDefs)
}
return strings.TrimLeft(fmt.Sprintf("%s.%s", packageName, fullName), "."), nil
}
func getGenericTypeName(file *ast.File, field ast.Expr) (string, error) {
switch fieldType := field.(type) {
case *ast.Ident:
if fieldType.Obj == nil {
return fieldType.Name, nil
}
tSpec := &TypeSpecDef{
File: file,
TypeSpec: fieldType.Obj.Decl.(*ast.TypeSpec),
PkgPath: file.Name.Name,
}
return tSpec.TypeName(), nil
case *ast.ArrayType:
tSpec := &TypeSpecDef{
File: file,
TypeSpec: fieldType.Elt.(*ast.Ident).Obj.Decl.(*ast.TypeSpec),
PkgPath: file.Name.Name,
}
return tSpec.TypeName(), nil
case *ast.SelectorExpr:
return fmt.Sprintf("%s.%s", fieldType.X.(*ast.Ident).Name, fieldType.Sel.Name), nil
}
return "", fmt.Errorf("unknown type %#v", field)
}
func (parser *Parser) parseGenericTypeExpr(file *ast.File, typeExpr ast.Expr) (*spec.Schema, error) {
switch expr := typeExpr.(type) {
// suppress debug messages for these types
case *ast.InterfaceType:
case *ast.StructType:
case *ast.Ident:
case *ast.StarExpr:
case *ast.SelectorExpr:
case *ast.ArrayType:
case *ast.MapType:
case *ast.FuncType:
case *ast.IndexExpr, *ast.IndexListExpr:
name, err := getExtendedGenericFieldType(file, expr, nil)
if err == nil {
if schema, err := parser.getTypeSchema(name, file, false); err == nil {
return schema, nil
}
}
parser.debug.Printf("Type definition of type '%T' is not supported yet. Using 'object' instead. (%s)\n", typeExpr, err)
default:
parser.debug.Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeExpr)
}
return PrimitiveSchema(OBJECT), nil
}
================================================
FILE: generics_test.go
================================================
package swag
import (
"encoding/json"
"fmt"
"go/ast"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
type testLogger struct {
Messages []string
}
func (t *testLogger) Printf(format string, v ...interface{}) {
t.Messages = append(t.Messages, fmt.Sprintf(format, v...))
}
func TestParseGenericsBasic(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_basic"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
p.Overrides = map[string]string{
"types.Field[string]": "string",
"types.DoubleField[string,string]": "[]string",
"types.TrippleField[string,string]": "[][]string",
}
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsArrays(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_arrays"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsNested(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_nested"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsMultiLevelNesting(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_multi_level_nesting"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsProperty(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_property"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsNames(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_names"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsPackageAlias(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_package_alias/internal"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New(SetParseDependency(1))
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGenericsFunctionScoped(t *testing.T) {
t.Parallel()
searchDir := "testdata/generics_function_scoped"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParametrizeStruct(t *testing.T) {
pd := PackagesDefinitions{
packages: make(map[string]*PackageDefinitions),
uniqueDefinitions: make(map[string]*TypeSpecDef),
}
// valid
typeSpec := pd.parametrizeGenericType(
&ast.File{Name: &ast.Ident{Name: "test2"}},
&TypeSpecDef{
File: &ast.File{Name: &ast.Ident{Name: "test"}},
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}, {Names: []*ast.Ident{{Name: "T2"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
}}, "test.Field[string, []string]")
assert.NotNil(t, typeSpec)
assert.Equal(t, "$test.Field-string-array_string", typeSpec.Name())
assert.Equal(t, "test.Field-string-array_string", typeSpec.TypeName())
// definition contains one type params, but two type params are provided
typeSpec = pd.parametrizeGenericType(
&ast.File{Name: &ast.Ident{Name: "test2"}},
&TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
}}, "test.Field[string, string]")
assert.Nil(t, typeSpec)
// definition contains two type params, but only one is used
typeSpec = pd.parametrizeGenericType(
&ast.File{Name: &ast.Ident{Name: "test2"}},
&TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}, {Names: []*ast.Ident{{Name: "T2"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
}}, "test.Field[string]")
assert.Nil(t, typeSpec)
// name is not a valid type name
typeSpec = pd.parametrizeGenericType(
&ast.File{Name: &ast.Ident{Name: "test2"}},
&TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}, {Names: []*ast.Ident{{Name: "T2"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
}}, "test.Field[string")
assert.Nil(t, typeSpec)
typeSpec = pd.parametrizeGenericType(
&ast.File{Name: &ast.Ident{Name: "test2"}},
&TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}, {Names: []*ast.Ident{{Name: "T2"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
}}, "test.Field[string, [string]")
assert.Nil(t, typeSpec)
typeSpec = pd.parametrizeGenericType(
&ast.File{Name: &ast.Ident{Name: "test2"}},
&TypeSpecDef{
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}, {Names: []*ast.Ident{{Name: "T2"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
}}, "test.Field[string, ]string]")
assert.Nil(t, typeSpec)
}
func TestSplitGenericsTypeNames(t *testing.T) {
t.Parallel()
field, params := splitGenericsTypeName("test.Field")
assert.Empty(t, field)
assert.Nil(t, params)
field, params = splitGenericsTypeName("test.Field]")
assert.Empty(t, field)
assert.Nil(t, params)
field, params = splitGenericsTypeName("test.Field[string")
assert.Empty(t, field)
assert.Nil(t, params)
field, params = splitGenericsTypeName("test.Field[string] ")
assert.Equal(t, "test.Field", field)
assert.Equal(t, []string{"string"}, params)
field, params = splitGenericsTypeName("test.Field[string, []string]")
assert.Equal(t, "test.Field", field)
assert.Equal(t, []string{"string", "[]string"}, params)
field, params = splitGenericsTypeName("test.Field[test.Field[ string, []string] ]")
assert.Equal(t, "test.Field", field)
assert.Equal(t, []string{"test.Field[string,[]string]"}, params)
}
func TestGetGenericFieldType(t *testing.T) {
field, err := getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexListExpr{
X: &ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}},
Indices: []ast.Expr{&ast.Ident{Name: "string"}},
},
nil,
)
assert.NoError(t, err)
assert.Equal(t, "test.Field[string]", field)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{}},
&ast.IndexListExpr{
X: &ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}},
Indices: []ast.Expr{&ast.Ident{Name: "string"}},
},
nil,
)
assert.NoError(t, err)
assert.Equal(t, "Field[string]", field)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexListExpr{
X: &ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}},
Indices: []ast.Expr{&ast.Ident{Name: "string"}, &ast.Ident{Name: "int"}},
},
nil,
)
assert.NoError(t, err)
assert.Equal(t, "test.Field[string,int]", field)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexListExpr{
X: &ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}},
Indices: []ast.Expr{&ast.Ident{Name: "string"}, &ast.ArrayType{Elt: &ast.Ident{Name: "int"}}},
},
nil,
)
assert.NoError(t, err)
assert.Equal(t, "test.Field[string,[]int]", field)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexListExpr{
X: &ast.BadExpr{},
Indices: []ast.Expr{&ast.Ident{Name: "string"}, &ast.Ident{Name: "int"}},
},
nil,
)
assert.Error(t, err)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexListExpr{
X: &ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}},
Indices: []ast.Expr{&ast.Ident{Name: "string"}, &ast.ArrayType{Elt: &ast.BadExpr{}}},
},
nil,
)
assert.Error(t, err)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexExpr{X: &ast.Ident{Name: "Field"}, Index: &ast.Ident{Name: "string"}},
nil,
)
assert.NoError(t, err)
assert.Equal(t, "test.Field[string]", field)
field, err = getFieldType(
&ast.File{Name: nil},
&ast.IndexExpr{X: &ast.Ident{Name: "Field"}, Index: &ast.Ident{Name: "string"}},
nil,
)
assert.Error(t, err)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexExpr{X: &ast.BadExpr{}, Index: &ast.Ident{Name: "string"}},
nil,
)
assert.Error(t, err)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexExpr{X: &ast.Ident{Name: "Field"}, Index: &ast.BadExpr{}},
nil,
)
assert.Error(t, err)
field, err = getFieldType(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexExpr{X: &ast.SelectorExpr{X: &ast.Ident{Name: "field"}, Sel: &ast.Ident{Name: "Name"}}, Index: &ast.Ident{Name: "string"}},
nil,
)
assert.NoError(t, err)
assert.Equal(t, "field.Name[string]", field)
}
func TestGetGenericTypeName(t *testing.T) {
field, err := getGenericTypeName(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}},
)
assert.NoError(t, err)
assert.Equal(t, "test.Field", field)
field, err = getGenericTypeName(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.ArrayType{Elt: &ast.Ident{Name: "types", Obj: &ast.Object{Decl: &ast.TypeSpec{Name: &ast.Ident{Name: "Field"}}}}},
)
assert.NoError(t, err)
assert.Equal(t, "test.Field", field)
field, err = getGenericTypeName(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.SelectorExpr{X: &ast.Ident{Name: "field"}, Sel: &ast.Ident{Name: "Name"}},
)
assert.NoError(t, err)
assert.Equal(t, "field.Name", field)
_, err = getGenericTypeName(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.BadExpr{},
)
assert.Error(t, err)
}
func TestParseGenericTypeExpr(t *testing.T) {
t.Parallel()
parser := New()
logger := &testLogger{}
SetDebugger(logger)(parser)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.InterfaceType{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.StructType{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.Ident{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.StarExpr{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.SelectorExpr{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.ArrayType{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.MapType{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.FuncType{})
assert.Empty(t, logger.Messages)
_, _ = parser.parseGenericTypeExpr(&ast.File{}, &ast.BadExpr{})
assert.NotEmpty(t, logger.Messages)
assert.Len(t, logger.Messages, 1)
parser.packages.uniqueDefinitions["field.Name[string]"] = &TypeSpecDef{
File: &ast.File{Name: &ast.Ident{Name: "test"}},
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{Name: "Field"},
TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}}},
Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}},
},
}
spec, err := parser.parseTypeExpr(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexExpr{X: &ast.SelectorExpr{X: &ast.Ident{Name: "field"}, Sel: &ast.Ident{Name: "Name"}}, Index: &ast.Ident{Name: "string"}},
false,
)
assert.NotNil(t, spec)
assert.NoError(t, err)
logger.Messages = []string{}
spec, err = parser.parseTypeExpr(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.IndexExpr{X: &ast.BadExpr{}, Index: &ast.Ident{Name: "string"}},
false,
)
assert.NotNil(t, spec)
assert.Equal(t, "object", spec.SchemaProps.Type[0])
assert.NotEmpty(t, logger.Messages)
assert.Len(t, logger.Messages, 1)
logger.Messages = []string{}
spec, err = parser.parseTypeExpr(
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.BadExpr{},
false,
)
assert.NotNil(t, spec)
assert.Equal(t, "object", spec.SchemaProps.Type[0])
assert.NotEmpty(t, logger.Messages)
assert.Len(t, logger.Messages, 1)
}
================================================
FILE: go.mod
================================================
module github.com/swaggo/swag
go 1.24.0
require (
github.com/KyleBanks/depth v1.2.1
github.com/go-openapi/spec v0.22.1
github.com/stretchr/testify v1.11.1
github.com/urfave/cli/v2 v2.3.0
golang.org/x/sync v0.12.0
golang.org/x/text v0.23.0
golang.org/x/tools v0.26.0
sigs.k8s.io/yaml v1.3.0
)
require (
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-openapi/jsonpointer v0.22.1 // indirect
github.com/go-openapi/jsonreference v0.21.3 // indirect
github.com/go-openapi/swag/conv v0.25.1 // indirect
github.com/go-openapi/swag/jsonname v0.25.1 // indirect
github.com/go-openapi/swag/jsonutils v0.25.1 // indirect
github.com/go-openapi/swag/loading v0.25.1 // indirect
github.com/go-openapi/swag/stringutils v0.25.1 // indirect
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.21.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
retract (
v1.16.0 // published accidentally
v1.9.0 // published accidentally
)
================================================
FILE: go.sum
================================================
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=
github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM=
github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc=
github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4=
github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k=
github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA=
github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0=
github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs=
github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU=
github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo=
github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8=
github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg=
github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw=
github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc=
github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw=
github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg=
github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA=
github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8=
github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk=
github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg=
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
================================================
FILE: golist.go
================================================
package swag
import (
"bytes"
"context"
"encoding/json"
"fmt"
"go/build"
"os/exec"
"path/filepath"
"slices"
)
func listPackages(ctx context.Context, dirs []string, env []string, args ...string) ([]*build.Package, error) {
pkgMap := make(map[string]*build.Package)
for i, dir := range dirs {
pkgs, err := listOnePackages(ctx, dir, env, args...)
if err != nil {
if i == 0 {
return nil, fmt.Errorf("pkg %s cannot find all dependencies, %s", dir, err)
}
continue // ignore search dir load error?
}
for _, pkg := range pkgs {
pkgMap[pkg.Dir] = pkg
}
}
pkgs := make([]*build.Package, 0, len(pkgMap))
for _, pkg := range pkgMap {
pkgs = append(pkgs, pkg)
}
slices.SortFunc(pkgs, func(a, b *build.Package) int {
if a.Dir < b.Dir {
return -1
} else if a.Dir > b.Dir {
return 1
}
return 0
})
return pkgs, nil
}
func listOnePackages(ctx context.Context, dir string, env []string, args ...string) (pkgs []*build.Package, finalErr error) {
cmd := exec.CommandContext(ctx, "go", append([]string{"list", "-json", "-e"}, args...)...)
cmd.Env = env
cmd.Dir = dir
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
var stderrBuf bytes.Buffer
cmd.Stderr = &stderrBuf
defer func() {
if (finalErr != nil) && (stderrBuf.Len() > 0) {
finalErr = fmt.Errorf("%v\n%s", finalErr, stderrBuf.Bytes())
}
}()
err = cmd.Start()
if err != nil {
return nil, err
}
dec := json.NewDecoder(stdout)
for dec.More() {
var pkg build.Package
err = dec.Decode(&pkg)
if err != nil {
return nil, err
}
pkgs = append(pkgs, &pkg)
}
err = cmd.Wait()
if err != nil {
return nil, err
}
return pkgs, nil
}
func (parser *Parser) getAllGoFileInfoFromDepsByList(pkg *build.Package, parseFlag ParseFlag) error {
ignoreInternal := pkg.Goroot && !parser.ParseInternal
if ignoreInternal { // ignored internal
return nil
}
if parser.skipPackageByPrefix(pkg.ImportPath) {
return nil // ignored by user-defined package path prefixes
}
srcDir := pkg.Dir
var err error
for i := range pkg.GoFiles {
err = parser.parseFile(pkg.ImportPath, filepath.Join(srcDir, pkg.GoFiles[i]), nil, parseFlag)
if err != nil {
return err
}
}
// parse .go source files that import "C"
for i := range pkg.CgoFiles {
err = parser.parseFile(pkg.ImportPath, filepath.Join(srcDir, pkg.CgoFiles[i]), nil, parseFlag)
if err != nil {
return err
}
}
return nil
}
================================================
FILE: golist_test.go
================================================
package swag
import (
"context"
"errors"
"fmt"
"go/build"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestListPackages(t *testing.T) {
cases := []struct {
name string
args []string
searchDir string
except error
}{
{
name: "errorArgs",
args: []string{"-abc"},
searchDir: "testdata/golist",
except: fmt.Errorf("exit status 2"),
},
{
name: "normal",
args: []string{"-deps"},
searchDir: "testdata/golist",
except: nil,
},
{
name: "list error",
args: []string{"-deps"},
searchDir: "testdata/golist_not_exist",
except: errors.New("searchDir not exist"),
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
_, err := listOnePackages(context.TODO(), c.searchDir, nil, c.args...)
if c.except != nil {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
}
})
}
}
func TestGetAllGoFileInfoFromDepsByList(t *testing.T) {
p := New(ParseUsingGoList(true))
pwd, err := os.Getwd()
assert.NoError(t, err)
cases := []struct {
name string
buildPackage *build.Package
ignoreInternal bool
except error
}{
{
name: "normal",
buildPackage: &build.Package{
Name: "main",
ImportPath: "github.com/swaggo/swag/testdata/golist",
Dir: "testdata/golist",
GoFiles: []string{"main.go"},
CgoFiles: []string{"api/api.go"},
},
except: nil,
},
{
name: "ignore internal",
buildPackage: &build.Package{
Goroot: true,
},
ignoreInternal: true,
except: nil,
},
{
name: "gofiles error",
buildPackage: &build.Package{
Dir: "testdata/golist_not_exist",
GoFiles: []string{"main.go"},
},
except: errors.New("file not exist"),
},
{
name: "cgofiles error",
buildPackage: &build.Package{
Dir: "testdata/golist_not_exist",
CgoFiles: []string{"main.go"},
},
except: errors.New("file not exist"),
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if c.ignoreInternal {
p.ParseInternal = false
}
c.buildPackage.Dir = filepath.Join(pwd, c.buildPackage.Dir)
err := p.getAllGoFileInfoFromDepsByList(c.buildPackage, ParseModels)
if c.except != nil {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
}
})
}
}
================================================
FILE: license
================================================
MIT License
Copyright (c) 2017 Eason Lin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: operation.go
================================================
package swag
import (
"encoding/json"
"fmt"
"go/ast"
goparser "go/parser"
"go/token"
"net/http"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/go-openapi/spec"
"golang.org/x/tools/go/loader"
)
// RouteProperties describes HTTP properties of a single router comment.
type RouteProperties struct {
HTTPMethod string
Path string
Deprecated bool
}
// Operation describes a single API operation on a path.
// For more information: https://github.com/swaggo/swag#api-operation
type Operation struct {
parser *Parser
codeExampleFilesDir string
spec.Operation
RouterProperties []RouteProperties
State string
}
var mimeTypeAliases = map[string]string{
"json": "application/json",
"xml": "text/xml",
"plain": "text/plain",
"html": "text/html",
"mpfd": "multipart/form-data",
"x-www-form-urlencoded": "application/x-www-form-urlencoded",
"json-api": "application/vnd.api+json",
"json-stream": "application/x-json-stream",
"octet-stream": "application/octet-stream",
"png": "image/png",
"jpeg": "image/jpeg",
"gif": "image/gif",
"event-stream": "text/event-stream",
}
var mimeTypePattern = regexp.MustCompile("^[^/]+/[^/]+$")
var securityPairSepPattern = regexp.MustCompile(`\|\||&&`) // || for compatibility with old version, && for clarity
// NewOperation creates a new Operation with default properties.
// map[int]Response.
func NewOperation(parser *Parser, options ...func(*Operation)) *Operation {
if parser == nil {
parser = New()
}
result := &Operation{
parser: parser,
RouterProperties: []RouteProperties{},
Operation: spec.Operation{
OperationProps: spec.OperationProps{
ID: "",
Description: "",
Summary: "",
Security: nil,
ExternalDocs: nil,
Deprecated: false,
Tags: []string{},
Consumes: []string{},
Produces: []string{},
Schemes: []string{},
Parameters: []spec.Parameter{},
Responses: &spec.Responses{
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{},
},
ResponsesProps: spec.ResponsesProps{
Default: nil,
StatusCodeResponses: make(map[int]spec.Response),
},
},
},
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{},
},
},
codeExampleFilesDir: "",
}
for _, option := range options {
option(result)
}
return result
}
// SetCodeExampleFilesDirectory sets the directory to search for codeExamples.
func SetCodeExampleFilesDirectory(directoryPath string) func(*Operation) {
return func(o *Operation) {
o.codeExampleFilesDir = directoryPath
}
}
// ParseComment parses comment for given comment string and returns error if error occurs.
func (operation *Operation) ParseComment(comment string, astFile *ast.File) error {
commentLine := strings.TrimSpace(strings.TrimLeft(comment, "/"))
if len(commentLine) == 0 {
return nil
}
fields := FieldsByAnySpace(commentLine, 2)
attribute := fields[0]
lowerAttribute := strings.ToLower(attribute)
var lineRemainder string
if len(fields) > 1 {
lineRemainder = fields[1]
}
switch lowerAttribute {
case stateAttr:
operation.ParseStateComment(lineRemainder)
case descriptionAttr:
operation.ParseDescriptionComment(lineRemainder)
case descriptionMarkdownAttr:
commentInfo, err := getMarkdownForTag(lineRemainder, operation.parser.markdownFileDir)
if err != nil {
return err
}
operation.ParseDescriptionComment(string(commentInfo))
case summaryAttr:
operation.Summary = lineRemainder
case idAttr:
operation.ID = lineRemainder
case tagsAttr:
operation.ParseTagsComment(lineRemainder)
case acceptAttr:
return operation.ParseAcceptComment(lineRemainder)
case produceAttr:
return operation.ParseProduceComment(lineRemainder)
case paramAttr:
return operation.ParseParamComment(lineRemainder, astFile)
case successAttr, failureAttr, responseAttr:
return operation.ParseResponseComment(lineRemainder, astFile)
case headerAttr:
return operation.ParseResponseHeaderComment(lineRemainder, astFile)
case routerAttr:
return operation.ParseRouterComment(lineRemainder, false)
case deprecatedRouterAttr:
return operation.ParseRouterComment(lineRemainder, true)
case securityAttr:
return operation.ParseSecurityComment(lineRemainder)
case deprecatedAttr:
operation.Deprecate()
case xCodeSamplesAttr:
return operation.ParseCodeSample(attribute, commentLine, lineRemainder)
default:
return operation.ParseMetadata(attribute, lowerAttribute, lineRemainder)
}
return nil
}
// ParseCodeSample parse code sample.
func (operation *Operation) ParseCodeSample(attribute, _, lineRemainder string) error {
if lineRemainder == "file" {
data, err := getCodeExampleForSummary(operation.Summary, operation.codeExampleFilesDir)
if err != nil {
return err
}
var valueJSON any
err = json.Unmarshal(data, &valueJSON)
if err != nil {
return fmt.Errorf("annotation %s need a valid json value", attribute)
}
// don't use the method provided by spec lib, because it will call toLower() on attribute names, which is wrongly
operation.Extensions[attribute[1:]] = valueJSON
return nil
}
// Fallback into existing logic
return operation.ParseMetadata(attribute, strings.ToLower(attribute), lineRemainder)
}
// ParseStateComment parse state comment.
func (operation *Operation) ParseStateComment(lineRemainder string) {
operation.State = lineRemainder
}
// ParseDescriptionComment parse description comment.
func (operation *Operation) ParseDescriptionComment(lineRemainder string) {
if operation.Description == "" {
operation.Description = lineRemainder
return
}
operation.Description = AppendDescription(operation.Description, lineRemainder)
}
// ParseMetadata parse metadata.
func (operation *Operation) ParseMetadata(attribute, lowerAttribute, lineRemainder string) error {
// parsing specific meta data extensions
if strings.HasPrefix(lowerAttribute, "@x-") {
if len(lineRemainder) == 0 {
return fmt.Errorf("annotation %s need a value", attribute)
}
var valueJSON any
err := json.Unmarshal([]byte(lineRemainder), &valueJSON)
if err != nil {
return fmt.Errorf("annotation %s need a valid json value", attribute)
}
// don't use the method provided by spec lib, because it will call toLower() on attribute names, which is wrongly
operation.Extensions[attribute[1:]] = valueJSON
}
return nil
}
var paramPattern = regexp.MustCompile(`(\S+)\s+(\w+)\s+([\S. ]+?)\s+(\w+)\s+"([^"]+)"`)
func findInSlice(arr []string, target string) bool {
for _, str := range arr {
if str == target {
return true
}
}
return false
}
// ParseParamComment parses params return []string of param properties
// E.g. @Param queryText formData string true "The email for login"
//
// [param name] [paramType] [data type] [is mandatory?] [Comment]
//
// E.g. @Param some_id path int true "Some ID".
func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.File) error {
matches := paramPattern.FindStringSubmatch(commentLine)
if len(matches) != 6 {
return fmt.Errorf("missing required param comment parameters \"%s\"", commentLine)
}
name := matches[1]
paramType := matches[2]
refType, format := TransToValidSchemeTypeWithFormat(matches[3])
// Detect refType
objectType := OBJECT
if strings.HasPrefix(refType, "[]") {
objectType = ARRAY
refType = strings.TrimPrefix(refType, "[]")
refType, format = TransToValidSchemeTypeWithFormat(refType)
} else if IsPrimitiveType(refType) ||
paramType == "formData" && refType == "file" {
objectType = PRIMITIVE
}
var enums []any
if !IsPrimitiveType(refType) {
schema, _ := operation.parser.getTypeSchema(refType, astFile, false)
if schema != nil && len(schema.Type) == 1 && schema.Enum != nil {
if objectType == OBJECT {
objectType = PRIMITIVE
}
refType, format = TransToValidSchemeTypeWithFormat(schema.Type[0])
enums = schema.Enum
}
}
requiredText := strings.ToLower(matches[4])
required := requiredText == "true" || requiredText == requiredLabel
description := strings.Join(strings.Split(matches[5], "\\n"), "\n")
param := createParameter(paramType, description, name, objectType, refType, format, required, enums, operation.parser.collectionFormatInQuery)
switch paramType {
case "path", "header", "query", "formData":
switch objectType {
case ARRAY:
if !IsPrimitiveType(refType) && !(refType == "file" && paramType == "formData") {
return fmt.Errorf("%s is not supported array type for %s", refType, paramType)
}
case PRIMITIVE:
break
case OBJECT:
schema, err := operation.parser.getTypeSchema(refType, astFile, false)
if err != nil {
return err
}
if len(schema.Properties) == 0 {
return nil
}
items := schema.Properties.ToOrderedSchemaItems()
for _, item := range items {
name, prop := item.Name, &item.Schema
if len(prop.Type) == 0 {
prop = operation.parser.getUnderlyingSchema(prop)
if len(prop.Type) == 0 {
continue
}
}
// load overridden type specific name from extensions if exists
// query params check "query" extension first, then fall back to "formData"
if paramType == "query" {
if nameVal, ok := item.Schema.Extensions.GetString(queryTag); ok {
name = nameVal
if name == "-" {
continue
}
} else if nameVal, ok := item.Schema.Extensions.GetString("formData"); ok {
name = nameVal
if name == "-" {
continue
}
}
} else if nameVal, ok := item.Schema.Extensions.GetString(paramType); ok {
name = nameVal
if name == "-" {
continue
}
}
switch {
case prop.Type[0] == ARRAY:
if prop.Items.Schema == nil {
continue
}
itemSchema := prop.Items.Schema
if len(itemSchema.Type) == 0 {
itemSchema = operation.parser.getUnderlyingSchema(prop.Items.Schema)
}
if itemSchema == nil {
continue
}
if len(itemSchema.Type) == 0 {
continue
}
if !IsSimplePrimitiveType(itemSchema.Type[0]) {
continue
}
collectionFormat := operation.parser.collectionFormatInQuery
if cfv, ok := prop.Extensions.GetString(collectionFormatTag); ok {
collectionFormat = cfv
}
param = createParameter(paramType, prop.Description, name, prop.Type[0], itemSchema.Type[0], format, findInSlice(schema.Required, item.Name), itemSchema.Enum, collectionFormat)
case IsSimplePrimitiveType(prop.Type[0]):
param = createParameter(paramType, prop.Description, name, PRIMITIVE, prop.Type[0], format, findInSlice(schema.Required, item.Name), nil, operation.parser.collectionFormatInQuery)
default:
operation.parser.debug.Printf("skip field [%s] in %s is not supported type for %s", name, refType, paramType)
continue
}
param.Nullable = prop.Nullable
param.Format = prop.Format
param.Default = prop.Default
param.Example = prop.Example
param.Extensions = prop.Extensions
param.CommonValidations.Maximum = prop.Maximum
param.CommonValidations.Minimum = prop.Minimum
param.CommonValidations.ExclusiveMaximum = prop.ExclusiveMaximum
param.CommonValidations.ExclusiveMinimum = prop.ExclusiveMinimum
param.CommonValidations.MaxLength = prop.MaxLength
param.CommonValidations.MinLength = prop.MinLength
param.CommonValidations.Pattern = prop.Pattern
param.CommonValidations.MaxItems = prop.MaxItems
param.CommonValidations.MinItems = prop.MinItems
param.CommonValidations.UniqueItems = prop.UniqueItems
param.CommonValidations.MultipleOf = prop.MultipleOf
param.CommonValidations.Enum = prop.Enum
operation.Operation.Parameters = append(operation.Operation.Parameters, param)
}
return nil
}
case "body":
if objectType == PRIMITIVE {
param.Schema = PrimitiveSchema(refType)
} else {
schema, err := operation.parseAPIObjectSchema(commentLine, objectType, refType, astFile)
if err != nil {
return err
}
param.Schema = schema
}
default:
return fmt.Errorf("not supported paramType: %s", paramType)
}
err := operation.parseParamAttribute(commentLine, objectType, refType, paramType, ¶m)
if err != nil {
return err
}
operation.Operation.Parameters = append(operation.Operation.Parameters, param)
return nil
}
const (
formTag = "form"
jsonTag = "json"
uriTag = "uri"
headerTag = "header"
queryTag = "query"
paramTag = "param"
bindingTag = "binding"
defaultTag = "default"
enumsTag = "enums"
exampleTag = "example"
schemaExampleTag = "schemaExample"
formatTag = "format"
titleTag = "title"
validateTag = "validate"
minimumTag = "minimum"
maximumTag = "maximum"
minLengthTag = "minLength"
maxLengthTag = "maxLength"
multipleOfTag = "multipleOf"
readOnlyTag = "readonly"
extensionsTag = "extensions"
collectionFormatTag = "collectionFormat"
)
var regexAttributes = map[string]*regexp.Regexp{
// for Enums(A, B)
enumsTag: regexp.MustCompile(`(?i)\s+enums\(.*?\)(?:\s|$)`),
// for maximum(0)
maximumTag: regexp.MustCompile(`(?i)\s+(?:maxinum|maximum)\(.*?\)(?:\s|$)`),
// for minimum(0)
minimumTag: regexp.MustCompile(`(?i)\s+(?:mininum|minimum)\(.*?\)(?:\s|$)`),
// for default(0)
defaultTag: regexp.MustCompile(`(?i)\s+default\(.*?\)(?:\s|$)`),
// for minlength(0)
minLengthTag: regexp.MustCompile(`(?i)\s+minlength\(.*?\)(?:\s|$)`),
// for maxlength(0)
maxLengthTag: regexp.MustCompile(`(?i)\s+maxlength\(.*?\)(?:\s|$)`),
// for format(email)
formatTag: regexp.MustCompile(`(?i)\s+format\(.*?\)(?:\s|$)`),
// for extensions(x-example=test)
extensionsTag: regexp.MustCompile(`(?i)\s+extensions\(.*?\)(?:\s|$)`),
// for collectionFormat(csv)
collectionFormatTag: regexp.MustCompile(`(?i)\s+collectionFormat\(.*?\)(?:\s|$)`),
// example(0)
exampleTag: regexp.MustCompile(`(?i)\s+example\(.*?\)(?:\s|$)`),
// schemaExample(0)
schemaExampleTag: regexp.MustCompile(`(?i)\s+schemaExample\(.*?\)(?:\s|$)`),
}
func (operation *Operation) parseParamAttribute(comment, objectType, schemaType, paramType string, param *spec.Parameter) error {
schemaType = TransToValidSchemeType(schemaType)
for attrKey, re := range regexAttributes {
attr, err := findAttr(re, comment)
if err != nil {
continue
}
switch attrKey {
case enumsTag:
err = setEnumParam(param, attr, objectType, schemaType, paramType)
case minimumTag, maximumTag:
err = setNumberParam(param, attrKey, schemaType, attr, comment)
case defaultTag:
err = setDefault(param, schemaType, attr)
case minLengthTag, maxLengthTag:
err = setStringParam(param, attrKey, schemaType, attr, comment)
case formatTag:
param.Format = attr
case exampleTag:
err = setExample(param, schemaType, attr)
case schemaExampleTag:
err = setSchemaExample(param, schemaType, attr)
case extensionsTag:
param.Extensions = setExtensionParam(attr)
case collectionFormatTag:
err = setCollectionFormatParam(param, attrKey, objectType, attr, comment)
}
if err != nil {
return err
}
}
return nil
}
func findAttr(re *regexp.Regexp, commentLine string) (string, error) {
attr := re.FindString(commentLine)
l, r := strings.Index(attr, "("), strings.LastIndex(attr, ")")
if l == -1 || r == -1 {
return "", fmt.Errorf("can not find regex=%s, comment=%s", re.String(), commentLine)
}
return strings.TrimSpace(attr[l+1 : r]), nil
}
func setStringParam(param *spec.Parameter, name, schemaType, attr, commentLine string) error {
if schemaType != STRING {
return fmt.Errorf("%s is attribute to set to a number. comment=%s got=%s", name, commentLine, schemaType)
}
n, err := strconv.ParseInt(attr, 10, 64)
if err != nil {
return fmt.Errorf("%s is allow only a number got=%s", name, attr)
}
switch name {
case minLengthTag:
param.MinLength = &n
case maxLengthTag:
param.MaxLength = &n
}
return nil
}
func setNumberParam(param *spec.Parameter, name, schemaType, attr, commentLine string) error {
switch schemaType {
case INTEGER, NUMBER:
n, err := strconv.ParseFloat(attr, 64)
if err != nil {
return fmt.Errorf("maximum is allow only a number. comment=%s got=%s", commentLine, attr)
}
switch name {
case minimumTag:
param.Minimum = &n
case maximumTag:
param.Maximum = &n
}
return nil
default:
return fmt.Errorf("%s is attribute to set to a number. comment=%s got=%s", name, commentLine, schemaType)
}
}
func setEnumParam(param *spec.Parameter, attr, objectType, schemaType, paramType string) error {
for _, e := range strings.Split(attr, ",") {
e = strings.TrimSpace(e)
value, err := defineType(schemaType, e)
if err != nil {
return err
}
switch objectType {
case ARRAY:
param.Items.Enum = append(param.Items.Enum, value)
default:
switch paramType {
case "body":
param.Schema.Enum = append(param.Schema.Enum, value)
default:
param.Enum = append(param.Enum, value)
}
}
}
return nil
}
func setExtensionParam(attr string) spec.Extensions {
extensions := spec.Extensions{}
for _, val := range splitNotWrapped(attr, ',') {
parts := strings.SplitN(val, "=", 2)
if len(parts) == 2 {
extensions.Add(parts[0], parts[1])
continue
}
if len(parts[0]) > 0 && string(parts[0][0]) == "!" {
extensions.Add(parts[0][1:], false)
continue
}
extensions.Add(parts[0], true)
}
return extensions
}
func setCollectionFormatParam(param *spec.Parameter, name, schemaType, attr, commentLine string) error {
if schemaType == ARRAY {
param.CollectionFormat = TransToValidCollectionFormat(attr)
return nil
}
return fmt.Errorf("%s is attribute to set to an array. comment=%s got=%s", name, commentLine, schemaType)
}
func setDefault(param *spec.Parameter, schemaType string, value string) error {
val, err := defineType(schemaType, value)
if err != nil {
return nil // Don't set a default value if it's not valid
}
param.Default = val
return nil
}
func setSchemaExample(param *spec.Parameter, schemaType string, value string) error {
val, err := defineType(schemaType, value)
if err != nil {
return nil // Don't set a example value if it's not valid
}
// skip schema
if param.Schema == nil {
return nil
}
switch v := val.(type) {
case string:
// replaces \r \n \t in example string values.
param.Schema.Example = strings.NewReplacer(`\r`, "\r", `\n`, "\n", `\t`, "\t").Replace(v)
default:
param.Schema.Example = val
}
return nil
}
func setExample(param *spec.Parameter, schemaType string, value string) error {
val, err := defineType(schemaType, value)
if err != nil {
return nil // Don't set a example value if it's not valid
}
param.Example = val
return nil
}
// defineType enum value define the type (object and array unsupported).
func defineType(schemaType string, value string) (v any, err error) {
schemaType = TransToValidSchemeType(schemaType)
switch schemaType {
case STRING:
return value, nil
case NUMBER:
v, err = strconv.ParseFloat(value, 64)
if err != nil {
return nil, fmt.Errorf("enum value %s can't convert to %s err: %s", value, schemaType, err)
}
case INTEGER:
v, err = strconv.Atoi(value)
if err != nil {
return nil, fmt.Errorf("enum value %s can't convert to %s err: %s", value, schemaType, err)
}
case BOOLEAN:
v, err = strconv.ParseBool(value)
if err != nil {
return nil, fmt.Errorf("enum value %s can't convert to %s err: %s", value, schemaType, err)
}
default:
return nil, fmt.Errorf("%s is unsupported type in enum value %s", schemaType, value)
}
return v, nil
}
// ParseTagsComment parses comment for given `tag` comment string.
func (operation *Operation) ParseTagsComment(commentLine string) {
for _, tag := range strings.Split(commentLine, ",") {
operation.Tags = append(operation.Tags, strings.TrimSpace(tag))
}
}
// ParseAcceptComment parses comment for given `accept` comment string.
func (operation *Operation) ParseAcceptComment(commentLine string) error {
return parseMimeTypeList(commentLine, &operation.Consumes, "%v accept type can't be accepted")
}
// ParseProduceComment parses comment for given `produce` comment string.
func (operation *Operation) ParseProduceComment(commentLine string) error {
return parseMimeTypeList(commentLine, &operation.Produces, "%v produce type can't be accepted")
}
// parseMimeTypeList parses a list of MIME Types for a comment like
// `produce` (`Content-Type:` response header) or
// `accept` (`Accept:` request header).
func parseMimeTypeList(mimeTypeList string, typeList *[]string, format string) error {
for _, typeName := range strings.Split(mimeTypeList, ",") {
if mimeTypePattern.MatchString(typeName) {
*typeList = append(*typeList, typeName)
continue
}
aliasMimeType, ok := mimeTypeAliases[typeName]
if !ok {
return fmt.Errorf(format, typeName)
}
*typeList = append(*typeList, aliasMimeType)
}
return nil
}
var routerPattern = regexp.MustCompile(`^(/[\w./\-{}\(\)+:$~@]*)[[:blank:]]+\[(\w+)]`)
// ParseRouterComment parses comment for given `router` comment string.
func (operation *Operation) ParseRouterComment(commentLine string, deprecated bool) error {
matches := routerPattern.FindStringSubmatch(commentLine)
if len(matches) != 3 {
return fmt.Errorf("can not parse router comment \"%s\"", commentLine)
}
signature := RouteProperties{
Path: matches[1],
HTTPMethod: strings.ToUpper(matches[2]),
Deprecated: deprecated,
}
if _, ok := allMethod[signature.HTTPMethod]; !ok {
return fmt.Errorf("invalid method: %s", signature.HTTPMethod)
}
operation.RouterProperties = append(operation.RouterProperties, signature)
return nil
}
// ParseSecurityComment parses comment for given `security` comment string.
func (operation *Operation) ParseSecurityComment(commentLine string) error {
if len(commentLine) == 0 {
operation.Security = []map[string][]string{}
return nil
}
var (
securityMap = make(map[string][]string)
securitySource = commentLine[strings.Index(commentLine, "@Security")+1:]
)
for _, securityOption := range securityPairSepPattern.Split(securitySource, -1) {
securityOption = strings.TrimSpace(securityOption)
left, right := strings.Index(securityOption, "["), strings.Index(securityOption, "]")
if !(left == -1 && right == -1) {
scopes := securityOption[left+1 : right]
var options []string
for _, scope := range strings.Split(scopes, ",") {
options = append(options, strings.TrimSpace(scope))
}
securityKey := securityOption[0:left]
securityMap[securityKey] = append(securityMap[securityKey], options...)
} else {
securityKey := strings.TrimSpace(securityOption)
securityMap[securityKey] = []string{}
}
}
operation.Security = append(operation.Security, securityMap)
return nil
}
// findTypeDef attempts to find the *ast.TypeSpec for a specific type given the
// type's name and the package's import path.
// TODO: improve finding external pkg.
func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) {
cwd, err := os.Getwd()
if err != nil {
return nil, err
}
conf := loader.Config{
ParserMode: goparser.SpuriousErrors,
Cwd: cwd,
}
conf.Import(importPath)
lprog, err := conf.Load()
if err != nil {
return nil, err
}
// If the pkg is vendored, the actual pkg path is going to resemble
// something like "{importPath}/vendor/{importPath}"
for k := range lprog.AllPackages {
realPkgPath := k.Path()
if strings.Contains(realPkgPath, "vendor/"+importPath) {
importPath = realPkgPath
}
}
pkgInfo := lprog.Package(importPath)
if pkgInfo == nil {
return nil, fmt.Errorf("package was nil")
}
// TODO: possibly cache pkgInfo since it's an expensive operation
for i := range pkgInfo.Files {
for _, astDeclaration := range pkgInfo.Files[i].Decls {
generalDeclaration, ok := astDeclaration.(*ast.GenDecl)
if ok && generalDeclaration.Tok == token.TYPE {
for _, astSpec := range generalDeclaration.Specs {
typeSpec, ok := astSpec.(*ast.TypeSpec)
if ok {
if typeSpec.Name.String() == typeName {
return typeSpec, nil
}
}
}
}
}
}
return nil, fmt.Errorf("type spec not found")
}
var responsePattern = regexp.MustCompile(`^([\w,]+)\s+([\w{}]+)\s+([\w\-.\\{}=,\[\s\]]+)\s*(".*)?`)
// ResponseType{data1=Type1,data2=Type2}.
var combinedPattern = regexp.MustCompile(`^([\w\-./\[\]]+){(.*)}$`)
func (operation *Operation) parseObjectSchema(refType string, astFile *ast.File) (*spec.Schema, error) {
return parseObjectSchema(operation.parser, refType, astFile)
}
func parseObjectSchema(parser *Parser, refType string, astFile *ast.File) (*spec.Schema, error) {
switch {
case refType == NIL:
return nil, nil
case refType == INTERFACE:
return &spec.Schema{}, nil
case refType == ANY:
return &spec.Schema{}, nil
case IsGolangPrimitiveType(refType):
return TransToValidPrimitiveSchema(refType), nil
case IsPrimitiveType(refType):
return PrimitiveSchema(refType), nil
case strings.HasPrefix(refType, "[]"):
schema, err := parseObjectSchema(parser, refType[2:], astFile)
if err != nil {
return nil, err
}
return spec.ArrayProperty(schema), nil
case strings.HasPrefix(refType, "map["):
// ignore key type
idx := strings.Index(refType, "]")
if idx < 0 {
return nil, fmt.Errorf("invalid type: %s", refType)
}
refType = refType[idx+1:]
if refType == INTERFACE || refType == ANY {
return spec.MapProperty(nil), nil
}
schema, err := parseObjectSchema(parser, refType, astFile)
if err != nil {
return nil, err
}
return spec.MapProperty(schema), nil
case strings.Contains(refType, "{"):
return parseCombinedObjectSchema(parser, refType, astFile)
default:
if parser != nil { // checking refType has existing in 'TypeDefinitions'
schema, err := parser.getTypeSchema(refType, astFile, true)
if err != nil {
return nil, err
}
return schema, nil
}
return RefSchema(refType), nil
}
}
func parseFields(s string) []string {
nestLevel := 0
return strings.FieldsFunc(s, func(char rune) bool {
if char == '{' {
nestLevel++
return false
} else if char == '}' {
nestLevel--
return false
}
return char == ',' && nestLevel == 0
})
}
func parseCombinedObjectSchema(parser *Parser, refType string, astFile *ast.File) (*spec.Schema, error) {
matches := combinedPattern.FindStringSubmatch(refType)
if len(matches) != 3 {
return nil, fmt.Errorf("invalid type: %s", refType)
}
schema, err := parseObjectSchema(parser, matches[1], astFile)
if err != nil {
return nil, err
}
fields, props := parseFields(matches[2]), map[string]spec.Schema{}
for _, field := range fields {
keyVal := strings.SplitN(field, "=", 2)
if len(keyVal) == 2 {
schema, err := parseObjectSchema(parser, keyVal[1], astFile)
if err != nil {
return nil, err
}
if schema == nil {
schema = PrimitiveSchema(OBJECT)
}
props[keyVal[0]] = *schema
}
}
if len(props) == 0 {
return schema, nil
}
if schema.Ref.GetURL() == nil && len(schema.Type) > 0 && schema.Type[0] == OBJECT && len(schema.Properties) == 0 && schema.AdditionalProperties == nil {
schema.Properties = props
return schema, nil
}
return spec.ComposedSchema(*schema, spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{OBJECT},
Properties: props,
},
}), nil
}
func (operation *Operation) parseAPIObjectSchema(commentLine, schemaType, refType string, astFile *ast.File) (*spec.Schema, error) {
if strings.HasSuffix(refType, ",") && strings.Contains(refType, "[") {
// regexp may have broken generic syntax. find closing bracket and add it back
allMatchesLenOffset := strings.Index(commentLine, refType) + len(refType)
lostPartEndIdx := strings.Index(commentLine[allMatchesLenOffset:], "]")
if lostPartEndIdx >= 0 {
refType += commentLine[allMatchesLenOffset : allMatchesLenOffset+lostPartEndIdx+1]
}
}
switch schemaType {
case OBJECT:
if !strings.HasPrefix(refType, "[]") {
return operation.parseObjectSchema(refType, astFile)
}
refType = refType[2:]
fallthrough
case ARRAY:
schema, err := operation.parseObjectSchema(refType, astFile)
if err != nil {
return nil, err
}
return spec.ArrayProperty(schema), nil
default:
return PrimitiveSchema(schemaType), nil
}
}
// ParseResponseComment parses comment for given `response` comment string.
func (operation *Operation) ParseResponseComment(commentLine string, astFile *ast.File) error {
matches := responsePattern.FindStringSubmatch(commentLine)
if len(matches) != 5 {
err := operation.ParseEmptyResponseComment(commentLine)
if err != nil {
return operation.ParseEmptyResponseOnly(commentLine)
}
return err
}
description := strings.Trim(matches[4], "\"")
schema, err := operation.parseAPIObjectSchema(commentLine, strings.Trim(matches[2], "{}"), strings.TrimSpace(matches[3]), astFile)
if err != nil {
return err
}
for _, codeStr := range strings.Split(matches[1], ",") {
if strings.EqualFold(codeStr, defaultTag) {
operation.DefaultResponse().WithSchema(schema).WithDescription(description)
continue
}
code, err := strconv.Atoi(codeStr)
if err != nil {
return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
}
resp := spec.NewResponse().WithSchema(schema).WithDescription(description)
if description == "" {
resp.WithDescription(http.StatusText(code))
}
operation.AddResponse(code, resp)
}
return nil
}
func newHeaderSpec(schemaType, description string) spec.Header {
return spec.Header{
SimpleSchema: spec.SimpleSchema{
Type: schemaType,
},
HeaderProps: spec.HeaderProps{
Description: description,
},
VendorExtensible: spec.VendorExtensible{
Extensions: nil,
},
CommonValidations: spec.CommonValidations{
Maximum: nil,
ExclusiveMaximum: false,
Minimum: nil,
ExclusiveMinimum: false,
MaxLength: nil,
MinLength: nil,
Pattern: "",
MaxItems: nil,
MinItems: nil,
UniqueItems: false,
MultipleOf: nil,
Enum: nil,
},
}
}
// ParseResponseHeaderComment parses comment for given `response header` comment string.
func (operation *Operation) ParseResponseHeaderComment(commentLine string, _ *ast.File) error {
matches := responsePattern.FindStringSubmatch(commentLine)
if len(matches) != 5 {
return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
}
header := newHeaderSpec(strings.Trim(matches[2], "{}"), strings.Trim(matches[4], "\""))
headerKey := strings.TrimSpace(matches[3])
if strings.EqualFold(matches[1], "all") {
if operation.Responses.Default != nil {
operation.Responses.Default.Headers[headerKey] = header
}
if operation.Responses.StatusCodeResponses != nil {
for code, response := range operation.Responses.StatusCodeResponses {
response.Headers[headerKey] = header
operation.Responses.StatusCodeResponses[code] = response
}
}
return nil
}
for _, codeStr := range strings.Split(matches[1], ",") {
if strings.EqualFold(codeStr, defaultTag) {
if operation.Responses.Default != nil {
operation.Responses.Default.Headers[headerKey] = header
}
continue
}
code, err := strconv.Atoi(codeStr)
if err != nil {
return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
}
if operation.Responses.StatusCodeResponses != nil {
response, responseExist := operation.Responses.StatusCodeResponses[code]
if responseExist {
response.Headers[headerKey] = header
operation.Responses.StatusCodeResponses[code] = response
}
}
}
return nil
}
var emptyResponsePattern = regexp.MustCompile(`([\w,]+)\s+"(.*)"`)
// ParseEmptyResponseComment parse only comment out status code and description,eg: @Success 200 "it's ok".
func (operation *Operation) ParseEmptyResponseComment(commentLine string) error {
matches := emptyResponsePattern.FindStringSubmatch(commentLine)
if len(matches) != 3 {
return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
}
description := strings.Trim(matches[2], "\"")
for _, codeStr := range strings.Split(matches[1], ",") {
if strings.EqualFold(codeStr, defaultTag) {
operation.DefaultResponse().WithDescription(description)
continue
}
code, err := strconv.Atoi(codeStr)
if err != nil {
return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
}
operation.AddResponse(code, spec.NewResponse().WithDescription(description))
}
return nil
}
// ParseEmptyResponseOnly parse only comment out status code ,eg: @Success 200.
func (operation *Operation) ParseEmptyResponseOnly(commentLine string) error {
for _, codeStr := range strings.Split(commentLine, ",") {
if strings.EqualFold(codeStr, defaultTag) {
_ = operation.DefaultResponse()
continue
}
code, err := strconv.Atoi(codeStr)
if err != nil {
return fmt.Errorf("can not parse response comment \"%s\"", commentLine)
}
operation.AddResponse(code, spec.NewResponse().WithDescription(http.StatusText(code)))
}
return nil
}
// DefaultResponse return the default response member pointer.
func (operation *Operation) DefaultResponse() *spec.Response {
if operation.Responses.Default == nil {
operation.Responses.Default = &spec.Response{
ResponseProps: spec.ResponseProps{
Description: "",
Headers: make(map[string]spec.Header),
},
}
}
return operation.Responses.Default
}
// AddResponse add a response for a code.
func (operation *Operation) AddResponse(code int, response *spec.Response) {
if response.Headers == nil {
response.Headers = make(map[string]spec.Header)
}
operation.Responses.StatusCodeResponses[code] = *response
}
// createParameter returns swagger spec.Parameter for given paramType, description, paramName, schemaType, required.
func createParameter(paramType, description, paramName, objectType, schemaType string, format string, required bool, enums []any, collectionFormat string) spec.Parameter {
// //five possible parameter types. query, path, body, header, form
result := spec.Parameter{
ParamProps: spec.ParamProps{
Name: paramName,
Description: description,
Required: required,
In: paramType,
},
}
if paramType == "body" {
return result
}
switch objectType {
case ARRAY:
result.Type = objectType
result.CollectionFormat = collectionFormat
result.Items = &spec.Items{
CommonValidations: spec.CommonValidations{
Enum: enums,
},
SimpleSchema: spec.SimpleSchema{
Type: schemaType,
Format: format,
},
}
case PRIMITIVE, OBJECT:
result.Type = schemaType
result.Enum = enums
result.Format = format
}
return result
}
func getCodeExampleForSummary(summaryName string, dirPath string) ([]byte, error) {
dirEntries, err := os.ReadDir(dirPath)
if err != nil {
return nil, err
}
for _, entry := range dirEntries {
if entry.IsDir() {
continue
}
fileName := entry.Name()
if !strings.Contains(fileName, ".json") {
continue
}
if strings.Contains(fileName, summaryName) {
fullPath := filepath.Join(dirPath, fileName)
commentInfo, err := os.ReadFile(fullPath)
if err != nil {
return nil, fmt.Errorf("Failed to read code example file %s error: %s ", fullPath, err)
}
return commentInfo, nil
}
}
return nil, fmt.Errorf("unable to find code example file for tag %s in the given directory", summaryName)
}
================================================
FILE: operation_test.go
================================================
package swag
import (
"encoding/json"
"fmt"
"go/ast"
goparser "go/parser"
"go/token"
"os"
"path/filepath"
"testing"
"github.com/go-openapi/spec"
"github.com/stretchr/testify/assert"
)
func TestParseEmptyComment(t *testing.T) {
t.Parallel()
operation := NewOperation(nil)
err := operation.ParseComment("//", nil)
assert.NoError(t, err)
}
func TestParseTagsComment(t *testing.T) {
t.Parallel()
operation := NewOperation(nil)
err := operation.ParseComment(`/@Tags pet, store,user`, nil)
assert.NoError(t, err)
assert.Equal(t, operation.Tags, []string{"pet", "store", "user"})
}
func TestParseAcceptComment(t *testing.T) {
t.Parallel()
comment := `/@Accept json,xml,plain,html,mpfd,x-www-form-urlencoded,json-api,json-stream,octet-stream,png,jpeg,gif,application/xhtml+xml,application/health+json`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Equal(t,
operation.Consumes,
[]string{"application/json",
"text/xml",
"text/plain",
"text/html",
"multipart/form-data",
"application/x-www-form-urlencoded",
"application/vnd.api+json",
"application/x-json-stream",
"application/octet-stream",
"image/png",
"image/jpeg",
"image/gif",
"application/xhtml+xml",
"application/health+json"})
}
func TestParseAcceptCommentErr(t *testing.T) {
t.Parallel()
comment := `/@Accept unknown`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestParseProduceComment(t *testing.T) {
t.Parallel()
expected := `{
"produces": [
"application/json",
"text/xml",
"text/plain",
"text/html",
"multipart/form-data",
"application/x-www-form-urlencoded",
"application/vnd.api+json",
"application/x-json-stream",
"application/octet-stream",
"image/png",
"image/jpeg",
"image/gif",
"application/health+json"
]
}`
comment := `/@Produce json,xml,plain,html,mpfd,x-www-form-urlencoded,json-api,json-stream,octet-stream,png,jpeg,gif,application/health+json`
operation := new(Operation)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
b, _ := json.MarshalIndent(operation, "", " ")
assert.JSONEq(t, expected, string(b))
}
func TestParseProduceCommentErr(t *testing.T) {
t.Parallel()
operation := new(Operation)
err := operation.ParseComment("/@Produce foo", nil)
assert.Error(t, err)
}
func TestParseRouterComment(t *testing.T) {
t.Parallel()
comment := `/@Router /customer/get-wishlist/{wishlist_id} [get]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Len(t, operation.RouterProperties, 1)
assert.Equal(t, "/customer/get-wishlist/{wishlist_id}", operation.RouterProperties[0].Path)
assert.Equal(t, "GET", operation.RouterProperties[0].HTTPMethod)
comment = `/@Router /customer/get-wishlist/{wishlist_id} [unknown]`
operation = NewOperation(nil)
err = operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestParseRouterMultipleComments(t *testing.T) {
t.Parallel()
comment := `/@Router /customer/get-wishlist/{wishlist_id} [get]`
anotherComment := `/@Router /customer/get-the-wishlist/{wishlist_id} [post]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
err = operation.ParseComment(anotherComment, nil)
assert.NoError(t, err)
assert.Len(t, operation.RouterProperties, 2)
assert.Equal(t, "/customer/get-wishlist/{wishlist_id}", operation.RouterProperties[0].Path)
assert.Equal(t, "GET", operation.RouterProperties[0].HTTPMethod)
assert.Equal(t, "/customer/get-the-wishlist/{wishlist_id}", operation.RouterProperties[1].Path)
assert.Equal(t, "POST", operation.RouterProperties[1].HTTPMethod)
}
func TestParseRouterOnlySlash(t *testing.T) {
t.Parallel()
comment := `// @Router / [get]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Len(t, operation.RouterProperties, 1)
assert.Equal(t, "/", operation.RouterProperties[0].Path)
assert.Equal(t, "GET", operation.RouterProperties[0].HTTPMethod)
}
func TestParseRouterCommentWithPlusSign(t *testing.T) {
t.Parallel()
comment := `/@Router /customer/get-wishlist/{proxy+} [post]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Len(t, operation.RouterProperties, 1)
assert.Equal(t, "/customer/get-wishlist/{proxy+}", operation.RouterProperties[0].Path)
assert.Equal(t, "POST", operation.RouterProperties[0].HTTPMethod)
}
func TestParseRouterCommentWithDollarSign(t *testing.T) {
t.Parallel()
comment := `/@Router /customer/get-wishlist/{wishlist_id}$move [post]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Len(t, operation.RouterProperties, 1)
assert.Equal(t, "/customer/get-wishlist/{wishlist_id}$move", operation.RouterProperties[0].Path)
assert.Equal(t, "POST", operation.RouterProperties[0].HTTPMethod)
}
func TestParseRouterCommentWithParens(t *testing.T) {
t.Parallel()
comment := `/@Router /customer({id}) [get]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Len(t, operation.RouterProperties, 1)
assert.Equal(t, "/customer({id})", operation.RouterProperties[0].Path)
assert.Equal(t, "GET", operation.RouterProperties[0].HTTPMethod)
}
func TestParseRouterCommentNoDollarSignAtPathStartErr(t *testing.T) {
t.Parallel()
comment := `/@Router $customer/get-wishlist/{wishlist_id}$move [post]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestParseRouterCommentWithColonSign(t *testing.T) {
t.Parallel()
comment := `/@Router /customer/get-wishlist/{wishlist_id}:move [post]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Len(t, operation.RouterProperties, 1)
assert.Equal(t, "/customer/get-wishlist/{wishlist_id}:move", operation.RouterProperties[0].Path)
assert.Equal(t, "POST", operation.RouterProperties[0].HTTPMethod)
}
func TestParseRouterCommentNoColonSignAtPathStartErr(t *testing.T) {
t.Parallel()
comment := `/@Router :customer/get-wishlist/{wishlist_id}:move [post]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestParseRouterCommentWithTilde(t *testing.T) {
t.Parallel()
comment := `@Router /customer/{id}/~last-name [patch]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
}
func TestParseRouterCommentWithAt(t *testing.T) {
t.Parallel()
comment := `@Router /users/@{id} [get]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
}
func TestParseRouterCommentMethodSeparationErr(t *testing.T) {
t.Parallel()
comment := `/@Router /api/{id}|,*[get`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestParseRouterCommentMethodMissingErr(t *testing.T) {
t.Parallel()
comment := `/@Router /customer/get-wishlist/{wishlist_id}`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestOperation_ParseResponseWithDefault(t *testing.T) {
t.Parallel()
comment := `@Success default {object} nil "An empty response"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Equal(t, "An empty response", operation.Responses.Default.Description)
comment = `@Success 200,default {string} Response "A response"`
operation = NewOperation(nil)
err = operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Equal(t, "A response", operation.Responses.Default.Description)
assert.Equal(t, "A response", operation.Responses.StatusCodeResponses[200].Description)
}
func TestParseResponseSuccessCommentWithEmptyResponse(t *testing.T) {
t.Parallel()
comment := `@Success 200 {object} nil "An empty response"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
response := operation.Responses.StatusCodeResponses[200]
assert.Equal(t, `An empty response`, response.Description)
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "An empty response"
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseFailureCommentWithEmptyResponse(t *testing.T) {
t.Parallel()
comment := `@Failure 500 {object} nil`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"500": {
"description": "Internal Server Error"
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentWithObjectType(t *testing.T) {
t.Parallel()
comment := `@Success 200 {object} model.OrderRow "Error message, if code != 200`
operation := NewOperation(nil)
operation.parser.addTestType("model.OrderRow")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
response := operation.Responses.StatusCodeResponses[200]
assert.Equal(t, `Error message, if code != 200`, response.Description)
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "Error message, if code != 200",
"schema": {
"$ref": "#/definitions/model.OrderRow"
}
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentWithNestedPrimitiveType(t *testing.T) {
t.Parallel()
comment := `@Success 200 {object} model.CommonHeader{data=string,data2=int} "Error message, if code != 200`
operation := NewOperation(nil)
operation.parser.addTestType("model.CommonHeader")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
response := operation.Responses.StatusCodeResponses[200]
assert.Equal(t, `Error message, if code != 200`, response.Description)
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "Error message, if code != 200",
"schema": {
"allOf": [
{
"$ref": "#/definitions/model.CommonHeader"
},
{
"type": "object",
"properties": {
"data": {
"type": "string"
},
"data2": {
"type": "integer"
}
}
}
]
}
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) {
t.Parallel()
comment := `@Success 200 {object} model.CommonHeader{data=[]string,data2=[]int} "Error message, if code != 200`
operation := NewOperation(nil)
operation.parser.addTestType("model.CommonHeader")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
response := operation.Responses.StatusCodeResponses[200]
assert.Equal(t, `Error message, if code != 200`, response.Description)
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "Error message, if code != 200",
"schema": {
"allOf": [
{
"$ref": "#/definitions/model.CommonHeader"
},
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "string"
}
},
"data2": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
]
}
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentWithNestedObjectType(t *testing.T) {
t.Parallel()
comment := `@Success 200 {object} model.CommonHeader{data=model.Payload,data2=model.Payload2} "Error message, if code != 200`
operation := NewOperation(nil)
operation.parser.addTestType("model.CommonHeader")
operation.parser.addTestType("model.Payload")
operation.parser.addTestType("model.Payload2")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
response := operation.Responses.StatusCodeResponses[200]
assert.Equal(t, `Error message, if code != 200`, response.Description)
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "Error message, if code != 200",
"schema": {
"allOf": [
{
"$ref": "#/definitions/model.CommonHeader"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/model.Payload"
},
"data2": {
"$ref": "#/definitions/model.Payload2"
}
}
}
]
}
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentWithNestedArrayObjectType(t *testing.T) {
t.Parallel()
comment := `@Success 200 {object} model.CommonHeader{data=[]model.Payload,data2=[]model.Payload2} "Error message, if code != 200`
operation := NewOperation(nil)
operation.parser.addTestType("model.CommonHeader")
operation.parser.addTestType("model.Payload")
operation.parser.addTestType("model.Payload2")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
response := operation.Responses.StatusCodeResponses[200]
assert.Equal(t, `Error message, if code != 200`, response.Description)
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "Error message, if code != 200",
"schema": {
"allOf": [
{
"$ref": "#/definitions/model.CommonHeader"
},
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/model.Payload"
}
},
"data2": {
"type": "array",
"items": {
"$ref": "#/definitions/model.Payload2"
}
}
}
}
]
}
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentWithNestedFields(t *testing.T) {
t.Parallel()
comment := `@Success 200 {object} model.CommonHeader{data1=int,data2=[]int,data3=model.Payload,data4=[]model.Payload} "Error message, if code != 200`
operation := NewOperation(nil)
operation.parser.addTestType("model.CommonHeader")
operation.parser.addTestType("model.Payload")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
response := operation.Responses.StatusCodeResponses[200]
assert.Equal(t, `Error message, if code != 200`, response.Description)
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "Error message, if code != 200",
"schema": {
"allOf": [
{
"$ref": "#/definitions/model.CommonHeader"
},
{
"type": "object",
"properties": {
"data1": {
"type": "integer"
},
"data2": {
"type": "array",
"items": {
"type": "integer"
}
},
"data3": {
"$ref": "#/definitions/model.Payload"
},
"data4": {
"type": "array",
"items": {
"$ref": "#/definitions/model.Payload"
}
}
}
}
]
}
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentWithDeepNestedFields(t *testing.T) {
t.Parallel()
comment := `@Success 200 {object} model.CommonHeader{data1=int,data2=[]int,data3=model.Payload{data1=int,data2=model.DeepPayload},data4=[]model.Payload{data1=[]int,data2=[]model.DeepPayload}} "Error message, if code != 200`
operation := NewOperation(nil)
operation.parser.addTestType("model.CommonHeader")
operation.parser.addTestType("model.Payload")
operation.parser.addTestType("model.DeepPayload")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
response := operation.Responses.StatusCodeResponses[200]
assert.Equal(t, `Error message, if code != 200`, response.Description)
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "Error message, if code != 200",
"schema": {
"allOf": [
{
"$ref": "#/definitions/model.CommonHeader"
},
{
"type": "object",
"properties": {
"data1": {
"type": "integer"
},
"data2": {
"type": "array",
"items": {
"type": "integer"
}
},
"data3": {
"allOf": [
{
"$ref": "#/definitions/model.Payload"
},
{
"type": "object",
"properties": {
"data1": {
"type": "integer"
},
"data2": {
"$ref": "#/definitions/model.DeepPayload"
}
}
}
]
},
"data4": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/definitions/model.Payload"
},
{
"type": "object",
"properties": {
"data1": {
"type": "array",
"items": {
"type": "integer"
}
},
"data2": {
"type": "array",
"items": {
"$ref": "#/definitions/model.DeepPayload"
}
}
}
}
]
}
}
}
}
]
}
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentWithNestedArrayMapFields(t *testing.T) {
t.Parallel()
comment := `@Success 200 {object} []map[string]model.CommonHeader{data1=[]map[string]model.Payload,data2=map[string][]int} "Error message, if code != 200`
operation := NewOperation(nil)
operation.parser.addTestType("model.CommonHeader")
operation.parser.addTestType("model.Payload")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
response := operation.Responses.StatusCodeResponses[200]
assert.Equal(t, `Error message, if code != 200`, response.Description)
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "Error message, if code != 200",
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": {
"allOf": [
{
"$ref": "#/definitions/model.CommonHeader"
},
{
"type": "object",
"properties": {
"data1": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/model.Payload"
}
}
},
"data2": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
]
}
}
}
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentWithObjectTypeInSameFile(t *testing.T) {
t.Parallel()
comment := `@Success 200 {object} testOwner "Error message, if code != 200"`
operation := NewOperation(nil)
operation.parser.addTestType("swag.testOwner")
fset := token.NewFileSet()
astFile, err := goparser.ParseFile(fset, "operation_test.go", `package swag
type testOwner struct {
}
`, goparser.ParseComments)
assert.NoError(t, err)
err = operation.ParseComment(comment, astFile)
assert.NoError(t, err)
response := operation.Responses.StatusCodeResponses[200]
assert.Equal(t, `Error message, if code != 200`, response.Description)
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "Error message, if code != 200",
"schema": {
"$ref": "#/definitions/swag.testOwner"
}
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentWithObjectTypeAnonymousField(t *testing.T) {
//TODO: test Anonymous
}
func TestParseResponseCommentWithObjectTypeErr(t *testing.T) {
t.Parallel()
comment := `@Success 200 {object} model.OrderRow "Error message, if code != 200"`
operation := NewOperation(nil)
operation.parser.addTestType("model.notexist")
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestParseResponseCommentWithArrayType(t *testing.T) {
t.Parallel()
comment := `@Success 200 {array} model.OrderRow "Error message, if code != 200`
operation := NewOperation(nil)
operation.parser.addTestType("model.OrderRow")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
response := operation.Responses.StatusCodeResponses[200]
assert.Equal(t, `Error message, if code != 200`, response.Description)
assert.Equal(t, spec.StringOrArray{"array"}, response.Schema.Type)
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "Error message, if code != 200",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/model.OrderRow"
}
}
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentWithBasicType(t *testing.T) {
t.Parallel()
comment := `@Success 200 {string} string "it's ok'"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "it's ok'",
"schema": {
"type": "string"
}
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentWithBasicTypeAndCodes(t *testing.T) {
t.Parallel()
comment := `@Success 200,201,default {string} string "it's ok"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "it's ok",
"schema": {
"type": "string"
}
},
"201": {
"description": "it's ok",
"schema": {
"type": "string"
}
},
"default": {
"description": "it's ok",
"schema": {
"type": "string"
}
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseEmptyResponseComment(t *testing.T) {
t.Parallel()
comment := `@Success 200 "it is ok"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "it is ok"
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseEmptyResponseCommentWithCodes(t *testing.T) {
t.Parallel()
comment := `@Success 200,201,default "it is ok"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "it is ok"
},
"201": {
"description": "it is ok"
},
"default": {
"description": "it is ok"
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentWithHeader(t *testing.T) {
t.Parallel()
operation := NewOperation(nil)
err := operation.ParseComment(`@Success 200 "it's ok"`, nil)
assert.NoError(t, err, "ParseComment should not fail")
err = operation.ParseComment(`@Header 200 {string} Token "qwerty"`, nil)
assert.NoError(t, err, "ParseComment should not fail")
b, err := json.MarshalIndent(operation, "", " ")
assert.NoError(t, err)
expected := `{
"responses": {
"200": {
"description": "it's ok",
"headers": {
"Token": {
"type": "string",
"description": "qwerty"
}
}
}
}
}`
assert.Equal(t, expected, string(b))
err = operation.ParseComment(`@Header 200 "Mallformed"`, nil)
assert.Error(t, err, "ParseComment should not fail")
err = operation.ParseComment(`@Header 200,asdsd {string} Token "qwerty"`, nil)
assert.Error(t, err, "ParseComment should not fail")
}
func TestParseResponseCommentWithHeaderForCodes(t *testing.T) {
t.Parallel()
operation := NewOperation(nil)
comment := `@Success 200,201,default "it's ok"`
err := operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
comment = `@Header 200,201,default {string} Token "qwerty"`
err = operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
comment = `@Header all {string} Token2 "qwerty"`
err = operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
b, err := json.MarshalIndent(operation, "", " ")
assert.NoError(t, err)
expected := `{
"responses": {
"200": {
"description": "it's ok",
"headers": {
"Token": {
"type": "string",
"description": "qwerty"
},
"Token2": {
"type": "string",
"description": "qwerty"
}
}
},
"201": {
"description": "it's ok",
"headers": {
"Token": {
"type": "string",
"description": "qwerty"
},
"Token2": {
"type": "string",
"description": "qwerty"
}
}
},
"default": {
"description": "it's ok",
"headers": {
"Token": {
"type": "string",
"description": "qwerty"
},
"Token2": {
"type": "string",
"description": "qwerty"
}
}
}
}
}`
assert.Equal(t, expected, string(b))
comment = `@Header 200 "Mallformed"`
err = operation.ParseComment(comment, nil)
assert.Error(t, err, "ParseComment should not fail")
}
func TestParseResponseCommentWithHeaderOnlyAll(t *testing.T) {
t.Parallel()
operation := NewOperation(nil)
comment := `@Success 200,201,default "it's ok"`
err := operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
comment = `@Header all {string} Token "qwerty"`
err = operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
b, err := json.MarshalIndent(operation, "", " ")
assert.NoError(t, err)
expected := `{
"responses": {
"200": {
"description": "it's ok",
"headers": {
"Token": {
"type": "string",
"description": "qwerty"
}
}
},
"201": {
"description": "it's ok",
"headers": {
"Token": {
"type": "string",
"description": "qwerty"
}
}
},
"default": {
"description": "it's ok",
"headers": {
"Token": {
"type": "string",
"description": "qwerty"
}
}
}
}
}`
assert.Equal(t, expected, string(b))
comment = `@Header 200 "Mallformed"`
err = operation.ParseComment(comment, nil)
assert.Error(t, err, "ParseComment should not fail")
}
func TestParseEmptyResponseOnlyCode(t *testing.T) {
t.Parallel()
operation := NewOperation(nil)
err := operation.ParseComment(`@Success 200`, nil)
assert.NoError(t, err, "ParseComment should not fail")
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "OK"
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseEmptyResponseOnlyCodes(t *testing.T) {
t.Parallel()
comment := `@Success 200,201,default`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err, "ParseComment should not fail")
b, _ := json.MarshalIndent(operation, "", " ")
expected := `{
"responses": {
"200": {
"description": "OK"
},
"201": {
"description": "Created"
},
"default": {
"description": ""
}
}
}`
assert.Equal(t, expected, string(b))
}
func TestParseResponseCommentParamMissing(t *testing.T) {
t.Parallel()
operation := NewOperation(nil)
paramLenErrComment := `@Success notIntCode`
paramLenErr := operation.ParseComment(paramLenErrComment, nil)
assert.EqualError(t, paramLenErr, `can not parse response comment "notIntCode"`)
paramLenErrComment = `@Success notIntCode {string} string "it ok"`
paramLenErr = operation.ParseComment(paramLenErrComment, nil)
assert.EqualError(t, paramLenErr, `can not parse response comment "notIntCode {string} string "it ok""`)
paramLenErrComment = `@Success notIntCode "it ok"`
paramLenErr = operation.ParseComment(paramLenErrComment, nil)
assert.EqualError(t, paramLenErr, `can not parse response comment "notIntCode "it ok""`)
}
func TestOperation_ParseParamComment(t *testing.T) {
t.Parallel()
t.Run("integer", func(t *testing.T) {
t.Parallel()
for _, paramType := range []string{"header", "path", "query", "formData"} {
t.Run(paramType, func(t *testing.T) {
o := NewOperation(nil)
err := o.ParseComment(`@Param some_id `+paramType+` int true "Some ID"`, nil)
assert.NoError(t, err)
assert.Equal(t, o.Parameters, []spec.Parameter{{
SimpleSchema: spec.SimpleSchema{
Type: "integer",
},
ParamProps: spec.ParamProps{
Name: "some_id",
Description: "Some ID",
In: paramType,
Required: true,
},
}})
})
}
})
t.Run("string", func(t *testing.T) {
t.Parallel()
for _, paramType := range []string{"header", "path", "query", "formData"} {
t.Run(paramType, func(t *testing.T) {
o := NewOperation(nil)
err := o.ParseComment(`@Param some_string `+paramType+` string true "Some String"`, nil)
assert.NoError(t, err)
assert.Equal(t, o.Parameters, []spec.Parameter{{
SimpleSchema: spec.SimpleSchema{
Type: "string",
},
ParamProps: spec.ParamProps{
Name: "some_string",
Description: "Some String",
In: paramType,
Required: true,
},
}})
})
}
})
t.Run("object", func(t *testing.T) {
t.Parallel()
for _, paramType := range []string{"header", "path", "query", "formData"} {
t.Run(paramType, func(t *testing.T) {
// unknown object returns error
assert.Error(t, NewOperation(nil).ParseComment(`@Param some_object `+paramType+` main.Object true "Some Object"`, nil))
// verify objects are supported here
o := NewOperation(nil)
o.parser.addTestType("main.TestObject")
err := o.ParseComment(`@Param some_object `+paramType+` main.TestObject true "Some Object"`, nil)
assert.NoError(t, err)
})
}
})
}
// Test ParseParamComment Query Params
func TestParseParamCommentBodyArray(t *testing.T) {
t.Parallel()
comment := `@Param names body []string true "Users List"`
o := NewOperation(nil)
err := o.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Equal(t, o.Parameters, []spec.Parameter{{
ParamProps: spec.ParamProps{
Name: "names",
Description: "Users List",
In: "body",
Required: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
},
},
},
},
}})
}
// Test ParseParamComment Params
func TestParseParamCommentArray(t *testing.T) {
paramTypes := []string{"header", "path", "query"}
for _, paramType := range paramTypes {
t.Run(paramType, func(t *testing.T) {
operation := NewOperation(nil)
err := operation.ParseComment(`@Param names `+paramType+` []string true "Users List"`, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "array",
"items": {
"type": "string"
},
"description": "Users List",
"name": "names",
"in": "` + paramType + `",
"required": true
}
]`
assert.Equal(t, expected, string(b))
err = operation.ParseComment(`@Param names `+paramType+` []model.User true "Users List"`, nil)
assert.Error(t, err)
})
}
}
// Test TestParseParamCommentDefaultValue Query Params
func TestParseParamCommentDefaultValue(t *testing.T) {
t.Parallel()
operation := NewOperation(nil)
err := operation.ParseComment(`@Param names query string true "Users List" default(test)`, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "string",
"default": "test",
"description": "Users List",
"name": "names",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
}
// Test ParseParamComment Query Params
func TestParseParamCommentQueryArrayFormat(t *testing.T) {
t.Parallel()
comment := `@Param names query []string true "Users List" collectionFormat(multi)`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"description": "Users List",
"name": "names",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
}
// Test ParseParamComment Query Params
func TestParseParamCommentQueryArrayFormatWithStructTag(t *testing.T) {
parser := New()
parser.packages.ParseFile("test",
"/test/test.go",
"package test\ntype MyQueryParams struct{Param []string `form:\"param\" collectionFormat:\"multi\"`}",
ParseAll)
parser.packages.ParseTypes()
comment := `@Param anyWhat query test.MyQueryParams true "List"`
operation := NewOperation(parser)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "array",
"items": {
"type": "string"
},
"collectionFormat": "multi",
"name": "param",
"in": "query"
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentQuerySkipWithStructTag(t *testing.T) {
t.Parallel()
parser := New()
parser.packages.ParseFile("test",
"/test/test.go",
"package test\ntype MyQueryParam struct{Param string `form:\"param\"`\nSkipField string `form:\"-\"`}",
ParseAll)
parser.packages.ParseTypes()
comment := `@Param anyWhat query test.MyQueryParam true "Parameter"`
operation := NewOperation(parser)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "string",
"name": "param",
"in": "query"
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentPathWithParamTag(t *testing.T) {
t.Parallel()
parser := New()
parser.packages.ParseFile("test",
"/test/test.go",
"package test\ntype MyPathParam struct{ProjectID int `param:\"projectId\"`\nName string `param:\"name\"`}",
ParseAll)
parser.packages.ParseTypes()
comment := `@Param anyWhat path test.MyPathParam true "Parameter"`
operation := NewOperation(parser)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "string",
"name": "name",
"in": "path"
},
{
"type": "integer",
"name": "projectId",
"in": "path"
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByID(t *testing.T) {
t.Parallel()
comment := `@Param unsafe_id[lte] query int true "Unsafe query param"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "integer",
"description": "Unsafe query param",
"name": "unsafe_id[lte]",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentWithMultilineDescriptions(t *testing.T) {
t.Parallel()
comment := `@Param some_id query int true "First line\nSecond line\nThird line"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "integer",
"description": "First line\nSecond line\nThird line",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByQueryType(t *testing.T) {
t.Parallel()
comment := `@Param some_id query int true "Some ID"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "integer",
"description": "Some ID",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByBodyType(t *testing.T) {
t.Parallel()
comment := `@Param some_id body model.OrderRow true "Some ID"`
operation := NewOperation(nil)
operation.parser.addTestType("model.OrderRow")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"description": "Some ID",
"name": "some_id",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/model.OrderRow"
}
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByBodyTextPlain(t *testing.T) {
t.Parallel()
comment := `@Param text body string true "Text to process"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"description": "Text to process",
"name": "text",
"in": "body",
"required": true,
"schema": {
"type": "string"
}
}
]`
assert.Equal(t, expected, string(b))
}
// TODO: fix this
func TestParseParamCommentByBodyEnumsText(t *testing.T) {
t.Parallel()
comment := `@Param text body string true "description" Enums(ENUM1, ENUM2, ENUM3)`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"description": "description",
"name": "text",
"in": "body",
"required": true,
"schema": {
"type": "string",
"enum": [
"ENUM1",
"ENUM2",
"ENUM3"
]
}
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByBodyTypeWithDeepNestedFields(t *testing.T) {
t.Parallel()
comment := `@Param body body model.CommonHeader{data=string,data2=int} true "test deep"`
operation := NewOperation(nil)
operation.parser.addTestType("model.CommonHeader")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Len(t, operation.Parameters, 1)
assert.Equal(t, "test deep", operation.Parameters[0].Description)
assert.True(t, operation.Parameters[0].Required)
b, err := json.MarshalIndent(operation.Parameters, "", " ")
assert.NoError(t, err)
expected := `[
{
"description": "test deep",
"name": "body",
"in": "body",
"required": true,
"schema": {
"allOf": [
{
"$ref": "#/definitions/model.CommonHeader"
},
{
"type": "object",
"properties": {
"data": {
"type": "string"
},
"data2": {
"type": "integer"
}
}
}
]
}
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByBodyTypeArrayOfPrimitiveGo(t *testing.T) {
t.Parallel()
comment := `@Param some_id body []int true "Some ID"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"description": "Some ID",
"name": "some_id",
"in": "body",
"required": true,
"schema": {
"type": "array",
"items": {
"type": "integer"
}
}
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByBodyTypeArrayOfPrimitiveGoWithDeepNestedFields(t *testing.T) {
t.Parallel()
comment := `@Param body body []model.CommonHeader{data=string,data2=int} true "test deep"`
operation := NewOperation(nil)
operation.parser.addTestType("model.CommonHeader")
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Len(t, operation.Parameters, 1)
assert.Equal(t, "test deep", operation.Parameters[0].Description)
assert.True(t, operation.Parameters[0].Required)
b, err := json.MarshalIndent(operation.Parameters, "", " ")
assert.NoError(t, err)
expected := `[
{
"description": "test deep",
"name": "body",
"in": "body",
"required": true,
"schema": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/definitions/model.CommonHeader"
},
{
"type": "object",
"properties": {
"data": {
"type": "string"
},
"data2": {
"type": "integer"
}
}
}
]
}
}
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByBodyTypeErr(t *testing.T) {
t.Parallel()
comment := `@Param some_id body model.OrderRow true "Some ID"`
operation := NewOperation(nil)
operation.parser.addTestType("model.notexist")
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestParseParamCommentByFormDataType(t *testing.T) {
t.Parallel()
comment := `@Param file formData file true "this is a test file"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "file",
"description": "this is a test file",
"name": "file",
"in": "formData",
"required": true
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByFormDataTypeUint64(t *testing.T) {
t.Parallel()
comment := `@Param file formData uint64 true "this is a test file"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "integer",
"format": "int64",
"description": "this is a test file",
"name": "file",
"in": "formData",
"required": true
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByNotSupportedType(t *testing.T) {
t.Parallel()
comment := `@Param some_id not_supported int true "Some ID"`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestParseParamCommentNotMatch(t *testing.T) {
t.Parallel()
comment := `@Param some_id body mock true`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestParseParamCommentByEnums(t *testing.T) {
t.Parallel()
comment := `@Param some_id query string true "Some ID" Enums(A, B, C)`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"enum": [
"A",
"B",
"C"
],
"type": "string",
"description": "Some ID",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
comment = `@Param some_id query int true "Some ID" Enums(1, 2, 3)`
operation = NewOperation(nil)
err = operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ = json.MarshalIndent(operation.Parameters, "", " ")
expected = `[
{
"enum": [
1,
2,
3
],
"type": "integer",
"description": "Some ID",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
comment = `@Param some_id query number true "Some ID" Enums(1.1, 2.2, 3.3)`
operation = NewOperation(nil)
err = operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ = json.MarshalIndent(operation.Parameters, "", " ")
expected = `[
{
"enum": [
1.1,
2.2,
3.3
],
"type": "number",
"description": "Some ID",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
comment = `@Param some_id query bool true "Some ID" Enums(true, false)`
operation = NewOperation(nil)
err = operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ = json.MarshalIndent(operation.Parameters, "", " ")
expected = `[
{
"enum": [
true,
false
],
"type": "boolean",
"description": "Some ID",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
operation = NewOperation(nil)
comment = `@Param some_id query int true "Some ID" Enums(A, B, C)`
assert.Error(t, operation.ParseComment(comment, nil))
comment = `@Param some_id query number true "Some ID" Enums(A, B, C)`
assert.Error(t, operation.ParseComment(comment, nil))
comment = `@Param some_id query boolean true "Some ID" Enums(A, B, C)`
assert.Error(t, operation.ParseComment(comment, nil))
comment = `@Param some_id query Document true "Some ID" Enums(A, B, C)`
assert.Error(t, operation.ParseComment(comment, nil))
}
func TestParseParamCommentByMaxLength(t *testing.T) {
t.Parallel()
comment := `@Param some_id query string true "Some ID" MaxLength(10)`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"maxLength": 10,
"type": "string",
"description": "Some ID",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
comment = `@Param some_id query int true "Some ID" MaxLength(10)`
assert.Error(t, operation.ParseComment(comment, nil))
comment = `@Param some_id query string true "Some ID" MaxLength(Goopher)`
assert.Error(t, operation.ParseComment(comment, nil))
}
func TestParseParamCommentByMinLength(t *testing.T) {
t.Parallel()
comment := `@Param some_id query string true "Some ID" MinLength(10)`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"minLength": 10,
"type": "string",
"description": "Some ID",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
comment = `@Param some_id query int true "Some ID" MinLength(10)`
assert.Error(t, operation.ParseComment(comment, nil))
comment = `@Param some_id query string true "Some ID" MinLength(Goopher)`
assert.Error(t, operation.ParseComment(comment, nil))
}
func TestParseParamCommentByMinimum(t *testing.T) {
t.Parallel()
comment := `@Param some_id query int true "Some ID" Minimum(10)`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"minimum": 10,
"type": "integer",
"description": "Some ID",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
comment = `@Param some_id query int true "Some ID" Mininum(10)`
assert.NoError(t, operation.ParseComment(comment, nil))
comment = `@Param some_id query string true "Some ID" Minimum(10)`
assert.Error(t, operation.ParseComment(comment, nil))
comment = `@Param some_id query integer true "Some ID" Minimum(Goopher)`
assert.Error(t, operation.ParseComment(comment, nil))
}
func TestParseParamCommentByMaximum(t *testing.T) {
t.Parallel()
comment := `@Param some_id query int true "Some ID" Maximum(10)`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"maximum": 10,
"type": "integer",
"description": "Some ID",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
comment = `@Param some_id query int true "Some ID" Maxinum(10)`
assert.NoError(t, operation.ParseComment(comment, nil))
comment = `@Param some_id query string true "Some ID" Maximum(10)`
assert.Error(t, operation.ParseComment(comment, nil))
comment = `@Param some_id query integer true "Some ID" Maximum(Goopher)`
assert.Error(t, operation.ParseComment(comment, nil))
}
func TestParseParamCommentByDefault(t *testing.T) {
t.Parallel()
comment := `@Param some_id query int true "Some ID" Default(10)`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "integer",
"default": 10,
"description": "Some ID",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByExampleInt(t *testing.T) {
t.Parallel()
comment := `@Param some_id query int true "Some ID" Example(10)`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "integer",
"example": 10,
"description": "Some ID",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByExampleString(t *testing.T) {
t.Parallel()
comment := `@Param some_id query string true "Some ID" Example(True feelings)`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "string",
"example": "True feelings",
"description": "Some ID",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByExampleStringComplex(t *testing.T) {
t.Parallel()
comment := `@Param some_id query string true "Some ID" Example(user_id.eq(1))`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "string",
"example": "user_id.eq(1)",
"description": "Some ID",
"name": "some_id",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentByExampleUnsupportedType(t *testing.T) {
t.Parallel()
var param spec.Parameter
setExample(¶m, "something", "random value")
assert.Equal(t, param.Example, nil)
setExample(¶m, STRING, "string value")
assert.Equal(t, param.Example, "string value")
setExample(¶m, INTEGER, "10")
assert.Equal(t, param.Example, 10)
setExample(¶m, NUMBER, "10")
assert.Equal(t, param.Example, float64(10))
}
func TestParseParamCommentBySchemaExampleString(t *testing.T) {
t.Parallel()
comment := `@Param some_id body string true "Some ID" SchemaExample(True feelings)`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"description": "Some ID",
"name": "some_id",
"in": "body",
"required": true,
"schema": {
"type": "string",
"example": "True feelings"
}
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentBySchemaExampleUnsupportedType(t *testing.T) {
t.Parallel()
var param spec.Parameter
setSchemaExample(¶m, "something", "random value")
assert.Nil(t, param.Schema)
setSchemaExample(¶m, STRING, "string value")
assert.Nil(t, param.Schema)
param.Schema = &spec.Schema{}
setSchemaExample(¶m, STRING, "string value")
assert.Equal(t, "string value", param.Schema.Example)
setSchemaExample(¶m, INTEGER, "10")
assert.Equal(t, 10, param.Schema.Example)
setSchemaExample(¶m, NUMBER, "10")
assert.Equal(t, float64(10), param.Schema.Example)
setSchemaExample(¶m, STRING, "string \\r\\nvalue")
assert.Equal(t, "string \r\nvalue", param.Schema.Example)
}
func TestParseParamArrayWithEnums(t *testing.T) {
t.Parallel()
comment := `@Param field query []string true "An enum collection" collectionFormat(csv) enums(also,valid)`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "array",
"items": {
"enum": [
"also",
"valid"
],
"type": "string"
},
"collectionFormat": "csv",
"description": "An enum collection",
"name": "field",
"in": "query",
"required": true
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseAndExtractionParamAttribute(t *testing.T) {
t.Parallel()
op := NewOperation(nil)
numberParam := spec.Parameter{}
err := op.parseParamAttribute(
" default(1) maximum(100) minimum(0) format(csv)",
"",
NUMBER,
"",
&numberParam,
)
assert.NoError(t, err)
assert.Equal(t, float64(0), *numberParam.Minimum)
assert.Equal(t, float64(100), *numberParam.Maximum)
assert.Equal(t, "csv", numberParam.SimpleSchema.Format)
assert.Equal(t, float64(1), numberParam.Default)
err = op.parseParamAttribute(" minlength(1)", "", NUMBER, "", nil)
assert.Error(t, err)
err = op.parseParamAttribute(" maxlength(1)", "", NUMBER, "", nil)
assert.Error(t, err)
stringParam := spec.Parameter{}
err = op.parseParamAttribute(
" default(test) maxlength(100) minlength(0) format(csv)",
"",
STRING,
"",
&stringParam,
)
assert.NoError(t, err)
assert.Equal(t, int64(0), *stringParam.MinLength)
assert.Equal(t, int64(100), *stringParam.MaxLength)
assert.Equal(t, "csv", stringParam.SimpleSchema.Format)
err = op.parseParamAttribute(" minimum(0)", "", STRING, "", nil)
assert.Error(t, err)
err = op.parseParamAttribute(" maximum(0)", "", STRING, "", nil)
assert.Error(t, err)
arrayParram := spec.Parameter{}
err = op.parseParamAttribute(" collectionFormat(tsv)", ARRAY, STRING, "", &arrayParram)
assert.Equal(t, "tsv", arrayParram.CollectionFormat)
assert.NoError(t, err)
err = op.parseParamAttribute(" collectionFormat(tsv)", STRING, STRING, "", nil)
assert.Error(t, err)
err = op.parseParamAttribute(" default(0)", "", ARRAY, "", nil)
assert.NoError(t, err)
}
func TestParseParamCommentByExtensions(t *testing.T) {
comment := `@Param some_id path int true "Some ID" extensions(x-example=test,x-custom=Goopher,x-custom2)`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation.Parameters, "", " ")
expected := `[
{
"type": "integer",
"x-custom": "Goopher",
"x-custom2": true,
"x-example": "test",
"description": "Some ID",
"name": "some_id",
"in": "path",
"required": true
}
]`
assert.Equal(t, expected, string(b))
}
func TestParseParamStructCodeExample(t *testing.T) {
t.Parallel()
fset := token.NewFileSet()
ast, err := goparser.ParseFile(fset, "operation_test.go", `package swag
import structs "github.com/swaggo/swag/testdata/param_structs"
`, goparser.ParseComments)
assert.NoError(t, err)
parser := New()
err = parser.parseFile("github.com/swaggo/swag/testdata/param_structs", "testdata/param_structs/structs.go", nil, ParseModels)
assert.NoError(t, err)
_, err = parser.packages.ParseTypes()
assert.NoError(t, err)
validateParameters := func(operation *Operation, params ...spec.Parameter) {
assert.Equal(t, len(params), len(operation.Parameters))
for _, param := range params {
found := false
for _, p := range operation.Parameters {
if p.Name == param.Name {
assert.Equal(t, param.ParamProps, p.ParamProps)
assert.Equal(t, param.CommonValidations, p.CommonValidations)
assert.Equal(t, param.SimpleSchema, p.SimpleSchema)
found = true
break
}
}
assert.True(t, found, "found parameter %s", param.Name)
}
}
// values used in validation checks
max := float64(10)
maxLen := int64(10)
min := float64(0)
// query and form behave the same
for _, param := range []string{"query", "formData"} {
t.Run(param+" struct", func(t *testing.T) {
operation := NewOperation(parser)
comment := fmt.Sprintf(`@Param model %s structs.FormModel true "query params"`, param)
err = operation.ParseComment(comment, ast)
assert.NoError(t, err)
validateParameters(operation,
spec.Parameter{
ParamProps: spec.ParamProps{
Name: "f",
Description: "",
In: param,
Required: true,
},
CommonValidations: spec.CommonValidations{
MaxLength: &maxLen,
},
SimpleSchema: spec.SimpleSchema{
Type: "string",
},
},
spec.Parameter{
ParamProps: spec.ParamProps{
Name: "b",
Description: "B is another field",
In: param,
},
SimpleSchema: spec.SimpleSchema{
Type: "boolean",
},
})
})
}
t.Run("header struct", func(t *testing.T) {
operation := NewOperation(parser)
comment := `@Param auth header structs.AuthHeader true "auth header"`
err = operation.ParseComment(comment, ast)
assert.NoError(t, err)
validateParameters(operation,
spec.Parameter{
ParamProps: spec.ParamProps{
Name: "X-Auth-Token",
Description: "Token is the auth token",
In: "header",
Required: true,
},
SimpleSchema: spec.SimpleSchema{
Type: "string",
},
}, spec.Parameter{
ParamProps: spec.ParamProps{
Name: "anotherHeader",
Description: "AnotherHeader is another header",
In: "header",
},
CommonValidations: spec.CommonValidations{
Maximum: &max,
Minimum: &min,
},
SimpleSchema: spec.SimpleSchema{
Type: "integer",
},
})
})
t.Run("path struct", func(t *testing.T) {
operation := NewOperation(parser)
comment := `@Param path path structs.PathModel true "path params"`
err = operation.ParseComment(comment, ast)
assert.NoError(t, err)
validateParameters(operation,
spec.Parameter{
ParamProps: spec.ParamProps{
Name: "id",
Description: "ID is the id",
In: "path",
Required: true,
},
SimpleSchema: spec.SimpleSchema{
Type: "integer",
},
}, spec.Parameter{
ParamProps: spec.ParamProps{
Name: "name",
Description: "",
In: "path",
},
CommonValidations: spec.CommonValidations{
MaxLength: &maxLen,
},
SimpleSchema: spec.SimpleSchema{
Type: "string",
},
})
})
}
func TestParseIdComment(t *testing.T) {
t.Parallel()
comment := `@Id myOperationId`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Equal(t, "myOperationId", operation.ID)
}
func TestFindTypeDefCoreLib(t *testing.T) {
t.Parallel()
s, err := findTypeDef("net/http", "Request")
assert.NoError(t, err)
assert.NotNil(t, s)
}
func TestFindTypeDefExternalPkg(t *testing.T) {
t.Parallel()
s, err := findTypeDef("github.com/KyleBanks/depth", "Tree")
assert.NoError(t, err)
assert.NotNil(t, s)
}
func TestFindTypeDefInvalidPkg(t *testing.T) {
t.Parallel()
s, err := findTypeDef("does-not-exist", "foo")
assert.Error(t, err)
assert.Nil(t, s)
}
func TestParseSecurityComment(t *testing.T) {
t.Parallel()
comment := `@Security OAuth2Implicit[read, write]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Equal(t, operation.Security, []map[string][]string{
{
"OAuth2Implicit": {"read", "write"},
},
})
}
func TestParseSecurityCommentSimple(t *testing.T) {
t.Parallel()
comment := `@Security ApiKeyAuth`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Equal(t, operation.Security, []map[string][]string{
{
"ApiKeyAuth": {},
},
})
}
func TestParseSecurityCommentAnd(t *testing.T) {
t.Parallel()
comment := `@Security OAuth2Implicit[read, write] && Firebase[]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
expect := []map[string][]string{
{
"OAuth2Implicit": {"read", "write"},
"Firebase": {""},
},
}
assert.Equal(t, operation.Security, expect)
oldVersionComment := `@Security OAuth2Implicit[read, write] || Firebase[]`
operation = NewOperation(nil)
err = operation.ParseComment(oldVersionComment, nil)
assert.NoError(t, err)
assert.Equal(t, operation.Security, expect)
}
func TestParseMultiDescription(t *testing.T) {
t.Parallel()
comment := `@Description line one`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
comment = `@Tags multi`
err = operation.ParseComment(comment, nil)
assert.NoError(t, err)
comment = `@Description line two x`
err = operation.ParseComment(comment, nil)
assert.NoError(t, err)
b, _ := json.MarshalIndent(operation, "", " ")
expected := `"description": "line one\nline two x"`
assert.Contains(t, string(b), expected)
}
func TestParseDescriptionMarkdown(t *testing.T) {
t.Parallel()
operation := NewOperation(nil)
operation.parser.markdownFileDir = "example/markdown"
comment := `@description.markdown admin.md`
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
comment = `@description.markdown missing.md`
err = operation.ParseComment(comment, nil)
assert.Error(t, err)
}
func TestParseSummary(t *testing.T) {
t.Parallel()
comment := `@summary line one`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
comment = `@Summary line one`
err = operation.ParseComment(comment, nil)
assert.NoError(t, err)
}
func TestParseDeprecationDescription(t *testing.T) {
t.Parallel()
comment := `@Deprecated`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
if !operation.Deprecated {
t.Error("Failed to parse @deprecated comment")
}
}
func TestParseExtentions(t *testing.T) {
t.Parallel()
// Fail if there are no args for attributes.
{
comment := `@x-amazon-apigateway-integration`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.EqualError(t, err, "annotation @x-amazon-apigateway-integration need a value")
}
// Fail if args of attributes are broken.
{
comment := `@x-amazon-apigateway-integration ["broken"}]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.EqualError(t, err, "annotation @x-amazon-apigateway-integration need a valid json value")
}
// OK
{
comment := `@x-amazon-apigateway-integration {"uri": "${some_arn}", "passthroughBehavior": "when_no_match", "httpMethod": "POST", "type": "aws_proxy"}`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Equal(t, operation.Extensions["x-amazon-apigateway-integration"],
map[string]interface{}{
"httpMethod": "POST",
"passthroughBehavior": "when_no_match",
"type": "aws_proxy",
"uri": "${some_arn}",
})
}
// Test x-tagGroups
{
comment := `@x-tagGroups [{"name":"Natural Persons","tags":["Person","PersonRisk","PersonDocuments"]}]`
operation := NewOperation(nil)
err := operation.ParseComment(comment, nil)
assert.NoError(t, err)
assert.Equal(t, operation.Extensions["x-tagGroups"],
[]interface{}{map[string]interface{}{
"name": "Natural Persons",
"tags": []interface{}{
"Person",
"PersonRisk",
"PersonDocuments",
},
}})
}
}
func TestFindInSlice(t *testing.T) {
t.Parallel()
assert.True(t, findInSlice([]string{"one", "two", "tree"}, "one"))
assert.True(t, findInSlice([]string{"tree", "two", "one"}, "one"))
assert.True(t, findInSlice([]string{"two", "one", "tree"}, "one"))
assert.False(t, findInSlice([]string{"one", "two", "tree"}, "four"))
}
func TestParseResponseHeaderComment(t *testing.T) {
t.Parallel()
operation := NewOperation(nil)
operation.Responses = &spec.Responses{}
err := operation.ParseResponseComment(`default {string} string "other error"`, nil)
assert.NoError(t, err)
err = operation.ParseResponseHeaderComment(`all {string} Token "qwerty"`, nil)
assert.NoError(t, err)
}
func TestParseObjectSchema(t *testing.T) {
t.Parallel()
operation := NewOperation(nil)
schema, err := operation.parseObjectSchema("interface{}", nil)
assert.NoError(t, err)
assert.Equal(t, schema, &spec.Schema{})
schema, err = operation.parseObjectSchema("any", nil)
assert.NoError(t, err)
assert.Equal(t, schema, &spec.Schema{})
schema, err = operation.parseObjectSchema("any{data=string}", nil)
assert.NoError(t, err)
assert.Equal(t, schema,
(&spec.Schema{}).WithAllOf(spec.Schema{}, *PrimitiveSchema(OBJECT).SetProperty("data", *PrimitiveSchema("string"))))
schema, err = operation.parseObjectSchema("int", nil)
assert.NoError(t, err)
assert.Equal(t, schema, PrimitiveSchema(INTEGER))
schema, err = operation.parseObjectSchema("[]string", nil)
assert.NoError(t, err)
assert.Equal(t, schema, spec.ArrayProperty(PrimitiveSchema(STRING)))
schema, err = operation.parseObjectSchema("[]int", nil)
assert.NoError(t, err)
assert.Equal(t, schema, spec.ArrayProperty(PrimitiveSchema(INTEGER)))
_, err = operation.parseObjectSchema("[]bleah", nil)
assert.Error(t, err)
schema, err = operation.parseObjectSchema("map[]string", nil)
assert.NoError(t, err)
assert.Equal(t, schema, spec.MapProperty(PrimitiveSchema(STRING)))
schema, err = operation.parseObjectSchema("map[]int", nil)
assert.NoError(t, err)
assert.Equal(t, schema, spec.MapProperty(PrimitiveSchema(INTEGER)))
schema, err = operation.parseObjectSchema("map[]interface{}", nil)
assert.NoError(t, err)
assert.Equal(t, schema, spec.MapProperty(nil))
_, err = operation.parseObjectSchema("map[string", nil)
assert.Error(t, err)
_, err = operation.parseObjectSchema("map[]bleah", nil)
assert.Error(t, err)
operation.parser = New()
operation.parser.packages = &PackagesDefinitions{
uniqueDefinitions: map[string]*TypeSpecDef{
"model.User": {
File: &ast.File{
Name: &ast.Ident{
Name: "user.go",
},
},
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{
Name: "User",
},
},
},
},
}
_, err = operation.parseObjectSchema("model.User", nil)
assert.NoError(t, err)
operation.parser = nil
schema, err = operation.parseObjectSchema("user.Model", nil)
assert.NoError(t, err)
assert.Equal(t, schema, RefSchema("user.Model"))
}
func TestParseCodeSamples(t *testing.T) {
t.Parallel()
const comment = `@x-codeSamples file`
t.Run("Find sample by file", func(t *testing.T) {
operation := NewOperation(nil, SetCodeExampleFilesDirectory("testdata/code_examples"))
operation.Summary = "example"
err := operation.ParseComment(comment, nil)
assert.NoError(t, err, "no error should be thrown")
assert.Equal(t, operation.Summary, "example")
assert.Equal(t, operation.Extensions["x-codeSamples"],
map[string]interface{}{"lang": "JavaScript", "source": "console.log('Hello World');"})
})
t.Run("With broken file sample", func(t *testing.T) {
operation := NewOperation(nil, SetCodeExampleFilesDirectory("testdata/code_examples"))
operation.Summary = "broken"
err := operation.ParseComment(comment, nil)
assert.Error(t, err, "no error should be thrown")
})
t.Run("Example file not found", func(t *testing.T) {
operation := NewOperation(nil, SetCodeExampleFilesDirectory("testdata/code_examples"))
operation.Summary = "badExample"
err := operation.ParseComment(comment, nil)
assert.Error(t, err, "error was expected, as file does not exist")
})
t.Run("Without line reminder", func(t *testing.T) {
comment := `@x-codeSamples`
operation := NewOperation(nil, SetCodeExampleFilesDirectory("testdata/code_examples"))
operation.Summary = "example"
err := operation.ParseComment(comment, nil)
assert.Error(t, err, "no error should be thrown")
})
t.Run(" broken dir", func(t *testing.T) {
operation := NewOperation(nil, SetCodeExampleFilesDirectory("testdata/fake_examples"))
operation.Summary = "code"
err := operation.ParseComment(comment, nil)
assert.Error(t, err, "no error should be thrown")
})
}
func TestParseDeprecatedRouter(t *testing.T) {
p := New()
searchDir := "./testdata/deprecated_router"
if err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth); err != nil {
t.Error("Failed to parse api: " + err.Error())
}
b, _ := json.MarshalIndent(p.swagger, "", " ")
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
assert.Equal(t, expected, b)
}
================================================
FILE: package.go
================================================
package swag
import (
"go/ast"
"go/token"
"reflect"
"strconv"
"golang.org/x/tools/go/packages"
)
// PackageDefinitions files and definition in a package.
type PackageDefinitions struct {
// files in this package, map key is file's relative path starting package path
Files map[string]*ast.File
// definitions in this package, map key is typeName
TypeDefinitions map[string]*TypeSpecDef
// const variables in this package, map key is the name
ConstTable map[string]*ConstVariable
// const variables in order in this package
OrderedConst []*ConstVariable
// package name
Name string
// package path
Path string
Package *packages.Package
}
// ConstVariableGlobalEvaluator an interface used to evaluate enums across packages
type ConstVariableGlobalEvaluator interface {
EvaluateConstValue(pkg *PackageDefinitions, cv *ConstVariable, recursiveStack map[string]struct{}) (any, ast.Expr)
EvaluateConstValueByName(file *ast.File, pkgPath, constVariableName string, recursiveStack map[string]struct{}) (any, ast.Expr)
FindTypeSpec(typeName string, file *ast.File) *TypeSpecDef
}
// NewPackageDefinitions new a PackageDefinitions object
func NewPackageDefinitions(name, pkgPath string) *PackageDefinitions {
return &PackageDefinitions{
Name: name,
Path: pkgPath,
Files: make(map[string]*ast.File),
TypeDefinitions: make(map[string]*TypeSpecDef),
ConstTable: make(map[string]*ConstVariable),
}
}
// AddFile add a file
func (pkg *PackageDefinitions) AddFile(pkgPath string, file *ast.File) *PackageDefinitions {
pkg.Files[pkgPath] = file
return pkg
}
// AddTypeSpec add a type spec.
func (pkg *PackageDefinitions) AddTypeSpec(name string, typeSpec *TypeSpecDef) *PackageDefinitions {
pkg.TypeDefinitions[name] = typeSpec
return pkg
}
// AddConst add a const variable.
func (pkg *PackageDefinitions) AddConst(astFile *ast.File, valueSpec *ast.ValueSpec) *PackageDefinitions {
for i := 0; i < len(valueSpec.Names) && i < len(valueSpec.Values); i++ {
variable := &ConstVariable{
Name: valueSpec.Names[i],
Type: valueSpec.Type,
Value: valueSpec.Values[i],
File: astFile,
}
//take the nearest line as comment from comment list or doc list. comment list first.
if valueSpec.Comment != nil && len(valueSpec.Comment.List) > 0 {
variable.Comment = valueSpec.Comment.List[0].Text
} else if valueSpec.Doc != nil && len(valueSpec.Doc.List) > 0 {
variable.Comment = valueSpec.Doc.List[len(valueSpec.Doc.List)-1].Text
}
pkg.ConstTable[valueSpec.Names[i].Name] = variable
pkg.OrderedConst = append(pkg.OrderedConst, variable)
}
return pkg
}
func (pkg *PackageDefinitions) evaluateConstValue(file *ast.File, iota int, expr ast.Expr, globalEvaluator ConstVariableGlobalEvaluator, recursiveStack map[string]struct{}) (any, ast.Expr) {
switch valueExpr := expr.(type) {
case *ast.Ident:
if valueExpr.Name == "iota" {
return iota, nil
}
if pkg.ConstTable != nil {
if cv, ok := pkg.ConstTable[valueExpr.Name]; ok {
return globalEvaluator.EvaluateConstValue(pkg, cv, recursiveStack)
}
}
case *ast.SelectorExpr:
pkgIdent, ok := valueExpr.X.(*ast.Ident)
if !ok {
return nil, nil
}
return globalEvaluator.EvaluateConstValueByName(file, pkgIdent.Name, valueExpr.Sel.Name, recursiveStack)
case *ast.BasicLit:
switch valueExpr.Kind {
case token.INT:
//a basic literal integer is int type in default, or must have an explicit converting type in front
if x, err := strconv.ParseInt(valueExpr.Value, 0, 64); err == nil {
return int(x), nil
} else if x, err := strconv.ParseUint(valueExpr.Value, 0, 64); err == nil {
return x, nil
} else {
panic(err)
}
case token.STRING:
if valueExpr.Value[0] == '`' {
return valueExpr.Value[1 : len(valueExpr.Value)-1], nil
}
return EvaluateEscapedString(valueExpr.Value[1 : len(valueExpr.Value)-1]), nil
case token.CHAR:
return EvaluateEscapedChar(valueExpr.Value[1 : len(valueExpr.Value)-1]), nil
}
case *ast.UnaryExpr:
x, evalType := pkg.evaluateConstValue(file, iota, valueExpr.X, globalEvaluator, recursiveStack)
if x == nil {
return x, evalType
}
return EvaluateUnary(x, valueExpr.Op, evalType)
case *ast.BinaryExpr:
x, evalTypex := pkg.evaluateConstValue(file, iota, valueExpr.X, globalEvaluator, recursiveStack)
y, evalTypey := pkg.evaluateConstValue(file, iota, valueExpr.Y, globalEvaluator, recursiveStack)
if x == nil || y == nil {
return nil, nil
}
return EvaluateBinary(x, y, valueExpr.Op, evalTypex, evalTypey)
case *ast.ParenExpr:
return pkg.evaluateConstValue(file, iota, valueExpr.X, globalEvaluator, recursiveStack)
case *ast.CallExpr:
//data conversion
if len(valueExpr.Args) != 1 {
return nil, nil
}
arg := valueExpr.Args[0]
if ident, ok := valueExpr.Fun.(*ast.Ident); ok {
name := ident.Name
if name == "uintptr" {
name = "uint"
}
value, _ := pkg.evaluateConstValue(file, iota, arg, globalEvaluator, recursiveStack)
if IsGolangPrimitiveType(name) {
value = EvaluateDataConversion(value, name)
return value, nil
} else if name == "len" {
return reflect.ValueOf(value).Len(), nil
}
typeDef := globalEvaluator.FindTypeSpec(name, file)
if typeDef == nil {
return nil, nil
}
return value, valueExpr.Fun
} else if selector, ok := valueExpr.Fun.(*ast.SelectorExpr); ok {
typeDef := globalEvaluator.FindTypeSpec(fullTypeName(selector.X.(*ast.Ident).Name, selector.Sel.Name), file)
if typeDef == nil {
return nil, nil
}
return arg, typeDef.TypeSpec.Type
}
}
return nil, nil
}
================================================
FILE: packages.go
================================================
package swag
import (
"fmt"
"go/ast"
"go/constant"
goparser "go/parser"
"go/token"
"go/types"
"os"
"path/filepath"
"runtime"
"slices"
"sort"
"strings"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/packages"
)
// PackagesDefinitions map[package import path]*PackageDefinitions.
type PackagesDefinitions struct {
files map[*ast.File]*AstFileInfo
packages map[string]*PackageDefinitions
uniqueDefinitions map[string]*TypeSpecDef
parseDependency ParseFlag
debug Debugger
}
// NewPackagesDefinitions create object PackagesDefinitions.
func NewPackagesDefinitions() *PackagesDefinitions {
return &PackagesDefinitions{
files: make(map[*ast.File]*AstFileInfo),
packages: make(map[string]*PackageDefinitions),
uniqueDefinitions: make(map[string]*TypeSpecDef),
}
}
// AddPackages store packages.Package to PackagesDefinitions.
func (pkgDefs *PackagesDefinitions) AddPackages(pkgs []*packages.Package) {
for _, pkg := range pkgs {
pkgDef, ok := pkgDefs.packages[pkg.PkgPath]
if !ok {
continue
}
if pkgDef.Package != nil {
continue
}
pkgDef.Package = pkg
imports := make([]*packages.Package, 0, len(pkg.Imports))
for _, dep := range pkg.Imports {
imports = append(imports, dep)
}
pkgDefs.AddPackages(imports)
}
}
// ParseFile parse a source file.
func (pkgDefs *PackagesDefinitions) ParseFile(packageDir, path string, src any, flag ParseFlag) error {
// positions are relative to FileSet
fileSet := token.NewFileSet()
astFile, err := goparser.ParseFile(fileSet, path, src, goparser.ParseComments)
if err != nil {
return fmt.Errorf("failed to parse file %s, error:%+v", path, err)
}
return pkgDefs.CollectAstFile(fileSet, packageDir, path, astFile, flag)
}
// CollectAstFile collect ast.file.
func (pkgDefs *PackagesDefinitions) CollectAstFile(fileSet *token.FileSet, packageDir, path string, astFile *ast.File, flag ParseFlag) error {
if pkgDefs.files == nil {
pkgDefs.files = make(map[*ast.File]*AstFileInfo)
}
if pkgDefs.packages == nil {
pkgDefs.packages = make(map[string]*PackageDefinitions)
}
// return without storing the file if we lack a packageDir
if packageDir == "" {
return nil
}
path, err := filepath.Abs(path)
if err != nil {
return err
}
dependency, ok := pkgDefs.packages[packageDir]
if ok {
// return without storing the file if it already exists
_, exists := dependency.Files[path]
if exists {
return nil
}
dependency.Files[path] = astFile
} else {
pkgDefs.packages[packageDir] = NewPackageDefinitions(astFile.Name.Name, packageDir).AddFile(path, astFile)
}
pkgDefs.files[astFile] = &AstFileInfo{
FileSet: fileSet,
File: astFile,
Path: path,
PackagePath: packageDir,
ParseFlag: flag,
}
return nil
}
// RangeFiles for range the collection of ast.File in alphabetic order.
func (pkgDefs *PackagesDefinitions) RangeFiles(handle func(info *AstFileInfo) error) error {
sortedFiles := make([]*AstFileInfo, 0, len(pkgDefs.files))
for _, info := range pkgDefs.files {
// ignore package path prefix with 'vendor' or $GOROOT,
// because the router info of api will not be included these files.
if strings.HasPrefix(info.PackagePath, "vendor") || (runtime.GOROOT() != "" && strings.HasPrefix(info.Path, runtime.GOROOT()+string(filepath.Separator))) {
continue
}
sortedFiles = append(sortedFiles, info)
}
sort.Slice(sortedFiles, func(i, j int) bool {
return strings.Compare(sortedFiles[i].Path, sortedFiles[j].Path) < 0
})
for _, info := range sortedFiles {
err := handle(info)
if err != nil {
return err
}
}
return nil
}
// ParseTypes parse types
// @Return parsed definitions.
func (pkgDefs *PackagesDefinitions) ParseTypes() (map[*TypeSpecDef]*Schema, error) {
parsedSchemas := make(map[*TypeSpecDef]*Schema)
for astFile, info := range pkgDefs.files {
pkgDefs.parseTypesFromFile(astFile, info.PackagePath, parsedSchemas)
pkgDefs.parseFunctionScopedTypesFromFile(astFile, info.PackagePath, parsedSchemas)
}
pkgDefs.removeAllNotUniqueTypes()
pkgDefs.evaluateAllConstVariables()
pkgDefs.collectConstEnums(parsedSchemas)
return parsedSchemas, nil
}
func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packagePath string, parsedSchemas map[*TypeSpecDef]*Schema) {
for _, astDeclaration := range astFile.Decls {
generalDeclaration, ok := astDeclaration.(*ast.GenDecl)
if !ok {
continue
}
if generalDeclaration.Tok == token.TYPE {
for _, astSpec := range generalDeclaration.Specs {
if typeSpec, ok := astSpec.(*ast.TypeSpec); ok {
typeSpecDef := &TypeSpecDef{
PkgPath: packagePath,
File: astFile,
TypeSpec: typeSpec,
}
if idt, ok := typeSpec.Type.(*ast.Ident); ok && IsGolangPrimitiveType(idt.Name) && parsedSchemas != nil {
parsedSchemas[typeSpecDef] = &Schema{
PkgPath: typeSpecDef.PkgPath,
Name: astFile.Name.Name,
Schema: TransToValidPrimitiveSchema(idt.Name),
}
}
if pkgDefs.uniqueDefinitions == nil {
pkgDefs.uniqueDefinitions = make(map[string]*TypeSpecDef)
}
fullName := typeSpecDef.TypeName()
anotherTypeDef, ok := pkgDefs.uniqueDefinitions[fullName]
if ok {
if anotherTypeDef == nil {
typeSpecDef.NotUnique = true
fullName = typeSpecDef.TypeName()
pkgDefs.uniqueDefinitions[fullName] = typeSpecDef
} else if typeSpecDef.PkgPath != anotherTypeDef.PkgPath {
pkgDefs.uniqueDefinitions[fullName] = nil
anotherTypeDef.NotUnique = true
pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef
anotherTypeDef.SetSchemaName()
typeSpecDef.NotUnique = true
fullName = typeSpecDef.TypeName()
pkgDefs.uniqueDefinitions[fullName] = typeSpecDef
}
} else {
pkgDefs.uniqueDefinitions[fullName] = typeSpecDef
}
typeSpecDef.SetSchemaName()
if pkgDefs.packages[typeSpecDef.PkgPath] == nil {
pkgDefs.packages[typeSpecDef.PkgPath] = NewPackageDefinitions(astFile.Name.Name, typeSpecDef.PkgPath).AddTypeSpec(typeSpecDef.Name(), typeSpecDef)
} else if _, ok = pkgDefs.packages[typeSpecDef.PkgPath].TypeDefinitions[typeSpecDef.Name()]; !ok {
pkgDefs.packages[typeSpecDef.PkgPath].AddTypeSpec(typeSpecDef.Name(), typeSpecDef)
}
}
}
} else if generalDeclaration.Tok == token.CONST {
// collect consts
pkgDefs.collectConstVariables(astFile, packagePath, generalDeclaration)
}
}
}
func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *ast.File, packagePath string, parsedSchemas map[*TypeSpecDef]*Schema) {
for _, astDeclaration := range astFile.Decls {
funcDeclaration, ok := astDeclaration.(*ast.FuncDecl)
if ok && funcDeclaration.Body != nil {
functionScopedTypes := make(map[string]*TypeSpecDef)
for _, stmt := range funcDeclaration.Body.List {
if declStmt, ok := (stmt).(*ast.DeclStmt); ok {
if genDecl, ok := (declStmt.Decl).(*ast.GenDecl); ok && genDecl.Tok == token.TYPE {
for _, astSpec := range genDecl.Specs {
if typeSpec, ok := astSpec.(*ast.TypeSpec); ok {
typeSpecDef := &TypeSpecDef{
PkgPath: packagePath,
File: astFile,
TypeSpec: typeSpec,
ParentSpec: astDeclaration,
}
if idt, ok := typeSpec.Type.(*ast.Ident); ok && IsGolangPrimitiveType(idt.Name) && parsedSchemas != nil {
parsedSchemas[typeSpecDef] = &Schema{
PkgPath: typeSpecDef.PkgPath,
Name: astFile.Name.Name,
Schema: TransToValidPrimitiveSchema(idt.Name),
}
}
fullName := typeSpecDef.TypeName()
if structType, ok := typeSpecDef.TypeSpec.Type.(*ast.StructType); ok {
for _, field := range structType.Fields.List {
var idt *ast.Ident
var ok bool
switch field.Type.(type) {
case *ast.Ident:
idt, ok = field.Type.(*ast.Ident)
case *ast.StarExpr:
idt, ok = field.Type.(*ast.StarExpr).X.(*ast.Ident)
case *ast.ArrayType:
idt, ok = field.Type.(*ast.ArrayType).Elt.(*ast.Ident)
}
if ok && !IsGolangPrimitiveType(idt.Name) {
if functype, ok := functionScopedTypes[idt.Name]; ok {
idt.Name = functype.TypeName()
}
}
}
}
if pkgDefs.uniqueDefinitions == nil {
pkgDefs.uniqueDefinitions = make(map[string]*TypeSpecDef)
}
anotherTypeDef, ok := pkgDefs.uniqueDefinitions[fullName]
if ok {
if anotherTypeDef == nil {
typeSpecDef.NotUnique = true
fullName = typeSpecDef.TypeName()
pkgDefs.uniqueDefinitions[fullName] = typeSpecDef
} else if typeSpecDef.PkgPath != anotherTypeDef.PkgPath {
pkgDefs.uniqueDefinitions[fullName] = nil
anotherTypeDef.NotUnique = true
pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef
anotherTypeDef.SetSchemaName()
typeSpecDef.NotUnique = true
fullName = typeSpecDef.TypeName()
pkgDefs.uniqueDefinitions[fullName] = typeSpecDef
}
} else {
pkgDefs.uniqueDefinitions[fullName] = typeSpecDef
functionScopedTypes[typeSpec.Name.Name] = typeSpecDef
}
typeSpecDef.SetSchemaName()
if pkgDefs.packages[typeSpecDef.PkgPath] == nil {
pkgDefs.packages[typeSpecDef.PkgPath] = NewPackageDefinitions(astFile.Name.Name, typeSpecDef.PkgPath).AddTypeSpec(fullName, typeSpecDef)
} else if _, ok = pkgDefs.packages[typeSpecDef.PkgPath].TypeDefinitions[fullName]; !ok {
pkgDefs.packages[typeSpecDef.PkgPath].AddTypeSpec(fullName, typeSpecDef)
}
}
}
}
}
}
}
}
}
func (pkgDefs *PackagesDefinitions) collectConstVariables(astFile *ast.File, packagePath string, generalDeclaration *ast.GenDecl) {
pkg, ok := pkgDefs.packages[packagePath]
if !ok {
pkg = NewPackageDefinitions(astFile.Name.Name, packagePath)
pkgDefs.packages[packagePath] = pkg
}
var lastValueSpec *ast.ValueSpec
for _, astSpec := range generalDeclaration.Specs {
valueSpec, ok := astSpec.(*ast.ValueSpec)
if !ok {
continue
}
if len(valueSpec.Names) == 1 && len(valueSpec.Values) == 1 {
lastValueSpec = valueSpec
} else if len(valueSpec.Names) == 1 && len(valueSpec.Values) == 0 && valueSpec.Type == nil && lastValueSpec != nil {
valueSpec.Type = lastValueSpec.Type
valueSpec.Values = lastValueSpec.Values
}
pkg.AddConst(astFile, valueSpec)
}
}
func (pkgDefs *PackagesDefinitions) evaluateAllConstVariables() {
for _, pkg := range pkgDefs.packages {
for _, constVar := range pkg.OrderedConst {
pkgDefs.EvaluateConstValue(pkg, constVar, nil)
}
}
}
func findFileInPackageByObject(pkg *packages.Package, obj types.Object) *ast.File {
return findFileInPackageByPos(pkg, obj.Pos())
}
func findFileInPackageByPos(pkg *packages.Package, pos token.Pos) *ast.File {
filename := pkg.Fset.Position(pos).Filename
for i, file := range pkg.CompiledGoFiles {
if file == filename {
return pkg.Syntax[i]
}
}
return nil
}
func findAstNodeInPackage(pkg *packages.Package, obj types.Object) (*ast.ValueSpec, *ast.GenDecl) {
file := findFileInPackageByObject(pkg, obj)
if file == nil {
return nil, nil
}
var found *ast.ValueSpec
var parent *ast.GenDecl
ast.Inspect(file, func(node ast.Node) bool {
if found != nil || node == nil {
return false
}
genDecl, ok := node.(*ast.GenDecl)
if !ok {
return true
}
i := slices.IndexFunc(genDecl.Specs, func(spec ast.Spec) bool {
return spec.Pos() == obj.Pos()
})
if i >= 0 {
found = genDecl.Specs[i].(*ast.ValueSpec)
parent = genDecl
return false
}
return true
})
return found, parent
}
func tryParseTypeFromPackage(pkg *packages.Package, constObj *types.Const) ast.Expr {
spec, parent := findAstNodeInPackage(pkg, constObj)
if spec == nil {
return nil
}
if spec.Type != nil {
return spec.Type
}
typeObj := constObj.Type()
for _, otherSpec := range parent.Specs {
if otherSpec == spec {
continue
}
declSpec, ok := otherSpec.(*ast.ValueSpec)
if !ok {
continue
}
if declSpec.Type == nil {
continue
}
otherType := pkg.TypesInfo.TypeOf(declSpec.Names[0])
if otherType == typeObj {
return declSpec.Type
}
}
return nil
}
// EvaluateConstValue evaluate a const variable.
func (pkgDefs *PackagesDefinitions) EvaluateConstValue(pkg *PackageDefinitions, cv *ConstVariable, recursiveStack map[string]struct{}) (any, ast.Expr) {
if pkg.Package != nil {
obj := pkg.Package.Types.Scope().Lookup(cv.Name.Name)
if obj != nil {
if constObj, ok := obj.(*types.Const); ok {
cv.Value = constant.Val(constObj.Val())
if cv.Type == nil {
cv.Type = tryParseTypeFromPackage(pkg.Package, constObj)
}
return cv.Value, cv.Type
}
}
}
if expr, ok := cv.Value.(ast.Expr); ok {
defer func() {
if err := recover(); err != nil {
if fi, ok := pkgDefs.files[cv.File]; ok {
pos := fi.FileSet.Position(cv.Name.NamePos)
pkgDefs.debug.Printf("warning: failed to evaluate const %s at %s:%d:%d, %v", cv.Name.Name, fi.Path, pos.Line, pos.Column, err)
}
}
}()
if recursiveStack == nil {
recursiveStack = make(map[string]struct{})
}
fullConstName := fullTypeName(pkg.Path, cv.Name.Name)
if _, ok = recursiveStack[fullConstName]; ok {
return nil, nil
}
recursiveStack[fullConstName] = struct{}{}
value, evalType := pkg.evaluateConstValue(cv.File, cv.Name.Obj.Data.(int), expr, pkgDefs, recursiveStack)
if cv.Type == nil && evalType != nil {
cv.Type = evalType
}
if value != nil {
cv.Value = value
}
return value, cv.Type
}
return cv.Value, cv.Type
}
// EvaluateConstValueByName evaluate a const variable by name.
func (pkgDefs *PackagesDefinitions) EvaluateConstValueByName(file *ast.File, pkgName, constVariableName string, recursiveStack map[string]struct{}) (any, ast.Expr) {
matchedPkgPaths, externalPkgPaths := pkgDefs.findPackagePathFromImports(pkgName, file)
for _, pkgPath := range matchedPkgPaths {
if pkg, ok := pkgDefs.packages[pkgPath]; ok {
if cv, ok := pkg.ConstTable[constVariableName]; ok {
return pkgDefs.EvaluateConstValue(pkg, cv, recursiveStack)
}
}
}
if pkgDefs.parseDependency > 0 {
for _, pkgPath := range externalPkgPaths {
if err := pkgDefs.loadExternalPackage(pkgPath); err == nil {
if pkg, ok := pkgDefs.packages[pkgPath]; ok {
if cv, ok := pkg.ConstTable[constVariableName]; ok {
return pkgDefs.EvaluateConstValue(pkg, cv, recursiveStack)
}
}
}
}
}
return nil, nil
}
func (pkgDefs *PackagesDefinitions) collectConstEnums(parsedSchemas map[*TypeSpecDef]*Schema) {
for _, pkg := range pkgDefs.packages {
for _, constVar := range pkg.OrderedConst {
if constVar.Type == nil {
continue
}
var (
ident *ast.Ident
ok bool
)
switch expr := constVar.Type.(type) {
case *ast.IndexExpr:
ident, ok = expr.X.(*ast.Ident)
case *ast.IndexListExpr:
ident, ok = expr.X.(*ast.Ident)
case *ast.Ident:
ident = expr
ok = true
default:
continue
}
if !ok || IsGolangPrimitiveType(ident.Name) {
continue
}
typeDef, ok := pkg.TypeDefinitions[ident.Name]
if !ok {
continue
}
// delete it from parsed schemas, and will parse it again
if _, ok = parsedSchemas[typeDef]; ok {
delete(parsedSchemas, typeDef)
}
if typeDef.Enums == nil {
typeDef.Enums = make([]EnumValue, 0)
}
name := constVar.VariableName()
if _, ok = constVar.Value.(ast.Expr); ok {
continue
}
enumValue := EnumValue{
key: name,
Value: constVar.Value,
Comment: commentWithoutNameOverride(constVar.Comment),
}
typeDef.Enums = append(typeDef.Enums, enumValue)
}
}
}
func (pkgDefs *PackagesDefinitions) removeAllNotUniqueTypes() {
for key, ud := range pkgDefs.uniqueDefinitions {
if ud == nil {
delete(pkgDefs.uniqueDefinitions, key)
}
}
}
func (pkgDefs *PackagesDefinitions) findTypeSpec(pkgPath string, typeName string) *TypeSpecDef {
if pkgDefs.packages == nil {
return nil
}
pd, found := pkgDefs.packages[pkgPath]
if found {
typeSpec, ok := pd.TypeDefinitions[typeName]
if ok {
return typeSpec
}
}
return nil
}
func (pkgDefs *PackagesDefinitions) loadExternalPackage(importPath string) error {
cwd, err := os.Getwd()
if err != nil {
return err
}
conf := loader.Config{
ParserMode: goparser.ParseComments,
Cwd: cwd,
}
conf.Import(importPath)
loaderProgram, err := conf.Load()
if err != nil {
return err
}
for _, info := range loaderProgram.AllPackages {
pkgPath := strings.TrimPrefix(info.Pkg.Path(), "vendor/")
for _, astFile := range info.Files {
pkgDefs.parseTypesFromFile(astFile, pkgPath, nil)
}
}
return nil
}
// findPackagePathFromImports finds out the package path of a package via ranging imports of an ast.File
// @pkg the name of the target package
// @file current ast.File in which to search imports
// @return the package paths of a package of @pkg.
func (pkgDefs *PackagesDefinitions) findPackagePathFromImports(pkg string, file *ast.File) (matchedPkgPaths, externalPkgPaths []string) {
if file == nil {
return
}
if strings.ContainsRune(pkg, '.') {
pkg = strings.Split(pkg, ".")[0]
}
matchLastPathPart := func(pkgPath string) bool {
paths := strings.Split(pkgPath, "/")
return paths[len(paths)-1] == pkg
}
// prior to match named package
for _, imp := range file.Imports {
path := strings.Trim(imp.Path.Value, `"`)
if imp.Name != nil {
if imp.Name.Name == pkg {
// if name match, break loop and return
_, ok := pkgDefs.packages[path]
if ok {
matchedPkgPaths = []string{path}
externalPkgPaths = nil
} else {
externalPkgPaths = []string{path}
matchedPkgPaths = nil
}
break
} else if imp.Name.Name == "_" && len(pkg) > 0 {
// for unused types
pd, ok := pkgDefs.packages[path]
if ok {
if pd.Name == pkg {
matchedPkgPaths = append(matchedPkgPaths, path)
}
} else if matchLastPathPart(path) {
externalPkgPaths = append(externalPkgPaths, path)
}
} else if imp.Name.Name == "." && len(pkg) == 0 {
_, ok := pkgDefs.packages[path]
if ok {
matchedPkgPaths = append(matchedPkgPaths, path)
} else if len(pkg) == 0 || matchLastPathPart(path) {
externalPkgPaths = append(externalPkgPaths, path)
}
}
} else if pkgDefs.packages != nil && len(pkg) > 0 {
pd, ok := pkgDefs.packages[path]
if ok {
if pd.Name == pkg {
matchedPkgPaths = append(matchedPkgPaths, path)
}
} else if matchLastPathPart(path) {
externalPkgPaths = append(externalPkgPaths, path)
}
}
}
if len(pkg) == 0 || file.Name.Name == pkg {
matchedPkgPaths = append(matchedPkgPaths, pkgDefs.files[file].PackagePath)
}
return
}
func (pkgDefs *PackagesDefinitions) findTypeSpecFromPackagePaths(matchedPkgPaths, externalPkgPaths []string, name string) (typeDef *TypeSpecDef) {
if pkgDefs.parseDependency > 0 {
for _, pkgPath := range externalPkgPaths {
if err := pkgDefs.loadExternalPackage(pkgPath); err == nil {
typeDef = pkgDefs.findTypeSpec(pkgPath, name)
if typeDef != nil {
return typeDef
}
}
}
}
for _, pkgPath := range matchedPkgPaths {
typeDef = pkgDefs.findTypeSpec(pkgPath, name)
if typeDef != nil {
return typeDef
}
}
return typeDef
}
// FindTypeSpec finds out TypeSpecDef of a type by typeName
// @typeName the name of the target type, if it starts with a package name, find its own package path from imports on top of @file
// @file the ast.file in which @typeName is used
// @pkgPath the package path of @file.
func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File) *TypeSpecDef {
if IsGolangPrimitiveType(typeName) {
return nil
}
if file == nil { // for test
return pkgDefs.uniqueDefinitions[typeName]
}
parts := strings.Split(strings.Split(typeName, "[")[0], ".")
if len(parts) > 1 {
pkgPaths, externalPkgPaths := pkgDefs.findPackagePathFromImports(parts[0], file)
if len(externalPkgPaths) == 0 || pkgDefs.parseDependency == ParseNone {
typeDef, ok := pkgDefs.uniqueDefinitions[typeName]
if ok {
return typeDef
}
}
typeDef := pkgDefs.findTypeSpecFromPackagePaths(pkgPaths, externalPkgPaths, parts[1])
return pkgDefs.parametrizeGenericType(file, typeDef, typeName)
}
typeDef, ok := pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, typeName)]
if ok {
return typeDef
}
name := parts[0]
typeDef, ok = pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, name)]
if !ok {
pkgPaths, externalPkgPaths := pkgDefs.findPackagePathFromImports("", file)
typeDef = pkgDefs.findTypeSpecFromPackagePaths(pkgPaths, externalPkgPaths, name)
}
if typeDef != nil {
return pkgDefs.parametrizeGenericType(file, typeDef, typeName)
}
// in case that comment //@name renamed the type with a name without a dot
for k, v := range pkgDefs.uniqueDefinitions {
if v == nil {
pkgDefs.debug.Printf("%s TypeSpecDef is nil", k)
continue
}
if v.SchemaName == typeName {
return v
}
}
return nil
}
func findGenericTypeFromPackage(pkg *packages.Package, pos token.Pos) types.Object {
file := findFileInPackageByPos(pkg, pos)
if file == nil {
return nil
}
var found ast.Node
ast.Inspect(file, func(node ast.Node) bool {
if node == nil || found != nil {
return false
}
if node.Pos() == pos {
found = node
return false
}
return true
})
typeSpec, _ := found.(*ast.TypeSpec)
if typeSpec == nil {
return nil
}
return pkg.TypesInfo.ObjectOf(typeSpec.Name)
}
// CheckTypeSpec check is there some warning or error
func (pkgDefs *PackagesDefinitions) CheckTypeSpec(typeSpecDef *TypeSpecDef) {
packageDefinition := pkgDefs.packages[typeSpecDef.PkgPath]
if packageDefinition == nil {
return
}
pkg := packageDefinition.Package
if pkg == nil {
return
}
obj := pkg.TypesInfo.ObjectOf(typeSpecDef.TypeSpec.Name)
if obj == nil {
// generic type has modified name
obj = findGenericTypeFromPackage(pkg, typeSpecDef.TypeSpec.Name.Pos())
}
if obj == nil {
pkgDefs.debug.Printf("warning: %s TypeSpecDef is nil", typeSpecDef.TypeSpec.Name.Name)
return
}
pkgDefs.checkJSONMarshal(pkg, obj)
}
func (pkgDefs *PackagesDefinitions) checkJSONMarshal(pkg *packages.Package, obj types.Object) {
methodSet := types.NewMethodSet(obj.Type())
method := methodSet.Lookup(pkg.Types, "MarshalJSON")
if method != nil {
pkgDefs.debug.Printf("warning: %s.%s has MarshalJSON method, may need special handling", pkg.PkgPath, obj.Name())
}
}
================================================
FILE: packages_test.go
================================================
package swag
import (
"go/ast"
"go/token"
"path/filepath"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPackagesDefinitions_ParseFile(t *testing.T) {
pd := PackagesDefinitions{}
packageDir := "github.com/swaggo/swag/testdata/simple"
assert.NoError(t, pd.ParseFile(packageDir, "testdata/simple/main.go", nil, ParseAll))
assert.Equal(t, 1, len(pd.packages))
assert.Equal(t, 1, len(pd.files))
}
func TestPackagesDefinitions_collectAstFile(t *testing.T) {
pd := PackagesDefinitions{}
fileSet := token.NewFileSet()
assert.NoError(t, pd.CollectAstFile(fileSet, "", "", nil, ParseAll))
firstFile := &ast.File{
Name: &ast.Ident{Name: "main.go"},
}
packageDir := "github.com/swaggo/swag/testdata/simple"
assert.NoError(t, pd.CollectAstFile(fileSet, packageDir, "testdata/simple/"+firstFile.Name.String(), firstFile, ParseAll))
assert.NotEmpty(t, pd.packages[packageDir])
absPath, _ := filepath.Abs("testdata/simple/" + firstFile.Name.String())
astFileInfo := &AstFileInfo{
FileSet: fileSet,
File: firstFile,
Path: absPath,
PackagePath: packageDir,
ParseFlag: ParseAll,
}
assert.Equal(t, pd.files[firstFile], astFileInfo)
// Override
assert.NoError(t, pd.CollectAstFile(fileSet, packageDir, "testdata/simple/"+firstFile.Name.String(), firstFile, ParseAll))
assert.Equal(t, pd.files[firstFile], astFileInfo)
// Another file
secondFile := &ast.File{
Name: &ast.Ident{Name: "api.go"},
}
assert.NoError(t, pd.CollectAstFile(fileSet, packageDir, "testdata/simple/"+secondFile.Name.String(), secondFile, ParseAll))
}
func TestPackagesDefinitions_rangeFiles(t *testing.T) {
pd := PackagesDefinitions{
files: map[*ast.File]*AstFileInfo{
{
Name: &ast.Ident{Name: "main.go"},
}: {
File: &ast.File{Name: &ast.Ident{Name: "main.go"}},
Path: "testdata/simple/main.go",
PackagePath: "main",
},
{
Name: &ast.Ident{Name: "api.go"},
}: {
File: &ast.File{Name: &ast.Ident{Name: "api.go"}},
Path: "testdata/simple/api/api.go",
PackagePath: "api",
},
},
}
i, expect := 0, []string{"testdata/simple/api/api.go", "testdata/simple/main.go"}
_ = pd.RangeFiles(func(fileInfo *AstFileInfo) error {
assert.Equal(t, expect[i], fileInfo.Path)
i++
return nil
})
}
func TestPackagesDefinitions_ParseTypes(t *testing.T) {
absPath, _ := filepath.Abs("")
mainAST := ast.File{
Name: &ast.Ident{Name: "main.go"},
Decls: []ast.Decl{
&ast.GenDecl{
Tok: token.TYPE,
Specs: []ast.Spec{
&ast.TypeSpec{
Name: &ast.Ident{Name: "Test"},
Type: &ast.Ident{
Name: "string",
},
},
},
},
},
}
pd := PackagesDefinitions{
files: map[*ast.File]*AstFileInfo{
&mainAST: {
File: &mainAST,
Path: filepath.Join(absPath, "testdata/simple/main.go"),
PackagePath: "main",
},
{
Name: &ast.Ident{Name: "api.go"},
}: {
File: &ast.File{Name: &ast.Ident{Name: "api.go"}},
Path: filepath.Join(absPath, "testdata/simple/api/api.go"),
PackagePath: "api",
},
},
packages: make(map[string]*PackageDefinitions),
}
_, err := pd.ParseTypes()
assert.NoError(t, err)
}
func TestPackagesDefinitions_parseFunctionScopedTypesFromFile(t *testing.T) {
mainAST := &ast.File{
Name: &ast.Ident{Name: "main.go"},
Decls: []ast.Decl{
&ast.FuncDecl{
Name: ast.NewIdent("TestFuncDecl"),
Body: &ast.BlockStmt{
List: []ast.Stmt{
&ast.DeclStmt{
Decl: &ast.GenDecl{
Tok: token.TYPE,
Specs: []ast.Spec{
&ast.TypeSpec{
Name: ast.NewIdent("response"),
Type: ast.NewIdent("struct"),
},
&ast.TypeSpec{
Name: ast.NewIdent("stringResponse"),
Type: ast.NewIdent("string"),
},
},
},
},
},
},
},
},
}
pd := PackagesDefinitions{
packages: make(map[string]*PackageDefinitions),
}
parsedSchema := make(map[*TypeSpecDef]*Schema)
pd.parseFunctionScopedTypesFromFile(mainAST, "main", parsedSchema)
assert.Len(t, parsedSchema, 1)
_, ok := pd.uniqueDefinitions["main.go.TestFuncDecl.response"]
assert.True(t, ok)
_, ok = pd.packages["main"].TypeDefinitions["main.go.TestFuncDecl.response"]
assert.True(t, ok)
}
func TestPackagesDefinitions_FindTypeSpec(t *testing.T) {
userDef := TypeSpecDef{
File: &ast.File{
Name: &ast.Ident{Name: "user.go"},
},
TypeSpec: &ast.TypeSpec{
Name: ast.NewIdent("User"),
},
PkgPath: "user",
}
var pkg = PackagesDefinitions{
uniqueDefinitions: map[string]*TypeSpecDef{
"user.Model": &userDef,
},
}
var nilDef *TypeSpecDef
assert.Equal(t, nilDef, pkg.FindTypeSpec("int", nil))
assert.Equal(t, nilDef, pkg.FindTypeSpec("bool", nil))
assert.Equal(t, nilDef, pkg.FindTypeSpec("string", nil))
assert.Equal(t, &userDef, pkg.FindTypeSpec("user.Model", nil))
assert.Equal(t, nilDef, pkg.FindTypeSpec("Model", nil))
}
func TestPackage_rangeFiles(t *testing.T) {
pd := NewPackagesDefinitions()
pd.files = map[*ast.File]*AstFileInfo{
{
Name: &ast.Ident{Name: "main.go"},
}: {
File: &ast.File{Name: &ast.Ident{Name: "main.go"}},
Path: "testdata/simple/main.go",
PackagePath: "main",
},
{
Name: &ast.Ident{Name: "api.go"},
}: {
File: &ast.File{Name: &ast.Ident{Name: "api.go"}},
Path: "testdata/simple/api/api.go",
PackagePath: "api",
},
{
Name: &ast.Ident{Name: "foo.go"},
}: {
File: &ast.File{Name: &ast.Ident{Name: "foo.go"}},
Path: "vendor/foo/foo.go",
PackagePath: "vendor/foo",
},
{
Name: &ast.Ident{Name: "bar.go"},
}: {
File: &ast.File{Name: &ast.Ident{Name: "bar.go"}},
Path: filepath.Join(runtime.GOROOT(), "bar.go"),
PackagePath: "bar",
},
}
var sorted []string
processor := func(fileInfo *AstFileInfo) error {
sorted = append(sorted, fileInfo.Path)
return nil
}
assert.NoError(t, pd.RangeFiles(processor))
assert.Equal(t, []string{"testdata/simple/api/api.go", "testdata/simple/main.go"}, sorted)
assert.Error(t, pd.RangeFiles(func(fileInfo *AstFileInfo) error {
return ErrFuncTypeField
}))
}
func TestPackagesDefinitions_findTypeSpec(t *testing.T) {
pd := PackagesDefinitions{}
var nilTypeSpec *TypeSpecDef
assert.Equal(t, nilTypeSpec, pd.findTypeSpec("model", "User"))
userTypeSpec := TypeSpecDef{
File: &ast.File{},
TypeSpec: &ast.TypeSpec{},
PkgPath: "model",
}
pd = PackagesDefinitions{
packages: map[string]*PackageDefinitions{
"model": {
TypeDefinitions: map[string]*TypeSpecDef{
"User": &userTypeSpec,
},
},
},
}
assert.Equal(t, &userTypeSpec, pd.findTypeSpec("model", "User"))
assert.Equal(t, nilTypeSpec, pd.findTypeSpec("others", "User"))
}
================================================
FILE: parser.go
================================================
package swag
import (
"context"
"encoding/json"
"errors"
"fmt"
"go/ast"
"go/build"
goparser "go/parser"
"go/token"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"reflect"
"sort"
"strconv"
"strings"
"github.com/KyleBanks/depth"
"github.com/go-openapi/spec"
)
const (
// CamelCase indicates using CamelCase strategy for struct field.
CamelCase = "camelcase"
// PascalCase indicates using PascalCase strategy for struct field.
PascalCase = "pascalcase"
// SnakeCase indicates using SnakeCase strategy for struct field.
SnakeCase = "snakecase"
idAttr = "@id"
acceptAttr = "@accept"
produceAttr = "@produce"
paramAttr = "@param"
successAttr = "@success"
failureAttr = "@failure"
responseAttr = "@response"
headerAttr = "@header"
tagsAttr = "@tags"
routerAttr = "@router"
deprecatedRouterAttr = "@deprecatedrouter"
summaryAttr = "@summary"
deprecatedAttr = "@deprecated"
securityAttr = "@security"
titleAttr = "@title"
conNameAttr = "@contact.name"
conURLAttr = "@contact.url"
conEmailAttr = "@contact.email"
licNameAttr = "@license.name"
licURLAttr = "@license.url"
versionAttr = "@version"
descriptionAttr = "@description"
descriptionMarkdownAttr = "@description.markdown"
secBasicAttr = "@securitydefinitions.basic"
secAPIKeyAttr = "@securitydefinitions.apikey"
secApplicationAttr = "@securitydefinitions.oauth2.application"
secImplicitAttr = "@securitydefinitions.oauth2.implicit"
secPasswordAttr = "@securitydefinitions.oauth2.password"
secAccessCodeAttr = "@securitydefinitions.oauth2.accesscode"
tosAttr = "@termsofservice"
extDocsDescAttr = "@externaldocs.description"
extDocsURLAttr = "@externaldocs.url"
xCodeSamplesAttr = "@x-codesamples"
scopeAttrPrefix = "@scope."
stateAttr = "@state"
)
// ParseFlag determine what to parse
type ParseFlag int
const (
// ParseNone parse nothing
ParseNone ParseFlag = 0x00
// ParseModels parse models
ParseModels = 0x01
// ParseOperations parse operations
ParseOperations = 0x02
// ParseAll parse operations and models
ParseAll = ParseOperations | ParseModels
)
var (
// ErrRecursiveParseStruct recursively parsing struct.
ErrRecursiveParseStruct = errors.New("recursively parsing struct")
// ErrFuncTypeField field type is func.
ErrFuncTypeField = errors.New("field type is func")
// ErrFailedConvertPrimitiveType Failed to convert for swag to interpretable type.
ErrFailedConvertPrimitiveType = errors.New("swag property: failed convert primitive type")
// ErrSkippedField .swaggo specifies field should be skipped.
ErrSkippedField = errors.New("field is skipped by global overrides")
)
var allMethod = map[string]struct{}{
http.MethodGet: {},
http.MethodPut: {},
http.MethodPost: {},
http.MethodDelete: {},
http.MethodOptions: {},
http.MethodHead: {},
http.MethodPatch: {},
}
// Parser implements a parser for Go source files.
type Parser struct {
// swagger represents the root document object for the API specification
swagger *spec.Swagger
// packages store entities of APIs, definitions, file, package path etc. and their relations
packages *PackagesDefinitions
// parsedSchemas store schemas which have been parsed from ast.TypeSpec
parsedSchemas map[*TypeSpecDef]*Schema
// outputSchemas store schemas which will be export to swagger
outputSchemas map[*TypeSpecDef]*Schema
// PropNamingStrategy naming strategy
PropNamingStrategy string
// ParseVendor parse vendor folder
ParseVendor bool
// ParseDependencies whether swag should be parse outside dependency folder: 0 none, 1 models, 2 operations, 3 all
ParseDependency ParseFlag
// ParseInternal whether swag should parse internal packages
ParseInternal bool
// Strict whether swag should error or warn when it detects cases which are most likely user errors
Strict bool
// RequiredByDefault set validation required for all fields by default
RequiredByDefault bool
// structStack stores full names of the structures that were already parsed or are being parsed now
structStack []*TypeSpecDef
// markdownFileDir holds the path to the folder, where markdown files are stored
markdownFileDir string
// codeExampleFilesDir holds path to the folder, where code example files are stored
codeExampleFilesDir string
// collectionFormatInQuery set the default collectionFormat otherwise then 'csv' for array in query params
collectionFormatInQuery string
// excludes excludes dirs and files in SearchDir
excludes map[string]struct{}
// packagePrefix is a list of package path prefixes, packages that do not
// match any one of them will be excluded when searching.
packagePrefix []string
// tells parser to include only specific extension
parseExtension string
// debugging output goes here
debug Debugger
// fieldParserFactory create FieldParser
fieldParserFactory FieldParserFactory
// Overrides allows global replacements of types. A blank replacement will be skipped.
Overrides map[string]string
// parseGoList whether swag use go list to parse dependency
parseGoList bool
// ParseGoPackages whether swag use golang.org/x/tools/go/packages to parse source.
// It ignores go source files which build tags do not match.
// It throws error when type check failed.
// It worked better for resolve const.
ParseGoPackages bool
// tags to filter the APIs after
tags map[string]struct{}
// HostState is the state of the host
HostState string
// ParseFuncBody whether swag should parse api info inside of funcs
ParseFuncBody bool
// UseStructName Dont use those ugly full-path names when using dependency flag
UseStructName bool
}
// FieldParserFactory create FieldParser.
type FieldParserFactory func(ps *Parser, field *ast.Field) FieldParser
// FieldParser parse struct field.
type FieldParser interface {
ShouldSkip() bool
FieldNames() ([]string, error)
FirstTagValue(tag string) string
FormName() string
QueryName() string
HeaderName() string
PathName() string
ParamName() string
CustomSchema() (*spec.Schema, error)
ComplementSchema(schema *spec.Schema) error
IsRequired() (bool, error)
}
// Debugger is the interface that wraps the basic Printf method.
type Debugger interface {
Printf(format string, v ...any)
}
// New creates a new Parser with default properties.
func New(options ...func(*Parser)) *Parser {
parser := &Parser{
swagger: &spec.Swagger{
SwaggerProps: spec.SwaggerProps{
Info: &spec.Info{
InfoProps: spec.InfoProps{
Contact: &spec.ContactInfo{},
License: nil,
},
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{},
},
},
Paths: &spec.Paths{
Paths: make(map[string]spec.PathItem),
VendorExtensible: spec.VendorExtensible{
Extensions: nil,
},
},
Definitions: make(map[string]spec.Schema),
SecurityDefinitions: make(map[string]*spec.SecurityScheme),
},
VendorExtensible: spec.VendorExtensible{
Extensions: nil,
},
},
packages: NewPackagesDefinitions(),
debug: log.New(os.Stdout, "", log.LstdFlags),
parsedSchemas: make(map[*TypeSpecDef]*Schema),
outputSchemas: make(map[*TypeSpecDef]*Schema),
excludes: make(map[string]struct{}),
tags: make(map[string]struct{}),
fieldParserFactory: newTagBaseFieldParser,
Overrides: make(map[string]string),
}
for _, option := range options {
option(parser)
}
parser.packages.debug = parser.debug
return parser
}
// SetParseDependency sets whether to parse the dependent packages.
func SetParseDependency(parseDependency int) func(*Parser) {
return func(p *Parser) {
p.ParseDependency = ParseFlag(parseDependency)
if p.packages != nil {
p.packages.parseDependency = p.ParseDependency
}
}
}
// SetUseStructName sets whether to strip the full-path definition name.
func SetUseStructName(useStructName bool) func(*Parser) {
return func(p *Parser) {
p.UseStructName = useStructName
}
}
// SetMarkdownFileDirectory sets the directory to search for markdown files.
func SetMarkdownFileDirectory(directoryPath string) func(*Parser) {
return func(p *Parser) {
p.markdownFileDir = directoryPath
}
}
// SetCodeExamplesDirectory sets the directory to search for code example files.
func SetCodeExamplesDirectory(directoryPath string) func(*Parser) {
return func(p *Parser) {
p.codeExampleFilesDir = directoryPath
}
}
// SetExcludedDirsAndFiles sets directories and files to be excluded when searching.
func SetExcludedDirsAndFiles(excludes string) func(*Parser) {
return func(p *Parser) {
for _, f := range strings.Split(excludes, ",") {
f = strings.TrimSpace(f)
if f != "" {
f = filepath.Clean(f)
p.excludes[f] = struct{}{}
}
}
}
}
// SetPackagePrefix sets a list of package path prefixes from a comma-separated
// string, packages that do not match any one of them will be excluded when
// searching.
func SetPackagePrefix(packagePrefix string) func(*Parser) {
return func(p *Parser) {
for _, f := range strings.Split(packagePrefix, ",") {
f = strings.TrimSpace(f)
if f != "" {
p.packagePrefix = append(p.packagePrefix, f)
}
}
}
}
// SetTags sets the tags to be included
func SetTags(include string) func(*Parser) {
return func(p *Parser) {
for _, f := range strings.Split(include, ",") {
f = strings.TrimSpace(f)
if f != "" {
p.tags[f] = struct{}{}
}
}
}
}
// SetParseExtension parses only those operations which match given extension
func SetParseExtension(parseExtension string) func(*Parser) {
return func(p *Parser) {
p.parseExtension = parseExtension
}
}
// SetStrict sets whether swag should error or warn when it detects cases which are most likely user errors.
func SetStrict(strict bool) func(*Parser) {
return func(p *Parser) {
p.Strict = strict
}
}
// SetDebugger allows the use of user-defined implementations.
func SetDebugger(logger Debugger) func(parser *Parser) {
return func(p *Parser) {
if logger != nil {
p.debug = logger
}
}
}
// SetFieldParserFactory allows the use of user-defined implementations.
func SetFieldParserFactory(factory FieldParserFactory) func(parser *Parser) {
return func(p *Parser) {
p.fieldParserFactory = factory
}
}
// SetOverrides allows the use of user-defined global type overrides.
func SetOverrides(overrides map[string]string) func(parser *Parser) {
return func(p *Parser) {
for k, v := range overrides {
p.Overrides[k] = v
}
}
}
// SetCollectionFormat set default collection format
func SetCollectionFormat(collectionFormat string) func(*Parser) {
return func(p *Parser) {
p.collectionFormatInQuery = collectionFormat
}
}
// ParseUsingGoList sets whether swag use go list to parse dependency
func ParseUsingGoList(enabled bool) func(parser *Parser) {
return func(p *Parser) {
p.parseGoList = enabled
}
}
// ParseAPI parses general api info for given searchDir and mainAPIFile.
func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string, parseDepth int) error {
return parser.ParseAPIMultiSearchDir([]string{searchDir}, mainAPIFile, parseDepth)
}
// skipPackageByPrefix returns true the given pkgpath does not match
// any user-defined package path prefixes.
func (parser *Parser) skipPackageByPrefix(pkgpath string) bool {
if len(parser.packagePrefix) == 0 {
return false
}
for _, prefix := range parser.packagePrefix {
if strings.HasPrefix(pkgpath, prefix) {
return false
}
}
return true
}
// ParseAPIMultiSearchDir is like ParseAPI but for multiple search dirs.
func (parser *Parser) ParseAPIMultiSearchDir(searchDirs []string, mainAPIFile string, parseDepth int) error {
absMainAPIFilePath, err := filepath.Abs(filepath.Join(searchDirs[0], mainAPIFile))
if err != nil {
return err
}
if parser.ParseGoPackages {
if err := parser.loadPackagesAndDeps(searchDirs, absMainAPIFilePath); err != nil {
return err
}
} else {
for _, searchDir := range searchDirs {
parser.debug.Printf("Generate general API Info, search dir:%s", searchDir)
packageDir, err := getPkgName(searchDir)
if err != nil {
parser.debug.Printf("warning: failed to get package name in dir: %s, error: %s", searchDir, err.Error())
}
err = parser.getAllGoFileInfo(packageDir, searchDir)
if err != nil {
return err
}
}
}
// Use 'go list' command instead of depth.Resolve()
if parser.ParseDependency > 0 && !parser.ParseGoPackages {
allDir := append([]string{filepath.Dir(absMainAPIFilePath)}, searchDirs...)
if parser.parseGoList {
pkgs, err := listPackages(context.Background(), allDir, nil, "-deps")
if err != nil {
return err
}
length := len(pkgs)
for i := 0; i < length; i++ {
err := parser.getAllGoFileInfoFromDepsByList(pkgs[i], parser.ParseDependency)
if err != nil {
return err
}
}
} else {
dirImported := make(map[string]struct{}) // for deduplication
for _, dir := range allDir { // ignore search dir (have been parsed)
absDir, err := filepath.Abs(dir)
if err == nil {
dirImported[absDir] = struct{}{}
}
}
for index, dir := range allDir {
var t depth.Tree
t.ResolveInternal = true
t.MaxDepth = parseDepth
pkgName, err := getPkgName(dir)
if err != nil {
if index == 0 { // ignore error when load search dir
return err
}
continue
}
err = t.Resolve(pkgName)
if err != nil {
return fmt.Errorf("pkg %s cannot find all dependencies, %s", pkgName, err)
}
for i := 0; i < len(t.Root.Deps); i++ {
err := parser.getAllGoFileInfoFromDeps(&t.Root.Deps[i], parser.ParseDependency, dirImported)
if err != nil {
return err
}
}
}
}
}
err = parser.ParseGeneralAPIInfo(absMainAPIFilePath)
if err != nil {
return err
}
parser.parsedSchemas, err = parser.packages.ParseTypes()
if err != nil {
return err
}
err = parser.packages.RangeFiles(parser.ParseRouterAPIInfo)
if err != nil {
return err
}
return parser.checkOperationIDUniqueness()
}
func getPkgName(searchDir string) (string, error) {
cmd := exec.Command("go", "list", "-f={{.ImportPath}}")
cmd.Dir = searchDir
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("execute go list command, %s, stdout:%s, stderr:%s", err, stdout.String(), stderr.String())
}
outStr, _ := stdout.String(), stderr.String()
if outStr[0] == '_' { // will shown like _/{GOPATH}/src/{YOUR_PACKAGE} when NOT enable GO MODULE.
outStr = strings.TrimPrefix(outStr, "_"+build.Default.GOPATH+"/src/")
}
f := strings.Split(outStr, "\n")
outStr = f[0]
return outStr, nil
}
// ParseGeneralAPIInfo parses general api info for given mainAPIFile path.
func (parser *Parser) ParseGeneralAPIInfo(mainAPIFile string) error {
fileTree, err := goparser.ParseFile(token.NewFileSet(), mainAPIFile, nil, goparser.ParseComments)
if err != nil {
return fmt.Errorf("cannot parse source files %s: %s", mainAPIFile, err)
}
parser.swagger.Swagger = "2.0"
for _, comment := range fileTree.Comments {
comments := strings.Split(comment.Text(), "\n")
if !isGeneralAPIComment(comments) {
continue
}
err = parseGeneralAPIInfo(parser, comments)
if err != nil {
return err
}
}
return nil
}
func parseGeneralAPIInfo(parser *Parser, comments []string) error {
previousAttribute := ""
var tag *spec.Tag
// parsing classic meta data model
for line := 0; line < len(comments); line++ {
commentLine := comments[line]
commentLine = strings.TrimSpace(commentLine)
if len(commentLine) == 0 {
continue
}
fields := FieldsByAnySpace(commentLine, 2)
attribute := fields[0]
var value string
if len(fields) > 1 {
value = fields[1]
}
switch attr := strings.ToLower(attribute); attr {
case versionAttr, titleAttr, tosAttr, licNameAttr, licURLAttr, conNameAttr, conURLAttr, conEmailAttr:
setSwaggerInfo(parser.swagger, attr, value)
case descriptionAttr:
if previousAttribute == attribute {
parser.swagger.Info.Description = AppendDescription(parser.swagger.Info.Description, value)
continue
}
setSwaggerInfo(parser.swagger, attr, value)
case descriptionMarkdownAttr:
commentInfo, err := getMarkdownForTag("api", parser.markdownFileDir)
if err != nil {
return err
}
setSwaggerInfo(parser.swagger, descriptionAttr, string(commentInfo))
case "@host":
parser.swagger.Host = value
case "@hoststate":
fields = FieldsByAnySpace(commentLine, 3)
if len(fields) != 3 {
return fmt.Errorf("%s needs 3 arguments", attribute)
}
if parser.HostState == fields[1] {
parser.swagger.Host = fields[2]
}
case "@basepath":
parser.swagger.BasePath = value
case acceptAttr:
err := parser.ParseAcceptComment(value)
if err != nil {
return err
}
case produceAttr:
err := parser.ParseProduceComment(value)
if err != nil {
return err
}
case "@schemes":
parser.swagger.Schemes = strings.Split(value, " ")
case "@tag.name":
if parser.matchTag(value) {
parser.swagger.Tags = append(parser.swagger.Tags, spec.Tag{
TagProps: spec.TagProps{
Name: value,
},
})
tag = &parser.swagger.Tags[len(parser.swagger.Tags)-1]
} else {
tag = nil
}
case "@tag.description":
if tag != nil {
tag.TagProps.Description = value
}
case "@tag.description.markdown":
if tag != nil {
commentInfo, err := getMarkdownForTag(tag.TagProps.Name, parser.markdownFileDir)
if err != nil {
return err
}
tag.TagProps.Description = string(commentInfo)
}
case "@tag.docs.url":
if tag != nil {
tag.TagProps.ExternalDocs = &spec.ExternalDocumentation{
URL: value,
}
}
case "@tag.docs.description":
if tag != nil {
if tag.TagProps.ExternalDocs == nil {
return fmt.Errorf("%s needs to come after a @tags.docs.url", attribute)
}
tag.TagProps.ExternalDocs.Description = value
}
case secBasicAttr, secAPIKeyAttr, secApplicationAttr, secImplicitAttr, secPasswordAttr, secAccessCodeAttr:
scheme, err := parseSecAttributes(attribute, comments, &line)
if err != nil {
return err
}
parser.swagger.SecurityDefinitions[value] = scheme
case securityAttr:
parser.swagger.Security = append(parser.swagger.Security, parseSecurity(value))
case "@query.collection.format":
parser.collectionFormatInQuery = TransToValidCollectionFormat(value)
case extDocsDescAttr, extDocsURLAttr:
if parser.swagger.ExternalDocs == nil {
parser.swagger.ExternalDocs = new(spec.ExternalDocumentation)
}
switch attr {
case extDocsDescAttr:
parser.swagger.ExternalDocs.Description = value
case extDocsURLAttr:
parser.swagger.ExternalDocs.URL = value
}
default:
if strings.HasPrefix(attribute, "@x-") {
extensionName := attribute[1:]
extExistsInSecurityDef := false
// for each security definition
for _, v := range parser.swagger.SecurityDefinitions {
// check if extension exists
_, extExistsInSecurityDef = v.VendorExtensible.Extensions.GetString(extensionName)
// if it exists in at least one, then we stop iterating
if extExistsInSecurityDef {
break
}
}
// if it is present on security def, don't add it again
if extExistsInSecurityDef {
break
}
if len(value) == 0 {
return fmt.Errorf("annotation %s need a value", attribute)
}
var valueJSON any
err := json.Unmarshal([]byte(value), &valueJSON)
if err != nil {
return fmt.Errorf("annotation %s need a valid json value", attribute)
}
if strings.Contains(extensionName, "logo") {
parser.swagger.Info.Extensions.Add(extensionName, valueJSON)
} else {
if parser.swagger.Extensions == nil {
parser.swagger.Extensions = make(map[string]any)
}
parser.swagger.Extensions[attribute[1:]] = valueJSON
}
} else if strings.HasPrefix(attribute, "@tag.x-") {
extensionName := attribute[5:]
if len(value) == 0 {
return fmt.Errorf("annotation %s need a value", attribute)
}
if tag.Extensions == nil {
tag.Extensions = make(map[string]any)
}
// tag.Extensions.Add(extensionName, value) works wrong (transforms extensionName to lower case)
// needed to save case for ReDoc
// https://redocly.com/docs/api-reference-docs/specification-extensions/x-display-name/
tag.Extensions[extensionName] = value
}
}
previousAttribute = attribute
}
return nil
}
func setSwaggerInfo(swagger *spec.Swagger, attribute, value string) {
switch attribute {
case versionAttr:
swagger.Info.Version = value
case titleAttr:
swagger.Info.Title = value
case tosAttr:
swagger.Info.TermsOfService = value
case descriptionAttr:
swagger.Info.Description = value
case conNameAttr:
swagger.Info.Contact.Name = value
case conEmailAttr:
swagger.Info.Contact.Email = value
case conURLAttr:
swagger.Info.Contact.URL = value
case licNameAttr:
swagger.Info.License = initIfEmpty(swagger.Info.License)
swagger.Info.License.Name = value
case licURLAttr:
swagger.Info.License = initIfEmpty(swagger.Info.License)
swagger.Info.License.URL = value
}
}
func parseSecAttributes(context string, lines []string, index *int) (*spec.SecurityScheme, error) {
const (
in = "@in"
name = "@name"
descriptionAttr = "@description"
tokenURL = "@tokenurl"
authorizationURL = "@authorizationurl"
)
var search []string
attribute := strings.ToLower(FieldsByAnySpace(lines[*index], 2)[0])
switch attribute {
case secBasicAttr:
return spec.BasicAuth(), nil
case secAPIKeyAttr:
search = []string{in, name}
case secApplicationAttr, secPasswordAttr:
search = []string{tokenURL}
case secImplicitAttr:
search = []string{authorizationURL}
case secAccessCodeAttr:
search = []string{tokenURL, authorizationURL}
}
// For the first line we get the attributes in the context parameter, so we skip to the next one
*index++
attrMap, scopes := make(map[string]string), make(map[string]string)
extensions, description := make(map[string]any), ""
loopline:
for ; *index < len(lines); *index++ {
v := strings.TrimSpace(lines[*index])
if len(v) == 0 {
continue
}
fields := FieldsByAnySpace(v, 2)
securityAttr := strings.ToLower(fields[0])
var value string
if len(fields) > 1 {
value = fields[1]
}
for _, findterm := range search {
if securityAttr == findterm {
attrMap[securityAttr] = value
continue loopline
}
}
if isExists, err := isExistsScope(securityAttr); err != nil {
return nil, err
} else if isExists {
scopes[securityAttr[len(scopeAttrPrefix):]] = value
continue
}
if strings.HasPrefix(securityAttr, "@x-") {
// Add the custom attribute without the @
extensions[securityAttr[1:]] = value
continue
}
// Not mandatory field
if securityAttr == descriptionAttr {
if description != "" {
description += "\n"
}
description += value
}
// next securityDefinitions
if strings.Index(securityAttr, "@securitydefinitions.") == 0 {
// Go back to the previous line and break
*index--
break
}
}
if len(attrMap) != len(search) {
return nil, fmt.Errorf("%s is %v required", context, search)
}
var scheme *spec.SecurityScheme
switch attribute {
case secAPIKeyAttr:
scheme = spec.APIKeyAuth(attrMap[name], attrMap[in])
case secApplicationAttr:
scheme = spec.OAuth2Application(attrMap[tokenURL])
case secImplicitAttr:
scheme = spec.OAuth2Implicit(attrMap[authorizationURL])
case secPasswordAttr:
scheme = spec.OAuth2Password(attrMap[tokenURL])
case secAccessCodeAttr:
scheme = spec.OAuth2AccessToken(attrMap[authorizationURL], attrMap[tokenURL])
}
scheme.Description = description
for extKey, extValue := range extensions {
scheme.AddExtension(extKey, extValue)
}
for scope, scopeDescription := range scopes {
scheme.AddScope(scope, scopeDescription)
}
return scheme, nil
}
func parseSecurity(commentLine string) map[string][]string {
securityMap := make(map[string][]string)
for _, securityOption := range securityPairSepPattern.Split(commentLine, -1) {
securityOption = strings.TrimSpace(securityOption)
left, right := strings.Index(securityOption, "["), strings.Index(securityOption, "]")
if !(left == -1 && right == -1) {
scopes := securityOption[left+1 : right]
var options []string
for _, scope := range strings.Split(scopes, ",") {
options = append(options, strings.TrimSpace(scope))
}
securityKey := securityOption[0:left]
securityMap[securityKey] = append(securityMap[securityKey], options...)
} else {
securityKey := strings.TrimSpace(securityOption)
securityMap[securityKey] = []string{}
}
}
return securityMap
}
func initIfEmpty(license *spec.License) *spec.License {
if license == nil {
return new(spec.License)
}
return license
}
// ParseAcceptComment parses comment for given `accept` comment string.
func (parser *Parser) ParseAcceptComment(commentLine string) error {
return parseMimeTypeList(commentLine, &parser.swagger.Consumes, "%v accept type can't be accepted")
}
// ParseProduceComment parses comment for given `produce` comment string.
func (parser *Parser) ParseProduceComment(commentLine string) error {
return parseMimeTypeList(commentLine, &parser.swagger.Produces, "%v produce type can't be accepted")
}
func isGeneralAPIComment(comments []string) bool {
for _, commentLine := range comments {
commentLine = strings.TrimSpace(commentLine)
if len(commentLine) == 0 {
continue
}
attribute := strings.ToLower(FieldsByAnySpace(commentLine, 2)[0])
switch attribute {
// The @summary, @router, @success, @failure annotation belongs to Operation
case summaryAttr, routerAttr, successAttr, failureAttr, responseAttr:
return false
}
}
return true
}
func getMarkdownForTag(tagName string, dirPath string) ([]byte, error) {
if tagName == "" {
// this happens when parsing the @description.markdown attribute
// it will be called properly another time with tagName="api"
// so we can safely return an empty byte slice here
return make([]byte, 0), nil
}
dirEntries, err := os.ReadDir(dirPath)
if err != nil {
return nil, err
}
for _, entry := range dirEntries {
if entry.IsDir() {
continue
}
fileName := entry.Name()
expectedFileName := tagName
if !strings.HasSuffix(tagName, ".md") {
expectedFileName = tagName + ".md"
}
if fileName == expectedFileName {
fullPath := filepath.Join(dirPath, fileName)
commentInfo, err := os.ReadFile(fullPath)
if err != nil {
return nil, fmt.Errorf("Failed to read markdown file %s error: %s ", fullPath, err)
}
return commentInfo, nil
}
}
return nil, fmt.Errorf("Unable to find markdown file for tag %s in the given directory", tagName)
}
func isExistsScope(scope string) (bool, error) {
s := strings.Fields(scope)
for _, v := range s {
if strings.HasPrefix(v, scopeAttrPrefix) {
if strings.Contains(v, ",") {
return false, fmt.Errorf("@scope can't use comma(,) get=%s", v)
}
}
}
return strings.HasPrefix(scope, scopeAttrPrefix), nil
}
func getTagsFromComment(comment string) (tags []string) {
commentLine := strings.TrimSpace(strings.TrimLeft(comment, "/"))
if len(commentLine) == 0 {
return nil
}
attribute := strings.Fields(commentLine)[0]
lineRemainder, lowerAttribute := strings.TrimSpace(commentLine[len(attribute):]), strings.ToLower(attribute)
if lowerAttribute == tagsAttr {
for _, tag := range strings.Split(lineRemainder, ",") {
tags = append(tags, strings.TrimSpace(tag))
}
}
return
}
func (parser *Parser) matchTag(tag string) bool {
if len(parser.tags) == 0 {
return true
}
if _, has := parser.tags["!"+tag]; has {
return false
}
if _, has := parser.tags[tag]; has {
return true
}
// If all tags are negation then we should return true
for key := range parser.tags {
if key[0] != '!' {
return false
}
}
return true
}
func (parser *Parser) matchTags(comments []*ast.Comment) (match bool) {
if len(parser.tags) == 0 {
return true
}
match = false
for _, comment := range comments {
for _, tag := range getTagsFromComment(comment.Text) {
if _, has := parser.tags["!"+tag]; has {
return false
}
if _, has := parser.tags[tag]; has {
match = true // keep iterating as it may contain a tag that is excluded
}
}
}
if !match {
// If all tags are negation then we should return true
for key := range parser.tags {
if key[0] != '!' {
return false
}
}
}
return true
}
func matchExtension(extensionToMatch string, comments []*ast.Comment) (match bool) {
if len(extensionToMatch) != 0 {
for _, comment := range comments {
commentLine := strings.TrimSpace(strings.TrimLeft(comment.Text, "/"))
fields := FieldsByAnySpace(commentLine, 2)
if len(fields) > 0 {
lowerAttribute := strings.ToLower(fields[0])
if lowerAttribute == fmt.Sprintf("@x-%s", strings.ToLower(extensionToMatch)) {
return true
}
}
}
return false
}
return true
}
func getFuncDoc(decl any) (*ast.CommentGroup, bool) {
switch astDecl := decl.(type) {
case *ast.FuncDecl: // func name() {}
return astDecl.Doc, true
case *ast.GenDecl: // var name = namePointToFuncDirectlyOrIndirectly
if astDecl.Tok != token.VAR {
return nil, false
}
if len(astDecl.Specs) == 0 {
return nil, false
}
varSpec, ok := astDecl.Specs[0].(*ast.ValueSpec)
if !ok || len(varSpec.Values) != 1 {
return nil, false
}
_, ok = getFuncDoc(varSpec)
return astDecl.Doc, ok
case *ast.ValueSpec:
if len(astDecl.Values) == 0 {
return nil, false
}
value, ok := astDecl.Values[0].(*ast.Ident)
if !ok || value == nil || value.Obj == nil || value.Obj.Decl == nil {
return nil, false
}
_, ok = getFuncDoc(value.Obj.Decl)
return astDecl.Doc, ok
}
return nil, false
}
// ParseRouterAPIInfo parses router api info for given astFile.
func (parser *Parser) ParseRouterAPIInfo(fileInfo *AstFileInfo) error {
if (fileInfo.ParseFlag & ParseOperations) == ParseNone {
return nil
}
// parse File.Comments instead of File.Decls.Doc if ParseFuncBody flag set to "true"
if parser.ParseFuncBody {
for _, astComments := range fileInfo.File.Comments {
if astComments.List != nil {
if err := parser.parseRouterAPIInfoComment(astComments.List, fileInfo); err != nil {
return err
}
}
}
return nil
}
for _, decl := range fileInfo.File.Decls {
funcDoc, ok := getFuncDoc(decl)
if ok && funcDoc != nil && funcDoc.List != nil {
if err := parser.parseRouterAPIInfoComment(funcDoc.List, fileInfo); err != nil {
return err
}
}
}
return nil
}
func (parser *Parser) parseRouterAPIInfoComment(comments []*ast.Comment, fileInfo *AstFileInfo) error {
if parser.matchTags(comments) && matchExtension(parser.parseExtension, comments) {
// for per 'function' comment, create a new 'Operation' object
operation := NewOperation(parser, SetCodeExampleFilesDirectory(parser.codeExampleFilesDir))
for _, comment := range comments {
err := operation.ParseComment(comment.Text, fileInfo.File)
if err != nil {
return fmt.Errorf("ParseComment error in file %s for comment: '%s': %+v", fileInfo.Path, comment.Text, err)
}
if operation.State != "" && operation.State != parser.HostState {
return nil
}
}
err := processRouterOperation(parser, operation)
if err != nil {
return err
}
}
return nil
}
func refRouteMethodOp(item *spec.PathItem, method string) (op **spec.Operation) {
switch method {
case http.MethodGet:
op = &item.Get
case http.MethodPost:
op = &item.Post
case http.MethodDelete:
op = &item.Delete
case http.MethodPut:
op = &item.Put
case http.MethodPatch:
op = &item.Patch
case http.MethodHead:
op = &item.Head
case http.MethodOptions:
op = &item.Options
}
return
}
func processRouterOperation(parser *Parser, operation *Operation) error {
for _, routeProperties := range operation.RouterProperties {
var (
pathItem spec.PathItem
ok bool
)
pathItem, ok = parser.swagger.Paths.Paths[routeProperties.Path]
if !ok {
pathItem = spec.PathItem{}
}
op := refRouteMethodOp(&pathItem, routeProperties.HTTPMethod)
// check if we already have an operation for this path and method
if *op != nil {
err := fmt.Errorf("route %s %s is declared multiple times", routeProperties.HTTPMethod, routeProperties.Path)
if parser.Strict {
return err
}
parser.debug.Printf("warning: %s\n", err)
}
if len(operation.RouterProperties) > 1 {
newOp := *operation
var validParams []spec.Parameter
for _, param := range newOp.Operation.OperationProps.Parameters {
if param.In == "path" && !strings.Contains(routeProperties.Path, param.Name) {
// This path param is not actually contained in the path, skip adding it to the final params
continue
}
validParams = append(validParams, param)
}
newOp.Operation.OperationProps.Parameters = validParams
*op = &newOp.Operation
} else {
*op = &operation.Operation
}
if routeProperties.Deprecated {
(*op).Deprecated = routeProperties.Deprecated
}
parser.swagger.Paths.Paths[routeProperties.Path] = pathItem
}
return nil
}
func convertFromSpecificToPrimitive(typeName string) (string, error) {
name := typeName
if strings.ContainsRune(name, '.') {
name = strings.Split(name, ".")[1]
}
switch strings.ToUpper(name) {
case "TIME", "OBJECTID", "UUID":
return STRING, nil
case "DECIMAL":
return NUMBER, nil
}
return typeName, ErrFailedConvertPrimitiveType
}
func (parser *Parser) getTypeSchema(typeName string, file *ast.File, ref bool) (*spec.Schema, error) {
if override, ok := parser.Overrides[typeName]; ok {
parser.debug.Printf("Override detected for %s: using %s instead", typeName, override)
return parseObjectSchema(parser, override, file)
}
if IsInterfaceLike(typeName) {
return &spec.Schema{}, nil
}
if IsGolangPrimitiveType(typeName) {
return TransToValidPrimitiveSchema(typeName), nil
}
schemaType, err := convertFromSpecificToPrimitive(typeName)
if err == nil {
return PrimitiveSchema(schemaType), nil
}
typeSpecDef := parser.packages.FindTypeSpec(typeName, file)
if typeSpecDef == nil {
return nil, fmt.Errorf("cannot find type definition: %s", typeName)
}
if override, ok := parser.Overrides[typeSpecDef.FullPath()]; ok {
if override == "" {
parser.debug.Printf("Override detected for %s: ignoring", typeSpecDef.FullPath())
return nil, ErrSkippedField
}
parser.debug.Printf("Override detected for %s: using %s instead", typeSpecDef.FullPath(), override)
separator := strings.LastIndex(override, ".")
if separator == -1 {
// treat as a swaggertype tag
parts := strings.Split(override, ",")
return BuildCustomSchema(parts)
}
typeSpecDef = parser.packages.findTypeSpec(override[0:separator], override[separator+1:])
}
parser.packages.CheckTypeSpec(typeSpecDef)
schema, ok := parser.parsedSchemas[typeSpecDef]
if !ok {
var err error
schema, err = parser.ParseDefinition(typeSpecDef)
if err != nil {
if err == ErrRecursiveParseStruct && ref {
return parser.getRefTypeSchema(typeSpecDef, schema), nil
}
return nil, fmt.Errorf("%s: %w", typeName, err)
}
}
if ref {
if IsComplexSchema(schema.Schema) {
return parser.getRefTypeSchema(typeSpecDef, schema), nil
}
// if it is a simple schema, just return a copy
newSchema := *schema.Schema
return &newSchema, nil
}
return schema.Schema, nil
}
func (parser *Parser) getRefTypeSchema(typeSpecDef *TypeSpecDef, schema *Schema) *spec.Schema {
_, ok := parser.outputSchemas[typeSpecDef]
if !ok {
parser.swagger.Definitions[schema.Name] = spec.Schema{}
if schema.Schema != nil {
parser.swagger.Definitions[schema.Name] = *schema.Schema
}
parser.outputSchemas[typeSpecDef] = schema
}
refSchema := RefSchema(schema.Name)
return refSchema
}
func (parser *Parser) isInStructStack(typeSpecDef *TypeSpecDef) bool {
for _, specDef := range parser.structStack {
if typeSpecDef == specDef {
return true
}
}
return false
}
// ParseDefinition parses given type spec that corresponds to the type under
// given name and package, and populates swagger schema definitions registry
// with a schema for the given type
func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) {
typeName := typeSpecDef.TypeName()
schema, found := parser.parsedSchemas[typeSpecDef]
if found {
parser.debug.Printf("Skipping '%s', already parsed.", typeName)
return schema, nil
}
if parser.isInStructStack(typeSpecDef) {
parser.debug.Printf("Skipping '%s', recursion detected.", typeName)
// Ensure SchemaName is set before using it
typeSpecDef.SetSchemaName()
schemaName := typeName
if typeSpecDef.SchemaName != "" {
schemaName = typeSpecDef.SchemaName
}
return &Schema{
Name: schemaName,
PkgPath: typeSpecDef.PkgPath,
Schema: PrimitiveSchema(OBJECT),
},
ErrRecursiveParseStruct
}
if parser.UseStructName {
schemaName := strings.Split(typeSpecDef.SchemaName, ".")
if len(schemaName) > 1 {
typeSpecDef.SchemaName = schemaName[len(schemaName)-1]
typeName = typeSpecDef.SchemaName
} else {
parser.debug.Printf("Could not strip type name of %s", typeName)
}
}
parser.structStack = append(parser.structStack, typeSpecDef)
parser.debug.Printf("Generating %s", typeName)
definition, err := parser.parseTypeExpr(typeSpecDef.File, typeSpecDef.TypeSpec.Type, false)
if err != nil {
parser.debug.Printf("Error parsing type definition '%s': %s", typeName, err)
return nil, err
}
if definition.Description == "" {
err = parser.fillDefinitionDescription(definition, typeSpecDef.File, typeSpecDef)
if err != nil {
return nil, err
}
}
if len(typeSpecDef.Enums) > 0 {
var varnames []string
var enumComments = make(map[string]string)
var enumDescriptions = make([]string, 0, len(typeSpecDef.Enums))
for _, value := range typeSpecDef.Enums {
definition.Enum = append(definition.Enum, value.Value)
varnames = append(varnames, value.key)
enumDescriptions = append(enumDescriptions, value.Comment)
if len(value.Comment) > 0 {
enumComments[value.key] = value.Comment
}
}
if definition.Extensions == nil {
definition.Extensions = make(spec.Extensions)
}
definition.Extensions[enumVarNamesExtension] = varnames
if len(enumComments) > 0 {
definition.Extensions[enumCommentsExtension] = enumComments
definition.Extensions[enumDescriptionsExtension] = enumDescriptions
}
}
schemaName := typeName
if typeSpecDef.SchemaName != "" {
schemaName = typeSpecDef.SchemaName
}
sch := Schema{
Name: schemaName,
PkgPath: typeSpecDef.PkgPath,
Schema: definition,
}
parser.parsedSchemas[typeSpecDef] = &sch
// update an empty schema as a result of recursion
s2, found := parser.outputSchemas[typeSpecDef]
if found {
parser.swagger.Definitions[s2.Name] = *definition
}
return &sch, nil
}
func fullTypeName(parts ...string) string {
return strings.Join(parts, ".")
}
// fillDefinitionDescription additionally fills fields in definition (spec.Schema)
// TODO: If .go file contains many types, it may work for a long time
func (parser *Parser) fillDefinitionDescription(definition *spec.Schema, file *ast.File, typeSpecDef *TypeSpecDef) (err error) {
if file == nil {
return
}
for _, astDeclaration := range file.Decls {
generalDeclaration, ok := astDeclaration.(*ast.GenDecl)
if !ok || generalDeclaration.Tok != token.TYPE {
continue
}
for _, astSpec := range generalDeclaration.Specs {
typeSpec, ok := astSpec.(*ast.TypeSpec)
if !ok || typeSpec != typeSpecDef.TypeSpec {
continue
}
var typeName string
if typeSpec.Name != nil {
typeName = typeSpec.Name.Name
}
definition.Description, err =
parser.extractDeclarationDescription(typeName, typeSpec.Doc, typeSpec.Comment, generalDeclaration.Doc)
if err != nil {
return
}
}
}
return nil
}
// extractDeclarationDescription gets first description
// from attribute descriptionAttr in commentGroups (ast.CommentGroup)
func (parser *Parser) extractDeclarationDescription(typeName string, commentGroups ...*ast.CommentGroup) (string, error) {
var description string
for _, commentGroup := range commentGroups {
if commentGroup == nil {
continue
}
isHandlingDescription := false
for _, comment := range commentGroup.List {
commentText := strings.TrimSpace(strings.TrimLeft(comment.Text, "/"))
if len(commentText) == 0 {
continue
}
fields := FieldsByAnySpace(commentText, 2)
attribute := fields[0]
if attr := strings.ToLower(attribute); attr == descriptionMarkdownAttr {
if len(fields) > 1 {
typeName = fields[1]
}
if typeName == "" {
continue
}
desc, err := getMarkdownForTag(typeName, parser.markdownFileDir)
if err != nil {
return "", err
}
// if found markdown description, we will only use the markdown file content
return string(desc), nil
} else if attr != descriptionAttr {
if !isHandlingDescription {
continue
}
break
}
isHandlingDescription = true
description += " " + strings.TrimSpace(commentText[len(attribute):])
}
}
return strings.TrimLeft(description, " "), nil
}
// parseTypeExpr parses given type expression that corresponds to the type under
// given name and package, and returns swagger schema for it.
func (parser *Parser) parseTypeExpr(file *ast.File, typeExpr ast.Expr, ref bool) (*spec.Schema, error) {
switch expr := typeExpr.(type) {
// type Foo interface{}
case *ast.InterfaceType:
return &spec.Schema{}, nil
// type Foo struct {...}
case *ast.StructType:
return parser.parseStruct(file, expr.Fields)
// type Foo Baz
case *ast.Ident:
return parser.getTypeSchema(expr.Name, file, ref)
// type Foo *Baz
case *ast.StarExpr:
return parser.parseTypeExpr(file, expr.X, ref)
// type Foo pkg.Bar
case *ast.SelectorExpr:
if xIdent, ok := expr.X.(*ast.Ident); ok {
return parser.getTypeSchema(fullTypeName(xIdent.Name, expr.Sel.Name), file, ref)
}
// type Foo []Baz
case *ast.ArrayType:
itemSchema, err := parser.parseTypeExpr(file, expr.Elt, true)
if err != nil {
return nil, err
}
return spec.ArrayProperty(itemSchema), nil
// type Foo map[string]Bar
case *ast.MapType:
if _, ok := expr.Value.(*ast.InterfaceType); ok {
return spec.MapProperty(nil), nil
}
schema, err := parser.parseTypeExpr(file, expr.Value, true)
if err != nil {
return nil, err
}
return spec.MapProperty(schema), nil
case *ast.FuncType:
return nil, ErrFuncTypeField
// ...
}
return parser.parseGenericTypeExpr(file, typeExpr)
}
func (parser *Parser) parseStruct(file *ast.File, fields *ast.FieldList) (*spec.Schema, error) {
required, properties := make([]string, 0), make(map[string]spec.Schema)
for _, field := range fields.List {
fieldProps, requiredFromAnon, err := parser.parseStructField(file, field)
if err != nil {
if errors.Is(err, ErrFuncTypeField) || errors.Is(err, ErrSkippedField) {
continue
}
return nil, err
}
if len(fieldProps) == 0 {
continue
}
required = append(required, requiredFromAnon...)
for k, v := range fieldProps {
properties[k] = v
}
}
sort.Strings(required)
return &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{OBJECT},
Properties: properties,
Required: required,
},
}, nil
}
func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[string]spec.Schema, []string, error) {
if field.Tag != nil {
skip, ok := reflect.StructTag(strings.ReplaceAll(field.Tag.Value, "`", "")).Lookup("swaggerignore")
if ok && strings.EqualFold(skip, "true") {
return nil, nil, nil
}
}
ps := parser.fieldParserFactory(parser, field)
if ps.ShouldSkip() {
return nil, nil, nil
}
fieldNames, err := ps.FieldNames()
if err != nil {
return nil, nil, err
}
if len(fieldNames) == 0 {
typeName, err := getFieldType(file, field.Type, nil)
if err != nil {
return nil, nil, err
}
schema, err := parser.getTypeSchema(typeName, file, false)
if err != nil {
return nil, nil, err
}
if len(schema.Type) > 0 && schema.Type[0] == OBJECT {
if len(schema.Properties) == 0 {
return nil, nil, nil
}
properties := map[string]spec.Schema{}
for k, v := range schema.Properties {
properties[k] = v
}
return properties, schema.SchemaProps.Required, nil
}
// for alias type of non-struct types ,such as array,map, etc. ignore field tag.
return map[string]spec.Schema{typeName: *schema}, nil, nil
}
schema, err := ps.CustomSchema()
if err != nil {
return nil, nil, fmt.Errorf("%v: %w", fieldNames, err)
}
if schema == nil {
typeName, err := getFieldType(file, field.Type, nil)
if err == nil {
// named type
schema, err = parser.getTypeSchema(typeName, file, true)
} else {
// unnamed type
schema, err = parser.parseTypeExpr(file, field.Type, false)
}
if err != nil {
return nil, nil, fmt.Errorf("%v: %w", fieldNames, err)
}
}
err = ps.ComplementSchema(schema)
if err != nil {
return nil, nil, fmt.Errorf("%v: %w", fieldNames, err)
}
var tagRequired []string
required, err := ps.IsRequired()
if err != nil {
return nil, nil, fmt.Errorf("%v: %w", fieldNames, err)
}
if required {
tagRequired = append(tagRequired, fieldNames...)
}
if formName := ps.FormName(); len(formName) > 0 {
schema.AddExtension("formData", formName)
}
if queryName := ps.QueryName(); len(queryName) > 0 {
schema.AddExtension("query", queryName)
}
if headerName := ps.HeaderName(); len(headerName) > 0 {
schema.AddExtension("header", headerName)
}
if pathName := ps.PathName(); len(pathName) > 0 {
schema.AddExtension("path", pathName)
} else if paramName := ps.ParamName(); len(paramName) > 0 {
schema.AddExtension("path", paramName)
}
if len(schema.Type) > 0 && schema.Type[0] == ARRAY {
if collectionFormat := ps.FirstTagValue(collectionFormatTag); len(collectionFormat) > 0 {
schema.AddExtension(collectionFormatTag, collectionFormat)
}
}
fields := make(map[string]spec.Schema)
for _, name := range fieldNames {
fields[name] = *schema
}
return fields, tagRequired, nil
}
func getFieldType(file *ast.File, field ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) (string, error) {
switch fieldType := field.(type) {
case *ast.Ident:
return fieldType.Name, nil
case *ast.SelectorExpr:
packageName, err := getFieldType(file, fieldType.X, genericParamTypeDefs)
if err != nil {
return "", err
}
return fullTypeName(packageName, fieldType.Sel.Name), nil
case *ast.StarExpr:
fullName, err := getFieldType(file, fieldType.X, genericParamTypeDefs)
if err != nil {
return "", err
}
return fullName, nil
default:
return getGenericFieldType(file, field, genericParamTypeDefs)
}
}
func (parser *Parser) getUnderlyingSchema(schema *spec.Schema) *spec.Schema {
if schema == nil {
return nil
}
if url := schema.Ref.GetURL(); url != nil {
if pos := strings.LastIndexByte(url.Fragment, '/'); pos >= 0 {
name := url.Fragment[pos+1:]
if schema, ok := parser.swagger.Definitions[name]; ok {
return &schema
}
}
}
if len(schema.AllOf) > 0 {
merged := &spec.Schema{}
MergeSchema(merged, schema)
for _, s := range schema.AllOf {
MergeSchema(merged, parser.getUnderlyingSchema(&s))
}
return merged
}
return nil
}
// GetSchemaTypePath get path of schema type.
func (parser *Parser) GetSchemaTypePath(schema *spec.Schema, depth int) []string {
if schema == nil || depth == 0 {
return nil
}
if underlying := parser.getUnderlyingSchema(schema); underlying != nil {
return parser.GetSchemaTypePath(underlying, depth)
}
if len(schema.Type) > 0 {
switch schema.Type[0] {
case ARRAY:
depth--
s := []string{schema.Type[0]}
return append(s, parser.GetSchemaTypePath(schema.Items.Schema, depth)...)
case OBJECT:
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
// for map
depth--
s := []string{schema.Type[0]}
return append(s, parser.GetSchemaTypePath(schema.AdditionalProperties.Schema, depth)...)
}
}
return []string{schema.Type[0]}
}
return []string{ANY}
}
// defineTypeOfExample example value define the type (object and array unsupported).
func defineTypeOfExample(schemaType, arrayType, exampleValue string) (interface{}, error) {
switch schemaType {
case STRING:
return exampleValue, nil
case NUMBER:
v, err := strconv.ParseFloat(exampleValue, 64)
if err != nil {
return nil, fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err)
}
return v, nil
case INTEGER:
v, err := strconv.Atoi(exampleValue)
if err != nil {
return nil, fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err)
}
return v, nil
case BOOLEAN:
v, err := strconv.ParseBool(exampleValue)
if err != nil {
return nil, fmt.Errorf("example value %s can't convert to %s err: %s", exampleValue, schemaType, err)
}
return v, nil
case ARRAY:
values := strings.Split(exampleValue, ",")
result := make([]any, 0)
for _, value := range values {
v, err := defineTypeOfExample(arrayType, "", value)
if err != nil {
return nil, err
}
result = append(result, v)
}
return result, nil
case OBJECT:
if arrayType == "" {
return nil, fmt.Errorf("%s is unsupported type in example value `%s`", schemaType, exampleValue)
}
values := strings.Split(exampleValue, ",")
result := map[string]any{}
for _, value := range values {
mapData := strings.SplitN(value, ":", 2)
if len(mapData) == 2 {
v, err := defineTypeOfExample(arrayType, "", mapData[1])
if err != nil {
return nil, err
}
result[mapData[0]] = v
continue
}
return nil, fmt.Errorf("example value %s should format: key:value", exampleValue)
}
return result, nil
}
return nil, fmt.Errorf("%s is unsupported type in example value %s", schemaType, exampleValue)
}
// GetAllGoFileInfo gets all Go source files information for given searchDir.
func (parser *Parser) getAllGoFileInfo(packageDir, searchDir string) error {
if parser.skipPackageByPrefix(packageDir) {
return nil // ignored by user-defined package path prefixes
}
return filepath.Walk(searchDir, func(path string, f os.FileInfo, wError error) error {
if wError != nil {
return fmt.Errorf("failed to access path %q, err: %v\n", path, wError)
}
err := parser.Skip(path, f)
if err != nil {
return err
}
if f.IsDir() {
return nil
}
relPath, err := filepath.Rel(searchDir, path)
if err != nil {
return err
}
return parser.parseFile(filepath.ToSlash(filepath.Dir(filepath.Clean(filepath.Join(packageDir, relPath)))), path, nil, ParseAll)
})
}
func (parser *Parser) getAllGoFileInfoFromDeps(pkg *depth.Pkg, parseFlag ParseFlag, dirImported map[string]struct{}) error {
ignoreInternal := pkg.Internal && !parser.ParseInternal
if ignoreInternal || !pkg.Resolved { // ignored internal and not resolved dependencies
return nil
}
if pkg.Raw != nil && parser.skipPackageByPrefix(pkg.Raw.ImportPath) {
return nil // ignored by user-defined package path prefixes
}
// Skip cgo
if pkg.Raw == nil && pkg.Name == "C" {
return nil
}
srcDir := pkg.Raw.Dir
if _, ok := dirImported[srcDir]; ok {
return nil
}
dirImported[srcDir] = struct{}{}
files, err := os.ReadDir(srcDir) // only parsing files in the dir(don't contain sub dir files)
if err != nil {
return err
}
for _, f := range files {
if f.IsDir() {
continue
}
path := filepath.Join(srcDir, f.Name())
if err := parser.parseFile(pkg.Name, path, nil, parseFlag); err != nil {
return err
}
}
for i := 0; i < len(pkg.Deps); i++ {
if err := parser.getAllGoFileInfoFromDeps(&pkg.Deps[i], parseFlag, dirImported); err != nil {
return err
}
}
return nil
}
func (parser *Parser) parseFile(packageDir, path string, src any, flag ParseFlag) error {
if strings.HasSuffix(strings.ToLower(path), "_test.go") || filepath.Ext(path) != ".go" {
return nil
}
return parser.packages.ParseFile(packageDir, path, src, flag)
}
func (parser *Parser) checkOperationIDUniqueness() error {
// operationsIds contains all operationId annotations to check it's unique
operationsIds := make(map[string]string)
for path, item := range parser.swagger.Paths.Paths {
var method, id string
for method = range allMethod {
op := refRouteMethodOp(&item, method)
if *op != nil {
id = (**op).ID
break
}
}
if id == "" {
continue
}
current := fmt.Sprintf("%s %s", method, path)
previous, ok := operationsIds[id]
if ok {
return fmt.Errorf(
"duplicated @id annotation '%s' found in '%s', previously declared in: '%s'",
id, current, previous)
}
operationsIds[id] = current
}
return nil
}
// Skip returns filepath.SkipDir error if match vendor and hidden folder.
func (parser *Parser) Skip(path string, f os.FileInfo) error {
return walkWith(parser.excludes, parser.ParseVendor)(path, f)
}
func walkWith(excludes map[string]struct{}, parseVendor bool) func(path string, fileInfo os.FileInfo) error {
return func(path string, f os.FileInfo) error {
if f.IsDir() {
if !parseVendor && f.Name() == "vendor" || // ignore "vendor"
f.Name() == "docs" || // exclude docs
len(f.Name()) > 1 && f.Name()[0] == '.' && f.Name() != ".." { // exclude all hidden folder
return filepath.SkipDir
}
if excludes != nil {
if _, ok := excludes[path]; ok {
return filepath.SkipDir
}
}
}
return nil
}
}
// GetSwagger returns *spec.Swagger which is the root document object for the API specification.
func (parser *Parser) GetSwagger() *spec.Swagger {
return parser.swagger
}
// addTestType just for tests.
func (parser *Parser) addTestType(typename string) {
typeDef := &TypeSpecDef{}
parser.packages.uniqueDefinitions[typename] = typeDef
parser.parsedSchemas[typeDef] = &Schema{
PkgPath: "",
Name: typename,
Schema: PrimitiveSchema(OBJECT),
}
}
================================================
FILE: parser_test.go
================================================
package swag
import (
"bytes"
"encoding/json"
"errors"
"go/ast"
goparser "go/parser"
"go/token"
"log"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/go-openapi/spec"
"github.com/stretchr/testify/assert"
)
const defaultParseDepth = 100
const mainAPIFile = "main.go"
func TestNew(t *testing.T) {
t.Run("SetMarkdownFileDirectory", func(t *testing.T) {
t.Parallel()
expected := "docs/markdown"
p := New(SetMarkdownFileDirectory(expected))
assert.Equal(t, expected, p.markdownFileDir)
})
t.Run("SetCodeExamplesDirectory", func(t *testing.T) {
t.Parallel()
expected := "docs/examples"
p := New(SetCodeExamplesDirectory(expected))
assert.Equal(t, expected, p.codeExampleFilesDir)
})
t.Run("SetStrict", func(t *testing.T) {
t.Parallel()
p := New()
assert.Equal(t, false, p.Strict)
p = New(SetStrict(true))
assert.Equal(t, true, p.Strict)
})
t.Run("SetDebugger", func(t *testing.T) {
t.Parallel()
logger := log.New(&bytes.Buffer{}, "", log.LstdFlags)
p := New(SetDebugger(logger))
assert.Equal(t, logger, p.debug)
})
t.Run("SetFieldParserFactory", func(t *testing.T) {
t.Parallel()
p := New(SetFieldParserFactory(nil))
assert.Nil(t, p.fieldParserFactory)
})
}
func TestSetOverrides(t *testing.T) {
t.Parallel()
overrides := map[string]string{
"foo": "bar",
}
p := New(SetOverrides(overrides))
assert.Equal(t, overrides, p.Overrides)
}
func TestOverrides_getTypeSchema(t *testing.T) {
t.Parallel()
overrides := map[string]string{
"sql.NullString": "string",
}
p := New(SetOverrides(overrides))
t.Run("Override sql.NullString by string", func(t *testing.T) {
t.Parallel()
s, err := p.getTypeSchema("sql.NullString", nil, false)
if assert.NoError(t, err) {
assert.Truef(t, s.Type.Contains("string"), "type sql.NullString should be overridden by string")
}
})
t.Run("Missing Override for sql.NullInt64", func(t *testing.T) {
t.Parallel()
_, err := p.getTypeSchema("sql.NullInt64", nil, false)
if assert.Error(t, err) {
assert.Equal(t, "cannot find type definition: sql.NullInt64", err.Error())
}
})
}
func TestParser_ParseDefinition(t *testing.T) {
p := New()
// Parsing existing type
definition := &TypeSpecDef{
PkgPath: "github.com/swagger/swag",
File: &ast.File{
Name: &ast.Ident{
Name: "swag",
},
},
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{
Name: "Test",
},
},
}
expected := &Schema{}
p.parsedSchemas[definition] = expected
schema, err := p.ParseDefinition(definition)
assert.NoError(t, err)
assert.Equal(t, expected, schema)
// Parsing *ast.FuncType
definition = &TypeSpecDef{
PkgPath: "github.com/swagger/swag/model",
File: &ast.File{
Name: &ast.Ident{
Name: "model",
},
},
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{
Name: "Test",
},
Type: &ast.FuncType{},
},
}
_, err = p.ParseDefinition(definition)
assert.Error(t, err)
// Parsing *ast.FuncType with parent spec
definition = &TypeSpecDef{
PkgPath: "github.com/swagger/swag/model",
File: &ast.File{
Name: &ast.Ident{
Name: "model",
},
},
TypeSpec: &ast.TypeSpec{
Name: &ast.Ident{
Name: "Test",
},
Type: &ast.FuncType{},
},
ParentSpec: &ast.FuncDecl{
Name: ast.NewIdent("TestFuncDecl"),
},
}
_, err = p.ParseDefinition(definition)
assert.Error(t, err)
assert.Equal(t, "model.TestFuncDecl.Test", definition.TypeName())
}
func TestParser_ParseGeneralApiInfo(t *testing.T) {
t.Parallel()
expected := `{
"schemes": [
"http",
"https"
],
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.\nIt has a lot of beautiful features.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0",
"x-logo": {
"altText": "Petstore logo",
"backgroundColor": "#FFFFFF",
"url": "https://redocly.github.io/redoc/petstore-logo.png"
}
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"paths": {},
"securityDefinitions": {
"ApiKeyAuth": {
"description": "some description",
"type": "apiKey",
"name": "Authorization",
"in": "header"
},
"BasicAuth": {
"type": "basic"
},
"OAuth2AccessCode": {
"type": "oauth2",
"flow": "accessCode",
"authorizationUrl": "https://example.com/oauth/authorize",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information"
},
"x-tokenname": "id_token"
},
"OAuth2Application": {
"type": "oauth2",
"flow": "application",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
},
"OAuth2Implicit": {
"type": "oauth2",
"flow": "implicit",
"authorizationUrl": "https://example.com/oauth/authorize",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
},
"x-google-audiences": "some_audience.google.com"
},
"OAuth2Password": {
"type": "oauth2",
"flow": "password",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"read": "Grants read access",
"write": "Grants write access"
}
}
},
"externalDocs": {
"description": "OpenAPI",
"url": "https://swagger.io/resources/open-api"
},
"x-google-endpoints": [
{
"allowCors": true,
"name": "name.endpoints.environment.cloud.goog"
}
],
"x-google-marks": "marks values"
}`
gopath := os.Getenv("GOPATH")
assert.NotNil(t, gopath)
p := New()
err := p.ParseGeneralAPIInfo("testdata/main.go")
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestParser_ParseGeneralApiInfoTemplated(t *testing.T) {
t.Parallel()
expected := `{
"swagger": "2.0",
"info": {
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"paths": {},
"securityDefinitions": {
"ApiKeyAuth": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
},
"BasicAuth": {
"type": "basic"
},
"OAuth2AccessCode": {
"type": "oauth2",
"flow": "accessCode",
"authorizationUrl": "https://example.com/oauth/authorize",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information"
}
},
"OAuth2Application": {
"type": "oauth2",
"flow": "application",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
},
"OAuth2Implicit": {
"type": "oauth2",
"flow": "implicit",
"authorizationUrl": "https://example.com/oauth/authorize",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
},
"OAuth2Password": {
"type": "oauth2",
"flow": "password",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"read": "Grants read access",
"write": "Grants write access"
}
}
},
"externalDocs": {
"description": "OpenAPI",
"url": "https://swagger.io/resources/open-api"
},
"x-google-endpoints": [
{
"allowCors": true,
"name": "name.endpoints.environment.cloud.goog"
}
],
"x-google-marks": "marks values"
}`
gopath := os.Getenv("GOPATH")
assert.NotNil(t, gopath)
p := New()
err := p.ParseGeneralAPIInfo("testdata/templated.go")
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestParser_ParseGeneralApiInfoExtensions(t *testing.T) {
// should return an error because extension value is not a valid json
t.Run("Test invalid extension value", func(t *testing.T) {
t.Parallel()
expected := "annotation @x-google-endpoints need a valid json value"
gopath := os.Getenv("GOPATH")
assert.NotNil(t, gopath)
p := New()
err := p.ParseGeneralAPIInfo("testdata/extensionsFail1.go")
if assert.Error(t, err) {
assert.Equal(t, expected, err.Error())
}
})
// should return an error because extension don't have a value
t.Run("Test missing extension value", func(t *testing.T) {
t.Parallel()
expected := "annotation @x-google-endpoints need a value"
gopath := os.Getenv("GOPATH")
assert.NotNil(t, gopath)
p := New()
err := p.ParseGeneralAPIInfo("testdata/extensionsFail2.go")
if assert.Error(t, err) {
assert.Equal(t, expected, err.Error())
}
})
}
func TestParser_ParseGeneralApiInfoWithOpsInSameFile(t *testing.T) {
t.Parallel()
expected := `{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.\nIt has a lot of beautiful features.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
"version": "1.0"
},
"paths": {}
}`
gopath := os.Getenv("GOPATH")
assert.NotNil(t, gopath)
p := New()
err := p.ParseGeneralAPIInfo("testdata/single_file_api/main.go")
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestParser_ParseGeneralAPIInfoMarkdown(t *testing.T) {
t.Parallel()
p := New(SetMarkdownFileDirectory("testdata"))
mainAPIFile := "testdata/markdown.go"
err := p.ParseGeneralAPIInfo(mainAPIFile)
assert.NoError(t, err)
expected := `{
"swagger": "2.0",
"info": {
"description": "Swagger Example API Markdown Description",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
"version": "1.0"
},
"paths": {},
"tags": [
{
"description": "Users Tag Markdown Description",
"name": "users"
}
]
}`
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
p = New()
err = p.ParseGeneralAPIInfo(mainAPIFile)
assert.Error(t, err)
}
func TestParser_ParseGeneralApiInfoFailed(t *testing.T) {
t.Parallel()
gopath := os.Getenv("GOPATH")
assert.NotNil(t, gopath)
p := New()
assert.Error(t, p.ParseGeneralAPIInfo("testdata/noexist.go"))
}
func TestParser_ParseAcceptComment(t *testing.T) {
t.Parallel()
expected := []string{
"application/json",
"text/xml",
"text/plain",
"text/html",
"multipart/form-data",
"application/x-www-form-urlencoded",
"application/vnd.api+json",
"application/x-json-stream",
"application/octet-stream",
"image/png",
"image/jpeg",
"image/gif",
"application/xhtml+xml",
"application/health+json",
"text/event-stream",
}
comment := `@Accept json,xml,plain,html,mpfd,x-www-form-urlencoded,json-api,json-stream,octet-stream,png,jpeg,gif,application/xhtml+xml,application/health+json,event-stream`
parser := New()
assert.NoError(t, parseGeneralAPIInfo(parser, []string{comment}))
assert.Equal(t, parser.swagger.Consumes, expected)
assert.Error(t, parseGeneralAPIInfo(parser, []string{`@Accept cookies,candies`}))
parser = New()
assert.NoError(t, parser.ParseAcceptComment(comment[len(acceptAttr)+1:]))
assert.Equal(t, parser.swagger.Consumes, expected)
}
func TestParser_ParseProduceComment(t *testing.T) {
t.Parallel()
expected := []string{
"application/json",
"text/xml",
"text/plain",
"text/html",
"multipart/form-data",
"application/x-www-form-urlencoded",
"application/vnd.api+json",
"application/x-json-stream",
"application/octet-stream",
"image/png",
"image/jpeg",
"image/gif",
"application/xhtml+xml",
"application/health+json",
"text/event-stream",
}
comment := `@Produce json,xml,plain,html,mpfd,x-www-form-urlencoded,json-api,json-stream,octet-stream,png,jpeg,gif,application/xhtml+xml,application/health+json,event-stream`
parser := New()
assert.NoError(t, parseGeneralAPIInfo(parser, []string{comment}))
assert.Equal(t, parser.swagger.Produces, expected)
assert.Error(t, parseGeneralAPIInfo(parser, []string{`@Produce cookies,candies`}))
parser = New()
assert.NoError(t, parser.ParseProduceComment(comment[len(produceAttr)+1:]))
assert.Equal(t, parser.swagger.Produces, expected)
}
func TestParser_ParseGeneralAPIInfoCollectionFormat(t *testing.T) {
t.Parallel()
parser := New()
assert.NoError(t, parseGeneralAPIInfo(parser, []string{
"@query.collection.format csv",
}))
assert.Equal(t, parser.collectionFormatInQuery, "csv")
assert.NoError(t, parseGeneralAPIInfo(parser, []string{
"@query.collection.format tsv",
}))
assert.Equal(t, parser.collectionFormatInQuery, "tsv")
}
func TestParser_ParseGeneralAPITagGroups(t *testing.T) {
t.Parallel()
parser := New()
assert.NoError(t, parseGeneralAPIInfo(parser, []string{
"@x-tagGroups [{\"name\":\"General\",\"tags\":[\"lanes\",\"video-recommendations\"]}]",
}))
expected := []interface{}{map[string]interface{}{"name": "General", "tags": []interface{}{"lanes", "video-recommendations"}}}
assert.Equal(t, parser.swagger.Extensions["x-tagGroups"], expected)
}
func TestParser_ParseGeneralAPITagDocs(t *testing.T) {
t.Parallel()
parser := New()
assert.Error(t, parseGeneralAPIInfo(parser, []string{
"@tag.name Test",
"@tag.docs.description Best example documentation"}))
parser = New()
err := parseGeneralAPIInfo(parser, []string{
"@tag.name test",
"@tag.description A test Tag",
"@tag.docs.url https://example.com",
"@tag.docs.description Best example documentation",
"@tag.x-displayName Test group"})
assert.NoError(t, err)
b, _ := json.MarshalIndent(parser.GetSwagger().Tags, "", " ")
expected := `[
{
"description": "A test Tag",
"name": "test",
"externalDocs": {
"description": "Best example documentation",
"url": "https://example.com"
},
"x-displayName": "Test group"
}
]`
assert.Equal(t, expected, string(b))
}
func TestParser_ParseGeneralAPITagDocsWithTagFilters(t *testing.T) {
t.Parallel()
filterTags := []string{"test1", "!test2"}
comments := []string{
"@tag.name test1",
"@tag.description A test1 Tag",
"@tag.docs.url https://example1.com",
"@tag.docs.description Best example1 documentation",
"@tag.name test2",
"@tag.description A test2 Tag",
"@tag.docs.url https://example2.com",
"@tag.docs.description Best example2 documentation"}
expected := `[
{
"description": "A test1 Tag",
"name": "test1",
"externalDocs": {
"description": "Best example1 documentation",
"url": "https://example1.com"
}
}
]`
for _, tag := range filterTags {
parser := New(SetTags(tag))
err := parseGeneralAPIInfo(parser, comments)
assert.NoError(t, err)
b, _ := json.MarshalIndent(parser.GetSwagger().Tags, "", " ")
assert.Equal(t, expected, string(b))
}
}
func TestParser_ParseGeneralAPISecurity(t *testing.T) {
t.Run("ApiKey", func(t *testing.T) {
t.Parallel()
parser := New()
assert.Error(t, parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.apikey ApiKey"}))
assert.Error(t, parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.apikey ApiKey",
"@in header"}))
assert.Error(t, parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.apikey ApiKey",
"@name X-API-KEY"}))
err := parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.apikey ApiKey",
"@in header",
"@name X-API-KEY",
"@description some",
"",
"@securitydefinitions.oauth2.accessCode OAuth2AccessCode",
"@tokenUrl https://example.com/oauth/token",
"@authorizationUrl https://example.com/oauth/authorize",
"@scope.admin foo",
})
assert.NoError(t, err)
b, _ := json.MarshalIndent(parser.GetSwagger().SecurityDefinitions, "", " ")
expected := `{
"ApiKey": {
"description": "some",
"type": "apiKey",
"name": "X-API-KEY",
"in": "header"
},
"OAuth2AccessCode": {
"type": "oauth2",
"flow": "accessCode",
"authorizationUrl": "https://example.com/oauth/authorize",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "foo"
}
}
}`
assert.Equal(t, expected, string(b))
})
t.Run("OAuth2Application", func(t *testing.T) {
t.Parallel()
parser := New()
assert.Error(t, parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.oauth2.application OAuth2Application"}))
err := parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.oauth2.application OAuth2Application",
"@tokenUrl https://example.com/oauth/token"})
assert.NoError(t, err)
b, _ := json.MarshalIndent(parser.GetSwagger().SecurityDefinitions, "", " ")
expected := `{
"OAuth2Application": {
"type": "oauth2",
"flow": "application",
"tokenUrl": "https://example.com/oauth/token"
}
}`
assert.Equal(t, expected, string(b))
})
t.Run("OAuth2Implicit", func(t *testing.T) {
t.Parallel()
parser := New()
assert.Error(t, parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.oauth2.implicit OAuth2Implicit"}))
err := parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.oauth2.implicit OAuth2Implicit",
"@authorizationurl https://example.com/oauth/authorize"})
assert.NoError(t, err)
b, _ := json.MarshalIndent(parser.GetSwagger().SecurityDefinitions, "", " ")
expected := `{
"OAuth2Implicit": {
"type": "oauth2",
"flow": "implicit",
"authorizationUrl": "https://example.com/oauth/authorize"
}
}`
assert.Equal(t, expected, string(b))
})
t.Run("OAuth2Password", func(t *testing.T) {
t.Parallel()
parser := New()
assert.Error(t, parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.oauth2.password OAuth2Password"}))
err := parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.oauth2.password OAuth2Password",
"@tokenUrl https://example.com/oauth/token"})
assert.NoError(t, err)
b, _ := json.MarshalIndent(parser.GetSwagger().SecurityDefinitions, "", " ")
expected := `{
"OAuth2Password": {
"type": "oauth2",
"flow": "password",
"tokenUrl": "https://example.com/oauth/token"
}
}`
assert.Equal(t, expected, string(b))
})
t.Run("OAuth2AccessCode", func(t *testing.T) {
t.Parallel()
parser := New()
assert.Error(t, parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.oauth2.accessCode OAuth2AccessCode"}))
assert.Error(t, parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.oauth2.accessCode OAuth2AccessCode",
"@tokenUrl https://example.com/oauth/token"}))
assert.Error(t, parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.oauth2.accessCode OAuth2AccessCode",
"@authorizationurl https://example.com/oauth/authorize"}))
err := parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.oauth2.accessCode OAuth2AccessCode",
"@tokenUrl https://example.com/oauth/token",
"@authorizationurl https://example.com/oauth/authorize"})
assert.NoError(t, err)
b, _ := json.MarshalIndent(parser.GetSwagger().SecurityDefinitions, "", " ")
expected := `{
"OAuth2AccessCode": {
"type": "oauth2",
"flow": "accessCode",
"authorizationUrl": "https://example.com/oauth/authorize",
"tokenUrl": "https://example.com/oauth/token"
}
}`
assert.Equal(t, expected, string(b))
assert.Error(t, parseGeneralAPIInfo(parser, []string{
"@securitydefinitions.oauth2.accessCode OAuth2AccessCode",
"@tokenUrl https://example.com/oauth/token",
"@authorizationurl https://example.com/oauth/authorize",
"@scope.read,write Multiple scope"}))
})
}
func TestParser_RefWithOtherPropertiesIsWrappedInAllOf(t *testing.T) {
t.Run("Readonly", func(t *testing.T) {
src := `
package main
type Teacher struct {
Name string
} //@name Teacher
type Student struct {
Name string
Age int ` + "`readonly:\"true\"`" + `
Teacher Teacher ` + "`readonly:\"true\"`" + `
OtherTeacher Teacher
} //@name Student
// @Success 200 {object} Student
// @Router /test [get]
func Fun() {
}
`
expected := `{
"info": {
"contact": {}
},
"paths": {
"/test": {
"get": {
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Student"
}
}
}
}
}
},
"definitions": {
"Student": {
"type": "object",
"properties": {
"age": {
"type": "integer",
"readOnly": true
},
"name": {
"type": "string"
},
"otherTeacher": {
"$ref": "#/definitions/Teacher"
},
"teacher": {
"allOf": [
{
"$ref": "#/definitions/Teacher"
}
],
"readOnly": true
}
}
},
"Teacher": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}
}
}`
p := New()
_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
_, err := p.packages.ParseTypes()
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
})
}
func TestGetAllGoFileInfo(t *testing.T) {
t.Parallel()
searchDir := "testdata/pet"
p := New()
err := p.getAllGoFileInfo("testdata", searchDir)
assert.NoError(t, err)
assert.Equal(t, 2, len(p.packages.files))
}
func TestParser_ParseType(t *testing.T) {
t.Parallel()
searchDir := "testdata/simple/"
p := New()
err := p.getAllGoFileInfo("testdata", searchDir)
assert.NoError(t, err)
_, err = p.packages.ParseTypes()
assert.NoError(t, err)
assert.NotNil(t, p.packages.uniqueDefinitions["api.Pet3"])
assert.NotNil(t, p.packages.uniqueDefinitions["web.Pet"])
assert.NotNil(t, p.packages.uniqueDefinitions["web.Pet2"])
}
func TestParseSimpleApi1(t *testing.T) {
t.Parallel()
expected, err := os.ReadFile("testdata/simple/expected.json")
assert.NoError(t, err)
searchDir := "testdata/simple"
p := New()
p.PropNamingStrategy = PascalCase
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.JSONEq(t, string(expected), string(b))
}
func TestParseInterfaceAndError(t *testing.T) {
t.Parallel()
expected, err := os.ReadFile("testdata/error/expected.json")
assert.NoError(t, err)
searchDir := "testdata/error"
p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.JSONEq(t, string(expected), string(b))
}
func TestParseSimpleApi_ForSnakecase(t *testing.T) {
t.Parallel()
expected := `{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"paths": {
"/file/upload": {
"post": {
"description": "Upload file",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"summary": "Upload file",
"operationId": "file.upload",
"parameters": [
{
"type": "file",
"description": "this is a test file",
"name": "file",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
},
"/testapi/get-string-by-int/{some_id}": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add a new pet to the store",
"operationId": "get-string-by-int",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Some ID",
"name": "some_id",
"in": "path",
"required": true
},
{
"description": "Some ID",
"name": "some_id",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.Pet"
}
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
},
"/testapi/get-struct-array-by-string/{some_id}": {
"get": {
"description": "get struct array by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "get-struct-array-by-string",
"parameters": [
{
"type": "string",
"description": "Some ID",
"name": "some_id",
"in": "path",
"required": true
},
{
"enum": [
1,
2,
3
],
"type": "integer",
"description": "Category",
"name": "category",
"in": "query",
"required": true
},
{
"minimum": 0,
"type": "integer",
"default": 0,
"description": "Offset",
"name": "offset",
"in": "query",
"required": true
},
{
"maximum": 50,
"type": "integer",
"default": 10,
"description": "Limit",
"name": "limit",
"in": "query",
"required": true
},
{
"maxLength": 50,
"minLength": 1,
"type": "string",
"default": "\"\"",
"description": "q",
"name": "q",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"BasicAuth": []
},
{
"OAuth2Application": [
"write"
]
},
{
"OAuth2Implicit": [
"read",
"admin"
]
},
{
"OAuth2AccessCode": [
"read"
]
},
{
"OAuth2Password": [
"admin"
]
}
]
}
}
},
"definitions": {
"web.APIError": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"error_code": {
"type": "integer"
},
"error_message": {
"type": "string"
}
}
},
"web.Pet": {
"type": "object",
"required": [
"price"
],
"properties": {
"birthday": {
"type": "integer"
},
"category": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "category_name"
},
"photo_urls": {
"type": "array",
"items": {
"type": "string",
"format": "url"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
},
"small_category": {
"type": "object",
"required": [
"name"
],
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "detail_category_name"
},
"photo_urls": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
}
}
}
}
},
"coeffs": {
"type": "array",
"items": {
"type": "number"
}
},
"custom_string": {
"type": "string"
},
"custom_string_arr": {
"type": "array",
"items": {
"type": "string"
}
},
"data": {},
"decimal": {
"type": "number"
},
"id": {
"type": "integer",
"format": "int64",
"example": 1
},
"is_alive": {
"type": "boolean",
"example": true
},
"name": {
"type": "string",
"example": "poti"
},
"null_int": {
"type": "integer"
},
"pets": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet2"
}
},
"pets2": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet2"
}
},
"photo_urls": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
},
"price": {
"type": "number",
"maximum": 130,
"minimum": 0,
"multipleOf": 0.01,
"example": 3.25
},
"status": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Tag"
}
},
"uuid": {
"type": "string"
}
}
},
"web.Pet2": {
"type": "object",
"properties": {
"deleted_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"middle_name": {
"type": "string"
}
}
},
"web.RevValue": {
"type": "object",
"properties": {
"data": {
"type": "integer"
},
"err": {
"type": "integer",
"format": "int32"
},
"status": {
"type": "boolean"
}
}
},
"web.Tag": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"pets": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet"
}
}
}
}
},
"securityDefinitions": {
"ApiKeyAuth": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
},
"BasicAuth": {
"type": "basic"
},
"OAuth2AccessCode": {
"type": "oauth2",
"flow": "accessCode",
"authorizationUrl": "https://example.com/oauth/authorize",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information"
}
},
"OAuth2Application": {
"type": "oauth2",
"flow": "application",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
},
"OAuth2Implicit": {
"type": "oauth2",
"flow": "implicit",
"authorizationUrl": "https://example.com/oauth/authorize",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
},
"OAuth2Password": {
"type": "oauth2",
"flow": "password",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"read": "Grants read access",
"write": "Grants write access"
}
}
}
}`
searchDir := "testdata/simple2"
p := New()
p.PropNamingStrategy = SnakeCase
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestParseSimpleApi_ForLowerCamelcase(t *testing.T) {
t.Parallel()
expected := `{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"paths": {
"/file/upload": {
"post": {
"description": "Upload file",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"summary": "Upload file",
"operationId": "file.upload",
"parameters": [
{
"type": "file",
"description": "this is a test file",
"name": "file",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
},
"/testapi/get-string-by-int/{some_id}": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add a new pet to the store",
"operationId": "get-string-by-int",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Some ID",
"name": "some_id",
"in": "path",
"required": true
},
{
"description": "Some ID",
"name": "some_id",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.Pet"
}
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
},
"/testapi/get-struct-array-by-string/{some_id}": {
"get": {
"description": "get struct array by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "get-struct-array-by-string",
"parameters": [
{
"type": "string",
"description": "Some ID",
"name": "some_id",
"in": "path",
"required": true
},
{
"enum": [
1,
2,
3
],
"type": "integer",
"description": "Category",
"name": "category",
"in": "query",
"required": true
},
{
"minimum": 0,
"type": "integer",
"default": 0,
"description": "Offset",
"name": "offset",
"in": "query",
"required": true
},
{
"maximum": 50,
"type": "integer",
"default": 10,
"description": "Limit",
"name": "limit",
"in": "query",
"required": true
},
{
"maxLength": 50,
"minLength": 1,
"type": "string",
"default": "\"\"",
"description": "q",
"name": "q",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
},
"security": [
{
"ApiKeyAuth": []
},
{
"BasicAuth": []
},
{
"OAuth2Application": [
"write"
]
},
{
"OAuth2Implicit": [
"read",
"admin"
]
},
{
"OAuth2AccessCode": [
"read"
]
},
{
"OAuth2Password": [
"admin"
]
}
]
}
}
},
"definitions": {
"web.APIError": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"errorCode": {
"type": "integer"
},
"errorMessage": {
"type": "string"
}
}
},
"web.Pet": {
"type": "object",
"properties": {
"category": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "category_name"
},
"photoURLs": {
"type": "array",
"items": {
"type": "string",
"format": "url"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
},
"smallCategory": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "detail_category_name"
},
"photoURLs": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
}
}
}
}
},
"data": {},
"decimal": {
"type": "number"
},
"id": {
"type": "integer",
"format": "int64",
"example": 1
},
"isAlive": {
"type": "boolean",
"example": true
},
"name": {
"type": "string",
"example": "poti"
},
"pets": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet2"
}
},
"pets2": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet2"
}
},
"photoURLs": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
},
"price": {
"type": "number",
"multipleOf": 0.01,
"example": 3.25
},
"status": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Tag"
}
},
"uuid": {
"type": "string"
}
}
},
"web.Pet2": {
"type": "object",
"properties": {
"deletedAt": {
"type": "string"
},
"id": {
"type": "integer"
},
"middleName": {
"type": "string"
}
}
},
"web.RevValue": {
"type": "object",
"properties": {
"data": {
"type": "integer"
},
"err": {
"type": "integer",
"format": "int32"
},
"status": {
"type": "boolean"
}
}
},
"web.Tag": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"pets": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet"
}
}
}
}
},
"securityDefinitions": {
"ApiKeyAuth": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
},
"BasicAuth": {
"type": "basic"
},
"OAuth2AccessCode": {
"type": "oauth2",
"flow": "accessCode",
"authorizationUrl": "https://example.com/oauth/authorize",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information"
}
},
"OAuth2Application": {
"type": "oauth2",
"flow": "application",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
},
"OAuth2Implicit": {
"type": "oauth2",
"flow": "implicit",
"authorizationUrl": "https://example.com/oauth/authorize",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
},
"OAuth2Password": {
"type": "oauth2",
"flow": "password",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"read": "Grants read access",
"write": "Grants write access"
}
}
}
}`
searchDir := "testdata/simple3"
p := New()
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestParseStructComment(t *testing.T) {
t.Parallel()
expected := `{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"host": "localhost:4000",
"basePath": "/api",
"paths": {
"/posts/{post_id}": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add a new pet to the store",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Some ID",
"name": "post_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
}
},
"definitions": {
"web.APIError": {
"description": "API error with information about it",
"type": "object",
"properties": {
"createdAt": {
"description": "Error time",
"type": "string"
},
"error": {
"description": "Error an Api error",
"type": "string"
},
"errorCtx": {
"description": "Error ` + "`" + `context` + "`" + ` tick comment",
"type": "string"
},
"errorNo": {
"description": "Error ` + "`" + `number` + "`" + ` tick comment",
"type": "integer",
"format": "int64"
}
}
}
}
}`
searchDir := "testdata/struct_comment"
p := New()
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestParseNonExportedJSONFields(t *testing.T) {
t.Parallel()
expected := `{
"swagger": "2.0",
"info": {
"description": "This is a sample server.",
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"host": "localhost:4000",
"basePath": "/api",
"paths": {
"/so-something": {
"get": {
"description": "Does something, but internal (non-exported) fields inside a struct won't be marshaled into JSON",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Call DoSomething",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/main.MyStruct"
}
}
}
}
}
},
"definitions": {
"main.MyStruct": {
"type": "object",
"properties": {
"data": {
"description": "Post data",
"type": "object",
"properties": {
"name": {
"description": "Post tag",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"id": {
"type": "integer",
"format": "int64",
"example": 1
},
"name": {
"description": "Post name",
"type": "string",
"example": "poti"
}
}
}
}
}`
searchDir := "testdata/non_exported_json_fields"
p := New()
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestParsePetApi(t *testing.T) {
t.Parallel()
expected := `{
"schemes": [
"http",
"https"
],
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key 'special-key' to test the authorization filters.",
"title": "Swagger Petstore",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"email": "apiteam@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"paths": {}
}`
searchDir := "testdata/pet"
p := New()
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestParseModelAsTypeAlias(t *testing.T) {
t.Parallel()
expected := `{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"paths": {
"/testapi/time-as-time-container": {
"get": {
"description": "test container with time and time alias",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Get container with time and time alias",
"operationId": "time-as-time-container",
"responses": {
"200": {
"description": "ok",
"schema": {
"$ref": "#/definitions/data.TimeContainer"
}
}
}
}
}
},
"definitions": {
"data.TimeContainer": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"name": {
"type": "string"
},
"timestamp": {
"type": "string"
}
}
}
}
}`
searchDir := "testdata/alias_type"
p := New()
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestParseComposition(t *testing.T) {
t.Parallel()
searchDir := "testdata/composition"
p := New()
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
// windows will fail: \r\n \n
assert.Equal(t, string(expected), string(b))
}
func TestParseImportAliases(t *testing.T) {
t.Parallel()
searchDir := "testdata/alias_import"
p := New()
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
// windows will fail: \r\n \n
assert.Equal(t, string(expected), string(b))
}
func TestParseTypeOverrides(t *testing.T) {
t.Parallel()
searchDir := "testdata/global_override"
p := New(SetOverrides(map[string]string{
"github.com/swaggo/swag/testdata/global_override/types.Application": "string",
"github.com/swaggo/swag/testdata/global_override/types.Application2": "github.com/swaggo/swag/testdata/global_override/othertypes.Application",
"github.com/swaggo/swag/testdata/global_override/types.ShouldSkip": "",
}))
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
//windows will fail: \r\n \n
assert.Equal(t, string(expected), string(b))
}
func TestGlobalSecurity(t *testing.T) {
t.Parallel()
searchDir := "testdata/global_security"
p := New()
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, string(expected), string(b))
}
func TestParseNested(t *testing.T) {
t.Parallel()
searchDir := "testdata/nested"
p := New(SetParseDependency(1))
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, string(expected), string(b))
}
func TestParseDuplicated(t *testing.T) {
t.Parallel()
searchDir := "testdata/duplicated"
p := New(SetParseDependency(1))
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.Errorf(t, err, "duplicated @id declarations successfully found")
}
func TestParseDuplicatedOtherMethods(t *testing.T) {
t.Parallel()
searchDir := "testdata/duplicated2"
p := New(SetParseDependency(1))
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.Errorf(t, err, "duplicated @id declarations successfully found")
}
func TestParseDuplicatedFunctionScoped(t *testing.T) {
t.Parallel()
searchDir := "testdata/duplicated_function_scoped"
p := New(SetParseDependency(1))
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.Errorf(t, err, "duplicated @id declarations successfully found")
}
func TestParseConflictSchemaName(t *testing.T) {
t.Parallel()
searchDir := "testdata/conflict_name"
p := New(SetParseDependency(1))
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseExternalModels(t *testing.T) {
searchDir := "testdata/external_models/main"
mainAPIFile := "main.go"
p := New(SetParseDependency(1))
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
//ioutil.WriteFile("./testdata/external_models/main/expected.json",b,0777)
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParseGoList(t *testing.T) {
mainAPIFile := "main.go"
p := New(ParseUsingGoList(true), SetParseDependency(1))
go111moduleEnv := os.Getenv("GO111MODULE")
cases := []struct {
name string
gomodule bool
searchDir string
err error
run func(searchDir string) error
}{
{
name: "disableGOMODULE",
gomodule: false,
searchDir: "testdata/golist_disablemodule",
run: func(searchDir string) error {
return p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
},
},
{
name: "enableGOMODULE",
gomodule: true,
searchDir: "testdata/golist",
run: func(searchDir string) error {
return p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
},
},
{
name: "invalid_main",
gomodule: true,
searchDir: "testdata/golist_invalid",
err: errors.New("no such file or directory"),
run: func(searchDir string) error {
return p.ParseAPI(searchDir, "invalid/main.go", defaultParseDepth)
},
},
{
name: "internal_invalid_pkg",
gomodule: true,
searchDir: "testdata/golist_invalid",
err: errors.New("expected 'package', found This"),
run: func(searchDir string) error {
mockErrGoFile := "testdata/golist_invalid/err.go"
f, err := os.OpenFile(mockErrGoFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write([]byte(`package invalid
function a() {}`))
if err != nil {
return err
}
defer os.Remove(mockErrGoFile)
return p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
},
},
{
name: "invalid_pkg",
gomodule: true,
searchDir: "testdata/golist_invalid",
err: errors.New("expected 'package', found This"),
run: func(searchDir string) error {
mockErrGoFile := "testdata/invalid_external_pkg/invalid/err.go"
f, err := os.OpenFile(mockErrGoFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write([]byte(`package invalid
function a() {}`))
if err != nil {
return err
}
defer os.Remove(mockErrGoFile)
return p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if c.gomodule {
os.Setenv("GO111MODULE", "on")
} else {
os.Setenv("GO111MODULE", "off")
}
err := c.run(c.searchDir)
os.Setenv("GO111MODULE", go111moduleEnv)
if c.err == nil {
assert.NoError(t, err)
} else {
assert.Error(t, err)
}
})
}
}
func TestParser_ParseStructArrayObject(t *testing.T) {
t.Parallel()
src := `
package api
type Response struct {
Code int
Table [][]string
Data []struct{
Field1 uint
Field2 string
}
}
// @Success 200 {object} Response
// @Router /api/{id} [get]
func Test(){
}
`
expected := `{
"api.Response": {
"type": "object",
"properties": {
"code": {
"type": "integer"
},
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"field1": {
"type": "integer"
},
"field2": {
"type": "string"
}
}
}
},
"table": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}`
p := New()
_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
_, err := p.packages.ParseTypes()
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
out, err := json.MarshalIndent(p.swagger.Definitions, "", " ")
assert.NoError(t, err)
assert.Equal(t, expected, string(out))
}
func TestParser_ParseEmbededStruct(t *testing.T) {
t.Parallel()
src := `
package api
type Response struct {
rest.ResponseWrapper
}
// @Success 200 {object} Response
// @Router /api/{id} [get]
func Test(){
}
`
restsrc := `
package rest
type ResponseWrapper struct {
Status string
Code int
Messages []string
Result interface{}
}
`
expected := `{
"api.Response": {
"type": "object",
"properties": {
"code": {
"type": "integer"
},
"messages": {
"type": "array",
"items": {
"type": "string"
}
},
"result": {},
"status": {
"type": "string"
}
}
}
}`
parser := New(SetParseDependency(1))
_ = parser.packages.ParseFile("api", "api/api.go", src, ParseAll)
_ = parser.packages.ParseFile("rest", "rest/rest.go", restsrc, ParseAll)
_, err := parser.packages.ParseTypes()
assert.NoError(t, err)
err = parser.packages.RangeFiles(parser.ParseRouterAPIInfo)
assert.NoError(t, err)
out, err := json.MarshalIndent(parser.swagger.Definitions, "", " ")
assert.NoError(t, err)
assert.Equal(t, expected, string(out))
}
func TestParser_ParseStructPointerMembers(t *testing.T) {
t.Parallel()
src := `
package api
type Child struct {
Name string
}
type Parent struct {
Test1 *string //test1
Test2 *Child //test2
}
// @Success 200 {object} Parent
// @Router /api/{id} [get]
func Test(){
}
`
expected := `{
"api.Child": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"api.Parent": {
"type": "object",
"properties": {
"test1": {
"description": "test1",
"type": "string"
},
"test2": {
"description": "test2",
"allOf": [
{
"$ref": "#/definitions/api.Child"
}
]
}
}
}
}`
p := New()
_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
_, err := p.packages.ParseTypes()
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
out, err := json.MarshalIndent(p.swagger.Definitions, "", " ")
assert.NoError(t, err)
assert.Equal(t, expected, string(out))
}
func TestParser_ParseStructMapMember(t *testing.T) {
t.Parallel()
src := `
package api
type MyMapType map[string]string
type Child struct {
Name string
}
type Parent struct {
Test1 map[string]interface{} //test1
Test2 map[string]string //test2
Test3 map[string]*string //test3
Test4 map[string]Child //test4
Test5 map[string]*Child //test5
Test6 MyMapType //test6
Test7 []Child //test7
Test8 []*Child //test8
Test9 []map[string]string //test9
}
// @Success 200 {object} Parent
// @Router /api/{id} [get]
func Test(){
}
`
expected := `{
"api.Child": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"api.MyMapType": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"api.Parent": {
"type": "object",
"properties": {
"test1": {
"description": "test1",
"type": "object",
"additionalProperties": true
},
"test2": {
"description": "test2",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"test3": {
"description": "test3",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"test4": {
"description": "test4",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/api.Child"
}
},
"test5": {
"description": "test5",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/api.Child"
}
},
"test6": {
"description": "test6",
"allOf": [
{
"$ref": "#/definitions/api.MyMapType"
}
]
},
"test7": {
"description": "test7",
"type": "array",
"items": {
"$ref": "#/definitions/api.Child"
}
},
"test8": {
"description": "test8",
"type": "array",
"items": {
"$ref": "#/definitions/api.Child"
}
},
"test9": {
"description": "test9",
"type": "array",
"items": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
}`
p := New()
_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
_, err := p.packages.ParseTypes()
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
out, err := json.MarshalIndent(p.swagger.Definitions, "", " ")
assert.NoError(t, err)
assert.Equal(t, expected, string(out))
}
func TestParser_ParseRouterApiInfoErr(t *testing.T) {
t.Parallel()
src := `
package test
// @Accept unknown
func Test(){
}
`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.Error(t, err)
}
func TestParser_ParseRouterApiGet(t *testing.T) {
t.Parallel()
src := `
package test
// @Router /api/{id} [get]
func Test(){
}
`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
ps := p.swagger.Paths.Paths
val, ok := ps["/api/{id}"]
assert.True(t, ok)
assert.NotNil(t, val.Get)
}
func TestParser_ParseRouterApiPOST(t *testing.T) {
t.Parallel()
src := `
package test
// @Router /api/{id} [post]
func Test(){
}
`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
ps := p.swagger.Paths.Paths
val, ok := ps["/api/{id}"]
assert.True(t, ok)
assert.NotNil(t, val.Post)
}
func TestParser_ParseRouterApiDELETE(t *testing.T) {
t.Parallel()
src := `
package test
// @Router /api/{id} [delete]
func Test(){
}
`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
ps := p.swagger.Paths.Paths
val, ok := ps["/api/{id}"]
assert.True(t, ok)
assert.NotNil(t, val.Delete)
}
func TestParser_ParseRouterApiPUT(t *testing.T) {
t.Parallel()
src := `
package test
// @Router /api/{id} [put]
func Test(){
}
`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
ps := p.swagger.Paths.Paths
val, ok := ps["/api/{id}"]
assert.True(t, ok)
assert.NotNil(t, val.Put)
}
func TestParser_ParseRouterApiPATCH(t *testing.T) {
t.Parallel()
src := `
package test
// @Router /api/{id} [patch]
func Test(){
}
`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
ps := p.swagger.Paths.Paths
val, ok := ps["/api/{id}"]
assert.True(t, ok)
assert.NotNil(t, val.Patch)
}
func TestParser_ParseRouterApiHead(t *testing.T) {
t.Parallel()
src := `
package test
// @Router /api/{id} [head]
func Test(){
}
`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
ps := p.swagger.Paths.Paths
val, ok := ps["/api/{id}"]
assert.True(t, ok)
assert.NotNil(t, val.Head)
}
func TestParser_ParseRouterApiOptions(t *testing.T) {
t.Parallel()
src := `
package test
// @Router /api/{id} [options]
func Test(){
}
`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
ps := p.swagger.Paths.Paths
val, ok := ps["/api/{id}"]
assert.True(t, ok)
assert.NotNil(t, val.Options)
}
func TestParser_ParseRouterApiMultipleRoutesForSameFunction(t *testing.T) {
t.Parallel()
src := `
package test
// @Router /api/v1/{id} [get]
// @Router /api/v2/{id} [post]
func Test(){
}
`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
ps := p.swagger.Paths.Paths
val, ok := ps["/api/v1/{id}"]
assert.True(t, ok)
assert.NotNil(t, val.Get)
val, ok = ps["/api/v2/{id}"]
assert.True(t, ok)
assert.NotNil(t, val.Post)
}
func TestParser_ParseRouterApiMultiple(t *testing.T) {
t.Parallel()
src := `
package test
// @Router /api/{id} [get]
func Test1(){
}
// @Router /api/{id} [patch]
func Test2(){
}
// @Router /api/{id} [delete]
func Test3(){
}
`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
ps := p.swagger.Paths.Paths
val, ok := ps["/api/{id}"]
assert.True(t, ok)
assert.NotNil(t, val.Get)
assert.NotNil(t, val.Patch)
assert.NotNil(t, val.Delete)
}
func TestParser_ParseRouterApiMultiplePathsWithMultipleParams(t *testing.T) {
t.Parallel()
src := `
package test
// @Success 200
// @Param group_id path int true "Group ID"
// @Param user_id path int true "User ID"
// @Router /examples/groups/{group_id}/user/{user_id}/address [get]
// @Router /examples/user/{user_id}/address [get]
func Test(){
}
`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
ps := p.swagger.Paths.Paths
val, ok := ps["/examples/groups/{group_id}/user/{user_id}/address"]
assert.True(t, ok)
assert.Equal(t, 2, len(val.Get.Parameters))
val, ok = ps["/examples/user/{user_id}/address"]
assert.True(t, ok)
assert.Equal(t, 1, len(val.Get.Parameters))
}
// func TestParseDeterministic(t *testing.T) {
// mainAPIFile := "main.go"
// for _, searchDir := range []string{
// "testdata/simple",
// "testdata/model_not_under_root/cmd",
// } {
// t.Run(searchDir, func(t *testing.T) {
// var expected string
// // run the same code 100 times and check that the output is the same every time
// for i := 0; i < 100; i++ {
// p := New()
// p.PropNamingStrategy = PascalCase
// err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
// b, _ := json.MarshalIndent(p.swagger, "", " ")
// assert.NotEqual(t, "", string(b))
// if expected == "" {
// expected = string(b)
// }
// assert.Equal(t, expected, string(b))
// }
// })
// }
// }
func TestParser_ParseRouterApiDuplicateRoute(t *testing.T) {
t.Parallel()
src := `
package api
import (
"net/http"
)
// @Router /api/endpoint [get]
func FunctionOne(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Router /api/endpoint [get]
func FunctionTwo(w http.ResponseWriter, r *http.Request) {
//write your code
}
`
p := New(SetStrict(true))
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.EqualError(t, err, "route GET /api/endpoint is declared multiple times")
p = New()
err = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
}
func TestApiParseTag(t *testing.T) {
t.Parallel()
searchDir := "testdata/tags"
p := New(SetMarkdownFileDirectory(searchDir))
p.PropNamingStrategy = PascalCase
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
if len(p.swagger.Tags) != 3 {
t.Error("Number of tags did not match")
}
dogs := p.swagger.Tags[0]
if dogs.TagProps.Name != "dogs" || dogs.TagProps.Description != "Dogs are cool" {
t.Error("Failed to parse dogs name or description")
}
cats := p.swagger.Tags[1]
if cats.TagProps.Name != "cats" || cats.TagProps.Description != "Cats are the devil" {
t.Error("Failed to parse cats name or description")
}
if cats.TagProps.ExternalDocs.URL != "https://google.de" || cats.TagProps.ExternalDocs.Description != "google is super useful to find out that cats are evil!" {
t.Error("URL: ", cats.TagProps.ExternalDocs.URL)
t.Error("Description: ", cats.TagProps.ExternalDocs.Description)
t.Error("Failed to parse cats external documentation")
}
}
func TestApiParseTag_NonExistendTag(t *testing.T) {
t.Parallel()
searchDir := "testdata/tags_nonexistend_tag"
p := New(SetMarkdownFileDirectory(searchDir))
p.PropNamingStrategy = PascalCase
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.Error(t, err)
}
func TestParseTagMarkdownDescription(t *testing.T) {
t.Parallel()
searchDir := "testdata/tags"
p := New(SetMarkdownFileDirectory(searchDir))
p.PropNamingStrategy = PascalCase
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
if err != nil {
t.Error("Failed to parse api description: " + err.Error())
}
if len(p.swagger.Tags) != 3 {
t.Error("Number of tags did not match")
}
apes := p.swagger.Tags[2]
if apes.TagProps.Description == "" {
t.Error("Failed to parse tag description markdown file")
}
}
func TestParseApiMarkdownDescription(t *testing.T) {
t.Parallel()
searchDir := "testdata/tags"
p := New(SetMarkdownFileDirectory(searchDir))
p.PropNamingStrategy = PascalCase
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
if err != nil {
t.Error("Failed to parse api description: " + err.Error())
}
if p.swagger.Info.Description == "" {
t.Error("Failed to parse api description: " + err.Error())
}
}
func TestIgnoreInvalidPkg(t *testing.T) {
t.Parallel()
searchDir := "testdata/deps_having_invalid_pkg"
p := New()
if err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth); err != nil {
t.Error("Failed to ignore valid pkg: " + err.Error())
}
}
func TestFixes432(t *testing.T) {
t.Parallel()
searchDir := "testdata/fixes-432"
mainAPIFile := "cmd/main.go"
p := New()
if err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth); err != nil {
t.Error("Failed to ignore valid pkg: " + err.Error())
}
}
func TestParseOutsideDependencies(t *testing.T) {
t.Parallel()
searchDir := "testdata/pare_outside_dependencies"
mainAPIFile := "cmd/main.go"
p := New(SetParseDependency(1))
if err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth); err != nil {
t.Error("Failed to parse api: " + err.Error())
}
}
func TestParseStructParamCommentByQueryType(t *testing.T) {
t.Parallel()
src := `
package main
type Student struct {
Name string
Age int
Teachers []string
SkipField map[string]string
}
// @Param request query Student true "query params"
// @Success 200
// @Router /test [get]
func Fun() {
}
`
expected := `{
"info": {
"contact": {}
},
"paths": {
"/test": {
"get": {
"parameters": [
{
"type": "integer",
"name": "age",
"in": "query"
},
{
"type": "string",
"name": "name",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"name": "teachers",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
_, err = p.packages.ParseTypes()
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestParseStructParamCommentByQueryTypeWithQueryTag(t *testing.T) {
t.Parallel()
src := `
package main
type Student struct {
ProjectID int ` + "`" + `query:"projectId"` + "`" + `
Name string ` + "`" + `query:"name"` + "`" + `
SkipField string ` + "`" + `query:"-"` + "`" + `
}
// @Param request query Student true "query params"
// @Success 200
// @Router /test [get]
func Fun() {
}
`
expected := `{
"info": {
"contact": {}
},
"paths": {
"/test": {
"get": {
"parameters": [
{
"type": "string",
"name": "name",
"in": "query"
},
{
"type": "integer",
"name": "projectId",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
_, err = p.packages.ParseTypes()
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestParseParamCommentExtension(t *testing.T) {
t.Parallel()
src := `
package main
// @Param request query string true "query params" extensions(x-example=[0, 9],x-foo=bar)
// @Success 200
// @Router /test [get]
func Fun() {
}
`
expected := `{
"info": {
"contact": {}
},
"paths": {
"/test": {
"get": {
"parameters": [
{
"type": "string",
"x-example": "[0, 9]",
"x-foo": "bar",
"description": "query params",
"name": "request",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}`
p := New()
_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
_, err := p.packages.ParseTypes()
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.JSONEq(t, expected, string(b))
}
func TestParseRenamedStructDefinition(t *testing.T) {
t.Parallel()
src := `
package main
type Child struct {
Name string
}//@name Student
type Parent struct {
Name string
Child Child
}//@name Teacher
// @Param request body Parent true "query params"
// @Success 200 {object} Parent
// @Router /test [get]
func Fun() {
}
`
p := New()
_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
_, err := p.packages.ParseTypes()
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
assert.NoError(t, err)
teacher, ok := p.swagger.Definitions["Teacher"]
assert.True(t, ok)
ref := teacher.Properties["child"].SchemaProps.Ref
assert.Equal(t, "#/definitions/Student", ref.String())
_, ok = p.swagger.Definitions["Student"]
assert.True(t, ok)
path, ok := p.swagger.Paths.Paths["/test"]
assert.True(t, ok)
assert.Equal(t, "#/definitions/Teacher", path.Get.Parameters[0].Schema.Ref.String())
ref = path.Get.Responses.ResponsesProps.StatusCodeResponses[200].ResponseProps.Schema.Ref
assert.Equal(t, "#/definitions/Teacher", ref.String())
}
func TestParseTabFormattedRenamedStructDefinition(t *testing.T) {
t.Parallel()
src := "package main\n" +
"\n" +
"type Child struct {\n" +
"\tName string\n" +
"}\t//\t@name\tPupil\n" +
"\n" +
"// @Success 200 {object} Pupil\n" +
"func Fun() { }"
p := New()
_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
_, err := p.packages.ParseTypes()
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
_, ok := p.swagger.Definitions["Pupil"]
assert.True(t, ok)
}
func TestParseFunctionScopedStructDefinition(t *testing.T) {
t.Parallel()
src := `
package main
// @Param request body main.Fun.request true "query params"
// @Success 200 {object} main.Fun.response
// @Router /test [post]
func Fun() {
type request struct {
Name string
}
type response struct {
Name string
Child string
}
}
`
p := New()
_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
_, err := p.packages.ParseTypes()
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
_, ok := p.swagger.Definitions["main.Fun.response"]
assert.True(t, ok)
}
func TestParseFunctionScopedComplexStructDefinition(t *testing.T) {
t.Parallel()
src := `
package main
// @Param request body main.Fun.request true "query params"
// @Success 200 {object} main.Fun.response
// @Router /test [post]
func Fun() {
type request struct {
Name string
}
type grandChild struct {
Name string
}
type pointerChild struct {
Name string
}
type arrayChild struct {
Name string
}
type child struct {
GrandChild grandChild
PointerChild *pointerChild
ArrayChildren []arrayChild
}
type response struct {
Children []child
}
}
`
p := New()
_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
_, err := p.packages.ParseTypes()
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
_, ok := p.swagger.Definitions["main.Fun.response"]
assert.True(t, ok)
_, ok = p.swagger.Definitions["main.Fun.child"]
assert.True(t, ok)
_, ok = p.swagger.Definitions["main.Fun.grandChild"]
assert.True(t, ok)
_, ok = p.swagger.Definitions["main.Fun.pointerChild"]
assert.True(t, ok)
_, ok = p.swagger.Definitions["main.Fun.arrayChild"]
assert.True(t, ok)
}
func TestParseFunctionScopedStructRequestResponseJSON(t *testing.T) {
t.Parallel()
src := `
package main
// @Param request body main.Fun.request true "query params"
// @Success 200 {object} main.Fun.response
// @Router /test [post]
func Fun() {
type request struct {
Name string
}
type response struct {
Name string
Child string
}
}
`
expected := `{
"info": {
"contact": {}
},
"paths": {
"/test": {
"post": {
"parameters": [
{
"description": "query params",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/main.Fun.request"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/main.Fun.response"
}
}
}
}
}
},
"definitions": {
"main.Fun.request": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"main.Fun.response": {
"type": "object",
"properties": {
"child": {
"type": "string"
},
"name": {
"type": "string"
}
}
}
}
}`
p := New()
_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
_, err := p.packages.ParseTypes()
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestParseFunctionScopedComplexStructRequestResponseJSON(t *testing.T) {
t.Parallel()
src := `
package main
type PublicChild struct {
Name string
}
// @Param request body main.Fun.request true "query params"
// @Success 200 {object} main.Fun.response
// @Router /test [post]
func Fun() {
type request struct {
Name string
}
type grandChild struct {
Name string
}
type child struct {
GrandChild grandChild
}
type response struct {
Children []child
PublicChild PublicChild
}
}
`
expected := `{
"info": {
"contact": {}
},
"paths": {
"/test": {
"post": {
"parameters": [
{
"description": "query params",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/main.Fun.request"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/main.Fun.response"
}
}
}
}
}
},
"definitions": {
"main.Fun.child": {
"type": "object",
"properties": {
"grandChild": {
"$ref": "#/definitions/main.Fun.grandChild"
}
}
},
"main.Fun.grandChild": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"main.Fun.request": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"main.Fun.response": {
"type": "object",
"properties": {
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Fun.child"
}
},
"publicChild": {
"$ref": "#/definitions/main.PublicChild"
}
}
},
"main.PublicChild": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}
}
}`
p := New()
_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
_, err := p.packages.ParseTypes()
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestPackagesDefinitions_CollectAstFileInit(t *testing.T) {
t.Parallel()
src := `
package main
// @Router /test [get]
func Fun() {
}
`
pkgs := NewPackagesDefinitions()
// unset the .files and .packages and check that they're re-initialized by collectAstFile
pkgs.packages = nil
pkgs.files = nil
_ = pkgs.ParseFile("api", "api/api.go", src, ParseAll)
assert.NotNil(t, pkgs.packages)
assert.NotNil(t, pkgs.files)
}
func TestCollectAstFileMultipleTimes(t *testing.T) {
t.Parallel()
src := `
package main
// @Router /test [get]
func Fun() {
}
`
p := New()
_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.Equal(t, 1, len(p.packages.files))
var path string
var file *ast.File
for path, file = range p.packages.packages["api"].Files {
break
}
assert.NotNil(t, file)
assert.NotNil(t, p.packages.files[file])
// if we collect the same again nothing should happen
_ = p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.Equal(t, 1, len(p.packages.files))
assert.Equal(t, file, p.packages.packages["api"].Files[path])
assert.NotNil(t, p.packages.files[file])
}
func TestParseJSONFieldString(t *testing.T) {
t.Parallel()
expected := `{
"swagger": "2.0",
"info": {
"description": "This is a sample server.",
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"host": "localhost:4000",
"basePath": "/",
"paths": {
"/do-something": {
"post": {
"description": "Does something",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Call DoSomething",
"parameters": [
{
"description": "My Struct",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/main.MyStruct"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/main.MyStruct"
}
},
"500": {
"description": "Internal Server Error"
}
}
}
}
},
"definitions": {
"main.MyStruct": {
"type": "object",
"properties": {
"boolvar": {
"description": "boolean as a string",
"type": "string",
"example": "false"
},
"floatvar": {
"description": "float as a string",
"type": "string",
"example": "0"
},
"id": {
"type": "integer",
"format": "int64",
"example": 1
},
"myint": {
"description": "integer as string",
"type": "string",
"example": "0"
},
"name": {
"type": "string",
"example": "poti"
},
"truebool": {
"description": "boolean as a string",
"type": "string",
"example": "true"
},
"uuids": {
"description": "string array with format",
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
}
}
}
}
}`
searchDir := "testdata/json_field_string"
p := New()
err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, _ := json.MarshalIndent(p.swagger, "", " ")
assert.Equal(t, expected, string(b))
}
func TestParseSwaggerignoreForEmbedded(t *testing.T) {
t.Parallel()
src := `
package main
type Child struct {
ChildName string
}//@name Student
type Parent struct {
Name string
Child ` + "`swaggerignore:\"true\"`" + `
}//@name Teacher
// @Param request body Parent true "query params"
// @Success 200 {object} Parent
// @Router /test [get]
func Fun() {
}
`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
_, _ = p.packages.ParseTypes()
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
teacher, ok := p.swagger.Definitions["Teacher"]
assert.True(t, ok)
name, ok := teacher.Properties["name"]
assert.True(t, ok)
assert.Len(t, name.Type, 1)
assert.Equal(t, "string", name.Type[0])
childName, ok := teacher.Properties["childName"]
assert.False(t, ok)
assert.Empty(t, childName)
}
func TestDefineTypeOfExample(t *testing.T) {
t.Run("String type", func(t *testing.T) {
t.Parallel()
example, err := defineTypeOfExample("string", "", "example")
assert.NoError(t, err)
assert.Equal(t, example.(string), "example")
})
t.Run("Number type", func(t *testing.T) {
t.Parallel()
example, err := defineTypeOfExample("number", "", "12.34")
assert.NoError(t, err)
assert.Equal(t, example.(float64), 12.34)
_, err = defineTypeOfExample("number", "", "two")
assert.Error(t, err)
})
t.Run("Integer type", func(t *testing.T) {
t.Parallel()
example, err := defineTypeOfExample("integer", "", "12")
assert.NoError(t, err)
assert.Equal(t, example.(int), 12)
_, err = defineTypeOfExample("integer", "", "two")
assert.Error(t, err)
})
t.Run("Boolean type", func(t *testing.T) {
t.Parallel()
example, err := defineTypeOfExample("boolean", "", "true")
assert.NoError(t, err)
assert.Equal(t, example.(bool), true)
_, err = defineTypeOfExample("boolean", "", "!true")
assert.Error(t, err)
})
t.Run("Array type", func(t *testing.T) {
t.Parallel()
example, err := defineTypeOfExample("array", "", "one,two,three")
assert.Error(t, err)
assert.Nil(t, example)
example, err = defineTypeOfExample("array", "string", "one,two,three")
assert.NoError(t, err)
var arr []string
for _, v := range example.([]interface{}) {
arr = append(arr, v.(string))
}
assert.Equal(t, arr, []string{"one", "two", "three"})
})
t.Run("Object type", func(t *testing.T) {
t.Parallel()
example, err := defineTypeOfExample("object", "", "key_one:one,key_two:two,key_three:three")
assert.Error(t, err)
assert.Nil(t, example)
example, err = defineTypeOfExample("object", "string", "key_one,key_two,key_three")
assert.Error(t, err)
assert.Nil(t, example)
example, err = defineTypeOfExample("object", "oops", "key_one:one,key_two:two,key_three:three")
assert.Error(t, err)
assert.Nil(t, example)
example, err = defineTypeOfExample("object", "string", "key_one:one,key_two:two,key_three:three")
assert.NoError(t, err)
obj := map[string]string{}
for k, v := range example.(map[string]interface{}) {
obj[k] = v.(string)
}
assert.Equal(t, obj, map[string]string{"key_one": "one", "key_two": "two", "key_three": "three"})
})
t.Run("Invalid type", func(t *testing.T) {
t.Parallel()
example, err := defineTypeOfExample("oops", "", "")
assert.Error(t, err)
assert.Nil(t, example)
})
}
type mockFS struct {
os.FileInfo
FileName string
IsDirectory bool
}
func (fs *mockFS) Name() string {
return fs.FileName
}
func (fs *mockFS) IsDir() bool {
return fs.IsDirectory
}
func TestParser_Skip(t *testing.T) {
t.Parallel()
parser := New()
parser.ParseVendor = true
assert.NoError(t, parser.Skip("", &mockFS{FileName: "vendor"}))
assert.NoError(t, parser.Skip("", &mockFS{FileName: "vendor", IsDirectory: true}))
parser.ParseVendor = false
assert.NoError(t, parser.Skip("", &mockFS{FileName: "vendor"}))
assert.Error(t, parser.Skip("", &mockFS{FileName: "vendor", IsDirectory: true}))
assert.NoError(t, parser.Skip("", &mockFS{FileName: "models", IsDirectory: true}))
assert.NoError(t, parser.Skip("", &mockFS{FileName: "admin", IsDirectory: true}))
assert.NoError(t, parser.Skip("", &mockFS{FileName: "release", IsDirectory: true}))
assert.NoError(t, parser.Skip("", &mockFS{FileName: "..", IsDirectory: true}))
parser = New(SetExcludedDirsAndFiles("admin/release,admin/models"))
assert.NoError(t, parser.Skip("admin", &mockFS{IsDirectory: true}))
assert.NoError(t, parser.Skip(filepath.Clean("admin/service"), &mockFS{IsDirectory: true}))
assert.Error(t, parser.Skip(filepath.Clean("admin/models"), &mockFS{IsDirectory: true}))
assert.Error(t, parser.Skip(filepath.Clean("admin/release"), &mockFS{IsDirectory: true}))
}
func TestGetFuncDoc_NilPointerSafety(t *testing.T) {
t.Parallel()
tests := []struct {
name string
decl interface{}
wantDoc *ast.CommentGroup
wantBool bool
}{
{
name: "GenDecl with empty Specs",
decl: &ast.GenDecl{
Tok: token.VAR,
Specs: []ast.Spec{}, // empty specs
},
wantDoc: nil,
wantBool: false,
},
{
name: "ValueSpec with empty Values",
decl: &ast.ValueSpec{
Values: []ast.Expr{}, // empty values
},
wantDoc: nil,
wantBool: false,
},
{
name: "ValueSpec with nil Obj",
decl: &ast.ValueSpec{
Values: []ast.Expr{
&ast.Ident{
Name: "test",
Obj: nil, // nil object
},
},
},
wantDoc: nil,
wantBool: false,
},
{
name: "ValueSpec with nil Obj.Decl",
decl: &ast.ValueSpec{
Values: []ast.Expr{
&ast.Ident{
Name: "test",
Obj: &ast.Object{
Decl: nil, // nil declaration
},
},
},
},
wantDoc: nil,
wantBool: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
gotDoc, gotBool := getFuncDoc(tt.decl)
assert.Equal(t, tt.wantDoc, gotDoc)
assert.Equal(t, tt.wantBool, gotBool)
})
}
}
func TestGetFieldType(t *testing.T) {
t.Parallel()
field, err := getFieldType(&ast.File{}, &ast.Ident{Name: "User"}, nil)
assert.NoError(t, err)
assert.Equal(t, "User", field)
_, err = getFieldType(&ast.File{}, &ast.FuncType{}, nil)
assert.Error(t, err)
field, err = getFieldType(&ast.File{}, &ast.SelectorExpr{X: &ast.Ident{Name: "models"}, Sel: &ast.Ident{Name: "User"}}, nil)
assert.NoError(t, err)
assert.Equal(t, "models.User", field)
_, err = getFieldType(&ast.File{}, &ast.SelectorExpr{X: &ast.FuncType{}, Sel: &ast.Ident{Name: "User"}}, nil)
assert.Error(t, err)
field, err = getFieldType(&ast.File{}, &ast.StarExpr{X: &ast.Ident{Name: "User"}}, nil)
assert.NoError(t, err)
assert.Equal(t, "User", field)
field, err = getFieldType(&ast.File{}, &ast.StarExpr{X: &ast.FuncType{}}, nil)
assert.Error(t, err)
field, err = getFieldType(&ast.File{}, &ast.StarExpr{X: &ast.SelectorExpr{X: &ast.Ident{Name: "models"}, Sel: &ast.Ident{Name: "User"}}}, nil)
assert.NoError(t, err)
assert.Equal(t, "models.User", field)
}
func TestTryAddDescription(t *testing.T) {
type args struct {
spec *spec.SecurityScheme
extensions map[string]interface{}
}
tests := []struct {
name string
lines []string
args args
want *spec.SecurityScheme
}{
{
name: "added description",
lines: []string{
"\t@securitydefinitions.apikey test",
"\t@in header",
"\t@name x-api-key",
"\t@description some description",
},
want: &spec.SecurityScheme{
SecuritySchemeProps: spec.SecuritySchemeProps{
Name: "x-api-key",
Type: "apiKey",
In: "header",
Description: "some description",
},
},
},
{
name: "added description with multiline",
lines: []string{
"\t@securitydefinitions.apikey test",
"\t@in header",
"\t@name x-api-key",
"\t@description line1",
"\t@description line2",
},
want: &spec.SecurityScheme{
SecuritySchemeProps: spec.SecuritySchemeProps{
Name: "x-api-key",
Type: "apiKey",
In: "header",
Description: "line1\nline2",
},
},
},
{
name: "no description",
lines: []string{
" @securitydefinitions.oauth2.application swagger",
" @tokenurl https://example.com/oauth/token",
" @not-description some description",
},
want: &spec.SecurityScheme{
SecuritySchemeProps: spec.SecuritySchemeProps{
Type: "oauth2",
Flow: "application",
TokenURL: "https://example.com/oauth/token",
Description: "",
},
},
},
{
name: "description has invalid format",
lines: []string{
"@securitydefinitions.oauth2.implicit swagger",
"@authorizationurl https://example.com/oauth/token",
"@description 12345",
},
want: &spec.SecurityScheme{
SecuritySchemeProps: spec.SecuritySchemeProps{
Type: "oauth2",
Flow: "implicit",
AuthorizationURL: "https://example.com/oauth/token",
Description: "12345",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
swag := spec.Swagger{
SwaggerProps: spec.SwaggerProps{
SecurityDefinitions: make(map[string]*spec.SecurityScheme),
},
}
line := 0
commentLine := tt.lines[line]
attribute := strings.Split(commentLine, " ")[0]
value := strings.TrimSpace(commentLine[len(attribute):])
secAttr, _ := parseSecAttributes(attribute, tt.lines, &line)
if !reflect.DeepEqual(secAttr, tt.want) {
t.Errorf("setSwaggerSecurity() = %#v, want %#v", swag.SecurityDefinitions[value], tt.want)
}
})
}
}
func Test_getTagsFromComment(t *testing.T) {
type args struct {
comment string
}
tests := []struct {
name string
args args
wantTags []string
}{
{
name: "no tags comment",
args: args{
comment: "//@name Student",
},
wantTags: nil,
},
{
name: "empty comment",
args: args{
comment: "//",
},
wantTags: nil,
},
{
name: "tags comment",
args: args{
comment: "//@Tags tag1,tag2,tag3",
},
wantTags: []string{"tag1", "tag2", "tag3"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if gotTags := getTagsFromComment(tt.args.comment); !reflect.DeepEqual(gotTags, tt.wantTags) {
t.Errorf("getTagsFromComment() = %v, want %v", gotTags, tt.wantTags)
}
})
}
}
func TestParser_matchTags(t *testing.T) {
type args struct {
comments []*ast.Comment
}
tests := []struct {
name string
parser *Parser
args args
wantMatch bool
}{
{
name: "no tags filter",
parser: New(),
args: args{comments: []*ast.Comment{{Text: "//@Tags tag1,tag2,tag3"}}},
wantMatch: true,
},
{
name: "with tags filter but no match",
parser: New(SetTags("tag4,tag5,!tag1")),
args: args{comments: []*ast.Comment{{Text: "//@Tags tag1,tag2,tag3"}}},
wantMatch: false,
},
{
name: "with tags filter but match",
parser: New(SetTags("tag4,tag5,tag1")),
args: args{comments: []*ast.Comment{{Text: "//@Tags tag1,tag2,tag3"}}},
wantMatch: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if gotMatch := tt.parser.matchTags(tt.args.comments); gotMatch != tt.wantMatch {
t.Errorf("Parser.matchTags() = %v, want %v", gotMatch, tt.wantMatch)
}
})
}
}
func TestParser_parseExtension(t *testing.T) {
packagePath := "testdata/parseExtension"
filePath := packagePath + "/parseExtension.go"
src, err := os.ReadFile(filePath)
assert.NoError(t, err)
fileSet := token.NewFileSet()
f, err := goparser.ParseFile(fileSet, "", src, goparser.ParseComments)
assert.NoError(t, err)
tests := []struct {
name string
parser *Parser
expectedPaths map[string]bool
}{
{
name: "when no flag is set, everything is exported",
parser: New(),
expectedPaths: map[string]bool{"/without-extension": true, "/with-another-extension": true, "/with-correct-extension": true, "/with-empty-comment-line": true},
},
{
name: "when nonexistent flag is set, nothing is exported",
parser: New(SetParseExtension("nonexistent-extension-filter")),
expectedPaths: map[string]bool{"/without-extension": false, "/with-another-extension": false, "/with-correct-extension": false, "/with-empty-comment-line": false},
},
{
name: "when correct flag is set, only that Path is exported",
parser: New(SetParseExtension("google-backend")),
expectedPaths: map[string]bool{"/without-extension": false, "/with-another-extension": false, "/with-correct-extension": true, "/with-empty-comment-line": false},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err = tt.parser.ParseRouterAPIInfo(&AstFileInfo{
FileSet: fileSet,
File: f,
Path: filePath,
PackagePath: packagePath,
ParseFlag: ParseAll,
})
assert.NoError(t, err)
for p, isExpected := range tt.expectedPaths {
_, ok := tt.parser.swagger.Paths.Paths[p]
assert.Equal(t, isExpected, ok)
}
for p := range tt.parser.swagger.Paths.Paths {
_, isExpected := tt.expectedPaths[p]
assert.Equal(t, isExpected, true)
}
})
}
}
func TestParser_collectionFormat(t *testing.T) {
tests := []struct {
name string
parser *Parser
format string
}{
{
name: "no collectionFormat",
parser: New(),
format: "",
},
{
name: "multi collectionFormat",
parser: New(SetCollectionFormat("multi")),
format: "multi",
},
{
name: "ssv collectionFormat",
parser: New(SetCollectionFormat("ssv")),
format: "ssv",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.parser.collectionFormatInQuery != tt.format {
t.Errorf("Parser.collectionFormatInQuery = %s, want %s", tt.parser.collectionFormatInQuery, tt.format)
}
})
}
}
func TestParser_skipPackageByPrefix(t *testing.T) {
t.Parallel()
parser := New()
assert.False(t, parser.skipPackageByPrefix("github.com/swaggo/swag"))
assert.False(t, parser.skipPackageByPrefix("github.com/swaggo/swag/cmd"))
assert.False(t, parser.skipPackageByPrefix("github.com/swaggo/swag/gen"))
parser = New(SetPackagePrefix("github.com/swaggo/swag/cmd"))
assert.True(t, parser.skipPackageByPrefix("github.com/swaggo/swag"))
assert.False(t, parser.skipPackageByPrefix("github.com/swaggo/swag/cmd"))
assert.True(t, parser.skipPackageByPrefix("github.com/swaggo/swag/gen"))
parser = New(SetPackagePrefix("github.com/swaggo/swag/cmd,github.com/swaggo/swag/gen"))
assert.True(t, parser.skipPackageByPrefix("github.com/swaggo/swag"))
assert.False(t, parser.skipPackageByPrefix("github.com/swaggo/swag/cmd"))
assert.False(t, parser.skipPackageByPrefix("github.com/swaggo/swag/gen"))
}
func TestParser_ParseRouterApiInFuncBody(t *testing.T) {
t.Parallel()
src := `
package test
func Test(){
// @Router /api/{id} [get]
_ = func() {
}
}
`
p := New()
p.ParseFuncBody = true
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
ps := p.swagger.Paths.Paths
val, ok := ps["/api/{id}"]
assert.True(t, ok)
assert.NotNil(t, val.Get)
}
func TestParser_ParseRouterApiInfoInAndOutFuncBody(t *testing.T) {
t.Parallel()
src := `
package test
// @Router /api/outside [get]
func otherRoute(){
}
func Test(){
// @Router /api/inside [get]
_ = func() {
}
}
`
p := New()
p.ParseFuncBody = true
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
ps := p.swagger.Paths.Paths
val1, ok := ps["/api/outside"]
assert.True(t, ok)
assert.NotNil(t, val1.Get)
val2, ok := ps["/api/inside"]
assert.True(t, ok)
assert.NotNil(t, val2.Get)
}
func TestParser_EmbeddedStructAsOtherAliasGoListNested(t *testing.T) {
t.Parallel()
p := New(SetParseDependency(1), ParseUsingGoList(true))
p.parseGoList = true
searchDir := "testdata/alias_nested"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
err = p.ParseAPI(searchDir, "cmd/main/main.go", 0)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
func TestParser_genVarDefinedFuncDoc(t *testing.T) {
t.Parallel()
src := `
package main
func f() {}
// @Summary generate var-defined functions' doc
// @Router /test [get]
var Func = f
// @Summary generate indirectly pointing
// @Router /test2 [get]
var Func2 = Func
`
p := New()
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)
_, _ = p.packages.ParseTypes()
err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)
val, ok := p.swagger.Paths.Paths["/test"]
assert.True(t, ok)
assert.NotNil(t, val.Get)
assert.Equal(t, val.Get.OperationProps.Summary, "generate var-defined functions' doc")
val2, ok := p.swagger.Paths.Paths["/test2"]
assert.True(t, ok)
assert.NotNil(t, val2.Get)
assert.Equal(t, val2.Get.OperationProps.Summary, "generate indirectly pointing")
}
func TestParser_DescriptionLineContinuation(t *testing.T) {
t.Parallel()
p := New()
searchDir := "testdata/description_line_continuation"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
// TestParser_ParseDefinitionWithRecursiveTypeAndNameAnnotation tests that @name annotations
// are properly respected for recursive types
func TestParser_ParseDefinitionWithRecursiveTypeAndNameAnnotation(t *testing.T) {
src := `package api
// TreeNode represents a node in a tree structure
type TreeNode struct {
Value string ` + "`json:\"value\"`" + `
Children []TreeNode ` + "`json:\"children\"`" + `
} // @name TreeNode
// LinkedNode represents a node in a linked structure
type LinkedNode struct {
Data string ` + "`json:\"data\"`" + `
Next *LinkedNode ` + "`json:\"next\"`" + `
} // @name CustomLinkedNode
`
p := New()
err := p.packages.ParseFile("api", "api/tree.go", src, ParseAll)
assert.NoError(t, err)
_, err = p.packages.ParseTypes()
assert.NoError(t, err)
// Find the TreeNode type
treeNodeDef := p.packages.FindTypeSpec("api.TreeNode", nil)
assert.NotNil(t, treeNodeDef)
// Parse the TreeNode definition
schema, err := p.ParseDefinition(treeNodeDef)
assert.NoError(t, err)
assert.NotNil(t, schema)
assert.Equal(t, "TreeNode", schema.Name) // Should use @name annotation
// Find the LinkedNode type
linkedNodeDef := p.packages.FindTypeSpec("api.LinkedNode", nil)
assert.NotNil(t, linkedNodeDef)
// Parse the LinkedNode definition
schema2, err := p.ParseDefinition(linkedNodeDef)
assert.NoError(t, err)
assert.NotNil(t, schema2)
assert.Equal(t, "CustomLinkedNode", schema2.Name) // Should use @name annotation
// Verify the definitions were added to swagger with correct names
assert.NotNil(t, p.swagger.Definitions["TreeNode"])
assert.NotNil(t, p.swagger.Definitions["CustomLinkedNode"])
// The fix is working if the definitions use the @name annotation
// TreeNode should be "TreeNode", not "api.TreeNode"
// LinkedNode should be "CustomLinkedNode", not "api.LinkedNode"
for name := range p.swagger.Definitions {
// Ensure no definitions use the full package path format
assert.NotContains(t, name, "api.TreeNode")
assert.NotContains(t, name, "api.LinkedNode")
}
}
================================================
FILE: parsergopackages.go
================================================
package swag
import (
"go/token"
"os"
"path/filepath"
"slices"
"golang.org/x/tools/go/packages"
)
func (parser *Parser) loadPackagesAndDeps(searchDirs []string, absMainAPIFilePath string) error {
mode := packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports |
packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo
if parser.ParseDependency > 0 {
mode |= packages.NeedDeps
}
absDirs := make([]string, 0, len(searchDirs)+1)
absDirs = append(absDirs, filepath.Dir(absMainAPIFilePath))
for _, dir := range searchDirs {
absDir, err := filepath.Abs(dir)
if err != nil {
return err
}
// load all subpackages keep the same logic with Parser.getAllGoFileInfo
absDirs = append(absDirs, absDir+"/...")
}
fset := token.NewFileSet()
pkgs, err := packages.Load(&packages.Config{
Mode: mode,
Fset: fset,
}, absDirs...)
if err != nil {
return err
}
for _, pkg := range pkgs {
for _, e := range pkg.Errors {
return e
}
}
err = parser.walkPackages(pkgs, func(pkg *packages.Package) error {
parseFlag := ParseFlag(ParseAll)
if !slices.Contains(pkgs, pkg) {
parseFlag = parser.ParseDependency
}
for i, file := range pkg.CompiledGoFiles {
// TODO handle vendor?
fileInfo, err := os.Stat(file)
if err != nil {
return err
}
if parser.Skip(file, fileInfo) != nil {
continue
}
if err = parser.packages.CollectAstFile(fset, pkg.PkgPath, file, pkg.Syntax[i], parseFlag); err != nil {
return err
}
}
return nil
})
parser.packages.AddPackages(pkgs)
return err
}
func (parser *Parser) walkPackages(pkgs []*packages.Package, f func(p *packages.Package) error) error {
pkgSeen := make(map[string]struct{})
return parser.walkPackagesInternal(pkgs, f, pkgSeen)
}
func (parser *Parser) walkPackagesInternal(pkgs []*packages.Package, f func(p *packages.Package) error,
pkgSeen map[string]struct{}) error {
for _, pkg := range pkgs {
if parser.skipPackageByPrefix(pkg.PkgPath) {
continue
}
if _, ok := pkgSeen[pkg.PkgPath]; ok {
continue
}
pkgSeen[pkg.PkgPath] = struct{}{}
if err := f(pkg); err != nil {
return err
}
if parser.ParseDependency > 0 {
imports := make([]*packages.Package, 0, len(pkg.Imports))
for _, dep := range pkg.Imports {
imports = append(imports, dep)
}
if err := parser.walkPackagesInternal(imports, f, pkgSeen); err != nil {
return err
}
}
}
return nil
}
================================================
FILE: schema.go
================================================
package swag
import (
"errors"
"fmt"
"github.com/go-openapi/spec"
"go/ast"
"regexp"
"strings"
)
const (
// ARRAY represent a array value.
ARRAY = "array"
// OBJECT represent a object value.
OBJECT = "object"
// PRIMITIVE represent a primitive value.
PRIMITIVE = "primitive"
// BOOLEAN represent a boolean value.
BOOLEAN = "boolean"
// INTEGER represent a integer value.
INTEGER = "integer"
// NUMBER represent a number value.
NUMBER = "number"
// STRING represent a string value.
STRING = "string"
// FUNC represent a function value.
FUNC = "func"
// ERROR represent a error value.
ERROR = "error"
// INTERFACE represent a interface value.
INTERFACE = "interface{}"
// ANY represent a any value.
ANY = "any"
// NIL represent a empty value.
NIL = "nil"
// IgnoreNameOverridePrefix Prepend to model to avoid renaming based on comment.
IgnoreNameOverridePrefix = '$'
)
// CheckSchemaType checks if typeName is not a name of primitive type.
func CheckSchemaType(typeName string) error {
if !IsPrimitiveType(typeName) {
return fmt.Errorf("%s is not basic types", typeName)
}
return nil
}
// IsSimplePrimitiveType determine whether the type name is a simple primitive type.
func IsSimplePrimitiveType(typeName string) bool {
switch typeName {
case STRING, NUMBER, INTEGER, BOOLEAN:
return true
}
return false
}
// IsPrimitiveType determine whether the type name is a primitive type.
func IsPrimitiveType(typeName string) bool {
switch typeName {
case STRING, NUMBER, INTEGER, BOOLEAN, ARRAY, OBJECT, FUNC:
return true
}
return false
}
// IsInterfaceLike determines whether the swagger type name is an go named interface type like error type.
func IsInterfaceLike(typeName string) bool {
return typeName == ERROR || typeName == ANY
}
// IsNumericType determines whether the swagger type name is a numeric type.
func IsNumericType(typeName string) bool {
return typeName == INTEGER || typeName == NUMBER
}
// TransToValidPrimitiveSchema transfer golang basic type to swagger schema with format considered.
func TransToValidPrimitiveSchema(typeName string) *spec.Schema {
switch typeName {
case "int", "uint":
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{INTEGER}}}
case "uint8", "int8", "uint16", "int16", "byte", "int32", "uint32", "rune":
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{INTEGER}, Format: "int32"}}
case "uint64", "int64":
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{INTEGER}, Format: "int64"}}
case "float32", "float64":
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{NUMBER}, Format: typeName}}
case "bool":
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{BOOLEAN}}}
case "string":
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{STRING}}}
}
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{typeName}}}
}
// TransToValidSchemeTypeWithFormat indicates type will transfer golang basic type to swagger supported type with format.
func TransToValidSchemeTypeWithFormat(typeName string) (string, string) {
switch typeName {
case "int", "uint":
return INTEGER, ""
case "uint8", "int8", "uint16", "int16", "byte", "int32", "uint32", "rune":
return INTEGER, "int32"
case "uint64", "int64":
return INTEGER, "int64"
case "float32", "float64":
return NUMBER, typeName
case "bool":
return BOOLEAN, ""
case "string":
return STRING, ""
}
return typeName, ""
}
// TransToValidSchemeType indicates type will transfer golang basic type to swagger supported type.
func TransToValidSchemeType(typeName string) string {
switch typeName {
case "uint", "int", "uint8", "int8", "uint16", "int16", "byte":
return INTEGER
case "uint32", "int32", "rune":
return INTEGER
case "uint64", "int64":
return INTEGER
case "float32", "float64":
return NUMBER
case "bool":
return BOOLEAN
case "string":
return STRING
}
return typeName
}
// IsGolangPrimitiveType determine whether the type name is a golang primitive type.
func IsGolangPrimitiveType(typeName string) bool {
switch typeName {
case "uint",
"int",
"uint8",
"int8",
"uint16",
"int16",
"byte",
"uint32",
"int32",
"rune",
"uint64",
"int64",
"float32",
"float64",
"bool",
"string":
return true
}
return false
}
// TransToValidCollectionFormat determine valid collection format.
func TransToValidCollectionFormat(format string) string {
switch format {
case "csv", "multi", "pipes", "tsv", "ssv":
return format
}
return ""
}
func ignoreNameOverride(name string) bool {
return len(name) != 0 && name[0] == IgnoreNameOverridePrefix
}
var overrideNameRegex = regexp.MustCompile(`(?i)^@name\s+(\S+)`)
func nameOverride(commentGroup *ast.CommentGroup) string {
if commentGroup == nil {
return ""
}
// get alias from comment '// @name '
for _, comment := range commentGroup.List {
trimmedComment := strings.TrimSpace(strings.TrimLeft(comment.Text, "/"))
texts := overrideNameRegex.FindStringSubmatch(trimmedComment)
if len(texts) > 1 {
return texts[1]
}
}
return ""
}
func commentWithoutNameOverride(comment string) string {
if len(comment) == 0 {
return ""
}
comment = strings.TrimPrefix(comment, "//")
comment = strings.TrimPrefix(comment, "/*")
comment = strings.TrimSuffix(comment, "*/")
comment = strings.TrimSpace(comment)
comment = overrideNameRegex.ReplaceAllString(comment, "")
comment = strings.TrimSpace(comment)
return comment
}
// IsComplexSchema whether a schema is complex and should be a ref schema
func IsComplexSchema(schema *spec.Schema) bool {
// a enum type should be complex
if len(schema.Enum) > 0 {
return true
}
// a deep array type is complex, how to determine deep? here more than 2 ,for example: [][]object,[][][]int
if len(schema.Type) > 2 {
return true
}
//Object included, such as Object or []Object
for _, st := range schema.Type {
if st == OBJECT {
return true
}
}
return false
}
// IsRefSchema whether a schema is a reference schema.
func IsRefSchema(schema *spec.Schema) bool {
return schema.Ref.Ref.GetURL() != nil
}
// RefSchema build a reference schema.
func RefSchema(refType string) *spec.Schema {
return spec.RefSchema("#/definitions/" + refType)
}
// PrimitiveSchema build a primitive schema.
func PrimitiveSchema(refType string) *spec.Schema {
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{refType}}}
}
// BuildCustomSchema build custom schema specified by tag swaggertype.
func BuildCustomSchema(types []string) (*spec.Schema, error) {
if len(types) == 0 {
return nil, nil
}
switch types[0] {
case PRIMITIVE:
if len(types) == 1 {
return nil, errors.New("need primitive type after primitive")
}
return BuildCustomSchema(types[1:])
case ARRAY:
if len(types) == 1 {
return nil, errors.New("need array item type after array")
}
schema, err := BuildCustomSchema(types[1:])
if err != nil {
return nil, err
}
return spec.ArrayProperty(schema), nil
case OBJECT:
if len(types) == 1 {
return PrimitiveSchema(types[0]), nil
}
schema, err := BuildCustomSchema(types[1:])
if err != nil {
return nil, err
}
return spec.MapProperty(schema), nil
default:
err := CheckSchemaType(types[0])
if err != nil {
return nil, err
}
return PrimitiveSchema(types[0]), nil
}
}
// MergeSchema merge schemas
func MergeSchema(dst *spec.Schema, src *spec.Schema) *spec.Schema {
if len(src.Type) > 0 {
dst.Type = src.Type
}
if len(src.Properties) > 0 {
dst.Properties = src.Properties
}
if src.Items != nil {
dst.Items = src.Items
}
if src.AdditionalProperties != nil {
dst.AdditionalProperties = src.AdditionalProperties
}
if len(src.Description) > 0 {
dst.Description = src.Description
}
if src.Nullable {
dst.Nullable = src.Nullable
}
if len(src.Format) > 0 {
dst.Format = src.Format
}
if src.Default != nil {
dst.Default = src.Default
}
if src.Example != nil {
dst.Example = src.Example
}
if len(src.Extensions) > 0 {
dst.Extensions = src.Extensions
}
if src.Maximum != nil {
dst.Maximum = src.Maximum
}
if src.Minimum != nil {
dst.Minimum = src.Minimum
}
if src.ExclusiveMaximum {
dst.ExclusiveMaximum = src.ExclusiveMaximum
}
if src.ExclusiveMinimum {
dst.ExclusiveMinimum = src.ExclusiveMinimum
}
if src.MaxLength != nil {
dst.MaxLength = src.MaxLength
}
if src.MinLength != nil {
dst.MinLength = src.MinLength
}
if len(src.Pattern) > 0 {
dst.Pattern = src.Pattern
}
if src.MaxItems != nil {
dst.MaxItems = src.MaxItems
}
if src.MinItems != nil {
dst.MinItems = src.MinItems
}
if src.UniqueItems {
dst.UniqueItems = src.UniqueItems
}
if src.MultipleOf != nil {
dst.MultipleOf = src.MultipleOf
}
if len(src.Enum) > 0 {
dst.Enum = src.Enum
}
if len(src.Extensions) > 0 {
dst.Extensions = src.Extensions
}
if len(src.ExtraProps) > 0 {
dst.ExtraProps = src.ExtraProps
}
return dst
}
================================================
FILE: schema_test.go
================================================
package swag
import (
"testing"
"github.com/go-openapi/spec"
"github.com/stretchr/testify/assert"
)
func TestValidDataType(t *testing.T) {
t.Parallel()
assert.NoError(t, CheckSchemaType(STRING))
assert.NoError(t, CheckSchemaType(NUMBER))
assert.NoError(t, CheckSchemaType(INTEGER))
assert.NoError(t, CheckSchemaType(BOOLEAN))
assert.NoError(t, CheckSchemaType(ARRAY))
assert.NoError(t, CheckSchemaType(OBJECT))
assert.Error(t, CheckSchemaType("oops"))
}
func TestTransToValidSchemeType(t *testing.T) {
t.Parallel()
assert.Equal(t, TransToValidSchemeType("uint"), INTEGER)
assert.Equal(t, TransToValidSchemeType("uint32"), INTEGER)
assert.Equal(t, TransToValidSchemeType("uint64"), INTEGER)
assert.Equal(t, TransToValidSchemeType("float32"), NUMBER)
assert.Equal(t, TransToValidSchemeType("bool"), BOOLEAN)
assert.Equal(t, TransToValidSchemeType("string"), STRING)
// should accept any type, due to user defined types
other := "oops"
assert.Equal(t, TransToValidSchemeType(other), other)
}
func TestTransToValidCollectionFormat(t *testing.T) {
t.Parallel()
assert.Equal(t, TransToValidCollectionFormat("csv"), "csv")
assert.Equal(t, TransToValidCollectionFormat("multi"), "multi")
assert.Equal(t, TransToValidCollectionFormat("pipes"), "pipes")
assert.Equal(t, TransToValidCollectionFormat("tsv"), "tsv")
assert.Equal(t, TransToValidSchemeType("string"), STRING)
// should accept any type, due to user defined types
assert.Equal(t, TransToValidCollectionFormat("oops"), "")
}
func TestIsGolangPrimitiveType(t *testing.T) {
t.Parallel()
assert.Equal(t, IsGolangPrimitiveType("uint"), true)
assert.Equal(t, IsGolangPrimitiveType("int"), true)
assert.Equal(t, IsGolangPrimitiveType("uint8"), true)
assert.Equal(t, IsGolangPrimitiveType("uint16"), true)
assert.Equal(t, IsGolangPrimitiveType("int16"), true)
assert.Equal(t, IsGolangPrimitiveType("byte"), true)
assert.Equal(t, IsGolangPrimitiveType("uint32"), true)
assert.Equal(t, IsGolangPrimitiveType("int32"), true)
assert.Equal(t, IsGolangPrimitiveType("rune"), true)
assert.Equal(t, IsGolangPrimitiveType("uint64"), true)
assert.Equal(t, IsGolangPrimitiveType("int64"), true)
assert.Equal(t, IsGolangPrimitiveType("float32"), true)
assert.Equal(t, IsGolangPrimitiveType("float64"), true)
assert.Equal(t, IsGolangPrimitiveType("bool"), true)
assert.Equal(t, IsGolangPrimitiveType("string"), true)
assert.Equal(t, IsGolangPrimitiveType("oops"), false)
}
func TestIsSimplePrimitiveType(t *testing.T) {
t.Parallel()
assert.Equal(t, IsSimplePrimitiveType("string"), true)
assert.Equal(t, IsSimplePrimitiveType("number"), true)
assert.Equal(t, IsSimplePrimitiveType("integer"), true)
assert.Equal(t, IsSimplePrimitiveType("boolean"), true)
assert.Equal(t, IsSimplePrimitiveType("oops"), false)
}
func TestBuildCustomSchema(t *testing.T) {
t.Parallel()
var (
schema *spec.Schema
err error
)
schema, err = BuildCustomSchema([]string{})
assert.NoError(t, err)
assert.Nil(t, schema)
schema, err = BuildCustomSchema([]string{"primitive"})
assert.Error(t, err)
assert.Nil(t, schema)
schema, err = BuildCustomSchema([]string{"primitive", "oops"})
assert.Error(t, err)
assert.Nil(t, schema)
schema, err = BuildCustomSchema([]string{"primitive", "string"})
assert.NoError(t, err)
assert.Equal(t, schema.SchemaProps.Type, spec.StringOrArray{"string"})
schema, err = BuildCustomSchema([]string{"array"})
assert.Error(t, err)
assert.Nil(t, schema)
schema, err = BuildCustomSchema([]string{"array", "oops"})
assert.Error(t, err)
assert.Nil(t, schema)
schema, err = BuildCustomSchema([]string{"array", "string"})
assert.NoError(t, err)
assert.Equal(t, schema.SchemaProps.Type, spec.StringOrArray{"array"})
assert.Equal(t, schema.SchemaProps.Items.Schema.SchemaProps.Type, spec.StringOrArray{"string"})
schema, err = BuildCustomSchema([]string{"object"})
assert.NoError(t, err)
assert.Equal(t, schema.SchemaProps.Type, spec.StringOrArray{"object"})
schema, err = BuildCustomSchema([]string{"object", "oops"})
assert.Error(t, err)
assert.Nil(t, schema)
schema, err = BuildCustomSchema([]string{"object", "string"})
assert.NoError(t, err)
assert.Equal(t, schema.SchemaProps.Type, spec.StringOrArray{"object"})
assert.Equal(t, schema.SchemaProps.AdditionalProperties.Schema.Type, spec.StringOrArray{"string"})
}
func TestIsNumericType(t *testing.T) {
t.Parallel()
assert.Equal(t, IsNumericType(INTEGER), true)
assert.Equal(t, IsNumericType(NUMBER), true)
assert.Equal(t, IsNumericType(STRING), false)
}
func TestIsInterfaceLike(t *testing.T) {
t.Parallel()
assert.Equal(t, IsInterfaceLike(ERROR), true)
assert.Equal(t, IsInterfaceLike(ANY), true)
assert.Equal(t, IsInterfaceLike(STRING), false)
}
================================================
FILE: spec.go
================================================
package swag
import (
"bytes"
"encoding/json"
"strings"
"text/template"
)
// Spec holds exported Swagger Info so clients can modify it.
type Spec struct {
Version string
Host string
BasePath string
Schemes []string
Title string
Description string
InfoInstanceName string
SwaggerTemplate string
LeftDelim string
RightDelim string
}
// ReadDoc parses SwaggerTemplate into swagger document.
func (i *Spec) ReadDoc() string {
i.Description = strings.ReplaceAll(i.Description, "\n", "\\n")
tpl := template.New("swagger_info").Funcs(template.FuncMap{
"marshal": func(v any) string {
a, _ := json.Marshal(v)
return string(a)
},
"escape": func(v any) string {
// escape tabs
var str = strings.ReplaceAll(v.(string), "\t", "\\t")
// replace " with \", and if that results in \\", replace that with \\\"
str = strings.ReplaceAll(str, "\"", "\\\"")
return strings.ReplaceAll(str, "\\\\\"", "\\\\\\\"")
},
})
if i.LeftDelim != "" && i.RightDelim != "" {
tpl = tpl.Delims(i.LeftDelim, i.RightDelim)
}
parsed, err := tpl.Parse(i.SwaggerTemplate)
if err != nil {
return i.SwaggerTemplate
}
var doc bytes.Buffer
if err = parsed.Execute(&doc, i); err != nil {
return i.SwaggerTemplate
}
return doc.String()
}
// InstanceName returns Spec instance name.
func (i *Spec) InstanceName() string {
return i.InfoInstanceName
}
================================================
FILE: spec_test.go
================================================
package swag
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSpec_InstanceName(t *testing.T) {
type fields struct {
Version string
Host string
BasePath string
Schemes []string
Title string
Description string
InfoInstanceName string
SwaggerTemplate string
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "TestInstanceNameCorrect",
fields: fields{
Version: "1.0",
Host: "localhost:8080",
BasePath: "/",
InfoInstanceName: "TestInstanceName1",
},
want: "TestInstanceName1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
doc := Spec{
Version: tt.fields.Version,
Host: tt.fields.Host,
BasePath: tt.fields.BasePath,
Schemes: tt.fields.Schemes,
Title: tt.fields.Title,
Description: tt.fields.Description,
InfoInstanceName: tt.fields.InfoInstanceName,
SwaggerTemplate: tt.fields.SwaggerTemplate,
}
assert.Equal(t, tt.want, doc.InstanceName())
})
}
}
func TestSpec_ReadDoc(t *testing.T) {
type fields struct {
Version string
Host string
BasePath string
Schemes []string
Title string
Description string
InfoInstanceName string
SwaggerTemplate string
LeftDelim string
RightDelim string
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "TestReadDocCorrect",
fields: fields{
Version: "1.0",
Host: "localhost:8080",
BasePath: "/",
InfoInstanceName: "TestInstanceName",
SwaggerTemplate: `{
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
}`,
},
want: "{" +
"\n\t\t\t\"swagger\": \"2.0\"," +
"\n\t\t\t\"info\": {" +
"\n\t\t\t\t\"description\": \"\",\n\t\t\t\t\"" +
"title\": \"\"," +
"\n\t\t\t\t\"version\": \"1.0\"" +
"\n\t\t\t}," +
"\n\t\t\t\"host\": \"localhost:8080\"," +
"\n\t\t\t\"basePath\": \"/\"," +
"\n\t\t}",
},
{
name: "TestReadDocMarshalTrigger",
fields: fields{
Version: "1.0",
Host: "localhost:8080",
BasePath: "/",
InfoInstanceName: "TestInstanceName",
SwaggerTemplate: "{{ marshal .Version }}",
},
want: "\"1.0\"",
},
{
name: "TestReadDocParseError",
fields: fields{
Version: "1.0",
Host: "localhost:8080",
BasePath: "/",
InfoInstanceName: "TestInstanceName",
SwaggerTemplate: "{{ ..Version }}",
},
want: "{{ ..Version }}",
},
{
name: "TestReadDocExecuteError",
fields: fields{
Version: "1.0",
Host: "localhost:8080",
BasePath: "/",
InfoInstanceName: "TestInstanceName",
SwaggerTemplate: "{{ .Schemesa }}",
},
want: "{{ .Schemesa }}",
},
{
name: "TestReadDocCustomDelims",
fields: fields{
Version: "1.0",
Host: "localhost:8080",
BasePath: "/",
InfoInstanceName: "TestInstanceName",
SwaggerTemplate: `{
"swagger": "2.0",
"info": {
"description": "{%escape .Description%}",
"title": "{%.Title%}",
"version": "{%.Version%}"
},
"host": "{%.Host%}",
"basePath": "{%.BasePath%}",
}`,
LeftDelim: "{%",
RightDelim: "%}",
},
want: "{" +
"\n\t\t\t\"swagger\": \"2.0\"," +
"\n\t\t\t\"info\": {" +
"\n\t\t\t\t\"description\": \"\",\n\t\t\t\t\"" +
"title\": \"\"," +
"\n\t\t\t\t\"version\": \"1.0\"" +
"\n\t\t\t}," +
"\n\t\t\t\"host\": \"localhost:8080\"," +
"\n\t\t\t\"basePath\": \"/\"," +
"\n\t\t}",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
doc := Spec{
Version: tt.fields.Version,
Host: tt.fields.Host,
BasePath: tt.fields.BasePath,
Schemes: tt.fields.Schemes,
Title: tt.fields.Title,
Description: tt.fields.Description,
InfoInstanceName: tt.fields.InfoInstanceName,
SwaggerTemplate: tt.fields.SwaggerTemplate,
LeftDelim: tt.fields.LeftDelim,
RightDelim: tt.fields.RightDelim,
}
assert.Equal(t, tt.want, doc.ReadDoc())
})
}
}
================================================
FILE: swagger.go
================================================
package swag
import (
"errors"
"fmt"
"sync"
)
// Name is a unique name be used to register swag instance.
const Name = "swagger"
var (
swaggerMu sync.RWMutex
swags map[string]Swagger
)
// Swagger is an interface to read swagger document.
type Swagger interface {
ReadDoc() string
}
// Register registers swagger for given name.
func Register(name string, swagger Swagger) {
swaggerMu.Lock()
defer swaggerMu.Unlock()
if swagger == nil {
panic("swagger is nil")
}
if swags == nil {
swags = make(map[string]Swagger)
}
if _, ok := swags[name]; ok {
panic("Register called twice for swag: " + name)
}
swags[name] = swagger
}
// GetSwagger returns the swagger instance for given name.
// If not found, returns nil.
func GetSwagger(name string) Swagger {
swaggerMu.RLock()
defer swaggerMu.RUnlock()
return swags[name]
}
// ReadDoc reads swagger document. An optional name parameter can be passed to read a specific document.
// The default name is "swagger".
func ReadDoc(optionalName ...string) (string, error) {
swaggerMu.RLock()
defer swaggerMu.RUnlock()
if swags == nil {
return "", errors.New("no swag has yet been registered")
}
name := Name
if len(optionalName) != 0 && optionalName[0] != "" {
name = optionalName[0]
}
swag, ok := swags[name]
if !ok {
return "", fmt.Errorf("no swag named \"%s\" was registered", name)
}
return swag.ReadDoc(), nil
}
================================================
FILE: swagger_test.go
================================================
package swag
import (
"testing"
"github.com/stretchr/testify/assert"
)
var doc = `{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"paths": {
"/testapi/get-string-by-int/{some_id}": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add a new pet to the store",
"parameters": [
{
"description": "Some ID",
"name": "some_id",
"in": "path",
"required": true,
"schema": {
"type": "int"
}
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"type": "object",
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"type": "object",
"$ref": "#/definitions/web.APIError"
}
}
}
}
},
"/testapi/get-struct-array-by-string/{some_id}": {
"get": {
"description": "get struct array by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"description": "Some ID",
"name": "some_id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "Offset",
"name": "offset",
"in": "query",
"required": true,
"schema": {
"type": "int"
}
},
{
"description": "Offset",
"name": "limit",
"in": "query",
"required": true,
"schema": {
"type": "int"
}
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"type": "object",
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"type": "object",
"$ref": "#/definitions/web.APIError"
}
}
}
}
}
},
"definitions": {
"web.APIError": {
"type": "object",
"properties": {
"ErrorCode": {
"type": "int"
},
"ErrorMessage": {
"type": "string"
}
}
}
},
"securityDefinitions": {
"ApiKey": {
"description: "some",
"type": "apiKey",
"name": "X-API-KEY",
"in": "header"
}
}
}`
type s struct{}
func (s *s) ReadDoc() string {
return doc
}
func TestRegister(t *testing.T) {
setup()
Register(Name, &s{})
d, _ := ReadDoc()
assert.Equal(t, doc, d)
}
func TestRegisterByName(t *testing.T) {
setup()
Register("another_name", &s{})
d, _ := ReadDoc("another_name")
assert.Equal(t, doc, d)
}
func TestRegisterMultiple(t *testing.T) {
setup()
Register(Name, &s{})
Register("another_name", &s{})
d1, _ := ReadDoc(Name)
d2, _ := ReadDoc("another_name")
assert.Equal(t, doc, d1)
assert.Equal(t, doc, d2)
}
func TestReadDocBeforeRegistered(t *testing.T) {
setup()
_, err := ReadDoc()
assert.Error(t, err)
}
func TestReadDocWithInvalidName(t *testing.T) {
setup()
Register(Name, &s{})
_, err := ReadDoc("invalid")
assert.Error(t, err)
}
func TestNilRegister(t *testing.T) {
setup()
var swagger Swagger
assert.Panics(t, func() {
Register(Name, swagger)
})
}
func TestCalledTwicelRegister(t *testing.T) {
setup()
assert.Panics(t, func() {
Register(Name, &s{})
Register(Name, &s{})
})
}
func setup() {
swags = nil
}
func TestGetSwagger(t *testing.T) {
setup()
instance := &s{}
Register(Name, instance)
swagger := GetSwagger(Name)
assert.Equal(t, instance, swagger)
swagger = GetSwagger("invalid")
assert.Nil(t, swagger)
}
================================================
FILE: testdata/alias_import/api/api.go
================================================
package api
import (
"log"
"net/http"
"github.com/swaggo/swag/testdata/alias_import/data"
"github.com/swaggo/swag/testdata/alias_type/types"
)
// @Summary Get application
// @Description test get application
// @ID get-application
// @Accept json
// @Produce json
// @Success 200 {object} data.ApplicationResponse "ok"
// @Router /testapi/application [get]
func GetApplication(w http.ResponseWriter, r *http.Request) {
var foo = data.ApplicationResponse{
Application: types.Application{
Name: "name",
},
ApplicationArray: []types.Application{
{Name: "name"},
},
}
log.Println(foo)
//write your code
}
================================================
FILE: testdata/alias_import/data/applicationresponse.go
================================================
package data
import (
typesapplication "github.com/swaggo/swag/testdata/alias_import/types"
)
type ApplicationResponse struct {
typesapplication.TypeToEmbed
Application typesapplication.Application `json:"application"`
ApplicationArray []typesapplication.Application `json:"application_array"`
ApplicationTime typesapplication.DateOnly `json:"application_time"`
}
================================================
FILE: testdata/alias_import/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"paths": {
"/testapi/application": {
"get": {
"description": "test get application",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Get application",
"operationId": "get-application",
"responses": {
"200": {
"description": "ok",
"schema": {
"$ref": "#/definitions/data.ApplicationResponse"
}
}
}
}
}
},
"definitions": {
"data.ApplicationResponse": {
"type": "object",
"properties": {
"application": {
"$ref": "#/definitions/types.Application"
},
"application_array": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Application"
}
},
"application_time": {
"type": "string"
},
"embedded": {
"type": "string"
}
}
},
"types.Application": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}
}
}
================================================
FILE: testdata/alias_import/main.go
================================================
package alias_import
import (
"net/http"
"github.com/swaggo/swag/testdata/alias_import/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
func main() {
http.HandleFunc("/testapi/application", api.GetApplication)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/alias_import/types/application.go
================================================
package types
import "time"
type Application struct {
Name string
}
type DateOnly time.Time
type TypeToEmbed struct {
Embedded string
}
================================================
FILE: testdata/alias_nested/cmd/main/main.go
================================================
package main
import "github.com/swaggo/swag/testdata/alias_nested/pkg/good"
// @Success 200 {object} good.Gen
// @Router /api [get].
func main() {
var _ good.Gen
}
================================================
FILE: testdata/alias_nested/expected.json
================================================
{
"swagger": "2.0",
"info": {
"contact": {}
},
"paths": {
"/api": {
"get": {
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Gen"
}
}
}
}
}
},
"definitions": {
"Gen": {
"type": "object",
"properties": {
"emb": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_alias_nested_pkg_good.Emb"
}
}
},
"github_com_swaggo_swag_testdata_alias_nested_pkg_good.Emb": {
"type": "object",
"properties": {
"good": {
"type": "boolean"
}
}
}
}
}
================================================
FILE: testdata/alias_nested/pkg/bad/data.go
================================================
package bad
type Emb struct {
Bad bool `json:"bad"`
} // @name Emb
================================================
FILE: testdata/alias_nested/pkg/good/data.go
================================================
package good
type Gen struct {
Emb Emb `json:"emb"`
} // @name Gen
type Emb struct {
Good bool `json:"good"`
}
================================================
FILE: testdata/alias_type/api/api.go
================================================
package api
import (
"log"
"net/http"
"time"
"github.com/swaggo/swag/testdata/alias_type/data"
)
/*// @Summary Get time as string
// @Description get time as string
// @ID time-as-string
// @Accept json
// @Produce json
// @Success 200 {object} data.StringAlias "ok"
// @Router /testapi/time-as-string [get]
func GetTimeAsStringAlias(w http.ResponseWriter, r *http.Request) {
var foo data.StringAlias = "test"
log.Println(foo)
//write your code
}*/
/*// @Summary Get time as time
// @Description get time as time
// @ID time-as-time
// @Accept json
// @Produce json
// @Success 200 {object} data.DateOnly "ok"
// @Router /testapi/time-as-time [get]
func GetTimeAsTimeAlias(w http.ResponseWriter, r *http.Request) {
var foo = data.DateOnly(time.Now())
log.Println(foo)
//write your code
}*/
// @Summary Get container with time and time alias
// @Description test container with time and time alias
// @ID time-as-time-container
// @Accept json
// @Produce json
// @Success 200 {object} data.TimeContainer "ok"
// @Router /testapi/time-as-time-container [get]
func GetTimeAsTimeContainer(w http.ResponseWriter, r *http.Request) {
now := time.Now()
var foo = data.TimeContainer{
Name: "test",
Timestamp: now,
//CreatedAt: &now,
}
log.Println(foo)
//write your code
}
================================================
FILE: testdata/alias_type/data/alias.go
================================================
package data
import (
"github.com/swaggo/swag/testdata/alias_type/types"
"time"
)
type TimeContainer struct {
Name types.StringAlias `json:"name"`
Timestamp time.Time `json:"timestamp"`
CreatedAt types.DateOnly `json:"created_at"`
}
================================================
FILE: testdata/alias_type/main.go
================================================
package alias_type
import (
"net/http"
"github.com/swaggo/swag/testdata/alias_type/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
func main() {
http.HandleFunc("/testapi/time-as-time-container", api.GetTimeAsTimeContainer)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/alias_type/types/alias.go
================================================
package types
import "time"
type StringAlias string
type DateOnly time.Time
================================================
FILE: testdata/api.md
================================================
Swagger Example API Markdown Description
================================================
FILE: testdata/code_examples/api/api1.go
================================================
package api
import (
_ "github.com/swaggo/swag/testdata/conflict_name/model"
"net/http"
)
// @Description Check if Health of service it's OK!
// @Router /health [get]
// @x-codeSamples file
func Get1(w http.ResponseWriter, r *http.Request) {
}
================================================
FILE: testdata/code_examples/broken.json
================================================
{
"key": value
}
================================================
FILE: testdata/code_examples/example.json
================================================
{
"lang": "JavaScript",
"source": "console.log('Hello World');"
}
================================================
FILE: testdata/code_examples/main.go
================================================
package main
// @title Swag test
// @version 1.0
// @description test for conflict name
func main() {
}
================================================
FILE: testdata/composition/api/api.go
================================================
package api
import (
"net/http"
"github.com/swaggo/swag/testdata/composition/common"
)
type Foo struct {
Field1 string
}
type Bar struct {
Field2 string
}
type EmptyStruct struct {
}
type unexported struct {
}
type Ignored struct {
Field5 string `swaggerignore:"true"`
}
type FooBar struct {
Foo
Bar
EmptyStruct
unexported
Ignored
}
type FooBarPointer struct {
*common.ResponseFormat
*Foo
*Bar
*EmptyStruct
*unexported
*Ignored
}
type BarMap map[string]Bar
type FooBarMap struct {
Field3 map[string]MapValue
}
type MapValue struct {
Field4 string
}
// @Description get Foo
// @ID get-foo
// @Accept json
// @Produce json
// @Success 200 {object} api.Foo
// @Router /testapi/get-foo [get]
func GetFoo(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = Foo{}
}
// @Description get Bar
// @ID get-bar
// @Accept json
// @Produce json
// @Success 200 {object} api.Bar
// @Router /testapi/get-bar [get]
func GetBar(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = Bar{}
}
// @Description get FooBar
// @ID get-foobar
// @Accept json
// @Produce json
// @Success 200 {object} api.FooBar
// @Router /testapi/get-foobar [get]
func GetFooBar(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = FooBar{}
}
// @Description get FooBarPointer
// @ID get-foobar-pointer
// @Accept json
// @Produce json
// @Success 200 {object} api.FooBarPointer
// @Router /testapi/get-foobar-pointer [get]
func GetFooBarPointer(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = FooBarPointer{}
}
// @Description get BarMap
// @ID get-bar-map
// @Accept json
// @Produce json
// @Success 200 {object} api.BarMap
// @Router /testapi/get-barmap [get]
func GetBarMap(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = BarMap{}
}
// @Description get FoorBarMap
// @ID get-foo-bar-map
// @Accept json
// @Produce json
// @Success 200 {object} api.FooBarMap
// @Router /testapi/get-foobarmap [get]
func GetFooBarMap(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = FooBarMap{}
}
================================================
FILE: testdata/composition/common/response.go
================================================
package common
type ResponseFormat struct {
Message string `json:"message"`
}
================================================
FILE: testdata/composition/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
"version": "1.0"
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"paths": {
"/testapi/get-bar": {
"get": {
"description": "get Bar",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "get-bar",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.Bar"
}
}
}
}
},
"/testapi/get-barmap": {
"get": {
"description": "get BarMap",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "get-bar-map",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.BarMap"
}
}
}
}
},
"/testapi/get-foo": {
"get": {
"description": "get Foo",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "get-foo",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.Foo"
}
}
}
}
},
"/testapi/get-foobar": {
"get": {
"description": "get FooBar",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "get-foobar",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.FooBar"
}
}
}
}
},
"/testapi/get-foobar-pointer": {
"get": {
"description": "get FooBarPointer",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "get-foobar-pointer",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.FooBarPointer"
}
}
}
}
},
"/testapi/get-foobarmap": {
"get": {
"description": "get FoorBarMap",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "get-foo-bar-map",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.FooBarMap"
}
}
}
}
}
},
"definitions": {
"api.Bar": {
"type": "object",
"properties": {
"field2": {
"type": "string"
}
}
},
"api.BarMap": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/api.Bar"
}
},
"api.Foo": {
"type": "object",
"properties": {
"field1": {
"type": "string"
}
}
},
"api.FooBar": {
"type": "object",
"properties": {
"field1": {
"type": "string"
},
"field2": {
"type": "string"
}
}
},
"api.FooBarMap": {
"type": "object",
"properties": {
"field3": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/api.MapValue"
}
}
}
},
"api.FooBarPointer": {
"type": "object",
"properties": {
"field1": {
"type": "string"
},
"field2": {
"type": "string"
},
"message": {
"type": "string"
}
}
},
"api.MapValue": {
"type": "object",
"properties": {
"field4": {
"type": "string"
}
}
}
}
}
================================================
FILE: testdata/composition/main.go
================================================
package composition
import (
"net/http"
"github.com/swaggo/swag/testdata/composition/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server
// @termsOfService http://swagger.io/terms/
// @host petstore.swagger.io
// @BasePath /v2
func main() {
http.handleFunc("/testapi/get-foo", api.GetFoo)
http.handleFunc("/testapi/get-bar", api.GetBar)
http.handleFunc("/testapi/get-foobar", api.GetFooBar)
http.handleFunc("/testapi/get-foobar-pointer", api.GetFooBarPointer)
http.handleFunc("/testapi/get-barmap", api.GetBarMap)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/conflict_name/api/api1.go
================================================
package api
import (
_ "github.com/swaggo/swag/testdata/conflict_name/model"
"net/http"
)
// @Tags Health
// @Description Check if Health of service it's OK!
// @ID health
// @Accept json
// @Produce json
// @Success 200 {object} model.ErrorsResponse
// @Router /health [get]
func Get1(w http.ResponseWriter, r *http.Request) {
}
================================================
FILE: testdata/conflict_name/api/api2.go
================================================
package api
import (
_ "github.com/swaggo/swag/testdata/conflict_name/model2"
"net/http"
)
// @Tags Health
// @Description Check if Health of service it's OK!
// @ID health2
// @Accept json
// @Produce json
// @Success 200 {object} model.ErrorsResponse
// @Router /health2 [get]
func Get2(w http.ResponseWriter, r *http.Request) {
}
================================================
FILE: testdata/conflict_name/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "test for conflict name",
"title": "Swag test",
"contact": {},
"version": "1.0"
},
"paths": {
"/health": {
"get": {
"description": "Check if Health of service it's OK!",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Health"
],
"operationId": "health",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_conflict_name_model.ErrorsResponse"
}
}
}
}
},
"/health2": {
"get": {
"description": "Check if Health of service it's OK!",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Health"
],
"operationId": "health2",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_conflict_name_model2.ErrorsResponse"
}
}
}
}
}
},
"definitions": {
"github_com_swaggo_swag_testdata_conflict_name_model.ErrorsResponse": {
"type": "object",
"properties": {
"newTime": {
"$ref": "#/definitions/model.MyPayload"
}
}
},
"github_com_swaggo_swag_testdata_conflict_name_model.MyStruct": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"github_com_swaggo_swag_testdata_conflict_name_model2.ErrorsResponse": {
"type": "object",
"properties": {
"newTime": {
"$ref": "#/definitions/model.MyPayload2"
}
}
},
"github_com_swaggo_swag_testdata_conflict_name_model2.MyStruct": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"model.MyPayload": {
"type": "object",
"properties": {
"my": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_conflict_name_model.MyStruct"
},
"name": {
"type": "string"
}
}
},
"model.MyPayload2": {
"type": "object",
"properties": {
"my": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_conflict_name_model2.MyStruct"
},
"name": {
"type": "string"
}
}
}
}
}
================================================
FILE: testdata/conflict_name/main.go
================================================
package main
// @title Swag test
// @version 1.0
// @description test for conflict name
func main() {
}
================================================
FILE: testdata/conflict_name/model/model.go
================================================
package model
type MyStruct struct {
Name string `json:"name"`
}
type MyPayload struct {
My MyStruct
Name string `json:"name"`
}
type ErrorsResponse struct {
NewTime MyPayload
}
================================================
FILE: testdata/conflict_name/model2/model.go
================================================
package model
type MyStruct struct {
Name string `json:"name"`
}
type MyPayload2 struct {
My MyStruct
Name string `json:"name"`
}
type ErrorsResponse struct {
NewTime MyPayload2
}
================================================
FILE: testdata/delims/api/api.go
================================================
package api
// MyFunc godoc
// @Description My Function
// @Success 200 {object} MyStruct
// @Router /myfunc [get]
func MyFunc() {}
type MyStruct struct {
URLTemplate string `json:"urltemplate" example:"http://example.org/{{ path }}" swaggertype:"string"`
}
================================================
FILE: testdata/delims/expected.json
================================================
{
"schemes": [],
"swagger": "2.0",
"info": {
"description": "Testing custom template delimeters",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
"version": "1.0"
},
"host": "",
"basePath": "",
"paths": {
"/myfunc": {
"get": {
"description": "My Function",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.MyStruct"
}
}
}
}
}
},
"definitions": {
"api.MyStruct": {
"type": "object",
"properties": {
"urltemplate": {
"type": "string",
"example": "http://example.org/{{ path }}"
}
}
}
}
}
================================================
FILE: testdata/delims/main.go
================================================
package main
import (
"github.com/swaggo/swag"
"github.com/swaggo/swag/testdata/delims/api"
_ "github.com/swaggo/swag/testdata/delims/docs"
)
func ReadDoc() string {
doc, _ := swag.ReadDoc("CustomDelims")
return doc
}
// @title Swagger Example API
// @version 1.0
// @description Testing custom template delimeters
// @termsOfService http://swagger.io/terms/
func main() {
api.MyFunc()
}
================================================
FILE: testdata/deprecated_router/api/api.go
================================================
package api
import "net/http"
// @Description add Foo
// @Deprecated
// @Success 200 {string} string
// @Router /testapi/foo1 [put]
// @Router /testapi/foo1 [post]
// @Router /test/api/foo1 [post]
func AddFoo(w http.ResponseWriter, r *http.Request) {}
// @Description get Foo
// @Success 200 {string} string
// @Router /testapi/foo1 [get]
// @DeprecatedRouter /test/api/foo1 [get]
func GetFoo(w http.ResponseWriter, r *http.Request) {}
================================================
FILE: testdata/deprecated_router/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "test data for deprecated router",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
"version": "1.0"
},
"paths": {
"/test/api/foo1": {
"get": {
"description": "get Foo",
"deprecated": true,
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
},
"post": {
"description": "add Foo",
"deprecated": true,
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
},
"/testapi/foo1": {
"get": {
"description": "get Foo",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
},
"put": {
"description": "add Foo",
"deprecated": true,
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
},
"post": {
"description": "add Foo",
"deprecated": true,
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
}
}
}
================================================
FILE: testdata/deprecated_router/main.go
================================================
package main
// @title Swagger Example API
// @version 1.0
// @description test data for deprecated router
// @termsOfService http://swagger.io/terms/
func main() {
}
================================================
FILE: testdata/deps_having_invalid_pkg/main.go
================================================
package main
import _ "golang.org/x/tools/go/loader" // it inside contains invalid package files
func main() {}
================================================
FILE: testdata/description_line_continuation/api/api.go
================================================
package api
import (
"net/http"
)
// @Summary Endpoint A
// @Description This is a mock endpoint description \
// @Description which is long and descriptions that \
// @Description end with backslash do not add a new line.
// @Description This sentence is in a new line.
// @Description
// @Description And this have an empty line above it.
// @Description Lorem ipsum dolor sit amet \
// @Description consectetur adipiscing elit, \
// @Description sed do eiusmod tempor incididunt \
// @Description ut labore et dolore magna aliqua.
// @Success 200
// @Router /a [get]
func EndpointA(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
// @Summary Endpoint B
// @Description Something something.
// @Description
// @Description A new line, \
// @Description continue to the line.
// @Success 200
// @Router /b [get]
func EndpointB(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
================================================
FILE: testdata/description_line_continuation/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "Example long description that should not be split into multiple lines.\nThis is a new line thatescapes new line withoutadding a whitespace.\n\nAnother line that has an empty line above it.",
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"host": "localhost:8080",
"paths": {
"/a": {
"get": {
"description": "This is a mock endpoint description which is long and descriptions that end with backslash do not add a new line.\nThis sentence is in a new line.\n\nAnd this have an empty line above it.\nLorem ipsum dolor sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"summary": "Endpoint A",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/b": {
"get": {
"description": "Something something.\n\nA new line, continue to the line.",
"summary": "Endpoint B",
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}
================================================
FILE: testdata/description_line_continuation/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/description_escape_new_line/api"
)
// @title Swagger Example API
// @version 1.0
// @description Example long description \
// @description that should not be split \
// @description into multiple lines.
// @description This is a new line that\
// @description escapes new line without\
// @description adding a whitespace.
// @description
// @description Another line that has an \
// @description empty line above it.
// @host localhost:8080
func main() {
http.HandleFunc("/a", api.EndpointA)
http.HandleFunc("/b", api.EndpointB)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/duplicated/api/api.go
================================================
package api
import "net/http"
// @Description get Foo
// @ID get-foo
// @Success 200 {string} string
// @Router /testapi/get-foo [get]
func GetFoo(w http.ResponseWriter, r *http.Request) {}
// @Description post Bar
// @ID get-foo
// @Success 200 {string} string
// @Router /testapi/post-bar [post]
func PostBar(w http.ResponseWriter, r *http.Request) {}
================================================
FILE: testdata/duplicated/main.go
================================================
package composition
import (
"net/http"
"github.com/swaggo/swag/testdata/duplicated/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server
// @termsOfService http://swagger.io/terms/
// @host petstore.swagger.io
// @BasePath /v2
func main() {
http.HandleFunc("/testapi/get-foo", api.GetFoo)
http.HandleFunc("/testapi/post-bar", api.PostBar)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/duplicated2/api/api.go
================================================
package api
import "net/http"
// @Description put Foo
// @ID put-foo
// @Success 200 {string} string
// @Router /testapi/put-foo [put]
func PutFoo(w http.ResponseWriter, r *http.Request) {}
// @Description head Foo
// @ID head-foo
// @Success 200 {string} string
// @Router /testapi/head-foo [head]
func HeadFoo(w http.ResponseWriter, r *http.Request) {}
// @Description options Foo
// @ID options-foo
// @Success 200 {string} string
// @Router /testapi/options-foo [options]
func OptionsFoo(w http.ResponseWriter, r *http.Request) {}
// @Description patch Foo
// @ID patch-foo
// @Success 200 {string} string
// @Router /testapi/patch-foo [patch]
func PatchFoo(w http.ResponseWriter, r *http.Request) {}
// @Description delete Foo
// @ID put-foo
// @Success 200 {string} string
// @Router /testapi/delete-foo [delete]
func DeleteFoo(w http.ResponseWriter, r *http.Request) {}
================================================
FILE: testdata/duplicated2/main.go
================================================
package composition
import (
"net/http"
"github.com/swaggo/swag/testdata/duplicated2/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server
// @termsOfService http://swagger.io/terms/
// @host petstore.swagger.io
// @BasePath /v2
func main() {
http.HandleFunc("/testapi/put-foo", api.PutFoo)
http.HandleFunc("/testapi/head-foo", api.HeadFoo)
http.HandleFunc("/testapi/options-foo", api.OptionsFoo)
http.HandleFunc("/testapi/patch-foo", api.PatchFoo)
http.HandleFunc("/testapi/delete-foo", api.DeleteFoo)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/duplicated_function_scoped/api/api.go
================================================
package api
import "net/http"
// @Description get Foo
// @ID get-foo
// @Success 200 {object} api.GetFoo.response
// @Router /testapi/get-foo [get]
func GetFoo(w http.ResponseWriter, r *http.Request) {
type response struct {
}
}
================================================
FILE: testdata/duplicated_function_scoped/main.go
================================================
package composition
import (
"net/http"
"github.com/swaggo/swag/testdata/duplicated_function_scoped/api"
otherapi "github.com/swaggo/swag/testdata/duplicated_function_scoped/other_api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server
// @termsOfService http://swagger.io/terms/
// @host petstore.swagger.io
// @BasePath /v2
func main() {
http.HandleFunc("/testapi/get-foo", api.GetFoo)
http.HandleFunc("/testapi/post-bar", otherapi.GetFoo)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/duplicated_function_scoped/other_api/api.go
================================================
package api
import "net/http"
// @Description get Foo
// @ID get-foo
// @Success 200 {object} api.GetFoo.response
// @Router /testapi/get-foo [get]
func GetFoo(w http.ResponseWriter, r *http.Request) {
type response struct {
}
}
================================================
FILE: testdata/enums/api/api.go
================================================
package api
import "github.com/swaggo/swag/testdata/enums/types"
// post students
//
// @Summary test enums in response models
// @Description test enums in response models
// @Failure 400 {object} types.Person "ok"
// @Router /students [post]
func API() {
_ = types.Person{}
}
// get students
//
// @Summary test enums in response request
// @Description test enums in response request
// @Param typeinquery query []types.Type true "type"
// @Param typeinheader header types.Type true "type"
// @Param typeinpath path types.Type true "type"
// @Success 200 "ok"
// @Router /students/{typeinpath}/ [get]
func API2() {
_ = types.Person{}
}
// post students
//
// @Summary test enums fields in formdata request
// @Description test enums fields in formdata request
// @Param student formData types.Person true "type"
// @Success 200 "ok"
// @Router /students2 [get]
func API3() {
_ = types.Person{}
}
// post students
//
// @Summary test array enums fields in formdata request
// @Description test array enums fields in formdata request
// @Param student formData types.PersonWithArrayEnum true "type"
// @Success 200 "ok"
// @Router /students4 [get]
func API4() {
_ = types.Person{}
}
================================================
FILE: testdata/enums/consts/const.go
================================================
package consts
const Base = 1
const uintSize = 32 << (^uint(uintptr(0)) >> 63)
const maxBase = 10 + ('z' - 'a' + 1) + ('Z' - 'A' + 1)
const shlByLen = 1 << len("aaa")
const hexnum = 0xFF
const octnum = 017
const nonescapestr = `aa\nbb\u8888cc`
const escapestr = "aa\nbb\u8888cc"
const escapechar = '\u8888'
const underscored = 1_000_000
const binaryInteger = 0b10001000
const octInteger = 0o755
================================================
FILE: testdata/enums/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"basePath": "/v2",
"paths": {
"/students": {
"post": {
"description": "test enums in response models",
"summary": "test enums in response models",
"responses": {
"400": {
"description": "ok",
"schema": {
"$ref": "#/definitions/types.Person"
}
}
}
}
},
"/students/{typeinpath}/": {
"get": {
"description": "test enums in response request",
"summary": "test enums in response request",
"parameters": [
{
"type": "array",
"items": {
"enum": [
"teacher",
"student",
"Other"
],
"type": "string"
},
"description": "type",
"name": "typeinquery",
"in": "query",
"required": true
},
{
"enum": [
"teacher",
"student",
"Other"
],
"type": "string",
"description": "type",
"name": "typeinheader",
"in": "header",
"required": true
},
{
"enum": [
"teacher",
"student",
"Other"
],
"type": "string",
"description": "type",
"name": "typeinpath",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "ok"
}
}
}
},
"/students2": {
"get": {
"description": "test enums fields in formdata request",
"summary": "test enums fields in formdata request",
"parameters": [
{
"enum": [
-1,
1,
2,
3,
4,
5
],
"type": "integer",
"x-enum-comments": {
"A": "AAA",
"B": "BBB"
},
"x-enum-descriptions": [
"",
"AAA",
"BBB",
"",
"",
""
],
"x-enum-varnames": [
"None",
"A",
"B",
"C",
"D",
"F"
],
"name": "class",
"in": "formData"
},
{
"enum": [
"easy",
"medium",
"hard"
],
"type": "string",
"x-enum-comments": {
"DifficultyHard": "This means really hard",
"Medium": "This one also has a comment"
},
"x-enum-descriptions": [
"",
"This one also has a comment",
"This means really hard"
],
"x-enum-varnames": [
"Easy",
"Medium",
"DifficultyHard"
],
"name": "difficulty",
"in": "formData"
},
{
"enum": [
"g_easy",
"g_medium",
"g_hard"
],
"type": "string",
"x-enum-comments": {
"GenericDifficultyHard": "This means really hard",
"GenericMedium": "This one also has a comment"
},
"x-enum-descriptions": [
"",
"This one also has a comment",
"This means really hard"
],
"x-enum-varnames": [
"GenericEasy",
"GenericMedium",
"GenericDifficultyHard"
],
"name": "genericDifficulty",
"in": "formData"
},
{
"enum": [
1,
2,
4,
8,
3
],
"type": "integer",
"x-enum-comments": {
"Mask1": "Mask1",
"Mask2": "Mask2",
"Mask3": "Mask3",
"Mask4": "Mask4"
},
"x-enum-descriptions": [
"Mask1",
"Mask2",
"Mask3",
"Mask4",
""
],
"x-enum-varnames": [
"Mask1",
"Mask2",
"Mask3",
"Mask4",
"Mask5"
],
"name": "mask",
"in": "formData"
},
{
"type": "string",
"name": "name",
"in": "formData"
},
{
"enum": [
0,
1,
2
],
"type": "integer",
"x-enum-comments": {
"SecurityClearanceSensitive": "Name override and comment rules apply here just as above",
"SuperSecret": "This one has a name override and a comment"
},
"x-enum-descriptions": [
"",
"Name override and comment rules apply here just as above",
"This one has a name override and a comment"
],
"x-enum-varnames": [
"Public",
"SecurityClearanceSensitive",
"SuperSecret"
],
"name": "securityClearance",
"in": "formData"
},
{
"enum": [
77,
70
],
"type": "integer",
"format": "int32",
"x-enum-varnames": [
"Male",
"Female"
],
"name": "sex",
"in": "formData"
},
{
"enum": [
"teacher",
"student",
"Other"
],
"type": "string",
"x-enum-comments": {
"Other": "Other",
"Student": "student",
"Teacher": "teacher"
},
"x-enum-descriptions": [
"teacher",
"student",
"Other"
],
"x-enum-varnames": [
"Teacher",
"Student",
"Other"
],
"name": "type",
"in": "formData"
}
],
"responses": {
"200": {
"description": "ok"
}
}
}
},
"/students4": {
"get": {
"description": "test array enums fields in formdata request",
"summary": "test array enums fields in formdata request",
"parameters": [
{
"type": "array",
"items": {
"enum": [
-1,
1,
2,
3,
4,
5
],
"type": "integer"
},
"name": "class",
"in": "formData"
},
{
"type": "array",
"items": {
"enum": [
"easy",
"medium",
"hard"
],
"type": "string"
},
"name": "difficulty",
"in": "formData"
},
{
"type": "array",
"items": {
"enum": [
"g_easy",
"g_medium",
"g_hard"
],
"type": "string"
},
"name": "genericDifficulty",
"in": "formData"
},
{
"type": "array",
"items": {
"enum": [
1,
2,
4,
8,
3
],
"type": "integer"
},
"name": "mask",
"in": "formData"
},
{
"type": "string",
"name": "name",
"in": "formData"
},
{
"type": "array",
"items": {
"enum": [
0,
1,
2
],
"type": "integer"
},
"name": "securityClearance",
"in": "formData"
},
{
"enum": [
"teacher",
"student",
"Other"
],
"type": "string",
"x-enum-comments": {
"Other": "Other",
"Student": "student",
"Teacher": "teacher"
},
"x-enum-descriptions": [
"teacher",
"student",
"Other"
],
"x-enum-varnames": [
"Teacher",
"Student",
"Other"
],
"name": "type",
"in": "formData"
}
],
"responses": {
"200": {
"description": "ok"
}
}
}
}
},
"definitions": {
"types.Class": {
"type": "integer",
"enum": [
-1,
1,
2,
3,
4,
5
],
"x-enum-comments": {
"A": "AAA",
"B": "BBB"
},
"x-enum-descriptions": [
"",
"AAA",
"BBB",
"",
"",
""
],
"x-enum-varnames": [
"None",
"A",
"B",
"C",
"D",
"F"
]
},
"types.Difficulty": {
"type": "string",
"enum": [
"easy",
"medium",
"hard"
],
"x-enum-comments": {
"DifficultyHard": "This means really hard",
"Medium": "This one also has a comment"
},
"x-enum-descriptions": [
"",
"This one also has a comment",
"This means really hard"
],
"x-enum-varnames": [
"Easy",
"Medium",
"DifficultyHard"
]
},
"types.GenericDifficulty-types_Level": {
"type": "string",
"enum": [
"g_easy",
"g_medium",
"g_hard"
],
"x-enum-comments": {
"GenericDifficultyHard": "This means really hard",
"GenericMedium": "This one also has a comment"
},
"x-enum-descriptions": [
"",
"This one also has a comment",
"This means really hard"
],
"x-enum-varnames": [
"GenericEasy",
"GenericMedium",
"GenericDifficultyHard"
]
},
"types.Mask": {
"type": "integer",
"enum": [
1,
2,
4,
8,
3
],
"x-enum-comments": {
"Mask1": "Mask1",
"Mask2": "Mask2",
"Mask3": "Mask3",
"Mask4": "Mask4"
},
"x-enum-descriptions": [
"Mask1",
"Mask2",
"Mask3",
"Mask4",
""
],
"x-enum-varnames": [
"Mask1",
"Mask2",
"Mask3",
"Mask4",
"Mask5"
]
},
"types.Person": {
"type": "object",
"properties": {
"class": {
"$ref": "#/definitions/types.Class"
},
"difficulty": {
"$ref": "#/definitions/types.Difficulty"
},
"genericDifficulty": {
"$ref": "#/definitions/types.GenericDifficulty-types_Level"
},
"mask": {
"$ref": "#/definitions/types.Mask"
},
"name": {
"type": "string"
},
"securityClearance": {
"$ref": "#/definitions/types.SecurityClearance"
},
"sex": {
"$ref": "#/definitions/types.Sex"
},
"type": {
"$ref": "#/definitions/types.Type"
}
}
},
"types.SecurityClearance": {
"type": "integer",
"enum": [
0,
1,
2
],
"x-enum-comments": {
"SecurityClearanceSensitive": "Name override and comment rules apply here just as above",
"SuperSecret": "This one has a name override and a comment"
},
"x-enum-descriptions": [
"",
"Name override and comment rules apply here just as above",
"This one has a name override and a comment"
],
"x-enum-varnames": [
"Public",
"SecurityClearanceSensitive",
"SuperSecret"
]
},
"types.Sex": {
"type": "integer",
"format": "int32",
"enum": [
77,
70
],
"x-enum-varnames": [
"Male",
"Female"
]
},
"types.Type": {
"type": "string",
"enum": [
"teacher",
"student",
"Other"
],
"x-enum-comments": {
"Other": "Other",
"Student": "student",
"Teacher": "teacher"
},
"x-enum-descriptions": [
"teacher",
"student",
"Other"
],
"x-enum-varnames": [
"Teacher",
"Student",
"Other"
]
}
}
}
================================================
FILE: testdata/enums/main.go
================================================
package main
// @title Swagger Example API
// @version 1.0
// @description This is a sample server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @BasePath /v2
func main() {
}
================================================
FILE: testdata/enums/types/model.go
================================================
package types
import (
"github.com/swaggo/swag/testdata/enums/consts"
)
type Class int
const (
None Class = -1
A Class = consts.Base + (iota+1-1)*2/2%100 - (1&1 | 1) + (2 ^ 2) // AAA
B /* BBB */
C
D = C + 1
F = Class(5)
//G is not enum
G = H + 10
//H is not enum
H = 10
//I is not enum
I = int(F + 2)
)
const J = 1 << uint16(I)
type Mask int
const (
Mask1 Mask = 0x02 << iota >> 1 // Mask1
Mask2 /* Mask2 */
Mask3 // Mask3
Mask4 // Mask4
Mask5 = Mask(A + B)
)
type Type string
const (
// Teacher this line is ignored by enum comment
// teacher
Teacher Type = "teacher"
Student Type = "student" /* student */
Other Type = "Other" // Other
Unknown = "Unknown"
OtherUnknown = string(Other + Unknown)
)
type Sex rune
const (
Male Sex = 'M'
Female Sex = 'F'
)
type Difficulty string
const (
DifficultyEasy Difficulty = "easy" // @name Easy
DifficultyMedium Difficulty = "medium" // @Name Medium This one also has a comment
DifficultyHard Difficulty = "hard" // This means really hard
)
type SecurityClearance int
const (
SecurityClearancePublic SecurityClearance = iota // @name Public
SecurityClearanceSensitive // Name override and comment rules apply here just as above
SecurityClearanceSecret // @name SuperSecret This one has a name override and a comment
)
type (
GenericDifficulty[T any] string
Level int
)
const (
GenericDifficultyEasy GenericDifficulty[Level] = "g_easy" // @name GenericEasy
GenericDifficultyMedium GenericDifficulty[Level] = "g_medium" // @name GenericMedium This one also has a comment
GenericDifficultyHard GenericDifficulty[Level] = "g_hard" // This means really hard
)
type Person struct {
Name string
Class Class
Mask Mask
Type Type
Sex Sex
Difficulty Difficulty
GenericDifficulty GenericDifficulty[Level]
SecurityClearance SecurityClearance
}
type PersonWithArrayEnum struct {
Name string
Class []Class
Mask []Mask
Difficulty []Difficulty
GenericDifficulty []GenericDifficulty[Level]
SecurityClearance []SecurityClearance
Type Type
}
================================================
FILE: testdata/error/api/api.go
================================================
package api
import (
"net/http"
. "github.com/swaggo/swag/testdata/error/errors"
_ "github.com/swaggo/swag/testdata/error/web"
)
// Upload do something
// @Summary Upload file
// @Description Upload file
// @ID file.upload
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "this is a test file"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.CrossErrors "Abort !!"
// @Router /file/upload [post]
func Upload(w http.ResponseWriter, r *http.Request) {
//write your code
_ = Errors{}
}
================================================
FILE: testdata/error/errors/errors.go
================================================
package errors
// CustomInterface some interface
type CustomInterface interface {
Error() string
}
// Errors errors and interfaces
type Errors struct {
Error error
ErrorInterface CustomInterface
Interface interface{}
Any any
}
================================================
FILE: testdata/error/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"paths": {
"/file/upload": {
"post": {
"description": "Upload file",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"summary": "Upload file",
"operationId": "file.upload",
"parameters": [
{
"type": "file",
"description": "this is a test file",
"name": "file",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "Abort !!",
"schema": {
"$ref": "#/definitions/web.CrossErrors"
}
}
}
}
}
},
"definitions": {
"web.CrossErrors": {
"type": "object",
"properties": {
"any": {},
"error": {},
"errorInterface": {},
"interface": {}
}
}
}
}
================================================
FILE: testdata/error/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/error/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
func main() {
http.HandleFunc("/testapi/upload", api.Upload)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/error/web/handler.go
================================================
package web
import (
"github.com/swaggo/swag/testdata/error/errors"
)
type CrossErrors errors.Errors
================================================
FILE: testdata/extensionsFail1.go
================================================
package main
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @description It has a lot of beautiful features.
// @termsOfService http://swagger.io/terms/
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
// @x-google-endpoints ["name":"name.endpoints.environment.cloud.goog","allowCors":true}]
================================================
FILE: testdata/extensionsFail2.go
================================================
package main
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @description It has a lot of beautiful features.
// @termsOfService http://swagger.io/terms/
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
// @x-google-endpoints
================================================
FILE: testdata/external_models/external/model.go
================================================
package external
import "github.com/urfave/cli/v2"
type MyError struct {
cli.Author
}
================================================
FILE: testdata/external_models/main/api/api.go
================================================
package api
import (
"net/http"
)
// GetExternalModels example
// @Summary parse external models
// @Description get string by ID
// @ID get_external_models
// @Accept json
// @Produce json
// @Success 200 {string} string "ok"
// @Failure 400 {object} http.Header "from internal pkg"
// @Router /testapi/external_models [get]
func GetExternalModels(w http.ResponseWriter, r *http.Request) {
}
================================================
FILE: testdata/external_models/main/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "Parse external models.",
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"basePath": "/v1",
"paths": {
"/testapi/external_models": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "parse external models",
"operationId": "get_external_models",
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "from internal pkg",
"schema": {
"$ref": "#/definitions/http.Header"
}
}
}
}
}
},
"definitions": {
"http.Header": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
================================================
FILE: testdata/external_models/main/main.go
================================================
package main
// @title Swagger Example API
// @version 1.0
// @description Parse external models.
// @BasePath /v1
func main() {
}
================================================
FILE: testdata/fixes-432/a/a.go
================================================
package a
================================================
FILE: testdata/fixes-432/b/b.go
================================================
package b
================================================
FILE: testdata/fixes-432/cmd/main.go
================================================
package main
================================================
FILE: testdata/format_dst/api/api.go
================================================
package api
import (
"net/http"
_ "github.com/swaggo/swag/testdata/simple/web"
)
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID get-string-by-int
// @Accept json
// @Produce json
// @Param some_id path int true "Some ID" Format(int64)
// @Param some_id body web.Pet true "Some ID"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
func GetStringByInt(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Description get struct array by ID
// @ID get-struct-array-by-string
// @Accept json
// @Produce json
// @Param some_id path string true "Some ID"
// @Param category query int true "Category" Enums(1, 2, 3)
// @Param offset query int true "Offset" Minimum(0) default(0)
// @Param limit query int true "Limit" Maximum(50) default(10)
// @Param q query string true "q" Minlength(1) Maxlength(50) default("")
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Security ApiKeyAuth
// @Security BasicAuth
// @Security OAuth2Application[write]
// @Security OAuth2Implicit[read, admin]
// @Security OAuth2AccessCode[read]
// @Security OAuth2Password[admin]
// @Router /testapi/get-struct-array-by-string/{some_id} [get]
func GetStructArrayByString(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Summary Upload file
// @Description Upload file
// @ID file.upload
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "this is a test file"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 401 {array} string
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /file/upload [post]
func Upload(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Summary use Anonymous field
// @Success 200 {object} web.RevValue "ok"
// @Router /AnonymousField [get]
func AnonymousField() {
}
// @Summary use pet2
// @Success 200 {object} web.Pet2 "ok"
// @Router /Pet2 [get]
func Pet2() {
}
// @Summary Use IndirectRecursiveTest
// @Success 200 {object} web.IndirectRecursiveTest
// @Router /IndirectRecursiveTest [get]
func IndirectRecursiveTest() {
}
// @Summary Use Tags
// @Success 200 {object} web.Tags
// @Router /Tags [get]
func Tags() {
}
// @Summary Use CrossAlias
// @Success 200 {object} web.CrossAlias
// @Router /CrossAlias [get]
func CrossAlias() {
}
// @Summary Use AnonymousStructArray
// @Success 200 {object} web.AnonymousStructArray
// @Router /AnonymousStructArray [get]
func AnonymousStructArray() {
}
type Pet3 struct {
ID int `json:"id"`
}
// @Success 200 {object} web.Pet5a "ok"
// @Router /GetPet5a [options]
func GetPet5a() {
}
// @Success 200 {object} web.Pet5b "ok"
// @Router /GetPet5b [head]
func GetPet5b() {
}
// @Success 200 {object} web.Pet5c "ok"
// @Router /GetPet5c [patch]
func GetPet5c() {
}
type SwagReturn []map[string]string
// @Success 200 {object} api.SwagReturn "ok"
// @Router /GetPet6MapString [get]
func GetPet6MapString() {
}
================================================
FILE: testdata/format_dst/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/simple/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
http.HandleFunc("/testapi/get-struct-array-by-string/", api.GetStructArrayByString)
http.HandleFunc("/testapi/upload", api.Upload)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/format_dst/web/handler.go
================================================
package web
import (
"time"
"github.com/gofrs/uuid"
"github.com/shopspring/decimal"
"github.com/swaggo/swag/testdata/simple/cross"
)
type Pet struct {
ID int `json:"id" example:"1" format:"int64" readonly:"true"`
Category struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"category_name"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" format:"url"`
SmallCategory struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"detail_category_name" binding:"required" minLength:"4" maxLength:"16"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"`
} `json:"small_category"`
} `json:"category"`
Name string `json:"name" example:"poti" binding:"required"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" binding:"required"`
Tags []Tag `json:"tags"`
Pets *[]Pet2 `json:"pets"`
Pets2 []*Pet2 `json:"pets2"`
Status string `json:"status" enums:"healthy,ill"`
Price float32 `json:"price" example:"3.25" minimum:"1.0" maximum:"1000" multipleOf:"0.01"`
IsAlive bool `json:"is_alive" example:"true" default:"true"`
Data interface{} `json:"data"`
Hidden string `json:"-"`
UUID uuid.UUID `json:"uuid"`
Decimal decimal.Decimal `json:"decimal"`
IntArray []int `json:"int_array" example:"1,2"`
StringMap map[string]string `json:"string_map" example:"key1:value,key2:value2"`
EnumArray []int `json:"enum_array" enums:"1,2,3,5,7"`
}
type Tag struct {
ID int `json:"id" format:"int64"`
Name string `json:"name"`
Pets []Pet `json:"pets"`
}
type Tags []*Tag
type AnonymousStructArray []struct {
Foo string `json:"foo"`
}
type CrossAlias cross.Cross
type Pet2 struct {
ID int `json:"id"`
MiddleName *string `json:"middlename" extensions:"x-nullable,x-abc=def,!x-omitempty"`
DeletedAt *time.Time `json:"deleted_at"`
}
type IndirectRecursiveTest struct {
Tags []Tag
}
type APIError struct {
ErrorCode int
ErrorMessage string
CreatedAt time.Time
}
type RevValueBase struct {
Status bool `json:"Status"`
Err int32 `json:"Err,omitempty"`
}
type RevValue struct {
RevValueBase `json:"rev_value_base"`
Data int `json:"Data"`
Cross cross.Cross `json:"cross"`
Crosses []cross.Cross `json:"crosses"`
}
// Below we have Pet5b as base type and Pet5a and Pet5c both have Pet5b as anonymous field, inheriting it's properties
// By using these names we ensure that our test will fill if the order of parsing matters at all
type Pet5a struct {
*Pet5b
Odd bool `json:"odd" binding:"required"`
}
type Pet5b struct {
Name string `json:"name" binding:"required"`
}
type Pet5c struct {
*Pet5b
Odd bool `json:"odd" binding:"required"`
}
// @testFunc a Func
// @test2 das A
func testFunc() {
}
================================================
FILE: testdata/format_empty/empty.go
================================================
package main
// nothing
================================================
FILE: testdata/format_src/api/api.go
================================================
package api
import (
"net/http"
_ "github.com/swaggo/swag/testdata/simple/web"
)
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID get-string-by-int
// @Accept json
// @Produce json
// @Param some_id path int true "Some ID" Format(int64)
// @Param some_id body web.Pet true "Some ID"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
func GetStringByInt(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Description get struct array by ID
// @ID get-struct-array-by-string
// @Accept json
// @Produce json
// @Param some_id path string true "Some ID"
// @Param category query int true "Category" Enums(1, 2, 3)
// @Param offset query int true "Offset" Minimum(0) default(0)
// @Param limit query int true "Limit" Maximum(50) default(10)
// @Param q query string true "q" Minlength(1) Maxlength(50) default("")
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Security ApiKeyAuth
// @Security BasicAuth
// @Security OAuth2Application[write]
// @Security OAuth2Implicit[read, admin]
// @Security OAuth2AccessCode[read]
// @Security OAuth2Password[admin]
// @Router /testapi/get-struct-array-by-string/{some_id} [get]
func GetStructArrayByString(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Summary Upload file
// @Description Upload file
// @ID file.upload
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "this is a test file"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 401 {array} string
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /file/upload [post]
func Upload(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Summary use Anonymous field
// @Success 200 {object} web.RevValue "ok"
// @Router /AnonymousField [get]
func AnonymousField() {
}
// @Summary use pet2
// @Success 200 {object} web.Pet2 "ok"
// @Router /Pet2 [get]
func Pet2() {
}
// @Summary Use IndirectRecursiveTest
// @Success 200 {object} web.IndirectRecursiveTest
// @Router /IndirectRecursiveTest [get]
func IndirectRecursiveTest() {
}
// @Summary Use Tags
// @Success 200 {object} web.Tags
// @Router /Tags [get]
func Tags() {
}
// @Summary Use CrossAlias
// @Success 200 {object} web.CrossAlias
// @Router /CrossAlias [get]
func CrossAlias() {
}
// @Summary Use AnonymousStructArray
// @Success 200 {object} web.AnonymousStructArray
// @Router /AnonymousStructArray [get]
func AnonymousStructArray() {
}
type Pet3 struct {
ID int `json:"id"`
}
// @Success 200 {object} web.Pet5a "ok"
// @Router /GetPet5a [options]
func GetPet5a() {
}
// @Success 200 {object} web.Pet5b "ok"
// @Router /GetPet5b [head]
func GetPet5b() {
}
// @Success 200 {object} web.Pet5c "ok"
// @Router /GetPet5c [patch]
func GetPet5c() {
}
type SwagReturn []map[string]string
// @Success 200 {object} api.SwagReturn "ok"
// @Router /GetPet6MapString [get]
func GetPet6MapString() {
}
================================================
FILE: testdata/format_src/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/simple/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
http.HandleFunc("/testapi/get-struct-array-by-string/", api.GetStructArrayByString)
http.HandleFunc("/testapi/upload", api.Upload)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/format_src/web/handler.go
================================================
package web
import (
"time"
"github.com/gofrs/uuid"
"github.com/shopspring/decimal"
"github.com/swaggo/swag/testdata/simple/cross"
)
type Pet struct {
ID int `json:"id" example:"1" format:"int64" readonly:"true"`
Category struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"category_name"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" format:"url"`
SmallCategory struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"detail_category_name" binding:"required" minLength:"4" maxLength:"16"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"`
} `json:"small_category"`
} `json:"category"`
Name string `json:"name" example:"poti" binding:"required"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" binding:"required"`
Tags []Tag `json:"tags"`
Pets *[]Pet2 `json:"pets"`
Pets2 []*Pet2 `json:"pets2"`
Status string `json:"status" enums:"healthy,ill"`
Price float32 `json:"price" example:"3.25" minimum:"1.0" maximum:"1000" multipleOf:"0.01"`
IsAlive bool `json:"is_alive" example:"true" default:"true"`
Data interface{} `json:"data"`
Hidden string `json:"-"`
UUID uuid.UUID `json:"uuid"`
Decimal decimal.Decimal `json:"decimal"`
IntArray []int `json:"int_array" example:"1,2"`
StringMap map[string]string `json:"string_map" example:"key1:value,key2:value2"`
EnumArray []int `json:"enum_array" enums:"1,2,3,5,7"`
}
type Tag struct {
ID int `json:"id" format:"int64"`
Name string `json:"name"`
Pets []Pet `json:"pets"`
}
type Tags []*Tag
type AnonymousStructArray []struct {
Foo string `json:"foo"`
}
type CrossAlias cross.Cross
type Pet2 struct {
ID int `json:"id"`
MiddleName *string `json:"middlename" extensions:"x-nullable,x-abc=def,!x-omitempty"`
DeletedAt *time.Time `json:"deleted_at"`
}
type IndirectRecursiveTest struct {
Tags []Tag
}
type APIError struct {
ErrorCode int
ErrorMessage string
CreatedAt time.Time
}
type RevValueBase struct {
Status bool `json:"Status"`
Err int32 `json:"Err,omitempty"`
}
type RevValue struct {
RevValueBase `json:"rev_value_base"`
Data int `json:"Data"`
Cross cross.Cross `json:"cross"`
Crosses []cross.Cross `json:"crosses"`
}
// Below we have Pet5b as base type and Pet5a and Pet5c both have Pet5b as anonymous field, inheriting it's properties
// By using these names we ensure that our test will fill if the order of parsing matters at all
type Pet5a struct {
*Pet5b
Odd bool `json:"odd" binding:"required"`
}
type Pet5b struct {
Name string `json:"name" binding:"required"`
}
type Pet5c struct {
*Pet5b
Odd bool `json:"odd" binding:"required"`
}
// @testFunc a Func
// @test2 das A
func testFunc() {
}
================================================
FILE: testdata/format_test/api/api.go
================================================
package api
import (
"net/http"
_ "github.com/swaggo/swag/testdata/simple/web"
)
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID get-string-by-int
// @Accept json
// @Produce json
// @Param some_id path int true "Some ID" Format(int64)
// @Param some_id body web.Pet true "Some ID"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
func GetStringByInt(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Description get struct array by ID
// @ID get-struct-array-by-string
// @Accept json
// @Produce json
// @Param some_id path string true "Some ID"
// @Param category query int true "Category" Enums(1, 2, 3)
// @Param offset query int true "Offset" Minimum(0) default(0)
// @Param limit query int true "Limit" Maximum(50) default(10)
// @Param q query string true "q" Minlength(1) Maxlength(50) default("")
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Security ApiKeyAuth
// @Security BasicAuth
// @Security OAuth2Application[write]
// @Security OAuth2Implicit[read, admin]
// @Security OAuth2AccessCode[read]
// @Security OAuth2Password[admin]
// @Router /testapi/get-struct-array-by-string/{some_id} [get]
func GetStructArrayByString(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Summary Upload file
// @Description Upload file
// @ID file.upload
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "this is a test file"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 401 {array} string
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /file/upload [post]
func Upload(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Summary use Anonymous field
// @Success 200 {object} web.RevValue "ok"
// @Router /AnonymousField [get]
func AnonymousField() {
}
// @Summary use pet2
// @Success 200 {object} web.Pet2 "ok"
// @Router /Pet2 [get]
func Pet2() {
}
// @Summary Use IndirectRecursiveTest
// @Success 200 {object} web.IndirectRecursiveTest
// @Router /IndirectRecursiveTest [get]
func IndirectRecursiveTest() {
}
// @Summary Use Tags
// @Success 200 {object} web.Tags
// @Router /Tags [get]
func Tags() {
}
// @Summary Use CrossAlias
// @Success 200 {object} web.CrossAlias
// @Router /CrossAlias [get]
func CrossAlias() {
}
// @Summary Use AnonymousStructArray
// @Success 200 {object} web.AnonymousStructArray
// @Router /AnonymousStructArray [get]
func AnonymousStructArray() {
}
type Pet3 struct {
ID int `json:"id"`
}
// @Success 200 {object} web.Pet5a "ok"
// @Router /GetPet5a [options]
func GetPet5a() {
}
// @Success 200 {object} web.Pet5b "ok"
// @Router /GetPet5b [head]
func GetPet5b() {
}
// @Success 200 {object} web.Pet5c "ok"
// @Router /GetPet5c [patch]
func GetPet5c() {
}
type SwagReturn []map[string]string
// @Success 200 {object} api.SwagReturn "ok"
// @Router /GetPet6MapString [get]
func GetPet6MapString() {
}
================================================
FILE: testdata/format_test/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/simple/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
http.HandleFunc("/testapi/get-struct-array-by-string/", api.GetStructArrayByString)
http.HandleFunc("/testapi/upload", api.Upload)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/format_test/web/handler.go
================================================
package web
import (
"time"
"github.com/gofrs/uuid"
"github.com/shopspring/decimal"
"github.com/swaggo/swag/testdata/simple/cross"
)
type Pet struct {
ID int `json:"id" example:"1" format:"int64" readonly:"true"`
Category struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"category_name"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" format:"url"`
SmallCategory struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"detail_category_name" binding:"required" minLength:"4" maxLength:"16"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"`
} `json:"small_category"`
} `json:"category"`
Name string `json:"name" example:"poti" binding:"required"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" binding:"required"`
Tags []Tag `json:"tags"`
Pets *[]Pet2 `json:"pets"`
Pets2 []*Pet2 `json:"pets2"`
Status string `json:"status" enums:"healthy,ill"`
Price float32 `json:"price" example:"3.25" minimum:"1.0" maximum:"1000" multipleOf:"0.01"`
IsAlive bool `json:"is_alive" example:"true" default:"true"`
Data interface{} `json:"data"`
Hidden string `json:"-"`
UUID uuid.UUID `json:"uuid"`
Decimal decimal.Decimal `json:"decimal"`
IntArray []int `json:"int_array" example:"1,2"`
StringMap map[string]string `json:"string_map" example:"key1:value,key2:value2"`
EnumArray []int `json:"enum_array" enums:"1,2,3,5,7"`
}
type Tag struct {
ID int `json:"id" format:"int64"`
Name string `json:"name"`
Pets []Pet `json:"pets"`
}
type Tags []*Tag
type AnonymousStructArray []struct {
Foo string `json:"foo"`
}
type CrossAlias cross.Cross
type Pet2 struct {
ID int `json:"id"`
MiddleName *string `json:"middlename" extensions:"x-nullable,x-abc=def,!x-omitempty"`
DeletedAt *time.Time `json:"deleted_at"`
}
type IndirectRecursiveTest struct {
Tags []Tag
}
type APIError struct {
ErrorCode int
ErrorMessage string
CreatedAt time.Time
}
type RevValueBase struct {
Status bool `json:"Status"`
Err int32 `json:"Err,omitempty"`
}
type RevValue struct {
RevValueBase `json:"rev_value_base"`
Data int `json:"Data"`
Cross cross.Cross `json:"cross"`
Crosses []cross.Cross `json:"crosses"`
}
// Below we have Pet5b as base type and Pet5a and Pet5c both have Pet5b as anonymous field, inheriting it's properties
// By using these names we ensure that our test will fill if the order of parsing matters at all
type Pet5a struct {
*Pet5b
Odd bool `json:"odd" binding:"required"`
}
type Pet5b struct {
Name string `json:"name" binding:"required"`
}
type Pet5c struct {
*Pet5b
Odd bool `json:"odd" binding:"required"`
}
// @testFunc a Func
// @test2 das A
func testFunc() {
}
================================================
FILE: testdata/generics_arrays/api/api.go
================================================
package api
import (
"net/http"
"github.com/swaggo/swag/testdata/generics_arrays/types"
"github.com/swaggo/swag/testdata/generics_arrays/web"
)
// @Summary List Posts
// @Description Get All of the Posts
// @Accept json
// @Produce json
// @Param data body web.GenericListBody[types.Post] true "Some ID"
// @Success 200 {object} web.GenericListResponse[types.Post]
// @Success 222 {object} web.GenericListResponseMulti[types.Post, types.Post]
// @Router /posts [get]
func GetPosts(w http.ResponseWriter, r *http.Request) {
_ = web.GenericListResponseMulti[types.Post, types.Post]{}
}
// @Summary Add new pets to the store
// @Description get string by ID
// @Accept json
// @Produce json
// @Param data body web.GenericListBodyMulti[types.Post, types.Post] true "Some ID"
// @Success 200 {object} web.GenericListResponse[types.Post]
// @Success 222 {object} web.GenericListResponseMulti[types.Post, types.Post]
// @Router /posts-multi [get]
func GetPostMulti(w http.ResponseWriter, r *http.Request) {
//write your code
_ = web.GenericListResponseMulti[types.Post, types.Post]{}
}
// @Summary Add new pets to the store
// @Description get string by ID
// @Accept json
// @Produce json
// @Param data body web.GenericListBodyMulti[types.Post, []types.Post] true "Some ID"
// @Success 200 {object} web.GenericListResponse[[]types.Post]
// @Success 222 {object} web.GenericListResponseMulti[types.Post, []types.Post]
// @Router /posts-multis [get]
func GetPostArray(w http.ResponseWriter, r *http.Request) {
//write your code
_ = web.GenericListResponseMulti[types.Post, []types.Post]{}
}
================================================
FILE: testdata/generics_arrays/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"host": "localhost:4000",
"basePath": "/api",
"paths": {
"/posts": {
"get": {
"description": "Get All of the Posts",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "List Posts",
"parameters": [
{
"description": "Some ID",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.GenericListBody-types_Post"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/web.GenericListResponse-types_Post"
}
},
"222": {
"description": "",
"schema": {
"$ref": "#/definitions/web.GenericListResponseMulti-types_Post-types_Post"
}
}
}
}
},
"/posts-multi": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add new pets to the store",
"parameters": [
{
"description": "Some ID",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.GenericListBodyMulti-types_Post-types_Post"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/web.GenericListResponse-types_Post"
}
},
"222": {
"description": "",
"schema": {
"$ref": "#/definitions/web.GenericListResponseMulti-types_Post-types_Post"
}
}
}
}
},
"/posts-multis": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add new pets to the store",
"parameters": [
{
"description": "Some ID",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.GenericListBodyMulti-types_Post-array_types_Post"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/web.GenericListResponse-array_types_Post"
}
},
"222": {
"description": "",
"schema": {
"$ref": "#/definitions/web.GenericListResponseMulti-types_Post-array_types_Post"
}
}
}
}
}
},
"definitions": {
"types.Post": {
"type": "object",
"properties": {
"@uri": {
"type": "string"
},
"data": {
"description": "Post data",
"type": "object",
"properties": {
"name": {
"description": "Post tag",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"id": {
"type": "integer",
"format": "int64",
"example": 1
},
"name": {
"description": "Post name",
"type": "string",
"example": "poti"
}
}
},
"web.GenericListBody-types_Post": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
}
},
"web.GenericListBodyMulti-types_Post-array_types_Post": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"meta": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
}
}
},
"web.GenericListBodyMulti-types_Post-types_Post": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"meta": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
}
},
"web.GenericListResponse-array_types_Post": {
"type": "object",
"properties": {
"items": {
"description": "Items from the list response",
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
},
"status": {
"description": "Status of some other stuff",
"type": "string"
}
}
},
"web.GenericListResponse-types_Post": {
"type": "object",
"properties": {
"items": {
"description": "Items from the list response",
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"status": {
"description": "Status of some other stuff",
"type": "string"
}
}
},
"web.GenericListResponseMulti-types_Post-array_types_Post": {
"type": "object",
"properties": {
"itemsOne": {
"description": "ItemsOne is the first thing",
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"itemsTwo": {
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
},
"status": {
"description": "Status of the things",
"type": "string"
}
}
},
"web.GenericListResponseMulti-types_Post-types_Post": {
"type": "object",
"properties": {
"itemsOne": {
"description": "ItemsOne is the first thing",
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"itemsTwo": {
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"status": {
"description": "Status of the things",
"type": "string"
}
}
}
}
}
================================================
FILE: testdata/generics_arrays/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/generics_arrays/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @host localhost:4000
// @basePath /api
func main() {
http.HandleFunc("/posts/", api.GetPosts)
http.HandleFunc("/posts-multi/", api.GetPostMulti)
http.HandleFunc("/posts-multis/", api.GetPostArray)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/generics_arrays/types/post.go
================================================
package types
type APIBase struct {
APIUrl string `json:"@uri,omitempty"`
ID int `json:"id" example:"1" format:"int64"`
}
type Post struct {
APIBase
// Post name
Name string `json:"name" example:"poti"`
// Post data
Data struct {
// Post tag
Tag []string `json:"name"`
} `json:"data"`
}
================================================
FILE: testdata/generics_arrays/web/handler.go
================================================
package web
import (
"time"
)
type GenericListBody[T any] struct {
Data []T
}
type GenericListBodyMulti[T any, X any] struct {
Data []T
Meta []X
}
// GenericListResponse[T]
// @Description Some Generic List Response
type GenericListResponse[T any] struct {
// Items from the list response
Items []T
// Status of some other stuff
Status string
}
// GenericListResponseMulti[T, X]
// @Description this contains a few things
type GenericListResponseMulti[T any, X any] struct {
// ItemsOne is the first thing
ItemsOne []T
// ItemsTwo is the second thing
ItemsTwo []X
// Status of the things
Status string
}
// APIError
// @Description API error
// @Description with information about it
// Other some summary
type APIError struct {
// Error an Api error
Error string // Error this is Line comment
// Error `number` tick comment
ErrorNo int64
ErrorCtx string // Error `context` tick comment
CreatedAt time.Time // Error time
}
================================================
FILE: testdata/generics_basic/.swaggo
================================================
replace types.Field[string] string
replace types.DoubleField[string,string] []string
replace types.TrippleField[string,string] [][]string
================================================
FILE: testdata/generics_basic/api/api.go
================================================
package api
import (
"net/http"
"github.com/swaggo/swag/testdata/generics_basic/types"
"github.com/swaggo/swag/testdata/generics_basic/web"
)
type Response[T any, X any] struct {
Data T
Meta X
Status string
}
type Response2[T, X any, Y any] struct {
Data T
Meta X
Status Y
}
type StringStruct struct {
Data string
}
type Foo = web.GenericResponseMulti[types.Post, types.Post]
// @Summary Add a new pet to the store
// @Description get string by ID
// @Accept json
// @Produce json
// @Param data body web.GenericBody[types.Post] true "Some ID"
// @Success 200 {object} web.GenericResponse[types.Post]
// @Success 201 {object} web.GenericResponse[types.Hello]
// @Success 202 {object} web.GenericResponse[types.Field[string]]
// @Success 203 {object} web.GenericResponse[types.Field[int]]
// @Success 204 {object} Response[string, types.Field[int]]
// @Success 205 {object} Response[StringStruct, types.Field[int]]
// @Success 206 {object} Response2[string, types.Field[int],string]
// @Success 207 {object} Response[[]map[string]string, map[string][]types.Field[int]]
// @Success 222 {object} web.GenericResponseMulti[types.Post, types.Post]
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /posts/ [post]
func GetPost(w http.ResponseWriter, r *http.Request) {
//write your code
_ = web.GenericResponse[types.Post]{}
}
// @Summary Add new pets to the store
// @Description get string by ID
// @Accept json
// @Produce json
// @Param data body web.GenericBodyMulti[types.Post, types.Post] true "Some ID"
// @Success 200 {object} web.GenericResponse[types.Post]
// @Success 201 {object} web.GenericResponse[types.Hello]
// @Success 202 {object} web.GenericResponse[types.Field[string]]
// @Success 203 {object} Foo
// @Success 222 {object} web.GenericResponseMulti[types.Post, types.Post]
// @Router /posts-multi/ [post]
func GetPostMulti(w http.ResponseWriter, r *http.Request) {
//write your code
_ = web.GenericResponse[types.Post]{}
}
// @Summary Add new pets to the store
// @Description get string by ID
// @Accept json
// @Produce json
// @Param data body web.GenericBodyMulti[[]types.Post, [][]types.Post] true "Some ID"
// @Success 200 {object} web.GenericResponse[[]types.Post]
// @Success 201 {object} web.GenericResponse[[]types.Hello]
// @Success 222 {object} web.GenericResponseMulti[[]types.Post, [][]types.Post]
// @Router /posts-multis/ [post]
func GetPostArray(w http.ResponseWriter, r *http.Request) {
//write your code
_ = web.GenericResponse[types.Post]{}
}
================================================
FILE: testdata/generics_basic/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"host": "localhost:4000",
"basePath": "/api",
"paths": {
"/posts-multi/": {
"post": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add new pets to the store",
"parameters": [
{
"description": "Some ID",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.GenericBodyMulti-types_Post-types_Post"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/web.GenericResponse-types_Post"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/web.GenericResponse-types_Hello"
}
},
"202": {
"description": "Accepted",
"schema": {
"$ref": "#/definitions/web.GenericResponse-types_Field-string"
}
},
"203": {
"description": "Non-Authoritative Information",
"schema": {
"$ref": "#/definitions/api.Foo"
}
},
"222": {
"description": "",
"schema": {
"$ref": "#/definitions/web.GenericResponseMulti-types_Post-types_Post"
}
}
}
}
},
"/posts-multis/": {
"post": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add new pets to the store",
"parameters": [
{
"description": "Some ID",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.GenericBodyMulti-array_types_Post-array_array_types_Post"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/web.GenericResponse-array_types_Post"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/web.GenericResponse-array_types_Hello"
}
},
"222": {
"description": "",
"schema": {
"$ref": "#/definitions/web.GenericResponseMulti-array_types_Post-array_array_types_Post"
}
}
}
}
},
"/posts/": {
"post": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add a new pet to the store",
"parameters": [
{
"description": "Some ID",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.GenericBody-types_Post"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/web.GenericResponse-types_Post"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/web.GenericResponse-types_Hello"
}
},
"202": {
"description": "Accepted",
"schema": {
"$ref": "#/definitions/web.GenericResponse-types_Field-string"
}
},
"203": {
"description": "Non-Authoritative Information",
"schema": {
"$ref": "#/definitions/web.GenericResponse-types_Field-int"
}
},
"204": {
"description": "No Content",
"schema": {
"$ref": "#/definitions/api.Response-string-types_Field-int"
}
},
"205": {
"description": "Reset Content",
"schema": {
"$ref": "#/definitions/api.Response-api_StringStruct-types_Field-int"
}
},
"206": {
"description": "Partial Content",
"schema": {
"$ref": "#/definitions/api.Response2-string-types_Field-int-string"
}
},
"207": {
"description": "Multi-Status",
"schema": {
"$ref": "#/definitions/api.Response-array_map_string_string-map_string_array_types_Field-int"
}
},
"222": {
"description": "",
"schema": {
"$ref": "#/definitions/web.GenericResponseMulti-types_Post-types_Post"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
}
},
"definitions": {
"api.Foo": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/types.Post"
},
"meta": {
"$ref": "#/definitions/types.Post"
},
"status": {
"type": "string"
}
}
},
"api.Response-api_StringStruct-types_Field-int": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/api.StringStruct"
},
"meta": {
"$ref": "#/definitions/types.Field-int"
},
"status": {
"type": "string"
}
}
},
"api.Response-array_map_string_string-map_string_array_types_Field-int": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"meta": {
"$ref": "#/definitions/map_string_array_types.Field-int"
},
"status": {
"type": "string"
}
}
},
"api.Response-string-types_Field-int": {
"type": "object",
"properties": {
"data": {
"type": "string"
},
"meta": {
"$ref": "#/definitions/types.Field-int"
},
"status": {
"type": "string"
}
}
},
"api.Response2-string-types_Field-int-string": {
"type": "object",
"properties": {
"data": {
"type": "string"
},
"meta": {
"$ref": "#/definitions/types.Field-int"
},
"status": {
"type": "string"
}
}
},
"api.StringStruct": {
"type": "object",
"properties": {
"data": {
"type": "string"
}
}
},
"map_string_array_types.Field-int": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Field-int"
}
}
},
"types.Field-int": {
"type": "object",
"properties": {
"value": {
"type": "integer"
}
}
},
"types.Field-string": {
"type": "object",
"properties": {
"value": {
"type": "string"
}
}
},
"types.Hello": {
"type": "object",
"properties": {
"arrayField": {
"type": "array",
"items": {
"type": "string"
}
},
"mapField": {
"$ref": "#/definitions/types.MapField-string-float64"
},
"mapFieldNestedStruct": {
"$ref": "#/definitions/types.MapFieldNestedStruct-string"
},
"myNewArrayField": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "string"
}
}
},
"myNewField": {
"type": "array",
"items": {
"type": "string"
}
},
"myStringField1": {
"type": "string"
},
"myStringField2": {
"type": "string"
},
"originArrayField": {
"type": "array",
"items": {
"type": "string"
}
},
"originMapField": {
"type": "object",
"additionalProperties": {
"type": "number",
"format": "float64"
}
}
}
},
"types.MapField-string-float64": {
"type": "object",
"additionalProperties": {
"type": "number",
"format": "float64"
}
},
"types.MapFieldNestedStruct-string": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/types.MapFieldValue"
}
},
"types.MapFieldValue": {
"type": "object",
"properties": {
"f": {
"type": "number",
"format": "float64"
},
"s": {
"type": "string"
}
}
},
"types.Post": {
"type": "object",
"properties": {
"@uri": {
"type": "string"
},
"data": {
"description": "Post data",
"type": "object",
"properties": {
"name": {
"description": "Post tag",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"id": {
"type": "integer",
"format": "int64",
"example": 1
},
"name": {
"description": "Post name",
"type": "string",
"example": "poti"
}
}
},
"web.APIError": {
"description": "API error with information about it",
"type": "object",
"properties": {
"createdAt": {
"description": "Error time",
"type": "string"
},
"error": {
"description": "Error an Api error",
"type": "string"
},
"errorCtx": {
"description": "Error `context` tick comment",
"type": "string"
},
"errorNo": {
"description": "Error `number` tick comment",
"type": "integer",
"format": "int64"
}
}
},
"web.GenericBody-types_Post": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/types.Post"
}
}
},
"web.GenericBodyMulti-array_types_Post-array_array_types_Post": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"meta": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
}
}
},
"web.GenericBodyMulti-types_Post-types_Post": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/types.Post"
},
"meta": {
"$ref": "#/definitions/types.Post"
}
}
},
"web.GenericResponse-array_types_Hello": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Hello"
}
},
"status": {
"type": "string"
}
}
},
"web.GenericResponse-array_types_Post": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"status": {
"type": "string"
}
}
},
"web.GenericResponse-types_Field-int": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/types.Field-int"
},
"status": {
"type": "string"
}
}
},
"web.GenericResponse-types_Field-string": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/types.Field-string"
},
"status": {
"type": "string"
}
}
},
"web.GenericResponse-types_Hello": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/types.Hello"
},
"status": {
"type": "string"
}
}
},
"web.GenericResponse-types_Post": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/types.Post"
},
"status": {
"type": "string"
}
}
},
"web.GenericResponseMulti-array_types_Post-array_array_types_Post": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"meta": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
},
"status": {
"type": "string"
}
}
},
"web.GenericResponseMulti-types_Post-types_Post": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/types.Post"
},
"meta": {
"$ref": "#/definitions/types.Post"
},
"status": {
"type": "string"
}
}
}
}
}
================================================
FILE: testdata/generics_basic/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/generics_basic/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @host localhost:4000
// @basePath /api
func main() {
http.HandleFunc("/posts/", api.GetPost)
http.HandleFunc("/posts-multi/", api.GetPostMulti)
http.HandleFunc("/posts-multis/", api.GetPostArray)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/generics_basic/types/post.go
================================================
package types
type APIBase struct {
APIUrl string `json:"@uri,omitempty"`
ID int `json:"id" example:"1" format:"int64"`
}
type Post struct {
APIBase
ID int `json:"id" example:"1" format:"int64"`
// Post name
Name string `json:"name" example:"poti"`
// Post data
Data struct {
// Post tag
Tag []string `json:"name"`
} `json:"data"`
}
================================================
FILE: testdata/generics_basic/types/string.go
================================================
package types
type Field[T any] struct {
Value T
}
type DoubleField[T1 any, T2 any] struct {
Value1 T1
Value2 T2
}
type TrippleField[T1 any, T2 any] struct {
Value1 T1
Value2 T2
}
type ArrayField[T any] []T
type MapField[K comparable, V any] map[K]V
type MapFieldValue struct {
S string
F float64
}
type MapFieldNestedStruct[K comparable] map[K]MapFieldValue
type Hello struct {
MyStringField1 Field[*string] `json:"myStringField1"`
MyStringField2 Field[string] `json:"myStringField2"`
MyArrayField DoubleField[*string, string] `json:"myNewField"`
MyArrayDepthField TrippleField[*string, string] `json:"myNewArrayField"`
ArrayField ArrayField[string] `json:"arrayField"`
MapField MapField[string, float64] `json:"mapField"`
OriginArrayField []string `json:"originArrayField"`
OriginMapField map[string]float64 `json:"originMapField"`
MapFieldNestedStruct MapFieldNestedStruct[string] `json:"mapFieldNestedStruct"`
}
================================================
FILE: testdata/generics_basic/web/handler.go
================================================
package web
import (
"time"
)
type GenericBody[T any] struct {
Data T
}
type GenericBodyMulti[T any, X any] struct {
Data T
Meta X
}
type GenericResponse[T any] struct {
Data T
Status string
}
type GenericResponseMulti[T any, X any] struct {
Data T
Meta X
Status string
}
// APIError
// @Description API error
// @Description with information about it
// Other some summary
type APIError struct {
// Error an Api error
Error string // Error this is Line comment
// Error `number` tick comment
ErrorNo int64
ErrorCtx string // Error `context` tick comment
CreatedAt time.Time // Error time
}
================================================
FILE: testdata/generics_function_scoped/api/api.go
================================================
package api
import (
"net/http"
"github.com/swaggo/swag/testdata/generics_function_scoped/types"
)
// @Summary Generic Response
// @Produce json
// @Success 200 {object} types.GenericResponse[api.GetGeneric.User]
// @Success 201 {object} types.GenericResponse[api.GetGeneric.Post]
// @Router / [get]
func GetGeneric(w http.ResponseWriter, r *http.Request) {
type User struct {
Username int `json:"username"`
Email string `json:"email"`
}
type Post struct {
Slug int `json:"slug"`
Title string `json:"title"`
}
_ = types.GenericResponse[any]{}
}
// @Summary Generic Response With Custom Type Names
// @Produce json
// @Success 200 {object} types.GenericResponse[api.GetGenericRenamed.User]
// @Success 201 {object} types.GenericResponse[api.GetGenericRenamed.Post]
// @Router /renamed [get]
func GetGenericRenamed(w http.ResponseWriter, r *http.Request) {
type User struct {
Username int `json:"username"`
Email string `json:"email"`
} // @Name RenamedUserData
type Post struct {
Slug int `json:"slug"`
Title string `json:"title"`
} // @Name RenamedPostData
_ = types.GenericResponse[any]{}
}
// @Summary Multiple Generic Response
// @Produce json
// @Success 200 {object} types.GenericMultiResponse[api.GetGenericMulti.MyStructA, api.GetGenericMulti.MyStructB]
// @Success 201 {object} types.GenericMultiResponse[api.GetGenericMulti.MyStructB, api.GetGenericMulti.MyStructA]
// @Router /multi [get]
func GetGenericMulti(w http.ResponseWriter, r *http.Request) {
type MyStructA struct {
SomeFieldA string `json:"some_field_a"`
}
type MyStructB struct {
SomeFieldB string `json:"some_field_b"`
}
_ = types.GenericMultiResponse[any, any]{}
}
// @Summary Multiple Generic Response With Custom Type Names
// @Produce json
// @Success 200 {object} types.GenericMultiResponse[api.GetGenericMultiRenamed.MyStructA, api.GetGenericMultiRenamed.MyStructB]
// @Success 201 {object} types.GenericMultiResponse[api.GetGenericMultiRenamed.MyStructB, api.GetGenericMultiRenamed.MyStructA]
// @Router /multi-renamed [get]
func GetGenericMultiRenamed(w http.ResponseWriter, r *http.Request) {
type MyStructA struct {
SomeFieldA string `json:"some_field_a"`
} // @Name NameForMyStructA
type MyStructB struct {
SomeFieldB string `json:"some_field_b"`
} // @Name NameForMyStructB
_ = types.GenericMultiResponse[any, any]{}
}
================================================
FILE: testdata/generics_function_scoped/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server.",
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"host": "localhost:8080",
"basePath": "/api",
"paths": {
"/": {
"get": {
"produces": [
"application/json"
],
"summary": "Generic Response",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.GenericResponse-api_GetGeneric_User"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/types.GenericResponse-api_GetGeneric_Post"
}
}
}
}
},
"/multi": {
"get": {
"produces": [
"application/json"
],
"summary": "Multiple Generic Response",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.GenericMultiResponse-api_GetGenericMulti_MyStructA-api_GetGenericMulti_MyStructB"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/types.GenericMultiResponse-api_GetGenericMulti_MyStructB-api_GetGenericMulti_MyStructA"
}
}
}
}
},
"/multi-renamed": {
"get": {
"produces": [
"application/json"
],
"summary": "Multiple Generic Response With Custom Type Names",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.GenericMultiResponse-NameForMyStructA-NameForMyStructB"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/types.GenericMultiResponse-NameForMyStructB-NameForMyStructA"
}
}
}
}
},
"/renamed": {
"get": {
"produces": [
"application/json"
],
"summary": "Generic Response With Custom Type Names",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.GenericResponse-RenamedUserData"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/types.GenericResponse-RenamedPostData"
}
}
}
}
}
},
"definitions": {
"NameForMyStructA": {
"type": "object",
"properties": {
"some_field_a": {
"type": "string"
}
}
},
"NameForMyStructB": {
"type": "object",
"properties": {
"some_field_b": {
"type": "string"
}
}
},
"RenamedPostData": {
"type": "object",
"properties": {
"slug": {
"type": "integer"
},
"title": {
"type": "string"
}
}
},
"RenamedUserData": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"username": {
"type": "integer"
}
}
},
"api.GetGeneric.Post": {
"type": "object",
"properties": {
"slug": {
"type": "integer"
},
"title": {
"type": "string"
}
}
},
"api.GetGeneric.User": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"username": {
"type": "integer"
}
}
},
"api.GetGenericMulti.MyStructA": {
"type": "object",
"properties": {
"some_field_a": {
"type": "string"
}
}
},
"api.GetGenericMulti.MyStructB": {
"type": "object",
"properties": {
"some_field_b": {
"type": "string"
}
}
},
"types.GenericMultiResponse-NameForMyStructA-NameForMyStructB": {
"type": "object",
"properties": {
"data_t": {
"$ref": "#/definitions/NameForMyStructA"
},
"data_x": {
"$ref": "#/definitions/NameForMyStructB"
},
"status": {
"type": "string"
}
}
},
"types.GenericMultiResponse-NameForMyStructB-NameForMyStructA": {
"type": "object",
"properties": {
"data_t": {
"$ref": "#/definitions/NameForMyStructB"
},
"data_x": {
"$ref": "#/definitions/NameForMyStructA"
},
"status": {
"type": "string"
}
}
},
"types.GenericMultiResponse-api_GetGenericMulti_MyStructA-api_GetGenericMulti_MyStructB": {
"type": "object",
"properties": {
"data_t": {
"$ref": "#/definitions/api.GetGenericMulti.MyStructA"
},
"data_x": {
"$ref": "#/definitions/api.GetGenericMulti.MyStructB"
},
"status": {
"type": "string"
}
}
},
"types.GenericMultiResponse-api_GetGenericMulti_MyStructB-api_GetGenericMulti_MyStructA": {
"type": "object",
"properties": {
"data_t": {
"$ref": "#/definitions/api.GetGenericMulti.MyStructB"
},
"data_x": {
"$ref": "#/definitions/api.GetGenericMulti.MyStructA"
},
"status": {
"type": "string"
}
}
},
"types.GenericResponse-RenamedPostData": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/RenamedPostData"
},
"status": {
"type": "string"
}
}
},
"types.GenericResponse-RenamedUserData": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/RenamedUserData"
},
"status": {
"type": "string"
}
}
},
"types.GenericResponse-api_GetGeneric_Post": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/api.GetGeneric.Post"
},
"status": {
"type": "string"
}
}
},
"types.GenericResponse-api_GetGeneric_User": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/api.GetGeneric.User"
},
"status": {
"type": "string"
}
}
}
}
}
================================================
FILE: testdata/generics_function_scoped/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/generics_function_scoped/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server.
// @host localhost:8080
// @basePath /api
func main() {
http.HandleFunc("/", api.GetGeneric)
http.HandleFunc("/renamed", api.GetGenericRenamed)
http.HandleFunc("/multi", api.GetGenericMulti)
http.HandleFunc("/multi-renamed", api.GetGenericMulti)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/generics_function_scoped/types/response.go
================================================
package types
type GenericResponse[T any] struct {
Status string `json:"status"`
Data T `json:"data"`
}
type GenericMultiResponse[T any, X any] struct {
Status string `json:"status"`
DataT T `json:"data_t"`
DataX X `json:"data_x"`
}
================================================
FILE: testdata/generics_multi_level_nesting/api/api.go
================================================
package api
import (
"net/http"
)
// GetPosts
// @Summary Test Generics with multi level nesting
// @Description Test one of the edge cases found in generics
// @Accept json
// @Produce json
// @Success 200 {object} web.TestResponse
// @Router /use-struct-and-generics-with-multi-level-nesting [get]
func GetPosts(w http.ResponseWriter, r *http.Request) {
}
================================================
FILE: testdata/generics_multi_level_nesting/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"host": "localhost:4000",
"basePath": "/api",
"paths": {
"/use-struct-and-generics-with-multi-level-nesting": {
"get": {
"description": "Test one of the edge cases found in generics",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Test Generics with multi level nesting",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/web.TestResponse"
}
}
}
}
}
},
"definitions": {
"web.DataPoint-float64": {
"type": "object",
"properties": {
"timestamp": {
"type": "integer"
},
"value": {
"type": "number"
}
}
},
"web.DataPoint-int64": {
"type": "object",
"properties": {
"timestamp": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"web.Entity-float64": {
"type": "object",
"properties": {
"line_with_fix_type": {
"type": "array",
"items": {
"$ref": "#/definitions/web.DataPoint-float64"
}
},
"line_with_generic_type": {
"type": "array",
"items": {
"$ref": "#/definitions/web.DataPoint-float64"
}
},
"multiple_lines": {
"type": "array",
"items": {
"$ref": "#/definitions/web.NamedLineData-float64"
}
}
}
},
"web.Entity-int64": {
"type": "object",
"properties": {
"line_with_fix_type": {
"type": "array",
"items": {
"$ref": "#/definitions/web.DataPoint-float64"
}
},
"line_with_generic_type": {
"type": "array",
"items": {
"$ref": "#/definitions/web.DataPoint-int64"
}
},
"multiple_lines": {
"type": "array",
"items": {
"$ref": "#/definitions/web.NamedLineData-int64"
}
}
}
},
"web.NamedLineData-float64": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/web.DataPoint-float64"
}
},
"name": {
"type": "string"
}
}
},
"web.NamedLineData-int64": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/web.DataPoint-int64"
}
},
"name": {
"type": "string"
}
}
},
"web.TestResponse": {
"type": "object",
"properties": {
"field_1": {
"$ref": "#/definitions/web.Entity-int64"
},
"field_2": {
"$ref": "#/definitions/web.Entity-float64"
}
}
}
}
}
================================================
FILE: testdata/generics_multi_level_nesting/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/generics_nested_my_version/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @host localhost:4000
// @basePath /api
func main() {
http.HandleFunc("/posts/", api.GetPosts)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/generics_multi_level_nesting/web/handler.go
================================================
package web
import (
"encoding/json"
)
type TestResponse struct {
Field1 Entity[int64] `json:"field_1"`
Field2 Entity[float64] `json:"field_2"`
}
type Entity[T int64 | float64] struct {
LineWithFixType EmptyArray[DataPoint[float64]] `json:"line_with_fix_type"`
LineWithGenericType EmptyArray[DataPoint[T]] `json:"line_with_generic_type"`
MultipleLines MultipleLines[T] `json:"multiple_lines"`
}
type DataPoint[T int64 | float64] struct {
Value T `json:"value"`
Timestamp int64 `json:"timestamp"`
}
// EmptyArray will show [] instead of nil.
type EmptyArray[T any] []T
func (arr EmptyArray[T]) MarshalJSON() ([]byte, error) {
if arr != nil {
return json.Marshal([]T(arr))
}
return json.Marshal(make([]T, 0))
}
type MultipleLines[T int64 | float64] []NamedLineData[T]
type NamedLineData[T int64 | float64] struct {
Name string `json:"name"`
Data EmptyArray[DataPoint[T]] `json:"data"`
}
================================================
FILE: testdata/generics_names/api/api.go
================================================
package api
import (
"net/http"
"github.com/swaggo/swag/testdata/generics_names/types"
"github.com/swaggo/swag/testdata/generics_names/web"
)
// @Summary Add a new pet to the store
// @Description get string by ID
// @Accept json
// @Produce json
// @Param data body web.GenericBody[types.Post] true "Some ID"
// @Success 200 {object} web.GenericResponse[types.Post]
// @Success 222 {object} web.GenericResponseMulti[types.Post, types.Post]
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /posts/ [post]
func GetPost(w http.ResponseWriter, r *http.Request) {
//write your code
_ = web.GenericResponse[types.Post]{}
}
// @Summary Add new pets to the store
// @Description get string by ID
// @Accept json
// @Produce json
// @Param data body web.GenericBodyMulti[types.Post, types.Post] true "Some ID"
// @Success 200 {object} web.GenericResponse[types.Post]
// @Success 222 {object} web.GenericResponseMulti[types.Post, types.Post]
// @Router /posts-multi/ [post]
func GetPostMulti(w http.ResponseWriter, r *http.Request) {
//write your code
_ = web.GenericResponse[types.Post]{}
}
// @Summary Add new pets to the store
// @Description get string by ID
// @Accept json
// @Produce json
// @Param data body web.GenericBodyMulti[[]types.Post, [][]types.Post] true "Some ID"
// @Success 200 {object} web.GenericResponse[[]types.Post]
// @Success 222 {object} web.GenericResponseMulti[[]types.Post, [][]types.Post]
// @Router /posts-multis/ [post]
func GetPostArray(w http.ResponseWriter, r *http.Request) {
//write your code
_ = web.GenericResponse[types.Post]{}
}
================================================
FILE: testdata/generics_names/api/api_alias_pkg.go
================================================
package api
import (
"net/http"
mytypes "github.com/swaggo/swag/testdata/generics_names/types"
myweb "github.com/swaggo/swag/testdata/generics_names/web"
)
// @Summary Add a new pet to the store
// @Description get string by ID
// @Accept json
// @Produce json
// @Success 200 {object} myweb.AliasPkgGenericResponse[mytypes.Post]
// @Router /posts/aliaspkg [post]
func GetPostFromAliasPkg(w http.ResponseWriter, r *http.Request) {
//write your code
_ = myweb.AliasPkgGenericResponse[mytypes.Post]{}
}
================================================
FILE: testdata/generics_names/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"host": "localhost:4000",
"basePath": "/api",
"paths": {
"/posts-multi/": {
"post": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add new pets to the store",
"parameters": [
{
"description": "Some ID",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/MultiBody-Post-Post"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Response-Post"
}
},
"222": {
"description": "",
"schema": {
"$ref": "#/definitions/MultiResponse-Post-Post"
}
}
}
}
},
"/posts-multis/": {
"post": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add new pets to the store",
"parameters": [
{
"description": "Some ID",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/MultiBody-array_Post-array_array_Post"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Response-array_Post"
}
},
"222": {
"description": "",
"schema": {
"$ref": "#/definitions/MultiResponse-array_Post-array_array_Post"
}
}
}
}
},
"/posts/": {
"post": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add a new pet to the store",
"parameters": [
{
"description": "Some ID",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/Body-Post"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Response-Post"
}
},
"222": {
"description": "",
"schema": {
"$ref": "#/definitions/MultiResponse-Post-Post"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
},
"/posts/aliaspkg": {
"post": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add a new pet to the store",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/web.AliasPkgGenericResponse-Post"
}
}
}
}
}
},
"definitions": {
"Body-Post": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/Post"
}
}
},
"MultiBody-Post-Post": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/Post"
},
"meta": {
"$ref": "#/definitions/Post"
}
}
},
"MultiBody-array_Post-array_array_Post": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/Post"
}
},
"meta": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/Post"
}
}
}
}
},
"MultiResponse-Post-Post": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/Post"
},
"meta": {
"$ref": "#/definitions/Post"
},
"status": {
"type": "string"
}
}
},
"MultiResponse-array_Post-array_array_Post": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/Post"
}
},
"meta": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/Post"
}
}
},
"status": {
"type": "string"
}
}
},
"Post": {
"type": "object",
"properties": {
"@uri": {
"type": "string"
},
"data": {
"description": "Post data",
"type": "object",
"properties": {
"name": {
"description": "Post tag",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"id": {
"type": "integer",
"format": "int64",
"example": 1
},
"name": {
"description": "Post name",
"type": "string",
"example": "poti"
}
}
},
"Response-Post": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/Post"
},
"status": {
"type": "string"
}
}
},
"Response-array_Post": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/Post"
}
},
"status": {
"type": "string"
}
}
},
"web.APIError": {
"description": "API error with information about it",
"type": "object",
"properties": {
"createdAt": {
"description": "Error time",
"type": "string"
},
"error": {
"description": "Error an Api error",
"type": "string"
},
"errorCtx": {
"description": "Error `context` tick comment",
"type": "string"
},
"errorNo": {
"description": "Error `number` tick comment",
"type": "integer",
"format": "int64"
}
}
},
"web.AliasPkgGenericResponse-Post": {
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/Post"
},
"status": {
"type": "string"
}
}
}
}
}
================================================
FILE: testdata/generics_names/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/generics_names/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @host localhost:4000
// @basePath /api
func main() {
http.HandleFunc("/posts/", api.GetPost)
http.HandleFunc("/posts-multi/", api.GetPostMulti)
http.HandleFunc("/posts-multis/", api.GetPostArray)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/generics_names/types/post.go
================================================
package types
type APIBase struct {
APIUrl string `json:"@uri,omitempty"`
ID int `json:"id" example:"1" format:"int64"`
}
type Post struct {
APIBase
// Post name
Name string `json:"name" example:"poti"`
// Post data
Data struct {
// Post tag
Tag []string `json:"name"`
} `json:"data"`
} // @name Post
================================================
FILE: testdata/generics_names/web/handler.go
================================================
package web
import (
"time"
)
type GenericBody[T any] struct {
Data T
} // @name Body
type GenericBodyMulti[T any, X any] struct {
Data T
Meta X
} // @name MultiBody
type GenericResponse[T any] struct {
Data T
Status string
} // @name Response
type GenericResponseMulti[T any, X any] struct {
Data T
Meta X
Status string
} // @name MultiResponse
// APIError
// @Description API error
// @Description with information about it
// Other some summary
type APIError struct {
// Error an Api error
Error string // Error this is Line comment
// Error `number` tick comment
ErrorNo int64
ErrorCtx string // Error `context` tick comment
CreatedAt time.Time // Error time
}
type AliasPkgGenericResponse[T any] struct {
Data T
Status string
}
================================================
FILE: testdata/generics_nested/api/api.go
================================================
package api
import (
"net/http"
"github.com/swaggo/swag/testdata/generics_nested/types"
"github.com/swaggo/swag/testdata/generics_nested/web"
)
// @Summary List Posts
// @Description Get All of the Posts
// @Accept json
// @Produce json
// @Param data body web.GenericNestedBody[web.GenericInnerType[types.Post]] true "Some ID"
// @Success 200 {object} web.GenericNestedResponse[types.Post]
// @Success 201 {object} web.GenericNestedResponse[web.GenericInnerType[types.Post]]
// @Success 202 {object} web.GenericNestedResponseMulti[types.Post, web.GenericInnerMultiType[types.Post, types.Post]]
// @Success 203 {object} web.GenericNestedResponseMulti[types.Post, web.GenericInnerMultiType[types.Post, web.GenericInnerType[types.Post]]]
// @Success 222 {object} web.GenericNestedResponseMulti[web.GenericInnerType[types.Post], types.Post]
// @Router /posts [get]
func GetPosts(w http.ResponseWriter, r *http.Request) {
_ = web.GenericNestedResponse[types.Post]{}
}
// @Summary List Posts
// @Description Get All of the Posts
// @Accept json
// @Produce json
// @Param data body web.GenericNestedBody[web.GenericInnerType[[]types.Post]] true "Some ID"
// @Success 200 {object} web.GenericNestedResponse[[]types.Post]
// @Success 201 {object} web.GenericNestedResponse[[]web.GenericInnerType[types.Post]]
// @Success 202 {object} web.GenericNestedResponse[[]web.GenericInnerType[[]types.Post]]
// @Success 203 {object} web.GenericNestedResponseMulti[[]types.Post, web.GenericInnerMultiType[[]types.Post, types.Post]]
// @Success 204 {object} web.GenericNestedResponseMulti[[]types.Post, []web.GenericInnerMultiType[[]types.Post, types.Post]]
// @Success 205 {object} web.GenericNestedResponseMulti[types.Post, web.GenericInnerMultiType[types.Post, []web.GenericInnerType[[][]types.Post]]]
// @Success 222 {object} web.GenericNestedResponseMulti[web.GenericInnerType[[]types.Post], []types.Post]
// @Router /posts-multis/ [get]
func GetPostArray(w http.ResponseWriter, r *http.Request) {
_ = web.GenericNestedResponse[types.Post]{}
}
// @Summary List Posts
// @Description Get All of the Posts
// @Accept json
// @Produce json
// @Success 200 {object} web.GenericNodeThree[string]
// @Router /posts-self-nested-struct/ [get]
func GetPostSelfNestStruct(w http.ResponseWriter, r *http.Request) {
}
================================================
FILE: testdata/generics_nested/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"host": "localhost:4000",
"basePath": "/api",
"paths": {
"/posts": {
"get": {
"description": "Get All of the Posts",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "List Posts",
"parameters": [
{
"description": "Some ID",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType-types_Post"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/web.GenericNestedResponse-types_Post"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/web.GenericNestedResponse-web_GenericInnerType-types_Post"
}
},
"202": {
"description": "Accepted",
"schema": {
"$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-types_Post"
}
},
"203": {
"description": "Non-Authoritative Information",
"schema": {
"$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-web_GenericInnerType-types_Post"
}
},
"222": {
"description": "",
"schema": {
"$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType-types_Post-types_Post"
}
}
}
}
},
"/posts-multis/": {
"get": {
"description": "Get All of the Posts",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "List Posts",
"parameters": [
{
"description": "Some ID",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType-array_types_Post"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/web.GenericNestedResponse-array_types_Post"
}
},
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType-types_Post"
}
},
"202": {
"description": "Accepted",
"schema": {
"$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType-array_types_Post"
}
},
"203": {
"description": "Non-Authoritative Information",
"schema": {
"$ref": "#/definitions/web.GenericNestedResponseMulti-array_types_Post-web_GenericInnerMultiType-array_types_Post-types_Post"
}
},
"204": {
"description": "No Content",
"schema": {
"$ref": "#/definitions/web.GenericNestedResponseMulti-array_types_Post-array_web_GenericInnerMultiType-array_types_Post-types_Post"
}
},
"205": {
"description": "Reset Content",
"schema": {
"$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array_array_types_Post"
}
},
"222": {
"description": "",
"schema": {
"$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType-array_types_Post-array_types_Post"
}
}
}
}
},
"/posts-self-nested-struct/": {
"get": {
"description": "Get All of the Posts",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "List Posts",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/web.GenericNodeThree-string"
}
}
}
}
}
},
"definitions": {
"types.Post": {
"type": "object",
"properties": {
"@uri": {
"type": "string"
},
"data": {
"description": "Post data",
"type": "object",
"properties": {
"name": {
"description": "Post tag",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"id": {
"type": "integer",
"format": "int64",
"example": 1
},
"name": {
"description": "Post name",
"type": "string",
"example": "poti"
}
}
},
"web.GenericInnerMultiType-array_types_Post-types_Post": {
"type": "object",
"properties": {
"itemOne": {
"description": "ItemsOne is the first thing",
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"itemsTwo": {
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
}
},
"web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array_array_types_Post": {
"type": "object",
"properties": {
"itemOne": {
"description": "ItemsOne is the first thing",
"allOf": [
{
"$ref": "#/definitions/types.Post"
}
]
},
"itemsTwo": {
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/web.GenericInnerType-array_array_types_Post"
}
}
}
}
},
"web.GenericInnerMultiType-types_Post-types_Post": {
"type": "object",
"properties": {
"itemOne": {
"description": "ItemsOne is the first thing",
"allOf": [
{
"$ref": "#/definitions/types.Post"
}
]
},
"itemsTwo": {
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
}
},
"web.GenericInnerMultiType-types_Post-web_GenericInnerType-types_Post": {
"type": "object",
"properties": {
"itemOne": {
"description": "ItemsOne is the first thing",
"allOf": [
{
"$ref": "#/definitions/types.Post"
}
]
},
"itemsTwo": {
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"$ref": "#/definitions/web.GenericInnerType-types_Post"
}
}
}
},
"web.GenericInnerType-array_array_types_Post": {
"type": "object",
"properties": {
"items": {
"description": "Items from the list response",
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
}
}
},
"web.GenericInnerType-array_types_Post": {
"type": "object",
"properties": {
"items": {
"description": "Items from the list response",
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
}
},
"web.GenericInnerType-types_Post": {
"type": "object",
"properties": {
"items": {
"description": "Items from the list response",
"allOf": [
{
"$ref": "#/definitions/types.Post"
}
]
}
}
},
"web.GenericNestedBody-web_GenericInnerType-array_types_Post": {
"type": "object",
"properties": {
"items": {
"description": "Items from the list response",
"allOf": [
{
"$ref": "#/definitions/web.GenericInnerType-array_types_Post"
}
]
},
"status": {
"description": "Status of some other stuff",
"type": "string"
}
}
},
"web.GenericNestedBody-web_GenericInnerType-types_Post": {
"type": "object",
"properties": {
"items": {
"description": "Items from the list response",
"allOf": [
{
"$ref": "#/definitions/web.GenericInnerType-types_Post"
}
]
},
"status": {
"description": "Status of some other stuff",
"type": "string"
}
}
},
"web.GenericNestedResponse-array_types_Post": {
"type": "object",
"properties": {
"items": {
"description": "Items from the list response",
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
},
"status": {
"description": "Status of some other stuff",
"type": "string"
}
}
},
"web.GenericNestedResponse-array_web_GenericInnerType-array_types_Post": {
"type": "object",
"properties": {
"items": {
"description": "Items from the list response",
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/web.GenericInnerType-array_types_Post"
}
}
},
"status": {
"description": "Status of some other stuff",
"type": "string"
}
}
},
"web.GenericNestedResponse-array_web_GenericInnerType-types_Post": {
"type": "object",
"properties": {
"items": {
"description": "Items from the list response",
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/web.GenericInnerType-types_Post"
}
}
},
"status": {
"description": "Status of some other stuff",
"type": "string"
}
}
},
"web.GenericNestedResponse-types_Post": {
"type": "object",
"properties": {
"items": {
"description": "Items from the list response",
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"status": {
"description": "Status of some other stuff",
"type": "string"
}
}
},
"web.GenericNestedResponse-web_GenericInnerType-types_Post": {
"type": "object",
"properties": {
"items": {
"description": "Items from the list response",
"type": "array",
"items": {
"$ref": "#/definitions/web.GenericInnerType-types_Post"
}
},
"status": {
"description": "Status of some other stuff",
"type": "string"
}
}
},
"web.GenericNestedResponseMulti-array_types_Post-array_web_GenericInnerMultiType-array_types_Post-types_Post": {
"type": "object",
"properties": {
"itemOne": {
"description": "ItemsOne is the first thing",
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"itemsTwo": {
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/web.GenericInnerMultiType-array_types_Post-types_Post"
}
}
},
"status": {
"description": "Status of the things",
"type": "string"
}
}
},
"web.GenericNestedResponseMulti-array_types_Post-web_GenericInnerMultiType-array_types_Post-types_Post": {
"type": "object",
"properties": {
"itemOne": {
"description": "ItemsOne is the first thing",
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"itemsTwo": {
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"$ref": "#/definitions/web.GenericInnerMultiType-array_types_Post-types_Post"
}
},
"status": {
"description": "Status of the things",
"type": "string"
}
}
},
"web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array_array_types_Post": {
"type": "object",
"properties": {
"itemOne": {
"description": "ItemsOne is the first thing",
"allOf": [
{
"$ref": "#/definitions/types.Post"
}
]
},
"itemsTwo": {
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"$ref": "#/definitions/web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array_array_types_Post"
}
},
"status": {
"description": "Status of the things",
"type": "string"
}
}
},
"web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-types_Post": {
"type": "object",
"properties": {
"itemOne": {
"description": "ItemsOne is the first thing",
"allOf": [
{
"$ref": "#/definitions/types.Post"
}
]
},
"itemsTwo": {
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"$ref": "#/definitions/web.GenericInnerMultiType-types_Post-types_Post"
}
},
"status": {
"description": "Status of the things",
"type": "string"
}
}
},
"web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-web_GenericInnerType-types_Post": {
"type": "object",
"properties": {
"itemOne": {
"description": "ItemsOne is the first thing",
"allOf": [
{
"$ref": "#/definitions/types.Post"
}
]
},
"itemsTwo": {
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"$ref": "#/definitions/web.GenericInnerMultiType-types_Post-web_GenericInnerType-types_Post"
}
},
"status": {
"description": "Status of the things",
"type": "string"
}
}
},
"web.GenericNestedResponseMulti-web_GenericInnerType-array_types_Post-array_types_Post": {
"type": "object",
"properties": {
"itemOne": {
"description": "ItemsOne is the first thing",
"allOf": [
{
"$ref": "#/definitions/web.GenericInnerType-array_types_Post"
}
]
},
"itemsTwo": {
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
},
"status": {
"description": "Status of the things",
"type": "string"
}
}
},
"web.GenericNestedResponseMulti-web_GenericInnerType-types_Post-types_Post": {
"type": "object",
"properties": {
"itemOne": {
"description": "ItemsOne is the first thing",
"allOf": [
{
"$ref": "#/definitions/web.GenericInnerType-types_Post"
}
]
},
"itemsTwo": {
"description": "ItemsTwo is the second thing",
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"status": {
"description": "Status of the things",
"type": "string"
}
}
},
"web.GenericNodeThree-string": {
"type": "object",
"properties": {
"current": {
"type": "array",
"items": {
"type": "string"
}
},
"next": {
"type": "array",
"items": {
"$ref": "#/definitions/web.GenericNodeThree-string"
}
}
}
}
}
}
================================================
FILE: testdata/generics_nested/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/generics_nested/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @host localhost:4000
// @basePath /api
func main() {
http.HandleFunc("/posts/", api.GetPosts)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/generics_nested/types/post.go
================================================
package types
type APIBase struct {
APIUrl string `json:"@uri,omitempty"`
ID int `json:"id" example:"1" format:"int64"`
}
type Post struct {
APIBase
// Post name
Name string `json:"name" example:"poti"`
// Post data
Data struct {
// Post tag
Tag []string `json:"name"`
} `json:"data"`
}
================================================
FILE: testdata/generics_nested/web/handler.go
================================================
package web
import (
"time"
)
// GenericNestedBody[T]
// @Description Some Generic Body
type GenericNestedBody[T any] struct {
// Items from the list response
Items T
// Status of some other stuff
Status string
}
// GenericInnerType[T]
// @Description Some Generic Body
type GenericInnerType[T any] struct {
// Items from the list response
Items T
}
// GenericInnerMultiType[T, X]
// @Description Some Generic Body
type GenericInnerMultiType[T any, X any] struct {
// ItemsOne is the first thing
ItemOne T
// ItemsTwo is the second thing
ItemsTwo []X
}
// GenericNestedResponse[T]
// @Description Some Generic List Response
type GenericNestedResponse[T any] struct {
// Items from the list response
Items []T
// Status of some other stuff
Status string
}
// GenericNestedResponseMulti[T, X]
// @Description this contains a few things
type GenericNestedResponseMulti[T any, X any] struct {
// ItemsOne is the first thing
ItemOne T
// ItemsTwo is the second thing
ItemsTwo []X
// Status of the things
Status string
}
// APIError
// @Description API error
// @Description with information about it
// Other some summary
type APIError struct {
// Error an Api error
Error string // Error this is Line comment
// Error `number` tick comment
ErrorNo int64
ErrorCtx string // Error `context` tick comment
CreatedAt time.Time // Error time
}
type GenericNodeThree[T any] struct {
CurrentData []T `json:"current"`
Next []*GenericNodeThree[T] `json:"next"`
}
================================================
FILE: testdata/generics_package_alias/external/external1/external.go
================================================
package external1
type Customer struct {
Name string
Age int
}
================================================
FILE: testdata/generics_package_alias/external/external2/external.go
================================================
package external2
type Customer struct {
Name string
Age int
}
================================================
FILE: testdata/generics_package_alias/external/external3/external.go
================================================
package external3
type Customer struct {
Name string
Age int
}
================================================
FILE: testdata/generics_package_alias/external/external4/external.go
================================================
package external4
type Customer struct {
Name string
Age int
}
================================================
FILE: testdata/generics_package_alias/internal/api/api1.go
================================================
package api
import (
myv1 "github.com/swaggo/swag/testdata/generics_package_alias/internal/path1/v1"
)
// @Summary Create movie
// @Description Create a new movie production
// @Accept json
// @Produce json
// @Success 200 {object} myv1.ListResult[myv1.ProductDto] ""
// @Router /api01 [post]
func CreateMovie01() {
_ = myv1.ListResult[myv1.ProductDto]{}
}
// @Summary Create movie
// @Description Create a new movie production
// @Accept json
// @Produce json
// @Success 200 {object} myv1.RenamedListResult[myv1.RenamedProductDto] ""
// @Router /api02 [post]
func CreateMovie02() {
_ = myv1.ListResult[myv1.ProductDto]{}
}
================================================
FILE: testdata/generics_package_alias/internal/api/api2.go
================================================
package api
import (
myv1 "github.com/swaggo/swag/testdata/generics_package_alias/internal/path1/v1"
myv2 "github.com/swaggo/swag/testdata/generics_package_alias/internal/path2/v1"
)
// @Summary Create movie
// @Description Create a new movie production
// @Accept json
// @Produce json
// @Success 200 {object} myv2.ListResult[myv2.ProductDto] ""
// @Router /api03 [post]
func CreateMovie03() {
_ = myv2.ListResult[myv2.ProductDto]{}
}
// @Summary Create movie
// @Description Create a new movie production
// @Accept json
// @Produce json
// @Success 200 {object} myv2.RenamedListResult[myv2.RenamedProductDto] ""
// @Router /api04 [post]
func CreateMovie04() {
_ = myv2.ListResult[myv2.ProductDto]{}
}
// @Summary Create movie
// @Description Create a new movie production
// @Accept json
// @Produce json
// @Success 200 {object} myv1.ListResult[myv2.ProductDto] ""
// @Router /api05 [post]
func CreateMovie05() {
_ = myv1.ListResult[myv2.ProductDto]{}
}
// @Summary Create movie
// @Description Create a new movie production
// @Accept json
// @Produce json
// @Success 200 {object} myv1.RenamedListResult[myv2.RenamedProductDto] ""
// @Router /api06 [post]
func CreateMovie06() {
_ = myv1.ListResult[myv2.ProductDto]{}
}
================================================
FILE: testdata/generics_package_alias/internal/api/api3.go
================================================
package api
import (
_ "github.com/swaggo/swag/testdata/generics_package_alias/internal/path1/v1"
. "github.com/swaggo/swag/testdata/generics_package_alias/internal/path2/v1"
)
// @Summary Create movie
// @Description models imported from an unnamed package
// @Accept json
// @Produce json
// @Success 200 {object} v1.ListResult[v1.ProductDto] ""
// @Router /api07 [post]
func CreateMovie07() {
var _ ProductDto
}
// @Summary Create movie
// @Description models imported from an unnamed package
// @Accept json
// @Produce json
// @Success 200 {object} ListResult[ProductDto] ""
// @Router /api08 [post]
func CreateMovie08() {
var _ ProductDto
}
// @Summary Create movie
// @Description models imported from an unnamed package
// @Accept json
// @Produce json
// @Success 200 {object} ListResult[v1.ProductDto] ""
// @Router /api09 [post]
func CreateMovie09() {
var _ ProductDto
}
// @Summary Create movie
// @Description models imported from an unnamed package
// @Accept json
// @Produce json
// @Success 200 {object} v1.ListResult[ProductDto] ""
// @Router /api10 [post]
func CreateMovie10() {
var _ ProductDto
}
================================================
FILE: testdata/generics_package_alias/internal/api/api4.go
================================================
package api
import (
"github.com/swaggo/swag/testdata/generics_package_alias/external/external1"
_ "github.com/swaggo/swag/testdata/generics_package_alias/internal/path1/v1"
)
// @Summary Create movie
// @Description models imported from an external package
// @Accept json
// @Produce json
// @Success 200 {object} v1.ListResult[external1.Customer] ""
// @Router /api11 [post]
func CreateMovie11() {
var _ external1.Customer
}
================================================
FILE: testdata/generics_package_alias/internal/api/api5.go
================================================
package api
import (
myexternal "github.com/swaggo/swag/testdata/generics_package_alias/external/external2"
_ "github.com/swaggo/swag/testdata/generics_package_alias/internal/path1/v1"
)
// @Summary Create movie
// @Description models imported from a named external package
// @Accept json
// @Produce json
// @Success 200 {object} v1.ListResult[myexternal.Customer] ""
// @Router /api12 [post]
func CreateMovie12() {
var _ myexternal.Customer
}
================================================
FILE: testdata/generics_package_alias/internal/api/api6.go
================================================
package api
import (
. "github.com/swaggo/swag/testdata/generics_package_alias/external/external3"
_ "github.com/swaggo/swag/testdata/generics_package_alias/internal/path1/v1"
)
// @Summary Create movie
// @Description models from an external package imported by mode dot
// @Accept json
// @Produce json
// @Success 200 {object} v1.ListResult[Customer] ""
// @Router /api13 [post]
func CreateMovie13() {
var _ Customer
}
================================================
FILE: testdata/generics_package_alias/internal/api/api7.go
================================================
package api
import (
_ "github.com/swaggo/swag/testdata/generics_package_alias/external/external4"
_ "github.com/swaggo/swag/testdata/generics_package_alias/internal/path1/v1"
)
// @Summary Create movie
// @Description models imported from an unnamed external package
// @Accept json
// @Produce json
// @Success 200 {object} v1.ListResult[external4.Customer] ""
// @Router /api14 [post]
func CreateMovie14() {
}
================================================
FILE: testdata/generics_package_alias/internal/api/api8.go
================================================
package api
import (
_ "github.com/swaggo/swag/testdata/generics_package_alias/internal/path1/v1"
_ "github.com/swaggo/swag/testdata/generics_package_alias/internal/path2/v1"
)
// @Summary Create movie
// @Description model from a package whose name conflicts with other packages
// @Accept json
// @Produce json
// @Success 200 {object} v1.UniqueProduct ""
// @Router /api15 [post]
func CreateMovie15() {
}
================================================
FILE: testdata/generics_package_alias/internal/expected.json
================================================
{
"swagger": "2.0",
"info": {
"contact": {}
},
"paths": {
"/api01": {
"post": {
"description": "Create a new movie production",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1_ProductDto"
}
}
}
}
},
"/api02": {
"post": {
"description": "Create a new movie production",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/ListResultV1-ProductDtoV1"
}
}
}
}
},
"/api03": {
"post": {
"description": "Create a new movie production",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1_ProductDto"
}
}
}
}
},
"/api04": {
"post": {
"description": "Create a new movie production",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/ListResultV2-ProductDtoV2"
}
}
}
}
},
"/api05": {
"post": {
"description": "Create a new movie production",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1_ProductDto"
}
}
}
}
},
"/api06": {
"post": {
"description": "Create a new movie production",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/ListResultV1-ProductDtoV2"
}
}
}
}
},
"/api07": {
"post": {
"description": "models imported from an unnamed package",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1_ProductDto"
}
}
}
}
},
"/api08": {
"post": {
"description": "models imported from an unnamed package",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1_ProductDto"
}
}
}
}
},
"/api09": {
"post": {
"description": "models imported from an unnamed package",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1_ProductDto"
}
}
}
}
},
"/api10": {
"post": {
"description": "models imported from an unnamed package",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1_ProductDto"
}
}
}
}
},
"/api11": {
"post": {
"description": "models imported from an external package",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-external1_Customer"
}
}
}
}
},
"/api12": {
"post": {
"description": "models imported from a named external package",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-external2_Customer"
}
}
}
}
},
"/api13": {
"post": {
"description": "models from an external package imported by mode dot",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-external3_Customer"
}
}
}
}
},
"/api14": {
"post": {
"description": "models imported from an unnamed external package",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-external4_Customer"
}
}
}
}
},
"/api15": {
"post": {
"description": "model from a package whose name conflicts with other packages",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/v1.UniqueProduct"
}
}
}
}
}
},
"definitions": {
"ListResultV1-ProductDtoV1": {
"type": "object",
"properties": {
"items11": {
"type": "array",
"items": {
"$ref": "#/definitions/ProductDtoV1"
}
}
}
},
"ListResultV1-ProductDtoV2": {
"type": "object",
"properties": {
"items11": {
"type": "array",
"items": {
"$ref": "#/definitions/ProductDtoV2"
}
}
}
},
"ListResultV2-ProductDtoV2": {
"type": "object",
"properties": {
"items22": {
"type": "array",
"items": {
"$ref": "#/definitions/ProductDtoV2"
}
}
}
},
"ProductDtoV1": {
"type": "object",
"properties": {
"name11": {
"type": "string"
}
}
},
"ProductDtoV2": {
"type": "object",
"properties": {
"name22": {
"type": "string"
}
}
},
"external1.Customer": {
"type": "object",
"properties": {
"age": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"external2.Customer": {
"type": "object",
"properties": {
"age": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"external3.Customer": {
"type": "object",
"properties": {
"age": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"external4.Customer": {
"type": "object",
"properties": {
"age": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-external1_Customer": {
"type": "object",
"properties": {
"items1": {
"type": "array",
"items": {
"$ref": "#/definitions/external1.Customer"
}
}
}
},
"github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-external2_Customer": {
"type": "object",
"properties": {
"items1": {
"type": "array",
"items": {
"$ref": "#/definitions/external2.Customer"
}
}
}
},
"github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-external3_Customer": {
"type": "object",
"properties": {
"items1": {
"type": "array",
"items": {
"$ref": "#/definitions/external3.Customer"
}
}
}
},
"github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-external4_Customer": {
"type": "object",
"properties": {
"items1": {
"type": "array",
"items": {
"$ref": "#/definitions/external4.Customer"
}
}
}
},
"github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1_ProductDto": {
"type": "object",
"properties": {
"items1": {
"type": "array",
"items": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ProductDto"
}
}
}
},
"github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1_ProductDto": {
"type": "object",
"properties": {
"items1": {
"type": "array",
"items": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1.ProductDto"
}
}
}
},
"github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ProductDto": {
"type": "object",
"properties": {
"name1": {
"type": "string"
}
}
},
"github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1_ProductDto": {
"type": "object",
"properties": {
"items2": {
"type": "array",
"items": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path1_v1.ProductDto"
}
}
}
},
"github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1_ProductDto": {
"type": "object",
"properties": {
"items2": {
"type": "array",
"items": {
"$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1.ProductDto"
}
}
}
},
"github_com_swaggo_swag_testdata_generics_package_alias_internal_path2_v1.ProductDto": {
"type": "object",
"properties": {
"name2": {
"type": "string"
}
}
},
"v1.UniqueProduct": {
"type": "object",
"properties": {
"unique_product_name": {
"type": "string"
}
}
}
}
}
================================================
FILE: testdata/generics_package_alias/internal/main.go
================================================
package main
func main() {
}
================================================
FILE: testdata/generics_package_alias/internal/path1/v1/product.go
================================================
package v1
type ProductDto struct {
Name1 string `json:"name1"`
}
type ListResult[T any] struct {
Items1 []T `json:"items1,omitempty"`
}
type RenamedProductDto struct {
Name11 string `json:"name11"`
} // @name ProductDtoV1
type RenamedListResult[T any] struct {
Items11 []T `json:"items11,omitempty"`
} // @name ListResultV1
================================================
FILE: testdata/generics_package_alias/internal/path2/v1/product.go
================================================
package v1
type ProductDto struct {
Name2 string `json:"name2"`
}
type ListResult[T any] struct {
Items2 []T `json:"items2,omitempty"`
}
type RenamedProductDto struct {
Name22 string `json:"name22"`
} // @name ProductDtoV2
type RenamedListResult[T any] struct {
Items22 []T `json:"items22,omitempty"`
} // @name ListResultV2
type UniqueProduct struct {
UniqueProductName string `json:"unique_product_name"`
}
================================================
FILE: testdata/generics_package_alias/internal/path3/v1/product.go
================================================
package v1
type ProductDto struct {
Name3 string `json:"name3"`
}
type ListResult[T any] struct {
Items3 []T `json:"items3,omitempty"`
}
type RenamedProductDto struct {
Name33 string `json:"name33"`
} // @name ProductDtoV3
type RenamedListResult[T any] struct {
Items33 []T `json:"items33,omitempty"`
} // @name ListResultV3
================================================
FILE: testdata/generics_property/api/api.go
================================================
package api
import (
"github.com/swaggo/swag/testdata/generics_property/types"
"github.com/swaggo/swag/testdata/generics_property/web"
"net/http"
)
type NestedResponse struct {
web.GenericResponse[[]string, *uint8]
Post types.Field[[]types.Post]
}
type Audience[T any] []T
type CreateMovie struct {
Name string
MainActor types.Field[Person]
SupportingCast types.Field[[]Person]
Directors types.Field[*[]Person]
CameraPeople types.Field[[]*Person]
Producer types.Field[*Person]
Audience Audience[Person]
AudienceNames Audience[string]
Detail1 types.Field[types.Field[Person]]
Detail2 types.Field[types.Field[string]]
}
type Person struct {
Name string
}
// @Summary List Posts
// @Description Get All of the Posts
// @Accept json
// @Produce json
// @Param data query web.PostPager true "1"
// @Success 200 {object} web.PostResponse "ok"
// @Success 201 {object} web.PostResponses "ok"
// @Success 202 {object} web.StringResponse "ok"
// @Success 203 {object} NestedResponse "ok"
// @Router /posts [get]
func GetPosts(w http.ResponseWriter, r *http.Request) {
}
// @Summary Create movie
// @Description Create a new movie production
// @Accept json
// @Produce json
// @Param data body CreateMovie true "Movie Create-Payload"
// @Success 201 {object} CreateMovie "ok"
// @Router /movie [post]
func CreateMovieApi(w http.ResponseWriter, r *http.Request) {
}
================================================
FILE: testdata/generics_property/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"host": "localhost:4000",
"basePath": "/api",
"paths": {
"/movie": {
"post": {
"description": "Create a new movie production",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Create movie",
"parameters": [
{
"description": "Movie Create-Payload",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/api.CreateMovie"
}
}
],
"responses": {
"201": {
"description": "ok",
"schema": {
"$ref": "#/definitions/api.CreateMovie"
}
}
}
}
},
"/posts": {
"get": {
"description": "Get All of the Posts",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "List Posts",
"parameters": [
{
"type": "string",
"name": "next_id",
"in": "query"
},
{
"type": "integer",
"name": "page",
"in": "query"
},
{
"type": "string",
"name": "prev_id",
"in": "query"
},
{
"type": "integer",
"name": "rows",
"in": "query"
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"$ref": "#/definitions/web.PostResponse"
}
},
"201": {
"description": "ok",
"schema": {
"$ref": "#/definitions/web.PostResponses"
}
},
"202": {
"description": "ok",
"schema": {
"$ref": "#/definitions/web.StringResponse"
}
},
"203": {
"description": "ok",
"schema": {
"$ref": "#/definitions/api.NestedResponse"
}
}
}
}
}
},
"definitions": {
"api.CreateMovie": {
"type": "object",
"properties": {
"audience": {
"type": "array",
"items": {
"$ref": "#/definitions/api.Person"
}
},
"audienceNames": {
"type": "array",
"items": {
"type": "string"
}
},
"cameraPeople": {
"$ref": "#/definitions/types.Field-array_api_Person"
},
"detail1": {
"$ref": "#/definitions/types.Field-types_Field-api_Person"
},
"detail2": {
"$ref": "#/definitions/types.Field-types_Field-string"
},
"directors": {
"$ref": "#/definitions/types.Field-array_api_Person"
},
"mainActor": {
"$ref": "#/definitions/types.Field-api_Person"
},
"name": {
"type": "string"
},
"producer": {
"$ref": "#/definitions/types.Field-api_Person"
},
"supportingCast": {
"$ref": "#/definitions/types.Field-array_api_Person"
}
}
},
"api.NestedResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "string"
}
},
"items2": {
"type": "integer",
"format": "int32"
},
"post": {
"$ref": "#/definitions/types.Field-array_types_Post"
}
}
},
"api.Person": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
},
"types.Field-api_Person": {
"type": "object",
"properties": {
"value": {
"$ref": "#/definitions/api.Person"
},
"value2": {
"$ref": "#/definitions/api.Person"
},
"value3": {
"type": "array",
"items": {
"$ref": "#/definitions/api.Person"
}
},
"value4": {
"$ref": "#/definitions/types.SubField1-api_Person-string"
}
}
},
"types.Field-array_api_Person": {
"type": "object",
"properties": {
"value": {
"type": "array",
"items": {
"$ref": "#/definitions/api.Person"
}
},
"value2": {
"type": "array",
"items": {
"$ref": "#/definitions/api.Person"
}
},
"value3": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/api.Person"
}
}
},
"value4": {
"$ref": "#/definitions/types.SubField1-array_api_Person-string"
}
}
},
"types.Field-array_types_Post": {
"type": "object",
"properties": {
"value": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"value2": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"value3": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
}
},
"value4": {
"$ref": "#/definitions/types.SubField1-array_types_Post-string"
}
}
},
"types.Field-string": {
"type": "object",
"properties": {
"value": {
"type": "string"
},
"value2": {
"type": "string"
},
"value3": {
"type": "array",
"items": {
"type": "string"
}
},
"value4": {
"$ref": "#/definitions/types.SubField1-string-string"
}
}
},
"types.Field-types_Field-api_Person": {
"type": "object",
"properties": {
"value": {
"$ref": "#/definitions/types.Field-api_Person"
},
"value2": {
"$ref": "#/definitions/types.Field-api_Person"
},
"value3": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Field-api_Person"
}
},
"value4": {
"$ref": "#/definitions/types.SubField1-types_Field-api_Person-string"
}
}
},
"types.Field-types_Field-string": {
"type": "object",
"properties": {
"value": {
"$ref": "#/definitions/types.Field-string"
},
"value2": {
"$ref": "#/definitions/types.Field-string"
},
"value3": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Field-string"
}
},
"value4": {
"$ref": "#/definitions/types.SubField1-types_Field-string-string"
}
}
},
"types.Post": {
"type": "object",
"properties": {
"@uri": {
"$ref": "#/definitions/types.Field-string"
},
"data": {
"description": "Post data",
"type": "object",
"properties": {
"name": {
"description": "Post tag",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"id": {
"type": "integer",
"format": "int64",
"example": 1
},
"name": {
"description": "Post name",
"type": "string",
"example": "poti"
}
}
},
"types.SubField1-api_Person-string": {
"type": "object",
"properties": {
"subValue1": {
"$ref": "#/definitions/api.Person"
},
"subValue2": {
"type": "string"
}
}
},
"types.SubField1-array_api_Person-string": {
"type": "object",
"properties": {
"subValue1": {
"type": "array",
"items": {
"$ref": "#/definitions/api.Person"
}
},
"subValue2": {
"type": "string"
}
}
},
"types.SubField1-array_types_Post-string": {
"type": "object",
"properties": {
"subValue1": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"subValue2": {
"type": "string"
}
}
},
"types.SubField1-string-string": {
"type": "object",
"properties": {
"subValue1": {
"type": "string"
},
"subValue2": {
"type": "string"
}
}
},
"types.SubField1-types_Field-api_Person-string": {
"type": "object",
"properties": {
"subValue1": {
"$ref": "#/definitions/types.Field-api_Person"
},
"subValue2": {
"type": "string"
}
}
},
"types.SubField1-types_Field-string-string": {
"type": "object",
"properties": {
"subValue1": {
"$ref": "#/definitions/types.Field-string"
},
"subValue2": {
"type": "string"
}
}
},
"web.PostResponse": {
"type": "object",
"properties": {
"items": {
"$ref": "#/definitions/types.Post"
},
"items2": {
"$ref": "#/definitions/types.Post"
}
}
},
"web.PostResponses": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/types.Post"
}
},
"items2": {
"$ref": "#/definitions/types.Post"
}
}
},
"web.StringResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "string"
}
},
"items2": {
"type": "integer",
"format": "int32"
}
}
}
}
}
================================================
FILE: testdata/generics_property/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/generics_property/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @host localhost:4000
// @basePath /api
func main() {
http.HandleFunc("/posts/", api.GetPosts)
http.HandleFunc("/movie/", api.CreateMovieApi)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/generics_property/types/post.go
================================================
package types
type SubField1[T any, T2 any] struct {
SubValue1 T
SubValue2 T2
}
type Field[T any] struct {
Value T
Value2 *T
Value3 []T
Value4 SubField1[T, string]
}
type APIBase struct {
APIUrl Field[string] `json:"@uri,omitempty"`
ID int `json:"id" example:"1" format:"int64"`
}
type Post struct {
APIBase
// Post name
Name string `json:"name" example:"poti"`
// Post data
Data struct {
// Post tag
Tag []string `json:"name"`
} `json:"data"`
}
================================================
FILE: testdata/generics_property/web/handler.go
================================================
package web
import "github.com/swaggo/swag/testdata/generics_property/types"
type PostSelector func(selector func())
type Filter interface {
~func(selector func())
}
type query[T any, F Filter] interface {
Where(ps ...F) T
}
type Pager[T query[T, F], F Filter] struct {
Rows uint8 `json:"rows" form:"rows"`
Page int `json:"page" form:"page"`
NextID *string `json:"next_id" form:"next_id"`
PrevID *string `json:"prev_id" form:"prev_id"`
query T
}
type String string
func (String) Where(ps ...PostSelector) String {
return ""
}
type PostPager struct {
Pager[String, PostSelector]
Search types.Field[string] `json:"search" form:"search"`
}
type PostResponse struct {
GenericResponse[types.Post, types.Post]
}
type PostResponses struct {
GenericResponse[[]types.Post, types.Post]
}
type StringResponse struct {
GenericResponse[[]string, *uint8]
}
type GenericResponse[T any, T2 any] struct {
Items T
Items2 T2
}
================================================
FILE: testdata/global_override/api/api.go
================================================
package api
import (
"log"
"net/http"
"github.com/swaggo/swag/testdata/alias_type/types"
"github.com/swaggo/swag/testdata/global_override/data"
)
// @Summary Get application
// @Description test get application
// @ID get-application
// @Accept json
// @Produce json
// @Success 200 {object} data.ApplicationResponse "ok"
// @Router /testapi/application [get]
func GetApplication(w http.ResponseWriter, r *http.Request) {
var foo = data.ApplicationResponse{
Application: types.Application{
Name: "name",
},
ApplicationArray: []types.Application{
{Name: "name"},
},
}
log.Println(foo)
//write your code
}
================================================
FILE: testdata/global_override/data/applicationresponse.go
================================================
package data
import (
typesapplication "github.com/swaggo/swag/testdata/global_override/types"
)
type ApplicationResponse struct {
typesapplication.TypeToEmbed
Application typesapplication.Application `json:"application"`
Application2 typesapplication.Application2 `json:"application2"`
ApplicationArray []typesapplication.Application `json:"application_array"`
ApplicationTime typesapplication.DateOnly `json:"application_time"`
ShouldSkip typesapplication.ShouldSkip `json:"should_skip"`
}
================================================
FILE: testdata/global_override/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"paths": {
"/testapi/application": {
"get": {
"description": "test get application",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Get application",
"operationId": "get-application",
"responses": {
"200": {
"description": "ok",
"schema": {
"$ref": "#/definitions/data.ApplicationResponse"
}
}
}
}
}
},
"definitions": {
"data.ApplicationResponse": {
"type": "object",
"properties": {
"application": {
"type": "string"
},
"application2": {
"$ref": "#/definitions/othertypes.Application"
},
"application_array": {
"type": "array",
"items": {
"type": "string"
}
},
"application_time": {
"type": "string"
},
"embedded": {
"type": "string"
}
}
},
"othertypes.Application": {
"type": "object",
"properties": {
"id": {
"type": "integer"
}
}
}
}
}
================================================
FILE: testdata/global_override/main.go
================================================
package global_override
import (
"net/http"
"github.com/swaggo/swag/testdata/global_override/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
func main() {
http.HandleFunc("/testapi/application", api.GetApplication)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/global_override/othertypes/application.go
================================================
package othertypes
type Application struct {
ID int
}
================================================
FILE: testdata/global_override/types/application.go
================================================
package types
import "time"
type Application struct {
Name string
}
type Application2 struct {
Name string
}
type DateOnly time.Time
type TypeToEmbed struct {
Embedded string
}
type ShouldSkip struct {
Name string
}
================================================
FILE: testdata/global_security/api/api.go
================================================
package api
import (
"net/http"
)
// @Summary default security
// @Success 200
// @Router /testapi/application [get]
func GetApplication(w http.ResponseWriter, r *http.Request) {}
// @Summary no security
// @Security
// @Success 200
// @Router /testapi/nosec [get]
func GetNoSec(w http.ResponseWriter, r *http.Request) {}
// @Summary basic security
// @Security BasicAuth
// @Success 200
// @Router /testapi/basic [get]
func GetBasic(w http.ResponseWriter, r *http.Request) {}
// @Summary oauth2 write
// @Security OAuth2Application[write]
// @Success 200
// @Router /testapi/oauth/write [get]
func GetOAuthWrite(w http.ResponseWriter, r *http.Request) {}
// @Summary oauth2 admin
// @Security OAuth2Application[admin]
// @Success 200
// @Router /testapi/oauth/admin [get]
func GetOAuthAdmin(w http.ResponseWriter, r *http.Request) {}
================================================
FILE: testdata/global_security/expected.json
================================================
{
"swagger": "2.0",
"info": {
"title": "Swagger Example API",
"contact": {},
"version": "1.0"
},
"paths": {
"/testapi/application": {
"get": {
"summary": "default security",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/testapi/basic": {
"get": {
"summary": "basic security",
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"BasicAuth": []
}
]
}
},
"/testapi/nosec": {
"get": {
"summary": "no security",
"responses": {
"200": {
"description": "OK"
}
},
"security": []
}
},
"/testapi/oauth/admin": {
"get": {
"summary": "oauth2 admin",
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"OAuth2Application": [
"admin"
]
}
]
}
},
"/testapi/oauth/write": {
"get": {
"summary": "oauth2 write",
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"OAuth2Application": [
"write"
]
}
]
}
}
},
"securityDefinitions": {
"APIKeyAuth": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
},
"BasicAuth": {
"type": "basic"
},
"OAuth2Application": {
"type": "oauth2",
"flow": "application",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
}
},
"security": [
{
"APIKeyAuth": [],
"OAuth2Application": []
}
]
}
================================================
FILE: testdata/global_security/main.go
================================================
package global_security
// @title Swagger Example API
// @version 1.0
// @securityDefinitions.apikey APIKeyAuth
// @in header
// @name Authorization
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @security APIKeyAuth && OAuth2Application
func main() {}
================================================
FILE: testdata/golist/api/api.go
================================================
package api
/*
#include "foo.h"
*/
import "C"
import (
"fmt"
"net/http"
)
func PrintInt(i, j int) {
res := C.add(C.int(i), C.int(j))
fmt.Println(res)
}
type Foo struct {
ID int `json:"id"`
Name string `json:"name"`
PhotoUrls []string `json:"photoUrls"`
Status string `json:"status"`
}
// GetFoo example
// @Summary Get foo
// @Description get foo
// @ID foo
// @Accept json
// @Produce json
// @Param some_id query int true "Some ID"
// @Param some_foo formData Foo true "Foo"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/foo [get]
func GetFoo(w http.ResponseWriter, r *http.Request) {
// write your code
}
================================================
FILE: testdata/golist/api/foo.c
================================================
int add(int a, int b) {
return a + b;
}
================================================
FILE: testdata/golist/api/foo.h
================================================
int add(int, int);
================================================
FILE: testdata/golist/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/example/basic/api"
goapi "github.com/swaggo/swag/testdata/golist/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @query.collection.format multi
// @host petstore.swagger.io
// @BasePath /v2
func main() {
goapi.PrintInt(10, 5)
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
http.HandleFunc("/testapi/get-struct-array-by-string/", api.GetStructArrayByString)
http.HandleFunc("/testapi/upload", api.Upload)
http.HandleFunc("/testapi/foo", goapi.GetFoo)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/golist_disablemodule/api/api.go
================================================
package api
/*
#include "foo.h"
*/
import "C"
import (
"fmt"
)
func PrintInt(i, j int) {
res := C.add(C.int(i), C.int(j))
fmt.Println(res)
}
================================================
FILE: testdata/golist_disablemodule/api/foo.c
================================================
int add(int a, int b) {
return a + b;
}
================================================
FILE: testdata/golist_disablemodule/api/foo.h
================================================
int add(int, int);
================================================
FILE: testdata/golist_disablemodule/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/example/basic/api"
internalapi "github.com/swaggo/swag/testdata/golist_disablemodule/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @host petstore.swagger.io
// @BasePath /v2
func main() {
internalapi.PrintInt(0, 1)
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
http.HandleFunc("/testapi/get-struct-array-by-string/", api.GetStructArrayByString)
http.HandleFunc("/testapi/upload", api.Upload)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/golist_invalid/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/example/basic/api"
"github.com/swaggo/swag/testdata/invalid_external_pkg/invalid"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @host petstore.swagger.io
// @BasePath /v2
func main() {
invalid.Foo()
http.HandleFunc("/testapi/upload", api.Upload)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/invalid_external_pkg/invalid/normal.go
================================================
package invalid
func Foo() {
}
================================================
FILE: testdata/invalid_external_pkg/main.go
================================================
package main
func main() {}
================================================
FILE: testdata/json_field_string/main.go
================================================
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type MyStruct struct {
ID int `json:"id" example:"1" format:"int64"`
Name string `json:"name" example:"poti"`
Intvar int `json:"myint,string"` // integer as string
Boolvar bool `json:",string"` // boolean as a string
TrueBool bool `json:"truebool,string" example:"true"` // boolean as a string
Floatvar float64 `json:",string"` // float as a string
UUIDs []string `json:"uuids" type:"array,string" format:"uuid"` // string array with format
}
// @Summary Call DoSomething
// @Description Does something
// @Accept json
// @Produce json
// @Param body body MyStruct true "My Struct"
// @Success 200 {object} MyStruct
// @Failure 500
// @Router /do-something [post]
func DoSomething(w http.ResponseWriter, r *http.Request) {
objectFromJSON := new(MyStruct)
if err := json.NewDecoder(r.Body).Decode(&objectFromJSON); err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Print(err.Error())
}
json.NewEncoder(w).Encode(ojbectFromJSON)
}
// @title Swagger Example API
// @version 1.0
// @description This is a sample server.
// @host localhost:4000
// @basePath /
func main() {
http.HandleFund("/do-something", DoSomething)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/main.go
================================================
package main
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @description It has a lot of beautiful features.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
// @schemes http https
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @description some description
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @x-google-audiences some_audience.google.com
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
// @x-tokenname id_token
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api
// @x-google-endpoints [{"name":"name.endpoints.environment.cloud.goog","allowCors":true}]
// @x-google-marks "marks values"
// @x-logo {"url":"https://redocly.github.io/redoc/petstore-logo.png", "altText": "Petstore logo", "backgroundColor": "#FFFFFF"}
func main() {}
================================================
FILE: testdata/markdown.go
================================================
package main
// @title Swagger Example API
// @version 1.0
// @description.markdown markdown.md
// @termsOfService http://swagger.io/terms/
// @tag.name users
// @tag.description.markdown
func main() {}
================================================
FILE: testdata/nested/api/api.go
================================================
package api
import (
"net/http"
"github.com/swaggo/swag/testdata/nested2"
)
type Foo struct {
Field1 string `validate:"required"`
OutsideData *nested2.Body
InsideData Bar `validate:"required"`
ArrayField1 []string `validate:"required"`
ArrayField2 []Bar `validate:"required"`
}
type Bar struct {
Field string
}
// @Description get Foo
// @ID get-foo
// @Accept json
// @Produce json
// @Success 200 {object} api.Foo
// @Router /testapi/get-foo [get]
func GetFoo(w http.ResponseWriter, r *http.Request) {
//write your code
var _ = Foo{}
}
================================================
FILE: testdata/nested/common/data.go
================================================
package common
type Data struct {
Value string `json:"message"`
}
================================================
FILE: testdata/nested/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
"version": "1.0"
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"paths": {
"/testapi/get-foo": {
"get": {
"description": "get Foo",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "get-foo",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api.Foo"
}
}
}
}
}
},
"definitions": {
"api.Bar": {
"type": "object",
"properties": {
"field": {
"type": "string"
}
}
},
"api.Foo": {
"type": "object",
"required": [
"arrayField1",
"arrayField2",
"field1",
"insideData"
],
"properties": {
"arrayField1": {
"type": "array",
"items": {
"type": "string"
}
},
"arrayField2": {
"type": "array",
"items": {
"$ref": "#/definitions/api.Bar"
}
},
"field1": {
"type": "string"
},
"insideData": {
"$ref": "#/definitions/api.Bar"
},
"outsideData": {
"$ref": "#/definitions/nested2.Body"
}
}
},
"nested2.Body": {
"type": "object",
"properties": {
"value": {
"type": "string"
}
}
}
}
}
================================================
FILE: testdata/nested/main.go
================================================
package composition
import (
"net/http"
"github.com/swaggo/swag/testdata/nested/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server
// @termsOfService http://swagger.io/terms/
// @host petstore.swagger.io
// @BasePath /v2
func main() {
http.HandleFunc("/testapi/get-foo", api.GetFoo)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/nested2/data.go
================================================
package nested2
type Body struct {
Value string `json:"value"`
}
================================================
FILE: testdata/nested2/inner/data.go
================================================
package inner
================================================
FILE: testdata/non_exported_json_fields/main.go
================================================
package main
import (
"net/http"
)
type MyStruct struct {
ID int `json:"id" example:"1" format:"int64"`
// Post name
Name string `json:"name" example:"poti"`
// Post data
Data struct {
// Post tag
Tag []string `json:"name"`
} `json:"data"`
// not-exported variable, for internal use only, not marshaled
internal1 string
internal2 int
internal3 bool
internal4 struct {
NestedInternal string
}
}
// @Summary Call DoSomething
// @Description Does something, but internal (non-exported) fields inside a struct won't be marshaled into JSON
// @Accept json
// @Produce json
// @Success 200 {object} MyStruct
// @Router /so-something [get]
func DoSomething(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @title Swagger Example API
// @version 1.0
// @description This is a sample server.
// @host localhost:4000
// @basePath /api
func main() {
http.HandleFunc("/do-something", DoSomething)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/param_structs/structs.go
================================================
package structs
type FormModel struct {
Foo string `form:"f" binding:"required" validate:"max=10"`
// B is another field
B bool
}
type AuthHeader struct {
// Token is the auth token
Token string `header:"X-Auth-Token" binding:"required"`
// AnotherHeader is another header
AnotherHeader int `validate:"gte=0,lte=10"`
}
type PathModel struct {
// ID is the id
Identifier int `uri:"id" binding:"required"`
Name string `validate:"max=10"`
}
================================================
FILE: testdata/pare_outside_dependencies/cmd/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/example/basic/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
func main() {
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
http.HandleFunc("//testapi/get-struct-array-by-string/", api.GetStructArrayByString)
http.HandleFunc("/testapi/upload", api.Upload)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/parseExtension/parseExtension.go
================================================
package main
// @Router /without-extension [get]
func Fun() {}
// @Router /with-another-extension [get]
// @x-another-extension {"address": "http://backend"}
func Fun2() {}
// @Router /with-correct-extension [get]
// @x-google-backend {"address": "http://backend"}
func Fun3() {}
// @Router /with-empty-comment-line [get]
func FunEmptyCommentLine() {}
================================================
FILE: testdata/pet/main.go
================================================
package main
// @title Swagger Petstore
// @version 1.0
// @description This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key 'special-key' to test the authorization filters.
// @termsOfService http://swagger.io/terms/
// @contact.email apiteam@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @basePath /v2
// @schemes http https
func main() {
}
================================================
FILE: testdata/pet/web/handler.go
================================================
package web
type Pet struct {
ID int `json:"id" example:"1"`
Category struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"category_name"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"`
SmallCategory struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"detail_category_name"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"`
} `json:"small_category"`
} `json:"category"`
Name string `json:"name" example:"poti"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"`
Tags []Tag `json:"tags"`
Status string `json:"status"`
Price float32 `json:"price" example:"3.25" multipleOf:"0.01"`
IsAlive bool `json:"is_alive" example:"true"`
}
type Tag struct {
ID int `json:"id"`
Name string `json:"name"`
}
================================================
FILE: testdata/quotes/api/api.go
================================================
package api
// RandomFunc dogoc
// @Description.markdown api
// @Success 200 {string} string "ok"
// @Router /random [get]
func RandomFunc() {}
================================================
FILE: testdata/quotes/api.md
================================================
# Title with "quotes"
As we want to verify that this markdown is formatted equally in both the general
API description, in a tag description and in an endpoint description, the tag
defined in main.go is called api, and the markdown file name is specified
manually in the description comment in api/api.go.
```json
{
"with": "escaped \"quotes\"",
"indentedWith": "tabs",
"foo": "bar",
"baz": null
}
```
Some more text.
================================================
FILE: testdata/quotes/expected.json
================================================
{
"schemes": [],
"swagger": "2.0",
"info": {
"description": "# Title with \"quotes\"\n\nAs we want to verify that this markdown is formatted equally in both the general\nAPI description, in a tag description and in an endpoint description, the tag\ndefined in main.go is called api, and the markdown file name is specified\nmanually in the description comment in api/api.go.\n\n```json\n{\n\t\"with\": \"escaped \\\"quotes\\\"\",\n\t\"indentedWith\": \"tabs\",\n\t\"foo\": \"bar\",\n\t\"baz\": null\n}\n```\n\nSome more text.\n",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {},
"version": "1.0"
},
"host": "",
"basePath": "",
"paths": {
"/random": {
"get": {
"description": "# Title with \"quotes\"\n\nAs we want to verify that this markdown is formatted equally in both the general\nAPI description, in a tag description and in an endpoint description, the tag\ndefined in main.go is called api, and the markdown file name is specified\nmanually in the description comment in api/api.go.\n\n```json\n{\n\t\"with\": \"escaped \\\"quotes\\\"\",\n\t\"indentedWith\": \"tabs\",\n\t\"foo\": \"bar\",\n\t\"baz\": null\n}\n```\n\nSome more text.\n",
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
}
}
}
}
},
"tags": [
{
"description": "# Title with \"quotes\"\n\nAs we want to verify that this markdown is formatted equally in both the general\nAPI description, in a tag description and in an endpoint description, the tag\ndefined in main.go is called api, and the markdown file name is specified\nmanually in the description comment in api/api.go.\n\n```json\n{\n\t\"with\": \"escaped \\\"quotes\\\"\",\n\t\"indentedWith\": \"tabs\",\n\t\"foo\": \"bar\",\n\t\"baz\": null\n}\n```\n\nSome more text.\n",
"name": "api"
}
]
}
================================================
FILE: testdata/quotes/main.go
================================================
package main
import (
"github.com/swaggo/swag"
"github.com/swaggo/swag/testdata/quotes/api"
_ "github.com/swaggo/swag/testdata/quotes/docs"
)
func ReadDoc() string {
doc, _ := swag.ReadDoc()
return doc
}
// @title Swagger Example API
// @version 1.0
// @description.markdown
// @tag.name api
// @tag.description.markdown
// @termsOfService http://swagger.io/terms/
func main() {
api.RandomFunc()
}
================================================
FILE: testdata/recursive_with_name/main.go
================================================
package main
import "net/http"
// @title Recursive Type with @name Test API
// @version 1.0
// @description This is a test for recursive types with @name annotations
// @BasePath /api/v1
// EntityHierarchyNode represents a node in the entity hierarchy tree.
// Each node contains an entity and its child nodes.
type EntityHierarchyNode struct {
ID string `json:"id"`
Name string `json:"name"`
Children []*EntityHierarchyNode `json:"children"`
} // @name EntityHierarchyNode
// TreeData represents a complete tree structure
type TreeData struct {
Root EntityHierarchyNode `json:"root"`
} // @name TreeData
// GetEntityHierarchy returns the entity hierarchy
// @Summary Get entity hierarchy
// @Description Get the complete entity hierarchy tree
// @Tags hierarchy
// @Accept json
// @Produce json
// @Success 200 {object} TreeData
// @Router /hierarchy [get]
func GetEntityHierarchy(w http.ResponseWriter, r *http.Request) {
// Implementation
}
func main() {
// Server setup
}
================================================
FILE: testdata/simple/api/api.go
================================================
package api
import (
"net/http"
. "github.com/swaggo/swag/testdata/simple/cross"
_ "github.com/swaggo/swag/testdata/simple/web"
)
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID get-string-by-int
// @Accept json
// @Produce json
// @Param some_id path int true "Some ID" Format(int64)
// @Param some_id body web.Pet true "Some ID"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
func GetStringByInt(w http.ResponseWriter, r *http.Request) {
_ = Cross{}
//write your code
}
// @Description get struct array by ID
// @ID get-struct-array-by-string
// @Accept json
// @Produce json
// @Param some_id path string true "Some ID"
// @Param category query int true "Category" Enums(1, 2, 3)
// @Param offset query int true "Offset" Minimum(0) default(0)
// @Param limit query int true "Limit" Maximum(50) default(10)
// @Param q query string true "q" Minlength(1) Maxlength(50) default("")
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Security ApiKeyAuth
// @Security BasicAuth
// @Security OAuth2Application[write]
// @Security OAuth2Implicit[read, admin]
// @Security OAuth2AccessCode[read]
// @Security OAuth2Password[admin]
// @Security OAuth2Implicit[read, write] || Firebase
// @Router /testapi/get-struct-array-by-string/{some_id} [get]
func GetStructArrayByString(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Summary Upload file
// @Description Upload file
// @ID file.upload
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "this is a test file"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 401 {array} string
// @Failure 404 {object} web.APIError "Can not find ID"
// @Failure 403 {object} Cross "cross"
// @Router /file/upload [post]
func Upload(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Summary use Anonymous field
// @Success 200 {object} web.RevValue "ok"
// @Router /AnonymousField [get]
func AnonymousField() {
}
// @Summary use pet2
// @Success 200 {object} web.Pet2 "ok"
// @Router /Pet2 [get]
func Pet2() {
}
// @Summary Use IndirectRecursiveTest
// @Success 200 {object} web.IndirectRecursiveTest
// @Router /IndirectRecursiveTest [get]
func IndirectRecursiveTest() {
}
// @Summary Use Tags
// @Success 200 {object} web.Tags
// @Router /Tags [get]
func Tags() {
}
// @Summary Use CrossAlias
// @Success 200 {object} web.CrossAlias
// @Router /CrossAlias [get]
func CrossAlias() {
}
// @Summary Use AnonymousStructArray
// @Success 200 {object} web.AnonymousStructArray
// @Router /AnonymousStructArray [get]
func AnonymousStructArray() {
}
type Pet3 struct {
ID int `json:"id"`
}
// @Success 200 {object} web.Pet5a "ok"
// @Router /GetPet5a [options]
func GetPet5a() {
}
// @Success 200 {object} web.Pet5b "ok"
// @Router /GetPet5b [head]
func GetPet5b() {
}
// @Success 200 {object} web.Pet5c "ok"
// @Router /GetPet5c [patch]
func GetPet5c() {
}
type SwagReturn []map[string]string
// @Success 200 {object} api.SwagReturn "ok"
// @Router /GetPet6MapString [get]
func GetPet6MapString() {
}
// @Success 200 {object} api.GetPet6FunctionScopedResponse.response "ok"
// @Router /GetPet6FunctionScopedResponse [get]
func GetPet6FunctionScopedResponse() {
type response struct {
Name string
}
}
// @Success 200 {object} api.GetPet6FunctionScopedComplexResponse.response "ok"
// @Router /GetPet6FunctionScopedComplexResponse [get]
func GetPet6FunctionScopedComplexResponse() {
type pet struct {
Name string
}
type pointerPet struct {
Name string
}
type response struct {
Pets []pet
PointerPet *pointerPet
}
}
================================================
FILE: testdata/simple/cross/test.go
================================================
package cross
type Cross struct {
Array []string
String string
}
================================================
FILE: testdata/simple/expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"paths": {
"/AnonymousField": {
"get": {
"summary": "use Anonymous field",
"responses": {
"200": {
"description": "ok",
"schema": {
"$ref": "#/definitions/web.RevValue"
}
}
}
}
},
"/AnonymousStructArray": {
"get": {
"summary": "Use AnonymousStructArray",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"foo": {
"type": "string"
}
}
}
}
}
}
}
},
"/CrossAlias": {
"get": {
"summary": "Use CrossAlias",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/web.CrossAlias"
}
}
}
}
},
"/GetPet5a": {
"options": {
"responses": {
"200": {
"description": "ok",
"schema": {
"$ref": "#/definitions/web.Pet5a"
}
}
}
}
},
"/GetPet5b": {
"head": {
"responses": {
"200": {
"description": "ok",
"schema": {
"$ref": "#/definitions/web.Pet5b"
}
}
}
}
},
"/GetPet5c": {
"patch": {
"responses": {
"200": {
"description": "ok",
"schema": {
"$ref": "#/definitions/web.Pet5c"
}
}
}
}
},
"/GetPet6FunctionScopedComplexResponse": {
"get": {
"responses": {
"200": {
"description": "ok",
"schema": {
"$ref": "#/definitions/api.GetPet6FunctionScopedComplexResponse.response"
}
}
}
}
},
"/GetPet6FunctionScopedResponse": {
"get": {
"responses": {
"200": {
"description": "ok",
"schema": {
"$ref": "#/definitions/api.GetPet6FunctionScopedResponse.response"
}
}
}
}
},
"/GetPet6MapString": {
"get": {
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
}
},
"/IndirectRecursiveTest": {
"get": {
"summary": "Use IndirectRecursiveTest",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/web.IndirectRecursiveTest"
}
}
}
}
},
"/Pet2": {
"get": {
"summary": "use pet2",
"responses": {
"200": {
"description": "ok",
"schema": {
"$ref": "#/definitions/web.Pet2"
}
}
}
}
},
"/Tags": {
"get": {
"summary": "Use Tags",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Tag"
}
}
}
}
}
},
"/file/upload": {
"post": {
"description": "Upload file",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"summary": "Upload file",
"operationId": "file.upload",
"parameters": [
{
"type": "file",
"description": "this is a test file",
"name": "file",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"403": {
"description": "cross",
"schema": {
"$ref": "#/definitions/cross.Cross"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
},
"/testapi/get-string-by-int/{some_id}": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add a new pet to the store",
"operationId": "get-string-by-int",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Some ID",
"name": "some_id",
"in": "path",
"required": true
},
{
"description": "Some ID",
"name": "some_id",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.Pet"
}
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
},
"/testapi/get-struct-array-by-string/{some_id}": {
"get": {
"security": [
{
"ApiKeyAuth": []
},
{
"BasicAuth": []
},
{
"OAuth2Application": [
"write"
]
},
{
"OAuth2Implicit": [
"read",
"admin"
]
},
{
"OAuth2AccessCode": [
"read"
]
},
{
"OAuth2Password": [
"admin"
]
},
{
"Firebase": [],
"OAuth2Implicit": [
"read",
"write"
]
}
],
"description": "get struct array by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "get-struct-array-by-string",
"parameters": [
{
"type": "string",
"description": "Some ID",
"name": "some_id",
"in": "path",
"required": true
},
{
"enum": [
1,
2,
3
],
"type": "integer",
"description": "Category",
"name": "category",
"in": "query",
"required": true
},
{
"minimum": 0,
"type": "integer",
"default": 0,
"description": "Offset",
"name": "offset",
"in": "query",
"required": true
},
{
"maximum": 50,
"type": "integer",
"default": 10,
"description": "Limit",
"name": "limit",
"in": "query",
"required": true
},
{
"maxLength": 50,
"minLength": 1,
"type": "string",
"default": "\"\"",
"description": "q",
"name": "q",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
}
},
"definitions": {
"api.GetPet6FunctionScopedComplexResponse.pet": {
"type": "object",
"properties": {
"Name": {
"type": "string"
}
}
},
"api.GetPet6FunctionScopedComplexResponse.pointerPet": {
"type": "object",
"properties": {
"Name": {
"type": "string"
}
}
},
"api.GetPet6FunctionScopedComplexResponse.response": {
"type": "object",
"properties": {
"Pets": {
"type": "array",
"items": {
"$ref": "#/definitions/api.GetPet6FunctionScopedComplexResponse.pet"
}
},
"PointerPet": {
"$ref": "#/definitions/api.GetPet6FunctionScopedComplexResponse.pointerPet"
}
}
},
"api.GetPet6FunctionScopedResponse.response": {
"type": "object",
"properties": {
"Name": {
"type": "string"
}
}
},
"cross.Cross": {
"type": "object",
"properties": {
"Array": {
"type": "array",
"items": {
"type": "string"
}
},
"String": {
"type": "string"
}
}
},
"web.APIError": {
"type": "object",
"properties": {
"CreatedAt": {
"type": "string"
},
"ErrorCode": {
"type": "integer"
},
"ErrorMessage": {
"type": "string"
}
}
},
"web.CrossAlias": {
"type": "object",
"properties": {
"Array": {
"type": "array",
"items": {
"type": "string"
}
},
"String": {
"type": "string"
}
}
},
"web.IndirectRecursiveTest": {
"type": "object",
"properties": {
"Tags": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Tag"
}
}
}
},
"web.Pet": {
"type": "object",
"required": [
"name",
"photo_urls"
],
"properties": {
"category": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "category_name"
},
"photo_urls": {
"type": "array",
"items": {
"type": "string",
"format": "url"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
},
"small_category": {
"type": "object",
"required": [
"name"
],
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"maxLength": 16,
"minLength": 4,
"example": "detail_category_name"
},
"photo_urls": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
}
}
}
}
},
"data": {},
"decimal": {
"type": "number"
},
"enum_array": {
"type": "array",
"items": {
"type": "integer",
"enum": [
1,
2,
3,
5,
7
]
}
},
"food_brands": {
"type": "array",
"items": {
"type": "string"
},
"x-some-extension": true
},
"food_types": {
"type": "array",
"items": {
"type": "integer",
"enum": [
0,
1,
2
],
"x-enum-varnames": [
"Wet",
"Dry",
"Raw"
]
},
"x-some-extension": true
},
"id": {
"type": "integer",
"format": "int64",
"readOnly": true,
"example": 1
},
"int_array": {
"type": "array",
"items": {
"type": "integer"
},
"example": [
1,
2
]
},
"is_alive": {
"type": "boolean",
"default": true,
"example": true
},
"name": {
"type": "string",
"example": "poti"
},
"pets": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet2"
}
},
"pets2": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet2"
}
},
"photo_urls": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
},
"price": {
"type": "number",
"maximum": 1000,
"minimum": 1,
"multipleOf": 0.01,
"example": 3.25
},
"single_enum_varname": {
"type": "integer",
"enum": [
1,
2,
3
],
"x-enum-varnames": [
"one",
"two",
"three"
],
"x-some-extension": true
},
"status": {
"type": "string",
"enum": [
"healthy",
"ill"
]
},
"string_map": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"example": {
"key1": "value",
"key2": "value2"
}
},
"tags": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Tag"
}
},
"uuid": {
"type": "string"
}
}
},
"web.Pet2": {
"type": "object",
"properties": {
"deleted_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"middlename": {
"type": "string",
"x-abc": "def",
"x-nullable": true,
"x-omitempty": false
}
}
},
"web.Pet5a": {
"type": "object",
"required": [
"name",
"odd"
],
"properties": {
"name": {
"type": "string"
},
"odd": {
"type": "boolean"
}
}
},
"web.Pet5b": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
},
"web.Pet5c": {
"type": "object",
"required": [
"name",
"odd"
],
"properties": {
"name": {
"type": "string"
},
"odd": {
"type": "boolean"
}
}
},
"web.RevValue": {
"type": "object",
"properties": {
"Data": {
"type": "integer"
},
"cross": {
"$ref": "#/definitions/cross.Cross"
},
"crosses": {
"type": "array",
"items": {
"$ref": "#/definitions/cross.Cross"
}
},
"rev_value_base": {
"$ref": "#/definitions/web.RevValueBase"
}
}
},
"web.RevValueBase": {
"type": "object",
"properties": {
"Err": {
"type": "integer"
},
"Status": {
"type": "boolean"
}
}
},
"web.Tag": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"pets": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet"
}
}
}
}
},
"securityDefinitions": {
"ApiKeyAuth": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
},
"BasicAuth": {
"type": "basic"
},
"OAuth2AccessCode": {
"type": "oauth2",
"flow": "accessCode",
"authorizationUrl": "https://example.com/oauth/authorize",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information"
}
},
"OAuth2Application": {
"type": "oauth2",
"flow": "application",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
},
"OAuth2Implicit": {
"type": "oauth2",
"flow": "implicit",
"authorizationUrl": "https://example.com/oauth/authorize",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
},
"OAuth2Password": {
"type": "oauth2",
"flow": "password",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"read": "Grants read access",
"write": "Grants write access"
}
}
}
}
================================================
FILE: testdata/simple/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/simple/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
http.HandleFunc("/testapi/get-struct-array-by-string/", api.GetStructArrayByString)
http.HandleFunc("/testapi/upload", api.Upload)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/simple/web/handler.go
================================================
package web
import (
"time"
"github.com/gofrs/uuid"
"github.com/shopspring/decimal"
"github.com/swaggo/swag/testdata/simple/cross"
)
type Pet struct {
ID int `json:"id" example:"1" format:"int64" readonly:"true"`
Category struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"category_name"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" format:"url"`
SmallCategory struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"detail_category_name" binding:"required" minLength:"4" maxLength:"16"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg"`
} `json:"small_category"`
} `json:"category"`
Name string `json:"name" example:"poti" binding:"required"`
PhotoUrls []string `json:"photo_urls" example:"http://test/image/1.jpg,http://test/image/2.jpg" binding:"required"`
Tags []Tag `json:"tags"`
Pets *[]Pet2 `json:"pets"`
Pets2 []*Pet2 `json:"pets2"`
Status string `json:"status" enums:"healthy,ill"`
Price float32 `json:"price" example:"3.25" minimum:"1.0" maximum:"1000" multipleOf:"0.01"`
IsAlive bool `json:"is_alive" example:"true" default:"true"`
Data interface{} `json:"data"`
Hidden string `json:"-"`
UUID uuid.UUID `json:"uuid"`
Decimal decimal.Decimal `json:"decimal"`
IntArray []int `json:"int_array" example:"1,2"`
StringMap map[string]string `json:"string_map" example:"key1:value,key2:value2"`
EnumArray []int `json:"enum_array" enums:"1,2,3,5,7"`
FoodTypes []string `json:"food_types" swaggertype:"array,integer" enums:"0,1,2" x-enum-varnames:"Wet,Dry,Raw" extensions:"x-some-extension"`
FoodBrands []string `json:"food_brands" extensions:"x-some-extension"`
SingleEnumVarname string `json:"single_enum_varname" swaggertype:"integer" enums:"1,2,3" x-enum-varnames:"one,two,three" extensions:"x-some-extension"`
}
type Tag struct {
ID int `json:"id" format:"int64"`
Name string `json:"name"`
Pets []Pet `json:"pets"`
}
type Tags []*Tag
type AnonymousStructArray []struct {
Foo string `json:"foo"`
}
type CrossAlias cross.Cross
type Pet2 struct {
ID int `json:"id"`
MiddleName *string `json:"middlename" extensions:"x-nullable,x-abc=def,!x-omitempty"`
DeletedAt *time.Time `json:"deleted_at"`
}
type IndirectRecursiveTest struct {
Tags []Tag
}
type APIError struct {
ErrorCode int
ErrorMessage string
CreatedAt time.Time
}
type RevValueBase struct {
Status bool `json:"Status"`
Err int32 `json:"Err,omitempty"`
}
type RevValue struct {
RevValueBase `json:"rev_value_base"`
Data int `json:"Data"`
Cross cross.Cross `json:"cross"`
Crosses []cross.Cross `json:"crosses"`
}
// Below we have Pet5b as base type and Pet5a and Pet5c both have Pet5b as anonymous field, inheriting it's properties
// By using these names we ensure that our test will fill if the order of parsing matters at all
type Pet5a struct {
*Pet5b
Odd bool `json:"odd" binding:"required"`
}
type Pet5b struct {
Name string `json:"name" binding:"required"`
}
type Pet5c struct {
*Pet5b
Odd bool `json:"odd" binding:"required"`
}
================================================
FILE: testdata/simple2/api/api.go
================================================
package api
import (
"net/http"
)
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID get-string-by-int
// @Accept json
// @Produce json
// @Param some_id path int true "Some ID" Format(int64)
// @Param some_id body web.Pet true "Some ID"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
func GetStringByInt(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Description get struct array by ID
// @ID get-struct-array-by-string
// @Accept json
// @Produce json
// @Param some_id path string true "Some ID"
// @Param category query int true "Category" Enums(1, 2, 3)
// @Param offset query int true "Offset" Minimum(0) default(0)
// @Param limit query int true "Limit" Maximum(50) default(10)
// @Param q query string true "q" Minlength(1) Maxlength(50) default("")
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Security ApiKeyAuth
// @Security BasicAuth
// @Security OAuth2Application[write]
// @Security OAuth2Implicit[read, admin]
// @Security OAuth2AccessCode[read]
// @Security OAuth2Password[admin]
// @Router /testapi/get-struct-array-by-string/{some_id} [get]
func GetStructArrayByString(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Summary Upload file
// @Description Upload file
// @ID file.upload
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "this is a test file"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /file/upload [post]
func Upload(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Summary use Anonymous field
// @Success 200 {object} web.RevValue "ok"
func AnonymousField() {
}
// @Summary use pet2
// @Success 200 {object} web.Pet2 "ok"
func Pet2() {
}
type Pet3 struct {
ID int `json:"id"`
}
================================================
FILE: testdata/simple2/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/simple2/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
http.HandleFunc("//testapi/get-struct-array-by-string/", api.GetStructArrayByString)
http.HandleFunc("/testapi/upload", api.Upload)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/simple2/web/handler.go
================================================
package web
import (
"database/sql"
"math/big"
"strconv"
"time"
uuid "github.com/gofrs/uuid"
"github.com/shopspring/decimal"
)
type TimestampTime struct {
time.Time
}
func (t *TimestampTime) MarshalJSON() ([]byte, error) {
bin := make([]byte, 16)
bin = strconv.AppendInt(bin[:0], t.Time.Unix(), 10)
return bin, nil
}
func (t *TimestampTime) UnmarshalJSON(bin []byte) error {
v, err := strconv.ParseInt(string(bin), 10, 64)
if err != nil {
return err
}
t.Time = time.Unix(v, 0)
return nil
}
type Pet struct {
ID int `example:"1" format:"int64"`
Category struct {
ID int `example:"1"`
Name string `example:"category_name"`
PhotoUrls []string `example:"http://test/image/1.jpg,http://test/image/2.jpg" format:"url"`
SmallCategory struct {
ID int `example:"1"`
Name string `example:"detail_category_name" validate:"required"`
PhotoUrls []string `example:"http://test/image/1.jpg,http://test/image/2.jpg"`
}
}
Name string `example:"poti"`
PhotoUrls []string `example:"http://test/image/1.jpg,http://test/image/2.jpg"`
Tags []Tag
Pets *[]Pet2
Pets2 []*Pet2
Status string
Price float32 `example:"3.25" validate:"required,gte=0,lte=130" multipleOf:"0.01"`
IsAlive bool `example:"true"`
Data interface{}
Hidden string `json:"-"`
UUID uuid.UUID
Decimal decimal.Decimal
CustomString CustomString
CustomStringArr []CustomString
NullInt sql.NullInt64 `swaggertype:"integer"`
Coeffs []big.Float `swaggertype:"array,number"`
Birthday TimestampTime `swaggertype:"primitive,integer"`
}
type CustomString string
type Tag struct {
ID int `format:"int64"`
Name string
Pets []Pet
}
type Pet2 struct {
ID int
MiddleName *string
DeletedAt *time.Time
}
type APIError struct {
ErrorCode int
ErrorMessage string
CreatedAt time.Time
}
type RevValueBase struct {
Status bool
Err int32
}
type RevValue struct {
RevValueBase
Data int
}
================================================
FILE: testdata/simple3/api/api.go
================================================
package api
import "net/http"
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID get-string-by-int
// @Accept json
// @Produce json
// @Param some_id path int true "Some ID" Format(int64)
// @Param some_id body web.Pet true "Some ID"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
func GetStringByInt(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Description get struct array by ID
// @ID get-struct-array-by-string
// @Accept json
// @Produce json
// @Param some_id path string true "Some ID"
// @Param category query int true "Category" Enums(1, 2, 3)
// @Param offset query int true "Offset" Minimum(0) default(0)
// @Param limit query int true "Limit" Maximum(50) default(10)
// @Param q query string true "q" Minlength(1) Maxlength(50) default("")
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Security ApiKeyAuth
// @Security BasicAuth
// @Security OAuth2Application[write]
// @Security OAuth2Implicit[read, admin]
// @Security OAuth2AccessCode[read]
// @Security OAuth2Password[admin]
// @Router /testapi/get-struct-array-by-string/{some_id} [get]
func GetStructArrayByString(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Summary Upload file
// @Description Upload file
// @ID file.upload
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "this is a test file"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /file/upload [post]
func Upload(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @Summary use Anonymous field
// @Success 200 {object} web.RevValue "ok"
func AnonymousField() {
}
// @Summary use pet2
// @Success 200 {object} web.Pet2 "ok"
func Pet2() {
}
type Pet3 struct {
ID int `json:"id"`
}
================================================
FILE: testdata/simple3/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/simple3/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
http.HandleFunc("//testapi/get-struct-array-by-string/", api.GetStructArrayByString)
http.HandleFunc("/testapi/upload", api.Upload)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/simple3/web/handler.go
================================================
package web
import (
"time"
uuid "github.com/gofrs/uuid"
"github.com/shopspring/decimal"
)
type Pet struct {
ID int `example:"1" format:"int64"`
Category struct {
ID int `example:"1"`
Name string `example:"category_name"`
PhotoURLs []string `example:"http://test/image/1.jpg,http://test/image/2.jpg" format:"url"`
SmallCategory struct {
ID int `example:"1"`
Name string `example:"detail_category_name"`
PhotoURLs []string `example:"http://test/image/1.jpg,http://test/image/2.jpg"`
}
}
Name string `example:"poti"`
PhotoURLs []string `example:"http://test/image/1.jpg,http://test/image/2.jpg"`
Tags []Tag
Pets *[]Pet2
Pets2 []*Pet2
Status string
Price float32 `example:"3.25" multipleOf:"0.01"`
IsAlive bool `example:"true"`
Data interface{}
Hidden string `json:"-"`
UUID uuid.UUID
Decimal decimal.Decimal
Function func()
}
type Tag struct {
ID int `format:"int64"`
Name string
Pets []Pet
}
type Pet2 struct {
ID int
MiddleName *string
DeletedAt *time.Time
}
type APIError struct {
ErrorCode int
ErrorMessage string
CreatedAt time.Time
}
type RevValueBase struct {
Status bool
Err int32
}
type RevValue struct {
RevValueBase
Data int
}
================================================
FILE: testdata/simple_cgo/api/api.go
================================================
package api
import "net/http"
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID get-string-by-int
// @Accept json
// @Produce json
// @Param some_id path int true "Some ID" Format(int64)
// @Param some_id body int true "Some ID"
// @Success 200 {string} string "ok"
// @Failure 400 {object} string "We need ID!!"
// @Failure 404 {object} string "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
func GetStringByInt(w http.ResponseWriter, r *http.Request) {
}
================================================
FILE: testdata/simple_cgo/main.go
================================================
package main
/*
#include
void Hello(){
printf("Hello world\n");
}
*/
import "C"
import (
"net/http"
"github.com/swaggo/swag/testdata/simple_cgo/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host petstore.swagger.io
// @BasePath /v2
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
C.Hello()
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByInt)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/single_file_api/main.go
================================================
package main
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @description It has a lot of beautiful features.
// @termsOfService http://swagger.io/terms/
// @Summary test op
// @Router /test [get]
// @Description This belongs to the operation, not the general API!
func op() bool {
return true
}
================================================
FILE: testdata/state/admin_expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "petstore-admin.swagger.io",
"basePath": "/v3",
"paths": {
"/admin/file/upload": {
"post": {
"description": "Upload file",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"summary": "Upload file",
"operationId": "admin.file.upload",
"parameters": [
{
"type": "file",
"description": "this is a test file",
"name": "file",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
},
"/admin/testapi/get-string-by-int/{some_id}": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add a new pet to the store",
"operationId": "admin.get-string-by-int",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Some ID",
"name": "some_id",
"in": "path",
"required": true
},
{
"description": "Some ID",
"name": "some_id",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.Pet"
}
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
},
"/admin/testapi/get-struct-array-by-string/{some_id}": {
"get": {
"security": [
{
"ApiKeyAuth": []
},
{
"BasicAuth": []
},
{
"OAuth2Application": [
"write"
]
},
{
"OAuth2Implicit": [
"read",
"admin"
]
},
{
"OAuth2AccessCode": [
"read"
]
},
{
"OAuth2Password": [
"admin"
]
}
],
"description": "get struct array by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "admin.get-struct-array-by-string",
"parameters": [
{
"type": "string",
"description": "Some ID",
"name": "some_id",
"in": "path",
"required": true
},
{
"enum": [
1,
2,
3
],
"type": "integer",
"description": "Category",
"name": "category",
"in": "query",
"required": true
},
{
"minimum": 0,
"type": "integer",
"default": 0,
"description": "Offset",
"name": "offset",
"in": "query",
"required": true
},
{
"maximum": 50,
"type": "integer",
"default": 10,
"description": "Limit",
"name": "limit",
"in": "query",
"required": true
},
{
"maxLength": 50,
"minLength": 1,
"type": "string",
"default": "\"\"",
"description": "q",
"name": "q",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
}
},
"definitions": {
"web.APIError": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"errorCode": {
"type": "integer"
},
"errorMessage": {
"type": "string"
}
}
},
"web.Pet": {
"type": "object",
"properties": {
"category": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "category_name"
},
"photoURLs": {
"type": "array",
"items": {
"type": "string",
"format": "url"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
},
"smallCategory": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "detail_category_name"
},
"photoURLs": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
}
}
}
}
},
"data": {},
"decimal": {
"type": "number"
},
"id": {
"type": "integer",
"format": "int64",
"example": 1
},
"isAlive": {
"type": "boolean",
"example": true
},
"name": {
"type": "string",
"example": "poti"
},
"pets": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet2"
}
},
"pets2": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet2"
}
},
"photoURLs": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
},
"price": {
"type": "number",
"multipleOf": 0.01,
"example": 3.25
},
"status": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Tag"
}
},
"uuid": {
"type": "string"
}
}
},
"web.Pet2": {
"type": "object",
"properties": {
"deletedAt": {
"type": "string"
},
"id": {
"type": "integer"
},
"middleName": {
"type": "string"
}
}
},
"web.RevValue": {
"type": "object",
"properties": {
"data": {
"type": "integer"
},
"err": {
"type": "integer",
"format": "int32"
},
"status": {
"type": "boolean"
}
}
},
"web.Tag": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"pets": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet"
}
}
}
}
}
}
================================================
FILE: testdata/state/api/api.go
================================================
package api
import "net/http"
// @State admin
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID admin.get-string-by-int
// @Accept json
// @Produce json
// @Param some_id path int true "Some ID" Format(int64)
// @Param some_id body web.Pet true "Some ID"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /admin/testapi/get-string-by-int/{some_id} [get]
func GetStringByInt(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @State admin
// @Description get struct array by ID
// @ID admin.get-struct-array-by-string
// @Accept json
// @Produce json
// @Param some_id path string true "Some ID"
// @Param category query int true "Category" Enums(1, 2, 3)
// @Param offset query int true "Offset" Minimum(0) default(0)
// @Param limit query int true "Limit" Maximum(50) default(10)
// @Param q query string true "q" Minlength(1) Maxlength(50) default("")
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Security ApiKeyAuth
// @Security BasicAuth
// @Security OAuth2Application[write]
// @Security OAuth2Implicit[read, admin]
// @Security OAuth2AccessCode[read]
// @Security OAuth2Password[admin]
// @Router /admin/testapi/get-struct-array-by-string/{some_id} [get]
func GetStructArrayByString(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @State admin
// @Summary Upload file
// @Description Upload file
// @ID admin.file.upload
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "this is a test file"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /admin/file/upload [post]
func Upload(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @State admin
// @Summary use Anonymous field
// @Success 200 {object} web.RevValue "ok"
func AnonymousField() {
}
// @State admin
// @Summary use pet2
// @Success 200 {object} web.Pet2 "ok"
func Pet2() {
}
type Pet3 struct {
ID int `json:"id"`
}
================================================
FILE: testdata/state/api/api_user.go
================================================
package api
import "net/http"
// @State user
// @Summary Add a new pet to the store
// @Description get string by ID
// @ID get-string-by-int
// @Accept json
// @Produce json
// @Param some_id path int true "Some ID" Format(int64)
// @Param some_id body web.Pet true "Some ID"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /testapi/get-string-by-int/{some_id} [get]
func GetStringByIntUser(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @State user
// @Description get struct array by ID
// @ID get-struct-array-by-string
// @Accept json
// @Produce json
// @Param some_id path string true "Some ID"
// @Param category query int true "Category" Enums(1, 2, 3)
// @Param offset query int true "Offset" Minimum(0) default(0)
// @Param limit query int true "Limit" Maximum(50) default(10)
// @Param q query string true "q" Minlength(1) Maxlength(50) default("")
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Security ApiKeyAuth
// @Security BasicAuth
// @Security OAuth2Application[write]
// @Security OAuth2Implicit[read, admin]
// @Security OAuth2AccessCode[read]
// @Security OAuth2Password[admin]
// @Router /testapi/get-struct-array-by-string/{some_id} [get]
func GetStructArrayByStringUser(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @State user
// @Summary Upload file
// @Description Upload file
// @ID file.upload
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "this is a test file"
// @Success 200 {string} string "ok"
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /file/upload [post]
func UploadUser(w http.ResponseWriter, r *http.Request) {
//write your code
}
// @State user
// @Summary use Anonymous field
// @Success 200 {object} web.RevValue "ok"
func AnonymousFieldUser() {
}
// @State user
// @Summary use pet2
// @Success 200 {object} web.Pet2 "ok"
func Pet2User() {
}
type Pet3User struct {
ID int `json:"id"`
}
================================================
FILE: testdata/state/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/state/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @hostState admin petstore-admin.swagger.io
// @hostState user petstore-user.swagger.io
// @BasePath /v3
func main() {
state := "admin" // "admin" or "user"
switch state {
case "admin":
http.HandleFunc("/admin/testapi/get-string-by-int/", api.GetStringByInt)
http.HandleFunc("/admin/testapi/get-struct-array-by-string/", api.GetStructArrayByString)
http.HandleFunc("/admin/testapi/upload", api.Upload)
http.ListenAndServe(":8080", nil)
case "user":
http.HandleFunc("/testapi/get-string-by-int/", api.GetStringByIntUser)
http.HandleFunc("/testapi/get-struct-array-by-string/", api.GetStructArrayByStringUser)
http.HandleFunc("/testapi/upload", api.UploadUser)
http.ListenAndServe(":8080", nil)
}
}
================================================
FILE: testdata/state/user_expected.json
================================================
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "petstore-user.swagger.io",
"basePath": "/v3",
"paths": {
"/file/upload": {
"post": {
"description": "Upload file",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"summary": "Upload file",
"operationId": "file.upload",
"parameters": [
{
"type": "file",
"description": "this is a test file",
"name": "file",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
},
"/testapi/get-string-by-int/{some_id}": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Add a new pet to the store",
"operationId": "get-string-by-int",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Some ID",
"name": "some_id",
"in": "path",
"required": true
},
{
"description": "Some ID",
"name": "some_id",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/web.Pet"
}
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
},
"/testapi/get-struct-array-by-string/{some_id}": {
"get": {
"security": [
{
"ApiKeyAuth": []
},
{
"BasicAuth": []
},
{
"OAuth2Application": [
"write"
]
},
{
"OAuth2Implicit": [
"read",
"admin"
]
},
{
"OAuth2AccessCode": [
"read"
]
},
{
"OAuth2Password": [
"admin"
]
}
],
"description": "get struct array by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"operationId": "get-struct-array-by-string",
"parameters": [
{
"type": "string",
"description": "Some ID",
"name": "some_id",
"in": "path",
"required": true
},
{
"enum": [
1,
2,
3
],
"type": "integer",
"description": "Category",
"name": "category",
"in": "query",
"required": true
},
{
"minimum": 0,
"type": "integer",
"default": 0,
"description": "Offset",
"name": "offset",
"in": "query",
"required": true
},
{
"maximum": 50,
"type": "integer",
"default": 10,
"description": "Limit",
"name": "limit",
"in": "query",
"required": true
},
{
"maxLength": 50,
"minLength": 1,
"type": "string",
"default": "\"\"",
"description": "q",
"name": "q",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "ok",
"schema": {
"type": "string"
}
},
"400": {
"description": "We need ID!!",
"schema": {
"$ref": "#/definitions/web.APIError"
}
},
"404": {
"description": "Can not find ID",
"schema": {
"$ref": "#/definitions/web.APIError"
}
}
}
}
}
},
"definitions": {
"web.APIError": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"errorCode": {
"type": "integer"
},
"errorMessage": {
"type": "string"
}
}
},
"web.Pet": {
"type": "object",
"properties": {
"category": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "category_name"
},
"photoURLs": {
"type": "array",
"items": {
"type": "string",
"format": "url"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
},
"smallCategory": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "detail_category_name"
},
"photoURLs": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
}
}
}
}
},
"data": {},
"decimal": {
"type": "number"
},
"id": {
"type": "integer",
"format": "int64",
"example": 1
},
"isAlive": {
"type": "boolean",
"example": true
},
"name": {
"type": "string",
"example": "poti"
},
"pets": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet2"
}
},
"pets2": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet2"
}
},
"photoURLs": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"http://test/image/1.jpg",
"http://test/image/2.jpg"
]
},
"price": {
"type": "number",
"multipleOf": 0.01,
"example": 3.25
},
"status": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Tag"
}
},
"uuid": {
"type": "string"
}
}
},
"web.Pet2": {
"type": "object",
"properties": {
"deletedAt": {
"type": "string"
},
"id": {
"type": "integer"
},
"middleName": {
"type": "string"
}
}
},
"web.RevValue": {
"type": "object",
"properties": {
"data": {
"type": "integer"
},
"err": {
"type": "integer",
"format": "int32"
},
"status": {
"type": "boolean"
}
}
},
"web.Tag": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string"
},
"pets": {
"type": "array",
"items": {
"$ref": "#/definitions/web.Pet"
}
}
}
}
}
}
================================================
FILE: testdata/state/web/handler.go
================================================
package web
import (
"time"
uuid "github.com/gofrs/uuid"
"github.com/shopspring/decimal"
)
type Pet struct {
ID int `example:"1" format:"int64"`
Category struct {
ID int `example:"1"`
Name string `example:"category_name"`
PhotoURLs []string `example:"http://test/image/1.jpg,http://test/image/2.jpg" format:"url"`
SmallCategory struct {
ID int `example:"1"`
Name string `example:"detail_category_name"`
PhotoURLs []string `example:"http://test/image/1.jpg,http://test/image/2.jpg"`
}
}
Name string `example:"poti"`
PhotoURLs []string `example:"http://test/image/1.jpg,http://test/image/2.jpg"`
Tags []Tag
Pets *[]Pet2
Pets2 []*Pet2
Status string
Price float32 `example:"3.25" multipleOf:"0.01"`
IsAlive bool `example:"true"`
Data interface{}
Hidden string `json:"-"`
UUID uuid.UUID
Decimal decimal.Decimal
Function func()
}
type Tag struct {
ID int `format:"int64"`
Name string
Pets []Pet
}
type Pet2 struct {
ID int
MiddleName *string
DeletedAt *time.Time
}
type APIError struct {
ErrorCode int
ErrorMessage string
CreatedAt time.Time
}
type RevValueBase struct {
Status bool
Err int32
}
type RevValue struct {
RevValueBase
Data int
}
================================================
FILE: testdata/struct_comment/api/api.go
================================================
package api
import "net/http"
// @Summary Add a new pet to the store
// @Description get string by ID
// @Accept json
// @Produce json
// @Param post_id path int true "Some ID" Format(int64)
// @Success 200 {string} web.Post
// @Failure 400 {object} web.APIError "We need ID!!"
// @Failure 404 {object} web.APIError "Can not find ID"
// @Router /posts/{post_id} [get]
func GetPost(w http.ResponseWriter, r *http.Request) {
//write your code
}
================================================
FILE: testdata/struct_comment/main.go
================================================
package main
import (
"net/http"
"github.com/swaggo/swag/testdata/struct_comment/api"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server Petstore server.
// @host localhost:4000
// @basePath /api
func main() {
http.HandleFunc("/posts/", api.GetPost)
http.ListenAndServe(":8080", nil)
}
================================================
FILE: testdata/struct_comment/web/handler.go
================================================
package web
import (
"time"
)
type Post struct {
ID int `json:"id" example:"1" format:"int64"`
// Post name
Name string `json:"name" example:"poti"`
// Post data
Data struct {
// Post tag
Tag []string `json:"name"`
} `json:"data"`
}
// APIError
// @Description API error
// @Description with information about it
// Other some summary
type APIError struct {
// Error an Api error
Error string // Error this is Line comment
// Error `number` tick comment
ErrorNo int64
ErrorCtx string // Error `context` tick comment
CreatedAt time.Time // Error time
}
================================================
FILE: testdata/tags/apes.md
================================================
## Apes
Apes are very cool!
================================================
FILE: testdata/tags/api.md
================================================
## CoolApi Title
### Cool API SubTitle
We love markdown!
================================================
FILE: testdata/tags/cats.md
================================================
## Cats
Cats are also very cool!
================================================
FILE: testdata/tags/main.go
================================================
package main
// @description.markdown
// @tag.name dogs
// @tag.description Dogs are cool
// @tag.name cats
// @tag.description Cats are the devil
// @tag.docs.url https://google.de
// @tag.docs.description google is super useful to find out that cats are evil!
// @tag.name apes
// @tag.description Apes are also cool
func main() {}
================================================
FILE: testdata/tags2/apes.md
================================================
## Apes
Apes are very cool!
================================================
FILE: testdata/tags2/api.md
================================================
## CoolApi Title
### Cool API SubTitle
We love markdown!
================================================
FILE: testdata/tags2/main.go
================================================
package main
// @description.markdown
// @tag.name dogs
// @tag.description Dogs are cool
// @tag.name cats
// @tag.description Cats are the devil
// @tag.docs.url https://google.de
// @tag.docs.description google is super useful to find out that cats are evil!
// @tag.name apes
// @tag.description.markdown
func main() {}
================================================
FILE: testdata/tags_nonexistend_tag/apes.md
================================================
## Apes
Apes are very cool!
================================================
FILE: testdata/tags_nonexistend_tag/api.md
================================================
## CoolApi Title
### Cool API SubTitle
We love markdown!
================================================
FILE: testdata/tags_nonexistend_tag/main.go
================================================
package main
// @description.markdown
// @tag.name dogs
// @tag.description Dogs are cool
// @tag.name cats
// @tag.description Cats are the devil
// @tag.docs.url https://google.de
// @tag.docs.description google is super useful to find out that cats are evil!
// @tag.description.markdown
func main() {}
================================================
FILE: testdata/templated.go
================================================
package main
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationurl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationurl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api
// @x-google-endpoints [{"name":"name.endpoints.environment.cloud.goog","allowCors":true}]
// @x-google-marks "marks values"
func main() {}
================================================
FILE: testdata/users.md
================================================
Users Tag Markdown Description
================================================
FILE: types.go
================================================
package swag
import (
"go/ast"
"go/token"
"strings"
"github.com/go-openapi/spec"
)
// Schema parsed schema.
type Schema struct {
*spec.Schema //
PkgPath string // package import path used to rename Name of a definition int case of conflict
Name string // Name in definitions
}
// TypeSpecDef the whole information of a typeSpec.
type TypeSpecDef struct {
// ast file where TypeSpec is
File *ast.File
// the TypeSpec of this type definition
TypeSpec *ast.TypeSpec
Enums []EnumValue
// path of package starting from under ${GOPATH}/src or from module path in go.mod
PkgPath string
ParentSpec ast.Decl
SchemaName string
NotUnique bool
}
// Name the name of the typeSpec.
func (t *TypeSpecDef) Name() string {
if t.TypeSpec != nil && t.TypeSpec.Name != nil {
return t.TypeSpec.Name.Name
}
return ""
}
// TypeName the type name of the typeSpec.
func (t *TypeSpecDef) TypeName() string {
if ignoreNameOverride(t.TypeSpec.Name.Name) {
return t.TypeSpec.Name.Name[1:]
}
var names []string
if t.NotUnique {
pkgPath := strings.Map(func(r rune) rune {
if r == '\\' || r == '/' || r == '.' {
return '_'
}
return r
}, t.PkgPath)
names = append(names, pkgPath)
} else if t.File != nil {
names = append(names, t.File.Name.Name)
}
if parentFun, ok := (t.ParentSpec).(*ast.FuncDecl); ok && parentFun != nil {
names = append(names, parentFun.Name.Name)
}
names = append(names, t.TypeSpec.Name.Name)
return fullTypeName(names...)
}
// FullPath return the full path of the typeSpec.
func (t *TypeSpecDef) FullPath() string {
return t.PkgPath + "." + t.Name()
}
func (t *TypeSpecDef) Alias() string {
return nameOverride(t.TypeSpec.Comment)
}
func (t *TypeSpecDef) SetSchemaName() {
if alias := t.Alias(); alias != "" {
t.SchemaName = alias
return
}
t.SchemaName = t.TypeName()
}
// AstFileInfo information of an ast.File.
type AstFileInfo struct {
//FileSet the FileSet object which is used to parse this go source file
FileSet *token.FileSet
// File ast.File
File *ast.File
// Path the path of the ast.File
Path string
// PackagePath package import path of the ast.File
PackagePath string
// ParseFlag determine what to parse
ParseFlag ParseFlag
}
================================================
FILE: utils.go
================================================
package swag
import (
"strings"
"unicode"
)
// FieldsFunc split a string s by a func splitter into max n parts
func FieldsFunc(s string, f func(rune2 rune) bool, n int) []string {
// A span is used to record a slice of s of the form s[start:end].
// The start index is inclusive and the end index is exclusive.
type span struct {
start int
end int
}
spans := make([]span, 0, 32)
// Find the field start and end indices.
// Doing this in a separate pass (rather than slicing the string s
// and collecting the result substrings right away) is significantly
// more efficient, possibly due to cache effects.
start := -1 // valid span start if >= 0
for end, rune := range s {
if f(rune) {
if start >= 0 {
spans = append(spans, span{start, end})
// Set start to a negative value.
// Note: using -1 here consistently and reproducibly
// slows down this code by a several percent on amd64.
start = ^start
}
} else {
if start < 0 {
start = end
if n > 0 && len(spans)+1 >= n {
break
}
}
}
}
// Last field might end at EOF.
if start >= 0 {
spans = append(spans, span{start, len(s)})
}
// Create strings from recorded field indices.
a := make([]string, len(spans))
for i, span := range spans {
a[i] = s[span.start:span.end]
}
return a
}
// FieldsByAnySpace split a string s by any space character into max n parts
func FieldsByAnySpace(s string, n int) []string {
return FieldsFunc(s, unicode.IsSpace, n)
}
// AppendDescription appends a new string to the existing description, treating
// a trailing backslash as a line continuation.
func AppendDescription(current, addition string) string {
if strings.HasSuffix(current, "\\") {
return current[:len(current)-1] + addition
}
return current + "\n" + addition
}
================================================
FILE: utils_test.go
================================================
package swag
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFieldsByAnySpace(t *testing.T) {
type args struct {
s string
n int
}
tests := []struct {
name string
args args
want []string
}{
{"test1",
args{
" aa bb cc dd ff",
2,
},
[]string{"aa", "bb\tcc dd \t\tff"},
},
{"test2",
args{
` aa "bb cc dd ff"`,
2,
},
[]string{"aa", `"bb cc dd ff"`},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, FieldsByAnySpace(tt.args.s, tt.args.n), "FieldsByAnySpace(%v, %v)", tt.args.s, tt.args.n)
})
}
}
func TestAppendDescription(t *testing.T) {
type args struct {
current string
addition string
}
tests := []struct {
name string
args args
want string
}{
{"test1",
args{
"aa",
"bb",
},
"aa\nbb",
},
{"test2",
args{
"aa\\",
"bb",
},
"aabb",
},
{"test3",
args{
"aa \\",
"bb",
},
"aa bb",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, AppendDescription(tt.args.current, tt.args.addition), "AppendDescription(%v, %v)", tt.args.current, tt.args.addition)
})
}
}
================================================
FILE: version.go
================================================
package swag
// Version of swag.
const Version = "v1.16.7"