diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/GenerateOpenApiSpecAction.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/GenerateOpenApiSpecAction.java index 9c2f4b26..1ed8ee6c 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/GenerateOpenApiSpecAction.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/actions/GenerateOpenApiSpecAction.java @@ -26,10 +26,14 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import com.kingsrook.qqq.api.model.APIVersion; +import com.kingsrook.qqq.api.model.APIVersionRange; import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput; import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput; import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput; import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData; +import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData; +import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.api.model.openapi.Components; import com.kingsrook.qqq.api.model.openapi.Contact; import com.kingsrook.qqq.api.model.openapi.Content; @@ -54,9 +58,12 @@ import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper; import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.utils.JsonUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils; @@ -64,6 +71,7 @@ import com.kingsrook.qqq.backend.core.utils.YamlUtils; import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder; import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder; import io.javalin.http.HttpStatus; +import org.apache.commons.lang.BooleanUtils; /******************************************************************************* @@ -71,6 +79,9 @@ import io.javalin.http.HttpStatus; *******************************************************************************/ public class GenerateOpenApiSpecAction extends AbstractQActionFunction { + private static final QLogger LOG = QLogger.getLogger(GenerateOpenApiSpecAction.class); + + /******************************************************************************* ** @@ -101,7 +112,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction()); openAPI.setPaths(new LinkedHashMap<>()); - LinkedHashMap componentResponses = new LinkedHashMap<>(); + LinkedHashMap componentResponses = new LinkedHashMap<>(); LinkedHashMap componentSchemas = new LinkedHashMap<>(); LinkedHashMap securitySchemes = new LinkedHashMap<>(); openAPI.setComponents(new Components() @@ -119,6 +130,16 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields(); + if(BooleanUtils.isTrue(apiTableMetaData.getIsExcluded())) + { + LOG.debug("Omitting table [" + tableName + "] because its apiTableMetaData marks it as excluded"); + continue; + } + + APIVersionRange apiVersionRange = apiTableMetaData.getApiVersionRange(); + if(!apiVersionRange.includes(new APIVersion(version))) + { + LOG.debug("Omitting table [" + tableName + "] because its api version range [" + apiVersionRange + "] does not include this version [" + version + "]"); + continue; + } + + QBackendMetaData tableBackend = qInstance.getBackendForTable(tableName); + boolean queryCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_QUERY); + boolean getCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_GET); + boolean updateCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_UPDATE); + boolean deleteCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_DELETE); + boolean insertCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_INSERT); + boolean countCapability = table.isCapabilityEnabled(tableBackend, Capability.TABLE_COUNT); + + if(!queryCapability && !getCapability && !updateCapability && !deleteCapability && !insertCapability) + { + LOG.debug("Omitting table [" + tableName + "] because it does not have any supported capabilities"); + continue; + } + + String tableApiName = StringUtils.hasContent(apiTableMetaData.getApiTableName()) ? apiTableMetaData.getApiTableName() : tableName; + String tableApiNameUcFirst = StringUtils.ucFirst(tableApiName); + String tableLabel = table.getLabel(); + String primaryKeyName = table.getPrimaryKeyField(); + QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField()); + String primaryKeyLabel = primaryKeyField.getLabel(); + List tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields(); + + ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(primaryKeyField); + String primaryKeyApiName = (apiFieldMetaData != null && StringUtils.hasContent(apiFieldMetaData.getApiFieldName())) ? apiFieldMetaData.getApiFieldName() : primaryKeyName; String tableReadPermissionName = PermissionsHelper.getTablePermissionName(tableName, TablePermissionSubType.READ); if(StringUtils.hasContent(tableReadPermissionName)) @@ -193,7 +253,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction tableFieldsWithoutPrimaryKey = new LinkedHashMap<>(); - componentSchemas.put(tableName + "WithoutPrimaryKey", new Schema() + componentSchemas.put(tableApiName + "WithoutPrimaryKey", new Schema() .withType("object") .withProperties(tableFieldsWithoutPrimaryKey)); @@ -211,18 +271,18 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction>> getSecurity(String permissionName) + { + return ListBuilder.of( + MapBuilder.of("OAuth2", List.of(permissionName)), + MapBuilder.of("bearerAuth", List.of(permissionName)), + MapBuilder.of("basicAuth", List.of(permissionName)) + ); + } + + + /******************************************************************************* ** *******************************************************************************/ @SuppressWarnings("checkstyle:indentation") - private Response buildMultiStatusResponse(String tableLabel, String primaryKeyName, QFieldMetaData primaryKeyField, String method) + private Response buildMultiStatusResponse(String tableLabel, String primaryKeyApiName, QFieldMetaData primaryKeyField, String method) { List example = switch(method.toLowerCase()) { @@ -485,7 +574,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction buildOrderByExamples(String primaryKeyName, List tableApiFields) + private Map buildOrderByExamples(String primaryKeyApiName, List tableApiFields) { Map rs = new LinkedHashMap<>(); @@ -547,7 +636,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction buildStandardErrorResponses() { return MapBuilder.of( - HttpStatus.BAD_REQUEST.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.BAD_REQUEST.getCode()), - HttpStatus.UNAUTHORIZED.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.UNAUTHORIZED.getCode()), - HttpStatus.FORBIDDEN.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.FORBIDDEN.getCode()), - HttpStatus.INTERNAL_SERVER_ERROR.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.INTERNAL_SERVER_ERROR.getCode()) + HttpStatus.BAD_REQUEST.getCode(), new Response().withRef("#/components/responses/error" + HttpStatus.BAD_REQUEST.getCode()), + HttpStatus.UNAUTHORIZED.getCode(), new Response().withRef("#/components/responses/error" + HttpStatus.UNAUTHORIZED.getCode()), + HttpStatus.FORBIDDEN.getCode(), new Response().withRef("#/components/responses/error" + HttpStatus.FORBIDDEN.getCode()), + HttpStatus.INTERNAL_SERVER_ERROR.getCode(), new Response().withRef("#/components/responses/error" + HttpStatus.INTERNAL_SERVER_ERROR.getCode()) ); } diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java index 0cf24fe6..d605eda8 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java @@ -34,7 +34,6 @@ import java.util.Set; import com.kingsrook.qqq.api.actions.GenerateOpenApiSpecAction; import com.kingsrook.qqq.api.actions.QRecordApiAdapter; import com.kingsrook.qqq.api.model.APIVersion; -import com.kingsrook.qqq.api.model.APIVersionRange; import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput; import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput; import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData; @@ -88,6 +87,7 @@ import io.javalin.apibuilder.ApiBuilder; import io.javalin.apibuilder.EndpointGroup; import io.javalin.http.ContentType; import io.javalin.http.Context; +import org.apache.commons.lang.BooleanUtils; import org.eclipse.jetty.http.HttpStatus; import org.json.JSONArray; import org.json.JSONObject; @@ -102,7 +102,9 @@ public class QJavalinApiHandler { private static final QLogger LOG = QLogger.getLogger(QJavalinApiHandler.class); - static QInstance qInstance; + private static QInstance qInstance; + + private static Map> tableApiNameMap = new HashMap<>(); @@ -198,35 +200,19 @@ public class QJavalinApiHandler - /******************************************************************************* - ** - *******************************************************************************/ - private static APIVersionRange getApiVersionRange(QTableMetaData table) - { - ApiTableMetaData middlewareMetaData = ApiTableMetaData.of(table); - if(middlewareMetaData != null && middlewareMetaData.getInitialVersion() != null) - { - return (APIVersionRange.afterAndIncluding(middlewareMetaData.getInitialVersion())); - } - - return (APIVersionRange.none()); - } - - - /******************************************************************************* ** *******************************************************************************/ private static void doGet(Context context) { - String version = context.pathParam("version"); - String tableName = context.pathParam("tableName"); - String primaryKey = context.pathParam("primaryKey"); + String version = context.pathParam("version"); + String tableApiName = context.pathParam("tableName"); + String primaryKey = context.pathParam("primaryKey"); try { - QTableMetaData table = qInstance.getTable(tableName); - validateTableAndVersion(context, version, table); + QTableMetaData table = validateTableAndVersion(context, version, tableApiName); + String tableName = table.getName(); GetInput getInput = new GetInput(); @@ -276,16 +262,16 @@ public class QJavalinApiHandler *******************************************************************************/ private static void doQuery(Context context) { - String version = context.pathParam("version"); - String tableName = context.pathParam("tableName"); - QQueryFilter filter = null; + String version = context.pathParam("version"); + String tableApiName = context.pathParam("tableName"); + QQueryFilter filter = null; try { List badRequestMessages = new ArrayList<>(); - QTableMetaData table = qInstance.getTable(tableName); - validateTableAndVersion(context, version, table); + QTableMetaData table = validateTableAndVersion(context, version, tableApiName); + String tableName = table.getName(); QueryInput queryInput = new QueryInput(); setupSession(context, queryInput); @@ -506,24 +492,74 @@ public class QJavalinApiHandler /******************************************************************************* ** *******************************************************************************/ - private static void validateTableAndVersion(Context context, String version, QTableMetaData table) throws QNotFoundException + private static QTableMetaData validateTableAndVersion(Context context, String version, String tableApiName) throws QNotFoundException { + QNotFoundException qNotFoundException = new QNotFoundException("Could not find any resources at path " + context.path()); + + QTableMetaData table = getTableByApiName(version, tableApiName); + if(table == null) { - throw (new QNotFoundException("Could not find any resources at path " + context.path())); + throw (qNotFoundException); + } + + if(BooleanUtils.isTrue(table.getIsHidden())) + { + throw (qNotFoundException); + } + + ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table); + if(apiTableMetaData == null) + { + throw (qNotFoundException); + } + + if(BooleanUtils.isTrue(apiTableMetaData.getIsExcluded())) + { + throw (qNotFoundException); } APIVersion requestApiVersion = new APIVersion(version); List supportedVersions = ApiInstanceMetaData.of(qInstance).getSupportedVersions(); if(CollectionUtils.nullSafeIsEmpty(supportedVersions) || !supportedVersions.contains(requestApiVersion)) { - throw (new QNotFoundException("This version of this API does not contain the resource path " + context.path())); + throw (qNotFoundException); } - if(!getApiVersionRange(table).includes(requestApiVersion)) + if(!apiTableMetaData.getApiVersionRange().includes(requestApiVersion)) { - throw (new QNotFoundException("This version of this API does not contain the resource path " + context.path())); + throw (qNotFoundException); } + + return (table); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private static QTableMetaData getTableByApiName(String version, String tableApiName) + { + if(tableApiNameMap.get(version) == null) + { + Map map = new HashMap<>(); + + for(QTableMetaData table : qInstance.getTables().values()) + { + ApiTableMetaData apiTableMetaData = ApiTableMetaData.of(table); + String name = table.getName(); + if(apiTableMetaData != null && StringUtils.hasContent(apiTableMetaData.getApiTableName())) + { + name = apiTableMetaData.getApiTableName(); + } + map.put(name, table); + } + + tableApiNameMap.put(version, map); + } + + return (tableApiNameMap.get(version).get(tableApiName)); } @@ -662,13 +698,13 @@ public class QJavalinApiHandler *******************************************************************************/ private static void doInsert(Context context) { - String version = context.pathParam("version"); - String tableName = context.pathParam("tableName"); + String version = context.pathParam("version"); + String tableApiName = context.pathParam("tableName"); try { - QTableMetaData table = qInstance.getTable(tableName); - validateTableAndVersion(context, version, table); + QTableMetaData table = validateTableAndVersion(context, version, tableApiName); + String tableName = table.getName(); InsertInput insertInput = new InsertInput(); @@ -722,13 +758,13 @@ public class QJavalinApiHandler *******************************************************************************/ private static void bulkInsert(Context context) { - String version = context.pathParam("version"); - String tableName = context.pathParam("tableName"); + String version = context.pathParam("version"); + String tableApiName = context.pathParam("tableName"); try { - QTableMetaData table = qInstance.getTable(tableName); - validateTableAndVersion(context, version, table); + QTableMetaData table = validateTableAndVersion(context, version, tableApiName); + String tableName = table.getName(); InsertInput insertInput = new InsertInput(); @@ -820,14 +856,14 @@ public class QJavalinApiHandler *******************************************************************************/ private static void doUpdate(Context context) { - String version = context.pathParam("version"); - String tableName = context.pathParam("tableName"); - String primaryKey = context.pathParam("primaryKey"); + String version = context.pathParam("version"); + String tableApiName = context.pathParam("tableName"); + String primaryKey = context.pathParam("primaryKey"); try { - QTableMetaData table = qInstance.getTable(tableName); - validateTableAndVersion(context, version, table); + QTableMetaData table = validateTableAndVersion(context, version, tableApiName); + String tableName = table.getName(); UpdateInput updateInput = new UpdateInput(); @@ -899,14 +935,14 @@ public class QJavalinApiHandler *******************************************************************************/ private static void doDelete(Context context) { - String version = context.pathParam("version"); - String tableName = context.pathParam("tableName"); - String primaryKey = context.pathParam("primaryKey"); + String version = context.pathParam("version"); + String tableApiName = context.pathParam("tableName"); + String primaryKey = context.pathParam("primaryKey"); try { - QTableMetaData table = qInstance.getTable(tableName); - validateTableAndVersion(context, version, table); + QTableMetaData table = validateTableAndVersion(context, version, tableApiName); + String tableName = table.getName(); DeleteInput deleteInput = new DeleteInput(); diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/metadata/tables/ApiTableMetaData.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/metadata/tables/ApiTableMetaData.java index a3cd975b..5116c7ec 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/metadata/tables/ApiTableMetaData.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/metadata/tables/ApiTableMetaData.java @@ -25,6 +25,7 @@ package com.kingsrook.qqq.api.model.metadata.tables; import java.util.ArrayList; import java.util.List; import com.kingsrook.qqq.api.ApiMiddlewareType; +import com.kingsrook.qqq.api.model.APIVersionRange; import com.kingsrook.qqq.api.model.metadata.fields.ApiFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QMiddlewareTableMetaData; @@ -40,6 +41,9 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData private String initialVersion; private String finalVersion; + private String apiTableName; + private Boolean isExcluded; + private List removedApiFields; @@ -54,6 +58,23 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData + /******************************************************************************* + ** + *******************************************************************************/ + public APIVersionRange getApiVersionRange() + { + if(getInitialVersion() == null) + { + return APIVersionRange.none(); + } + + return (getFinalVersion() != null + ? APIVersionRange.betweenAndIncluding(getInitialVersion(), getFinalVersion()) + : APIVersionRange.afterAndIncluding(getInitialVersion())); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -218,4 +239,66 @@ public class ApiTableMetaData extends QMiddlewareTableMetaData return (this); } + + + /******************************************************************************* + ** Getter for apiTableName + *******************************************************************************/ + public String getApiTableName() + { + return (this.apiTableName); + } + + + + /******************************************************************************* + ** Setter for apiTableName + *******************************************************************************/ + public void setApiTableName(String apiTableName) + { + this.apiTableName = apiTableName; + } + + + + /******************************************************************************* + ** Fluent setter for apiTableName + *******************************************************************************/ + public ApiTableMetaData withApiTableName(String apiTableName) + { + this.apiTableName = apiTableName; + return (this); + } + + + + /******************************************************************************* + ** Getter for isExcluded + *******************************************************************************/ + public Boolean getIsExcluded() + { + return (this.isExcluded); + } + + + + /******************************************************************************* + ** Setter for isExcluded + *******************************************************************************/ + public void setIsExcluded(Boolean isExcluded) + { + this.isExcluded = isExcluded; + } + + + + /******************************************************************************* + ** Fluent setter for isExcluded + *******************************************************************************/ + public ApiTableMetaData withIsExcluded(Boolean isExcluded) + { + this.isExcluded = isExcluded; + return (this); + } + } diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/Components.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/Components.java index e838f31d..678f7bdc 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/Components.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/Components.java @@ -32,7 +32,7 @@ public class Components { private Map examples; private Map schemas; - private Map responses; + private Map responses; private Map securitySchemes; @@ -102,7 +102,7 @@ public class Components /******************************************************************************* ** Getter for responses *******************************************************************************/ - public Map getResponses() + public Map getResponses() { return (this.responses); } @@ -112,7 +112,7 @@ public class Components /******************************************************************************* ** Setter for responses *******************************************************************************/ - public void setResponses(Map responses) + public void setResponses(Map responses) { this.responses = responses; } @@ -122,7 +122,7 @@ public class Components /******************************************************************************* ** Fluent setter for responses *******************************************************************************/ - public Components withResponses(Map responses) + public Components withResponses(Map responses) { this.responses = responses; return (this); diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/Parameter.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/Parameter.java index 53001884..4077f25b 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/Parameter.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/Parameter.java @@ -32,6 +32,7 @@ public class Parameter { private String name; private String description; + private Boolean required; private String in; private Schema schema; private Boolean explode; @@ -223,4 +224,35 @@ public class Parameter return (this); } + + + /******************************************************************************* + ** Getter for required + *******************************************************************************/ + public Boolean getRequired() + { + return (this.required); + } + + + + /******************************************************************************* + ** Setter for required + *******************************************************************************/ + public void setRequired(Boolean required) + { + this.required = required; + } + + + + /******************************************************************************* + ** Fluent setter for required + *******************************************************************************/ + public Parameter withRequired(Boolean required) + { + this.required = required; + return (this); + } + } diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/SecurityScheme.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/SecurityScheme.java index cd2f0c77..3209031e 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/SecurityScheme.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/openapi/SecurityScheme.java @@ -28,6 +28,8 @@ package com.kingsrook.qqq.api.model.openapi; public class SecurityScheme { private String type; + private String scheme; + private String bearerFormat; @@ -60,4 +62,66 @@ public class SecurityScheme return (this); } + + + /******************************************************************************* + ** Getter for scheme + *******************************************************************************/ + public String getScheme() + { + return (this.scheme); + } + + + + /******************************************************************************* + ** Setter for scheme + *******************************************************************************/ + public void setScheme(String scheme) + { + this.scheme = scheme; + } + + + + /******************************************************************************* + ** Fluent setter for scheme + *******************************************************************************/ + public SecurityScheme withScheme(String scheme) + { + this.scheme = scheme; + return (this); + } + + + + /******************************************************************************* + ** Getter for bearerFormat + *******************************************************************************/ + public String getBearerFormat() + { + return (this.bearerFormat); + } + + + + /******************************************************************************* + ** Setter for bearerFormat + *******************************************************************************/ + public void setBearerFormat(String bearerFormat) + { + this.bearerFormat = bearerFormat; + } + + + + /******************************************************************************* + ** Fluent setter for bearerFormat + *******************************************************************************/ + public SecurityScheme withBearerFormat(String bearerFormat) + { + this.bearerFormat = bearerFormat; + return (this); + } + } diff --git a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/actions/GenerateOpenApiSpecActionTest.java b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/actions/GenerateOpenApiSpecActionTest.java index 445ca4f0..4ee2e258 100644 --- a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/actions/GenerateOpenApiSpecActionTest.java +++ b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/actions/GenerateOpenApiSpecActionTest.java @@ -22,19 +22,21 @@ package com.kingsrook.qqq.api.actions; +import java.util.Set; import com.kingsrook.qqq.api.BaseTest; import com.kingsrook.qqq.api.TestUtils; -import com.kingsrook.qqq.api.model.APIVersion; import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput; import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput; -import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData; import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.exceptions.QException; -import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; /******************************************************************************* @@ -49,23 +51,111 @@ class GenerateOpenApiSpecActionTest extends BaseTest @Test void test() throws QException { - String version = TestUtils.V2023_Q1; - - QInstance qInstance = QContext.getQInstance(); - qInstance.withMiddlewareMetaData(new ApiInstanceMetaData() - .withCurrentVersion(new APIVersion(version)) - ); - - for(QTableMetaData table : qInstance.getTables().values()) - { - table.withMiddlewareMetaData(new ApiTableMetaData().withInitialVersion(version)); - break; - } - - new QInstanceEnricher(qInstance).enrich(); - - GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(version)); + GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(TestUtils.CURRENT_API_VERSION)); System.out.println(output.getYaml()); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testExcludedTables() throws QException + { + QInstance qInstance = QContext.getQInstance(); + + qInstance.addTable(new QTableMetaData() + .withName("supportedTable") + .withBackendName(TestUtils.MEMORY_BACKEND_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withMiddlewareMetaData(new ApiTableMetaData() + .withInitialVersion(TestUtils.V2022_Q4))); + + qInstance.addTable(new QTableMetaData() + .withName("hiddenTable") + .withBackendName(TestUtils.MEMORY_BACKEND_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withIsHidden(true) + .withMiddlewareMetaData(new ApiTableMetaData() + .withInitialVersion(TestUtils.V2022_Q4))); + + qInstance.addTable(new QTableMetaData() + .withName("excludedTable") + .withBackendName(TestUtils.MEMORY_BACKEND_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withMiddlewareMetaData(new ApiTableMetaData() + .withIsExcluded(true))); + + qInstance.addTable(new QTableMetaData() + .withName("tableWithoutApiMetaData") + .withBackendName(TestUtils.MEMORY_BACKEND_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER))); + + qInstance.addTable(new QTableMetaData() + .withName("tableWithFutureVersion") + .withBackendName(TestUtils.MEMORY_BACKEND_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withMiddlewareMetaData(new ApiTableMetaData() + .withInitialVersion(TestUtils.V2023_Q2))); + + qInstance.addTable(new QTableMetaData() + .withName("tableWithOnlyPastVersions") + .withBackendName(TestUtils.MEMORY_BACKEND_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withMiddlewareMetaData(new ApiTableMetaData() + .withInitialVersion(TestUtils.V2022_Q4) + .withFinalVersion(TestUtils.V2022_Q4))); + + qInstance.addTable(new QTableMetaData() + .withName("tableWithNoSupportedCapabilities") + .withBackendName(TestUtils.MEMORY_BACKEND_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withoutCapabilities(Capability.TABLE_QUERY, Capability.TABLE_GET, Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE) + .withMiddlewareMetaData(new ApiTableMetaData() + .withInitialVersion(TestUtils.V2022_Q4))); + + GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(TestUtils.CURRENT_API_VERSION)); + Set apiPaths = output.getOpenAPI().getPaths().keySet(); + assertTrue(apiPaths.stream().anyMatch(s -> s.contains("/supportedTable/"))); + assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/hiddenTable/"))); + assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/excludedTable/"))); + assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/tableWithoutApiMetaData/"))); + assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/tableWithoutApiMetaData/"))); + assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/tableWithFutureVersion/"))); + assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/tableWithNoSupportedCapabilities/"))); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testApiTableName() throws QException + { + QInstance qInstance = QContext.getQInstance(); + + qInstance.addTable(new QTableMetaData() + .withName("internalName") + .withBackendName(TestUtils.MEMORY_BACKEND_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withMiddlewareMetaData(new ApiTableMetaData() + .withApiTableName("externalName") + .withInitialVersion(TestUtils.V2022_Q4))); + + GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(new GenerateOpenApiSpecInput().withVersion(TestUtils.CURRENT_API_VERSION)); + Set apiPaths = output.getOpenAPI().getPaths().keySet(); + assertTrue(apiPaths.stream().anyMatch(s -> s.contains("/externalName/"))); + assertTrue(apiPaths.stream().noneMatch(s -> s.contains("/internalName/"))); + } + } \ No newline at end of file diff --git a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java index 43b06c21..9d0e9c12 100644 --- a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java +++ b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.List; import com.kingsrook.qqq.api.BaseTest; import com.kingsrook.qqq.api.TestUtils; +import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData; import com.kingsrook.qqq.backend.core.actions.tables.GetAction; import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.exceptions.QException; @@ -35,6 +36,9 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.javalin.QJavalinImplementation; import kong.unirest.HttpResponse; @@ -72,6 +76,16 @@ class QJavalinApiHandlerTest extends BaseTest static void beforeAll() throws QInstanceValidationException { QInstance qInstance = TestUtils.defineInstance(); + + qInstance.addTable(new QTableMetaData() + .withName("internalName") + .withBackendName(TestUtils.MEMORY_BACKEND_NAME) + .withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER)) + .withMiddlewareMetaData(new ApiTableMetaData() + .withApiTableName("externalName") + .withInitialVersion(TestUtils.V2022_Q4))); + qJavalinImplementation = new QJavalinImplementation(qInstance); qJavalinImplementation.startJavalinServer(PORT); qJavalinImplementation.getJavalinService().routes(new QJavalinApiHandler(qInstance).getRoutes()); @@ -686,13 +700,33 @@ class QJavalinApiHandlerTest extends BaseTest @Test void testDelete404() { - HttpResponse response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/1") - .asString(); + HttpResponse response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/1").asString(); assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find Person with Id of 1", response); } + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testRenamedTable() + { + { + HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/internalName/query").asString(); + assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus()); + } + + { + HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/externalName/query").asString(); + assertEquals(HttpStatus.OK_200, response.getStatus()); + JSONObject jsonObject = new JSONObject(response.getBody()); + assertEquals(0, jsonObject.getInt("count")); + } + } + + + /******************************************************************************* ** *******************************************************************************/