Azure / Azure/azure-functions-openapi-extension

OpenApiProperty Nullable does not seem to work for classes

Open
#200 14 comments 4 reactions 0 assignees View on GitHub
discussion
Dominant language
C#
Stars
388
Forks
202
PR merge metrics
No merged PRs in 30d

Description

## Overview

I have an existing Function using the OpenAPI library that I was able to apply Nullable to for some string properties.
However other properties such as classes do not generate the expected nullable section in swagger.json

While attempting to create a reproducible, I'm unable to see any nullable/required generated in the swagger for OpenAPI V2 (default), and it doesn't work with classes for OpenAPI V3.

## Reproducible

Here is a simple Function.
`csproj`
```csproj


netcoreapp3.1
v3
Enable
9.0







PreserveNewest


PreserveNewest
Never

```

Function1.cs
```cs
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Abstractions;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;

namespace FunctionApp2
{
public class ChildModel
{
[OpenApiProperty(Nullable = false, Description = "required!")]
public string RequiredData { get; set; } = string.Empty;

[OpenApiProperty(Nullable = true, Description = "optional!")]
public string? OptionalData { get; set; }
}

public class FooModel
{
[OpenApiProperty(Nullable = false)]
public ChildModel RequiredChild { get; set; } = new ChildModel();

[OpenApiProperty(Nullable = true)]
public ChildModel? OptionalChild { get; set; }

[OpenApiProperty(Nullable = false, Description = "required!")]
public string RequiredData { get; set; } = string.Empty;

[OpenApiProperty(Nullable = true, Description = "optional!")]
public string? OptionalData { get; set; }
}

public class OpenApiConfigurationOptions : IOpenApiConfigurationOptions
{
public OpenApiInfo Info { get; set; } = new OpenApiInfo
{
Title = "Test API",
Version = "1.0"
};

public List Servers { get; set; } = new();

public OpenApiVersionType OpenApiVersion { get; set; } = OpenApiVersionType.V3;
public bool IncludeRequestingHostName { get; set; } = false;
}

public class FooFunction
{
[FunctionName("foo")]
[OpenApiOperation("foo")]
[OpenApiResponseWithBody(HttpStatusCode.OK, "application/json", typeof(FooModel))]
public async Task> GetFoo(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
ILogger log)
{
var model = new FooModel();
return new OkObjectResult(model);
}
}
}
```

`swagger.json`
```json
{
"openapi": "3.0.1",
"info": {
"title": "Test API",
"version": "1.0"
},
"servers": [
{
"url": "http://localhost:7071/api"
}
],
"paths": {
"/foo": {
"get": {
"operationId": "foo",
"responses": {
"200": {
"description": "Payload of FooModel",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/fooModel"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"childModel": {
"type": "object",
"properties": {
"requiredData": {
"type": "string",
"description": "required!"
},
"optionalData": {
"type": "string",
"description": "optional!",
"nullable": true
}
}
},
"fooModel": {
"type": "object",
"properties": {
"requiredChild": {
"$ref": "#/components/schemas/childModel"
},
"optionalChild": {
"$ref": "#/components/schemas/childModel"
},
"requiredData": {
"type": "string",
"description": "required!"
},
"optionalData": {
"type": "string",
"description": "optional!",
"nullable": true
}
}
}
}
}
}
```

Generated FooModel using NSwag.
```cs
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class FooModel
{
[Newtonsoft.Json.JsonProperty("requiredChild", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public ChildModel? RequiredChild { get; set; }= default!;

[Newtonsoft.Json.JsonProperty("optionalChild", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public ChildModel? OptionalChild { get; set; }= default!;

/// required!
[Newtonsoft.Json.JsonProperty("requiredData", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string? RequiredData { get; set; }= default!;

/// optional!
[Newtonsoft.Json.JsonProperty("optionalData", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string? OptionalData { get; set; }= default!;

private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary();

[Newtonsoft.Json.JsonExtensionData]
public System.Collections.Generic.IDictionary AdditionalProperties
{
get { return _additionalProperties; }
set { _additionalProperties = value; }
}


}
```

## Expected
I would expect "nullable": true in the "components.schemas.fooModel.optionalChild" node.
And the generated code to have the same Required value as the "optionalData" property.

In the docs it's not clear that this will not do anything unless V3 is specified, or if this is intentional or not.

Contributor guide

Open the contributing guide

Research direction

Start with the Function1.cs reproduction and its csproj package configuration, then inspect the generated swagger.json and NSwag output shown in the issue. Compare nullable and required handling for the string and ChildModel properties in OpenAPI V2 and V3. Done means optionalChild emits nullable true and the generated code applies the expected Required value, with the documented version behavior made clear.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, csharp, openapi
Domain
api
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.