OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.81k stars 6.58k forks source link

[REQ] [csharp-netcore] Complete nullable reference types question / request #15556

Open Eldriann opened 1 year ago

Eldriann commented 1 year ago

Is your feature request related to a problem? Please describe.

When using the 'csharp-netcore' generator even with nullableReferenceTypes set to true and targetting only net6.0 / net7.0 nullable reference/value types in generated models do not use nullable annotations. This leads projects referencing generated models and assigning null values to nullable elements generating warnings. I don't know if I'm doing something wrong or if this is not currently supported by the generator.

Describe the solution you'd like

When nullableReferenceTypes is set to true the reference types set to nullable or not required should have nullable annotations. A new config option nullableValueTypes could also be added to add a nullable annotation to value types in models differently from reference types.

Describe alternatives you've considered

Disable nullable context warnings in projects referencing generated models assigning null values. Currently working on custom overriding model templates as it seems from what I saw the generator just does not support this currently.

Additional context

If I didn't miss something and this is something currently not supported by the generator I can work on a new pull request supporting it.

Reproducible example

OpenApi definition (openapi.yaml):

openapi: 3.0.3
info:
  description: |
    An Open API description
  title: A title
  version: 0.1.0
servers:
- url: https://example.com
tags:
- description: All authentication related routes
  name: Authentication
paths:
  /auth/oauth/v2/token:
    post:
      description: Retrieve a Bearer token from a client id / client secret
      operationId: PostAuthOauthV2Token
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/AuthenticationBody'
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BearerToken'
          description: Bearer token returned
      security: []
      servers:
      - url: https://auth.example.com
      summary: /auth/oauth/v2/token - Retrieve a Bearer token
      tags:
      - Authentication
components:
  schemas:
    AuthenticationBody:
      properties:
        client_id:
          type: string
        client_secret:
          type: string
        grant_type:
          type: string
      required:
      - client_id
      - client_secret
      - grant_type
      type: object
    BearerToken:
      properties:
        access_token:
          type: string
        token_type:
          type: string
        expires_in:
          type: integer
          nullable: true
        scope:
          type: string
          nullable: true
        nullableSubobject:
          nullable: true
          allOf: 
            - $ref: '#/components/schemas/SubObject'
      required:
      - access_token
      - token_type
      type: object
    SubObject:
      properties:
        aProp:
          type: string
          nullable: true
        bProp:
          type: integer
          nullable: true
      type: object
  securitySchemes:
    bearerAuth:
      scheme: bearer
      type: http

Generated BearerToken class

/*
 * A title
 *
 * An Open API description 
 *
 * The version of the OpenAPI document: 0.1.0
 * Generated by: https://github.com/openapitools/openapi-generator.git
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.IO;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using System.ComponentModel.DataAnnotations;
using OpenAPIDateConverter = OpenApiClient.OpenAPIDateConverter;

namespace OpenApiClient.Model
{
    /// <summary>
    /// BearerToken
    /// </summary>
    [DataContract(Name = "BearerToken")]
    public partial class BearerToken : IEquatable<BearerToken>, IValidatableObject
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="BearerToken" /> class.
        /// </summary>
        [JsonConstructorAttribute]
        protected BearerToken() { }
        /// <summary>
        /// Initializes a new instance of the <see cref="BearerToken" /> class.
        /// </summary>
        /// <param name="accessToken">accessToken (required).</param>
        /// <param name="tokenType">tokenType (required).</param>
        /// <param name="expiresIn">expiresIn.</param>
        /// <param name="scope">scope.</param>
        /// <param name="nullableSubobject">nullableSubobject.</param>
        public BearerToken(string accessToken = default(string), string tokenType = default(string), int? expiresIn = default(int?), string scope = default(string), SubObject nullableSubobject = default(SubObject))
        {
            // to ensure "accessToken" is required (not null)
            if (accessToken == null)
            {
                throw new ArgumentNullException("accessToken is a required property for BearerToken and cannot be null");
            }
            this.AccessToken = accessToken;
            // to ensure "tokenType" is required (not null)
            if (tokenType == null)
            {
                throw new ArgumentNullException("tokenType is a required property for BearerToken and cannot be null");
            }
            this.TokenType = tokenType;
            this.ExpiresIn = expiresIn;
            this.Scope = scope;
            this.NullableSubobject = nullableSubobject;
        }

        /// <summary>
        /// Gets or Sets AccessToken
        /// </summary>
        [DataMember(Name = "access_token", IsRequired = true, EmitDefaultValue = true)]
        public string AccessToken { get; set; }

        /// <summary>
        /// Gets or Sets TokenType
        /// </summary>
        [DataMember(Name = "token_type", IsRequired = true, EmitDefaultValue = true)]
        public string TokenType { get; set; }

        /// <summary>
        /// Gets or Sets ExpiresIn
        /// </summary>
        [DataMember(Name = "expires_in", EmitDefaultValue = true)]
        public int? ExpiresIn { get; set; }

        /// <summary>
        /// Gets or Sets Scope
        /// </summary>
        [DataMember(Name = "scope", EmitDefaultValue = true)]
        public string Scope { get; set; }

        /// <summary>
        /// Gets or Sets NullableSubobject
        /// </summary>
        [DataMember(Name = "nullableSubobject", EmitDefaultValue = true)]
        public SubObject NullableSubobject { get; set; }

        /// <summary>
        /// Returns the string presentation of the object
        /// </summary>
        /// <returns>String presentation of the object</returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("class BearerToken {\n");
            sb.Append("  AccessToken: ").Append(AccessToken).Append("\n");
            sb.Append("  TokenType: ").Append(TokenType).Append("\n");
            sb.Append("  ExpiresIn: ").Append(ExpiresIn).Append("\n");
            sb.Append("  Scope: ").Append(Scope).Append("\n");
            sb.Append("  NullableSubobject: ").Append(NullableSubobject).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 Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.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 BearerToken);
        }

        /// <summary>
        /// Returns true if BearerToken instances are equal
        /// </summary>
        /// <param name="input">Instance of BearerToken to be compared</param>
        /// <returns>Boolean</returns>
        public bool Equals(BearerToken input)
        {
            if (input == null)
            {
                return false;
            }
            return 
                (
                    this.AccessToken == input.AccessToken ||
                    (this.AccessToken != null &&
                    this.AccessToken.Equals(input.AccessToken))
                ) && 
                (
                    this.TokenType == input.TokenType ||
                    (this.TokenType != null &&
                    this.TokenType.Equals(input.TokenType))
                ) && 
                (
                    this.ExpiresIn == input.ExpiresIn ||
                    (this.ExpiresIn != null &&
                    this.ExpiresIn.Equals(input.ExpiresIn))
                ) && 
                (
                    this.Scope == input.Scope ||
                    (this.Scope != null &&
                    this.Scope.Equals(input.Scope))
                ) && 
                (
                    this.NullableSubobject == input.NullableSubobject ||
                    (this.NullableSubobject != null &&
                    this.NullableSubobject.Equals(input.NullableSubobject))
                );
        }

        /// <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.AccessToken != null)
                {
                    hashCode = (hashCode * 59) + this.AccessToken.GetHashCode();
                }
                if (this.TokenType != null)
                {
                    hashCode = (hashCode * 59) + this.TokenType.GetHashCode();
                }
                if (this.ExpiresIn != null)
                {
                    hashCode = (hashCode * 59) + this.ExpiresIn.GetHashCode();
                }
                if (this.Scope != null)
                {
                    hashCode = (hashCode * 59) + this.Scope.GetHashCode();
                }
                if (this.NullableSubobject != null)
                {
                    hashCode = (hashCode * 59) + this.NullableSubobject.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;
        }
    }

}

What I expected (truncated to show only changed elements):

       /* ... */

        public BearerToken(string accessToken = default(string), string tokenType = default(string), int? expiresIn = default(int?), string? scope = default(string?), SubObject? nullableSubobject = default(SubObject?))

       /* ... */

        /// <summary>
        /// Gets or Sets Scope
        /// </summary>
        [DataMember(Name = "scope", EmitDefaultValue = true)]
        public string? Scope { get; set; }

        /// <summary>
        /// Gets or Sets NullableSubobject
        /// </summary>
        [DataMember(Name = "nullableSubobject", EmitDefaultValue = true)]
        public SubObject? NullableSubobject { get; set; }

       /* ... */

Command used: openapi-generator-cli generate -i ./openapi.yaml -g csharp-netcore -o ./Autogenerated -c ./openapi-generator-config.json

Config files:

devhl-labs commented 1 year ago

Consider trying the generichost library. It has better support for nrt.