diff --git a/.github/workflows/update-doc.yml b/.github/workflows/update-doc.yml new file mode 100644 index 00000000..0eb3fecc --- /dev/null +++ b/.github/workflows/update-doc.yml @@ -0,0 +1,134 @@ +# SPDX-FileCopyrightText: Copyright 2015-2026 go-swagger maintainers +# SPDX-License-Identifier: Apache-2.0 + +name: "Update documentation" + +permissions: + contents: read + +on: + push: + tags: + - v* + branches: [ "master" ] + paths: + - docs/** + - hack/doc-site/** + - .github/workflows/update-doc.yml + + pull_request: + paths: + - docs/** + - hack/doc-site/** + - .github/workflows/update-doc.yml + +concurrency: + group: "pages" + cancel-in-progress: false + +defaults: + run: + shell: bash + +jobs: + build-doc: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: '1' + submodules: recursive + # No sparse-checkout: the `code` shortcode mounts the repo root + # (assets/examples ← ../../..) to embed the real example sources, + # which live in top-level dirs (cli/, task-tracker/, …), not under + # docs/. A sparse checkout of hack/+docs/ would leave those files + # absent and every `code` shortcode would fail to resolve its asset. + - + name: Get all tags [go-swagger repo] + if: ${{ github.repository == 'go-swagger/examples' }} + run: | + git fetch origin --prune --update-shallow --tags 'refs/tags/*:refs/tags/*' + - + name: Get all tags [fork] + if: ${{ github.repository != 'go-swagger/examples' }} + run: | + git remote add upstream "https://github.com/go-swagger/examples" + git fetch upstream --prune --update-shallow --tags 'refs/tags/*:refs/tags/*' + git fetch origin --prune --update-shallow --tags 'refs/tags/*:refs/tags/*' + - + name: Initialize theme + env: + RELEARN_VERSION: 9.0.3 + run: | + cd hack/doc-site/hugo + + # Clone theme + curl -sL -o relearn.tgz https://github.com/McShelby/hugo-theme-relearn/archive/refs/tags/"${RELEARN_VERSION}".tar.gz + tar xf relearn.tgz + rm -rf themes/hugo-relearn + mv "hugo-theme-relearn-${RELEARN_VERSION}" hugo-relearn + mv hugo-relearn themes/ + - + name: Prepare config + run: | + # Builds a commit-dependant extra config to inject parameterization. + # HUGO doesn't support config from the command line. + # + # Set specific parameters that are used in some parameterized document. + # This is used to keep up-to-date installation instructions. + cd hack/doc-site/hugo + + ROOT=$(git rev-parse --show-toplevel) + VERSION_MESSAGE="Documentation set for latest master." + REQUIRED_GO_VERSION=$(grep "^go\s" "${ROOT}"/go.mod|cut -d" " -f2) + LATEST_RELEASE=$(git tag --list --sort -version:refname 'v*' 2>/dev/null | head -1 || echo "dev") + BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + + echo " Latest release: ${LATEST_RELEASE}" + echo " Go version: ${REQUIRED_GO_VERSION}" + echo " Build time: ${BUILD_TIME}" + echo " Version message: ${VERSION_MESSAGE}" + + # Generate dynamic config + cat examples.yaml.template \ + | sed "s|{{ GO_VERSION }}|${REQUIRED_GO_VERSION}|g" \ + | sed "s|{{ LATEST_RELEASE }}|${LATEST_RELEASE}|g" \ + | sed "s|{{ VERSION_MESSAGE }}|${VERSION_MESSAGE}|g" \ + | sed "s|{{ BUILD_TIME }}|${BUILD_TIME}|g" \ + > examples.yaml + - + name: Build site with Hugo + uses: crazy-max/ghaction-hugo@d629f74d3e4a9da53050610da35a59863ab9b26c # v3.3.0 + with: + version: v0.153.3 # <- pin the HUGO version, as they often break things + extended: true + args: --config hugo.yaml,examples.yaml --buildDrafts --cleanDestinationDir --minify --printPathWarnings --ignoreCache --noBuildLock --logLevel info --source ${{ github.workspace }}/hack/doc-site/hugo + - + name: Upload artifact + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: hack/doc-site/hugo/public + + deploy-doc: + if: ${{ github.event_name != 'pull_request' }} + needs: build-doc + outputs: + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - + name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 + - + name: Report URL + run: | + echo "::notice::Deployed doc site to ${{ steps.deployment.outputs.page_url }}" diff --git a/alias-compatibility/api.go b/alias-compatibility/api.go index ccbd5f1b..59f6faae 100644 --- a/alias-compatibility/api.go +++ b/alias-compatibility/api.go @@ -33,12 +33,16 @@ // swagger:meta package demo +// snippet:aliases + // Identifier represents a unique identifier. type Identifier string // UserID is an alias to Identifier for user-specific IDs. type UserID = Identifier +// endsnippet:aliases + // User represents a user in the system. type User struct { ID UserID `json:"id"` diff --git a/authentication/restapi/configure_auth_sample.go b/authentication/restapi/configure_auth_sample.go index 9825b8e8..2312659b 100644 --- a/authentication/restapi/configure_auth_sample.go +++ b/authentication/restapi/configure_auth_sample.go @@ -41,6 +41,7 @@ func configureAPI(api *operations.AuthSampleAPI) http.Handler { api.JSONProducer = runtime.JSONProducer() // Applies when the "x-token" header is set + // snippet:keyauth if api.KeyAuth == nil { api.KeyAuth = func(token string) (*models.Principal, error) { _ = token @@ -48,6 +49,7 @@ func configureAPI(api *operations.AuthSampleAPI) http.Handler { return nil, errors.NotImplemented("api key auth (key) x-token from header param [x-token] has not yet been implemented") } } + // endsnippet:keyauth // Set your custom authorizer if needed. Default one is security.Authorized() // Expected interface runtime.Authorizer diff --git a/authentication/swagger.yml b/authentication/swagger.yml index 81c388c8..48b3d50e 100644 --- a/authentication/swagger.yml +++ b/authentication/swagger.yml @@ -11,6 +11,7 @@ consumes: - application/keyauth.api.v1+json produces: - application/keyauth.api.v1+json +# snippet:security securityDefinitions: key: type: apiKey @@ -18,6 +19,7 @@ securityDefinitions: name: x-token security: - key: [] +# endsnippet:security paths: /customers: post: diff --git a/auto-configure/implementation/todos_impl.go b/auto-configure/implementation/todos_impl.go index a3b79cf0..01fb0cfa 100644 --- a/auto-configure/implementation/todos_impl.go +++ b/auto-configure/implementation/todos_impl.go @@ -20,6 +20,8 @@ type TodosHandlerImpl struct { idx int64 } +// snippet:add-one + func (i *TodosHandlerImpl) AddOne(params todos.AddOneParams, principal any) middleware.Responder { _ = principal @@ -41,6 +43,8 @@ func (i *TodosHandlerImpl) AddOne(params todos.AddOneParams, principal any) midd return todos.NewAddOneCreated().WithPayload(newItem) } +// endsnippet:add-one + func (i *TodosHandlerImpl) DestroyOne(params todos.DestroyOneParams, principal any) middleware.Responder { _ = principal diff --git a/composed-auth/auth/authorizers.go b/composed-auth/auth/authorizers.go index ad3b92c5..821e6c17 100644 --- a/composed-auth/auth/authorizers.go +++ b/composed-auth/auth/authorizers.go @@ -53,6 +53,8 @@ func init() { // Customized authorizer methods for our sample API +// snippet:is-registered + // IsRegistered determines if the user is properly registered, // i.e if a valid username:password pair has been provided. func IsRegistered(user, pass string) (*models.Principal, error) { @@ -66,6 +68,8 @@ func IsRegistered(user, pass string) (*models.Principal, error) { }, nil } +// endsnippet:is-registered + // IsReseller tells if the API key is a JWT signed by us with a claim to be a reseller. func IsReseller(token string) (*models.Principal, error) { claims, err := parseAndCheckToken(token) diff --git a/composed-auth/restapi/configure_multi_auth_example.go b/composed-auth/restapi/configure_multi_auth_example.go index 44af7c53..34b6af5a 100644 --- a/composed-auth/restapi/configure_multi_auth_example.go +++ b/composed-auth/restapi/configure_multi_auth_example.go @@ -36,6 +36,7 @@ func configureAPI(api *operations.MultiAuthExampleAPI) http.Handler { api.JSONProducer = runtime.JSONProducer() + // snippet:wiring api.HasRoleAuth = func(token string, scopes []string) (*models.Principal, error) { // The header: Authorization: Bearer {base64 string} (or ?access_token={base 64 string} param) has already // been decoded by the runtime as a token @@ -59,6 +60,7 @@ func configureAPI(api *operations.MultiAuthExampleAPI) http.Handler { api.Logger("ResellerQueryAuth handler called") return auth.IsReseller(token) } + // endsnippet:wiring // Set your custom authorizer if needed. Default one is security.Authorized() // Expected interface runtime.Authorizer diff --git a/composed-auth/swagger.yml b/composed-auth/swagger.yml index 755b1e5a..c99f3e6f 100644 --- a/composed-auth/swagger.yml +++ b/composed-auth/swagger.yml @@ -40,6 +40,7 @@ schemes: - http # https in a normal setup basePath: /api securityDefinitions: + # snippet:schemes isRegistered: # This scheme uses the header: "Authorization: Basic {base64 encoded string defined by username:password}" # Scopes are not supported with this type of authorization. @@ -71,6 +72,7 @@ securityDefinitions: scopes: customer: scope of registered customers inventoryManager: scope of resellers acting as inventory managers + # endsnippet:schemes # Default Security requirements for all operations security: @@ -140,6 +142,7 @@ paths: Registered customers should be able to add purchase orders. Registered inventory managers should be able to add replenishment orders. + # snippet:composed-security security: - isRegistered: [] hasRole: [ customer ] @@ -147,6 +150,7 @@ paths: hasRole: [ inventoryManager ] - isResellerQuery: [] hasRole: [ inventoryManager ] + # endsnippet:composed-security parameters: - name: order in: body diff --git a/docs/doc-site/_index.md b/docs/doc-site/_index.md new file mode 100644 index 00000000..2c2e3658 --- /dev/null +++ b/docs/doc-site/_index.md @@ -0,0 +1,82 @@ +--- +title: "go-swagger examples" +type: home +description: 'Runnable examples and tutorials for go-swagger spec-first code generation' +weight: 1 +--- + +A curated collection of **runnable examples** for +[`go-swagger`](https://github.com/go-swagger/go-swagger) — generating servers, +clients and CLIs from an OpenAPI 2.0 (Swagger) spec. + +Every example here is committed to the +[go-swagger/examples](https://github.com/go-swagger/examples) repository and kept +in sync with the latest go-swagger release by automated regeneration. + +### Status + +{{% button href="https://github.com/go-swagger/examples/fork" hint="fork me on github" style=primary icon=code-fork %}}Fork me{{% /button %}} +Actively maintained. Regenerated weekly against `swagger@master`. + +### Which site do I want? + +These examples are all **spec-first**: you have an OpenAPI spec and want +`swagger generate` to produce typed code. The sibling sites cover the other two +approaches — pick by what you start from: + +| I start from… | I want… | Go here | +|---------------|---------|---------| +| an **OpenAPI spec** | generate a typed server / client / CLI | **this site** | +| **Go interfaces**, no codegen | hand-wire an untyped client or server | [go-openapi/runtime](https://go-openapi.github.io/runtime/) | +| **Go code** | produce a spec *from* the code (code-first) | [go-openapi/codescan](https://go-openapi.github.io/codescan/) | + +### New to go-swagger? + +Install the toolchain and read the command reference on go-swagger's own site: + +```cmd +go install github.com/go-swagger/go-swagger/cmd/swagger@latest +``` + +→ [go-swagger.io](https://goswagger.io/go-swagger/) for install, the `generate` +command families, and project-layout reference. This site assumes you have +`swagger` on your `PATH` and focuses on **what to build with it**. + +### Where to go next + +{{< cards >}} +{{% card title="Guides" %}} +The example catalog, grouped by concern — servers, clients & CLI, authentication, +streaming, and codegen customization. One page per example. + +→ [guides](./guides/) +{{% /card %}} + +{{% card title="Tutorials" %}} +Sequential, end-to-end walkthroughs. Start with the todo-list tutorial to build a +server and client from scratch. + +→ [tutorials](./tutorials/) +{{% /card %}} + +{{% card title="Project" %}} +Repository README, licensing, contributing guidelines and how the examples stay in +sync with go-swagger. + +→ [project](./project/) +{{% /card %}} +{{< /cards >}} + +## Licensing + +`SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers` + +These examples ship under the [Apache-2.0 license](./project/LICENSE.md). + +## Contributing + +Issues and pull requests welcome. See [project/](./project/) for guidelines. + +--- + +{{< children type="card" description="true" >}} diff --git a/docs/doc-site/guides/_index.md b/docs/doc-site/guides/_index.md new file mode 100644 index 00000000..45cd647a --- /dev/null +++ b/docs/doc-site/guides/_index.md @@ -0,0 +1,14 @@ +--- +title: Guides +weight: 2 +description: | + The example catalog, grouped by concern. Each page covers one runnable example: + what it demonstrates, the spec excerpt, the generate command, how to run it, and + the key generated files to look at. +--- + +Browse by concern. Every guide maps to a directory in the +[go-swagger/examples](https://github.com/go-swagger/examples) repository, so you can +clone it and run the code alongside the page. + +{{< children type="card" description="true" >}} diff --git a/docs/doc-site/guides/authentication/_index.md b/docs/doc-site/guides/authentication/_index.md new file mode 100644 index 00000000..ca03a4be --- /dev/null +++ b/docs/doc-site/guides/authentication/_index.md @@ -0,0 +1,15 @@ +--- +title: Authentication +weight: 3 +description: | + Wiring security into a generated server — basic and API-key auth, composed + security requirements, and a full OAuth2 access-code handshake. +--- + +{{% notice info %}} +These examples wire authentication into **generated** servers. Looking to +hand-wire auth on an untyped runtime server instead? See the +[runtime auth examples](https://go-openapi.github.io/runtime/usage/examples/auth/). +{{% /notice %}} + +{{< children type="card" description="true" >}} diff --git a/docs/doc-site/guides/authentication/basic-and-apikey.md b/docs/doc-site/guides/authentication/basic-and-apikey.md new file mode 100644 index 00000000..1044a7ee --- /dev/null +++ b/docs/doc-site/guides/authentication/basic-and-apikey.md @@ -0,0 +1,85 @@ +--- +title: "Basic & API-key auth" +weight: 1 +description: "Basic auth and API-key security schemes" +--- + +This is the starting point for security in a generated server. A +`securityDefinition` in the spec becomes a generated **authenticator hook** you +implement, and — when you generate with a typed principal — every protected +handler receives that principal as a typed argument. + +{{% notice tip %}} +Source: [`authentication/`](https://github.com/go-swagger/examples/tree/master/authentication). +Generated with `swagger generate server --name AuthSample --spec ./swagger.yml --principal models.Principal`. +{{% /notice %}} + +## Declaring the scheme + +The `authentication` example uses a single API-key scheme, carried in the +`x-token` header, and applies it to every endpoint via a top-level `security` +requirement: + +{{< code file="authentication/swagger.yml" lang="yaml" region="security" >}} + +## The generated authenticator hook + +Because the spec names one `apiKey` scheme called `key`, the generated API exposes +an `api.KeyAuth` hook. The `--principal models.Principal` flag makes it return a +typed `*models.Principal`; the scaffold leaves it returning `NotImplemented`: + +{{< code file="authentication/restapi/configure_auth_sample.go" lang="go" region="keyauth" >}} + +You replace the body with your token check. On success return a principal; on +failure return an `errors.New(401, …)`: + +```go +api.KeyAuth = func(token string) (*models.Principal, error) { + if token == "abcdefuvwxyz" { + prin := models.Principal(token) + return &prin, nil + } + return nil, errors.New(401, "incorrect api key auth") +} +``` + +The returned principal is then passed to every handler protected by this scheme: + +```go +api.CustomersGetIDHandler = customers.GetIDHandlerFunc( + func(params customers.GetIDParams, principal *models.Principal) middleware.Responder { + // principal is the value your KeyAuth hook returned + ... + }) +``` + +## Basic auth is the same shape + +A `type: basic` scheme works identically, except the runtime decodes the +`Authorization: Basic` header for you and the hook receives a **username/password +pair** instead of a single token: + +```go +api.MyBasicAuth = func(user, pass string) (*models.Principal, error) { ... } +``` + +For a worked basic-auth authenticator — plus mixing several schemes — see +[Composed auth](../composed-auth/). + +## Trying it + +```shellsession +$ curl -i -H 'X-Token: abcdefuvwxyz' http://127.0.0.1:35307/api/customers +HTTP/1.1 501 Not Implemented # authenticated, handler not implemented + +$ curl -i -H 'X-Token: wrong' http://127.0.0.1:35307/api/customers +HTTP/1.1 401 Unauthorized +{"code":401,"message":"incorrect api key auth"} +``` + +## Related + +- [Composed auth](../composed-auth/) — basic + API-key + scoped tokens, composed with AND/OR. +- [OAuth2 access-code](../oauth2-access-code/) — a full OAuth2 handshake. +- Hand-wiring auth without codegen? See the + [runtime auth examples](https://go-openapi.github.io/runtime/). diff --git a/docs/doc-site/guides/authentication/composed-auth.md b/docs/doc-site/guides/authentication/composed-auth.md new file mode 100644 index 00000000..2fff9f84 --- /dev/null +++ b/docs/doc-site/guides/authentication/composed-auth.md @@ -0,0 +1,70 @@ +--- +title: "Composed auth" +weight: 2 +description: "Composing multiple security requirements" +--- + +Real APIs rarely have a single security scheme. The **composed-auth** example +mixes four — basic auth, an API key (by header *or* query), and scoped JWT +tokens — and composes them per operation with **AND** and **OR** semantics. It's +the reference for anything beyond a single authenticator. + +{{% notice tip %}} +Source: [`composed-auth/`](https://github.com/go-swagger/examples/tree/master/composed-auth). +Generated with `swagger generate server --name multi-auth-example --spec ./swagger.yml --principal models.Principal`. +The `restapi/configure_*.go` and `auth/authorizers.go` files are hand-written. +{{% /notice %}} + +## Four schemes + +The spec declares basic auth (`isRegistered`), an API key in either the header +(`isReseller`) or a query param (`isResellerQuery`), and a scoped `oauth2`-typed +scheme (`hasRole`) used purely to carry JWT scopes — go-swagger does not run an +OAuth2 flow here, it just extracts the required scopes and hands them to your +authorizer: + +{{< code file="composed-auth/swagger.yml" lang="yaml" region="schemes" >}} + +## Composing requirements with AND / OR + +A `security` block on an operation is a **list of alternatives (OR)**, and each +alternative is a **map of schemes that must all pass (AND)**. So `/order/add` +accepts a registered customer, *or* a reseller (by header), *or* a reseller (by +query) — each combined with the right JWT role: + +{{< code file="composed-auth/swagger.yml" lang="yaml" region="composed-security" >}} + +An empty `security: []` on an operation opts it out entirely (public), overriding +the spec's top-level default. + +## The authorizers + +Each scheme maps to a hook whose signature depends on its type: basic auth gets +`(user, pass)`, an API key gets `(token)`, and a scoped scheme gets +`(token, scopes)`. The example delegates each to a function in +`auth/authorizers.go`: + +{{< code file="composed-auth/restapi/configure_multi_auth_example.go" lang="go" region="wiring" >}} + +The basic-auth authorizer is a plain credential check returning a typed +principal: + +{{< code file="composed-auth/auth/authorizers.go" lang="go" region="is-registered" >}} + +The scoped `HasRole` authorizer goes further: it parses the JWT, then checks the +token's claimed roles against the `scopes` the runtime passed in — the mechanism +that makes `hasRole: [ customer ]` in the spec actually mean something. + +## Trying it + +Generate test keys and JWTs, then exercise the composed requirements: + +```shellsession +$ cd hack/tools && go run . gen-tokens # RSA keypair + role JWTs +$ ./composed-auth/exerciser.sh # sends a sequence of curl requests +``` + +## Related + +- [Basic & API-key auth](../basic-and-apikey/) — the single-scheme starting point. +- [OAuth2 access-code](../oauth2-access-code/) — a real OAuth2 handshake (vs. JWT-scope extraction here). diff --git a/docs/doc-site/guides/authentication/oauth2-access-code.md b/docs/doc-site/guides/authentication/oauth2-access-code.md new file mode 100644 index 00000000..9a746125 --- /dev/null +++ b/docs/doc-site/guides/authentication/oauth2-access-code.md @@ -0,0 +1,71 @@ +--- +title: "OAuth2 access-code" +weight: 3 +description: "A full OAuth2 access-code handshake" +--- + +The other auth examples *validate* a token the client already has. This one runs +the full **OAuth2 access-code handshake** — redirecting the user to an identity +provider (Google), receiving a callback, exchanging the code for a token, then +using that token to authenticate API calls. + +{{% notice tip %}} +Source: [`oauth2/`](https://github.com/go-swagger/examples/tree/master/oauth2). +Generated with `swagger generate server --name oauthSample --spec ./swagger.yml --principal models.Principal`. +The handshake lives in the hand-written `restapi/implementation.go`. +{{% /notice %}} + +## The scheme + +The spec declares an `oauth2` scheme with the `accessCode` flow and its +authorization/token URLs. Unlike [composed-auth](../composed-auth/) — which only +borrows the `oauth2` type to carry scopes — here the URLs are real and drive an +actual handshake: + +{{< code file="oauth2/swagger.yml" lang="yaml" region="security" >}} + +{{% notice note %}} +go-swagger does not implement the OAuth2 workflow for you: the generator produces +the authenticator hook and the routing, but the redirect/callback/exchange dance +is application code. That's exactly what this example provides. +{{% /notice %}} + +## Step 1 — redirect to the provider + +The public `/login` endpoint sends the user to Google's consent screen, using the +`golang.org/x/oauth2` config built in `implementation.go`: + +{{< code file="oauth2/restapi/implementation.go" lang="go" region="login" >}} + +## Step 2 — handle the callback and exchange the code + +Google redirects back to `/auth/callback` with a `state` and a `code`. The +handler verifies `state`, then exchanges the code for an access token via the +oauth2 client: + +{{< code file="oauth2/restapi/implementation.go" lang="go" region="callback" >}} + +## Step 3 — authenticate API calls with the token + +Every protected endpoint runs through the generated `OauthSecurityAuth` hook. It +validates the bearer token (here by calling Google's userinfo endpoint) and +returns the principal — the token string itself in this minimal example: + +{{< code file="oauth2/restapi/configure_oauth_sample.go" lang="go" region="oauth-auth" >}} + +## Setup + +Register an OAuth client at the [Google credentials console](https://console.cloud.google.com/apis/credentials/), +set the callback URL to `http://127.0.0.1:12345/api/auth/callback`, and put the +resulting client ID/secret into the `var` block of `implementation.go`. Then: + +```shellsession +$ go run ./oauth2/cmd/oauth-sample-server/main.go --port 12345 +# open http://127.0.0.1:12345/api/login in a browser, log in, copy the token +$ curl -i -H 'Authorization: Bearer ' http://127.0.0.1:12345/api/customers +``` + +## Related + +- [Basic & API-key auth](../basic-and-apikey/) — validating a pre-shared credential. +- [Composed auth](../composed-auth/) — mixing schemes and extracting JWT scopes. diff --git a/docs/doc-site/guides/clients-and-cli/_index.md b/docs/doc-site/guides/clients-and-cli/_index.md new file mode 100644 index 00000000..c2403663 --- /dev/null +++ b/docs/doc-site/guides/clients-and-cli/_index.md @@ -0,0 +1,9 @@ +--- +title: Clients & CLI +weight: 2 +description: | + Generating a typed SDK client (classic and stratoscale flavors) and a command-line + client tool from the same spec. +--- + +{{< children type="card" description="true" >}} diff --git a/docs/doc-site/guides/clients-and-cli/cli-client.md b/docs/doc-site/guides/clients-and-cli/cli-client.md new file mode 100644 index 00000000..616ea73d --- /dev/null +++ b/docs/doc-site/guides/clients-and-cli/cli-client.md @@ -0,0 +1,89 @@ +--- +title: "CLI client" +weight: 2 +description: "Generate a command-line client tool" +--- + +`swagger generate cli` produces a command-line tool that wraps the generated +client: it reads flags and arguments, builds the operation parameters, calls the +server, and prints the response. It's built on [cobra](https://github.com/spf13/cobra) +and [viper](https://github.com/spf13/viper), with shell-completion support. + +{{% notice tip %}} +Source: [`cli/`](https://github.com/go-swagger/examples/tree/master/cli). +Generated with `swagger generate cli --spec ./swagger.yml --cli-app-name todoctl`. +It targets the same spec as [auto-configure](../../servers/auto-configure/), so you +can run that server and drive it from this CLI. +{{% /notice %}} + +## Command layout + +The generated command tree mirrors the spec: + +- the **root** command holds global flags (`--hostname`, `--scheme`, auth tokens); +- each **tag** becomes a sub-command (an *operation group*); +- each **operationId** becomes a sub-command under its tag; +- each path/query parameter becomes a flag; the body becomes a `--body` JSON flag, + with a flag per body field layered on top. + +The tag → sub-command mapping is a `cobra.Command` per group, wiring in one child +per operation: + +{{< code file="cli/cli/cli.go" lang="go" lines="209-240" >}} + +## An operation command + +Each operationId gets its own command with a `RunE` that calls the server, plus +generated flag registration for its parameters: + +{{< code file="cli/cli/add_one_operation.go" lang="go" lines="17-29" >}} + +Body parameters are handled two ways at once — a whole-body `--body` JSON string +as a base payload, and a generated flag per field (recursing into sub-definitions) +that overrides it. That's where `--item.description` comes from: + +{{< code file="cli/cli/add_one_operation.go" lang="go" lines="67-84" >}} + +## Running it + +Drive the [auto-configure](../../servers/auto-configure/) server with the tool: + +```shellsession +$ go run ./cli/cmd/todoctl/main.go --hostname localhost:12345 \ + --x-todolist-token "example token" \ + todos addOne --item.description "hi" --body "{}" +{"description":"hi"} +``` + +The path is `todoctl` → `todos` (the tag) → `addOne` (the operationId), with +`--item.description` setting a body field. + +## Config files and completion + +Common flags — `hostname`, `scheme`, `base_path`, auth tokens — can live in a +config file instead of the command line, loaded via viper from +`~/.config//config.json` (or `--config`, in JSON/YAML/env form): + +```json +{ + "hostname": "localhost:12345", + "scheme": "http", + "x-todolist-token": "example token" +} +``` + +Shell completions (bash, zsh, fish, PowerShell) come for free from cobra: + +```shellsession +$ source <(./todoctl completion bash) +``` + +{{% notice note %}} +The CLI generator is under active development. A few spec shapes aren't covered +yet — arrays/maps in a body, and enums in help text and completions. +{{% /notice %}} + +## Related + +- [Generated client SDK](../generated-client/) — the typed client this CLI wraps. +- [dockerctl](https://github.com/go-swagger/dockerctl) — a full CLI generated this way for the Docker Engine API. diff --git a/docs/doc-site/guides/clients-and-cli/generated-client.md b/docs/doc-site/guides/clients-and-cli/generated-client.md new file mode 100644 index 00000000..5d97f27c --- /dev/null +++ b/docs/doc-site/guides/clients-and-cli/generated-client.md @@ -0,0 +1,69 @@ +--- +title: "Generated client SDK" +weight: 1 +description: "Classic vs stratoscale client flavors" +--- + +From the same spec that drives a server, `swagger generate client` produces a +typed Go SDK: one method per operation, with generated parameter and response +types. The **tutorials/client** example generates that SDK in two flavors from +one spec — the **classic** go-swagger client and the **stratoscale** contributed +template — so you can compare the ergonomics. + +{{% notice tip %}} +Source: [`tutorials/client/`](https://github.com/go-swagger/examples/tree/master/tutorials/client). +Two client packages are generated from one `swagger.yml`: + +- classic — `swagger generate client -A TodoList --spec swagger.yml --client-package classic-client` +- stratoscale — the same, plus `--template stratoscale` (reusing `--existing-models`). +{{% /notice %}} + +## Classic client + +The classic client generates a `ClientService` interface. Each method takes the +operation's params plus an explicit `runtime.ClientAuthInfoWriter` and variadic +`ClientOption`s; a parallel `…Context` variant threads a `context.Context`: + +{{< code file="tutorials/client/classic_client/todos/todos_client.go" lang="go" lines="101-103" >}} + +## Stratoscale client + +The stratoscale template generates a leaner `API` interface: **context-first**, +auth folded into the transport, no options parameter. It also emits a +`//go:generate mockery` directive so the interface is trivially mockable in tests: + +{{< code file="tutorials/client/stratoscale_client/todos/todos_client.go" lang="go" lines="18-19" >}} + +Pick classic for the full go-swagger surface (per-call auth, per-call options); +pick stratoscale for a compact, context-first, easily-mocked client. + +## Multiple success responses + +This example deliberately exercises a tricky spec shape: `addOne` declares **two** +success responses (`201 Created` and `204 No Content`). Both flavors reflect that +in the return signature — the method hands back a pointer for *each* possible +success, and exactly one is non-nil: + +```go +created, noContent, err := c.AddOne(ctx, params) +switch { +case err != nil: + // transport or error response +case created != nil: + // 201 — use created.Payload +case noContent != nil: + // 204 — nothing to read +} +``` + +The same mechanism covers operations with **no default response**: without a +`default`, an undeclared status code surfaces as a generic error rather than a +typed payload, because the generated response reader only knows the codes the spec +listed. + +## Related + +- [CLI client](../cli-client/) — a cobra command-line tool wrapping a generated client. +- [Custom templates](../../customizing-codegen/custom-templates/) — how the stratoscale flavor is produced. +- Hand-wiring a client without codegen? See the + [go-openapi/runtime](https://go-openapi.github.io/runtime/) examples. diff --git a/docs/doc-site/guides/customizing-codegen/_index.md b/docs/doc-site/guides/customizing-codegen/_index.md new file mode 100644 index 00000000..462626af --- /dev/null +++ b/docs/doc-site/guides/customizing-codegen/_index.md @@ -0,0 +1,9 @@ +--- +title: Customizing code generation +weight: 5 +description: | + Going beyond the defaults — custom templates, external type bindings, generation + flags, plugging in net/http middleware, and alias compatibility. +--- + +{{< children type="card" description="true" >}} diff --git a/docs/doc-site/guides/customizing-codegen/alias-compatibility.md b/docs/doc-site/guides/customizing-codegen/alias-compatibility.md new file mode 100644 index 00000000..0fe64cb1 --- /dev/null +++ b/docs/doc-site/guides/customizing-codegen/alias-compatibility.md @@ -0,0 +1,50 @@ +--- +title: "Alias compatibility" +weight: 5 +description: "Type-alias compatibility in generated code" +--- + +{{% notice info %}} +This example runs the *other* direction — **code → spec** (`swagger generate +spec`), not spec → code. That code-first workflow is the subject of the +[go-openapi/codescan](https://go-openapi.github.io/codescan/) site; it lives here +only because the repo hosts the example. The rest of this site is spec-first +codegen. +{{% /notice %}} + +The **alias-compatibility** example shows how a Go **type alias** is reflected +when you generate a spec *from* Go code, and how the `--transparent-aliases` flag +controls it. + +{{% notice tip %}} +Source: [`alias-compatibility/`](https://github.com/go-swagger/examples/tree/master/alias-compatibility). +{{% /notice %}} + +## The aliases + +`UserID` is a true Go alias (`=`) of `Identifier`, not a distinct named type: + +{{< code file="alias-compatibility/api.go" lang="go" region="aliases" >}} + +## What the flag does + +When `swagger generate spec` walks this code, the alias can be treated two ways: + +- **Default (post-[#3227](https://github.com/go-swagger/go-swagger/issues/3227))** — + `UserID` appears as its own definition, and `User.id` references + `#/definitions/UserID`. +- **`--transparent-aliases`** — `UserID` is *not* emitted; `User.id` references + `#/definitions/Identifier` directly (the pre-#3227 behavior). + +See the difference by generating both and diffing: + +```shellsession +$ swagger generate spec -m -o without-flag.json +$ swagger generate spec -m --transparent-aliases -o with-flag.json +$ diff <(jq . without-flag.json) <(jq . with-flag.json) +``` + +## Related + +- [go-openapi/codescan](https://go-openapi.github.io/codescan/) — the code-first (code → spec) workflow this example belongs to. +- [External types](../external-types/) — the spec-first counterpart: binding a schema to an existing Go type. diff --git a/docs/doc-site/guides/customizing-codegen/custom-middleware.md b/docs/doc-site/guides/customizing-codegen/custom-middleware.md new file mode 100644 index 00000000..871dfb65 --- /dev/null +++ b/docs/doc-site/guides/customizing-codegen/custom-middleware.md @@ -0,0 +1,85 @@ +--- +title: "Custom middleware" +weight: 4 +description: "Plug classic net/http middleware into a server" +--- + +A generated server is a standard `net/http` stack, so any `http.Handler` +middleware composes with it. The **middleware** example wires two real concerns — +security response headers and Prometheus metrics — into a generated server using +only the hook points codegen already leaves for you. **No `--exclude-main` and no +custom router required.** + +{{% notice tip %}} +Source: [`middleware/`](https://github.com/go-swagger/examples/tree/master/middleware). +Generated with `swagger generate server -A Greeter -f ./swagger.yml`. Answers +go-swagger issues [#2683](https://github.com/go-swagger/go-swagger/issues/2683) +(security headers) and [#1120](https://github.com/go-swagger/go-swagger/issues/1120) +(a `/metrics` endpoint). +{{% /notice %}} + +## Two extension points + +Codegen leaves two hooks in `restapi/configure_*.go`, and they run at different +stages of the request: + +| Hook | Runs | Sees the matched route? | Use it for | +|------|------|:-----------------------:|-----------| +| `setupGlobalMiddleware` | Before swagger routing — wraps *everything* (spec, UI, all routes) | No | Cross-cutting concerns: security headers, panic recovery, a metrics mount | +| `setupMiddlewares` | After routing — only matched operations | Yes (`middleware.MatchedRouteFrom`) | Per-route concerns: instrumentation labelled by route template | + +## The global hook — headers + metrics mount + +`setupGlobalMiddleware` wraps the entire server. Reading outermost to innermost: +`metrics.Mount` intercepts `GET /metrics` so scrape traffic bypasses routing; +`unrolled/secure` adds HSTS and other headers to every response, including the +spec and UI: + +{{< code file="middleware/restapi/configure_greeter.go" lang="go" region="setup-global" >}} + +## The per-route hook — instrumentation + +`setupMiddlewares` runs *after* routing, so the matched route is available. That's +what lets metrics be labelled by route: + +{{< code file="middleware/restapi/configure_greeter.go" lang="go" region="setup-middlewares" >}} + +## The go-swagger-specific glue: the route label + +The one piece that's specific to a go-swagger server is the metrics `route` +label. It uses `middleware.MatchedRouteFrom(r).PathPattern` — the swagger path +*template* (`/greet/{name}`) rather than the literal path (`/greet/alice`) — so +Prometheus label cardinality stays bounded instead of exploding one series per +distinct URL: + +{{< code file="middleware/internal/metrics/metrics.go" lang="go" region="instrument" >}} + +## Trying it + +```shellsession +$ go run ./middleware/cmd/greeter-server --port 8080 +$ curl -i http://127.0.0.1:8080/api/greet +HTTP/1.1 200 OK +Strict-Transport-Security: max-age=63072000; includeSubDomains +X-Frame-Options: DENY +X-Content-Type-Options: nosniff +... +{"message":"hello"} + +$ curl http://127.0.0.1:8080/metrics +http_requests_total{code="200",method="GET",route="/api/greet/{name}"} 3 +``` + +The `route` label carries the `basePath` (`/api`) because the matched template is +the full path the router serves. + +## Other middleware, same pattern + +Any `http.Handler` middleware composes identically via `setupGlobalMiddleware` — +panic recovery (`gorilla/handlers.RecoveryHandler`), a request ID, an access log, +and so on. The example keeps to headers + metrics to stay focused on the wiring. + +## Related + +- [Todo list server](../../servers/todo-list/) — where the `configure_*.go` hooks come from. +- [Generation flags](../generation-flags/) — `--exclude-spec` and flag strategies (no `--exclude-main` needed here). diff --git a/docs/doc-site/guides/customizing-codegen/custom-templates.md b/docs/doc-site/guides/customizing-codegen/custom-templates.md new file mode 100644 index 00000000..829925e0 --- /dev/null +++ b/docs/doc-site/guides/customizing-codegen/custom-templates.md @@ -0,0 +1,44 @@ +--- +title: "Custom templates" +weight: 1 +description: "Generate with contributed (stratoscale) templates" +--- + +go-swagger renders code from Go templates, and you can swap in your own. The +**contributed-templates** example uses the built-in `--template stratoscale` +option — a community template set that produces a different, interface-first +shape optimized for testability. + +{{% notice tip %}} +Source: [`contributed-templates/stratoscale/`](https://github.com/go-swagger/examples/tree/master/contributed-templates/stratoscale). +Generated with `swagger generate server -A Petstore --template stratoscale` (and the +matching `swagger generate client --template stratoscale`). +{{% /notice %}} + +## What the template changes + +Instead of the default's per-operation handler *func fields*, the stratoscale +template groups operations into **interfaces** — one per tag — that your code +implements, and emits `//go:generate mockery` directives so those interfaces are +trivially mockable in tests: + +{{< code file="contributed-templates/stratoscale/restapi/configure_petstore.go" lang="go" lines="26-42" >}} + +Every handler is `context`-first (`ctx context.Context, params …`), matching the +[stratoscale client flavor](../../clients-and-cli/generated-client/). You provide +one implementation per interface (`PetAPI`, `StoreAPI`, …) rather than assigning +individual handler funcs. + +## When to use it + +Reach for a custom template set when the default output doesn't fit your codebase +conventions — here, interface-based handlers plus generated mocks. Because it's a +whole template family, it changes server *and* client output consistently. + +To go further, `--template-dir` points the generator at your own template +directory, letting you override any individual template go-swagger ships. + +## Related + +- [Generated client SDK](../../clients-and-cli/generated-client/) — the stratoscale client interface, side by side with the classic one. +- [Generation flags](../generation-flags/) — other flags that reshape the output. diff --git a/docs/doc-site/guides/customizing-codegen/external-types.md b/docs/doc-site/guides/customizing-codegen/external-types.md new file mode 100644 index 00000000..bc2c40f5 --- /dev/null +++ b/docs/doc-site/guides/customizing-codegen/external-types.md @@ -0,0 +1,60 @@ +--- +title: "External types" +weight: 2 +description: "Bind schemas to externally defined Go types" +--- + +By default every schema in your spec becomes a generated Go struct. Sometimes you +want a schema to map onto a type you already have — a hand-written type, one from +another package, or a shared domain type. The **external-types** example shows how +`x-go-type` binds a schema to an externally defined Go type instead of generating +one. + +{{% notice tip %}} +Source: [`external-types/`](https://github.com/go-swagger/examples/tree/master/external-types). +See also the go-swagger [external types reference](https://goswagger.io/go-swagger/use/models/schemas/#external-types). +{{% /notice %}} + +## The `x-go-type` extension + +Attach `x-go-type` to a schema to name the Go type it should use, and where to +import it from. Here a property is bound to `MyAlternateInteger` from the `fred` +package instead of getting a generated type: + +{{< code file="external-types/example-external-types.yaml" lang="yaml" region="x-go-type" >}} + +The `import.package` (and optional `alias`) tell the generator which import to add. +The generated code references your type directly — no definition is emitted for it. + +## The generated result + +A definition bound to an external type collapses to exactly that type, with the +external package imported (and its name mangled to avoid collisions). This +`MyExtCollection` is a slice of an external `go-ext` type: + +{{< code file="external-types/models/my_ext_collection.go" lang="go" lines="16-19" >}} + +Because the external type is expected to satisfy the runtime's `Validatable` +interface, the generated `Validate` still calls into it per item — so your type +participates in validation like any generated model. + +## What it covers + +The example exercises the full range of external-type use cases: + +- an external type as its own definition, or nested inside an object/slice/map/tuple; +- types pulled from the default models package or from an arbitrary import path; +- embedding an external type to add the `Validatable` interface; +- annotation *hints* to resolve nullable/struct-vs-interface questions and to skip + validation of an external type. + +{{% notice note %}} +The example spec adds an `additionalItems` clause to demonstrate tuples, which +makes it not strictly valid against the Swagger 2.0 meta-schema — intentional, to +show the tuple binding. +{{% /notice %}} + +## Related + +- [Custom templates](../custom-templates/) — reshape the generated code itself. +- [Generation flags](../generation-flags/) — control model/target packages. diff --git a/docs/doc-site/guides/customizing-codegen/generation-flags.md b/docs/doc-site/guides/customizing-codegen/generation-flags.md new file mode 100644 index 00000000..a838ecec --- /dev/null +++ b/docs/doc-site/guides/customizing-codegen/generation-flags.md @@ -0,0 +1,61 @@ +--- +title: "Generation flags" +weight: 3 +description: "How the various generate flags materialize" +--- + +The generated `main.go` for a server isn't fixed — a couple of flags change how +it parses command-line options and whether it carries the spec inside the binary. +The **flags** example generates the *same* API six ways so you can compare the +results side by side. + +{{% notice tip %}} +Source: [`flags/`](https://github.com/go-swagger/examples/tree/master/flags). +Six sub-packages (`pflag/`, `flag/`, `go-flags/` and their `x…` variants) are +each generated from one `swagger.yml` with a different flag combination. +{{% /notice %}} + +## `--flag-strategy` — how the server parses CLI options + +The flag strategy selects the library the generated `main.go` uses for its +command-line flags. The API is identical; only the flag plumbing differs: + +| `--flag-strategy` | Library | Flag style | +|-------------------|---------|-----------| +| `go-flags` (default) | [`jessevdk/go-flags`](https://github.com/jessevdk/go-flags) | `--port=8080`, env-var bindings (`[$PORT]`), grouped options | +| `pflag` | [`spf13/pflag`](https://github.com/spf13/pflag) | GNU-style `--port 8080` | +| `flag` | stdlib `flag` | single-dash `-port 8080` | + +```bash +(mkdir pflag && cd pflag && swagger generate server --spec=../swagger.yml --flag-strategy=pflag) +``` + +All three expose the same server options — listeners (`--scheme`), timeouts, +TLS settings, socket path — just rendered in each library's idiom. Pick the one +that matches the rest of your CLI. + +## `--exclude-spec` — embedded vs. runtime spec + +By default the spec is **embedded** in the generated binary (the `x…` variants +drop this): + +- **default (embedded)** — the spec is baked in; the server is fully + self-contained and serves its own `swagger.json`. +- **`--exclude-spec`** — the spec is *not* embedded. An extra `--spec` CLI flag + appears so the server loads the document at startup instead. + +Embed for a single shippable artifact; exclude when you want to swap the spec +without rebuilding, or to keep the binary small. + +## Trying it + +Build any variant's server and ask for help to see that strategy's flag layout: + +```shellsession +$ go build -o srv ./flags/pflag/cmd/simple-to-do-list-api-server && ./srv -h +``` + +## Related + +- [Custom templates](../custom-templates/) — change the generated code shape, not just its flags. +- [CLI client](../../clients-and-cli/cli-client/) — a different use of flags: a generated cobra command-line *client*. diff --git a/docs/doc-site/guides/servers/_index.md b/docs/doc-site/guides/servers/_index.md new file mode 100644 index 00000000..06a5128b --- /dev/null +++ b/docs/doc-site/guides/servers/_index.md @@ -0,0 +1,9 @@ +--- +title: Servers +weight: 1 +description: | + Generating HTTP servers from a spec — from the canonical todo-list server to + strict handlers, custom error handling, file upload/download and CRUD APIs. +--- + +{{< children type="card" description="true" >}} diff --git a/docs/doc-site/guides/servers/auto-configure.md b/docs/doc-site/guides/servers/auto-configure.md new file mode 100644 index 00000000..02ecaf25 --- /dev/null +++ b/docs/doc-site/guides/servers/auto-configure.md @@ -0,0 +1,54 @@ +--- +title: "Auto-configure" +weight: 4 +description: "Auto-wire handler implementations to the generated API" +--- + +Normally you edit `configure_*.go` by hand to attach each handler. The +**auto-configure** example takes a different route: generate with +`--implementation-package`, point it at a package you write, and the generator +emits the wiring for you. Every operation is routed to a method on your +implementation — no hand-editing of a configure file. + +{{% notice tip %}} +Source: [`auto-configure/`](https://github.com/go-swagger/examples/tree/master/auto-configure). +Generated with `swagger generate server --name AToDoListApplication --spec ./swagger.yml --implementation-package github.com/go-swagger/examples/auto-configure/implementation --principal any`. +{{% /notice %}} + +## The generated contract + +Instead of a `configure_*.go`, the generator produces +`auto_configure_*.go`. It declares the `Handler` interface your package must +satisfy, and binds a package-level `Impl` to your constructor via +`implementation.New()`: + +{{< code file="auto-configure/restapi/auto_configure_a_to_do_list_application.go" lang="go" lines="24-55" >}} + +Each generated handler then simply delegates to that `Impl`: + +{{< code file="auto-configure/restapi/auto_configure_a_to_do_list_application.go" lang="go" lines="76-79" >}} + +## Your implementation package + +You write a package that implements `Handler`. This is ordinary hand-written code +— nothing generated — so it's the natural home for your business logic. Here's +the `AddOne` method backing the in-memory store: + +{{< code file="auto-configure/implementation/todos_impl.go" lang="go" region="add-one" >}} + +The example splits the interface across small types — `TodosHandlerImpl`, +`ConfigureImpl`, `AuthImpl` — composed by a single `HandlerImpl` that `New()` +returns. That keeps handlers, server configuration, and authentication in +separate files while still satisfying the one generated `Handler` interface. + +## When to use it + +Auto-configure shines when you regenerate often and don't want a hand-edited +`configure_*.go` in the loop: your implementation lives entirely in a package you +own, and regeneration only ever touches the generated wiring. It's also a clean +way to keep the transport-facing glue separate from your domain code. + +## Related + +- [Todo list server](../todo-list/) — the conventional hand-wired `configure_*.go`. +- [Custom error handling](../error-handling/) — centralized error responses. diff --git a/docs/doc-site/guides/servers/error-handling.md b/docs/doc-site/guides/servers/error-handling.md new file mode 100644 index 00000000..f52c2561 --- /dev/null +++ b/docs/doc-site/guides/servers/error-handling.md @@ -0,0 +1,61 @@ +--- +title: "Custom error handling" +weight: 3 +description: "Customizing error responses in a generated server" +--- + +By default a generated handler returns a `middleware.Responder` and you build +error responses yourself (`NewAddOneDefault(500).WithPayload(...)`). The +**todo-list-errors** example shows the alternative: generate with +`--return-errors` so handlers may return a plain `error`, and install a single +custom error handler that shapes every error response in one place. + +{{% notice tip %}} +Source: [`todo-list-errors/`](https://github.com/go-swagger/examples/tree/master/todo-list-errors). +Generated with `swagger generate server -A TodoList -f ./swagger.yml --return-errors` +(the `//go:generate` line in `configure_todo_list.go` uses the long-form flags). +{{% /notice %}} + +## Handlers that return an error + +With `--return-errors`, the handler signature becomes +`func(params) (middleware.Responder, error)`. A handler can now short-circuit by +returning an `error` instead of constructing a response: + +{{< code file="todo-list-errors/restapi/configure_todo_list.go" lang="go" region="handler" >}} + +Here `errAlreadyExists` is a sentinel the handler returns directly — no response +plumbing at the call site. + +## The centralized error handler + +Returned errors flow through `api.ServeError`, which you can override. This +example wires it to a `catcher` that recognizes the sentinel (with +`errors.Is`), logs it, then defers to the runtime's default `ServeError` for the +actual HTTP response: + +{{< code file="todo-list-errors/restapi/configure_todo_list.go" lang="go" region="catcher" >}} + +Installing it is one line in `configureAPI`: + +```go +api.ServeError = catcher +``` + +Because every operation's error funnels through the same hook, you get one place +to classify errors, attach correlation IDs, translate domain errors to status +codes, or emit metrics — without repeating that logic in each handler. + +## When to use it + +Use returned errors when your handlers naturally surface Go `error` values (a +data layer, a validation step) and you'd rather map them to responses centrally +than build a `*Default` responder at every return. Stick with the default +responder style when each handler already knows the exact response it wants. + +## Related + +- [Todo list server](../todo-list/) — the default responder style. +- [Strict server](../strict-server/) — compiler-enforced responders. +- Hand-wiring error handling without codegen? See the + [go-openapi/runtime](https://go-openapi.github.io/runtime/) examples. diff --git a/docs/doc-site/guides/servers/file-server.md b/docs/doc-site/guides/servers/file-server.md new file mode 100644 index 00000000..7400b7ea --- /dev/null +++ b/docs/doc-site/guides/servers/file-server.md @@ -0,0 +1,51 @@ +--- +title: "File server" +weight: 5 +description: "File upload and download endpoints" +--- + +The **file-server** example demonstrates a file-upload endpoint: how the spec's +`type: file` maps onto the generated server and client, and how the runtime +surfaces the uploaded file to your handler. + +{{% notice tip %}} +Source: [`file-server/`](https://github.com/go-swagger/examples/tree/master/file-server). +Build the server under `restapi/cmd/file-upload-server`, then run the client with +`go run upload_file.go swagger.yml`. +{{% /notice %}} + +## The spec + +An upload is a `multipart/form-data` operation with a `formData` parameter of +`type: file`: + +{{< code file="file-server/swagger.yml" lang="yaml" region="upload-path" >}} + +## Server side + +The generated handler receives the file as an `io.ReadCloser` on +`params.File`. At runtime it's a `*runtime.File`, so a type assertion gives you +the multipart header — filename and size — before you stream the body to disk: + +{{< code file="file-server/restapi/configure_file_upload.go" lang="go" region="upload-handler" >}} + +Note the `defer params.File.Close()` and the `io.Copy` into a fresh file — the +handler owns the stream and is responsible for draining and closing it. + +## Client side + +On the client, a file argument is a `runtime.NamedReadCloser` — an +`io.ReadCloser` that also reports a `Name()`. A plain `*os.File` satisfies it, so +you open the file and pass it straight to the generated parameter builder: + +{{< code file="file-server/upload_file.go" lang="go" region="client-upload" >}} + +The generated `UploadFile` client method handles the multipart encoding; you only +supply the reader. + +## Related + +- Streaming request/response bodies instead of a one-shot upload? See + [Streaming](../../streaming/). +- Hand-wiring multipart without codegen? See the + [go-openapi/runtime](https://go-openapi.github.io/runtime/) examples. diff --git a/docs/doc-site/guides/servers/petstore.md b/docs/doc-site/guides/servers/petstore.md new file mode 100644 index 00000000..1abf48b5 --- /dev/null +++ b/docs/doc-site/guides/servers/petstore.md @@ -0,0 +1,59 @@ +--- +title: "Petstore" +weight: 7 +description: "The classic Swagger petstore, generated" +--- + +The **Swagger Petstore** is the canonical OpenAPI 2.0 sample. The `generated/` +example is that spec run through `swagger generate server` — a complete server +scaffold for a multi-resource API, useful as a reference for what a "full" spec +produces. + +{{% notice tip %}} +Source: [`generated/`](https://github.com/go-swagger/examples/tree/master/generated). +Generated with `swagger generate server --name Petstore --spec ./swagger-petstore.json --principal any`. +{{% /notice %}} + +## Three resource groups + +The petstore spec organizes operations under three tags, each becoming its own +package of generated handlers: + +- **pet** — `addPet`, `updatePet`, `findPetsByStatus`, `findPetsByTags`, + `getPetById`, `deletePet`, `uploadFile` +- **store** — `getInventory`, `placeOrder`, `getOrderById`, `deleteOrder` +- **user** — `createUser`, `getUserByName`, `loginUser`, `logoutUser`, … + +## A generated model + +The `Pet` model shows the usual spec-to-Go mapping — required fields as pointers, +nested models by reference, arrays as slices — alongside a generated `Validate` +method (not shown) that enforces the spec's constraints: + +{{< code file="generated/models/pet.go" lang="go" lines="20-42" >}} + +## Two security schemes + +Petstore mixes an api-key header with an OAuth2 flow. The generator emits a +distinct authenticator hook for each — note the OAuth2 hook receives the required +`scopes` so you can authorize per operation: + +{{< code file="generated/restapi/configure_petstore.go" lang="go" region="auth" >}} + +See the [OAuth2 guide](../../authentication/oauth2-access-code/) for a fully +implemented flow. + +## Typed vs untyped + +This repo also ships the **same petstore API hand-wired without codegen**, using +the go-openapi runtime directly, under +[`2.0/petstore`](https://github.com/go-swagger/examples/tree/master/2.0/petstore). +That untyped style — building the API from runtime primitives rather than +generated code — is the subject of the +[go-openapi/runtime](https://go-openapi.github.io/runtime/) site. Compare the two +to see exactly what `swagger generate` buys you. + +## Related + +- [Todo list server](../todo-list/) — a smaller server to start from. +- [Task tracker](../task-tracker/) — another full spec, generated end to end. diff --git a/docs/doc-site/guides/servers/strict-server.md b/docs/doc-site/guides/servers/strict-server.md new file mode 100644 index 00000000..825fa4d1 --- /dev/null +++ b/docs/doc-site/guides/servers/strict-server.md @@ -0,0 +1,53 @@ +--- +title: "Strict server" +weight: 2 +description: "Server generated with the strict responder interface" +--- + +The **strict server** is generated with `--strict-responders`. Instead of every +handler returning a generic `middleware.Responder` — which lets you hand back +*any* response, including ones the spec never declared — each operation gets its +own **responder interface**. The compiler then enforces that a handler can only +return responses declared for that operation. + +{{% notice tip %}} +Source: [`todo-list-strict/`](https://github.com/go-swagger/examples/tree/master/todo-list-strict). +Generated with `swagger generate server -A todo-list -f ./swagger.yml --strict-responders --regenerate-configureapi`. +{{% /notice %}} + +## The generated responder interface + +For each operation, the generator emits a marker interface embedding +`middleware.Responder`. Only the response types declared for `addOne` implement +`AddOneResponder`, so nothing else can be returned: + +{{< code file="todo-list-strict/restapi/operations/todos/add_one_responses.go" lang="go" lines="127-130" >}} + +Every generated response for the operation — `AddOneCreated`, `AddOneDefault`, +`AddOneNotImplemented` — carries a no-op `AddOneResponder()` method, which is +what admits it to the interface. A `FindDefault` value, for instance, simply +won't compile inside an `addOne` handler. + +## The handler signature + +Compare with the [default todo-list server](../todo-list/), where the handler +returns `middleware.Responder`. Here the return type is the operation-specific +`todos.AddOneResponder`: + +{{< code file="todo-list-strict/restapi/configure_simple_to_do_list_api.go" lang="go" region="strict-handler" >}} + +The scaffold guards each assignment with `if … == nil`, so you can wire a handler +from elsewhere and leave the rest returning `NotImplemented`. You replace the +body with real logic, returning one of the operation's typed responders. + +## When to use it + +Reach for strict responders when you want the type system to guarantee your +handlers stay in sync with the contract — you can't accidentally return a +response shape the spec doesn't describe. The cost is a little more generated +surface (one interface per operation) and slightly more verbose returns. + +## Related + +- [Todo list server](../todo-list/) — the default (non-strict) responder style. +- [Custom error handling](../error-handling/) — returning errors from handlers. diff --git a/docs/doc-site/guides/servers/task-tracker.md b/docs/doc-site/guides/servers/task-tracker.md new file mode 100644 index 00000000..e528571d --- /dev/null +++ b/docs/doc-site/guides/servers/task-tracker.md @@ -0,0 +1,63 @@ +--- +title: "Task tracker" +weight: 6 +description: "A CRUD task-tracker API" +--- + +The **task-tracker** example is a larger, realistic CRUD API — an issue tracker +with tasks, comments and file attachments. Its spec is the one go-swagger uses to +exercise code generation, so it deliberately packs in almost every construct: +nested resources, composition, arrays, file uploads, and multiple security +schemes. It's the best example to see what the generator produces for a +non-trivial contract. + +{{% notice tip %}} +Source: [`task-tracker/`](https://github.com/go-swagger/examples/tree/master/task-tracker). +Generated with `swagger generate server --name TaskTracker --spec ./swagger.yml --principal any`. +{{% /notice %}} + +## The API surface + +The spec defines a full CRUD surface across three nested resources: + +| Path | Operations | +|------|-----------| +| `/tasks` | `listTasks`, `createTask` | +| `/tasks/{id}` | `getTaskDetails`, `updateTask`, `deleteTask` | +| `/tasks/{id}/comments` | `addCommentToTask`, `getTaskComments` | +| `/tasks/{id}/files` | `uploadTaskFile` | + +Each becomes a typed handler on the generated API, with parameters (path, query, +body, multipart) bound and validated for you. + +## Model composition + +The spec builds `Task` on top of a shared `TaskCard`, and the generator preserves +that with Go embedding — plus read-only fields, maps of attachments, and slices of +related models: + +{{< code file="task-tracker/models/task.go" lang="go" lines="23-53" >}} + +Read-only fields (`Comments`, `LastUpdated`) are populated by the server on +responses and ignored on input, exactly as the spec declares. + +## Two API-key schemes + +The spec declares two `apiKey` security definitions — one carried as a query +parameter, one as a header: + +{{< code file="task-tracker/swagger.yml" lang="yaml" region="security" >}} + +The generator turns each into an authenticator hook you implement. The scaffold +leaves them returning `NotImplemented`; you fill in the token validation: + +{{< code file="task-tracker/restapi/configure_task_tracker.go" lang="go" region="auth" >}} + +For worked authentication examples (basic, api-key, composed, OAuth2), see the +[Authentication guides](../../authentication/). + +## Related + +- [Todo list server](../todo-list/) — the minimal CRUD server to start from. +- [File server](../file-server/) — the `type: file` upload mechanics used by `/tasks/{id}/files`. +- [Petstore](../petstore/) — another full spec, generated end to end. diff --git a/docs/doc-site/guides/servers/todo-list.md b/docs/doc-site/guides/servers/todo-list.md new file mode 100644 index 00000000..6738c8da --- /dev/null +++ b/docs/doc-site/guides/servers/todo-list.md @@ -0,0 +1,86 @@ +--- +title: "Todo list server" +weight: 1 +description: "Canonical full server: unix, http and https listeners" +--- + +The **todo-list** example is the canonical go-swagger server: a small CRUD API +generated from a spec, wired to a trivial in-memory store. It's the best place to +see what `swagger generate server` produces and which files you're expected to +edit. + +Prefer a step-by-step build? Start with the [todo-list tutorial](../../../tutorials/todo-list/). +This page is the reference tour of the finished example. + +{{% notice tip %}} +Source: [`todo-list/`](https://github.com/go-swagger/examples/tree/master/todo-list). +Regenerate with `go run ./hack/tools regen` (or the `//go:generate` directive in +`restapi/configure_todo_list.go`). +{{% /notice %}} + +## The spec + +Two definitions drive everything: an `item` (with a required, `minLength: 1` +`description` and a read-only `id`) and a generic `error`. + +{{< code file="todo-list/swagger.yml" lang="yaml" region="definitions" >}} + +The `readOnly: true` on `id` means the server assigns it — clients that send one +have it ignored on create. + +## Generated models + +Each definition becomes a Go struct. Note how the spec constraints map onto the +type: `description` is required and `minLength: 1`, so it's generated as a +non-pointer-safe `*string` carrying validation, while the read-only `id` is a +plain `int64`. + +{{< code file="todo-list/models/item.go" lang="go" lines="17-30" >}} + +The generator also emits the validation methods that enforce the spec's +constraints at runtime — here the `description` field's `required` and +`minLength: 1` rules, wired straight from the spec. You never hand-write this: + +{{< code file="todo-list/models/item.go" lang="go" lines="46-57" >}} + +## Wiring the handlers + +`restapi/configure_todo_list.go` is the one file you edit — it's marked *safe to +edit* and survives regeneration. The generated scaffold leaves each handler +returning `501 Not Implemented`; you replace those with real logic. Here's the +implemented version from the tutorial's `server-complete`, backing the store with +a map: + +{{< code file="tutorials/todo-list/server-complete/restapi/configure_todo_list.go" lang="go" region="handlers" >}} + +Each response constructor (`NewAddOneCreated`, `NewDestroyOneNoContent`, …) is +generated from a response you declared in the spec, so the compiler keeps your +handlers honest against the contract. + +## Running it + +The generated server listens on a Unix socket, HTTP and HTTPS by default. For a +quick local test, enable just the HTTP listener on a fixed port: + +```shellsession +$ go run ./todo-list/cmd/todo-list-server --scheme=http --port=8765 +serving todo list at http://127.0.0.1:8765 +``` + +```shellsession +$ curl -i localhost:8765 \ + -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json' \ + -d '{"description":"go shopping"}' +HTTP/1.1 201 Created +... +{"description":"go shopping","id":1} +``` + +See the [todo-list README](https://github.com/go-swagger/examples/blob/master/todo-list/README.md) +for the full listener matrix (unix/http/https) and TLS options. + +## Variations + +- [Strict server](../strict-server/) — the strict responder interface. +- [Custom error handling](../error-handling/) — shaping error responses. +- [Todo list tutorial](../../../tutorials/todo-list/) — build this from scratch. diff --git a/docs/doc-site/guides/streaming/_index.md b/docs/doc-site/guides/streaming/_index.md new file mode 100644 index 00000000..11598c77 --- /dev/null +++ b/docs/doc-site/guides/streaming/_index.md @@ -0,0 +1,9 @@ +--- +title: Streaming +weight: 4 +description: | + Generating a server that streams newline-delimited JSON bodies, and a client that + consumes the stream. +--- + +{{< children type="card" description="true" >}} diff --git a/docs/doc-site/guides/streaming/client.md b/docs/doc-site/guides/streaming/client.md new file mode 100644 index 00000000..a247f138 --- /dev/null +++ b/docs/doc-site/guides/streaming/client.md @@ -0,0 +1,72 @@ +--- +title: "Streaming client" +weight: 2 +description: "Consume a stream from a generated client" +--- + +A generated client normally reads the *entire* response body and unmarshals it +into a typed payload. To consume a stream you override that behavior — swap the +consumer, and either buffer the whole thing or read it chunk-by-chunk. Two +examples show both approaches. + +{{% notice tip %}} +Sources: [`stream-server/elapsed_client.go`](https://github.com/go-swagger/examples/blob/master/stream-server/elapsed_client.go) +(non-blocking, pairs with the [streaming server](../server/)) and +[`stream-client/jigsaw.go`](https://github.com/go-swagger/examples/blob/master/stream-client/jigsaw.go) +(blocking, against an external server). +{{% /notice %}} + +## Why the default doesn't stream + +The generated client hands the response body to a **consumer**, which does an +`io.Copy` into the destination you pass. With the default `JSONConsumer` that +copy blocks until the body is complete and then unmarshals — exactly what you +*don't* want for a stream. The fix is to install a `ByteStreamConsumer` for the +response mime, so the bytes flow through untouched. + +## Non-blocking: consume chunks as they arrive + +Override the consumer, then pass an `io.Pipe` writer as the destination. The +client's `io.Copy` writes into the pipe while a goroutine reads the other end — +so bytes are processed the moment they arrive: + +{{< code file="stream-server/elapsed_client.go" lang="go" region="consumer" >}} + +A `bufio.Scanner` on the read end splits the stream on newlines and unmarshals +each line independently. `cancel()` (via `defer`) tears down the request if the +reader stops early: + +{{< code file="stream-server/elapsed_client.go" lang="go" region="scan" >}} + +The request runs on the main goroutine, writing into the pipe the scanner drains; +a context timeout bounds how long it will keep the connection open: + +{{< code file="stream-server/elapsed_client.go" lang="go" region="request" >}} + +## Blocking: buffer the whole stream + +If you don't need incremental processing, the simplest path is a destination that +knows how to accept `text/plain`. Give a buffer an `UnmarshalText` method and the +default consumer will fill it: + +{{< code file="stream-client/jigsaw.go" lang="go" region="buffer" >}} + +Then pass it straight to the operation and read it once the call returns: + +{{< code file="stream-client/jigsaw.go" lang="go" region="blocking" >}} + +The `jigsaw.go` example also has a non-blocking variant that installs +`transport.Consumers[runtime.TextMime] = runtime.ByteStreamConsumer()` — the same +technique as above, for a `text/plain` stream instead of newline-delimited JSON. + +## Choosing an approach + +- **Blocking + `UnmarshalText`** — least code; fine when you can wait for the full + response. +- **Non-blocking + `ByteStreamConsumer` + `io.Pipe`** — process items as they + arrive, cancel early, bound with a context. Use this for long-lived or unbounded + streams. + +## Related + +- [Streaming server](../server/) — the countdown server `elapsed_client.go` consumes. diff --git a/docs/doc-site/guides/streaming/server.md b/docs/doc-site/guides/streaming/server.md new file mode 100644 index 00000000..93842eba --- /dev/null +++ b/docs/doc-site/guides/streaming/server.md @@ -0,0 +1,70 @@ +--- +title: "Streaming server" +weight: 1 +description: "Stream newline-delimited JSON bodies from a generated server" +--- + +Swagger 2.0 has no first-class notion of a streaming response, but you can still +generate a server that streams. The **stream-server** example is a countdown API: +`GET /elapse/{length}` emits one newline-delimited JSON object per second until it +reaches zero. + +{{% notice tip %}} +Source: [`stream-server/`](https://github.com/go-swagger/examples/tree/master/stream-server). +Generated with `swagger generate server --spec ./swagger.yml`. +{{% /notice %}} + +## Declaring a streaming response + +The trick is the response schema: `type: string, format: binary`. That's the +closest Swagger 2.0 gets to "this endpoint streams bytes", and it makes the +generator produce a response the handler writes to directly rather than a typed +payload it serializes for you: + +{{< code file="stream-server/swagger.yml" lang="yaml" region="streaming-response" >}} + +## Writing the stream from the handler + +Instead of returning a generated responder, the handler returns a +`middleware.ResponderFunc` — a closure with raw access to the +`http.ResponseWriter`. It grabs the `http.Flusher` and writes through a small +wrapper so each write is pushed to the client immediately: + +{{< code file="stream-server/restapi/configure_countdown.go" lang="go" region="handler" >}} + +The `flushWriter` is what turns a normal write into a streamed chunk — it flushes +after every write, so the client sees each line as it's produced rather than at +the end: + +{{< code file="stream-server/restapi/configure_countdown.go" lang="go" region="flush-writer" >}} + +## Producing the chunks + +The business logic just encodes one `Mark` per iteration into the writer, with a +one-second pause between them. Because the writer flushes on every `Encode`, each +`{"remains":N}` line reaches the client in real time: + +{{< code file="stream-server/biz/count.go" lang="go" region="producer" >}} + +## Trying it + +```shellsession +$ go run ./stream-server/cmd/countdown-server --port=8000 +$ curl -N http://127.0.0.1:8000/elapse/5 +{"remains":5} +{"remains":4} +{"remains":3} +{"remains":2} +{"remains":1} +{"remains":0} +``` + +The response uses `Transfer-Encoding: chunked`; each line arrives a second apart. +A length of `11` returns `403` (a contrived error to show non-streaming responses +still work normally). + +## Related + +- [Streaming client](../client/) — consuming this stream from a generated client. +- Hand-wiring a streaming server without codegen? See the + [go-openapi/runtime](https://go-openapi.github.io/runtime/) examples. diff --git a/docs/doc-site/project/LICENSE.md b/docs/doc-site/project/LICENSE.md new file mode 100644 index 00000000..80e49151 --- /dev/null +++ b/docs/doc-site/project/LICENSE.md @@ -0,0 +1,35 @@ +--- +title: "License" +weight: 2 +description: "Apache-2.0 license" +--- + +The `go-swagger/examples` repository is licensed under the **Apache License, +Version 2.0**. + +{{% notice tip %}} +Full text: [`LICENSE`](https://github.com/go-swagger/examples/blob/master/LICENSE) +in the repository, or the canonical +[apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0). +{{% /notice %}} + +## SPDX headers + +Every `.go` file in the repository — hand-written *and* generated — carries an +SPDX header so the license is machine-verifiable per file: + +```go +// SPDX-FileCopyrightText: Copyright 2015-2026 go-swagger maintainers +// SPDX-License-Identifier: Apache-2.0 +``` + +The header is part of the contribution rules and is checked in CI. Generated files +carry it too, emitted by the templates, so a regeneration never strips it. + +## Using the examples + +Apache-2.0 is a permissive license: you may use, modify, and redistribute the +example code, including in commercial and closed-source work, provided you retain +the license and copyright notices and state significant changes. The code is +provided **as-is**, without warranty. See the full text for the authoritative +terms. diff --git a/docs/doc-site/project/README.md b/docs/doc-site/project/README.md new file mode 100644 index 00000000..82abd042 --- /dev/null +++ b/docs/doc-site/project/README.md @@ -0,0 +1,65 @@ +--- +title: "Repository README" +weight: 1 +description: "The go-swagger/examples repository overview" +--- + +This site documents the [`go-swagger/examples`](https://github.com/go-swagger/examples) +repository — a collection of runnable, committed examples for +[`go-swagger`](https://github.com/go-swagger/go-swagger), the **spec-first** code +generator for OpenAPI 2.0 (Swagger). + +## What's here + +Every example is a real Go project generated from an OpenAPI 2.0 spec: servers, +typed client SDKs, and CLIs. The generated code is **committed** and kept in sync +with go-swagger `master` by automated [regeneration](../regeneration/), so what you +read on this site matches what the current generator emits. + +Browse the material two ways: + +- **[Guides](../../guides/)** — reference recipes you dip into (servers, + clients & CLI, authentication, streaming, customizing codegen). +- **[Tutorials](../../tutorials/)** — sequential, end-to-end walkthroughs that build + something from scratch. + +## Where this fits — three sibling sites + +go-swagger and go-openapi split their example material across three sites by +*workflow*. This one is the **spec-first** corner: you write an OpenAPI spec and +generate typed Go from it. + +| Site | Workflow | You start from | +|------|----------|----------------| +| **this site** | spec-first codegen | an OpenAPI 2.0 spec → generated server/client/CLI | +| [go-openapi/runtime](https://go-openapi.github.io/runtime/) | untyped / hand-wired | the runtime API, no codegen | +| [go-openapi/codescan](https://go-openapi.github.io/codescan/) | code-first | Go code → generated spec | + +When a topic straddles two workflows, the page links across. + +## Getting started + +```sh +git clone https://github.com/go-swagger/examples +``` + +You'll need the `swagger` CLI on your `PATH` to regenerate or follow the +tutorials — see the [installation instructions](https://goswagger.io/go-swagger/install/). +Then head to the [todo-list tutorial](../../tutorials/todo-list/) to build a server +and client from one spec. + +## Status & releasing + +The examples track go-swagger code generation for servers and clients. The +repository is deliberately left **unreleased**: it follows the generator on +`go-swagger/go-swagger@master` rather than tagging versions of its own. + +## Licensing + +This software ships under the [Apache-2.0](../license/) license. + +## Other documentation + +- [Contributing guidelines](../contributing/) +- [Regeneration](../regeneration/) — how the examples stay in sync +- [All-time contributors](https://github.com/go-swagger/examples/blob/master/CONTRIBUTORS.md) diff --git a/docs/doc-site/project/_index.md b/docs/doc-site/project/_index.md new file mode 100644 index 00000000..f4371204 --- /dev/null +++ b/docs/doc-site/project/_index.md @@ -0,0 +1,9 @@ +--- +title: Project +weight: 4 +description: | + About the go-swagger/examples repository — licensing, contributing, and how the + examples are kept in sync with go-swagger. +--- + +{{< children type="card" description="true" >}} diff --git a/docs/doc-site/project/contributing.md b/docs/doc-site/project/contributing.md new file mode 100644 index 00000000..cb0da417 --- /dev/null +++ b/docs/doc-site/project/contributing.md @@ -0,0 +1,95 @@ +--- +title: "Contributing" +weight: 3 +description: "How to contribute examples" +--- + +Contributions are always welcome — and not just code. Reporting issues, improving +docs, triaging bugs, and adding test coverage all help. These guidelines are the +standard ones shared across every `go-openapi` and `go-swagger` repository; if +you've contributed to a Go project on GitHub before, you'll feel at home. + +{{% notice tip %}} +Authoritative sources: +[`.github/CONTRIBUTING.md`](https://github.com/go-swagger/examples/blob/master/.github/CONTRIBUTING.md) +and [`docs/STYLE.md`](https://github.com/go-swagger/examples/blob/master/docs/STYLE.md). +This page summarizes the essentials. +{{% /notice %}} + +## Git flow + +Fork the repo, branch from `master`, and open a pull request from your fork. +Branch naming is not enforced (it's your fork), but the common convention is +`fix/XXX-something` or `feature/XXX-something`, where `XXX` is the issue number. + +Keep pull requests **focused** — small, single-purpose PRs are reviewed faster and +are less likely to lose the thread than large ones. + +## A special note for generated code + +Most of this repository is **generated** and must not be hand-edited — a +[regeneration](../regeneration/) would silently overwrite your change. If your +contribution affects generated output: + +- change the **spec** or the **generation command** (in `hack/tools/regen.go`), not + the generated `.go` files; +- run `go run ./hack/tools regen` and commit the regenerated result; +- hand-written glue (the `configure_*.go` files, custom handlers, `main.go` in the + custom-server example) *is* editable — that's the code these examples exist to + illustrate. + +## Tests + +Submit unit tests for your changes and run the full suite before opening a PR: + +```sh +go test ./... +``` + +CI measures patch coverage; aim for at least 80% of your change. It's an indicator +maintainers weigh, not a hard gate. + +## Code style & linting + +The project runs the `golangci-lint` meta-linter with a deliberate posture: +**`default: all`, then disable what doesn't earn its keep**. The disabled list in +[`.golangci.yml`](https://github.com/go-swagger/examples/blob/master/.golangci.yml) +is a design rationale, not technical debt. Two rules matter most when contributing: + +- every `//nolint` directive **must** carry an inline comment explaining why; +- prefer disabling a linter globally over scattering `//nolint` — if a linter + fights an intentional pattern, the linter goes, not the code. + +Run it (and the formatter) before committing: + +```sh +golangci-lint run +golangci-lint fmt +``` + +## Sign your work (DCO) + +Every commit must be **signed off** under the +[Developer Certificate of Origin](https://developercertificate.org), using your +real name and email: + +``` +Signed-off-by: Joe Smith +``` + +Add it automatically with `git commit -s`. PGP-signed commits are appreciated but +not required. Squash your commits into logical units (`git rebase -i`) before +requesting review. + +## AI agents + +Agentic contributors are welcome, with a few rules: + +1. Issues and PRs written or posted by an agent should mention the original + **human** poster for reference. +2. PRs must **not** be attributed to an agent as author — no commits authored by + `@claude.code` or similar. Agents and bots may be listed as `Co-Authored-By:`; + the commit author must be the human sponsor. +3. Security reports produced by an agent must be filed **privately** (see the + [security policy](https://github.com/go-swagger/examples/blob/master/SECURITY.md)) + and mention the human poster. diff --git a/docs/doc-site/project/regeneration.md b/docs/doc-site/project/regeneration.md new file mode 100644 index 00000000..3e99ff53 --- /dev/null +++ b/docs/doc-site/project/regeneration.md @@ -0,0 +1,69 @@ +--- +title: "Regeneration" +weight: 4 +description: "How examples stay in sync with go-swagger" +--- + +The generated code in this repository is **committed**, yet it always reflects the +*current* go-swagger generator. That's the whole point of the examples: what you +read here is what `swagger generate` emits from `master` today, not a frozen +snapshot. A single tool keeps them in sync. + +## Regenerating everything + +```sh +go run ./hack/tools regen +``` + +The `regen` command drives the whole repository from one place — +[`hack/tools/regen.go`](https://github.com/go-swagger/examples/blob/master/hack/tools/regen.go) +holds a table with one entry per example: which directories to clean, and the exact +`swagger generate` command(s) to run. For each example it: + +1. **ensures the generator is present** — if `swagger` isn't on your `PATH`, it + installs it from source (`go install github.com/go-swagger/go-swagger/cmd/swagger@master`), + pinning regeneration to the latest generator; +2. **cleans** the generated sub-directories (`models`, `restapi`, `client`, `cmd`, …) + so nothing stale survives; +3. **runs** that example's generate command(s) — server, client, or both, with the + flags and templates each example needs; +4. **restores** any preserved hand-written files that live inside a cleaned tree; +5. finally, once every example is regenerated, runs `go test ./...` across the whole + module as a smoke test. + +Because the command list is data, adding or changing an example's generation is a +one-line edit to that table — not a shell script to maintain. + +## Auth material for runnable examples + +A few examples need secrets to actually *run* (TLS certificates, JWT signing keys). +These are deliberately **not committed**. Generate them locally with the same tool: + +```sh +go run ./hack/tools gen-certs # self-signed TLS certs (todo-list-errors) +go run ./hack/tools gen-tokens # RSA keypair + JWT tokens (composed-auth) +``` + +## Automated regeneration in CI + +Regeneration also runs unattended, so the committed code never drifts from the +generator: + +| Workflow | Trigger | Purpose | +|----------|---------|---------| +| `regen.yml` | Weekly (Mon 06:00 UTC) + manual | Regenerate from `swagger@master`; open an auto-merged PR if output changed | +| `go-test.yml` | PR, push to `master` | Lint + build matrix (2 Go versions × 3 OS) | +| `auto-merge.yml` | PR | Auto-approve/merge bot PRs (dependabot, scheduled regen) | +| `codeql.yml` | PR, push, weekly | CodeQL semantic analysis | +| `scanner.yml` | Push, weekly | Trivy + govulncheck vulnerability scans | +| `contributors.yml` | Weekly + manual | Refresh the all-time contributors list | + +On top of the weekly job, go-swagger itself triggers a **cross-repo** pipeline: +PRs to go-swagger that touch code generation open a regeneration PR *here* +automatically, so a generator change and its effect on the examples are reviewed +together. + +## Related + +- [Contributing](../contributing/) — why you edit the spec or the regen table, never the generated files. +- [Repository README](../readme/) — how the examples are organized. diff --git a/docs/doc-site/tutorials/_index.md b/docs/doc-site/tutorials/_index.md new file mode 100644 index 00000000..d3e7cea6 --- /dev/null +++ b/docs/doc-site/tutorials/_index.md @@ -0,0 +1,10 @@ +--- +title: Tutorials +weight: 3 +description: | + Sequential, end-to-end walkthroughs. Unlike the guides — which are reference + recipes you dip into — these build something from scratch, step by step. Start + with the todo-list to generate both a server and a client. +--- + +{{< children type="card" description="true" >}} diff --git a/docs/doc-site/tutorials/client-sdk.md b/docs/doc-site/tutorials/client-sdk.md new file mode 100644 index 00000000..35979c47 --- /dev/null +++ b/docs/doc-site/tutorials/client-sdk.md @@ -0,0 +1,159 @@ +--- +title: "Client SDK tutorial" +weight: 2 +description: "Generate and use a typed SDK client" +--- + +The [todo-list tutorial](../todo-list/) built a **server** from a spec. This one +takes the *same* kind of spec and generates a typed **client SDK** — one Go method +per operation, with generated parameter and response types — then walks through +actually calling an API with it. + +You'll generate the SDK in two flavors from one spec (the **classic** go-swagger +client and the leaner **stratoscale** contributed template) and see how the +generated signature copes with two spec shapes that trip people up: an operation +with **multiple success responses**, and one with **no default response**. + +{{% notice info %}} +You'll need the `swagger` CLI on your `PATH` — see +[goswagger.io](https://goswagger.io/go-swagger/install/). The finished code lives +under [`tutorials/client/`](https://github.com/go-swagger/examples/tree/master/tutorials/client) +in the examples repository. For a side-by-side *reference* comparison of the two +flavors, see the [generated client SDK guide](../../guides/clients-and-cli/generated-client/); +this page is the hands-on walkthrough. +{{% /notice %}} + +## Step 1 — the spec + +Start from a todo-list `swagger.yml`. The only detail that shapes the client here +is the security scheme: the API is protected by an API-key header, so every +request the client sends must carry a `x-todolist-token`: + +{{< code file="tutorials/client/swagger.yml" lang="yaml" region="security" >}} + +That `security` requirement is what forces an *auth writer* into the calls below. + +## Step 2 — generate the classic client + +Point `swagger generate client` at the spec: + +```sh +swagger generate client -A TodoList --spec swagger.yml --client-package classic_client +``` + +This writes a `classic_client/` package: a top-level `TodoList` client whose +fields group the operations by tag (`Todos`, `Experimental`), plus generated +`…Params` and `…Responses` types under each tag package. + +## Step 3 — call the API + +Instantiate the client over a transport, then call an operation. Because the spec +requires an API key, you pass a `runtime.ClientAuthInfoWriter` built from the +transport's `APIKeyAuth` helper — the classic client takes it **per call**: + +```go +import ( + httptransport "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag/conv" + + client "github.com/go-swagger/examples/tutorials/client/classic_client" + "github.com/go-swagger/examples/tutorials/client/classic_client/todos" + "github.com/go-swagger/examples/tutorials/client/models" +) + +// point the transport at the running server +transport := httptransport.New("localhost:8080", "/", []string{"http"}) +c := client.New(transport, strfmt.Default) + +// the API-key writer that satisfies the `key` security scheme +auth := httptransport.APIKeyAuth("x-todolist-token", "header", "my-secret-token") + +params := todos.NewAddOneParams().WithBody(&models.Item{ + Description: conv.Pointer("write the client tutorial"), +}) + +created, noContent, err := c.Todos.AddOne(params, auth) +``` + +Notice the call returns **three** values, not two — that's the next step. + +## Step 4 — the stratoscale flavor + +Regenerate the same spec with the stratoscale template for a leaner, context-first +client (auth folded into construction, no per-call options, and a +`//go:generate mockery` directive for easy mocking): + +```sh +swagger generate client -A TodoList --spec swagger.yml \ + --template stratoscale --existing-models ... --client-package stratoscale_client +``` + +Usage folds the auth writer into the constructor and threads a `context.Context` +through each call instead of an auth argument: + +```go +import ( + "github.com/go-openapi/runtime" + httptransport "github.com/go-openapi/runtime/client" + + client "github.com/go-swagger/examples/tutorials/client/stratoscale_client" + "github.com/go-swagger/examples/tutorials/client/stratoscale_client/todos" +) + +c := client.New(client.Config{ + URL: mustParse("http://localhost:8080/"), + AuthInfo: httptransport.APIKeyAuth("x-todolist-token", "header", "my-secret-token"), +}) + +created, noContent, err := c.Todos.AddOne(ctx, todos.NewAddOneParams().WithBody(item)) +``` + +Same operation, same three return values — only the ergonomics differ. Pick +classic for the full go-swagger surface (per-call auth and options); pick +stratoscale for a compact, mockable client. + +## Step 5 — multiple success responses + +Why three return values? Because `addOne` declares **two** success responses in +the spec — `201 Created` *and* `204 No Content`: + +```yaml + post: + operationId: addOne + responses: + '201': + description: Created + schema: + $ref: "#/definitions/item" + '204': + description: Already there +``` + +The generated method reflects that by handing back a pointer for *each* possible +success; exactly one is non-nil. Switch on them: + +```go +created, noContent, err := c.Todos.AddOne(params, auth) +switch { +case err != nil: + // transport failure, or a typed error response +case created != nil: + // 201 — the new item is in created.Payload + log.Printf("created #%d", created.Payload.ID) +case noContent != nil: + // 204 — the item already existed; nothing to read + log.Print("already there") +} +``` + +The **no default response** case (the `experimental` operations declare `401`/`405` +but no `default`) works by the same mechanism in reverse: with no `default`, any +status code the spec didn't list can't map to a typed payload, so it surfaces as a +generic error from the response reader rather than a `*…Default` value. + +## Related + +- [Generated client SDK guide](../../guides/clients-and-cli/generated-client/) — the reference comparison of both flavors. +- [CLI client guide](../../guides/clients-and-cli/cli-client/) — wrap a generated client in a cobra command-line tool. +- [Todo list tutorial](../todo-list/) — the server side of the same spec. diff --git a/docs/doc-site/tutorials/custom-server.md b/docs/doc-site/tutorials/custom-server.md new file mode 100644 index 00000000..35ce0f01 --- /dev/null +++ b/docs/doc-site/tutorials/custom-server.md @@ -0,0 +1,115 @@ +--- +title: "Custom server tutorial" +weight: 3 +description: "Embed a generated core in a custom server" +--- + +The [todo-list tutorial](../todo-list/) generated a *whole* server — `main.go` +and all — and you edited the `configure_*.go` file it left for you. Sometimes you +want the opposite balance: keep go-swagger's generated **core** (the models, +router, and typed operations) but own the `main` yourself, so the CLI is a thin +hand-written layer that wires configuration and handlers around that core. + +That's what `--exclude-main` is for. This tutorial builds a tiny *greeter* server +that way. + +{{% notice info %}} +You'll need the `swagger` CLI on your `PATH` — see +[goswagger.io](https://goswagger.io/go-swagger/install/). The finished code lives +under [`tutorials/custom-server/`](https://github.com/go-swagger/examples/tree/master/tutorials/custom-server). +{{% /notice %}} + +## Step 1 — the spec + +The greeter is deliberately minimal: one `GET /hello` that takes an optional +`name` query parameter and returns a plain-text greeting. + +```yaml +swagger: '2.0' +info: + version: 1.0.0 + title: Greeting Server +paths: + /hello: + get: + produces: + - text/plain + parameters: + - name: name + required: false + type: string + in: query + description: defaults to World if not given + operationId: getGreeting + responses: + 200: + description: returns a greeting + schema: + type: string + description: contains the actual greeting as plain text +``` + +## Step 2 — generate the core only + +Generate the server into a `gen/` sub-tree with `--exclude-main`, so go-swagger +emits everything *except* a `main.go`: + +```sh +rm -rf gen && mkdir gen +swagger generate server --exclude-main -A greeter -t gen -f ./swagger/swagger.yml +``` + +You get `gen/restapi/` — the embedded spec, the `NewServer` constructor, the +router, and `operations/` with the typed `GreeterAPI`, its `GetGreetingParams`, +and the `GetGreetingOK` responder. What you *don't* get is a `cmd/` entry point. +That's yours to write. + +## Step 3 — write your own `main` + +Your `main` does what the generated `main.go` would have — load the embedded spec, +construct the API, and hand it to `NewServer` — but it's plain code you control: + +{{< code file="tutorials/custom-server/cmd/greeter/main.go" lang="go" region="wiring" >}} + +Because you own this file, you can add your own flags, config loading, dependency +injection, logging, or lifecycle management around this core — none of it is +generated, none of it gets overwritten on regeneration. + +## Step 4 — attach the handler + +The generated `GreeterAPI` exposes one handler field per operation. Assign your +implementation to `GetGreetingHandler` before serving — this is the same handler +you'd otherwise place in a generated `configure_*.go`, but here it lives in your +`main`: + +{{< code file="tutorials/custom-server/cmd/greeter/main.go" lang="go" region="handler" >}} + +`conv.Value` dereferences the optional `*string` parameter, defaulting to `World`, +and `NewGetGreetingOK().WithPayload(...)` returns the typed `200` responder the +generated code defined. + +## Step 5 — run it + +```shellsession +$ go run ./cmd/greeter/main.go --port 3000 +``` + +Then exercise it (here with [httpie](https://httpie.org)): + +```shellsession +$ http get :3000/hello # Hello, World! +$ http get :3000/hello name==Swagger # Hello, Swagger! +``` + +## Regenerating safely + +The whole point of the split is that regeneration only ever touches `gen/`. When +the spec changes, rerun the Step 2 command — `gen/` is rewritten, your `cmd/` +`main` is untouched. Keep the two apart (generated core under `gen/`, your code +outside it) and the two never collide. + +## Related + +- [Todo list tutorial](../todo-list/) — the opposite balance: generate the whole server and edit `configure_*.go`. +- [Custom middleware guide](../../guides/customizing-codegen/custom-middleware/) — extend a *fully* generated server via its hook points, no `--exclude-main` needed. +- [Generation flags guide](../../guides/customizing-codegen/generation-flags/) — other flags that reshape the generated `main`. diff --git a/docs/doc-site/tutorials/todo-list.md b/docs/doc-site/tutorials/todo-list.md new file mode 100644 index 00000000..493687f7 --- /dev/null +++ b/docs/doc-site/tutorials/todo-list.md @@ -0,0 +1,580 @@ +--- +title: "Todo list tutorial" +weight: 1 +description: "Build a todo-list API server from a spec, step by step" +--- + +This tutorial walks you through a hypothetical project, building a todo list. + +It uses a todo list because this is a well-understood application, so you can +focus on the go-swagger pieces. Here we build the **server**; when you're done, +head to the [client SDK tutorial](../client-sdk/) to generate a typed client +against the same spec. + +{{% notice info %}} +You'll need the `swagger` CLI on your `PATH`. See +[goswagger.io](https://goswagger.io/go-swagger/install/) for installation, and the +[command reference](https://goswagger.io/go-swagger/usage/) for the full set of +generate options. The finished code for each stage of this tutorial lives under +[`tutorials/todo-list/`](https://github.com/go-swagger/examples/tree/master/tutorials/todo-list) +in the examples repository (`server-1`, `server-2`, `server-complete`). +{{% /notice %}} + +To create your application start with `swagger init`: + +```sh +swagger init spec \ + --title "A Todo list application" \ + --description "From the todo list tutorial on goswagger.io" \ + --version 1.0.0 \ + --scheme http \ + --consumes application/io.goswagger.examples.todo-list.v1+json \ + --produces application/io.goswagger.examples.todo-list.v1+json +``` + +This gives you a skeleton `swagger.yml` file: + +```yaml +definitions: + item: + type: object + required: + - description + properties: + id: + type: integer + format: int64 + readOnly: true + description: + type: string + minLength: 1 + completed: + type: boolean +``` + +In this model definition we say that the model `item` is an _object_ with a required property `description`. This item model has 3 properties: `id`, `description`, and `completed`. The `id` property is an int64 value and is marked as _readOnly_, meaning that it will be provided by the API server and it will be ignored when the item is created. + +This document also says that the description must be at least 1 char long, which results in a string property that's [not a pointer](https://goswagger.io/go-swagger/reference/models/schemas/#nullability). + +At this moment you have enough so that actual code could be generated, but let's continue defining the rest of the API so that the code generation will be more useful. Now that you have a model so you can add some endpoints to list the todo's: + +```yaml +paths: + /: + get: + tags: + - todos + parameters: + - name: since + in: query + type: integer + format: int64 + - name: limit + in: query + type: integer + format: int32 + default: 20 + responses: + 200: + description: list the todo operations + schema: + type: array + items: + $ref: "#/definitions/item" +``` + +With this new version of the operation you now have query params. These parameters have defaults so users can leave them off and the API will still function as intended. + +However, this definition is extremely optimistic and only defines a response for the "happy path". It's very likely that the API will need to return errors too. That means you have to define a model errors, as well as at least one more response definition to cover the error response. + +The error definition looks like this: + +```yaml +paths: + /: + get: + tags: + - todos + parameters: + - name: since + in: query + type: integer + format: int64 + - name: limit + in: query + type: integer + format: int32 + default: 20 + responses: + 200: + description: list the todo operations + schema: + type: array + items: + $ref: "#/definitions/item" + default: + description: generic error response + schema: + $ref: "#/definitions/error" +``` + +At this point you've defined your first endpoint completely. To improve the strength of this contract you could define responses for each of the status codes and perhaps return different error messages for different statuses. For now, the status code will be provided in the error message. + +Try validating the specification again with `swagger validate ./swagger.yml` to ensure that code generation will work as expected. Generating code from an invalid specification leads to unpredictable results. + +Your completed spec should look like this: + +```yaml +paths: + /: + get: + tags: + - todos + operationId: find_todos + ... +``` + +These `operationId` values are used to name the generated files: + +``` +. +├── cmd +│ └── todo-list-server +│ └── main.go +├── models +│ ├── error.go +│ ├── find_todos_okbody.go +│ ├── get_okbody.go +│ └── item.go +├── restapi +│ ├── configure_todo_list.go +│ ├── doc.go +│ ├── embedded_spec.go +│ ├── operations +│ │ ├── todo_list_api.go +│ │ └── todos +│ │ ├── find_todos.go +│ │ ├── find_todos_parameters.go +│ │ ├── find_todos_responses.go +│ │ └── find_todos_urlbuilder.go +│ └── server.go +└── swagger.yml +``` + +You can see that the files under `restapi/operations/todos` now use the `operationId` as part of the generated file names. + +At this point can start the server, but first let's see what `--help` gives you. First install the server binary and then run it: + +```sh +± ~/go/src/.../examples/tutorials/todo-list/server-1 +» go install ./cmd/todo-list-server/ +± ~/go/src/.../examples/tutorials/todo-list/server-1 +» todo-list-server --help +Usage: + todo-list-server [OPTIONS] + +From the todo list tutorial on goswagger.io + +Application Options: + --scheme= the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec + --cleanup-timeout= grace period for which to wait before shutting down the server (default: 10s) + --max-header-size= controls the maximum number of bytes the server will read parsing the request header's keys and values, including the + request line. It does not limit the size of the request body. (default: 1MiB) + --socket-path= the unix socket to listen on (default: /var/run/todo-list.sock) + --host= the IP to listen on (default: localhost) [$HOST] + --port= the port to listen on for insecure connections, defaults to a random value [$PORT] + --listen-limit= limit the number of outstanding requests + --keep-alive= sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download) + (default: 3m) + --read-timeout= maximum duration before timing out read of the request (default: 30s) + --write-timeout= maximum duration before timing out write of the response (default: 60s) + --tls-host= the IP to listen on for tls, when not specified it's the same as --host [$TLS_HOST] + --tls-port= the port to listen on for secure connections, defaults to a random value [$TLS_PORT] + --tls-certificate= the certificate to use for secure connections [$TLS_CERTIFICATE] + --tls-key= the private key to use for secure connections [$TLS_PRIVATE_KEY] + --tls-ca= the certificate authority file to be used with mutual tls auth [$TLS_CA_CERTIFICATE] + --tls-listen-limit= limit the number of outstanding requests + --tls-keep-alive= sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download) + --tls-read-timeout= maximum duration before timing out read of the request + --tls-write-timeout= maximum duration before timing out write of the response + +Help Options: + -h, --help Show this help message +``` + +If you run your application now it will start on a random port by default. This might not be what you want, so you can configure a port through a command line argument or a `PORT` env var. + +```sh +git:(master) ✗ !? » todo-list-server +serving todo list at http://127.0.0.1:64637 +``` + +You can use `curl` to check your API: + +```sh +git:(master) ✗ !? » curl -i http://127.0.0.1:64637/ +``` +```http +HTTP/1.1 501 Not Implemented +Content-Type: application/io.goswagger.examples.todo-list.v1+json +Date: Thu, 31 Dec 2015 22:42:10 GMT +Content-Length: 57 + +"operation todos.FindTodos has not yet been implemented" +``` + +As you can see, the generated API isn't very usable yet, but we know it runs and does something. To make it useful you'll need to implement the actual logic behind those endpoints. And you'll also want to add some more endpoints, like adding a new todo item and updating an existing item to change its description or mark it completed. + +To supporting adding a todo item you should define a `POST` operation: + +```yaml +paths: + /{id}: + delete: + tags: + - todos + operationId: destroyOne + parameters: + - type: integer + format: int64 + name: id + in: path + required: true + responses: + 204: + description: Deleted + default: + description: error + schema: + $ref: "#/definitions/error" +``` + +This time you're defining a parameter that is part of the `path`. This operation will look in the URI templated path for an id. Since there's nothing to return after a delete, the success response is `204 No Content`. + +Finally, you need to define a way to update an existing item: + +```yaml +swagger: "2.0" +info: + description: From the todo list tutorial on goswagger.io + title: A Todo list application + version: 1.0.0 +consumes: +- application/io.goswagger.examples.todo-list.v1+json +produces: +- application/io.goswagger.examples.todo-list.v1+json +schemes: +- http +- https +paths: + /: + get: + tags: + - todos + operationId: findTodos + parameters: + - name: since + in: query + type: integer + format: int64 + - name: limit + in: query + type: integer + format: int32 + default: 20 + responses: + 200: + description: list the todo operations + schema: + type: array + items: + $ref: "#/definitions/item" + default: + description: generic error response + schema: + $ref: "#/definitions/error" + post: + tags: + - todos + operationId: addOne + parameters: + - name: body + in: body + schema: + $ref: "#/definitions/item" + responses: + 201: + description: Created + schema: + $ref: "#/definitions/item" + default: + description: error + schema: + $ref: "#/definitions/error" + /{id}: + parameters: + - type: integer + format: int64 + name: id + in: path + required: true + put: + tags: + - todos + operationId: updateOne + parameters: + - name: body + in: body + schema: + $ref: "#/definitions/item" + responses: + 200: + description: OK + schema: + $ref: "#/definitions/item" + default: + description: error + schema: + $ref: "#/definitions/error" + delete: + tags: + - todos + operationId: destroyOne + responses: + 204: + description: Deleted + default: + description: error + schema: + $ref: "#/definitions/error" +definitions: + item: + type: object + required: + - description + properties: + id: + type: integer + format: int64 + readOnly: true + description: + type: string + minLength: 1 + completed: + type: boolean + error: + type: object + required: + - message + properties: + code: + type: integer + format: int64 + message: + type: string +``` + +This is a good time to sanity check and by validating the schema: + +```sh +± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-2 +git:(master) ✗ !? » swagger validate ./swagger.yml +The swagger spec at "./swagger.yml" is valid against swagger specification 2.0 +``` + +Now you're ready to generate the API and start filling in the actual operations: + +```sh +git:(master) ✗ !? » swagger generate server -A TodoList -f ./swagger.yml +... elided output ... +2015/12/31 18:16:28 rendered main template: server.TodoList +± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-2 +git:(master) ✗ !? » tree +. +├── cmd +│ └── todo-list-server +│ └── main.go +├── models +│ ├── error.go +│ ├── find_todos_okbody.go +│ └── item.go +├── restapi +│ ├── configure_todo_list.go +│ ├── doc.go +│ ├── embedded_spec.go +│ ├── operations +│ │ ├── todo_list_api.go +│ │ └── todos +│ │ ├── add_one.go +│ │ ├── add_one_parameters.go +│ │ ├── add_one_responses.go +│ │ ├── add_one_urlbuilder.go +│ │ ├── destroy_one.go +│ │ ├── destroy_one_parameters.go +│ │ ├── destroy_one_responses.go +│ │ ├── destroy_one_urlbuilder.go +│ │ ├── find_todos.go +│ │ ├── find_todos_parameters.go +│ │ ├── find_todos_responses.go +│ │ ├── find_todos_urlbuilder.go +│ │ ├── update_one.go +│ │ ├── update_one_parameters.go +│ │ ├── update_one_responses.go +│ │ └── update_one_urlbuilder.go +│ └── server.go +└── swagger.yml + +6 directories, 26 files +``` + +To implement the core of your application you start by editing `restapi/configure_todo_list.go`. This file is safe to edit. Its content will not be overwritten if you run `swagger generate` again the future. + +The simplest way to implement this application is to simply store all the todo items in a golang `map`. This provides a simple way to move forward without bringing in complications like a database or files. + +To do this you'll need a map and a counter to track the last assigned id: + +```go +// the variables we need throughout our implementation +var items = make(map[int64]*models.Item) +var lastID int64 +``` + +The simplest handler to implement now is the delete handler. Because the store is a map and the id of the item is provided in the request it's a one liner. + +```go +api.TodosDestroyOneHandler = todos.DestroyOneHandlerFunc(func(params todos.DestroyOneParams) middleware.Responder { + delete(items, params.ID) + return todos.NewDestroyOneNoContent() +}) +``` + +After deleting the item from the store, you need to provide a response. The code generator created responders for each response you defined in the swagger specification, and you can see how one of those is being used in the example above. + +The other 3 handler implementations are similar to this one. They are provided in the +[source for this tutorial](https://github.com/go-swagger/examples/blob/master/tutorials/todo-list/server-complete/restapi/configure_todo_list.go). + +So assuming you go ahead and implement the remainder of the endpoints, you're all set to test it out: + +```sh +» curl -i localhost:8765 +``` +```http +HTTP/1.1 200 OK +Content-Type: application/io.goswagger.examples.todo-list.v1+json +Date: Fri, 01 Jan 2016 19:56:01 GMT +Content-Length: 3 + +[] +``` +```sh +± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete +``` +```http +» curl -i localhost:8765 -d "{\"description\":\"message $RANDOM\"}" +HTTP/1.1 415 Unsupported Media Type +Content-Type: application/io.goswagger.examples.todo-list.v1+json +Date: Fri, 01 Jan 2016 19:56:11 GMT +Content-Length: 157 + +{"code":415,"message":"unsupported media type \"application/x-www-form-urlencoded\", only [application/io.goswagger.examples.todo-list.v1+json] are allowed"} +~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete +``` +```sh +» curl -i localhost:8765 -d "{\"description\":\"message $RANDOM\"}" -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json' +``` +```http +HTTP/1.1 201 Created +Content-Type: application/io.goswagger.examples.todo-list.v1+json +Date: Fri, 01 Jan 2016 19:56:20 GMT +Content-Length: 39 + +{"description":"message 30925","id":1} +``` +```sh +± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete +» curl -i localhost:8765 -d "{\"description\":\"message $RANDOM\"}" -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json' +``` +```http +HTTP/1.1 201 Created +Content-Type: application/io.goswagger.examples.todo-list.v1+json +Date: Fri, 01 Jan 2016 19:56:23 GMT +Content-Length: 37 + +{"description":"message 104","id":2} +``` +```sh +± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete +» curl -i localhost:8765 -d "{\"description\":\"message $RANDOM\"}" -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json' +``` +```http +HTTP/1.1 201 Created +Content-Type: application/io.goswagger.examples.todo-list.v1+json +Date: Fri, 01 Jan 2016 19:56:24 GMT +Content-Length: 39 + +{"description":"message 15225","id":3} +``` +```sh +± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete +» curl -i localhost:8765 +``` +```http +HTTP/1.1 200 OK +Content-Type: application/io.goswagger.examples.todo-list.v1+json +Date: Fri, 01 Jan 2016 19:56:26 GMT +Content-Length: 117 + +[{"description":"message 30925","id":1},{"description":"message 104","id":2},{"description":"message 15225","id":3}] +``` +```sh +± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete +» curl -i localhost:8765/3 -X PUT -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json' -d '{"description":"go shopping"}' +``` +```http +HTTP/1.1 200 OK +Content-Type: application/io.goswagger.examples.todo-list.v1+json +Date: Fri, 01 Jan 2016 19:56:32 GMT +Content-Length: 37 + +{"description":"go shopping","id":3} +``` +```sh +± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete +» curl -i localhost:8765 +``` +```http +HTTP/1.1 200 OK +Content-Type: application/io.goswagger.examples.todo-list.v1+json +Date: Fri, 01 Jan 2016 19:56:34 GMT +Content-Length: 115 + +[{"description":"message 30925","id":1},{"description":"message 104","id":2},{"description":"go shopping","id":3}] +``` +```sh +± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete +» curl -i localhost:8765/1 -X DELETE -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json' +``` +```http +HTTP/1.1 204 No Content +Content-Type: application/io.goswagger.examples.todo-list.v1+json +Date: Fri, 01 Jan 2016 19:57:04 GMT +``` +```sh +± ~/go/src/github.com/go-swagger/examples/tutorials/todo-list/server-complete +» curl -i localhost:8765 +``` +```http +HTTP/1.1 200 OK +Content-Type: application/io.goswagger.examples.todo-list.v1+json +Date: Fri, 01 Jan 2016 19:57:06 GMT +Content-Length: 76 + +[{"description":"message 104","id":2},{"description":"go shopping","id":3}] +``` + +## Next steps + +- Generate a typed client against this same spec in the + [client SDK tutorial](../client-sdk/). +- Browse the [servers guides](../../guides/servers/) for variations: strict + handlers, custom error handling, file serving and more. diff --git a/external-types/example-external-types.yaml b/external-types/example-external-types.yaml index c81a14e1..0ee19177 100644 --- a/external-types/example-external-types.yaml +++ b/external-types/example-external-types.yaml @@ -175,6 +175,7 @@ definitions: x-go-type: type: MyInteger + # snippet:x-go-type gamma: description: | Property defined as an external type from package "fred" @@ -183,6 +184,7 @@ definitions: type: MyAlternateInteger import: package: "github.com/go-swagger/examples/external-types/fred" + # endsnippet:x-go-type epsilon: type: array diff --git a/file-server/restapi/configure_file_upload.go b/file-server/restapi/configure_file_upload.go index cdf02a27..2db53567 100644 --- a/file-server/restapi/configure_file_upload.go +++ b/file-server/restapi/configure_file_upload.go @@ -55,6 +55,7 @@ func configureAPI(api *operations.FileUploadAPI) http.Handler { } uploadCounter := 0 + // snippet:upload-handler api.UploadsUploadFileHandler = uploads.UploadFileHandlerFunc(func(params uploads.UploadFileParams) middleware.Responder { if params.File == nil { return middleware.Error(http.StatusNotFound, stderrors.New("no file provided")) @@ -87,6 +88,7 @@ func configureAPI(api *operations.FileUploadAPI) http.Handler { return uploads.NewUploadFileOK() }) + // endsnippet:upload-handler api.PreServerShutdown = func() {} diff --git a/file-server/swagger.yml b/file-server/swagger.yml index ea6b491c..9626f1ab 100644 --- a/file-server/swagger.yml +++ b/file-server/swagger.yml @@ -12,6 +12,7 @@ produces: - application/json paths: + # snippet:upload-path /upload: post: tags: @@ -25,6 +26,7 @@ paths: in: formData type: file required: true + # endsnippet:upload-path responses: "200": description: OK diff --git a/file-server/upload_file.go b/file-server/upload_file.go index e28fbf5f..2b210356 100644 --- a/file-server/upload_file.go +++ b/file-server/upload_file.go @@ -30,6 +30,7 @@ func main() { } } +// snippet:client-upload func upload(reader runtime.NamedReadCloser) error { config := client.DefaultTransportConfig().WithHost("localhost:8000") @@ -41,3 +42,5 @@ func upload(reader runtime.NamedReadCloser) error { return err } + +// endsnippet:client-upload diff --git a/generated/restapi/configure_petstore.go b/generated/restapi/configure_petstore.go index 751e1461..feafef81 100644 --- a/generated/restapi/configure_petstore.go +++ b/generated/restapi/configure_petstore.go @@ -44,6 +44,7 @@ func configureAPI(api *operations.PetstoreAPI) http.Handler { api.JSONProducer = runtime.JSONProducer() api.XMLProducer = runtime.XMLProducer() + // snippet:auth // Applies when the "api_key" header is set if api.APIKeyAuth == nil { api.APIKeyAuth = func(token string) (any, error) { @@ -60,6 +61,7 @@ func configureAPI(api *operations.PetstoreAPI) http.Handler { return nil, errors.NotImplemented("oauth2 bearer auth (petstore_auth) has not yet been implemented") } } + // endsnippet:auth // Set your custom authorizer if needed. Default one is security.Authorized() // Expected interface runtime.Authorizer diff --git a/hack/doc-site/hugo/.gitignore b/hack/doc-site/hugo/.gitignore new file mode 100644 index 00000000..3d0f225c --- /dev/null +++ b/hack/doc-site/hugo/.gitignore @@ -0,0 +1,3 @@ +public +*.lock +examples.yaml diff --git a/hack/doc-site/hugo/README.md b/hack/doc-site/hugo/README.md new file mode 100644 index 00000000..353b1657 --- /dev/null +++ b/hack/doc-site/hugo/README.md @@ -0,0 +1,62 @@ +# Hugo documentation site + +This directory holds the Hugo configuration that builds +. + +## Layout + +```text +hugo/ +├── hugo.yaml # Static Hugo config +├── examples.yaml.template # Build-time config template (version info) +├── examples.yaml # Generated from the template (git-ignored output) +├── gendoc.go # Local development helper (`go run gendoc.go`) +├── themes/ +│ ├── hugo-relearn/ # Relearn theme (downloaded by CI / dev script) +│ ├── examples-assets/ # Custom logo / SCSS +│ └── examples-static/ # Static branding (favicon, …) +└── layouts/ + ├── shortcodes/ # Custom Hugo shortcodes + └── partials/ # Custom partial templates +``` + +## Content + +Markdown content is mounted from `../../../docs/doc-site/` via the +`module.mounts` block in `hugo.yaml`. Editing those files (or adding new ones) +is enough — no codegen or generator is involved. + +## Local preview + +```sh +go run gendoc.go +``` + +The script: + +1. Extracts version info from git tags and the root `go.mod` +2. Renders `examples.yaml` from `examples.yaml.template` +3. Starts `hugo server` on with live reload + +Requires `hugo` (extended, ≥ v0.150) and `git` on `PATH`. + +## Configuration + +Two-layer config, mirroring the pattern used by other go-openapi doc sites: + +1. **`hugo.yaml`** — static configuration (theme, mounts, menu, params) +2. **`examples.yaml`** — dynamic configuration (Go version, latest release tag, + build timestamp), generated from `examples.yaml.template` + +Both files are passed together via `--config hugo.yaml,examples.yaml`. The +dynamic values land under `params.examples.*` and are referenced from the +markdown content. + +## Deployment + +GitHub Actions workflow `.github/workflows/update-doc.yml`: + +- Builds on every push to `master` and on tags `v*` that touch `docs/**`, + `hack/doc-site/**`, or the workflow itself +- Publishes the rendered site to GitHub Pages + () diff --git a/hack/doc-site/hugo/examples.yaml.template b/hack/doc-site/hugo/examples.yaml.template new file mode 100644 index 00000000..da74ae97 --- /dev/null +++ b/hack/doc-site/hugo/examples.yaml.template @@ -0,0 +1,16 @@ +# Dynamic configuration generated at build time +# This file provides version information extracted from the repository + +params: + runtime: + # Go version requirement (from go.mod) + goVersion: '{{ GO_VERSION }}' + + # Latest release tag (from git tags) + latestRelease: '{{ LATEST_RELEASE }}' + + # Version message for the documentation set + versionMessage: '{{ VERSION_MESSAGE }}' + + # Build timestamp + buildTime: '{{ BUILD_TIME }}' diff --git a/hack/doc-site/hugo/gendoc.go b/hack/doc-site/hugo/gendoc.go new file mode 100644 index 00000000..ccd02072 --- /dev/null +++ b/hack/doc-site/hugo/gendoc.go @@ -0,0 +1,140 @@ +//go:build ignore + +// Local development script for Hugo documentation. +// +// Usage: go run gendoc.go +// +// Requires: +// * hugo +// * git +package main + +import ( + "bufio" + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "strings" + "time" +) + +//nolint:forbidigo,dogsled +func main() { + ctx := context.Background() + + // Change to the directory containing this script. + _, thisFile, _, _ := runtime.Caller(0) + scriptDir := filepath.Dir(thisFile) + if err := os.Chdir(scriptDir); err != nil { + fatalf("chdir: %v", err) + } + + fmt.Println("==> Preparing Hugo documentation site...") + + latestRelease := gitLatestRelease(ctx) + requiredGoVersion := goVersionFromMod() + buildTime := time.Now().UTC().Format("2006-01-02T15:04:05Z") + versionMessage := "Documentation test for latest master" + + fmt.Printf(" Latest release: %s\n", latestRelease) + fmt.Printf(" Go version: %s\n", requiredGoVersion) + fmt.Printf(" Build time: %s\n", buildTime) + + // Generate dynamic config from template. + generateRuntimeYAML(requiredGoVersion, latestRelease, versionMessage, buildTime) + fmt.Println("==> Generated examples.yaml") + + // Check if theme exists. + if _, err := os.Stat("themes/hugo-relearn"); os.IsNotExist(err) { + fatalf("Relearn theme not found at themes/hugo-relearn\n" + + "Run: unzip hugo-theme-relearn-main.zip -d themes/ && mv themes/hugo-theme-relearn-main themes/hugo-relearn") + } + + // Check if generated docs exist. + if _, err := os.Stat("../../../docs/doc-site"); os.IsNotExist(err) { + fmt.Println("WARNING: Generated docs not found at ../../../docs/doc-site") + fmt.Println("You may need to run: go generate ./...") + fmt.Println() + fmt.Println("Creating placeholder content directory...") + os.MkdirAll("content", 0o755) //nolint:errcheck,mnd + } + + fmt.Println("==> Starting Hugo development server...") + fmt.Println(" Visit: http://localhost:1313/examples/") + fmt.Println() + + // Start Hugo server with both configs. + cmd := exec.CommandContext(ctx, "hugo", "server", + "--config", "hugo.yaml,examples.yaml", + "--buildDrafts", + "--disableFastRender", + "--navigateToChanged", + "--bind", "0.0.0.0", + "--port", "1313", + "--baseURL", "http://localhost:1313/examples/", + "--appendPort=false", + "--logLevel", "info", + "--cleanDestinationDir", + ) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + + if err := cmd.Run(); err != nil { + fatalf("hugo: %v", err) + } +} + +// gitLatestRelease returns the latest semver tag, or "dev" if none found. +func gitLatestRelease(ctx context.Context) string { + out, err := exec.CommandContext(ctx, "git", "tag", "--list", "--sort", "-version:refname", "v*").Output() + if err != nil || len(out) == 0 { + return "dev" + } + sc := bufio.NewScanner(strings.NewReader(string(out))) + if sc.Scan() { + return strings.TrimSpace(sc.Text()) + } + return "dev" +} + +// goVersionFromMod extracts the go version from the root go.mod. +func goVersionFromMod() string { + data, err := os.ReadFile("../../../go.mod") + if err != nil { + fatalf("reading go.mod: %v", err) + } + re := regexp.MustCompile(`(?m)^go\s+(\S+)`) + m := re.FindSubmatch(data) + if m == nil { + fatalf("could not find go version in go.mod") + } + return string(m[1]) +} + +// generateRuntimeYAML reads the template and writes examples.yaml with substitutions. +func generateRuntimeYAML(goVersion, latestRelease, versionMessage, buildTime string) { + tmpl, err := os.ReadFile("examples.yaml.template") + if err != nil { + fatalf("reading template: %v", err) + } + + out := string(tmpl) + out = strings.ReplaceAll(out, "{{ GO_VERSION }}", goVersion) + out = strings.ReplaceAll(out, "{{ LATEST_RELEASE }}", latestRelease) + out = strings.ReplaceAll(out, "{{ VERSION_MESSAGE }}", versionMessage) + out = strings.ReplaceAll(out, "{{ BUILD_TIME }}", buildTime) + + if err := os.WriteFile("examples.yaml", []byte(out), 0o600); err != nil { //nolint:mnd + fatalf("writing examples.yaml: %v", err) + } +} + +func fatalf(format string, args ...any) { + fmt.Fprintf(os.Stderr, "ERROR: "+format+"\n", args...) + os.Exit(1) +} diff --git a/hack/doc-site/hugo/hugo.yaml b/hack/doc-site/hugo/hugo.yaml new file mode 100644 index 00000000..7ec7b942 --- /dev/null +++ b/hack/doc-site/hugo/hugo.yaml @@ -0,0 +1,163 @@ +baseURL: https://go-swagger.github.io/examples/ +title: go-swagger examples +theme: hugo-relearn +languageCode: en-us + +# Enable features +enableEmoji: true +enableGitInfo: true +enableRobotsTXT: true + +# Content configuration +contentDir: content + +# Output formats +outputs: + home: + - html + - rss + - print + +# Markup configuration +markup: + goldmark: + renderer: + unsafe: true # Allow raw HTML in markdown + parser: + attribute: + title: true + block: true + renderHooks: + image: + useEmbedded: always + highlight: + codeFences: true + guessSyntax: false + lineNos: false + lineNumbersInTable: false + noClasses: true + style: monokai + tabWidth: 4 + +# Taxonomies are disabled for now +disableKinds: + - taxonomy + - term +# Module mounts +# Following go-swagger pattern: simple mounts for content and assets +module: + mounts: + # Custom layouts (override theme if needed) + - source: layouts + target: layouts + + # Generated documentation content + - source: '../../../docs/doc-site' + target: content + + # Real, committed example projects surfaced via the `code` shortcode. + # Unlike sibling sites, this repo *is* the examples: we mount the project + # root so pages embed the actual generated/hand-written source. The mounted + # path is root-relative, so it matches the file's true path on GitHub — the + # `code` shortcode's source link uses `examplesSourcePrefix` (empty here). + # includeFiles/excludeFiles are the real Hugo mount keys; excludeFiles keeps + # worktrees, the doc-site tooling and VCS metadata out of the asset graph. + - source: '../../..' + target: assets/examples + includeFiles: + - '**/*.go' + - '**/*.json' + - '**/*.yaml' + - '**/*.yml' + excludeFiles: + - '.git/**' + - '.worktrees/**' + - 'hack/**' + - 'docs/**' + - '**/vendor/**' + + # Custom SCSS (for theme customization) + logo + - source: themes/examples-assets + target: assets + + # Custom static files (favicon) + - source: themes/examples-static + target: static + +# Relearn theme parameters +params: + editURL: 'https://github.com/go-swagger/examples/edit/master/' + externalLinkTarget: _blank + + # Repository info + sourceRepository: 'https://github.com/go-swagger/examples' + + # Path prefix prepended to a `code` shortcode `file` when building the GitHub + # "Full source" link. Runtime keeps examples under docs/examples/; here the + # examples ARE the repo, mounted from the root, so the prefix is empty. + examplesSourcePrefix: '' + + # Branding + author: + name: 'go-openapi maintainers' + hideAuthorEmail: true + + # Theme customization + themeVariant: + - zen-dark + - relearn-dark + - relearn-light + showVisitedLinks: true + collapsibleMenu: true + disableBreadcrumb: false + disableNextPrev: false + disableLandingPageButton: true + titleSeparator: '|' + + # Features + search: + disable: false + index: + disable: false + page: + disable: false + adapter: + identifier: lunr + disableLanguageSwitchingButton: true + disableInlineCopyToClipBoard: false + + # Menu ordering + ordersectionsby: 'weight' + # Proposal for enhancement: configure versions + #versions: + #- baseURL: https://go-swagger.github.io/examples/ + # identifier: v2.1.0 + # isLatest: true + #version: 'v2.1.0' + + # Custom params + examples: + goVersion: '' + latestRelease: '' + versionMessage: '' + buildTime: '' + +# Menu configuration +menu: + shortcuts: + - name: "GitHub" + identifier: github + url: "https://github.com/go-swagger/examples" + weight: 10 + + - name: "go-openapi toolkit" + identifier: go-openapi + url: "https://github.com/go-openapi" + weight: 20 + +# Privacy settings +privacy: + disqus: + disable: true + googleAnalytics: + disable: true diff --git a/hack/doc-site/hugo/layouts/partials/.gitkeep b/hack/doc-site/hugo/layouts/partials/.gitkeep new file mode 100644 index 00000000..78022112 --- /dev/null +++ b/hack/doc-site/hugo/layouts/partials/.gitkeep @@ -0,0 +1,2 @@ +# Custom partials directory +# Override theme partials here if needed diff --git a/hack/doc-site/hugo/layouts/partials/bodys/single.html b/hack/doc-site/hugo/layouts/partials/bodys/single.html new file mode 100644 index 00000000..50a81a64 --- /dev/null +++ b/hack/doc-site/hugo/layouts/partials/bodys/single.html @@ -0,0 +1,7 @@ +{{- .Store.Set "relearnIsNested" false }} +{{- if gt .ReadingTime 1 }} +
+📖 {{ .ReadingTime }} min read (~ {{ .FuzzyWordCount }} words). +
+{{- end }} +{{- .Render "article" }} diff --git a/hack/doc-site/hugo/layouts/partials/content-footer.html b/hack/doc-site/hugo/layouts/partials/content-footer.html new file mode 100644 index 00000000..1ff3a481 --- /dev/null +++ b/hack/doc-site/hugo/layouts/partials/content-footer.html @@ -0,0 +1,37 @@ +{{- $LastModifierDisplayName := "" }} +{{- $LastModifierEmail := "" }} +{{- $Date := "" }} +{{- $dateFormat := site.Params.dateFormat | default ":date_medium" }} +{{- with .GitInfo }} + {{- with and (not site.Params.hideAuthorName) .AuthorName }} + {{- $LastModifierDisplayName = . }} + {{- end }} + {{- with and (not site.Params.hideAuthorEmail) .AuthorEmail }} + {{- $LastModifierEmail = . }} + {{- end }} + {{- with and (not site.Params.hideAuthorDate) .AuthorDate }} + {{- $Date = . | time.Format $dateFormat }} + {{- end }} +{{- else }} + {{- with and (not site.Params.hideAuthorName) .Params.LastModifierDisplayName }} + {{- $LastModifierDisplayName = . }} + {{- end }} + {{- with and (not site.Params.hideAuthorEmail) .Params.LastModifierEmail }} + {{- $LastModifierEmail = . }} + {{- end }} + {{- with and (not site.Params.hideAuthorDate) .Date }} + {{- $Date = . | time.Format $dateFormat }} + {{- end }} +{{- end }} +{{- if $LastModifierDisplayName }} + Last edited by: {{ with $LastModifierEmail }}{{ end }}{{ $LastModifierDisplayName }}{{ with $LastModifierEmail }}{{ end }} +{{- end }} +{{- with $Date }} + {{ . }} +{{- end }} +
Copyright 2015-2025 go-openapi maintainers. This documentation is under an Apache 2.0 license. +{{- partial "term-list.html" (dict + "page" . + "taxonomy" "categories" + "icon" "layer-group" +) }} diff --git a/hack/doc-site/hugo/layouts/partials/custom-header.html b/hack/doc-site/hugo/layouts/partials/custom-header.html new file mode 100644 index 00000000..50cd9ae3 --- /dev/null +++ b/hack/doc-site/hugo/layouts/partials/custom-header.html @@ -0,0 +1,30 @@ + diff --git a/hack/doc-site/hugo/layouts/shortcodes/.gitkeep b/hack/doc-site/hugo/layouts/shortcodes/.gitkeep new file mode 100644 index 00000000..f4bdeec4 --- /dev/null +++ b/hack/doc-site/hugo/layouts/shortcodes/.gitkeep @@ -0,0 +1,2 @@ +# Custom shortcodes directory +# Add custom Hugo shortcodes here diff --git a/hack/doc-site/hugo/layouts/shortcodes/code.html b/hack/doc-site/hugo/layouts/shortcodes/code.html new file mode 100644 index 00000000..3a63df77 --- /dev/null +++ b/hack/doc-site/hugo/layouts/shortcodes/code.html @@ -0,0 +1,123 @@ +{{- /* + code: include a source file from the examples mount with syntax highlighting, + followed by a "Full source" link back to the file on GitHub. + + Parameters: + file (required) Path relative to docs/examples, e.g. "customcodec/uint32.go". + lang Chroma lexer name. Defaults to "text". + options Chroma options passed through, e.g. "linenos=table,hl_lines=3-5". + lines "N-M" (1-based, inclusive) to show only a slice of the file. + When set, the GitHub source link gets a matching #L{N}-L{M} anchor. + Mutually exclusive with `region`. + region Name of a named region delimited by `snippet:NAME` and + `endsnippet:NAME` markers in the file. The comment prefix is + free: `// snippet:NAME` in Go, `# snippet:NAME` in YAML specs, + etc. — matching is on the `snippet:NAME` substring. Marker + lines are stripped; captured lines are de-indented by the + leading whitespace of the first non-blank captured line. + Mutually exclusive with `lines`. + nolink Set to "true" to suppress the "Full source" footnote. +*/ -}} +{{- $file := .Get "file" -}} +{{- if not $file -}} + {{- errorf "code shortcode at %s: missing required parameter %q" .Position "file" -}} +{{- end -}} +{{- $lang := .Get "lang" | default "text" -}} +{{- $opts := .Get "options" | default "" -}} +{{- $lines := .Get "lines" -}} +{{- $region := .Get "region" -}} +{{- $nolink := eq (.Get "nolink") "true" -}} +{{- if and $lines $region -}} + {{- errorf "code shortcode at %s: %q and %q are mutually exclusive" .Position "lines" "region" -}} +{{- end -}} +{{- $resourcePath := printf "examples/%s" $file -}} +{{- $resource := resources.Get $resourcePath -}} +{{- if not $resource -}} + {{- errorf "code shortcode at %s: file %q not found under assets/examples" .Position $file -}} +{{- else -}} + {{- $content := $resource.Content -}} + {{- $anchor := "" -}} + {{- with $lines -}} + {{- $parts := split . "-" -}} + {{- $startStr := index $parts 0 -}} + {{- $endStr := cond (gt (len $parts) 1) (index $parts 1) $startStr -}} + {{- $start := int $startStr -}} + {{- $end := int $endStr -}} + {{- $all := split $content "\n" -}} + {{- $count := add (sub $end $start) 1 -}} + {{- $content = delimit (first $count (after (sub $start 1) $all)) "\n" -}} + {{- $anchor = printf "#L%s-L%s" $startStr $endStr -}} + {{- end -}} + {{- with $region -}} + {{- /* Comment-prefix-agnostic: `// snippet:x` (Go), `# snippet:x` (YAML) both match. */ -}} + {{- $startMarker := printf "snippet:%s" . -}} + {{- $endMarker := printf "endsnippet:%s" . -}} + {{- $all := split $content "\n" -}} + {{- $captured := slice -}} + {{- $inRegion := false -}} + {{- $startLine := 0 -}} + {{- $endLine := 0 -}} + {{- range $i, $line := $all -}} + {{- if and $inRegion (strings.Contains $line $endMarker) -}} + {{- $inRegion = false -}} + {{- $endLine = add $i 1 -}} + {{- else if and (not $inRegion) (strings.Contains $line $startMarker) (not (strings.Contains $line $endMarker)) -}} + {{- $inRegion = true -}} + {{- $startLine = add $i 2 -}} + {{- else if $inRegion -}} + {{- $captured = $captured | append $line -}} + {{- end -}} + {{- end -}} + {{- if eq (len $captured) 0 -}} + {{- errorf "code shortcode at %s: region %q not found in %q" $.Position . $file -}} + {{- end -}} + {{- /* dedent: leading whitespace of first non-blank captured line */ -}} + {{- $prefix := "" -}} + {{- $found := false -}} + {{- range $captured -}} + {{- if and (not $found) (ne (trim . " \t") "") -}} + {{- $stripped := strings.TrimLeft " \t" . -}} + {{- $prefix = substr . 0 (sub (len .) (len $stripped)) -}} + {{- $found = true -}} + {{- end -}} + {{- end -}} + {{- $dedented := slice -}} + {{- range $captured -}} + {{- $dedented = $dedented | append (strings.TrimPrefix $prefix .) -}} + {{- end -}} + {{- /* drop a leading blank line (common when a blank line separates the snippet marker from a godoc-eligible symbol) */ -}} + {{- if and (gt (len $dedented) 0) (eq (trim (index $dedented 0) " \t") "") -}} + {{- $dedented = after 1 $dedented -}} + {{- end -}} + {{- /* drop a trailing blank line (common when endsnippet sits on its own line) */ -}} + {{- $n := len $dedented -}} + {{- if and (gt $n 0) (eq (trim (index $dedented (sub $n 1)) " \t") "") -}} + {{- $dedented = first (sub $n 1) $dedented -}} + {{- end -}} + {{- $content = delimit $dedented "\n" -}} + {{- $anchor = printf "#L%d-L%d" $startLine $endLine -}} + {{- end -}} + {{- /* strip trailing //nolint:... directives so suppressions in source don't leak into rendered snippets */ -}} + {{- $lineSet := split $content "\n" -}} + {{- $cleaned := slice -}} + {{- range $lineSet -}} + {{- $line := . -}} + {{- $stripped := replaceRE `\s*//\s*nolint:.*$` "" $line -}} + {{- if or (ne $stripped "") (eq (trim $line " \t") "") -}} + {{- $cleaned = $cleaned | append $stripped -}} + {{- end -}} + {{- end -}} + {{- $content = delimit $cleaned "\n" -}} + {{- highlight $content $lang $opts }} + {{- if not $nolink }} + {{- $repo := site.Params.sourceRepository | default "https://github.com/go-openapi/runtime" -}} + {{- /* `default` treats "" as unset, so use isset to allow an explicit empty prefix */ -}} + {{- $prefix := "docs/examples/" -}} + {{- if isset site.Params "examplessourceprefix" -}} + {{- $prefix = site.Params.examplesSourcePrefix -}} + {{- end -}} + {{- $sourceURL := printf "%s/blob/master/%s%s%s" $repo $prefix $file $anchor }} + +

Full source: {{ $prefix }}{{ $file }}

+ {{- end }} +{{- end -}} diff --git a/hack/doc-site/hugo/themes/.gitignore b/hack/doc-site/hugo/themes/.gitignore new file mode 100644 index 00000000..a8a17ed2 --- /dev/null +++ b/hack/doc-site/hugo/themes/.gitignore @@ -0,0 +1 @@ +hugo-relearn diff --git a/hack/doc-site/hugo/themes/examples-assets/colorized.png b/hack/doc-site/hugo/themes/examples-assets/colorized.png new file mode 100644 index 00000000..b5b783cb Binary files /dev/null and b/hack/doc-site/hugo/themes/examples-assets/colorized.png differ diff --git a/hack/doc-site/hugo/themes/examples-assets/images/favicon.png b/hack/doc-site/hugo/themes/examples-assets/images/favicon.png new file mode 100644 index 00000000..32f319f8 Binary files /dev/null and b/hack/doc-site/hugo/themes/examples-assets/images/favicon.png differ diff --git a/hack/doc-site/hugo/themes/examples-assets/logo.png b/hack/doc-site/hugo/themes/examples-assets/logo.png new file mode 100644 index 00000000..528a099f Binary files /dev/null and b/hack/doc-site/hugo/themes/examples-assets/logo.png differ diff --git a/hack/doc-site/hugo/themes/examples-static/github.png b/hack/doc-site/hugo/themes/examples-static/github.png new file mode 100644 index 00000000..fe006d4d Binary files /dev/null and b/hack/doc-site/hugo/themes/examples-static/github.png differ diff --git a/middleware/internal/metrics/metrics.go b/middleware/internal/metrics/metrics.go index b3cecad4..cd36d652 100644 --- a/middleware/internal/metrics/metrics.go +++ b/middleware/internal/metrics/metrics.go @@ -61,6 +61,8 @@ func Mount(next http.Handler) http.Handler { }) } +// snippet:instrument + // Instrument records request count and latency for the wrapped handler. // // It is meant to be installed in the generated server's setupMiddlewares hook @@ -89,6 +91,8 @@ func Instrument(next http.Handler) http.Handler { }) } +// endsnippet:instrument + type statusRecorder struct { http.ResponseWriter diff --git a/middleware/restapi/configure_greeter.go b/middleware/restapi/configure_greeter.go index 1116d1c8..02e66597 100644 --- a/middleware/restapi/configure_greeter.go +++ b/middleware/restapi/configure_greeter.go @@ -65,6 +65,8 @@ func configureServer(server *http.Server, scheme, addr string) { _ = addr } +// snippet:setup-middlewares + // setupMiddlewares wraps the swagger handler after routing. // // At this point, [middleware.MatchedRouteFrom] returns the matched route, so @@ -75,6 +77,10 @@ func setupMiddlewares(handler http.Handler) http.Handler { return metrics.Instrument(handler) } +// endsnippet:setup-middlewares + +// snippet:setup-global + // setupGlobalMiddleware wraps everything the server serves, including the // swagger spec document and the embedded UI. // @@ -108,3 +114,5 @@ func setupGlobalMiddleware(handler http.Handler) http.Handler { return metrics.Mount(sec.Handler(handler)) } + +// endsnippet:setup-global diff --git a/oauth2/restapi/configure_oauth_sample.go b/oauth2/restapi/configure_oauth_sample.go index 5e41fc10..10e04616 100644 --- a/oauth2/restapi/configure_oauth_sample.go +++ b/oauth2/restapi/configure_oauth_sample.go @@ -39,6 +39,7 @@ func configureAPI(api *operations.OauthSampleAPI) http.Handler { api.JSONProducer = runtime.JSONProducer() + // snippet:oauth-auth api.OauthSecurityAuth = func(token string, scopes []string) (*models.Principal, error) { _ = scopes @@ -53,6 +54,7 @@ func configureAPI(api *operations.OauthSampleAPI) http.Handler { return &prin, nil } + // endsnippet:oauth-auth // Set your custom authorizer if needed. Default one is security.Authorized() // Expected interface runtime.Authorizer diff --git a/oauth2/restapi/implementation.go b/oauth2/restapi/implementation.go index 034a20b2..586d03c4 100644 --- a/oauth2/restapi/implementation.go +++ b/oauth2/restapi/implementation.go @@ -55,6 +55,7 @@ var ( } ) +// snippet:login func login(r *http.Request) middleware.Responder { // implements the login with a redirection return middleware.ResponderFunc( @@ -63,6 +64,9 @@ func login(r *http.Request) middleware.Responder { }) } +// endsnippet:login + +// snippet:callback func callback(r *http.Request) (string, error) { // we expect the redirected client to call us back // with 2 query params: state and code. @@ -96,6 +100,8 @@ func callback(r *http.Request) (string, error) { return oauth2Token.AccessToken, nil } +// endsnippet:callback + func authenticated(token string) (bool, error) { // validates the token by sending a request at userInfoURL ctx := context.Background() diff --git a/oauth2/swagger.yml b/oauth2/swagger.yml index 1e6fb1a7..58c02d23 100644 --- a/oauth2/swagger.yml +++ b/oauth2/swagger.yml @@ -9,6 +9,7 @@ produces: schemes: - http basePath: /api +# snippet:security securityDefinitions: OauthSecurity: type: oauth2 @@ -18,6 +19,7 @@ securityDefinitions: scopes: admin: Admin scope user: User scope +# endsnippet:security security: - OauthSecurity: - user diff --git a/stream-client/jigsaw.go b/stream-client/jigsaw.go index 9c6d2fda..d9568608 100644 --- a/stream-client/jigsaw.go +++ b/stream-client/jigsaw.go @@ -20,6 +20,7 @@ import ( "github.com/go-swagger/examples/stream-client/client/operations" ) +// snippet:buffer // Buffer knows how to UnmarshalText type Buffer struct { *bytes.Buffer @@ -31,6 +32,8 @@ func (b *Buffer) UnmarshalText(text []byte) error { return err } +// endsnippet:buffer + // NewBuffer creates a new buffer that knows how to unmarshal text/plain func NewBuffer() *Buffer { return &Buffer{ @@ -84,6 +87,7 @@ func customTransport(withChunks bool) *httptransport.Runtime { } // chunkedBlocking consumes some text/plain resource, blocking for the response to be completely sent +// snippet:blocking func chunkedBlocking(withChunks bool) error { c := client.New(customTransport(withChunks), nil).Operations @@ -103,6 +107,8 @@ func chunkedBlocking(withChunks bool) error { return nil } +// endsnippet:blocking + // chunkedNonBlocking consumes some text/plain resource asynchronously func chunkedNonBlocking(withChunks bool) error { transport := customTransport(withChunks) diff --git a/stream-server/biz/count.go b/stream-server/biz/count.go index a87d40d7..719ed5ed 100644 --- a/stream-server/biz/count.go +++ b/stream-server/biz/count.go @@ -13,6 +13,8 @@ import ( // MyCounter is the concrete implementation. type MyCounter struct{} +// snippet:producer + // Down is the concrete implementation that spits out the JSON bodies. func (mc *MyCounter) Down(maximum int64, w io.Writer) error { if maximum == 11 { @@ -33,3 +35,5 @@ func (mc *MyCounter) Down(maximum int64, w io.Writer) error { return nil } + +// endsnippet:producer diff --git a/stream-server/elapsed_client.go b/stream-server/elapsed_client.go index a60b49f1..2c1b206a 100644 --- a/stream-server/elapsed_client.go +++ b/stream-server/elapsed_client.go @@ -39,6 +39,7 @@ func main() { } func ask(n int64) error { + // snippet:consumer customized := httptransport.New("localhost:8000", "/", []string{"http"}) customized.Consumers[runtime.JSONMime] = runtime.ByteStreamConsumer() @@ -47,12 +48,14 @@ func ask(n int64) error { reader, writer := io.Pipe() scanner := bufio.NewScanner(reader) + // endsnippet:consumer ctx, cancel := context.WithCancel(context.Background()) // consumes asynchronously the response buffer var wg sync.WaitGroup + // snippet:scan wg.Add(1) go func(wg *sync.WaitGroup) { defer wg.Done() @@ -81,13 +84,16 @@ func ask(n int64) error { log.Println("EOF") }(&wg) + // endsnippet:scan queryCtx, timedOut := context.WithTimeout(ctx, 7*time.Second) defer timedOut() + // snippet:request elapsed := operations.NewElapseParamsWithContext(queryCtx).WithLength(n) _, err := countdowns.Operations.Elapse(elapsed, writer) + // endsnippet:request if err == nil { log.Printf("response complete") diff --git a/stream-server/restapi/configure_countdown.go b/stream-server/restapi/configure_countdown.go index ea407276..eb23d18c 100644 --- a/stream-server/restapi/configure_countdown.go +++ b/stream-server/restapi/configure_countdown.go @@ -38,6 +38,7 @@ func configureAPI(api *operations.CountdownAPI) http.Handler { api.JSONProducer = runtime.JSONProducer() myCounter := &biz.MyCounter{} + // snippet:handler api.ElapseHandler = operations.ElapseHandlerFunc(func(params operations.ElapseParams) middleware.Responder { if params.Length == 11 { return operations.NewElapseForbidden() @@ -49,12 +50,14 @@ func configureAPI(api *operations.CountdownAPI) http.Handler { _ = myCounter.Down(params.Length, &flushWriter{f: f, w: rw}) }) }) + // endsnippet:handler api.ServerShutdown = func() {} return setupGlobalMiddleware(api.Serve(setupMiddlewares)) } +// snippet:flush-writer // Via https://play.golang.org/p/PpbPyXbtEs type flushWriter struct { f http.Flusher @@ -70,6 +73,8 @@ func (fw *flushWriter) Write(p []byte) (n int, err error) { return } +// endsnippet:flush-writer + // The TLS configuration before HTTPS server starts. func configureTLS(tlsConfig *tls.Config) { // Make all necessary changes to the TLS configuration here. diff --git a/stream-server/swagger.yml b/stream-server/swagger.yml index 79a38850..fa768cbf 100644 --- a/stream-server/swagger.yml +++ b/stream-server/swagger.yml @@ -25,6 +25,7 @@ type: integer minimum: 2 maximum: 30 + # snippet:streaming-response responses: 200: description: Secondly update on remaining time @@ -37,6 +38,7 @@ format: binary 403: description: Contrived - thrown when length of 11 is chosen + # endsnippet:streaming-response definitions: # Notice this is never directly used/referenced anywhere else in the # Swagger document. However, the generated `models` object is used in diff --git a/task-tracker/restapi/configure_task_tracker.go b/task-tracker/restapi/configure_task_tracker.go index 4304c759..c923e484 100644 --- a/task-tracker/restapi/configure_task_tracker.go +++ b/task-tracker/restapi/configure_task_tracker.go @@ -40,6 +40,7 @@ func configureAPI(api *operations.TaskTrackerAPI) http.Handler { api.JSONProducer = runtime.JSONProducer() + // snippet:auth // Applies when the "token" query is set if api.APIKeyAuth == nil { api.APIKeyAuth = func(token string) (any, error) { @@ -56,6 +57,7 @@ func configureAPI(api *operations.TaskTrackerAPI) http.Handler { return nil, errors.NotImplemented("api key auth (token_header) X-Token from header param [X-Token] has not yet been implemented") } } + // endsnippet:auth // Set your custom authorizer if needed. Default one is security.Authorized() // Expected interface runtime.Authorizer diff --git a/task-tracker/swagger.yml b/task-tracker/swagger.yml index 629a3ad1..17dbbf02 100644 --- a/task-tracker/swagger.yml +++ b/task-tracker/swagger.yml @@ -10,6 +10,7 @@ consumes: - application/vnd.goswagger.examples.task-tracker.v1+json securityDefinitions: + # snippet:security api_key: type: apiKey name: token @@ -18,6 +19,7 @@ securityDefinitions: type: apiKey name: X-Token in: header + # endsnippet:security info: version: "1.0.0" diff --git a/todo-list-errors/restapi/configure_todo_list.go b/todo-list-errors/restapi/configure_todo_list.go index b9afa990..09d0b0b0 100644 --- a/todo-list-errors/restapi/configure_todo_list.go +++ b/todo-list-errors/restapi/configure_todo_list.go @@ -23,6 +23,7 @@ func configureFlags(api *operations.TodoListAPI) { _ = api } +// snippet:catcher func catcher(w http.ResponseWriter, r *http.Request, err error) { if errors.Is(err, errAlreadyExists) { slog.Info("we catch custom error! congratulations!") @@ -34,6 +35,8 @@ func catcher(w http.ResponseWriter, r *http.Request, err error) { //nolint:gochecknoglobals var errAlreadyExists = errors.New("already exists") +// endsnippet:catcher + func configureAPI(api *operations.TodoListAPI) http.Handler { // configure the api here api.ServeError = catcher @@ -54,12 +57,14 @@ func configureAPI(api *operations.TodoListAPI) http.Handler { // You may change here the memory limit for this multipart form parser. Below is the default (32 MB). // todos.FindMaxParseMemory = 32 << 20 + // snippet:handler api.TodosAddOneHandler = todos.AddOneHandlerFunc( func(params todos.AddOneParams) (middleware.Responder, error) { _ = params return nil, errAlreadyExists }) + // endsnippet:handler api.PreServerShutdown = func() {} api.ServerShutdown = func() {} diff --git a/todo-list-strict/restapi/configure_simple_to_do_list_api.go b/todo-list-strict/restapi/configure_simple_to_do_list_api.go index 52c6cefa..3a63c014 100644 --- a/todo-list-strict/restapi/configure_simple_to_do_list_api.go +++ b/todo-list-strict/restapi/configure_simple_to_do_list_api.go @@ -55,6 +55,7 @@ func configureAPI(api *operations.SimpleToDoListAPIAPI) http.Handler { // You may change here the memory limit for this multipart form parser. Below is the default (32 MB). // todos.FindMaxParseMemory = 32 << 20 + // snippet:strict-handler if api.TodosAddOneHandler == nil { api.TodosAddOneHandler = todos.AddOneHandlerFunc(func(params todos.AddOneParams, principal any) todos.AddOneResponder { _ = params @@ -63,6 +64,7 @@ func configureAPI(api *operations.SimpleToDoListAPIAPI) http.Handler { return todos.AddOneNotImplemented() }) } + // endsnippet:strict-handler if api.TodosDestroyOneHandler == nil { api.TodosDestroyOneHandler = todos.DestroyOneHandlerFunc(func(params todos.DestroyOneParams, principal any) todos.DestroyOneResponder { _ = params diff --git a/todo-list/swagger.yml b/todo-list/swagger.yml index 6ee7af65..bf6a6323 100644 --- a/todo-list/swagger.yml +++ b/todo-list/swagger.yml @@ -122,6 +122,7 @@ paths: schema: $ref: "#/definitions/error" definitions: + # snippet:definitions item: type: object required: @@ -146,3 +147,4 @@ definitions: format: int64 message: type: string + # endsnippet:definitions diff --git a/tutorials/client/swagger.yml b/tutorials/client/swagger.yml index 8f04d3ff..b9fc618d 100644 --- a/tutorials/client/swagger.yml +++ b/tutorials/client/swagger.yml @@ -18,6 +18,7 @@ info: name: go-openapi maintainers email: nowhere@example.com url: https://github.com/go-openapi +# snippet:security securityDefinitions: key: type: apiKey @@ -25,6 +26,7 @@ securityDefinitions: name: x-todolist-token security: - key: [] +# endsnippet:security consumes: - application/io.swagger.examples.todo-list.v1+json produces: diff --git a/tutorials/custom-server/cmd/greeter/main.go b/tutorials/custom-server/cmd/greeter/main.go index ea9a7639..11e2c8f9 100644 --- a/tutorials/custom-server/cmd/greeter/main.go +++ b/tutorials/custom-server/cmd/greeter/main.go @@ -22,6 +22,7 @@ func main() { } func serve() error { + // snippet:wiring // load embedded swagger file swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "") if err != nil { @@ -31,6 +32,7 @@ func serve() error { // create new service API api := operations.NewGreeterAPI(swaggerSpec) server := restapi.NewServer(api) + // endsnippet:wiring defer func() { _ = server.Shutdown() }() @@ -40,6 +42,7 @@ func serve() error { // set the port this service will be run on server.Port = *portFlag + // snippet:handler // GetGreetingHandler greets the given name, // in case the name is not given, it will default to World api.GetGreetingHandler = operations.GetGreetingHandlerFunc( @@ -52,6 +55,7 @@ func serve() error { greeting := fmt.Sprintf("Hello, %s!", name) return operations.NewGetGreetingOK().WithPayload(greeting) }) + // endsnippet:handler // serve API return server.Serve() diff --git a/tutorials/todo-list/server-complete/restapi/configure_todo_list.go b/tutorials/todo-list/server-complete/restapi/configure_todo_list.go index 2986594d..1f50018f 100644 --- a/tutorials/todo-list/server-complete/restapi/configure_todo_list.go +++ b/tutorials/todo-list/server-complete/restapi/configure_todo_list.go @@ -121,6 +121,7 @@ func configureAPI(api *operations.TodoListAPI) http.Handler { api.JSONProducer = runtime.JSONProducer() + // snippet:handlers api.TodosAddOneHandler = todos.AddOneHandlerFunc(func(params todos.AddOneParams) middleware.Responder { if err := addItem(params.Body); err != nil { return todos.NewAddOneDefault(500).WithPayload(&models.Error{Code: 500, Message: conv.Pointer(err.Error())}) @@ -133,6 +134,7 @@ func configureAPI(api *operations.TodoListAPI) http.Handler { } return todos.NewDestroyOneNoContent() }) + // endsnippet:handlers api.TodosFindTodosHandler = todos.FindTodosHandlerFunc(func(params todos.FindTodosParams) middleware.Responder { mergedParams := todos.NewFindTodosParams() mergedParams.Since = conv.Pointer(int64(0))