Skip to content

Commit

Permalink
#1644 support query params in the style where not comma separation is…
Browse files Browse the repository at this point in the history
… used, but duplicated keys

* e.g.: `?fields=thingId&fields=policyId`

Signed-off-by: Thomas Jäckle <thomas.jaeckle@beyonnex.io>
  • Loading branch information
thjaeckle committed Jun 12, 2023
1 parent 432e65c commit 38c388b
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
Expand Down Expand Up @@ -136,11 +137,15 @@ private static Attributes createSupervisionStrategy() {
/**
* Calculates a JsonFieldSelector from the passed {@code fieldsString}.
*
* @param fieldsString the fields as string.
* @param fields the fields as potentially comma separated strings.
* @return the Optional JsonFieldSelector
*/
protected static Optional<JsonFieldSelector> calculateSelectedFields(final Optional<String> fieldsString) {
return fieldsString.map(fs -> JsonFactory.newFieldSelector(fs, JSON_FIELD_SELECTOR_PARSE_OPTIONS));
protected static Optional<JsonFieldSelector> calculateSelectedFields(final List<String> fields) {
if (fields.isEmpty()) {
return Optional.empty();
} else {
return Optional.of(JsonFactory.newFieldSelector(fields, JSON_FIELD_SELECTOR_PARSE_OPTIONS));
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,10 @@ private Route connections(final RequestContext ctx, final DittoHeaders dittoHead
concat(
get(() -> // GET /connections?ids-only=false
parameterOptional(ConnectionsParameter.IDS_ONLY.toString(), idsOnly ->
parameterOptional(ConnectionsParameter.FIELDS.toString(), fieldsString ->
parameterList(ConnectionsParameter.FIELDS.toString(), fields ->
{
final Optional<JsonFieldSelector> selectedFields =
calculateSelectedFields(fieldsString);
calculateSelectedFields(fields);
return handlePerRequest(ctx, RetrieveConnections.newInstance(
idsOnly.map(Boolean::valueOf).orElseGet(() -> selectedFields
.filter(sf -> sf.getPointers().size() == 1 &&
Expand Down Expand Up @@ -219,9 +219,9 @@ private Route connectionsEntry(final RequestContext ctx,
)
),
get(() -> // GET /connections/<connectionId>
parameterOptional(ConnectionsParameter.FIELDS.toString(), fieldsString ->
parameterList(ConnectionsParameter.FIELDS.toString(), fields ->
handlePerRequest(ctx, RetrieveConnection.of(connectionId,
calculateSelectedFields(fieldsString).orElse(null),
calculateSelectedFields(fields).orElse(null),
dittoHeaders)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@ private Route policyId(final RequestContext ctx, final DittoHeaders dittoHeaders
return pathEndOrSingleSlash(() ->
concat(
// GET /policies/<policyId>?fields=<fieldsString>
get(() -> parameterOptional(PoliciesParameter.FIELDS.toString(), fieldsString ->
get(() -> parameterList(PoliciesParameter.FIELDS.toString(), fields ->
handlePerRequest(ctx, RetrievePolicy.of(policyId, dittoHeaders,
calculateSelectedFields(fieldsString).orElse(null)))
calculateSelectedFields(fields).orElse(null)))
)),
put(() -> // PUT /policies/<policyId>
ensureMediaTypeJsonWithFallbacksThenExtractDataBytes(ctx, dittoHeaders,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ private Route buildThingsSseRoute(final RequestContext ctx,
// /things/<thingId>
rawPathPrefix(PathMatchers.slash().concat(PathMatchers.segment()), thingId ->
parameterMap(parameters -> {
final HashMap<String, String> params = new HashMap<>(parameters);
final Map<String, String> params = new HashMap<>(parameters);
params.put(ThingsParameter.IDS.toString(), thingId);

return concat(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ private Route features(final RequestContext ctx, final DittoHeaders dittoHeaders
return pathEndOrSingleSlash(() ->
concat(
// GET /features?fields=<fieldsString>
get(() -> parameterOptional(ThingsParameter.FIELDS.toString(), fieldsString ->
get(() -> parameterList(ThingsParameter.FIELDS.toString(), fields ->
handlePerRequest(ctx, RetrieveFeatures.of(thingId,
calculateSelectedFields(fieldsString).orElse(null),
calculateSelectedFields(fields).orElse(null),
dittoHeaders))
)
),
Expand Down Expand Up @@ -143,9 +143,9 @@ private Route featuresEntry(final RequestContext ctx, final DittoHeaders dittoHe
pathEndOrSingleSlash(() ->
concat(
// GET /features/{featureId}?fields=<fieldsString>
get(() -> parameterOptional(ThingsParameter.FIELDS.toString(),
fieldsString -> handlePerRequest(ctx, RetrieveFeature.of(thingId, featureId,
calculateSelectedFields(fieldsString).orElse(null),
get(() -> parameterList(ThingsParameter.FIELDS.toString(), fields ->
handlePerRequest(ctx, RetrieveFeature.of(thingId, featureId,
calculateSelectedFields(fields).orElse(null),
dittoHeaders))
)
),
Expand Down Expand Up @@ -235,10 +235,10 @@ private Route featuresEntryProperties(final RequestContext ctx, final DittoHeade
pathEndOrSingleSlash(() ->
concat(
// GET /features/{featureId}/properties?fields=<fieldsString>
get(() -> parameterOptional(ThingsParameter.FIELDS.toString(),
fieldsString -> handlePerRequest(ctx,
get(() -> parameterList(ThingsParameter.FIELDS.toString(), fields ->
handlePerRequest(ctx,
RetrieveFeatureProperties.of(thingId, featureId,
calculateSelectedFields(fieldsString).orElse(null),
calculateSelectedFields(fields).orElse(null),
dittoHeaders))
)
),
Expand Down Expand Up @@ -334,10 +334,10 @@ private Route featuresEntryDesiredProperties(final RequestContext ctx, final Dit
pathEndOrSingleSlash(() ->
concat(
// GET /features/{featureId}/desiredProperties?fields=<fieldsString>
get(() -> parameterOptional(ThingsParameter.FIELDS.toString(),
fieldsString -> handlePerRequest(ctx,
get(() -> parameterList(ThingsParameter.FIELDS.toString(), fields ->
handlePerRequest(ctx,
RetrieveFeatureDesiredProperties.of(thingId, featureId,
calculateSelectedFields(fieldsString).orElse(null),
calculateSelectedFields(fields).orElse(null),
dittoHeaders))
)
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@

import static org.eclipse.ditto.base.model.exceptions.DittoJsonException.wrapJsonRuntimeException;

import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

import javax.annotation.Nullable;

Expand Down Expand Up @@ -177,49 +177,56 @@ private Route things(final RequestContext ctx, final DittoHeaders dittoHeaders)
}

private Route buildRetrieveThingsRoute(final RequestContext ctx, final DittoHeaders dittoHeaders) {
// GET /things?ids=...
return parameter(ThingsParameter.IDS.toString(), idsString ->
parameterOptional(ThingsParameter.FIELDS.toString(), fieldsString ->
handlePerRequest(ctx, RetrieveThings.getBuilder(splitThingIdString(idsString))
.selectedFields(calculateSelectedFields(fieldsString))

return parameterList(ThingsParameter.IDS.toString(), idsStrings -> {
if (!idsStrings.isEmpty()) {
// GET /things?ids=...
return parameterList(ThingsParameter.FIELDS.toString(), fields ->
handlePerRequest(ctx, RetrieveThings.getBuilder(splitThingIdStrings(idsStrings))
.selectedFields(calculateSelectedFields(fields))
.dittoHeaders(dittoHeaders)
.build(),
(responseValue, response) ->
response.withEntity(determineResponseContentType(ctx), responseValue.toString())
response.withEntity(determineResponseContentType(ctx),
responseValue.toString())
)
)
).orElse( // GET /things
thingSearchParameterOptional(params ->
parameterOptional(ThingsParameter.FIELDS.toString(), fieldsString ->
handlePerRequest(ctx, QueryThings.of(null, // allow filter only on /search/things but not here
ThingSearchRoute.calculateOptions(params.get(ThingSearchParameter.OPTION)),
calculateSelectedFields(fieldsString).orElse(null),
);
} else {
// GET /things
return thingSearchParameterOptional(params ->
parameterList(ThingsParameter.FIELDS.toString(), fields ->
handlePerRequest(ctx,
QueryThings.of(null, // allow filter only on /search/things but not here
ThingSearchRoute.calculateOptions(
params.get(ThingSearchParameter.OPTION)),
calculateSelectedFields(fields).orElse(null),
ThingSearchRoute.calculateNamespaces(
params.get(ThingSearchParameter.NAMESPACES)),
dittoHeaders),
(responseValue, response) ->
transformQueryThingsResult(ctx, responseValue, response)
)
)
)
);
);
}
});
}

private Route thingSearchParameterOptional(
final Function<EnumMap<ThingSearchParameter, Optional<String>>, Route> inner) {
final Function<EnumMap<ThingSearchParameter, List<String>>, Route> inner) {
return thingSearchParameterOptionalImpl(ThingSearchParameter.values(),
new EnumMap<>(ThingSearchParameter.class), inner);
}

private Route thingSearchParameterOptionalImpl(final ThingSearchParameter[] values,
final EnumMap<ThingSearchParameter, Optional<String>> accumulator,
final Function<EnumMap<ThingSearchParameter, Optional<String>>, Route> inner) {
final EnumMap<ThingSearchParameter, List<String>> accumulator,
final Function<EnumMap<ThingSearchParameter, List<String>>, Route> inner) {
if (accumulator.size() >= values.length) {
return inner.apply(accumulator);
} else {
final ThingSearchParameter parameter = values[accumulator.size()];
return parameterOptional(parameter.toString(), parameterValueOptional -> {
accumulator.put(parameter, parameterValueOptional);
return parameterList(parameter.toString(), parameterValues -> {
accumulator.put(parameter, parameterValues);
return thingSearchParameterOptionalImpl(values, accumulator, inner);
});
}
Expand Down Expand Up @@ -271,12 +278,13 @@ private static akka.http.javadsl.model.ContentType.NonBinary determineResponseCo
return contentType;
}

private static List<ThingId> splitThingIdString(final String thingIdString) {
private static List<ThingId> splitThingIdStrings(final List<String> thingIdStrings) {
final List<ThingId> result;
if (thingIdString.isEmpty()) {
if (thingIdStrings.isEmpty()) {
result = List.of();
} else {
result = Stream.of(thingIdString.split(","))
result = thingIdStrings.stream()
.flatMap(tid -> Arrays.stream(tid.split(",")))
.map(ThingId::of)
.toList();
}
Expand Down Expand Up @@ -329,9 +337,9 @@ private Route thingsEntry(final RequestContext ctx, final DittoHeaders dittoHead
return pathEndOrSingleSlash(() ->
concat(
// GET /things/<thingId>?fields=<fieldsString>
get(() -> parameterOptional(ThingsParameter.FIELDS.toString(), fieldsString ->
get(() -> parameterList(ThingsParameter.FIELDS.toString(), fields ->
handlePerRequest(ctx, RetrieveThing.getBuilder(thingId, dittoHeaders)
.withSelectedFields(calculateSelectedFields(fieldsString)
.withSelectedFields(calculateSelectedFields(fields)
.orElse(null))
.build())
)
Expand Down Expand Up @@ -433,9 +441,9 @@ private Route thingsEntryAttributes(final RequestContext ctx, final DittoHeaders
pathEndOrSingleSlash(() ->
concat(
// GET /things/<thingId>/attributes?fields=<fieldsString>
get(() -> parameterOptional(ThingsParameter.FIELDS.toString(), fieldsString ->
get(() -> parameterList(ThingsParameter.FIELDS.toString(), fields ->
handlePerRequest(ctx, RetrieveAttributes.of(thingId,
calculateSelectedFields(fieldsString).orElse(null),
calculateSelectedFields(fields).orElse(null),
dittoHeaders))
)
),
Expand Down
Loading

0 comments on commit 38c388b

Please sign in to comment.