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)* [![Build Status](https://github.com/swaggo/swag/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/features/actions) [![Coverage Status](https://img.shields.io/codecov/c/github/swaggo/swag/master.svg)](https://codecov.io/gh/swaggo/swag) [![Go Report Card](https://goreportcard.com/badge/github.com/swaggo/swag)](https://goreportcard.com/report/github.com/swaggo/swag) [![Go Doc](https://godoc.org/github.com/swaggo/swagg?status.svg)](https://godoc.org/github.com/swaggo/swag) [![Backers on Open Collective](https://opencollective.com/swag/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/swag/sponsors/badge.svg)](#sponsors) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fswaggo%2Fswag.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fswaggo%2Fswag?ref=badge_shield) [![Release](https://img.shields.io/github/release/swaggo/swag.svg?style=flat-square)](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: ![swagger_index.html](https://raw.githubusercontent.com/swaggo/swag/master/assets/swagger-image.png) ## 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: 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 [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fswaggo%2Fswag.svg?type=large)](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)* [![Build Status](https://github.com/swaggo/swag/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/features/actions) [![Coverage Status](https://img.shields.io/codecov/c/github/swaggo/swag/master.svg)](https://codecov.io/gh/swaggo/swag) [![Go Report Card](https://goreportcard.com/badge/github.com/swaggo/swag)](https://goreportcard.com/report/github.com/swaggo/swag) [![codebeat badge](https://codebeat.co/badges/71e2f5e5-9e6b-405d-baf9-7cc8b5037330)](https://codebeat.co/projects/github-com-swaggo-swag-master) [![Go Doc](https://godoc.org/github.com/swaggo/swagg?status.svg)](https://godoc.org/github.com/swaggo/swag) [![Backers on Open Collective](https://opencollective.com/swag/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/swag/sponsors/badge.svg)](#sponsors) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fswaggo%2Fswag.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fswaggo%2Fswag?ref=badge_shield) [![Release](https://img.shields.io/github/release/swaggo/swag.svg?style=flat-square)](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: ![swagger_index.html](https://raw.githubusercontent.com/swaggo/swag/master/assets/swagger-image.png) ## 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 [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fswaggo%2Fswag.svg?type=large)](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)* [![Travis Status](https://img.shields.io/travis/swaggo/swag/master.svg)](https://travis-ci.org/swaggo/swag) [![Coverage Status](https://img.shields.io/codecov/c/github/swaggo/swag/master.svg)](https://codecov.io/gh/swaggo/swag) [![Go Report Card](https://goreportcard.com/badge/github.com/swaggo/swag)](https://goreportcard.com/report/github.com/swaggo/swag) [![codebeat badge](https://codebeat.co/badges/71e2f5e5-9e6b-405d-baf9-7cc8b5037330)](https://codebeat.co/projects/github-com-swaggo-swag-master) [![Go Doc](https://godoc.org/github.com/swaggo/swagg?status.svg)](https://godoc.org/github.com/swaggo/swag) [![Backers on Open Collective](https://opencollective.com/swag/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/swag/sponsors/badge.svg)](#sponsors) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fswaggo%2Fswag.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fswaggo%2Fswag?ref=badge_shield) [![Release](https://img.shields.io/github/release/swaggo/swag.svg?style=flat-square)](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文档,如下所示: ![swagger_index.html](https://raw.githubusercontent.com/swaggo/swag/master/assets/swagger-image.png) ## 格式化说明 可以针对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 [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fswaggo%2Fswag.svg?type=large)](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"