Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions cwms-data-api/src/main/java/cwms/cda/api/CatalogController.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -102,11 +103,13 @@ public void getAll(Context ctx) {
),
@OpenApiParam(name = TIMESERIES_CATEGORY_LIKE,
description = "Posix <a href=\"regexp.html\">regular expression</a> "
+ "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 <a href=\"regexp.html\">regular expression</a> "
+ "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 <a href=\"regexp.html\">regular expression</a> "
Expand All @@ -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 "
Expand All @@ -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 <a href=\"regexp.html\">regular expression</a> matching "
Expand Down Expand Up @@ -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));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Serializable> details = new LinkedHashMap<>();

public UnsupportedParametersException(List<String> params)
{
this(MESSAGE, params);
}

public UnsupportedParametersException(String message, List<String> 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<String> 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<String, Serializable> getDetails()
{
return details;
}

private static Map<String, Serializable> buildDetailsMap(List<String> fields)
{
Map<String, Serializable> details = new LinkedHashMap<>();
details.put(UNSUPPORTED_QUERY_PARAMETERS, String.join(",", fields));
return details;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
}