diff --git a/build.gradle b/build.gradle index f65b98e..f21ef61 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ dependencies { // json/yaml implementation 'org.json:json:20240303' implementation 'org.yaml:snakeyaml' - // databases + // databases // todo: remove and add via plugin-system runtimeOnly 'org.postgresql:postgresql' runtimeOnly 'org.xerial:sqlite-jdbc' // testing diff --git a/src/main/java/org/dynapi/dynapi/core/database/impl/postgresql/PostgreSQLMetaQueryGenerator.java b/src/main/java/org/dynapi/dynapi/core/database/impl/postgresql/PostgreSQLMetaQueryGenerator.java index 5b12710..04f285b 100644 --- a/src/main/java/org/dynapi/dynapi/core/database/impl/postgresql/PostgreSQLMetaQueryGenerator.java +++ b/src/main/java/org/dynapi/dynapi/core/database/impl/postgresql/PostgreSQLMetaQueryGenerator.java @@ -12,10 +12,10 @@ public String listSchemas() { Table pgNamespace = new Schema("pg_catalog").table("pg_namespace"); Field schemaName = pgNamespace.field("nspname").as("schema_name"); return new PostgreSQLQuery() - .from(pgNamespace) - .select(schemaName) - .where(schemaName.not_like("pg_%")) - .getSql(); + .from(pgNamespace) + .select(schemaName) + .where(schemaName.not_like("pg_%")) + .getSql(); } @Override diff --git a/src/main/java/org/dynapi/dynapi/core/database/impl/sqlite/SQLiteMetaQueryGenerator.java b/src/main/java/org/dynapi/dynapi/core/database/impl/sqlite/SQLiteMetaQueryGenerator.java index 37c9654..42e3982 100644 --- a/src/main/java/org/dynapi/dynapi/core/database/impl/sqlite/SQLiteMetaQueryGenerator.java +++ b/src/main/java/org/dynapi/dynapi/core/database/impl/sqlite/SQLiteMetaQueryGenerator.java @@ -51,6 +51,7 @@ public String listTablesOfSchema(String schemaName) { @Override public String listColumnsOfTable(String schemaName, String tableName) { + // todo: $schema.pragme_table_info($tableName) Selectable tableInfo = new TableValuedFunction("pragma_table_info", tableName); return new SQLiteQuery() .from(tableInfo) diff --git a/src/main/java/org/dynapi/dynapi/core/database/interfaces/Database.java b/src/main/java/org/dynapi/dynapi/core/database/interfaces/Database.java index 2c42eb7..23b137b 100644 --- a/src/main/java/org/dynapi/dynapi/core/database/interfaces/Database.java +++ b/src/main/java/org/dynapi/dynapi/core/database/interfaces/Database.java @@ -13,6 +13,7 @@ public interface Database { /** * note: it's recommended to use the getter instead + * * @return a dialect appropriate {@link MetaQueryGenerator} used to fetch table-information * @see #getSchemaNames() * @see #getTableNamesOfSchema(String) @@ -47,7 +48,7 @@ public interface Database { /** * @param schemaName schema of the table - * @param tableName table + * @param tableName table * @return column-information of the table */ List getColumnInformationOfTable(@NonNull String schemaName, @NonNull String tableName); @@ -62,14 +63,14 @@ public interface Database { /** * @param schemaName name of the schema of the table - * @param tableName name of the table + * @param tableName name of the table * @return that the table exists */ boolean existsTable(@NonNull String schemaName, @NonNull String tableName); /** - * @param schemaName name of the schema of the table - * @param tableName name of the table + * @param schemaName name of the schema of the table + * @param tableName name of the table * @param columnNames columns to verify * @return that all specified columns exist on the table */ @@ -78,8 +79,9 @@ public interface Database { /** * Utility-function used to validate that a table has all {@code columns}
* If {@code columns} contains {@code *} then it's directly marked as valid - * @param schemaName schema-name - * @param tableName table-name + * + * @param schemaName schema-name + * @param tableName table-name * @param columnNames columns to check again * @throws org.springframework.web.server.ResponseStatusException if one or more columns are not existing */ diff --git a/src/main/java/org/dynapi/dynapi/core/database/interfaces/MetaQueryGenerator.java b/src/main/java/org/dynapi/dynapi/core/database/interfaces/MetaQueryGenerator.java index e97916b..e44a498 100644 --- a/src/main/java/org/dynapi/dynapi/core/database/interfaces/MetaQueryGenerator.java +++ b/src/main/java/org/dynapi/dynapi/core/database/interfaces/MetaQueryGenerator.java @@ -16,6 +16,7 @@ public interface MetaQueryGenerator { /** * returns an SQL-Query that returns the available tables of a specific schema
* format: {@code {'schema_name', 'table_name'}} + * * @param schemaName database schema */ String listTablesOfSchema(String schemaName); @@ -23,8 +24,9 @@ public interface MetaQueryGenerator { /** * returns an SQL-Query that returns the column-information of a specific table of a specific schema
* format: {@code {'column_name', 'type'}} + * * @param schemaName database schema - * @param tableName table of {@code schemaName} + * @param tableName table of {@code schemaName} */ String listColumnsOfTable(String schemaName, String tableName); } diff --git a/src/main/java/org/dynapi/dynapi/core/datatypes/DataType.java b/src/main/java/org/dynapi/dynapi/core/datatypes/DataType.java new file mode 100644 index 0000000..4da88af --- /dev/null +++ b/src/main/java/org/dynapi/dynapi/core/datatypes/DataType.java @@ -0,0 +1,23 @@ +package org.dynapi.dynapi.core.datatypes; + +import com.fasterxml.jackson.databind.JsonNode; +import org.dynapi.openapispec.core.schema.Schema; + +public interface DataType { + /** + * @return schema for /openapi + */ + Schema getOpenApiSchema(); + + /** + * @param node node to parse + * @return parsed value + */ + Object parseJsonNode(JsonNode node); + + /** + * @param object saved or so object + * @return json-save object + */ + Object formatObject(Object object); +} diff --git a/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeArray.java b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeArray.java new file mode 100644 index 0000000..5778913 --- /dev/null +++ b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeArray.java @@ -0,0 +1,52 @@ +package org.dynapi.dynapi.core.datatypes.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import org.dynapi.dynapi.core.datatypes.DataType; +import org.dynapi.openapispec.core.schema.Schema; +import org.dynapi.openapispec.core.schema.TArray; + +public class DataTypeArray implements DataType { + private final DataType subType; + + public DataTypeArray(DataType subType) { + this.subType = subType; + } + + /** + * @return schema for /openapi + */ + @Override + public Schema getOpenApiSchema() { + return new TArray(subType.getOpenApiSchema()); + } + + /** + * @param node node to parse + * @return parsed value + */ + @Override + public Object[] parseJsonNode(JsonNode node) { + assert node.isArray(); + Object[] array = new Object[node.size()]; + for (int i = 0; i < node.size(); i++) { + JsonNode element = node.get(i); + array[i] = subType.parseJsonNode(element); + } + return array; + } + + /** + * @param object saved or so object + * @return json-save object + */ + @Override + public Object formatObject(Object object) { + if (object instanceof Object[] a) { + Object[] array = new Object[a.length]; + for (int i = 0; i < a.length; i++) + array[i] = subType.formatObject(a[i]); + return array; + } + throw new IllegalArgumentException("Object is not an array"); + } +} diff --git a/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeBlob.java b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeBlob.java new file mode 100644 index 0000000..63fa133 --- /dev/null +++ b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeBlob.java @@ -0,0 +1,41 @@ +package org.dynapi.dynapi.core.datatypes.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import org.dynapi.dynapi.core.datatypes.DataType; +import org.dynapi.openapispec.core.schema.Schema; +import org.dynapi.openapispec.core.schema.TString; + +import java.util.Base64; + +public class DataTypeBlob implements DataType { + /** + * @return schema for /openapi + */ + @Override + public Schema getOpenApiSchema() { + return new TString() + .format(TString.CommonFormats.BINARY); + } + + /** + * @param node node to parse + * @return parsed value + */ + @Override + public byte[] parseJsonNode(JsonNode node) { + assert node.isTextual(); + String base64String = node.textValue(); + return Base64.getDecoder().decode(base64String); + } + + /** + * @param object saved or so object + * @return json-save object + */ + @Override + public Object formatObject(Object object) { + if (object instanceof byte[] b) + return Base64.getEncoder().encodeToString(b); + throw new IllegalArgumentException("Object is not a blob"); + } +} diff --git a/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeBoolean.java b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeBoolean.java new file mode 100644 index 0000000..4615683 --- /dev/null +++ b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeBoolean.java @@ -0,0 +1,37 @@ +package org.dynapi.dynapi.core.datatypes.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import org.dynapi.dynapi.core.datatypes.DataType; +import org.dynapi.openapispec.core.schema.Schema; +import org.dynapi.openapispec.core.schema.TBoolean; + +public class DataTypeBoolean implements DataType { + /** + * @return schema for /openapi + */ + @Override + public Schema getOpenApiSchema() { + return new TBoolean(); + } + + /** + * @param node node to parse + * @return parsed value + */ + @Override + public Object parseJsonNode(JsonNode node) { + assert node.isBoolean(); + return node.booleanValue(); + } + + /** + * @param object saved or so object + * @return json-save object + */ + @Override + public Object formatObject(Object object) { + if (object instanceof Boolean b) + return b; + throw new IllegalArgumentException("Object is not a boolean"); + } +} diff --git a/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeDate.java b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeDate.java new file mode 100644 index 0000000..90e4a0a --- /dev/null +++ b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeDate.java @@ -0,0 +1,46 @@ +package org.dynapi.dynapi.core.datatypes.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import org.dynapi.dynapi.core.datatypes.DataType; +import org.dynapi.openapispec.core.schema.Schema; +import org.dynapi.openapispec.core.schema.TString; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public class DataTypeDate implements DataType { + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE; + + /** + * @return schema for /openapi + */ + @Override + public Schema getOpenApiSchema() { + return new TString() + .format(TString.CommonFormats.DATE); + } + + /** + * @param node node to parse + * @return parsed value + */ + @Override + public LocalDate parseJsonNode(JsonNode node) { + assert node.isTextual(); + return LocalDate.parse(node.textValue(), DATE_FORMATTER); + } + + /** + * @param object saved or so object + * @return json-save object + */ + @Override + public String formatObject(Object object) { + if (object instanceof LocalDateTime || object instanceof LocalDate || object instanceof Instant) + return DATE_FORMATTER.format((TemporalAccessor) object); + throw new IllegalArgumentException("Object is not a date"); + } +} diff --git a/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeDateTime.java b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeDateTime.java new file mode 100644 index 0000000..9d05dde --- /dev/null +++ b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeDateTime.java @@ -0,0 +1,46 @@ +package org.dynapi.dynapi.core.datatypes.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import org.dynapi.dynapi.core.datatypes.DataType; +import org.dynapi.openapispec.core.schema.Schema; +import org.dynapi.openapispec.core.schema.TString; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public class DataTypeDateTime implements DataType { + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + + /** + * @return schema for /openapi + */ + @Override + public Schema getOpenApiSchema() { + return new TString() + .format(TString.CommonFormats.DATETIME); + } + + /** + * @param node node to parse + * @return parsed value + */ + @Override + public LocalDateTime parseJsonNode(JsonNode node) { + assert node.isTextual(); + return LocalDateTime.parse(node.textValue(), DATE_FORMATTER); + } + + /** + * @param object saved or so object + * @return json-save object + */ + @Override + public String formatObject(Object object) { + if (object instanceof LocalDateTime || object instanceof LocalDate || object instanceof Instant) + return DATE_FORMATTER.format((TemporalAccessor) object); + throw new IllegalArgumentException("Object is not a date"); + } +} diff --git a/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeDecimal.java b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeDecimal.java new file mode 100644 index 0000000..ebad9f6 --- /dev/null +++ b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeDecimal.java @@ -0,0 +1,39 @@ +package org.dynapi.dynapi.core.datatypes.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import org.dynapi.dynapi.core.datatypes.DataType; +import org.dynapi.openapispec.core.schema.Schema; +import org.dynapi.openapispec.core.schema.TNumber; + +public class DataTypeDecimal implements DataType { + /** + * @return schema for /openapi + */ + @Override + public Schema getOpenApiSchema() { + return new TNumber(); + } + + /** + * @param node node to parse + * @return parsed value + */ + @Override + public Number parseJsonNode(JsonNode node) { + assert node.isFloatingPointNumber(); + return node.isDouble() ? node.doubleValue() : node.floatValue(); + } + + /** + * @param object saved or so object + * @return json-save object + */ + @Override + public Object formatObject(Object object) { + if (object instanceof Double d) + return d; + if (object instanceof Float f) + return f; + throw new IllegalArgumentException("Object is not a decimal"); + } +} diff --git a/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeInteger.java b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeInteger.java new file mode 100644 index 0000000..59c897b --- /dev/null +++ b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeInteger.java @@ -0,0 +1,39 @@ +package org.dynapi.dynapi.core.datatypes.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import org.dynapi.dynapi.core.datatypes.DataType; +import org.dynapi.openapispec.core.schema.Schema; +import org.dynapi.openapispec.core.schema.TInteger; + +public class DataTypeInteger implements DataType { + /** + * @return schema for /openapi + */ + @Override + public Schema getOpenApiSchema() { + return new TInteger(); + } + + /** + * @param node node to parse + * @return parsed value + */ + @Override + public Number parseJsonNode(JsonNode node) { + assert node.isIntegralNumber(); + return node.isLong() ? node.longValue() : node.intValue(); + } + + /** + * @param object saved or so object + * @return json-save object + */ + @Override + public Object formatObject(Object object) { + if (object instanceof Long l) + return l; + if (object instanceof Integer i) + return i; + throw new IllegalArgumentException("Object is not an integer"); + } +} diff --git a/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeJson.java b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeJson.java new file mode 100644 index 0000000..38966b7 --- /dev/null +++ b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeJson.java @@ -0,0 +1,36 @@ +package org.dynapi.dynapi.core.datatypes.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import org.dynapi.dynapi.core.datatypes.DataType; +import org.dynapi.openapispec.core.schema.Schema; +import org.dynapi.openapispec.core.schema.TObject; + +public class DataTypeJson implements DataType { + /** + * @return schema for /openapi + */ + @Override + public Schema getOpenApiSchema() { + return new TObject(); + } + + /** + * @param node node to parse + * @return parsed value + */ + @Override + public JsonNode parseJsonNode(JsonNode node) { + return node; + } + + /** + * @param object saved or so object + * @return json-save object + */ + @Override + public JsonNode formatObject(Object object) { + if (object instanceof JsonNode j) + return j; + throw new IllegalArgumentException("Object is not a JSON object"); + } +} diff --git a/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeString.java b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeString.java new file mode 100644 index 0000000..1bbd6bb --- /dev/null +++ b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeString.java @@ -0,0 +1,37 @@ +package org.dynapi.dynapi.core.datatypes.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import org.dynapi.dynapi.core.datatypes.DataType; +import org.dynapi.openapispec.core.schema.Schema; +import org.dynapi.openapispec.core.schema.TString; + +public class DataTypeString implements DataType { + /** + * @return schema for /openapi + */ + @Override + public Schema getOpenApiSchema() { + return new TString(); + } + + /** + * @param node node to parse + * @return parsed value + */ + @Override + public String parseJsonNode(JsonNode node) { + assert node.isTextual(); + return node.textValue(); + } + + /** + * @param object saved or so object + * @return json-save object + */ + @Override + public String formatObject(Object object) { + if (object instanceof String s) + return s; + throw new IllegalArgumentException("Object is not a string"); + } +} diff --git a/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeUUID.java b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeUUID.java new file mode 100644 index 0000000..ccee057 --- /dev/null +++ b/src/main/java/org/dynapi/dynapi/core/datatypes/impl/DataTypeUUID.java @@ -0,0 +1,41 @@ +package org.dynapi.dynapi.core.datatypes.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import org.dynapi.dynapi.core.datatypes.DataType; +import org.dynapi.openapispec.core.schema.Schema; +import org.dynapi.openapispec.core.schema.TString; + +import java.util.UUID; + +public class DataTypeUUID implements DataType { + /** + * @return schema for /openapi + */ + @Override + public Schema getOpenApiSchema() { + return new TString() + .format(TString.CommonFormats.UUID); + } + + /** + * @param node node to parse + * @return parsed value + */ + @Override + public Object parseJsonNode(JsonNode node) { + assert node.isTextual(); + String uuidString = node.asText(); + return UUID.fromString(uuidString); + } + + /** + * @param object saved or so object + * @return json-save object + */ + @Override + public String formatObject(Object object) { + if (object instanceof UUID u) + return u.toString(); + throw new IllegalArgumentException("Object is not a UUID"); + } +}