J'ai migré de Springfox Swagger vers Springdoc OpenApi. J'ai ajouté quelques lignes dans ma configuration à propos de springdoc:
springdoc:
pathsToMatch: /api/**
packagesToScan: pl.sims.invipoconnector
api-docs:
path: /api-docs
swagger-ui:
path: /swagger-ui.html
Dans la classe de configuration MainConfig.kt
, J'ai le code suivant:
val customGson: Gson = GsonBuilder()
.registerTypeAdapter(LocalDateTime::class.Java, DateSerializer())
.registerTypeAdapter(ZonedDateTime::class.Java, ZonedDateSerializer())
.addSerializationExclusionStrategy(AnnotationExclusionStrategy())
.enableComplexMapKeySerialization()
.setPrettyPrinting()
.create()
override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
converters.add(GsonHttpMessageConverter(customGson))
}
Quand je vais à http: // localhost: 8013/swagger-ui.html (dans la configuration j'ai server.port: 8013
) La page n'est pas redirigée vers swagger-ui/index.html?url=/api-docs&validatorUrl=
. Mais ce n'est pas mon problème principal :). Lorsque je vais à swagger-ui/index.html?url=/api-docs&validatorUrl=
, J'ai une page contenant ces informations:
Impossible de rendre cette définition La définition fournie ne spécifie pas de champ de version valide. Veuillez indiquer un champ de version Swagger ou OpenAPI valide. Les champs de version pris en charge sont swagger: "2.0" et ceux qui correspondent à openapi: 3.0.n (par exemple, openapi: 3.0.0).
Mais quand je vais à http: // localhost: 8013/api-docs j'ai ce résultat:
"{\"openapi\":\"3.0.1\",\"info\":{(...)}}"
J'ai essayé d'utiliser la configuration par défaut et j'ai commenté la méthode configureMessageConverters()
et le résultat de \api-docs
Ressemble maintenant au JSON normal:
// 20191218134933
// http://localhost:8013/api-docs
{
"openapi": "3.0.1",
"info": {(...)}
}
Je me souviens que lorsque j'utilisais Springfox, il y avait un problème avec la sérialisation et mon customGson
avait une ligne supplémentaire: .registerTypeAdapter(Json::class.Java, JsonSerializer<Json> { src, _, _ -> JsonParser.parseString(src.value()) })
Je me demandais que je devrais avoir un JsonSerializer
spécial. Après le débogage, ma première pensée conduisait à la classe OpenApi
dans le package io.swagger.v3.oas.models
. J'ai ajouté ce code: .registerTypeAdapter(OpenAPI::class.Java, JsonSerializer<OpenAPI> { _, _, _ -> JsonParser.parseString("") })
à customGson
et rien n'a changé ... Donc, je creusais plus profondément ...
Après quand j'ai exécuté mes tests Swagger:
@EnableAutoConfiguration
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ExtendWith(SpringExtension::class)
@ActiveProfiles("test")
class SwaggerIntegrationTest(@Autowired private val mockMvc: MockMvc) {
@Test
fun `should display Swagger UI page`() {
val result = mockMvc.perform(MockMvcRequestBuilders.get("/swagger-ui/index.html"))
.andExpect(status().isOk)
.andReturn()
assertTrue(result.response.contentAsString.contains("Swagger UI"))
}
@Disabled("Redirect doesn't work. Check it later")
@Test
fun `should display Swagger UI page with redirect`() {
mockMvc.perform(MockMvcRequestBuilders.get("/swagger-ui.html"))
.andExpect(status().isOk)
.andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
}
@Test
fun `should get api docs`() {
mockMvc.perform(MockMvcRequestBuilders.get("/api-docs"))
.andExpect(status().isOk)
.andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.jsonPath("\$.openapi").exists())
}
}
J'ai vu dans la console ceci:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api-docs
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = org.springdoc.api.OpenApiResource
Method = org.springdoc.api.OpenApiResource#openapiJson(HttpServletRequest, String)
Ensuite, je vérifie openapiJson
dans OpenApiResource
et ...
@Operation(hidden = true)
@GetMapping(value = API_DOCS_URL, produces = MediaType.APPLICATION_JSON_VALUE)
public String openapiJson(HttpServletRequest request, @Value(API_DOCS_URL) String apiDocsUrl)
throws JsonProcessingException {
calculateServerUrl(request, apiDocsUrl);
OpenAPI openAPI = this.getOpenApi();
return Json.mapper().writeValueAsString(openAPI);
}
OK, Jackson ... J'ai désactivé Jackson par @EnableAutoConfiguration(exclude = [(JacksonAutoConfiguration::class)])
parce que moi (et mes collègues) préfère GSON, mais cela n'explique pas pourquoi la sérialisation tourne mal après l'ajout de GsonHttpMessageConverter
personnalisé. Je n'ai aucune idée de ce que j'ai fait de mal. Cette openapiJson()
est le point final et peut-être que ça gâche quelque chose ... Je ne sais pas. Je n'ai aucune idée. Avez-vous eu un problème similaire? Pouvez-vous donner des conseils ou des indices?
PS. Désolé pour mon mauvais anglais :).
J'ai eu le même problème avec un projet écrit en Java, et je viens de le résoudre en définissant un filtre pour formater ma documentation springdoc-openapi json à l'aide de Gson. Je suppose que vous pouvez facilement porter cette solution de contournement vers Kotlin.
@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
throws IOException, ServletException {
ByteResponseWrapper byteResponseWrapper = new ByteResponseWrapper((HttpServletResponse) response);
ByteRequestWrapper byteRequestWrapper = new ByteRequestWrapper((HttpServletRequest) request);
chain.doFilter(byteRequestWrapper, byteResponseWrapper);
String jsonResponse = new String(byteResponseWrapper.getBytes(), response.getCharacterEncoding());
response.getOutputStream().write((new com.google.gson.JsonParser().parse(jsonResponse).getAsString())
.getBytes(response.getCharacterEncoding()));
}
@Override
public void destroy() {
}
static class ByteResponseWrapper extends HttpServletResponseWrapper {
private PrintWriter writer;
private ByteOutputStream output;
public byte[] getBytes() {
writer.flush();
return output.getBytes();
}
public ByteResponseWrapper(HttpServletResponse response) {
super(response);
output = new ByteOutputStream();
writer = new PrintWriter(output);
}
@Override
public PrintWriter getWriter() {
return writer;
}
@Override
public ServletOutputStream getOutputStream() {
return output;
}
}
static class ByteRequestWrapper extends HttpServletRequestWrapper {
byte[] requestBytes = null;
private ByteInputStream byteInputStream;
public ByteRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream inputStream = request.getInputStream();
byte[] buffer = new byte[4096];
int read = 0;
while ((read = inputStream.read(buffer)) != -1) {
baos.write(buffer, 0, read);
}
replaceRequestPayload(baos.toByteArray());
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() {
return byteInputStream;
}
public void replaceRequestPayload(byte[] newPayload) {
requestBytes = newPayload;
byteInputStream = new ByteInputStream(new ByteArrayInputStream(requestBytes));
}
}
static class ByteOutputStream extends ServletOutputStream {
private ByteArrayOutputStream bos = new ByteArrayOutputStream();
@Override
public void write(int b) {
bos.write(b);
}
public byte[] getBytes() {
return bos.toByteArray();
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
}
static class ByteInputStream extends ServletInputStream {
private InputStream inputStream;
public ByteInputStream(final InputStream inputStream) {
this.inputStream = inputStream;
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
}
Vous devrez également enregistrer votre filtre uniquement pour votre modèle d'URL de documentation.
@Bean
public FilterRegistrationBean<DocsFormatterFilter> loggingFilter() {
FilterRegistrationBean<DocsFormatterFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new DocsFormatterFilter());
registrationBean.addUrlPatterns("/v3/api-docs");
return registrationBean;
}