Repository: ogen-go/ogen
Pull request #745 (2023-01-12 11:30:20)

feat(gen): vet header names, ignore reserved header parameters


Patch:
diff --git a/gen/gen_headers.go b/gen/gen_headers.go
index 19d71b34..7b625331 100644
--- a/gen/gen_headers.go
+++ b/gen/gen_headers.go
@@ -1,8 +1,6 @@
 package gen
 
 import (
-	"net/http"
-
 	"github.com/ogen-go/ogen/gen/ir"
 	"github.com/ogen-go/ogen/openapi"
 )
@@ -18,11 +16,7 @@ func (g *Generator) generateHeaders(
 
 	result := make(map[string]*ir.Parameter, len(headers))
 	for hname, header := range headers {
-		if http.CanonicalHeaderKey(hname) == "Content-Type" {
-			g.log.Warn(
-				"Content-Type is described separately and will be ignored in this section.",
-				zapPosition(header),
-			)
+		if vetHeaderParameterName(g.log, hname, header, "Content-Type") {
 			continue
 		}
 
diff --git a/gen/gen_parameters.go b/gen/gen_parameters.go
index 8e4d5d62..2fa0c70b 100644
--- a/gen/gen_parameters.go
+++ b/gen/gen_parameters.go
@@ -1,16 +1,49 @@
 package gen
 
 import (
+	"fmt"
+	"net/http"
+
 	"github.com/go-faster/errors"
+	"go.uber.org/zap"
 
 	"github.com/ogen-go/ogen/gen/ir"
 	"github.com/ogen-go/ogen/jsonschema"
 	"github.com/ogen-go/ogen/openapi"
 )
 
+func vetHeaderParameterName(log *zap.Logger, name string, loc position, ignore ...string) (skip bool) {
+	canonical := http.CanonicalHeaderKey(name)
+	if canonical != name {
+		log.Warn(
+			"Header name is not canonical, canonical name will be used",
+			zapPosition(loc),
+			zap.String("original_name", name),
+			zap.String("canonical_name", canonical),
+		)
+	}
+	for _, ign := range ignore {
+		if ign == canonical {
+			skip = true
+			log.Warn(
+				fmt.Sprintf("%s is described separately and will be ignored in this section", ign),
+				zapPosition(loc),
+			)
+			break
+		}
+	}
+	return skip
+}
+
 func (g *Generator) generateParameters(ctx *genctx, opName string, params []*openapi.Parameter) (_ []*ir.Parameter, err error) {
 	result := make([]*ir.Parameter, 0, len(params))
 	for _, p := range params {
+		if p.In == openapi.LocationHeader {
+			if vetHeaderParameterName(g.log, p.Name, p, "Content-Type", "Accept", "Authorization") {
+				continue
+			}
+		}
+
 		param, err := g.generateParameter(ctx, opName, p)
 		if err != nil {
 			if err := g.trySkip(err, "Skipping parameter", p); err != nil {


Test patch:
diff --git a/gen/gen_parameters_test.go b/gen/gen_parameters_test.go
new file mode 100644
index 00000000..498ff1f6
--- /dev/null
+++ b/gen/gen_parameters_test.go
@@ -0,0 +1,42 @@
+package gen
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+	"go.uber.org/zap/zaptest"
+	"go.uber.org/zap/zaptest/observer"
+
+	"github.com/ogen-go/ogen/openapi"
+)
+
+func Test_vetHeaderParameterName(t *testing.T) {
+	observerCore, logs := observer.New(zap.WarnLevel)
+	logger := zaptest.NewLogger(t).WithOptions(
+		zap.WrapCore(func(orig zapcore.Core) zapcore.Core {
+			return zapcore.NewTee(orig, observerCore)
+		}),
+	)
+
+	a := require.New(t)
+	{
+		vetHeaderParameterName(logger, "content-type", &openapi.Header{}, "Content-Type")
+		l := logs.TakeAll()
+		a.Len(l, 2)
+		a.Equal("Header name is not canonical, canonical name will be used", l[0].Message)
+		a.Equal("Content-Type is described separately and will be ignored in this section", l[1].Message)
+	}
+	{
+		vetHeaderParameterName(logger, "x-foo-bar", &openapi.Header{}, "Content-Type")
+		l := logs.TakeAll()
+		a.Len(l, 1)
+
+		row := l[0]
+		a.Equal("Header name is not canonical, canonical name will be used", row.Message)
+		keys := row.ContextMap()
+		a.Equal("x-foo-bar", keys["original_name"])
+		a.Equal("X-Foo-Bar", keys["canonical_name"])
+	}
+}