RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.61k stars 1.22k forks source link

c# client. OneOf on request types with derived abstract base class generates a method but not with the base class as parameter but the first of the derived classes #4824

Open svha1977 opened 3 months ago

svha1977 commented 3 months ago

When having a request class defined by

public abstract class BaseClass
{
    [Required]
    public string BaseProp { get; set; }
}

public class DerivedA : BaseClass
{
    [Required]
    public string DerivedAProp { get; set; }
}

public class DerivedB : BaseClass
{
    [Required]
    public string DerivedBProp { get; set; }
}

and a controller taking the BaseClass as a param

[HttpPost("doSomething", Name = "DoSomething")]
[ProducesResponseType<string>(StatusCodes.Status200OK)]
public IActionResult DoSomething(BaseClass request)
{
    return Ok();
}

with configuration

builder.Services.AddSwaggerGen(swaggerGenOptions =>
{
    swaggerGenOptions.UseAllOfForInheritance();
    swaggerGenOptions.UseOneOfForPolymorphism();
    swaggerGenOptions.SelectSubTypesUsing(baseType =>
        typeof(Program).Assembly.GetTypes().Where(type => type.IsSubclassOf(baseType))
    );
});

The schema gets to be

{
  "openapi": "3.0.1",
  "info": {
    "title": "PocDemo.WebApi2",
    "version": "1.0"
  },
  "paths": {
    "/api/MyController/doSomething": {
      "post": {
        "tags": [
          "Policies"
        ],
        "operationId": "DoSomething",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/DerivedA"
                  },
                  {
                    "$ref": "#/components/schemas/DerivedB"
                  }
                ]
              }
            },
            "text/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/DerivedA"
                  },
                  {
                    "$ref": "#/components/schemas/DerivedB"
                  }
                ]
              }
            },
            "application/*+json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/DerivedA"
                  },
                  {
                    "$ref": "#/components/schemas/DerivedB"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/json": {
                "schema": {
                  "type": "string"
                }
              },
              "text/json": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "BaseClass": {
        "required": [
          "baseProp"
        ],
        "type": "object",
        "properties": {
          "baseProp": {
            "minLength": 1,
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "DerivedA": {
        "required": [
          "derivedAProp"
        ],
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseClass"
          }
        ],
        "properties": {
          "derivedAProp": {
            "minLength": 1,
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "DerivedB": {
        "required": [
          "derivedBProp"
        ],
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseClass"
          }
        ],
        "properties": {
          "derivedBProp": {
            "minLength": 1,
            "type": "string"
          }
        },
        "additionalProperties": false
      }
    }
  }
}

The generated c# client does not take the BaseClass as a param but instead the first of the derived types / oneOf´s and hence cannot be called with all possible derived request types.

public virtual System.Threading.Tasks.Task<string> DoSomethingAsync(DerivedA body)
{
    return DoSomethingAsync(body, System.Threading.CancellationToken.None);
}

Is it not suppose to figure out that it should pick the base type, so the operation can be called with either DerivedA or DerivedB?

It has figured out the both DerivedA and DerivedB derives from BaseClass and hence the base class should be the method argumenT?

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
    public partial class DerivedA : BaseClass
    {
        [Newtonsoft.Json.JsonProperty("derivedAProp", Required = Newtonsoft.Json.Required.Always)]
        [System.ComponentModel.DataAnnotations.Required]
        public string DerivedAProp { get; set; }

    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
    public partial class DerivedB : BaseClass
    {
        [Newtonsoft.Json.JsonProperty("derivedAProp", Required = Newtonsoft.Json.Required.Always)]
        [System.ComponentModel.DataAnnotations.Required]
        public string DerivedAProp { get; set; }

    }