Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C#] Bug: OneOf used inside other OneOf causes duplicated fields in composed type #4478

Open
fzzle opened this issue Apr 10, 2024 · 5 comments
Labels
generator Issues or improvements relater to generation capabilities. type:bug A broken experience
Milestone

Comments

@fzzle
Copy link

fzzle commented Apr 10, 2024

When I generate a client with the following schemas (a OneOf within a OneOf) it duplicates the properties for inner OneOf model.

This is an example of the input I give to Kiota:

{
  "openapi": "3.0.1",
  "info": {
    "title": "Umbraco Delivery API",
    "description": "",
    "version": "Latest"
  },
  "paths": {
    "/umbraco/delivery/api/v1/content/item/{path}": {
      "get": {
        "tags": ["Content"],
        "operationId": "GetContentItemByPath",
        "parameters": [
          {
            "name": "path",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "default": ""
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResponseModelBase"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ElementModel1": {
        "type": "object",
        "properties": {
          "contentType": {
            "type": "string",
            "readOnly": true
          }
        },
        "additionalProperties": false
      },
      "ElementModel2": {
        "type": "object",
        "properties": {
          "contentType": {
            "type": "string",
            "readOnly": true
          }
        },
        "additionalProperties": false
      },
      "ResponseModel2": {
        "type": "object",
        "properties": {
          "contentType": {
            "type": "string",
            "readOnly": true
          },
          "first": {
            "$ref": "#/components/schemas/ElementModelBase"
          },
          "second": {
            "$ref": "#/components/schemas/ElementModelBase"
          },
          "third": {
            "$ref": "#/components/schemas/ElementModelBase"
          }
        },
        "additionalProperties": false
      },
      "ResponseModel1": {
        "type": "object",
        "properties": {
          "contentType": {
            "type": "string",
            "readOnly": true
          },
          "first": {
            "$ref": "#/components/schemas/ElementModelBase"
          },
          "second": {
            "$ref": "#/components/schemas/ElementModelBase"
          },
          "third": {
            "$ref": "#/components/schemas/ElementModelBase"
          }
        },
        "additionalProperties": false
      },
      "ResponseModelBase": {
        "type": "object",
        "oneOf": [
          {
            "$ref": "#/components/schemas/ResponseModel1"
          },
          {
            "$ref": "#/components/schemas/ResponseModel2"
          }
        ],
        "additionalProperties": false,
        "discriminator": {
          "propertyName": "contentType",
          "mapping": {
            "a": "#/components/schemas/ResponseModel1",
            "b": "#/components/schemas/ResponseModel2"
          }
        }
      },
      "ElementModelBase": {
        "type": "object",
        "oneOf": [
          {
            "$ref": "#/components/schemas/ElementModel1"
          },
          {
            "$ref": "#/components/schemas/ElementModel2"
          }
        ],
        "additionalProperties": false,
        "discriminator": {
          "propertyName": "contentType",
          "mapping": {
            "c": "#/components/schemas/ElementModel1",
            "d": "#/components/schemas/ElementModel2"
          }
        }
      }
    }
  }
}

And this is the generated ElementModelBase:

// <auto-generated/>
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System;
namespace Namespace.Models {
    /// <summary>
    /// Composed type wrapper for classes <see cref="ElementModel1"/>, <see cref="ElementModel2"/>
    /// </summary>
    public class ElementModelBase : IComposedTypeWrapper, IParsable 
    {
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel1? ElementModel1 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel1 ElementModel1 { get; set; }
#endif
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel2? ElementModel2 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel2 ElementModel2 { get; set; }
#endif
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel1? ElementModelBaseElementModel1 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel1 ElementModelBaseElementModel1 { get; set; }
#endif
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel1? ElementModelBaseElementModel10 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel1 ElementModelBaseElementModel10 { get; set; }
#endif
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel1? ElementModelBaseElementModel11 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel1 ElementModelBaseElementModel11 { get; set; }
#endif
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel1? ElementModelBaseElementModel12 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel1 ElementModelBaseElementModel12 { get; set; }
#endif
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel1? ElementModelBaseElementModel13 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel1 ElementModelBaseElementModel13 { get; set; }
#endif
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel2? ElementModelBaseElementModel2 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel2 ElementModelBaseElementModel2 { get; set; }
#endif
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel2? ElementModelBaseElementModel20 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel2 ElementModelBaseElementModel20 { get; set; }
#endif
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel2? ElementModelBaseElementModel21 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel2 ElementModelBaseElementModel21 { get; set; }
#endif
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel2? ElementModelBaseElementModel22 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel2 ElementModelBaseElementModel22 { get; set; }
#endif
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel2? ElementModelBaseElementModel23 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel2 ElementModelBaseElementModel23 { get; set; }
#endif
        /// <summary>
        /// Creates a new instance of the appropriate class based on discriminator value
        /// </summary>
        /// <returns>A <see cref="ElementModelBase"/></returns>
        /// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
        public static ElementModelBase CreateFromDiscriminatorValue(IParseNode parseNode)
        {
            _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode));
            var mappingValue = parseNode.GetChildNode("contentType")?.GetStringValue();
            var result = new ElementModelBase();
            if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModel1 = new Namespace.Models.ElementModel1();
            }
            else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModel2 = new Namespace.Models.ElementModel2();
            }
            else if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModelBaseElementModel1 = new Namespace.Models.ElementModel1();
            }
            else if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModelBaseElementModel10 = new Namespace.Models.ElementModel1();
            }
            else if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModelBaseElementModel11 = new Namespace.Models.ElementModel1();
            }
            else if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModelBaseElementModel12 = new Namespace.Models.ElementModel1();
            }
            else if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModelBaseElementModel13 = new Namespace.Models.ElementModel1();
            }
            else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModelBaseElementModel2 = new Namespace.Models.ElementModel2();
            }
            else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModelBaseElementModel20 = new Namespace.Models.ElementModel2();
            }
            else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModelBaseElementModel21 = new Namespace.Models.ElementModel2();
            }
            else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModelBaseElementModel22 = new Namespace.Models.ElementModel2();
            }
            else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModelBaseElementModel23 = new Namespace.Models.ElementModel2();
            }
            return result;
        }
        /// <summary>
        /// The deserialization information for the current model
        /// </summary>
        /// <returns>A IDictionary&lt;string, Action&lt;IParseNode&gt;&gt;</returns>
        public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
        {
            if(ElementModel1 != null)
            {
                return ElementModel1.GetFieldDeserializers();
            }
            else if(ElementModel2 != null)
            {
                return ElementModel2.GetFieldDeserializers();
            }
            else if(ElementModelBaseElementModel1 != null)
            {
                return ElementModelBaseElementModel1.GetFieldDeserializers();
            }
            else if(ElementModelBaseElementModel10 != null)
            {
                return ElementModelBaseElementModel10.GetFieldDeserializers();
            }
            else if(ElementModelBaseElementModel11 != null)
            {
                return ElementModelBaseElementModel11.GetFieldDeserializers();
            }
            else if(ElementModelBaseElementModel12 != null)
            {
                return ElementModelBaseElementModel12.GetFieldDeserializers();
            }
            else if(ElementModelBaseElementModel13 != null)
            {
                return ElementModelBaseElementModel13.GetFieldDeserializers();
            }
            else if(ElementModelBaseElementModel2 != null)
            {
                return ElementModelBaseElementModel2.GetFieldDeserializers();
            }
            else if(ElementModelBaseElementModel20 != null)
            {
                return ElementModelBaseElementModel20.GetFieldDeserializers();
            }
            else if(ElementModelBaseElementModel21 != null)
            {
                return ElementModelBaseElementModel21.GetFieldDeserializers();
            }
            else if(ElementModelBaseElementModel22 != null)
            {
                return ElementModelBaseElementModel22.GetFieldDeserializers();
            }
            else if(ElementModelBaseElementModel23 != null)
            {
                return ElementModelBaseElementModel23.GetFieldDeserializers();
            }
            return new Dictionary<string, Action<IParseNode>>();
        }
        /// <summary>
        /// Serializes information the current object
        /// </summary>
        /// <param name="writer">Serialization writer to use to serialize this model</param>
        public virtual void Serialize(ISerializationWriter writer)
        {
            _ = writer ?? throw new ArgumentNullException(nameof(writer));
            if(ElementModel1 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModel1);
            }
            else if(ElementModel2 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModel2);
            }
            else if(ElementModelBaseElementModel1 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModelBaseElementModel1);
            }
            else if(ElementModelBaseElementModel10 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModelBaseElementModel10);
            }
            else if(ElementModelBaseElementModel11 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModelBaseElementModel11);
            }
            else if(ElementModelBaseElementModel12 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModelBaseElementModel12);
            }
            else if(ElementModelBaseElementModel13 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModelBaseElementModel13);
            }
            else if(ElementModelBaseElementModel2 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModelBaseElementModel2);
            }
            else if(ElementModelBaseElementModel20 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModelBaseElementModel20);
            }
            else if(ElementModelBaseElementModel21 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModelBaseElementModel21);
            }
            else if(ElementModelBaseElementModel22 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModelBaseElementModel22);
            }
            else if(ElementModelBaseElementModel23 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModelBaseElementModel23);
            }
        }
    }
}

Generated using:

kiota generate --openapi swagger_problem.json -l csharp -n Namespace -o DeliveryApiClientProblem -c DeliveryApiClient -i "**/{path}" --ebc --ll Trace
@andrueastman
Copy link
Member

Thanks for raising this @fzzle

This looks to be a duplicate of #4178.
@andreaTP Any chance you can confirm?

@andreaTP
Copy link
Contributor

Looks very similar, @fzzle any chance you can test using Kiota from my branch ( https://github.com/andreaTP/kiota/tree/initial-fixes-simple-oneOf-py ) with the fixes or from this (non-official)"pre-release" which includes the fixes: https://github.com/andreaTP/kiota-prerelease/releases/tag/v0.0.0-pre%2BandreaTP.initial-fixes-simple-oneOf-py.b46369a

@fzzle
Copy link
Author

fzzle commented Apr 11, 2024

Hi @andrueastman and @andreaTP,

Thanks for the swift reply.

I've tested your branch and I can see that it solves the problem.

The model now looks like this.

// <auto-generated/>
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System;
namespace Namespace.Models {
    /// <summary>
    /// Composed type wrapper for classes <see cref="ElementModel1"/>, <see cref="ElementModel2"/>
    /// </summary>
    public class ElementModelBase : IComposedTypeWrapper, IParsable 
    {
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel1"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel1? ElementModel1 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel1 ElementModel1 { get; set; }
#endif
        /// <summary>Composed type representation for type <see cref="Namespace.Models.ElementModel2"/></summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
        public Namespace.Models.ElementModel2? ElementModel2 { get; set; }
#nullable restore
#else
        public Namespace.Models.ElementModel2 ElementModel2 { get; set; }
#endif
        /// <summary>
        /// Creates a new instance of the appropriate class based on discriminator value
        /// </summary>
        /// <returns>A <see cref="ElementModelBase"/></returns>
        /// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
        public static ElementModelBase CreateFromDiscriminatorValue(IParseNode parseNode)
        {
            _ = parseNode ?? throw new ArgumentNullException(nameof(parseNode));
            var mappingValue = parseNode.GetChildNode("contentType")?.GetStringValue();
            var result = new ElementModelBase();
            if("c".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModel1 = new Namespace.Models.ElementModel1();
            }
            else if("d".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))
            {
                result.ElementModel2 = new Namespace.Models.ElementModel2();
            }
            return result;
        }
        /// <summary>
        /// The deserialization information for the current model
        /// </summary>
        /// <returns>A IDictionary&lt;string, Action&lt;IParseNode&gt;&gt;</returns>
        public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
        {
            if(ElementModel1 != null)
            {
                return ElementModel1.GetFieldDeserializers();
            }
            else if(ElementModel2 != null)
            {
                return ElementModel2.GetFieldDeserializers();
            }
            return new Dictionary<string, Action<IParseNode>>();
        }
        /// <summary>
        /// Serializes information the current object
        /// </summary>
        /// <param name="writer">Serialization writer to use to serialize this model</param>
        public virtual void Serialize(ISerializationWriter writer)
        {
            _ = writer ?? throw new ArgumentNullException(nameof(writer));
            if(ElementModel1 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel1>(null, ElementModel1);
            }
            else if(ElementModel2 != null)
            {
                writer.WriteObjectValue<Namespace.Models.ElementModel2>(null, ElementModel2);
            }
        }
    }
}

@baywet
Copy link
Member

baywet commented May 17, 2024

@fzzle @andreaTP can you try with the latest preview from yesterday and confirm whether you observe the problem? We've made significant improvements to the handling of allof edge scenarios with #4668 and #4381

@baywet baywet added type:bug A broken experience generator Issues or improvements relater to generation capabilities. status:waiting-for-author-feedback Issue that we've responded but needs author feedback to close labels May 17, 2024
@baywet baywet added this to the Backlog milestone May 17, 2024
@andreaTP
Copy link
Contributor

Checked and the problem is still present.

@baywet baywet removed the status:waiting-for-author-feedback Issue that we've responded but needs author feedback to close label May 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
generator Issues or improvements relater to generation capabilities. type:bug A broken experience
Projects
Status: Todo 📃
Development

No branches or pull requests

4 participants