diff --git a/cmd/ogen/main.go b/cmd/ogen/main.go index 009b149bf..7c5d08231 100644 --- a/cmd/ogen/main.go +++ b/cmd/ogen/main.go @@ -114,6 +114,7 @@ func run() error { if flag.NArg() == 0 || specPath == "" { return errors.New("no spec provided") } + specPath = filepath.Clean(specPath) switch files, err := os.ReadDir(*targetDir); { case os.IsNotExist(err): @@ -138,6 +139,7 @@ func run() error { _ = logger.Sync() }() + _, fileName := filepath.Split(specPath) opts := gen.Options{ NoClient: *noClient, NoServer: *noServer, @@ -146,6 +148,7 @@ func run() error { SkipUnimplemented: *skipUnimplemented, InferSchemaType: *inferTypes, AllowRemote: *allowRemote, + Filename: fileName, Filters: gen.Filters{ PathRegex: filterPath, Methods: filterMethods, diff --git a/gen/generator.go b/gen/generator.go index d2841555c..c58f40a8e 100644 --- a/gen/generator.go +++ b/gen/generator.go @@ -49,6 +49,10 @@ type Options struct { AllowRemote bool // Remote is remote reference resolver options. Remote RemoteOptions + // Filename is a name of the spec file. + // + // Used for error messages. + Filename string // Filters contains filters to skip operations. Filters Filters // IgnoreNotImplemented contains ErrNotImplemented messages to ignore. @@ -98,6 +102,7 @@ func NewGenerator(spec *ogen.Spec, opts Options) (*Generator, error) { } api, err := parser.Parse(spec, parser.Settings{ External: external, + Filename: opts.Filename, InferTypes: opts.InferSchemaType, }) if err != nil { diff --git a/gen_test.go b/gen_test.go index 14b113fbd..326b50653 100644 --- a/gen_test.go +++ b/gen_test.go @@ -119,11 +119,14 @@ func TestGenerate(t *testing.T) { func TestNegative(t *testing.T) { walkTestdata(t, "_testdata/negative", func(t *testing.T, file string, data []byte) { a := require.New(t) + _, name := path.Split(file) spec, err := ogen.Parse(data) a.NoError(err) - _, err = gen.NewGenerator(spec, gen.Options{}) + _, err = gen.NewGenerator(spec, gen.Options{ + Filename: name, + }) a.Error(err) t.Logf("%+v", err) }) diff --git a/json/location.go b/json/location.go index 68cb5e4d1..ba631d47a 100644 --- a/json/location.go +++ b/json/location.go @@ -46,7 +46,7 @@ func (l Location) String() string { if l.Line == 0 { return strconv.Quote(l.JSONPointer) } - return fmt.Sprintf("line %d:%d", l.Line, l.Column) + return fmt.Sprintf("%d:%d", l.Line, l.Column) } // Locatable is an interface for JSON value location store. diff --git a/openapi/parser/errors.go b/openapi/parser/errors.go index 1040c90fe..bb9df54ec 100644 --- a/openapi/parser/errors.go +++ b/openapi/parser/errors.go @@ -16,8 +16,9 @@ var _ interface { // LocationError is a wrapper for an error that has a location. type LocationError struct { - loc ogenjson.Location - err error + file string + loc ogenjson.Location + err error } // Unwrap implements errors.Wrapper. @@ -25,15 +26,33 @@ 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", e.loc) + p.Printf("at %s%s", e.fileName(), e.loc) return e.err } // Error implements error. func (e *LocationError) Error() string { - return fmt.Sprintf("at %s: %s", e.loc, e.err) + return fmt.Sprintf("at %s%s: %s", e.fileName(), e.loc, e.err) } func (p *parser) wrapLocation(l ogenjson.Locatable, err error) error { @@ -45,7 +64,8 @@ func (p *parser) wrapLocation(l ogenjson.Locatable, err error) error { return err } return &LocationError{ - loc: loc, - err: err, + file: p.filename, + loc: loc, + err: err, } } diff --git a/openapi/parser/parser.go b/openapi/parser/parser.go index 833b5ac63..341d760f8 100644 --- a/openapi/parser/parser.go +++ b/openapi/parser/parser.go @@ -26,6 +26,7 @@ type parser struct { external jsonschema.ExternalResolver schemas map[string][]byte depthLimit int + filename string // optional, used for error messages schemaParser *jsonschema.Parser } @@ -58,6 +59,7 @@ func Parse(spec *ogen.Spec, s Settings) (*openapi.API, error) { "": spec.Raw, }, depthLimit: s.DepthLimit, + filename: s.Filename, schemaParser: jsonschema.NewParser(jsonschema.Settings{ External: s.External, Resolver: componentsResolver{ diff --git a/openapi/parser/resolve.go b/openapi/parser/resolve.go index 349651906..dead26efd 100644 --- a/openapi/parser/resolve.go +++ b/openapi/parser/resolve.go @@ -34,7 +34,6 @@ func resolvePointer(root []byte, ptr string, to interface{}) error { if err != nil { return err } - return json.Unmarshal(data, to) } diff --git a/openapi/parser/settings.go b/openapi/parser/settings.go index 2450b89ec..1bb6c5280 100644 --- a/openapi/parser/settings.go +++ b/openapi/parser/settings.go @@ -7,6 +7,11 @@ type Settings struct { // External is external JSON Schema resolver. If nil, NoExternal resolver is used. External jsonschema.ExternalResolver + // Filename is a name of the file being parsed. + // + // Used for error messages. + Filename string + // DepthLimit limits the number of nested references. Default is 1000. DepthLimit int