All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.rocketbase.commons.openapi.OpenApiClientCreatorService Maven / Gradle / Ivy

There is a newer version: 3.3.2
Show newest version
package io.rocketbase.commons.openapi;

import io.pebbletemplates.pebble.PebbleEngine;
import io.pebbletemplates.pebble.extension.AbstractExtension;
import io.pebbletemplates.pebble.extension.Function;
import io.pebbletemplates.pebble.loader.ClasspathLoader;
import io.pebbletemplates.pebble.template.EvaluationContext;
import io.pebbletemplates.pebble.template.PebbleTemplate;
import io.rocketbase.commons.config.OpenApiGeneratorProperties;
import io.rocketbase.commons.openapi.model.OpenApiController;
import io.rocketbase.commons.openapi.model.ReactQueryVersion;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.utils.Constants;
import org.springdoc.webmvc.api.OpenApiWebMvcResource;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties;

import java.beans.Introspector;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
@RequiredArgsConstructor
public class OpenApiClientCreatorService {

    private final SpringDataWebProperties springDataWebProperties;
    private final OpenApiGeneratorProperties openApiGeneratorProperties;
    private final OpenApiWebMvcResource openApiWebMvcResource;
    private final OpenApiConverter typescriptConverter;
    private final InfiniteOptionsTemplateBuilder templateBuilder;

    private Map compiledTemplateMap = new HashMap<>();

    @SneakyThrows
    public List getControllers(HttpServletRequest request) {
        byte[] openapiJson = openApiWebMvcResource.openapiJson(request, Constants.DEFAULT_API_DOCS_URL, Locale.getDefault());
        OpenAPI openAPI = Json.mapper().readValue(openapiJson, OpenAPI.class);
        Map> remapped = new HashMap<>();
        for (Map.Entry entry : openAPI.getPaths().entrySet()) {
            addOperation(remapped, PathItem.HttpMethod.GET, entry.getKey(), entry.getValue().getGet());
            addOperation(remapped, PathItem.HttpMethod.PUT, entry.getKey(), entry.getValue().getPut());
            addOperation(remapped, PathItem.HttpMethod.POST, entry.getKey(), entry.getValue().getPost());
            addOperation(remapped, PathItem.HttpMethod.DELETE, entry.getKey(), entry.getValue().getDelete());
            addOperation(remapped, PathItem.HttpMethod.PATCH, entry.getKey(), entry.getValue().getPatch());
        }
        return remapped.entrySet().stream().map(e -> new OpenApiController(e.getKey(), e.getValue(), typescriptConverter)).collect(Collectors.toList());
    }

    public void getTypescriptClients(ReactQueryVersion reactQueryVersion, HttpServletRequest request, HttpServletResponse response, String baseUrl, String groupName, String filename) {

        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment;filename=" + filename);
        response.setStatus(HttpServletResponse.SC_OK);

        List controllers = getControllers(request);

        try (ZipOutputStream zippedOut = new ZipOutputStream(response.getOutputStream())) {
            Map context = new HashMap<>();
            context.put("controllers", controllers);
            context.put("baseUrl", baseUrl);
            context.put("groupName", groupName);
            context.put("configuredGroupVar", Introspector.decapitalize(groupName));
            context.put("timestamp", Instant.now());
            context.put("generatorConfig", openApiGeneratorProperties);
            context.put("springDataWebConfig", springDataWebProperties);
            context.put("reactQueryVersion", reactQueryVersion);

            generateModels(zippedOut, context);
            generateClients(controllers, zippedOut, context);
            generateHooks(reactQueryVersion, controllers, zippedOut, context);
            generateIndexAndPackageJson(zippedOut, context);


            zippedOut.finish();
        } catch (Exception e) {
            // Exception handling goes here
            log.error("write zip: {}", e.getMessage(), e);
        }
    }

    private void generateModels(ZipOutputStream zippedOut, Map context) throws IOException {
        Writer writer;
        zippedOut.putNextEntry(new ZipEntry("src/model/"));

        writer = new StringWriter();
        zippedOut.putNextEntry(new ZipEntry("src/model/index.ts"));
        getCompiledTemplate("model/index").evaluate(writer, context);
        zippedOut.write(writer.toString().getBytes("UTF-8"));
        zippedOut.closeEntry();

        writer = new StringWriter();
        zippedOut.putNextEntry(new ZipEntry("src/model/request.ts"));
        getCompiledTemplate("model/request").evaluate(writer, context);
        zippedOut.write(writer.toString().getBytes("UTF-8"));
        zippedOut.closeEntry();
    }

    private void generateIndexAndPackageJson(ZipOutputStream zippedOut, Map context) throws IOException {
        Writer writer;
        zippedOut.putNextEntry(new ZipEntry("src/"));

        writer = new StringWriter();
        zippedOut.putNextEntry(new ZipEntry("package.json"));
        getCompiledTemplate("package").evaluate(writer, context);
        zippedOut.write(writer.toString().getBytes("UTF-8"));
        zippedOut.closeEntry();


        zippedOut.putNextEntry(new ZipEntry("src/util.ts"));
        writer = new StringWriter();
        getCompiledTemplate("util").evaluate(writer, context);
        zippedOut.write(writer.toString().getBytes("UTF-8"));
        zippedOut.closeEntry();

        writer = new StringWriter();
        zippedOut.putNextEntry(new ZipEntry("src/index.ts"));
        getCompiledTemplate("index").evaluate(writer, context);
        zippedOut.write(writer.toString().getBytes("UTF-8"));
        zippedOut.closeEntry();
    }

    private void generateClients(List controllers, ZipOutputStream zippedOut, Map context) throws IOException {
        Writer writer;
        zippedOut.putNextEntry(new ZipEntry("src/" + openApiGeneratorProperties.getClientFolder() + "/"));
        for (OpenApiController c : controllers) {
            zippedOut.putNextEntry(new ZipEntry("src/" + openApiGeneratorProperties.getClientFolder() + "/" + c.getFilename() + ".ts"));
            writer = new StringWriter();

            context.put("controller", c);
            getCompiledTemplate("client/controller-template").evaluate(writer, context);
            zippedOut.write(writer.toString().getBytes("UTF-8"));
            zippedOut.closeEntry();
        }

        zippedOut.putNextEntry(new ZipEntry("src/" + openApiGeneratorProperties.getClientFolder() + "/index.ts"));
        writer = new StringWriter();
        getCompiledTemplate("client/index").evaluate(writer, context);
        zippedOut.write(writer.toString().getBytes("UTF-8"));
        zippedOut.closeEntry();
    }

    private void generateHooks(ReactQueryVersion reactQueryVersion, List controllers, ZipOutputStream zippedOut, Map context) throws IOException {
        Writer writer;
        zippedOut.putNextEntry(new ZipEntry("src/" + openApiGeneratorProperties.getHookFolder() + "/"));
        for (OpenApiController c : controllers) {
            zippedOut.putNextEntry(new ZipEntry("src/" + openApiGeneratorProperties.getHookFolder() + "/" + c.getFilename() + ".ts"));

            writer = new StringWriter();
            context.put("controller", c);
            getCompiledTemplate("hook/hook-template").evaluate(writer, context);
            zippedOut.write(writer.toString().getBytes("UTF-8"));
            zippedOut.closeEntry();
        }

        zippedOut.putNextEntry(new ZipEntry("src/" + openApiGeneratorProperties.getHookFolder() + "/index.ts"));
        writer = new StringWriter();
        getCompiledTemplate("hook/index").evaluate(writer, context);
        zippedOut.write(writer.toString().getBytes("UTF-8"));
        zippedOut.closeEntry();
    }

    protected PebbleTemplate getCompiledTemplate(String keyName) {
        if (!compiledTemplateMap.containsKey(keyName)) {
            ClasspathLoader classpathLoader = new ClasspathLoader();
            classpathLoader.setPrefix("templates/");
            classpathLoader.setSuffix(".pebble");

            PebbleEngine engine = new PebbleEngine.Builder()
                    .loader(classpathLoader)
                    .autoEscaping(false)
                    .extension(new AbstractExtension() {
                        @Override
                        public Map getFunctions() {
                            return Map.of("infiniteOptions", new InfiniteOptions(),
                                    "infiniteParams", new InfiniteParams());
                        }
                    })
                    .build();
            compiledTemplateMap.put(keyName, engine.getTemplate(keyName));
        }
        return compiledTemplateMap.get(keyName);
    }

    public class InfiniteOptions implements Function {

        @Override
        public Object execute(Map map, PebbleTemplate pebbleTemplate, EvaluationContext evaluationContext, int i) {
            Object parameter = map.getOrDefault("method", null);
            if (parameter instanceof OpenApiControllerMethodExtraction) {
                return templateBuilder.buildQueryOptions((OpenApiControllerMethodExtraction) parameter);
            } else {
                log.error("parameter not correctly used for infinite options template");
                return "";
            }
        }

        @Override
        public List getArgumentNames() {
            return List.of("method");
        }
    }

    public class InfiniteParams implements Function {

        @Override
        public Object execute(Map map, PebbleTemplate pebbleTemplate, EvaluationContext evaluationContext, int i) {
            Object parameter = map.getOrDefault("method", null);
            if (parameter instanceof OpenApiControllerMethodExtraction) {
                return templateBuilder.buildQueryParams((OpenApiControllerMethodExtraction) parameter);
            } else {
                log.error("parameter not correctly used for infinite params template");
                return "";
            }
        }

        @Override
        public List getArgumentNames() {
            return List.of("method");
        }
    }

    protected Set pageParams() {
        return Set.of(springDataWebProperties.getPageable().getPageParameter(), springDataWebProperties.getPageable().getSizeParameter(), springDataWebProperties.getSort().getSortParameter());
    }

    protected void addOperation(Map> map, PathItem.HttpMethod httpMethod, String path, Operation operation) {
        if (operation != null && operation.getExtensions() != null) {
            String controllerBean = (String) operation.getExtensions().get(OpenApiCustomExtractor.CONTROLLER_BEAN);
            boolean disabled = (Boolean) operation.getExtensions().getOrDefault(OpenApiCustomExtractor.DISABLED, false);
            if (!disabled) {
                map.putIfAbsent(controllerBean, new ArrayList<>());
                map.get(controllerBean).add(new OpenApiControllerMethodExtraction(new OpenApiControllerMethodExtraction.ExtractorConfig(pageParams(), typescriptConverter, httpMethod, path, operation, openApiGeneratorProperties.getDefaultStaleTime())));
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy