Repository: ogen-go/ogen
Pull request #208 (2022-02-18 20:56:27)

fix(gen): allow to use schema references when mapping oneOf variants


Patch:
diff --git a/gen/schema_gen_sum.go b/gen/schema_gen_sum.go
index 05568cc8..82be136d 100644
--- a/gen/schema_gen_sum.go
+++ b/gen/schema_gen_sum.go
@@ -126,7 +126,7 @@ func (g *schemaGen) oneOf(name string, schema *jsonschema.Schema) (*ir.Type, err
 			// Explicit mapping.
 			var found bool
 			for _, s := range sum.SumOf {
-				if path.Base(s.Schema.Ref) == v {
+				if s.Schema.Ref == v || path.Base(s.Schema.Ref) == v {
 					found = true
 					sum.SumSpec.Mapping = append(sum.SumSpec.Mapping, ir.SumSpecMap{
 						Key:  k,
@@ -135,7 +135,7 @@ func (g *schemaGen) oneOf(name string, schema *jsonschema.Schema) (*ir.Type, err
 				}
 			}
 			if !found {
-				return nil, errors.Errorf("discriminator: unable to map %s to %s", k, v)
+				return nil, errors.Errorf("discriminator: unable to map %q to %q", k, v)
 			}
 		}
 		if len(sum.SumSpec.Mapping) == 0 {
diff --git a/internal/sample_api/oas_json_gen.go b/internal/sample_api/oas_json_gen.go
index fbfbe986..5b36ae09 100644
--- a/internal/sample_api/oas_json_gen.go
+++ b/internal/sample_api/oas_json_gen.go
@@ -3128,13 +3128,23 @@ func (s OneOfBugs) Encode(e *jx.Writer) {
 			s.OneOfMinusUUIDMinusIntMinusEnum.Encode(e)
 		}
 	}
+	{
+		if s.OneOfMinusMappingMinusReference.Set {
+			e.Comma()
+		}
+		if s.OneOfMinusMappingMinusReference.Set {
+			e.RawStr("\"oneOf-mapping-reference\"" + ":")
+			s.OneOfMinusMappingMinusReference.Encode(e)
+		}
+	}
 	e.ObjEnd()
 }
 
-var jsonFieldsNameOfOneOfBugs = [3]string{
+var jsonFieldsNameOfOneOfBugs = [4]string{
 	0: "issue143",
 	1: "additional-fields",
 	2: "oneOf-uuid-int-enum",
+	3: "oneOf-mapping-reference",
 }
 
 // Decode decodes OneOfBugs from json.
@@ -3176,6 +3186,16 @@ func (s *OneOfBugs) Decode(d *jx.Decoder) error {
 			}(); err != nil {
 				return errors.Wrap(err, "decode field \"oneOf-uuid-int-enum\"")
 			}
+		case "oneOf-mapping-reference":
+			if err := func() error {
+				s.OneOfMinusMappingMinusReference.Reset()
+				if err := s.OneOfMinusMappingMinusReference.Decode(d); err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"oneOf-mapping-reference\"")
+			}
 		default:
 			return d.Skip()
 		}
@@ -3219,6 +3239,308 @@ func (s *OneOfBugs) Decode(d *jx.Decoder) error {
 	return nil
 }
 
+// Encode encodes OneOfMappingReference as json.
+func (s OneOfMappingReference) Encode(e *jx.Writer) {
+	switch s.Type {
+	case OneOfMappingReferenceAOneOfMappingReference:
+		s.OneOfMappingReferenceA.Encode(e)
+	case OneOfMappingReferenceBOneOfMappingReference:
+		s.OneOfMappingReferenceB.Encode(e)
+	}
+}
+
+// Decode decodes OneOfMappingReference from json.
+func (s *OneOfMappingReference) Decode(d *jx.Decoder) error {
+	if s == nil {
+		return errors.New("invalid: unable to decode OneOfMappingReference to nil")
+	}
+	// Sum type discriminator.
+	if d.Next() != jx.Object {
+		return errors.Errorf("unexpected json type %q", d.Next())
+	}
+
+	var found bool
+	if err := d.Capture(func(d *jx.Decoder) error {
+		return d.ObjBytes(func(d *jx.Decoder, key []byte) error {
+			if found {
+				return d.Skip()
+			}
+			switch string(key) {
+			case "infoType":
+				typ, err := d.Str()
+				if err != nil {
+					return err
+				}
+				switch typ {
+				case "extended":
+					s.Type = OneOfMappingReferenceBOneOfMappingReference
+					found = true
+				case "simple":
+					s.Type = OneOfMappingReferenceAOneOfMappingReference
+					found = true
+				default:
+					return errors.Errorf("unknown type %s", typ)
+				}
+				return nil
+			}
+			return d.Skip()
+		})
+	}); err != nil {
+		return errors.Wrap(err, "capture")
+	}
+	if !found {
+		return errors.New("unable to detect sum type variant")
+	}
+	switch s.Type {
+	case OneOfMappingReferenceAOneOfMappingReference:
+		if err := s.OneOfMappingReferenceA.Decode(d); err != nil {
+			return err
+		}
+	case OneOfMappingReferenceBOneOfMappingReference:
+		if err := s.OneOfMappingReferenceB.Decode(d); err != nil {
+			return err
+		}
+	default:
+		return errors.Errorf("inferred invalid type: %s", s.Type)
+	}
+	return nil
+}
+
+// Encode implements json.Marshaler.
+func (s OneOfMappingReferenceA) Encode(e *jx.Writer) {
+	e.ObjStart()
+	var (
+		first = true
+		_     = first
+	)
+	{
+		if s.InfoType.Set {
+			if !first {
+				e.Comma()
+			}
+			first = false
+		}
+		if s.InfoType.Set {
+			e.RawStr("\"infoType\"" + ":")
+			s.InfoType.Encode(e)
+		}
+	}
+	{
+		if s.Description.Set {
+			if !first {
+				e.Comma()
+			}
+			first = false
+		}
+		if s.Description.Set {
+			e.RawStr("\"description\"" + ":")
+			s.Description.Encode(e)
+		}
+	}
+	e.ObjEnd()
+}
+
+var jsonFieldsNameOfOneOfMappingReferenceA = [2]string{
+	0: "infoType",
+	1: "description",
+}
+
+// Decode decodes OneOfMappingReferenceA from json.
+func (s *OneOfMappingReferenceA) Decode(d *jx.Decoder) error {
+	if s == nil {
+		return errors.New("invalid: unable to decode OneOfMappingReferenceA to nil")
+	}
+
+	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
+		switch string(k) {
+		case "infoType":
+			if err := func() error {
+				s.InfoType.Reset()
+				if err := s.InfoType.Decode(d); err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"infoType\"")
+			}
+		case "description":
+			if err := func() error {
+				s.Description.Reset()
+				if err := s.Description.Decode(d); err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"description\"")
+			}
+		default:
+			return d.Skip()
+		}
+		return nil
+	}); err != nil {
+		return errors.Wrap(err, "decode OneOfMappingReferenceA")
+	}
+
+	return nil
+}
+
+// Encode implements json.Marshaler.
+func (s OneOfMappingReferenceB) Encode(e *jx.Writer) {
+	e.ObjStart()
+	var (
+		first = true
+		_     = first
+	)
+	{
+		if s.InfoType.Set {
+			if !first {
+				e.Comma()
+			}
+			first = false
+		}
+		if s.InfoType.Set {
+			e.RawStr("\"infoType\"" + ":")
+			s.InfoType.Encode(e)
+		}
+	}
+	{
+		if s.Code.Set {
+			if !first {
+				e.Comma()
+			}
+			first = false
+		}
+		if s.Code.Set {
+			e.RawStr("\"code\"" + ":")
+			s.Code.Encode(e)
+		}
+	}
+	{
+		if s.Data != nil {
+			if !first {
+				e.Comma()
+			}
+			first = false
+		}
+		if s.Data != nil {
+			e.RawStr("\"data\"" + ":")
+			s.Data.Encode(e)
+		}
+	}
+	{
+		if s.Info.Set {
+			if !first {
+				e.Comma()
+			}
+			first = false
+		}
+		if s.Info.Set {
+			e.RawStr("\"info\"" + ":")
+			s.Info.Encode(e)
+		}
+	}
+	e.ObjEnd()
+}
+
+var jsonFieldsNameOfOneOfMappingReferenceB = [4]string{
+	0: "infoType",
+	1: "code",
+	2: "data",
+	3: "info",
+}
+
+// Decode decodes OneOfMappingReferenceB from json.
+func (s *OneOfMappingReferenceB) Decode(d *jx.Decoder) error {
+	if s == nil {
+		return errors.New("invalid: unable to decode OneOfMappingReferenceB to nil")
+	}
+
+	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
+		switch string(k) {
+		case "infoType":
+			if err := func() error {
+				s.InfoType.Reset()
+				if err := s.InfoType.Decode(d); err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"infoType\"")
+			}
+		case "code":
+			if err := func() error {
+				s.Code.Reset()
+				if err := s.Code.Decode(d); err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"code\"")
+			}
+		case "data":
+			if err := func() error {
+				s.Data = nil
+				var elem OneOfMappingReferenceBData
+				if err := elem.Decode(d); err != nil {
+					return err
+				}
+				s.Data = &elem
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"data\"")
+			}
+		case "info":
+			if err := func() error {
+				s.Info.Reset()
+				if err := s.Info.Decode(d); err != nil {
+					return err
+				}
+				return nil
+			}(); err != nil {
+				return errors.Wrap(err, "decode field \"info\"")
+			}
+		default:
+			return d.Skip()
+		}
+		return nil
+	}); err != nil {
+		return errors.Wrap(err, "decode OneOfMappingReferenceB")
+	}
+
+	return nil
+}
+
+// Encode implements json.Marshaler.
+func (s OneOfMappingReferenceBData) Encode(e *jx.Writer) {
+	e.ObjStart()
+	var (
+		first = true
+		_     = first
+	)
+	e.ObjEnd()
+}
+
+var jsonFieldsNameOfOneOfMappingReferenceBData = [0]string{}
+
+// Decode decodes OneOfMappingReferenceBData from json.
+func (s *OneOfMappingReferenceBData) Decode(d *jx.Decoder) error {
+	if s == nil {
+		return errors.New("invalid: unable to decode OneOfMappingReferenceBData to nil")
+	}
+
+	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
+		switch string(k) {
+		default:
+			return d.Skip()
+		}
+		return nil
+	}); err != nil {
+		return errors.Wrap(err, "decode OneOfMappingReferenceBData")
+	}
+
+	return nil
+}
+
 // Encode encodes OneOfUUIDAndIntEnum as json.
 func (s OneOfUUIDAndIntEnum) Encode(e *jx.Writer) {
 	switch s.Type {
@@ -3991,6 +4313,33 @@ func (o *OptInt) Decode(d *jx.Decoder) error {
 	}
 }
 
+// Encode encodes int32 as json.
+func (o OptInt32) Encode(e *jx.Writer) {
+	if !o.Set {
+		return
+	}
+	e.Int32(int32(o.Value))
+}
+
+// Decode decodes int32 from json.
+func (o *OptInt32) Decode(d *jx.Decoder) error {
+	if o == nil {
+		return errors.New("invalid: unable to decode OptInt32 to nil")
+	}
+	switch d.Next() {
+	case jx.Number:
+		o.Set = true
+		v, err := d.Int32()
+		if err != nil {
+			return err
+		}
+		o.Value = int32(v)
+		return nil
+	default:
+		return errors.Errorf("unexpected type %q while reading OptInt32", d.Next())
+	}
+}
+
 // Encode encodes MapWithProperties as json.
 func (o OptMapWithProperties) Encode(e *jx.Writer) {
 	if !o.Set {
@@ -4194,6 +4543,31 @@ func (o *OptNullableEnums) Decode(d *jx.Decoder) error {
 	}
 }
 
+// Encode encodes OneOfMappingReference as json.
+func (o OptOneOfMappingReference) Encode(e *jx.Writer) {
+	if !o.Set {
+		return
+	}
+	o.Value.Encode(e)
+}
+
+// Decode decodes OneOfMappingReference from json.
+func (o *OptOneOfMappingReference) Decode(d *jx.Decoder) error {
+	if o == nil {
+		return errors.New("invalid: unable to decode OptOneOfMappingReference to nil")
+	}
+	switch d.Next() {
+	case jx.String:
+		o.Set = true
+		if err := o.Value.Decode(d); err != nil {
+			return err
+		}
+		return nil
+	default:
+		return errors.Errorf("unexpected type %q while reading OptOneOfMappingReference", d.Next())
+	}
+}
+
 // Encode encodes OneOfUUIDAndIntEnum as json.
 func (o OptOneOfUUIDAndIntEnum) Encode(e *jx.Writer) {
 	if !o.Set {
diff --git a/internal/sample_api/oas_schemas_gen.go b/internal/sample_api/oas_schemas_gen.go
index f741878f..436c7c50 100644
--- a/internal/sample_api/oas_schemas_gen.go
+++ b/internal/sample_api/oas_schemas_gen.go
@@ -795,8 +795,94 @@ type OneOfBugs struct {
 	Issue143                        Issue143                    "json:\"issue143\""
 	AdditionalMinusFields           OneVariantHasNoUniqueFields "json:\"additional-fields\""
 	OneOfMinusUUIDMinusIntMinusEnum OptOneOfUUIDAndIntEnum      "json:\"oneOf-uuid-int-enum\""
+	OneOfMinusMappingMinusReference OptOneOfMappingReference    "json:\"oneOf-mapping-reference\""
 }
 
+// Ref: #/components/schemas/OneOfMappingReference
+// OneOfMappingReference represents sum type.
+type OneOfMappingReference struct {
+	Type                   OneOfMappingReferenceType // switch on this field
+	OneOfMappingReferenceA OneOfMappingReferenceA
+	OneOfMappingReferenceB OneOfMappingReferenceB
+}
+
+// OneOfMappingReferenceType is oneOf type of OneOfMappingReference.
+type OneOfMappingReferenceType string
+
+// Possible values for OneOfMappingReferenceType.
+const (
+	OneOfMappingReferenceAOneOfMappingReference OneOfMappingReferenceType = "OneOfMappingReferenceA"
+	OneOfMappingReferenceBOneOfMappingReference OneOfMappingReferenceType = "OneOfMappingReferenceB"
+)
+
+// IsOneOfMappingReferenceA reports whether OneOfMappingReference is OneOfMappingReferenceA.
+func (s OneOfMappingReference) IsOneOfMappingReferenceA() bool {
+	return s.Type == OneOfMappingReferenceAOneOfMappingReference
+}
+
+// IsOneOfMappingReferenceB reports whether OneOfMappingReference is OneOfMappingReferenceB.
+func (s OneOfMappingReference) IsOneOfMappingReferenceB() bool {
+	return s.Type == OneOfMappingReferenceBOneOfMappingReference
+}
+
+// SetOneOfMappingReferenceA sets OneOfMappingReference to OneOfMappingReferenceA.
+func (s *OneOfMappingReference) SetOneOfMappingReferenceA(v OneOfMappingReferenceA) {
+	s.Type = OneOfMappingReferenceAOneOfMappingReference
+	s.OneOfMappingReferenceA = v
+}
+
+// GetOneOfMappingReferenceA returns OneOfMappingReferenceA and true boolean if OneOfMappingReference is OneOfMappingReferenceA.
+func (s OneOfMappingReference) GetOneOfMappingReferenceA() (v OneOfMappingReferenceA, ok bool) {
+	if !s.IsOneOfMappingReferenceA() {
+		return v, false
+	}
+	return s.OneOfMappingReferenceA, true
+}
+
+// NewOneOfMappingReferenceAOneOfMappingReference returns new OneOfMappingReference from OneOfMappingReferenceA.
+func NewOneOfMappingReferenceAOneOfMappingReference(v OneOfMappingReferenceA) OneOfMappingReference {
+	var s OneOfMappingReference
+	s.SetOneOfMappingReferenceA(v)
+	return s
+}
+
+// SetOneOfMappingReferenceB sets OneOfMappingReference to OneOfMappingReferenceB.
+func (s *OneOfMappingReference) SetOneOfMappingReferenceB(v OneOfMappingReferenceB) {
+	s.Type = OneOfMappingReferenceBOneOfMappingReference
+	s.OneOfMappingReferenceB = v
+}
+
+// GetOneOfMappingReferenceB returns OneOfMappingReferenceB and true boolean if OneOfMappingReference is OneOfMappingReferenceB.
+func (s OneOfMappingReference) GetOneOfMappingReferenceB() (v OneOfMappingReferenceB, ok bool) {
+	if !s.IsOneOfMappingReferenceB() {
+		return v, false
+	}
+	return s.OneOfMappingReferenceB, true
+}
+
+// NewOneOfMappingReferenceBOneOfMappingReference returns new OneOfMappingReference from OneOfMappingReferenceB.
+func NewOneOfMappingReferenceBOneOfMappingReference(v OneOfMappingReferenceB) OneOfMappingReference {
+	var s OneOfMappingReference
+	s.SetOneOfMappingReferenceB(v)
+	return s
+}
+
+// Ref: #/components/schemas/OneOfMappingReferenceA
+type OneOfMappingReferenceA struct {
+	InfoType    OptString "json:\"infoType\""
+	Description OptString "json:\"description\""
+}
+
+// Ref: #/components/schemas/OneOfMappingReferenceB
+type OneOfMappingReferenceB struct {
+	InfoType OptString                   "json:\"infoType\""
+	Code     OptInt32                    "json:\"code\""
+	Data     *OneOfMappingReferenceBData "json:\"data\""
+	Info     OptString                   "json:\"info\""
+}
+
+type OneOfMappingReferenceBData struct{}
+
 // Ref: #/components/schemas/OneOfUUIDAndIntEnum
 // OneOfUUIDAndIntEnum represents sum type.
 type OneOfUUIDAndIntEnum struct {
@@ -1986,6 +2072,52 @@ func (o OptNullableEnums) Or(d NullableEnums) NullableEnums {
 	return d
 }
 
+// NewOptOneOfMappingReference returns new OptOneOfMappingReference with value set to v.
+func NewOptOneOfMappingReference(v OneOfMappingReference) OptOneOfMappingReference {
+	return OptOneOfMappingReference{
+		Value: v,
+		Set:   true,
+	}
+}
+
+// OptOneOfMappingReference is optional OneOfMappingReference.
+type OptOneOfMappingReference struct {
+	Value OneOfMappingReference
+	Set   bool
+}
+
+// IsSet returns true if OptOneOfMappingReference was set.
+func (o OptOneOfMappingReference) IsSet() bool { return o.Set }
+
+// Reset unsets value.
+func (o *OptOneOfMappingReference) Reset() {
+	var v OneOfMappingReference
+	o.Value = v
+	o.Set = false
+}
+
+// SetTo sets value to v.
+func (o *OptOneOfMappingReference) SetTo(v OneOfMappingReference) {
+	o.Set = true
+	o.Value = v
+}
+
+// Get returns value and boolean that denotes whether value was set.
+func (o OptOneOfMappingReference) Get() (v OneOfMappingReference, ok bool) {
+	if !o.Set {
+		return v, false
+	}
+	return o.Value, true
+}
+
+// Or returns value if set, or given parameter if does not.
+func (o OptOneOfMappingReference) Or(d OneOfMappingReference) OneOfMappingReference {
+	if v, ok := o.Get(); ok {
+		return v
+	}
+	return d
+}
+
 // NewOptOneOfUUIDAndIntEnum returns new OptOneOfUUIDAndIntEnum with value set to v.
 func NewOptOneOfUUIDAndIntEnum(v OneOfUUIDAndIntEnum) OptOneOfUUIDAndIntEnum {
 	return OptOneOfUUIDAndIntEnum{


Test patch:
diff --git a/_testdata/positive/sample.json b/_testdata/positive/sample.json
index 0554d805..7412daa4 100644
--- a/_testdata/positive/sample.json
+++ b/_testdata/positive/sample.json
@@ -1286,6 +1286,56 @@
           }
         ]
       },
+      "OneOfMappingReference": {
+        "type": "object",
+        "oneOf": [
+          {
+            "$ref": "#/components/schemas/OneOfMappingReferenceA"
+          },
+          {
+            "$ref": "#/components/schemas/OneOfMappingReferenceB"
+          }
+        ],
+        "discriminator": {
+          "propertyName": "infoType",
+          "mapping": {
+            "simple": "#/components/schemas/OneOfMappingReferenceA",
+            "extended": "#/components/schemas/OneOfMappingReferenceB"
+          }
+        }
+      },
+      "OneOfMappingReferenceA": {
+        "type": "object",
+        "properties": {
+          "infoType": {
+            "type": "string"
+          },
+          "description": {
+            "type": "string"
+          }
+        }
+      },
+      "OneOfMappingReferenceB": {
+        "type": "object",
+        "properties": {
+          "infoType": {
+            "type": "string"
+          },
+          "code": {
+            "type": "integer",
+            "format": "int32"
+          },
+          "data": {
+            "type": "object",
+            "not": {
+              "type": "integer"
+            }
+          },
+          "info": {
+            "type": "string"
+          }
+        }
+      },
       "AnyOfTest": {
         "description": "Type for testing some anyOf cases from Jaeger operator API schema",
         "required": [
@@ -1369,6 +1419,9 @@
           },
           "oneOf-uuid-int-enum": {
             "$ref": "#/components/schemas/OneOfUUIDAndIntEnum"
+          },
+          "oneOf-mapping-reference": {
+            "$ref": "#/components/schemas/OneOfMappingReference"
           }
         }
       },