swagger-api / swagger-core

Examples and server integrations for generating the Swagger API Specification, which enables easy access to your REST API
http://swagger.io
Apache License 2.0
7.39k stars 2.19k forks source link

Could `@Schema(nullable = true)` work when outputting OpenAPI 3.1.0? #4555

Open karlvr opened 1 year ago

karlvr commented 1 year ago

Due to the removal of nullable: true from schema in 3.1.0, it appears that the nullable = true property on the @Schema annotation no longer works. That isn't obvious in the documentation for the annotation, and it's inconvenient to boot ;-)

I think it will be quite easy to continue to support the nullable attribute under 3.1.0, I suggest a little change to Schema.getTypes:

    @OpenAPI31
    public Set<String> getTypes() {
        if (types != null) {
            if (nullable != null && nullable.booleanValue()) {
                Set<String> ss = new LinkedHashSet<>(types);
                ss.add("null");
                return ss;
            } else {
                return types;
            }
        } else {
            return null;
        }
    }

We simply add "null" to the list of types if the schema is nullable... which ends up nicely in the schema output.

I'm happy to make a PR of this, or a better suggestion, or I'd love to know if this is more like the wrong approach!

karlvr commented 1 year ago

Ugh, nullable properties that use a $ref aren't that simple... it seems they need to transform into:

allOf:
  - $ref: '#/...'
  - { type: "null" }

Heh, I'm not immediately a fan of OpenAPI 3.1 :-D

Instead I've solved this with a filter. If you, the maintainers, think that support for Schema.nullable is nice for OpenAPI 3.1 support, I'd be happy to work on a PR, otherwise I'll leave this filter here for anyone else in this predicament to enjoy and please close this issue!

import java.util.List;
import java.util.Map;
import java.util.Optional;

import io.swagger.v3.core.filter.AbstractSpecFilter;
import io.swagger.v3.core.util.AnnotationsUtils;
import io.swagger.v3.oas.models.SpecVersion;
import io.swagger.v3.oas.models.media.Schema;

/**
 * A filter to add support for {@link Schema#getNullable()} under OpenAPI 3.1.0. As of swagger-core 2.2.19 that property
 * is ignored when serializing to OpenAPI 3.1.0. This filter modifies property schemas to translate nullable to
 * OpenAPI 3.1.0.
 * <p>
 * OpenAPI 3.1.0 doesn't directly support {@code nullable} anymore; instead we must add {@code null} to the array of types.
 */
public class NullableToOpenAPI31Filter extends AbstractSpecFilter {

    @Override
    public Optional<Schema> filterSchemaProperty(Schema property, Schema schema, String propName,
            Map<String, List<String>> params, Map<String, String> cookies, Map<String, List<String>> headers) {
        if (property.getNullable() != null && property.getNullable().booleanValue()) {
            if (property.get$ref() != null) {
                Schema clone = AnnotationsUtils.clone(property, isOpenAPI31Filter());

                clone.$ref(null);

                clone.addAnyOfItem(new Schema(property.getSpecVersion()).$ref(property.get$ref()));
                clone.addAnyOfItem(new NullSchema(property.getSpecVersion()));

                return Optional.of(clone);
            } else {
                Schema clone = AnnotationsUtils.clone(property, isOpenAPI31Filter());
                if (!clone.getTypes().contains("null")) {
                    clone.addType("null");
                }
                return Optional.of(clone);
            }
        } else {
            return super.filterSchemaProperty(property, schema, propName, params, cookies, headers);
        }
    }

    @Override
    public boolean isOpenAPI31Filter() {
        return true;
    }

    public static class NullSchema extends Schema {

        public NullSchema(SpecVersion specVersion) {
            super("null", null, specVersion);
        }

    }

}
zeldigas commented 5 months ago

@karlvr how do you apply this filter?

karlvr commented 4 months ago

@zeldigas sorry for the slow reply, like this in my class that extends javax.ws.rs.core.Application:

OpenAPI oas = new OpenAPI();
...
SwaggerConfiguration oasConfig = new SwaggerConfiguration();
oasConfig.setOpenAPI(oas);
oasConfig.openAPI31(true); /* Required so that descriptions on properties that turn into $refs stay on the property */
oasConfig.setFilterClass(NullableToOpenAPI31Filter.class.getName());

try {
    new JaxrsOpenApiContextBuilder()
            .servletConfig(servletConfig)
            .application(this)
            .openApiConfiguration(oasConfig)
            .buildContext(true);
} catch (OpenApiConfigurationException e) {
    throw new RuntimeException(e.getMessage(), e);
}