diff --git a/src/org/kar/archidata/catcher/RestErrorResponse.java b/src/org/kar/archidata/catcher/RestErrorResponse.java index c009234..5cd8a39 100644 --- a/src/org/kar/archidata/catcher/RestErrorResponse.java +++ b/src/org/kar/archidata/catcher/RestErrorResponse.java @@ -5,14 +5,21 @@ import java.util.UUID; import org.kar.archidata.tools.UuidUtils; +import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.core.Response; public class RestErrorResponse { + @NotNull public UUID uuid = UuidUtils.nextUUID(); + @NotNull public String name; // Mandatory for TS generic error + @NotNull public String message; // Mandatory for TS generic error + @NotNull public String time; + @NotNull final public int status; + @NotNull final public String statusMessage; public RestErrorResponse(final Response.Status status, final String time, final String error, diff --git a/src/org/kar/archidata/externalRestApi/AnalyzeApi.java b/src/org/kar/archidata/externalRestApi/AnalyzeApi.java index fc15af0..c0aab67 100644 --- a/src/org/kar/archidata/externalRestApi/AnalyzeApi.java +++ b/src/org/kar/archidata/externalRestApi/AnalyzeApi.java @@ -11,16 +11,64 @@ import org.slf4j.LoggerFactory; public class AnalyzeApi { static final Logger LOGGER = LoggerFactory.getLogger(AnalyzeApi.class); - public List apiModels = new ArrayList<>(); - public List classModels = new ArrayList<>(); - - public void createApi(final List> classs) throws Exception { - final ModelGroup previousModel = new ModelGroup(this.classModels); - for (final Class clazz : classs) { - final ApiGroupModel parsed = new ApiGroupModel(clazz, previousModel); - this.apiModels.add(parsed); - } - AnalyzeModel.fillModel(previousModel.previousModel); + protected final List apiModels = new ArrayList<>(); + protected final ModelGroup modelGroup = new ModelGroup(); + + public void addAllModel(final List> classes) throws Exception { + this.modelGroup.addAll(classes); + analyzeModels(); } + public void addModel(final Class clazz) throws Exception { + this.modelGroup.add(clazz); + analyzeModels(); + } + + public void addApi(final Class clazz) throws Exception { + this.apiModels.add(new ApiGroupModel(clazz, this.modelGroup)); + analyzeModels(); + } + + public void addAllApi(final List> classes) throws Exception { + for (final Class clazz : classes) { + this.apiModels.add(new ApiGroupModel(clazz, this.modelGroup)); + } + analyzeModels(); + } + + public List getAllApi() { + return this.apiModels; + } + + public List getAllModel() { + return this.modelGroup.getModels(); + } + + private void analyzeModels() throws Exception { + final List dones = new ArrayList<>(); + while (dones.size() < getAllModel().size()) { + final List copyList = new ArrayList<>(this.modelGroup.getModels()); + for (final ClassModel model : copyList) { + if (dones.contains(model)) { + continue; + } + LOGGER.info("Analyze: {}", model); + model.analyze(this.modelGroup); + dones.add(model); + } + } + } + + public List getCompatibleModels(final List> search) { + final List out = new ArrayList<>(); + for (final ClassModel model : getAllModel()) { + if (search.contains(model.getOriginClasses())) { + out.add(model); + } + } + if (out.isEmpty()) { + return null; + } + return out; + } } diff --git a/src/org/kar/archidata/externalRestApi/AnalyzeModel.java b/src/org/kar/archidata/externalRestApi/AnalyzeModel.java deleted file mode 100644 index 0642582..0000000 --- a/src/org/kar/archidata/externalRestApi/AnalyzeModel.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.kar.archidata.externalRestApi; - -import java.util.ArrayList; -import java.util.List; - -import org.kar.archidata.externalRestApi.model.ClassModel; -import org.kar.archidata.externalRestApi.model.ModelGroup; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class AnalyzeModel { - static final Logger LOGGER = LoggerFactory.getLogger(AnalyzeModel.class); - - public static void fillModel(final List models) throws Exception { - final ModelGroup previousModel = new ModelGroup(models); - final List dones = new ArrayList<>(); - while (dones.size() < previousModel.previousModel.size()) { - LOGGER.info("Do a cycle of annalyze : new model detected: {} < {}", dones.size(), - previousModel.previousModel.size()); - final List copyList = new ArrayList<>(previousModel.previousModel); - for (final ClassModel model : copyList) { - if (dones.contains(model)) { - continue; - } - LOGGER.info("Analyze: {}", model); - model.analyze(previousModel); - dones.add(model); - } - } - } - -} diff --git a/src/org/kar/archidata/externalRestApi/GeneratePythonModel.java b/src/org/kar/archidata/externalRestApi/GeneratePythonModel.java deleted file mode 100644 index ce59eb5..0000000 --- a/src/org/kar/archidata/externalRestApi/GeneratePythonModel.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.kar.archidata.externalRestApi; - -public class GeneratePythonModel { - -} diff --git a/src/org/kar/archidata/externalRestApi/GenerateTsModel.java b/src/org/kar/archidata/externalRestApi/GenerateTsModel.java deleted file mode 100644 index ab6d35f..0000000 --- a/src/org/kar/archidata/externalRestApi/GenerateTsModel.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.kar.archidata.externalRestApi; - -public class GenerateTsModel { - -} diff --git a/src/org/kar/archidata/externalRestApi/GeneratePythonApi.java b/src/org/kar/archidata/externalRestApi/PythonGenerateApi.java similarity index 73% rename from src/org/kar/archidata/externalRestApi/GeneratePythonApi.java rename to src/org/kar/archidata/externalRestApi/PythonGenerateApi.java index e6697e6..d691cef 100644 --- a/src/org/kar/archidata/externalRestApi/GeneratePythonApi.java +++ b/src/org/kar/archidata/externalRestApi/PythonGenerateApi.java @@ -1,8 +1,8 @@ package org.kar.archidata.externalRestApi; -public class GeneratePythonApi { - +public class PythonGenerateApi { + public static void generateApi(final AnalyzeApi api) { - + } } diff --git a/src/org/kar/archidata/externalRestApi/TsApiGeneration.java b/src/org/kar/archidata/externalRestApi/TsApiGeneration.java index 0852b58..fe36671 100644 --- a/src/org/kar/archidata/externalRestApi/TsApiGeneration.java +++ b/src/org/kar/archidata/externalRestApi/TsApiGeneration.java @@ -3,13 +3,22 @@ package org.kar.archidata.externalRestApi; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map.Entry; +import java.util.Set; import org.kar.archidata.dataAccess.DataExport; +import org.kar.archidata.externalRestApi.TsClassElement.DefinedPosition; import org.kar.archidata.externalRestApi.model.ApiGroupModel; import org.kar.archidata.externalRestApi.model.ApiModel; +import org.kar.archidata.externalRestApi.model.ClassEnumModel; +import org.kar.archidata.externalRestApi.model.ClassListModel; +import org.kar.archidata.externalRestApi.model.ClassMapModel; import org.kar.archidata.externalRestApi.model.ClassModel; +import org.kar.archidata.externalRestApi.model.ClassObjectModel; import org.kar.archidata.externalRestApi.model.RestTypeRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,53 +27,152 @@ import jakarta.ws.rs.core.MediaType; public class TsApiGeneration { static final Logger LOGGER = LoggerFactory.getLogger(TsApiGeneration.class); - + public static String getBaseHeader() { return """ /** * Interface of the server (auto-generated code) */ - import { - HTTPMimeType, - HTTPRequestModel, - ModelResponseHttp, - RESTCallbacks, - RESTConfig, - RESTRequestJson, - RESTRequestJsonArray, - RESTRequestVoid - } from "../rest-tools" - """; } + public static String generateClassEnumModelTypescript( + final ClassEnumModel model, + final TsClassElementGroup tsGroup, + final Set imports, + final boolean writeMode) throws IOException { + imports.add(model); + final TsClassElement tsModel = tsGroup.find(model); + return tsModel.tsTypeName; + } + + public static String generateClassObjectModelTypescript( + final ClassObjectModel model, + final TsClassElementGroup tsGroup, + final Set imports, + final boolean writeMode) throws IOException { + final TsClassElement tsModel = tsGroup.find(model); + if (tsModel.nativeType != DefinedPosition.NATIVE) { + imports.add(model); + } + if (tsModel.nativeType == DefinedPosition.BASIC) { + return tsModel.tsTypeName; + } + if (writeMode) { + return tsModel.tsTypeName + "Write"; + } + return tsModel.tsTypeName; + } + + public static String generateClassMapModelTypescript( + final ClassMapModel model, + final TsClassElementGroup tsGroup, + final Set imports, + final boolean writeMode) throws IOException { + final StringBuilder out = new StringBuilder(); + out.append("{[key: "); + out.append(generateClassModelTypescript(model.keyModel, tsGroup, imports, writeMode)); + out.append("]: "); + out.append(generateClassModelTypescript(model.valueModel, tsGroup, imports, writeMode)); + out.append(";}"); + return out.toString(); + } + + public static String generateClassListModelTypescript( + final ClassListModel model, + final TsClassElementGroup tsGroup, + final Set imports, + final boolean writeMode) throws IOException { + final StringBuilder out = new StringBuilder(); + out.append(generateClassModelTypescript(model.valueModel, tsGroup, imports, writeMode)); + out.append("[]"); + return out.toString(); + } + + public static String generateClassModelTypescript( + final ClassModel model, + final TsClassElementGroup tsGroup, + final Set imports, + final boolean writeMode) throws IOException { + if (model instanceof final ClassObjectModel objectModel) { + return generateClassObjectModelTypescript(objectModel, tsGroup, imports, writeMode); + } + if (model instanceof final ClassListModel listModel) { + return generateClassListModelTypescript(listModel, tsGroup, imports, writeMode); + } + if (model instanceof final ClassMapModel mapModel) { + return generateClassMapModelTypescript(mapModel, tsGroup, imports, writeMode); + } + if (model instanceof final ClassEnumModel enumModel) { + return generateClassEnumModelTypescript(enumModel, tsGroup, imports, writeMode); + } + throw new IOException("Impossible model:" + model); + } + + public static String generateClassModelsTypescript( + final List models, + final TsClassElementGroup tsGroup, + final Set imports, + final boolean writeMode) throws IOException { + if (models.size() == 0) { + return "void"; + } + final StringBuilder out = new StringBuilder(); + boolean isFirst = true; + for (final ClassModel model : models) { + if (isFirst) { + isFirst = false; + } else { + out.append(" | "); + } + final String data = generateClassModelTypescript(model, tsGroup, imports, writeMode); + out.append(data); + } + return out.toString(); + } + + public static String capitalizeFirstLetter(final String str) { + if (str == null || str.isEmpty()) { + return str; + } + return str.substring(0, 1).toUpperCase() + str.substring(1); + } + public static void generateApiFile( final ApiGroupModel element, final String pathPackage, final TsClassElementGroup tsGroup) throws IOException { final StringBuilder data = new StringBuilder(); - data.append(getBaseHeader()); data.append("export namespace "); data.append(element.name); data.append(" {\n"); - + final Set imports = new HashSet<>(); + final Set zodImports = new HashSet<>(); + final Set isImports = new HashSet<>(); + final Set writeImports = new HashSet<>(); + final Set toolImports = new HashSet<>(); for (final ApiModel interfaceElement : element.interfaces) { - final String methodName = interfaceElement.name; - final String methodPath = interfaceElement.restEndPoint; - final RestTypeRequest methodType = interfaceElement.restTypeRequest; final List consumes = interfaceElement.consumes; final List produces = interfaceElement.produces; final boolean needGenerateProgress = interfaceElement.needGenerateProgress; - final List returnTypeModel = interfaceElement.returnTypes; - + final String returnModelNameIfComplex = capitalizeFirstLetter(interfaceElement.name) + "TypeReturn"; + final String returnComplexModel = TsClassElement.generateLocalModel(returnModelNameIfComplex, + interfaceElement.returnTypes, tsGroup); + if (returnComplexModel != null) { + data.append("\n\n"); + data.append(returnComplexModel.replaceAll("(?m)^", "\t")); + for (final ClassModel elem : interfaceElement.returnTypes) { + zodImports.addAll(elem.getDependencyGroupModels()); + } + } if (interfaceElement.description != null) { data.append("\n\t/**\n\t * "); data.append(interfaceElement.description); data.append("\n\t */"); } data.append("\n\texport function "); - data.append(methodName); + data.append(interfaceElement.name); data.append("({\n\t\t\trestConfig,"); if (!interfaceElement.queries.isEmpty()) { data.append("\n\t\t\tqueries,"); @@ -83,31 +191,33 @@ public class TsApiGeneration { } data.append("\n\t\t}: {"); data.append("\n\t\trestConfig: RESTConfig,"); + toolImports.add("RESTConfig"); if (!interfaceElement.queries.isEmpty()) { data.append("\n\t\tqueries: {"); for (final Entry> queryEntry : interfaceElement.queries.entrySet()) { data.append("\n\t\t\t"); data.append(queryEntry.getKey()); data.append("?: "); - data.append(queryEntry.getValue()); + data.append(generateClassModelsTypescript(queryEntry.getValue(), tsGroup, imports, false)); data.append(","); } data.append("\n\t\t},"); } if (!interfaceElement.parameters.isEmpty()) { data.append("\n\t\tparams: {"); - for (final Entry> pathEntry : interfaceElement.parameters.entrySet()) { + for (final Entry> paramEntry : interfaceElement.parameters.entrySet()) { data.append("\n\t\t\t"); - data.append(pathEntry.getKey()); + data.append(paramEntry.getKey()); data.append(": "); - data.append(pathEntry.getValue()); + data.append(generateClassModelsTypescript(paramEntry.getValue(), tsGroup, imports, false)); data.append(","); } data.append("\n\t\t},"); } if (interfaceElement.unnamedElement.size() == 1) { data.append("\n\t\tdata: "); - data.append(interfaceElement.unnamedElement.get(0)); + data.append(generateClassModelTypescript(interfaceElement.unnamedElement.get(0), tsGroup, writeImports, + true)); data.append(","); } else if (interfaceElement.multiPartParameters.size() != 0) { data.append("\n\t\tdata: {"); @@ -116,7 +226,7 @@ public class TsApiGeneration { data.append("\n\t\t\t"); data.append(pathEntry.getKey()); data.append(": "); - data.append(pathEntry.getValue()); + data.append(generateClassModelsTypescript(pathEntry.getValue(), tsGroup, writeImports, true)); data.append(","); } data.append("\n\t\t},"); @@ -129,12 +239,15 @@ public class TsApiGeneration { if (MediaType.APPLICATION_JSON.equals(elem)) { lastElement = "HTTPMimeType.JSON"; + toolImports.add("HTTPMimeType"); } if (MediaType.MULTIPART_FORM_DATA.equals(elem)) { lastElement = "HTTPMimeType.MULTIPART"; + toolImports.add("HTTPMimeType"); } if (DataExport.CSV_TYPE.equals(elem)) { lastElement = "HTTPMimeType.CSV"; + toolImports.add("HTTPMimeType"); } if (lastElement != null) { if (isFist == null) { @@ -151,51 +264,55 @@ public class TsApiGeneration { } if (needGenerateProgress) { data.append("\n\t\tcallback?: RESTCallbacks,"); + toolImports.add("RESTCallbacks"); } data.append("\n\t}): Promise<"); - /** - if (interfaceElement.returnTypes.size() == 0 // - || tmpReturn.get(0).tsTypeName == null // - || tmpReturn.get(0).tsTypeName.equals("void")) { - data.append("void"); - } else { - data.append(ApiTool.convertInTypeScriptType(tmpReturn, returnModelIsArray)); - } - */ - data.append("> {"); - /** - if (tmpReturn.size() == 0 // - || tmpReturn.get(0).tsTypeName == null // - || tmpReturn.get(0).tsTypeName.equals("void")) { - data.append("\n\t\treturn RESTRequestVoid({"); - } else if (returnModelIsArray) { - data.append("\n\t\treturn RESTRequestJsonArray({"); - } else { + if (returnComplexModel != null) { + data.append(returnModelNameIfComplex); + data.append("> {"); data.append("\n\t\treturn RESTRequestJson({"); + toolImports.add("RESTRequestJson"); + } else { + final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, tsGroup, imports, + false); + data.append(returnType); + data.append("> {"); + if ("void".equals(returnType)) { + data.append("\n\t\treturn RESTRequestVoid({"); + toolImports.add("RESTRequestVoid"); + } else { + isImports.addAll(interfaceElement.returnTypes); + data.append("\n\t\treturn RESTRequestJson({"); + toolImports.add("RESTRequestJson"); + } } - */ data.append("\n\t\t\trestModel: {"); data.append("\n\t\t\t\tendPoint: \""); data.append(interfaceElement.restEndPoint); data.append("\","); data.append("\n\t\t\t\trequestType: HTTPRequestModel."); - data.append(methodType); + toolImports.add("HTTPRequestModel"); + data.append(interfaceElement.restTypeRequest); data.append(","); if (consumes != null) { for (final String elem : consumes) { if (MediaType.APPLICATION_JSON.equals(elem)) { data.append("\n\t\t\t\tcontentType: HTTPMimeType.JSON,"); + toolImports.add("HTTPMimeType"); break; } else if (MediaType.MULTIPART_FORM_DATA.equals(elem)) { data.append("\n\t\t\t\tcontentType: HTTPMimeType.MULTIPART,"); + toolImports.add("HTTPMimeType"); break; } else if (MediaType.TEXT_PLAIN.equals(elem)) { data.append("\n\t\t\t\tcontentType: HTTPMimeType.TEXT_PLAIN,"); + toolImports.add("HTTPMimeType"); break; } } - } else if ("DELETE".equals(methodType)) { + } else if (RestTypeRequest.DELETE.equals(interfaceElement.restTypeRequest)) { data.append("\n\t\t\t\tcontentType: HTTPMimeType.TEXT_PLAIN,"); + toolImports.add("HTTPMimeType"); } if (produces != null) { if (produces.size() > 1) { @@ -204,6 +321,7 @@ public class TsApiGeneration { for (final String elem : produces) { if (MediaType.APPLICATION_JSON.equals(elem)) { data.append("\n\t\t\t\taccept: HTTPMimeType.JSON,"); + toolImports.add("HTTPMimeType"); break; } } @@ -226,23 +344,90 @@ public class TsApiGeneration { data.append("\n\t\t\tcallback,"); } data.append("\n\t\t}"); - /** - if (tmpReturn.size() != 0 && tmpReturn.get(0).tsTypeName != null - && !tmpReturn.get(0).tsTypeName.equals("void")) { - data.append(", "); - // TODO: correct this it is really bad ... - data.append(ApiTool.convertInTypeScriptCheckType(tmpReturn)); + if (returnComplexModel != null) { + data.append(", is"); + data.append(returnModelNameIfComplex); + } else { + final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, tsGroup, imports, + false); + if (!"void".equals(returnType)) { + data.append(", is"); + data.append(returnType); + } } - **/ data.append(");"); data.append("\n\t};"); } data.append("\n}\n"); + + final StringBuilder out = new StringBuilder(); + out.append(getBaseHeader()); + + final List toolImportsList = new ArrayList<>(toolImports); + Collections.sort(toolImportsList); + if (toolImportsList.size() != 0) { + out.append("import {"); + for (final String elem : toolImportsList) { + out.append("\n\t"); + out.append(elem); + out.append(","); + } + out.append("\n} from \"../rest-tools\";\n\n"); + } + + if (zodImports.size() != 0) { + out.append("import { z as zod } from \"zod\"\n"); + } + + final List finalImportList = new ArrayList<>(); + + for (final ClassModel model : imports) { + final TsClassElement tsModel = tsGroup.find(model); + if (tsModel.nativeType == DefinedPosition.NATIVE) { + continue; + } + finalImportList.add(tsModel.tsTypeName); + } + for (final ClassModel model : isImports) { + final TsClassElement tsModel = tsGroup.find(model); + if (tsModel.nativeType == DefinedPosition.NATIVE) { + continue; + } + finalImportList.add("is" + tsModel.tsTypeName); + } + for (final ClassModel model : zodImports) { + final TsClassElement tsModel = tsGroup.find(model); + if (tsModel.nativeType == DefinedPosition.NATIVE) { + continue; + } + finalImportList.add("Zod" + tsModel.tsTypeName); + } + for (final ClassModel model : writeImports) { + final TsClassElement tsModel = tsGroup.find(model); + if (tsModel.nativeType == DefinedPosition.NATIVE) { + continue; + } + finalImportList.add(tsModel.tsTypeName + "Write"); + } + Collections.sort(finalImportList); + + if (finalImportList.size() != 0) { + out.append("import {"); + for (final String elem : finalImportList) { + out.append("\n\t"); + out.append(elem); + out.append(","); + } + out.append("\n} from \"../model\";\n\n"); + } + + out.append(data.toString()); + final String fileName = TsClassElement.determineFileName(element.name); final FileWriter myWriter = new FileWriter( pathPackage + File.separator + "api" + File.separator + fileName + ".ts"); - myWriter.write(data.toString()); + myWriter.write(out.toString()); myWriter.close(); } - + } \ No newline at end of file diff --git a/src/org/kar/archidata/externalRestApi/TsClassElement.java b/src/org/kar/archidata/externalRestApi/TsClassElement.java index cbdae4e..4878720 100644 --- a/src/org/kar/archidata/externalRestApi/TsClassElement.java +++ b/src/org/kar/archidata/externalRestApi/TsClassElement.java @@ -19,13 +19,13 @@ import org.slf4j.LoggerFactory; public class TsClassElement { static final Logger LOGGER = LoggerFactory.getLogger(TsClassElement.class); - + public enum DefinedPosition { NATIVE, // Native element of TS language. BASIC, // basic wrapping for JAVA type. NORMAL // Normal Object to interpret. } - + public List models; public String zodName; public String tsTypeName; @@ -34,11 +34,11 @@ public class TsClassElement { public String fileName = null; public String comment = null; public DefinedPosition nativeType = DefinedPosition.NORMAL; - + public static String determineFileName(final String className) { return className.replaceAll("([a-z])([A-Z])", "$1-$2").replaceAll("([A-Z])([A-Z][a-z])", "$1-$2").toLowerCase(); } - + public TsClassElement(final List model, final String zodName, final String tsTypeName, final String tsCheckType, final String declaration, final DefinedPosition nativeType) { this.models = model; @@ -49,7 +49,7 @@ public class TsClassElement { this.nativeType = nativeType; this.fileName = determineFileName(tsTypeName); } - + public TsClassElement(final ClassModel model) { this.models = List.of(model); this.zodName = "Zod" + model.getOriginClasses().getSimpleName(); @@ -58,27 +58,27 @@ public class TsClassElement { this.declaration = null; this.fileName = determineFileName(this.tsTypeName); } - + public boolean isCompatible(final ClassModel model) { return this.models.contains(model); } - + public String getBaseHeader() { return """ /** * Interface of the server (auto-generated code) */ import { z as zod } from "zod"; - + """; } - + public String generateEnum(final ClassEnumModel model, final TsClassElementGroup tsGroup) throws IOException { final StringBuilder out = new StringBuilder(); out.append(getBaseHeader()); out.append("\n"); //out.append(generateComment(model)); - + if (System.getenv("ARCHIDATA_GENERATE_ZOD_ENUM") != null) { boolean first = true; out.append("export const "); @@ -102,12 +102,7 @@ public class TsClassElement { out.append("\n\t])"); } out.append(";\n"); - - out.append("\nexport type "); - out.append(this.tsTypeName); - out.append(" = zod.infer;\n"); + out.append(generateZodInfer(this.tsTypeName, this.zodName)); } else { boolean first = true; out.append("export enum "); @@ -140,25 +135,24 @@ public class TsClassElement { out.append(generateExportCheckFunctionWrite("")); return out.toString(); } - - private String generateExportCheckFunctionWrite(final String writeString) { + + private static String generateExportCheckFunction( + final String tsCheckType, + final String tsTypeName, + final String zodName) { final StringBuilder out = new StringBuilder(); out.append("\nexport function "); - out.append(this.tsCheckType); - out.append(writeString); + out.append(tsCheckType); out.append("(data: any): data is "); - out.append(this.tsTypeName); - out.append(writeString); + out.append(tsTypeName); out.append(" {\n\ttry {\n\t\t"); - out.append(this.zodName); - out.append(writeString); + out.append(zodName); out.append(""" .parse(data); return true; } catch (e: any) { console.log(`Fail to parse data type='"""); - out.append(this.zodName); - out.append(writeString); + out.append(zodName); out.append(""" ' error=${e}`); return false; @@ -167,7 +161,12 @@ public class TsClassElement { """); return out.toString(); } - + + private String generateExportCheckFunctionWrite(final String writeString) { + return generateExportCheckFunction(this.tsCheckType + writeString, this.tsTypeName + writeString, + this.zodName + writeString); + } + public String generateImports(final List depModels, final TsClassElementGroup tsGroup) throws IOException { final StringBuilder out = new StringBuilder(); @@ -183,7 +182,7 @@ public class TsClassElement { } return out.toString(); } - + private Object generateComment(final ClassObjectModel model) { final StringBuilder out = new StringBuilder(); if (model.getDescription() != null || model.getExample() != null) { @@ -209,14 +208,17 @@ public class TsClassElement { } return out.toString(); } - + public String optionalTypeZod(final FieldProperty field) { if (field.model().getOriginClasses() == null || field.model().getOriginClasses().isPrimitive()) { return ""; } + if (field.notNull()) { + return ""; + } return ".optional()"; } - + public String maxSizeZod(final FieldProperty field) { final StringBuilder builder = new StringBuilder(); final Class clazz = field.model().getOriginClasses(); @@ -227,39 +229,39 @@ public class TsClassElement { } return builder.toString(); } - + public String readOnlyZod(final FieldProperty field) { if (field.readOnly()) { return ".readonly()"; } return ""; } - + public String generateBaseObject() { final StringBuilder out = new StringBuilder(); out.append(getBaseHeader()); out.append("\n"); - + out.append("export const "); out.append(this.zodName); out.append(" = "); out.append(this.declaration); out.append(";"); - generateZodInfer(this.tsTypeName, this.zodName); + out.append(generateZodInfer(this.tsTypeName, this.zodName)); return out.toString(); } - + public String generateObject(final ClassObjectModel model, final TsClassElementGroup tsGroup) throws IOException { final StringBuilder out = new StringBuilder(); out.append(getBaseHeader()); out.append(generateImports(model.getDependencyModels(), tsGroup)); out.append("\n"); - + out.append(generateComment(model)); out.append("export const "); out.append(this.zodName); out.append(" = "); - + if (model.getExtendsClass() != null) { final ClassModel parentClass = model.getExtendsClass(); final TsClassElement tsParentModel = tsGroup.find(parentClass); @@ -299,7 +301,7 @@ public class TsClassElement { out.append("\n});\n"); out.append(generateZodInfer(this.tsTypeName, this.zodName)); out.append(generateExportCheckFunctionWrite("")); - + // Generate the Write Type associated. out.append("\nexport const "); out.append(this.zodName); @@ -314,16 +316,16 @@ public class TsClassElement { } out.append("\n})"); } - out.append(";\n"); + out.append(".partial();\n"); out.append(generateZodInfer(this.tsTypeName + "Write", this.zodName + "Write")); - + // Check only the input value ==> no need of the output out.append(generateExportCheckFunctionWrite("Write")); - + return out.toString(); } - - private String generateZodInfer(final String tsName, final String zodName) { + + private static String generateZodInfer(final String tsName, final String zodName) { final StringBuilder out = new StringBuilder(); out.append("\nexport type "); out.append(tsName); @@ -332,8 +334,8 @@ public class TsClassElement { out.append(">;\n"); return out.toString(); } - - private String generateTsMap(final ClassMapModel model, final TsClassElementGroup tsGroup) { + + private static String generateTsMap(final ClassMapModel model, final TsClassElementGroup tsGroup) { final StringBuilder out = new StringBuilder(); out.append("zod.record("); if (model.keyModel instanceof final ClassListModel fieldListModel) { @@ -366,18 +368,18 @@ public class TsClassElement { out.append(")"); return out.toString(); } - - private String generateTsEnum(final ClassEnumModel model, final TsClassElementGroup tsGroup) { + + private static String generateTsEnum(final ClassEnumModel model, final TsClassElementGroup tsGroup) { final TsClassElement tsParentModel = tsGroup.find(model); return tsParentModel.zodName; } - - private String generateTsObject(final ClassObjectModel model, final TsClassElementGroup tsGroup) { + + private static String generateTsObject(final ClassObjectModel model, final TsClassElementGroup tsGroup) { final TsClassElement tsParentModel = tsGroup.find(model); return tsParentModel.zodName; } - - private String generateTsList(final ClassListModel model, final TsClassElementGroup tsGroup) { + + private static String generateTsList(final ClassListModel model, final TsClassElementGroup tsGroup) { final StringBuilder out = new StringBuilder(); out.append("zod.array("); if (model.valueModel instanceof final ClassListModel fieldListModel) { @@ -393,14 +395,14 @@ public class TsClassElement { out.append(")"); return out.toString(); } - + public void generateFile(final String pathPackage, final TsClassElementGroup tsGroup) throws IOException { if (this.nativeType == DefinedPosition.NATIVE) { return; } final ClassModel model = this.models.get(0); String data = ""; - if (this.nativeType == DefinedPosition.BASIC && model instanceof final ClassObjectModel modelObject) { + if (this.nativeType == DefinedPosition.BASIC && model instanceof ClassObjectModel) { data = generateBaseObject(); } else if (model instanceof final ClassEnumModel modelEnum) { data = generateEnum(modelEnum, tsGroup); @@ -417,4 +419,55 @@ public class TsClassElement { myWriter.close(); } + private static String generateLocalModelBase(final ClassModel model, final TsClassElementGroup tsGroup) + throws IOException { + if (model instanceof final ClassObjectModel objectModel) { + return generateTsObject(objectModel, tsGroup); + } + if (model instanceof final ClassEnumModel enumModel) { + return generateTsEnum(enumModel, tsGroup); + } + if (model instanceof final ClassListModel listModel) { + return generateTsList(listModel, tsGroup); + } + if (model instanceof final ClassMapModel mapModel) { + return generateTsMap(mapModel, tsGroup); + } + return ""; + } + + public static String generateLocalModel( + final String ModelName, + final List models, + final TsClassElementGroup tsGroup) throws IOException { + if (models.size() == 1) { + if (models.get(0) instanceof ClassObjectModel) { + return null; + } + if (models.get(0) instanceof ClassEnumModel) { + return null; + } + } + final StringBuilder out = new StringBuilder(); + out.append("export const Zod"); + out.append(ModelName); + out.append(" = "); + if (models.size() == 1) { + out.append(generateLocalModelBase(models.get(0), tsGroup)); + out.append(";"); + } else { + out.append("z.union([\n"); + for (final ClassModel model : models) { + out.append("\t"); + out.append(generateLocalModelBase(models.get(0), tsGroup)); + out.append(",\n"); + } + out.append("]);"); + } + //model.getDependencyModels() + out.append(generateZodInfer(ModelName, "Zod" + ModelName)); + out.append(generateExportCheckFunction("is" + ModelName, ModelName, "Zod" + ModelName)); + return out.toString(); + } + } \ No newline at end of file diff --git a/src/org/kar/archidata/externalRestApi/TsGenerateApi.java b/src/org/kar/archidata/externalRestApi/TsGenerateApi.java index fcd7ec6..94dd5a0 100644 --- a/src/org/kar/archidata/externalRestApi/TsGenerateApi.java +++ b/src/org/kar/archidata/externalRestApi/TsGenerateApi.java @@ -11,34 +11,21 @@ import java.time.Instant; import java.time.LocalDate; import java.time.LocalTime; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.UUID; +import org.kar.archidata.catcher.RestErrorResponse; import org.kar.archidata.dataAccess.DataFactoryTsApi; import org.kar.archidata.externalRestApi.TsClassElement.DefinedPosition; import org.kar.archidata.externalRestApi.model.ApiGroupModel; import org.kar.archidata.externalRestApi.model.ClassModel; public class TsGenerateApi { - - public static List getCompatibleModels( - final List requestedModel, - final List> search) { - final List out = new ArrayList<>(); - for (final ClassModel model : requestedModel) { - if (search.contains(model.getOriginClasses())) { - out.add(model); - } - } - if (out.isEmpty()) { - return null; - } - return out; - } - - public static void generateApi(final AnalyzeApi api, final String pathPackage) throws IOException { + + public static void generateApi(final AnalyzeApi api, final String pathPackage) throws Exception { final List localModel = generateApiModel(api); final TsClassElementGroup tsGroup = new TsClassElementGroup(localModel); // Generates all MODEL files @@ -47,16 +34,31 @@ public class TsGenerateApi { } // Generate index of model files createModelIndex(pathPackage, tsGroup); - + for (final ApiGroupModel element : api.apiModels) { TsApiGeneration.generateApiFile(element, pathPackage, tsGroup); } // Generate index of model files createResourceIndex(pathPackage, api.apiModels); - + createIndex(pathPackage); copyResourceFile("rest-tools.ts", pathPackage + File.separator + "rest-tools.ts"); } + private static void createIndex(final String pathPackage) throws IOException { + final String out = """ + /** + * Interface of the server (auto-generated code) + */ + export * from \"./model\"; + export * from \"./api\"; + export * from \"./rest-tools\"; + + """; + final FileWriter myWriter = new FileWriter(pathPackage + File.separator + "index.ts"); + myWriter.write(out); + myWriter.close(); + } + private static void createResourceIndex(final String pathPackage, final List apiModels) throws IOException { final StringBuilder out = new StringBuilder(""" @@ -64,17 +66,21 @@ public class TsGenerateApi { * Interface of the server (auto-generated code) */ """); + final List files = new ArrayList<>(); for (final ApiGroupModel elem : apiModels) { - final String fileName = TsClassElement.determineFileName(elem.name); + files.add(TsClassElement.determineFileName(elem.name)); + } + Collections.sort(files); + for (final String elem : files) { out.append("export * from \"./"); - out.append(fileName); + out.append(elem); out.append("\"\n"); } final FileWriter myWriter = new FileWriter(pathPackage + File.separator + "api" + File.separator + "index.ts"); myWriter.write(out.toString()); myWriter.close(); } - + private static void createModelIndex(final String pathPackage, final TsClassElementGroup tsGroup) throws IOException { final StringBuilder out = new StringBuilder(""" @@ -82,12 +88,17 @@ public class TsGenerateApi { * Interface of the server (auto-generated code) */ """); + final List files = new ArrayList<>(); for (final TsClassElement elem : tsGroup.getTsElements()) { if (elem.nativeType == DefinedPosition.NATIVE) { continue; } + files.add(elem.fileName); + } + Collections.sort(files); + for (final String elem : files) { out.append("export * from \"./"); - out.append(elem.fileName); + out.append(elem); out.append("\"\n"); } final FileWriter myWriter = new FileWriter( @@ -95,95 +106,97 @@ public class TsGenerateApi { myWriter.write(out.toString()); myWriter.close(); } - - private static List generateApiModel(final AnalyzeApi api) { + + private static List generateApiModel(final AnalyzeApi api) throws Exception { // First step is to add all specific basic elements the wrap correctly the model final List tsModels = new ArrayList<>(); - List models = getCompatibleModels(api.classModels, List.of(Void.class, void.class)); + List models = api.getCompatibleModels(List.of(Void.class, void.class)); if (models != null) { tsModels.add(new TsClassElement(models, "void", "void", null, null, DefinedPosition.NATIVE)); } - models = getCompatibleModels(api.classModels, List.of(Object.class)); + models = api.getCompatibleModels(List.of(Object.class)); if (models != null) { tsModels.add( new TsClassElement(models, "zod.object()", "object", null, "zod.object()", DefinedPosition.NATIVE)); } // Map is binded to any ==> can not determine this complex model for now - models = getCompatibleModels(api.classModels, List.of(Map.class)); + models = api.getCompatibleModels(List.of(Map.class)); if (models != null) { tsModels.add(new TsClassElement(models, "zod.any()", "any", null, null, DefinedPosition.NATIVE)); } - models = getCompatibleModels(api.classModels, List.of(String.class)); + models = api.getCompatibleModels(List.of(String.class)); if (models != null) { tsModels.add( new TsClassElement(models, "zod.string()", "string", null, "zod.string()", DefinedPosition.NATIVE)); } - models = getCompatibleModels(api.classModels, List.of(InputStream.class)); + models = api.getCompatibleModels(List.of(InputStream.class)); if (models != null) { tsModels.add(new TsClassElement(models, "z.instanceof(File)", "File", null, "z.instanceof(File)", DefinedPosition.NATIVE)); } - models = getCompatibleModels(api.classModels, List.of(Boolean.class, boolean.class)); + models = api.getCompatibleModels(List.of(Boolean.class, boolean.class)); if (models != null) { tsModels.add(new TsClassElement(models, "zod.boolean()", "boolean", null, "zod.boolean()", DefinedPosition.NATIVE)); } - models = getCompatibleModels(api.classModels, List.of(UUID.class)); + models = api.getCompatibleModels(List.of(UUID.class)); if (models != null) { tsModels.add(new TsClassElement(models, "ZodUUID", "UUID", "isUUID", "zod.string().uuid()", DefinedPosition.BASIC)); } - models = getCompatibleModels(api.classModels, List.of(Long.class, long.class)); + models = api.getCompatibleModels(List.of(Long.class, long.class)); if (models != null) { tsModels.add( new TsClassElement(models, "ZodLong", "Long", "isLong", "zod.number()", DefinedPosition.BASIC)); } - models = getCompatibleModels(api.classModels, List.of(Short.class, short.class)); + models = api.getCompatibleModels(List.of(Short.class, short.class)); if (models != null) { tsModels.add(new TsClassElement(models, "ZodShort", "Short", "isShort", "zod.number().safe()", DefinedPosition.BASIC)); } - models = getCompatibleModels(api.classModels, List.of(Integer.class, int.class)); + models = api.getCompatibleModels(List.of(Integer.class, int.class)); if (models != null) { tsModels.add(new TsClassElement(models, "ZodInteger", "Integer", "isInteger", "zod.number().safe()", DefinedPosition.BASIC)); } - models = getCompatibleModels(api.classModels, List.of(Double.class, double.class)); + models = api.getCompatibleModels(List.of(Double.class, double.class)); if (models != null) { tsModels.add(new TsClassElement(models, "ZodDouble", "Double", "isDouble", "zod.number()", DefinedPosition.BASIC)); } - models = getCompatibleModels(api.classModels, List.of(Float.class, float.class)); + models = api.getCompatibleModels(List.of(Float.class, float.class)); if (models != null) { tsModels.add( new TsClassElement(models, "ZodFloat", "Float", "isFloat", "zod.number()", DefinedPosition.BASIC)); } - models = getCompatibleModels(api.classModels, List.of(Instant.class)); + models = api.getCompatibleModels(List.of(Instant.class)); if (models != null) { tsModels.add(new TsClassElement(models, "ZodInstant", "Instant", "isInstant", "zod.string()", DefinedPosition.BASIC)); } - models = getCompatibleModels(api.classModels, List.of(Date.class)); + models = api.getCompatibleModels(List.of(Date.class)); if (models != null) { tsModels.add(new TsClassElement(models, "ZodIsoDate", "IsoDate", "isIsoDate", "zod.string().datetime({ precision: 3 })", DefinedPosition.BASIC)); } - models = getCompatibleModels(api.classModels, List.of(Timestamp.class)); + models = api.getCompatibleModels(List.of(Timestamp.class)); if (models != null) { tsModels.add(new TsClassElement(models, "ZodTimestamp", "Timestamp", "isTimestamp", "zod.string().datetime({ precision: 3 })", DefinedPosition.BASIC)); } - models = getCompatibleModels(api.classModels, List.of(LocalDate.class)); + models = api.getCompatibleModels(List.of(LocalDate.class)); if (models != null) { tsModels.add(new TsClassElement(models, "ZodLocalDate", "LocalDate", "isLocalDate", "zod.string().date()", DefinedPosition.BASIC)); } - models = getCompatibleModels(api.classModels, List.of(LocalTime.class)); + models = api.getCompatibleModels(List.of(LocalTime.class)); if (models != null) { tsModels.add(new TsClassElement(models, "ZodLocalTime", "LocalTime", "isLocalTime", "zod.string().time()", DefinedPosition.BASIC)); } - for (final ClassModel model : api.classModels) { + // needed for Rest interface + api.addModel(RestErrorResponse.class); + for (final ClassModel model : api.getAllModel()) { boolean alreadyExist = false; for (final TsClassElement elem : tsModels) { if (elem.isCompatible(model)) { @@ -197,9 +210,9 @@ public class TsGenerateApi { tsModels.add(new TsClassElement(model)); } return tsModels; - + } - + public static void copyResourceFile(final String name, final String destinationPath) throws IOException { final InputStream ioStream = DataFactoryTsApi.class.getClassLoader().getResourceAsStream(name); if (ioStream == null) { diff --git a/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java b/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java index c15640f..2e5e4d5 100644 --- a/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java +++ b/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java @@ -6,11 +6,11 @@ import java.util.List; import java.util.Set; public class ClassEnumModel extends ClassModel { - + protected ClassEnumModel(final Class clazz) { this.originClasses = clazz; } - + @Override public String toString() { final StringBuilder out = new StringBuilder(); @@ -19,25 +19,35 @@ public class ClassEnumModel extends ClassModel { out.append("]"); return out.toString(); } - + final List listOfValues = new ArrayList<>(); - + @Override public void analyze(final ModelGroup group) throws IOException { + if (this.analyzeDone) { + return; + } + this.analyzeDone = true; // TODO: check if we really need to have multiple type for enums ??? + // TODO: manage enum with int, String and bitField ... final Class clazz = this.originClasses; final Object[] arr = clazz.getEnumConstants(); for (final Object elem : arr) { this.listOfValues.add(elem.toString()); } } - + public List getListOfValues() { return this.listOfValues; } - + @Override public Set getAlls() { return Set.of(this); } + + @Override + public Set getDependencyGroupModels() { + return Set.of(this); + } } diff --git a/src/org/kar/archidata/externalRestApi/model/ClassListModel.java b/src/org/kar/archidata/externalRestApi/model/ClassListModel.java index 47991b7..6032665 100644 --- a/src/org/kar/archidata/externalRestApi/model/ClassListModel.java +++ b/src/org/kar/archidata/externalRestApi/model/ClassListModel.java @@ -7,36 +7,41 @@ import java.util.Set; public class ClassListModel extends ClassModel { public ClassModel valueModel; - + public ClassListModel(final ClassModel valueModel) { this.valueModel = valueModel; } - + public ClassListModel(final Class clazz, final ModelGroup previousModel) throws IOException { this.valueModel = getModel(clazz, previousModel); } - + public ClassListModel(final Type model, final ModelGroup previousModel) throws IOException { this.valueModel = getModel(model, previousModel); } - + public ClassListModel(final ParameterizedType listType, final ModelGroup previousModel) throws IOException { final Type model = listType.getActualTypeArguments()[0]; this.valueModel = getModel(model, previousModel); } - + @Override public String toString() { return "ClassListModel [valueModel=" + this.valueModel + "]"; } - + @Override public void analyze(final ModelGroup group) throws IOException { throw new IOException("Analyze can not be done at this phase for List..."); } - + @Override public Set getAlls() { return this.valueModel.getAlls(); } + + @Override + public Set getDependencyGroupModels() { + return this.valueModel.getDependencyGroupModels(); + } } diff --git a/src/org/kar/archidata/externalRestApi/model/ClassMapModel.java b/src/org/kar/archidata/externalRestApi/model/ClassMapModel.java index a6fe7a8..b57231a 100644 --- a/src/org/kar/archidata/externalRestApi/model/ClassMapModel.java +++ b/src/org/kar/archidata/externalRestApi/model/ClassMapModel.java @@ -9,37 +9,44 @@ import java.util.Set; public class ClassMapModel extends ClassModel { public ClassModel keyModel; public ClassModel valueModel; - + public ClassMapModel(final ClassModel keyModel, final ClassModel valueModel) { this.keyModel = keyModel; this.valueModel = valueModel; } - + public ClassMapModel(final Type listTypeKey, final Type listTypeValue, final ModelGroup previousModel) throws IOException { this.keyModel = getModel(listTypeKey, previousModel); this.valueModel = getModel(listTypeValue, previousModel); } - + public ClassMapModel(final ParameterizedType listType, final ModelGroup previousModel) throws IOException { this.keyModel = getModel(listType.getActualTypeArguments()[0], previousModel); this.valueModel = getModel(listType.getActualTypeArguments()[1], previousModel); } - + @Override public String toString() { return "ClassMapModel [keyModel=" + this.keyModel + ", valueModel=" + this.valueModel + "]"; } - + @Override public void analyze(final ModelGroup group) throws IOException { throw new IOException("Analyze can not be done at this phase for Map..."); } - + @Override public Set getAlls() { final Set out = new HashSet<>(this.keyModel.getAlls()); out.addAll(this.valueModel.getAlls()); return out; } + + @Override + public Set getDependencyGroupModels() { + final Set out = new HashSet<>(this.valueModel.getDependencyGroupModels()); + out.addAll(this.keyModel.getDependencyGroupModels()); + return out; + } } diff --git a/src/org/kar/archidata/externalRestApi/model/ClassModel.java b/src/org/kar/archidata/externalRestApi/model/ClassModel.java index b2f2620..26b0c1d 100644 --- a/src/org/kar/archidata/externalRestApi/model/ClassModel.java +++ b/src/org/kar/archidata/externalRestApi/model/ClassModel.java @@ -9,21 +9,24 @@ import java.util.Map; import java.util.Set; public abstract class ClassModel { + protected boolean analyzeDone = false; protected Class originClasses = null; protected List dependencyModels = new ArrayList<>(); - + public Class getOriginClasses() { return this.originClasses; } - + protected boolean isCompatible(final Class clazz) { return this.originClasses == clazz; } - + public List getDependencyModels() { return this.dependencyModels; } + public abstract Set getDependencyGroupModels(); + public static ClassModel getModel(final Type type, final ModelGroup previousModel) throws IOException { if (type instanceof final ParameterizedType paramType) { final Type[] typeArguments = paramType.getActualTypeArguments(); @@ -37,7 +40,7 @@ public abstract class ClassModel { } return previousModel.add((Class) type); } - + public static ClassModel getModelBase( final Class clazz, final Type parameterizedType, @@ -53,7 +56,7 @@ public abstract class ClassModel { */ return getModel(parameterizedType, previousModel); } - + public static ClassModel getModel(final Class type, final ModelGroup previousModel) throws IOException { if (type == List.class) { throw new IOException("Fail to manage parametrized type..."); @@ -63,13 +66,13 @@ public abstract class ClassModel { } return previousModel.add(type); } - - public abstract void analyze(final ModelGroup group) throws Exception; - - public abstract Set getAlls(); + public abstract void analyze(final ModelGroup group) throws Exception; + + public abstract Set getAlls(); + public List getReadOnlyField() { return List.of(); } - + } diff --git a/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java b/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java index 6156c7e..fb7f344 100644 --- a/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java +++ b/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java @@ -15,11 +15,11 @@ import org.slf4j.LoggerFactory; public class ClassObjectModel extends ClassModel { static final Logger LOGGER = LoggerFactory.getLogger(ClassObjectModel.class); - + public ClassObjectModel(final Class clazz) { this.originClasses = clazz; } - + @Override public String toString() { final StringBuilder out = new StringBuilder(); @@ -28,7 +28,7 @@ public class ClassObjectModel extends ClassModel { out.append("]"); return out.toString(); } - + private static boolean isFieldFromSuperClass(final Class model, final String filedName) { final Class superClass = model.getSuperclass(); if (superClass == null) { @@ -48,66 +48,77 @@ public class ClassObjectModel extends ClassModel { } return false; } - + public record FieldProperty( String name, ClassModel model, String comment, int limitSize, - boolean readOnly) { - + boolean readOnly, + boolean notNull, + boolean nullable) { + public FieldProperty(final String name, final ClassModel model, final String comment, final int limitSize, - final boolean readOnly) { + final boolean readOnly, final boolean notNull, final boolean nullable) { this.name = name; this.model = model; this.comment = comment; this.limitSize = limitSize; this.readOnly = readOnly; + this.notNull = notNull; + this.nullable = nullable; + } - + public FieldProperty(final Field field, final ModelGroup previous) throws Exception { this(field.getName(), // ClassModel.getModel(field.getGenericType(), previous), // AnnotationTools.getComment(field), // AnnotationTools.getLimitSize(field), // - AnnotationTools.getSchemaReadOnly(field)); + AnnotationTools.getSchemaReadOnly(field), // + AnnotationTools.getConstraintsNotNull(field), // + AnnotationTools.getColumnNotNull(field)); } - + } - + String name = ""; boolean isPrimitive = false; String description = null; String example = null; ClassModel extendsClass = null; List fields = new ArrayList<>(); - + public String getName() { return this.name; } - + public boolean isPrimitive() { return this.isPrimitive; } - + public String getDescription() { return this.description; } - + public String getExample() { return this.example; } - + public ClassModel getExtendsClass() { return this.extendsClass; } - + public List getFields() { return this.fields; } - + @Override public void analyze(final ModelGroup previous) throws Exception { + if (this.analyzeDone) { + return; + } + this.analyzeDone = true; final Class clazz = this.originClasses; this.isPrimitive = clazz.isPrimitive(); if (this.isPrimitive) { @@ -119,7 +130,7 @@ public class ClassObjectModel extends ClassModel { if (basicClass.contains(clazz)) { return; } - + // Local generation of class: LOGGER.trace("parse class: '{}'", clazz.getCanonicalName()); final List alreadyAdded = new ArrayList<>(); @@ -148,7 +159,7 @@ public class ClassObjectModel extends ClassModel { this.fields.add(new FieldProperty(elem, previous)); } this.name = clazz.getName(); - + final String[] elems = this.name.split("\\$"); if (elems.length == 2) { LOGGER.warn("Can have conflict in generation: {} (Remove class path) ==> {}", this.name, elems[1]); @@ -163,12 +174,17 @@ public class ClassObjectModel extends ClassModel { this.dependencyModels.add(this.extendsClass); } } - + + @Override + public Set getDependencyGroupModels() { + return Set.of(this); + } + @Override public Set getAlls() { return Set.of(this); } - + @Override public List getReadOnlyField() { final List out = new ArrayList<>(); @@ -182,5 +198,5 @@ public class ClassObjectModel extends ClassModel { } return out; } - + } diff --git a/src/org/kar/archidata/externalRestApi/model/ModelGroup.java b/src/org/kar/archidata/externalRestApi/model/ModelGroup.java index ef78118..8ad6e08 100644 --- a/src/org/kar/archidata/externalRestApi/model/ModelGroup.java +++ b/src/org/kar/archidata/externalRestApi/model/ModelGroup.java @@ -10,12 +10,14 @@ import jakarta.ws.rs.core.Response; public class ModelGroup { static final Logger LOGGER = LoggerFactory.getLogger(ModelGroup.class); - public List previousModel = new ArrayList<>(); - + public List models = new ArrayList<>(); + public ModelGroup() {} - public ModelGroup(final List models) { - this.previousModel = models; + public void addAll(final List> classes) { + for (final Class clazz : classes) { + add(clazz); + } } public ClassModel add(Class clazz) { @@ -26,7 +28,7 @@ public class ModelGroup { return null; } //LOGGER.trace("Search element {}", clazz.getCanonicalName()); - for (final ClassModel value : this.previousModel) { + for (final ClassModel value : this.models) { if (value.isCompatible(clazz)) { //LOGGER.trace(" ==> return {}", value); return value; @@ -34,14 +36,18 @@ public class ModelGroup { } if (clazz.isEnum()) { final ClassModel elem = new ClassEnumModel(clazz); - this.previousModel.add(elem); + this.models.add(elem); //LOGGER.trace(" ==> return enum {}", elem); return elem; } // create new model: final ClassModel elem = new ClassObjectModel(clazz); - this.previousModel.add(elem); + this.models.add(elem); //LOGGER.trace(" ==> return object {}", elem); return elem; } + + public List getModels() { + return this.models; + } } diff --git a/src/org/kar/archidata/model/GenericDataSoftDelete.java b/src/org/kar/archidata/model/GenericDataSoftDelete.java index adfbe13..3ad202b 100644 --- a/src/org/kar/archidata/model/GenericDataSoftDelete.java +++ b/src/org/kar/archidata/model/GenericDataSoftDelete.java @@ -5,7 +5,6 @@ import org.kar.archidata.annotation.DataNotRead; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.Column; -import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.DefaultValue; public class GenericDataSoftDelete extends GenericData { @@ -13,7 +12,6 @@ public class GenericDataSoftDelete extends GenericData { @Column(nullable = false) @DefaultValue("'0'") @DataDeleted - @NotNull @Schema(description = "Deleted state", hidden = true, required = false, readOnly = true) public Boolean deleted = null; } diff --git a/src/org/kar/archidata/model/GenericTiming.java b/src/org/kar/archidata/model/GenericTiming.java index 0bbcd60..7c045e3 100644 --- a/src/org/kar/archidata/model/GenericTiming.java +++ b/src/org/kar/archidata/model/GenericTiming.java @@ -10,20 +10,17 @@ import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.Column; -import jakarta.validation.constraints.NotNull; public class GenericTiming { @DataNotRead @CreationTimestamp @Column(nullable = false) - @NotNull @Schema(description = "Create time of the object", required = false, example = "2000-01-23T01:23:45.678+01:00", readOnly = true) @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") public Date createdAt = null; @DataNotRead @UpdateTimestamp @Column(nullable = false) - @NotNull @Schema(description = "When update the object", required = false, example = "2000-01-23T00:23:45.678Z", readOnly = true) @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") // public Instant updatedAt = null; diff --git a/src/org/kar/archidata/model/GetToken.java b/src/org/kar/archidata/model/GetToken.java index 78610c1..d70cb76 100644 --- a/src/org/kar/archidata/model/GetToken.java +++ b/src/org/kar/archidata/model/GetToken.java @@ -8,13 +8,13 @@ import jakarta.persistence.Column; public class GetToken { @Column(length = -1, nullable = false) public String jwt; - + public GetToken() { - + } - + public GetToken(final String jwt) { this.jwt = jwt; } - + } diff --git a/src/org/kar/archidata/model/Token.java b/src/org/kar/archidata/model/Token.java index b0b6465..dff30d7 100644 --- a/src/org/kar/archidata/model/Token.java +++ b/src/org/kar/archidata/model/Token.java @@ -9,9 +9,9 @@ public class Token { public String token; public String createTime; public String endValidityTime; - + public Token() {} - + public Token(final long id, final long userId, final String token, final String createTime, final String endValidityTime) { this.id = id; @@ -20,7 +20,7 @@ public class Token { this.createTime = createTime; this.endValidityTime = endValidityTime; } - + public Token(final ResultSet rs) { int iii = 1; try { @@ -33,7 +33,7 @@ public class Token { ex.printStackTrace(); } } - + @Override public String toString() { return "Token{" + "id=" + this.id + ", userId=" + this.userId + ", token='" + this.token + '\'' diff --git a/src/org/kar/archidata/model/UUIDGenericData.java b/src/org/kar/archidata/model/UUIDGenericData.java index 3578d5e..57388d8 100644 --- a/src/org/kar/archidata/model/UUIDGenericData.java +++ b/src/org/kar/archidata/model/UUIDGenericData.java @@ -5,6 +5,7 @@ import java.util.UUID; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.Column; import jakarta.persistence.Id; +import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.DefaultValue; public class UUIDGenericData extends GenericTiming { @@ -12,5 +13,6 @@ public class UUIDGenericData extends GenericTiming { @DefaultValue("(UUID_TO_BIN(UUID(), TRUE))") @Column(nullable = false, unique = true) @Schema(description = "Unique UUID of the object", required = false, readOnly = true, example = "e6b33c1c-d24d-11ee-b616-02420a030102") + @NotNull public UUID uuid = null; } diff --git a/src/org/kar/archidata/model/UUIDGenericDataSoftDelete.java b/src/org/kar/archidata/model/UUIDGenericDataSoftDelete.java index 29c8b5f..d06cb65 100644 --- a/src/org/kar/archidata/model/UUIDGenericDataSoftDelete.java +++ b/src/org/kar/archidata/model/UUIDGenericDataSoftDelete.java @@ -5,7 +5,6 @@ import org.kar.archidata.annotation.DataNotRead; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.Column; -import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.DefaultValue; public class UUIDGenericDataSoftDelete extends UUIDGenericData { @@ -13,7 +12,6 @@ public class UUIDGenericDataSoftDelete extends UUIDGenericData { @Column(nullable = false) @DefaultValue("'0'") @DataDeleted - @NotNull @Schema(description = "Deleted state", hidden = true, required = false, readOnly = true) public Boolean deleted = null; } diff --git a/src/resources/rest-tools.ts b/src/resources/rest-tools.ts index 836dc44..4740e67 100644 --- a/src/resources/rest-tools.ts +++ b/src/resources/rest-tools.ts @@ -50,23 +50,6 @@ export interface ModelResponseHttp { data: any; } -export function isArrayOf( - data: any, - typeChecker: (subData: any) => subData is TYPE, - length?: number -): data is TYPE[] { - if (!Array.isArray(data)) { - return false; - } - if (!data.every(typeChecker)) { - return false; - } - if (length !== undefined && data.length != length) { - return false; - } - return true; -} - function isNullOrUndefined(data: any): data is undefined | null { return data === undefined || data === null; } @@ -325,8 +308,6 @@ export function RESTRequest({ restModel, restConfig, data, params, queries, call }); } - - export function RESTRequestJson(request: RESTRequestType, checker: (data: any) => data is TYPE): Promise { return new Promise((resolve, reject) => { RESTRequest(request).then((value: ModelResponseHttp) => { @@ -349,25 +330,6 @@ export function RESTRequestJson(request: RESTRequestType, checker: (data: }); }); } -export function RESTRequestJsonArray(request: RESTRequestType, checker: (data: any) => data is TYPE): Promise { - return new Promise((resolve, reject) => { - RESTRequest(request).then((value: ModelResponseHttp) => { - if (isArrayOf(value.data, checker)) { - resolve(value.data); - } else { - reject({ - time: Date().toString(), - status: 950, - error: "REST Fail to verify the data", - statusMessage: "API cast ERROR", - message: "api.ts Check type as fail" - } as RestErrorResponse); - } - }).catch((reason: RestErrorResponse) => { - reject(reason); - }); - }); -} export function RESTRequestVoid(request: RESTRequestType): Promise { return new Promise((resolve, reject) => { diff --git a/src/resources/zod-tools.ts b/src/resources/zod-tools.ts deleted file mode 100644 index d92f79c..0000000 --- a/src/resources/zod-tools.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** @file - * @author Edouard DUPIN - * @copyright 2024, Edouard DUPIN, all right reserved - * @license MPL-2 - */ - -import { z as zod, ZodTypeAny, ZodObject } from 'zod'; - -export function removeReadonly(schema: T): T { - if (schema instanceof ZodObject) { - const shape: Record = {}; - for (const key in schema.shape) { - const field = schema.shape[key]; - shape[key] = field._def.typeName === 'ZodReadonly' - ? field._def.innerType - : removeReadonly(field); - } - return zod.object(shape) as T; - } - return schema; -} \ No newline at end of file diff --git a/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiName.java b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiName.java index d8fc840..08f836f 100644 --- a/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiName.java +++ b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiName.java @@ -29,7 +29,7 @@ public class TestAnalyzeApiName { @Test public void testNames() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(ApiName.class)); + api.addAllApi(List.of(ApiName.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals("ApiName", api.apiModels.get(0).name); diff --git a/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiParameterParamQuery.java b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiParameterParamQuery.java new file mode 100644 index 0000000..a48b860 --- /dev/null +++ b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiParameterParamQuery.java @@ -0,0 +1,17 @@ +package test.kar.archidata.externalRestApi; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TestAnalyzeApiParameterParamQuery { + final static private Logger LOGGER = LoggerFactory.getLogger(TestAnalyzeApiParameterParamQuery.class); + + @Test + public void testNotImplemented() throws Exception { + Assertions.assertEquals(1, 0); + + } + +} diff --git a/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiParameterType.java b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiParameterType.java index bf759ac..4b05567 100644 --- a/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiParameterType.java +++ b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiParameterType.java @@ -47,7 +47,7 @@ public class TestAnalyzeApiParameterType { @Test public void testBasicParameter() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(BasicParameter.class)); + api.addAllApi(List.of(BasicParameter.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(5, api.apiModels.get(0).interfaces.size()); @@ -102,7 +102,7 @@ public class TestAnalyzeApiParameterType { @Test public void testListParameter() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(ListParameter.class)); + api.addAllApi(List.of(ListParameter.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(1, api.apiModels.get(0).interfaces.size()); @@ -125,7 +125,7 @@ public class TestAnalyzeApiParameterType { @Test public void testMapParameter() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(MapParameter.class)); + api.addAllApi(List.of(MapParameter.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(1, api.apiModels.get(0).interfaces.size()); diff --git a/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiParameterTypeAsync.java b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiParameterTypeAsync.java new file mode 100644 index 0000000..8104e0c --- /dev/null +++ b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiParameterTypeAsync.java @@ -0,0 +1,17 @@ +package test.kar.archidata.externalRestApi; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TestAnalyzeApiParameterTypeAsync { + final static private Logger LOGGER = LoggerFactory.getLogger(TestAnalyzeApiParameterTypeAsync.class); + + @Test + public void testNotImplemented() throws Exception { + Assertions.assertEquals(1, 0); + + } + +} diff --git a/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiPath.java b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiPath.java index 7b931ce..1f8c8c4 100644 --- a/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiPath.java +++ b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiPath.java @@ -37,7 +37,7 @@ public class TestAnalyzeApiPath { @Test public void testNoPath() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(NoPath.class)); + api.addAllApi(List.of(NoPath.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals("", api.apiModels.get(0).restEndPoint); @@ -82,7 +82,7 @@ public class TestAnalyzeApiPath { @Test public void testWithPath() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(WithPath.class)); + api.addAllApi(List.of(WithPath.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals("/kaboom", api.apiModels.get(0).restEndPoint); diff --git a/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiReturn.java b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiReturn.java index 38cad58..2aecdd4 100644 --- a/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiReturn.java +++ b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiReturn.java @@ -41,7 +41,7 @@ public class TestAnalyzeApiReturn { @Test public void testReturnVoid() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(ReturnValueVoid.class)); + api.addAllApi(List.of(ReturnValueVoid.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(2, api.apiModels.get(0).interfaces.size()); @@ -80,7 +80,7 @@ public class TestAnalyzeApiReturn { @Test public void testReturnInteger() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(ReturnValueInteger.class)); + api.addAllApi(List.of(ReturnValueInteger.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(2, api.apiModels.get(0).interfaces.size()); @@ -121,7 +121,7 @@ public class TestAnalyzeApiReturn { @Test public void testReturnShort() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(ReturnValueShort.class)); + api.addAllApi(List.of(ReturnValueShort.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(2, api.apiModels.get(0).interfaces.size()); @@ -162,7 +162,7 @@ public class TestAnalyzeApiReturn { @Test public void testReturnLong() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(ReturnValueLong.class)); + api.addAllApi(List.of(ReturnValueLong.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(2, api.apiModels.get(0).interfaces.size()); @@ -204,7 +204,7 @@ public class TestAnalyzeApiReturn { @Test public void testReturnFloat() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(ReturnValueFloat.class)); + api.addAllApi(List.of(ReturnValueFloat.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(2, api.apiModels.get(0).interfaces.size()); @@ -245,7 +245,7 @@ public class TestAnalyzeApiReturn { @Test public void testReturnDouble() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(ReturnValueDouble.class)); + api.addAllApi(List.of(ReturnValueDouble.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(2, api.apiModels.get(0).interfaces.size()); @@ -281,7 +281,7 @@ public class TestAnalyzeApiReturn { @Test public void testReturnString() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(ReturnValueString.class)); + api.addAllApi(List.of(ReturnValueString.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(1, api.apiModels.get(0).interfaces.size()); @@ -312,7 +312,7 @@ public class TestAnalyzeApiReturn { @Test public void testReturnAny() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(ReturnValueAny.class)); + api.addAllApi(List.of(ReturnValueAny.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(2, api.apiModels.get(0).interfaces.size()); @@ -348,7 +348,7 @@ public class TestAnalyzeApiReturn { @Test public void testReturnEnum() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(ReturnValueEnum.class)); + api.addAllApi(List.of(ReturnValueEnum.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(1, api.apiModels.get(0).interfaces.size()); @@ -394,7 +394,7 @@ public class TestAnalyzeApiReturn { @Test public void testReturnList() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(ReturnValueList.class)); + api.addAllApi(List.of(ReturnValueList.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(5, api.apiModels.get(0).interfaces.size()); @@ -507,7 +507,7 @@ public class TestAnalyzeApiReturn { @Test public void testReturnMap() throws Exception { final AnalyzeApi api = new AnalyzeApi(); - api.createApi(List.of(ReturnValueMap.class)); + api.addAllApi(List.of(ReturnValueMap.class)); Assertions.assertEquals(1, api.apiModels.size()); Assertions.assertEquals(5, api.apiModels.get(0).interfaces.size()); diff --git a/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiReturnAsync.java b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiReturnAsync.java new file mode 100644 index 0000000..20b7354 --- /dev/null +++ b/test/src/test/kar/archidata/externalRestApi/TestAnalyzeApiReturnAsync.java @@ -0,0 +1,17 @@ +package test.kar.archidata.externalRestApi; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TestAnalyzeApiReturnAsync { + final static private Logger LOGGER = LoggerFactory.getLogger(TestAnalyzeApiReturnAsync.class); + + @Test + public void testNotImplemented() throws Exception { + Assertions.assertEquals(1, 0); + + } + +}