Skip to content

Required enum ignored in C# client #581

@FelixS90

Description

@FelixS90
Description

As of #6851, an enum must not be declared required in order to be marked as nullable (see the change in modules/swagger-codegen/src/main/resources/csharp/modelGeneric.mustache).

However, together with this change introduced in #4145, the result is now that a required enum that is not handed over to the constructor gets silently replaced with 0. This is a nightmare since it bypasses validation and makes the whole point of declaring the property as required useless in the first place.

Swagger-codegen version

3.0.15

Swagger declaration file content or url
openapi: 3.0.0
info:
  version: '2.0'
  title: Test
components:
  schemas:
    modelResourceAttributes:
      type: object
      required:
        - fancyParam
        - otherParam
        - mode
      properties:
        name:
          description: A short name of the model.
          type: string
        fancyParam:
          description: The first important parameter.
          type: string
          enum:
            - Default
            - Default 2
        otherParam:
          description: Another important parameter.
          type: string
          enum:
            - Default
            - SomethingElse
        mode:
          description: Some integer.
          type: integer
          format: int32
          minimum: 1
Command line used for generation

java -DdebugModels -Dmodels -jar swagger-codegen-cli-3.0.15.jar generate -DtargetFramework=v4.5 -l csharp -i "<path-to-spec>"

Steps to reproduce
  1. Generate the C# files using the command line above
  2. Open the file ModelResourceAttributes.cs
Actual result
/* 
 * Test
 *
 * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
 *
 * OpenAPI spec version: 2.0
 * 
 * Generated by: https://github.com/swagger-api/swagger-codegen.git
 */
using System;
using System.Linq;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.ComponentModel.DataAnnotations;
using SwaggerDateConverter = IO.Swagger.Client.SwaggerDateConverter;

namespace IO.Swagger.Model
{
    /// <summary>
    /// ModelResourceAttributes
    /// </summary>
    [DataContract]
        public partial class ModelResourceAttributes :  IEquatable<ModelResourceAttributes>, IValidatableObject
    {
        /// <summary>
        /// The first important parameter.
        /// </summary>
        /// <value>The first important parameter.</value>
        [JsonConverter(typeof(StringEnumConverter))]
                public enum FancyParamEnum
        {
            /// <summary>
            /// Enum Default for value: Default
            /// </summary>
            [EnumMember(Value = "Default")]
            Default = 1,
            /// <summary>
            /// Enum Default2 for value: Default 2
            /// </summary>
            [EnumMember(Value = "Default 2")]
            Default2 = 2        }
        /// <summary>
        /// The first important parameter.
        /// </summary>
        /// <value>The first important parameter.</value>
        [DataMember(Name="fancyParam", EmitDefaultValue=false)]
        public FancyParamEnum FancyParam { get; set; }
        /// <summary>
        /// Another important parameter.
        /// </summary>
        /// <value>Another important parameter.</value>
        [JsonConverter(typeof(StringEnumConverter))]
                public enum OtherParamEnum
        {
            /// <summary>
            /// Enum Default for value: Default
            /// </summary>
            [EnumMember(Value = "Default")]
            Default = 1,
            /// <summary>
            /// Enum SomethingElse for value: SomethingElse
            /// </summary>
            [EnumMember(Value = "SomethingElse")]
            SomethingElse = 2        }
        /// <summary>
        /// Another important parameter.
        /// </summary>
        /// <value>Another important parameter.</value>
        [DataMember(Name="otherParam", EmitDefaultValue=false)]
        public OtherParamEnum OtherParam { get; set; }
        /// <summary>
        /// Initializes a new instance of the <see cref="ModelResourceAttributes" /> class.
        /// </summary>
        /// <param name="name">A short name of the model..</param>
        /// <param name="fancyParam">The first important parameter. (required).</param>
        /// <param name="otherParam">Another important parameter. (required).</param>
        /// <param name="mode">Some integer. (required).</param>
        public ModelResourceAttributes(string name = default(string), FancyParamEnum fancyParam = default(FancyParamEnum), OtherParamEnum otherParam = default(OtherParamEnum), int? mode = default(int?))
        {
            // to ensure "fancyParam" is required (not null)
            if (fancyParam == null)
            {
                throw new InvalidDataException("fancyParam is a required property for ModelResourceAttributes and cannot be null");
            }
            else
            {
                this.FancyParam = fancyParam;
            }
            // to ensure "otherParam" is required (not null)
            if (otherParam == null)
            {
                throw new InvalidDataException("otherParam is a required property for ModelResourceAttributes and cannot be null");
            }
            else
            {
                this.OtherParam = otherParam;
            }
            // to ensure "mode" is required (not null)
            if (mode == null)
            {
                throw new InvalidDataException("mode is a required property for ModelResourceAttributes and cannot be null");
            }
            else
            {
                this.Mode = mode;
            }
            this.Name = name;
        }
        
        /// <summary>
        /// A short name of the model.
        /// </summary>
        /// <value>A short name of the model.</value>
        [DataMember(Name="name", EmitDefaultValue=false)]
        public string Name { get; set; }



        /// <summary>
        /// Some integer.
        /// </summary>
        /// <value>Some integer.</value>
        [DataMember(Name="mode", EmitDefaultValue=false)]
        public int? Mode { get; set; }

        /// <summary>
        /// Returns the string presentation of the object
        /// </summary>
        /// <returns>String presentation of the object</returns>
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.Append("class ModelResourceAttributes {\n");
            sb.Append("  Name: ").Append(Name).Append("\n");
            sb.Append("  FancyParam: ").Append(FancyParam).Append("\n");
            sb.Append("  OtherParam: ").Append(OtherParam).Append("\n");
            sb.Append("  Mode: ").Append(Mode).Append("\n");
            sb.Append("}\n");
            return sb.ToString();
        }
  
        /// <summary>
        /// Returns the JSON string presentation of the object
        /// </summary>
        /// <returns>JSON string presentation of the object</returns>
        public virtual string ToJson()
        {
            return JsonConvert.SerializeObject(this, Formatting.Indented);
        }

        /// <summary>
        /// Returns true if objects are equal
        /// </summary>
        /// <param name="input">Object to be compared</param>
        /// <returns>Boolean</returns>
        public override bool Equals(object input)
        {
            return this.Equals(input as ModelResourceAttributes);
        }

        /// <summary>
        /// Returns true if ModelResourceAttributes instances are equal
        /// </summary>
        /// <param name="input">Instance of ModelResourceAttributes to be compared</param>
        /// <returns>Boolean</returns>
        public bool Equals(ModelResourceAttributes input)
        {
            if (input == null)
                return false;

            return 
                (
                    this.Name == input.Name ||
                    (this.Name != null &&
                    this.Name.Equals(input.Name))
                ) && 
                (
                    this.FancyParam == input.FancyParam ||
                    (this.FancyParam != null &&
                    this.FancyParam.Equals(input.FancyParam))
                ) && 
                (
                    this.OtherParam == input.OtherParam ||
                    (this.OtherParam != null &&
                    this.OtherParam.Equals(input.OtherParam))
                ) && 
                (
                    this.Mode == input.Mode ||
                    (this.Mode != null &&
                    this.Mode.Equals(input.Mode))
                );
        }

        /// <summary>
        /// Gets the hash code
        /// </summary>
        /// <returns>Hash code</returns>
        public override int GetHashCode()
        {
            unchecked // Overflow is fine, just wrap
            {
                int hashCode = 41;
                if (this.Name != null)
                    hashCode = hashCode * 59 + this.Name.GetHashCode();
                if (this.FancyParam != null)
                    hashCode = hashCode * 59 + this.FancyParam.GetHashCode();
                if (this.OtherParam != null)
                    hashCode = hashCode * 59 + this.OtherParam.GetHashCode();
                if (this.Mode != null)
                    hashCode = hashCode * 59 + this.Mode.GetHashCode();
                return hashCode;
            }
        }

        /// <summary>
        /// To validate all properties of the instance
        /// </summary>
        /// <param name="validationContext">Validation context</param>
        /// <returns>Validation Result</returns>
        IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
        {
            yield break;
        }
    }
}

image
The screenshot of the errors shows a related/follow-up issue since the null-check does not make sense for non-nullable type. Related issue: #8519

The main problem is that any json object that is going to be received and that does not contain the required properties will be silently accepted as can be easily verified by executing the following in C# Interactive:

#r "bin/IO.Swagger.Model.dll"
using IO.Swagger.Model;
var model = "{\r\n  \"mode\": 1\r\n}";
var original = (ModelResourceAttributes)JsonConvert.DeserializeObject(model, typeof(ModelResourceAttributes));

The same holds for creating the object via the ctor directly.

Expected result

See added null-conditional operator ?)

/* 
 * Test
 *
 * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
 *
 * OpenAPI spec version: 2.0
 * 
 * Generated by: https://github.com/swagger-api/swagger-codegen.git
 */
using System;
using System.Linq;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.ComponentModel.DataAnnotations;
using SwaggerDateConverter = IO.Swagger.Client.SwaggerDateConverter;

namespace IO.Swagger.Model
{
    /// <summary>
    /// ModelResourceAttributes
    /// </summary>
    [DataContract]
        public partial class ModelResourceAttributes :  IEquatable<ModelResourceAttributes>, IValidatableObject
    {
        /// <summary>
        /// The first important parameter.
        /// </summary>
        /// <value>The first important parameter.</value>
        [JsonConverter(typeof(StringEnumConverter))]
                public enum FancyParamEnum
        {
            /// <summary>
            /// Enum Default for value: Default
            /// </summary>
            [EnumMember(Value = "Default")]
            Default = 1,
            /// <summary>
            /// Enum Default2 for value: Default 2
            /// </summary>
            [EnumMember(Value = "Default 2")]
            Default2 = 2        }
        /// <summary>
        /// The first important parameter.
        /// </summary>
        /// <value>The first important parameter.</value>
        [DataMember(Name="fancyParam", EmitDefaultValue=false)]
        public FancyParamEnum? FancyParam { get; set; }
        /// <summary>
        /// Another important parameter.
        /// </summary>
        /// <value>Another important parameter.</value>
        [JsonConverter(typeof(StringEnumConverter))]
                public enum OtherParamEnum
        {
            /// <summary>
            /// Enum Default for value: Default
            /// </summary>
            [EnumMember(Value = "Default")]
            Default = 1,
            /// <summary>
            /// Enum SomethingElse for value: SomethingElse
            /// </summary>
            [EnumMember(Value = "SomethingElse")]
            SomethingElse = 2        }
        /// <summary>
        /// Another important parameter.
        /// </summary>
        /// <value>Another important parameter.</value>
        [DataMember(Name="otherParam", EmitDefaultValue=false)]
        public OtherParamEnum? OtherParam { get; set; }
        /// <summary>
        /// Initializes a new instance of the <see cref="ModelResourceAttributes" /> class.
        /// </summary>
        /// <param name="name">A short name of the model..</param>
        /// <param name="fancyParam">The first important parameter. (required).</param>
        /// <param name="otherParam">Another important parameter. (required).</param>
        /// <param name="mode">Some integer. (required).</param>
        public ModelResourceAttributes(string name = default(string), FancyParamEnum? fancyParam = default(FancyParamEnum?), OtherParamEnum? otherParam = default(OtherParamEnum?), int? mode = default(int?))
        {
            // to ensure "fancyParam" is required (not null)
            if (fancyParam == null)
            {
                throw new InvalidDataException("fancyParam is a required property for ModelResourceAttributes and cannot be null");
            }
            else
            {
                this.FancyParam = fancyParam;
            }
            // to ensure "otherParam" is required (not null)
            if (otherParam == null)
            {
                throw new InvalidDataException("otherParam is a required property for ModelResourceAttributes and cannot be null");
            }
            else
            {
                this.OtherParam = otherParam;
            }
            // to ensure "mode" is required (not null)
            if (mode == null)
            {
                throw new InvalidDataException("mode is a required property for ModelResourceAttributes and cannot be null");
            }
            else
            {
                this.Mode = mode;
            }
            this.Name = name;
        }
        
        /// <summary>
        /// A short name of the model.
        /// </summary>
        /// <value>A short name of the model.</value>
        [DataMember(Name="name", EmitDefaultValue=false)]
        public string Name { get; set; }



        /// <summary>
        /// Some integer.
        /// </summary>
        /// <value>Some integer.</value>
        [DataMember(Name="mode", EmitDefaultValue=false)]
        public int? Mode { get; set; }

        /// <summary>
        /// Returns the string presentation of the object
        /// </summary>
        /// <returns>String presentation of the object</returns>
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.Append("class ModelResourceAttributes {\n");
            sb.Append("  Name: ").Append(Name).Append("\n");
            sb.Append("  FancyParam: ").Append(FancyParam).Append("\n");
            sb.Append("  OtherParam: ").Append(OtherParam).Append("\n");
            sb.Append("  Mode: ").Append(Mode).Append("\n");
            sb.Append("}\n");
            return sb.ToString();
        }
  
        /// <summary>
        /// Returns the JSON string presentation of the object
        /// </summary>
        /// <returns>JSON string presentation of the object</returns>
        public virtual string ToJson()
        {
            return JsonConvert.SerializeObject(this, Formatting.Indented);
        }

        /// <summary>
        /// Returns true if objects are equal
        /// </summary>
        /// <param name="input">Object to be compared</param>
        /// <returns>Boolean</returns>
        public override bool Equals(object input)
        {
            return this.Equals(input as ModelResourceAttributes);
        }

        /// <summary>
        /// Returns true if ModelResourceAttributes instances are equal
        /// </summary>
        /// <param name="input">Instance of ModelResourceAttributes to be compared</param>
        /// <returns>Boolean</returns>
        public bool Equals(ModelResourceAttributes input)
        {
            if (input == null)
                return false;

            return 
                (
                    this.Name == input.Name ||
                    (this.Name != null &&
                    this.Name.Equals(input.Name))
                ) && 
                (
                    this.FancyParam == input.FancyParam ||
                    (this.FancyParam != null &&
                    this.FancyParam.Equals(input.FancyParam))
                ) && 
                (
                    this.OtherParam == input.OtherParam ||
                    (this.OtherParam != null &&
                    this.OtherParam.Equals(input.OtherParam))
                ) && 
                (
                    this.Mode == input.Mode ||
                    (this.Mode != null &&
                    this.Mode.Equals(input.Mode))
                );
        }

        /// <summary>
        /// Gets the hash code
        /// </summary>
        /// <returns>Hash code</returns>
        public override int GetHashCode()
        {
            unchecked // Overflow is fine, just wrap
            {
                int hashCode = 41;
                if (this.Name != null)
                    hashCode = hashCode * 59 + this.Name.GetHashCode();
                if (this.FancyParam != null)
                    hashCode = hashCode * 59 + this.FancyParam.GetHashCode();
                if (this.OtherParam != null)
                    hashCode = hashCode * 59 + this.OtherParam.GetHashCode();
                if (this.Mode != null)
                    hashCode = hashCode * 59 + this.Mode.GetHashCode();
                return hashCode;
            }
        }

        /// <summary>
        /// To validate all properties of the instance
        /// </summary>
        /// <param name="validationContext">Validation context</param>
        /// <returns>Validation Result</returns>
        IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
        {
            yield break;
        }
    }
}

The code executed above interactively will throw an appropriate exception stating that the required properties are missing.

There are certainly ways other than adding the null-conditional operator in order to make sure that the required field is properly checked. One could for example also remove the default from the ctor.

Suggest a fix/enhancement

Remove the required tag from the ctor as well as the declared enum in modelGeneric.mustache as introduced in this change).

I will try to file a PR within the next days.

Related issues/PR

swagger-api/swagger-codegen#4262
swagger-api/swagger-codegen#6873
swagger-api/swagger-codegen#6690
swagger-api/swagger-codegen#6459
swagger-api/swagger-codegen#8519

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions