-
Notifications
You must be signed in to change notification settings - Fork 436
Description
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
- Generate the C# files using the command line above
- 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;
}
}
}
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