I have a Gateway service with dynamically created routes. I want it to expose its endpoints through Swagger UI. Since my routes are not static (they come and go as new services are registered and deregistered with Eureka), I can't do it purely with static yamls (like so). So what I did was:
springdoc:
api-docs:
enabled: false
swagger-ui:
enabled: true
path: /swagger-ui.html
config-url: /swagger-ui-config
package by.afinny.apigateway.controller;
import by.afinny.apigateway.model.uiConfig.SwaggerUiConfig;
import by.afinny.apigateway.service.SwaggerUiConfigProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
@RequiredArgsConstructor
public class SwaggerUiConfigController {
private final SwaggerUiConfigProvider configProvider;
@GetMapping("/swagger-ui-config")
public Mono<SwaggerUiConfig> getConfig() {
return configProvider.getSwaggerUiConfig();
}
}
package by.afinny.apigateway.model.uiConfig;
import by.afinny.apigateway.model.documentedApplication.SwaggerApplication;
import by.afinny.apigateway.service.SwaggerUiConfigSerializer;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.Collection;
@NoArgsConstructor
@Getter
public class SwaggerUiConfig {
@JsonProperty("urls")
@JsonSerialize(contentUsing = SwaggerUiConfigSerializer.class)
private Collection<SwaggerApplication> swaggerApplications;
public SwaggerUiConfig(Collection<SwaggerApplication> swaggerApplications) {
this.swaggerApplications = swaggerApplications;
}
public static SwaggerUiConfig from(Collection<SwaggerApplication> swaggerApplications) {
return new SwaggerUiConfig(swaggerApplications);
}
}
package by.afinny.apigateway.service;
import by.afinny.apigateway.model.documentedApplication.SwaggerApplication;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;
import java.io.IOException;
import java.text.MessageFormat;
public class SwaggerUiConfigSerializer extends JsonSerializer<SwaggerApplication> {
@Override
@SneakyThrows
public void serialize(SwaggerApplication swaggerApplication, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("url", MessageFormat.format("/{0}{1}", swaggerApplication.getName(), SwaggerApplication.SWAGGER_DOC_PATH));
jsonGenerator.writeStringField("name", swaggerApplication.getName());
jsonGenerator.writeEndObject();
}
}
lb://SERVICE-NAME/v3/api-docs
// I'll omit that code
I won't provide all the code since it doesn't really matter, and I don't want to overwhelm you. I wanted to show you a general flow
But here's the thing: I already fetched those JSON docs to build my business routes (a route per endpoint), they are already stored in memory (and updated). I don't want to go through all that fuss just to let Swagger do that unnecessary extra work of fetching the docs again
Can I somehow give Swagger UI docs directly instead of telling it where to find them?
The most direct way I could find is writing endpoints for both the config and the docs themselves. Doc endpoints may return cached OpenAPI
objects
package com.example.dynamicgateway.controller;
import com.example.dynamicgateway.model.uiConfig.SwaggerUiConfig;
import com.example.dynamicgateway.service.swaggerUiSupport.SwaggerUiSupport;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
@RequiredArgsConstructor
public class SwaggerUiConfigController {
private final SwaggerUiSupport uiSupport;
@GetMapping("/swagger-ui-config")
public Mono<SwaggerUiConfig> getConfig() {
return uiSupport.getSwaggerUiConfig();
}
}
package com.example.dynamicgateway.controller;
import com.example.dynamicgateway.service.swaggerUiSupport.SwaggerUiSupport;
import io.swagger.v3.oas.models.OpenAPI;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
@RequiredArgsConstructor
public class SwaggerDocController {
private final SwaggerUiSupport uiSupport;
@GetMapping("{application-name}/doc")
public Mono<OpenAPI> getSwaggerAppDoc(@PathVariable("application-name") String applicationName) {
// return cached OpenAPI object right away
return uiSupport.getSwaggerAppDoc(applicationName);
}
}
package com.example.dynamicgateway.model.uiConfig;
import com.example.dynamicgateway.model.documentedApplication.SwaggerApplication;
import com.example.dynamicgateway.service.swaggerUiSupport.SwaggerUiConfigSerializer;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.Collection;
@NoArgsConstructor
@Getter
public class SwaggerUiConfig {
@JsonProperty("urls")
@JsonSerialize(contentUsing = SwaggerUiConfigSerializer.class)
private Collection<SwaggerApplication> swaggerApplications;
public SwaggerUiConfig(Collection<SwaggerApplication> swaggerApplications) {
this.swaggerApplications = swaggerApplications;
}
public static SwaggerUiConfig from(Collection<SwaggerApplication> swaggerApplications) {
return new SwaggerUiConfig(swaggerApplications);
}
}
package com.example.dynamicgateway.service.swaggerUiSupport;
import com.example.dynamicgateway.model.documentedApplication.SwaggerApplication;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;
import java.text.MessageFormat;
public class SwaggerUiConfigSerializer extends JsonSerializer<SwaggerApplication> {
@Override
@SneakyThrows
public void serialize(SwaggerApplication swaggerApplication, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("url", MessageFormat.format("/{0}/doc", swaggerApplication.getName()));
jsonGenerator.writeStringField("name", swaggerApplication.getName());
jsonGenerator.writeEndObject();
}
}
springdoc:
swagger-ui:
enabled: true
path: /swagger-ui.html
config-url: /swagger-ui-config