Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkg/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) {
if err := ValidateSpec(spec); err != nil {
return "", err
}
if opts.Generate.StdHTTPServer {
if err := ValidateStdHTTPPaths(spec); err != nil {
return "", err
}
}
Comment on lines +181 to +185

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Filtered Paths Still Fail

When std-http-server is enabled with tag or operation-id filters, those filters can remove every operation under a mixed segment while leaving the path key in spec.Paths. This check still validates that unused path and returns an error, so a route that would never be emitted can block generation.

Knowledge Base Used: Codegen Pipeline

Prompt To Fix With AI
This is a comment left during a code review.
Path: pkg/codegen/codegen.go
Line: 181-185

Comment:
**Filtered Paths Still Fail**

When `std-http-server` is enabled with tag or operation-id filters, those filters can remove every operation under a mixed segment while leaving the path key in `spec.Paths`. This check still validates that unused path and returns an error, so a route that would never be emitted can block generation.

**Knowledge Base Used:** [Codegen Pipeline](https://app.greptile.com/oapi-codegen/-/custom-context/knowledge-base/oapi-codegen/oapi-codegen/-/docs/codegen-pipeline.md)

How can I resolve this? If you propose a fix, please make it concise.


// if we are provided an override for the response type suffix update it
if opts.OutputOptions.ResponseTypeSuffix != "" {
Expand Down
37 changes: 37 additions & 0 deletions pkg/codegen/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package codegen

import (
"bytes"
"errors"
"cmp"
"fmt"
"go/token"
Expand Down Expand Up @@ -682,6 +683,42 @@ func SwaggerUriToStdHttpUri(uri string) string {
return uri
}

// ValidateStdHTTPPath reports whether an OpenAPI path can be registered with
// net/http ServeMux. ServeMux wildcards must occupy an entire path segment, so
// mixed segments such as "{resourceId}:apply" are rejected at codegen time
// instead of panicking at server startup (see #2488).
func ValidateStdHTTPPath(path string) error {
for _, seg := range strings.Split(path, "/") {
if seg == "" || seg == "{$}" {
continue
}
loc := pathParamRE.FindStringIndex(seg)
if loc == nil {
// pure literal segment
continue
}
// Wildcard must be the entire segment.
if loc[0] != 0 || loc[1] != len(seg) {
return fmt.Errorf("path %q: segment %q mixes a path parameter with literal text; net/http ServeMux requires wildcards to occupy an entire path segment (std-http-server)", path, seg)
}
}
return nil
}

// ValidateStdHTTPPaths validates every path in the document for ServeMux.
func ValidateStdHTTPPaths(spec *openapi3.T) error {
if spec == nil || spec.Paths == nil {
return nil
}
var errs []error
for _, path := range SortedMapKeys(spec.Paths.Map()) {
if err := ValidateStdHTTPPath(path); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}

// OrderedParamsFromUri returns the argument names, in order, in a given URI string, so for
// /path/{param1}/{.param2*}/{?param3}, it would return param1, param2, param3
func OrderedParamsFromUri(uri string) []string {
Expand Down
59 changes: 59 additions & 0 deletions pkg/codegen/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -837,3 +837,62 @@ func Test_replaceInitialism(t *testing.T) {
})
}
}


func TestValidateStdHTTPPath(t *testing.T) {
tests := []struct {
path string
wantErr bool
}{
{"/resources/{resourceId}", false},
{"/resources/{resourceId}/apply", false},
{"/resources/{resourceId}:apply", true},
{"/resources/prefix{resourceId}", true},
{"/resources/{resourceId}suffix", true},
{"/pets:validate", false}, // pure literal segment with colon is fine for ServeMux
{"/path/{arg1}/{arg2}/foo", false},
}
for _, tt := range tests {
err := ValidateStdHTTPPath(tt.path)
if tt.wantErr && err == nil {
t.Errorf("path %q: expected error", tt.path)
}
if !tt.wantErr && err != nil {
t.Errorf("path %q: unexpected error: %v", tt.path, err)
}
}
}

func TestGenerateRejectsMixedServeMuxPathParam(t *testing.T) {
spec := `openapi: 3.0.3
info:
title: mixed path param
version: 1.0.0
paths:
/resources/{resourceId}:apply:
post:
operationId: applyResource
parameters:
- name: resourceId
in: path
required: true
schema:
type: string
responses:
"204":
description: Applied
`
loader := openapi3.NewLoader()
swagger, err := loader.LoadFromData([]byte(spec))
require.NoError(t, err)

_, err = Generate(swagger, Configuration{
PackageName: "api",
Generate: GenerateOptions{
StdHTTPServer: true,
Models: true,
},
})
require.Error(t, err)
require.Contains(t, err.Error(), "mixes a path parameter")
}