diff --git a/cwms-data-api/src/main/java/cwms/cda/api/BasinController.java b/cwms-data-api/src/main/java/cwms/cda/api/BasinController.java
index 65631c358..c8bdf4f4a 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/BasinController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/BasinController.java
@@ -193,7 +193,7 @@ public void getOne(@NotNull Context ctx, @NotNull String name) {
String units =
ctx.queryParamAsClass(UNIT, String.class).getOrDefault(UnitSystem.EN.value());
- String office = ctx.queryParam(OFFICE);
+ String office = requiredParam(ctx, OFFICE);
String formatHeader = ctx.header(Header.ACCEPT);
ContentType contentType = Formats.parseHeader(formatHeader, Basin.class);
ctx.contentType(contentType.toString());
@@ -303,7 +303,7 @@ public void delete(@NotNull Context ctx, @NotNull String name) {
cwms.cda.data.dao.basin.BasinDao basinDao = new cwms.cda.data.dao.basin.BasinDao(dsl);
CwmsId basinId = new CwmsId.Builder()
.withName(name)
- .withOfficeId(ctx.queryParam(OFFICE))
+ .withOfficeId(requiredParam(ctx, OFFICE))
.build();
basinDao.deleteBasin(basinId, deleteMethod.getRule());
StatusResponse re = new StatusResponse(basinId.getOfficeId(), "Deleted CWMS Basin", basinId.getName());
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/BinaryTimeSeriesValueController.java b/cwms-data-api/src/main/java/cwms/cda/api/BinaryTimeSeriesValueController.java
index cffac4407..798bb5806 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/BinaryTimeSeriesValueController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/BinaryTimeSeriesValueController.java
@@ -64,7 +64,8 @@ public BinaryTimeSeriesValueController(MetricRegistry metrics) {
@OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office of "
+ "the Binary TimeSeries whose data is to be included in the response."),
@OpenApiParam(name = BLOB_ID, description = "Will be removed in a schema update. " +
- "This is a placeholder for integration testing with schema 23.3.16", deprecated = true)
+ "This is a placeholder for integration testing with schema 23.3.16", deprecated = true,
+ required = true)
},
responses = {
@OpenApiResponse(status = STATUS_200,
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/EmbankmentController.java b/cwms-data-api/src/main/java/cwms/cda/api/EmbankmentController.java
index 1db14e616..12da35431 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/EmbankmentController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/EmbankmentController.java
@@ -90,7 +90,7 @@ private Timer.Context markAndTime(String subject) {
@Override
public void getAll(Context ctx) {
String office = ctx.queryParam(OFFICE);
- String projectId = ctx.queryParam(PROJECT_ID);
+ String projectId = requiredParam(ctx, PROJECT_ID);
try (Timer.Context ignored = markAndTime(GET_ALL)) {
DSLContext dsl = getDslContext(ctx);
EmbankmentDao dao = new EmbankmentDao(dsl);
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/LevelsController.java b/cwms-data-api/src/main/java/cwms/cda/api/LevelsController.java
index 5ca14bfa4..f6ec6f48b 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/LevelsController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/LevelsController.java
@@ -55,6 +55,7 @@
import cwms.cda.formatters.FormattingException;
import cwms.cda.formatters.UnsupportedFormatException;
import cwms.cda.helpers.DateUtils;
+import cwms.cda.helpers.annotations.IgnoreRequiredQueryParamMismatch;
import io.javalin.apibuilder.CrudHandler;
import io.javalin.core.util.Header;
import io.javalin.http.Context;
@@ -378,6 +379,7 @@ public void getAll(@NotNull Context ctx) {
description = "Retrieves requested Location Level",
tags = TAG
)
+ @IgnoreRequiredQueryParamMismatch(parameterNames = {EFFECTIVE_DATE})
@Override
public void getOne(@NotNull Context ctx, @NotNull String levelId) {
String office = requiredParam(ctx, OFFICE);
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/LocationGroupController.java b/cwms-data-api/src/main/java/cwms/cda/api/LocationGroupController.java
index 31f2d5815..7d9ffb79b 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/LocationGroupController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/LocationGroupController.java
@@ -86,10 +86,10 @@ private Timer.Context markAndTime(String subject) {
+ " the assigned locations in the returned location groups. (default: false)"),
@OpenApiParam(name = LOCATION_CATEGORY_LIKE, description = "Posix regular expression "
+ "matching against the location category id"),
- @OpenApiParam(name = CATEGORY_OFFICE_ID, required = true, description = "Specifies the "
+ @OpenApiParam(name = CATEGORY_OFFICE_ID, description = "Specifies the "
+ "owning office of the category the location group belongs to "
+ "whose data is to be included in the response."),
- @OpenApiParam(name = LOCATION_OFFICE_ID, required = true, description = "Specifies the "
+ @OpenApiParam(name = LOCATION_OFFICE_ID, description = "Specifies the "
+ "owning office of the location assigned to the location group whose data is to be included in the response."),
},
responses = {
@@ -108,8 +108,8 @@ public void getAll(@NotNull Context ctx) {
DSLContext dsl = getDslContext(ctx);
LocationGroupDao cdm = new LocationGroupDao(dsl);
- String groupOfficeId = requiredParam(ctx, OFFICE);
- String categoryOfficeId = requiredParam(ctx, CATEGORY_OFFICE_ID);
+ String groupOfficeId = ctx.queryParam(OFFICE);
+ String categoryOfficeId = ctx.queryParam(CATEGORY_OFFICE_ID);
String locationOfficeId = ctx.queryParam(LOCATION_OFFICE_ID);
boolean includeAssigned = queryParamAsClass(ctx, new String[]{INCLUDE_ASSIGNED},
@@ -152,11 +152,12 @@ Boolean.class, false, metrics, name(LocationGroupController.class.getName(),
@OpenApiParam(name = OFFICE, required = true, description = "Specifies the "
+ "owning office of the location group whose data is to be included "
+ "in the response."),
- @OpenApiParam(name = GROUP_OFFICE_ID, required = true, description = "Specifies the "
- + "owning office of the location group whose data is to be included in the response."),
- @OpenApiParam(name = CATEGORY_OFFICE_ID, required = true, description = "Specifies the "
+ @OpenApiParam(name = GROUP_OFFICE_ID, description = "Specifies the "
+ + "owning office of the location group whose data is to be included in the response. "
+ + "Required for GEO JSON format."),
+ @OpenApiParam(name = CATEGORY_OFFICE_ID, description = "Specifies the "
+ "owning office of the category the location group belongs to "
- + "whose data is to be included in the response."),
+ + "whose data is to be included in the response. Required for GEO JSON format."),
@OpenApiParam(name = CATEGORY_ID, required = true, description = "Specifies"
+ " the category containing the location group whose data is to be "
+ "included in the response."),
@@ -325,8 +326,8 @@ public void delete(@NotNull Context ctx, @NotNull String groupId) {
DSLContext dsl = getDslContext(ctx);
LocationGroupDao dao = new LocationGroupDao(dsl);
- String office = ctx.queryParam(OFFICE);
- String categoryId = ctx.queryParam(CATEGORY_ID);
+ String office = requiredParam(ctx, OFFICE);
+ String categoryId = requiredParam(ctx, CATEGORY_ID);
boolean cascadeDelete = ctx.queryParamAsClass(CASCADE_DELETE, Boolean.class).getOrDefault(false);
dao.delete(categoryId, groupId, cascadeDelete, office);
ctx.status(HttpServletResponse.SC_NO_CONTENT);
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java
index be7ea5b71..92b48e6e9 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java
@@ -216,9 +216,9 @@ public void update(@NotNull Context ctx, @NotNull String locationId) {
},
queryParams = {
@OpenApiParam(name = OFFICE, required = true, description = "Specifies the office of the measurements to delete"),
- @OpenApiParam(name = BEGIN, required = true, description = "The start of the time window to delete. " +
+ @OpenApiParam(name = BEGIN, description = "The start of the time window to delete. " +
TIME_FORMAT_DESC),
- @OpenApiParam(name = END, required = true, description = "The end of the time window to delete." +
+ @OpenApiParam(name = END, description = "The end of the time window to delete." +
TIME_FORMAT_DESC),
@OpenApiParam(name = TIMEZONE, description = "This field specifies a default timezone "
+ "to be used if the format of the " + BEGIN + "and " + END
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/PoolController.java b/cwms-data-api/src/main/java/cwms/cda/api/PoolController.java
index 2841cdc2b..6905608bf 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/PoolController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/PoolController.java
@@ -14,6 +14,7 @@
import static cwms.cda.api.Controllers.OFFICE;
import static cwms.cda.api.Controllers.PAGE;
import static cwms.cda.api.Controllers.PAGE_SIZE;
+import static cwms.cda.api.Controllers.PARAMETER_ID;
import static cwms.cda.api.Controllers.POOL_ID;
import static cwms.cda.api.Controllers.PROJECT_ID;
import static cwms.cda.api.Controllers.RESULTS;
@@ -23,6 +24,7 @@
import static cwms.cda.api.Controllers.STATUS_501;
import static cwms.cda.api.Controllers.TOP_MASK;
import static cwms.cda.api.Controllers.queryParamAsClass;
+import static cwms.cda.api.Controllers.requiredParam;
import static cwms.cda.data.dao.JooqDao.getDslContext;
import com.codahale.metrics.Histogram;
@@ -190,8 +192,8 @@ public void getOne(@NotNull Context ctx, @NotNull String poolId) {
PoolDao dao = new PoolDao(dsl);
// These are required
- String office = ctx.queryParam(OFFICE);
- String projectId = ctx.queryParam(PROJECT_ID);
+ String office = requiredParam(ctx, OFFICE);;
+ String projectId = requiredParam(ctx, PROJECT_ID);
// These are optional
String bottomMask =
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java b/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java
index 62c92d9e7..c5e592d76 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java
@@ -231,8 +231,8 @@ public void create(@NotNull Context ctx) {
@OpenApiParam(name = NAME, description = "The name of the project to be renamed"),
},
queryParams = {
- @OpenApiParam(name = NAME, description = "The new name of the project"),
- @OpenApiParam(name = OFFICE, description = "The office of the project to be renamed"),
+ @OpenApiParam(name = NAME, required = true, description = "The new name of the project"),
+ @OpenApiParam(name = OFFICE, required = true, description = "The office of the project to be renamed"),
},
requestBody = @OpenApiRequestBody(
content = {
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/SpecifiedLevelController.java b/cwms-data-api/src/main/java/cwms/cda/api/SpecifiedLevelController.java
index 550c9efda..31638a5d4 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/SpecifiedLevelController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/SpecifiedLevelController.java
@@ -163,7 +163,7 @@ public void create(Context ctx) {
queryParams = {
@OpenApiParam(name = OFFICE, required = true, description = "Specifies the "
+ "owning office of the specified level to be renamed"),
- @OpenApiParam(name = SPECIFIED_LEVEL_ID, description = "The new specified level id.")
+ @OpenApiParam(name = SPECIFIED_LEVEL_ID, required = true, description = "The new specified level id.")
},
method = HttpMethod.PATCH,
tags = {TAG}
@@ -174,8 +174,8 @@ public void update(Context ctx, @NotNull String oldSpecifiedLevelId) {
DSLContext dsl = getDslContext(ctx);
SpecifiedLevelDao dao = getDao(dsl);
- String newSpecifiedLevelId = ctx.queryParam(SPECIFIED_LEVEL_ID);
- String office = ctx.queryParam(OFFICE);
+ String newSpecifiedLevelId = requiredParam(ctx, SPECIFIED_LEVEL_ID);
+ String office = requiredParam(ctx, OFFICE);
dao.update(oldSpecifiedLevelId, newSpecifiedLevelId, office);
ctx.status(HttpServletResponse.SC_NO_CONTENT);
}
@@ -201,7 +201,7 @@ public void delete(Context ctx, String specifiedLevelId) {
DSLContext dsl = getDslContext(ctx);
SpecifiedLevelDao dao = getDao(dsl);
- String office = ctx.queryParam(OFFICE);
+ String office = requiredParam(ctx, OFFICE);
dao.delete(specifiedLevelId, office);
ctx.status(HttpServletResponse.SC_NO_CONTENT);
}
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/StandardTextController.java b/cwms-data-api/src/main/java/cwms/cda/api/StandardTextController.java
index 0038a39f7..bdf3d737a 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/StandardTextController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/StandardTextController.java
@@ -133,10 +133,7 @@ public void getAll(Context ctx) {
@Override
public void getOne(@NotNull Context ctx, @NotNull String stdTextId) {
try (Timer.Context ignored = markAndTime(DELETE)) {
- String office = ctx.queryParam(OFFICE);
- if (office == null) {
- throw new IllegalArgumentException(OFFICE + " is a required parameter");
- }
+ String office = requiredParam(ctx, OFFICE);
DSLContext dsl = getDslContext(ctx);
StandardTextValue standardTextValue = getDao(dsl).retrieveStandardText(stdTextId, office);
@@ -204,12 +201,8 @@ public void update(@NotNull Context ctx, @NotNull String oldTextTimeSeriesId) {
@Override
public void delete(@NotNull Context ctx, @NotNull String stdTextId) {
try (Timer.Context ignored = markAndTime(DELETE)) {
- String office = ctx.queryParam(OFFICE);
- if (office == null) {
- throw new IllegalArgumentException(OFFICE + " is a required parameter");
- }
- JooqDao.DeleteMethod deleteMethod = ctx.queryParamAsClass(METHOD, JooqDao.DeleteMethod.class)
- .getOrThrow(e -> new IllegalArgumentException(METHOD + " is a required parameter"));
+ String office = requiredParam(ctx, OFFICE);
+ JooqDao.DeleteMethod deleteMethod = requiredParamAs(ctx, METHOD, JooqDao.DeleteMethod.class);
String deleteAction;
switch (deleteMethod) {
case DELETE_ALL:
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/TextTimeSeriesValueController.java b/cwms-data-api/src/main/java/cwms/cda/api/TextTimeSeriesValueController.java
index 5203b105b..1f3cd343c 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/TextTimeSeriesValueController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/TextTimeSeriesValueController.java
@@ -59,7 +59,8 @@ public TextTimeSeriesValueController(MetricRegistry metrics) {
@OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office of "
+ "the Text TimeSeries whose data is to be included in the response."),
@OpenApiParam(name = CLOB_ID, description = "Will be removed in a schema update. " +
- "This is a placeholder for integration testing with schema 23.3.16", deprecated = true)
+ "This is a placeholder for integration testing with schema 23.3.16", deprecated = true,
+ required = true)
},
responses = {
@OpenApiResponse(status = STATUS_200,
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/TimeSeriesFilteredController.java b/cwms-data-api/src/main/java/cwms/cda/api/TimeSeriesFilteredController.java
index da8f26eef..3393c2389 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/TimeSeriesFilteredController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/TimeSeriesFilteredController.java
@@ -214,7 +214,7 @@ public void handle(@NotNull Context ctx) {
? DateUtils.parseUserDate(end, timezone)
: ZonedDateTime.now(tz);
- String office = requiredParam(ctx, OFFICE);
+ String office = ctx.queryParam(OFFICE);
FilteredTimeSeriesParameters ftsParams = FilteredTimeSeriesParameters.Builder.from(ctx)
.build();
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/TimeSeriesGroupController.java b/cwms-data-api/src/main/java/cwms/cda/api/TimeSeriesGroupController.java
index 708629088..2eccdb7f8 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/TimeSeriesGroupController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/TimeSeriesGroupController.java
@@ -98,7 +98,7 @@ private Timer.Context markAndTime(String subject) {
+ "timeseries assigned to the group(s) whose data is to be included in the response. If this "
+ "field is not specified, group information for all assigned TS offices shall be returned."),
@OpenApiParam(name = GROUP_OFFICE_ID, description = "Specifies the owning office of the "
- + "timeseries group", required = true),
+ + "timeseries group"),
@OpenApiParam(name = INCLUDE_ASSIGNED, type = Boolean.class, description = "Include"
+ " the assigned timeseries in the returned timeseries groups. (default: true)"),
@OpenApiParam(name = TIMESERIES_CATEGORY_LIKE, description = "Posix regular expression "
@@ -163,15 +163,15 @@ Boolean.class, true, metrics, name(TimeSeriesGroupController.class.getName(),
+ "the timeseries group whose data is to be included in the response")
},
queryParams = {
- @OpenApiParam(name = OFFICE, required = true, description = "Specifies the "
+ @OpenApiParam(name = OFFICE, description = "Specifies the "
+ "owning office of the timeseries assigned to the group whose data is to be included"
+ " in the response. This will limit the assigned timeseries returned to only those"
+ " assigned to the specified office."),
@OpenApiParam(name = CATEGORY_OFFICE_ID, description = "Specifies the owning office of the "
- + "timeseries group category", required = true),
+ + "timeseries group category"),
@OpenApiParam(name = GROUP_OFFICE_ID, description = "Specifies the owning office of the "
- + "timeseries group", required = true),
- @OpenApiParam(name = CATEGORY_ID, required = true, description = "Specifies"
+ + "timeseries group"),
+ @OpenApiParam(name = CATEGORY_ID, description = "Specifies"
+ " the category containing the timeseries group whose data is to be "
+ "included in the response."),
},
@@ -337,8 +337,8 @@ public void delete(@NotNull Context ctx, @NotNull String groupId) {
DSLContext dsl = getDslContext(ctx);
TimeSeriesGroupDao dao = new TimeSeriesGroupDao(dsl);
- String office = ctx.queryParam(OFFICE);
- String categoryId = ctx.queryParam(CATEGORY_ID);
+ String office = requiredParam(ctx, OFFICE);
+ String categoryId = requiredParam(ctx, CATEGORY_ID);
dao.delete(categoryId, groupId, office);
ctx.status(HttpServletResponse.SC_NO_CONTENT);
}
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/TimeSeriesIdentifierDescriptorController.java b/cwms-data-api/src/main/java/cwms/cda/api/TimeSeriesIdentifierDescriptorController.java
index 5081e6ab2..142a430c6 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/TimeSeriesIdentifierDescriptorController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/TimeSeriesIdentifierDescriptorController.java
@@ -180,7 +180,7 @@ public void getOne(@NotNull Context ctx, @NotNull String timeseriesId) {
DSLContext dsl = getDslContext(ctx);
TimeSeriesIdentifierDescriptorDao dao = new TimeSeriesIdentifierDescriptorDao(dsl);
- String office = ctx.queryParam(OFFICE);
+ String office = requiredParam(ctx, OFFICE);
String formatHeader = ctx.header(Header.ACCEPT);
if (Formats.DEFAULT.equals(formatHeader)) {
@@ -329,7 +329,7 @@ public void update(@NotNull Context ctx, @NotNull String name) {
@Override
public void delete(@NotNull Context ctx, @NotNull String timeseriesId) {
- JooqDao.DeleteMethod method = ctx.queryParamAsClass(METHOD, JooqDao.DeleteMethod.class).get();
+ JooqDao.DeleteMethod method =requiredParamAs(ctx, METHOD, JooqDao.DeleteMethod.class);
String office = requiredParam(ctx, OFFICE);
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/TurbineChangesDeleteController.java b/cwms-data-api/src/main/java/cwms/cda/api/TurbineChangesDeleteController.java
index 597a0ad4f..15cbf4ca9 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/TurbineChangesDeleteController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/TurbineChangesDeleteController.java
@@ -26,29 +26,22 @@
import static com.codahale.metrics.MetricRegistry.name;
import static cwms.cda.api.Controllers.*;
-import static cwms.cda.api.Controllers.SINCE;
import static cwms.cda.data.dao.JooqDao.getDslContext;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
-import cwms.cda.api.enums.UnitSystem;
import cwms.cda.data.dao.location.kind.TurbineDao;
import cwms.cda.data.dto.CwmsId;
import cwms.cda.data.dto.StatusResponse;
-import cwms.cda.data.dto.location.kind.TurbineChange;
-import cwms.cda.formatters.ContentType;
-import cwms.cda.formatters.Formats;
-import io.javalin.core.util.Header;
+import cwms.cda.helpers.annotations.IgnoreRequiredQueryParamMismatch;
import io.javalin.http.Context;
import io.javalin.http.Handler;
import io.javalin.plugin.openapi.annotations.HttpMethod;
import io.javalin.plugin.openapi.annotations.OpenApi;
-import io.javalin.plugin.openapi.annotations.OpenApiContent;
import io.javalin.plugin.openapi.annotations.OpenApiParam;
import io.javalin.plugin.openapi.annotations.OpenApiResponse;
import java.time.Instant;
-import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.jooq.DSLContext;
@@ -99,6 +92,7 @@ private Timer.Context markAndTime(String subject) {
+ "inputs provided the project was not found.")
}
)
+ @IgnoreRequiredQueryParamMismatch(parameterNames = {TIMEZONE})
public void handle(@NotNull Context ctx) throws Exception {
String projectId = ctx.pathParam(NAME);
String office = ctx.pathParam(OFFICE);
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/TurbineChangesGetController.java b/cwms-data-api/src/main/java/cwms/cda/api/TurbineChangesGetController.java
index e8d3b936d..870608d0e 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/TurbineChangesGetController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/TurbineChangesGetController.java
@@ -32,22 +32,18 @@
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import cwms.cda.api.enums.UnitSystem;
-import cwms.cda.api.errors.CdaError;
-import cwms.cda.api.errors.RequiredQueryParameterException;
import cwms.cda.data.dao.location.kind.TurbineDao;
import cwms.cda.data.dto.CwmsId;
import cwms.cda.data.dto.location.kind.TurbineChange;
import cwms.cda.formatters.ContentType;
import cwms.cda.formatters.Formats;
-import io.javalin.apibuilder.CrudHandler;
+import cwms.cda.helpers.annotations.IgnoreRequiredQueryParamMismatch;
import io.javalin.core.util.Header;
import io.javalin.http.Context;
import io.javalin.http.Handler;
-import io.javalin.plugin.openapi.annotations.HttpMethod;
import io.javalin.plugin.openapi.annotations.OpenApi;
import io.javalin.plugin.openapi.annotations.OpenApiContent;
import io.javalin.plugin.openapi.annotations.OpenApiParam;
-import io.javalin.plugin.openapi.annotations.OpenApiRequestBody;
import io.javalin.plugin.openapi.annotations.OpenApiResponse;
import java.time.Instant;
import java.util.List;
@@ -116,6 +112,7 @@ private Timer.Context markAndTime(String subject) {
description = "Returns matching CWMS Turbine Change Data for a Reservoir Project.",
tags = {TurbineController.TAG}
)
+ @IgnoreRequiredQueryParamMismatch(parameterNames = {TIMEZONE})
public void handle(@NotNull Context ctx) throws Exception {
String projectId = ctx.pathParam(NAME);
String office = ctx.pathParam(OFFICE);
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/GateChangeDeleteController.java b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/GateChangeDeleteController.java
index 9fbf1bf82..b8c793e32 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/GateChangeDeleteController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/GateChangeDeleteController.java
@@ -20,24 +20,24 @@
package cwms.cda.api.location.kind;
+import static cwms.cda.api.Controllers.*;
+
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import cwms.cda.api.BaseHandler;
import cwms.cda.data.dao.JooqDao;
import cwms.cda.data.dao.location.kind.OutletDao;
import cwms.cda.data.dto.CwmsId;
+import cwms.cda.helpers.annotations.IgnoreRequiredQueryParamMismatch;
import io.javalin.http.Context;
import io.javalin.plugin.openapi.annotations.HttpMethod;
import io.javalin.plugin.openapi.annotations.OpenApi;
import io.javalin.plugin.openapi.annotations.OpenApiParam;
import io.javalin.plugin.openapi.annotations.OpenApiResponse;
-import java.sql.Timestamp;
import java.time.Instant;
import javax.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.jooq.DSLContext;
-import static cwms.cda.api.Controllers.*;
-import static cwms.cda.api.Controllers.GET_ALL;
public class GateChangeDeleteController extends BaseHandler {
@@ -70,6 +70,7 @@ public GateChangeDeleteController(MetricRegistry metrics) {
tags = {OutletController.TAG},
method = HttpMethod.DELETE
)
+ @IgnoreRequiredQueryParamMismatch(parameterNames = {TIMEZONE})
@Override
public void handle(@NotNull Context context) throws Exception {
String office = context.pathParam(OFFICE);
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/GateChangeGetAllController.java b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/GateChangeGetAllController.java
index e3761f8f6..fdc6cfc97 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/GateChangeGetAllController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/GateChangeGetAllController.java
@@ -20,6 +20,8 @@
package cwms.cda.api.location.kind;
+import static cwms.cda.api.Controllers.*;
+
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import cwms.cda.api.BaseHandler;
@@ -30,19 +32,18 @@
import cwms.cda.data.dto.location.kind.GateChange;
import cwms.cda.formatters.ContentType;
import cwms.cda.formatters.Formats;
+import cwms.cda.helpers.annotations.IgnoreRequiredQueryParamMismatch;
import io.javalin.core.util.Header;
import io.javalin.http.Context;
import io.javalin.plugin.openapi.annotations.OpenApi;
import io.javalin.plugin.openapi.annotations.OpenApiContent;
import io.javalin.plugin.openapi.annotations.OpenApiParam;
import io.javalin.plugin.openapi.annotations.OpenApiResponse;
-import java.sql.Timestamp;
import java.time.Instant;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.jooq.DSLContext;
-import static cwms.cda.api.Controllers.*;
public class GateChangeGetAllController extends BaseHandler {
private static final int DEFAULT_PAGE_SIZE = 500;
@@ -94,6 +95,7 @@ public GateChangeGetAllController(MetricRegistry metrics) {
description = "Returns matching CWMS gate change data for a Reservoir Project.",
tags = {OutletController.TAG}
)
+ @IgnoreRequiredQueryParamMismatch(parameterNames = {TIMEZONE})
@Override
public void handle(@NotNull Context context) {
String office = context.pathParam(OFFICE);
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/LockController.java b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/LockController.java
index 1acb7e586..547559878 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/LockController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/LockController.java
@@ -108,7 +108,7 @@ private Timer.Context markAndTime(String subject) {
public void getAll(@NotNull Context ctx) {
try (Timer.Context ignored = markAndTime(GET_ALL)) {
String office = requiredParam(ctx, OFFICE);
- String projectId = ctx.queryParam(PROJECT_ID);
+ String projectId = requiredParam(ctx, PROJECT_ID);
CwmsId project = CwmsId.buildCwmsId(office, projectId);
DSLContext dsl = getDslContext(ctx);
LockDao dao = new LockDao(dsl);
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/project/UpdateLockRevokerRights.java b/cwms-data-api/src/main/java/cwms/cda/api/project/UpdateLockRevokerRights.java
index a428d913e..50c58904f 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/project/UpdateLockRevokerRights.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/project/UpdateLockRevokerRights.java
@@ -29,6 +29,7 @@
import static cwms.cda.api.Controllers.PROJECT_MASK;
import static cwms.cda.api.Controllers.USER_ID;
import static cwms.cda.api.Controllers.requiredParam;
+import static cwms.cda.api.Controllers.requiredParamAs;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
@@ -82,8 +83,7 @@ public void handle(@NotNull Context ctx) throws Exception {
String userId = requiredParam(ctx, USER_ID);
String projMask = ctx.queryParamAsClass(PROJECT_MASK, String.class).getOrDefault("*");
String appId = requiredParam(ctx, APPLICATION_ID);
- Boolean allow = ctx.queryParamAsClass(Controllers.ALLOW, Boolean.class)
- .getOrThrow(e -> new RequiredQueryParameterException(Controllers.ALLOW));
+ Boolean allow = requiredParamAs(ctx, Controllers.ALLOW, Boolean.class);
try (final Timer.Context ignored = markAndTime("updateRights")) {
ProjectLockDao lockDao = new ProjectLockDao(JooqDao.getDslContext(ctx));
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingController.java b/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingController.java
index 78d8ed7dd..365816242 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingController.java
@@ -52,6 +52,7 @@
import static cwms.cda.api.Controllers.UPDATE;
import static cwms.cda.api.Controllers.VERSION_DATE;
import static cwms.cda.api.Controllers.addDeprecatedContentTypeWarning;
+import static cwms.cda.api.Controllers.requiredParam;
import static cwms.cda.data.dao.JooqDao.getDslContext;
import com.codahale.metrics.Histogram;
@@ -245,9 +246,9 @@ public void delete(@NotNull Context ctx, @NotNull String ratingSpecId) {
DSLContext dsl = getDslContext(ctx);
String timezone = ctx.queryParamAsClass(TIMEZONE, String.class).getOrDefault("UTC");
- Instant startTimeDate = DateUtils.parseUserDate(ctx.queryParam(BEGIN), timezone).toInstant();
- Instant endTimeDate = DateUtils.parseUserDate(ctx.queryParam(END), timezone).toInstant();
- String office = ctx.queryParam(OFFICE);
+ Instant startTimeDate = DateUtils.parseUserDate(requiredParam(ctx, BEGIN), timezone).toInstant();
+ Instant endTimeDate = DateUtils.parseUserDate(requiredParam(ctx, END), timezone).toInstant();
+ String office = requiredParam(ctx, OFFICE);
RatingDao ratingDao = getRatingDao(dsl);
ratingDao.delete(office, ratingSpecId, startTimeDate, endTimeDate);
ctx.status(HttpServletResponse.SC_NO_CONTENT);
@@ -400,7 +401,7 @@ public void getAll(@NotNull Context ctx) {
public void getOne(@NotNull Context ctx, @NotNull String rating) {
try (final Timer.Context ignored = markAndTime(GET_ONE)) {
- String officeId = ctx.queryParam(OFFICE);
+ String officeId = requiredParam(ctx, OFFICE);
String timezone = ctx.queryParamAsClass(TIMEZONE, String.class).getOrDefault("UTC");
VerticalDatum verticalDatum = VerticalDatum.getVerticalDatum(ctx.queryParam(DATUM));
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingLatestController.java b/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingLatestController.java
index c7db67d17..aff922c10 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingLatestController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingLatestController.java
@@ -28,6 +28,7 @@
import static cwms.cda.api.Controllers.OFFICE;
import static cwms.cda.api.Controllers.RATING_ID;
import static cwms.cda.api.Controllers.STATUS_200;
+import static cwms.cda.api.Controllers.requiredParam;
import static cwms.cda.data.dao.JooqDao.getDslContext;
import com.codahale.metrics.MetricRegistry;
@@ -91,7 +92,7 @@ public void handle(@NotNull Context ctx) throws Exception {
ContentType contentType = new ContentType(ctx.contentType() != null ? ctx.contentType() : Formats.JSONV2);
- String officeId = ctx.queryParam(OFFICE);
+ String officeId = requiredParam(ctx, OFFICE);
if (!contentType.toString().equals(Formats.JSONV2) && !contentType.toString().equals(Formats.XMLV2)) {
ctx.status(HttpCode.UNSUPPORTED_MEDIA_TYPE);
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingSpecController.java b/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingSpecController.java
index b477afdb7..63f157b45 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingSpecController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingSpecController.java
@@ -153,7 +153,7 @@ public void getAll(Context ctx) {
+ "the rating-id of the Rating Spec to be included in the response")
},
queryParams = {
- @OpenApiParam(name = OFFICE, required = true, description = "Specifies the "
+ @OpenApiParam(name = OFFICE, description = "Specifies the "
+ "owning office of the Rating Specs whose data is to be included in "
+ "the response. If this field is not specified, matching rating "
+ "information from all offices shall be returned."),
@@ -275,9 +275,9 @@ public void delete(Context ctx, @NotNull String ratingSpecId) {
try (final Timer.Context ignored = markAndTime(DELETE)) {
DSLContext dsl = getDslContext(ctx);
- String office = ctx.queryParam(OFFICE);
+ String office = requiredParam(ctx, OFFICE);
RatingSpecDao ratingDao = getRatingSpecDao(dsl);
- JooqDao.DeleteMethod method = ctx.queryParamAsClass(METHOD, JooqDao.DeleteMethod.class).get();
+ JooqDao.DeleteMethod method = requiredParamAs(ctx, METHOD, JooqDao.DeleteMethod.class);
ratingDao.delete(office, method, ratingSpecId);
ctx.status(HttpServletResponse.SC_NO_CONTENT);
}
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingTemplateController.java b/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingTemplateController.java
index 5b5a1e49a..bd783d083 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingTemplateController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/rating/RatingTemplateController.java
@@ -154,7 +154,7 @@ private RatingTemplateDao getRatingTemplateDao(DSLContext dsl) {
+ " the template whose data is to be included in the response")
},
queryParams = {
- @OpenApiParam(name = OFFICE, required = true, description = "Specifies the "
+ @OpenApiParam(name = OFFICE, description = "Specifies the "
+ "owning office of the Rating Templates whose data is to be included"
+ " in the response. If this field is not specified, matching rating "
+ "information from all offices shall be returned."),
@@ -280,9 +280,9 @@ public void delete(Context ctx, String ratingTemplateId) {
try (final Timer.Context ignored = markAndTime(DELETE)){
DSLContext dsl = getDslContext(ctx);
- String office = ctx.queryParam(OFFICE);
+ String office = requiredParam(ctx, OFFICE);
RatingTemplateDao ratingDao = new RatingTemplateDao(dsl);
- JooqDao.DeleteMethod method = ctx.queryParamAsClass(METHOD, JooqDao.DeleteMethod.class).get();
+ JooqDao.DeleteMethod method = requiredParamAs(ctx, METHOD, JooqDao.DeleteMethod.class);
ratingDao.delete(office, method, ratingTemplateId);
ctx.status(HttpServletResponse.SC_NO_CONTENT);
}
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/timeseriesprofile/TimeSeriesProfileController.java b/cwms-data-api/src/main/java/cwms/cda/api/timeseriesprofile/TimeSeriesProfileController.java
index 38c2fdfab..6e535b9eb 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/timeseriesprofile/TimeSeriesProfileController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/timeseriesprofile/TimeSeriesProfileController.java
@@ -60,7 +60,8 @@ public TimeSeriesProfileController(MetricRegistry metrics) {
@OpenApi(
queryParams = {
- @OpenApiParam(name = OFFICE, description = "The office ID associated with the time series profile"),
+ @OpenApiParam(name = OFFICE, required = true,
+ description = "The office ID associated with the time series profile"),
},
pathParams = {
@OpenApiParam(name = PARAMETER_ID, description = "The key parameter ID associated with the time "
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/timeseriesprofile/TimeSeriesProfileDeleteController.java b/cwms-data-api/src/main/java/cwms/cda/api/timeseriesprofile/TimeSeriesProfileDeleteController.java
index 4a15335b2..04d8a4803 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/timeseriesprofile/TimeSeriesProfileDeleteController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/timeseriesprofile/TimeSeriesProfileDeleteController.java
@@ -59,7 +59,8 @@ public TimeSeriesProfileDeleteController(MetricRegistry metrics) {
@OpenApi(
queryParams = {
- @OpenApiParam(name = OFFICE, description = "The office associated with the time series profile"),
+ @OpenApiParam(name = OFFICE, required = true,
+ description = "The office associated with the time series profile"),
},
pathParams = {
@OpenApiParam(name = LOCATION_ID, description = "The location ID associated with the time "
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/timeseriesprofile/TimeSeriesProfileInstanceCreateController.java b/cwms-data-api/src/main/java/cwms/cda/api/timeseriesprofile/TimeSeriesProfileInstanceCreateController.java
index ccdeb5528..2f7682a34 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/timeseriesprofile/TimeSeriesProfileInstanceCreateController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/timeseriesprofile/TimeSeriesProfileInstanceCreateController.java
@@ -35,6 +35,7 @@
import cwms.cda.data.dao.timeseriesprofile.TimeSeriesProfileInstanceDao;
import cwms.cda.data.dto.timeseriesprofile.TimeSeriesProfile;
import cwms.cda.formatters.Formats;
+import cwms.cda.helpers.annotations.IgnoreRequiredQueryParamMismatch;
import io.javalin.http.Context;
import io.javalin.http.Handler;
import io.javalin.plugin.openapi.annotations.HttpMethod;
@@ -82,6 +83,7 @@ public TimeSeriesProfileInstanceCreateController(MetricRegistry metrics) {
@OpenApiResponse(status = "409", description = "Time series profile instance already exists")
}
)
+ @IgnoreRequiredQueryParamMismatch(parameterNames = {TIMEZONE})
@Override
public void handle(@NotNull Context ctx) {
try (final Timer.Context ignored = markAndTime(CREATE)) {
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/watersupply/AccountingCatalogController.java b/cwms-data-api/src/main/java/cwms/cda/api/watersupply/AccountingCatalogController.java
index 1e10310c5..1b192feaa 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/watersupply/AccountingCatalogController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/watersupply/AccountingCatalogController.java
@@ -47,6 +47,7 @@
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
+import com.google.common.flogger.FluentLogger;
import cwms.cda.api.Controllers;
import cwms.cda.api.errors.CdaError;
import cwms.cda.data.dao.watersupply.WaterContractDao;
@@ -57,6 +58,7 @@
import cwms.cda.data.dto.watersupply.WaterUserContract;
import cwms.cda.formatters.ContentType;
import cwms.cda.formatters.Formats;
+import cwms.cda.helpers.annotations.IgnoreRequiredQueryParamMismatch;
import io.javalin.core.util.Header;
import io.javalin.http.Context;
import io.javalin.http.Handler;
@@ -67,7 +69,6 @@
import io.javalin.plugin.openapi.annotations.OpenApiResponse;
import java.time.Instant;
import java.util.List;
-import com.google.common.flogger.FluentLogger;
import javax.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.jooq.DSLContext;
@@ -141,7 +142,7 @@ protected WaterSupplyAccountingDao getWaterSupplyAccountingDao(DSLContext dsl) {
method = HttpMethod.GET,
tags = {TAG}
)
-
+ @IgnoreRequiredQueryParamMismatch(parameterNames = {TIMEZONE})
@Override
public void handle(Context ctx) {
try (Timer.Context ignored = markAndTime(GET_ALL)) {
@@ -151,17 +152,15 @@ public void handle(Context ctx) {
final String locationId = ctx.pathParam(PROJECT_ID);
final Instant startTime = requiredInstant(ctx, START);
final Instant endTime = requiredInstant(ctx, END);
- final String units = ctx.queryParam(UNIT) != null ? ctx.queryParam(UNIT) : "cms";
- final boolean startInclusive = ctx.queryParam(START_TIME_INCLUSIVE) == null
- || Boolean.parseBoolean(ctx.queryParam(START_TIME_INCLUSIVE));
- final boolean endInclusive = ctx.queryParam(END_TIME_INCLUSIVE) == null
- || Boolean.parseBoolean(ctx.queryParam(END_TIME_INCLUSIVE));
- final boolean ascending = ctx.queryParam(ASCENDING) == null
- || Boolean.parseBoolean(ctx.queryParam(ASCENDING));
- final int rowLimit = ctx.queryParam(ROW_LIMIT) != null ? Integer.parseInt(ctx.queryParam(ROW_LIMIT)) : 0;
+ final String units = ctx.queryParamAsClass(UNIT, String.class).getOrDefault("cms");
+ final boolean startInclusive = ctx.queryParamAsClass(START_TIME_INCLUSIVE, Boolean.class)
+ .getOrDefault(true);
+ final boolean endInclusive = ctx.queryParamAsClass(END_TIME_INCLUSIVE, Boolean.class).getOrDefault(true);
+ final boolean ascending = ctx.queryParamAsClass(ASCENDING, Boolean.class).getOrDefault(true);
+ final int rowLimit = ctx.queryParamAsClass(ROW_LIMIT, Integer.class).getOrDefault(0);
DSLContext dsl = getDslContext(ctx);
- String formatHeader = ctx.header(Header.ACCEPT) != null ? ctx.header(Header.ACCEPT) : Formats.JSONV1;
+ String formatHeader = ctx.headerAsClass(Header.ACCEPT, String.class).getOrDefault(Formats.JSONV1);
ContentType contentType = Formats.parseHeader(formatHeader, WaterSupplyAccounting.class);
ctx.contentType(contentType.toString());
CwmsId projectLocation = new CwmsId.Builder().withOfficeId(office).withName(locationId).build();
diff --git a/cwms-data-api/src/main/java/cwms/cda/helpers/annotations/IgnoreRequiredQueryParamMismatch.java b/cwms-data-api/src/main/java/cwms/cda/helpers/annotations/IgnoreRequiredQueryParamMismatch.java
new file mode 100644
index 000000000..cd06733e1
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/helpers/annotations/IgnoreRequiredQueryParamMismatch.java
@@ -0,0 +1,51 @@
+/*
+ *
+ * MIT License
+ *
+ * Copyright (c) 2026 Hydrologic Engineering Center
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package cwms.cda.helpers.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/*
+ * Inform the OpenAPI documentation verification process to ignore mismatches
+ * in required query parameters between the annotated element and the OpenAPI specification.
+ * This is useful in cases where the implementation intentionally deviates from the specification
+ * for certain parameters, such as when a parameter is one of a set of mutually exclusive required parameters.
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IgnoreRequiredQueryParamMismatch {
+ /**
+ * Returns the names of the parameters that are being ignored in the required parameter mismatch check.
+ *
+ * @return the names of the parameters to be ignored
+ */
+ String[] parameterNames();
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/api/OpenApiDocTest.java b/cwms-data-api/src/test/java/cwms/cda/api/OpenApiDocTest.java
index 48322cde5..660b55495 100644
--- a/cwms-data-api/src/test/java/cwms/cda/api/OpenApiDocTest.java
+++ b/cwms-data-api/src/test/java/cwms/cda/api/OpenApiDocTest.java
@@ -210,7 +210,7 @@ private void testPathParameters(List expectedPathParameters, S
assertAll(
() -> assertTrue(receivedItems.isEmpty(), "Found used undocumented path parameter: " + extraInfo),
() -> assertTrue(missingItems.isEmpty(), "Found documented path parameter that is not used: " + missingInfo),
- () -> assertAll(expectedParams.stream().map(expectedParam -> testParamInfo(expectedParam, verifiedUsages)))
+ () -> assertAll(expectedParams.stream().map(expectedParam -> testParamInfo(expectedParam, verifiedUsages, true)))
);
}
@@ -246,11 +246,11 @@ private void testQueryParameters(List expectedQueryParameters,
.collect(Collectors.joining(", "));
assertAll(() -> assertTrue(receivedItems.isEmpty(), "Found used undocumented query parameter: " + extraInfo),
() -> assertTrue(missingItems.isEmpty(), "Found documented query parameter that is not used: " + missingInfo),
- () -> assertAll(expectedParams.stream().map(expectedParam -> testParamInfo(expectedParam, verifiedUsages))));
+ () -> assertAll(expectedParams.stream().map(expectedParam -> testParamInfo(expectedParam, verifiedUsages, false))));
}
private Executable testParamInfo(OpenApiParamInfo expectedParam,
- Set receivedQueryParameters) {
+ Set receivedQueryParameters, boolean pathParam) {
OpenApiParamUsageInfo receivedInfo = receivedQueryParameters.stream()
.filter(receivedUsageInfo -> receivedUsageInfo.getParamInfo()
.getName()
@@ -263,7 +263,18 @@ private Executable testParamInfo(OpenApiParamInfo expectedParam,
//Real tests
return () -> assertAll(() -> assertTrue(receivedInfo.isUsed(), "Unable to find a usage of documented parameter: " + expectedParam.getName()),
- () -> assertTrue(receivedInfo.isNullHandled(), "Unable to find a null handled usage of documented parameter: " + expectedParam.getName()));
+ () -> assertTrue(receivedInfo.isNullHandled(), "Unable to find a null handled usage of documented parameter: " + expectedParam.getName()),
+ // Disabled type checking due to many parameters being read as strings and then converted,
+ // which is a valid way to read parameters, but makes it difficult to verify the type is correct.
+ // We can re-enable this in the future if we want to be more strict about how parameters are read.
+ //() -> assertEquals(receivedInfo.getParamInfo().getType(), expectedParam.getType(), "Incorrect type for parameter: " + expectedParam.getName()),
+ () -> assertEquals(receivedInfo.getParamInfo().getName(), expectedParam.getName(), "Incorrect name for parameter: " + expectedParam.getName()),
+ () -> {
+ if (!pathParam && !expectedParam.ignoreRequired()) // Path parameters are always required, so we don't need to check that.
+ {
+ assertEquals(receivedInfo.getParamInfo().isRequired(), expectedParam.isRequired(), "Incorrect required status for parameter: " + expectedParam.getName());
+ }
+ });
}
private OpenApiParamUsage parseParamInfo(CompilationUnit unit, Class> clazz, Method method) {
@@ -458,7 +469,7 @@ private OpenApiParamUsageInfo readUsageFromCall(CompilationUnit unit, Class> c
boolean used = true;
boolean nullHandled = true;
if (!required) {
- //Check if null is handled via getOrDefault
+ //TODO: Check if null is handled via getOrDefault
}
return new OpenApiParamUsageInfo(new OpenApiParamInfo(paramName, required, paramClass), used, nullHandled);
}).orElseGet(() -> {
diff --git a/cwms-data-api/src/test/java/helpers/OpenApiParamInfo.java b/cwms-data-api/src/test/java/helpers/OpenApiParamInfo.java
index ef85450d6..38e6feae3 100644
--- a/cwms-data-api/src/test/java/helpers/OpenApiParamInfo.java
+++ b/cwms-data-api/src/test/java/helpers/OpenApiParamInfo.java
@@ -26,6 +26,7 @@ public class OpenApiParamInfo {
private String name;
private final boolean required;
private final Class> type;
+ private boolean ignoreRequired = false;
public OpenApiParamInfo(String name, boolean required, Class> type) {
this.name = name;
@@ -38,6 +39,15 @@ public OpenApiParamInfo setName(String name) {
return this;
}
+ public OpenApiParamInfo setIgnoreRequired(boolean ignoreRequired) {
+ this.ignoreRequired = ignoreRequired;
+ return this;
+ }
+
+ public boolean ignoreRequired() {
+ return ignoreRequired;
+ }
+
public String getName() {
return name;
}
diff --git a/cwms-data-api/src/test/java/helpers/OpenApiTestHelper.java b/cwms-data-api/src/test/java/helpers/OpenApiTestHelper.java
index 919b2abaa..b3e62d21a 100644
--- a/cwms-data-api/src/test/java/helpers/OpenApiTestHelper.java
+++ b/cwms-data-api/src/test/java/helpers/OpenApiTestHelper.java
@@ -8,6 +8,7 @@
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
+import cwms.cda.helpers.annotations.IgnoreRequiredQueryParamMismatch;
import io.javalin.plugin.openapi.annotations.OpenApi;
import io.javalin.plugin.openapi.annotations.OpenApiParam;
import java.io.IOException;
@@ -47,10 +48,20 @@ public static OpenApiDocInfo readDocParams(Method m) {
if (oa == null || oa.ignore()) {
return new OpenApiDocInfo(m, true);
}
+ String[] ignored = new String[0];
+ IgnoreRequiredQueryParamMismatch ignore = m.getAnnotation(IgnoreRequiredQueryParamMismatch.class);
+ if (ignore != null) {
+ ignored = ignore.parameterNames();
+ }
OpenApiDocInfo info = new OpenApiDocInfo(m, false);
for (OpenApiParam p : oa.queryParams()) {
if (p != null && !p.name().trim().isEmpty()) {
OpenApiParamInfo paramObj = new OpenApiParamInfo(p.name(), p.required(), p.type());
+ for (String ignoredName : ignored) {
+ if (p.name().equalsIgnoreCase(ignoredName)) {
+ paramObj = paramObj.setIgnoreRequired(true);
+ }
+ }
info.getQueryParameters().add(paramObj);
}
}