From 09cfcfc5786fcb91ceaa18709b415cf8e57055c6 Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Sat, 8 Jun 2024 11:42:38 +0200 Subject: [PATCH] [FEAT] generate a full Zod object for write mode. - Add @NoWriteSpecificMode to permit to remove specific object write model - refactor Zod Write model - Add .nullable() in write Optional element Residual bug element use in APi that is mark as no write --- .classpath | 2 +- .project | 4 +- .../archidata/annotation/AnnotationTools.java | 8 ++ .../annotation/NoWriteSpecificMode.java | 13 ++ .../archidata/catcher/RestErrorResponse.java | 2 + .../externalRestApi/model/ClassEnumModel.java | 1 + .../externalRestApi/model/ClassModel.java | 5 + .../model/ClassObjectModel.java | 1 + .../typescript/TsClassElement.java | 132 ++++++++++++++---- 9 files changed, 140 insertions(+), 28 deletions(-) create mode 100644 src/org/kar/archidata/annotation/NoWriteSpecificMode.java diff --git a/.classpath b/.classpath index c84a787..948a0ab 100644 --- a/.classpath +++ b/.classpath @@ -25,7 +25,7 @@ - + diff --git a/.project b/.project index 1e3f1c4..ccbb290 100644 --- a/.project +++ b/.project @@ -11,12 +11,12 @@ - org.eclipse.m2e.core.maven2Builder + edu.umd.cs.findbugs.plugin.eclipse.findbugsBuilder - edu.umd.cs.findbugs.plugin.eclipse.findbugsBuilder + org.eclipse.m2e.core.maven2Builder diff --git a/src/org/kar/archidata/annotation/AnnotationTools.java b/src/org/kar/archidata/annotation/AnnotationTools.java index af5d4ef..454c383 100644 --- a/src/org/kar/archidata/annotation/AnnotationTools.java +++ b/src/org/kar/archidata/annotation/AnnotationTools.java @@ -80,6 +80,14 @@ public class AnnotationTools { return ((Schema) annotation[0]).example(); } + public static boolean getNoWriteSpecificMode(final Class element) { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(NoWriteSpecificMode.class); + if (annotation.length == 0) { + return false; + } + return true; + } + public static String getSchemaDescription(final Class element) throws DataAccessException { final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class); if (annotation.length == 0) { diff --git a/src/org/kar/archidata/annotation/NoWriteSpecificMode.java b/src/org/kar/archidata/annotation/NoWriteSpecificMode.java new file mode 100644 index 0000000..4d6a2e8 --- /dev/null +++ b/src/org/kar/archidata/annotation/NoWriteSpecificMode.java @@ -0,0 +1,13 @@ +package org.kar.archidata.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** When we wend to have only One type for read and write mode (Wrapping API). */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface NoWriteSpecificMode { + +} diff --git a/src/org/kar/archidata/catcher/RestErrorResponse.java b/src/org/kar/archidata/catcher/RestErrorResponse.java index 246a794..d9c540a 100644 --- a/src/org/kar/archidata/catcher/RestErrorResponse.java +++ b/src/org/kar/archidata/catcher/RestErrorResponse.java @@ -3,12 +3,14 @@ package org.kar.archidata.catcher; import java.time.Instant; import java.util.UUID; +import org.kar.archidata.annotation.NoWriteSpecificMode; import org.kar.archidata.tools.UuidUtils; import jakarta.persistence.Column; import jakarta.validation.constraints.NotNull; import jakarta.ws.rs.core.Response; +@NoWriteSpecificMode public class RestErrorResponse { public UUID uuid = UuidUtils.nextUUID(); @NotNull diff --git a/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java b/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java index 1791f6d..665a35f 100644 --- a/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java +++ b/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java @@ -10,6 +10,7 @@ public class ClassEnumModel extends ClassModel { protected ClassEnumModel(final Class clazz) { this.originClasses = clazz; + this.noWriteSpecificMode = true; } @Override diff --git a/src/org/kar/archidata/externalRestApi/model/ClassModel.java b/src/org/kar/archidata/externalRestApi/model/ClassModel.java index 1e81c4b..27ab71d 100644 --- a/src/org/kar/archidata/externalRestApi/model/ClassModel.java +++ b/src/org/kar/archidata/externalRestApi/model/ClassModel.java @@ -11,12 +11,17 @@ import java.util.Set; public abstract class ClassModel { protected boolean analyzeDone = false; protected Class originClasses = null; + protected boolean noWriteSpecificMode = false; protected List dependencyModels = new ArrayList<>(); public Class getOriginClasses() { return this.originClasses; } + public boolean isNoWriteSpecificMode() { + return this.noWriteSpecificMode; + } + protected boolean isCompatible(final Class clazz) { return this.originClasses == clazz; } diff --git a/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java b/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java index 3f1ddd6..1d97f02 100644 --- a/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java +++ b/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java @@ -123,6 +123,7 @@ public class ClassObjectModel extends ClassModel { } this.analyzeDone = true; final Class clazz = this.originClasses; + this.noWriteSpecificMode = AnnotationTools.getNoWriteSpecificMode(clazz); this.isPrimitive = clazz.isPrimitive(); if (this.isPrimitive) { return; diff --git a/src/org/kar/archidata/externalRestApi/typescript/TsClassElement.java b/src/org/kar/archidata/externalRestApi/typescript/TsClassElement.java index fdd626c..9c3cc2e 100644 --- a/src/org/kar/archidata/externalRestApi/typescript/TsClassElement.java +++ b/src/org/kar/archidata/externalRestApi/typescript/TsClassElement.java @@ -181,12 +181,18 @@ public class TsClassElement { if (tsModel.nativeType != DefinedPosition.NATIVE) { out.append("import {"); out.append(tsModel.zodName); + if (tsModel.nativeType == DefinedPosition.NORMAL && !(tsModel.models.get(0).isNoWriteSpecificMode())) { + out.append(", "); + out.append(tsModel.zodName); + out.append("Write "); + } out.append("} from \"./"); out.append(tsModel.fileName); out.append("\";\n"); } } return out.toString(); + } private Object generateComment(final ClassObjectModel model) { @@ -215,22 +221,38 @@ public class TsClassElement { return out.toString(); } - public String optionalTypeZod(final FieldProperty field) { + public boolean isOptionalTypeZod(final FieldProperty field) { // Common checking element (apply to List, Map, ...) if (field.nullable()) { - return ".optional()"; + return true; } if (field.notNull()) { - return ""; + return false; } // Other object: if (field.model().getOriginClasses() == null || field.model().getOriginClasses().isPrimitive()) { - return ""; + return false; } if (field.columnNotNull()) { - return ""; + return false; } - return ".optional()"; + return true; + } + + public String optionalTypeZod(final FieldProperty field) { + // Common checking element (apply to List, Map, ...) + if (isOptionalTypeZod(field)) { + return ".optional()"; + } + return ""; + } + + public String optionalWriteTypeZod(final FieldProperty field) { + // Common checking element (apply to List, Map, ...) + if (isOptionalTypeZod(field)) { + return ".nullable()"; + } + return ""; } public String maxSizeZod(final FieldProperty field) { @@ -270,7 +292,9 @@ public class TsClassElement { out.append(getBaseHeader()); out.append(generateImports(model.getDependencyModels(), tsGroup)); out.append("\n"); - + // ------------------------------------------------------------------------ + // -- Generate read mode + // ------------------------------------------------------------------------ out.append(generateComment(model)); out.append("export const "); out.append(this.zodName); @@ -315,27 +339,85 @@ public class TsClassElement { out.append("\n});\n"); out.append(generateZodInfer(this.tsTypeName, this.zodName)); out.append(generateExportCheckFunctionWrite("")); + // check if we need to generate write mode : + if (!model.isNoWriteSpecificMode()) { + // ------------------------------------------------------------------------ + // -- Generate write mode + // ------------------------------------------------------------------------ + //out.append(generateComment(model)); + out.append("export const "); + out.append(this.zodName); + out.append("Write = "); - // Generate the Write Type associated. - out.append("\nexport const "); - out.append(this.zodName); - out.append("Write = "); - out.append(this.zodName); - if (omitField.size() != 0) { - out.append(".omit({\n"); - for (final String elem : omitField) { - out.append("\t"); - out.append(elem); - out.append(": true,\n"); + if (model.getExtendsClass() != null) { + final ClassModel parentClass = model.getExtendsClass(); + final TsClassElement tsParentModel = tsGroup.find(parentClass); + out.append(tsParentModel.zodName); + out.append("Write"); + out.append(".extend({"); + } else { + out.append("zod.object({"); } - out.append("\n})"); + out.append("\n"); + for (final FieldProperty field : model.getFields()) { + // remove all readOnly field + if (field.readOnly()) { + continue; + } + final ClassModel fieldModel = field.model(); + if (field.comment() != null) { + out.append("\t/**\n"); + out.append("\t * "); + out.append(field.comment()); + out.append("\n\t */\n"); + } + out.append("\t"); + out.append(field.name()); + out.append(": "); + if (fieldModel instanceof ClassEnumModel || fieldModel instanceof ClassObjectModel) { + final TsClassElement tsFieldModel = tsGroup.find(fieldModel); + out.append(tsFieldModel.zodName); + } else if (fieldModel instanceof final ClassListModel fieldListModel) { + final String data = generateTsList(fieldListModel, tsGroup); + out.append(data); + } else if (fieldModel instanceof final ClassMapModel fieldMapModel) { + final String data = generateTsMap(fieldMapModel, tsGroup); + out.append(data); + } + out.append(maxSizeZod(field)); + out.append(optionalWriteTypeZod(field)); + // all write field are optional + if (field.model() instanceof final ClassObjectModel plop) { + if (!plop.isPrimitive()) { + out.append(".optional()"); + } + } else { + out.append(".optional()"); + } + out.append(",\n"); + } + out.append("\n});\n"); + out.append(generateZodInfer(this.tsTypeName + "Write", this.zodName + "Write")); + // Check only the input value ==> no need of the output + out.append(generateExportCheckFunctionWrite("Write")); + // Generate the Write Type associated. + /* + out.append("\nexport const "); + out.append(this.zodName); + out.append("Write = "); + out.append(this.zodName); + if (omitField.size() != 0) { + out.append(".omit({\n"); + for (final String elem : omitField) { + out.append("\t"); + out.append(elem); + out.append(": true,\n"); + } + out.append("\n})"); + } + out.append(".partial();\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(); }