OpenAPITools / OpenAPITools/openapi-generator
[REQ] [csharp-netcore] Complete nullable reference types question / request
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 26.8k
- Forks
- 7.7k
- PR merge metrics
- PR metrics pending
Description
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:
- openapitools.json
{
"$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "6.6.0"
}
}
- openapi-generator-config.json
{
"globalProperties": {
"apiTests": false,
"modelTests": false
},
"apiName": "ApiName",
"netCoreProjectFile": true,
"nullableReferenceTypes": true,
"packageName": "OpenApiClient",
"packageVersion": "1.0.0",
"targetFramework": "net6.0"
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the csharp-netcore generator's handling of the nullableReferenceTypes option and compare its output with the supplied OpenAPI example. Check how nullable reference and value types are represented in the generated BearerToken and SubObject models. Done means nullable schema properties receive the expected C# annotations without changing required properties.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- api
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100