diff --git a/cwms-data-api/src/main/java/cwms/cda/api/CatalogController.java b/cwms-data-api/src/main/java/cwms/cda/api/CatalogController.java
index af9cc6093..8562d5988 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/CatalogController.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/CatalogController.java
@@ -34,6 +34,7 @@
import org.jetbrains.annotations.NotNull;
import org.jooq.DSLContext;
import org.owasp.html.PolicyFactory;
+import cwms.cda.api.errors.UnsupportedParametersException;
public class CatalogController implements CrudHandler {
@@ -102,11 +103,13 @@ public void getAll(Context ctx) {
),
@OpenApiParam(name = TIMESERIES_CATEGORY_LIKE,
description = "Posix regular expression "
- + "matching against the timeseries category id"
+ + "matching against the timeseries category id. Note: This parameter is "
+ + "unsupported when dataset is Locations."
),
@OpenApiParam(name = TIMESERIES_GROUP_LIKE,
description = "Posix regular expression "
- + "matching against the timeseries group id"
+ + "matching against the timeseries group id. Note: This parameter is "
+ + "unsupported when dataset is Locations."
),
@OpenApiParam(name = LOCATION_CATEGORY_LIKE,
description = "Posix regular expression "
@@ -122,7 +125,8 @@ public void getAll(Context ctx) {
+ "items with no bounding office set will not be present in results."),
@OpenApiParam(name = INCLUDE_EXTENTS, type = Boolean.class,
description = "Whether the returned catalog entries should include timeseries "
- + "extents. Only valid for TIMESERIES. "
+ + "extents. Only valid for TIMESERIES. Note: This parameter is "
+ + "unsupported when dataset is Locations."
+ "Default is " + INCLUDE_EXTENTS_DEFAULT + "."),
@OpenApiParam(name = EXCLUDE_EMPTY, type = Boolean.class,
description = "Specifies "
@@ -131,7 +135,8 @@ public void getAll(Context ctx) {
+ "'empty' is defined as VERSION_TIME, EARLIEST_TIME, LATEST_TIME "
+ "and LAST_UPDATE all being null. This parameter does not control "
+ "whether the extents are returned to the user, only whether matching "
- + "timeseries are excluded. Only valid for TIMESERIES. "
+ + "timeseries are excluded. Only valid for TIMESERIES. Note: This parameter is "
+ + "unsupported when dataset is Locations."
+ "Default is " + EXCLUDE_EMPTY_DEFAULT + "."),
@OpenApiParam(name = LOCATION_KIND_LIKE,
description = "Posix regular expression matching "
@@ -299,8 +304,7 @@ private static void warnAboutNotSupported(@NotNull Context ctx, String[] warnAbo
notSupported.retainAll(queryParamMap.keySet());
if (!notSupported.isEmpty()) {
- throw new IllegalArgumentException("The following parameters are not yet "
- + "supported for this method: " + notSupported);
+ throw new UnsupportedParametersException(List.copyOf(notSupported));
}
}
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/errors/UnsupportedParametersException.java b/cwms-data-api/src/main/java/cwms/cda/api/errors/UnsupportedParametersException.java
new file mode 100644
index 000000000..4935087f7
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/api/errors/UnsupportedParametersException.java
@@ -0,0 +1,59 @@
+package cwms.cda.api.errors;
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Exception indicating that one or more provided query parameters are not supported
+ * for the requested operation. Intended for direct user feedback (HTTP 400).
+ * Default CDA_MESSAGE specific to Locations catalog.
+ */
+public final class UnsupportedParametersException extends ApplicationException
+{
+ private static final Level LOG_LEVEL = Level.INFO;
+ public static final String UNSUPPORTED_QUERY_PARAMETERS = "unsupported query parameters";
+ public static final String MESSAGE = "unsupported query parameters present";
+ public static final String CDA_MESSAGE = "Unsupported parameter(s) for Locations catalog";
+ private final Map details = new LinkedHashMap<>();
+
+ public UnsupportedParametersException(List params)
+ {
+ this(MESSAGE, params);
+ }
+
+ public UnsupportedParametersException(String message, List params)
+ {
+ super(message, USER_INPUT_SOURCE, CDA_MESSAGE, HttpServletResponse.SC_BAD_REQUEST,
+ LOG_LEVEL, buildDetailsMap(params), null);
+ details.put(UNSUPPORTED_QUERY_PARAMETERS, String.join(",", params));
+ }
+
+ // option for controller-specific CDA messages
+ public UnsupportedParametersException(String message, String cdaMessage, List params)
+ {
+ super(message, USER_INPUT_SOURCE, cdaMessage, HttpServletResponse.SC_BAD_REQUEST,
+ LOG_LEVEL, buildDetailsMap(params), null);
+ details.put(UNSUPPORTED_QUERY_PARAMETERS, String.join(",", params));
+ }
+
+ public UnsupportedParametersException(String param)
+ {
+ this(MESSAGE, List.of(param));
+ }
+
+ @Override
+ public Map getDetails()
+ {
+ return details;
+ }
+
+ private static Map buildDetailsMap(List fields)
+ {
+ Map details = new LinkedHashMap<>();
+ details.put(UNSUPPORTED_QUERY_PARAMETERS, String.join(",", fields));
+ return details;
+ }
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/test/java/cwms/cda/api/CatalogControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/CatalogControllerTestIT.java
index 55df8eb37..cc10e68b5 100644
--- a/cwms-data-api/src/test/java/cwms/cda/api/CatalogControllerTestIT.java
+++ b/cwms-data-api/src/test/java/cwms/cda/api/CatalogControllerTestIT.java
@@ -686,4 +686,59 @@ void testFilterLocations() throws Exception{
.statusCode(is(HttpServletResponse.SC_OK))
.body("entries.size()", is(0));
}
+
+ @Test
+ void test_locations_unsupported_param_single() {
+ // When requesting the LOCATIONS catalog, certain timeseries params are not supported.
+ // Verify that supplying a single unsupported parameter results in a 400 with only that parameter listed.
+ Response resp =
+ given()
+ .log().ifValidationFails(LogDetail.ALL, true)
+ .accept(Formats.JSON)
+ .queryParam(OFFICE, OFFICE)
+ .queryParam(INCLUDE_EXTENTS, true)
+ .when()
+ .get("/catalog/LOCATIONS")
+ .then()
+ .log().ifValidationFails(LogDetail.ALL, true)
+ .assertThat()
+ .statusCode(is(HttpServletResponse.SC_BAD_REQUEST))
+ .body("message", is("Unsupported parameter(s) for Locations catalog"))
+ .body("details.'unsupported query parameters'", is(INCLUDE_EXTENTS))
+ .extract()
+ .response();
+
+ // Ensure only the provided unsupported parameter is mentioned
+ String details = resp.path("details.'unsupported query parameters'");
+ assertEquals(INCLUDE_EXTENTS, details);
+ }
+
+ @Test
+ void test_locations_unsupported_params_multiple() {
+ // Verify that if multiple unsupported params are provided, only those provided are reported.
+ Response resp =
+ given()
+ .log().ifValidationFails(LogDetail.ALL, true)
+ .accept(Formats.JSON)
+ .queryParam(OFFICE, OFFICE)
+ .queryParam(INCLUDE_EXTENTS, true)
+ .queryParam(EXCLUDE_EMPTY, true)
+ .when()
+ .get("/catalog/LOCATIONS")
+ .then()
+ .log().ifValidationFails(LogDetail.ALL, true)
+ .assertThat()
+ .statusCode(is(HttpServletResponse.SC_BAD_REQUEST))
+ .body("message", is("Unsupported parameter(s) for Locations catalog"))
+ .body("details", hasKey("unsupported query parameters"))
+ .extract()
+ .response();
+
+ String details = resp.path("details.'unsupported query parameters'");
+ assertNotNull(details);
+ String[] parts = details.split(",");
+ assertEquals(2, parts.length, "Expected exactly two unsupported parameters to be reported");
+ // Order of parameters in the message is not guaranteed; verify as a set
+ assertTrue(List.of(parts).containsAll(List.of(INCLUDE_EXTENTS, EXCLUDE_EMPTY)));
+ }
}