Skip to content

Commit

Permalink
[#1034] added WoT Discovery compatible API for GET /api/2/things to r…
Browse files Browse the repository at this point in the history
…etrieve a list of TDs - including adding "Links" as http headers for the "next" page

* also adjusted OpenAPI docs accordingly

Signed-off-by: Thomas Jaeckle <thomas.jaeckle@bosch.io>
  • Loading branch information
thjaeckle committed Feb 24, 2022
1 parent b555036 commit a3f2401
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 62 deletions.
24 changes: 9 additions & 15 deletions documentation/src/main/resources/openapi/ditto-api-2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,41 +36,37 @@ security:
paths:
/things:
get:
summary: Retrieve multiple things with specified IDs
summary: Retrieve visible things or things with specified IDs
description: |-
Returns all things passed in by the required parameter `ids`, which you (the authorized subject) are allowed to read.
Returns all visible things or things passed in by the required parameter `ids`, which you (the authorized subject) are allowed to read.
Optionally, if you want to retrieve only some of the thing's fields, you can use the specific field selectors (see parameter `fields`) .
Tip: If you don't know the thing IDs, start with the search resource.
Tip: In order to formulate a `filter` which things to search for, take a look at the `/search` resource.
tags:
- Things
parameters:
- name: ids
in: query
description: |-
Contains a comma-separated list of `thingId`s to retrieve in one
single request.
required: true
description: Contains a comma-separated list of `thingId`s to retrieve in one single request.
required: false
schema:
type: string
- $ref: '#/components/parameters/ThingFieldsQueryParam'
- $ref: '#/components/parameters/TimeoutParam'
responses:
'200':
description: |-
The successfully completed request contains as its result the first 200 for the user available Things.
The Things are sorted in the same order as the Thing IDs were provided in the `ids` parameter.
The successfully completed request contains a list of the for the user available Things, or the Things asked for via the `ids` paramter.
The Things are sorted either by their ID, or in the same order as the Thing IDs were provided in the `ids` parameter.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Thing'
'400':
description: |-
The request could not be completed. At least one of the defined
query parameters was invalid.
description: The request could not be completed. At least one of the defined query parameters was invalid.
content:
application/json:
schema:
Expand All @@ -82,9 +78,7 @@ paths:
schema:
$ref: '#/components/schemas/AdvancedError'
'414':
description: |-
The request could not be completed due to an URI length exceeding 8k
characters.
description: The request could not be completed due to an URI length exceeding 8k characters.
post:
summary: Create a new thing
description: |-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,30 @@
#
# SPDX-License-Identifier: EPL-2.0
get:
summary: Retrieve multiple things with specified IDs
summary: Retrieve visible things or things with specified IDs
description: |-
Returns all things passed in by the required parameter `ids`, which you (the authorized subject) are allowed to read.
Returns all visible things or things passed in by the required parameter `ids`, which you (the authorized subject) are allowed to read.
Optionally, if you want to retrieve only some of the thing's fields, you can use the specific field selectors (see parameter `fields`) .
Tip: If you don't know the thing IDs, start with the search resource.
Tip: In order to formulate a `filter` which things to search for, take a look at the `/search` resource.
tags:
- Things
parameters:
- name: ids
in: query
description: |-
Contains a comma-separated list of `thingId`s to retrieve in one
single request.
required: true
Contains a comma-separated list of `thingId`s to retrieve in one single request.
required: false
schema:
type: string
- $ref: '../../parameters/thingFieldsQueryParam.yml'
- $ref: '../../parameters/timeoutParam.yml'
responses:
'200':
description: |-
The successfully completed request contains as its result the first 200 for the user available Things.
The Things are sorted in the same order as the Thing IDs were provided in the `ids` parameter.
The successfully completed request contains a list of the for the user available Things, or the Things asked for via the `ids` paramter.
The Things are sorted either by their ID, or in the same order as the Thing IDs were provided in the `ids` parameter.
content:
application/json:
schema:
Expand All @@ -42,8 +41,7 @@ get:
$ref: '../../schemas/things/thing.yml'
'400':
description: |-
The request could not be completed. At least one of the defined
query parameters was invalid.
The request could not be completed. At least one of the defined query parameters was invalid.
content:
application/json:
schema:
Expand All @@ -56,8 +54,7 @@ get:
$ref: '../../schemas/errors/advancedError.yml'
'414':
description: |-
The request could not be completed due to an URI length exceeding 8k
characters.
The request could not be completed due to an URI length exceeding 8k characters.
post:
summary: Create a new thing
description: |-
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ description: |-
JSON string of the definition to be modified. Consider that the
value has to be a JSON string or `null`, examples:
* an string: `"value"` - Currently the definition should follow the pattern: [_a-zA-Z0-9\-]:[_a-zA-Z0-9\-]:[_a-zA-Z0-9\-]
* a string: `"value"` - Currently the definition should follow the pattern: [_a-zA-Z0-9\-]:[_a-zA-Z0-9\-]:[_a-zA-Z0-9\-]
* an empty string: `""`
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -51,13 +53,11 @@
import akka.NotUsed;
import akka.actor.ActorRef;
import akka.actor.Status;
import akka.http.javadsl.model.ContentTypes;
import akka.http.javadsl.model.HttpResponse;
import akka.http.javadsl.model.MediaTypes;
import akka.http.javadsl.server.AllDirectives;
import akka.http.javadsl.server.RequestContext;
import akka.http.javadsl.server.Route;
import akka.japi.function.Function;
import akka.stream.ActorAttributes;
import akka.stream.Attributes;
import akka.stream.Supervision;
Expand Down Expand Up @@ -183,7 +183,7 @@ protected Route handlePerRequest(final RequestContext ctx, final Command<?> comm
}

protected Route handlePerRequest(final RequestContext ctx, final Command<?> command,
final Function<JsonValue, JsonValue> responseTransformFunction) {
@Nullable final BiFunction<JsonValue, HttpResponse, HttpResponse> responseTransformFunction) {

return handlePerRequest(ctx, command.getDittoHeaders(), Source.empty(),
emptyRequestBody -> command, responseTransformFunction);
Expand All @@ -193,12 +193,13 @@ protected Route handlePerRequest(final RequestContext ctx,
final DittoHeaders dittoHeaders,
final Source<ByteString, ?> payloadSource,
final Function<String, Command<?>> requestJsonToCommandFunction,
@Nullable final Function<JsonValue, JsonValue> responseTransformFunction) {
@Nullable final BiFunction<JsonValue, HttpResponse, HttpResponse> responseTransformFunction) {

return withCustomRequestTimeout(dittoHeaders.getTimeout().orElse(null),
this::validateCommandTimeout,
timeout -> doHandlePerRequest(ctx, dittoHeaders.toBuilder().timeout(timeout).build(), payloadSource,
requestJsonToCommandFunction, responseTransformFunction));
requestJsonToCommandFunction,
null != responseTransformFunction ? responseTransformFunction : (json, resp) -> resp));
}

protected <M> M runWithSupervisionStrategy(final RunnableGraph<M> graph) {
Expand All @@ -209,7 +210,7 @@ private Route doHandlePerRequest(final RequestContext ctx,
final DittoHeaders dittoHeaders,
final Source<ByteString, ?> payloadSource,
final Function<String, Command<?>> requestJsonToCommandFunction,
@Nullable final Function<JsonValue, JsonValue> responseTransformFunction) {
@Nullable final BiFunction<JsonValue, HttpResponse, HttpResponse> responseValueTransformFunction) {

final CompletableFuture<HttpResponse> httpResponseFuture = new CompletableFuture<>();

Expand All @@ -235,7 +236,7 @@ private Route doHandlePerRequest(final RequestContext ctx,
);

// optional step: transform the response entity:
if (responseTransformFunction != null) {
if (responseValueTransformFunction != null) {
final CompletableFuture<HttpResponse> transformedResponse = httpResponseFuture.thenApply(response -> {
final boolean isSuccessfulResponse = response.status().isSuccess();
// we have to check if response is empty, because otherwise we'll get an IOException when trying to
Expand All @@ -249,8 +250,7 @@ private Route doHandlePerRequest(final RequestContext ctx,
);
final JsonValue jsonValue = JsonFactory.readFrom(new InputStreamReader(inputStream));
try {
final JsonValue transformed = responseTransformFunction.apply(jsonValue);
return response.withEntity(ContentTypes.APPLICATION_JSON, transformed.toString());
return responseValueTransformFunction.apply(jsonValue, response);
} catch (final Exception e) {
throw JsonParseException.newBuilder()
.message("Could not transform JSON: " + e.getMessage())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,36 @@
import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;

import java.util.Map;
import java.util.function.BiFunction;

import javax.annotation.Nullable;

import org.eclipse.ditto.base.api.common.RetrieveConfig;
import org.eclipse.ditto.base.api.devops.ImmutableLoggerConfig;
import org.eclipse.ditto.base.api.devops.signals.commands.ChangeLogLevel;
import org.eclipse.ditto.base.api.devops.signals.commands.DevOpsCommand;
import org.eclipse.ditto.base.api.devops.signals.commands.ExecutePiggybackCommand;
import org.eclipse.ditto.base.api.devops.signals.commands.RetrieveLoggerConfig;
import org.eclipse.ditto.base.model.exceptions.DittoJsonException;
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.base.model.headers.DittoHeadersBuilder;
import org.eclipse.ditto.base.service.devops.DevOpsCommandsActor;
import org.eclipse.ditto.gateway.service.endpoints.directives.auth.DevOpsOAuth2AuthenticationDirective;
import org.eclipse.ditto.gateway.service.endpoints.directives.auth.DevopsAuthenticationDirective;
import org.eclipse.ditto.gateway.service.endpoints.routes.AbstractRoute;
import org.eclipse.ditto.gateway.service.endpoints.routes.QueryParametersToHeadersMap;
import org.eclipse.ditto.gateway.service.endpoints.routes.RouteBaseProperties;
import org.eclipse.ditto.gateway.service.util.config.endpoints.HttpConfig;
import org.eclipse.ditto.json.JsonFactory;
import org.eclipse.ditto.json.JsonObject;
import org.eclipse.ditto.json.JsonPointer;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.base.model.exceptions.DittoJsonException;
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.base.model.headers.DittoHeadersBuilder;
import org.eclipse.ditto.base.api.devops.ImmutableLoggerConfig;
import org.eclipse.ditto.gateway.service.endpoints.routes.AbstractRoute;
import org.eclipse.ditto.gateway.service.endpoints.routes.QueryParametersToHeadersMap;
import org.eclipse.ditto.base.service.devops.DevOpsCommandsActor;
import org.eclipse.ditto.base.api.common.RetrieveConfig;
import org.eclipse.ditto.base.api.devops.signals.commands.ChangeLogLevel;
import org.eclipse.ditto.base.api.devops.signals.commands.DevOpsCommand;
import org.eclipse.ditto.base.api.devops.signals.commands.ExecutePiggybackCommand;
import org.eclipse.ditto.base.api.devops.signals.commands.RetrieveLoggerConfig;

import akka.http.javadsl.model.ContentTypes;
import akka.http.javadsl.model.HttpResponse;
import akka.http.javadsl.server.PathMatchers;
import akka.http.javadsl.server.RequestContext;
import akka.http.javadsl.server.Route;
import akka.japi.function.Function;

/**
* Builder for creating Akka HTTP routes for {@code /devops}.
Expand Down Expand Up @@ -272,16 +274,18 @@ private Route routeConfig(final RequestContext ctx,
headersWithAggregate))));
}

private static Function<JsonValue, JsonValue> transformResponse(final CharSequence serviceName,
private static BiFunction<JsonValue, HttpResponse, HttpResponse> transformResponse(final CharSequence serviceName,
final CharSequence instance) {

final JsonPointer transformerPointer = transformerPointer(serviceName, instance);
if (transformerPointer.isEmpty()) {
return resp -> resp;
return (val, resp) -> resp;
}
return resp -> resp.asObject()
return (val, resp) -> resp.withEntity(ContentTypes.APPLICATION_JSON, val.asObject()
.getValue(transformerPointer)
.orElse(JsonFactory.nullObject());
.orElse(JsonFactory.nullObject())
.toString()
);
}

private static JsonPointer transformerPointer(@Nullable final CharSequence serviceName,
Expand Down
Loading

0 comments on commit a3f2401

Please sign in to comment.