Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1644 support query params in the style where not comma separation is used, but duplicated keys #1652

Merged
merged 2 commits into from
Jun 22, 2023
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
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