Pular para conteúdo

d5-framework-springdoc

Gera a documentação OpenAPI da API REST do backend e a expõe através do Swagger UI, a partir de springdoc-openapi.

Como nenhuma classe da aplicação depende dele, o módulo pode ser declarado apenas em tempo de execução (<scope>runtime</scope>), como no tutorial. Os caminhos do Swagger UI e da documentação OpenAPI são liberados sem autenticação por SegurancaConfiguracaoAbstrata, que assume os caminhos padrão do springdoc-openapi.

SpringDocConfiguracao

Declara basic como o esquema de segurança da API, do tipo HTTP, e o aplica a toda a documentação gerada. É esse esquema que faz o Swagger UI exibir o botão Authorize e anexar o cabeçalho Authorization com usuário e senha a cada requisição, depois de autenticado. Também registra o OperationCustomizer que expande parâmetros @ModelAttribute, como o de pesquisa, em parâmetros individuais; veja Expansão da consulta no Swagger UI. Por fim, registra o ModelConverter que documenta objetos de valor de string como string; veja ObjetoValorStringConversor.

SpringDocConfiguracao.java
package dev.sauloaraujo.d5.framework.springdoc;

import org.springdoc.core.customizers.OperationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.swagger.v3.core.converter.ModelConverter;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;

@Configuration
@OpenAPIDefinition(security = @SecurityRequirement(name = "basic"))
@SecurityScheme(name = "basic", type = SecuritySchemeType.HTTP, scheme = "basic")
public class SpringDocConfiguracao {
    @Bean
    OperationCustomizer modelAttributeCustomizador() {
        return new ModelAttributeCustomizador();
    }

    @Bean
    ModelConverter objetoValorStringConversor() {
        return new ObjetoValorStringConversor();
    }
}

ObjetoValorStringConversor

Descreve como string, na documentação OpenAPI, todo tipo que estende ObjetoValorStringAbstrato. Esses objetos trafegam como uma string simples, mas o springdoc-openapi desconhece essa serialização e, sem o conversor, documentaria o tipo a partir de seus getters — por exemplo, um identificador com o método getUnidadeFederativa() apareceria como um objeto com a propriedade unidadeFederativa, tanto nos parâmetros de caminho quanto no corpo das requisições. Veja Serialização de objetos de valor.

ObjetoValorStringConversor.java
package dev.sauloaraujo.d5.framework.springdoc;

import java.util.Iterator;

import org.jspecify.annotations.Nullable;

import dev.sauloaraujo.d5.framework.dominio.impl.ObjetoValorStringAbstrato;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverter;
import io.swagger.v3.core.converter.ModelConverterContext;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;

/**
 * Objetos de valor de string são serializados como string simples; portanto, a
 * documentação não deve expor suas propriedades.
 */
class ObjetoValorStringConversor implements ModelConverter {
    @Override
    public @Nullable Schema<?> resolve(AnnotatedType tipo, ModelConverterContext contexto,
            Iterator<ModelConverter> cadeia) {
        var tipoJava = Json.mapper().constructType(tipo.getType());
        if (tipoJava != null && ObjetoValorStringAbstrato.class.isAssignableFrom(tipoJava.getRawClass())) {
            return new StringSchema();
        }
        return cadeia.hasNext() ? cadeia.next().resolve(tipo, contexto, cadeia) : null;
    }
}

SpringDocEnvironmentPostProcessor

Habilita, por padrão, o cabeçalho de CSRF nas requisições de escrita do Swagger UI, definindo a propriedade springdoc.swagger-ui.csrf.enabled como true; veja CSRF no Swagger UI. A propriedade é adicionada como a fonte de propriedades de menor precedência, de modo que a aplicação pode sobrescrevê-la — por exemplo, em application.properties.

SpringDocEnvironmentPostProcessor.java
package dev.sauloaraujo.d5.framework.springdoc;

import java.util.Map;

import org.springframework.boot.EnvironmentPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;

public class SpringDocEnvironmentPostProcessor implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        environment.getPropertySources().addLast(
                new MapPropertySource("d5-framework-springdoc", Map.of("springdoc.swagger-ui.csrf.enabled", true)));
    }
}

É registrado em META-INF/spring.factories, e não em META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports como SpringDocConfiguracao, porque não é uma autoconfiguração: o Spring Boot executa os EnvironmentPostProcessor ao preparar o Environment, antes de criar o contexto da aplicação — e, portanto, antes de processar qualquer autoconfiguração —, e os descobre apenas através de spring.factories.