How do I include subclasses in Swagger API documentation/ OpenAPI specification using Swashbuckle?

前端 未结 5 752
情书的邮戳
情书的邮戳 2020-11-30 00:44

I have an Asp.Net web API 5.2 project in c# and generating documentation with Swashbuckle.

I have model that contain inheritance something like having an Animal prop

5条回答
  •  囚心锁ツ
    2020-11-30 01:20

    It seems Swashbuckle doesn't implement polymorphism correctly and I understand the point of view of the author about subclasses as parameters (if an action expects an Animal class and behaves differently if you call it with a dog object or a cat object, then you should have 2 different actions...) but as return types I believe that it is correct to return Animal and the objects could be Dog or Cat types.

    So to describe my API and produce a proper JSON schema in line with correct guidelines (be aware of the way I describe the disciminator, if you have your own discriminator you may need to change that part in particular), I use document and schema filters as follows:

    SwaggerDocsConfig configuration;
    .....
    configuration.DocumentFilter>();
    configuration.SchemaFilter>();
    .....
    
    public class PolymorphismSchemaFilter : ISchemaFilter
    {
        private readonly Lazy> derivedTypes = new Lazy>(Init);
    
        private static HashSet Init()
        {
            var abstractType = typeof(T);
            var dTypes = abstractType.Assembly
                                     .GetTypes()
                                     .Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
    
            var result = new HashSet();
    
            foreach (var item in dTypes)
                result.Add(item);
    
            return result;
        }
    
        public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
        {
            if (!derivedTypes.Value.Contains(type)) return;
    
            var clonedSchema = new Schema
                                    {
                                        properties = schema.properties,
                                        type = schema.type,
                                        required = schema.required
                                    };
    
            //schemaRegistry.Definitions[typeof(T).Name]; does not work correctly in SwashBuckle
            var parentSchema = new Schema { @ref = "#/definitions/" + typeof(T).Name };   
    
            schema.allOf = new List { parentSchema, clonedSchema };
    
            //reset properties for they are included in allOf, should be null but code does not handle it
            schema.properties = new Dictionary();
        }
    }
    
    public class PolymorphismDocumentFilter : IDocumentFilter
    {
        public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, System.Web.Http.Description.IApiExplorer apiExplorer)
        {
            RegisterSubClasses(schemaRegistry, typeof(T));
        }
    
        private static void RegisterSubClasses(SchemaRegistry schemaRegistry, Type abstractType)
        {
            const string discriminatorName = "discriminator";
    
            var parentSchema = schemaRegistry.Definitions[SchemaIdProvider.GetSchemaId(abstractType)];
    
            //set up a discriminator property (it must be required)
            parentSchema.discriminator = discriminatorName;
            parentSchema.required = new List { discriminatorName };
    
            if (!parentSchema.properties.ContainsKey(discriminatorName))
                parentSchema.properties.Add(discriminatorName, new Schema { type = "string" });
    
            //register all subclasses
            var derivedTypes = abstractType.Assembly
                                           .GetTypes()
                                           .Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
    
            foreach (var item in derivedTypes)
                schemaRegistry.GetOrRegister(item);
        }
    }
    

    What the previous code implements is specified here, in the section "Models with Polymorphism Support. It basically produces something like the following:

    {
      "definitions": {
        "Pet": {
          "type": "object",
          "discriminator": "petType",
          "properties": {
            "name": {
              "type": "string"
            },
            "petType": {
              "type": "string"
            }
          },
          "required": [
            "name",
            "petType"
          ]
        },
        "Cat": {
          "description": "A representation of a cat",
          "allOf": [
            {
              "$ref": "#/definitions/Pet"
            },
            {
              "type": "object",
              "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",
          "allOf": [
            {
              "$ref": "#/definitions/Pet"
            },
            {
              "type": "object",
              "properties": {
                "packSize": {
                  "type": "integer",
                  "format": "int32",
                  "description": "the size of the pack the dog is from",
                  "default": 0,
                  "minimum": 0
                }
              },
              "required": [
                "packSize"
              ]
            }
          ]
        }
      }
    }
    

提交回复
热议问题