Compare commits

...

7 Commits

13 changed files with 335 additions and 205 deletions

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>kangaroo-and-rabbit</groupId> <groupId>kangaroo-and-rabbit</groupId>
<artifactId>archidata</artifactId> <artifactId>archidata</artifactId>
<version>0.9.0</version> <version>0.10.0</version>
<properties> <properties>
<java.version>21</java.version> <java.version>21</java.version>
<maven.compiler.version>3.1</maven.compiler.version> <maven.compiler.version>3.1</maven.compiler.version>

View File

@@ -5,21 +5,25 @@ import java.util.UUID;
import org.kar.archidata.tools.UuidUtils; import org.kar.archidata.tools.UuidUtils;
import jakarta.persistence.Column;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
public class RestErrorResponse { public class RestErrorResponse {
@NotNull
public UUID uuid = UuidUtils.nextUUID(); public UUID uuid = UuidUtils.nextUUID();
@NotNull @NotNull
@Column(length = 0)
public String name; // Mandatory for TS generic error public String name; // Mandatory for TS generic error
@NotNull @NotNull
@Column(length = 0)
public String message; // Mandatory for TS generic error public String message; // Mandatory for TS generic error
@NotNull @NotNull
@Column(length = 0)
public String time; public String time;
@NotNull @NotNull
final public int status; final public int status;
@NotNull @NotNull
@Column(length = 0)
final public String statusMessage; final public String statusMessage;
public RestErrorResponse(final Response.Status status, final String time, final String error, public RestErrorResponse(final Response.Status status, final String time, final String error,

View File

@@ -796,7 +796,7 @@ public class DataAccess {
// External checker of data: // External checker of data:
final List<CheckFunction> checks = options.get(CheckFunction.class); final List<CheckFunction> checks = options.get(CheckFunction.class);
for (final CheckFunction check : checks) { for (final CheckFunction check : checks) {
check.getChecker().check("", data, AnnotationTools.getFieldsNames(clazz)); check.getChecker().check("", data, AnnotationTools.getFieldsNames(clazz), options);
} }
final DBEntry entry = DBInterfaceOption.getAutoEntry(options); final DBEntry entry = DBInterfaceOption.getAutoEntry(options);
@@ -1119,7 +1119,7 @@ public class DataAccess {
if (options != null) { if (options != null) {
final List<CheckFunction> checks = options.get(CheckFunction.class); final List<CheckFunction> checks = options.get(CheckFunction.class);
for (final CheckFunction check : checks) { for (final CheckFunction check : checks) {
check.getChecker().check("", data, filter.getValues()); check.getChecker().check("", data, filter.getValues(), options);
} }
} }
final List<LazyGetter> asyncActions = new ArrayList<>(); final List<LazyGetter> asyncActions = new ArrayList<>();
@@ -1293,8 +1293,7 @@ public class DataAccess {
return stmt.execute(query); return stmt.execute(query);
} }
public static <T> T getWhere(final Class<T> clazz, final QueryOption... option) throws Exception { public static <T> T getWhere(final Class<T> clazz, final QueryOptions options) throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Limit(1)); options.add(new Limit(1));
final List<T> values = getsWhere(clazz, options); final List<T> values = getsWhere(clazz, options);
if (values.size() == 0) { if (values.size() == 0) {
@@ -1303,6 +1302,11 @@ public class DataAccess {
return values.get(0); return values.get(0);
} }
public static <T> T getWhere(final Class<T> clazz, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option);
return getWhere(clazz, options);
}
public static void generateSelectField(// public static void generateSelectField(//
final StringBuilder querySelect, // final StringBuilder querySelect, //
final StringBuilder query, // final StringBuilder query, //
@@ -1484,12 +1488,19 @@ public class DataAccess {
return data; return data;
} }
public static <ID_TYPE> long count(final Class<?> clazz, final ID_TYPE id) throws Exception { public static <ID_TYPE> long count(final Class<?> clazz, final ID_TYPE id, final QueryOption... option)
return DataAccess.countWhere(clazz, new Condition(getTableIdCondition(clazz, id))); throws Exception {
final QueryOptions options = new QueryOptions(option);
options.add(new Condition(getTableIdCondition(clazz, id)));
return DataAccess.countWhere(clazz, options);
} }
public static long countWhere(final Class<?> clazz, final QueryOption... option) throws Exception { public static long countWhere(final Class<?> clazz, final QueryOption... option) throws Exception {
final QueryOptions options = new QueryOptions(option); final QueryOptions options = new QueryOptions(option);
return countWhere(clazz, options);
}
public static long countWhere(final Class<?> clazz, final QueryOptions options) throws Exception {
final Condition condition = conditionFusionOrEmpty(options, false); final Condition condition = conditionFusionOrEmpty(options, false);
final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz); final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz);
DBEntry entry = DBInterfaceOption.getAutoEntry(options); DBEntry entry = DBInterfaceOption.getAutoEntry(options);

View File

@@ -1,7 +1,6 @@
package org.kar.archidata.dataAccess; package org.kar.archidata.dataAccess;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.kar.archidata.dataAccess.options.AccessDeletedItems; import org.kar.archidata.dataAccess.options.AccessDeletedItems;
@@ -25,10 +24,16 @@ public class QueryOptions {
if (elems == null || elems.length == 0) { if (elems == null || elems.length == 0) {
return; return;
} }
Collections.addAll(this.options, elems); for (final QueryOption elem : elems) {
add(elem);
}
} }
public void add(final QueryOption option) { public void add(final QueryOption option) {
if (option == null) {
return;
}
this.options.add(option); this.options.add(option);
} }

View File

@@ -3,6 +3,7 @@ package org.kar.archidata.dataAccess.options;
import java.util.List; import java.util.List;
import org.kar.archidata.annotation.AnnotationTools; import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.dataAccess.QueryOptions;
/** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */ /** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */
public interface CheckFunctionInterface { public interface CheckFunctionInterface {
@@ -11,10 +12,11 @@ public interface CheckFunctionInterface {
* @param data The object that might be injected. * @param data The object that might be injected.
* @param filterValue List of fields that might be check. If null, then all column must be checked. * @param filterValue List of fields that might be check. If null, then all column must be checked.
* @throws Exception Exception is generate if the data are incorrect. */ * @throws Exception Exception is generate if the data are incorrect. */
void check(final String baseName, Object data, List<String> filterValue) throws Exception; void check(final String baseName, Object data, List<String> filterValue, final QueryOptions options)
throws Exception;
default void checkAll(final String baseName, final Object data) throws Exception { default void checkAll(final String baseName, final Object data, final QueryOptions options) throws Exception {
check(baseName, data, AnnotationTools.getAllFieldsNames(data.getClass())); check(baseName, data, AnnotationTools.getAllFieldsNames(data.getClass()), options);
} }
} }

View File

@@ -2,10 +2,16 @@ package org.kar.archidata.dataAccess.options;
import java.util.List; import java.util.List;
import org.kar.archidata.dataAccess.QueryOptions;
/** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */ /** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */
public class CheckFunctionVoid implements CheckFunctionInterface { public class CheckFunctionVoid implements CheckFunctionInterface {
@Override @Override
public void check(final String baseName, Object data, List<String> filterValue) { public void check(
final String baseName,
final Object data,
final List<String> filterValue,
final QueryOptions options) {
} }

View File

@@ -16,6 +16,7 @@ import org.kar.archidata.annotation.AnnotationTools;
import org.kar.archidata.annotation.DataJson; import org.kar.archidata.annotation.DataJson;
import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.dataAccess.QueryCondition; import org.kar.archidata.dataAccess.QueryCondition;
import org.kar.archidata.dataAccess.QueryOptions;
import org.kar.archidata.exception.DataAccessException; import org.kar.archidata.exception.DataAccessException;
import org.kar.archidata.exception.InputException; import org.kar.archidata.exception.InputException;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -37,7 +38,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
* @param data The object that might be injected. * @param data The object that might be injected.
* @param filterValue List of fields that might be check. If null, then all column must be checked. * @param filterValue List of fields that might be check. If null, then all column must be checked.
* @throws Exception Exception is generate if the data are incorrect. */ * @throws Exception Exception is generate if the data are incorrect. */
void check(final String baseName, final K data) throws Exception; void check(final String baseName, final K data, final QueryOptions options) throws Exception;
} }
protected Map<String, List<CheckInterface<T>>> checking = null; protected Map<String, List<CheckInterface<T>>> checking = null;
@@ -66,20 +67,20 @@ public class CheckJPA<T> implements CheckFunctionInterface {
for (final Field field : this.clazz.getFields()) { for (final Field field : this.clazz.getFields()) {
final String fieldName = field.getName(); // AnnotationTools.getFieldName(field); final String fieldName = field.getName(); // AnnotationTools.getFieldName(field);
if (AnnotationTools.isPrimaryKey(field)) { if (AnnotationTools.isPrimaryKey(field)) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
throw new InputException(baseName + fieldName, throw new InputException(baseName + fieldName,
"This is a '@Id' (primaryKey) ==> can not be change"); "This is a '@Id' (primaryKey) ==> can not be change");
}); });
} }
if (AnnotationTools.getConstraintsNotNull(field)) { if (AnnotationTools.getConstraintsNotNull(field)) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
if (field.get(data) == null) { if (field.get(data) == null) {
throw new InputException(baseName + fieldName, "Can not be null"); throw new InputException(baseName + fieldName, "Can not be null");
} }
}); });
} }
if (AnnotationTools.isCreatedAtField(field) || AnnotationTools.isUpdateAtField(field)) { if (AnnotationTools.isCreatedAtField(field) || AnnotationTools.isUpdateAtField(field)) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
throw new InputException(baseName + fieldName, "It is forbidden to change this field"); throw new InputException(baseName + fieldName, "It is forbidden to change this field");
}); });
} }
@@ -88,7 +89,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
if (type == Long.class || type == long.class) { if (type == Long.class || type == long.class) {
final Long maxValue = AnnotationTools.getConstraintsMax(field); final Long maxValue = AnnotationTools.getConstraintsMax(field);
if (maxValue != null) { if (maxValue != null) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
@@ -101,7 +102,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
} }
final Long minValue = AnnotationTools.getConstraintsMin(field); final Long minValue = AnnotationTools.getConstraintsMin(field);
if (minValue != null) { if (minValue != null) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
@@ -114,12 +115,19 @@ public class CheckJPA<T> implements CheckFunctionInterface {
} }
final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field); final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field);
if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) { if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
} }
final long count = DataAccess.count(annotationManyToOne.targetEntity(), elem); final List<ConditionChecker> condCheckers = options.get(ConditionChecker.class);
long count = 0;
if (condCheckers.isEmpty()) {
count = DataAccess.count(annotationManyToOne.targetEntity(), elem);
} else {
count = DataAccess.count(annotationManyToOne.targetEntity(), elem,
condCheckers.get(0).toCondition());
}
if (count == 0) { if (count == 0) {
throw new InputException(baseName + fieldName, throw new InputException(baseName + fieldName,
"Foreign element does not exist in the DB:" + elem); "Foreign element does not exist in the DB:" + elem);
@@ -131,7 +139,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
final Long maxValueRoot = AnnotationTools.getConstraintsMax(field); final Long maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) { if (maxValueRoot != null) {
final int maxValue = maxValueRoot.intValue(); final int maxValue = maxValueRoot.intValue();
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
@@ -145,7 +153,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
final Long minValueRoot = AnnotationTools.getConstraintsMin(field); final Long minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) { if (minValueRoot != null) {
final int minValue = minValueRoot.intValue(); final int minValue = minValueRoot.intValue();
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
@@ -158,7 +166,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
} }
final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field); final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field);
if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) { if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
@@ -173,7 +181,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
} else if (type == UUID.class) { } else if (type == UUID.class) {
final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field); final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field);
if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) { if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
@@ -191,7 +199,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
final Long maxValueRoot = AnnotationTools.getConstraintsMax(field); final Long maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) { if (maxValueRoot != null) {
final float maxValue = maxValueRoot.floatValue(); final float maxValue = maxValueRoot.floatValue();
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
@@ -205,7 +213,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
final Long minValueRoot = AnnotationTools.getConstraintsMin(field); final Long minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) { if (minValueRoot != null) {
final float minValue = minValueRoot.floatValue(); final float minValue = minValueRoot.floatValue();
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
@@ -220,7 +228,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
final Long maxValueRoot = AnnotationTools.getConstraintsMax(field); final Long maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) { if (maxValueRoot != null) {
final double maxValue = maxValueRoot.doubleValue(); final double maxValue = maxValueRoot.doubleValue();
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
@@ -234,7 +242,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
final Long minValueRoot = AnnotationTools.getConstraintsMin(field); final Long minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) { if (minValueRoot != null) {
final double minValue = minValueRoot.doubleValue(); final double minValue = minValueRoot.doubleValue();
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
@@ -254,7 +262,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
} else if (type == String.class) { } else if (type == String.class) {
final int maxSizeString = AnnotationTools.getLimitSize(field); final int maxSizeString = AnnotationTools.getLimitSize(field);
if (maxSizeString > 0) { if (maxSizeString > 0) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
@@ -268,7 +276,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
} }
final Size limitSize = AnnotationTools.getConstraintsSize(field); final Size limitSize = AnnotationTools.getConstraintsSize(field);
if (limitSize != null) { if (limitSize != null) {
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
@@ -280,14 +288,14 @@ public class CheckJPA<T> implements CheckFunctionInterface {
} }
if (elemTyped.length() < limitSize.min()) { if (elemTyped.length() < limitSize.min()) {
throw new InputException(baseName + fieldName, throw new InputException(baseName + fieldName,
"Too small size (constraints) must be >= " + limitSize.max()); "Too small size (constraints) must be >= " + limitSize.min());
} }
}); });
} }
final String patternString = AnnotationTools.getConstraintsPattern(field); final String patternString = AnnotationTools.getConstraintsPattern(field);
if (patternString != null) { if (patternString != null) {
final Pattern pattern = Pattern.compile(patternString); final Pattern pattern = Pattern.compile(patternString);
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object elem = field.get(data); final Object elem = field.get(data);
if (elem == null) { if (elem == null) {
return; return;
@@ -306,8 +314,8 @@ public class CheckJPA<T> implements CheckFunctionInterface {
// Here if we have an error it crash at start and no new instance after creation... // Here if we have an error it crash at start and no new instance after creation...
final CheckFunctionInterface instance = jsonAnnotation.checker().getDeclaredConstructor() final CheckFunctionInterface instance = jsonAnnotation.checker().getDeclaredConstructor()
.newInstance(); .newInstance();
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
instance.checkAll(baseName + fieldName + ".", field.get(data)); instance.checkAll(baseName + fieldName + ".", field.get(data), options);
}); });
} }
} else if (type.isEnum()) { } else if (type.isEnum()) {
@@ -316,9 +324,17 @@ public class CheckJPA<T> implements CheckFunctionInterface {
// keep this is last ==> take more time... // keep this is last ==> take more time...
if (AnnotationTools.isUnique(field)) { if (AnnotationTools.isUnique(field)) {
// Create the request ... // Create the request ...
add(fieldName, (final String baseName, final T data) -> { add(fieldName, (final String baseName, final T data, final QueryOptions options) -> {
final Object other = DataAccess.getWhere(this.clazz, final List<ConditionChecker> condCheckers = options.get(ConditionChecker.class);
new Condition(new QueryCondition(fieldName, "==", field.get(data)))); Object other = null;
if (condCheckers.isEmpty()) {
other = DataAccess.getWhere(this.clazz,
new Condition(new QueryCondition(fieldName, "==", field.get(data))));
} else {
other = DataAccess.getWhere(this.clazz,
new Condition(new QueryCondition(fieldName, "==", field.get(data))),
condCheckers.get(0).toCondition());
}
if (other != null) { if (other != null) {
throw new InputException(baseName + fieldName, "Name already exist in the DB"); throw new InputException(baseName + fieldName, "Name already exist in the DB");
} }
@@ -333,7 +349,11 @@ public class CheckJPA<T> implements CheckFunctionInterface {
} }
@Override @Override
public void check(final String baseName, final Object data, final List<String> filterValue) throws Exception { public void check(
final String baseName,
final Object data,
final List<String> filterValue,
final QueryOptions options) throws Exception {
if (this.checking == null) { if (this.checking == null) {
initialize(); initialize();
} }
@@ -348,13 +368,13 @@ public class CheckJPA<T> implements CheckFunctionInterface {
continue; continue;
} }
for (final CheckInterface<T> action : actions) { for (final CheckInterface<T> action : actions) {
action.check(baseName, dataCasted); action.check(baseName, dataCasted, options);
} }
} }
checkTyped(dataCasted, filterValue); checkTyped(dataCasted, filterValue, options);
} }
public void checkTyped(final T data, final List<String> filterValue) throws Exception { public void checkTyped(final T data, final List<String> filterValue, final QueryOptions options) throws Exception {
// nothing to do ... // nothing to do ...
} }
} }

View File

@@ -0,0 +1,21 @@
package org.kar.archidata.dataAccess.options;
import org.kar.archidata.dataAccess.QueryItem;
/** Condition model apply to the check models. */
public class ConditionChecker extends QueryOption {
public final QueryItem condition;
public ConditionChecker(final QueryItem items) {
this.condition = items;
}
public ConditionChecker() {
this.condition = null;
}
public Condition toCondition() {
return new Condition(this.condition);
}
}

View File

@@ -55,7 +55,7 @@ public class TsApiGeneration {
if (tsModel.nativeType != DefinedPosition.NATIVE) { if (tsModel.nativeType != DefinedPosition.NATIVE) {
imports.add(model); imports.add(model);
} }
if (tsModel.nativeType == DefinedPosition.BASIC) { if (tsModel.nativeType != DefinedPosition.NORMAL) {
return tsModel.tsTypeName; return tsModel.tsTypeName;
} }
if (writeMode) { if (writeMode) {
@@ -348,11 +348,11 @@ public class TsApiGeneration {
data.append(", is"); data.append(", is");
data.append(returnModelNameIfComplex); data.append(returnModelNameIfComplex);
} else { } else {
final String returnType = generateClassModelsTypescript(interfaceElement.returnTypes, tsGroup, imports, final TsClassElement retType = tsGroup.find(interfaceElement.returnTypes.get(0));
false); if (retType.tsCheckType != null) {
if (!"void".equals(returnType)) { data.append(", ");
data.append(", is"); data.append(retType.tsCheckType);
data.append(returnType); imports.add(interfaceElement.returnTypes.get(0));
} }
} }
data.append(");"); data.append(");");
@@ -393,7 +393,9 @@ public class TsApiGeneration {
if (tsModel.nativeType == DefinedPosition.NATIVE) { if (tsModel.nativeType == DefinedPosition.NATIVE) {
continue; continue;
} }
finalImportList.add("is" + tsModel.tsTypeName); if (tsModel.tsCheckType != null) {
finalImportList.add(tsModel.tsCheckType);
}
} }
for (final ClassModel model : zodImports) { for (final ClassModel model : zodImports) {
final TsClassElement tsModel = tsGroup.find(model); final TsClassElement tsModel = tsGroup.find(model);

View File

@@ -17,6 +17,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import org.glassfish.jersey.media.multipart.ContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.kar.archidata.catcher.RestErrorResponse; import org.kar.archidata.catcher.RestErrorResponse;
import org.kar.archidata.externalRestApi.TsClassElement.DefinedPosition; import org.kar.archidata.externalRestApi.TsClassElement.DefinedPosition;
import org.kar.archidata.externalRestApi.model.ApiGroupModel; import org.kar.archidata.externalRestApi.model.ApiGroupModel;
@@ -128,7 +130,8 @@ public class TsGenerateApi {
tsModels.add( tsModels.add(
new TsClassElement(models, "zod.string()", "string", null, "zod.string()", DefinedPosition.NATIVE)); new TsClassElement(models, "zod.string()", "string", null, "zod.string()", DefinedPosition.NATIVE));
} }
models = api.getCompatibleModels(List.of(InputStream.class)); models = api.getCompatibleModels(
List.of(InputStream.class, FormDataContentDisposition.class, ContentDisposition.class));
if (models != null) { if (models != null) {
tsModels.add(new TsClassElement(models, "z.instanceof(File)", "File", null, "z.instanceof(File)", tsModels.add(new TsClassElement(models, "z.instanceof(File)", "File", null, "z.instanceof(File)",
DefinedPosition.NATIVE)); DefinedPosition.NATIVE));

View File

@@ -147,11 +147,17 @@ public class ApiModel {
final String queryParam = ApiTool.apiAnnotationGetQueryParam(parameter); final String queryParam = ApiTool.apiAnnotationGetQueryParam(parameter);
final String formDataParam = ApiTool.apiAnnotationGetFormDataParam(parameter); final String formDataParam = ApiTool.apiAnnotationGetFormDataParam(parameter);
if (queryParam != null) { if (queryParam != null) {
this.queries.put(queryParam, parameterModel); if (!this.queries.containsKey(queryParam)) {
this.queries.put(queryParam, parameterModel);
}
} else if (pathParam != null) { } else if (pathParam != null) {
this.parameters.put(pathParam, parameterModel); if (!this.parameters.containsKey(pathParam)) {
this.parameters.put(pathParam, parameterModel);
}
} else if (formDataParam != null) { } else if (formDataParam != null) {
this.multiPartParameters.put(formDataParam, parameterModel); if (!this.multiPartParameters.containsKey(formDataParam)) {
this.multiPartParameters.put(formDataParam, parameterModel);
}
} else { } else {
this.unnamedElement.addAll(parameterModel); this.unnamedElement.addAll(parameterModel);
} }

View File

@@ -4,25 +4,25 @@
* @license MPL-2 * @license MPL-2
*/ */
import { RestErrorResponse } from "./model" import { RestErrorResponse } from "./model";
export enum HTTPRequestModel { export enum HTTPRequestModel {
DELETE = 'DELETE', DELETE = "DELETE",
GET = 'GET', GET = "GET",
PATCH = 'PATCH', PATCH = "PATCH",
POST = 'POST', POST = "POST",
PUT = 'PUT', PUT = "PUT",
} }
export enum HTTPMimeType { export enum HTTPMimeType {
ALL = '*/*', ALL = "*/*",
CSV = 'text/csv', CSV = "text/csv",
IMAGE = 'image/*', IMAGE = "image/*",
IMAGE_JPEG = 'image/jpeg', IMAGE_JPEG = "image/jpeg",
IMAGE_PNG = 'image/png', IMAGE_PNG = "image/png",
JSON = 'application/json', JSON = "application/json",
MULTIPART = 'multipart/form-data', MULTIPART = "multipart/form-data",
OCTET_STREAM = 'application/octet-stream', OCTET_STREAM = "application/octet-stream",
TEXT_PLAIN = 'text/plain', TEXT_PLAIN = "text/plain",
} }
export interface RESTConfig { export interface RESTConfig {
@@ -58,52 +58,61 @@ function isNullOrUndefined(data: any): data is undefined | null {
export type ProgressCallback = (count: number, total: number) => void; export type ProgressCallback = (count: number, total: number) => void;
export interface RESTAbort { export interface RESTAbort {
abort?: () => boolean abort?: () => boolean;
} }
// Rest generic callback have a basic model to upload and download advancement. // Rest generic callback have a basic model to upload and download advancement.
export interface RESTCallbacks { export interface RESTCallbacks {
progressUpload?: ProgressCallback, progressUpload?: ProgressCallback;
progressDownload?: ProgressCallback, progressDownload?: ProgressCallback;
abortHandle?: RESTAbort, abortHandle?: RESTAbort;
}; }
export interface RESTRequestType { export interface RESTRequestType {
restModel: RESTModel, restModel: RESTModel;
restConfig: RESTConfig, restConfig: RESTConfig;
data?: any, data?: any;
params?: object, params?: object;
queries?: object, queries?: object;
callback?: RESTCallbacks, callback?: RESTCallbacks;
}; }
function replaceAll(input, searchValue, replaceValue) { function replaceAll(input, searchValue, replaceValue) {
return input.split(searchValue).join(replaceValue); return input.split(searchValue).join(replaceValue);
} }
function removeTrailingSlashes(input: string): string { function removeTrailingSlashes(input: string): string {
if (isNullOrUndefined(input)) { if (isNullOrUndefined(input)) {
return "undefined"; return "undefined";
} }
return input.replace(/\/+$/, ''); return input.replace(/\/+$/, "");
} }
function removeLeadingSlashes(input: string): string { function removeLeadingSlashes(input: string): string {
if (isNullOrUndefined(input)) { if (isNullOrUndefined(input)) {
return ""; return "";
} }
return input.replace(/^\/+/, ''); return input.replace(/^\/+/, "");
} }
export function RESTUrl({ restModel, restConfig, params, queries }: RESTRequestType): string { export function RESTUrl({
restModel,
restConfig,
params,
queries,
}: RESTRequestType): string {
// Create the URL PATH: // Create the URL PATH:
let generateUrl = `${removeTrailingSlashes(restConfig.server)}/${removeLeadingSlashes(restModel.endPoint)}`; let generateUrl = `${removeTrailingSlashes(
restConfig.server
)}/${removeLeadingSlashes(restModel.endPoint)}`;
if (params !== undefined) { if (params !== undefined) {
for (let key of Object.keys(params)) { for (let key of Object.keys(params)) {
generateUrl = replaceAll(generateUrl, `{${key}}`, `${params[key]}`); generateUrl = replaceAll(generateUrl, `{${key}}`, `${params[key]}`);
} }
} }
if (queries === undefined && (restConfig.token === undefined || restModel.tokenInUrl !== true)) { if (
queries === undefined &&
(restConfig.token === undefined || restModel.tokenInUrl !== true)
) {
return generateUrl; return generateUrl;
} }
const searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
@@ -111,8 +120,8 @@ export function RESTUrl({ restModel, restConfig, params, queries }: RESTRequestT
for (let key of Object.keys(queries)) { for (let key of Object.keys(queries)) {
const value = queries[key]; const value = queries[key];
if (Array.isArray(value)) { if (Array.isArray(value)) {
for (let iii = 0; iii < value.length; iii++) { for (const element of value) {
searchParams.append(`${key}`, `${value[iii]}`); searchParams.append(`${key}`, `${element}`);
} }
} else { } else {
searchParams.append(`${key}`, `${value}`); searchParams.append(`${key}`, `${value}`);
@@ -120,36 +129,43 @@ export function RESTUrl({ restModel, restConfig, params, queries }: RESTRequestT
} }
} }
if (restConfig.token !== undefined && restModel.tokenInUrl === true) { if (restConfig.token !== undefined && restModel.tokenInUrl === true) {
searchParams.append('Authorization', `Bearer ${restConfig.token}`); searchParams.append("Authorization", `Bearer ${restConfig.token}`);
} }
return generateUrl + "?" + searchParams.toString(); return generateUrl + "?" + searchParams.toString();
} }
export function fetchProgress(
export function fetchProgress(generateUrl: string, { method, headers, body }: { generateUrl: string,
method: HTTPRequestModel, {
headers: any, method,
body: any, headers,
}, { progressUpload, progressDownload, abortHandle }: RESTCallbacks): Promise<Response> { body,
const xhr = { }: {
io: new XMLHttpRequest() method: HTTPRequestModel;
} headers: any;
body: any;
},
{ progressUpload, progressDownload, abortHandle }: RESTCallbacks
): Promise<Response> {
const xhr: {
io?: XMLHttpRequest;
} = {
io: new XMLHttpRequest(),
};
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Stream the upload progress // Stream the upload progress
if (progressUpload) { if (progressUpload) {
xhr.io.upload.addEventListener("progress", (dataEvent) => { xhr.io?.upload.addEventListener("progress", (dataEvent) => {
if (dataEvent.lengthComputable) { if (dataEvent.lengthComputable) {
//console.log(` ==> has a progress event: ${dataEvent.loaded} / ${dataEvent.total}`);
progressUpload(dataEvent.loaded, dataEvent.total); progressUpload(dataEvent.loaded, dataEvent.total);
} }
}); });
} }
// Stream the download progress // Stream the download progress
if (progressDownload) { if (progressDownload) {
xhr.io.addEventListener("progress", (dataEvent) => { xhr.io?.addEventListener("progress", (dataEvent) => {
if (dataEvent.lengthComputable) { if (dataEvent.lengthComputable) {
//console.log(` ==> download progress:: ${dataEvent.loaded} / ${dataEvent.total}`); progressDownload(dataEvent.loaded, dataEvent.total);
progressUpload(dataEvent.loaded, dataEvent.total);
} }
}); });
} }
@@ -160,38 +176,43 @@ export function fetchProgress(generateUrl: string, { method, headers, body }: {
xhr.io.abort(); xhr.io.abort();
return true; return true;
} }
console.log(`Request abort (FAIL) on the XMLHttpRequest: ${generateUrl}`); console.log(
`Request abort (FAIL) on the XMLHttpRequest: ${generateUrl}`
);
return false; return false;
} };
} }
// Check if we have an internal Fail: // Check if we have an internal Fail:
xhr.io.addEventListener('error', () => { xhr.io?.addEventListener("error", () => {
xhr.io = undefined; xhr.io = undefined;
reject(new TypeError('Failed to fetch')) reject(new TypeError("Failed to fetch"));
}); });
// Capture the end of the stream // Capture the end of the stream
xhr.io.addEventListener("loadend", () => { xhr.io?.addEventListener("loadend", () => {
if (xhr.io.readyState !== XMLHttpRequest.DONE) { if (xhr.io?.readyState !== XMLHttpRequest.DONE) {
//console.log(` ==> READY state`);
return; return;
} }
if (xhr.io.status === 0) { if (xhr.io?.status === 0) {
//the stream has been aborted //the stream has been aborted
reject(new TypeError('Fetch has been aborted')); reject(new TypeError("Fetch has been aborted"));
return; return;
} }
// Stream is ended, transform in a generic response: // Stream is ended, transform in a generic response:
const response = new Response(xhr.io.response, { const response = new Response(xhr.io.response, {
status: xhr.io.status, status: xhr.io.status,
statusText: xhr.io.statusText statusText: xhr.io.statusText,
}); });
const headersArray = replaceAll(xhr.io.getAllResponseHeaders().trim(), "\r\n", "\n").split('\n'); const headersArray = replaceAll(
xhr.io.getAllResponseHeaders().trim(),
"\r\n",
"\n"
).split("\n");
headersArray.forEach(function (header) { headersArray.forEach(function (header) {
const firstColonIndex = header.indexOf(':'); const firstColonIndex = header.indexOf(":");
if (firstColonIndex !== -1) { if (firstColonIndex !== -1) {
var key = header.substring(0, firstColonIndex).trim(); const key = header.substring(0, firstColonIndex).trim();
var value = header.substring(firstColonIndex + 1).trim(); const value = header.substring(firstColonIndex + 1).trim();
response.headers.set(key, value); response.headers.set(key, value);
} else { } else {
response.headers.set(header, ""); response.headers.set(header, "");
@@ -200,31 +221,38 @@ export function fetchProgress(generateUrl: string, { method, headers, body }: {
xhr.io = undefined; xhr.io = undefined;
resolve(response); resolve(response);
}); });
xhr.io.open(method, generateUrl, true); xhr.io?.open(method, generateUrl, true);
if (!isNullOrUndefined(headers)) { if (!isNullOrUndefined(headers)) {
for (const [key, value] of Object.entries(headers)) { for (const [key, value] of Object.entries(headers)) {
xhr.io.setRequestHeader(key, value as string); xhr.io?.setRequestHeader(key, value as string);
} }
} }
xhr.io.send(body); xhr.io?.send(body);
}); });
} }
export function RESTRequest({ restModel, restConfig, data, params, queries, callback }: RESTRequestType): Promise<ModelResponseHttp> { export function RESTRequest({
restModel,
restConfig,
data,
params,
queries,
callback,
}: RESTRequestType): Promise<ModelResponseHttp> {
// Create the URL PATH: // Create the URL PATH:
let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries }); let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries });
let headers: any = {}; let headers: any = {};
if (restConfig.token !== undefined && restModel.tokenInUrl !== true) { if (restConfig.token !== undefined && restModel.tokenInUrl !== true) {
headers['Authorization'] = `Bearer ${restConfig.token}`; headers["Authorization"] = `Bearer ${restConfig.token}`;
} }
if (restModel.accept !== undefined) { if (restModel.accept !== undefined) {
headers['Accept'] = restModel.accept; headers["Accept"] = restModel.accept;
} }
if (restModel.requestType !== HTTPRequestModel.GET) { if (restModel.requestType !== HTTPRequestModel.GET) {
// if Get we have not a content type, the body is empty // if Get we have not a content type, the body is empty
if (restModel.contentType !== HTTPMimeType.MULTIPART) { if (restModel.contentType !== HTTPMimeType.MULTIPART) {
// special case of multi-part ==> no content type otherwise the browser does not set the ";bundary=--****" // special case of multi-part ==> no content type otherwise the browser does not set the ";bundary=--****"
headers['Content-Type'] = restModel.contentType; headers["Content-Type"] = restModel.contentType;
} }
} }
let body = data; let body = data;
@@ -235,14 +263,16 @@ export function RESTRequest({ restModel, restConfig, data, params, queries, call
for (const name in data) { for (const name in data) {
formData.append(name, data[name]); formData.append(name, data[name]);
} }
body = formData body = formData;
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let action: undefined | Promise<Response> = undefined; let action: undefined | Promise<Response> = undefined;
if (isNullOrUndefined(callback) if (
|| (isNullOrUndefined(callback.progressDownload) isNullOrUndefined(callback) ||
&& isNullOrUndefined(callback.progressUpload) (isNullOrUndefined(callback.progressDownload) &&
&& isNullOrUndefined(callback.abortHandle))) { isNullOrUndefined(callback.progressUpload) &&
isNullOrUndefined(callback.abortHandle))
) {
// No information needed: call the generic fetch interface // No information needed: call the generic fetch interface
action = fetch(generateUrl, { action = fetch(generateUrl, {
method: restModel.requestType, method: restModel.requestType,
@@ -251,92 +281,112 @@ export function RESTRequest({ restModel, restConfig, data, params, queries, call
}); });
} else { } else {
// need progression information: call old fetch model (XMLHttpRequest) that permit to keep % upload and % download for HTTP1.x // need progression information: call old fetch model (XMLHttpRequest) that permit to keep % upload and % download for HTTP1.x
action = fetchProgress(generateUrl, { action = fetchProgress(
method: restModel.requestType ?? HTTPRequestModel.GET, generateUrl,
headers, {
body, method: restModel.requestType ?? HTTPRequestModel.GET,
}, callback); headers,
body,
},
callback
);
} }
action.then((response: Response) => { action
if (response.status >= 200 && response.status <= 299) { .then((response: Response) => {
const contentType = response.headers.get('Content-Type'); if (response.status >= 200 && response.status <= 299) {
if (!isNullOrUndefined(restModel.accept) && restModel.accept !== contentType) { const contentType = response.headers.get("Content-Type");
reject({ if (
time: Date().toString(), !isNullOrUndefined(restModel.accept) &&
status: 901, restModel.accept !== contentType
error: `REST check wrong type: ${restModel.accept} != ${contentType}`, ) {
statusMessage: "Fetch error", reject({
message: "rest-tools.ts Wrong type in the message return type" name: "Model accept type incompatible",
} as RestErrorResponse); time: Date().toString(),
} else if (contentType === HTTPMimeType.JSON) { status: 901,
response error: `REST check wrong type: ${restModel.accept} != ${contentType}`,
.json() statusMessage: "Fetch error",
.then((value: any) => { message: "rest-tools.ts Wrong type in the message return type",
//console.log(`RECEIVE ==> ${response.status}=${ JSON.stringify(value, null, 2)}`); } as RestErrorResponse);
resolve({ status: response.status, data: value }); } else if (contentType === HTTPMimeType.JSON) {
}) response
.catch((reason: any) => { .json()
reject({ .then((value: any) => {
time: Date().toString(), resolve({ status: response.status, data: value });
status: 902, })
error: `REST parse json fail: ${reason}`, .catch((reason: any) => {
statusMessage: "Fetch parse error", reject({
message: "rest-tools.ts Wrong message model to parse" name: "API serialization error",
} as RestErrorResponse); time: Date().toString(),
}); status: 902,
error: `REST parse json fail: ${reason}`,
statusMessage: "Fetch parse error",
message: "rest-tools.ts Wrong message model to parse",
} as RestErrorResponse);
});
} else {
resolve({ status: response.status, data: response.body });
}
} else { } else {
resolve({ status: response.status, data: response.body }); reject({
name: "REST return no OK status",
time: Date().toString(),
status: response.status,
error: `${response.body}`,
statusMessage: "Fetch code error",
message: "rest-tools.ts Wrong return code",
} as RestErrorResponse);
} }
} else { })
.catch((error: any) => {
reject({ reject({
time: Date().toString(), name: "Request fail",
status: response.status, time: Date(),
error: `${response.body}`, status: 999,
statusMessage: "Fetch code error", error: error,
message: "rest-tools.ts Wrong return code" statusMessage: "Fetch catch error",
} as RestErrorResponse); message: "rest-tools.ts detect an error in the fetch request",
} });
}).catch((error: any) => {
reject({
time: Date(),
status: 999,
error: error,
statusMessage: "Fetch catch error",
message: "rest-tools.ts detect an error in the fetch request"
}); });
});
}); });
} }
export function RESTRequestJson<TYPE>(request: RESTRequestType, checker: (data: any) => data is TYPE): Promise<TYPE> { export function RESTRequestJson<TYPE>(
request: RESTRequestType,
checker?: (data: any) => data is TYPE
): Promise<TYPE> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
RESTRequest(request).then((value: ModelResponseHttp) => { RESTRequest(request)
if (isNullOrUndefined(checker)) { .then((value: ModelResponseHttp) => {
console.log(`Have no check of MODEL in API: ${RESTUrl(request)}`); if (isNullOrUndefined(checker)) {
resolve(value.data); console.log(`Have no check of MODEL in API: ${RESTUrl(request)}`);
} else if (checker(value.data)) { resolve(value.data);
resolve(value.data); } else if (checker === undefined || checker(value.data)) {
} else { resolve(value.data);
reject({ } else {
time: Date().toString(), reject({
status: 950, name: "Model check fail",
error: "REST Fail to verify the data", time: Date().toString(),
statusMessage: "API cast ERROR", status: 950,
message: "api.ts Check type as fail" error: "REST Fail to verify the data",
} as RestErrorResponse); statusMessage: "API cast ERROR",
} message: "api.ts Check type as fail",
}).catch((reason: RestErrorResponse) => { } as RestErrorResponse);
reject(reason); }
}); })
.catch((reason: RestErrorResponse) => {
reject(reason);
});
}); });
} }
export function RESTRequestVoid(request: RESTRequestType): Promise<void> { export function RESTRequestVoid(request: RESTRequestType): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
RESTRequest(request).then((value: ModelResponseHttp) => { RESTRequest(request)
resolve(); .then((value: ModelResponseHttp) => {
}).catch((reason: RestErrorResponse) => { resolve();
reject(reason); })
}); .catch((reason: RestErrorResponse) => {
reject(reason);
});
}); });
} }

View File

@@ -1 +1 @@
0.9.0 0.10.0