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

[BUG] [C#] Polymorphism with 2 level derived classes generates invalid client #8849

Open
scavarda opened this issue Feb 26, 2021 · 5 comments
Labels
Inline Schema Handling Schema contains a complex schema in items/additionalProperties/allOf/oneOf/anyOf Issue: Bug

Comments

@scavarda
Copy link

Generated client is invalid for this example:
Using latest version 5.0.1

    public class BaseClass1
    {
        public int Number { get; set; }
    }

    public class DerivedClass_A : BaseClass1
    {
        public int Number2 { get; set; }
    }

    public class DerivedClass_B : DerivedClass_A
    {
        public int Number3 { get; set; }
    }

document

{
  "openapi": "3.0.1",
  "info": {
    "title": "Test",
    "description": "Test",
    "contact": {
      "name": "John Doe",
      "url": "https://mydomain.com",
      "email": "johndoe@hotmail.com"
    },
    "license": {
      "name": "-"
    },
    "version": "1.0.0.0"
  },
  "servers": [
    {
      "url": "http://127.0.0.1:5000"
    }
  ],
  "paths": {
    "/poly1": {
      "get": {
        "tags": [
          "Test"
        ],
        "operationId": "TestPolymorphism1",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/BaseClass1"
                    },
                    {
                      "$ref": "#/components/schemas/DerivedClass_A"
                    },
                    {
                      "$ref": "#/components/schemas/DerivedClass_B"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "BaseClass1": {
        "required": [
          "discriminator"
        ],
        "type": "object",
        "properties": {
          "discriminator": {
            "type": "string"
          },
          "number": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false,
        "discriminator": {
          "propertyName": "discriminator",
          "mapping": {
            "BaseClass1": "#/components/schemas/BaseClass1",
            "DerivedClass_A": "#/components/schemas/DerivedClass_A",
            "DerivedClass_B": "#/components/schemas/DerivedClass_B"
          }
        }
      },
      "DerivedClass_A": {
        "required": [
          "discriminator"
        ],
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseClass1"
          }
        ],
        "properties": {
          "discriminator": {
            "type": "string"
          },
          "number2": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false,
        "discriminator": {
          "propertyName": "discriminator",
          "mapping": {
            "DerivedClass_A": "#/components/schemas/DerivedClass_A",
            "DerivedClass_B": "#/components/schemas/DerivedClass_B"
          }
        }
      },
      "DerivedClass_B": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/DerivedClass_A"
          }
        ],
        "properties": {
          "number3": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false
      }
    }
  }
}

The error is in the constructor of DerivedClass_A when calling base(....),
discriminator is missing in the arguments

@Blackclaws
Copy link
Contributor

Blackclaws commented Feb 26, 2021

EDIT: The spec wasn't wrong. I'm leaving this below however to document how its not done. See the official spec documentation: https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/

Your spec is wrong. Your derived classes are missing the baseclass properties.

The correct spec would be:

{
  "openapi": "3.0.1",
  "info": {
    "title": "Test",
    "description": "Test",
    "contact": {
      "name": "John Doe",
      "url": "https://mydomain.com",
      "email": "johndoe@hotmail.com"
    },
    "license": {
      "name": "-"
    },
    "version": "1.0.0.0"
  },
  "servers": [
    {
      "url": "http://127.0.0.1:5000"
    }
  ],
  "paths": {
    "/poly1": {
      "get": {
        "tags": [
          "Test"
        ],
        "operationId": "TestPolymorphism1",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/BaseClass1"
                    },
                    {
                      "$ref": "#/components/schemas/DerivedClass_A"
                    },
                    {
                      "$ref": "#/components/schemas/DerivedClass_B"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "BaseClass1": {
        "required": [
          "discriminator"
        ],
        "type": "object",
        "properties": {
          "discriminator": {
            "type": "string"
          },
          "number": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false,
        "discriminator": {
          "propertyName": "discriminator",
          "mapping": {
            "BaseClass1": "#/components/schemas/BaseClass1",
            "DerivedClass_A": "#/components/schemas/DerivedClass_A",
            "DerivedClass_B": "#/components/schemas/DerivedClass_B"
          }
        }
      },
      "DerivedClass_A": {
        "required": [
          "discriminator"
        ],
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseClass1"
          }
        ],
        "properties": {
          "discriminator": {
            "type": "string"
          },
          "number": {
            "type": "integer",
            "format": "int32"
          },
          "number2": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false,
        "discriminator": {
          "propertyName": "discriminator",
          "mapping": {
            "DerivedClass_A": "#/components/schemas/DerivedClass_A",
            "DerivedClass_B": "#/components/schemas/DerivedClass_B"
          }
        }
      },
      "DerivedClass_B": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/DerivedClass_A"
          }
        ],
        "properties": {
          "discriminator": {
            "type": "string"
          },
          "number": {
            "type": "integer",
            "format": "int32"
          },
          "number2": {
            "type": "integer",
            "format": "int32"
          },
          "number3": {
            "type": "integer",
            "format": "int32"
          }
        },
        "additionalProperties": false,
        "discriminator": {
          "propertyName": "discriminator",
          "mapping": {
            "DerivedClass_B": "#/components/schemas/DerivedClass_B"
          }
        }
      }
    }
  }
}

which produces functioning classes.

@scavarda
Copy link
Author

If I use your example derived class properties are duplicated (see the picture below: Discriminator and Number), and the constructor invoke parameterless base constructor, I think is not the best way, any ideas?

image

@Blackclaws
Copy link
Contributor

Yeah you're right. I'll check what is happening under the hood there.

@Blackclaws
Copy link
Contributor

Confirmed that these are actually two separate bugs at work. Working on a fix.

@Blackclaws
Copy link
Contributor

Pull request is up which fixes the class issue, the client still won't compile because the return type of the api Operations is a weird one that doesn't exist.

I'm not sure if that is an issue of the Client per se, or of the spec. I'm guessing its something the client generator should be able to handle but its also something you can fix on the spec side of things.

Simply change the response type of your Api endpoint to just return a BaseClass1 type (which is the only thing that semantically makes sense in such a scenario in my eyes anyway) the client will instantiate the actual type returned correctly and you can then check for the type in your code with the usual:

returnedObject is DerivedClassB

@spacether spacether added the Inline Schema Handling Schema contains a complex schema in items/additionalProperties/allOf/oneOf/anyOf label Mar 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Inline Schema Handling Schema contains a complex schema in items/additionalProperties/allOf/oneOf/anyOf Issue: Bug
Projects
None yet
Development

No branches or pull requests

3 participants