mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
CE-1887 Initial checkin - dev-tools for middleware api
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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 + "]");
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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;
|
Reference in New Issue
Block a user