├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── DEVELOPMENT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── cmd ├── arguments │ ├── cli.go │ └── doc.const.go ├── entrypoint.go ├── generate.go ├── root.go └── version.go ├── common └── string.utils.go ├── definitions ├── helpers.go └── types.go ├── docs ├── ADVANCED.md ├── ANNOTATIONS.md ├── AUTHENTICATION.md ├── CONFIG.md ├── ERROR_HANDLING.md ├── MIDDLEWARES.md ├── STEPBYSTEP.md ├── VALIDATION.md └── routers │ ├── CHI_INTEGRATION.md │ ├── ECHO_INTEGRATION.md │ ├── FIBER_INTEGRATION.md │ ├── GIN_INTEGRATION.md │ └── MUX_INTEGRATION.md ├── e2e ├── assets │ ├── custom.validations.go │ ├── e2e.controller.go │ ├── openapi3.0.0.json │ └── openapi3.1.0.json ├── chi │ ├── assets │ │ ├── AfterOperationRoutesExtension.hbs │ │ ├── BeforeOperationRoutesExtension.hbs │ │ ├── FunctionDeclarationsExtension.hbs │ │ ├── ImportsExtension.hbs │ │ ├── JsonBodyValidationErrorResponseExtension.hbs │ │ ├── JsonErrorResponseExtension.hbs │ │ ├── JsonResponseExtension.hbs │ │ ├── ParamsValidationErrorResponseExtension.hbs │ │ ├── RegisterRoutesExtension.hbs │ │ ├── ResponseHeadersExtension.hbs │ │ ├── RouteEndRoutesExtension.hbs │ │ ├── RouteStartRoutesExtension.hbs │ │ ├── RunValidatorExtension.hbs │ │ ├── TypeDeclarationsExtension.hbs │ │ └── chi.custom.response.headers.hbs │ ├── auth │ │ └── chi_e2e_authorization.go │ ├── ex_extra_routes │ │ └── chi.e2e.ex_extra.gleece.go │ ├── middlewares │ │ └── chi.middlewares.go │ ├── openapi │ │ ├── openapi3.0.0.json │ │ └── openapi3.1.0.json │ ├── routes │ │ └── chi.e2e.gleece.go │ └── tester.go ├── common │ └── common.go ├── e2e.chi.gleece.config.json ├── e2e.echo.gleece.config.json ├── e2e.fiber.gleece.config.json ├── e2e.gin.gleece.config.json ├── e2e.mux.gleece.config.json ├── e2e_auth_test.go ├── e2e_controller_test.go ├── e2e_custumizing_test.go ├── e2e_errors_test.go ├── e2e_middlewares_test.go ├── e2e_models_test.go ├── e2e_routing_test.go ├── e2e_specification_test.go ├── echo │ ├── assets │ │ ├── AfterOperationRoutesExtension.hbs │ │ ├── BeforeOperationRoutesExtension.hbs │ │ ├── FunctionDeclarationsExtension.hbs │ │ ├── ImportsExtension.hbs │ │ ├── JsonBodyValidationErrorResponseExtension.hbs │ │ ├── JsonErrorResponseExtension.hbs │ │ ├── JsonResponseExtension.hbs │ │ ├── ParamsValidationErrorResponseExtension.hbs │ │ ├── RegisterRoutesExtension.hbs │ │ ├── ResponseHeadersExtension.hbs │ │ ├── RouteEndRoutesExtension.hbs │ │ ├── RouteStartRoutesExtension.hbs │ │ ├── RunValidatorExtension.hbs │ │ ├── TypeDeclarationsExtension.hbs │ │ └── echo.custom.response.headers.hbs │ ├── auth │ │ └── echo_e2e_authorization.go │ ├── ex_extra_routes │ │ └── echo.e2e.ex_extra.gleece.go │ ├── middlewares │ │ └── echo.middlewares.go │ ├── openapi │ │ ├── openapi3.0.0.json │ │ └── openapi3.1.0.json │ ├── routes │ │ └── echo.e2e.gleece.go │ └── tester.go ├── fiber │ ├── assets │ │ ├── AfterOperationRoutesExtension.hbs │ │ ├── BeforeOperationRoutesExtension.hbs │ │ ├── FunctionDeclarationsExtension.hbs │ │ ├── ImportsExtension.hbs │ │ ├── JsonBodyValidationErrorResponseExtension.hbs │ │ ├── JsonErrorResponseExtension.hbs │ │ ├── JsonResponseExtension.hbs │ │ ├── ParamsValidationErrorResponseExtension.hbs │ │ ├── RegisterRoutesExtension.hbs │ │ ├── ResponseHeadersExtension.hbs │ │ ├── RouteEndRoutesExtension.hbs │ │ ├── RouteStartRoutesExtension.hbs │ │ ├── RunValidatorExtension.hbs │ │ ├── TypeDeclarationsExtension.hbs │ │ └── fiber.custom.response.headers.hbs │ ├── auth │ │ └── fiber_e2e_authorization.go │ ├── ex_extra_routes │ │ └── fiber.e2e.ex_extra.gleece.go │ ├── middlewares │ │ └── fiber.middlewares.go │ ├── openapi │ │ ├── openapi3.0.0.json │ │ └── openapi3.1.0.json │ ├── routes │ │ └── fiber.e2e.gleece.go │ └── tester.go ├── gin │ ├── assets │ │ ├── AfterOperationRoutesExtension.hbs │ │ ├── BeforeOperationRoutesExtension.hbs │ │ ├── FunctionDeclarationsExtension.hbs │ │ ├── ImportsExtension.hbs │ │ ├── JsonBodyValidationErrorResponseExtension.hbs │ │ ├── JsonErrorResponseExtension.hbs │ │ ├── JsonResponseExtension.hbs │ │ ├── ParamsValidationErrorResponseExtension.hbs │ │ ├── RegisterRoutesExtension.hbs │ │ ├── ResponseHeadersExtension.hbs │ │ ├── RouteEndRoutesExtension.hbs │ │ ├── RouteStartRoutesExtension.hbs │ │ ├── RunValidatorExtension.hbs │ │ ├── TypeDeclarationsExtension.hbs │ │ └── gin.custom.response.headers.hbs │ ├── auth │ │ └── gin_e2e_authorization.go │ ├── ex_extra_routes │ │ └── gin.e2e.ex_extra.gleece.go │ ├── middlewares │ │ └── gin.middlewares.go │ ├── openapi │ │ ├── openapi3.0.0.json │ │ └── openapi3.1.0.json │ ├── routes │ │ └── gin.e2e.gleece.go │ └── tester.go ├── mux │ ├── assets │ │ ├── AfterOperationRoutesExtension.hbs │ │ ├── BeforeOperationRoutesExtension.hbs │ │ ├── FunctionDeclarationsExtension.hbs │ │ ├── ImportsExtension.hbs │ │ ├── JsonBodyValidationErrorResponseExtension.hbs │ │ ├── JsonErrorResponseExtension.hbs │ │ ├── JsonResponseExtension.hbs │ │ ├── ParamsValidationErrorResponseExtension.hbs │ │ ├── RegisterRoutesExtension.hbs │ │ ├── ResponseHeadersExtension.hbs │ │ ├── RouteEndRoutesExtension.hbs │ │ ├── RouteStartRoutesExtension.hbs │ │ ├── RunValidatorExtension.hbs │ │ ├── TypeDeclarationsExtension.hbs │ │ └── mux.custom.response.headers.hbs │ ├── auth │ │ └── mux_e2e_authorization.go │ ├── ex_extra_routes │ │ └── mux.e2e.ex_extra.gleece.go │ ├── middlewares │ │ └── mux.middlewares.go │ ├── openapi │ │ ├── openapi3.0.0.json │ │ └── openapi3.1.0.json │ ├── routes │ │ └── mux.e2e.gleece.go │ └── tester.go └── suite_test.go ├── extractor ├── annotations │ ├── annotation_validator.go │ ├── annotation_validator_test.go │ ├── annotations_test.go │ ├── helpers.go │ ├── holder.go │ └── suite_test.go ├── arbitrators │ ├── ast.arbitrator.go │ └── packages.facade.go ├── ast.utils.go ├── packages.abstraction.go └── visitors │ ├── controller │ ├── auxiliary.go │ ├── auxiliary_test.go │ ├── controller.go │ ├── diagnostics.go │ ├── models.go │ ├── public.go │ ├── route.go │ ├── route_test.go │ ├── security.go │ ├── suite_test.go │ └── visitor.go │ └── type.visitor.go ├── generator ├── compilation │ └── utils.go ├── routes │ ├── context.go │ ├── generator.go │ ├── generator_test.go │ └── template.helpers.go ├── swagen │ ├── spec_manager.go │ ├── spec_manager_test.go │ ├── suite_test.go │ ├── swagen30 │ │ ├── models_generator.go │ │ ├── models_generator_test.go │ │ ├── paths_generator.go │ │ ├── paths_generator_test.go │ │ ├── security_generator.go │ │ ├── security_generator_test.go │ │ ├── spec_common.go │ │ ├── spec_common_test.go │ │ ├── spec_generator.go │ │ ├── spec_generator_test.go │ │ ├── suite_test.go │ │ ├── validatation_converter.go │ │ └── validatation_converter_test.go │ ├── swagen31 │ │ ├── models_generator31.go │ │ ├── models_generator31_test.go │ │ ├── paths_generatorv31.go │ │ ├── security_generator31.go │ │ ├── security_generator31_test.go │ │ ├── spec_common31.go │ │ ├── spec_common31_test.go │ │ ├── spec_gnerator31.go │ │ ├── spec_gnerator31_test.go │ │ ├── suite_test.go │ │ ├── validatation_converter31_test.go │ │ └── validation_converter31.go │ └── swagtool │ │ ├── spec_helpers.go │ │ ├── spec_helpers_test.go │ │ ├── spec_utiles_test.go │ │ ├── spec_utils.go │ │ └── suite_test.go └── templates │ ├── chi │ ├── embeds.go │ ├── partials │ │ ├── authorization.call.hbs │ │ ├── function.declarations.hbs │ │ ├── imports.hbs │ │ ├── json.body.validation.error.response.hbs │ │ ├── json.error.response.hbs │ │ ├── json.response.hbs │ │ ├── method.parameter.list.hbs │ │ ├── middleware.hbs │ │ ├── params.validation.error.response.hbs │ │ ├── register.middleware.hbs │ │ ├── reply.response.hbs │ │ ├── request.args.parsing.hbs │ │ ├── request.switch.param.type.hbs │ │ ├── response.headers.hbs │ │ ├── run.validator.hbs │ │ └── type.declarations.hbs │ └── routes.hbs │ ├── echo │ ├── embeds.go │ ├── partials │ │ ├── authorization.call.hbs │ │ ├── function.declarations.hbs │ │ ├── imports.hbs │ │ ├── json.body.validation.error.response.hbs │ │ ├── json.error.response.hbs │ │ ├── json.response.hbs │ │ ├── method.parameter.list.hbs │ │ ├── middleware.hbs │ │ ├── params.validation.error.response.hbs │ │ ├── register.middleware.hbs │ │ ├── reply.response.hbs │ │ ├── request.args.parsing.hbs │ │ ├── request.switch.param.type.hbs │ │ ├── response.headers.hbs │ │ ├── run.validator.hbs │ │ └── type.declarations.hbs │ └── routes.hbs │ ├── fiber │ ├── embeds.go │ ├── partials │ │ ├── authorization.call.hbs │ │ ├── function.declarations.hbs │ │ ├── imports.hbs │ │ ├── json.body.validation.error.response.hbs │ │ ├── json.error.response.hbs │ │ ├── json.response.hbs │ │ ├── method.parameter.list.hbs │ │ ├── middleware.hbs │ │ ├── params.validation.error.response.hbs │ │ ├── register.middleware.hbs │ │ ├── reply.response.hbs │ │ ├── request.args.parsing.hbs │ │ ├── request.switch.param.type.hbs │ │ ├── response.headers.hbs │ │ ├── run.validator.hbs │ │ └── type.declarations.hbs │ └── routes.hbs │ ├── gin │ ├── embeds.go │ ├── partials │ │ ├── authorization.call.hbs │ │ ├── function.declarations.hbs │ │ ├── imports.hbs │ │ ├── json.body.validation.error.response.hbs │ │ ├── json.error.response.hbs │ │ ├── json.response.hbs │ │ ├── method.parameter.list.hbs │ │ ├── middleware.hbs │ │ ├── params.validation.error.response.hbs │ │ ├── register.middleware.hbs │ │ ├── reply.response.hbs │ │ ├── request.args.parsing.hbs │ │ ├── request.switch.param.type.hbs │ │ ├── response.headers.hbs │ │ ├── run.validator.hbs │ │ └── type.declarations.hbs │ └── routes.hbs │ └── mux │ ├── embeds.go │ ├── partials │ ├── authorization.call.hbs │ ├── function.declarations.hbs │ ├── imports.hbs │ ├── json.body.validation.error.response.hbs │ ├── json.error.response.hbs │ ├── json.response.hbs │ ├── method.parameter.list.hbs │ ├── middleware.hbs │ ├── params.validation.error.response.hbs │ ├── register.middleware.hbs │ ├── reply.response.hbs │ ├── request.args.parsing.hbs │ ├── request.switch.param.type.hbs │ ├── response.headers.hbs │ ├── run.validator.hbs │ └── type.declarations.hbs │ └── routes.hbs ├── gleece.config.json ├── go.mod ├── go.sum ├── infrastructure ├── logger │ ├── logger.go │ └── logger_test.go └── validation │ ├── validator.go │ └── validator_test.go ├── main.go └── test ├── commandline ├── commandline.controller.go ├── commandline_test.go ├── gleece.test.config.echo.json ├── gleece.test.config.gin.json └── gleece.test.config.json ├── context ├── context.controller.go ├── context_test.go └── gleece.test.config.json ├── errorhandling ├── dummy.controller.go ├── errorhandling_test.go ├── gleece.broken.json.config ├── gleece.broken.override.syntax.config.json ├── gleece.invalid.config.json ├── gleece.missing.extension.config.json ├── gleece.missing.partial.config.json ├── gleece.test.config.json ├── gleece.unknown.extension.config.json ├── gleece.unknown.partial.config.json ├── gleece.unscanned.types.json ├── imports.broken.hbs └── unscanned.type..controller.go ├── errors ├── errors.controller.go ├── errors_test.go └── gleece.test.config.json ├── fixtures ├── authorization.go ├── basic.controller.go └── custom.validations.go ├── imports ├── gleece.test.config.json ├── imports.controller.go └── imports_test.go ├── sanity ├── gleece.test.config.json ├── sanity.controller.go └── sanity_test.go ├── security ├── gleece.test.config.json ├── security.controller.go └── security_test.go ├── types └── basic.types.go ├── units ├── ast │ └── ast.utils_test.go ├── compilation │ └── compilation.utils_test.go ├── helpers │ └── helper.utils_test.go └── units.test.types.go ├── utils └── test.utils.go └── visitors ├── configs ├── receiver.with.invalid.json.json ├── receiver.with.no.comments.json ├── receiver.with.no.method.annotation.json ├── receiver.with.no.route.annotation.json ├── receiver.with.non.error.return.json └── receiver.with.void.return.json ├── controllers ├── receiver.with.invalid.json.go ├── receiver.with.no.comments.go ├── receiver.with.no.method.annotation.go ├── receiver.with.no.route.annotation.go ├── receiver.with.non.error.return.go └── receiver.with.void.return.go └── visitor_test.go /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Gleece CI/CD ⚙️ 2 | 3 | on: [push, workflow_dispatch] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: 1️⃣ Checkout repository 🛎️ 11 | uses: actions/checkout@v4 12 | 13 | # Cache Go modules 14 | - name: Cache Go modules 🗄️ 15 | uses: actions/cache@v3 16 | with: 17 | path: | 18 | ~/go/pkg/mod 19 | ~/.cache/go-build 20 | ~/go/bin 21 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 22 | restore-keys: | 23 | ${{ runner.os }}-go- 24 | 25 | - name: 2️⃣ Set up Go 🚀 26 | uses: actions/setup-go@v5 27 | with: 28 | go-version: '1.23.3' 29 | 30 | - name: 3️⃣ Install dependencies 📦 31 | run: | 32 | go mod download 33 | go install github.com/onsi/ginkgo/v2/ginkgo@v2.22.2 34 | go install github.com/mattn/goveralls@latest 35 | 36 | - name: 4️⃣ Run tests 🧪 37 | if: github.ref != 'refs/heads/main' 38 | run: | 39 | ginkgo ./... 40 | 41 | - name: 5️⃣ Send coverage 📊 42 | if: github.ref == 'refs/heads/main' 43 | env: 44 | COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | run: | 46 | mkdir dist 47 | go test -covermode atomic -coverpkg=$(go list ./... | grep -vE "/test/|/e2e/" | tr '\n' ',') -coverprofile=dist/covprofile ./... 48 | goveralls -coverprofile=dist/covprofile -service=github 49 | 50 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 On Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | notify-pkg-go-dev: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: 📥 Checkout repository 13 | uses: actions/checkout@v2 14 | 15 | - name: 🔧 Set up Go 16 | uses: actions/setup-go@v3 17 | with: 18 | go-version: '1.20' 19 | 20 | - name: 🏷️ Get version from release tag 21 | id: update_version 22 | run: | 23 | VERSION=$(echo "${GITHUB_REF}" | sed 's/refs\/tags\///') 24 | echo "VERSION=${VERSION}" >> $GITHUB_ENV 25 | 26 | - name: 🔔 Notify pkg.go.dev 27 | run: | 28 | curl -sSf https://proxy.golang.org/github.com/gopher-fleece/gleece/@v/${{ env.VERSION }}.info || echo "Failed to notify pkg.go.dev" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | 27 | dist/ 28 | temp/ 29 | 30 | __debug* 31 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug Main", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "debug", 12 | "program": "${workspaceFolder}/main.go", 13 | "args": [], 14 | "buildFlags": "", 15 | "env": {}, 16 | }, 17 | { 18 | "name": "Debug Ginkgo", 19 | "type": "go", 20 | "request": "launch", 21 | "mode": "test", 22 | "program": "${file}", 23 | "args": [], 24 | "env": { 25 | "GINKGO_DEBUG": "true", 26 | "GOMAXPROCS": "1" 27 | }, 28 | "showLog": true 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Gleece", 4 | "Struct" 5 | ] 6 | } -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | ## Step-by-Step Guide 2 | 3 | ### Step 1: Install Dependencies 4 | 5 | First, you need to install the dependencies for your Go project. You can do this using the following command: 6 | 7 | ```bash 8 | go mod download 9 | ``` 10 | 11 | This command will download all the dependencies specified in your go.mod file. 12 | 13 | ### Step 2: Install ginkgo Testing Tool 14 | 15 | Next, you need to install the ginkgo testing tool. ginkgo is a popular testing framework for Go. You can install it by running: 16 | ```bash 17 | go install github.com/onsi/ginkgo/v2/ginkgo@latest 18 | ``` 19 | This command will install the latest version of `ginkgo`. 20 | 21 | ### Step 3: Run All Tests 22 | 23 | Once you have ginkgo installed, you can run all the tests in your project using: 24 | ```bash 25 | ginkgo ./... 26 | ``` 27 | This command will execute all the tests in your project directory and its subdirectories. 28 | 29 | ### Step 4: Build CLI 30 | ```bash 31 | go build 32 | ``` 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Haim Kastner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 6 | 7 | For any suspected vulnerability or security-related concerns, please contact us at [security@gleece.dev](mailto:security@gleece.dev?subject=[Gleece%20Security%20Report]) 8 | 9 | Please provide as much information as you can, including: 10 | 11 | * General information about the issue such as its type (e.g., validation bypass, privilege escalation, etc.), 12 | its estimated impact, and potential attack vectors 13 | * Your version of *Gleece* (CLI & runtime) 14 | * The type of router engine (e.g., *gin*, *echo*, etc.) you're using (if relevant) 15 | * Step-by-step reproduction instructions, including any relevant configurations 16 | * A proof-of-concept showcasing exploitation of the issue, if possible 17 | 18 | Vulnerability disclosures will receive a reply as soon as possible. If the vulnerability is confirmed, we will address it with the highest priority. 19 | 20 | Once fixed, we will open an official pull request and would be happy to publicly acknowledge your contribution (with your permission). -------------------------------------------------------------------------------- /cmd/arguments/cli.go: -------------------------------------------------------------------------------- 1 | package arguments 2 | 3 | type CliArguments struct { 4 | ConfigPath string 5 | 6 | // The debug logger's verbosity level. Must be a value between 0 (All) and 5 (Fatal only). Default - 4 (Error/Fatal) 7 | Verbosity uint8 8 | 9 | NoBanner bool 10 | } 11 | 12 | type ExecuteWithArgsResult struct { 13 | Error error 14 | StdOut string 15 | StdErr string 16 | Logs string 17 | } 18 | -------------------------------------------------------------------------------- /cmd/generate.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/gopher-fleece/gleece/infrastructure/logger" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var generateCmd = &cobra.Command{ 11 | Use: "generate spec-and-routes --config \"/path/to/gleece.config.json\"", 12 | Short: "Generate OpenAPI schema and routing middlewares from a Go project", 13 | Run: func(cmd *cobra.Command, args []string) { 14 | }, 15 | } 16 | 17 | var specCommand = &cobra.Command{ 18 | Use: "spec", 19 | Short: "Generates an OpenAPI schema from the codebase using the specified configuration file", 20 | Run: func(cmd *cobra.Command, args []string) { 21 | err := GenerateSpec(cliArgs) 22 | if err != nil { 23 | logger.Fatal("Failed to generate spec: %v", err) 24 | os.Exit(1) 25 | } 26 | }, 27 | } 28 | 29 | var routesCommand = &cobra.Command{ 30 | Use: "routes", 31 | Short: "Generates a routing middleware file from the codebase using the specified configuration file", 32 | Run: func(cmd *cobra.Command, args []string) { 33 | err := GenerateRoutes(cliArgs) 34 | if err != nil { 35 | logger.Fatal("Failed to generate routes: %v", err) 36 | os.Exit(1) 37 | } 38 | }, 39 | } 40 | 41 | var specAndRoutesCommand = &cobra.Command{ 42 | Use: "spec-and-routes", 43 | Short: "Generates an OpenAPI schema and a routing middleware file from the codebase using the specified configuration file", 44 | Run: func(cmd *cobra.Command, args []string) { 45 | err := GenerateSpecAndRoutes(cliArgs) 46 | if err != nil { 47 | logger.Fatal("Failed to generate spec and routes: %v", err) 48 | os.Exit(1) 49 | } 50 | }, 51 | } 52 | 53 | func initGenerateCommandHierarchy() { 54 | generateCmd.PersistentFlags().StringVarP( 55 | &cliArgs.ConfigPath, 56 | "config", 57 | "c", 58 | "./gleece.config.json", 59 | "/project-directory/gleece.config.json", 60 | ) 61 | 62 | generateCmd.AddCommand(specCommand) 63 | generateCmd.AddCommand(routesCommand) 64 | generateCmd.AddCommand(specAndRoutesCommand) 65 | } 66 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var ( 10 | Version string 11 | BuildDate string 12 | Commit string 13 | TargetOs string 14 | TargetArch string 15 | ) 16 | 17 | // versionCmd represents the version command 18 | var versionCmd = &cobra.Command{ 19 | Use: "version", 20 | Short: "Display Gleece's version and build information", 21 | Long: "", 22 | Run: func(cmd *cobra.Command, args []string) { 23 | out := cmd.OutOrStdout() 24 | fmt.Fprintln(out, "Gleece") 25 | fmt.Fprintf(out, "Version: %s\n", Version) 26 | fmt.Fprintf(out, "Build Date: %s\n", BuildDate) 27 | fmt.Fprintf(out, "Commit: %s\n", Commit) 28 | fmt.Fprintf(out, "Target architecture: %s\n", TargetOs) 29 | fmt.Fprintf(out, "Target platform: %s\n", TargetArch) 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /common/string.utils.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | var multiSlashRegex = *regexp.MustCompile(`/+`) 9 | 10 | // UnwrapArrayTypeString Unwraps type strings like "[]string" to "string". 11 | // Works iteratively ('Recursive' is name is for clarity) to unwrap nested arrays as well. 12 | // For example, "[][][]string" will be unwrapped to "string". 13 | func UnwrapArrayTypeString(value string) string { 14 | resultValue := value 15 | for { 16 | newValue := strings.TrimPrefix(resultValue, "[]") 17 | if len(resultValue) == len(newValue) { 18 | break 19 | } 20 | resultValue = newValue 21 | } 22 | return resultValue 23 | } 24 | 25 | func RemoveDuplicateSlash(value string) string { 26 | return multiSlashRegex.ReplaceAllString(value, "/") 27 | } 28 | -------------------------------------------------------------------------------- /docs/ADVANCED.md: -------------------------------------------------------------------------------- 1 | The content moved to https://docs.gleece.dev/docs/category/extras -------------------------------------------------------------------------------- /docs/ANNOTATIONS.md: -------------------------------------------------------------------------------- 1 | The content moved to https://docs.gleece.dev/docs/extras/annotations -------------------------------------------------------------------------------- /docs/AUTHENTICATION.md: -------------------------------------------------------------------------------- 1 | The content moved to https://docs.gleece.dev/docs/basics/authorization -------------------------------------------------------------------------------- /docs/CONFIG.md: -------------------------------------------------------------------------------- 1 | The content moved to https://docs.gleece.dev/docs/extras/configuration -------------------------------------------------------------------------------- /docs/ERROR_HANDLING.md: -------------------------------------------------------------------------------- 1 | The content moved to https://docs.gleece.dev/docs/basics/errors -------------------------------------------------------------------------------- /docs/MIDDLEWARES.md: -------------------------------------------------------------------------------- 1 | The content moved to https://docs.gleece.dev/docs/basics/middlewares -------------------------------------------------------------------------------- /docs/STEPBYSTEP.md: -------------------------------------------------------------------------------- 1 | The content moved to https://docs.gleece.dev/docs/intro -------------------------------------------------------------------------------- /docs/VALIDATION.md: -------------------------------------------------------------------------------- 1 | The content moved to https://docs.gleece.dev/docs/basics/validations -------------------------------------------------------------------------------- /docs/routers/CHI_INTEGRATION.md: -------------------------------------------------------------------------------- 1 | The content moved to https://docs.gleece.dev/docs/routers/chi -------------------------------------------------------------------------------- /docs/routers/ECHO_INTEGRATION.md: -------------------------------------------------------------------------------- 1 | The content moved to https://docs.gleece.dev/docs/routers/echo -------------------------------------------------------------------------------- /docs/routers/FIBER_INTEGRATION.md: -------------------------------------------------------------------------------- 1 | The content moved to https://docs.gleece.dev/docs/routers/fiber -------------------------------------------------------------------------------- /docs/routers/GIN_INTEGRATION.md: -------------------------------------------------------------------------------- 1 | The content moved to https://docs.gleece.dev/docs/routers/gin -------------------------------------------------------------------------------- /docs/routers/MUX_INTEGRATION.md: -------------------------------------------------------------------------------- 1 | The content moved to https://docs.gleece.dev/docs/routers/g_mux -------------------------------------------------------------------------------- /e2e/assets/custom.validations.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "unicode" 5 | 6 | "github.com/gopher-fleece/runtime" 7 | ) 8 | 9 | // Custom validation function to check if a string starts with a letter 10 | func ValidateStartsWithLetter(fl runtime.ValidationFieldLevel) bool { 11 | field := fl.Field().String() 12 | if field == "" { 13 | return false 14 | } 15 | firstChar := rune(field[0]) 16 | return unicode.IsLetter(firstChar) 17 | } 18 | -------------------------------------------------------------------------------- /e2e/chi/assets/AfterOperationRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-AfterOperationRoutesExtension", "{{{ OperationId }}}") -------------------------------------------------------------------------------- /e2e/chi/assets/BeforeOperationRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-BeforeOperationRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/chi/assets/FunctionDeclarationsExtension.hbs: -------------------------------------------------------------------------------- 1 | // FunctionDeclarationsExtension - test -------------------------------------------------------------------------------- /e2e/chi/assets/ImportsExtension.hbs: -------------------------------------------------------------------------------- 1 | // ImportsExtension - test -------------------------------------------------------------------------------- /e2e/chi/assets/JsonBodyValidationErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-JsonBodyValidationErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/chi/assets/JsonErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-JsonErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/chi/assets/JsonResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-JsonResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/chi/assets/ParamsValidationErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-ParamsValidationErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/chi/assets/RegisterRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | // RegisterRoutesExtension - test -------------------------------------------------------------------------------- /e2e/chi/assets/ResponseHeadersExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-ResponseHeadersExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/chi/assets/RouteEndRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-RouteEndRoutesExtension", "{{{ OperationId }}}") 2 | 3 | {{#if TemplateContext.MODE }} 4 | w.Header().Set("x-mode", "{{{ TemplateContext.MODE.Options.mode }}}") 5 | {{/if }} 6 | {{#if TemplateContext.LEVEL }} 7 | w.Header().Set("x-level", "{{{ TemplateContext.LEVEL.Options.value }}}") 8 | {{/if }} 9 | -------------------------------------------------------------------------------- /e2e/chi/assets/RouteStartRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-RouteStartRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/chi/assets/RunValidatorExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-RunValidatorExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/chi/assets/TypeDeclarationsExtension.hbs: -------------------------------------------------------------------------------- 1 | // TypeDeclarationsExtension - test -------------------------------------------------------------------------------- /e2e/chi/assets/chi.custom.response.headers.hbs: -------------------------------------------------------------------------------- 1 | for key, value := range controller.GetHeaders() { 2 | w.Header().Set(key, value) 3 | } 4 | w.Header().Set("x-inject", "true") -------------------------------------------------------------------------------- /e2e/chi/auth/chi_e2e_authorization.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/gopher-fleece/gleece/e2e/assets" 9 | "github.com/gopher-fleece/runtime" 10 | ) 11 | 12 | func GleeceRequestAuthorization(ctx context.Context, r *http.Request, check runtime.SecurityCheck) (context.Context, *runtime.SecurityError) { 13 | finalCtx := context.WithValue(ctx, assets.ContextAuth, "123") 14 | // A WA to set the header for the test with the given LAST run scope 15 | r.Header.Set("x-test-scopes", check.SchemaName+check.Scopes[0]) 16 | // Simulate auth failed 17 | authCode := 401 18 | 19 | failCodeStr := r.Header.Get("fail-code") 20 | if failCodeStr != "" { 21 | num, _ := strconv.Atoi(failCodeStr) 22 | authCode = num 23 | } 24 | 25 | if r.Header.Get("fail-auth") == check.SchemaName { 26 | return finalCtx, &runtime.SecurityError{ 27 | Message: "Failed to authorize", 28 | StatusCode: runtime.HttpStatusCode(authCode), 29 | } 30 | } 31 | 32 | // Simulate auth failed with custom error 33 | if r.Header.Get("fail-auth-custom") == check.SchemaName { 34 | return finalCtx, &runtime.SecurityError{ 35 | Message: "Failed to authorize", 36 | StatusCode: runtime.HttpStatusCode(authCode), 37 | CustomError: &runtime.CustomError{ 38 | Payload: struct { 39 | Message string `json:"message"` 40 | Description string `json:"description"` 41 | }{ 42 | Message: "Custom error message", 43 | Description: "Custom error description", 44 | }, 45 | }, 46 | } 47 | } 48 | return finalCtx, nil 49 | } 50 | -------------------------------------------------------------------------------- /e2e/chi/tester.go: -------------------------------------------------------------------------------- 1 | package chi 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "net/url" 9 | "strings" 10 | "unicode" 11 | 12 | "github.com/go-chi/chi/v5" 13 | 14 | "github.com/gopher-fleece/gleece/e2e/common" 15 | ) 16 | 17 | var ChiRouter *chi.Mux 18 | var ChiExExtraRouter *chi.Mux 19 | 20 | func ChiRouterTest(routerTest common.RouterTest) common.RouterTestResult { 21 | // Create a response recorder 22 | w := httptest.NewRecorder() 23 | queryParams := url.Values{} 24 | formParams := url.Values{} 25 | 26 | path := routerTest.Path 27 | 28 | // Add query parameters 29 | if routerTest.Query != nil { 30 | for k, v := range routerTest.Query { 31 | queryParams.Add(k, v) 32 | } 33 | path += "?" + queryParams.Encode() 34 | } 35 | 36 | var req *http.Request 37 | 38 | // Handle form data 39 | if routerTest.Form != nil { 40 | // Convert form data to url.Values 41 | for k, v := range routerTest.Form { 42 | formParams.Add(k, v) 43 | } 44 | // Create request with form data 45 | req = httptest.NewRequest(routerTest.Method, path, strings.NewReader(formParams.Encode())) 46 | // Set content type for form data 47 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 48 | } else if routerTest.Body != nil { 49 | // Handle JSON body 50 | jsonData, _ := json.Marshal(routerTest.Body) 51 | req = httptest.NewRequest(routerTest.Method, path, bytes.NewBuffer(jsonData)) 52 | req.Header.Set("Content-Type", "application/json") 53 | } else { 54 | // No body or form data 55 | req = httptest.NewRequest(routerTest.Method, path, nil) 56 | } 57 | 58 | // Add headers to the request 59 | if routerTest.Headers != nil { 60 | for k, v := range routerTest.Headers { 61 | req.Header.Add(strings.ToLower(k), v) 62 | } 63 | } 64 | 65 | // Use Chi router to serve the request 66 | switch *routerTest.RunningMode { 67 | case common.RunOnVanillaRoutes: 68 | ChiExExtraRouter.ServeHTTP(w, req) 69 | case common.RunOnFullyFeaturedRoutes: 70 | ChiRouter.ServeHTTP(w, req) 71 | default: 72 | return common.RouterTestResult{} 73 | } 74 | 75 | // Convert response headers to map[string]string 76 | headers := make(map[string]string) 77 | for k, v := range w.Header() { 78 | if len(v) > 0 { 79 | headers[strings.ToLower(k)] = v[0] 80 | } 81 | } 82 | 83 | bodyRes := w.Body.String() 84 | if bodyRes != "" { 85 | bodyRes = strings.TrimRightFunc(bodyRes, unicode.IsSpace) 86 | } 87 | return common.RouterTestResult{ 88 | Code: w.Code, 89 | Body: bodyRes, 90 | Headers: headers, 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /e2e/common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type RunningMode string 4 | 5 | const ( 6 | // Run test on fully featured routes only (where all features are activated. g.e. middleware, templates execution & override, etc) 7 | RunOnFullyFeaturedRoutes RunningMode = "RunOnFullyFeaturedRoutes" 8 | // Run test on vanilla routes only same as default but without any extra features 9 | RunOnVanillaRoutes RunningMode = "RunOnVanillaRoutes" 10 | // Run test on all routes (fully featured and vanilla) 11 | RunOnAllRoutes RunningMode = "RunOnAllRoutes" 12 | ) 13 | 14 | type RouterTest struct { 15 | Name string 16 | Path string 17 | Method string 18 | Body any 19 | Query map[string]string 20 | Headers map[string]string 21 | Form map[string]string 22 | ExpectedStatus int 23 | ExpectedBody string 24 | ExpectedBodyContain string 25 | ExpendedHeaders map[string]string 26 | RunningMode *RunningMode 27 | } 28 | 29 | type RouterTestResult struct { 30 | Code int 31 | Body string 32 | Headers map[string]string 33 | } 34 | -------------------------------------------------------------------------------- /e2e/e2e_specification_test.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | _ "embed" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/gopher-fleece/gleece/definitions" 9 | "github.com/gopher-fleece/gleece/generator/swagen/swagtool" 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | //go:embed assets/openapi3.0.0.json 15 | var expectedOpenapi300 string 16 | 17 | //go:embed assets/openapi3.1.0.json 18 | var expectedOpenapi310 string 19 | 20 | func GetEngineSpecification(engine string, path string) string { 21 | // Read the file as a string 22 | content, _ := os.ReadFile(fmt.Sprintf("./%s/%s", engine, path)) 23 | return string(content) 24 | } 25 | 26 | var _ = Describe("E2E Specification", func() { 27 | 28 | It("Should generate a valid 3.0.0 specification", func() { 29 | for _, engine := range definitions.SupportedRoutingEngineStrings { 30 | spec := GetEngineSpecification(engine, "openapi/openapi3.0.0.json") 31 | areEqual, _ := swagtool.AreJSONsIdentical([]byte(spec), []byte(expectedOpenapi300)) 32 | Expect(areEqual).To(BeTrueBecause( 33 | "Test for engine '%s' in version 3.0.0 yielded a difference between expected and generated spec", 34 | engine, 35 | )) 36 | } 37 | }) 38 | 39 | It("Should generate a valid 3.1.0 specification", func() { 40 | for _, engine := range definitions.SupportedRoutingEngineStrings { 41 | spec := GetEngineSpecification(engine, "openapi/openapi3.1.0.json") 42 | areEqual, _ := swagtool.AreJSONsIdentical([]byte(spec), []byte(expectedOpenapi310)) 43 | Expect(areEqual).To(BeTrueBecause( 44 | "Test for engine '%s' in version 3.1.0 yielded a difference between expected and generated spec", 45 | engine, 46 | )) 47 | } 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /e2e/echo/assets/AfterOperationRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | echoCtx.Response().Header().Set("x-AfterOperationRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/echo/assets/BeforeOperationRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | echoCtx.Response().Header().Set("x-BeforeOperationRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/echo/assets/FunctionDeclarationsExtension.hbs: -------------------------------------------------------------------------------- 1 | // FunctionDeclarationsExtension - test -------------------------------------------------------------------------------- /e2e/echo/assets/ImportsExtension.hbs: -------------------------------------------------------------------------------- 1 | // ImportsExtension - test -------------------------------------------------------------------------------- /e2e/echo/assets/JsonBodyValidationErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | echoCtx.Response().Header().Set("x-JsonBodyValidationErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/echo/assets/JsonErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | echoCtx.Response().Header().Set("x-JsonErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/echo/assets/JsonResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | echoCtx.Response().Header().Set("x-JsonResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/echo/assets/ParamsValidationErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | echoCtx.Response().Header().Set("x-ParamsValidationErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/echo/assets/RegisterRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | // RegisterRoutesExtension - test -------------------------------------------------------------------------------- /e2e/echo/assets/ResponseHeadersExtension.hbs: -------------------------------------------------------------------------------- 1 | echoCtx.Response().Header().Set("x-ResponseHeadersExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/echo/assets/RouteEndRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | echoCtx.Response().Header().Set("x-RouteEndRoutesExtension", "{{{ OperationId }}}") 2 | 3 | {{#if TemplateContext.MODE }} 4 | echoCtx.Response().Header().Set("x-mode", "{{{ TemplateContext.MODE.Options.mode }}}") 5 | {{/if }} 6 | {{#if TemplateContext.LEVEL }} 7 | echoCtx.Response().Header().Set("x-level", "{{{ TemplateContext.LEVEL.Options.value }}}") 8 | {{/if }} 9 | -------------------------------------------------------------------------------- /e2e/echo/assets/RouteStartRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | echoCtx.Response().Header().Set("x-RouteStartRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/echo/assets/RunValidatorExtension.hbs: -------------------------------------------------------------------------------- 1 | echoCtx.Response().Header().Set("x-RunValidatorExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/echo/assets/TypeDeclarationsExtension.hbs: -------------------------------------------------------------------------------- 1 | // TypeDeclarationsExtension - test -------------------------------------------------------------------------------- /e2e/echo/assets/echo.custom.response.headers.hbs: -------------------------------------------------------------------------------- 1 | for key, value := range controller.GetHeaders() { 2 | echoCtx.Response().Header().Set(key, value) 3 | } 4 | echoCtx.Response().Header().Set("x-inject", "true") -------------------------------------------------------------------------------- /e2e/echo/auth/echo_e2e_authorization.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | 7 | "github.com/gopher-fleece/gleece/e2e/assets" 8 | "github.com/gopher-fleece/runtime" 9 | "github.com/labstack/echo/v4" 10 | ) 11 | 12 | func GleeceRequestAuthorization(ctx context.Context, echoCtx echo.Context, check runtime.SecurityCheck) (context.Context, *runtime.SecurityError) { 13 | finalCtx := context.WithValue(ctx, assets.ContextAuth, "123") 14 | 15 | // A WA to set the header for the test with the given LAST run scope 16 | echoCtx.Request().Header.Set("x-test-scopes", check.SchemaName+check.Scopes[0]) 17 | // Simulate auth failed 18 | authCode := 401 19 | 20 | failCodeStr := echoCtx.Request().Header.Get("fail-code") 21 | if failCodeStr != "" { 22 | num, _ := strconv.Atoi(failCodeStr) 23 | authCode = num 24 | } 25 | 26 | if echoCtx.Request().Header.Get("fail-auth") == check.SchemaName { 27 | return finalCtx, &runtime.SecurityError{ 28 | Message: "Failed to authorize", 29 | StatusCode: runtime.HttpStatusCode(authCode), 30 | } 31 | } 32 | 33 | // Simulate auth failed with custom error 34 | if echoCtx.Request().Header.Get("fail-auth-custom") == check.SchemaName { 35 | return finalCtx, &runtime.SecurityError{ 36 | Message: "Failed to authorize", 37 | StatusCode: runtime.HttpStatusCode(authCode), 38 | CustomError: &runtime.CustomError{ 39 | Payload: struct { 40 | Message string `json:"message"` 41 | Description string `json:"description"` 42 | }{ 43 | Message: "Custom error message", 44 | Description: "Custom error description", 45 | }, 46 | }, 47 | } 48 | } 49 | return finalCtx, nil 50 | } 51 | -------------------------------------------------------------------------------- /e2e/echo/tester.go: -------------------------------------------------------------------------------- 1 | package echo 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "net/url" 9 | "strings" 10 | "unicode" 11 | 12 | "github.com/gopher-fleece/gleece/e2e/common" 13 | "github.com/labstack/echo/v4" 14 | ) 15 | 16 | var EchoRouter *echo.Echo 17 | var EchoExExtraRouter *echo.Echo 18 | 19 | func EchoRouterTest(routerTest common.RouterTest) common.RouterTestResult { 20 | // Create a response recorder 21 | w := httptest.NewRecorder() 22 | queryParams := url.Values{} 23 | formParams := url.Values{} 24 | 25 | path := routerTest.Path 26 | 27 | // Add query parameters 28 | if routerTest.Query != nil { 29 | for k, v := range routerTest.Query { 30 | queryParams.Add(k, v) 31 | } 32 | path += "?" + queryParams.Encode() 33 | } 34 | 35 | var req *http.Request 36 | 37 | // Handle form data 38 | if routerTest.Form != nil { 39 | // Convert form data to url.Values 40 | for k, v := range routerTest.Form { 41 | formParams.Add(k, v) 42 | } 43 | // Create request with form data 44 | req = httptest.NewRequest(routerTest.Method, path, strings.NewReader(formParams.Encode())) 45 | // Set content type for form data 46 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 47 | } else if routerTest.Body != nil { 48 | // Handle JSON body 49 | jsonData, _ := json.Marshal(routerTest.Body) 50 | req = httptest.NewRequest(routerTest.Method, path, bytes.NewBuffer(jsonData)) 51 | req.Header.Set("Content-Type", "application/json") 52 | } else { 53 | // No body or form data 54 | req = httptest.NewRequest(routerTest.Method, path, nil) 55 | } 56 | 57 | // Add headers to the request 58 | if routerTest.Headers != nil { 59 | for k, v := range routerTest.Headers { 60 | req.Header.Add(strings.ToLower(k), v) 61 | } 62 | } 63 | 64 | switch *routerTest.RunningMode { 65 | case common.RunOnVanillaRoutes: 66 | EchoExExtraRouter.ServeHTTP(w, req) 67 | case common.RunOnFullyFeaturedRoutes: 68 | EchoRouter.ServeHTTP(w, req) 69 | default: 70 | return common.RouterTestResult{} 71 | } 72 | 73 | // Convert response headers to map[string]string 74 | headers := make(map[string]string) 75 | for k, v := range w.Header() { 76 | if len(v) > 0 { 77 | headers[strings.ToLower(k)] = v[0] 78 | } 79 | } 80 | 81 | bodyRes := w.Body.String() 82 | if bodyRes != "" { 83 | bodyRes = strings.TrimRightFunc(bodyRes, unicode.IsSpace) 84 | } 85 | 86 | return common.RouterTestResult{ 87 | Code: w.Code, 88 | Body: bodyRes, 89 | Headers: headers, 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /e2e/fiber/assets/AfterOperationRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | fiberCtx.Set("x-AfterOperationRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/fiber/assets/BeforeOperationRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | fiberCtx.Set("x-BeforeOperationRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/fiber/assets/FunctionDeclarationsExtension.hbs: -------------------------------------------------------------------------------- 1 | // FunctionDeclarationsExtension - test -------------------------------------------------------------------------------- /e2e/fiber/assets/ImportsExtension.hbs: -------------------------------------------------------------------------------- 1 | // ImportsExtension - test -------------------------------------------------------------------------------- /e2e/fiber/assets/JsonBodyValidationErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | fiberCtx.Set("x-JsonBodyValidationErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/fiber/assets/JsonErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | fiberCtx.Set("x-JsonErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/fiber/assets/JsonResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | fiberCtx.Set("x-JsonResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/fiber/assets/ParamsValidationErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | fiberCtx.Set("x-ParamsValidationErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/fiber/assets/RegisterRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | // RegisterRoutesExtension - test -------------------------------------------------------------------------------- /e2e/fiber/assets/ResponseHeadersExtension.hbs: -------------------------------------------------------------------------------- 1 | fiberCtx.Set("x-ResponseHeadersExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/fiber/assets/RouteEndRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | fiberCtx.Set("x-RouteEndRoutesExtension", "{{{ OperationId }}}") 2 | 3 | {{#if TemplateContext.MODE }} 4 | fiberCtx.Set("x-mode", "{{{ TemplateContext.MODE.Options.mode }}}") 5 | {{/if }} 6 | {{#if TemplateContext.LEVEL }} 7 | fiberCtx.Set("x-level", "{{{ TemplateContext.LEVEL.Options.value }}}") 8 | {{/if }} 9 | -------------------------------------------------------------------------------- /e2e/fiber/assets/RouteStartRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | fiberCtx.Set("x-RouteStartRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/fiber/assets/RunValidatorExtension.hbs: -------------------------------------------------------------------------------- 1 | fiberCtx.Set("x-RunValidatorExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/fiber/assets/TypeDeclarationsExtension.hbs: -------------------------------------------------------------------------------- 1 | // TypeDeclarationsExtension - test -------------------------------------------------------------------------------- /e2e/fiber/assets/fiber.custom.response.headers.hbs: -------------------------------------------------------------------------------- 1 | for key, value := range controller.GetHeaders() { 2 | fiberCtx.Set(key, value) 3 | } 4 | fiberCtx.Set("x-inject", "true") -------------------------------------------------------------------------------- /e2e/fiber/auth/fiber_e2e_authorization.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | 7 | "github.com/gofiber/fiber/v2" 8 | "github.com/gopher-fleece/gleece/e2e/assets" 9 | "github.com/gopher-fleece/runtime" 10 | ) 11 | 12 | func GleeceRequestAuthorization(ctx context.Context, fiberCtx *fiber.Ctx, check runtime.SecurityCheck) (context.Context, *runtime.SecurityError) { 13 | finalCtx := context.WithValue(ctx, assets.ContextAuth, "123") 14 | 15 | // Set the header for the test with the given LAST run scope. 16 | // Fiber gives you access to the underlying fasthttp request. 17 | fiberCtx.Request().Header.Set("x-test-scopes", check.SchemaName+check.Scopes[0]) 18 | 19 | // Simulate auth failed 20 | authCode := 401 21 | 22 | // Retrieve the "fail-code" header. Convert the byte slice to string. 23 | failCodeStr := string(fiberCtx.Request().Header.Peek("fail-code")) 24 | if failCodeStr != "" { 25 | num, _ := strconv.Atoi(failCodeStr) 26 | authCode = num 27 | } 28 | 29 | // Check if the "fail-auth" header equals the schema name in the check. 30 | if string(fiberCtx.Request().Header.Peek("fail-auth")) == check.SchemaName { 31 | return finalCtx, &runtime.SecurityError{ 32 | Message: "Failed to authorize", 33 | StatusCode: runtime.HttpStatusCode(authCode), 34 | } 35 | } 36 | 37 | // Simulate auth failed with a custom error 38 | if string(fiberCtx.Request().Header.Peek("fail-auth-custom")) == check.SchemaName { 39 | return finalCtx, &runtime.SecurityError{ 40 | Message: "Failed to authorize", 41 | StatusCode: runtime.HttpStatusCode(authCode), 42 | CustomError: &runtime.CustomError{ 43 | Payload: struct { 44 | Message string `json:"message"` 45 | Description string `json:"description"` 46 | }{ 47 | Message: "Custom error message", 48 | Description: "Custom error description", 49 | }, 50 | }, 51 | } 52 | } 53 | 54 | return finalCtx, nil 55 | } 56 | -------------------------------------------------------------------------------- /e2e/gin/assets/AfterOperationRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | ginCtx.Header("x-AfterOperationRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/gin/assets/BeforeOperationRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | ginCtx.Header("x-BeforeOperationRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/gin/assets/FunctionDeclarationsExtension.hbs: -------------------------------------------------------------------------------- 1 | // FunctionDeclarationsExtension - test -------------------------------------------------------------------------------- /e2e/gin/assets/ImportsExtension.hbs: -------------------------------------------------------------------------------- 1 | // ImportsExtension - test -------------------------------------------------------------------------------- /e2e/gin/assets/JsonBodyValidationErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | ginCtx.Header("x-JsonBodyValidationErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/gin/assets/JsonErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | ginCtx.Header("x-JsonErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/gin/assets/JsonResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | ginCtx.Header("x-JsonResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/gin/assets/ParamsValidationErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | ginCtx.Header("x-ParamsValidationErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/gin/assets/RegisterRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | // RegisterRoutesExtension - test -------------------------------------------------------------------------------- /e2e/gin/assets/ResponseHeadersExtension.hbs: -------------------------------------------------------------------------------- 1 | ginCtx.Header("x-ResponseHeadersExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/gin/assets/RouteEndRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | ginCtx.Header("x-RouteEndRoutesExtension", "{{{ OperationId }}}") 2 | 3 | {{#if TemplateContext.MODE }} 4 | ginCtx.Header("x-mode", "{{{ TemplateContext.MODE.Options.mode }}}") 5 | {{/if }} 6 | {{#if TemplateContext.LEVEL }} 7 | ginCtx.Header("x-level", "{{{ TemplateContext.LEVEL.Options.value }}}") 8 | {{/if }} 9 | -------------------------------------------------------------------------------- /e2e/gin/assets/RouteStartRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | ginCtx.Header("x-RouteStartRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/gin/assets/RunValidatorExtension.hbs: -------------------------------------------------------------------------------- 1 | ginCtx.Header("x-RunValidatorExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/gin/assets/TypeDeclarationsExtension.hbs: -------------------------------------------------------------------------------- 1 | // TypeDeclarationsExtension - test -------------------------------------------------------------------------------- /e2e/gin/assets/gin.custom.response.headers.hbs: -------------------------------------------------------------------------------- 1 | for key, value := range controller.GetHeaders() { 2 | ginCtx.Header(key, value) 3 | } 4 | ginCtx.Header("x-inject", "true") -------------------------------------------------------------------------------- /e2e/gin/auth/gin_e2e_authorization.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/gopher-fleece/gleece/e2e/assets" 9 | "github.com/gopher-fleece/runtime" 10 | ) 11 | 12 | func GleeceRequestAuthorization(ctx context.Context, ginCtx *gin.Context, check runtime.SecurityCheck) (context.Context, *runtime.SecurityError) { 13 | 14 | finalCtx := context.WithValue(ctx, assets.ContextAuth, "123") 15 | 16 | // A WA to set the header for the test with the given LAST run scope 17 | ginCtx.Request.Header.Set("x-test-scopes", check.SchemaName+check.Scopes[0]) 18 | // Simulate auth failed 19 | authCode := 401 20 | 21 | failCodeStr := ginCtx.GetHeader("fail-code") 22 | if failCodeStr != "" { 23 | num, _ := strconv.Atoi(failCodeStr) 24 | authCode = num 25 | } 26 | 27 | if ginCtx.GetHeader("fail-auth") == check.SchemaName { 28 | return finalCtx, &runtime.SecurityError{ 29 | Message: "Failed to authorize", 30 | StatusCode: runtime.HttpStatusCode(authCode), 31 | } 32 | } 33 | 34 | // Simulate auth failed with custom error 35 | if ginCtx.GetHeader("fail-auth-custom") == check.SchemaName { 36 | return finalCtx, &runtime.SecurityError{ 37 | Message: "Failed to authorize", 38 | StatusCode: runtime.HttpStatusCode(authCode), 39 | CustomError: &runtime.CustomError{ 40 | Payload: struct { 41 | Message string `json:"message"` 42 | Description string `json:"description"` 43 | }{ 44 | Message: "Custom error message", 45 | Description: "Custom error description", 46 | }, 47 | }, 48 | } 49 | } 50 | return finalCtx, nil 51 | } 52 | -------------------------------------------------------------------------------- /e2e/gin/tester.go: -------------------------------------------------------------------------------- 1 | package gin 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "net/url" 9 | "strings" 10 | 11 | "github.com/gin-gonic/gin" 12 | "github.com/gopher-fleece/gleece/e2e/common" 13 | ) 14 | 15 | var GinRouter *gin.Engine 16 | var GinExExtraRouter *gin.Engine 17 | 18 | func GinRouterTest(routerTest common.RouterTest) common.RouterTestResult { 19 | w := httptest.NewRecorder() 20 | queryParams := url.Values{} 21 | formParams := url.Values{} 22 | 23 | path := routerTest.Path 24 | 25 | // Handle query parameters 26 | if routerTest.Query != nil { 27 | for k, v := range routerTest.Query { 28 | queryParams.Add(k, v) 29 | } 30 | path += "?" + queryParams.Encode() 31 | } 32 | 33 | var req *http.Request 34 | 35 | // Handle form data 36 | if routerTest.Form != nil { 37 | // Convert form data to url.Values 38 | for k, v := range routerTest.Form { 39 | formParams.Add(k, v) 40 | } 41 | // Create request with form data 42 | req = httptest.NewRequest(routerTest.Method, path, strings.NewReader(formParams.Encode())) 43 | // Set content type for form data 44 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 45 | } else if routerTest.Body != nil { 46 | // Handle JSON body 47 | jsonData, _ := json.Marshal(routerTest.Body) 48 | req = httptest.NewRequest(routerTest.Method, path, bytes.NewBuffer(jsonData)) 49 | req.Header.Set("Content-Type", "application/json") 50 | } else { 51 | // No body or form data 52 | req = httptest.NewRequest(routerTest.Method, path, nil) 53 | } 54 | 55 | // Add custom headers 56 | if routerTest.Headers != nil { 57 | for k, v := range routerTest.Headers { 58 | req.Header.Add(strings.ToLower(k), v) 59 | } 60 | } 61 | 62 | switch *routerTest.RunningMode { 63 | case common.RunOnVanillaRoutes: 64 | GinExExtraRouter.ServeHTTP(w, req) 65 | case common.RunOnFullyFeaturedRoutes: 66 | GinRouter.ServeHTTP(w, req) 67 | default: 68 | return common.RouterTestResult{} 69 | } 70 | 71 | // Convert response headers to map[string]string 72 | headers := make(map[string]string) 73 | for k, v := range w.Header() { 74 | if len(v) > 0 { 75 | headers[strings.ToLower(k)] = v[0] 76 | } 77 | } 78 | 79 | return common.RouterTestResult{ 80 | Code: w.Code, 81 | Body: w.Body.String(), 82 | Headers: headers, 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /e2e/mux/assets/AfterOperationRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-AfterOperationRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/mux/assets/BeforeOperationRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-BeforeOperationRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/mux/assets/FunctionDeclarationsExtension.hbs: -------------------------------------------------------------------------------- 1 | // FunctionDeclarationsExtension - test -------------------------------------------------------------------------------- /e2e/mux/assets/ImportsExtension.hbs: -------------------------------------------------------------------------------- 1 | // ImportsExtension - test -------------------------------------------------------------------------------- /e2e/mux/assets/JsonBodyValidationErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-JsonBodyValidationErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/mux/assets/JsonErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-JsonErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/mux/assets/JsonResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-JsonResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/mux/assets/ParamsValidationErrorResponseExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-ParamsValidationErrorResponseExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/mux/assets/RegisterRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | // RegisterRoutesExtension - test -------------------------------------------------------------------------------- /e2e/mux/assets/ResponseHeadersExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-ResponseHeadersExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/mux/assets/RouteEndRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-RouteEndRoutesExtension", "{{{ OperationId }}}") 2 | 3 | {{#if TemplateContext.MODE }} 4 | w.Header().Set("x-mode", "{{{ TemplateContext.MODE.Options.mode }}}") 5 | {{/if }} 6 | {{#if TemplateContext.LEVEL }} 7 | w.Header().Set("x-level", "{{{ TemplateContext.LEVEL.Options.value }}}") 8 | {{/if }} 9 | -------------------------------------------------------------------------------- /e2e/mux/assets/RouteStartRoutesExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-RouteStartRoutesExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/mux/assets/RunValidatorExtension.hbs: -------------------------------------------------------------------------------- 1 | w.Header().Set("x-RunValidatorExtension", "{{{ OperationId }}}") 2 | -------------------------------------------------------------------------------- /e2e/mux/assets/TypeDeclarationsExtension.hbs: -------------------------------------------------------------------------------- 1 | // TypeDeclarationsExtension - test -------------------------------------------------------------------------------- /e2e/mux/assets/mux.custom.response.headers.hbs: -------------------------------------------------------------------------------- 1 | for key, value := range controller.GetHeaders() { 2 | w.Header().Set(key, value) 3 | } 4 | w.Header().Set("x-inject", "true") -------------------------------------------------------------------------------- /e2e/mux/auth/mux_e2e_authorization.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/gopher-fleece/gleece/e2e/assets" 9 | "github.com/gopher-fleece/runtime" 10 | ) 11 | 12 | func GleeceRequestAuthorization(ctx context.Context, r *http.Request, check runtime.SecurityCheck) (context.Context, *runtime.SecurityError) { 13 | finalCtx := context.WithValue(ctx, assets.ContextAuth, "123") 14 | 15 | // A WA to set the header for the test with the given LAST run scope 16 | r.Header.Set("x-test-scopes", check.SchemaName+check.Scopes[0]) 17 | // Simulate auth failed 18 | authCode := 401 19 | 20 | failCodeStr := r.Header.Get("fail-code") 21 | if failCodeStr != "" { 22 | num, _ := strconv.Atoi(failCodeStr) 23 | authCode = num 24 | } 25 | 26 | if r.Header.Get("fail-auth") == check.SchemaName { 27 | return finalCtx, &runtime.SecurityError{ 28 | Message: "Failed to authorize", 29 | StatusCode: runtime.HttpStatusCode(authCode), 30 | } 31 | } 32 | 33 | // Simulate auth failed with custom error 34 | if r.Header.Get("fail-auth-custom") == check.SchemaName { 35 | return finalCtx, &runtime.SecurityError{ 36 | Message: "Failed to authorize", 37 | StatusCode: runtime.HttpStatusCode(authCode), 38 | CustomError: &runtime.CustomError{ 39 | Payload: struct { 40 | Message string `json:"message"` 41 | Description string `json:"description"` 42 | }{ 43 | Message: "Custom error message", 44 | Description: "Custom error description", 45 | }, 46 | }, 47 | } 48 | } 49 | return finalCtx, nil 50 | } 51 | -------------------------------------------------------------------------------- /e2e/mux/tester.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "net/url" 9 | "strings" 10 | "unicode" 11 | 12 | "github.com/gopher-fleece/gleece/e2e/common" 13 | "github.com/gorilla/mux" 14 | ) 15 | 16 | var MuxRouter *mux.Router 17 | var MuxExExtraRouter *mux.Router 18 | 19 | func MuxRouterTest(routerTest common.RouterTest) common.RouterTestResult { 20 | // Create a response recorder 21 | w := httptest.NewRecorder() 22 | queryParams := url.Values{} 23 | formParams := url.Values{} 24 | 25 | path := routerTest.Path 26 | 27 | // Add query parameters 28 | if routerTest.Query != nil { 29 | for k, v := range routerTest.Query { 30 | queryParams.Add(k, v) 31 | } 32 | path += "?" + queryParams.Encode() 33 | } 34 | 35 | var req *http.Request 36 | 37 | // Handle form data 38 | if routerTest.Form != nil { 39 | // Convert form data to url.Values 40 | for k, v := range routerTest.Form { 41 | formParams.Add(k, v) 42 | } 43 | // Create request with form data 44 | req = httptest.NewRequest(routerTest.Method, path, strings.NewReader(formParams.Encode())) 45 | // Set content type for form data 46 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 47 | } else if routerTest.Body != nil { 48 | // Handle JSON body 49 | jsonData, _ := json.Marshal(routerTest.Body) 50 | req = httptest.NewRequest(routerTest.Method, path, bytes.NewBuffer(jsonData)) 51 | req.Header.Set("Content-Type", "application/json") 52 | } else { 53 | // No body or form data 54 | req = httptest.NewRequest(routerTest.Method, path, nil) 55 | } 56 | 57 | // Add headers to the request 58 | if routerTest.Headers != nil { 59 | for k, v := range routerTest.Headers { 60 | req.Header.Add(strings.ToLower(k), v) 61 | } 62 | } 63 | 64 | // Use Mux router to serve the request 65 | switch *routerTest.RunningMode { 66 | case common.RunOnVanillaRoutes: 67 | MuxExExtraRouter.ServeHTTP(w, req) 68 | case common.RunOnFullyFeaturedRoutes: 69 | MuxRouter.ServeHTTP(w, req) 70 | default: 71 | return common.RouterTestResult{} 72 | } 73 | 74 | // Convert response headers to map[string]string 75 | headers := make(map[string]string) 76 | for k, v := range w.Header() { 77 | if len(v) > 0 { 78 | headers[strings.ToLower(k)] = v[0] 79 | } 80 | } 81 | 82 | bodyRes := w.Body.String() 83 | if bodyRes != "" { 84 | bodyRes = strings.TrimRightFunc(bodyRes, unicode.IsSpace) 85 | } 86 | return common.RouterTestResult{ 87 | Code: w.Code, 88 | Body: bodyRes, 89 | Headers: headers, 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /extractor/annotations/helpers.go: -------------------------------------------------------------------------------- 1 | package annotations 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func getSliceProperty[TPropertyType any](value *any, targetType reflect.Type) (*TPropertyType, error) { 9 | // Ensure the value is also a slice 10 | if reflect.TypeOf(*value).Kind() != reflect.Slice { 11 | return nil, fmt.Errorf("value %v cannot be converted to type %s", value, targetType.String()) 12 | } 13 | 14 | sourceSlice := reflect.ValueOf(*value) 15 | targetElemType := targetType.Elem() 16 | 17 | // Create a new slice of the target type 18 | convertedSlice := reflect.MakeSlice(targetType, sourceSlice.Len(), sourceSlice.Len()) 19 | 20 | // Iterate through the source slice and convert each element 21 | for i := 0; i < sourceSlice.Len(); i++ { 22 | sourceElem := sourceSlice.Index(i).Interface() 23 | sourceElemValue := reflect.ValueOf(sourceElem) 24 | 25 | // Check if the source element can be converted to the target element type 26 | if !sourceElemValue.Type().ConvertibleTo(targetElemType) { 27 | return nil, fmt.Errorf("element %v at index %d cannot be converted to type %s", sourceElem, i, targetElemType.String()) 28 | } 29 | 30 | // Convert the source element and set it in the target slice 31 | convertedElem := sourceElemValue.Convert(targetElemType) 32 | convertedSlice.Index(i).Set(convertedElem) 33 | } 34 | 35 | // Return the converted slice as the desired type 36 | converted := convertedSlice.Interface().(TPropertyType) 37 | return &converted, nil 38 | } 39 | 40 | func GetCastProperty[TPropertyType any](attrib *Attribute, property string) (*TPropertyType, error) { 41 | value := attrib.GetProperty(property) 42 | if value == nil { 43 | return nil, nil 44 | } 45 | 46 | targetType := reflect.TypeOf((*TPropertyType)(nil)).Elem() 47 | if targetType.Kind() == reflect.Slice { 48 | return getSliceProperty[TPropertyType](value, targetType) 49 | } 50 | 51 | castParam, castOk := (*value).(TPropertyType) 52 | if castOk { 53 | return &castParam, nil 54 | } 55 | 56 | return nil, fmt.Errorf("property '%s' exists but cannot be cast to %s", property, targetType.Name()) 57 | } 58 | -------------------------------------------------------------------------------- /extractor/annotations/suite_test.go: -------------------------------------------------------------------------------- 1 | package annotations_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gopher-fleece/gleece/infrastructure/logger" 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | func TestAnnotations(t *testing.T) { 12 | logger.SetLogLevel(logger.LogLevelNone) 13 | RegisterFailHandler(Fail) 14 | RunSpecs(t, "Annotations") 15 | } 16 | -------------------------------------------------------------------------------- /extractor/packages.abstraction.go: -------------------------------------------------------------------------------- 1 | package extractor 2 | 3 | import ( 4 | "fmt" 5 | "go/types" 6 | 7 | "golang.org/x/tools/go/packages" 8 | ) 9 | 10 | func DoesStructEmbedType(pkg *packages.Package, structName string, embeddedStructFullPackage string, embeddedStructName string) (bool, error) { 11 | // Look for the struct definition in the package 12 | obj := pkg.Types.Scope().Lookup(structName) 13 | if obj == nil { 14 | return false, fmt.Errorf("struct '%s' not found in package '%s'", structName, pkg.PkgPath) 15 | } 16 | 17 | // Ensure it's a named type (i.e., a struct) 18 | named, ok := obj.Type().(*types.Named) 19 | if !ok { 20 | return false, fmt.Errorf("type '%s' is not a named type", structName) 21 | } 22 | 23 | // Get the underlying struct type 24 | structType, ok := named.Underlying().(*types.Struct) 25 | if !ok { 26 | return false, fmt.Errorf("type '%s' is not a struct", structName) 27 | } 28 | 29 | // Check if any field in the struct embeds the target type 30 | var expectedFullyQualifiedName string 31 | if len(embeddedStructFullPackage) > 0 { 32 | expectedFullyQualifiedName = embeddedStructFullPackage + "." + embeddedStructName 33 | } else { 34 | expectedFullyQualifiedName = embeddedStructName 35 | } 36 | 37 | for i := 0; i < structType.NumFields(); i++ { 38 | field := structType.Field(i) 39 | if field.Embedded() { 40 | // Check if the field type matches the embedded struct we are looking for 41 | fieldType := field.Type().String() 42 | // Check if the type matches the embedded struct (full package path + type name) 43 | if fieldType == expectedFullyQualifiedName { 44 | return true, nil 45 | } 46 | } 47 | } 48 | 49 | return false, nil 50 | } 51 | -------------------------------------------------------------------------------- /extractor/visitors/controller/auxiliary_test.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gopher-fleece/gleece/definitions" 5 | "github.com/gopher-fleece/gleece/extractor/annotations" 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("Auxiliary Tests", func() { 11 | var visitor ControllerVisitor 12 | 13 | BeforeEach(func() { 14 | visitor = ControllerVisitor{} 15 | }) 16 | 17 | Context("when processing template context attributes", func() { 18 | It("should successfully create template context map", func() { 19 | attributes, _ := annotations.NewAnnotationHolder([]string{ 20 | "// @TemplateContext(testcontext, {key: \"value\"}) Test description", 21 | }, annotations.CommentSourceRoute) 22 | 23 | // Act 24 | result, err := visitor.getTemplateContextMetadata(&attributes) 25 | 26 | // Assert 27 | Expect(err).To(BeNil()) 28 | Expect(result).To(HaveLen(1)) 29 | Expect(result["testcontext"]).To(Equal(definitions.TemplateContext{ 30 | Options: map[string]any{"key": "value"}, 31 | Description: "Test description", 32 | })) 33 | }) 34 | 35 | It("should return error on duplicate template contexts", func() { 36 | // Arrange 37 | 38 | attributes, _ := annotations.NewAnnotationHolder([]string{ 39 | "// @TemplateContext(duplicatecontext, {key1: \"value1\"}) First description", 40 | "// @TemplateContext(duplicatecontext, {key2: \"value2\"}) Second description", 41 | }, annotations.CommentSourceRoute) 42 | 43 | // Act 44 | result, err := visitor.getTemplateContextMetadata(&attributes) 45 | 46 | // Assert 47 | Expect(err).To(HaveOccurred()) 48 | Expect(err.Error()).To(ContainSubstring("Duplicate template context attribute")) 49 | Expect(result).To(BeNil()) 50 | }) 51 | 52 | It("should handle empty attributes", func() { 53 | // Arrange 54 | attributes, _ := annotations.NewAnnotationHolder([]string{}, annotations.CommentSourceRoute) 55 | 56 | // Act 57 | result, err := visitor.getTemplateContextMetadata(&attributes) 58 | 59 | // Assert 60 | Expect(err).To(BeNil()) 61 | Expect(result).To(BeEmpty()) 62 | }) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /extractor/visitors/controller/diagnostics.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/gopher-fleece/gleece/infrastructure/logger" 8 | ) 9 | 10 | // enter records a stackframe and message in the diagnostic stack that can be used for 11 | // debugging and/or printing failures in a clear way. 12 | // 13 | // Note that this method does nothing if the diagnostic stack is frozen 14 | func (v *ControllerVisitor) enter(message string) { 15 | if v.stackFrozen { 16 | return 17 | } 18 | 19 | var formattedMessage string 20 | if len(message) > 0 { 21 | formattedMessage = fmt.Sprintf("- (%s)", message) 22 | } 23 | 24 | pc, file, line, ok := runtime.Caller(1) 25 | if !ok { 26 | v.diagnosticStack = append(v.diagnosticStack, fmt.Sprintf(". - %s", formattedMessage)) 27 | logger.Warn("Could not determine caller for diagnostic message %s", formattedMessage) 28 | return 29 | } 30 | 31 | fn := runtime.FuncForPC(pc) 32 | if fn == nil { 33 | v.diagnosticStack = append(v.diagnosticStack, fmt.Sprintf("%s:%d - %s", file, line, formattedMessage)) 34 | logger.Warn("Could not determine caller function for diagnostic message %s", formattedMessage) 35 | return 36 | } 37 | 38 | v.diagnosticStack = append(v.diagnosticStack, fmt.Sprintf("%s\n\t\t%s:%d%s", fn.Name(), file, line, formattedMessage)) 39 | } 40 | 41 | // exit pops the diagnostic stack to indicate the function returned successfully. 42 | // 43 | // Note that this method does nothing if the diagnostic stack is frozen 44 | func (v *ControllerVisitor) exit() { 45 | if !v.stackFrozen && len(v.diagnosticStack) > 0 { 46 | v.diagnosticStack = v.diagnosticStack[:len(v.diagnosticStack)-1] 47 | } 48 | } 49 | 50 | // getFrozenError is an idempotent function that creates an error from the given format string and arguments 51 | // and freezes the visitor's diagnostic stack. 52 | func (v *ControllerVisitor) getFrozenError(format string, args ...any) error { 53 | v.stackFrozen = true 54 | return fmt.Errorf(format, args...) 55 | } 56 | 57 | // frozenError is an idempotent function that returns the provided error unmodified 58 | // and freezes the visitor's diagnostic stack. 59 | func (v *ControllerVisitor) frozenError(err error) error { 60 | // Just a convenient way to freeze the diagnostic stack while returning the same error 61 | v.stackFrozen = true 62 | return err 63 | } 64 | -------------------------------------------------------------------------------- /extractor/visitors/controller/models.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import "github.com/gopher-fleece/gleece/definitions" 4 | 5 | func (v *ControllerVisitor) addToTypeMap( 6 | existingTypesMap *map[string]string, 7 | existingModels *[]definitions.TypeMetadata, 8 | typeMeta definitions.TypeMetadata, 9 | ) error { 10 | if typeMeta.IsUniverseType { 11 | return nil 12 | } 13 | 14 | existsInPackage, exists := (*existingTypesMap)[typeMeta.Name] 15 | if exists { 16 | if existsInPackage == typeMeta.FullyQualifiedPackage { 17 | // Same type referenced from a separate location 18 | return nil 19 | } 20 | 21 | return v.getFrozenError( 22 | "type '%s' exists in more that one package (%s and %s). This is not currently supported", 23 | typeMeta.Name, 24 | typeMeta.FullyQualifiedPackage, 25 | existsInPackage, 26 | ) 27 | } 28 | 29 | (*existingTypesMap)[typeMeta.Name] = typeMeta.FullyQualifiedPackage 30 | (*existingModels) = append((*existingModels), typeMeta) 31 | return nil 32 | } 33 | 34 | func (v *ControllerVisitor) insertRouteTypeList( 35 | existingTypesMap *map[string]string, 36 | existingModels *[]definitions.TypeMetadata, 37 | route *definitions.RouteMetadata, 38 | ) (bool, error) { 39 | 40 | plainErrorEncountered := false 41 | for _, param := range route.FuncParams { 42 | if param.TypeMeta.IsUniverseType && param.TypeMeta.Name == "error" && param.TypeMeta.FullyQualifiedPackage == "" { 43 | // Mark whether we've encountered any 'error' type 44 | plainErrorEncountered = true 45 | } 46 | err := v.addToTypeMap(existingTypesMap, existingModels, param.TypeMeta) 47 | if err != nil { 48 | return plainErrorEncountered, v.frozenError(err) 49 | } 50 | } 51 | 52 | for _, param := range route.Responses { 53 | if param.IsUniverseType && param.Name == "error" && param.FullyQualifiedPackage == "" { 54 | // Mark whether we've encountered any 'error' type 55 | plainErrorEncountered = true 56 | } 57 | err := v.addToTypeMap(existingTypesMap, existingModels, param.TypeMetadata) 58 | if err != nil { 59 | return plainErrorEncountered, v.frozenError(err) 60 | } 61 | } 62 | 63 | return plainErrorEncountered, nil 64 | } 65 | -------------------------------------------------------------------------------- /extractor/visitors/controller/suite_test.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gopher-fleece/gleece/infrastructure/logger" 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | func TestControllerVisitorModule(t *testing.T) { 12 | // Disable logging to reduce clutter. 13 | logger.SetLogLevel(logger.LogLevelNone) 14 | RegisterFailHandler(Fail) 15 | RunSpecs(t, "Controller Visitor Suite") 16 | } 17 | -------------------------------------------------------------------------------- /generator/compilation/utils.go: -------------------------------------------------------------------------------- 1 | package compilation 2 | 3 | import ( 4 | "fmt" 5 | "go/format" 6 | "strings" 7 | 8 | "golang.org/x/tools/imports" 9 | ) 10 | 11 | func OptimizeImportsAndFormat(sourceCode string) (string, error) { 12 | // Use imports.Process to optimize imports and format the code 13 | optSource, err := imports.Process("", []byte(sourceCode), nil) 14 | if err != nil { 15 | return "", fmt.Errorf("failed to optimize imports: %w", err) 16 | } 17 | 18 | // Ensure the code is properly formatted 19 | formattedSource, err := format.Source(optSource) 20 | if err != nil { 21 | return "", fmt.Errorf("failed to format source code: %w", err) 22 | } 23 | 24 | collapsed := []byte(strings.ReplaceAll(string(formattedSource), "\n\n", "\n")) 25 | return string(collapsed), nil 26 | } 27 | -------------------------------------------------------------------------------- /generator/routes/context.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gopher-fleece/gleece/definitions" 7 | ) 8 | 9 | type Argument struct { 10 | Type definitions.ParamPassedIn 11 | Name string 12 | ValueType any 13 | } 14 | 15 | type RouteCtx struct { 16 | definitions.RouteMetadata 17 | } 18 | 19 | type ControllerMeta struct { 20 | Routes []RouteCtx 21 | } 22 | 23 | type PackageImport struct { 24 | FullPath string 25 | Alias string 26 | } 27 | 28 | type RoutesContext struct { 29 | PackageName string 30 | Controllers []definitions.ControllerMetadata 31 | GenerationDate string 32 | AuthConfig definitions.AuthorizationConfig 33 | ValidateResponsePayload bool 34 | ExperimentalConfig definitions.ExperimentalConfig 35 | Models definitions.Models 36 | } 37 | 38 | func GetTemplateContext( 39 | models *definitions.Models, 40 | config *definitions.GleeceConfig, 41 | controllers []definitions.ControllerMetadata, 42 | ) (RoutesContext, error) { 43 | 44 | if models == nil { 45 | models = &definitions.Models{ 46 | Structs: make([]definitions.StructMetadata, 0), 47 | Enums: make([]definitions.EnumMetadata, 0), 48 | } 49 | } 50 | 51 | ctx := RoutesContext{ 52 | Controllers: controllers, 53 | AuthConfig: config.RoutesConfig.AuthorizationConfig, 54 | ValidateResponsePayload: config.RoutesConfig.ValidateResponsePayload, 55 | ExperimentalConfig: config.ExperimentalConfig, 56 | Models: *models, 57 | } 58 | if len(config.RoutesConfig.PackageName) > 0 { 59 | ctx.PackageName = config.RoutesConfig.PackageName 60 | } else { 61 | ctx.PackageName = "routes" 62 | } 63 | 64 | ctx.GenerationDate = time.Now().Format(time.DateOnly) 65 | 66 | return ctx, nil 67 | } 68 | -------------------------------------------------------------------------------- /generator/swagen/spec_manager.go: -------------------------------------------------------------------------------- 1 | package swagen 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/gopher-fleece/gleece/definitions" 9 | "github.com/gopher-fleece/gleece/generator/swagen/swagen30" 10 | "github.com/gopher-fleece/gleece/generator/swagen/swagen31" 11 | "github.com/gopher-fleece/gleece/generator/swagen/swagtool" 12 | "github.com/gopher-fleece/gleece/infrastructure/logger" 13 | ) 14 | 15 | // GenerateSpec generates the OpenAPI specification 16 | func GenerateSpec(config *definitions.OpenAPIGeneratorConfig, defs []definitions.ControllerMetadata, models *definitions.Models, hasAnyErrorTypes bool) ([]byte, error) { 17 | // In case of a default error in use, add the RFC-7807, otherwise skip and assume the user define it using structs by themselves 18 | swagtool.AppendErrorSchema(&models.Structs, hasAnyErrorTypes) 19 | 20 | // Since the tools and validation are WAY better for 3.0.0, 21 | // And our logic his focusing in 3.0 and not feature will be added if not can be support in it too, 22 | // We will use it any way for validation and alignment 23 | specV300, err := swagen30.GenerateSpec(config, defs, models) 24 | 25 | // In case of error, abort 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | switch config.OpenAPI { 31 | case "3.0.0": 32 | return specV300, nil // In case of 3.0.0, we already have the spec 33 | case "3.1.0": 34 | return swagen31.GenerateSpec(config, defs, models) 35 | default: 36 | return nil, fmt.Errorf("Unsupported OpenAPI version: %s", config.OpenAPI) 37 | } 38 | } 39 | 40 | func GenerateAndOutputSpec(config *definitions.OpenAPIGeneratorConfig, defs []definitions.ControllerMetadata, models *definitions.Models, hasAnyErrorTypes bool) error { 41 | jsonBytes, err := GenerateSpec(config, defs, models, hasAnyErrorTypes) 42 | 43 | if err != nil { 44 | return err 45 | } 46 | 47 | // Extract path from file path 48 | // Extract the directory path 49 | dirPath := filepath.Dir(config.SpecGeneratorConfig.OutputPath) 50 | // Create the output directory if it doesn't exist 51 | if err := os.MkdirAll(dirPath, os.ModePerm); err != nil { 52 | logger.Error("Failed to create directory - %v", err) 53 | return err 54 | } 55 | 56 | // Write the JSON to the file 57 | if err := os.WriteFile(config.SpecGeneratorConfig.OutputPath, jsonBytes, 0644); err != nil { 58 | logger.Error("Failed to write file - %v", err) 59 | return err 60 | } 61 | 62 | // Print the path to the generated JSON file 63 | logger.Info("OpenAPI specification written to '%s'", config.SpecGeneratorConfig.OutputPath) 64 | return nil 65 | 66 | } 67 | -------------------------------------------------------------------------------- /generator/swagen/suite_test.go: -------------------------------------------------------------------------------- 1 | package swagen 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gopher-fleece/gleece/infrastructure/logger" 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | func TestSwagenManagerModule(t *testing.T) { 12 | // Disable logging to reduce clutter. 13 | logger.SetLogLevel(logger.LogLevelNone) 14 | RegisterFailHandler(Fail) 15 | RunSpecs(t, "Swagen Manager Suite") 16 | } 17 | 18 | var _ = BeforeEach(func() { 19 | }) 20 | -------------------------------------------------------------------------------- /generator/swagen/swagen30/security_generator.go: -------------------------------------------------------------------------------- 1 | package swagen30 2 | 3 | import ( 4 | "github.com/getkin/kin-openapi/openapi3" 5 | "github.com/gopher-fleece/gleece/definitions" 6 | ) 7 | 8 | func GenerateSecuritySpec(openapi *openapi3.T, securityConfig *[]definitions.SecuritySchemeConfig) error { 9 | securitySchemes := openapi3.SecuritySchemes{} 10 | for _, scheme := range *securityConfig { 11 | securitySchemes[scheme.SecurityName] = &openapi3.SecuritySchemeRef{ 12 | Value: &openapi3.SecurityScheme{ 13 | Type: string(scheme.Type), 14 | In: string(scheme.In), 15 | Name: scheme.FieldName, 16 | Description: scheme.Description, 17 | }, 18 | } 19 | if scheme.Scheme != "" { 20 | securitySchemes[scheme.SecurityName].Value.Scheme = string(scheme.Scheme) 21 | } 22 | 23 | if scheme.OpenIdConnectUrl != "" { 24 | securitySchemes[scheme.SecurityName].Value.OpenIdConnectUrl = scheme.OpenIdConnectUrl 25 | } 26 | if scheme.Flows != nil { 27 | openAPIFlows := &openapi3.OAuthFlows{} 28 | 29 | if scheme.Flows.Implicit != nil { 30 | openAPIFlows.Implicit = &openapi3.OAuthFlow{ 31 | AuthorizationURL: scheme.Flows.Implicit.AuthorizationURL, 32 | RefreshURL: scheme.Flows.Implicit.RefreshURL, 33 | Scopes: scheme.Flows.Implicit.Scopes, 34 | } 35 | } 36 | 37 | if scheme.Flows.Password != nil { 38 | openAPIFlows.Password = &openapi3.OAuthFlow{ 39 | TokenURL: scheme.Flows.Password.TokenURL, 40 | RefreshURL: scheme.Flows.Password.RefreshURL, 41 | Scopes: scheme.Flows.Password.Scopes, 42 | } 43 | } 44 | 45 | if scheme.Flows.ClientCredentials != nil { 46 | openAPIFlows.ClientCredentials = &openapi3.OAuthFlow{ 47 | TokenURL: scheme.Flows.ClientCredentials.TokenURL, 48 | RefreshURL: scheme.Flows.ClientCredentials.RefreshURL, 49 | Scopes: scheme.Flows.ClientCredentials.Scopes, 50 | } 51 | } 52 | 53 | if scheme.Flows.AuthorizationCode != nil { 54 | openAPIFlows.AuthorizationCode = &openapi3.OAuthFlow{ 55 | AuthorizationURL: scheme.Flows.AuthorizationCode.AuthorizationURL, 56 | TokenURL: scheme.Flows.AuthorizationCode.TokenURL, 57 | RefreshURL: scheme.Flows.AuthorizationCode.RefreshURL, 58 | Scopes: scheme.Flows.AuthorizationCode.Scopes, 59 | } 60 | } 61 | 62 | securitySchemes[scheme.SecurityName].Value.Flows = openAPIFlows 63 | } 64 | } 65 | 66 | openapi.Components.SecuritySchemes = securitySchemes 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /generator/swagen/swagen30/security_generator_test.go: -------------------------------------------------------------------------------- 1 | package swagen30 2 | 3 | import ( 4 | "github.com/getkin/kin-openapi/openapi3" 5 | "github.com/gopher-fleece/gleece/definitions" 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("Common Utilities", func() { 11 | Describe("GenerateSecuritySpec", func() { 12 | It("should generate security specifications correctly", func() { 13 | openapi := &openapi3.T{ 14 | Components: &openapi3.Components{ 15 | Schemas: openapi3.Schemas{}, 16 | }, 17 | } 18 | 19 | securityConfig := []definitions.SecuritySchemeConfig{ 20 | { 21 | SecurityName: "apiKeyAuth", 22 | Type: "apiKey", 23 | In: "header", 24 | FieldName: "X-API-Key", 25 | Description: "API Key Authentication", 26 | }, 27 | { 28 | SecurityName: "basicAuth", 29 | Type: "http", 30 | In: "header", 31 | FieldName: "Authorization", 32 | Description: "Basic HTTP Authentication", 33 | }, 34 | } 35 | 36 | err := GenerateSecuritySpec(openapi, &securityConfig) 37 | Expect(err).To(BeNil()) 38 | Expect(openapi.Components.SecuritySchemes).To(HaveKey("apiKeyAuth")) 39 | Expect(openapi.Components.SecuritySchemes["apiKeyAuth"].Value.Type).To(Equal("apiKey")) 40 | Expect(openapi.Components.SecuritySchemes["apiKeyAuth"].Value.In).To(Equal("header")) 41 | Expect(openapi.Components.SecuritySchemes["apiKeyAuth"].Value.Name).To(Equal("X-API-Key")) 42 | Expect(openapi.Components.SecuritySchemes["apiKeyAuth"].Value.Description).To(Equal("API Key Authentication")) 43 | 44 | Expect(openapi.Components.SecuritySchemes).To(HaveKey("basicAuth")) 45 | Expect(openapi.Components.SecuritySchemes["basicAuth"].Value.Type).To(Equal("http")) 46 | Expect(openapi.Components.SecuritySchemes["basicAuth"].Value.In).To(Equal("header")) 47 | Expect(openapi.Components.SecuritySchemes["basicAuth"].Value.Name).To(Equal("Authorization")) 48 | Expect(openapi.Components.SecuritySchemes["basicAuth"].Value.Description).To(Equal("Basic HTTP Authentication")) 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /generator/swagen/swagen30/suite_test.go: -------------------------------------------------------------------------------- 1 | package swagen30 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gopher-fleece/gleece/infrastructure/logger" 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | func TestSwagen30Module(t *testing.T) { 12 | // Disable logging to reduce clutter. 13 | logger.SetLogLevel(logger.LogLevelNone) 14 | RegisterFailHandler(Fail) 15 | RunSpecs(t, "Swagen v3.0 Module Suite") 16 | } 17 | 18 | var _ = BeforeEach(func() { 19 | // Clear the schemaRefMap before each test 20 | schemaRefMap = []SchemaRefMap{} 21 | }) 22 | -------------------------------------------------------------------------------- /generator/swagen/swagen31/security_generator31_test.go: -------------------------------------------------------------------------------- 1 | package swagen31 2 | 3 | import ( 4 | "github.com/gopher-fleece/gleece/definitions" 5 | . "github.com/onsi/ginkgo/v2" 6 | . "github.com/onsi/gomega" 7 | "github.com/pb33f/libopenapi/datamodel/high/base" 8 | v3 "github.com/pb33f/libopenapi/datamodel/high/v3" 9 | "github.com/pb33f/libopenapi/orderedmap" 10 | ) 11 | 12 | var _ = Describe("Common Utilities", func() { 13 | Describe("GenerateSecuritySpec", func() { 14 | It("should generate security specifications correctly", func() { 15 | doc := &v3.Document{ 16 | Components: &v3.Components{ 17 | Schemas: orderedmap.New[string, *base.SchemaProxy](), 18 | SecuritySchemes: orderedmap.New[string, *v3.SecurityScheme](), 19 | }, 20 | } 21 | 22 | securityConfig := []definitions.SecuritySchemeConfig{ 23 | { 24 | SecurityName: "apiKeyAuth", 25 | Type: "apiKey", 26 | In: "header", 27 | FieldName: "X-API-Key", 28 | Description: "API Key Authentication", 29 | }, 30 | { 31 | SecurityName: "basicAuth", 32 | Type: "http", 33 | In: "header", 34 | FieldName: "Authorization", 35 | Description: "Basic HTTP Authentication", 36 | }, 37 | } 38 | 39 | err := GenerateSecuritySpec(doc, &securityConfig) 40 | Expect(err).To(BeNil()) 41 | 42 | apiKeyAuthScheme, _ := doc.Components.SecuritySchemes.Get("apiKeyAuth") 43 | Expect(apiKeyAuthScheme).ToNot(BeNil()) 44 | Expect(apiKeyAuthScheme.Type).To(Equal("apiKey")) 45 | Expect(apiKeyAuthScheme.In).To(Equal("header")) 46 | Expect(apiKeyAuthScheme.Name).To(Equal("X-API-Key")) 47 | Expect(apiKeyAuthScheme.Description).To(Equal("API Key Authentication")) 48 | 49 | basicAuthScheme, _ := doc.Components.SecuritySchemes.Get("basicAuth") 50 | Expect(basicAuthScheme).ToNot(BeNil()) 51 | Expect(basicAuthScheme.Type).To(Equal("http")) 52 | Expect(basicAuthScheme.In).To(Equal("header")) 53 | Expect(basicAuthScheme.Name).To(Equal("Authorization")) 54 | Expect(basicAuthScheme.Description).To(Equal("Basic HTTP Authentication")) 55 | }) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /generator/swagen/swagen31/spec_common31.go: -------------------------------------------------------------------------------- 1 | package swagen31 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/gopher-fleece/gleece/generator/swagen/swagtool" 7 | "github.com/pb33f/libopenapi-validator/errors" 8 | highbase "github.com/pb33f/libopenapi/datamodel/high/base" 9 | v3 "github.com/pb33f/libopenapi/datamodel/high/v3" 10 | ) 11 | 12 | func ToOpenApiSchemaV3(typeName string) *highbase.Schema { 13 | return &highbase.Schema{ 14 | Type: []string{typeName}, 15 | } 16 | } 17 | 18 | func InterfaceToSchemaV3(doc *v3.Document, interfaceType string) *highbase.SchemaProxy { 19 | 20 | openapiType := swagtool.ToOpenApiType(interfaceType) 21 | fieldSchema := ToOpenApiSchemaV3(openapiType) 22 | 23 | if openapiType == "object" && !swagtool.IsMapObject(interfaceType) { // For now, ignore map objects, they will be handled later 24 | return highbase.CreateSchemaProxyRef("#/components/schemas/" + interfaceType) 25 | } 26 | if openapiType == "array" { 27 | // Handle array types 28 | itemType := swagtool.GetArrayItemType(interfaceType) 29 | // Once the item type is determined, create a schema reference for it in a recursive manner 30 | itemSchemaRef := InterfaceToSchemaV3(doc, itemType) 31 | fieldSchema.Items = &highbase.DynamicValue[*highbase.SchemaProxy, bool]{ 32 | A: itemSchemaRef, 33 | } 34 | } 35 | return highbase.CreateSchemaProxy(fieldSchema) 36 | } 37 | 38 | func FormatValidationErrors(validationErrors []*errors.ValidationError) string { 39 | if len(validationErrors) == 0 { 40 | return "" 41 | } 42 | 43 | errorMsgs := make([]string, len(validationErrors)) 44 | for i, err := range validationErrors { 45 | errorMsgs[i] = err.Error() 46 | } 47 | 48 | return strings.Join(errorMsgs, "\n") 49 | } 50 | 51 | func FormatErrors(validationErrors []error) string { 52 | if len(validationErrors) == 0 { 53 | return "" 54 | } 55 | 56 | errorMsgs := make([]string, len(validationErrors)) 57 | for i, err := range validationErrors { 58 | errorMsgs[i] = err.Error() 59 | } 60 | 61 | return strings.Join(errorMsgs, "\n") 62 | } 63 | 64 | func ToResponseDescription(description string) string { 65 | // This "hack" is since if the description is empty, 66 | // the libopenapi will not generate the response description at all, when the description is a required field in the spec. 67 | if description == "" { 68 | description = " " 69 | } 70 | return description 71 | } 72 | -------------------------------------------------------------------------------- /generator/swagen/swagen31/suite_test.go: -------------------------------------------------------------------------------- 1 | package swagen31 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gopher-fleece/gleece/infrastructure/logger" 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | func TestSwagen31Module(t *testing.T) { 12 | // Disable logging to reduce clutter. 13 | logger.SetLogLevel(logger.LogLevelNone) 14 | RegisterFailHandler(Fail) 15 | RunSpecs(t, "Swagen v3.1 Module Suite") 16 | } 17 | 18 | var _ = BeforeEach(func() { 19 | }) 20 | -------------------------------------------------------------------------------- /generator/swagen/swagtool/spec_utiles_test.go: -------------------------------------------------------------------------------- 1 | package swagtool 2 | 3 | import ( 4 | "github.com/gopher-fleece/runtime" 5 | . "github.com/onsi/ginkgo/v2" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | var _ = Describe("Swagtools - Spec Utilities", func() { 10 | 11 | Describe("HttpStatusCodeToString", func() { 12 | It("should convert HttpStatusCode to string", func() { 13 | code := runtime.HttpStatusCode(200) 14 | Expect(HttpStatusCodeToString(code)).To(Equal("200")) 15 | }) 16 | }) 17 | 18 | Describe("ParseNumber", func() { 19 | It("should parse a valid number string", func() { 20 | Expect(*ParseNumber("123.45")).To((BeEquivalentTo(123.45))) 21 | }) 22 | 23 | It("should return nil for an invalid number string", func() { 24 | Expect(ParseNumber("abc")).To(BeNil()) 25 | }) 26 | }) 27 | 28 | Describe("ParseInteger", func() { 29 | It("should parse a valid integer string", func() { 30 | Expect(*ParseInteger("123")).To((BeEquivalentTo(123))) 31 | }) 32 | 33 | It("should return nil for an invalid integer string", func() { 34 | Expect(ParseInteger("abc")).To(BeNil()) 35 | }) 36 | }) 37 | 38 | Describe("ParseUInteger", func() { 39 | It("should parse a valid integer string", func() { 40 | Expect(*ParseUInteger("123")).To((BeEquivalentTo(123))) 41 | }) 42 | 43 | It("should return nil for an invalid integer string", func() { 44 | Expect(ParseInteger("abc")).To(BeNil()) 45 | }) 46 | }) 47 | 48 | Describe("ParseBool", func() { 49 | It("should parse a valid boolean string", func() { 50 | Expect(*ParseBool("true")).To((BeTrue())) 51 | Expect(*ParseBool("false")).To((BeFalse())) 52 | }) 53 | 54 | It("should return nil for an invalid boolean string", func() { 55 | Expect(ParseBool("notabool")).To(BeNil()) 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /generator/swagen/swagtool/spec_utils.go: -------------------------------------------------------------------------------- 1 | package swagtool 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "reflect" 8 | "strconv" 9 | 10 | "github.com/gopher-fleece/runtime" 11 | ) 12 | 13 | func HttpStatusCodeToString(httpStatusCode runtime.HttpStatusCode) string { 14 | statusCode := uint64(httpStatusCode) 15 | return strconv.FormatUint(statusCode, 10) 16 | } 17 | 18 | // Helper function to parse numeric validation values 19 | func ParseNumber(value string) *float64 { 20 | if v, err := strconv.ParseFloat(value, 64); err == nil { 21 | return &v 22 | } 23 | return nil 24 | } 25 | 26 | // Helper function to parse integer validation values 27 | func ParseInteger(value string) *int64 { 28 | if v, err := strconv.ParseInt(value, 10, 64); err == nil { 29 | return &v 30 | } 31 | return nil 32 | } 33 | 34 | // Helper function to parse integer validation values 35 | func ParseUInteger(value string) *uint64 { 36 | if v, err := strconv.ParseUint(value, 10, 64); err == nil { 37 | return &v 38 | } 39 | return nil 40 | } 41 | 42 | // Helper function to parse boolean validation values 43 | func ParseBool(value string) *bool { 44 | if v, err := strconv.ParseBool(value); err == nil { 45 | return &v 46 | } 47 | return nil 48 | } 49 | 50 | func AreJSONsIdentical(json1 []byte, json2 []byte) (bool, error) { 51 | var obj1, obj2 map[string]interface{} 52 | 53 | err := json.Unmarshal(json1, &obj1) 54 | if err != nil { 55 | return false, fmt.Errorf("invalid JSON 1: %v", err) 56 | } 57 | 58 | err = json.Unmarshal(json2, &obj2) 59 | if err != nil { 60 | return false, fmt.Errorf("invalid JSON 2: %v", err) 61 | } 62 | 63 | return reflect.DeepEqual(obj1, obj2), nil 64 | } 65 | 66 | func FileExists(filename string) bool { 67 | _, err := os.Stat(filename) 68 | if err != nil { 69 | if os.IsNotExist(err) { 70 | return false 71 | } 72 | } 73 | return true 74 | } 75 | -------------------------------------------------------------------------------- /generator/swagen/swagtool/suite_test.go: -------------------------------------------------------------------------------- 1 | package swagtool 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gopher-fleece/gleece/infrastructure/logger" 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | func TestSwagenToolsModule(t *testing.T) { 12 | // Disable logging to reduce clutter. 13 | logger.SetLogLevel(logger.LogLevelNone) 14 | RegisterFailHandler(Fail) 15 | RunSpecs(t, "Swagen Tools Suite") 16 | } 17 | 18 | var _ = BeforeEach(func() { 19 | }) 20 | -------------------------------------------------------------------------------- /generator/templates/chi/partials/authorization.call.hbs: -------------------------------------------------------------------------------- 1 | authorize( 2 | req, 3 | []SecurityCheckList { {{#each Security}} 4 | { 5 | {{#each SecurityAnnotation}} 6 | Relation: SecurityListRelationAnd, 7 | Checks: []runtime.SecurityCheck { 8 | { 9 | SchemaName: "{{{SchemaName}}}", 10 | Scopes: []string{ 11 | {{#each Scopes}}"{{{.}}}",{{#unless @last}} 12 | {{/unless}}{{/each}} 13 | },{{/each}} 14 | }, 15 | }, 16 | }, 17 | {{/each}} 18 | }, 19 | ) 20 | -------------------------------------------------------------------------------- /generator/templates/chi/partials/imports.hbs: -------------------------------------------------------------------------------- 1 | import ( 2 | "context" 3 | "encoding/json" 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | "reflect" 10 | "regexp" 11 | "github.com/go-playground/validator/v10" 12 | "github.com/go-chi/chi/v5" 13 | "github.com/gopher-fleece/runtime" 14 | RequestAuth "{{{AuthConfig.AuthFileFullPackageName}}}" 15 | 16 | {{#each Controllers}} 17 | {{{Name}}}Import "{{{FullyQualifiedPackage}}}" 18 | {{#each Routes}} 19 | {{#each FuncParams}} 20 | {{#if TypeMeta.FullyQualifiedPackage}} 21 | Param{{{UniqueImportSerial}}}{{{UnwrapArrayTypeRecursive Name}}} "{{{TypeMeta.FullyQualifiedPackage}}}" 22 | {{/if}} 23 | {{/each}} 24 | {{#each Responses}} 25 | {{#if FullyQualifiedPackage}} 26 | Response{{{UniqueImportSerial}}}{{{UnwrapArrayTypeRecursive Name}}} "{{{FullyQualifiedPackage}}}" 27 | {{/if}} 28 | {{/each}} 29 | {{/each}} 30 | {{/each}} 31 | 32 | {{> ImportsExtension }} 33 | 34 | ) -------------------------------------------------------------------------------- /generator/templates/chi/partials/json.body.validation.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="conversionErr" }} 2 | 3 | validationError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(http.StatusUnprocessableEntity), 5 | Detail: fmt.Sprintf( 6 | "A request was made to operation '{{{OperationId}}}' but body parameter '%s' did not pass validation of '%s' - %s", 7 | "{{ToLowerCamel Name}}", 8 | "{{{TypeMeta.Name}}}", 9 | extractValidationErrorMessage(conversionErr, nil), 10 | ), 11 | Status: http.StatusUnprocessableEntity, 12 | Instance: "/validation/error/{{{OperationId}}}", 13 | } 14 | 15 | {{> JsonBodyValidationErrorResponseExtension}} 16 | 17 | w.WriteHeader(http.StatusUnprocessableEntity) 18 | json.NewEncoder(w).Encode(validationError) 19 | return -------------------------------------------------------------------------------- /generator/templates/chi/partials/json.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{#LastTypeNameEquals Responses "error"}} 2 | 3 | stdError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(statusCode), 5 | Detail: "Encountered an error during operation '{{{OperationId}}}'", 6 | Status: statusCode, 7 | Instance: "/controller/error/{{{OperationId}}}", 8 | Extensions: map[string]string{"error": opError.Error()}, 9 | } 10 | {{> JsonErrorResponseExtension}} 11 | w.WriteHeader(statusCode) 12 | json.NewEncoder(w).Encode(stdError) 13 | {{else}} 14 | w.WriteHeader(statusCode) 15 | json.NewEncoder(w).Encode(opError) 16 | {{/LastTypeNameEquals}} -------------------------------------------------------------------------------- /generator/templates/chi/partials/json.response.hbs: -------------------------------------------------------------------------------- 1 | {{> JsonResponseExtension}} 2 | {{#equal HasReturnValue true}} 3 | {{!-- If validation of output is enabled AND the payload response is an object --}} 4 | {{#if ValidateResponsePayload}} 5 | {{#equal Responses.[0].IsUniverseType false}} 6 | 7 | 8 | var outputValidationErr error 9 | {{#if Responses.[0].IsByAddress}} 10 | if value == nil { 11 | outputValidationErr = fmt.Errorf("Response payload is nil") 12 | } else { 13 | {{/if}} 14 | outputValidationErr = validateDataRecursive(value, "") 15 | {{#if Responses.[0].IsByAddress}} 16 | } 17 | {{/if}} 18 | 19 | if outputValidationErr != nil { 20 | {{> Middleware isErrorMiddleware=true middlewares="onOutputValidationMiddlewares" errorName="outputValidationErr" }} 21 | outputValidationStatusCode := http.StatusInternalServerError 22 | outputValidationRfc7807Error := runtime.Rfc7807Error{ 23 | Type: http.StatusText(outputValidationStatusCode), 24 | Detail: "Encountered an error during operation '{{{OperationId}}}'", 25 | Status: outputValidationStatusCode, 26 | Instance: "/controller/error/{{{OperationId}}}", 27 | Extensions: map[string]string{}, 28 | } 29 | w.Header().Set("Content-Type", "application/json") 30 | w.WriteHeader(outputValidationStatusCode) 31 | json.NewEncoder(w).Encode(outputValidationRfc7807Error) 32 | return 33 | } 34 | {{/equal}} 35 | {{/if}} 36 | {{> Middleware isErrorMiddleware=false middlewares="afterOperationSuccessMiddlewares" }} 37 | {{> AfterOperationRoutesExtension }} 38 | w.Header().Set("Content-Type", "application/json") 39 | w.WriteHeader(statusCode) 40 | json.NewEncoder(w).Encode(value) 41 | {{> RouteEndRoutesExtension }} 42 | {{/equal}} 43 | {{#equal HasReturnValue false}} 44 | {{> Middleware isErrorMiddleware=false middlewares="afterOperationSuccessMiddlewares" }} 45 | {{> AfterOperationRoutesExtension }} 46 | w.WriteHeader(statusCode) 47 | {{> RouteEndRoutesExtension }} 48 | {{/equal}} -------------------------------------------------------------------------------- /generator/templates/chi/partials/method.parameter.list.hbs: -------------------------------------------------------------------------------- 1 | {{#each FuncParams}} 2 | {{#if IsContext}} 3 | getRequestContext(req) 4 | {{else}} 5 | {{#if TypeMeta.IsByAddress}} 6 | 7 | {{else}} 8 | * 9 | {{/if}} 10 | {{ToLowerCamel Name}}RawPtr 11 | {{/if}} 12 | {{#unless @last}}, {{/unless}} 13 | {{/each}} -------------------------------------------------------------------------------- /generator/templates/chi/partials/middleware.hbs: -------------------------------------------------------------------------------- 1 | // Middlewares {{middlewares}} section 2 | for _, middleware := range {{middlewares}} { 3 | {{#if isErrorMiddleware}} 4 | middlewareCtx, continueOperation := middleware(getRequestContext(req), w, req, {{errorName}}) 5 | setRequestContext(req, middlewareCtx) 6 | if !continueOperation { 7 | return 8 | } 9 | {{else}} 10 | middlewareCtx, continueOperation := middleware(getRequestContext(req), w, req) 11 | setRequestContext(req, middlewareCtx) 12 | if !continueOperation { 13 | return 14 | } 15 | {{/if}} 16 | } 17 | // End middlewares {{middlewares}} section 18 | 19 | -------------------------------------------------------------------------------- /generator/templates/chi/partials/params.validation.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="conversionErr" }} 2 | 3 | validationError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(http.StatusUnprocessableEntity), 5 | Detail: fmt.Sprintf( 6 | "A request was made to operation '{{{OperationId}}}' but parameter '%s' was not properly sent - Expected %s but got %s", 7 | "{{ToLowerCamel Name}}", 8 | "{{{TypeMeta.Name}}}", 9 | reflect.TypeOf({{ToLowerCamel Name}}{{#ifEqual PassedIn "Body"}}{{else}}Raw{{/ifEqual}}).String(), 10 | ), 11 | Status: http.StatusUnprocessableEntity, 12 | Instance: "/validation/error/{{{OperationId}}}", 13 | Extensions: map[string]string{"error": conversionErr.Error()}, 14 | } 15 | {{> ParamsValidationErrorResponseExtension}} 16 | w.WriteHeader(http.StatusUnprocessableEntity) 17 | json.NewEncoder(w).Encode(validationError) 18 | return -------------------------------------------------------------------------------- /generator/templates/chi/partials/register.middleware.hbs: -------------------------------------------------------------------------------- 1 | type MiddlewareFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool) 2 | type ErrorMiddlewareFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) (context.Context, bool) 3 | 4 | var beforeOperationMiddlewares []MiddlewareFunc 5 | var afterOperationSuccessMiddlewares []MiddlewareFunc 6 | var onErrorMiddlewares []ErrorMiddlewareFunc 7 | var onInputValidationMiddlewares []ErrorMiddlewareFunc 8 | var onOutputValidationMiddlewares []ErrorMiddlewareFunc 9 | 10 | func RegisterMiddleware(executionType runtime.MiddlewareExecutionType, middlewareFunc MiddlewareFunc) { 11 | switch executionType { 12 | case runtime.BeforeOperation: 13 | beforeOperationMiddlewares = append(beforeOperationMiddlewares, middlewareFunc) 14 | case runtime.AfterOperationSuccess: 15 | afterOperationSuccessMiddlewares = append(afterOperationSuccessMiddlewares, middlewareFunc) 16 | } 17 | } 18 | 19 | func RegisterErrorMiddleware(executionType runtime.ErrorMiddlewareExecutionType, errorMiddlewareFunc ErrorMiddlewareFunc) { 20 | switch executionType { 21 | case runtime.OnInputValidationError: 22 | onInputValidationMiddlewares = append(onInputValidationMiddlewares, errorMiddlewareFunc) 23 | case runtime.OnOutputValidationError: 24 | onOutputValidationMiddlewares = append(onOutputValidationMiddlewares, errorMiddlewareFunc) 25 | case runtime.OnOperationError: 26 | onErrorMiddlewares = append(onErrorMiddlewares, errorMiddlewareFunc) 27 | } 28 | } -------------------------------------------------------------------------------- /generator/templates/chi/partials/reply.response.hbs: -------------------------------------------------------------------------------- 1 | statusCode := getStatusCode(&controller, {{{HasReturnValue}}}, opError) 2 | {{#LastTypeNameEquals Responses "error"}} 3 | if opError != nil { 4 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 5 | {{else}} 6 | {{#LastTypeIsByAddress Responses}} 7 | if opError != nil { 8 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 9 | {{else}} 10 | emptyErr := {{GetLastTyeFullyQualified Responses}}{} 11 | if opError != emptyErr { 12 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 13 | 14 | {{/LastTypeIsByAddress}} 15 | {{/LastTypeNameEquals}} 16 | {{#equal ResponseContentType "application/json"}} 17 | {{> JsonErrorResponse}} 18 | {{/equal}} 19 | return 20 | } 21 | {{#equal ResponseContentType "application/json"}} 22 | {{> JsonResponse}} 23 | {{/equal}} -------------------------------------------------------------------------------- /generator/templates/chi/partials/request.args.parsing.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#equal PassedIn "Path"}} 3 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 4 | {{ToLowerCamel Name}}Raw := chi.URLParam(req, "{{{NameInSchema}}}") 5 | is{{Name}}Exists := true // if parameter is in route but not provided, it won't reach this handler 6 | {{> RequestSwitchParamType}} 7 | {{> RunValidator}} 8 | {{/equal}} 9 | 10 | {{#equal PassedIn "Query"}} 11 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 12 | 13 | {{ToLowerCamel Name}}Raw := req.URL.Query().Get("{{{NameInSchema}}}") 14 | is{{Name}}Exists := req.URL.Query().Has("{{{NameInSchema}}}") 15 | {{> RequestSwitchParamType}} 16 | {{> RunValidator}} 17 | {{/equal}} 18 | 19 | {{#equal PassedIn "Header"}} 20 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 21 | {{ToLowerCamel Name}}Raw := req.Header.Get("{{{NameInSchema}}}") 22 | _, is{{Name}}Exists := req.Header["{{{NameInSchema}}}"] 23 | if !is{{Name}}Exists { 24 | // In echo, the req..Header["key"] is not 100% reliable, so we need other check, but only if is was not found in the first method 25 | headerValues := req.Header.Values("{{{NameInSchema}}}") 26 | is{{Name}}Exists = len(headerValues) > 0 27 | } 28 | {{> RequestSwitchParamType}} 29 | {{> RunValidator}} 30 | {{/equal}} 31 | 32 | {{#equal PassedIn "Form"}} 33 | req.ParseForm() 34 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 35 | {{ToLowerCamel Name}}RawArr, is{{Name}}Exists := req.PostForm["{{{NameInSchema}}}"] 36 | {{ToLowerCamel Name}}Raw := "" 37 | if is{{Name}}Exists { 38 | {{ToLowerCamel Name}}Raw = {{ToLowerCamel Name}}RawArr[0] // Get first value since form values are slices 39 | } 40 | 41 | {{> RequestSwitchParamType}} 42 | {{> RunValidator}} 43 | {{/equal}} 44 | 45 | {{#equal PassedIn "Body"}} 46 | var {{ToLowerCamel Name}}RawPtr *{{SlicePrefix TypeMeta.Name}}{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{SliceSlice TypeMeta.Name}} = nil 47 | conversionErr = bindAndValidateBody(req, "{{{ResponseContentType}}}", "{{Validator}}",&{{ToLowerCamel Name}}RawPtr) 48 | if conversionErr != nil { 49 | {{> JsonBodyValidationErrorResponse }} 50 | } 51 | {{/equal}} 52 | -------------------------------------------------------------------------------- /generator/templates/chi/partials/response.headers.hbs: -------------------------------------------------------------------------------- 1 | for key, value := range controller.GetHeaders() { 2 | w.Header().Set(key, value) 3 | } 4 | -------------------------------------------------------------------------------- /generator/templates/chi/partials/run.validator.hbs: -------------------------------------------------------------------------------- 1 | {{#if Validator}} 2 | if validatorErr := validatorInstance.Var({{ToLowerCamel Name}}RawPtr, "{{Validator}}"); validatorErr != nil { 3 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="validatorErr" }} 4 | fieldName := "{{ToLowerCamel Name}}" 5 | validationError := wrapValidatorError(validatorErr, "{{{OperationId}}}", fieldName) 6 | 7 | {{> RunValidatorExtension}} 8 | 9 | w.WriteHeader(http.StatusUnprocessableEntity) 10 | json.NewEncoder(w).Encode(validationError) 11 | return 12 | } 13 | {{/if}} 14 | -------------------------------------------------------------------------------- /generator/templates/chi/partials/type.declarations.hbs: -------------------------------------------------------------------------------- 1 | type SecurityListRelation string 2 | 3 | const ( 4 | SecurityListRelationAnd SecurityListRelation = "AND" 5 | ) 6 | 7 | type SecurityCheckList struct { 8 | Checks []runtime.SecurityCheck 9 | Relation SecurityListRelation 10 | } 11 | 12 | {{> TypeDeclarationsExtension}} -------------------------------------------------------------------------------- /generator/templates/echo/partials/authorization.call.hbs: -------------------------------------------------------------------------------- 1 | authorize( 2 | echoCtx, 3 | []SecurityCheckList { {{#each Security}} 4 | { 5 | {{#each SecurityAnnotation}} 6 | Relation: SecurityListRelationAnd, 7 | Checks: []runtime.SecurityCheck { 8 | { 9 | SchemaName: "{{{SchemaName}}}", 10 | Scopes: []string{ 11 | {{#each Scopes}}"{{{.}}}",{{#unless @last}} 12 | {{/unless}}{{/each}} 13 | },{{/each}} 14 | }, 15 | }, 16 | }, 17 | {{/each}} 18 | }, 19 | ) 20 | -------------------------------------------------------------------------------- /generator/templates/echo/partials/imports.hbs: -------------------------------------------------------------------------------- 1 | import ( 2 | "context" 3 | "encoding/json" 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | "reflect" 10 | "regexp" 11 | "github.com/go-playground/validator/v10" 12 | "github.com/labstack/echo/v4" 13 | "github.com/gopher-fleece/runtime" 14 | RequestAuth "{{{AuthConfig.AuthFileFullPackageName}}}" 15 | {{#each Controllers}} 16 | {{{Name}}}Import "{{{FullyQualifiedPackage}}}" 17 | {{#each Routes}} 18 | {{#each FuncParams}} 19 | {{#if TypeMeta.FullyQualifiedPackage}} 20 | Param{{{UniqueImportSerial}}}{{{UnwrapArrayTypeRecursive Name}}} "{{{TypeMeta.FullyQualifiedPackage}}}" 21 | {{/if}} 22 | {{/each}} 23 | {{#each Responses}} 24 | {{#if FullyQualifiedPackage}} 25 | Response{{{UniqueImportSerial}}}{{{UnwrapArrayTypeRecursive Name}}} "{{{FullyQualifiedPackage}}}" 26 | {{/if}} 27 | {{/each}} 28 | {{/each}} 29 | {{/each}} 30 | 31 | {{> ImportsExtension }} 32 | 33 | ) -------------------------------------------------------------------------------- /generator/templates/echo/partials/json.body.validation.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="conversionErr" }} 2 | 3 | validationError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(http.StatusUnprocessableEntity), 5 | Detail: fmt.Sprintf( 6 | "A request was made to operation '{{{OperationId}}}' but body parameter '%s' did not pass validation of '%s' - %s", 7 | "{{ToLowerCamel Name}}", 8 | "{{{TypeMeta.Name}}}", 9 | extractValidationErrorMessage(conversionErr, nil), 10 | ), 11 | Status: http.StatusUnprocessableEntity, 12 | Instance: "/validation/error/{{{OperationId}}}", 13 | } 14 | 15 | {{> JsonBodyValidationErrorResponseExtension}} 16 | 17 | return echoCtx.JSON(http.StatusUnprocessableEntity, validationError) 18 | -------------------------------------------------------------------------------- /generator/templates/echo/partials/json.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{#LastTypeNameEquals Responses "error"}} 2 | 3 | stdError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(statusCode), 5 | Detail: "Encountered an error during operation '{{{OperationId}}}'", 6 | Status: statusCode, 7 | Instance: "/controller/error/{{{OperationId}}}", 8 | Extensions: map[string]string{"error": opError.Error()}, 9 | } 10 | {{> JsonErrorResponseExtension}} 11 | return echoCtx.JSON(statusCode, stdError) 12 | {{else}} 13 | return echoCtx.JSON(statusCode, opError) 14 | {{/LastTypeNameEquals}} -------------------------------------------------------------------------------- /generator/templates/echo/partials/json.response.hbs: -------------------------------------------------------------------------------- 1 | {{> JsonResponseExtension}} 2 | {{#equal HasReturnValue true}} 3 | {{!-- If validation of output is enabled AND the payload response is an object --}} 4 | {{#if ValidateResponsePayload}} 5 | {{#equal Responses.[0].IsUniverseType false}} 6 | 7 | 8 | var outputValidationErr error 9 | {{#if Responses.[0].IsByAddress}} 10 | if value == nil { 11 | outputValidationErr = fmt.Errorf("Response payload is nil") 12 | } else { 13 | {{/if}} 14 | outputValidationErr = validateDataRecursive(value, "") 15 | {{#if Responses.[0].IsByAddress}} 16 | } 17 | {{/if}} 18 | 19 | if outputValidationErr != nil { 20 | {{> Middleware isErrorMiddleware=true middlewares="onOutputValidationMiddlewares" errorName="outputValidationErr" }} 21 | outputValidationStatusCode := http.StatusInternalServerError 22 | outputValidationRfc7807Error := runtime.Rfc7807Error{ 23 | Type: http.StatusText(outputValidationStatusCode), 24 | Detail: "Encountered an error during operation '{{{OperationId}}}'", 25 | Status: outputValidationStatusCode, 26 | Instance: "/controller/error/{{{OperationId}}}", 27 | Extensions: map[string]string{}, 28 | } 29 | return echoCtx.JSON(outputValidationStatusCode, outputValidationRfc7807Error) 30 | } 31 | {{/equal}} 32 | {{/if}} 33 | {{> Middleware isErrorMiddleware=false middlewares="afterOperationSuccessMiddlewares" }} 34 | {{> AfterOperationRoutesExtension }} 35 | echoCtx.JSON(statusCode, value) 36 | {{> RouteEndRoutesExtension }} 37 | return nil 38 | {{/equal}} 39 | {{#equal HasReturnValue false}} 40 | {{> Middleware isErrorMiddleware=false middlewares="afterOperationSuccessMiddlewares" }} 41 | {{> AfterOperationRoutesExtension }} 42 | echoCtx.Response().WriteHeader(statusCode) 43 | {{> RouteEndRoutesExtension }} 44 | return nil 45 | {{/equal}} -------------------------------------------------------------------------------- /generator/templates/echo/partials/method.parameter.list.hbs: -------------------------------------------------------------------------------- 1 | {{#each FuncParams}} 2 | {{#if IsContext}} 3 | getRequestContext(echoCtx) 4 | {{else}} 5 | {{#if TypeMeta.IsByAddress}} 6 | 7 | {{else}} 8 | * 9 | {{/if}} 10 | {{ToLowerCamel Name}}RawPtr 11 | {{/if}} 12 | {{#unless @last}}, {{/unless}} 13 | {{/each}} -------------------------------------------------------------------------------- /generator/templates/echo/partials/middleware.hbs: -------------------------------------------------------------------------------- 1 | // Middlewares {{middlewares}} section 2 | for _, middleware := range {{middlewares}} { 3 | {{#if isErrorMiddleware}} 4 | middlewareCtx, continueOperation := middleware(getRequestContext(echoCtx), echoCtx, {{errorName}}) 5 | setRequestContext(echoCtx, middlewareCtx) 6 | if !continueOperation { 7 | return nil 8 | } 9 | {{else}} 10 | middlewareCtx, continueOperation := middleware(getRequestContext(echoCtx), echoCtx) 11 | setRequestContext(echoCtx, middlewareCtx) 12 | if !continueOperation { 13 | return nil 14 | } 15 | {{/if}} 16 | } 17 | // End middlewares {{middlewares}} section 18 | -------------------------------------------------------------------------------- /generator/templates/echo/partials/params.validation.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="conversionErr" }} 2 | 3 | validationError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(http.StatusUnprocessableEntity), 5 | Detail: fmt.Sprintf( 6 | "A request was made to operation '{{{OperationId}}}' but parameter '%s' was not properly sent - Expected %s but got %s", 7 | "{{ToLowerCamel Name}}", 8 | "{{{TypeMeta.Name}}}", 9 | reflect.TypeOf({{ToLowerCamel Name}}{{#ifEqual PassedIn "Body"}}{{else}}Raw{{/ifEqual}}).String(), 10 | ), 11 | Status: http.StatusUnprocessableEntity, 12 | Instance: "/validation/error/{{{OperationId}}}", 13 | Extensions: map[string]string{"error": conversionErr.Error()}, 14 | } 15 | {{> ParamsValidationErrorResponseExtension}} 16 | return echoCtx.JSON(http.StatusUnprocessableEntity, validationError) -------------------------------------------------------------------------------- /generator/templates/echo/partials/register.middleware.hbs: -------------------------------------------------------------------------------- 1 | type MiddlewareFunc func(ctx context.Context, echoCtx echo.Context) (context.Context, bool) 2 | type ErrorMiddlewareFunc func(ctx context.Context, echoCtx echo.Context, err error) (context.Context, bool) 3 | 4 | var beforeOperationMiddlewares []MiddlewareFunc 5 | var afterOperationSuccessMiddlewares []MiddlewareFunc 6 | var onErrorMiddlewares []ErrorMiddlewareFunc 7 | var onInputValidationMiddlewares []ErrorMiddlewareFunc 8 | var onOutputValidationMiddlewares []ErrorMiddlewareFunc 9 | 10 | func RegisterMiddleware(executionType runtime.MiddlewareExecutionType, middlewareFunc MiddlewareFunc) { 11 | switch executionType { 12 | case runtime.BeforeOperation: 13 | beforeOperationMiddlewares = append(beforeOperationMiddlewares, middlewareFunc) 14 | case runtime.AfterOperationSuccess: 15 | afterOperationSuccessMiddlewares = append(afterOperationSuccessMiddlewares, middlewareFunc) 16 | } 17 | } 18 | 19 | func RegisterErrorMiddleware(executionType runtime.ErrorMiddlewareExecutionType, errorMiddlewareFunc ErrorMiddlewareFunc) { 20 | switch executionType { 21 | case runtime.OnInputValidationError: 22 | onInputValidationMiddlewares = append(onInputValidationMiddlewares, errorMiddlewareFunc) 23 | case runtime.OnOutputValidationError: 24 | onOutputValidationMiddlewares = append(onOutputValidationMiddlewares, errorMiddlewareFunc) 25 | case runtime.OnOperationError: 26 | onErrorMiddlewares = append(onErrorMiddlewares, errorMiddlewareFunc) 27 | } 28 | } -------------------------------------------------------------------------------- /generator/templates/echo/partials/reply.response.hbs: -------------------------------------------------------------------------------- 1 | statusCode := getStatusCode(&controller, {{{HasReturnValue}}}, opError) 2 | {{#LastTypeNameEquals Responses "error"}} 3 | if opError != nil { 4 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 5 | {{else}} 6 | {{#LastTypeIsByAddress Responses}} 7 | if opError != nil { 8 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 9 | {{else}} 10 | emptyErr := {{GetLastTyeFullyQualified Responses}}{} 11 | if opError != emptyErr { 12 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 13 | 14 | {{/LastTypeIsByAddress}} 15 | {{/LastTypeNameEquals}} 16 | {{#equal ResponseContentType "application/json"}} 17 | {{> JsonErrorResponse}} 18 | {{/equal}} 19 | } 20 | {{#equal ResponseContentType "application/json"}} 21 | {{> JsonResponse}} 22 | {{/equal}} -------------------------------------------------------------------------------- /generator/templates/echo/partials/request.args.parsing.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#equal PassedIn "Path"}} 3 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 4 | {{ToLowerCamel Name}}Raw := echoCtx.Param("{{{NameInSchema}}}") 5 | is{{Name}}Exists := true // if parameter is in route but not provided, it won't reach this handler 6 | {{> RequestSwitchParamType}} 7 | {{> RunValidator}} 8 | {{/equal}} 9 | 10 | {{#equal PassedIn "Query"}} 11 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 12 | {{ToLowerCamel Name}}Raw := echoCtx.QueryParam("{{{NameInSchema}}}") 13 | is{{Name}}Exists := echoCtx.Request().URL.Query().Has("{{{NameInSchema}}}") 14 | {{> RequestSwitchParamType}} 15 | {{> RunValidator}} 16 | {{/equal}} 17 | 18 | {{#equal PassedIn "Header"}} 19 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 20 | {{ToLowerCamel Name}}Raw := echoCtx.Request().Header.Get("{{{NameInSchema}}}") 21 | _, is{{Name}}Exists := echoCtx.Request().Header["{{{NameInSchema}}}"] 22 | if !is{{Name}}Exists { 23 | // In echo, the echoCtx.Request().Header["key"] is not 100% reliable, so we need other check, but only if is was not found in the first method 24 | headerValues := echoCtx.Request().Header.Values("{{{NameInSchema}}}") 25 | is{{Name}}Exists = len(headerValues) > 0 26 | } 27 | {{> RequestSwitchParamType}} 28 | {{> RunValidator}} 29 | {{/equal}} 30 | 31 | {{#equal PassedIn "Form"}} 32 | echoCtx.Request().ParseForm() 33 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 34 | {{ToLowerCamel Name}}RawArr, is{{Name}}Exists := echoCtx.Request().PostForm["{{{NameInSchema}}}"] 35 | {{ToLowerCamel Name}}Raw := "" 36 | if is{{Name}}Exists { 37 | {{ToLowerCamel Name}}Raw = {{ToLowerCamel Name}}RawArr[0] // Get first value since form values are slices 38 | } 39 | 40 | {{> RequestSwitchParamType}} 41 | {{> RunValidator}} 42 | {{/equal}} 43 | 44 | {{#equal PassedIn "Body"}} 45 | var {{ToLowerCamel Name}}RawPtr *{{SlicePrefix TypeMeta.Name}}{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{SliceSlice TypeMeta.Name}} = nil 46 | conversionErr = bindAndValidateBody(echoCtx, "{{{ResponseContentType}}}", "{{Validator}}",&{{ToLowerCamel Name}}RawPtr) 47 | if conversionErr != nil { 48 | {{> JsonBodyValidationErrorResponse }} 49 | } 50 | {{/equal}} 51 | -------------------------------------------------------------------------------- /generator/templates/echo/partials/response.headers.hbs: -------------------------------------------------------------------------------- 1 | for key, value := range controller.GetHeaders() { 2 | echoCtx.Response().Header().Set(key, value) 3 | } 4 | -------------------------------------------------------------------------------- /generator/templates/echo/partials/run.validator.hbs: -------------------------------------------------------------------------------- 1 | {{#if Validator}} 2 | if validatorErr := validatorInstance.Var({{ToLowerCamel Name}}RawPtr, "{{Validator}}"); validatorErr != nil { 3 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="validatorErr" }} 4 | fieldName := "{{ToLowerCamel Name}}" 5 | validationError := wrapValidatorError(validatorErr, "{{{OperationId}}}", fieldName) 6 | 7 | {{> RunValidatorExtension}} 8 | 9 | return echoCtx.JSON(http.StatusUnprocessableEntity, validationError) 10 | } 11 | {{/if}} 12 | -------------------------------------------------------------------------------- /generator/templates/echo/partials/type.declarations.hbs: -------------------------------------------------------------------------------- 1 | type SecurityListRelation string 2 | 3 | const ( 4 | SecurityListRelationAnd SecurityListRelation = "AND" 5 | ) 6 | 7 | type SecurityCheckList struct { 8 | Checks []runtime.SecurityCheck 9 | Relation SecurityListRelation 10 | } 11 | 12 | {{> TypeDeclarationsExtension}} -------------------------------------------------------------------------------- /generator/templates/fiber/partials/authorization.call.hbs: -------------------------------------------------------------------------------- 1 | authorize( 2 | fiberCtx, 3 | []SecurityCheckList { {{#each Security}} 4 | { 5 | {{#each SecurityAnnotation}} 6 | Relation: SecurityListRelationAnd, 7 | Checks: []runtime.SecurityCheck { 8 | { 9 | SchemaName: "{{{SchemaName}}}", 10 | Scopes: []string{ 11 | {{#each Scopes}}"{{{.}}}",{{#unless @last}} 12 | {{/unless}}{{/each}} 13 | },{{/each}} 14 | }, 15 | }, 16 | }, 17 | {{/each}} 18 | }, 19 | ) 20 | -------------------------------------------------------------------------------- /generator/templates/fiber/partials/imports.hbs: -------------------------------------------------------------------------------- 1 | import ( 2 | "context" 3 | "encoding/json" 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | "reflect" 10 | "regexp" 11 | "github.com/go-playground/validator/v10" 12 | "github.com/gofiber/fiber/v2" 13 | "github.com/gopher-fleece/runtime" 14 | RequestAuth "{{{AuthConfig.AuthFileFullPackageName}}}" 15 | {{#each Controllers}} 16 | {{{Name}}}Import "{{{FullyQualifiedPackage}}}" 17 | {{#each Routes}} 18 | {{#each FuncParams}} 19 | {{#if TypeMeta.FullyQualifiedPackage}} 20 | Param{{{UniqueImportSerial}}}{{{UnwrapArrayTypeRecursive Name}}} "{{{TypeMeta.FullyQualifiedPackage}}}" 21 | {{/if}} 22 | {{/each}} 23 | {{#each Responses}} 24 | {{#if FullyQualifiedPackage}} 25 | Response{{{UniqueImportSerial}}}{{{UnwrapArrayTypeRecursive Name}}} "{{{FullyQualifiedPackage}}}" 26 | {{/if}} 27 | {{/each}} 28 | {{/each}} 29 | {{/each}} 30 | 31 | {{> ImportsExtension }} 32 | 33 | ) -------------------------------------------------------------------------------- /generator/templates/fiber/partials/json.body.validation.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="conversionErr" }} 2 | 3 | validationError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(http.StatusUnprocessableEntity), 5 | Detail: fmt.Sprintf( 6 | "A request was made to operation '{{{OperationId}}}' but body parameter '%s' did not pass validation of '%s' - %s", 7 | "{{ToLowerCamel Name}}", 8 | "{{{TypeMeta.Name}}}", 9 | extractValidationErrorMessage(conversionErr, nil), 10 | ), 11 | Status: http.StatusUnprocessableEntity, 12 | Instance: "/validation/error/{{{OperationId}}}", 13 | } 14 | 15 | {{> JsonBodyValidationErrorResponseExtension}} 16 | 17 | return fiberCtx.Status(http.StatusUnprocessableEntity).JSON(validationError) 18 | -------------------------------------------------------------------------------- /generator/templates/fiber/partials/json.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{#LastTypeNameEquals Responses "error"}} 2 | 3 | stdError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(statusCode), 5 | Detail: "Encountered an error during operation '{{{OperationId}}}'", 6 | Status: statusCode, 7 | Instance: "/controller/error/{{{OperationId}}}", 8 | Extensions: map[string]string{"error": opError.Error()}, 9 | } 10 | {{> JsonErrorResponseExtension}} 11 | return fiberCtx.Status(statusCode).JSON(stdError) 12 | {{else}} 13 | return fiberCtx.Status(statusCode).JSON(opError) 14 | {{/LastTypeNameEquals}} -------------------------------------------------------------------------------- /generator/templates/fiber/partials/json.response.hbs: -------------------------------------------------------------------------------- 1 | {{> JsonResponseExtension}} 2 | {{#equal HasReturnValue true}} 3 | {{!-- If validation of output is enabled AND the payload response is an object --}} 4 | {{#if ValidateResponsePayload}} 5 | {{#equal Responses.[0].IsUniverseType false}} 6 | 7 | 8 | var outputValidationErr error 9 | {{#if Responses.[0].IsByAddress}} 10 | if value == nil { 11 | outputValidationErr = fmt.Errorf("Response payload is nil") 12 | } else { 13 | {{/if}} 14 | outputValidationErr = validateDataRecursive(value, "") 15 | {{#if Responses.[0].IsByAddress}} 16 | } 17 | {{/if}} 18 | 19 | if outputValidationErr != nil { 20 | {{> Middleware isErrorMiddleware=true middlewares="onOutputValidationMiddlewares" errorName="outputValidationErr" }} 21 | outputValidationStatusCode := http.StatusInternalServerError 22 | outputValidationRfc7807Error := runtime.Rfc7807Error{ 23 | Type: http.StatusText(outputValidationStatusCode), 24 | Detail: "Encountered an error during operation '{{{OperationId}}}'", 25 | Status: outputValidationStatusCode, 26 | Instance: "/controller/error/{{{OperationId}}}", 27 | Extensions: map[string]string{}, 28 | } 29 | return fiberCtx.Status(outputValidationStatusCode).JSON(outputValidationRfc7807Error) 30 | } 31 | {{/equal}} 32 | {{/if}} 33 | {{> Middleware isErrorMiddleware=false middlewares="afterOperationSuccessMiddlewares" }} 34 | {{> AfterOperationRoutesExtension }} 35 | fiberCtx.Status(statusCode).JSON(value) 36 | {{> RouteEndRoutesExtension }} 37 | return nil 38 | {{/equal}} 39 | {{#equal HasReturnValue false}} 40 | {{> Middleware isErrorMiddleware=false middlewares="afterOperationSuccessMiddlewares" }} 41 | {{> AfterOperationRoutesExtension }} 42 | fiberCtx.Status(statusCode) 43 | {{> RouteEndRoutesExtension }} 44 | return nil 45 | {{/equal}} -------------------------------------------------------------------------------- /generator/templates/fiber/partials/method.parameter.list.hbs: -------------------------------------------------------------------------------- 1 | {{#each FuncParams}} 2 | {{#if IsContext}} 3 | getRequestContext(fiberCtx) 4 | {{else}} 5 | {{#if TypeMeta.IsByAddress}} 6 | 7 | {{else}} 8 | * 9 | {{/if}} 10 | {{ToLowerCamel Name}}RawPtr 11 | {{/if}} 12 | {{#unless @last}}, {{/unless}} 13 | {{/each}} -------------------------------------------------------------------------------- /generator/templates/fiber/partials/middleware.hbs: -------------------------------------------------------------------------------- 1 | // Middlewares {{middlewares}} section 2 | for _, middleware := range {{middlewares}} { 3 | {{#if isErrorMiddleware}} 4 | middlewareCtx, continueOperation := middleware(getRequestContext(fiberCtx), fiberCtx, {{errorName}}) 5 | setRequestContext(fiberCtx, middlewareCtx) 6 | if !continueOperation { 7 | return nil 8 | } 9 | {{else}} 10 | middlewareCtx, continueOperation := middleware(getRequestContext(fiberCtx), fiberCtx) 11 | setRequestContext(fiberCtx, middlewareCtx) 12 | if !continueOperation { 13 | return nil 14 | } 15 | {{/if}} 16 | } 17 | // End middlewares {{middlewares}} section 18 | -------------------------------------------------------------------------------- /generator/templates/fiber/partials/params.validation.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="conversionErr" }} 2 | 3 | validationError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(http.StatusUnprocessableEntity), 5 | Detail: fmt.Sprintf( 6 | "A request was made to operation '{{{OperationId}}}' but parameter '%s' was not properly sent - Expected %s but got %s", 7 | "{{ToLowerCamel Name}}", 8 | "{{{TypeMeta.Name}}}", 9 | reflect.TypeOf({{ToLowerCamel Name}}{{#ifEqual PassedIn "Body"}}{{else}}Raw{{/ifEqual}}).String(), 10 | ), 11 | Status: http.StatusUnprocessableEntity, 12 | Instance: "/validation/error/{{{OperationId}}}", 13 | Extensions: map[string]string{"error": conversionErr.Error()}, 14 | } 15 | {{> ParamsValidationErrorResponseExtension}} 16 | return fiberCtx.Status(http.StatusUnprocessableEntity).JSON(validationError) -------------------------------------------------------------------------------- /generator/templates/fiber/partials/register.middleware.hbs: -------------------------------------------------------------------------------- 1 | type MiddlewareFunc func(ctx context.Context, fiberCtx *fiber.Ctx) (context.Context, bool) 2 | type ErrorMiddlewareFunc func(ctx context.Context, fiberCtx *fiber.Ctx, err error) (context.Context, bool) 3 | 4 | var beforeOperationMiddlewares []MiddlewareFunc 5 | var afterOperationSuccessMiddlewares []MiddlewareFunc 6 | var onErrorMiddlewares []ErrorMiddlewareFunc 7 | var onInputValidationMiddlewares []ErrorMiddlewareFunc 8 | var onOutputValidationMiddlewares []ErrorMiddlewareFunc 9 | 10 | func RegisterMiddleware(executionType runtime.MiddlewareExecutionType, middlewareFunc MiddlewareFunc) { 11 | switch executionType { 12 | case runtime.BeforeOperation: 13 | beforeOperationMiddlewares = append(beforeOperationMiddlewares, middlewareFunc) 14 | case runtime.AfterOperationSuccess: 15 | afterOperationSuccessMiddlewares = append(afterOperationSuccessMiddlewares, middlewareFunc) 16 | } 17 | } 18 | 19 | func RegisterErrorMiddleware(executionType runtime.ErrorMiddlewareExecutionType, errorMiddlewareFunc ErrorMiddlewareFunc) { 20 | switch executionType { 21 | case runtime.OnInputValidationError: 22 | onInputValidationMiddlewares = append(onInputValidationMiddlewares, errorMiddlewareFunc) 23 | case runtime.OnOutputValidationError: 24 | onOutputValidationMiddlewares = append(onOutputValidationMiddlewares, errorMiddlewareFunc) 25 | case runtime.OnOperationError: 26 | onErrorMiddlewares = append(onErrorMiddlewares, errorMiddlewareFunc) 27 | } 28 | } -------------------------------------------------------------------------------- /generator/templates/fiber/partials/reply.response.hbs: -------------------------------------------------------------------------------- 1 | statusCode := getStatusCode(&controller, {{{HasReturnValue}}}, opError) 2 | {{#LastTypeNameEquals Responses "error"}} 3 | if opError != nil { 4 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 5 | {{else}} 6 | {{#LastTypeIsByAddress Responses}} 7 | if opError != nil { 8 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 9 | {{else}} 10 | emptyErr := {{GetLastTyeFullyQualified Responses}}{} 11 | if opError != emptyErr { 12 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 13 | 14 | {{/LastTypeIsByAddress}} 15 | {{/LastTypeNameEquals}} 16 | {{#equal ResponseContentType "application/json"}} 17 | {{> JsonErrorResponse}} 18 | {{/equal}} 19 | } 20 | {{#equal ResponseContentType "application/json"}} 21 | {{> JsonResponse}} 22 | {{/equal}} -------------------------------------------------------------------------------- /generator/templates/fiber/partials/request.args.parsing.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#equal PassedIn "Path"}} 3 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 4 | {{ToLowerCamel Name}}Raw := fiberCtx.Params("{{{NameInSchema}}}") 5 | is{{Name}}Exists := true // if parameter is in route but not provided, it won't reach this handler 6 | {{> RequestSwitchParamType}} 7 | {{> RunValidator}} 8 | {{/equal}} 9 | 10 | {{#equal PassedIn "Query"}} 11 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 12 | {{ToLowerCamel Name}}Raw := fiberCtx.Query("{{{NameInSchema}}}") 13 | is{{Name}}Exists := fiberCtx.Context().QueryArgs().Has("{{{NameInSchema}}}") 14 | {{> RequestSwitchParamType}} 15 | {{> RunValidator}} 16 | {{/equal}} 17 | 18 | {{#equal PassedIn "Header"}} 19 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 20 | {{ToLowerCamel Name}}Raw := fiberCtx.Get("{{{NameInSchema}}}") 21 | is{{Name}}Exists := len(fiberCtx.Request().Header.Peek("{{{NameInSchema}}}")) > 0 22 | {{> RequestSwitchParamType}} 23 | {{> RunValidator}} 24 | {{/equal}} 25 | 26 | {{#equal PassedIn "Form"}} 27 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 28 | {{ToLowerCamel Name}}Raw := fiberCtx.FormValue("{{{NameInSchema}}}") 29 | is{{Name}}Exists := fiberCtx.Context().PostArgs().Has("{{{NameInSchema}}}") 30 | {{> RequestSwitchParamType}} 31 | {{> RunValidator}} 32 | {{/equal}} 33 | 34 | {{#equal PassedIn "Body"}} 35 | var {{ToLowerCamel Name}}RawPtr *{{SlicePrefix TypeMeta.Name}}{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{SliceSlice TypeMeta.Name}} = nil 36 | conversionErr = bindAndValidateBody(fiberCtx, "{{{ResponseContentType}}}", "{{Validator}}",&{{ToLowerCamel Name}}RawPtr) 37 | if conversionErr != nil { 38 | {{> JsonBodyValidationErrorResponse }} 39 | } 40 | {{/equal}} 41 | -------------------------------------------------------------------------------- /generator/templates/fiber/partials/response.headers.hbs: -------------------------------------------------------------------------------- 1 | for key, value := range controller.GetHeaders() { 2 | fiberCtx.Set(key, value) 3 | } 4 | -------------------------------------------------------------------------------- /generator/templates/fiber/partials/run.validator.hbs: -------------------------------------------------------------------------------- 1 | {{#if Validator}} 2 | if validatorErr := validatorInstance.Var({{ToLowerCamel Name}}RawPtr, "{{Validator}}"); validatorErr != nil { 3 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="validatorErr" }} 4 | fieldName := "{{ToLowerCamel Name}}" 5 | validationError := wrapValidatorError(validatorErr, "{{{OperationId}}}", fieldName) 6 | 7 | {{> RunValidatorExtension}} 8 | 9 | return fiberCtx.Status(http.StatusUnprocessableEntity).JSON(validationError) 10 | } 11 | {{/if}} 12 | -------------------------------------------------------------------------------- /generator/templates/fiber/partials/type.declarations.hbs: -------------------------------------------------------------------------------- 1 | type SecurityListRelation string 2 | 3 | const ( 4 | SecurityListRelationAnd SecurityListRelation = "AND" 5 | ) 6 | 7 | type SecurityCheckList struct { 8 | Checks []runtime.SecurityCheck 9 | Relation SecurityListRelation 10 | } 11 | 12 | {{> TypeDeclarationsExtension}} -------------------------------------------------------------------------------- /generator/templates/gin/partials/authorization.call.hbs: -------------------------------------------------------------------------------- 1 | authorize( 2 | ginCtx, 3 | []SecurityCheckList { {{#each Security}} 4 | { 5 | {{#each SecurityAnnotation}} 6 | Relation: SecurityListRelationAnd, 7 | Checks: []runtime.SecurityCheck { 8 | { 9 | SchemaName: "{{{SchemaName}}}", 10 | Scopes: []string{ 11 | {{#each Scopes}}"{{{.}}}",{{#unless @last}} 12 | {{/unless}}{{/each}} 13 | },{{/each}} 14 | }, 15 | }, 16 | }, 17 | {{/each}} 18 | }, 19 | ) 20 | -------------------------------------------------------------------------------- /generator/templates/gin/partials/imports.hbs: -------------------------------------------------------------------------------- 1 | import ( 2 | "context" 3 | "encoding/json" 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | "reflect" 10 | "regexp" 11 | "github.com/go-playground/validator/v10" 12 | "github.com/gin-gonic/gin" 13 | "github.com/gopher-fleece/runtime" 14 | RequestAuth "{{{AuthConfig.AuthFileFullPackageName}}}" 15 | 16 | {{#each Controllers}} 17 | {{{Name}}}Import "{{{FullyQualifiedPackage}}}" 18 | {{#each Routes}} 19 | {{#each FuncParams}} 20 | {{#if TypeMeta.FullyQualifiedPackage}} 21 | Param{{{UniqueImportSerial}}}{{{UnwrapArrayTypeRecursive Name}}} "{{{TypeMeta.FullyQualifiedPackage}}}" 22 | {{/if}} 23 | {{/each}} 24 | {{#each Responses}} 25 | {{#if FullyQualifiedPackage}} 26 | Response{{{UniqueImportSerial}}}{{{UnwrapArrayTypeRecursive Name}}} "{{{FullyQualifiedPackage}}}" 27 | {{/if}} 28 | {{/each}} 29 | {{/each}} 30 | {{/each}} 31 | 32 | {{> ImportsExtension }} 33 | 34 | ) -------------------------------------------------------------------------------- /generator/templates/gin/partials/json.body.validation.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="conversionErr" }} 2 | 3 | validationError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(http.StatusUnprocessableEntity), 5 | Detail: fmt.Sprintf( 6 | "A request was made to operation '{{{OperationId}}}' but body parameter '%s' did not pass validation of '%s' - %s", 7 | "{{ToLowerCamel Name}}", 8 | "{{{TypeMeta.Name}}}", 9 | extractValidationErrorMessage(conversionErr, nil), 10 | ), 11 | Status: http.StatusUnprocessableEntity, 12 | Instance: "/validation/error/{{{OperationId}}}", 13 | } 14 | 15 | {{> JsonBodyValidationErrorResponseExtension}} 16 | 17 | ginCtx.JSON(http.StatusUnprocessableEntity, validationError) 18 | return -------------------------------------------------------------------------------- /generator/templates/gin/partials/json.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{#LastTypeNameEquals Responses "error"}} 2 | 3 | stdError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(statusCode), 5 | Detail: "Encountered an error during operation '{{{OperationId}}}'", 6 | Status: statusCode, 7 | Instance: "/controller/error/{{{OperationId}}}", 8 | Extensions: map[string]string{"error": opError.Error()}, 9 | } 10 | {{> JsonErrorResponseExtension}} 11 | ginCtx.JSON(statusCode, stdError) 12 | {{else}} 13 | ginCtx.JSON(statusCode, opError) 14 | {{/LastTypeNameEquals}} -------------------------------------------------------------------------------- /generator/templates/gin/partials/json.response.hbs: -------------------------------------------------------------------------------- 1 | {{> JsonResponseExtension}} 2 | {{#equal HasReturnValue true}} 3 | {{!-- If validation of output is enabled AND the payload response is an object --}} 4 | {{#if ValidateResponsePayload}} 5 | {{#equal Responses.[0].IsUniverseType false}} 6 | 7 | 8 | var outputValidationErr error 9 | {{#if Responses.[0].IsByAddress}} 10 | if value == nil { 11 | outputValidationErr = fmt.Errorf("Response payload is nil") 12 | } else { 13 | {{/if}} 14 | outputValidationErr = validateDataRecursive(value, "") 15 | {{#if Responses.[0].IsByAddress}} 16 | } 17 | {{/if}} 18 | 19 | if outputValidationErr != nil { 20 | {{> Middleware isErrorMiddleware=true middlewares="onOutputValidationMiddlewares" errorName="outputValidationErr" }} 21 | outputValidationStatusCode := http.StatusInternalServerError 22 | outputValidationRfc7807Error := runtime.Rfc7807Error{ 23 | Type: http.StatusText(outputValidationStatusCode), 24 | Detail: "Encountered an error during operation '{{{OperationId}}}'", 25 | Status: outputValidationStatusCode, 26 | Instance: "/controller/error/{{{OperationId}}}", 27 | Extensions: map[string]string{}, 28 | } 29 | ginCtx.JSON(outputValidationStatusCode, outputValidationRfc7807Error) 30 | return 31 | } 32 | {{/equal}} 33 | {{/if}} 34 | {{> Middleware isErrorMiddleware=false middlewares="afterOperationSuccessMiddlewares" }} 35 | {{> AfterOperationRoutesExtension }} 36 | ginCtx.JSON(statusCode, value) 37 | {{> RouteEndRoutesExtension }} 38 | {{/equal}} 39 | {{#equal HasReturnValue false}} 40 | {{> Middleware isErrorMiddleware=false middlewares="afterOperationSuccessMiddlewares" }} 41 | {{> AfterOperationRoutesExtension }} 42 | ginCtx.Status(statusCode) 43 | {{> RouteEndRoutesExtension }} 44 | {{/equal}} -------------------------------------------------------------------------------- /generator/templates/gin/partials/method.parameter.list.hbs: -------------------------------------------------------------------------------- 1 | {{#each FuncParams}} 2 | {{#if IsContext}} 3 | getRequestContext(ginCtx) 4 | {{else}} 5 | {{#if TypeMeta.IsByAddress}} 6 | 7 | {{else}} 8 | * 9 | {{/if}} 10 | {{ToLowerCamel Name}}RawPtr 11 | {{/if}} 12 | {{#unless @last}}, {{/unless}} 13 | {{/each}} -------------------------------------------------------------------------------- /generator/templates/gin/partials/middleware.hbs: -------------------------------------------------------------------------------- 1 | // Middlewares {{middlewares}} section 2 | for _, middleware := range {{middlewares}} { 3 | {{#if isErrorMiddleware}} 4 | middlewareCtx, continueOperation := middleware(getRequestContext(ginCtx), ginCtx, {{errorName}}) 5 | setRequestContext(ginCtx, middlewareCtx) 6 | if !continueOperation { 7 | return 8 | } 9 | {{else}} 10 | middlewareCtx, continueOperation := middleware(getRequestContext(ginCtx), ginCtx) 11 | setRequestContext(ginCtx, middlewareCtx) 12 | if !continueOperation { 13 | return 14 | } 15 | {{/if}} 16 | } 17 | // End middlewares {{middlewares}} section 18 | -------------------------------------------------------------------------------- /generator/templates/gin/partials/params.validation.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="conversionErr" }} 2 | 3 | validationError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(http.StatusUnprocessableEntity), 5 | Detail: fmt.Sprintf( 6 | "A request was made to operation '{{{OperationId}}}' but parameter '%s' was not properly sent - Expected %s but got %s", 7 | "{{ToLowerCamel Name}}", 8 | "{{{TypeMeta.Name}}}", 9 | reflect.TypeOf({{ToLowerCamel Name}}{{#ifEqual PassedIn "Body"}}{{else}}Raw{{/ifEqual}}).String(), 10 | ), 11 | Status: http.StatusUnprocessableEntity, 12 | Instance: "/validation/error/{{{OperationId}}}", 13 | Extensions: map[string]string{"error": conversionErr.Error()}, 14 | } 15 | {{> ParamsValidationErrorResponseExtension}} 16 | ginCtx.JSON(http.StatusUnprocessableEntity, validationError) 17 | return -------------------------------------------------------------------------------- /generator/templates/gin/partials/register.middleware.hbs: -------------------------------------------------------------------------------- 1 | type MiddlewareFunc func(ctx context.Context, ginCtx *gin.Context) (context.Context, bool) 2 | type ErrorMiddlewareFunc func(ctx context.Context, ginCtx *gin.Context, err error) (context.Context, bool) 3 | 4 | var beforeOperationMiddlewares []MiddlewareFunc 5 | var afterOperationSuccessMiddlewares []MiddlewareFunc 6 | var onErrorMiddlewares []ErrorMiddlewareFunc 7 | var onInputValidationMiddlewares []ErrorMiddlewareFunc 8 | var onOutputValidationMiddlewares []ErrorMiddlewareFunc 9 | 10 | func RegisterMiddleware(executionType runtime.MiddlewareExecutionType, middlewareFunc MiddlewareFunc) { 11 | switch executionType { 12 | case runtime.BeforeOperation: 13 | beforeOperationMiddlewares = append(beforeOperationMiddlewares, middlewareFunc) 14 | case runtime.AfterOperationSuccess: 15 | afterOperationSuccessMiddlewares = append(afterOperationSuccessMiddlewares, middlewareFunc) 16 | } 17 | } 18 | 19 | func RegisterErrorMiddleware(executionType runtime.ErrorMiddlewareExecutionType, errorMiddlewareFunc ErrorMiddlewareFunc) { 20 | switch executionType { 21 | case runtime.OnInputValidationError: 22 | onInputValidationMiddlewares = append(onInputValidationMiddlewares, errorMiddlewareFunc) 23 | case runtime.OnOutputValidationError: 24 | onOutputValidationMiddlewares = append(onOutputValidationMiddlewares, errorMiddlewareFunc) 25 | case runtime.OnOperationError: 26 | onErrorMiddlewares = append(onErrorMiddlewares, errorMiddlewareFunc) 27 | } 28 | } -------------------------------------------------------------------------------- /generator/templates/gin/partials/reply.response.hbs: -------------------------------------------------------------------------------- 1 | statusCode := getStatusCode(&controller, {{{HasReturnValue}}}, opError) 2 | {{#LastTypeNameEquals Responses "error"}} 3 | if opError != nil { 4 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 5 | {{else}} 6 | {{#LastTypeIsByAddress Responses}} 7 | if opError != nil { 8 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 9 | {{else}} 10 | emptyErr := {{GetLastTyeFullyQualified Responses}}{} 11 | if opError != emptyErr { 12 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 13 | 14 | {{/LastTypeIsByAddress}} 15 | {{/LastTypeNameEquals}} 16 | {{#equal ResponseContentType "application/json"}} 17 | {{> JsonErrorResponse}} 18 | {{/equal}} 19 | return 20 | } 21 | {{#equal ResponseContentType "application/json"}} 22 | {{> JsonResponse}} 23 | {{/equal}} -------------------------------------------------------------------------------- /generator/templates/gin/partials/request.args.parsing.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#equal PassedIn "Path"}} 3 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 4 | {{ToLowerCamel Name}}Raw, is{{Name}}Exists := ginCtx.Params.Get("{{{NameInSchema}}}") 5 | {{> RequestSwitchParamType}} 6 | {{> RunValidator}} 7 | {{/equal}} 8 | 9 | {{#equal PassedIn "Query"}} 10 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 11 | {{ToLowerCamel Name}}Raw, is{{Name}}Exists := ginCtx.GetQuery("{{{NameInSchema}}}") 12 | {{> RequestSwitchParamType}} 13 | {{> RunValidator}} 14 | {{/equal}} 15 | 16 | {{#equal PassedIn "Header"}} 17 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 18 | {{ToLowerCamel Name}}Raw := ginCtx.GetHeader("{{{NameInSchema}}}") 19 | _, is{{Name}}Exists := ginCtx.Request.Header[textproto.CanonicalMIMEHeaderKey("{{{NameInSchema}}}")] 20 | {{> RequestSwitchParamType}} 21 | {{> RunValidator}} 22 | {{/equal}} 23 | 24 | {{#equal PassedIn "Form"}} 25 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 26 | {{ToLowerCamel Name}}Raw, is{{Name}}Exists := ginCtx.GetPostForm("{{{NameInSchema}}}") 27 | {{> RequestSwitchParamType}} 28 | {{> RunValidator}} 29 | {{/equal}} 30 | 31 | {{#equal PassedIn "Body"}} 32 | var {{ToLowerCamel Name}}RawPtr *{{SlicePrefix TypeMeta.Name}}{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{SliceSlice TypeMeta.Name}} = nil 33 | conversionErr = bindAndValidateBody(ginCtx, "{{{ResponseContentType}}}", "{{Validator}}",&{{ToLowerCamel Name}}RawPtr) 34 | if conversionErr != nil { 35 | {{> JsonBodyValidationErrorResponse }} 36 | } 37 | {{/equal}} 38 | -------------------------------------------------------------------------------- /generator/templates/gin/partials/response.headers.hbs: -------------------------------------------------------------------------------- 1 | for key, value := range controller.GetHeaders() { 2 | ginCtx.Header(key, value) 3 | } 4 | -------------------------------------------------------------------------------- /generator/templates/gin/partials/run.validator.hbs: -------------------------------------------------------------------------------- 1 | {{#if Validator}} 2 | if validatorErr := validatorInstance.Var({{ToLowerCamel Name}}RawPtr, "{{Validator}}"); validatorErr != nil { 3 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="validatorErr" }} 4 | fieldName := "{{ToLowerCamel Name}}" 5 | validationError := wrapValidatorError(validatorErr, "{{{OperationId}}}", fieldName) 6 | 7 | {{> RunValidatorExtension}} 8 | 9 | ginCtx.JSON(http.StatusUnprocessableEntity, validationError) 10 | return 11 | } 12 | {{/if}} 13 | -------------------------------------------------------------------------------- /generator/templates/gin/partials/type.declarations.hbs: -------------------------------------------------------------------------------- 1 | type SecurityListRelation string 2 | 3 | const ( 4 | SecurityListRelationAnd SecurityListRelation = "AND" 5 | ) 6 | 7 | type SecurityCheckList struct { 8 | Checks []runtime.SecurityCheck 9 | Relation SecurityListRelation 10 | } 11 | 12 | {{> TypeDeclarationsExtension}} -------------------------------------------------------------------------------- /generator/templates/mux/partials/authorization.call.hbs: -------------------------------------------------------------------------------- 1 | authorize( 2 | req, 3 | []SecurityCheckList { {{#each Security}} 4 | { 5 | {{#each SecurityAnnotation}} 6 | Relation: SecurityListRelationAnd, 7 | Checks: []runtime.SecurityCheck { 8 | { 9 | SchemaName: "{{{SchemaName}}}", 10 | Scopes: []string{ 11 | {{#each Scopes}}"{{{.}}}",{{#unless @last}} 12 | {{/unless}}{{/each}} 13 | },{{/each}} 14 | }, 15 | }, 16 | }, 17 | {{/each}} 18 | }, 19 | ) 20 | -------------------------------------------------------------------------------- /generator/templates/mux/partials/imports.hbs: -------------------------------------------------------------------------------- 1 | import ( 2 | "context" 3 | "encoding/json" 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | "reflect" 10 | "regexp" 11 | "github.com/go-playground/validator/v10" 12 | "github.com/gorilla/mux" 13 | "github.com/gopher-fleece/runtime" 14 | RequestAuth "{{{AuthConfig.AuthFileFullPackageName}}}" 15 | 16 | {{#each Controllers}} 17 | {{{Name}}}Import "{{{FullyQualifiedPackage}}}" 18 | {{#each Routes}} 19 | {{#each FuncParams}} 20 | {{#if TypeMeta.FullyQualifiedPackage}} 21 | Param{{{UniqueImportSerial}}}{{{UnwrapArrayTypeRecursive Name}}} "{{{TypeMeta.FullyQualifiedPackage}}}" 22 | {{/if}} 23 | {{/each}} 24 | {{#each Responses}} 25 | {{#if FullyQualifiedPackage}} 26 | Response{{{UniqueImportSerial}}}{{{UnwrapArrayTypeRecursive Name}}} "{{{FullyQualifiedPackage}}}" 27 | {{/if}} 28 | {{/each}} 29 | {{/each}} 30 | {{/each}} 31 | 32 | {{> ImportsExtension }} 33 | 34 | ) -------------------------------------------------------------------------------- /generator/templates/mux/partials/json.body.validation.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="conversionErr" }} 2 | 3 | validationError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(http.StatusUnprocessableEntity), 5 | Detail: fmt.Sprintf( 6 | "A request was made to operation '{{{OperationId}}}' but body parameter '%s' did not pass validation of '%s' - %s", 7 | "{{ToLowerCamel Name}}", 8 | "{{{TypeMeta.Name}}}", 9 | extractValidationErrorMessage(conversionErr, nil), 10 | ), 11 | Status: http.StatusUnprocessableEntity, 12 | Instance: "/validation/error/{{{OperationId}}}", 13 | } 14 | 15 | {{> JsonBodyValidationErrorResponseExtension}} 16 | 17 | w.WriteHeader(http.StatusUnprocessableEntity) 18 | json.NewEncoder(w).Encode(validationError) 19 | return -------------------------------------------------------------------------------- /generator/templates/mux/partials/json.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{#LastTypeNameEquals Responses "error"}} 2 | 3 | stdError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(statusCode), 5 | Detail: "Encountered an error during operation '{{{OperationId}}}'", 6 | Status: statusCode, 7 | Instance: "/controller/error/{{{OperationId}}}", 8 | Extensions: map[string]string{"error": opError.Error()}, 9 | } 10 | {{> JsonErrorResponseExtension}} 11 | w.WriteHeader(statusCode) 12 | json.NewEncoder(w).Encode(stdError) 13 | {{else}} 14 | w.WriteHeader(statusCode) 15 | json.NewEncoder(w).Encode(opError) 16 | {{/LastTypeNameEquals}} -------------------------------------------------------------------------------- /generator/templates/mux/partials/json.response.hbs: -------------------------------------------------------------------------------- 1 | {{> JsonResponseExtension}} 2 | {{#equal HasReturnValue true}} 3 | {{!-- If validation of output is enabled AND the payload response is an object --}} 4 | {{#if ValidateResponsePayload}} 5 | {{#equal Responses.[0].IsUniverseType false}} 6 | 7 | var outputValidationErr error 8 | {{#if Responses.[0].IsByAddress}} 9 | if value == nil { 10 | outputValidationErr = fmt.Errorf("Response payload is nil") 11 | } else { 12 | {{/if}} 13 | outputValidationErr = validateDataRecursive(value, "") 14 | {{#if Responses.[0].IsByAddress}} 15 | } 16 | {{/if}} 17 | 18 | if outputValidationErr != nil { 19 | {{> Middleware isErrorMiddleware=true middlewares="onOutputValidationMiddlewares" errorName="outputValidationErr" }} 20 | outputValidationStatusCode := http.StatusInternalServerError 21 | outputValidationRfc7807Error := runtime.Rfc7807Error{ 22 | Type: http.StatusText(outputValidationStatusCode), 23 | Detail: "Encountered an error during operation '{{{OperationId}}}'", 24 | Status: outputValidationStatusCode, 25 | Instance: "/controller/error/{{{OperationId}}}", 26 | Extensions: map[string]string{}, 27 | } 28 | w.Header().Set("Content-Type", "application/json") 29 | w.WriteHeader(outputValidationStatusCode) 30 | json.NewEncoder(w).Encode(outputValidationRfc7807Error) 31 | return 32 | } 33 | {{/equal}} 34 | {{/if}} 35 | {{> Middleware isErrorMiddleware=false middlewares="afterOperationSuccessMiddlewares" }} 36 | {{> AfterOperationRoutesExtension }} 37 | w.Header().Set("Content-Type", "application/json") 38 | w.WriteHeader(statusCode) 39 | json.NewEncoder(w).Encode(value) 40 | {{> RouteEndRoutesExtension }} 41 | {{/equal}} 42 | {{#equal HasReturnValue false}} 43 | {{> Middleware isErrorMiddleware=false middlewares="afterOperationSuccessMiddlewares" }} 44 | {{> AfterOperationRoutesExtension }} 45 | w.WriteHeader(statusCode) 46 | {{> RouteEndRoutesExtension }} 47 | {{/equal}} -------------------------------------------------------------------------------- /generator/templates/mux/partials/method.parameter.list.hbs: -------------------------------------------------------------------------------- 1 | {{#each FuncParams}} 2 | {{#if IsContext}} 3 | getRequestContext(req) 4 | {{else}} 5 | {{#if TypeMeta.IsByAddress}} 6 | 7 | {{else}} 8 | * 9 | {{/if}} 10 | {{ToLowerCamel Name}}RawPtr 11 | {{/if}} 12 | {{#unless @last}}, {{/unless}} 13 | {{/each}} -------------------------------------------------------------------------------- /generator/templates/mux/partials/middleware.hbs: -------------------------------------------------------------------------------- 1 | // Middlewares {{middlewares}} section 2 | for _, middleware := range {{middlewares}} { 3 | {{#if isErrorMiddleware}} 4 | middlewareCtx, continueOperation := middleware(getRequestContext(req), w, req, {{errorName}}) 5 | setRequestContext(req, middlewareCtx) 6 | if !continueOperation { 7 | return 8 | } 9 | {{else}} 10 | middlewareCtx, continueOperation := middleware(getRequestContext(req), w, req) 11 | setRequestContext(req, middlewareCtx) 12 | if !continueOperation { 13 | return 14 | } 15 | {{/if}} 16 | } 17 | // End middlewares {{middlewares}} section 18 | -------------------------------------------------------------------------------- /generator/templates/mux/partials/params.validation.error.response.hbs: -------------------------------------------------------------------------------- 1 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="conversionErr" }} 2 | 3 | validationError := runtime.Rfc7807Error{ 4 | Type: http.StatusText(http.StatusUnprocessableEntity), 5 | Detail: fmt.Sprintf( 6 | "A request was made to operation '{{{OperationId}}}' but parameter '%s' was not properly sent - Expected %s but got %s", 7 | "{{ToLowerCamel Name}}", 8 | "{{{TypeMeta.Name}}}", 9 | reflect.TypeOf({{ToLowerCamel Name}}{{#ifEqual PassedIn "Body"}}{{else}}Raw{{/ifEqual}}).String(), 10 | ), 11 | Status: http.StatusUnprocessableEntity, 12 | Instance: "/validation/error/{{{OperationId}}}", 13 | Extensions: map[string]string{"error": conversionErr.Error()}, 14 | } 15 | {{> ParamsValidationErrorResponseExtension}} 16 | w.WriteHeader(http.StatusUnprocessableEntity) 17 | json.NewEncoder(w).Encode(validationError) 18 | return -------------------------------------------------------------------------------- /generator/templates/mux/partials/register.middleware.hbs: -------------------------------------------------------------------------------- 1 | type MiddlewareFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool) 2 | type ErrorMiddlewareFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) (context.Context, bool) 3 | 4 | var beforeOperationMiddlewares []MiddlewareFunc 5 | var afterOperationSuccessMiddlewares []MiddlewareFunc 6 | var onErrorMiddlewares []ErrorMiddlewareFunc 7 | var onInputValidationMiddlewares []ErrorMiddlewareFunc 8 | var onOutputValidationMiddlewares []ErrorMiddlewareFunc 9 | 10 | func RegisterMiddleware(executionType runtime.MiddlewareExecutionType, middlewareFunc MiddlewareFunc) { 11 | switch executionType { 12 | case runtime.BeforeOperation: 13 | beforeOperationMiddlewares = append(beforeOperationMiddlewares, middlewareFunc) 14 | case runtime.AfterOperationSuccess: 15 | afterOperationSuccessMiddlewares = append(afterOperationSuccessMiddlewares, middlewareFunc) 16 | } 17 | } 18 | 19 | func RegisterErrorMiddleware(executionType runtime.ErrorMiddlewareExecutionType, errorMiddlewareFunc ErrorMiddlewareFunc) { 20 | switch executionType { 21 | case runtime.OnInputValidationError: 22 | onInputValidationMiddlewares = append(onInputValidationMiddlewares, errorMiddlewareFunc) 23 | case runtime.OnOutputValidationError: 24 | onOutputValidationMiddlewares = append(onOutputValidationMiddlewares, errorMiddlewareFunc) 25 | case runtime.OnOperationError: 26 | onErrorMiddlewares = append(onErrorMiddlewares, errorMiddlewareFunc) 27 | } 28 | } -------------------------------------------------------------------------------- /generator/templates/mux/partials/reply.response.hbs: -------------------------------------------------------------------------------- 1 | statusCode := getStatusCode(&controller, {{{HasReturnValue}}}, opError) 2 | {{#LastTypeNameEquals Responses "error"}} 3 | if opError != nil { 4 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 5 | {{else}} 6 | {{#LastTypeIsByAddress Responses}} 7 | if opError != nil { 8 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 9 | {{else}} 10 | emptyErr := {{GetLastTyeFullyQualified Responses}}{} 11 | if opError != emptyErr { 12 | {{> Middleware isErrorMiddleware=true middlewares="onErrorMiddlewares" errorName="opError" }} 13 | 14 | {{/LastTypeIsByAddress}} 15 | {{/LastTypeNameEquals}} 16 | {{#equal ResponseContentType "application/json"}} 17 | {{> JsonErrorResponse}} 18 | {{/equal}} 19 | return 20 | } 21 | {{#equal ResponseContentType "application/json"}} 22 | {{> JsonResponse}} 23 | {{/equal}} -------------------------------------------------------------------------------- /generator/templates/mux/partials/request.args.parsing.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#equal PassedIn "Path"}} 3 | {{ToLowerCamel Name}}vars := mux.Vars(req) 4 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 5 | {{ToLowerCamel Name}}Raw, is{{Name}}Exists := {{ToLowerCamel Name}}vars["{{{NameInSchema}}}"] 6 | {{> RequestSwitchParamType}} 7 | {{> RunValidator}} 8 | {{/equal}} 9 | 10 | {{#equal PassedIn "Query"}} 11 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 12 | 13 | {{ToLowerCamel Name}}Raw := req.URL.Query().Get("{{{NameInSchema}}}") 14 | is{{Name}}Exists := req.URL.Query().Has("{{{NameInSchema}}}") 15 | {{> RequestSwitchParamType}} 16 | {{> RunValidator}} 17 | {{/equal}} 18 | 19 | {{#equal PassedIn "Header"}} 20 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 21 | {{ToLowerCamel Name}}Raw := req.Header.Get("{{{NameInSchema}}}") 22 | _, is{{Name}}Exists := req.Header["{{{NameInSchema}}}"] 23 | if !is{{Name}}Exists { 24 | // In echo, the req..Header["key"] is not 100% reliable, so we need other check, but only if is was not found in the first method 25 | headerValues := req.Header.Values("{{{NameInSchema}}}") 26 | is{{Name}}Exists = len(headerValues) > 0 27 | } 28 | {{> RequestSwitchParamType}} 29 | {{> RunValidator}} 30 | {{/equal}} 31 | 32 | {{#equal PassedIn "Form"}} 33 | req.ParseForm() 34 | var {{ToLowerCamel Name}}RawPtr *{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{{TypeMeta.Name}}} = nil 35 | {{ToLowerCamel Name}}RawArr, is{{Name}}Exists := req.PostForm["{{{NameInSchema}}}"] 36 | {{ToLowerCamel Name}}Raw := "" 37 | if is{{Name}}Exists { 38 | {{ToLowerCamel Name}}Raw = {{ToLowerCamel Name}}RawArr[0] // Get first value since form values are slices 39 | } 40 | 41 | {{> RequestSwitchParamType}} 42 | {{> RunValidator}} 43 | {{/equal}} 44 | 45 | {{#equal PassedIn "Body"}} 46 | var {{ToLowerCamel Name}}RawPtr *{{SlicePrefix TypeMeta.Name}}{{#if TypeMeta.FullyQualifiedPackage}}Param{{{UniqueImportSerial}}}{{{Name}}}.{{/if}}{{SliceSlice TypeMeta.Name}} = nil 47 | conversionErr = bindAndValidateBody(req, "{{{ResponseContentType}}}", "{{Validator}}",&{{ToLowerCamel Name}}RawPtr) 48 | if conversionErr != nil { 49 | {{> JsonBodyValidationErrorResponse }} 50 | } 51 | {{/equal}} 52 | -------------------------------------------------------------------------------- /generator/templates/mux/partials/response.headers.hbs: -------------------------------------------------------------------------------- 1 | for key, value := range controller.GetHeaders() { 2 | w.Header().Set(key, value) 3 | } 4 | -------------------------------------------------------------------------------- /generator/templates/mux/partials/run.validator.hbs: -------------------------------------------------------------------------------- 1 | {{#if Validator}} 2 | if validatorErr := validatorInstance.Var({{ToLowerCamel Name}}RawPtr, "{{Validator}}"); validatorErr != nil { 3 | {{> Middleware isErrorMiddleware=true middlewares="onInputValidationMiddlewares" errorName="validatorErr" }} 4 | fieldName := "{{ToLowerCamel Name}}" 5 | validationError := wrapValidatorError(validatorErr, "{{{OperationId}}}", fieldName) 6 | 7 | {{> RunValidatorExtension}} 8 | 9 | w.WriteHeader(http.StatusUnprocessableEntity) 10 | json.NewEncoder(w).Encode(validationError) 11 | return 12 | } 13 | {{/if}} 14 | -------------------------------------------------------------------------------- /generator/templates/mux/partials/type.declarations.hbs: -------------------------------------------------------------------------------- 1 | type SecurityListRelation string 2 | 3 | const ( 4 | SecurityListRelationAnd SecurityListRelation = "AND" 5 | ) 6 | 7 | type SecurityCheckList struct { 8 | Checks []runtime.SecurityCheck 9 | Relation SecurityListRelation 10 | } 11 | 12 | {{> TypeDeclarationsExtension}} -------------------------------------------------------------------------------- /gleece.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./*.go", 5 | "./**/*.go" 6 | ] 7 | }, 8 | "routesConfig": { 9 | "engine": "gin", 10 | "outputPath": "./dist/gleece.go", 11 | "outputFilePerms": "0644", 12 | "authorizationConfig": { 13 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 14 | "enforceSecurityOnAllRoutes": true 15 | } 16 | }, 17 | "openapiGeneratorConfig": { 18 | "openapi" : "3.0.0", 19 | "info": { 20 | "title": "Sample API", 21 | "description": "This is a sample API", 22 | "termsOfService": "http://example.com/terms/", 23 | "contact": { 24 | "name": "API Support", 25 | "url": "http://www.example.com/support", 26 | "email": "support@example.com" 27 | }, 28 | "license": { 29 | "name": "Apache 2.0", 30 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 31 | }, 32 | "version": "1.0.0" 33 | }, 34 | "baseUrl": "https://api.example.com", 35 | "securitySchemes": [ 36 | { 37 | "description": "API Key for accessing the API", 38 | "name": "securitySchemaName", 39 | "fieldName": "x-header-name", 40 | "type": "apiKey", 41 | "in": "header" 42 | } 43 | ], 44 | "defaultSecurity": { 45 | "name": "securitySchemaName", 46 | "scopes": [ 47 | "read", 48 | "write" 49 | ] 50 | }, 51 | "specGeneratorConfig": { 52 | "outputPath": "./dist/swagger.json" 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /infrastructure/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | var verbosityLevel LogLevel = 0 9 | 10 | // A logger's level, valued 0-6 where lower levels higher verbosity 11 | type LogLevel uint8 12 | 13 | const ( 14 | // Print everything 15 | LogLevelAll LogLevel = iota 16 | 17 | // Print debug, info, warning, errors and fatal errors 18 | LogLevelDebug 19 | 20 | // Print info, warning, errors and fatal errors 21 | LogLevelInfo 22 | 23 | // Print warnings, errors and fatal errors 24 | LogLevelWarn 25 | 26 | // Print only errors and fatal errors 27 | LogLevelError 28 | 29 | // Print only fatal errors 30 | LogLevelFatal 31 | 32 | // Print nothing. Effectively disables logging 33 | LogLevelNone 34 | ) 35 | 36 | // getPrintPrefix Gets a log prefix for the given log level 37 | func getPrintPrefix(level LogLevel) string { 38 | switch level { 39 | case LogLevelDebug: 40 | return "DEBUG" 41 | case LogLevelInfo: 42 | return "INFO" 43 | case LogLevelWarn: 44 | return "WARN" 45 | case LogLevelError: 46 | return "ERROR" 47 | case LogLevelFatal: 48 | return "FATAL" 49 | } 50 | 51 | return "DEBUG" // Should not get here 52 | } 53 | 54 | // SetLogLevel Sets the logger's verbosity level 55 | func SetLogLevel(level LogLevel) { 56 | verbosityLevel = level 57 | System("Verbosity level set to %s\n", getPrintPrefix(level)) 58 | } 59 | 60 | // Prints a message, if level is greater to or equal to the currently set verbosity level 61 | func logger(level LogLevel, format string, v ...interface{}) { 62 | if level >= verbosityLevel { 63 | prefix := fmt.Sprintf("[%s]", getPrintPrefix(level)) 64 | message := fmt.Sprintf("%-8s %s", prefix, format) 65 | log.Printf(message, v...) 66 | } 67 | } 68 | 69 | // Prints a debug message 70 | func Debug(format string, v ...interface{}) { 71 | logger(LogLevelDebug, format, v...) 72 | } 73 | 74 | // Prints an info message 75 | func Info(format string, v ...interface{}) { 76 | logger(LogLevelInfo, format, v...) 77 | } 78 | 79 | // Prints a warning message 80 | func Warn(format string, v ...interface{}) { 81 | logger(LogLevelWarn, format, v...) 82 | } 83 | 84 | // Prints an error message 85 | func Error(format string, v ...interface{}) { 86 | logger(LogLevelError, format, v...) 87 | } 88 | 89 | // Prints a fatal message 90 | func Fatal(format string, v ...interface{}) { 91 | logger(LogLevelFatal, format, v...) 92 | } 93 | 94 | func System(format string, v ...interface{}) { 95 | message := fmt.Sprintf("[SYSTEM] %s", format) 96 | log.Printf(message, v...) 97 | } 98 | -------------------------------------------------------------------------------- /infrastructure/logger/logger_test.go: -------------------------------------------------------------------------------- 1 | package logger_test 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "testing" 7 | 8 | "github.com/gopher-fleece/gleece/infrastructure/logger" 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | var _ = Describe("Logger Tests", func() { 14 | var buf *bytes.Buffer 15 | 16 | BeforeEach(func() { 17 | buf = new(bytes.Buffer) 18 | log.SetOutput(buf) // Redirect log output to capture it 19 | }) 20 | 21 | AfterEach(func() { 22 | log.SetOutput(GinkgoWriter) // Restore default output 23 | }) 24 | 25 | Context("Setting log levels", func() { 26 | It("should set the log level correctly", func() { 27 | logger.SetLogLevel(logger.LogLevelDebug) 28 | Expect(buf.String()).To(ContainSubstring("[SYSTEM] Verbosity level set to DEBUG")) 29 | 30 | buf.Reset() 31 | logger.SetLogLevel(logger.LogLevelInfo) 32 | Expect(buf.String()).To(ContainSubstring("[SYSTEM] Verbosity level set to INFO")) 33 | }) 34 | }) 35 | 36 | Context("Logging behavior", func() { 37 | BeforeEach(func() { 38 | logger.SetLogLevel(logger.LogLevelInfo) 39 | buf.Reset() 40 | }) 41 | 42 | It("should log messages at or above the set level", func() { 43 | logger.Info("This is an info message") 44 | Expect(buf.String()).To(ContainSubstring("[INFO] This is an info message")) 45 | buf.Reset() 46 | 47 | logger.Warn("This is a warning") 48 | Expect(buf.String()).To(ContainSubstring("[WARN] This is a warning")) 49 | buf.Reset() 50 | 51 | logger.Error("This is an error") 52 | Expect(buf.String()).To(ContainSubstring("[ERROR] This is an error")) 53 | }) 54 | 55 | It("should not log messages below the set level", func() { 56 | logger.Debug("This debug message should not appear") 57 | Expect(buf.String()).NotTo(ContainSubstring("[DEBUG]")) 58 | }) 59 | 60 | It("should log fatal messages even at high log levels", func() { 61 | logger.SetLogLevel(logger.LogLevelFatal) 62 | buf.Reset() 63 | logger.Fatal("This is a fatal error") 64 | Expect(buf.String()).To(ContainSubstring("[FATAL] This is a fatal error")) 65 | }) 66 | }) 67 | }) 68 | 69 | func TestSanityController(t *testing.T) { 70 | logger.SetLogLevel(logger.LogLevelNone) 71 | RegisterFailHandler(Fail) 72 | RunSpecs(t, "Logger Tests") 73 | } 74 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gopher-fleece/gleece/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /test/commandline/commandline.controller.go: -------------------------------------------------------------------------------- 1 | package imports_test 2 | 3 | import ( 4 | "github.com/gopher-fleece/runtime" 5 | ) 6 | 7 | // @Tag(Commandline Controller Tag) 8 | // @Route(/test/commandline) 9 | type CommandlineController struct { 10 | runtime.GleeceController 11 | } 12 | 13 | // @Method(POST) 14 | // @Route(/empty-function) 15 | func (ec *CommandlineController) EmptyFunction() error { 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /test/commandline/gleece.test.config.echo.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./commandline.controller.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "echo", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "commandlineSchema", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "commandlineSchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/commandline/gleece.test.config.gin.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./commandline.controller.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "commandlineSchema", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "commandlineSchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/commandline/gleece.test.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./commandline.controller.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "commandlineSchema", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "commandlineSchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/context/context.controller.go: -------------------------------------------------------------------------------- 1 | package sanity_test 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gopher-fleece/runtime" 7 | ) 8 | 9 | // @Tag(Context Controller Tag) 10 | // @Route(/test/context) 11 | // @Description Context Controller 12 | type ContextController struct { 13 | runtime.GleeceController // Embedding the GleeceController to inherit its methods 14 | } 15 | 16 | // @Method(POST) 17 | // @Route(/context-and-route-param/{id}) 18 | // @Path(id) 19 | func (ec *ContextController) MethodWithContext(ctx context.Context, id string) error { 20 | return nil 21 | } 22 | 23 | // @Method(POST) 24 | // @Route(/{id}/context-as-last-param) 25 | // @Path(id) 26 | func (ec *ContextController) MethodWithLastParamContext(id string, ctx context.Context) error { 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /test/context/gleece.test.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./context.controller.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "securitySchemaName", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "sanitySchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/errorhandling/dummy.controller.go: -------------------------------------------------------------------------------- 1 | package errorhandling_test 2 | 3 | import ( 4 | "github.com/gopher-fleece/runtime" 5 | ) 6 | 7 | // @Tag(Dummy Controller Tag) 8 | // @Route(/test/sanity) 9 | // @Description Sanity Controller 10 | type DummyController struct { 11 | runtime.GleeceController // Embedding the GleeceController to inherit its methods 12 | } 13 | 14 | // @Method(POST) 15 | // @Route(/some/method) 16 | func (ec *DummyController) EmptyMethod() error { 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /test/errorhandling/gleece.broken.json.config: -------------------------------------------------------------------------------- 1 | // Broken config to test error-handling 2 | 3 | "commonConfig": { 4 | "controllerGlobs": [ 5 | "./errorhandling.controller.go" 6 | ] 7 | }, 8 | "routesConfig": { 9 | "engine": "gin", 10 | "outputPath": "./dist/gleece.go", 11 | "outputFilePerms": "0644", 12 | "authorizationConfig": { 13 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 14 | "enforceSecurityOnAllRoutes": true 15 | } 16 | }, 17 | "openapiGeneratorConfig": { 18 | "openapi": "3.0.0", 19 | "info": { 20 | "title": "Sample API", 21 | "description": "This is a sample API", 22 | "termsOfService": "http://example.com/terms/", 23 | "contact": { 24 | "name": "API Support", 25 | "url": "http://www.example.com/support", 26 | "email": "support@example.com" 27 | }, 28 | "license": { 29 | "name": "Apache 2.0", 30 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 31 | }, 32 | "version": "1.0.0" 33 | }, 34 | "baseUrl": "https://api.example.com", 35 | "securitySchemes": [ 36 | { 37 | "description": "API Key for accessing the API", 38 | "name": "securitySchemaName", 39 | "fieldName": "x-header-name", 40 | "type": "apiKey", 41 | "in": "header" 42 | } 43 | ], 44 | "defaultSecurity": { 45 | "name": "sanitySchema", 46 | "scopes": [ 47 | "read", 48 | "write" 49 | ] 50 | }, 51 | "specGeneratorConfig": { 52 | "outputPath": "./dist/swagger.json" 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /test/errorhandling/gleece.broken.override.syntax.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": ["./*.controller.*.go"] 4 | }, 5 | "routesConfig": { 6 | "engine": "gin", 7 | "outputPath": "./dist/gleece.go", 8 | "outputFilePerms": "0644", 9 | "authorizationConfig": { 10 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 11 | "enforceSecurityOnAllRoutes": true 12 | }, 13 | "templateOverrides": { 14 | "Imports": "./imports.broken.hbs" 15 | } 16 | }, 17 | "openapiGeneratorConfig": { 18 | "openapi": "3.0.0", 19 | "info": { 20 | "title": "Sample API", 21 | "description": "This is a sample API", 22 | "termsOfService": "http://example.com/terms/", 23 | "contact": { 24 | "name": "API Support", 25 | "url": "http://www.example.com/support", 26 | "email": "support@example.com" 27 | }, 28 | "license": { 29 | "name": "Apache 2.0", 30 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 31 | }, 32 | "version": "1.0.0" 33 | }, 34 | "baseUrl": "https://api.example.com", 35 | "securitySchemes": [ 36 | { 37 | "description": "API Key for accessing the API", 38 | "name": "securitySchemaName", 39 | "fieldName": "x-header-name", 40 | "type": "apiKey", 41 | "in": "header" 42 | } 43 | ], 44 | "defaultSecurity": { 45 | "name": "sanitySchema", 46 | "scopes": [ 47 | "read", 48 | "write" 49 | ] 50 | }, 51 | "specGeneratorConfig": { 52 | "outputPath": "./dist/swagger.json" 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /test/errorhandling/gleece.invalid.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [] 4 | }, 5 | "routesConfig": { 6 | "engine": "gin", 7 | "outputPath": "./dist/gleece.go", 8 | "outputFilePerms": "0644", 9 | "authorizationConfig": { 10 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 11 | "enforceSecurityOnAllRoutes": true 12 | } 13 | }, 14 | "openapiGeneratorConfig": { 15 | "openapi": "3.0.0", 16 | "info": { 17 | "title": "Sample API", 18 | "description": "This is a sample API", 19 | "termsOfService": "http://example.com/terms/", 20 | "contact": { 21 | "name": "API Support", 22 | "url": "http://www.example.com/support", 23 | "email": "support@example.com" 24 | }, 25 | "license": { 26 | "name": "Apache 2.0", 27 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 28 | }, 29 | "version": "1.0.0" 30 | }, 31 | "baseUrl": "https://api.example.com", 32 | "securitySchemes": [ 33 | { 34 | "description": "API Key for accessing the API", 35 | "name": "securitySchemaName", 36 | "fieldName": "x-header-name", 37 | "type": "apiKey", 38 | "in": "header" 39 | } 40 | ], 41 | "defaultSecurity": { 42 | "name": "sanitySchema", 43 | "scopes": [ 44 | "read", 45 | "write" 46 | ] 47 | }, 48 | "specGeneratorConfig": { 49 | "outputPath": "./dist/swagger.json" 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /test/errorhandling/gleece.missing.extension.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": ["./*.controller.*.go"] 4 | }, 5 | "routesConfig": { 6 | "engine": "gin", 7 | "outputPath": "./dist/gleece.go", 8 | "outputFilePerms": "0644", 9 | "authorizationConfig": { 10 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 11 | "enforceSecurityOnAllRoutes": true 12 | }, 13 | "templateExtensions": { 14 | "ImportsExtension": "./this/does/not.exist" 15 | } 16 | }, 17 | "openapiGeneratorConfig": { 18 | "openapi": "3.0.0", 19 | "info": { 20 | "title": "Sample API", 21 | "description": "This is a sample API", 22 | "termsOfService": "http://example.com/terms/", 23 | "contact": { 24 | "name": "API Support", 25 | "url": "http://www.example.com/support", 26 | "email": "support@example.com" 27 | }, 28 | "license": { 29 | "name": "Apache 2.0", 30 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 31 | }, 32 | "version": "1.0.0" 33 | }, 34 | "baseUrl": "https://api.example.com", 35 | "securitySchemes": [ 36 | { 37 | "description": "API Key for accessing the API", 38 | "name": "securitySchemaName", 39 | "fieldName": "x-header-name", 40 | "type": "apiKey", 41 | "in": "header" 42 | } 43 | ], 44 | "defaultSecurity": { 45 | "name": "sanitySchema", 46 | "scopes": [ 47 | "read", 48 | "write" 49 | ] 50 | }, 51 | "specGeneratorConfig": { 52 | "outputPath": "./dist/swagger.json" 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /test/errorhandling/gleece.missing.partial.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": ["./*.controller.*.go"] 4 | }, 5 | "routesConfig": { 6 | "engine": "gin", 7 | "outputPath": "./dist/gleece.go", 8 | "outputFilePerms": "0644", 9 | "authorizationConfig": { 10 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 11 | "enforceSecurityOnAllRoutes": true 12 | }, 13 | "templateOverrides": { 14 | "Imports": "./this/does/not.exist" 15 | } 16 | }, 17 | "openapiGeneratorConfig": { 18 | "openapi": "3.0.0", 19 | "info": { 20 | "title": "Sample API", 21 | "description": "This is a sample API", 22 | "termsOfService": "http://example.com/terms/", 23 | "contact": { 24 | "name": "API Support", 25 | "url": "http://www.example.com/support", 26 | "email": "support@example.com" 27 | }, 28 | "license": { 29 | "name": "Apache 2.0", 30 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 31 | }, 32 | "version": "1.0.0" 33 | }, 34 | "baseUrl": "https://api.example.com", 35 | "securitySchemes": [ 36 | { 37 | "description": "API Key for accessing the API", 38 | "name": "securitySchemaName", 39 | "fieldName": "x-header-name", 40 | "type": "apiKey", 41 | "in": "header" 42 | } 43 | ], 44 | "defaultSecurity": { 45 | "name": "sanitySchema", 46 | "scopes": [ 47 | "read", 48 | "write" 49 | ] 50 | }, 51 | "specGeneratorConfig": { 52 | "outputPath": "./dist/swagger.json" 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /test/errorhandling/gleece.test.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./errorhandling.controller.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "securitySchemaName", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "sanitySchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/errorhandling/gleece.unknown.extension.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": ["./*.controller.*.go"] 4 | }, 5 | "routesConfig": { 6 | "engine": "gin", 7 | "outputPath": "./dist/gleece.go", 8 | "outputFilePerms": "0644", 9 | "authorizationConfig": { 10 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 11 | "enforceSecurityOnAllRoutes": true 12 | }, 13 | "templateExtensions": { 14 | "thisExtensionsDoesNotExist": "" 15 | } 16 | }, 17 | "openapiGeneratorConfig": { 18 | "openapi": "3.0.0", 19 | "info": { 20 | "title": "Sample API", 21 | "description": "This is a sample API", 22 | "termsOfService": "http://example.com/terms/", 23 | "contact": { 24 | "name": "API Support", 25 | "url": "http://www.example.com/support", 26 | "email": "support@example.com" 27 | }, 28 | "license": { 29 | "name": "Apache 2.0", 30 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 31 | }, 32 | "version": "1.0.0" 33 | }, 34 | "baseUrl": "https://api.example.com", 35 | "securitySchemes": [ 36 | { 37 | "description": "API Key for accessing the API", 38 | "name": "securitySchemaName", 39 | "fieldName": "x-header-name", 40 | "type": "apiKey", 41 | "in": "header" 42 | } 43 | ], 44 | "defaultSecurity": { 45 | "name": "sanitySchema", 46 | "scopes": [ 47 | "read", 48 | "write" 49 | ] 50 | }, 51 | "specGeneratorConfig": { 52 | "outputPath": "./dist/swagger.json" 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /test/errorhandling/gleece.unknown.partial.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": ["./*.controller.*.go"] 4 | }, 5 | "routesConfig": { 6 | "engine": "gin", 7 | "outputPath": "./dist/gleece.go", 8 | "outputFilePerms": "0644", 9 | "authorizationConfig": { 10 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 11 | "enforceSecurityOnAllRoutes": true 12 | }, 13 | "templateOverrides": { 14 | "thisPartialDoesNotExist": "" 15 | } 16 | }, 17 | "openapiGeneratorConfig": { 18 | "openapi": "3.0.0", 19 | "info": { 20 | "title": "Sample API", 21 | "description": "This is a sample API", 22 | "termsOfService": "http://example.com/terms/", 23 | "contact": { 24 | "name": "API Support", 25 | "url": "http://www.example.com/support", 26 | "email": "support@example.com" 27 | }, 28 | "license": { 29 | "name": "Apache 2.0", 30 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 31 | }, 32 | "version": "1.0.0" 33 | }, 34 | "baseUrl": "https://api.example.com", 35 | "securitySchemes": [ 36 | { 37 | "description": "API Key for accessing the API", 38 | "name": "securitySchemaName", 39 | "fieldName": "x-header-name", 40 | "type": "apiKey", 41 | "in": "header" 42 | } 43 | ], 44 | "defaultSecurity": { 45 | "name": "sanitySchema", 46 | "scopes": [ 47 | "read", 48 | "write" 49 | ] 50 | }, 51 | "specGeneratorConfig": { 52 | "outputPath": "./dist/swagger.json" 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /test/errorhandling/gleece.unscanned.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./unscanned.type..controller.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "securitySchemaName", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "sanitySchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/errorhandling/imports.broken.hbs: -------------------------------------------------------------------------------- 1 | {{This should throw a render error 2 | -------------------------------------------------------------------------------- /test/errorhandling/unscanned.type..controller.go: -------------------------------------------------------------------------------- 1 | package errorhandling_test 2 | 3 | import ( 4 | . "github.com/gopher-fleece/gleece/test/types" 5 | "github.com/gopher-fleece/runtime" 6 | ) 7 | 8 | // @Tag(Dummy Controller Tag) 9 | // @Route(/test/sanity) 10 | // @Description Sanity Controller 11 | type UnScannedTypeController struct { 12 | runtime.GleeceController // Embedding the GleeceController to inherit its methods 13 | } 14 | 15 | // @Method(POST) 16 | // @Route(/some/method) 17 | func (ec *UnScannedTypeController) EmptyMethod() (HoldsVeryNestedStructs, error) { 18 | return HoldsVeryNestedStructs{}, nil 19 | } 20 | -------------------------------------------------------------------------------- /test/errors/errors.controller.go: -------------------------------------------------------------------------------- 1 | package imports_test 2 | 3 | import ( 4 | "github.com/gopher-fleece/runtime" 5 | ) 6 | 7 | type SimpleCustomError struct { 8 | error 9 | } 10 | 11 | type ErrorDetails struct { 12 | Code int 13 | Source string 14 | } 15 | 16 | type ComplexCustomError struct { 17 | Details ErrorDetails 18 | AdditionalInfo string 19 | Epoch uint64 20 | error 21 | } 22 | 23 | // @Tag(Errors Controller Tag) 24 | // @Route(/test/errors) 25 | type ErrorsController struct { 26 | runtime.GleeceController 27 | } 28 | 29 | // @Method(POST) 30 | // @Route(/returns-a-simple-non-standard-error) 31 | func (ec *ErrorsController) ReturnsASimpleCustomError() SimpleCustomError { 32 | return SimpleCustomError{} 33 | } 34 | 35 | // @Method(POST) 36 | // @Route(/returns-a-complex-non-standard-error) 37 | func (ec *ErrorsController) ReturnsAComplexCustomError() ComplexCustomError { 38 | return ComplexCustomError{} 39 | } 40 | -------------------------------------------------------------------------------- /test/errors/errors_test.go: -------------------------------------------------------------------------------- 1 | package imports_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gopher-fleece/gleece/definitions" 7 | "github.com/gopher-fleece/gleece/infrastructure/logger" 8 | "github.com/gopher-fleece/gleece/test/utils" 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | var metadata []definitions.ControllerMetadata 14 | var models []definitions.StructMetadata 15 | 16 | var _ = BeforeSuite(func() { 17 | controllers, flatModels, _ := utils.GetControllersAndModelsOrFail() 18 | metadata = controllers 19 | models = flatModels 20 | }) 21 | 22 | var _ = Describe("Errors Controller", func() { 23 | It("Simple errors should be properly detected and resolved", func() { 24 | route := metadata[0].Routes[0] 25 | 26 | Expect(route.Responses).To(HaveLen(1)) 27 | Expect(route.Responses[0].TypeMetadata.Name).To(Equal("SimpleCustomError")) 28 | Expect(route.Responses[0].TypeMetadata.FullyQualifiedPackage).To(Equal("github.com/gopher-fleece/gleece/test/errors")) 29 | Expect(route.Responses[0].TypeMetadata.DefaultPackageAlias).To(Equal("errors")) 30 | Expect(route.Responses[0].TypeMetadata.Import).To(Equal(definitions.ImportTypeNone)) 31 | Expect(route.Responses[0].TypeMetadata.IsUniverseType).To(BeFalse()) 32 | Expect(route.Responses[0].TypeMetadata.IsByAddress).To(BeFalse()) 33 | Expect(route.Responses[0].TypeMetadata.EntityKind).To(Equal(definitions.AstNodeKindStruct)) 34 | }) 35 | 36 | It("Complex errors should be properly detected and resolved", func() { 37 | route := metadata[0].Routes[1] 38 | 39 | Expect(route.Responses).To(HaveLen(1)) 40 | Expect(route.Responses[0].TypeMetadata.Name).To(Equal("ComplexCustomError")) 41 | Expect(route.Responses[0].TypeMetadata.FullyQualifiedPackage).To(Equal("github.com/gopher-fleece/gleece/test/errors")) 42 | Expect(route.Responses[0].TypeMetadata.DefaultPackageAlias).To(Equal("errors")) 43 | Expect(route.Responses[0].TypeMetadata.Import).To(Equal(definitions.ImportTypeNone)) 44 | Expect(route.Responses[0].TypeMetadata.IsUniverseType).To(BeFalse()) 45 | Expect(route.Responses[0].TypeMetadata.IsByAddress).To(BeFalse()) 46 | Expect(route.Responses[0].TypeMetadata.EntityKind).To(Equal(definitions.AstNodeKindStruct)) 47 | }) 48 | }) 49 | 50 | func TestErrorsController(t *testing.T) { 51 | logger.SetLogLevel(logger.LogLevelNone) 52 | RegisterFailHandler(Fail) 53 | RunSpecs(t, "Errors Controller") 54 | } 55 | -------------------------------------------------------------------------------- /test/errors/gleece.test.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./errors.controller.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "securitySchemaName", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "sanitySchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/fixtures/authorization.go: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/gopher-fleece/runtime" 6 | ) 7 | 8 | func GleeceRequestAuthorization(ctx *gin.Context, check runtime.SecurityCheck) *runtime.SecurityError { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/custom.validations.go: -------------------------------------------------------------------------------- 1 | package fixtures 2 | 3 | import ( 4 | "unicode" 5 | 6 | "github.com/gopher-fleece/runtime" 7 | ) 8 | 9 | // Custom validation function to check if a string starts with a letter 10 | func ValidateStartsWithLetter(fl runtime.ValidationFieldLevel) bool { 11 | field := fl.Field().String() 12 | if field == "" { 13 | return false 14 | } 15 | firstChar := rune(field[0]) 16 | return unicode.IsLetter(firstChar) 17 | } 18 | -------------------------------------------------------------------------------- /test/imports/gleece.test.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./imports.controller.go", 5 | "../types/*.go" 6 | ] 7 | }, 8 | "routesConfig": { 9 | "engine": "gin", 10 | "outputPath": "./dist/gleece.go", 11 | "outputFilePerms": "0644", 12 | "authorizationConfig": { 13 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 14 | "enforceSecurityOnAllRoutes": true 15 | } 16 | }, 17 | "openapiGeneratorConfig": { 18 | "openapi": "3.0.0", 19 | "info": { 20 | "title": "Sample API", 21 | "description": "This is a sample API", 22 | "termsOfService": "http://example.com/terms/", 23 | "contact": { 24 | "name": "API Support", 25 | "url": "http://www.example.com/support", 26 | "email": "support@example.com" 27 | }, 28 | "license": { 29 | "name": "Apache 2.0", 30 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 31 | }, 32 | "version": "1.0.0" 33 | }, 34 | "baseUrl": "https://api.example.com", 35 | "securitySchemes": [ 36 | { 37 | "description": "API Key for accessing the API", 38 | "name": "securitySchemaName", 39 | "fieldName": "x-header-name", 40 | "type": "apiKey", 41 | "in": "header" 42 | } 43 | ], 44 | "defaultSecurity": { 45 | "name": "sanitySchema", 46 | "scopes": [ 47 | "read", 48 | "write" 49 | ] 50 | }, 51 | "specGeneratorConfig": { 52 | "outputPath": "./dist/swagger.json" 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /test/imports/imports.controller.go: -------------------------------------------------------------------------------- 1 | package imports_test 2 | 3 | import ( 4 | "github.com/gopher-fleece/gleece/test/types" 5 | . "github.com/gopher-fleece/gleece/test/types" 6 | alias "github.com/gopher-fleece/gleece/test/types" 7 | . "github.com/gopher-fleece/runtime" 8 | ) 9 | 10 | // @Tag(Imports Controller Tag) 11 | // @Route(/test/imports) 12 | type ImportsController struct { 13 | // Importing with . to test that specific AST enumeration branch 14 | // which is slightly different than for function parameters/return values 15 | GleeceController 16 | } 17 | 18 | // @Method(POST) 19 | // @Route(/imported-with-dot) 20 | // @Body(input) 21 | func (ec *ImportsController) ImportedWithDot(input ImportedWithDot) (ImportedWithDot, error) { 22 | return ImportedWithDot{}, nil 23 | } 24 | 25 | // @Method(POST) 26 | // @Route(/imported-with-default-alias) 27 | // @Body(input) 28 | func (ec *ImportsController) ImportedWithDefaultAlias( 29 | input types.ImportedWithDefaultAlias, 30 | ) (types.ImportedWithDefaultAlias, error) { 31 | return types.ImportedWithDefaultAlias{}, nil 32 | } 33 | 34 | // @Method(POST) 35 | // @Route(/imported-with-custom-alias) 36 | // @Body(input) 37 | func (ec *ImportsController) ImportedWithCustomAlias( 38 | input alias.ImportedWithCustomAlias, 39 | ) (alias.ImportedWithCustomAlias, error) { 40 | return alias.ImportedWithCustomAlias{}, nil 41 | } 42 | -------------------------------------------------------------------------------- /test/sanity/gleece.test.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./sanity.controller.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "securitySchemaName", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "sanitySchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/sanity/sanity.controller.go: -------------------------------------------------------------------------------- 1 | package sanity_test 2 | 3 | import ( 4 | "github.com/gopher-fleece/gleece/test/units" 5 | "github.com/gopher-fleece/runtime" 6 | ) 7 | 8 | // Some comment 9 | // @Description This should be the actual description 10 | type SimpleResponseModel struct { 11 | // A description for the value 12 | SomeValue int `validate:"required,min=0,max=10"` 13 | } 14 | 15 | // @Tag(Sanity Controller Tag) 16 | // @Route(/test/sanity) 17 | // @Description Sanity Controller 18 | type SanityController struct { 19 | runtime.GleeceController // Embedding the GleeceController to inherit its methods 20 | } 21 | 22 | // A sanity test controller method 23 | // @Method(POST) 24 | // @Route(/{routeParamAlias}) 25 | // @Path(routeParam, {name: "routeParamAlias"}) 26 | // @Query(queryParam) 27 | // @Header(headerParam) 28 | // @Response(200) Description for HTTP 200 29 | // @ErrorResponse(500) Code 500 30 | // @ErrorResponse(502) Code 502 31 | func (ec *SanityController) ValidMethodWithSimpleRouteQueryAndHeaderParameters( 32 | routeParam string, 33 | queryParam int, 34 | headerParam float32, 35 | ) (SimpleResponseModel, error) { 36 | return SimpleResponseModel{}, nil 37 | } 38 | 39 | // @Method(POST) 40 | // @Route(/imports-from-other-package) 41 | // @Body(body) 42 | func (ec *SanityController) ImportsTypeFromOtherPackage(body []units.StructWithStructSlice) error { 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /test/security/gleece.test.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./security.controller.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "Schema 1", 37 | "name": "secSchema1", 38 | "fieldName": "x-header-1", 39 | "type": "apiKey", 40 | "in": "header" 41 | }, 42 | { 43 | "description": "Schema 2", 44 | "name": "secSchema2", 45 | "fieldName": "x-header-2", 46 | "type": "apiKey", 47 | "in": "header" 48 | } 49 | ], 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/security/security.controller.go: -------------------------------------------------------------------------------- 1 | package security_test 2 | 3 | import ( 4 | "github.com/gopher-fleece/runtime" 5 | ) 6 | 7 | // @Tag(Sanity Controller Tag) 8 | // @Route(/test/sanity) 9 | type SecurityController struct { 10 | runtime.GleeceController // Embedding the GleeceController to inherit its methods 11 | } 12 | 13 | // A sanity test controller method 14 | // @Method(POST) 15 | // @Route(/security) 16 | // @Security(secSchema1, { scopes: ["scope1"] }) 17 | // @Security(secSchema2, { scopes: ["2", "3"] }) 18 | func (ec *SecurityController) ValidMethodWithComplexSecurity() error { 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /test/security/security_test.go: -------------------------------------------------------------------------------- 1 | package security_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gopher-fleece/gleece/definitions" 7 | "github.com/gopher-fleece/gleece/infrastructure/logger" 8 | "github.com/gopher-fleece/gleece/test/utils" 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | var metadata []definitions.ControllerMetadata 14 | 15 | var _ = BeforeSuite(func() { 16 | controllers, _, _ := utils.GetControllersAndModelsOrFail() 17 | metadata = controllers 18 | }) 19 | 20 | var _ = Describe("Security Controller", func() { 21 | It("Created metadata has length", func() { 22 | Expect(metadata).ToNot(BeNil()) 23 | Expect(metadata).To(HaveLen(1)) 24 | }) 25 | 26 | It("Produces correct controller level security", func() { 27 | controllerMeta := metadata[0] 28 | Expect(controllerMeta.Security).To(HaveLen(0)) 29 | }) 30 | 31 | It("Produces correct route level security", func() { 32 | route := metadata[0].Routes[0] 33 | Expect(route.Security).To(HaveLen(2)) 34 | Expect(route.Security[0].SecurityAnnotation).To(HaveLen(1)) 35 | Expect(route.Security[0].SecurityAnnotation[0].SchemaName).To(Equal("secSchema1")) 36 | Expect(route.Security[0].SecurityAnnotation[0].Scopes).To(HaveLen(1)) 37 | Expect(route.Security[0].SecurityAnnotation[0].Scopes[0]).To(Equal("scope1")) 38 | 39 | Expect(route.Security[1].SecurityAnnotation).To(HaveLen(1)) 40 | Expect(route.Security[1].SecurityAnnotation[0].SchemaName).To(Equal("secSchema2")) 41 | Expect(route.Security[1].SecurityAnnotation[0].Scopes).To(HaveLen(2)) 42 | Expect(route.Security[1].SecurityAnnotation[0].Scopes).To(HaveExactElements("2", "3")) 43 | }) 44 | }) 45 | 46 | func TestSecurityController(t *testing.T) { 47 | logger.SetLogLevel(logger.LogLevelNone) 48 | RegisterFailHandler(Fail) 49 | RunSpecs(t, "Security Controller") 50 | } 51 | -------------------------------------------------------------------------------- /test/types/basic.types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type StringAlias string 4 | 5 | type ImportedWithDefaultAlias struct { 6 | FieldA uint 7 | FieldB string 8 | } 9 | type ImportedWithCustomAlias struct { 10 | FieldA uint 11 | FieldB string 12 | } 13 | type ImportedWithDot struct { 14 | FieldA uint 15 | FieldB string 16 | } 17 | 18 | type SomeVeryNestedStruct struct { 19 | // This is a very nested field A 20 | FieldA string 21 | FieldB uint64 22 | } 23 | 24 | type SomeNestedStruct struct { 25 | FieldA int 26 | // This is field B 27 | FieldB SomeVeryNestedStruct 28 | } 29 | 30 | // This struct holds some nested structs 31 | // @Deprecated 32 | // This comment should not be included in the description 33 | type HoldsVeryNestedStructs struct { 34 | FieldA float32 35 | FieldB uint `json:"fieldB" validator:"required"` 36 | FieldC SomeNestedStruct 37 | } 38 | -------------------------------------------------------------------------------- /test/units/compilation/compilation.utils_test.go: -------------------------------------------------------------------------------- 1 | package ast_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gopher-fleece/gleece/generator/compilation" 7 | "github.com/gopher-fleece/gleece/infrastructure/logger" 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | ) 11 | 12 | const stdUnformattedCodeChunk = ` 13 | package abc 14 | 15 | func TestFunc()( string ,error ) { 16 | return "", nil 17 | } 18 | ` 19 | 20 | const invalidImportsCodeChunk = ` 21 | package abc, def 22 | 23 | func TestFunc() (string, error) { 24 | return "", nil 25 | } 26 | ` 27 | 28 | const stdFormattedCodeChunk = "package abc\nfunc TestFunc() (string, error) {\n\treturn \"\", nil\n}\n" 29 | 30 | var _ = Describe("Unit Tests - Compilation", func() { 31 | Context("OptimizeImportsAndFormat", func() { 32 | It("Given correct input, formats without error and returns correct value", func() { 33 | formatted, err := compilation.OptimizeImportsAndFormat(stdUnformattedCodeChunk) 34 | Expect(err).To(BeNil()) 35 | Expect(formatted).To(Equal(stdFormattedCodeChunk)) 36 | }) 37 | 38 | It("Given invalid imports, returns correct error", func() { 39 | formatted, err := compilation.OptimizeImportsAndFormat(invalidImportsCodeChunk) 40 | Expect(err).To(MatchError(ContainSubstring("failed to optimize imports"))) 41 | Expect(err).To(MatchError(ContainSubstring("expected ';', found ','"))) 42 | Expect(formatted).To(BeEmpty()) 43 | }) 44 | 45 | // Imports optimization performs gross syntax validation and as such, 46 | // it's borderline (or outright) impossible to get format.Source to break if imports.Process did not, 47 | // hence no third test here. 48 | }) 49 | }) 50 | 51 | func TestUnits(t *testing.T) { 52 | logger.SetLogLevel(logger.LogLevelNone) 53 | RegisterFailHandler(Fail) 54 | RunSpecs(t, "Unit Tests - Compilation") 55 | } 56 | -------------------------------------------------------------------------------- /test/units/units.test.types.go: -------------------------------------------------------------------------------- 1 | package units 2 | 3 | import "go/ast" 4 | 5 | type StructA struct { 6 | } 7 | 8 | type InterfaceA interface { 9 | FuncA() 10 | } 11 | 12 | type EnumTypeA string 13 | 14 | const ( 15 | EnumValueA EnumTypeA = "A" 16 | EnumValueB EnumTypeA = "B" 17 | ) 18 | 19 | type AliasTypeA = string 20 | 21 | const ConstA = 1 22 | 23 | type IntAlias int 24 | 25 | type StructForGetUnderlyingTypeName struct { 26 | FieldIntAlias IntAlias 27 | FieldStringPtr *string 28 | FieldIntSlice []int 29 | FieldIntArray [3]int 30 | FieldStringIntMap map[string]int 31 | FieldChannelInt chan int 32 | FieldFunc func() 33 | FieldInterface any 34 | FieldStruct struct{} 35 | FieldInt (int) 36 | FieldComment ast.Comment 37 | } 38 | 39 | func SimpleVariadicFunc(...int) {} 40 | 41 | func NotAReceiver() {} 42 | 43 | type StructWithReceivers struct{} 44 | 45 | func (s StructWithReceivers) ValueReceiverForStructWithReceivers() {} 46 | func (s *StructWithReceivers) PointerReceiverForStructWithReceivers() {} 47 | 48 | type SimpleStruct struct { 49 | FieldA string 50 | FieldB int 51 | } 52 | 53 | type StructWithStructSlice struct { 54 | SimpleStructSlice []SimpleStruct 55 | } 56 | -------------------------------------------------------------------------------- /test/visitors/configs/receiver.with.invalid.json.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./controllers/receiver.with.invalid.json.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "securitySchemaName", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "sanitySchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/visitors/configs/receiver.with.no.comments.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./controllers/receiver.with.no.comments.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "securitySchemaName", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "sanitySchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/visitors/configs/receiver.with.no.method.annotation.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./controllers/receiver.with.no.method.annotation.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "securitySchemaName", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "sanitySchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/visitors/configs/receiver.with.no.route.annotation.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./controllers/receiver.with.no.route.annotation.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "securitySchemaName", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "sanitySchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/visitors/configs/receiver.with.non.error.return.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./controllers/receiver.with.non.error.return.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "securitySchemaName", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "sanitySchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/visitors/configs/receiver.with.void.return.json: -------------------------------------------------------------------------------- 1 | { 2 | "commonConfig": { 3 | "controllerGlobs": [ 4 | "./controllers/receiver.with.void.return.go" 5 | ] 6 | }, 7 | "routesConfig": { 8 | "engine": "gin", 9 | "outputPath": "./dist/gleece.go", 10 | "outputFilePerms": "0644", 11 | "authorizationConfig": { 12 | "authFileFullPackageName": "github.com/gopher-fleece/gleece/test/fixtures", 13 | "enforceSecurityOnAllRoutes": true 14 | } 15 | }, 16 | "openapiGeneratorConfig": { 17 | "openapi": "3.0.0", 18 | "info": { 19 | "title": "Sample API", 20 | "description": "This is a sample API", 21 | "termsOfService": "http://example.com/terms/", 22 | "contact": { 23 | "name": "API Support", 24 | "url": "http://www.example.com/support", 25 | "email": "support@example.com" 26 | }, 27 | "license": { 28 | "name": "Apache 2.0", 29 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 30 | }, 31 | "version": "1.0.0" 32 | }, 33 | "baseUrl": "https://api.example.com", 34 | "securitySchemes": [ 35 | { 36 | "description": "API Key for accessing the API", 37 | "name": "securitySchemaName", 38 | "fieldName": "x-header-name", 39 | "type": "apiKey", 40 | "in": "header" 41 | } 42 | ], 43 | "defaultSecurity": { 44 | "name": "sanitySchema", 45 | "scopes": [ 46 | "read", 47 | "write" 48 | ] 49 | }, 50 | "specGeneratorConfig": { 51 | "outputPath": "./dist/swagger.json" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /test/visitors/controllers/receiver.with.invalid.json.go: -------------------------------------------------------------------------------- 1 | package visitors_test 2 | 3 | import ( 4 | "github.com/gopher-fleece/runtime" 5 | ) 6 | 7 | // @Tag(Receiver With Invalid Json) 8 | // @Route(/test/route) 9 | // @Description Receiver With Invalid Json 10 | type ReceiverWithNoComments struct { 11 | runtime.GleeceController 12 | } 13 | 14 | // @Query(id, {' Invalid JSON5 }) 15 | func (rc *ReceiverWithNoComments) HasInvalidJson() error { 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /test/visitors/controllers/receiver.with.no.comments.go: -------------------------------------------------------------------------------- 1 | package visitors_test 2 | 3 | import ( 4 | "github.com/gopher-fleece/runtime" 5 | ) 6 | 7 | // @Tag(Receiver With No Comments) 8 | // @Route(/test/route) 9 | // @Description Receiver With No Comments 10 | type ReceiverWithInvalidJson struct { 11 | runtime.GleeceController 12 | } 13 | 14 | func (rc *ReceiverWithInvalidJson) NotAnApiMethod() error { 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /test/visitors/controllers/receiver.with.no.method.annotation.go: -------------------------------------------------------------------------------- 1 | package visitors_test 2 | 3 | import ( 4 | "github.com/gopher-fleece/runtime" 5 | ) 6 | 7 | // @Tag(Receiver With No Method Annotation) 8 | // @Route(/test/route) 9 | // @Description Receiver With No Method Annotation 10 | type ReceiverWithNoMethodAnnotation struct { 11 | runtime.GleeceController 12 | } 13 | 14 | // @Route(/) 15 | func (rc *ReceiverWithNoMethodAnnotation) NoMethodAnnotation() error { 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /test/visitors/controllers/receiver.with.no.route.annotation.go: -------------------------------------------------------------------------------- 1 | package visitors_test 2 | 3 | import ( 4 | "github.com/gopher-fleece/runtime" 5 | ) 6 | 7 | // @Tag(Receiver With No Route Annotation) 8 | // @Route(/test/route) 9 | // @Description Receiver With No Route Annotation 10 | type ReceiverWithNoRouteAnnotation struct { 11 | runtime.GleeceController 12 | } 13 | 14 | // @Method(GET) 15 | func (rc *ReceiverWithNoRouteAnnotation) NoRouteAnnotation() error { 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /test/visitors/controllers/receiver.with.non.error.return.go: -------------------------------------------------------------------------------- 1 | package visitors_test 2 | 3 | import ( 4 | "github.com/gopher-fleece/runtime" 5 | ) 6 | 7 | // @Tag(Receiver With Non Error Return) 8 | // @Route(/test/route) 9 | // @Description Receiver With Non Error Return 10 | type ReceiverWithNonErrorReturn struct { 11 | runtime.GleeceController 12 | } 13 | 14 | // @Method(GET) 15 | // @Route(/) 16 | func (rc *ReceiverWithNonErrorReturn) NonErrorReturn() bool { 17 | return true 18 | } 19 | -------------------------------------------------------------------------------- /test/visitors/controllers/receiver.with.void.return.go: -------------------------------------------------------------------------------- 1 | package visitors_test 2 | 3 | import ( 4 | "github.com/gopher-fleece/runtime" 5 | ) 6 | 7 | // @Tag(Receiver With Void Return) 8 | // @Route(/test/route) 9 | // @Description Receiver With Void Return 10 | type ReceiverWithVoidReturn struct { 11 | runtime.GleeceController 12 | } 13 | 14 | // @Method(GET) 15 | // @Route(/) 16 | func (rc *ReceiverWithVoidReturn) VoidReturn() { 17 | } 18 | --------------------------------------------------------------------------------