Azure / Azure/azure-functions-openapi-extension
OpenApiProperty Nullable does not seem to work for classes
- 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
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