RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.61k stars 1.22k forks source link

OpenAPI document generator does not account for `required` keyword when determining if a property is required #4878

Open thorhj opened 2 months ago

thorhj commented 2 months ago

Generated Open API definition does not mark properties as required if I use the required keyword on the properties.

Consider this request body type:

public record PatchUserRequest
{
  public string? Message { get; init; }
  public required int Age { get; init; }
}

When generating the Open API definition using NSwag, this type schema is generated:

      "PatchUserRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "message": {
            "type": "string",
            "nullable": true
          },
          "age": {
            "type": "integer",
            "format": "int32"
          }
        }
      }

Notice there are no required properties in this object. I think the usage of required on the property should add the property to the required list in the object schema. Right now I have to annotate the property with [Required] to get the desired effect.

With or without RequiredAttribute, deserialization to this object fails if "age" is omitted in the JSON if I use the default JSON serializer for ASP.NET (System.Text.Json). I think Open API document generation should match this behavior and mark these properties as required.

I think this can be solved by looking for the RequiredMemberAttribute in addition to RequiredAttribute when determining if a property is required or not. RequiredMemberAttribute is generated by the compiler for required properties.

Alex69rus commented 1 month ago

I'm struggling with the same issue. @thorhj have you found any workaround (except RequiredAttribute) ?

thorhj commented 1 month ago

@Alex69rus Sorry, no.

Alex69rus commented 1 month ago

I wrote some workaround solution:

public class RequiredPropertiesSchemaProcessor : ISchemaProcessor
{
    public void Process(SchemaProcessorContext context)
    {
        if (context.Schema.Type != JsonObjectType.Object)
            return;

        foreach (var (_, schemaProp) in context.Schema.Properties)
        {
            // NOTE: this type and schema properties match won't work for exotic namings such as kebab-case and snake_case
            var typeProp = context.ContextualType.Properties
                .FirstOrDefault(x => x.Name.Equals(schemaProp.Name, StringComparison.InvariantCultureIgnoreCase));

            if (typeProp == null)
                continue;

            var requiredAttrs = typeProp.GetCustomAttributes(typeof(RequiredMemberAttribute), true);

            if (requiredAttrs.Length == 0)
                continue;

            schemaProp.IsRequired = true;
        }
    }
}

But it has disadvantages. It won't work for exotic cases, because schema and type fields matching is done by name (case-insensetive) and any extra symbols in schema property names will case mismatch.

But in my case (camel case) it works fine.