Repository: ogen-go/ogen
Pull request #268 (2022-03-29 17:28:54)

test: build generated testing schemas


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ad234d4b..555b2711 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -50,7 +50,10 @@ jobs:
             test-${{ runner.os }}-go-
 
       - name: Run tests
-        run: make test
+        env:
+          GOARCH: ${{ matrix.arch }}
+          GOFLAGS: ${{ matrix.flags }}
+        run: go test --timeout 15m ./...
 
   test-examples:
     runs-on: ubuntu-latest
diff --git a/cmd/jschemagen/main.go b/cmd/jschemagen/main.go
index 1747dbc2..4bce7923 100644
--- a/cmd/jschemagen/main.go
+++ b/cmd/jschemagen/main.go
@@ -2,30 +2,12 @@ package main
 
 import (
 	"flag"
-	"go/format"
 	"os"
-	"path/filepath"
 
 	"github.com/ogen-go/ogen/gen"
+	"github.com/ogen-go/ogen/gen/genfs"
 )
 
-type formattedSource struct {
-	Format bool
-	Root   string
-}
-
-func (t formattedSource) WriteFile(name string, content []byte) error {
-	out := content
-	if t.Format {
-		buf, err := format.Source(content)
-		if err != nil {
-			return err
-		}
-		out = buf
-	}
-	return os.WriteFile(filepath.Join(t.Root, name), out, 0600)
-}
-
 func main() {
 	var (
 		specPath    = flag.String("schema", "", "Path to openapi spec file")
@@ -44,7 +26,7 @@ func main() {
 		panic(err)
 	}
 
-	fs := formattedSource{
+	fs := genfs.FormattedSource{
 		Root:   "./",
 		Format: *performFormat,
 	}
diff --git a/cmd/ogen/main.go b/cmd/ogen/main.go
index 373c760f..bdc54cc4 100644
--- a/cmd/ogen/main.go
+++ b/cmd/ogen/main.go
@@ -4,7 +4,6 @@ package main
 import (
 	"flag"
 	"fmt"
-	"go/format"
 	"os"
 	"path/filepath"
 	"regexp"
@@ -12,25 +11,9 @@ import (
 
 	"github.com/ogen-go/ogen"
 	"github.com/ogen-go/ogen/gen"
+	"github.com/ogen-go/ogen/gen/genfs"
 )
 
-type formattedSource struct {
-	Format bool
-	Root   string
-}
-
-func (t formattedSource) WriteFile(name string, content []byte) error {
-	out := content
-	if t.Format {
-		buf, err := format.Source(content)
-		if err != nil {
-			return err
-		}
-		out = buf
-	}
-	return os.WriteFile(filepath.Join(t.Root, name), out, 0600)
-}
-
 func main() {
 	var (
 		specPath          = flag.String("schema", "", "Path to openapi spec file")
@@ -90,7 +73,7 @@ func main() {
 		}
 	}
 
-	fs := formattedSource{
+	fs := genfs.FormattedSource{
 		Root:   *targetDir,
 		Format: *performFormat,
 	}
diff --git a/examples/go.mod b/examples/go.mod
index 470eb925..d0f3adda 100644
--- a/examples/go.mod
+++ b/examples/go.mod
@@ -8,9 +8,9 @@ require (
 	github.com/google/uuid v1.3.0
 	github.com/ogen-go/ogen v0.0.0
 	github.com/stretchr/testify v1.7.1
-	go.opentelemetry.io/otel v1.6.1
+	go.opentelemetry.io/otel v1.6.0
 	go.opentelemetry.io/otel/metric v0.28.0
-	go.opentelemetry.io/otel/trace v1.6.1
+	go.opentelemetry.io/otel/trace v1.6.0
 )
 
 require (
diff --git a/examples/go.sum b/examples/go.sum
index 9298e02b..b7da2199 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -39,14 +39,12 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
 github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4=
 github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0=
 github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+go.opentelemetry.io/otel v1.6.0 h1:YV6GkGe/Ag2PKsm4rjlqdSNs0w0A5ZzxeGkxhx1T+t4=
 go.opentelemetry.io/otel v1.6.0/go.mod h1:bfJD2DZVw0LBxghOTlgnlI0CV3hLDu9XF/QKOUXMTQQ=
-go.opentelemetry.io/otel v1.6.1 h1:6r1YrcTenBvYa1x491d0GGpTVBsNECmrc/K6b+zDeis=
-go.opentelemetry.io/otel v1.6.1/go.mod h1:blzUabWHkX6LJewxvadmzafgh/wnvBSDBdOuwkAtrWQ=
 go.opentelemetry.io/otel/metric v0.28.0 h1:o5YNh+jxACMODoAo1bI7OES0RUW4jAMae0Vgs2etWAQ=
 go.opentelemetry.io/otel/metric v0.28.0/go.mod h1:TrzsfQAmQaB1PDcdhBauLMk7nyyg9hm+GoQq/ekE9Iw=
+go.opentelemetry.io/otel/trace v1.6.0 h1:NDzPermp9ISkhxIaJXjBTi2O60xOSHDHP/EezjOL2wo=
 go.opentelemetry.io/otel/trace v1.6.0/go.mod h1:qs7BrU5cZ8dXQHBGxHMOxwME/27YH2qEp4/+tZLLwJE=
-go.opentelemetry.io/otel/trace v1.6.1 h1:f8c93l5tboBYZna1nWk0W9DYyMzJXDWdZcJZ0Kb400U=
-go.opentelemetry.io/otel/trace v1.6.1/go.mod h1:RkFRM1m0puWIq10oxImnGEduNBzxiN7TXluRBtE+5j0=
 golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
diff --git a/gen/_template/param_dec.tmpl b/gen/_template/param_dec.tmpl
index 248456b4..d3a82e76 100644
--- a/gen/_template/param_dec.tmpl
+++ b/gen/_template/param_dec.tmpl
@@ -56,8 +56,11 @@ func decode{{ $op.Name }}Params(args [{{ $op.PathParamsCount }}]string, r *http.
 		} {{- end }}
 	}
 	{{- else if $p.Spec.In.Path }}
+	{{- /* If we cannot find such path parameter, defined path parameter is not used */ -}}
+	{{- $idx := $op.PathParamIndex $p.Spec.Name }}
+	{{- if ge $idx 0 }}
 	{
-		param := args[{{ $op.PathParamIndex $p.Spec.Name }}]
+		param := args[{{ $idx }}]
 		if len(param) > 0 {
 			d := uri.NewPathDecoder(uri.PathDecoderConfig{
 				Param: {{ quote $p.Spec.Name}},
@@ -75,6 +78,9 @@ func decode{{ $op.Name }}Params(args [{{ $op.PathParamsCount }}]string, r *http.
 			return params, errors.New({{ quote $errNotSpecified }})
 		} {{ end }}
 	}
+	{{- else }}
+	// Not used.
+	{{- end }}
 	{{- else if $p.Spec.In.Header }}
 	{
 		param := r.Header.Get({{ quote $p.Spec.Name }})
diff --git a/gen/_template/uri/encode.tmpl b/gen/_template/uri/encode.tmpl
index 9a3144bc..9c97764a 100644
--- a/gen/_template/uri/encode.tmpl
+++ b/gen/_template/uri/encode.tmpl
@@ -1,4 +1,4 @@
-{{- /*gotype: github.com/ogen-go/ogen/internal/ir.Type*/ -}}
+{{- /*gotype: github.com/ogen-go/ogen/gen.Elem*/ -}}
 {{- define "uri/encode" }}
 {{- $t := $.Type }}
 {{- $var := $.Var }}
diff --git a/gen/genfs/fmtfs.go b/gen/genfs/fmtfs.go
new file mode 100644
index 00000000..b2a22876
--- /dev/null
+++ b/gen/genfs/fmtfs.go
@@ -0,0 +1,12 @@
+package genfs
+
+import "go/format"
+
+// CheckFS is in-memory gen.FileSystem implementation for checking Go sources.
+type CheckFS struct{}
+
+// WriteFile implements gen.FileSystem.
+func (n CheckFS) WriteFile(baseName string, source []byte) error {
+	_, err := format.Source(source)
+	return err
+}
diff --git a/gen/genfs/genfs.go b/gen/genfs/genfs.go
new file mode 100644
index 00000000..180c36c7
--- /dev/null
+++ b/gen/genfs/genfs.go
@@ -0,0 +1,27 @@
+// Package genfs contains gen.FileSystem implementations.
+package genfs
+
+import (
+	"go/format"
+	"os"
+	"path/filepath"
+)
+
+// FormattedSource is gen.FileSystem implementation that format and writes Go sources.
+type FormattedSource struct {
+	Format bool
+	Root   string
+}
+
+// WriteFile implements gen.FileSystem.
+func (t FormattedSource) WriteFile(name string, content []byte) error {
+	out := content
+	if t.Format {
+		buf, err := format.Source(content)
+		if err != nil {
+			return err
+		}
+		out = buf
+	}
+	return os.WriteFile(filepath.Join(t.Root, name), out, 0600)
+}
diff --git a/go.mod b/go.mod
index f71ed9a5..52a01067 100644
--- a/go.mod
+++ b/go.mod
@@ -9,9 +9,9 @@ require (
 	github.com/google/uuid v1.3.0
 	github.com/stretchr/testify v1.7.1
 	github.com/valyala/fasthttp v1.34.0
-	go.opentelemetry.io/otel v1.6.1
+	go.opentelemetry.io/otel v1.6.0
 	go.opentelemetry.io/otel/metric v0.28.0
-	go.opentelemetry.io/otel/trace v1.6.1
+	go.opentelemetry.io/otel/trace v1.6.0
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 )
 
diff --git a/go.sum b/go.sum
index ecb4db28..ce814164 100644
--- a/go.sum
+++ b/go.sum
@@ -39,14 +39,12 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
 github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4=
 github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0=
 github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+go.opentelemetry.io/otel v1.6.0 h1:YV6GkGe/Ag2PKsm4rjlqdSNs0w0A5ZzxeGkxhx1T+t4=
 go.opentelemetry.io/otel v1.6.0/go.mod h1:bfJD2DZVw0LBxghOTlgnlI0CV3hLDu9XF/QKOUXMTQQ=
-go.opentelemetry.io/otel v1.6.1 h1:6r1YrcTenBvYa1x491d0GGpTVBsNECmrc/K6b+zDeis=
-go.opentelemetry.io/otel v1.6.1/go.mod h1:blzUabWHkX6LJewxvadmzafgh/wnvBSDBdOuwkAtrWQ=
 go.opentelemetry.io/otel/metric v0.28.0 h1:o5YNh+jxACMODoAo1bI7OES0RUW4jAMae0Vgs2etWAQ=
 go.opentelemetry.io/otel/metric v0.28.0/go.mod h1:TrzsfQAmQaB1PDcdhBauLMk7nyyg9hm+GoQq/ekE9Iw=
+go.opentelemetry.io/otel/trace v1.6.0 h1:NDzPermp9ISkhxIaJXjBTi2O60xOSHDHP/EezjOL2wo=
 go.opentelemetry.io/otel/trace v1.6.0/go.mod h1:qs7BrU5cZ8dXQHBGxHMOxwME/27YH2qEp4/+tZLLwJE=
-go.opentelemetry.io/otel/trace v1.6.1 h1:f8c93l5tboBYZna1nWk0W9DYyMzJXDWdZcJZ0Kb400U=
-go.opentelemetry.io/otel/trace v1.6.1/go.mod h1:RkFRM1m0puWIq10oxImnGEduNBzxiN7TXluRBtE+5j0=
 golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
diff --git a/internal/ir/template_helpers.go b/internal/ir/template_helpers.go
index ff5966a2..adb5bc49 100644
--- a/internal/ir/template_helpers.go
+++ b/internal/ir/template_helpers.go
@@ -20,6 +20,8 @@ func (t *Type) EncodeFn() string {
 		return t.Item.EncodeFn() + "Array"
 	}
 	switch t.Primitive {
+	case ByteSlice:
+		return "Bytes"
 	case Int, Int64, Int32, String, Bool, Float32, Float64:
 		return capitalize.Capitalize(t.Primitive.String())
 	case UUID, Time, IP, Duration, URL:
@@ -30,17 +32,19 @@ func (t *Type) EncodeFn() string {
 }
 
 func (t Type) ToString() string {
-	if t.EncodeFn() == "" {
-		return ""
+	encodeFn := t.EncodeFn()
+	if encodeFn == "" {
+		panic(fmt.Sprintf("unexpected %+v", t))
 	}
-	return t.EncodeFn() + "ToString"
+	return encodeFn + "ToString"
 }
 
 func (t Type) FromString() string {
-	if t.EncodeFn() == "" {
-		return ""
+	encodeFn := t.EncodeFn()
+	if encodeFn == "" {
+		panic(fmt.Sprintf("unexpected %+v", t))
 	}
-	return "To" + t.EncodeFn()
+	return "To" + encodeFn
 }
 
 func (t *Type) IsInteger() bool {
diff --git a/internal/oas/parser/resolver.go b/internal/oas/parser/resolver.go
index c2ce2af5..8bc8141d 100644
--- a/internal/oas/parser/resolver.go
+++ b/internal/oas/parser/resolver.go
@@ -17,7 +17,7 @@ type componentsResolver struct {
 func (c componentsResolver) ResolveReference(ref string) (*jsonschema.RawSchema, error) {
 	const prefix = "#/components/schemas/"
 	if !strings.HasPrefix(ref, prefix) {
-		return nil, errors.Errorf("invalid schema reference %q", ref)
+		return nil, errors.New("invalid schema reference")
 	}
 
 	name := strings.TrimPrefix(ref, prefix)
diff --git a/jsonpointer/jsonpointer.go b/jsonpointer/jsonpointer.go
index 31034d60..f0fdab4d 100644
--- a/jsonpointer/jsonpointer.go
+++ b/jsonpointer/jsonpointer.go
@@ -73,7 +73,7 @@ func find(ptr string, buf []byte) ([]byte, error) {
 				return errors.Wrapf(err, "find index %q", part)
 			}
 		default:
-			return errors.Errorf("unexpected %s", tt)
+			return errors.Errorf("unexpected type %q", tt)
 		}
 		if !ok {
 			return &NotFoundError{Pointer: ptr}
@@ -130,7 +130,7 @@ func findKey(d *jx.Decoder, part string) (result []byte, ok bool, _ error) {
 	}); err != nil {
 		return nil, false, err
 	}
-	return result, true, nil
+	return result, ok, nil
 }
 
 var (
diff --git a/jsonschema/parser.go b/jsonschema/parser.go
index f6b947af..e269d54a 100644
--- a/jsonschema/parser.go
+++ b/jsonschema/parser.go
@@ -76,7 +76,7 @@ func (p *Parser) parseSchema(schema *RawSchema, ctx resolveCtx, hook func(*Schem
 	if ref := schema.Ref; ref != "" {
 		s, err := p.resolve(ref, ctx)
 		if err != nil {
-			return nil, errors.Wrapf(err, "reference %q", ref)
+			return nil, errors.Wrapf(err, "resolve %q", ref)
 		}
 		return s, nil
 	}
@@ -316,13 +316,13 @@ func (p *Parser) resolve(ref string, ctx resolveCtx) (*Schema, error) {
 	}
 
 	if _, ok := ctx[ref]; ok {
-		return nil, errors.Errorf("infinite recursion: %q", ref)
+		return nil, errors.New("infinite recursion")
 	}
 	ctx[ref] = struct{}{}
 
 	raw, err := p.resolver.ResolveReference(ref)
 	if err != nil {
-		return nil, errors.Wrapf(err, "resolve reference %q", ref)
+		return nil, errors.Wrap(err, "find schema")
 	}
 
 	return p.parse1(raw, ctx, func(s *Schema) *Schema {
diff --git a/tools/sgcollector/worker.go b/tools/sgcollector/worker.go
index a8f54ced..362e1948 100644
--- a/tools/sgcollector/worker.go
+++ b/tools/sgcollector/worker.go
@@ -5,7 +5,6 @@ import (
 	"context"
 	"crypto/sha256"
 	"fmt"
-	"go/format"
 	"sort"
 	"strings"
 
@@ -15,6 +14,7 @@ import (
 
 	"github.com/ogen-go/ogen"
 	"github.com/ogen-go/ogen/gen"
+	"github.com/ogen-go/ogen/gen/genfs"
 )
 
 var errPanic = errors.New("panic")
@@ -93,11 +93,12 @@ func (p *GenerateError) Error() string {
 	return fmt.Sprintf("%s: %s", p.stage, p.err)
 }
 
-type fmtFs struct{}
+type errFs struct {
+	genfs.CheckFS
+}
 
-func (n fmtFs) WriteFile(baseName string, source []byte) error {
-	_, err := format.Source(source)
-	if err != nil {
+func (n errFs) WriteFile(baseName string, source []byte) error {
+	if err := n.CheckFS.WriteFile(baseName, source); err != nil {
 		return &GenerateError{stage: Format, err: err}
 	}
 	return nil
@@ -145,7 +146,7 @@ func generate(data []byte, isYAML bool) error {
 		return &GenerateError{stage: BuildIR, notImpl: notImpl, err: err}
 	}
 
-	if err := g.WriteSource(fmtFs{}, "api"); err != nil {
+	if err := g.WriteSource(errFs{}, "api"); err != nil {
 		var pse *GenerateError
 		if errors.As(err, &pse) {
 			return err


Test patch:
diff --git a/gen/gen_schema_test.go b/gen/gen_schema_test.go
index 4ac97d11..a23034ef 100644
--- a/gen/gen_schema_test.go
+++ b/gen/gen_schema_test.go
@@ -2,21 +2,14 @@ package gen
 
 import (
 	"embed"
-	"go/format"
 	"io/fs"
 	"path/filepath"
 	"testing"
 
 	"github.com/stretchr/testify/require"
-)
-
-// TODO: Create validationFs.
-type fmtFs struct{}
 
-func (n fmtFs) WriteFile(baseName string, source []byte) error {
-	_, err := format.Source(source)
-	return err
-}
+	"github.com/ogen-go/ogen/gen/genfs"
+)
 
 //go:embed _testdata/jsonschema
 var testdata embed.FS
@@ -35,7 +28,7 @@ func TestGenerateSchema(t *testing.T) {
 		t.Run(file, func(t *testing.T) {
 			require.NoError(t, GenerateSchema(
 				input,
-				fmtFs{},
+				genfs.CheckFS{},
 				"Type",
 				"output.go",
 				"output",
diff --git a/gen_test.go b/gen_test.go
index dd4289b1..bedd87ec 100644
--- a/gen_test.go
+++ b/gen_test.go
@@ -2,8 +2,9 @@ package ogen_test
 
 import (
 	"embed"
-	"go/format"
 	"io/fs"
+	"os"
+	"os/exec"
 	"path/filepath"
 	"strings"
 	"testing"
@@ -12,19 +13,12 @@ import (
 
 	"github.com/ogen-go/ogen"
 	"github.com/ogen-go/ogen/gen"
+	"github.com/ogen-go/ogen/gen/genfs"
 )
 
 //go:embed _testdata
 var testdata embed.FS
 
-// TODO: Create validationFs.
-type fmtFs struct{}
-
-func (n fmtFs) WriteFile(baseName string, source []byte) error {
-	_, err := format.Source(source)
-	return err
-}
-
 func testGenerate(t *testing.T, name string, ignore ...string) {
 	t.Helper()
 
@@ -37,6 +31,18 @@ func testGenerate(t *testing.T, name string, ignore ...string) {
 		InferSchemaType:      true,
 	}
 	t.Run("Gen", func(t *testing.T) {
+		temp := t.TempDir()
+		do := func(args ...string) {
+			e := exec.Command("go", args...)
+			e.Dir = temp
+			e.Stdout = os.Stdout
+			stderr := &strings.Builder{}
+			e.Stderr = stderr
+
+			err := e.Run()
+			require.NoError(t, err, stderr.String())
+		}
+
 		defer func() {
 			if rr := recover(); rr != nil {
 				t.Fatalf("panic: %+v", rr)
@@ -46,7 +52,17 @@ func testGenerate(t *testing.T, name string, ignore ...string) {
 		g, err := gen.NewGenerator(spec, opt)
 		require.NoError(t, err)
 
-		require.NoError(t, g.WriteSource(fmtFs{}, "api"))
+		if testing.Short() {
+			require.NoError(t, g.WriteSource(genfs.CheckFS{}, "api"))
+			return
+		}
+		require.NoError(t, g.WriteSource(genfs.FormattedSource{
+			Format: true,
+			Root:   temp,
+		}, "api"))
+		do("mod", "init", "check")
+		do("mod", "tidy", "-v")
+		do("build", "./")
 	})
 	if len(opt.IgnoreNotImplemented) > 0 {
 		t.Run("Full", func(t *testing.T) {
@@ -102,21 +118,23 @@ func TestGenerate(t *testing.T) {
 		},
 	}
 
-	if err := fs.WalkDir(testdata, "_testdata/positive", func(path string, d fs.DirEntry, err error) error {
+	testDataPath := "_testdata/positive"
+	if err := fs.WalkDir(testdata, testDataPath, func(path string, d fs.DirEntry, err error) error {
 		if err != nil || d == nil || d.IsDir() {
 			return err
 		}
-		_, file := filepath.Split(path)
 
+		_, file := filepath.Split(path)
 		skip, ok := skipSets[file]
 		if !ok {
 			skip = []string{"all"}
 		}
 
-		file = strings.TrimSuffix(file, ".json")
-		file = strings.TrimSuffix(file, ".yml")
-		file = strings.TrimSuffix(file, ".yaml")
-		t.Run(file, g(path, skip...))
+		testName := strings.TrimPrefix(path, testDataPath+"/")
+		testName = strings.TrimSuffix(testName, ".json")
+		testName = strings.TrimSuffix(testName, ".yml")
+		testName = strings.TrimSuffix(testName, ".yaml")
+		t.Run(testName, g(path, skip...))
 		return nil
 	}); err != nil {
 		t.Fatal(err)