Repository: ogen-go/ogen
Pull request #656 (2022-11-03 16:37:00)

fix(parser): pass root file if context file is zero


Patch:
diff --git a/cmd/ogen/main.go b/cmd/ogen/main.go
index 79493d58..2bfdbb1a 100644
--- a/cmd/ogen/main.go
+++ b/cmd/ogen/main.go
@@ -150,6 +150,7 @@ func parseSpecPath(
 	p string,
 	client *http.Client,
 	readFile func(string) ([]byte, error),
+	logger *zap.Logger,
 ) (f file, opts gen.RemoteOptions, _ error) {
 	// FIXME(tdakkota): pass context.
 	if u, _ := url.Parse(p); u != nil {
@@ -177,10 +178,11 @@ func parseSpecPath(
 				rootURL:  u,
 			}
 			opts = gen.RemoteOptions{
+				HTTPClient: client,
 				ReadFile: func(p string) ([]byte, error) {
 					return nil, errors.New("local files are not supported in remote mode")
 				},
-				HTTPClient: client,
+				Logger: logger,
 			}
 			return f, opts, nil
 		case "":
@@ -208,6 +210,7 @@ func parseSpecPath(
 	opts = gen.RemoteOptions{
 		HTTPClient: client,
 		ReadFile:   readFile,
+		Logger:     logger,
 	}
 
 	return f, opts, nil
@@ -336,6 +339,7 @@ func run() error {
 		specPath,
 		&http.Client{Timeout: time.Minute},
 		os.ReadFile,
+		logger.Named("remote"),
 	)
 	if err != nil {
 		return err
diff --git a/gen/errors.go b/gen/errors.go
index b5382be9..303bddcd 100644
--- a/gen/errors.go
+++ b/gen/errors.go
@@ -58,7 +58,7 @@ func (e *ErrUnsupportedContentTypes) Error() string {
 	return fmt.Sprintf("unsupported content types: [%s]", strings.Join(e.ContentTypes, ", "))
 }
 
-func (g *Generator) trySkip(err error, msg string, l pos) error {
+func (g *Generator) trySkip(err error, msg string, l position) error {
 	if err == nil {
 		return nil
 	}
@@ -70,8 +70,8 @@ func (g *Generator) trySkip(err error, msg string, l pos) error {
 	if uErr, ok := errors.Into[unimplementedError](err); ok {
 		reason = uErr.Error()
 	}
-	g.log.Info(msg,
-		g.zapPosition(l),
+	g.log.WithOptions(zap.AddCallerSkip(1)).Info(msg,
+		zapPosition(l),
 		zap.String("reason_error", reason),
 	)
 	return nil
diff --git a/gen/gen_contents.go b/gen/gen_contents.go
index a090dc17..68b0fdd3 100644
--- a/gen/gen_contents.go
+++ b/gen/gen_contents.go
@@ -374,7 +374,7 @@ func (g *Generator) generateContents(
 				}
 
 				g.log.Info(`Content type is unsupported, set "format" to "binary" to use io.Reader`,
-					g.zapPosition(media),
+					zapPosition(media),
 					zap.String("contentType", contentType),
 				)
 				unsupported = append(unsupported, contentType)
diff --git a/gen/gen_headers.go b/gen/gen_headers.go
index 105c7454..0ced7ee9 100644
--- a/gen/gen_headers.go
+++ b/gen/gen_headers.go
@@ -22,7 +22,7 @@ func (g *Generator) generateHeaders(
 		if http.CanonicalHeaderKey(hname) == "Content-Type" {
 			g.log.Warn(
 				"Content-Type is described separately and will be ignored in this section.",
-				g.zapPosition(header),
+				zapPosition(header),
 			)
 			continue
 		}
diff --git a/gen/gen_schema.go b/gen/gen_schema.go
index 9dc28c9f..9770053c 100644
--- a/gen/gen_schema.go
+++ b/gen/gen_schema.go
@@ -38,7 +38,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(g.opt.File.Name, ctx.lookupRef)
+	gen := newSchemaGen(ctx.lookupRef)
 	gen.log = g.log.Named("schemagen")
 	gen.fail = g.fail
 
@@ -100,7 +100,7 @@ func GenerateSchema(schema *jsonschema.Schema, fs FileSystem, opts GenerateSchem
 	}
 
 	// TODO(tdakkota): pass input filename
-	gen := newSchemaGen("", func(ref string) (*ir.Type, bool) {
+	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 3224865c..852c7955 100644
--- a/gen/generator.go
+++ b/gen/generator.go
@@ -109,7 +109,7 @@ func (g *Generator) makeOps(ops []*openapi.Operation) error {
 
 	for _, spec := range ops {
 		routePath := spec.Path.String()
-		log := g.log.With(g.zapPosition(spec))
+		log := g.log.With(zapPosition(spec))
 
 		if !g.opt.Filters.accept(spec) {
 			log.Info("Skipping filtered operation")
@@ -168,7 +168,7 @@ func (g *Generator) makeWebhooks(webhooks []openapi.Webhook) error {
 			Name: w.Name,
 		}
 		for _, spec := range w.Operations {
-			log := g.log.With(g.zapPosition(spec))
+			log := g.log.With(zapPosition(spec))
 
 			if !g.opt.Filters.accept(spec) {
 				log.Info("Skipping filtered operation")
diff --git a/gen/schema_gen.go b/gen/schema_gen.go
index 43134eda..f9cbb909 100644
--- a/gen/schema_gen.go
+++ b/gen/schema_gen.go
@@ -19,11 +19,10 @@ type schemaGen struct {
 	nameRef   func(ref string) (string, error)
 	fail      func(err error) error
 
-	filename string
-	log      *zap.Logger
+	log *zap.Logger
 }
 
-func newSchemaGen(filename string, lookupRef func(ref string) (*ir.Type, bool)) *schemaGen {
+func newSchemaGen(lookupRef func(ref string) (*ir.Type, bool)) *schemaGen {
 	return &schemaGen{
 		side:      nil,
 		localRefs: map[string]*ir.Type{},
@@ -38,8 +37,7 @@ func newSchemaGen(filename string, lookupRef func(ref string) (*ir.Type, bool))
 		fail: func(err error) error {
 			return err
 		},
-		filename: filename,
-		log:      zap.NewNop(),
+		log: zap.NewNop(),
 	}
 }
 
@@ -295,7 +293,7 @@ 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.zapPosition(schema),
+			zapPosition(schema),
 			zap.String("name", name),
 		)
 		return g.regtype(name, ir.Any(schema)), nil
diff --git a/gen/utils.go b/gen/utils.go
index a80a8f8d..14465033 100644
--- a/gen/utils.go
+++ b/gen/utils.go
@@ -75,12 +75,12 @@ func statusText(code int) string {
 	return fmt.Sprintf("Code%d", code)
 }
 
-type pos interface {
+type position interface {
 	Position() (location.Position, bool)
 	File() location.File
 }
 
-func zapPosition(filename string, l pos) zap.Field {
+func zapPosition(l position) zap.Field {
 	if l == nil {
 		return zap.Skip()
 	}
@@ -88,13 +88,6 @@ func zapPosition(filename string, l pos) zap.Field {
 	if !ok {
 		return zap.Skip()
 	}
-	return zap.String("at", loc.WithFilename(filename))
-}
-
-func (g *Generator) zapPosition(l pos) zap.Field {
-	return zapPosition(l.File().Name, l)
-}
-
-func (g *schemaGen) zapPosition(l pos) zap.Field {
-	return zapPosition(l.File().Name, l)
+	file := l.File()
+	return zap.String("at", loc.WithFilename(file.Name))
 }
diff --git a/internal/location/file.go b/internal/location/file.go
index bbdd8d07..a02961da 100644
--- a/internal/location/file.go
+++ b/internal/location/file.go
@@ -22,6 +22,8 @@ func (f File) IsZero() bool {
 }
 
 // NewFile creates a new File.
+//
+// Do not modify the data after calling this function, Lines will point to it.
 func NewFile(name, source string, data []byte) File {
 	f := File{
 		Name:   name,
diff --git a/internal/location/lines.go b/internal/location/lines.go
index 3e31ac9c..a9a4e16a 100644
--- a/internal/location/lines.go
+++ b/internal/location/lines.go
@@ -2,8 +2,6 @@ package location
 
 import (
 	"bytes"
-
-	"golang.org/x/exp/slices"
 )
 
 // Lines is a sorted slice of newline offsets.
@@ -51,7 +49,7 @@ func (l Lines) Line(n int) (start, end int) {
 
 // Collect fills the given slice with the offset of newlines.
 func (l *Lines) Collect(data []byte) {
-	l.data = slices.Clone(data)
+	l.data = data
 	l.lines = l.lines[:0]
 
 	var (
diff --git a/jsonschema/errors.go b/jsonschema/errors.go
index ac81db85..8752196d 100644
--- a/jsonschema/errors.go
+++ b/jsonschema/errors.go
@@ -3,12 +3,21 @@ package jsonschema
 import (
 	"github.com/go-faster/errors"
 
+	"github.com/ogen-go/ogen/internal/jsonpointer"
 	"github.com/ogen-go/ogen/internal/location"
 )
 
 // LocationError is a wrapper for an error that has a location.
 type LocationError = location.Error
 
+func (p *Parser) file(ctx *jsonpointer.ResolveCtx) location.File {
+	file := ctx.File()
+	if file.IsZero() || ctx.IsRoot() {
+		return p.rootFile
+	}
+	return file
+}
+
 func (p *Parser) wrapField(field string, file location.File, l location.Locator, err error) error {
 	if err == nil || p == nil {
 		return err
@@ -28,7 +37,7 @@ func (p *Parser) wrapLocation(file location.File, l location.Locator, err error)
 		return err
 	}
 	if file.IsZero() {
-		file = p.file
+		file = p.rootFile
 	}
 	return &LocationError{
 		File: file,
diff --git a/jsonschema/external.go b/jsonschema/external.go
index c400b0d9..566fd14e 100644
--- a/jsonschema/external.go
+++ b/jsonschema/external.go
@@ -6,8 +6,10 @@ import (
 	"net/http"
 	"net/url"
 	"os"
+	"time"
 
 	"github.com/go-faster/errors"
+	"go.uber.org/zap"
 )
 
 // ExternalResolver resolves external links.
@@ -31,6 +33,8 @@ type ExternalOptions struct {
 	HTTPClient *http.Client
 	// ReadFile sets function for reading files from fs. Defaults to os.ReadFile.
 	ReadFile func(p string) ([]byte, error)
+	// Logger sets logger to use. Defaults to zap.NewNop().
+	Logger *zap.Logger
 }
 
 func (r *ExternalOptions) setDefaults() {
@@ -40,6 +44,9 @@ func (r *ExternalOptions) setDefaults() {
 	if r.ReadFile == nil {
 		r.ReadFile = os.ReadFile
 	}
+	if r.Logger == nil {
+		r.Logger = zap.NewNop()
+	}
 }
 
 var _ ExternalResolver = externalResolver{}
@@ -47,6 +54,7 @@ var _ ExternalResolver = externalResolver{}
 type externalResolver struct {
 	client   *http.Client
 	readFile func(p string) ([]byte, error)
+	logger   *zap.Logger
 }
 
 // NewExternalResolver creates new ExternalResolver.
@@ -58,6 +66,7 @@ func NewExternalResolver(opts ExternalOptions) ExternalResolver {
 	return externalResolver{
 		client:   opts.HTTPClient,
 		readFile: opts.ReadFile,
+		logger:   opts.Logger,
 	}
 }
 
@@ -70,6 +79,7 @@ func (e externalResolver) httpGet(ctx context.Context, u *url.URL) ([]byte, erro
 		req.SetBasicAuth(u.User.Username(), pass)
 	}
 
+	start := time.Now()
 	resp, err := e.client.Do(req)
 	if err != nil {
 		return nil, errors.Wrap(err, "do")
@@ -79,6 +89,11 @@ func (e externalResolver) httpGet(ctx context.Context, u *url.URL) ([]byte, erro
 			_ = resp.Body.Close()
 		}
 	}()
+	e.logger.Debug("Get",
+		zap.String("url", u.Redacted()),
+		zap.Int("status", resp.StatusCode),
+		zap.Duration("duration", time.Since(start)),
+	)
 
 	if code := resp.StatusCode; code >= 299 {
 		text := http.StatusText(code)
diff --git a/jsonschema/parser.go b/jsonschema/parser.go
index 34eacef1..1b73cc29 100644
--- a/jsonschema/parser.go
+++ b/jsonschema/parser.go
@@ -20,7 +20,7 @@ type Parser struct {
 	schemas  map[string]resolver
 	refcache map[jsonpointer.RefKey]*Schema
 
-	file location.File // optional, used for error messages
+	rootFile location.File // optional, used for error messages
 
 	inferTypes bool
 }
@@ -37,7 +37,7 @@ func NewParser(s Settings) *Parser {
 			},
 		},
 		refcache:   map[jsonpointer.RefKey]*Schema{},
-		file:       s.File,
+		rootFile:   s.File,
 		inferTypes: s.InferTypes,
 	}
 }
@@ -60,11 +60,11 @@ func (p *Parser) Resolve(ref string, ctx *jsonpointer.ResolveCtx) (*Schema, erro
 func (p *Parser) parse(schema *RawSchema, ctx *jsonpointer.ResolveCtx) (_ *Schema, rerr error) {
 	if schema != nil {
 		defer func() {
-			rerr = p.wrapLocation(ctx.File(), schema.Common.Locator, rerr)
+			rerr = p.wrapLocation(p.file(ctx), schema.Common.Locator, rerr)
 		}()
 	}
 	return p.parse1(schema, ctx, func(s *Schema) *Schema {
-		return p.extendInfo(schema, s, ctx.File())
+		return p.extendInfo(schema, s, p.file(ctx))
 	})
 }
 
@@ -86,7 +86,7 @@ func (p *Parser) parse1(schema *RawSchema, ctx *jsonpointer.ResolveCtx, hook fun
 					if ok, _ := ogenjson.Equal(a, b); ok {
 						loc := loc.Index(i)
 						err := errors.Errorf("duplicate enum value: %q", a)
-						return nil, p.wrapLocation(ctx.File(), loc, err)
+						return nil, p.wrapLocation(p.file(ctx), loc, err)
 					}
 				}
 			}
@@ -94,7 +94,7 @@ func (p *Parser) parse1(schema *RawSchema, ctx *jsonpointer.ResolveCtx, hook fun
 			values, err := parseEnumValues(s, enum)
 			if err != nil {
 				err := errors.Wrap(err, "parse enum values")
-				return nil, p.wrapLocation(ctx.File(), loc, err)
+				return nil, p.wrapLocation(p.file(ctx), loc, err)
 			}
 			s.Enum = values
 			handleNullableEnum(s)
@@ -115,7 +115,7 @@ func (p *Parser) parse1(schema *RawSchema, ctx *jsonpointer.ResolveCtx, hook fun
 				return nil
 			}(); err != nil {
 				err := errors.Wrap(err, "parse default")
-				return nil, p.wrapField("default", ctx.File(), schema.Common.Locator, err)
+				return nil, p.wrapField("default", p.file(ctx), schema.Common.Locator, err)
 			}
 		}
 		if a, ok := schema.Common.Extensions["x-ogen-name"]; ok {
@@ -133,7 +133,7 @@ func (p *Parser) parseSchema(schema *RawSchema, ctx *jsonpointer.ResolveCtx, hoo
 		return nil, nil
 	}
 	wrapField := func(field string, err error) error {
-		return p.wrapField(field, ctx.File(), schema.Common.Locator, err)
+		return p.wrapField(field, p.file(ctx), schema.Common.Locator, err)
 	}
 
 	validateMinMax := func(prop string, min, max *uint64) (rerr error) {
@@ -307,12 +307,12 @@ func (p *Parser) parseSchema(schema *RawSchema, ctx *jsonpointer.ResolveCtx, hoo
 				if err != nil {
 					loc := ppLoc.Key(pattern)
 					err := errors.Wrapf(err, "compile pattern %q", pattern)
-					return nil, p.wrapLocation(ctx.File(), loc, err)
+					return nil, p.wrapLocation(p.file(ctx), loc, err)
 				}
 				sch, err := p.parse(prop.Schema, ctx)
 				if err != nil {
 					err := errors.Wrapf(err, "pattern schema %q", pattern)
-					return nil, p.wrapField(pattern, ctx.File(), ppLoc, err)
+					return nil, p.wrapField(pattern, p.file(ctx), ppLoc, err)
 				}
 				patterns[idx] = PatternProperty{
 					Pattern: r,
@@ -327,7 +327,7 @@ func (p *Parser) parseSchema(schema *RawSchema, ctx *jsonpointer.ResolveCtx, hoo
 			prop, err := p.parse(propSpec.Schema, ctx)
 			if err != nil {
 				err := errors.Wrapf(err, "property %q", propSpec.Name)
-				return nil, p.wrapField(propSpec.Name, ctx.File(), propsLoc, err)
+				return nil, p.wrapField(propSpec.Name, p.file(ctx), propsLoc, err)
 			}
 
 			var description string
@@ -449,7 +449,7 @@ func (p *Parser) parseMany(schemas []*RawSchema, loc location.Locator, ctx *json
 	for i, schema := range schemas {
 		s, err := p.parse(schema, ctx)
 		if err != nil {
-			return nil, p.wrapLocation(ctx.File(), loc.Index(i), err)
+			return nil, p.wrapLocation(p.file(ctx), loc.Index(i), err)
 		}
 		result = append(result, s)
 	}
diff --git a/jsonschema/resolve.go b/jsonschema/resolve.go
index f116f566..2ac49d5b 100644
--- a/jsonschema/resolve.go
+++ b/jsonschema/resolve.go
@@ -48,7 +48,7 @@ func (p *Parser) getResolver(loc string) (r resolver, rerr error) {
 
 	r = resolver{
 		ReferenceResolver: NewRootResolver(&node),
-		file:              location.NewFile(loc, loc, raw),
+		file:              file,
 	}
 	p.schemas[loc] = r
 
@@ -86,6 +86,6 @@ func (p *Parser) resolve(ref string, ctx *jsonpointer.ResolveCtx) (*Schema, erro
 	return p.parse1(raw, ctx, func(s *Schema) *Schema {
 		s.Ref = ref
 		p.refcache[key] = s
-		return p.extendInfo(raw, s, ctx.File())
+		return p.extendInfo(raw, s, p.file(ctx))
 	})
 }
diff --git a/openapi/parser/errors.go b/openapi/parser/errors.go
index 52633a77..29e6b1a6 100644
--- a/openapi/parser/errors.go
+++ b/openapi/parser/errors.go
@@ -3,12 +3,21 @@ package parser
 import (
 	"github.com/go-faster/errors"
 
+	"github.com/ogen-go/ogen/internal/jsonpointer"
 	"github.com/ogen-go/ogen/internal/location"
 )
 
 // LocationError is a wrapper for an error that has a location.
 type LocationError = location.Error
 
+func (p *parser) file(ctx *jsonpointer.ResolveCtx) location.File {
+	file := ctx.File()
+	if file.IsZero() || ctx.IsRoot() {
+		return p.rootFile
+	}
+	return file
+}
+
 func (p *parser) wrapRef(file location.File, l location.Locator, err error) error {
 	if err == nil || p == nil {
 		return err
@@ -35,7 +44,7 @@ func (p *parser) wrapLocation(file location.File, l location.Locator, err error)
 		return err
 	}
 	if file.IsZero() {
-		file = p.file
+		file = p.rootFile
 	}
 	return &LocationError{
 		File: file,
diff --git a/openapi/parser/parse_components.go b/openapi/parser/parse_components.go
index a481cbe2..1c1aa212 100644
--- a/openapi/parser/parse_components.go
+++ b/openapi/parser/parse_components.go
@@ -26,7 +26,7 @@ func validateComponentsKey[Object any](p *parser, m map[string]Object, locator l
 		if !componentsKeyRegex.MatchString(name) {
 			locator := locator.Key(name)
 			err := errors.Errorf("invalid name: %q doesn't match %q", name, componentsKeyRegex)
-			return p.wrapLocation(p.file, locator, err)
+			return p.wrapLocation(p.rootFile, locator, err)
 		}
 	}
 	return nil
@@ -85,7 +85,7 @@ func (p *parser) parseComponents(c *ogen.Components) (_ *openapi.Components, rer
 	}
 	locator := c.Common.Locator
 	defer func() {
-		rerr = p.wrapLocation(p.file, locator, rerr)
+		rerr = p.wrapLocation(p.rootFile, locator, rerr)
 	}()
 
 	if err := validateComponentsKeys(p, c); err != nil {
@@ -93,7 +93,7 @@ func (p *parser) parseComponents(c *ogen.Components) (_ *openapi.Components, rer
 	}
 	if len(c.PathItems) > 0 {
 		if err := p.requireMinorVersion("pathItem components", 1); err != nil {
-			err := p.wrapLocation(p.file, locator.Field("pathItems"), err)
+			err := p.wrapLocation(p.rootFile, locator.Field("pathItems"), err)
 			return nil, err
 		}
 	}
@@ -108,7 +108,7 @@ func (p *parser) parseComponents(c *ogen.Components) (_ *openapi.Components, rer
 	wrapErr := func(component, name string, err error) error {
 		locator := locator.Field(component).Field(name)
 		err = errors.Wrapf(err, "%s: %q", component, name)
-		return p.wrapLocation(p.file, locator, err)
+		return p.wrapLocation(p.rootFile, locator, err)
 	}
 
 	for name := range c.Schemas {
diff --git a/openapi/parser/parse_example.go b/openapi/parser/parse_example.go
index 1e5c716d..aa8dcfae 100644
--- a/openapi/parser/parse_example.go
+++ b/openapi/parser/parse_example.go
@@ -12,12 +12,12 @@ func (p *parser) parseExample(e *ogen.Example, ctx *jsonpointer.ResolveCtx) (_ *
 	}
 	locator := e.Common.Locator
 	defer func() {
-		rerr = p.wrapLocation(ctx.File(), locator, rerr)
+		rerr = p.wrapLocation(p.file(ctx), locator, rerr)
 	}()
 	if ref := e.Ref; ref != "" {
 		resolved, err := p.resolveExample(ref, ctx)
 		if err != nil {
-			return nil, p.wrapRef(ctx.File(), locator, err)
+			return nil, p.wrapRef(p.file(ctx), locator, err)
 		}
 		return resolved, nil
 	}
@@ -27,6 +27,6 @@ func (p *parser) parseExample(e *ogen.Example, ctx *jsonpointer.ResolveCtx) (_ *
 		Description:   e.Description,
 		Value:         e.Value,
 		ExternalValue: e.ExternalValue,
-		Pointer:       locator.Pointer(ctx.File()),
+		Pointer:       locator.Pointer(p.file(ctx)),
 	}, nil
 }
diff --git a/openapi/parser/parse_header.go b/openapi/parser/parse_header.go
index 802930be..1181f732 100644
--- a/openapi/parser/parse_header.go
+++ b/openapi/parser/parse_header.go
@@ -30,12 +30,12 @@ func (p *parser) parseHeader(name string, header *ogen.Header, ctx *jsonpointer.
 	}
 	locator := header.Common.Locator
 	defer func() {
-		rerr = p.wrapLocation(ctx.File(), locator, rerr)
+		rerr = p.wrapLocation(p.file(ctx), locator, rerr)
 	}()
 	if ref := header.Ref; ref != "" {
 		resolved, err := p.resolveHeader(name, ref, ctx)
 		if err != nil {
-			return nil, p.wrapRef(ctx.File(), locator, err)
+			return nil, p.wrapRef(p.file(ctx), locator, err)
 		}
 		return resolved, nil
 	}
@@ -45,7 +45,7 @@ func (p *parser) parseHeader(name string, header *ogen.Header, ctx *jsonpointer.
 			return nil
 		}
 		err := errors.Errorf(`%q MUST NOT be specified, got %q`, name, got)
-		return p.wrapField(name, ctx.File(), locator, err)
+		return p.wrapField(name, p.file(ctx), locator, err)
 	}
 	if err := mustNotSpecified("in", header.In); err != nil {
 		return nil, err
@@ -55,19 +55,19 @@ func (p *parser) parseHeader(name string, header *ogen.Header, ctx *jsonpointer.
 	}
 	locatedIn := openapi.LocationHeader
 
-	if err := p.validateParameter(name, locatedIn, header, ctx.File()); err != nil {
+	if err := p.validateParameter(name, locatedIn, header, p.file(ctx)); err != nil {
 		return nil, err
 	}
 
 	schema, err := p.parseSchema(header.Schema, ctx)
 	if err != nil {
-		return nil, p.wrapField("schema", ctx.File(), locator, err)
+		return nil, p.wrapField("schema", p.file(ctx), locator, err)
 	}
 
 	content, err := p.parseParameterContent(header.Content, locator.Field("content"), ctx)
 	if err != nil {
 		err := errors.Wrap(err, "content")
-		return nil, p.wrapField("content", ctx.File(), locator, err)
+		return nil, p.wrapField("content", p.file(ctx), locator, err)
 	}
 
 	op := &openapi.Header{
@@ -80,12 +80,12 @@ func (p *parser) parseHeader(name string, header *ogen.Header, ctx *jsonpointer.
 		Style:       inferParamStyle(locatedIn, header.Style),
 		Explode:     inferParamExplode(locatedIn, header.Explode),
 		Required:    header.Required,
-		Pointer:     locator.Pointer(ctx.File()),
+		Pointer:     locator.Pointer(p.file(ctx)),
 	}
 
 	// TODO: Validate content?
 	if header.Content == nil {
-		if err := p.validateParamStyle(op, ctx.File()); err != nil {
+		if err := p.validateParamStyle(op, p.file(ctx)); err != nil {
 			return nil, err
 		}
 	}
diff --git a/openapi/parser/parse_mediatype.go b/openapi/parser/parse_mediatype.go
index 65ceec1d..ad00ebf2 100644
--- a/openapi/parser/parse_mediatype.go
+++ b/openapi/parser/parse_mediatype.go
@@ -27,7 +27,7 @@ func (p *parser) parseContent(
 		ct, _, err := mime.ParseMediaType(name)
 		if err != nil {
 			err := errors.Wrapf(err, "content type %q", name)
-			return nil, p.wrapLocation(ctx.File(), locator.Key(name), err)
+			return nil, p.wrapLocation(p.file(ctx), locator.Key(name), err)
 		}
 
 		result[name], err = p.parseMediaType(ct, m, ctx)
@@ -54,14 +54,14 @@ func (p *parser) parseParameterContent(
 		}
 
 		err := errors.New(`"content" map MUST only contain one entry`)
-		return nil, p.wrapLocation(ctx.File(), locator, err)
+		return nil, p.wrapLocation(p.file(ctx), locator, err)
 	}
 
 	for name, m := range content {
 		ct, _, err := mime.ParseMediaType(name)
 		if err != nil {
 			err := errors.Wrapf(err, "content type %q", name)
-			return nil, p.wrapLocation(ctx.File(), locator.Key(name), err)
+			return nil, p.wrapLocation(p.file(ctx), locator.Key(name), err)
 		}
 
 		media, err := p.parseMediaType(ct, m, ctx)
@@ -80,7 +80,7 @@ func (p *parser) parseParameterContent(
 func (p *parser) parseMediaType(ct string, m ogen.Media, ctx *jsonpointer.ResolveCtx) (_ *openapi.MediaType, rerr error) {
 	locator := m.Common.Locator
 	defer func() {
-		rerr = p.wrapLocation(ctx.File(), locator, rerr)
+		rerr = p.wrapLocation(p.file(ctx), locator, rerr)
 	}()
 
 	s, err := p.parseSchema(m.Schema, ctx)
@@ -103,7 +103,7 @@ func (p *parser) parseMediaType(ct string, m ogen.Media, ctx *jsonpointer.Resolv
 			parseEncoding := func(name string, prop jsonschema.Property, e ogen.Encoding) (rerr error) {
 				locator := e.Common.Locator
 				defer func() {
-					rerr = p.wrapLocation(ctx.File(), locator, rerr)
+					rerr = p.wrapLocation(p.file(ctx), locator, rerr)
 				}()
 
 				encoding := &openapi.Encoding{
@@ -112,11 +112,11 @@ func (p *parser) parseMediaType(ct string, m ogen.Media, ctx *jsonpointer.Resolv
 					Style:         inferParamStyle(openapi.LocationQuery, e.Style),
 					Explode:       inferParamExplode(openapi.LocationQuery, e.Explode),
 					AllowReserved: e.AllowReserved,
-					Pointer:       locator.Pointer(ctx.File()),
+					Pointer:       locator.Pointer(p.file(ctx)),
 				}
 				encoding.Headers, err = p.parseHeaders(e.Headers, ctx)
 				if err != nil {
-					return p.wrapField("headers", ctx.File(), locator, err)
+					return p.wrapField("headers", p.file(ctx), locator, err)
 				}
 				encodings[name] = encoding
 
@@ -129,7 +129,7 @@ func (p *parser) parseMediaType(ct string, m ogen.Media, ctx *jsonpointer.Resolv
 					Required:      prop.Required,
 					AllowReserved: encoding.AllowReserved,
 					Pointer:       encoding.Pointer,
-				}, ctx.File()); err != nil {
+				}, p.file(ctx)); err != nil {
 					return errors.Wrap(err, "param style")
 				}
 
@@ -142,7 +142,7 @@ func (p *parser) parseMediaType(ct string, m ogen.Media, ctx *jsonpointer.Resolv
 				if !ok {
 					loc := encodingLoc.Key(name)
 					err := errors.Errorf("unknown property %q", name)
-					return nil, p.wrapLocation(ctx.File(), loc, err)
+					return nil, p.wrapLocation(p.file(ctx), loc, err)
 				}
 
 				if err := parseEncoding(name, prop, e); err != nil {
@@ -177,6 +177,6 @@ func (p *parser) parseMediaType(ct string, m ogen.Media, ctx *jsonpointer.Resolv
 		Example:  json.RawMessage(m.Example),
 		Examples: examples,
 		Encoding: encodings,
-		Pointer:  locator.Pointer(ctx.File()),
+		Pointer:  locator.Pointer(p.file(ctx)),
 	}, nil
 }
diff --git a/openapi/parser/parse_parameter.go b/openapi/parser/parse_parameter.go
index aa894181..36c0b1d0 100644
--- a/openapi/parser/parse_parameter.go
+++ b/openapi/parser/parse_parameter.go
@@ -55,7 +55,7 @@ func (p *parser) parseParams(
 		if spec == nil {
 			loc := locator.Index(idx)
 			err := errors.Errorf("parameter %d is empty or null", idx)
-			return nil, p.wrapLocation(ctx.File(), loc, err)
+			return nil, p.wrapLocation(p.file(ctx), loc, err)
 		}
 
 		param, err := p.parseParameter(spec, ctx)
@@ -69,7 +69,7 @@ func (p *parser) parseParams(
 		}
 		if _, ok := unique[ploc]; ok {
 			err := errors.Errorf("duplicate parameter: %q in %q", param.Name, param.In)
-			return nil, p.wrapLocation(ctx.File(), spec.Common.Locator, err)
+			return nil, p.wrapLocation(p.file(ctx), spec.Common.Locator, err)
 		}
 
 		unique[ploc] = struct{}{}
@@ -120,12 +120,12 @@ func (p *parser) parseParameter(param *ogen.Parameter, ctx *jsonpointer.ResolveC
 	}
 	locator := param.Common.Locator
 	defer func() {
-		rerr = p.wrapLocation(ctx.File(), locator, rerr)
+		rerr = p.wrapLocation(p.file(ctx), locator, rerr)
 	}()
 	if ref := param.Ref; ref != "" {
 		parsed, err := p.resolveParameter(ref, ctx)
 		if err != nil {
-			return nil, p.wrapRef(ctx.File(), locator, err)
+			return nil, p.wrapRef(p.file(ctx), locator, err)
 		}
 		return parsed, nil
 	}
@@ -140,22 +140,22 @@ func (p *parser) parseParameter(param *ogen.Parameter, ctx *jsonpointer.ResolveC
 	locatedIn, exists := types[strings.ToLower(param.In)]
 	if !exists {
 		err := errors.Errorf("unknown parameter location %q", param.In)
-		return nil, p.wrapField("in", ctx.File(), locator, err)
+		return nil, p.wrapField("in", p.file(ctx), locator, err)
 	}
 
-	if err := p.validateParameter(param.Name, locatedIn, param, ctx.File()); err != nil {
+	if err := p.validateParameter(param.Name, locatedIn, param, p.file(ctx)); err != nil {
 		return nil, err
 	}
 
 	schema, err := p.parseSchema(param.Schema, ctx)
 	if err != nil {
-		return nil, p.wrapField("schema", ctx.File(), locator, err)
+		return nil, p.wrapField("schema", p.file(ctx), locator, err)
 	}
 
 	content, err := p.parseParameterContent(param.Content, locator.Field("content"), ctx)
 	if err != nil {
 		err := errors.Wrap(err, "content")
-		return nil, p.wrapField("content", ctx.File(), locator, err)
+		return nil, p.wrapField("content", p.file(ctx), locator, err)
 	}
 
 	op := &openapi.Parameter{
@@ -169,12 +169,12 @@ func (p *parser) parseParameter(param *ogen.Parameter, ctx *jsonpointer.ResolveC
 		Explode:       inferParamExplode(locatedIn, param.Explode),
 		Required:      param.Required,
 		AllowReserved: param.AllowReserved,
-		Pointer:       locator.Pointer(ctx.File()),
+		Pointer:       locator.Pointer(p.file(ctx)),
 	}
 
 	// TODO: Validate content?
 	if param.Content == nil {
-		if err := p.validateParamStyle(op, ctx.File()); err != nil {
+		if err := p.validateParamStyle(op, p.file(ctx)); err != nil {
 			return nil, err
 		}
 	}
diff --git a/openapi/parser/parse_path_item.go b/openapi/parser/parse_path_item.go
index e29e952b..0f01214e 100644
--- a/openapi/parser/parse_path_item.go
+++ b/openapi/parser/parse_path_item.go
@@ -21,13 +21,13 @@ func (p *parser) parsePathItem(
 	}
 	locator := item.Common.Locator
 	defer func() {
-		rerr = p.wrapLocation(ctx.File(), locator, rerr)
+		rerr = p.wrapLocation(p.file(ctx), locator, rerr)
 	}()
 
 	if ref := item.Ref; ref != "" {
 		ops, err := p.resolvePathItem(path, ref, ctx)
 		if err != nil {
-			return nil, p.wrapRef(ctx.File(), locator, err)
+			return nil, p.wrapRef(p.file(ctx), locator, err)
 		}
 		return ops, nil
 	}
@@ -41,13 +41,13 @@ func (p *parser) parsePathItem(
 	if err := forEachOps(item, func(method string, op ogen.Operation) error {
 		locator := op.Common.Locator
 		defer func() {
-			rerr = p.wrapLocation(ctx.File(), locator, rerr)
+			rerr = p.wrapLocation(p.file(ctx), locator, rerr)
 		}()
 
 		if id := op.OperationID; id != "" {
 			if _, ok := p.operationIDs[id]; ok {
 				err := errors.Errorf("duplicate operationId: %q", id)
-				return p.wrapField("operationId", ctx.File(), locator, err)
+				return p.wrapField("operationId", p.file(ctx), locator, err)
 			}
 			p.operationIDs[id] = struct{}{}
 		}
@@ -77,7 +77,7 @@ func (p *parser) parseOp(
 ) (_ *openapi.Operation, err error) {
 	locator := spec.Common.Locator
 	defer func() {
-		err = p.wrapLocation(ctx.File(), locator, err)
+		err = p.wrapLocation(p.file(ctx), locator, err)
 	}()
 
 	op := &openapi.Operation{
@@ -86,7 +86,7 @@ func (p *parser) parseOp(
 		Description: spec.Description,
 		Deprecated:  spec.Deprecated,
 		HTTPMethod:  httpMethod,
-		Pointer:     locator.Pointer(ctx.File()),
+		Pointer:     locator.Pointer(p.file(ctx)),
 	}
 
 	opParams, err := p.parseParams(spec.Parameters, locator.Field("parameters"), ctx)
@@ -114,7 +114,7 @@ func (p *parser) parseOp(
 		op.Responses, err = p.parseResponses(spec.Responses, locator, ctx)
 		if err != nil {
 			err := errors.Wrap(err, "responses")
-			return nil, p.wrapLocation(ctx.File(), locator, err)
+			return nil, p.wrapLocation(p.file(ctx), locator, err)
 		}
 	}
 
@@ -122,7 +122,7 @@ func (p *parser) parseOp(
 		op.Security, err = p.parseSecurityRequirements(spec, locator, ctx)
 		if err != nil {
 			err := errors.Wrap(err, "security")
-			return p.wrapLocation(ctx.File(), locator, err)
+			return p.wrapLocation(p.file(ctx), locator, err)
 		}
 		return nil
 	}
diff --git a/openapi/parser/parse_request_body.go b/openapi/parser/parse_request_body.go
index fe610377..ecf0d9a7 100644
--- a/openapi/parser/parse_request_body.go
+++ b/openapi/parser/parse_request_body.go
@@ -14,12 +14,12 @@ func (p *parser) parseRequestBody(body *ogen.RequestBody, ctx *jsonpointer.Resol
 	}
 	locator := body.Common.Locator
 	defer func() {
-		rerr = p.wrapLocation(ctx.File(), locator, rerr)
+		rerr = p.wrapLocation(p.file(ctx), locator, rerr)
 	}()
 	if ref := body.Ref; ref != "" {
 		resolved, err := p.resolveRequestBody(ref, ctx)
 		if err != nil {
-			return nil, p.wrapRef(ctx.File(), locator, err)
+			return nil, p.wrapRef(p.file(ctx), locator, err)
 		}
 		return resolved, nil
 	}
@@ -38,13 +38,13 @@ func (p *parser) parseRequestBody(body *ogen.RequestBody, ctx *jsonpointer.Resol
 		return content, nil
 	}()
 	if err != nil {
-		return nil, p.wrapField("content", ctx.File(), locator, err)
+		return nil, p.wrapField("content", p.file(ctx), locator, err)
 	}
 
 	return &openapi.RequestBody{
 		Description: body.Description,
 		Required:    body.Required,
 		Content:     content,
-		Pointer:     locator.Pointer(ctx.File()),
+		Pointer:     locator.Pointer(p.file(ctx)),
 	}, nil
 }
diff --git a/openapi/parser/parse_response.go b/openapi/parser/parse_response.go
index edd355fc..30af3e91 100644
--- a/openapi/parser/parse_response.go
+++ b/openapi/parser/parse_response.go
@@ -23,13 +23,13 @@ func (p *parser) parseResponses(
 	result := make(map[string]*openapi.Response, len(responses))
 	for status, response := range responses {
 		if err := validateStatusCode(status); err != nil {
-			return nil, p.wrapLocation(ctx.File(), locator.Key(status), err)
+			return nil, p.wrapLocation(p.file(ctx), locator.Key(status), err)
 		}
 
 		resp, err := p.parseResponse(response, ctx)
 		if err != nil {
 			err := errors.Wrap(err, status)
-			return nil, p.wrapLocation(ctx.File(), locator.Field(status), err)
+			return nil, p.wrapLocation(p.file(ctx), locator.Field(status), err)
 		}
 
 		result[status] = resp
@@ -44,12 +44,12 @@ func (p *parser) parseResponse(resp *ogen.Response, ctx *jsonpointer.ResolveCtx)
 	}
 	locator := resp.Common.Locator
 	defer func() {
-		rerr = p.wrapLocation(ctx.File(), locator, rerr)
+		rerr = p.wrapLocation(p.file(ctx), locator, rerr)
 	}()
 	if ref := resp.Ref; ref != "" {
 		resolved, err := p.resolveResponse(ref, ctx)
 		if err != nil {
-			return nil, p.wrapRef(ctx.File(), locator, err)
+			return nil, p.wrapRef(p.file(ctx), locator, err)
 		}
 		return resolved, nil
 	}
@@ -57,20 +57,20 @@ func (p *parser) parseResponse(resp *ogen.Response, ctx *jsonpointer.ResolveCtx)
 	content, err := p.parseContent(resp.Content, locator.Field("content"), ctx)
 	if err != nil {
 		err := errors.Wrap(err, "content")
-		return nil, p.wrapField("content", ctx.File(), locator, err)
+		return nil, p.wrapField("content", p.file(ctx), locator, err)
 	}
 
 	headers, err := p.parseHeaders(resp.Headers, ctx)
 	if err != nil {
 		err := errors.Wrap(err, "headers")
-		return nil, p.wrapField("headers", ctx.File(), locator, err)
+		return nil, p.wrapField("headers", p.file(ctx), locator, err)
 	}
 
 	return &openapi.Response{
 		Description: resp.Description,
 		Headers:     headers,
 		Content:     content,
-		Pointer:     locator.Pointer(ctx.File()),
+		Pointer:     locator.Pointer(p.file(ctx)),
 	}, nil
 }
 
diff --git a/openapi/parser/parse_security.go b/openapi/parser/parse_security.go
index d0c27d49..2f76dfde 100644
--- a/openapi/parser/parse_security.go
+++ b/openapi/parser/parse_security.go
@@ -22,13 +22,13 @@ func (p *parser) parseSecurityScheme(
 	}
 	locator := scheme.Common.Locator
 	defer func() {
-		rerr = p.wrapLocation(ctx.File(), locator, rerr)
+		rerr = p.wrapLocation(p.file(ctx), locator, rerr)
 	}()
 
 	if ref := scheme.Ref; ref != "" {
 		resolved, err := p.resolveSecurityScheme(ref, ctx)
 		if err != nil {
-			return nil, p.wrapRef(ctx.File(), locator, err)
+			return nil, p.wrapRef(p.file(ctx), locator, err)
 		}
 		return resolved, nil
 	}
@@ -40,11 +40,11 @@ func (p *parser) parseSecurityScheme(
 			case "query", "header", "cookie":
 			default:
 				err := errors.Errorf(`invalid "in": %q`, scheme.In)
-				return p.wrapField("in", ctx.File(), locator, err)
+				return p.wrapField("in", p.file(ctx), locator, err)
 			}
 			if scheme.Name == "" {
 				err := errors.New(`"name" is required and MUST be a non-empty string`)
-				return p.wrapField("name", ctx.File(), locator, err)
+				return p.wrapField("name", p.file(ctx), locator, err)
 			}
 			return nil
 		case "http":
@@ -66,26 +66,26 @@ func (p *parser) parseSecurityScheme(
 				"vapid":
 			default:
 				err := errors.Errorf(`invalid "scheme": %q`, scheme.Scheme)
-				return p.wrapField("scheme", ctx.File(), locator, err)
+				return p.wrapField("scheme", p.file(ctx), locator, err)
 			}
 			return nil
 		case "mutualTLS":
 			if err := p.requireMinorVersion("mutualTLS security", 1); err != nil {
-				return p.wrapField("type", ctx.File(), locator, err)
+				return p.wrapField("type", p.file(ctx), locator, err)
 			}
 			return nil
 		case "oauth2":
-			err := p.validateOAuthFlows(scheme.Flows, ctx.File())
-			return p.wrapField("flows", ctx.File(), locator, err)
+			err := p.validateOAuthFlows(scheme.Flows, p.file(ctx))
+			return p.wrapField("flows", p.file(ctx), locator, err)
 		case "openIdConnect":
 			if _, err := url.ParseRequestURI(scheme.OpenIDConnectURL); err != nil {
 				err := errors.Wrap(err, `"openIdConnectUrl" MUST be in the form of a URL`)
-				return p.wrapField("openIdConnectUrl", ctx.File(), locator, err)
+				return p.wrapField("openIdConnectUrl", p.file(ctx), locator, err)
 			}
 			return nil
 		default:
 			err := errors.Errorf("unknown security scheme type %q", scheme.Type)
-			return p.wrapField("type", ctx.File(), locator, err)
+			return p.wrapField("type", p.file(ctx), locator, err)
 		}
 	}(); err != nil {
 		return nil, errors.Wrap(err, scheme.Type)
@@ -196,14 +196,14 @@ func (p *parser) parseSecurityRequirements(
 			v, ok := p.securitySchemes[name]
 			if !ok {
 				err := errors.Errorf("unknown security scheme %q", name)
-				return nil, p.wrapLocation(ctx.File(), locator.Key(name), err)
+				return nil, p.wrapLocation(p.file(ctx), locator.Key(name), err)
 			}
 
 			spec, err := p.parseSecurityScheme(v, ctx)
 			if err != nil {
 				loc := securitySchemesLoc.Field(name)
 				err := errors.Wrapf(err, "parse security scheme %q", name)
-				return nil, p.wrapLocation(ctx.File(), loc, err)
+				return nil, p.wrapLocation(p.file(ctx), loc, err)
 			}
 
 			if len(scopes) > 0 {
@@ -213,7 +213,7 @@ func (p *parser) parseSecurityRequirements(
 					// Point to the first scope in the list.
 					locator := locator.Field(name).Index(0)
 					err := errors.Errorf(`list of scopes MUST be empty for "type" %q`, spec.Type)
-					return nil, p.wrapLocation(ctx.File(), locator, err)
+					return nil, p.wrapLocation(p.file(ctx), locator, err)
 				}
 			}
 
@@ -232,9 +232,9 @@ func (p *parser) parseSecurityRequirements(
 					In:               spec.In,
 					Scheme:           spec.Scheme,
 					BearerFormat:     spec.BearerFormat,
-					Flows:            cloneOAuthFlows(flows, ctx.File()),
+					Flows:            cloneOAuthFlows(flows, p.file(ctx)),
 					OpenIDConnectURL: spec.OpenIDConnectURL,
-					Pointer:          spec.Common.Locator.Pointer(ctx.File()),
+					Pointer:          spec.Common.Locator.Pointer(p.file(ctx)),
 				},
 			})
 		}
diff --git a/openapi/parser/parse_server.go b/openapi/parser/parse_server.go
index b34e44b2..d3bb942f 100644
--- a/openapi/parser/parse_server.go
+++ b/openapi/parser/parse_server.go
@@ -37,7 +37,7 @@ func (p *parser) parseServer(
 ) (_ openapi.Server, rerr error) {
 	locator := s.Common.Locator
 	defer func() {
-		rerr = p.wrapLocation(ctx.File(), locator, rerr)
+		rerr = p.wrapLocation(p.file(ctx), locator, rerr)
 	}()
 
 	// Validate variables.
@@ -47,7 +47,7 @@ func (p *parser) parseServer(
 			locator := locator.Field("enum")
 			if !slices.Contains(v.Enum, v.Default) {
 				err := errors.Errorf("the default value %q MUST exist in the enum's values", v.Default)
-				return openapi.Server{}, p.wrapLocation(ctx.File(), locator, err)
+				return openapi.Server{}, p.wrapLocation(p.file(ctx), locator, err)
 			}
 			for i, e := range v.Enum {
 				locator := locator.Index(i)
@@ -57,7 +57,7 @@ func (p *parser) parseServer(
 				}
 				if found != i {
 					err := errors.Errorf("enum MUST NOT contain duplicate values: %q", e)
-					return openapi.Server{}, p.wrapLocation(ctx.File(), locator, err)
+					return openapi.Server{}, p.wrapLocation(p.file(ctx), locator, err)
 				}
 			}
 		}
@@ -83,7 +83,7 @@ func (p *parser) parseServer(
 	}()
 	if err != nil {
 		locator := locator.Field("url")
-		return openapi.Server{}, p.wrapLocation(ctx.File(), locator, err)
+		return openapi.Server{}, p.wrapLocation(p.file(ctx), locator, err)
 	}
 
 	server := openapi.Server{
@@ -118,7 +118,7 @@ func (p *parser) parseServer(
 			return nil
 		}(); err != nil {
 			locator := locator.Field(nameKey)
-			return openapi.Server{}, p.wrapLocation(ctx.File(), locator, err)
+			return openapi.Server{}, p.wrapLocation(p.file(ctx), locator, err)
 		}
 	}
 
diff --git a/openapi/parser/parse_webhook.go b/openapi/parser/parse_webhook.go
index 102d481e..784752f6 100644
--- a/openapi/parser/parse_webhook.go
+++ b/openapi/parser/parse_webhook.go
@@ -18,7 +18,7 @@ func (p *parser) parseWebhook(name string, item *ogen.PathItem, ctx *jsonpointer
 	return openapi.Webhook{
 		Name:       name,
 		Operations: pi,
-		Pointer:    item.Common.Locator.Pointer(ctx.File()),
+		Pointer:    item.Common.Locator.Pointer(p.file(ctx)),
 	}, nil
 }
 
@@ -31,7 +31,7 @@ func (p *parser) parseWebhooks(webhooks map[string]*ogen.PathItem) (r []openapi.
 		ctx     = p.resolveCtx()
 	)
 	defer func() {
-		rerr = p.wrapLocation(ctx.File(), locator, rerr)
+		rerr = p.wrapLocation(p.file(ctx), locator, rerr)
 	}()
 	if err := p.requireMinorVersion("webhooks", 1); err != nil {
 		return nil, err
@@ -43,7 +43,7 @@ func (p *parser) parseWebhooks(webhooks map[string]*ogen.PathItem) (r []openapi.
 		webhook, err := p.parseWebhook(name, item, ctx)
 		if err != nil {
 			err := errors.Wrapf(err, "parse webhook %q", name)
-			return nil, p.wrapLocation(ctx.File(), locator.Field(name), err)
+			return nil, p.wrapLocation(p.file(ctx), locator.Field(name), err)
 		}
 		r = append(r, webhook)
 	}
diff --git a/openapi/parser/parser.go b/openapi/parser/parser.go
index 5bfb1eb6..3d80e52b 100644
--- a/openapi/parser/parser.go
+++ b/openapi/parser/parser.go
@@ -52,7 +52,7 @@ type parser struct {
 	rootURL    *url.URL
 	schemas    map[string]resolver
 	depthLimit int
-	file       location.File // optional, used for error messages
+	rootFile   location.File // optional, used for error messages
 
 	schemaParser *jsonschema.Parser
 }
@@ -95,7 +95,7 @@ func Parse(spec *ogen.Spec, s Settings) (_ *openapi.API, rerr error) {
 			},
 		},
 		depthLimit: s.DepthLimit,
-		file:       s.File,
+		rootFile:   s.File,
 		schemaParser: jsonschema.NewParser(jsonschema.Settings{
 			External: s.External,
 			Resolver: componentsResolver{
@@ -111,7 +111,7 @@ func Parse(spec *ogen.Spec, s Settings) (_ *openapi.API, rerr error) {
 		loc.FromNode(spec.Raw)
 		p.rootLoc.SetPosition(loc)
 		defer func() {
-			rerr = p.wrapLocation(p.file, p.rootLoc, rerr)
+			rerr = p.wrapLocation(p.rootFile, p.rootLoc, rerr)
 		}()
 	}
 
@@ -175,13 +175,13 @@ func (p *parser) parsePathItems() error {
 
 			return nil
 		}(); err != nil {
-			return p.wrapLocation(p.file, pathsLoc.Key(path), err)
+			return p.wrapLocation(p.rootFile, pathsLoc.Key(path), err)
 		}
 
 		ops, err := p.parsePathItem(path, item, p.resolveCtx())
 		if err != nil {
 			err := errors.Wrapf(err, "path %q", path)
-			return p.wrapLocation(p.file, pathsLoc.Field(path), err)
+			return p.wrapLocation(p.rootFile, pathsLoc.Field(path), err)
 		}
 		p.operations = append(p.operations, ops...)
 	}
diff --git a/openapi/parser/resolve.go b/openapi/parser/resolve.go
index 552e834f..835ffee7 100644
--- a/openapi/parser/resolve.go
+++ b/openapi/parser/resolve.go
@@ -91,7 +91,7 @@ func resolveComponent[Raw, Target any](
 		return r, true, nil
 	}
 
-	file := p.file
+	file := p.rootFile
 	var raw Raw
 	if key.Loc == "" && ctx.IsRoot() {
 		name := strings.TrimPrefix(ref, cr.prefix)
diff --git a/openapi/parser/version.go b/openapi/parser/version.go
index f0d8fb7c..8022d7d8 100644
--- a/openapi/parser/version.go
+++ b/openapi/parser/version.go
@@ -10,7 +10,7 @@ import (
 
 func (p *parser) parseVersion() (rerr error) {
 	defer func() {
-		rerr = p.wrapLocation(p.file, p.rootLoc.Field("openapi"), rerr)
+		rerr = p.wrapLocation(p.rootFile, p.rootLoc.Field("openapi"), rerr)
 	}()
 	if err := p.version.UnmarshalText([]byte(p.spec.OpenAPI)); err != nil {
 		return errors.Wrap(err, "invalid version")


Test patch:
diff --git a/cmd/ogen/main_test.go b/cmd/ogen/main_test.go
index a8390cbe..dc7a6064 100644
--- a/cmd/ogen/main_test.go
+++ b/cmd/ogen/main_test.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/go-faster/errors"
 	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zaptest"
 )
 
 type roundTripFunc func(req *http.Request) (*http.Response, error)
@@ -82,7 +83,7 @@ func Test_parseSpecPath(t *testing.T) {
 				}
 			}
 
-			f, _, err := parseSpecPath(tt.input, httpClient, readFile)
+			f, _, err := parseSpecPath(tt.input, httpClient, readFile, zaptest.NewLogger(t))
 			a.NoError(err)
 			a.Equal(tt.wantFilename, f.fileName)
 			a.Equal(tt.wantURL, f.rootURL)
diff --git a/gen/schema_gen_test.go b/gen/schema_gen_test.go
index ccaec76a..f915b180 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)