Skip to content
Closed
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
29 changes: 29 additions & 0 deletions fizz.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,35 @@ func XInternal() func(*openapi.OperationInfo) {
}
}

// PathParam adds an extra path parameter to the operation.
// This is useful when the path parameter is part of a parent route
// and not defined in the handler's input model.
// For example, when routes are defined under /v1/:contract_id/...,
// the contract_id parameter needs to be declared in each operation.
func PathParam(name, description string) func(*openapi.OperationInfo) {
return func(o *openapi.OperationInfo) {
o.ExtraParams = append(o.ExtraParams, &openapi.ExtraParam{
Name: name,
In: "path",
Description: description,
Required: true,
})
}
}

// QueryParam adds an extra query parameter to the operation.
// This is useful for query parameters not defined in the handler's input model.
func QueryParam(name, description string, required bool) func(*openapi.OperationInfo) {
return func(o *openapi.OperationInfo) {
o.ExtraParams = append(o.ExtraParams, &openapi.ExtraParam{
Name: name,
In: "query",
Description: description,
Required: required,
})
}
}

// OperationFromContext returns the OpenAPI operation from
// the given Gin context or an error if none is found.
func OperationFromContext(ctx context.Context) (*openapi.Operation, error) {
Expand Down
59 changes: 52 additions & 7 deletions openapi/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,14 +275,25 @@ func (g *Generator) AddOperation(path, method, tag string, in, out reflect.Type,
method != http.MethodHead &&
method != http.MethodDelete

// Collect extra params from info (if any)
var extraParams []*ExtraParam
if info != nil {
extraParams = info.ExtraParams
}

if in != nil {
if in.Kind() == reflect.Ptr {
in = in.Elem()
}
if in.Kind() != reflect.Struct {
return nil, errors.New("input type is not a struct")
}
if err := g.setOperationParams(op, in, in, allowBody, path); err != nil {
if err := g.setOperationParams(op, in, in, allowBody, path, extraParams); err != nil {
return nil, err
}
} else if len(extraParams) > 0 {
// Even without an input type, we may have extra params to add
if err := g.setOperationParams(op, nil, nil, allowBody, path, extraParams); err != nil {
return nil, err
}
}
Expand Down Expand Up @@ -437,13 +448,47 @@ func (g *Generator) setOperationResponse(op *Operation, t reflect.Type, code, mt
}

// setOperationParams adds the fields of the struct type t
// to the given operation.
func (g *Generator) setOperationParams(op *Operation, t, parent reflect.Type, allowBody bool, path string) error {
if t.Kind() != reflect.Struct {
return errors.New("input type is not a struct")
// to the given operation, along with any extra params provided.
func (g *Generator) setOperationParams(op *Operation, t, parent reflect.Type, allowBody bool, path string, extraParams []*ExtraParam) error {
if t != nil {
if t.Kind() != reflect.Struct {
return errors.New("input type is not a struct")
}
if err := g.buildParamsRecursive(op, t, parent, allowBody); err != nil {
return err
}
}
if err := g.buildParamsRecursive(op, t, parent, allowBody); err != nil {
return err

// Add extra params (e.g., path params from parent routes)
for _, ep := range extraParams {
if ep == nil {
continue
}
// Check if a parameter with same name/location already exists
exists := false
for _, p := range op.Parameters {
if p != nil && p.Name == ep.Name && p.In == ep.In {
exists = true
break
}
}
if exists {
continue // Skip duplicate
}
// Path params are always required
required := ep.Required
if ep.In == "path" {
required = true
}
op.Parameters = append(op.Parameters, &ParameterOrRef{
Parameter: &Parameter{
Name: ep.Name,
In: ep.In,
Description: ep.Description,
Required: required,
Schema: &SchemaOrRef{Schema: &Schema{Type: "string"}},
},
})
}
// Input fields that are neither path- nor query-bound
// have been extracted into the operation's RequestBody
Expand Down
11 changes: 11 additions & 0 deletions openapi/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ type OperationInfo struct {
Security []*SecurityRequirement
XCodeSamples []*XCodeSample
XInternal bool
ExtraParams []*ExtraParam // Additional parameters not derived from InputModel
}

// ExtraParam represents an additional parameter to include in the operation.
// This is useful for path parameters that come from parent routes (e.g., /v1/:contract_id/...)
// where the contract_id is not part of the handler's input model.
type ExtraParam struct {
Name string // Parameter name (e.g., "contract_id")
In string // Parameter location: "path", "query", "header", "cookie"
Description string // Optional description
Required bool // Whether the parameter is required (always true for path params)
}

// ResponseHeader represents a single header that
Expand Down
Loading