Skip to content

Swagger 2.X Extensions

frantuma edited this page Nov 28, 2022 · 9 revisions

NOTE: Swagger Core 2.X produces OpenApi 3.0 definition files. For more information, check out the OpenAPI specification repository. If you're looking for swagger 1.5.X and OpenApi 2.0, please refer to 1.5.X JAX-RS Setup


NOTE: Since version 2.2.0 Swagger Core supports OpenAPI 3.1; see this page for details


NOTE: Jakarta namespace support (since version 2.1.7)

Since version 2.1.7 Swagger Core supports also Jakarta namespace, with a parallel set of artifacts with -jakarta suffix, providing the same functionality as the "standard" javax namespace ones.

While behaviour described in this documentation is the same for both namespaces, artifact IDs, JEE / Jakarta EE versions and Jackson versions mentioned refer to javax namespace.

If you are using jakarta namespace:

  • when you read artifact IDs in the form: swagger-* (e.g. swagger-core), replace them with swagger-*-jakarta (e.g. swagger-core-jakarta)
  • when you read javax.* in package names, replace that with jakarta (e.g jakarta.ws.rs.GET)
  • when JEE / Jakarta EE dependencies are provided in examples, replace their version with Jakarta EE 9 versions.
  • When Jackson dependencies are provided in examples, add the jakarta classifier for artifacts supporting it. See Jackson release notes Jakarta namespace Swagger Core artifacts need Jackson 2.12+

Customising the Swagger Definition

If for any reason you need to customise the generated OpenAPI definition beyond what is possible with the annotations, there are different ways to do it (also applying more than one at the same time):

Reader listeners

You can provide the Swagger engine with a ReaderListener that provides the corresponding callbacks:

public interface ReaderListener {

    /**
     * Called before the OpenAPI definition gets populated from scanned classes. Use this method to
     * pre-process the OpenAPI definition before it gets populated.
     *
     * @param reader the reader used to read annotations and build the OpenAPI definition
     * @param openAPI the initial OpenAPI definition
     */

    void beforeScan(Reader reader, OpenAPI openAPI);

    /**
     * Called after a OpenAPI definition has been populated from scanned classes. Use this method to
     * post-process OpenAPI definitions.
     *
     * @param reader the reader used to read annotations and build the OpenAPI definition
     * @param openAPI the configured OpenAPI definition
     */

    void afterScan(Reader reader, OpenAPI openAPI);
}

Any class found during resource scanning implementing this interface will be instantiated and invoked correspondingly. For example the following class:

public class BasePathModifier implements ReaderListener {
    void beforeScan(Reader reader, Swagger openAPI){}

    void afterScan(Reader reader, Swagger openAPI){
        openAPI.getInfo().setTitle(System.getProperty( "swagger.title", openAPI.getInfo().getTitle() ));
    }
}

Would allow you to override the title from a system-property.

Filters

You can provide an OpenAPISpecFilter implementation in Configuration to be able to filter out or even completely change the resolved definition.

When implementing the filter, extending AbstractSpecFilter is probably the most convenient option, as it allows to override only the needed filter method.

A simple example of a filter could be:

/**
 * Sample filter to avoid all resources for the /user resource
 **/
public class NoPetOperationsFilter extends AbstractSpecFilter {

    public static final String PET_RESOURCE = "/pet";

    @Override
    public Optional<Operation> filterOperation(Operation operation, ApiDescription api, Map<String, List<String>> params, Map<String, String> cookies, Map<String, List<String>> headers) {
        if (api.getPath().startsWith(PET_RESOURCE)) {
            return Optional.empty();
        }
        return Optional.of(operation);
    }
}

check out the tests for some additional examples of filter usage.

When relying on provided JAX-RS resources to expose the API (since version 2.0.8 also using maven or gradle plugin), the filter defined in configuration will be applied automatically, otherwise, e.g. when serving the definition from own servlet or resource, you can apply it for example like:

    OpenAPI oas = resolveDefinition()
    OpenAPISpecFilter filterImpl = my.custom.Filter;
    SpecFilter f = new SpecFilter();
    oas = f.filter(oas, filterImpl, getQueryParams(uriInfo.getQueryParameters()), getCookies(headers),
            getHeaders(headers));

Configure a custom scanner

Please check out Resource scanning

Extending Reader

As mentioned in Resolve the OpenAPI Definition, the default JAX-RS reader is responsible for the core logic which builds the OpenAPI definition out of JAX-RS resources and applied configuration.

In cases where you want to customize such logic (e.g. you might want to also consider custom annotations, or any other available metadata), you can provide your own implementation of Reader, implementing OpenApiReader interface and providing the qualified class name in the Configuration:

readerClass: my.custom.MyReader
prettyPrint: true

You can either extend default Reader e.g. to customize the logic resolving the application path:

    class MyReader extends Reader {
        @Override
        protected String resolveApplicationPath() {
            return "custom logic to resolve path";
        }
    }

or you can provide a completely customized reader implementation with the same mechanism by directly implementing OpenApiReader, for example in non JAX-RS environments.

Extending core Resolver

swagger-core converter/resolver logic is responsible for resolving types into OpenAPI schemas. This applies e.g to response schemas, request body, parameters, etc, and uses as input the POJO classes together with available metadata, including various annotations (e.g. Swagger, JAX-RS, Jackson, JAXB, etc.).

You can customize such logic by implementing your own ModelConverter (or chain of these, e.g. each handling a different type):

public class SamplePropertyConverter implements ModelConverter {

    @Override
    public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterator<ModelConverter> chain) {
        if (type.isSchemaProperty()) {
            JavaType _type = Json.mapper().constructType(type.getType());
            if (_type != null) {
                Class<?> cls = _type.getRawClass();
                if (MyCustomClass.class.isAssignableFrom(cls)) {
                    return new DateTimeSchema();
                }
            }
        }
        if (chain.hasNext()) {
            return chain.next().resolve(type, context, chain);
        } else {
            return null;
        }
    }
}

and registering it:

ModelConverters.getInstance().addConverter(new SamplePropertyConverter());

Check out test samples for same more examples.

You can also extend ModelResolver (default implementation of ModelConverter) and override specific resolve.. methods to fine tune only a subset of definition properties, e.g.:

public class MyCustomResolver extends ModelResolver {

    public MyCustomResolver(ObjectMapper mapper) {
        super(mapper);
    }

    @Override
    protected Object resolveExample(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
        if (a.getAnnotation(CustomAnnotation.class) != null) {
            return customResolveExampleFromCustomAnnotation(a.getAnnotation(CustomAnnotation.class));
        }
        return super.resolveExample(a, annotations, schema);
    }
}

and register it:

ModelConverters.getInstance().addConverter(new MyCustomResolver(Json.mapper()));
Clone this wiki locally