Revisión prehospitalaria o "¡Hola, Swagger! ¿Dónde están mis errores?

¿Alguna vez te has ajustado durante la revisión al maestro? No? ¡Pero lo logré!

Esta historia trata sobre cómo olvidé actualizar la documentación. Como resultado, escribí un complemento para Swagger (la segunda vez). ¡Y cómo me dejé llevar por eso, así que me olvidé de mi baja por enfermedad y me recuperé!


Y un poco más sobre Opcional, no de Java 8.

Usamos Swagger para crear documentación interactiva.

Por lo tanto, cuando crea un método en la API, entonces:

0. Agregue las anotaciones necesarias, como @RequestMapping, etc.

1. Agregue @ErrorCodes (nuestra propia anotación) y enumere los códigos de error de cadena que este método puede devolver en la respuesta.

2. Agregue @ApiOperation y duplique información sobre estos errores en el campo de notas.

3. Agregue el resto de las anotaciones ...

Se veía algo así (eliminado innecesario y simplificado):

@ApiOperation( value = "Some description.", notes = "List of possible error codes:" + "<ul>" + " <li>sms.verification.code.fail</li>" + "</ul>") @PostMapping("/security/confirmation/check") @ErrorCodes(values = {"sms.verification.code.fail"}) public ResponseDto check(@ApiParam @RequestBody @Valid RequestDto request) {... } 

El punto 2 fue la fuente de mi falla cuando agregué @ErrorCodes, pero olvidé enumerar los códigos de error de cadena en @ApiOperation. Satisfecho conmigo mismo, pero con un ligero sentimiento de ansiedad, presenté mi solicitud de extracción para revisión de código. ¡Y aquí me informaron que me olvidé de las notas! También explicaron que Swagger no recoge información de @ErrorCodes y es por eso que debe registrarla manualmente. Esa noche, todo terminó felizmente. Corrigió su defecto y se fue de baja por enfermedad.

Quizás sería normal tomar y seguir adelante. Ponga una marca de verificación en el estante para otros igual que dicen, Max, tenga cuidado, preste atención ...

Pero no tuve éxito. Pasamos toda la tarde y la mañana siguiente enseñando a Swagger a leer nuestra anotación y a agregar de forma independiente estos mismos códigos en las notas.

Paso 1. Alguien estuvo aquí ...


De una búsqueda superficial, logré descubrir que alguien ya había tratado de hacer amigos Swagger con su anotación. ¡También había un enlace a la documentación de SpringFox que decía que puedes escribir un complemento!

¡La felicidad feliz me ha cubierto tanto que incluso me olvidé del resfriado común y la baja por enfermedad! En mi futuro artículo, "Cómo no dejar la empresa tres veces", comparto tres historias de salvar a personas que se están ahogando. Una de ellas es sobre cómo logré escribir un complemento para Chrome + Firefox, que aceleró el trabajo con Jenkins varias veces. ¡Fue muy divertido escribirlo! Después de todo, ¡este es un microproyecto! Mi inicio muy, muy simple, pero con personas reales que lo usan. Luego salí de la rutina otra vez y encontré inspiración. Burnout se ha ido. Pero será mejor que hablemos de esto en un artículo futuro. Mientras tanto, volvamos al complemento de Swagger.

Acción 2. ¡Funciona!


Escribir algo que funciona resultó ser fácil. Tomé un ejemplo de un complemento de la documentación oficial de SpringFox, eliminé todo lo innecesario y agregué el correcto.

Plugin Versión 1
 @Component @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1) public class SwaggerErrorCodesConfiguration implements OperationBuilderPlugin { /** * Appends note of operation. Adds error codes to note of operation. * <code> * <h3>List of possible errors:</h3> * <ul> * <li>error.code.1</li> * <li>error.code.2</li> * <li>error.code.3</li> * </ul> * </code> * @param context operation context */ @Override public void apply(OperationContext context) { Method operationMethod = context.getHandlerMethod().getMethod(); // Check method is annotated by @ErrorCodes ErrorCodes errorCodes = findAnnotation(operationMethod, ErrorCodes.class); if (errorCodes == null) { return; } StringBuilder errorCodesNote = new StringBuilder(); errorCodesNote.append("<h3>List of possible errors:</h3>"); errorCodesNote.append("<ul>"); for (String errorCode: errorCodes.values()) { errorCodesNote.append("<li>").append(errorCode).append("</li>"); } errorCodesNote.append("</ul>"); // Write new version of notes. context.operationBuilder().notes(errorCodesNote.toString()).build(); } @Override public boolean supports(DocumentationType delimiter) { return SwaggerPluginSupport.pluginDoesApply(delimiter); } } 


Comencé a probar con un método que no especifica un valor para las notas en @ApiOperation.

 @ApiOperation(value = "Some description.") @PostMapping("/security/confirmation/check") @ErrorCodes(values = {"sms.verification.code.fail"}) public ResponseDto check(@ApiParam @RequestBody @Valid RequestDto request) { ... } 

Lanzamiento y ... ¡El resultado! ¡Hurra, funciona! El código de cadena sms.verification.code.fail apareció en las notas.



Paso 3. Funciona, pero no funciona.


Luego agregué algunas palabras a las notas y obtuve este código:

 @ApiOperation(value = "Some description.", notes = "Some initial note.") @PostMapping("/security/confirmation/check") @ErrorCodes(values = {"sms.verification.code.fail"}) public ResponseDto check(@ApiParam @RequestBody @Valid RequestDto request) { ... } 

Lanzado de nuevo. El resultado fue ... inesperado. ¡El complemento SpringFox sobrescribe el valor de las notas al generar documentación (O_o)!

Miro cómo funciona context.operationBuilder (). Notes (String) y veo lo siguiente allí:

 public OperationBuilder notes(String notes) { this.notes = (String)BuilderDefaults.defaultIfAbsent(notes, this.notes); return this; } 

Um ... Ok, entonces tomaremos el valor actual de las notas y agregaremos códigos de error. Queda por obtener la anotación @ApiOperation, tomar el valor deseado y agregarlo a lo que estoy formando.

Entonces, la versión final ( disponible en gist.github.com )

Plugin Versión 2
 @Component @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1) public class SwaggerErrorCodesConfiguration implements OperationBuilderPlugin { /** * Appends note of operation. Adds error codes to note of operation. * <code> * <h3>List of possible errors:</h3> * <ul> * <li>error.code.1</li> * <li>error.code.2</li> * <li>error.code.3</li> * </ul> * </code> * @param context operation context */ @Override public void apply(OperationContext context) { Method operationMethod = context.getHandlerMethod().getMethod(); // Check method is annotated by @ApiOperation ApiOperation apiOperation = findApiOperationAnnotation(operationMethod).orNull(); if (apiOperation == null) { return; } // Check method is annotated by @ErrorCodes ErrorCodes errorCodes = findAnnotation(operationMethod, ErrorCodes.class); if (errorCodes == null) { return; } // Prepend notes by using current value of notes in @ApiOperation StringBuilder errorCodesNote = new StringBuilder(apiOperation.notes()); errorCodesNote.append("<h3>List of possible errors:</h3>"); errorCodesNote.append("<ul>"); for (String errorCode: errorCodes.values()) { errorCodesNote.append("<li>").append(errorCode).append("</li>"); } errorCodesNote.append("</ul>"); // Write new version of notes. context.operationBuilder().notes(errorCodesNote.toString()).build(); } @Override public boolean supports(DocumentationType delimiter) { return SwaggerPluginSupport.pluginDoesApply(delimiter); } } 


¡Ahora resultó como debería!



Paso 4. ¿Qué pasa con Opcional no de Java 8?


Al comienzo del trabajo en el complemento, no podía entender qué estaba mal con el Opcional, que se devuelve al buscar anotaciones. Esta clase no tenía los métodos estándar que están acostumbrados a trabajar con java.util.Optional . Por ejemplo, no hay un método ifPresent , pero hay un método orNull .
Resultó que SpringFox usa Opcional de Guava .

TL; DR


Escribí un complemento para SpringFox, que es un componente Spring y se llama en la etapa de generación de documentación para leer valores de nuestra anotación @ErrorCodes.

Código de complemento
 @Component @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1) public class SwaggerErrorCodesConfiguration implements OperationBuilderPlugin { /** * Appends note of operation. Adds error codes to note of operation. * <code> * <h3>List of possible errors:</h3> * <ul> * <li>error.code.1</li> * <li>error.code.2</li> * <li>error.code.3</li> * </ul> * </code> * @param context operation context */ @Override public void apply(OperationContext context) { Method operationMethod = context.getHandlerMethod().getMethod(); // Check method is annotated by @ApiOperation ApiOperation apiOperation = findApiOperationAnnotation(operationMethod).orNull(); if (apiOperation == null) { return; } // Check method is annotated by @ErrorCodes ErrorCodes errorCodes = findAnnotation(operationMethod, ErrorCodes.class); if (errorCodes == null) { return; } // Prepend notes by using current value of notes in @ApiOperation StringBuilder errorCodesNote = new StringBuilder(apiOperation.notes()); errorCodesNote.append("<h3>List of possible errors:</h3>"); errorCodesNote.append("<ul>"); for (String errorCode: errorCodes.values()) { errorCodesNote.append("<li>").append(errorCode).append("</li>"); } errorCodesNote.append("</ul>"); // Write new version of notes. context.operationBuilder().notes(errorCodesNote.toString()).build(); } @Override public boolean supports(DocumentationType delimiter) { return SwaggerPluginSupport.pluginDoesApply(delimiter); } } 

Source: https://habr.com/ru/post/es420841/


All Articles