Skip to content

Generate types from components/securitySchemes to be used as a key in context.WithValue#1187

Merged
mromaszewicz merged 6 commits into
oapi-codegen:mainfrom
nek023:security-scheme-types
Mar 4, 2026
Merged

Generate types from components/securitySchemes to be used as a key in context.WithValue#1187
mromaszewicz merged 6 commits into
oapi-codegen:mainfrom
nek023:security-scheme-types

Conversation

@nek023
Copy link
Copy Markdown
Contributor

@nek023 nek023 commented Aug 8, 2023

Summary

Generate types from components/securitySchemes to be used as a key in context.WithValue.

Background

For example, prepare the following API schema and configuration.

schemas.yaml

paths:
  /example:
    get:
      summary: Example
      operationId: example
      security:
        - BearerAuth: []

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer

config.yaml

package: schemas
generate:
  chi-server: true
  models: true
output: schemas.gen.go
output-options:
  skip-prune: true

If you run oapi-codegen, the following code is generated.

const (
	BearerAuthScopes = "BearerAuth.Scopes"
)

func (siw *ServerInterfaceWrapper) Example(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()

	ctx = context.WithValue(ctx, BearerAuthScopes, []string{})

	handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		siw.Handler.Example(w, r)
	}))

	for _, middleware := range siw.HandlerMiddlewares {
		handler = middleware(handler)
	}

	handler.ServeHTTP(w, r.WithContext(ctx))
}

The constant BearerAuthScopes is generated as a key for context.WithValue, but its type is a string and there is a risk of collision between packages.

The provided key must be comparable and should not be of type string or any other built-in type to avoid collisions between packages using context. Users of WithValue should define their own types for keys.

https://pkg.go.dev/context#WithValue

If you use staticcheck, it will also warn this.

https://staticcheck.dev/docs/checks/#SA1029

This can be avoided by defining a type as shown below.

type bearerAuthContextKey string

const (
	BearerAuthScopes bearerAuthContextKey = "BearerAuth.Scopes"
)

In this PR, I have implemented to generate types from components/securitySchemes to be used as keys for context.WithValue, as shown above.

Considerations

While the implementation has been done to avoid breaking the existing code as much as possible, if users are directly using the underlying string (in this case, "BearerAuth.Scopes), they will no longer be able to extract values from the context.

However, this is considered to be a rare case.

type contextKey string

const exampleContextKey contextKey = "example"

func main() {
	ctx := context.Background()
	ctx = context.WithValue(ctx, exampleContextKey, "hello")

	val := ctx.Value(exampleContextKey)
	fmt.Println(val) // => hello

	val = ctx.Value("example")
	fmt.Println(val) // => nil
}

Comment thread internal/test/schemas/schemas.gen.go
@jamietanna jamietanna self-requested a review August 9, 2023 13:19
}

// openIdContextKey is the context key for OpenId security scheme
type openIdContextKey string
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would we get any benefits from

Suggested change
type openIdContextKey string
type openIdContextKey = string

I feel this would reduce the limited impact of a breaking change, but not sure if it's worth it for any other reason.

Copy link
Copy Markdown
Contributor Author

@nek023 nek023 Aug 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that approach would avoid a breaking change.
However, by using a type alias, it ends up being the same as using the string type, so it doesn't change the situation.

package main

import (
	"context"
	"fmt"
)

type keyType1 = string // type alias
type keyType2 = string

const key1 keyType1 = "KEY"
const key2 keyType2 = "KEY"

func main() {
	ctx := context.Background()
	ctx = context.WithValue(ctx, key1, "VALUE")

	// key1 conflicts with key2
	fmt.Println(ctx.Value(key1)) // => VALUE
	fmt.Println(ctx.Value(key2)) // => VALUE (want: nil)
}
$ staticcheck ./example.go
example.go:16:31: should not use built-in type string as key for value; define your own type to avoid collisions (SA1029)

@GRbit
Copy link
Copy Markdown

GRbit commented Dec 27, 2024

@jamietanna any updates on this? I would really love seeing this merged. Not like I'm the one who makes decision here, yet I think it's good to go.

@jamietanna jamietanna requested a review from a team as a code owner April 24, 2025 08:05
@jamietanna jamietanna added this to the v2.5.0 milestone Apr 24, 2025
@jamietanna jamietanna added the enhancement New feature or request label Apr 24, 2025
@jamietanna
Copy link
Copy Markdown
Member

jamietanna commented Apr 24, 2025

Will need to double check if #1200 and #836 are related here / can be closed after this

@jamietanna jamietanna modified the milestones: v2.5.0, v2.6.0 May 11, 2025
@mromaszewicz
Copy link
Copy Markdown
Member

You're right, this is an oversight in my original code and we should fix it. I'm pulling this branch and fixing it up for the current state of the code before approving and merging. I think the small risk of breakage (that is easy to fix) is worth having correctness.

@mromaszewicz
Copy link
Copy Markdown
Member

@greptileai

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 4, 2026

Greptile Summary

This PR implements a valuable improvement to address the staticcheck (SA1029) warning by generating a private named type (e.g., bearerAuthContextKey string) for each entry in components/securitySchemes and using that type as the key for corresponding scope constants. This eliminates cross-package key collisions when using context.WithValue and framework Set calls.

The core implementation is sound and consistent: both the Go codegen path (SchemaNameToTypeNameLowercaseFirstCharacter) and the template pipeline (schemaNameToTypeName | lcFirst) derive type names identically. The echo, gin, and iris wrappers correctly apply string() casts where their respective Set methods require a string argument, and fiber's SetUserValue properly accepts the typed constant as-is.

Three minor style issues were found: a doc comment references the wrong function name, an unused template parameter, and no-op parentheses that look like a failed type cast.

Confidence Score: 4/5

  • Safe to merge after addressing three minor style issues; no logic bugs or compilation errors introduced.
  • The implementation is logically correct and the core feature (typed security scheme context keys) works as intended. Type derivation is consistent between codegen and templates. The three issues found are all cleanliness concerns (wrong doc comment, unused parameter, no-op parentheses) that do not affect runtime behavior or prevent compilation.
  • pkg/codegen/utils.go (doc comment), pkg/codegen/codegen.go (unused parameter), pkg/codegen/templates/fiber/fiber-middleware.tmpl (no-op parentheses)

Last reviewed commit: 2791e30

Comment thread pkg/codegen/codegen.go
Comment thread pkg/codegen/templates/gin/gin-wrappers.tmpl Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 4, 2026

Additional Comments (2)

pkg/codegen/templates/iris/iris-middleware.tmpl
Missing string() cast for iris.Context.Set key

iris.Context.Set(key string, value interface{}) requires a string key. After this PR, the constant is a named string type, which is not directly assignable to string. Generated code for iris servers with security schemes will fail to compile.

Add the string() cast:

    ctx.Set(string({{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes), {{toStringArray .Scopes}})

pkg/codegen/templates/echo/v5/echo-wrappers.tmpl
Missing string() cast for echo v5 ctx.Set key

This echo v5 template was not updated alongside echo v1. echo.Context.Set(key string, value interface{}) requires a string key, but the constant is now a named string type. Generated code for echo v5 servers with security schemes will fail to compile.

Apply the same fix as echo v1:

    ctx.Set(string({{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes), {{toStringArray .Scopes}})

The constants template used `sanitizeGoIdentity | lcFirst` to derive the
context key type name, while the type definition in codegen.go used
`LowercaseFirstCharacter(SchemaNameToTypeName(...))`. For security scheme
names containing underscores (e.g. `api_key`, `petstore_auth`), these
produced different results (`api_keyContextKey` vs `apiKeyContextKey`),
causing compilation errors.

Fix by exposing `SchemaNameToTypeName` as a template function and using
it in the constants template so both paths produce the same type name.

All affected generated files have been regenerated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 4, 2026

Additional Comments (2)

pkg/codegen/templates/echo/v5/echo-wrappers.tmpl, line 29
Missing string() cast in echo v5 template

The echo v4 template was correctly updated with string(...), but this echo v5 template was not. Echo v5's ctx.Set(key string, val interface{}) requires a string key. After this PR the constant is now a named type (e.g. bearerAuthContextKey), which is not implicitly assignable to string in Go — this will cause a compile error for any user of the echo v5 server generator.

    ctx.Set(string({{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes), {{toStringArray .Scopes}})

pkg/codegen/templates/iris/iris-middleware.tmpl, line 38
Missing string() cast in iris template

Iris's context.Context.Set(key string, value interface{}) requires a string key. The constant is now a named type after this PR, which is not implicitly assignable to string. This will fail to compile for iris server users, just like the already-noted issue in the gin template.

    ctx.Set(string({{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes), {{toStringArray .Scopes}})

Gin, Iris, and Echo v5 use framework-level string-keyed maps for their
Set() methods, which require a string argument. With the new typed
context key constants, these calls fail to compile without an explicit
string() conversion.

Echo v4 already had this cast. Chi, gorilla, stdhttp, and fiber use
context.WithValue (which accepts any) and are unaffected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mromaszewicz
Copy link
Copy Markdown
Member

@greptileai

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants