diff --git a/src/org/kar/archidata/dataAccess/DataFactoryTsApi.java b/src/org/kar/archidata/dataAccess/DataFactoryTsApi.java index 1f70e16..d710f00 100644 --- a/src/org/kar/archidata/dataAccess/DataFactoryTsApi.java +++ b/src/org/kar/archidata/dataAccess/DataFactoryTsApi.java @@ -3,28 +3,22 @@ package org.kar.archidata.dataAccess; import java.io.File; import java.io.FileWriter; import java.lang.annotation.Annotation; -import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.lang.reflect.ParameterizedType; -import java.sql.Timestamp; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalTime; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.UUID; -import org.kar.archidata.annotation.AnnotationTools; import org.kar.archidata.annotation.AsyncType; -import org.kar.archidata.exception.DataAccessException; +import org.kar.archidata.catcher.RestErrorResponse; +import org.kar.archidata.dataAccess.DataFactoryZod.ClassElement; +import org.kar.archidata.dataAccess.DataFactoryZod.GeneratedTypes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +31,7 @@ import jakarta.ws.rs.POST; import jakarta.ws.rs.PUT; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; @@ -44,143 +39,46 @@ import jakarta.ws.rs.core.MediaType; public class DataFactoryTsApi { static final Logger LOGGER = LoggerFactory.getLogger(DataFactoryTsApi.class); - public static String convertTypeZodSimpleType(final Class type, final Map previousClassesGenerated, final List order) throws Exception { - if (type == UUID.class) { - return "string"; - } - if (type == Long.class) { - return "Bigint"; - } - if (type == long.class) { - return "Bigint"; - } - if (type == Integer.class || type == int.class) { - return "number"; - } - if (type == Boolean.class || type == boolean.class) { - return "boolean"; - } - if (type == double.class || type == float.class || type == Double.class || type == Float.class) { - return "number"; - } - if (type == Instant.class) { - return "string"; - } - if (type == Date.class || type == Timestamp.class) { - return "string"; - } - if (type == LocalDate.class) { - return "string"; - } - if (type == LocalTime.class) { - return "string"; - } - if (type == String.class) { - return "string"; - } - if (type.isEnum()) { - final Object[] arr = type.getEnumConstants(); - final StringBuilder out = new StringBuilder(); - boolean first = true; - out.append("zod.enum(["); - for (final Object elem : arr) { - if (!first) { - out.append(", "); - } - first = false; - out.append("\""); - out.append(elem.toString()); - out.append("\""); - } - out.append("])"); - return out.toString(); - } - if (type == List.class) { - return null; - } - // createTable(type, previousClassesGenerated, order); - return "Zod" + type.getSimpleName(); - } - - public static String convertTypeZod(final Field field, final Map previousClassesGenerated, final List order) throws Exception { - final Class type = field.getType(); - final String simpleType = convertTypeZodSimpleType(type, previousClassesGenerated, order); - if (simpleType != null) { - return simpleType; - } - if (type == List.class) { - final ParameterizedType listType = (ParameterizedType) field.getGenericType(); - final Class listClass = (Class) listType.getActualTypeArguments()[0]; - final String simpleSubType = convertTypeZodSimpleType(listClass, previousClassesGenerated, order); - return "zod.array(" + simpleSubType + ")"; - } - throw new DataAccessException("Imcompatible type of element in object for: " + type.getCanonicalName()); - } - - public static String optionalTypeZod(final Class type) throws Exception { - if (type.isPrimitive()) { - return ""; - } - return ".optional()"; - } - - public static void createTablesSpecificType(final Field elem, final int fieldId, final StringBuilder builder, final Map previousClassesGenerated, final List order) - throws Exception { - final String name = elem.getName(); - final Class classModel = elem.getType(); - final int limitSize = AnnotationTools.getLimitSize(elem); - - final String comment = AnnotationTools.getComment(elem); - - if (fieldId != 0) { - builder.append(","); - } - if (comment != null) { - builder.append("\n\t// "); - builder.append(comment); - } - builder.append("\n\t"); - builder.append(name); - builder.append(": "); - builder.append(convertTypeZod(elem, previousClassesGenerated, order)); - if (limitSize > 0 && classModel == String.class) { - builder.append(".max("); - builder.append(limitSize); - builder.append(")"); - } - if (AnnotationTools.getSchemaReadOnly(elem)) { - builder.append(".readonly()"); - } - builder.append(optionalTypeZod(classModel)); + record APIModel(String data, String className) { } /** Request the generation of the TypeScript file for the "Zod" export model * @param classs List of class used in the model - * @return A string representing the Server models * @throws Exception */ - public static String createApi(final List> classs, final Set> classNeeded) throws Exception { + public static List createApi(final List> classs, final GeneratedTypes previous, final String pathPackage) throws Exception { final List apis = new ArrayList<>(); - for (final Class clazz : classs) { - final String api = createSingleApi(clazz, classNeeded); - apis.add(api); - } - final StringBuilder generatedDataElems = new StringBuilder(); - for (final String elem : apis) { - generatedDataElems.append(elem); - generatedDataElems.append("\n\n"); - } - final StringBuilder generatedData = new StringBuilder(); - generatedData.append(""" + final String globalheader = """ /** * API of the server (auto-generated code) */ - import {"""); - for (final Class elem : classNeeded) { - generatedData.append(elem.getSimpleName()); - generatedData.append(", "); + import { HTTPMimeType, HTTPRequestModel, ModelResponseHttp, RESTConfig, RESTRequest, isArrayOf } from "./rest-tools" + import {"""; + + for (final Class clazz : classs) { + final Set includeModel = new HashSet<>(); + includeModel.add("RestErrorResponse"); + final APIModel api = createSingleApi(clazz, includeModel, previous); + final StringBuilder generatedData = new StringBuilder(); + generatedData.append(globalheader); + for (final String elem : includeModel) { + if (elem == null || elem.equals("string") || elem.equals("String")) { + continue; + } + generatedData.append(elem); + generatedData.append(", "); + } + generatedData.append("} from \"./model\"\n"); + generatedData.append(api.data()); + + String fileName = api.className(); + fileName = fileName.replaceAll("([A-Z])", "-$1").toLowerCase(); + fileName = fileName.replaceAll("^\\-*", ""); + apis.add(fileName); + final FileWriter myWriter = new FileWriter(pathPackage + File.separator + fileName + ".ts"); + myWriter.write(generatedData.toString()); + myWriter.close(); } - generatedData.append("} from \"./model.ts\"\n\n"); - return generatedData.toString() + generatedDataElems.toString(); + return apis; } public static String apiAnnotationGetPath(final Class element) throws Exception { @@ -191,6 +89,30 @@ public class DataFactoryTsApi { return ((Path) annotation[0]).value(); } + public static List apiAnnotationProduces(final Class element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(Produces.class); + if (annotation.length == 0) { + return null; + } + return Arrays.asList(((Produces) annotation[0]).value()); + } + + public static List apiAnnotationProduces(final Method element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(Produces.class); + if (annotation.length == 0) { + return null; + } + return Arrays.asList(((Produces) annotation[0]).value()); + } + + public static List apiAnnotationProduces(final Class clazz, final Method method) throws Exception { + final List data = apiAnnotationProduces(method); + if (data != null) { + return data; + } + return apiAnnotationProduces(clazz); + } + public static String apiAnnotationGetOperationDescription(final Method element) throws Exception { final Annotation[] annotation = element.getDeclaredAnnotationsByType(Operation.class); if (annotation.length == 0) { @@ -258,11 +180,27 @@ public class DataFactoryTsApi { return Arrays.asList(((Consumes) annotation[0]).value()); } + public static List apiAnnotationGetConsumes(final Class element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(Consumes.class); + if (annotation.length == 0) { + return null; + } + return Arrays.asList(((Consumes) annotation[0]).value()); + } + + public static List apiAnnotationGetConsumes(final Class clazz, final Method method) throws Exception { + final List data = apiAnnotationGetConsumes(method); + if (data != null) { + return data; + } + return apiAnnotationGetConsumes(clazz); + } + public static boolean apiAnnotationIsContext(final Parameter element) throws Exception { return element.getDeclaredAnnotationsByType(Context.class).length != 0; } - public static String createSingleApi(final Class clazz, final Set> classNeeded) throws Exception { + public static APIModel createSingleApi(final Class clazz, final Set includeModel, final GeneratedTypes previous) throws Exception { final StringBuilder builder = new StringBuilder(); // the basic path has no specific elements... final String basicPath = apiAnnotationGetPath(clazz); @@ -270,59 +208,93 @@ public class DataFactoryTsApi { builder.append("export namespace "); builder.append(classSimpleName); - builder.append("API {\n"); + builder.append(" {\n"); LOGGER.info("Parse Class for path: {} => {}", classSimpleName, basicPath); for (final Method method : clazz.getDeclaredMethods()) { final String methodName = method.getName(); final String methodPath = apiAnnotationGetPath(method); final String methodType = apiAnnotationGetTypeRequest(method); final String methodDescription = apiAnnotationGetOperationDescription(method); - final List consumes = apiAnnotationGetConsumes(method); + final List consumes = apiAnnotationGetConsumes(clazz, method); + final List produces = apiAnnotationProduces(clazz, method); if (consumes != null && consumes.contains(MediaType.MULTIPART_FORM_DATA)) { LOGGER.error(" [{}] {} => {}/{} ==> Multipart is not managed ...", methodType, methodName, basicPath, methodPath); continue; } LOGGER.trace(" [{}] {} => {}/{}", methodType, methodName, basicPath, methodPath); - final Class returnType = method.getReturnType(); if (methodDescription != null) { LOGGER.trace(" description: {}", methodDescription); } - LOGGER.trace(" return: {}", returnType.getSimpleName()); - final Map> queryParams = new HashMap<>(); - final Map> pathParams = new HashMap<>(); - final List> emptyElement = new ArrayList<>(); + Class returnTypeModel = method.getReturnType(); + boolean returnModelIsArray = false; + ClassElement tmpReturn; + if (returnTypeModel == List.class) { + final ParameterizedType listType = (ParameterizedType) method.getGenericReturnType(); + returnTypeModel = (Class) listType.getActualTypeArguments()[0]; + tmpReturn = DataFactoryZod.createTable(returnTypeModel, previous); + returnModelIsArray = true; + includeModel.add(tmpReturn.tsTypeName); + } else { + tmpReturn = DataFactoryZod.createTable(returnTypeModel, previous); + + } + includeModel.add(tmpReturn.tsTypeName); + includeModel.add(tmpReturn.tsCheckType); + LOGGER.trace(" return: {}", tmpReturn.tsTypeName); + final Map queryParams = new HashMap<>(); + final Map pathParams = new HashMap<>(); + final List emptyElement = new ArrayList<>(); // LOGGER.info(" Parameters:"); for (final Parameter parameter : method.getParameters()) { + // Security context are internal parameter (not available from API) if (apiAnnotationIsContext(parameter)) { continue; } final Class parameterType = parameter.getType(); + String parameterTypeString; + if (parameterType == List.class) { + parameterTypeString = "any[]"; + } else { + final ClassElement tmpClassElement = DataFactoryZod.createTable(parameterType, previous); + includeModel.add(tmpClassElement.tsTypeName); + final ClassElement tmp = new ClassElement(parameterType); + parameterTypeString = tmp.tsTypeName; + } final String pathParam = apiAnnotationGetPathParam(parameter); final String queryParam = apiAnnotationGetQueryParam(parameter); if (queryParam != null) { - queryParams.put(queryParam, parameterType); + queryParams.put(queryParam, parameterTypeString); } else if (pathParam != null) { - pathParams.put(pathParam, parameterType); + pathParams.put(pathParam, parameterTypeString); } else { final Class asyncType = apiAnnotationGetAsyncType(parameter); if (asyncType != null) { - emptyElement.add(asyncType); + final ClassElement tmpClassElement = DataFactoryZod.createTable(asyncType, previous); + includeModel.add(tmpClassElement.tsTypeName); + final ClassElement tmp = new ClassElement(asyncType); + emptyElement.add(tmp.tsTypeName); + } else if (parameterType == List.class) { + parameterTypeString = "any[]"; + final Class plop = parameterType.arrayType(); + LOGGER.info("ArrayType = {}", plop); } else { - emptyElement.add(parameterType); + final ClassElement tmpClassElement = DataFactoryZod.createTable(parameterType, previous); + includeModel.add(tmpClassElement.tsTypeName); + final ClassElement tmp = new ClassElement(parameterType); + emptyElement.add(tmp.tsTypeName); } - // LOGGER.info(" - {} ", parameterType.getSimpleName()); } } if (!queryParams.isEmpty()) { LOGGER.trace(" Query parameter:"); - for (final Entry> queryEntry : queryParams.entrySet()) { - LOGGER.trace(" - {}: {}", queryEntry.getKey(), queryEntry.getValue().getSimpleName()); + for (final Entry queryEntry : queryParams.entrySet()) { + LOGGER.trace(" - {}: {}", queryEntry.getKey(), queryEntry.getValue()); } } if (!pathParams.isEmpty()) { LOGGER.trace(" Path parameter:"); - for (final Entry> pathEntry : pathParams.entrySet()) { - LOGGER.trace(" - {}: {}", pathEntry.getKey(), pathEntry.getValue().getSimpleName()); + for (final Entry pathEntry : pathParams.entrySet()) { + LOGGER.trace(" - {}: {}", pathEntry.getKey(), pathEntry.getValue()); } } @@ -330,7 +302,7 @@ public class DataFactoryTsApi { LOGGER.error(" Fail to parse: Too much element in the model for the data ..."); continue; } else if (emptyElement.size() == 1) { - LOGGER.trace(" data type: {}", emptyElement.get(0).getSimpleName()); + LOGGER.trace(" data type: {}", emptyElement.get(0)); } // ALL is good can generate the Elements @@ -342,8 +314,7 @@ public class DataFactoryTsApi { builder.append("\n\texport function "); builder.append(methodName); builder.append("({"); - builder.append("options,"); - builder.append(" serverUrl,"); + builder.append("restConfig,"); if (!queryParams.isEmpty()) { builder.append(" queries,"); } @@ -354,78 +325,131 @@ public class DataFactoryTsApi { builder.append(" data,"); } builder.append(" } : {"); - builder.append("\n\t\t\toptions: any,"); - builder.append("\n\t\t\tserverUrl: string,"); + builder.append("\n\t\t\trestConfig: RESTConfig,"); if (!queryParams.isEmpty()) { builder.append("\n\t\t\tqueries: {"); - for (final Entry> queryEntry : queryParams.entrySet()) { - classNeeded.add(queryEntry.getValue()); + for (final Entry queryEntry : queryParams.entrySet()) { builder.append("\n\t\t\t\t"); builder.append(queryEntry.getKey()); builder.append(": "); - builder.append(queryEntry.getValue().getSimpleName()); + builder.append(queryEntry.getValue()); builder.append(","); } builder.append("\n\t\t\t},"); } if (!pathParams.isEmpty()) { builder.append("\n\t\t\tparams: {"); - for (final Entry> pathEntry : pathParams.entrySet()) { - classNeeded.add(pathEntry.getValue()); + for (final Entry pathEntry : pathParams.entrySet()) { builder.append("\n\t\t\t\t"); builder.append(pathEntry.getKey()); builder.append(": "); - builder.append(pathEntry.getValue().getSimpleName()); + builder.append(pathEntry.getValue()); builder.append(","); } builder.append("\n\t\t\t},"); } if (emptyElement.size() == 1) { builder.append("\n\t\t\tdata: "); - classNeeded.add(emptyElement.get(0)); - builder.append(emptyElement.get(0).getSimpleName()); + builder.append(emptyElement.get(0)); builder.append(","); } builder.append("\n\t\t}) : Promise<"); - if (returnType == Void.class) { - builder.append("void"); - } else { - classNeeded.add(returnType); - builder.append(returnType.getSimpleName()); + builder.append(tmpReturn.tsTypeName); + if (returnModelIsArray) { + builder.append("[]"); } builder.append("> {"); builder.append("\n\t\treturn new Promise((resolve, reject) => {"); - /* fetch('https://example.com?' + new URLSearchParams({ foo: 'value', bar: 2, })) */ + builder.append("\n\t\t\tRESTRequest({"); + builder.append("\n\t\t\t\trestModel: {"); + builder.append("\n\t\t\t\t\tendPoint: \""); + builder.append(basicPath); + if (methodPath != null) { + builder.append("/"); + builder.append(methodPath); + } + builder.append("\","); + builder.append("\n\t\t\t\t\trequestType: HTTPRequestModel."); + builder.append(methodType); + builder.append(","); + if (consumes != null) { + for (final String elem : consumes) { + if (MediaType.APPLICATION_JSON.equals(elem)) { + builder.append("\n\t\t\t\t\tcontentType: HTTPMimeType.JSON,"); + break; + } + } + } + if (produces != null) { + for (final String elem : produces) { + if (MediaType.APPLICATION_JSON.equals(elem)) { + builder.append("\n\t\t\t\t\taccept: HTTPMimeType.JSON,"); + break; + } + } + } + builder.append("\n\t\t\t\t},"); + builder.append("\n\t\t\t\trestConfig,"); + if (!pathParams.isEmpty()) { + builder.append("\n\t\t\t\tparams,"); + } + if (!queryParams.isEmpty()) { + builder.append("\n\t\t\t\tqueries,"); + } + if (emptyElement.size() == 1) { + builder.append("\n\t\t\t\tdata,"); + } + builder.append("\n\t\t\t}).then((value: ModelResponseHttp) => {"); + if (returnModelIsArray) { + builder.append("\n\t\t\t\tif (isArrayOf(value.data, is"); + builder.append(tmpReturn.tsTypeName); + builder.append(")) {"); + } else { + builder.append("\n\t\t\t\tif (is"); + builder.append(tmpReturn.tsTypeName); + builder.append("(value.data)) {"); + } + builder.append("\n\t\t\t\t\tresolve(value.data);"); + builder.append("\n\t\t\t\t} else {"); + builder.append("\n\t\t\t\t\treject({"); + builder.append("\n\t\t\t\t\t\ttime: Date().toString(),"); + builder.append("\n\t\t\t\t\t\tstatus: 950,"); + builder.append("\n\t\t\t\t\t\terror: \"REST Fail to verify the data\","); + builder.append("\n\t\t\t\t\t\tstatusMessage: \"API cast ERROR\","); + builder.append("\n\t\t\t\t\t\tmessage: \"api.ts Check type as fail\""); + builder.append("\n\t\t\t\t\t} as RestErrorResponse);"); + builder.append("\n\t\t\t\t}"); + builder.append("\n\t\t\t}).catch((reason: RestErrorResponse) => {"); + builder.append("\n\t\t\t\treject(reason);"); + builder.append("\n\t\t\t});"); builder.append("\n\t\t});"); builder.append("\n\t};"); } - builder.append("\n}\n"); - return builder.toString(); + return new APIModel(builder.toString(), classSimpleName); } public static void generatePackage(final List> classApi, final List> classModel, final String pathPackage) throws Exception { - final Set> classNeeded = new HashSet<>(classModel); - final String data = createApi(classApi, classNeeded); - FileWriter myWriter = new FileWriter(pathPackage + File.separator + "api.ts"); - myWriter.write(data); - myWriter.close(); - final String packageApi = DataFactoryZod.createTables(new ArrayList<>(classNeeded)); - myWriter = new FileWriter(pathPackage + File.separator + "model.ts"); + final GeneratedTypes previous = DataFactoryZod.createBasicType(); + DataFactoryZod.createTable(RestErrorResponse.class, previous); + final List listApi = createApi(classApi, previous, pathPackage); + final String packageApi = DataFactoryZod.createTables(new ArrayList<>(classModel), previous); + FileWriter myWriter = new FileWriter(pathPackage + File.separator + "model.ts"); myWriter.write(packageApi.toString()); myWriter.close(); - final String index = """ + + final StringBuilder index = new StringBuilder(""" /** * Global import of the package */ - export * from "./model.ts"; - export * from "./api.ts"; - - """; + export * from "./model"; + """); + for (final String api : listApi) { + index.append("export * from \"./").append(api).append("\";\n"); + } myWriter = new FileWriter(pathPackage + File.separator + "index.ts"); - myWriter.write(index); + myWriter.write(index.toString()); myWriter.close(); - return; } diff --git a/src/org/kar/archidata/dataAccess/DataFactoryZod.java b/src/org/kar/archidata/dataAccess/DataFactoryZod.java index c92da4b..71701d9 100644 --- a/src/org/kar/archidata/dataAccess/DataFactoryZod.java +++ b/src/org/kar/archidata/dataAccess/DataFactoryZod.java @@ -10,9 +10,7 @@ import java.time.LocalDate; import java.time.LocalTime; import java.util.ArrayList; import java.util.Date; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.UUID; import org.kar.archidata.annotation.AnnotationTools; @@ -22,98 +20,149 @@ import org.slf4j.LoggerFactory; public class DataFactoryZod { static final Logger LOGGER = LoggerFactory.getLogger(DataFactoryZod.class); - - public static String convertTypeZodSimpleType(final Class type, final Map previousClassesGenerated, final List order) throws Exception { - if (type == UUID.class) { - return "ZodUUID"; + + static public class ClassElement { + public Class[] model; + public String zodName; + public String tsTypeName; + public String tsCheckType; + public String declaration; + public String comment = null; + public boolean nativeType; + + public ClassElement(final Class model[], final String zodName, final String tsTypeName, final String tsCheckType, final String declaration, final boolean nativeType) { + this.model = model; + this.zodName = zodName; + this.tsTypeName = tsTypeName; + this.tsCheckType = tsCheckType; + this.declaration = declaration; + this.nativeType = nativeType; } - if (type == Long.class) { - return "ZodLong"; + + public ClassElement(final Class model) { + this(new Class[] { model }); } - if (type == long.class) { - return "ZodLong"; + + public ClassElement(final Class model[]) { + this.model = model; + this.zodName = "Zod" + model[0].getSimpleName(); + this.tsTypeName = model[0].getSimpleName(); + this.tsCheckType = "is" + model[0].getSimpleName(); + this.declaration = null; + this.nativeType = false; } - if (type == Integer.class || type == int.class) { - return "ZodInteger"; - } - if (type == Boolean.class || type == boolean.class) { - return "zod.boolean()"; - } - if (type == double.class || type == Double.class) { - return "ZodDouble"; - } - if (type == float.class || type == Float.class) { - return "ZodFloat"; - } - if (type == Instant.class) { - return "ZodInstant"; - } - if (type == Date.class || type == Timestamp.class) { - return "ZodDate"; - } - if (type == LocalDate.class) { - return "ZodLocalDate"; - } - if (type == LocalTime.class) { - return "ZodLocalTime"; - } - if (type == String.class) { - return "zod.string()"; - } - if (type.isEnum()) { - final Object[] arr = type.getEnumConstants(); - final StringBuilder out = new StringBuilder(); - boolean first = true; - out.append("zod.enum(["); - for (final Object elem : arr) { - if (!first) { - out.append(", "); + } + + public static class GeneratedTypes { + final List previousGeneration = new ArrayList<>(); + final List> order = new ArrayList<>(); + + public ClassElement find(final Class clazz) { + for (final ClassElement elem : this.previousGeneration) { + for (final Class elemClass : elem.model) { + if (elemClass == clazz) { + return elem; + } } - first = false; - out.append("\""); - out.append(elem.toString()); - out.append("\""); } - out.append("])"); - return out.toString(); - } - if (type == List.class) { return null; } - createTable(type, previousClassesGenerated, order); - return "Zod" + type.getSimpleName(); + + public void add(final ClassElement elem) { + this.previousGeneration.add(elem); + } + + public void add(final ClassElement elem, final boolean addOrder) { + this.previousGeneration.add(elem); + if (addOrder) { + this.order.add(elem.model[0]); + } + } + + public void addOrder(final ClassElement elem) { + this.order.add(elem.model[0]); + } } - - public static String convertTypeZod(final Field field, final Map previousClassesGenerated, final List order) throws Exception { + + public static ClassElement convertTypeZodEnum(final Class clazz, final GeneratedTypes previous) throws Exception { + final ClassElement element = new ClassElement(clazz); + previous.add(element); + + final Object[] arr = clazz.getEnumConstants(); + final StringBuilder out = new StringBuilder(); + boolean first = true; + out.append("zod.enum(["); + for (final Object elem : arr) { + if (!first) { + out.append(", \n\t"); + } else { + out.append("\n\t"); + } + first = false; + out.append("'"); + out.append(elem.toString()); + out.append("'"); + } + out.append("\n\t]);"); + element.declaration = out.toString(); + previous.addOrder(element); + return element; + } + + public static String convertTypeZod(final Class type, final GeneratedTypes previous) throws Exception { + final ClassElement previousType = previous.find(type); + if (previousType != null) { + return previousType.zodName; + } + if (type.isEnum()) { + return convertTypeZodEnum(type, previous).zodName; + } + if (type == List.class) { + throw new DataAccessException("Imcompatible type of element in object for: " + type.getCanonicalName() + " Unmanaged List of List ... "); + } + final ClassElement elemCreated = createTable(type, previous); + if (elemCreated != null) { + return elemCreated.zodName; + } + throw new DataAccessException("Imcompatible type of element in object for: " + type.getCanonicalName()); + } + + public static String convertTypeZod(final Field field, final GeneratedTypes previous) throws Exception { final Class type = field.getType(); - final String simpleType = convertTypeZodSimpleType(type, previousClassesGenerated, order); - if (simpleType != null) { - return simpleType; + final ClassElement previousType = previous.find(type); + if (previousType != null) { + return previousType.zodName; + } + if (type.isEnum()) { + return convertTypeZodEnum(type, previous).zodName; } if (type == List.class) { final ParameterizedType listType = (ParameterizedType) field.getGenericType(); final Class listClass = (Class) listType.getActualTypeArguments()[0]; - final String simpleSubType = convertTypeZodSimpleType(listClass, previousClassesGenerated, order); + final String simpleSubType = convertTypeZod(listClass, previous); return "zod.array(" + simpleSubType + ")"; } + final ClassElement elemCreated = createTable(type, previous); + if (elemCreated != null) { + return elemCreated.zodName; + } throw new DataAccessException("Imcompatible type of element in object for: " + type.getCanonicalName()); } - + public static String optionalTypeZod(final Class type) throws Exception { if (type.isPrimitive()) { return ""; } return ".optional()"; } - - public static void createTablesSpecificType(final Field elem, final int fieldId, final StringBuilder builder, final Map previousClassesGenerated, final List order) - throws Exception { + + public static void createTablesSpecificType(final Field elem, final int fieldId, final StringBuilder builder, final GeneratedTypes previous) throws Exception { final String name = elem.getName(); final Class classModel = elem.getType(); final int limitSize = AnnotationTools.getLimitSize(elem); - + final String comment = AnnotationTools.getComment(elem); - + if (fieldId != 0) { builder.append(","); } @@ -124,7 +173,7 @@ public class DataFactoryZod { builder.append("\n\t"); builder.append(name); builder.append(": "); - builder.append(convertTypeZod(elem, previousClassesGenerated, order)); + builder.append(convertTypeZod(elem, previous)); if (limitSize > 0 && classModel == String.class) { builder.append(".max("); builder.append(limitSize); @@ -135,7 +184,7 @@ public class DataFactoryZod { } builder.append(optionalTypeZod(classModel)); } - + private static boolean isFieldFromSuperClass(final Class model, final String filedName) { final Class superClass = model.getSuperclass(); if (superClass == null) { @@ -155,80 +204,81 @@ public class DataFactoryZod { } return false; } - + + public static GeneratedTypes createBasicType() throws Exception { + final GeneratedTypes previous = new GeneratedTypes(); + previous.add(new ClassElement(new Class[] { Void.class, void.class }, "void", "void", null, null, true)); + previous.add(new ClassElement(new Class[] { String.class }, "zod.string()", "string", null, "zod.string()", true)); + previous.add(new ClassElement(new Class[] { Boolean.class, boolean.class }, "zod.boolean()", "boolean", null, "zod.boolean()", true)); + previous.add(new ClassElement(new Class[] { UUID.class }, "ZodUUID", "UUID", "isUUID", "zod.string().uuid()", false), true); + previous.add(new ClassElement(new Class[] { Long.class, long.class }, "ZodLong", "Long", "isLong", + // "zod.bigint()", + "zod.number()", false), true); + previous.add(new ClassElement(new Class[] { Integer.class, int.class }, "ZodInteger", "Integer", "isInteger", "zod.number().safe()", false), true); + previous.add(new ClassElement(new Class[] { Double.class, double.class }, "ZodDouble", "Double", "isDouble", "zod.number()", true), true); + previous.add(new ClassElement(new Class[] { Float.class, float.class }, "ZodFloat", "Float", "isFloat", "zod.number()", false), true); + previous.add(new ClassElement(new Class[] { Instant.class }, "ZodInstant", "Instant", "isInstant", "zod.string()", false), true); + previous.add(new ClassElement(new Class[] { Date.class }, "ZodDate", "Date", "isDate", "zod.date()", false), true); + previous.add(new ClassElement(new Class[] { Timestamp.class }, "ZodTimestamp", "Timestamp", "isTimestamp", "zod.date()", false), true); + previous.add(new ClassElement(new Class[] { LocalDate.class }, "ZodLocalDate", "LocalDate", "isLocalDate", "zod.date()", false), true); + previous.add(new ClassElement(new Class[] { LocalTime.class }, "ZodLocalTime", "LocalTime", "isLocalTime", "zod.date()", false), true); + return previous; + } + /** Request the generation of the TypeScript file for the "Zod" export model * @param classs List of class used in the model * @return A string representing the Server models * @throws Exception */ public static String createTables(final List> classs) throws Exception { - final Map previousClassesGenerated = new LinkedHashMap<>(); - final List order = new ArrayList<>(); + return createTables(classs, createBasicType()); + } + + public static String createTables(final List> classs, final GeneratedTypes previous) throws Exception { for (final Class clazz : classs) { - createTable(clazz, previousClassesGenerated, order); + createTable(clazz, previous); } + final StringBuilder generatedData = new StringBuilder(); generatedData.append(""" /** * Interface of the server (auto-generated code) */ import { z as zod } from \"zod\"; - - export const ZodUUID = zod.string().uuid(); - export type UUID = zod.infer; - - export const ZodLong = zod.bigint(); - export type Long = zod.infer; - - export const ZodInteger = zod.number().safe(); - export type Integer = zod.infer; - - export const ZodDouble = zod.number(); - export type Double = zod.infer; - - export const ZodFloat = zod.number(); - export type Float = zod.infer; - - export const ZodInstant = zod.string(); - export type Instant = zod.infer; - - export const ZodDate = zod.date(); - export type Date = zod.infer; - - export const ZodTimestamp = zod.date(); - export type Timestamp = zod.infer; - - export const ZodLocalDate = zod.date(); - export type LocalDate = zod.infer; - - export const ZodLocalTime = zod.date(); - export type LocalTime = zod.infer; + """); - for (final String elem : order) { - final String data = previousClassesGenerated.get(elem); - generatedData.append(data); - generatedData.append("\n\n"); + for (final Class elem : previous.order) { + final ClassElement data = previous.find(elem); + if (!data.nativeType) { + if (data.comment != null) { + generatedData.append(data.comment); + } + generatedData.append("export const "); + generatedData.append(data.zodName); + generatedData.append(" = "); + generatedData.append(data.declaration); + generatedData.append(";"); + generatedData.append(createDeclaration(data)); + generatedData.append("\n\n"); + } } LOGGER.info("generated: {}", generatedData.toString()); return generatedData.toString(); } - - public static void createTable(final Class clazz, final Map previousClassesGenerated, final List order) throws Exception { + + public static ClassElement createTable(final Class clazz, final GeneratedTypes previous) throws Exception { if (clazz == null) { - return; + return null; + } + final ClassElement alreadyExist = previous.find(clazz); + if (previous.find(clazz) != null) { + return alreadyExist; } if (clazz.isPrimitive()) { - return; - } - if (clazz == Double.class || clazz == Float.class || clazz == Integer.class || clazz == Long.class || clazz == UUID.class || clazz == Instant.class || clazz == Date.class - || clazz == Timestamp.class || clazz == LocalDate.class || clazz == LocalTime.class || clazz == String.class) { - return; - } - - if (previousClassesGenerated.get(clazz.getCanonicalName()) != null) { - return; + return null; } // add the current class to prevent multiple creation - previousClassesGenerated.put(clazz.getCanonicalName(), "In Generation"); + final ClassElement curentElementClass = new ClassElement(clazz); + previous.add(curentElementClass); // Local generation of class: final StringBuilder internalBuilder = new StringBuilder(); final List alreadyAdded = new ArrayList<>(); @@ -258,62 +308,67 @@ public class DataFactoryZod { * tableName + " field name=" + AnnotationTools.getFieldName(elem) + " type=" + elem.getType()); } fieldId++; */ } else { LOGGER.trace("Create type for: {} ==> {}", AnnotationTools.getFieldName(elem), elem.getType()); - DataFactoryZod.createTablesSpecificType(elem, fieldId, internalBuilder, previousClassesGenerated, order); + DataFactoryZod.createTablesSpecificType(elem, fieldId, internalBuilder, previous); fieldId++; } - + } final String description = AnnotationTools.getSchemaDescription(clazz); final String example = AnnotationTools.getSchemaExample(clazz); - final StringBuilder generatedData = new StringBuilder(); + final StringBuilder generatedCommentedData = new StringBuilder(); if (description != null || example != null) { - generatedData.append("/**\n"); + generatedCommentedData.append("/**\n"); if (description != null) { for (final String elem : description.split("\n")) { - generatedData.append(" * "); - generatedData.append(elem); - generatedData.append("\n"); + generatedCommentedData.append(" * "); + generatedCommentedData.append(elem); + generatedCommentedData.append("\n"); } } if (example != null) { - generatedData.append(" * Example:\n"); - generatedData.append(" * ```\n"); + generatedCommentedData.append(" * Example:\n"); + generatedCommentedData.append(" * ```\n"); for (final String elem : example.split("\n")) { - generatedData.append(" * "); - generatedData.append(elem); - generatedData.append("\n"); + generatedCommentedData.append(" * "); + generatedCommentedData.append(elem); + generatedCommentedData.append("\n"); } - generatedData.append(" * ```\n"); + generatedCommentedData.append(" * ```\n"); } - generatedData.append(" */\n"); + generatedCommentedData.append(" */\n"); } - generatedData.append("export const Zod"); - generatedData.append(clazz.getSimpleName()); - generatedData.append(" = "); + curentElementClass.comment = generatedCommentedData.toString(); + final StringBuilder generatedData = new StringBuilder(); final Class parentClass = clazz.getSuperclass(); - if (parentClass != null && parentClass != Object.class) { - createTable(parentClass, previousClassesGenerated, order); - generatedData.append("Zod"); - generatedData.append(parentClass.getSimpleName()); + if (parentClass != null && parentClass != Object.class && parentClass != Record.class) { + final ClassElement parentDeclaration = createTable(parentClass, previous); + generatedData.append(parentDeclaration.zodName); generatedData.append(".extend({"); } else { generatedData.append("zod.object({"); } generatedData.append(internalBuilder.toString()); - generatedData.append("\n});"); - // declare generic type: + generatedData.append("\n})"); + // Remove the previous to reorder the map ==> parent must be inserted before us. + curentElementClass.declaration = generatedData.toString(); + previous.addOrder(curentElementClass); + return curentElementClass; + } + + public static String createDeclaration(final ClassElement elem) { + final StringBuilder generatedData = new StringBuilder(); generatedData.append("\nexport type "); - generatedData.append(clazz.getSimpleName()); - generatedData.append(" = zod.infer;"); // declare generic isXXX: - generatedData.append("\nexport function is"); - generatedData.append(clazz.getSimpleName()); + generatedData.append("\nexport function "); + generatedData.append(elem.tsCheckType); generatedData.append("(data: any): data is "); - generatedData.append(clazz.getSimpleName()); - generatedData.append(" {\n\ttry {\n\t\tZod"); - generatedData.append(clazz.getSimpleName()); + generatedData.append(elem.tsTypeName); + generatedData.append(" {\n\ttry {\n\t\t"); + generatedData.append(elem.zodName); generatedData.append(""" .parse(data); return true; @@ -323,11 +378,9 @@ public class DataFactoryZod { } } """); - // Remove the previous to reorder the map ==> parent must be inserted before us. - previousClassesGenerated.put(clazz.getCanonicalName(), generatedData.toString()); - order.add(clazz.getCanonicalName()); + return generatedData.toString(); } - + public static void generatePackage(final List> classs, final String pathPackage) throws Exception { final String packageApi = createTables(classs); FileWriter myWriter = new FileWriter(pathPackage + File.separator + "model.ts"); @@ -338,11 +391,11 @@ public class DataFactoryZod { * Global import of the package */ export * from "./model.ts"; - + """; myWriter = new FileWriter(pathPackage + File.separator + "index.ts"); myWriter.write(index); myWriter.close(); } - + } \ No newline at end of file diff --git a/src/org/kar/archidata/tools/DataTools.java b/src/org/kar/archidata/tools/DataTools.java index 883a957..1611131 100644 --- a/src/org/kar/archidata/tools/DataTools.java +++ b/src/org/kar/archidata/tools/DataTools.java @@ -11,6 +11,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.sql.SQLException; import java.util.List; +import java.util.UUID; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.kar.archidata.dataAccess.DataAccess; @@ -103,7 +104,7 @@ public class DataTools { return null; } - public static void undelete(final Long id) { + public static void undelete(final UUID id) { try { DataAccess.unsetDelete(Data.class, id); } catch (final Exception e) {