Repository: ogen-go/ogen
Pull request #461 (2022-07-07 00:36:35)

feat(parser): pass location via `openapi` structs


Patch:
diff --git a/dsl.go b/dsl.go
index ea92d128..8799b07d 100644
--- a/dsl.go
+++ b/dsl.go
@@ -7,7 +7,6 @@ import (
 	"github.com/go-faster/jx"
 	jsonv2 "github.com/go-json-experiment/json"
 
-	"github.com/ogen-go/ogen/gen/ir"
 	"github.com/ogen-go/ogen/jsonschema"
 	"github.com/ogen-go/ogen/openapi"
 )
@@ -244,7 +243,7 @@ func (r *RequestBody) AddContent(mt string, s *Schema) *RequestBody {
 
 // SetJSONContent sets the given Schema under the JSON MediaType to the Content of the Response.
 func (r *RequestBody) SetJSONContent(s *Schema) *RequestBody {
-	return r.AddContent(string(ir.ContentTypeJSON), s)
+	return r.AddContent("application/json", s)
 }
 
 // initContent ensures the Content map is allocated.
@@ -597,7 +596,7 @@ func (p *Parameter) SetIn(i string) *Parameter {
 	return p
 }
 
-// InPath sets the In of the Parameter to "PathItem".
+// InPath sets the In of the Parameter to "path".
 func (p *Parameter) InPath() *Parameter {
 	return p.SetIn(string(openapi.LocationPath))
 }
@@ -724,7 +723,7 @@ func (r *Response) AddContent(mt string, s *Schema) *Response {
 
 // SetJSONContent sets the given Schema under the JSON MediaType to the Content of the Response.
 func (r *Response) SetJSONContent(s *Schema) *Response {
-	return r.AddContent(string(ir.ContentTypeJSON), s)
+	return r.AddContent("application/json", s)
 }
 
 // initContent ensures the Content map is allocated.
diff --git a/gen/gen_contents.go b/gen/gen_contents.go
index 6dfeacef..26bb4311 100644
--- a/gen/gen_contents.go
+++ b/gen/gen_contents.go
@@ -283,6 +283,7 @@ func (g *Generator) generateContents(
 				}
 
 				g.log.Info(`Content type is unsupported, set "format" to "binary" to use io.Reader`,
+					g.zapLocation(media),
 					zap.String("contentType", contentType),
 				)
 				unsupported = append(unsupported, contentType)
diff --git a/gen/gen_headers.go b/gen/gen_headers.go
index 9c3e8d2c..d4d1eea8 100644
--- a/gen/gen_headers.go
+++ b/gen/gen_headers.go
@@ -4,13 +4,16 @@ import (
 	"net/http"
 
 	"github.com/go-faster/errors"
-	"go.uber.org/zap"
 
 	"github.com/ogen-go/ogen/gen/ir"
 	"github.com/ogen-go/ogen/openapi"
 )
 
-func (g *Generator) generateHeaders(ctx *genctx, name string, headers map[string]*openapi.Header) (_ map[string]*ir.Parameter, err error) {
+func (g *Generator) generateHeaders(
+	ctx *genctx,
+	name string,
+	headers map[string]*openapi.Header,
+) (_ map[string]*ir.Parameter, err error) {
 	if len(headers) == 0 {
 		return nil, nil
 	}
@@ -19,7 +22,10 @@ func (g *Generator) generateHeaders(ctx *genctx, name string, headers map[string
 	for hname, header := range headers {
 		ctx := ctx.appendPath(hname)
 		if http.CanonicalHeaderKey(hname) == "Content-Type" {
-			g.log.Warn("Content-Type is described separately and will be ignored in this section.", zap.String("pointer", ctx.JSONPointer()))
+			g.log.Warn(
+				"Content-Type is described separately and will be ignored in this section.",
+				g.zapLocation(header),
+			)
 			continue
 		}
 
diff --git a/gen/gen_schema.go b/gen/gen_schema.go
index 6026b671..fccbc748 100644
--- a/gen/gen_schema.go
+++ b/gen/gen_schema.go
@@ -39,7 +39,7 @@ func saveSchemaTypes(ctx *genctx, gen *schemaGen) error {
 }
 
 func (g *Generator) generateSchema(ctx *genctx, name string, schema *jsonschema.Schema, optional bool) (*ir.Type, error) {
-	gen := newSchemaGen(ctx.lookupRef)
+	gen := newSchemaGen(g.opt.Filename, ctx.lookupRef)
 	gen.log = g.log.Named("schemagen")
 
 	t, err := gen.generate(name, schema, optional)
@@ -99,7 +99,8 @@ func GenerateSchema(schema *jsonschema.Schema, fs FileSystem, opts GenerateSchem
 		local:   newTStorage(),
 	}
 
-	gen := newSchemaGen(func(ref string) (*ir.Type, bool) {
+	// TODO(tdakkota): pass input filename
+	gen := newSchemaGen("", func(ref string) (*ir.Type, bool) {
 		return nil, false
 	})
 	gen.log = opts.Logger.Named("schemagen")
diff --git a/gen/generator.go b/gen/generator.go
index c58f40a8..a6673e4b 100644
--- a/gen/generator.go
+++ b/gen/generator.go
@@ -138,14 +138,10 @@ func (g *Generator) makeIR(ops []*openapi.Operation) error {
 
 	for _, spec := range ops {
 		routePath := spec.Path.String()
-		log := g.log.With(
-			zap.String("path", routePath),
-			zap.String("method", spec.HTTPMethod),
-			zap.String("operationID", spec.OperationID),
-		)
+		log := g.log.With(g.zapLocation(spec))
 
 		if !g.opt.Filters.accept(spec) {
-			g.log.Info("Skipping filtered operation")
+			log.Info("Skipping filtered operation")
 			continue
 		}
 
diff --git a/gen/panic.go b/gen/panic.go
deleted file mode 100644
index adbad9ae..00000000
--- a/gen/panic.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package gen
-
-import "fmt"
-
-func unreachable(v interface{}) string {
-	return fmt.Sprintf("unreachable: %v", v)
-}
diff --git a/gen/schema_gen.go b/gen/schema_gen.go
index d534d367..450f914b 100644
--- a/gen/schema_gen.go
+++ b/gen/schema_gen.go
@@ -17,10 +17,12 @@ type schemaGen struct {
 	localRefs map[string]*ir.Type
 	lookupRef func(ref string) (*ir.Type, bool)
 	nameRef   func(ref string) (string, error)
-	log       *zap.Logger
+
+	filename string
+	log      *zap.Logger
 }
 
-func newSchemaGen(lookupRef func(ref string) (*ir.Type, bool)) *schemaGen {
+func newSchemaGen(filename string, lookupRef func(ref string) (*ir.Type, bool)) *schemaGen {
 	return &schemaGen{
 		side:      nil,
 		localRefs: map[string]*ir.Type{},
@@ -32,7 +34,8 @@ func newSchemaGen(lookupRef func(ref string) (*ir.Type, bool)) *schemaGen {
 			}
 			return name, nil
 		},
-		log: zap.NewNop(),
+		filename: filename,
+		log:      zap.NewNop(),
 	}
 }
 
@@ -276,8 +279,8 @@ func (g *schemaGen) generate2(name string, schema *jsonschema.Schema) (ret *ir.T
 		return g.regtype(name, t), nil
 	case jsonschema.Empty:
 		g.log.Info("Type is not defined, using any",
+			g.zapLocation(schema),
 			zap.String("name", name),
-			zap.String("ref", schema.Ref),
 		)
 		return g.regtype(name, ir.Any(schema)), nil
 	default:
diff --git a/gen/utils.go b/gen/utils.go
index 2bd3838a..5da0f7f7 100644
--- a/gen/utils.go
+++ b/gen/utils.go
@@ -4,10 +4,17 @@ import (
 	"fmt"
 	"net/http"
 
+	"go.uber.org/zap"
+
 	"github.com/ogen-go/ogen/gen/ir"
+	ogenjson "github.com/ogen-go/ogen/json"
 	"github.com/ogen-go/ogen/jsonschema"
 )
 
+func unreachable(v interface{}) string {
+	return fmt.Sprintf("unreachable: %v", v)
+}
+
 func isBinary(s *jsonschema.Schema) bool {
 	if s == nil {
 		return false
@@ -67,3 +74,23 @@ func statusText(code int) string {
 	}
 	return fmt.Sprintf("Code%d", code)
 }
+
+func (g *Generator) zapLocation(l interface {
+	Location() (ogenjson.Location, bool)
+}) zap.Field {
+	loc, ok := l.Location()
+	if !ok {
+		return zap.Skip()
+	}
+	return zap.String("at", loc.WithFilename(g.opt.Filename))
+}
+
+func (g *schemaGen) zapLocation(l interface {
+	Location() (ogenjson.Location, bool)
+}) zap.Field {
+	loc, ok := l.Location()
+	if !ok {
+		return zap.Skip()
+	}
+	return zap.String("at", loc.WithFilename(g.filename))
+}
diff --git a/json/location.go b/json/location.go
index 4527ccf8..db50291d 100644
--- a/json/location.go
+++ b/json/location.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"fmt"
 	"sort"
-	"strconv"
 
 	"github.com/go-json-experiment/json"
 )
@@ -94,11 +93,31 @@ type Location struct {
 // String implements fmt.Stringer.
 func (l Location) String() string {
 	if l.Line == 0 {
-		return strconv.Quote(l.JSONPointer)
+		return l.JSONPointer
 	}
 	return fmt.Sprintf("%d:%d", l.Line, l.Column)
 }
 
+// WithFilename prints the location with the given filename.
+//
+// If filename is empty, the location is printed as is.
+func (l Location) WithFilename(filename string) string {
+	if filename != "" {
+		switch {
+		case l.Line != 0:
+			// Line is set, so return "${filename}:".
+			filename += ":"
+		case l.JSONPointer != "":
+			// Line is not set, but JSONPointer is set, so return "${filename}#${JSONPointer}".
+			filename += "#"
+		default:
+			// Neither line nor JSONPointer is set, so return empty string.
+			return ""
+		}
+	}
+	return filename + l.String()
+}
+
 // Locatable is an interface for JSON value location store.
 type Locatable interface {
 	setLocation(Location)
diff --git a/jsonschema/parser.go b/jsonschema/parser.go
index 1a51e031..c85d04d8 100644
--- a/jsonschema/parser.go
+++ b/jsonschema/parser.go
@@ -395,6 +395,8 @@ func (p *Parser) extendInfo(schema *RawSchema, s *Schema) *Schema {
 			Mapping:      d.Mapping,
 		}
 	}
+
+	s.Locator = schema.Locator
 	return s
 }
 
diff --git a/jsonschema/schema.go b/jsonschema/schema.go
index 826768d8..031b29ef 100644
--- a/jsonschema/schema.go
+++ b/jsonschema/schema.go
@@ -4,6 +4,8 @@ import (
 	"regexp"
 
 	"github.com/go-json-experiment/json"
+
+	ogenjson "github.com/ogen-go/ogen/json"
 )
 
 // SchemaType is a JSON Schema type.
@@ -81,6 +83,8 @@ type Schema struct {
 	// Default schema value.
 	Default    interface{}
 	DefaultSet bool
+
+	ogenjson.Locator
 }
 
 // AddExample adds example for this Schema.
diff --git a/openapi/example.go b/openapi/example.go
index 3c3d0bc1..b1110131 100644
--- a/openapi/example.go
+++ b/openapi/example.go
@@ -1,12 +1,19 @@
 package openapi
 
-import "github.com/go-json-experiment/json"
+import (
+	"github.com/go-json-experiment/json"
+
+	ogenjson "github.com/ogen-go/ogen/json"
+)
 
 // Example is an OpenAPI Example.
 type Example struct {
-	Ref           string        `json:"$ref,omitempty"` // ref object
-	Summary       string        `json:"summary,omitempty"`
-	Description   string        `json:"description,omitempty"`
-	Value         json.RawValue `json:"value,omitempty"`
-	ExternalValue string        `json:"externalValue,omitempty"`
+	Ref string
+
+	Summary       string
+	Description   string
+	Value         json.RawValue
+	ExternalValue string
+
+	ogenjson.Locator
 }
diff --git a/openapi/mediatype.go b/openapi/mediatype.go
index 82dc7d85..f628760b 100644
--- a/openapi/mediatype.go
+++ b/openapi/mediatype.go
@@ -3,6 +3,7 @@ package openapi
 import (
 	"github.com/go-json-experiment/json"
 
+	ogenjson "github.com/ogen-go/ogen/json"
 	"github.com/ogen-go/ogen/jsonschema"
 )
 
@@ -12,6 +13,8 @@ type MediaType struct {
 	Example  json.RawValue
 	Examples map[string]*Example
 	Encoding map[string]*Encoding
+
+	ogenjson.Locator
 }
 
 // Encoding is Encoding Type Object.
@@ -21,4 +24,6 @@ type Encoding struct {
 	Style         ParameterStyle
 	Explode       bool
 	AllowReserved bool
+
+	ogenjson.Locator
 }
diff --git a/openapi/operation.go b/openapi/operation.go
index 2a858f78..28b3ed20 100644
--- a/openapi/operation.go
+++ b/openapi/operation.go
@@ -1,5 +1,9 @@
 package openapi
 
+import (
+	ogenjson "github.com/ogen-go/ogen/json"
+)
+
 // Operation is an OpenAPI Operation.
 type Operation struct {
 	OperationID string // optional
@@ -23,6 +27,8 @@ type Operation struct {
 	//  * default
 	//  * 1XX, 2XX, 3XX, 4XX, 5XX
 	Responses map[string]*Response
+
+	ogenjson.Locator
 }
 
 // Path is an operation path.
@@ -52,6 +58,8 @@ type RequestBody struct {
 	Description string
 	Required    bool
 	Content     map[string]*MediaType
+
+	ogenjson.Locator
 }
 
 // Header is an OpenAPI Header definition.
@@ -64,4 +72,6 @@ type Response struct {
 	Headers     map[string]*Header
 	Content     map[string]*MediaType
 	// Links map[string]*Link
+
+	ogenjson.Locator
 }
diff --git a/openapi/parameter.go b/openapi/parameter.go
index c10851bd..9998eb30 100644
--- a/openapi/parameter.go
+++ b/openapi/parameter.go
@@ -1,6 +1,9 @@
 package openapi
 
-import "github.com/ogen-go/ogen/jsonschema"
+import (
+	ogenjson "github.com/ogen-go/ogen/json"
+	"github.com/ogen-go/ogen/jsonschema"
+)
 
 // ParameterLocation defines where OpenAPI parameter is located.
 type ParameterLocation string
@@ -30,16 +33,19 @@ func (l ParameterLocation) Cookie() bool { return l == LocationCookie }
 
 // Parameter is an OpenAPI Operation Parameter.
 type Parameter struct {
-	Ref         string
+	Ref string
+
 	Name        string
 	Description string
+	Deprecated  bool
 	Schema      *jsonschema.Schema
 	Content     *ParameterContent
 	In          ParameterLocation
 	Style       ParameterStyle
 	Explode     bool
 	Required    bool
-	Deprecated  bool
+
+	ogenjson.Locator
 }
 
 // ParameterContent describes OpenAPI Parameter content field.
diff --git a/openapi/parser/errors.go b/openapi/parser/errors.go
index e572137e..753f1fc1 100644
--- a/openapi/parser/errors.go
+++ b/openapi/parser/errors.go
@@ -26,33 +26,15 @@ func (e *LocationError) Unwrap() error {
 	return e.err
 }
 
-func (e *LocationError) fileName() string {
-	filename := e.file
-	if filename != "" {
-		switch {
-		case e.loc.Line != 0:
-			// Line is set, so return "${filename}:".
-			filename += ":"
-		case e.loc.JSONPointer != "":
-			// Line is not set, but JSONPointer is set, so return "${filename}#${JSONPointer}".
-			filename += "#"
-		default:
-			// Neither line nor JSONPointer is set, so return empty string.
-			return ""
-		}
-	}
-	return filename
-}
-
 // FormatError implements errors.Formatter.
 func (e *LocationError) FormatError(p errors.Printer) (next error) {
-	p.Printf("at %s%s", e.fileName(), e.loc)
+	p.Printf("at %s", e.loc.WithFilename(e.file))
 	return e.err
 }
 
 // Error implements error.
 func (e *LocationError) Error() string {
-	return fmt.Sprintf("at %s%s: %s", e.fileName(), e.loc, e.err)
+	return fmt.Sprintf("at %s: %s", e.loc.WithFilename(e.file), e.err)
 }
 
 func (p *parser) wrapLocation(l ogenjson.Locatable, err error) error {
diff --git a/openapi/parser/parse_example.go b/openapi/parser/parse_example.go
index 0bea0d3e..a3aecf73 100644
--- a/openapi/parser/parse_example.go
+++ b/openapi/parser/parse_example.go
@@ -28,5 +28,6 @@ func (p *parser) parseExample(e *ogen.Example, ctx *resolveCtx) (_ *openapi.Exam
 		Description:   e.Description,
 		Value:         e.Value,
 		ExternalValue: e.ExternalValue,
+		Locator:       e.Locator,
 	}, nil
 }
diff --git a/openapi/parser/parse_header.go b/openapi/parser/parse_header.go
index 6f32ccb6..aa87660d 100644
--- a/openapi/parser/parse_header.go
+++ b/openapi/parser/parse_header.go
@@ -63,13 +63,14 @@ func (p *parser) parseHeader(name string, header *ogen.Header, ctx *resolveCtx)
 	op := &openapi.Header{
 		Name:        name,
 		Description: header.Description,
+		Deprecated:  header.Deprecated,
 		Schema:      schema,
 		Content:     content,
 		In:          locatedIn,
 		Style:       inferParamStyle(locatedIn, header.Style),
 		Explode:     inferParamExplode(locatedIn, header.Explode),
 		Required:    header.Required,
-		Deprecated:  header.Deprecated,
+		Locator:     header.Locator,
 	}
 
 	if header.Content != nil {
diff --git a/openapi/parser/parse_mediatype.go b/openapi/parser/parse_mediatype.go
index af3081c9..e9fc7e01 100644
--- a/openapi/parser/parse_mediatype.go
+++ b/openapi/parser/parse_mediatype.go
@@ -89,6 +89,7 @@ func (p *parser) parseMediaType(m ogen.Media, ctx *resolveCtx) (_ *openapi.Media
 				Style:         inferParamStyle(openapi.LocationQuery, e.Style),
 				Explode:       inferParamExplode(openapi.LocationQuery, e.Explode),
 				AllowReserved: e.AllowReserved,
+				Locator:       e.Locator,
 			}
 			encoding.Headers, err = p.parseHeaders(e.Headers, ctx)
 			if err != nil {
@@ -143,5 +144,6 @@ func (p *parser) parseMediaType(m ogen.Media, ctx *resolveCtx) (_ *openapi.Media
 		Example:  m.Example,
 		Examples: examples,
 		Encoding: encodings,
+		Locator:  m.Locator,
 	}, nil
 }
diff --git a/openapi/parser/parse_parameter.go b/openapi/parser/parse_parameter.go
index 8b56c174..fd8c0efb 100644
--- a/openapi/parser/parse_parameter.go
+++ b/openapi/parser/parse_parameter.go
@@ -11,6 +11,28 @@ import (
 	"github.com/ogen-go/ogen/openapi"
 )
 
+func mergeParams(opParams, itemParams []*openapi.Parameter) []*openapi.Parameter {
+	lookupOp := func(name string, in openapi.ParameterLocation) bool {
+		for _, param := range opParams {
+			if param.Name == name && param.In == in {
+				return true
+			}
+		}
+		return false
+	}
+
+	for _, param := range itemParams {
+		// Param defined in operation take precedence over param defined in pathItem.
+		if lookupOp(param.Name, param.In) {
+			continue
+		}
+
+		opParams = append(opParams, param)
+	}
+
+	return opParams
+}
+
 func (p *parser) parseParams(params []*ogen.Parameter) ([]*openapi.Parameter, error) {
 	// Unique parameter is defined by a combination of a name and location.
 	type pnameLoc struct {
diff --git a/openapi/parser/parse_request_body.go b/openapi/parser/parse_request_body.go
index 1a5f773e..29e45274 100644
--- a/openapi/parser/parse_request_body.go
+++ b/openapi/parser/parse_request_body.go
@@ -35,7 +35,8 @@ func (p *parser) parseRequestBody(body *ogen.RequestBody, ctx *resolveCtx) (_ *o
 
 	return &openapi.RequestBody{
 		Description: body.Description,
-		Content:     content,
 		Required:    body.Required,
+		Content:     content,
+		Locator:     body.Locator,
 	}, nil
 }
diff --git a/openapi/parser/parse_response.go b/openapi/parser/parse_response.go
index b3ab6a45..cd13380f 100644
--- a/openapi/parser/parse_response.go
+++ b/openapi/parser/parse_response.go
@@ -66,8 +66,9 @@ func (p *parser) parseResponse(resp *ogen.Response, ctx *resolveCtx) (_ *openapi
 
 	return &openapi.Response{
 		Description: resp.Description,
-		Content:     content,
 		Headers:     headers,
+		Content:     content,
+		Locator:     resp.Locator,
 	}, nil
 }
 
diff --git a/openapi/parser/parse_security.go b/openapi/parser/parse_security.go
index ca9e0416..4271b4e1 100644
--- a/openapi/parser/parse_security.go
+++ b/openapi/parser/parse_security.go
@@ -149,6 +149,7 @@ func cloneOAuthFlows(flows ogen.OAuthFlows) (r openapi.OAuthFlows) {
 			TokenURL:         flow.TokenURL,
 			RefreshURL:       flow.RefreshURL,
 			Scopes:           make(map[string]string, len(flow.Scopes)),
+			Locator:          flow.Locator,
 		}
 		for k, v := range flow.Scopes {
 			r.Scopes[k] = v
@@ -161,6 +162,7 @@ func cloneOAuthFlows(flows ogen.OAuthFlows) (r openapi.OAuthFlows) {
 		Password:          cloneFlow(flows.Password),
 		ClientCredentials: cloneFlow(flows.ClientCredentials),
 		AuthorizationCode: cloneFlow(flows.AuthorizationCode),
+		Locator:           flows.Locator,
 	}
 }
 
@@ -195,6 +197,7 @@ func (p *parser) parseSecurityRequirements(requirements ogen.SecurityRequirement
 					BearerFormat:     spec.BearerFormat,
 					Flows:            cloneOAuthFlows(flows),
 					OpenIDConnectURL: spec.OpenIDConnectURL,
+					Locator:          spec.Locator,
 				},
 			})
 		}
diff --git a/openapi/parser/parser.go b/openapi/parser/parser.go
index 4a29c5d7..71038e61 100644
--- a/openapi/parser/parser.go
+++ b/openapi/parser/parser.go
@@ -153,6 +153,7 @@ func (p *parser) parseOp(
 		Description: spec.Description,
 		Deprecated:  spec.Deprecated,
 		HTTPMethod:  httpMethod,
+		Locator:     spec.Locator,
 	}
 
 	opParams, err := p.parseParams(spec.Parameters)
@@ -197,24 +198,26 @@ func (p *parser) parseOp(
 	return op, nil
 }
 
-func mergeParams(opParams, itemParams []*openapi.Parameter) []*openapi.Parameter {
-	lookupOp := func(name string, in openapi.ParameterLocation) bool {
-		for _, param := range opParams {
-			if param.Name == name && param.In == in {
-				return true
-			}
+func forEachOps(item *ogen.PathItem, f func(method string, op ogen.Operation) error) error {
+	var err error
+	handle := func(method string, op *ogen.Operation) {
+		if err != nil || op == nil {
+			return
 		}
-		return false
-	}
 
-	for _, param := range itemParams {
-		// Param defined in operation take precedence over param defined in pathItem.
-		if lookupOp(param.Name, param.In) {
-			continue
+		err = f(method, *op)
+		if err != nil {
+			err = errors.Wrap(err, method)
 		}
-
-		opParams = append(opParams, param)
 	}
 
-	return opParams
+	handle("get", item.Get)
+	handle("put", item.Put)
+	handle("post", item.Post)
+	handle("delete", item.Delete)
+	handle("options", item.Options)
+	handle("head", item.Head)
+	handle("patch", item.Patch)
+	handle("trace", item.Trace)
+	return err
 }
diff --git a/openapi/parser/utils.go b/openapi/parser/utils.go
deleted file mode 100644
index d503d1ae..00000000
--- a/openapi/parser/utils.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package parser
-
-import (
-	"github.com/go-faster/errors"
-	"github.com/ogen-go/ogen"
-)
-
-func forEachOps(item *ogen.PathItem, f func(method string, op ogen.Operation) error) error {
-	var err error
-	handle := func(method string, op *ogen.Operation) {
-		if err != nil || op == nil {
-			return
-		}
-
-		err = f(method, *op)
-		if err != nil {
-			err = errors.Wrap(err, method)
-		}
-	}
-
-	handle("get", item.Get)
-	handle("put", item.Put)
-	handle("post", item.Post)
-	handle("delete", item.Delete)
-	handle("options", item.Options)
-	handle("head", item.Head)
-	handle("patch", item.Patch)
-	handle("trace", item.Trace)
-	return err
-}
diff --git a/openapi/security.go b/openapi/security.go
index efce09d6..d3d7e298 100644
--- a/openapi/security.go
+++ b/openapi/security.go
@@ -1,5 +1,9 @@
 package openapi
 
+import (
+	ogenjson "github.com/ogen-go/ogen/json"
+)
+
 // SecurityRequirements is parsed security requirements.
 type SecurityRequirements struct {
 	Scopes   []string
@@ -17,6 +21,8 @@ type Security struct {
 	BearerFormat     string
 	Flows            OAuthFlows
 	OpenIDConnectURL string
+
+	ogenjson.Locator
 }
 
 // OAuthFlows allows configuration of the supported OAuth Flows.
@@ -25,6 +31,8 @@ type OAuthFlows struct {
 	Password          *OAuthFlow
 	ClientCredentials *OAuthFlow
 	AuthorizationCode *OAuthFlow
+
+	ogenjson.Locator
 }
 
 // OAuthFlow is configuration details for a supported OAuth Flow.
@@ -33,4 +41,6 @@ type OAuthFlow struct {
 	TokenURL         string
 	RefreshURL       string
 	Scopes           map[string]string // name -> description
+
+	ogenjson.Locator
 }


Test patch:
diff --git a/gen/schema_gen_test.go b/gen/schema_gen_test.go
index f915b180..ccaec76a 100644
--- a/gen/schema_gen_test.go
+++ b/gen/schema_gen_test.go
@@ -15,7 +15,7 @@ func TestSchemaGenAnyWarn(t *testing.T) {
 	a := require.New(t)
 
 	core, ob := observer.New(zap.InfoLevel)
-	s := newSchemaGen(func(ref string) (*ir.Type, bool) {
+	s := newSchemaGen("", func(ref string) (*ir.Type, bool) {
 		return nil, false
 	})
 	s.log = zap.New(core)