Repository: ogen-go/ogen
Pull request #237 (2022-03-10 03:01:20)

feat(gen): filter most specific content types


Patch:
diff --git a/gen/gen_contents.go b/gen/gen_contents.go
index f2654895..e73a2c44 100644
--- a/gen/gen_contents.go
+++ b/gen/gen_contents.go
@@ -1,19 +1,64 @@
 package gen
 
 import (
+	"mime"
+	"strings"
+
 	"github.com/go-faster/errors"
 
 	"github.com/ogen-go/ogen/internal/ir"
 	"github.com/ogen-go/ogen/jsonschema"
 )
 
+func filterMostSpecific(contents map[string]*jsonschema.Schema) error {
+	keep := func(current, mask string) bool {
+		for {
+			star := strings.IndexByte(mask, '*')
+			if star < 0 {
+				return true
+			}
+
+			prefix := mask[:star]
+			for contentType := range contents {
+				if contentType == current {
+					continue
+				}
+				if strings.HasPrefix(contentType, prefix) {
+					return false
+				}
+			}
+			mask = mask[star+1:]
+		}
+	}
+
+	for k := range contents {
+		contentType, _, err := mime.ParseMediaType(k)
+		if err != nil {
+			return errors.Wrapf(err, "parse content type %q", k)
+		}
+
+		if !keep(k, contentType) {
+			delete(contents, k)
+		}
+	}
+	return nil
+}
+
 func (g *Generator) generateContents(ctx *genctx, name string, optional bool, contents map[string]*jsonschema.Schema) (_ map[ir.ContentType]*ir.Type, err error) {
 	var (
 		result      = make(map[ir.ContentType]*ir.Type, len(contents))
 		unsupported []string
 	)
+	if err := filterMostSpecific(contents); err != nil {
+		return nil, errors.Wrap(err, "filter most specific")
+	}
 
 	for contentType, schema := range contents {
+		parsedContentType, _, err := mime.ParseMediaType(contentType)
+		if err != nil {
+			return nil, errors.Wrapf(err, "parse content type %q", contentType)
+		}
+
 		typeName := name
 		if len(contents) > 1 {
 			typeName, err = pascal(name, contentType)
@@ -24,7 +69,7 @@ func (g *Generator) generateContents(ctx *genctx, name string, optional bool, co
 
 		ctx := ctx.appendPath(contentType)
 		if err := func() error {
-			switch contentType {
+			switch parsedContentType {
 			case "application/json":
 				t, err := g.generateSchema(ctx, typeName, schema)
 				if err != nil {


Test patch:
diff --git a/gen/gen_contents_test.go b/gen/gen_contents_test.go
new file mode 100644
index 00000000..c498ad5f
--- /dev/null
+++ b/gen/gen_contents_test.go
@@ -0,0 +1,54 @@
+package gen
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/ogen-go/ogen/jsonschema"
+)
+
+func Test_filterMostSpecific(t *testing.T) {
+	sch := new(jsonschema.Schema)
+	list := func(input ...string) (r map[string]*jsonschema.Schema) {
+		r = map[string]*jsonschema.Schema{}
+		for _, value := range input {
+			r[value] = sch
+		}
+		return r
+	}
+
+	tests := []struct {
+		contents map[string]*jsonschema.Schema
+		expected map[string]*jsonschema.Schema
+		wantErr  bool
+	}{
+		{list("text/html", "text/*"), list("text/html"), false},
+		{list("text/*"), list("text/*"), false},
+		{list("*/*"), list("*/*"), false},
+		{list("text/html", "*/*"), list("text/html"), false},
+		{list("text/*", "*/*"), list("text/*"), false},
+		{list("text/html", "text/*", "*/*", "*"), list("text/html"), false},
+		{list("*/json", "application/json"), list("application/json"), false},
+		{list("*/json", "application/json", "text/json"), list("application/json", "text/json"), false},
+		{list("*/json", "*application/json"), list("*application/json"), false},
+		{list("application/*", "text/html"), list("application/*", "text/html"), false},
+	}
+	for i, tt := range tests {
+		tt := tt
+		t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) {
+			contents := map[string]*jsonschema.Schema{}
+			for k, v := range tt.contents {
+				contents[k] = v
+			}
+			err := filterMostSpecific(contents)
+			if tt.wantErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+				require.Equal(t, tt.expected, contents)
+			}
+		})
+	}
+}