openapiquarkussmallryeeclipse-microprofile-config

Override ref value in smallrye microprofile using custom OASFilter in quarkus application


I'm trying to override ref value in schema for microprofile health check API in quarkus application. I followed the below link Smallrye open api interceptor and created a custom filter which overrides OASFilter. But, the ref value is not picking the new ref value from the filter.

@Override
public APIResponse filterAPIResponse(APIResponse apiResponse) {
    if (apiResponse.getRef()=="#/components/schemas/HealthCheckResponse")
    {
        String ref = "#components/schemas/microProfile";
        apiResponse.setRef(ref);
    }

    return apiResponse;
}

Basically, I need to add description to each property inside a schema.

Existing schema:

HealthCheckResponse:
  type: object
  properties:
    data:
      type: object
      nullable: true
    name:
      type: string
    status:
      $ref: '#/components/schemas/HealthCheckStatus'
HealthCheckStatus:
  enum:
  - DOWN
  - UP
  type: string

Expected schema change:

microProfile:
  description: microprofile response
  type: object
  properties:
    data:
      description: "Information of the service. If the service is down, this holds\
        \ the information of why it is failed."
      type: object
    name:
      description: 'Service Name '
      type: string
    status:
      description: 'Service Status '
      type: string

I added the property mp.openapi.filter= custom filter name in application.properties file. Any help is greatly appreciated.


Solution

  • There are 2 ways that I am aware off:



    You can programmatically create your own custom schema and reference it.

    In this case, the Schema is created programmatically, and does not have to exist anywhere in your openapi.yaml file.

        void updateHealthStatusRefWithProgrammaticSchema(OpenAPI openAPI) {
    
            openAPI.getPaths().getPathItems().forEach((String pathName, PathItem pathItem) -> {
                if (pathName.equalsIgnoreCase("/health-check")) {
    
                    Schema dynamicSchema = OASFactory.createSchema().title("Programmatic-MicroProfile").description("dynamic-schema-description").type(Schema.SchemaType.OBJECT).properties(Map.of("custom-field-data", OASFactory.createSchema().description("Information of the service. If the service is down, this holds the information of why it is failed.").type(Schema.SchemaType.OBJECT)));
                    openAPI.getComponents().addSchema("Dynamic-MicroProfile", dynamicSchema);
    
                    pathItem.getGET().getResponses().getAPIResponse("200").getContent().getMediaType("application/json").setSchema(dynamicSchema);
                }
            });
        }
    

    enter image description here



    You can have a defined Static Schema in your openapi.yaml which you can reference programmatically.

    In this case, the schema must exist in your openapi.yaml file, as you can see we are searching for it by doing get()**

        void updateHealthStatusRefByUsingStaticSchema(OpenAPI openAPI) {
    
            openAPI.getPaths().getPathItems().forEach((String pathName, PathItem pathItem) -> {
                if (pathName.equalsIgnoreCase("/health-check")) {
                    Schema staticMicroProfileSchema = openAPI.getComponents().getSchemas().get("Static-MicroProfile");
                    pathItem.getGET().getResponses().getAPIResponse("200").getContent().getMediaType(MediaType.APPLICATION_JSON).setSchema(staticMicroProfileSchema);
                }
            });
        }
    

    enter image description here



    You update the openapi.yaml only if you want to have a Static Schema already defined.

    openapi.yaml

    components:
      schemas:
          HealthCheckResponse:
            type: object
            properties:
              data:
                type: object
                nullable: true
              name:
                type: string
              status:
                $ref: '#/components/schemas/HealthCheckStatus'
          HealthCheckStatus:
            enum:
              - DOWN
              - UP
            type: string
          Static-MicroProfile:
            description: microprofile response
            type: object
            properties:
              data:
                description: "Information of the service. If the service is down, this holds\
                  \ the information of why it is failed."
                type: object
              name:
                description: 'Service Name '
                type: string
              status:
                description: 'Service Status '
                type: string
    

    enter image description here


    Filter:

    package org.acme;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.node.ObjectNode;
    import org.eclipse.microprofile.openapi.OASFactory;
    import org.eclipse.microprofile.openapi.OASFilter;
    import org.eclipse.microprofile.openapi.models.Components;
    import org.eclipse.microprofile.openapi.models.OpenAPI;
    
    import java.io.IOException;
    import java.nio.charset.StandardCharsets;
    import java.util.HashMap;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    import org.eclipse.microprofile.openapi.models.PathItem;
    import org.eclipse.microprofile.openapi.models.examples.Example;
    
    import io.quarkus.logging.Log;
    import org.eclipse.microprofile.openapi.models.media.Schema;
    
    import javax.ws.rs.core.MediaType;
    
    public class CustomOASFilter implements OASFilter {
    
        ObjectMapper objectMapper = new ObjectMapper();
    
        @Override
        public void filterOpenAPI(OpenAPI openAPI) {
    
            //openApi.getComponents() will result in NULL as we don't have any openapi.yaml file.
            Components defaultComponents = OASFactory.createComponents();
            if (openAPI.getComponents() == null) {
                openAPI.setComponents(defaultComponents);
            }
    
            generateExamples().forEach(openAPI.getComponents()::addExample);
    
            updateHealthStatusRefWithProgrammaticSchema(openAPI);
        }
    
        Map<String, Example> generateExamples() {
    
            Map<String, Example> examples = new LinkedHashMap<>();
    
            try {
    
                ClassLoader loader = Thread.currentThread().getContextClassLoader();
    
                String userJSON = new String(loader.getResourceAsStream("user.json").readAllBytes(), StandardCharsets.UTF_8);
                String customerJson = new String(loader.getResourceAsStream("customer.json").readAllBytes(), StandardCharsets.UTF_8);
    
                Example userExample = OASFactory.createExample().description("User JSON Example Description").value(objectMapper.readValue(userJSON, ObjectNode.class));
    
                Example customerExample = OASFactory.createExample().description("Customer JSON Example Description").value(objectMapper.readValue(customerJson, ObjectNode.class));
    
                examples.put("userExample", userExample);
                examples.put("customerExample", customerExample);
    
            } catch (IOException ioException) {
                Log.error(ioException);
            }
            return examples;
        }
    
            void updateHealthStatusRefWithProgrammaticSchema(OpenAPI openAPI) {
    
                openAPI.getPaths().getPathItems().forEach((String pathName, PathItem pathItem) -> {
                    if (pathName.equalsIgnoreCase("/health-check")) {
    
                        Schema dynamicSchema = OASFactory.createSchema().title("Programmatic-MicroProfile").description("dynamic-schema-description").type(Schema.SchemaType.OBJECT).properties(Map.of("custom-field-data", OASFactory.createSchema().description("Information of the service. If the service is down, this holds the information of why it is failed.").type(Schema.SchemaType.OBJECT)));
                        openAPI.getComponents().addSchema("Dynamic-MicroProfile", dynamicSchema);
    
                        pathItem.getGET().getResponses().getAPIResponse("200").getContent().getMediaType("application/json").setSchema(dynamicSchema);
                    }
                });
            }
    
            void updateHealthStatusRefByUsingStaticSchema(OpenAPI openAPI) {
    
                openAPI.getPaths().getPathItems().forEach((String pathName, PathItem pathItem) -> {
                    if (pathName.equalsIgnoreCase("/health-check")) {
                        Schema staticMicroProfileSchema = openAPI.getComponents().getSchemas().get("Static-MicroProfile");
                        pathItem.getGET().getResponses().getAPIResponse("200").getContent().getMediaType(MediaType.APPLICATION_JSON).setSchema(staticMicroProfileSchema);
                    }
                });
            }
    }
    

    application.properties:

    mp.openapi.filter=org.acme.CustomOASFilter
    

    Full-Example:

    https://github.com/smustafa/quarkuks-openapi-exampleobject-loading-external-files