GraehamF
GraehamF

Reputation: 1991

Autorest can't override parent property in child class

I'm using Autorest to generate a csharp sdk from a swagger 2.0 definition and I'm wondering if it is possible to have a generated child class override a parent's property with a different type.

Example swagger:

{
    "swagger": "2.0",
    "info": {
        "version": "1",
        "title": "My API"
    },
    "schemes": [
        "https"
    ],
    "consumes": [
        "application/json"
    ],
    "produces": [
        "application/json"
    ],
    "paths": {
        "/myResource": {
            "post": {
                "parameters": [
                    {
                        "name": "myResource_data",
                        "in": "body",
                        "schema": {
                            "$ref": "#/definitions/myResourceCreateBody"
                        },
                        "required": true
                    }
                ],
                "responses": {
                    "201": {
                        "description": "myResource response",
                        "schema": {
                            "$ref": "#/definitions/myResourceResponseExtended"
                        }
                    }
                }
            }
        }
    },
    "definitions": {
        "metaResponse": {
            "type": "object",
            "description": "Response metadata.",
            "required": [
                "api_version"
            ],
            "properties": {
                "api_version": {
                    "type": "string"
                }
            },
            "x-services": [
                "shared"
            ]
        },
        "extendedMetaResponse": {
            "allOf": [
                {
                    "$ref": "#/definitions/metaResponse"
                },
                {
                    "type": "object",
                    "properties": {
                        "myExtendedProp": {
                            "type": "string"
                        }
                    }
                }
            ],
            "x-services": [
                "shared"
            ]
        },
        "myResourceResponse": {
            "type": "object",
            "required": [
                "meta"
            ],
            "additionalProperties": false,
            "properties": {
                "meta": {
                    "$ref": "#/definitions/metaResponse"
                }
            },
            "x-services": [
                "myResource"
            ]
        },
        "myResourceResponseExtended": {
            "allOf": [
                {
                    "$ref": "#/definitions/myResourceResponse"
                },
                {
                    "type": "object",
                    "properties": {
                        "meta": {
                            "$ref": "#/definitions/extendedMetaResponse"
                        }
                    }
                }
            ],
            "x-services": [
                "myResource"
            ]
        },
        "myResourceCreateBody": {
            "type": "object",
            "required": [
                "myResource_id"
            ],
            "additionalProperties": false,
            "properties": {
                "myResource_id": {
                    "type": "string",
                    "description": "myResource identifier"
                }
            },
            "x-services": [
                "myResource"
            ]
        }
    }
}

Which generates the following C# base class:

    public partial class MyResourceResponse
    {
        public MyResourceResponse()
        {
            CustomInit();
        }

        public MyResourceResponse(MetaResponse meta)
        {
            Meta = meta;
            CustomInit();
        }

        partial void CustomInit();

        [JsonProperty(PropertyName = "meta")]
        public MetaResponse Meta { get; set; }

        public virtual void Validate()
        {
            if (Meta == null)
            {
                throw new ValidationException(ValidationRules.CannotBeNull, "Meta");
            }
            if (Meta != null)
            {
                Meta.Validate();
            }
        }
    }
}

And the following child class:

    public partial class MyResourceResponseExtended : MyResourceResponse
    {
        public MyResourceResponseExtended()
        {
            CustomInit();
        }

        public MyResourceResponseExtended(MetaResponse meta)
            : base(meta)
        {
            CustomInit();
        }

        partial void CustomInit();

        public override void Validate()
        {
            base.Validate();
        }
    }

But my desired output would be to have MyResourceResponseExtended to override the Meta property with a Meta property of type MetaResponseExtended to expose its additional properties.

For example, my desired output would be:

public partial class MyResourceResponseExtended : MyResourceResponse
{
    [JsonProperty(PropertyName = "meta")]
    public new ExtendedMetaResponse Meta { get; set; }

    public MyResourceResponseExtended(ExtendedMetaResponse meta)
        : base(meta)
    {
        Meta = meta;
        CustomInit();
    }
}

Is there a better way to define this in Swagger 2.0? Is this a bug/limitation of Autorest?

Thanks! :-)

PS: I've looked into using discriminator in the swagger file, but I'm either not using it correctly or it is not designed for this specific purpose.

Upvotes: 1

Views: 774

Answers (1)

Garrett Serack
Garrett Serack

Reputation: 953

You definitely need to use a discriminator, and make a polymorphic type out of it.

In order to have a polymorphic type, you have to have a property that you declare as a discriminator and each type that is a child (ie, using allOf) of the parent class must have an x-ms-discriminator-value that it can use as a key to allow the deserializer to choose the correct type to deserialize into.

simple example:

swagger: '2.0'
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
host: petstore.swagger.io
basePath: "/v1"
schemes:
- https
consumes:
- application/json
produces:
- application/json
paths:
  "/pets":
    put:
      summary: Add a pet
      operationId: addPet
      tags:
      - pets
      parameters:
      - schema:
          "$ref": "#/definitions/Pet"
      responses:
        '200':
          description: OK
definitions:
  Pet:
    properties:
      petKind:
        type: string
      name:
        type: string
    discriminator: petKind
    required:
    - name
    - petType
  Cat:
    description: A representation of a cat
    x-ms-discriminator-value: CAT
    allOf:
    - "$ref": "#/definitions/Pet"
    - properties:
        huntingSkill:
          type: string
          description: The measured skill for hunting
          default: lazy
          enum:
          - clueless
          - lazy
          - adventurous
          - aggressive
      required:
      - huntingSkill
  Dog:
    description: A representation of a dog
    x-ms-discriminator-value: DOG
    allOf:
    - "$ref": "#/definitions/Pet"
    - properties:
        packSize:
          type: integer
          format: int32
          description: the size of the pack the dog is from
          default: 0
          minimum: 0
      required:
      - packSize

Pet objects have a petKind property (DOG or CAT) that tells the deserializer what model to use to deserialize the value.

(In generated c# it would use an attribute on the class for that.)

Upvotes: 1

Related Questions