CE-1887 Initial checkin - dev-tools for middleware api

This commit is contained in:
2024-10-17 12:34:41 -05:00
parent 8500a2559c
commit a46397df39
7 changed files with 1089 additions and 0 deletions

View File

@ -0,0 +1,160 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.tools;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.yaml.snakeyaml.LoaderOptions;
/*******************************************************************************
**
*******************************************************************************/
public class APIUtils
{
public static final String PUBLISHED_API_LOCATION = "qqq-middleware-javalin/src/main/resources/openapi/";
public static final List<String> FILE_FORMATS = List.of("json", "yaml");
/*******************************************************************************
**
*******************************************************************************/
public static String getPublishedAPIFile(String apiPath, String name, String fileFormat) throws Exception
{
String fileLocation = apiPath + "/" + name + "." + fileFormat;
File file = new File(fileLocation);
if(!file.exists())
{
throw (new Exception("Error: The file [" + file.getPath() + "] could not be found."));
}
Path path = Paths.get(fileLocation);
return (StringUtils.join("\n", Files.readAllLines(path)));
}
/*******************************************************************************
** get a map representation of the yaml.
** we'll remove things from that map, writing out specific sub-files that we know we want (e.g., per-table & process).
** also, there are some objects we just don't care about (e.g., tags, they'll always be in lock-step with the tables).
** then we'll write out everything else left in the map at the end.
*******************************************************************************/
@SuppressWarnings("unchecked")
static Map<String, Map<String, Object>> splitUpYamlForPublishing(String openApiYaml) throws JsonProcessingException
{
Map<String, Object> apiYaml = parseYaml(openApiYaml);
Map<String, Object> components = (Map<String, Object>) apiYaml.get("components");
Map<String, Object> schemas = (Map<String, Object>) components.get("schemas");
Map<String, Object> paths = (Map<String, Object>) apiYaml.remove("paths");
apiYaml.remove("tags");
Map<String, Map<String, Object>> groupedPaths = new HashMap<>();
for(Map.Entry<String, Object> entry : paths.entrySet())
{
///////////////////////////////////////////////////////////////////////////////
// keys here look like: apiName/apiVersion/table-or-process/<maybe-more> //
// let's discard the apiName & version, and group under the table-or-process //
///////////////////////////////////////////////////////////////////////////////
String key = entry.getKey();
String[] parts = key.split("/");
String uniquePart = parts[3];
groupedPaths.computeIfAbsent(uniquePart, k -> new TreeMap<>());
groupedPaths.get(uniquePart).put(entry.getKey(), entry.getValue());
}
for(Map.Entry<String, Map<String, Object>> entry : groupedPaths.entrySet())
{
String name = entry.getKey();
Map<String, Object> subMap = entry.getValue();
if(schemas.containsKey(name))
{
subMap.put("schema", schemas.remove(name));
}
name += "SearchResult";
if(schemas.containsKey(name))
{
subMap.put("searchResultSchema", schemas.remove(name));
}
}
////////////////////////////////////////////////////////
// put the left-over yaml as a final entry in the map //
////////////////////////////////////////////////////////
groupedPaths.put("openapi", apiYaml);
return groupedPaths;
}
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("unchecked")
private static Map<String, Object> parseYaml(String yaml) throws JsonProcessingException
{
////////////////////////////////////////////////////////////////////////////////////////////////////////
// need a larger limit than you get by default (and qqq's yamlUtils doens't let us customize this...) //
////////////////////////////////////////////////////////////////////////////////////////////////////////
LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setCodePointLimit(100 * 1024 * 1024); // 100 MB
YAMLFactory yamlFactory = YAMLFactory.builder()
.loaderOptions(loaderOptions)
.build();
YAMLMapper mapper = new YAMLMapper(yamlFactory);
mapper.findAndRegisterModules();
return (mapper.readValue(yaml, Map.class));
}
/*******************************************************************************
**
*******************************************************************************/
public static File[] listPublishedAPIFiles(String path) throws Exception
{
File file = new File(path);
if(!file.exists())
{
throw (new Exception("Error: API Directory [" + file.getPath() + "] could not be found."));
}
File[] files = file.listFiles();
return (files);
}
}

View File

@ -0,0 +1,150 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.tools;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.Callable;
import com.fasterxml.jackson.databind.MapperFeature;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.YamlUtils;
import com.kingsrook.qqq.middleware.javalin.specs.AbstractMiddlewareVersion;
import com.kingsrook.qqq.middleware.javalin.specs.v1.MiddlewareVersionV1;
import com.kingsrook.qqq.openapi.model.OpenAPI;
import picocli.CommandLine;
/*******************************************************************************
**
*******************************************************************************/
@CommandLine.Command(name = "publishAPI")
public class PublishAPI implements Callable<Integer>
{
@CommandLine.Option(names = { "-r", "--repoRoot" })
private String repoRoot;
@CommandLine.Option(names = { "--sortFileContentsForHuman" }, description = "By default, properties in the yaml are sorted alphabetically, to help with stability (for diffing). This option preserves the 'natural' order of the file, so may look a little bette for human consumption")
private boolean sortFileContentsForHuman = false;
/*******************************************************************************
**
*******************************************************************************/
public static void main(String[] args) throws Exception
{
// for a run from the IDE, to override args... args = new String[] { "-r", "/Users/dkelkhoff/git/kingsrook/qqq/" };
int exitCode = new CommandLine(new PublishAPI()).execute(args);
System.exit(exitCode);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Integer call() throws Exception
{
AbstractMiddlewareVersion middlewareVersion = new MiddlewareVersionV1();
if(!StringUtils.hasContent(repoRoot))
{
throw (new QException("Repo root argument was not given."));
}
if(!new File(repoRoot).exists())
{
throw (new QException("Repo root directory [" + repoRoot + "] was not found."));
}
String allApisPath = repoRoot + "/" + APIUtils.PUBLISHED_API_LOCATION + "/";
if(!new File(allApisPath).exists())
{
throw (new QException("APIs directory [" + allApisPath + "] was not found."));
}
File versionDirectory = new File(allApisPath + middlewareVersion.getVersion() + "/");
if(!versionDirectory.exists())
{
if(!versionDirectory.mkdirs())
{
// CTEngCliUtils.printError("Error: An error occurred creating directory [" + apiDirectory.getPath() + "].");
System.err.println("Error: An error occurred creating directory [" + versionDirectory.getPath() + "].");
return (1);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// build the openapi spec - then run it through a "grouping" function, which will make several //
// subsets of it (e.g., grouped by table mostly) - then we'll write out each such file //
/////////////////////////////////////////////////////////////////////////////////////////////////
OpenAPI openAPI = middlewareVersion.generate("qqq");
String yaml = YamlUtils.toYaml(openAPI, mapper ->
{
if(sortFileContentsForHuman)
{
////////////////////////////////////////////////
// this is actually the default mapper config //
////////////////////////////////////////////////
}
else
{
mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
}
});
writeFile(yaml, versionDirectory, "openapi.yaml");
//////////////////////////////////////////////////////////////////////////////////////
// if we want to split up by some paths, components, we could use a version of this //
//////////////////////////////////////////////////////////////////////////////////////
// Map<String, Map<String, Object>> groupedPaths = APIUtils.splitUpYamlForPublishing(yaml);
// for(String name : groupedPaths.keySet())
// {
// writeFile(groupedPaths.get(name), versionDirectory, name + ".yaml");
// }
// CTEngCliUtils.printSuccess("Files for [" + apiInstanceMetaData.getName() + "] [" + apiVersion + "] have been successfully published.");
// System.out.println("Files for [" + middlewareVersion.getClass().getSimpleName() + "] have been successfully published.");
return (0);
}
/*******************************************************************************
**
*******************************************************************************/
private void writeFile(String yaml, File directory, String fileBaseName) throws IOException
{
String yamlFileName = directory.getAbsolutePath() + "/" + fileBaseName;
Path yamlPath = Paths.get(yamlFileName);
Files.write(yamlPath, yaml.getBytes());
System.out.println("Wrote [" + yamlPath + "]");
}
}

View File

@ -0,0 +1,253 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.tools;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import com.fasterxml.jackson.databind.MapperFeature;
import com.kingsrook.qqq.backend.core.utils.YamlUtils;
import com.kingsrook.qqq.middleware.javalin.specs.AbstractMiddlewareVersion;
import com.kingsrook.qqq.middleware.javalin.specs.v1.MiddlewareVersionV1;
import com.kingsrook.qqq.openapi.model.OpenAPI;
import picocli.CommandLine;
/*******************************************************************************
**
*******************************************************************************/
@CommandLine.Command(name = "validateApiVersions")
public class ValidateAPIVersions implements Callable<Integer>
{
@CommandLine.Option(names = { "-r", "--repoRoot" })
String repoRoot;
/*******************************************************************************
**
*******************************************************************************/
public static void main(String[] args) throws Exception
{
// for a run from the IDE, to override args... args = new String[] { "-r", "/Users/dkelkhoff/git/kingsrook/qqq/" };
int exitCode = new CommandLine(new ValidateAPIVersions()).execute(args);
System.out.println("Exiting with code: " + exitCode);
System.exit(exitCode);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Integer call() throws Exception
{
String fileFormat = "yaml";
boolean hadErrors = false;
List<String> errorHeaders = new ArrayList<>();
List<AbstractMiddlewareVersion> specList = List.of(new MiddlewareVersionV1());
for(AbstractMiddlewareVersion middlewareVersion : specList)
{
String version = middlewareVersion.getVersion();
boolean hadErrorsThisVersion = false;
//////////
// todo //
//////////
// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// // if this api version is in the list of "future" versions, consider it a "beta" and don't do any validation //
// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// for(APIVersion futureAPIVersion : apiInstanceMetaData.getFutureVersions())
// {
// if(apiVersion.equals(futureAPIVersion))
// {
// continue versionLoop;
// }
// }
try
{
////////////////////////////////////////////////////////////
// list current files - so we can tell if all get diff'ed //
////////////////////////////////////////////////////////////
Set<String> existingFileNames = new HashSet<>();
String versionPath = repoRoot + "/" + APIUtils.PUBLISHED_API_LOCATION + "/" + version + "/";
versionPath = versionPath.replaceAll("/+", "/");
for(File file : APIUtils.listPublishedAPIFiles(versionPath))
{
existingFileNames.add(file.getPath().replaceFirst(versionPath, ""));
}
///////////////////////////////////////////////////////////
// generate a new spec based on current code in codebase //
///////////////////////////////////////////////////////////
OpenAPI openAPI = middlewareVersion.generate("qqq");
String yaml = YamlUtils.toYaml(openAPI, mapper ->
{
mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
});
/////////////////////////////////////////////////////////////////////
// get the published API file - then diff it to what we just wrote //
/////////////////////////////////////////////////////////////////////
String publishedAPI = APIUtils.getPublishedAPIFile(versionPath, "openapi", fileFormat);
String newFileName = "/tmp/" + version + "-new." + fileFormat;
String publishedFileName = "/tmp/" + version + "-published." + fileFormat;
Files.write(Path.of(newFileName), yaml.getBytes());
Files.write(Path.of(publishedFileName), publishedAPI.getBytes());
Runtime rt = Runtime.getRuntime();
String[] commands = { "diff", "-w", publishedFileName, newFileName };
Process proc = rt.exec(commands);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
StringBuilder diffOutput = new StringBuilder();
String s;
while((s = stdInput.readLine()) != null)
{
diffOutput.append(s).append("\n");
}
if(!"".contentEquals(diffOutput))
{
String message = "Error: Differences were found in openapi.yaml file between the published docs and the newly generated file for API Version [" + version + "].";
errorHeaders.add(message);
System.out.println(message);
System.out.println(diffOutput);
hadErrors = true;
hadErrorsThisVersion = true;
}
//////////////////////////////////////////////////////////////////////////////////////
// if we want to split up by some paths, components, we could use a version of this //
//////////////////////////////////////////////////////////////////////////////////////
/*
Map<String, Map<String, Object>> groupedPaths = APIUtils.splitUpYamlForPublishing(yaml);
///////////////////////////////////////////////////////////////////////////////////////
// for each of the groupings (e.g., files), compare to what was previously published //
///////////////////////////////////////////////////////////////////////////////////////
for(Map.Entry<String, Map<String, Object>> entry : groupedPaths.entrySet())
{
try
{
String name = entry.getKey();
String newFileToDiff = YamlUtils.toYaml(entry.getValue());
/////////////////////////////////////////////////////////////////////
// get the published API file - then diff it to what we just wrote //
/////////////////////////////////////////////////////////////////////
String publishedAPI = APIUtils.getPublishedAPIFile(versionPath, name, fileFormat);
existingFileNames.remove(name + "." + fileFormat);
String newFileName = "/tmp/" + version + "-new." + fileFormat;
String publishedFileName = "/tmp/" + version + "-published." + fileFormat;
Files.write(Path.of(newFileName), newFileToDiff.getBytes());
Files.write(Path.of(publishedFileName), publishedAPI.getBytes());
Runtime rt = Runtime.getRuntime();
String[] commands = { "diff", "-w", publishedFileName, newFileName };
Process proc = rt.exec(commands);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
StringBuilder diffOutput = new StringBuilder();
String s;
while((s = stdInput.readLine()) != null)
{
diffOutput.append(s).append("\n");
}
if(!"".contentEquals(diffOutput))
{
String message = "Error: Differences were found in file [" + name + "] between the published docs and the newly generated " + fileFormat + " file for API Version [" + version + "].";
errorHeaders.add(message);
System.out.println(message);
System.out.println(diffOutput);
hadErrors = true;
hadErrorsThisVersion = true;
}
}
catch(Exception e)
{
errorHeaders.add(e.getMessage());
System.out.println(e.getMessage());
hadErrors = true;
hadErrorsThisVersion = true;
}
}
/////////////////////////////////////////////////////////////////////////////////////
// if any existing files weren't evaluated in the loop above, then that's an error //
// e.g., someone removed a thing that was previously in the API //
/////////////////////////////////////////////////////////////////////////////////////
if(!existingFileNames.isEmpty())
{
hadErrors = true;
hadErrorsThisVersion = true;
for(String existingFileName : existingFileNames)
{
String message = "Error: Previously published file [" + existingFileName + "] was not found in the current OpenAPI object for API Version [" + version + "].";
errorHeaders.add(message);
System.out.println(message);
}
}
*/
}
catch(Exception e)
{
errorHeaders.add(e.getMessage());
System.out.println(e.getMessage());
hadErrors = true;
hadErrorsThisVersion = true;
}
if(!hadErrorsThisVersion)
{
System.out.println("Success: No differences were found between the published docs and the newly generated " + fileFormat + " file for API Version [" + version + "].");
}
}
if(!errorHeaders.isEmpty())
{
System.out.println("\nError summary:");
errorHeaders.forEach(e -> System.out.println(" - " + e));
}
return (hadErrors ? 1 : 0);
}
}

View File

@ -0,0 +1,172 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.tools.codegenerators;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.FileUtils;
/*******************************************************************************
**
*******************************************************************************/
class ExecutorCodeGenerator
{
/***************************************************************************
**
***************************************************************************/
public static void main(String[] args)
{
try
{
String qqqDir = "/Users/dkelkhoff/git/kingsrook/qqq/";
new ExecutorCodeGenerator().writeAllFiles(qqqDir, "ProcessMetaData"); // don't include "Executor" on the end.
}
catch(IOException e)
{
//noinspection CallToPrintStackTrace
e.printStackTrace();
}
}
/***************************************************************************
**
***************************************************************************/
private void writeOne(String fullPath, String content) throws IOException
{
File file = new File(fullPath);
File directory = file.getParentFile();
if(!directory.exists())
{
throw (new RuntimeException("Directory for: " + fullPath + " does not exists, and I refuse to mkdir (do it yourself and/or fix your arguments)."));
}
if(file.exists())
{
throw (new RuntimeException("File at: " + fullPath + " already exists, and I refuse to overwrite files."));
}
System.out.println("Writing: " + file);
FileUtils.writeStringToFile(file, content, StandardCharsets.UTF_8);
}
/***************************************************************************
**
***************************************************************************/
private void writeAllFiles(String rootPath, String baseName) throws IOException
{
if(baseName.endsWith("Executor"))
{
throw new IllegalArgumentException("Base name must not end with 'Executor'.");
}
String basePath = rootPath + "qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/";
writeOne(basePath + "executors/" + baseName + "Executor.java", makeExecutor(baseName));
writeOne(basePath + "executors/io/" + baseName + "Input.java", makeInput(baseName));
writeOne(basePath + "executors/io/" + baseName + "OutputInterface.java", makeOutputInterface(baseName));
}
/***************************************************************************
**
***************************************************************************/
private String makeExecutor(String baseName)
{
return """
package com.kingsrook.qqq.middleware.javalin.executors;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.middleware.javalin.executors.io.${baseName}Input;
import com.kingsrook.qqq.middleware.javalin.executors.io.${baseName}OutputInterface;
/*******************************************************************************
**
*******************************************************************************/
public class ${baseName}Executor extends AbstractMiddlewareExecutor<${baseName}Input, ${baseName}OutputInterface>
{
/***************************************************************************
**
***************************************************************************/
@Override
public void execute(${baseName}Input input, ${baseName}OutputInterface output) throws QException
{
}
}
""".replaceAll("\\$\\{baseName}", baseName);
}
/***************************************************************************
**
***************************************************************************/
private String makeInput(String baseName)
{
return """
package com.kingsrook.qqq.middleware.javalin.executors.io;
/*******************************************************************************
**
*******************************************************************************/
public class ${baseName}Input extends AbstractMiddlewareInput
{
}
""".replaceAll("\\$\\{baseName}", baseName);
}
/***************************************************************************
**
***************************************************************************/
private String makeOutputInterface(String baseName)
{
return """
package com.kingsrook.qqq.middleware.javalin.executors.io;
/*******************************************************************************
**
*******************************************************************************/
public interface ${baseName}OutputInterface extends AbstractMiddlewareOutputInterface
{
}
""".replaceAll("\\$\\{baseName}", baseName);
}
}

View File

@ -0,0 +1,300 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.tools.codegenerators;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.FileUtils;
/*******************************************************************************
**
*******************************************************************************/
class SpecCodeGenerator
{
/***************************************************************************
**
***************************************************************************/
public static void main(String[] args)
{
try
{
String qqqDir = "/Users/dkelkhoff/git/kingsrook/qqq/";
/////////////////
// normal case //
/////////////////
new SpecCodeGenerator().writeAllFiles(qqqDir, "v1", "ProcessMetaData");
///////////////////////////////////////////////////////////////////////////////
// if the executor isn't named the same as the spec (e.g., reused executors) //
///////////////////////////////////////////////////////////////////////////////
// new SpecCodeGenerator().writeAllFiles(qqqDir, "v1", "ProcessInsert", "ProcessInsertOrSetp");
}
catch(IOException e)
{
//noinspection CallToPrintStackTrace
e.printStackTrace();
}
}
/***************************************************************************
**
***************************************************************************/
private void writeOne(String fullPath, String content) throws IOException
{
File file = new File(fullPath);
File directory = file.getParentFile();
if(!directory.exists())
{
throw (new RuntimeException("Directory for: " + fullPath + " does not exists, and I refuse to mkdir (do it yourself and/or fix your arguments)."));
}
if(file.exists())
{
throw (new RuntimeException("File at: " + fullPath + " already exists, and I refuse to overwrite files."));
}
System.out.println("Writing: " + file);
FileUtils.writeStringToFile(file, content, StandardCharsets.UTF_8);
}
/***************************************************************************
**
***************************************************************************/
private void writeAllFiles(String rootPath, String version, String baseName) throws IOException
{
writeAllFiles(rootPath, version, baseName, baseName);
}
/***************************************************************************
**
***************************************************************************/
private void writeAllFiles(String rootPath, String version, String baseName, String executorBaseName) throws IOException
{
String basePath = rootPath + "qqq-middleware-javalin/src/main/java/com/kingsrook/qqq/middleware/javalin/";
writeOne(basePath + "specs/" + version.toLowerCase() + "/" + baseName + "Spec" + version.toUpperCase() + ".java", makeSpec(version, baseName, executorBaseName));
writeOne(basePath + "specs/" + version.toLowerCase() + "/responses/" + baseName + "Response" + version.toUpperCase() + ".java", makeResponse(version, baseName, executorBaseName));
System.out.println();
System.out.println("Hey - You probably want to add a line like:");
System.out.println(" list.add(new " + baseName + "Spec" + version.toUpperCase() + "());");
System.out.println("In:");
System.out.println(" MiddlewareVersion" + version.toUpperCase() + ".java");
System.out.println();
}
/***************************************************************************
**
***************************************************************************/
private String makeSpec(String version, String baseName, String executorBaseName)
{
return """
package com.kingsrook.qqq.middleware.javalin.specs.${version.toLowerCase};
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.middleware.javalin.executors.${executorBaseName}Executor;
import com.kingsrook.qqq.middleware.javalin.executors.io.${executorBaseName}Input;
import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
import com.kingsrook.qqq.middleware.javalin.specs.BasicOperation;
import com.kingsrook.qqq.middleware.javalin.specs.BasicResponse;
import com.kingsrook.qqq.middleware.javalin.specs.${version.toLowerCase}.responses.${executorBaseName}Response${version.toUpperCase};
import com.kingsrook.qqq.middleware.javalin.specs.${version.toLowerCase}.utils.Tags${version.toUpperCase};
import com.kingsrook.qqq.openapi.model.Content;
import com.kingsrook.qqq.openapi.model.Example;
import com.kingsrook.qqq.openapi.model.HttpMethod;
import com.kingsrook.qqq.openapi.model.In;
import com.kingsrook.qqq.openapi.model.Parameter;
import com.kingsrook.qqq.openapi.model.RequestBody;
import com.kingsrook.qqq.openapi.model.Schema;
import com.kingsrook.qqq.openapi.model.Type;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
/*******************************************************************************
**
*******************************************************************************/
public class ${baseName}Spec${version.toUpperCase} extends AbstractEndpointSpec<${executorBaseName}Input, ${baseName}Response${version.toUpperCase}, ${executorBaseName}Executor>
{
/***************************************************************************
**
***************************************************************************/
public BasicOperation defineBasicOperation()
{
return new BasicOperation()
.withPath(TODO)
.withHttpMethod(HttpMethod.TODO)
.withTag(Tags${version.toUpperCase}.TODO)
.withShortSummary(TODO)
.withLongDescription(""\"
TODO""\"
);
}
/***************************************************************************
**
***************************************************************************/
@Override
public List<Parameter> defineRequestParameters()
{
return List.of(
new Parameter()
.withName(TODO)
.withDescription(TODO)
.withRequired(TODO)
.withSchema(new Schema().withType(Type.TODO))
.withExamples(Map.of("TODO", new Example().withValue(TODO)))
.withIn(In.TODO)
);
}
/***************************************************************************
**
***************************************************************************/
@Override
public RequestBody defineRequestBody()
{
return new RequestBody()
.withContent(Map.of(
ContentType.TODO.getMimeType(), new Content()
.withSchema(new Schema()
.withType(Type.TODO)
.withProperties(Map.of(
"TODO", new Schema()
))
)
));
}
/***************************************************************************
**
***************************************************************************/
@Override
public ${executorBaseName}Input buildInput(Context context) throws Exception
{
${executorBaseName}Input input = new ${executorBaseName}Input();
input.setTODO
return (input);
}
/***************************************************************************
**
***************************************************************************/
@Override
public BasicResponse defineBasicSuccessResponse()
{
Map<String, Example> examples = Map.of(
"TODO", new Example()
.withValue(new ${baseName}Response${version.toUpperCase}()
.withTODO
)
);
return new BasicResponse(""\"
TODO""\",
new ${baseName}Response${version.toUpperCase}().toSchema(),
examples
);
}
/***************************************************************************
**
***************************************************************************/
@Override
public void handleOutput(Context context, ${baseName}Response${version.toUpperCase} output) throws Exception
{
context.result(JsonUtils.toJson(output));
}
}
"""
.replaceAll("\\$\\{version.toLowerCase}", version.toLowerCase())
.replaceAll("\\$\\{version.toUpperCase}", version.toUpperCase())
.replaceAll("\\$\\{executorBaseName}", executorBaseName)
.replaceAll("\\$\\{baseName}", baseName)
;
}
/***************************************************************************
**
***************************************************************************/
private String makeResponse(String version, String baseName, String executorBaseName)
{
return """
package com.kingsrook.qqq.middleware.javalin.specs.${version.toLowerCase}.responses;
import com.kingsrook.qqq.middleware.javalin.executors.io.${executorBaseName}OutputInterface;
import com.kingsrook.qqq.middleware.javalin.schemabuilder.annotations.OpenAPIDescription;
import com.kingsrook.qqq.middleware.javalin.specs.ToSchema;
/*******************************************************************************
**
*******************************************************************************/
public class ${baseName}Response${version.toUpperCase} implements ${executorBaseName}OutputInterface, ToSchema
{
@OpenAPIDescription(TODO)
private String TODO;
TODO gsw
}
"""
.replaceAll("\\$\\{version.toLowerCase}", version.toLowerCase())
.replaceAll("\\$\\{version.toUpperCase}", version.toUpperCase())
.replaceAll("\\$\\{executorBaseName}", executorBaseName)
.replaceAll("\\$\\{baseName}", baseName)
;
}
}

View File

@ -0,0 +1,27 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/*******************************************************************************
** These classes are meant as tools to be executed manually by a developer,
** to create other new classes (since there's a bit of boilerplate, innit?)
**
*******************************************************************************/
package com.kingsrook.qqq.middleware.javalin.tools.codegenerators;

View File

@ -0,0 +1,27 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/*******************************************************************************
** This tools path - is for non-production code - rather, development and CI/CD
** tools.
**
*******************************************************************************/
package com.kingsrook.qqq.middleware.javalin.tools;