Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nullable Strings not being marked correctly #580

Open
JamesFieldist opened this issue Jan 10, 2025 · 3 comments
Open

Nullable Strings not being marked correctly #580

JamesFieldist opened this issue Jan 10, 2025 · 3 comments
Assignees
Labels
bug Something isn't working

Comments

@JamesFieldist
Copy link

Describe the bug
Nullable strings in the openapi.json marked correctly are not being made string? in contracts.

Consider the following openapi.json snippet:

      "AddressDto": {
        "required": [
          "street",
          "city",
          "country",
          "postalCode",
          "isDefault",
          "isVerified",
          "isHome",
          "isWork",
          "id"
        ],
        "type": "object",
        "properties": {
          "street": {
            "type": "string"
          },
          "street2": {
            "type": "string",
            "nullable": true
          },
          "street3": {
            "type": "string",
            "nullable": true
          },
          "careOf": {
            "type": "string",
            "nullable": true
          },
          "city": {
            "type": "string"
          },
          "stateProvince": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string"
          },
          "postalCode": {
            "type": "string"
          },
          "latitude": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "longitude": {
            "type": "number",
            "format": "double",
            "nullable": true
          },
          "owningContactId": {
            "type": "string",
            "nullable": true
          },
          "isDefault": {
            "type": "boolean"
          },
          "isVerified": {
            "type": "boolean"
          },
          "isHome": {
            "type": "boolean"
          },
          "isWork": {
            "type": "boolean"
          },
          "id": {
            "type": "string",
            "description": "Id. Null if new.",
            "format": "uuid",
            "nullable": true
          }
        }
      },

This produces this C#:

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
    public partial record AddressDto
    {
        [System.Text.Json.Serialization.JsonConstructor]

        public AddressDto(string @careOf, string @city, string @country, System.Guid? @id, bool @isDefault, bool @isHome, bool @isVerified, bool @isWork, double? @latitude, double? @longitude, string @owningContactId, string @postalCode, string @stateProvince, string @street, string @street2, string @street3)

        {

            this.Street = @street;

            this.Street2 = @street2;

            this.Street3 = @street3;

            this.CareOf = @careOf;

            this.City = @city;

            this.StateProvince = @stateProvince;

            this.Country = @country;

            this.PostalCode = @postalCode;

            this.Latitude = @latitude;

            this.Longitude = @longitude;

            this.OwningContactId = @owningContactId;

            this.IsDefault = @isDefault;

            this.IsVerified = @isVerified;

            this.IsHome = @isHome;

            this.IsWork = @isWork;

            this.Id = @id;

        }
        [System.Text.Json.Serialization.JsonPropertyName("street")]
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string Street { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("street2")]
        public string Street2 { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("street3")]
        public string Street3 { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("careOf")]
        public string CareOf { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("city")]
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string City { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("stateProvince")]
        public string StateProvince { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("country")]
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string Country { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("postalCode")]
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string PostalCode { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("latitude")]
        public double? Latitude { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("longitude")]
        public double? Longitude { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("owningContactId")]
        public string OwningContactId { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("isDefault")]
        public bool IsDefault { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("isVerified")]
        public bool IsVerified { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("isHome")]
        public bool IsHome { get; init; }

        [System.Text.Json.Serialization.JsonPropertyName("isWork")]
        public bool IsWork { get; init; }

        /// <summary>
        /// Id. Null if new.
        /// </summary>

        [System.Text.Json.Serialization.JsonPropertyName("id")]
        public System.Guid? Id { get; init; }

        private System.Collections.Generic.IDictionary<string, object> _additionalProperties;

        [System.Text.Json.Serialization.JsonExtensionData]
        public System.Collections.Generic.IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }

    }

Note that Street2 is not marked as string? etc. Only the doubles are.

And that's using the following:

dotnet tool run refitter https://localhost:15159/help/v1/openapi.json --output ./ --namespace TestClient.Interfaces --contracts-namespace TestClient.Contracts --trim-unused-schema --immutable-records --cancellation-tokens --multiple-files --no-deprecated-operations --optional-nullable-parameters --nullable-reference-types --disposable

This should have produced nullable string values.

Support Key: [my-support-key]

lxoej4f

Additional context
This appears to only be a problem with strings. nswag does this correctly from the definition provided.

@JamesFieldist JamesFieldist added the bug Something isn't working label Jan 10, 2025
@christianhelle
Copy link
Owner

@JamesFieldist Thanks for taking the time to report this

Refitter uses NSwag for parsing the OpenAPI document and for generating the contracts. If NSwag can do this out-of-the-box then I need to align how Refitter uses NSwag with that behavior. Let me see what I can do. Refitter caters to quite a number of users which have introduced multiple options for generating contracts

Do you use NSwag from the desktop app or CLI? Do you mind sharing the options you use with NSwag?

@JamesFieldist
Copy link
Author

nswag openapi2csclient /input:your-schema.json /output:Client.cs /nullableReferenceTypes:true /generateNullableProperties:true

Although there seems to be some claims that you have to use nullable:true in the openapi.json documentation file, but it looks like that was fixed?

@christianhelle
Copy link
Owner

And that's using the following:

dotnet tool run refitter https://localhost:15159/help/v1/openapi.json --output ./ --namespace TestClient.Interfaces --contracts-namespace TestClient.Contracts --trim-unused-schema --immutable-records --cancellation-tokens --multiple-files --no-deprecated-operations --optional-nullable-parameters --nullable-reference-types --disposable

This should have produced nullable string values.

@JamesFieldist Sorry, I just overlooked that the --optional-nullable-parameters --nullable-reference-types options don't exist as CLI arguments in Refitter. These are exposed through the settings file. Try using a .refitter settings file

Try creating a .refitter file with the following contents

{
  "openApiPath": "https://localhost:15159/help/v1/openapi.json",
  "namespace": "TestClient.Interfaces",
  "contractsNamespace": "TestClient.Contracts",
  "trimUnusedSchema": true,
  "immutableRecords": true,
  "useCancellationTokens": true,
  "generateMultipleFiles": true,
  "generateDeprecatedOperations": false,  
  "codeGeneratorSettings": {
    "generateOptionalPropertiesAsNullable": true,
    "generateNullableReferenceTypes": true
  }
}

then run Refitter with the following arguments

refitter --settings-file .refitter

The .refitter file format is described in https://refitter.github.io/articles/refitter-file-format.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants